1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-27 02:05:21 +03:00

Make port_handler work with PWA (#3958)

* Make port_handler work with PWA

* Modify the port_handler more the Vue way

* Fixes after review

* Fix request permission option not being deselected

* Hide baud selection in port picker if virtual port

* Added port override option for manual

* Fix virtual port state when loading the page

* Fix request permission adds the same device several times

* Fix automatic selection of device under Linux
This commit is contained in:
Míguel Ángel Mulero Martínez 2024-05-17 10:24:44 +02:00 committed by GitHub
parent b91698d0f4
commit fce0c8305b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 246 additions and 166 deletions

View file

@ -62,6 +62,10 @@
"message": "Virtual Mode (Experimental)", "message": "Virtual Mode (Experimental)",
"description": "Configure a Virtual Flight Controller without the need of a physical FC." "description": "Configure a Virtual Flight Controller without the need of a physical FC."
}, },
"portsSelectPermission": {
"message": "--- I can't find my device ---",
"description": "Option in the port selection dropdown to allow the user to give permissions to the system to access the device."
},
"virtualMSPVersion": { "virtualMSPVersion": {
"message": "Virtual Firmware Version" "message": "Virtual Firmware Version"
}, },

View file

@ -13,6 +13,7 @@ import StatusBar from "./status-bar/StatusBar.vue";
import BatteryIcon from "./quad-status/BatteryIcon.vue"; import BatteryIcon from "./quad-status/BatteryIcon.vue";
import FC from '../js/fc.js'; import FC from '../js/fc.js';
import MSP from '../js/msp.js'; import MSP from '../js/msp.js';
import PortHandler from '../js/port_handler.js';
import PortUsage from '../js/port_usage.js'; import PortUsage from '../js/port_usage.js';
import PortPicker from './port-picker/PortPicker.vue'; import PortPicker from './port-picker/PortPicker.vue';
import CONFIGURATOR from '../js/data_storage.js'; import CONFIGURATOR from '../js/data_storage.js';
@ -26,6 +27,7 @@ const betaflightModel = {
FC, FC,
MSP, MSP,
PortUsage, PortUsage,
PortHandler,
}; };
i18next.on('initialized', function() { i18next.on('initialized', function() {

View file

@ -1,13 +1,13 @@
<template> <template>
<div <div
id="firmware-virtual-option" id="firmware-virtual-option"
:style="{ display: isVirtual ? 'block' : 'none' }"
> >
<div class="dropdown dropdown-dark"> <div class="dropdown dropdown-dark">
<select <select
id="firmware-version-dropdown" id="firmware-version-dropdown"
class="dropdown-select" class="dropdown-select"
:title="$t('virtualMSPVersion')" :title="$t('virtualMSPVersion')"
v-model="value"
> >
<option <option
v-for="(version, index) in firmwareVersions" v-for="(version, index) in firmwareVersions"
@ -24,9 +24,9 @@
<script> <script>
export default { export default {
props: { props: {
isVirtual: { value: {
type: Boolean, type: String,
default: true, default: "1.46.0",
}, },
}, },
data() { data() {
@ -50,7 +50,7 @@ export default {
width: 180px; width: 180px;
margin-right: 15px; margin-right: 15px;
margin-top: 0px; margin-top: 0px;
display: none; display: block;
} }
.dropdown { .dropdown {
display: inline-block; display: inline-block;

View file

@ -1,42 +1,43 @@
<template> <template>
<div <div id="port-override-option">
id="port-override-option"
style="display: none"
:style="{ display: isManual ? 'flex' : 'none' }"
>
<label <label
for="port-override" for="port-override"
><span>{{ $t("portOverrideText") }}</span> ><span>{{ $t("portOverrideText") }}</span>
<input <input
id="port-override" id="port-override"
type="text" type="text"
value="/dev/rfcomm0" v-model="value"
></label> ></label>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
isManual: { value: {
type: Boolean, type: String,
default: true, default: "/dev/rfcomm0",
},
}, },
isManual: {
type: Boolean,
default: true,
},
},
}; };
</script> </script>
<style scoped> <style scoped>
#port-override-option { #port-override-option {
font-family: "Open Sans", "Segoe UI", Tahoma, sans-serif; font-family: "Open Sans", "Segoe UI", Tahoma, sans-serif;
font-size: 12px; font-size: 12px;
margin-top: 16px; margin-top: 4px;
margin-right: 15px; margin-right: 15px;
label { label {
background-color: #2b2b2b; background-color: #2b2b2b;
border-radius: 3px; border-radius: 3px;
padding: 3px; padding: 3px;
color: var(--subtleAccent); color: var(--subtleAccent);
} };
display: block;
} }
#port-override-option label { #port-override-option label {

View file

@ -1,24 +1,50 @@
<template> <template>
<div class="web-port-picker"> <div class="web-port-picker">
<FirmwareVirtualOption :is-virtual="port === 'virtual'" /> <PortOverrideOption
<PortsInput v-model="port" /> v-if="value.selectedPort === 'manual'"
v-model="value.portOverride"
/>
<FirmwareVirtualOption
v-if="value.selectedPort === 'virtual'"
v-model="value.virtualMspVersion"
/>
<PortsInput
v-model="value"
:connected-devices="connectedDevices"
:disabled="disabled" />
</div> </div>
</template> </template>
<script> <script>
import PortOverrideOption from "./PortOverrideOption.vue";
import FirmwareVirtualOption from "./FirmwareVirtualOption.vue"; import FirmwareVirtualOption from "./FirmwareVirtualOption.vue";
import PortsInput from "./PortsInput.vue"; import PortsInput from "./PortsInput.vue";
export default { export default {
props: {
value: {
type: Object,
default: {
selectedPort: "manual",
selectedBaud: 115200,
portOverride: "/dev/rfcomm0",
virtualMspVersion: "1.46.0",
},
},
connectedDevices: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
},
},
components: { components: {
PortOverrideOption,
FirmwareVirtualOption, FirmwareVirtualOption,
PortsInput, PortsInput,
}, },
data() {
return {
port: 'manual',
};
},
}; };
</script> </script>

View file

@ -8,8 +8,9 @@
id="port" id="port"
class="dropdown-select" class="dropdown-select"
:title="$t('firmwareFlasherManualPort')" :title="$t('firmwareFlasherManualPort')"
@value="value" :disabled="disabled"
@input="$emit('input', $event.target.value)" v-model="value.selectedPort"
@change="onChange"
> >
<option value="manual"> <option value="manual">
{{ $t("portsSelectManual") }} {{ $t("portsSelectManual") }}
@ -20,16 +21,29 @@
> >
{{ $t("portsSelectVirtual") }} {{ $t("portsSelectVirtual") }}
</option> </option>
<option
v-for="connectedDevice in connectedDevices"
:key="connectedDevice.path"
:value="connectedDevice.path"
>
{{ connectedDevice.displayName }}
</option>
<option value="requestpermission">
{{ $t("portsSelectPermission") }}
</option>
</select> </select>
</div> </div>
<div id="auto-connect-and-baud"> <div id="auto-connect-and-baud">
<div id="baudselect"> <div id="baudselect"
v-if="value.selectedPort !== 'virtual'"
>
<div class="dropdown dropdown-dark"> <div class="dropdown dropdown-dark">
<select <select
id="baud" id="baud"
v-model="selectedBaudRate" v-model="value.selectedBauds"
class="dropdown-select" class="dropdown-select"
:title="$t('firmwareFlasherBaudRate')" :title="$t('firmwareFlasherBaudRate')"
:disabled="disabled"
> >
<option <option
v-for="baudRate in baudRates" v-for="baudRate in baudRates"
@ -52,15 +66,25 @@ import { EventBus } from '../eventBus';
export default { export default {
props: { props: {
value: { value: {
type: String, type: Object,
default: 'manual', default: {
selectedPort: 'manual',
selectedBaud: 115200,
},
},
connectedDevices: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
}, },
}, },
data() { data() {
return { return {
showVirtual: false, showVirtual: false,
selectedBaudRate: "115200",
baudRates: [ baudRates: [
{ value: "1000000", label: "1000000" }, { value: "1000000", label: "1000000" },
{ value: "500000", label: "500000" }, { value: "500000", label: "500000" },
@ -80,13 +104,23 @@ export default {
}, },
mounted() { mounted() {
EventBus.$on('config-storage:set', this.setShowVirtual); EventBus.$on('config-storage:set', this.setShowVirtual);
this.setShowVirtual('showVirtualMode');
}, },
destroyed() { destroyed() {
EventBus.$off('config-storage:set', this.setShowVirtual); EventBus.$off('config-storage:set', this.setShowVirtual);
}, },
methods: { methods: {
setShowVirtual() { setShowVirtual(element) {
this.showVirtual = getConfig('showVirtualMode').showVirtualMode; if (element === 'showVirtualMode') {
this.showVirtual = getConfig('showVirtualMode').showVirtualMode;
}
},
onChange(event) {
if (event.target.value === 'requestpermission') {
EventBus.$emit('ports-input:request-permission');
} else {
EventBus.$emit('ports-input:change', event.target.value);
}
}, },
}, },
}; };

View file

@ -26,7 +26,11 @@
:firmware-id="FC.CONFIG.flightControllerIdentifier" :firmware-id="FC.CONFIG.flightControllerIdentifier"
:hardware-id="FC.CONFIG.hardwareName" :hardware-id="FC.CONFIG.hardwareName"
></betaflight-logo> ></betaflight-logo>
<port-picker></port-picker> <port-picker
v-model="PortHandler.portPicker"
:connected-devices="PortHandler.currentPorts"
:disabled="PortHandler.portPickerDisabled"
></port-picker>
<div class="header-wrapper"> <div class="header-wrapper">
<div id="quad-status_wrapper"> <div id="quad-status_wrapper">
<battery-icon <battery-icon

View file

@ -45,7 +45,7 @@ export function set(input) {
tmpObj[element] = input[element]; tmpObj[element] = input[element];
try { try {
localStorage.setItem(element, JSON.stringify(tmpObj)); localStorage.setItem(element, JSON.stringify(tmpObj));
EventBus.$emit(`config-storage:set`, 'element'); EventBus.$emit('config-storage:set', element);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View file

@ -1,18 +1,28 @@
import GUI, { TABS } from "./gui"; import GUI, { TABS } from "./gui";
import FC from "./fc"; import FC from "./fc";
import { i18n } from "./localization"; import { i18n } from "./localization";
import { generateVirtualApiVersions, getTextWidth } from './utils/common';
import { get as getConfig } from "./ConfigStorage"; import { get as getConfig } from "./ConfigStorage";
import serial from "./serial";
import MdnsDiscovery from "./mdns_discovery"; import MdnsDiscovery from "./mdns_discovery";
import { isWeb } from "./utils/isWeb"; import { isWeb } from "./utils/isWeb";
import { usbDevices } from "./usb_devices"; import { usbDevices } from "./usb_devices";
import { serialShim } from "./serial_shim.js";
import { EventBus } from "../components/eventBus";
const TIMEOUT_CHECK = 500 ; // With 250 it seems that it produces a memory leak and slowdown in some versions, reason unknown const serial = serialShim();
const DEFAULT_PORT = 'manual';
const DEFAULT_BAUDS = 115200;
const PortHandler = new function () { const PortHandler = new function () {
this.currentPorts = []; this.currentPorts = [];
this.initialPorts = false; this.initialPorts = false;
this.portPicker = {
selectedPort: DEFAULT_PORT,
selectedBauds: DEFAULT_BAUDS,
portOverride: "/dev/rfcomm0",
virtualMspVersion: "1.46.0",
};
this.portPickerDisabled = false;
this.port_detected_callbacks = []; this.port_detected_callbacks = [];
this.port_removed_callbacks = []; this.port_removed_callbacks = [];
this.dfu_available = false; this.dfu_available = false;
@ -24,21 +34,13 @@ const PortHandler = new function () {
}; };
PortHandler.initialize = function () { PortHandler.initialize = function () {
const self = this;
// currently web build doesn't need port handler, EventBus.$on('ports-input:request-permission', this.askPermissionPort.bind(this));
// so just bail out. EventBus.$on('ports-input:change', this.onChangeSelectedPort.bind(this));
if (isWeb()) {
return 'not implemented';
}
const portPickerElementSelector = "div#port-picker #port"; serial.addEventListener("addedDevice", this.check_serial_devices.bind(this));
self.portPickerElement = $(portPickerElementSelector);
self.selectList = document.querySelector(portPickerElementSelector);
self.initialWidth = self.selectList.offsetWidth + 12;
// fill dropdown with version numbers serial.addEventListener("removedDevice", this.check_serial_devices.bind(this));
generateVirtualApiVersions();
this.reinitialize(); // just to prevent code redundancy this.reinitialize(); // just to prevent code redundancy
}; };
@ -73,15 +75,15 @@ PortHandler.check = function () {
self.check_serial_devices(); self.check_serial_devices();
} }
self.usbCheckLoop = setTimeout(() => {
self.check(); this.check_serial_devices();
}, TIMEOUT_CHECK);
}; };
PortHandler.check_serial_devices = function () { PortHandler.check_serial_devices = function () {
const self = this; const self = this;
serial.getDevices(function(cp) { const updatePorts = function(cp) {
self.currentPorts = []; self.currentPorts = [];
if (self.useMdnsBrowser) { if (self.useMdnsBrowser) {
@ -109,11 +111,25 @@ PortHandler.check_serial_devices = function () {
} else { } else {
self.removePort(); self.removePort();
self.detectPort(); self.detectPort();
self.selectActivePort();
} }
}); };
serial.getDevices().then(updatePorts);
};
PortHandler.onChangeSelectedPort = function(port) {
this.portPicker.selectedPort = port;
}; };
PortHandler.check_usb_devices = function (callback) { PortHandler.check_usb_devices = function (callback) {
// TODO needs USB code refactor for web
if (isWeb()) {
return;
}
const self = this; const self = this;
chrome.usb.getDevices(usbDevices, function (result) { chrome.usb.getDevices(usbDevices, function (result) {
@ -137,7 +153,7 @@ PortHandler.check_usb_devices = function (callback) {
})); }));
self.portPickerElement.append($('<option/>', { self.portPickerElement.append($('<option/>', {
value: 'manual', value: DEFAULT_PORT,
text: i18n.getMessage('portsSelectManual'), text: i18n.getMessage('portsSelectManual'),
/** /**
* @deprecated please avoid using `isDFU` and friends for new code. * @deprecated please avoid using `isDFU` and friends for new code.
@ -203,7 +219,6 @@ PortHandler.removePort = function() {
self.initialPorts.splice(self.initialPorts.indexOf(port, 1)); self.initialPorts.splice(self.initialPorts.indexOf(port, 1));
} }
self.updatePortSelect(self.initialPorts); self.updatePortSelect(self.initialPorts);
self.portPickerElement.trigger('change');
} }
}; };
@ -216,8 +231,8 @@ PortHandler.detectPort = function() {
console.log(`PortHandler - Found: ${JSON.stringify(newPorts)}`); console.log(`PortHandler - Found: ${JSON.stringify(newPorts)}`);
if (newPorts.length === 1) { if (newPorts.length === 1) {
self.portPickerElement.val(newPorts[0].path); this.portPicker.selectedPort = newPorts[0].path;
} else if (newPorts.length > 1) { } else {
self.selectActivePort(); self.selectActivePort();
} }
@ -227,8 +242,6 @@ PortHandler.detectPort = function() {
TABS.firmware_flasher.boardNeedsVerification = true; TABS.firmware_flasher.boardNeedsVerification = true;
} }
self.portPickerElement.trigger('change');
// auto-connect if enabled // auto-connect if enabled
if (GUI.auto_connect && !GUI.connecting_to && !GUI.connected_to && GUI.active_tab !== 'firmware_flasher') { if (GUI.auto_connect && !GUI.connecting_to && !GUI.connected_to && GUI.active_tab !== 'firmware_flasher') {
// start connect procedure. We need firmware flasher protection over here // start connect procedure. We need firmware flasher protection over here
@ -263,105 +276,39 @@ PortHandler.sortPorts = function(ports) {
}); });
}; };
PortHandler.addNoPortSelection = function() {
if (!this.showVirtualMode && !this.showManualMode) {
this.portPickerElement.append($("<option/>", {
value: 'none',
text: i18n.getMessage('portsSelectNone'),
}));
}
};
PortHandler.updatePortSelect = function (ports) { PortHandler.updatePortSelect = function (ports) {
ports = this.sortPorts(ports); ports = this.sortPorts(ports);
this.portPickerElement.empty();
for (const port of ports) {
const portText = port.displayName ? `${port.path} - ${port.displayName}` : port.path;
this.portPickerElement.append($("<option/>", {
value: port.path,
text: portText,
/**
* @deprecated please avoid using `isDFU` and friends for new code.
*/
data: {isManual: false},
}));
}
if (this.showVirtualMode) {
this.portPickerElement.append($("<option/>", {
value: 'virtual',
text: i18n.getMessage('portsSelectVirtual'),
/**
* @deprecated please avoid using `isDFU` and friends for new code.
*/
data: {isVirtual: true},
}));
}
if (this.showManualMode) {
this.portPickerElement.append($("<option/>", {
value: 'manual',
text: i18n.getMessage('portsSelectManual'),
/**
* @deprecated please avoid using `isDFU` and friends for new code.
*/
data: {isManual: true},
}));
}
if (!ports.length) {
this.addNoPortSelection();
}
this.setPortsInputWidth();
this.currentPorts = ports; this.currentPorts = ports;
}; };
PortHandler.askPermissionPort = function() {
serial.requestPermissionDevice().then(() => {
this.check_serial_devices();
}).catch(() => {
// In the catch we call the check_serial_devices too to change the request permission option from the select for other
this.check_serial_devices();
});
};
PortHandler.selectActivePort = function() { PortHandler.selectActivePort = function() {
const ports = this.currentPorts; const ports = this.currentPorts;
const OS = GUI.operating_system; const OS = GUI.operating_system;
let selectedPort;
for (let i = 0; i < ports.length; i++) { for (let i = 0; i < ports.length; i++) {
const portName = ports[i].displayName; const portName = ports[i].displayName;
if (portName) { if (portName) {
const pathSelect = ports[i].path; const pathSelect = ports[i].path;
const isWindows = (OS === 'Windows'); const deviceFilter = ['AT32', 'CP210', 'SPR', 'STM'];
const isTty = pathSelect.includes('tty');
const deviceFilter = ['AT32', 'CP210', 'SPR', 'STM32'];
const deviceRecognized = deviceFilter.some(device => portName.includes(device)); const deviceRecognized = deviceFilter.some(device => portName.includes(device));
const legacyDeviceRecognized = portName.includes('usb'); const legacyDeviceRecognized = portName.includes('usb');
if (isWindows && deviceRecognized || isTty && (deviceRecognized || legacyDeviceRecognized)) { if (deviceRecognized || legacyDeviceRecognized) {
this.portPickerElement.val(pathSelect); selectedPort = pathSelect;
this.port_available = true; this.port_available = true;
console.log(`Porthandler detected device ${portName} on port: ${pathSelect}`); console.log(`Porthandler detected device ${portName} on port: ${pathSelect}`);
} }
} }
} }
}; this.portPicker.selectedPort = selectedPort || DEFAULT_PORT;
PortHandler.setPortsInputWidth = function() {
function findMaxLengthOption(selectEl) {
let max = 0;
$(selectEl.options).each(function () {
const textSize = getTextWidth(this.textContent);
if (textSize > max) {
max = textSize;
}
});
return max;
}
const correction = 32; // account for up/down button and spacing
let width = findMaxLengthOption(this.selectList) + correction;
width = (width > this.initialWidth) ? width : this.initialWidth;
const portsInput = document.querySelector("div#port-picker #portsinput");
portsInput.style.width = `${width}px`;
}; };
PortHandler.port_detected = function(name, code, timeout, ignore_timeout) { PortHandler.port_detected = function(name, code, timeout, ignore_timeout) {

View file

@ -26,6 +26,7 @@ import BuildApi from "./BuildApi";
import { isWeb } from "./utils/isWeb"; import { isWeb } from "./utils/isWeb";
import { serialShim } from "./serial_shim.js"; import { serialShim } from "./serial_shim.js";
import { EventBus } from "../components/eventBus";
let serial = serialShim(); let serial = serialShim();
@ -73,19 +74,16 @@ export function initializeSerialBackend() {
$('#port-override').val(data.portOverride); $('#port-override').val(data.portOverride);
} }
$('div#port-picker #port').change(function (target) { EventBus.$on('ports-input:change', () => GUI.updateManualPortVisibility());
GUI.updateManualPortVisibility();
});
$("div.connect_controls a.connect").on('click', function () { $("div.connect_controls a.connect").on('click', function () {
const selectedPort = $('#port').val(); const selectedPort = PortHandler.portPicker.selectedPort;
let portName; let portName;
if (selectedPort === 'manual') { if (selectedPort === 'manual') {
portName = $('#port-override').val(); portName = $('#port-override').val();
} else { } else {
portName = String($('div#port-picker #port').val()); portName = selectedPort;
} }
if (!GUI.connect_lock && selectedPort !== 'none') { if (!GUI.connect_lock && selectedPort !== 'none') {
@ -93,8 +91,8 @@ export function initializeSerialBackend() {
GUI.configuration_loaded = false; GUI.configuration_loaded = false;
const selected_baud = parseInt($('div#port-picker #baud').val()); const selected_baud = PortHandler.portPicker.selectedBauds;
const selectedPort = $('#port').val(); const selectedPort = portName;
if (selectedPort === 'DFU') { if (selectedPort === 'DFU') {
$('select#baud').hide(); $('select#baud').hide();
@ -106,10 +104,10 @@ export function initializeSerialBackend() {
GUI.connecting_to = portName; GUI.connecting_to = portName;
// lock port select & baud while we are connecting / connected // lock port select & baud while we are connecting / connected
$('div#port-picker #port, div#port-picker #baud, div#port-picker #delay').prop('disabled', true); PortHandler.portPickerDisabled = true;
$('div.connect_controls div.connect_state').text(i18n.getMessage('connecting')); $('div.connect_controls div.connect_state').text(i18n.getMessage('connecting'));
const baudRate = parseInt($('#baud').val()); const baudRate = selected_baud;
if (selectedPort === 'virtual') { if (selectedPort === 'virtual') {
CONFIGURATOR.virtualMode = true; CONFIGURATOR.virtualMode = true;
CONFIGURATOR.virtualApiVersion = $('#firmware-version-dropdown').val(); CONFIGURATOR.virtualApiVersion = $('#firmware-version-dropdown').val();
@ -117,7 +115,7 @@ export function initializeSerialBackend() {
// Hack to get virtual working on the web // Hack to get virtual working on the web
serial = serialShim(); serial = serialShim();
serial.connect('virtual', {}, onOpenVirtual); serial.connect('virtual', {}, onOpenVirtual);
} else if (isWeb()) { } else {
CONFIGURATOR.virtualMode = false; CONFIGURATOR.virtualMode = false;
serial = serialShim(); serial = serialShim();
// Explicitly disconnect the event listeners before attaching the new ones. // Explicitly disconnect the event listeners before attaching the new ones.
@ -127,10 +125,7 @@ export function initializeSerialBackend() {
serial.removeEventListener('disconnect', disconnectHandler); serial.removeEventListener('disconnect', disconnectHandler);
serial.addEventListener('disconnect', disconnectHandler); serial.addEventListener('disconnect', disconnectHandler);
serial.connect({ baudRate }); serial.connect(portName, { baudRate });
} else {
serial.connect(portName, { bitrate: selected_baud }, onOpen);
toggleStatus();
} }
} else { } else {
@ -230,8 +225,7 @@ function finishClose(finishedCallback) {
$('#dialogReportProblems-closebtn').click(); $('#dialogReportProblems-closebtn').click();
// unlock port select & baud // unlock port select & baud
$('div#port-picker #port').prop('disabled', false); PortHandler.portPickerDisabled = false;
if (!GUI.auto_connect) $('div#port-picker #baud').prop('disabled', false);
// reset connect / disconnect button // reset connect / disconnect button
$('div.connect_controls a.connect').removeClass('active'); $('div.connect_controls a.connect').removeClass('active');
@ -275,7 +269,7 @@ function abortConnection() {
$('div#connectbutton a.connect').removeClass('active'); $('div#connectbutton a.connect').removeClass('active');
// unlock port select & baud // unlock port select & baud
$('div#port-picker #port, div#port-picker #baud, div#port-picker #delay').prop('disabled', false); PortHandler.portPickerDisabled = false;
// reset data // reset data
isConnected = false; isConnected = false;

View file

@ -1,3 +1,10 @@
export const vendorIdNames = {
1027: "FTDI",
1155: "STM Electronics",
4292: "Silicon Labs",
0x2e3c: "AT32",
};
export const serialDevices = [ export const serialDevices = [
{ vendorId: 1027, productId: 24577 }, // FT232R USB UART { vendorId: 1027, productId: 24577 }, // FT232R USB UART
{ vendorId: 1155, productId: 22336 }, // STM Electronics Virtual COM Port { vendorId: 1155, productId: 22336 }, // STM Electronics Virtual COM Port

View file

@ -1,4 +1,4 @@
import { webSerialDevices } from "./serial_devices"; import { webSerialDevices, vendorIdNames } from "./serial_devices";
async function* streamAsyncIterable(reader, keepReadingFlag) { async function* streamAsyncIterable(reader, keepReadingFlag) {
try { try {
@ -30,12 +30,34 @@ class WebSerial extends EventTarget {
this.logHead = "SERIAL: "; this.logHead = "SERIAL: ";
this.port_counter = 0;
this.ports = [];
this.port = null; this.port = null;
this.reader = null; this.reader = null;
this.writer = null; this.writer = null;
this.reading = false; this.reading = false;
this.connect = this.connect.bind(this); this.connect = this.connect.bind(this);
navigator.serial.addEventListener("connect", e => this.handleNewDevice(e.target));
navigator.serial.addEventListener("disconnect", e => this.handleRemovedDevice(e.target));
this.loadDevices();
}
handleNewDevice(device) {
const added = this.createPort(device);
this.ports.push(added);
this.dispatchEvent(new CustomEvent("addedDevice", { detail: added }));
return added;
}
handleRemovedDevice(device) {
const removed = this.ports.find(port => port.port === device);
this.ports = this.ports.filter(port => port.port !== device);
this.dispatchEvent(new CustomEvent("removedDevice", { detail: removed }));
} }
handleReceiveBytes(info) { handleReceiveBytes(info) {
@ -44,16 +66,56 @@ class WebSerial extends EventTarget {
handleDisconnect() { handleDisconnect() {
this.removeEventListener('receive', this.handleReceiveBytes); this.removeEventListener('receive', this.handleReceiveBytes);
this.removeEventListener('disconnect', this.handleDisconnect); this.dispatchEvent(new CustomEvent("disconnect", { detail: false }));
} }
async connect(options) { getConnectedPort() {
this.openRequested = true; return this.port;
this.port = await navigator.serial.requestPort({ }
createPort(port) {
return {
path: `D${this.port_counter}`,
displayName: `Betaflight ${vendorIdNames[port.getInfo().usbVendorId]}`,
vendorId: port.getInfo().usbVendorId,
productId: port.getInfo().usbProductId,
port: port,
};
}
async loadDevices() {
const ports = await navigator.serial.getPorts({
filters: webSerialDevices, filters: webSerialDevices,
}); });
this.port_counter = 1;
this.ports = ports.map(function (port) {
return this.createPort(port);
}, this);
};
async requestPermissionDevice() {
const permissionPort = await navigator.serial.requestPort({
filters: webSerialDevices,
});
const found = this.ports.find(port => port.port === device);
if (!found) {
return this.handleNewDevice(permissionPort);
}
return null;
};
async getDevices() {
return this.ports;
}
async connect(path, options) {
this.openRequested = true;
this.port = this.ports.find(device => device.path === path).port;
await this.port.open(options); await this.port.open(options);
const connectionInfo = this.port.getInfo(); const connectionInfo = this.port.getInfo();
this.connectionInfo = connectionInfo; this.connectionInfo = connectionInfo;
this.writer = this.port.writable.getWriter(); this.writer = this.port.writable.getWriter();
@ -69,7 +131,6 @@ class WebSerial extends EventTarget {
this.openRequested = false; this.openRequested = false;
this.addEventListener("receive", this.handleReceiveBytes); this.addEventListener("receive", this.handleReceiveBytes);
this.addEventListener('disconnect', this.handleDisconnect);
console.log( console.log(
`${this.logHead} Connection opened with ID: ${connectionInfo.connectionId}, Baud: ${options.baudRate}`, `${this.logHead} Connection opened with ID: ${connectionInfo.connectionId}, Baud: ${options.baudRate}`,