1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-24 16:55:24 +03:00

Add bluetooth support (#4024)

* Add bluetooth support

* Add new files

* CLI now works over BT

* Fix MSP over bluetooth

* Cleanup

* More cleanup

* Fix bind for disconnect

* Rename port to device

* Reboot does not trigger event gattserverdisconnected

* Fix dual permission request

* Increase minimum MSP timeout for PWA

* Small refactor

* Reboot

* Do not crash when bluetooth flag is disabled

* Cleanup excessive logging

* Abstract navigator
This commit is contained in:
Mark Haslinghuis 2024-06-28 20:02:32 +02:00 committed by GitHub
parent 91dab8750e
commit 5a3f06f890
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 512 additions and 38 deletions

View file

@ -84,9 +84,22 @@
"description": "Configure a Virtual Flight Controller without the need of a physical FC."
},
"portsSelectPermission": {
"message": "--- I can't find my device ---",
"message": "--- I can't find my USB device ---",
"description": "Option in the port selection dropdown to allow the user to give permissions to the system to access the device."
},
"portsSelectPermissionBluetooth": {
"message": "--- I can't find my Bluetooth device---",
"description": "Option in the port selection dropdown to allow the user to give permissions to the system to access a Bluetooth device."
},
"bluetoothConnected": {
"message": "Connected to Bluetooth device: $1"
},
"bluetoothConnectionType": {
"message": "Bluetooth connection type: $1"
},
"bluetoothConnectionError": {
"message": "Bluetooth error: $1"
},
"virtualMSPVersion": {
"message": "Virtual Firmware Version"
},

View file

@ -12,6 +12,7 @@
/>
<PortsInput
:value="value"
:connected-bluetooth-devices="connectedBluetoothDevices"
:connected-serial-devices="connectedSerialDevices"
:connected-usb-devices="connectedUsbDevices"
:disabled="disabled"
@ -44,7 +45,7 @@ export default {
autoConnect: true,
}),
},
connectedUsbDevices: {
connectedBluetoothDevices: {
type: Array,
default: () => [],
},
@ -52,6 +53,10 @@ export default {
type: Array,
default: () => [],
},
connectedUsbDevices: {
type: Array,
default: () => [],
},
showVirtualOption: {
type: Boolean,
default: true,

View file

@ -30,6 +30,13 @@
>
{{ $t("portsSelectVirtual") }}
</option>
<option
v-for="connectedBluetoothDevice in connectedBluetoothDevices"
:key="connectedBluetoothDevice.path"
:value="connectedBluetoothDevice.path"
>
{{ connectedBluetoothDevice.displayName }}
</option>
<option
v-for="connectedSerialDevice in connectedSerialDevices"
:key="connectedSerialDevice.path"
@ -47,6 +54,9 @@
<option value="requestpermission">
{{ $t("portsSelectPermission") }}
</option>
<option value="requestpermissionbluetooth">
{{ $t("portsSelectPermissionBluetooth") }}
</option>
</select>
</div>
<div id="auto-connect-and-baud">
@ -114,6 +124,10 @@ export default {
type: Array,
default: () => [],
},
connectedBluetoothDevices: {
type: Array,
default: () => [],
},
disabled: {
type: Boolean,
default: false,
@ -154,6 +168,8 @@ export default {
onChangePort(event) {
if (event.target.value === 'requestpermission') {
EventBus.$emit('ports-input:request-permission');
} else if (event.target.value === 'requestpermissionbluetooth') {
EventBus.$emit('ports-input:request-permission-bluetooth');
} else {
EventBus.$emit('ports-input:change', event.target.value);
}

View file

@ -1,11 +1,8 @@
import GUI from "./gui.js";
import CONFIGURATOR from "./data_storage.js";
import serialNWJS from "./serial.js";
import serialWeb from "./webSerial.js";
import { isWeb } from "./utils/isWeb.js";
import { serialShim } from "./serial_shim.js";
const serial = serialShim();
let serial = serialShim();
const MSP = {
symbols: {
@ -57,7 +54,7 @@ const MSP = {
packet_error: 0,
unsupported: 0,
MIN_TIMEOUT: 100,
MIN_TIMEOUT: 200,
MAX_TIMEOUT: 2000,
timeout: 200,
@ -309,7 +306,10 @@ const MSP = {
return bufferOut;
},
send_message(code, data, callback_sent, callback_msp, doCallbackOnError) {
const connected = isWeb() ? serial.connected : serial.connectionId;
// Hack to make BT work
serial = serialShim();
const connected = serial.connected;
if (code === undefined || !connected || CONFIGURATOR.virtualMode) {
if (callback_msp) {

View file

@ -2,6 +2,7 @@ import { get as getConfig } from "./ConfigStorage";
import { EventBus } from "../components/eventBus";
import serial from "./webSerial";
import usb from "./protocols/webusbdfu";
import BT from "./protocols/bluetooth";
const DEFAULT_PORT = 'noselection';
const DEFAULT_BAUDS = 115200;
@ -9,6 +10,8 @@ const DEFAULT_BAUDS = 115200;
const PortHandler = new function () {
this.currentSerialPorts = [];
this.currentUsbPorts = [];
this.currentBluetoothPorts = [];
this.portPicker = {
selectedPort: DEFAULT_PORT,
selectedBauds: DEFAULT_BAUDS,
@ -16,7 +19,10 @@ const PortHandler = new function () {
virtualMspVersion: "1.46.0",
autoConnect: getConfig('autoConnect', false).autoConnect,
};
this.portPickerDisabled = false;
this.bluetoothAvailable = false;
this.dfuAvailable = false;
this.portAvailable = false;
this.showAllSerialDevices = false;
@ -27,14 +33,19 @@ const PortHandler = new function () {
PortHandler.initialize = function () {
EventBus.$on('ports-input:request-permission-bluetooth', this.askBluetoothPermissionPort.bind(this));
EventBus.$on('ports-input:request-permission', this.askSerialPermissionPort.bind(this));
EventBus.$on('ports-input:change', this.onChangeSelectedPort.bind(this));
BT.addEventListener("addedDevice", (event) => this.addedBluetoothDevice(event.detail));
BT.addEventListener("removedDevice", (event) => this.addedBluetoothDevice(event.detail));
serial.addEventListener("addedDevice", (event) => this.addedSerialDevice(event.detail));
serial.addEventListener("removedDevice", (event) => this.removedSerialDevice(event.detail));
usb.addEventListener("addedDevice", (event) => this.addedUsbDevice(event.detail));
this.addedBluetoothDevice();
this.addedSerialDevice();
this.addedUsbDevice();
};
@ -73,6 +84,26 @@ PortHandler.removedSerialDevice = function (device) {
});
};
PortHandler.addedBluetoothDevice = function (device) {
this.updateCurrentBluetoothPortsList()
.then(() => {
const selectedPort = this.selectActivePort(device);
if (!device || selectedPort === device.path) {
// Send this event when the port handler auto selects a new device
EventBus.$emit('port-handler:auto-select-bluetooth-device', selectedPort);
}
});
};
PortHandler.removedBluetoothDevice = function (device) {
this.updateCurrentBluetoothPortsList()
.then(() => {
if (this.portPicker.selectedPort === device.path) {
this.selectActivePort();
}
});
};
PortHandler.addedUsbDevice = function (device) {
this.updateCurrentUsbPortsList()
.then(() => {
@ -102,6 +133,15 @@ PortHandler.updateCurrentUsbPortsList = async function () {
this.currentUsbPorts = orderedPorts;
};
PortHandler.updateCurrentBluetoothPortsList = async function () {
if (BT.bluetooth) {
const ports = await BT.getDevices();
const orderedPorts = this.sortPorts(ports);
this.bluetoothAvailable = orderedPorts.length > 0;
this.currentBluetoothPorts = orderedPorts;
}
};
PortHandler.sortPorts = function(ports) {
return ports.sort(function(a, b) {
return a.path.localeCompare(b.path, window.navigator.language, {
@ -111,6 +151,18 @@ PortHandler.sortPorts = function(ports) {
});
};
PortHandler.askBluetoothPermissionPort = function() {
if (BT.bluetooth) {
BT.requestPermissionDevice()
.then((port) => {
// When giving permission to a new device, the port is selected in the handleNewDevice method, but if the user
// selects a device that had already permission, or cancels the permission request, we need to select the port
// so do it here too
this.selectActivePort(port);
});
}
};
PortHandler.askSerialPermissionPort = function() {
serial.requestPermissionDevice(this.showAllSerialDevices)
.then((port) => {
@ -136,6 +188,11 @@ PortHandler.selectActivePort = function(suggestedDevice) {
selectedPort = this.currentUsbPorts.find(device => device === usb.getConnectedPort());
}
// Return the same that is connected to bluetooth
if (BT.device) {
selectedPort = this.currentBluetoothPorts.find(device => device === BT.getConnectedPort());
}
// Return the suggested device (the new device that has been detected)
if (!selectedPort && suggestedDevice) {
selectedPort = suggestedDevice.path;
@ -157,6 +214,14 @@ PortHandler.selectActivePort = function(suggestedDevice) {
}
}
// Return some bluetooth port that is recognized by the filter
if (!selectedPort) {
selectedPort = this.currentBluetoothPorts.find(device => deviceFilter.some(filter => device.displayName.includes(filter)));
if (selectedPort) {
selectedPort = selectedPort.path;
}
}
// Return the virtual port
if (!selectedPort && this.showVirtualMode) {
selectedPort = "virtual";
@ -170,7 +235,7 @@ PortHandler.selectActivePort = function(suggestedDevice) {
// Return the default port if no other port was selected
this.portPicker.selectedPort = selectedPort || DEFAULT_PORT;
console.log(`Porthandler automatically selected device is '${this.portPicker.selectedPort}'`);
console.log(`[PORTHANDLER] automatically selected device is '${this.portPicker.selectedPort}'`);
return selectedPort;
};

View file

@ -0,0 +1,361 @@
import { i18n } from "../localization";
import { gui_log } from "../gui_log";
/* Certain flags needs to be enabled in the browser to use BT
*
* app.commandLine.appendSwitch('enable-web-bluetooth', "true");
* app.commandLine.appendSwitch('disable-hid-blocklist')
* app.commandLine.appendSwitch('enable-experimental-web-platform-features');
*
*/
const bluetoothDevices = [
{ name: "CC2541", serviceUuid: '0000ffe0-0000-1000-8000-00805f9b34fb', writeCharacteristic: '0000ffe1-0000-1000-8000-00805f9b34fb', readCharacteristic: '0000ffe1-0000-1000-8000-00805f9b34fb' },
{ name: "HC-05", serviceUuid: '00001101-0000-1000-8000-00805f9b34fb', writeCharacteristic: '00001101-0000-1000-8000-00805f9b34fb', readCharacteristic: '00001101-0000-1000-8000-00805f9b34fb' },
{ name: "HM-10", serviceUuid: '0000ffe1-0000-1000-8000-00805f9b34fb', writeCharacteristic: '0000ffe1-0000-1000-8000-00805f9b34fb', readCharacteristic: '0000ffe1-0000-1000-8000-00805f9b34fb' },
{ name: "HM-11", serviceUuid: '6e400001-b5a3-f393-e0a9-e50e24dcca9e', writeCharacteristic: '6e400003-b5a3-f393-e0a9-e50e24dcca9e', readCharacteristic: '6e400002-b5a3-f393-e0a9-e50e24dcca9e' },
{ name: "Nordic NRF", serviceUuid: '6e400001-b5a3-f393-e0a9-e50e24dcca9e', writeCharacteristic: '6e400003-b5a3-f393-e0a9-e50e24dcca9e', readCharacteristic: '6e400002-b5a3-f393-e0a9-e50e24dcca9e' },
{ name: "SpeedyBee V1", serviceUuid: '00001000-0000-1000-8000-00805f9b34fb', writeCharacteristic: '00001001-0000-1000-8000-00805f9b34fb', readCharacteristic: '00001002-0000-1000-8000-00805f9b34fb' },
{ name: "SpeedyBee V2", serviceUuid: '0000abf0-0000-1000-8000-00805f9b34fb', writeCharacteristic: '0000abf1-0000-1000-8000-00805f9b34fb', readCharacteristic: '0000abf2-0000-1000-8000-00805f9b34fb' },
];
class BT extends EventTarget {
constructor() {
super();
if (!this.bluetooth && window && window.navigator && window.navigator.bluetooth) {
this.bluetooth = navigator.bluetooth;
} else {
console.error(`${this.logHead} Bluetooth API not available`);
return;
}
this.connected = false;
this.openRequested = false;
this.openCanceled = false;
this.closeRequested = false;
this.transmitting = false;
this.connectionInfo = null;
this.bitrate = 0;
this.bytesSent = 0;
this.bytesReceived = 0;
this.failed = 0;
this.logHead = "[BLUETOOTH]";
this.portCounter = 0;
this.devices = [];
this.device = null;
this.connect = this.connect.bind(this);
this.bluetooth.addEventListener("connect", e => this.handleNewDevice(e.target));
this.bluetooth.addEventListener("disconnect", e => this.handleRemovedDevice(e.target));
this.bluetooth.addEventListener("gatserverdisconnected", e => this.handleRemovedDevice(e.target));
this.loadDevices();
}
handleNewDevice(device) {
const added = this.createPort(device);
this.devices.push(added);
this.dispatchEvent(new CustomEvent("addedDevice", { detail: added }));
return added;
}
handleRemovedDevice(device) {
const removed = this.devices.find(port => port.port === device);
this.devices = this.devices.filter(port => port.port !== device);
this.dispatchEvent(new CustomEvent("removedDevice", { detail: removed }));
}
handleReceiveBytes(info) {
this.bytesReceived += info.detail.byteLength;
}
handleDisconnect() {
this.disconnect();
this.closeRequested = true;
}
getConnectedPort() {
return this.device;
}
createPort(device) {
return {
path: `bluetooth_${this.portCounter++}`,
displayName: device.name,
vendorId: "unknown",
productId: device.id,
port: device,
};
}
async loadDevices() {
const devices = await this.bluetooth.getDevices();
this.portCounter = 1;
this.devices = devices.map(device => this.createPort(device));
}
async requestPermissionDevice() {
let newPermissionPort = null;
const uuids = [];
bluetoothDevices.forEach(device => {
uuids.push(device.serviceUuid);
});
const options = { acceptAllDevices: true, optionalServices: uuids };
try {
const userSelectedPort = await this.bluetooth.requestDevice(options);
newPermissionPort = this.devices.find(port => port.port === userSelectedPort);
if (!newPermissionPort) {
newPermissionPort = this.handleNewDevice(userSelectedPort);
}
console.info(`${this.logHead} User selected Bluetooth device from permissions:`, newPermissionPort.path);
} catch (error) {
console.error(`${this.logHead} User didn't select any Bluetooth device when requesting permission:`, error);
}
return newPermissionPort;
}
async getDevices() {
return this.devices;
}
getAvailability() {
this.bluetooth.getAvailability().then((available) => {
console.log(`${this.logHead} Bluetooth available:`, available);
this.available = available;
return available;
});
}
async connect(path, options) {
this.openRequested = true;
this.closeRequested = false;
this.device = this.devices.find(device => device.path === path).port;
console.log(`${this.logHead} Opening connection with ID: ${path}, Baud: ${options.baudRate}`);
this.device.addEventListener('gattserverdisconnected', this.handleDisconnect.bind(this));
try {
console.log(`${this.logHead} Connecting to GATT Server`);
await this.gattConnect();
gui_log(i18n.getMessage('bluetoothConnected', [this.device.name]));
await this.getServices();
await this.getCharacteristics();
await this.startNotifications();
} catch (error) {
gui_log(i18n.getMessage('bluetoothConnectionError', [error]));
}
// Bluetooth API doesn't provide a way for getInfo() or similar to get the connection info
const connectionInfo = this.device.gatt.connected;
if (connectionInfo && !this.openCanceled) {
this.connected = true;
this.connectionId = this.device.port;
this.bitrate = options.baudRate;
this.bytesReceived = 0;
this.bytesSent = 0;
this.failed = 0;
this.openRequested = false;
this.device.addEventListener("disconnect", this.handleDisconnect.bind(this));
this.addEventListener("receive", this.handleReceiveBytes);
console.log(
`${this.logHead} Connection opened with ID: ${this.connectionId}, Baud: ${options.baudRate}`,
);
this.dispatchEvent(
new CustomEvent("connect", { detail: connectionInfo }),
);
} else if (connectionInfo && this.openCanceled) {
this.connectionId = this.device.port;
console.log(
`${this.logHead} Connection opened with ID: ${connectionInfo.connectionId}, but request was canceled, disconnecting`,
);
// some bluetooth dongles/dongle drivers really doesn't like to be closed instantly, adding a small delay
setTimeout(() => {
this.openRequested = false;
this.openCanceled = false;
this.disconnect(() => {
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
});
}, 150);
} else if (this.openCanceled) {
console.log(
`${this.logHead} Connection didn't open and request was canceled`,
);
this.openRequested = false;
this.openCanceled = false;
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
} else {
this.openRequested = false;
console.log(`${this.logHead} Failed to open bluetooth port`);
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
}
}
async gattConnect() {
this.server = await this.device.gatt?.connect();
}
async getServices() {
console.log(`${this.logHead} Get primary services`);
this.services = await this.server.getPrimaryServices();
this.service = this.services.find(service => {
this.deviceDescription = bluetoothDevices.find(device => device.serviceUuid == service.uuid);
return this.deviceDescription;
});
if (!this.deviceDescription) {
throw new Error("Unsupported device");
}
gui_log(i18n.getMessage('bluetoothConnectionType', [this.deviceDescription.name]));
console.log(`${this.logHead} Connected to service:`, this.service.uuid);
return this.service;
}
async getCharacteristics() {
const characteristics = await this.service.getCharacteristics();
characteristics.forEach(characteristic => {
// console.log("Characteristic: ", characteristic);
if (characteristic.uuid == this.deviceDescription.writeCharacteristic) {
this.writeCharacteristic = characteristic;
}
if (characteristic.uuid == this.deviceDescription.readCharacteristic) {
this.readCharacteristic = characteristic;
}
return this.writeCharacteristic && this.readCharacteristic;
});
if (!this.writeCharacteristic) {
throw new Error("Unexpected write characteristic found - should be", this.deviceDescription.writeCharacteristic);
}
if (!this.readCharacteristic) {
throw new Error("Unexpected read characteristic found - should be", this.deviceDescription.readCharacteristic);
}
this.readCharacteristic.addEventListener('characteristicvaluechanged', this.handleNotification.bind(this));
return await this.readCharacteristic.readValue();
}
handleNotification(event) {
const buffer = new Uint8Array(event.target.value.byteLength);
for (let i = 0; i < event.target.value.byteLength; i++) {
buffer[i] = event.target.value.getUint8(i);
}
this.dispatchEvent(new CustomEvent("receive", { detail: buffer }));
}
startNotifications() {
if (!this.readCharacteristic) {
throw new Error("No read characteristic");
}
if (!this.readCharacteristic.properties.notify) {
throw new Error("Read characteristic unable to notify.");
}
return this.readCharacteristic.startNotifications();
}
async disconnect() {
this.connected = false;
this.transmitting = false;
this.bytesReceived = 0;
this.bytesSent = 0;
// if we are already closing, don't do it again
if (this.closeRequested) {
return;
}
const doCleanup = async () => {
this.removeEventListener('receive', this.handleReceiveBytes);
if (this.device) {
this.device.removeEventListener("disconnect", this.handleDisconnect.bind(this));
this.device.removeEventListener('gattserverdisconnected', this.handleDisconnect);
this.readCharacteristic.removeEventListener('characteristicvaluechanged', this.handleNotification.bind(this));
if (this.device.gatt.connected) {
this.device.gatt.disconnect();
}
this.writeCharacteristic = false;
this.readCharacteristic = false;
this.deviceDescription = false;
this.device = null;
}
};
try {
await doCleanup();
console.log(
`${this.logHead} Connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`,
);
this.connectionId = false;
this.bitrate = 0;
this.dispatchEvent(new CustomEvent("disconnect", { detail: true }));
} catch (error) {
console.error(error);
console.error(
`${this.logHead} Failed to close connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`,
);
this.dispatchEvent(new CustomEvent("disconnect", { detail: false }));
} finally {
if (this.openCanceled) {
this.openCanceled = false;
}
}
}
async send(data) {
if (!this.writeCharacteristic) {
return;
}
// There is no writable stream in the bluetooth API
this.bytesSent += data.byteLength;
const dataBuffer = new Uint8Array(data);
await this.writeCharacteristic.writeValue(dataBuffer);
return {
bytesSent: data.byteLength,
resultCode: 0,
};
}
}
export default new BT();

View file

@ -24,7 +24,6 @@ import CryptoES from "crypto-es";
import $ from 'jquery';
import BuildApi from "./BuildApi";
import { isWeb } from "./utils/isWeb";
import { serialShim } from "./serial_shim.js";
import { EventBus } from "../components/eventBus";
@ -64,6 +63,18 @@ export function initializeSerialBackend() {
}
});
EventBus.$on('port-handler:auto-select-bluetooth-device', function(device) {
if (!GUI.connected_to && !GUI.connecting_to && GUI.active_tab !== 'firmware_flasher'
&& ((PortHandler.portPicker.autoConnect && !["manual", "virtual"].includes(device))
|| Date.now() - rebootTimestamp < REBOOT_CONNECT_MAX_TIME_MS)) {
connectDisconnect();
}
});
// Using serialShim for serial and bluetooth we don't know which event we need before we connect
// Perhaps we should implement a Connection class that handles the connection and events for bluetooth, serial and sockets
// TODO: use event gattserverdisconnected for save and reboot and device removal.
serial.addEventListener("removedDevice", (event) => {
if (event.detail.path === GUI.connected_to) {
connectDisconnect();
@ -88,39 +99,39 @@ export function initializeSerialBackend() {
function connectDisconnect() {
const selectedPort = PortHandler.portPicker.selectedPort;
let portName;
if (selectedPort === 'manual') {
portName = PortHandler.portPicker.portOverride;
} else {
portName = selectedPort;
}
const portName = selectedPort === 'manual' ? PortHandler.portPicker.portOverride : selectedPort;
if (!GUI.connect_lock && selectedPort !== 'noselection' && !selectedPort.path?.startsWith('usb_')) {
// GUI control overrides the user control
GUI.configuration_loaded = false;
const selected_baud = PortHandler.portPicker.selectedBauds;
const baudRate = PortHandler.portPicker.selectedBauds;
const selectedPort = portName;
if (!isConnected) {
console.log(`Connecting to: ${portName}`);
// prevent connection when we do not have permission
if (selectedPort.startsWith('requestpermission')) {
return;
}
console.log(`[SERIAL-BACKEND] Connecting to: ${portName}`);
GUI.connecting_to = portName;
// lock port select & baud while we are connecting / connected
PortHandler.portPickerDisabled = true;
$('div.connect_controls div.connect_state').text(i18n.getMessage('connecting'));
const baudRate = selected_baud;
if (selectedPort === 'virtual') {
CONFIGURATOR.virtualMode = true;
CONFIGURATOR.virtualMode = selectedPort === 'virtual';
CONFIGURATOR.bluetoothMode = selectedPort.startsWith('bluetooth');
if (CONFIGURATOR.virtualMode) {
CONFIGURATOR.virtualApiVersion = PortHandler.portPicker.virtualMspVersion;
// Hack to get virtual working on the web
serial = serialShim();
serial.connect('virtual', {}, onOpenVirtual);
} else {
CONFIGURATOR.virtualMode = false;
serial = serialShim();
// Explicitly disconnect the event listeners before attaching the new ones.
serial.removeEventListener('connect', connectHandler);
@ -163,6 +174,7 @@ function finishClose(finishedCallback) {
$('#dialogResetToCustomDefaults')[0].close();
}
// serialShim calls the disconnect method for selected connection type.
serial.disconnect(onClosed);
MSP.disconnect_cleanup();
@ -262,18 +274,16 @@ function onOpen(openInfo) {
result = getConfig('expertMode')?.expertMode ?? false;
$('input[name="expertModeCheckbox"]').prop('checked', result).trigger('change');
if(isWeb()) {
serial.removeEventListener('receive', read_serial_adapter);
serial.addEventListener('receive', read_serial_adapter);
} else {
serial.onReceive.addListener(read_serial);
}
// serialShim adds event listener for selected connection type
serial.removeEventListener('receive', read_serial_adapter);
serial.addEventListener('receive', read_serial_adapter);
setConnectionTimeout();
FC.resetState();
mspHelper = new MspHelper();
MSP.listen(mspHelper.process_data.bind(mspHelper));
MSP.timeout = 250;
console.log(`Requesting configuration data`);
console.log(`[SERIAL-BACKEND] Requesting configuration data`);
MSP.send_message(MSPCodes.MSP_API_VERSION, false, false, function () {
gui_log(i18n.getMessage('apiVersionReceived', FC.CONFIG.apiVersion));
@ -642,11 +652,7 @@ function onConnect() {
}
function onClosed(result) {
if (result) { // All went as expected
gui_log(i18n.getMessage('serialPortClosedOk'));
} else { // Something went wrong
gui_log(i18n.getMessage('serialPortClosedFail'));
}
gui_log(i18n.getMessage(result ? 'serialPortClosedOk' : 'serialPortClosedFail'));
$('#tabs ul.mode-connected').hide();
$('#tabs ul.mode-connected-cli').hide();
@ -760,7 +766,7 @@ function startLiveDataRefreshTimer() {
export function reinitializeConnection(callback) {
// In virtual mode reconnect when autoconnect is enabled
if (PortHandler.portPicker.selectedPort === 'virtual' && PortHandler.portPicker.autoConnect) {
if (CONFIGURATOR.virtualMode && PortHandler.portPicker.autoConnect) {
return setTimeout(function() {
$('a.connect').trigger('click');
}, 500);
@ -769,6 +775,11 @@ export function reinitializeConnection(callback) {
rebootTimestamp = Date.now();
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false);
if (CONFIGURATOR.bluetoothMode) {
// Bluetooth devices are not disconnected when rebooting
connectDisconnect();
}
gui_log(i18n.getMessage('deviceRebooting'));
// wait for the device to reboot

View file

@ -1,6 +1,6 @@
import CONFIGURATOR from "./data_storage";
import serialNWJS from "./serial.js";
import serialWeb from "./webSerial.js";
import { isWeb } from "./utils/isWeb";
import BT from "./protocols/bluetooth.js";
export let serialShim = () => CONFIGURATOR.virtualMode ? serialNWJS : isWeb() ? serialWeb : serialNWJS;
export let serialShim = () => CONFIGURATOR.virtualMode ? serialNWJS : CONFIGURATOR.bluetoothMode ? BT : serialWeb;

View file

@ -29,7 +29,7 @@ class WebSerial extends EventTarget {
this.bytesReceived = 0;
this.failed = 0;
this.logHead = "SERIAL: ";
this.logHead = "[SERIAL] ";
this.portCounter = 0;
this.ports = [];
@ -96,10 +96,13 @@ class WebSerial extends EventTarget {
async requestPermissionDevice(showAllSerialDevices = false) {
let newPermissionPort = null;
try {
const options = showAllSerialDevices ? {} : { filters: webSerialDevices };
const userSelectedPort = await navigator.serial.requestPort(options);
newPermissionPort = this.ports.find(port => port.port === userSelectedPort);
if (!newPermissionPort) {
newPermissionPort = this.handleNewDevice(userSelectedPort);
}