mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-18 22:05:13 +03:00
Serial refactor part 2 (#4404)
* Serial refactor part 2 * Fix callback * Fix some sonar complaints * Does no longer block dfu * Excempt presets * Add interval for reboot dialog * Update message after review Vitroid
This commit is contained in:
parent
c842b9d60b
commit
2d36e62474
6 changed files with 402 additions and 135 deletions
|
@ -320,6 +320,12 @@
|
||||||
"configuratorUpdateWebsite": {
|
"configuratorUpdateWebsite": {
|
||||||
"message": "Go to Release Website"
|
"message": "Go to Release Website"
|
||||||
},
|
},
|
||||||
|
"rebootFlightController": {
|
||||||
|
"message": "Rebooting flight controller, reconnect when ready"
|
||||||
|
},
|
||||||
|
"rebootFlightControllerReady": {
|
||||||
|
"message": "Flight Controller is ready"
|
||||||
|
},
|
||||||
"deviceRebooting": {
|
"deviceRebooting": {
|
||||||
"message": "Device - <span class=\"message-negative\">Rebooting</span>"
|
"message": "Device - <span class=\"message-negative\">Rebooting</span>"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { get as getConfig } from "./ConfigStorage";
|
||||||
import { EventBus } from "../components/eventBus";
|
import { EventBus } from "../components/eventBus";
|
||||||
import { serial } from "./serial.js";
|
import { serial } from "./serial.js";
|
||||||
import WEBUSBDFU from "./protocols/webusbdfu";
|
import WEBUSBDFU from "./protocols/webusbdfu";
|
||||||
import WebBluetooth from "./protocols/WebBluetooth.js";
|
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
|
|
||||||
const DEFAULT_PORT = "noselection";
|
const DEFAULT_PORT = "noselection";
|
||||||
|
@ -35,44 +34,42 @@ const PortHandler = new (function () {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
PortHandler.initialize = function () {
|
PortHandler.initialize = function () {
|
||||||
EventBus.$on("ports-input:request-permission-bluetooth", this.askBluetoothPermissionPort.bind(this));
|
EventBus.$on("ports-input:request-permission-bluetooth", () => this.requestDevicePermission("bluetooth"));
|
||||||
EventBus.$on("ports-input:request-permission", this.askSerialPermissionPort.bind(this));
|
EventBus.$on("ports-input:request-permission", () => this.requestDevicePermission("serial"));
|
||||||
EventBus.$on("ports-input:change", this.onChangeSelectedPort.bind(this));
|
EventBus.$on("ports-input:change", this.onChangeSelectedPort.bind(this));
|
||||||
|
|
||||||
// Use serial for all protocol events
|
// Use serial for all protocol events
|
||||||
serial.addEventListener("addedDevice", (event) => {
|
serial.addEventListener("addedDevice", (event) => {
|
||||||
const detail = event.detail;
|
const detail = event.detail;
|
||||||
|
|
||||||
// Determine the device type based on its properties
|
|
||||||
if (detail?.path?.startsWith("bluetooth")) {
|
if (detail?.path?.startsWith("bluetooth")) {
|
||||||
this.addedBluetoothDevice(detail);
|
this.handleDeviceAdded(detail, "bluetooth");
|
||||||
} else if (detail?.path?.startsWith("usb_")) {
|
|
||||||
this.addedUsbDevice(detail);
|
|
||||||
} else {
|
} else {
|
||||||
this.addedSerialDevice(detail);
|
this.handleDeviceAdded(detail, "serial");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
serial.addEventListener("removedDevice", (event) => {
|
serial.addEventListener("removedDevice", (event) => {
|
||||||
const detail = event.detail;
|
this.removedSerialDevice(event.detail);
|
||||||
|
|
||||||
// Determine the device type based on its properties
|
|
||||||
if (detail?.path?.startsWith("bluetooth")) {
|
|
||||||
this.removedBluetoothDevice(detail);
|
|
||||||
} else if (detail?.path?.startsWith("usb_")) {
|
|
||||||
// Handle USB device removal if needed
|
|
||||||
} else {
|
|
||||||
this.removedSerialDevice(detail);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep USB listener separate as it's not part of the serial protocols
|
// Keep USB listener separate as it's not part of the serial protocols
|
||||||
WEBUSBDFU.addEventListener("addedDevice", (event) => this.addedUsbDevice(event.detail));
|
WEBUSBDFU.addEventListener("addedDevice", (event) => this.addedUsbDevice(event.detail));
|
||||||
|
|
||||||
// Initial device discovery
|
// Initial device discovery using the serial facade
|
||||||
this.addedSerialDevice();
|
this.refreshAllDeviceLists();
|
||||||
this.addedBluetoothDevice();
|
};
|
||||||
this.addedUsbDevice();
|
|
||||||
|
// Refactored refreshAllDeviceLists to use updateDeviceList
|
||||||
|
PortHandler.refreshAllDeviceLists = async function () {
|
||||||
|
// Update all device lists in parallel
|
||||||
|
return Promise.all([
|
||||||
|
this.updateDeviceList("serial"),
|
||||||
|
this.updateDeviceList("bluetooth"),
|
||||||
|
this.updateDeviceList("usb"),
|
||||||
|
]).then(() => {
|
||||||
|
this.selectActivePort();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
PortHandler.setShowVirtualMode = function (showVirtualMode) {
|
PortHandler.setShowVirtualMode = function (showVirtualMode) {
|
||||||
|
@ -89,44 +86,40 @@ PortHandler.setShowAllSerialDevices = function (showAllSerialDevices) {
|
||||||
this.showAllSerialDevices = showAllSerialDevices;
|
this.showAllSerialDevices = showAllSerialDevices;
|
||||||
};
|
};
|
||||||
|
|
||||||
PortHandler.addedSerialDevice = function (device) {
|
|
||||||
this.updateCurrentSerialPortsList().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-serial-device", selectedPort);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
PortHandler.removedSerialDevice = function (device) {
|
PortHandler.removedSerialDevice = function (device) {
|
||||||
this.updateCurrentSerialPortsList().then(() => {
|
console.log(`${this.logHead} Device removal event received:`, device);
|
||||||
if (this.portPicker.selectedPort === device.path) {
|
|
||||||
this.selectActivePort();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
PortHandler.addedBluetoothDevice = function (device) {
|
// Get device path safely
|
||||||
this.updateCurrentBluetoothPortsList().then(() => {
|
const devicePath = device?.path || (typeof device === "string" ? device : null);
|
||||||
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) {
|
if (!devicePath) {
|
||||||
this.updateCurrentBluetoothPortsList().then(() => {
|
console.warn(`${this.logHead} Device removal event missing path information`, device);
|
||||||
if (this.portPicker.selectedPort === device.path) {
|
// Still update ports, but don't try to use the undefined path
|
||||||
|
this.updateDeviceList("serial").then(() => {
|
||||||
this.selectActivePort();
|
this.selectActivePort();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the appropriate ports list based on the device type
|
||||||
|
const updatePromise = devicePath.startsWith("bluetooth")
|
||||||
|
? this.updateDeviceList("bluetooth")
|
||||||
|
: this.updateDeviceList("serial");
|
||||||
|
|
||||||
|
const wasSelectedPort = this.portPicker.selectedPort === devicePath;
|
||||||
|
|
||||||
|
updatePromise.then(() => {
|
||||||
|
if (wasSelectedPort) {
|
||||||
|
this.selectActivePort();
|
||||||
|
|
||||||
|
// Send event for UI components that might need to update
|
||||||
|
EventBus.$emit("port-handler:device-removed", devicePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
PortHandler.addedUsbDevice = function (device) {
|
PortHandler.addedUsbDevice = function (device) {
|
||||||
this.updateCurrentUsbPortsList().then(() => {
|
this.updateDeviceList("usb").then(() => {
|
||||||
const selectedPort = this.selectActivePort(device);
|
const selectedPort = this.selectActivePort(device);
|
||||||
if (!device || selectedPort === device.path) {
|
if (!device || selectedPort === device.path) {
|
||||||
// Send this event when the port handler auto selects a new device
|
// Send this event when the port handler auto selects a new device
|
||||||
|
@ -139,30 +132,28 @@ PortHandler.onChangeSelectedPort = function (port) {
|
||||||
this.portPicker.selectedPort = port;
|
this.portPicker.selectedPort = port;
|
||||||
};
|
};
|
||||||
|
|
||||||
PortHandler.updateCurrentSerialPortsList = async function () {
|
/**
|
||||||
const ports = await serial.getDevices();
|
* Request permission for a device of the specified type
|
||||||
const orderedPorts = this.sortPorts(ports);
|
* @param {string} deviceType - Type of device ('serial' or 'bluetooth')
|
||||||
this.portAvailable = orderedPorts.length > 0;
|
*/
|
||||||
console.log(`${this.logHead} Found serial port`, orderedPorts);
|
PortHandler.requestDevicePermission = function (deviceType = "serial") {
|
||||||
this.currentSerialPorts = [...orderedPorts];
|
// Determine whether to show all devices based on device type
|
||||||
};
|
const showAllDevices = deviceType === "serial" ? this.showAllSerialDevices : false;
|
||||||
|
|
||||||
PortHandler.updateCurrentUsbPortsList = async function () {
|
// Use serial facade to request permission
|
||||||
const ports = await WEBUSBDFU.getDevices();
|
serial
|
||||||
const orderedPorts = this.sortPorts(ports);
|
.requestPermissionDevice(showAllDevices, deviceType)
|
||||||
this.dfuAvailable = orderedPorts.length > 0;
|
.then((port) => {
|
||||||
console.log(`${this.logHead} Found DFU port`, orderedPorts);
|
if (port) {
|
||||||
this.currentUsbPorts = [...orderedPorts];
|
console.log(`${this.logHead} Permission granted for ${deviceType} device:`, port);
|
||||||
};
|
this.selectActivePort(port);
|
||||||
|
} else {
|
||||||
PortHandler.updateCurrentBluetoothPortsList = async function () {
|
console.log(`${this.logHead} Permission request cancelled or failed for ${deviceType}`);
|
||||||
if (WebBluetooth.bluetooth) {
|
|
||||||
const ports = await WebBluetooth.getDevices();
|
|
||||||
const orderedPorts = this.sortPorts(ports);
|
|
||||||
this.bluetoothAvailable = orderedPorts.length > 0;
|
|
||||||
console.log(`${this.logHead} Found bluetooth port`, orderedPorts);
|
|
||||||
this.currentBluetoothPorts = [...orderedPorts];
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(`${this.logHead} Error requesting permission for ${deviceType}:`, error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
PortHandler.sortPorts = function (ports) {
|
PortHandler.sortPorts = function (ports) {
|
||||||
|
@ -174,31 +165,11 @@ PortHandler.sortPorts = function (ports) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
PortHandler.askBluetoothPermissionPort = function () {
|
PortHandler.selectActivePort = function (suggestedDevice = false) {
|
||||||
if (WebBluetooth.bluetooth) {
|
|
||||||
WebBluetooth.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) => {
|
|
||||||
// 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.selectActivePort = function (suggestedDevice) {
|
|
||||||
const deviceFilter = ["AT32", "CP210", "SPR", "STM"];
|
const deviceFilter = ["AT32", "CP210", "SPR", "STM"];
|
||||||
let selectedPort;
|
let selectedPort;
|
||||||
|
|
||||||
// Return the same that is connected to serial
|
// First check for active connections
|
||||||
if (serial.connected) {
|
if (serial.connected) {
|
||||||
selectedPort = this.currentSerialPorts.find((device) => device === serial.getConnectedPort());
|
selectedPort = this.currentSerialPorts.find((device) => device === serial.getConnectedPort());
|
||||||
}
|
}
|
||||||
|
@ -208,10 +179,22 @@ PortHandler.selectActivePort = function (suggestedDevice) {
|
||||||
selectedPort = this.currentUsbPorts.find((device) => device === WEBUSBDFU.getConnectedPort());
|
selectedPort = this.currentUsbPorts.find((device) => device === WEBUSBDFU.getConnectedPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the same that is connected to bluetooth
|
// If there is a connection, return it
|
||||||
if (WebBluetooth.device) {
|
// if (selectedPort) {
|
||||||
selectedPort = this.currentBluetoothPorts.find((device) => device === WebBluetooth.getConnectedPort());
|
// console.log(`${this.logHead} Using connected device: ${selectedPort.path}`);
|
||||||
}
|
// selectedPort = selectedPort.path;
|
||||||
|
// return selectedPort;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// If there is no connection, check for the last used device
|
||||||
|
// Check if the device is already connected
|
||||||
|
// if (this.portPicker.selectedPort && this.portPicker.selectedPort !== DEFAULT_PORT) {
|
||||||
|
// selectedPort = this.currentSerialPorts.find((device) => device.path === this.portPicker.selectedPort);
|
||||||
|
// if (selectedPort) {
|
||||||
|
// console.log(`${this.logHead} Using previously selected device: ${selectedPort.path}`);
|
||||||
|
// return selectedPort.path;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Return the suggested device (the new device that has been detected)
|
// Return the suggested device (the new device that has been detected)
|
||||||
if (!selectedPort && suggestedDevice) {
|
if (!selectedPort && suggestedDevice) {
|
||||||
|
@ -269,6 +252,81 @@ PortHandler.selectActivePort = function (suggestedDevice) {
|
||||||
return selectedPort;
|
return selectedPort;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create a unified handler for device addition
|
||||||
|
PortHandler.handleDeviceAdded = function (device, deviceType) {
|
||||||
|
if (!device) {
|
||||||
|
console.warn(`${this.logHead} Invalid ${deviceType} device added event`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${this.logHead} ${deviceType} device added:`, device);
|
||||||
|
|
||||||
|
// Update the appropriate device list
|
||||||
|
const updatePromise =
|
||||||
|
deviceType === "bluetooth" ? this.updateDeviceList("bluetooth") : this.updateDeviceList("serial");
|
||||||
|
|
||||||
|
updatePromise.then(() => {
|
||||||
|
const selectedPort = this.selectActivePort(device);
|
||||||
|
|
||||||
|
if (selectedPort === device.path) {
|
||||||
|
// Emit an event with the proper type for backward compatibility
|
||||||
|
EventBus.$emit(`port-handler:auto-select-${deviceType}-device`, selectedPort);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update device list with common implementation
|
||||||
|
* @param {string} deviceType - Type of device ('serial', 'bluetooth', 'usb')
|
||||||
|
* @returns {Promise} - Promise that resolves after updating the ports list
|
||||||
|
*/
|
||||||
|
PortHandler.updateDeviceList = async function (deviceType) {
|
||||||
|
let ports = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (deviceType) {
|
||||||
|
case "bluetooth":
|
||||||
|
ports = await serial.getDevices("bluetooth");
|
||||||
|
break;
|
||||||
|
case "usb":
|
||||||
|
ports = await WEBUSBDFU.getDevices();
|
||||||
|
break;
|
||||||
|
case "serial":
|
||||||
|
default:
|
||||||
|
ports = await serial.getDevices("serial");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the ports
|
||||||
|
const orderedPorts = this.sortPorts(ports);
|
||||||
|
|
||||||
|
// Update the appropriate properties based on device type
|
||||||
|
switch (deviceType) {
|
||||||
|
case "bluetooth":
|
||||||
|
this.bluetoothAvailable = orderedPorts.length > 0;
|
||||||
|
this.currentBluetoothPorts = [...orderedPorts];
|
||||||
|
console.log(`${this.logHead} Found bluetooth port(s)`, orderedPorts);
|
||||||
|
break;
|
||||||
|
case "usb":
|
||||||
|
this.dfuAvailable = orderedPorts.length > 0;
|
||||||
|
this.currentUsbPorts = [...orderedPorts];
|
||||||
|
console.log(`${this.logHead} Found DFU port(s)`, orderedPorts);
|
||||||
|
break;
|
||||||
|
case "serial":
|
||||||
|
default:
|
||||||
|
this.portAvailable = orderedPorts.length > 0;
|
||||||
|
this.currentSerialPorts = [...orderedPorts];
|
||||||
|
console.log(`${this.logHead} Found serial port(s)`, orderedPorts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderedPorts;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${this.logHead} Error updating ${deviceType} devices:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// We need to explicit make it reactive. If not, Vue3 does not detect correctly changes in array properties
|
// We need to explicit make it reactive. If not, Vue3 does not detect correctly changes in array properties
|
||||||
// like currentSerialPorts, currentUsbPorts, currentBluetoothPorts
|
// like currentSerialPorts, currentUsbPorts, currentBluetoothPorts
|
||||||
export default reactive(PortHandler);
|
export default reactive(PortHandler);
|
||||||
|
|
|
@ -272,7 +272,7 @@ class WebSerial extends EventTarget {
|
||||||
|
|
||||||
if (this.reader) {
|
if (this.reader) {
|
||||||
await this.reader.cancel();
|
await this.reader.cancel();
|
||||||
this.reader.releaseLock();
|
await this.reader.releaseLock();
|
||||||
this.reader = null;
|
this.reader = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
126
src/js/serial.js
126
src/js/serial.js
|
@ -22,6 +22,14 @@ class Serial extends EventTarget {
|
||||||
this._websocket = new Websocket();
|
this._websocket = new Websocket();
|
||||||
this._virtual = new VirtualSerial();
|
this._virtual = new VirtualSerial();
|
||||||
|
|
||||||
|
// Update protocol map to use consistent naming
|
||||||
|
this._protocolMap = {
|
||||||
|
serial: this._webSerial, // TODO: should be 'webserial'
|
||||||
|
bluetooth: this._bluetooth, // TODO: should be 'webbluetooth'
|
||||||
|
websocket: this._websocket,
|
||||||
|
virtual: this._virtual,
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize with default protocol
|
// Initialize with default protocol
|
||||||
this.selectProtocol(false);
|
this.selectProtocol(false);
|
||||||
|
|
||||||
|
@ -29,6 +37,42 @@ class Serial extends EventTarget {
|
||||||
this._setupEventForwarding();
|
this._setupEventForwarding();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a getter method to safely access the protocol map
|
||||||
|
_getProtocolByType(type) {
|
||||||
|
if (!type) {
|
||||||
|
return this._protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocol = this._protocolMap[type.toLowerCase()];
|
||||||
|
|
||||||
|
if (!protocol) {
|
||||||
|
console.warn(`${this.logHead} Unknown protocol type: ${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the protocol type as a string
|
||||||
|
* @param {Object} protocol - Protocol instance
|
||||||
|
* @returns {string} - Protocol type name
|
||||||
|
*/
|
||||||
|
_getProtocolType(protocol) {
|
||||||
|
if (protocol === this._webSerial) {
|
||||||
|
return "webserial";
|
||||||
|
}
|
||||||
|
if (protocol === this._bluetooth) {
|
||||||
|
return "webbluetooth";
|
||||||
|
}
|
||||||
|
if (protocol === this._websocket) {
|
||||||
|
return "websocket";
|
||||||
|
}
|
||||||
|
if (protocol === this._virtual) {
|
||||||
|
return "virtual";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up event forwarding from all protocols to the Serial class
|
* Set up event forwarding from all protocols to the Serial class
|
||||||
*/
|
*/
|
||||||
|
@ -41,27 +85,20 @@ class Serial extends EventTarget {
|
||||||
events.forEach((eventType) => {
|
events.forEach((eventType) => {
|
||||||
protocol.addEventListener(eventType, (event) => {
|
protocol.addEventListener(eventType, (event) => {
|
||||||
let newDetail;
|
let newDetail;
|
||||||
|
if (event.type === "receive") {
|
||||||
// Special handling for 'receive' events to ensure data is properly passed through
|
// For 'receive' events, we need to handle the data differently
|
||||||
if (eventType === "receive") {
|
|
||||||
// If it's already a Uint8Array or ArrayBuffer, keep it as is
|
|
||||||
newDetail = {
|
newDetail = {
|
||||||
data: event.detail,
|
data: event.detail,
|
||||||
protocolType:
|
protocolType: this._getProtocolType(protocol),
|
||||||
protocol === this._webSerial
|
|
||||||
? "webSerial"
|
|
||||||
: protocol === this._bluetooth
|
|
||||||
? "bluetooth"
|
|
||||||
: protocol === this._websocket
|
|
||||||
? "websocket"
|
|
||||||
: protocol === this._virtual
|
|
||||||
? "virtual"
|
|
||||||
: "unknown",
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// For all other events, pass through the detail as is
|
// For other events, we can use the detail directly
|
||||||
newDetail = event.detail;
|
newDetail = {
|
||||||
|
...event.detail,
|
||||||
|
protocolType: this._getProtocolType(protocol),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch the event with the new detail
|
// Dispatch the event with the new detail
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent(event.type, {
|
new CustomEvent(event.type, {
|
||||||
|
@ -102,14 +139,14 @@ class Serial extends EventTarget {
|
||||||
CONFIGURATOR.bluetoothMode = false;
|
CONFIGURATOR.bluetoothMode = false;
|
||||||
CONFIGURATOR.manualMode = true;
|
CONFIGURATOR.manualMode = true;
|
||||||
} else if (portPath.startsWith("bluetooth")) {
|
} else if (portPath.startsWith("bluetooth")) {
|
||||||
console.log(`${this.logHead} Using bluetooth protocol (based on port path)`);
|
console.log(`${this.logHead} Using bluetooth protocol (based on port path: ${portPath})`);
|
||||||
newProtocol = this._bluetooth;
|
newProtocol = this._bluetooth;
|
||||||
// Update CONFIGURATOR flags for consistency
|
// Update CONFIGURATOR flags for consistency
|
||||||
CONFIGURATOR.virtualMode = false;
|
CONFIGURATOR.virtualMode = false;
|
||||||
CONFIGURATOR.bluetoothMode = true;
|
CONFIGURATOR.bluetoothMode = true;
|
||||||
CONFIGURATOR.manualMode = false;
|
CONFIGURATOR.manualMode = false;
|
||||||
} else {
|
} else {
|
||||||
console.log(`${this.logHead} Using web serial protocol (based on port path)`);
|
console.log(`${this.logHead} Using web serial protocol (based on port path: ${portPath})`);
|
||||||
newProtocol = this._webSerial;
|
newProtocol = this._webSerial;
|
||||||
// Update CONFIGURATOR flags for consistency
|
// Update CONFIGURATOR flags for consistency
|
||||||
CONFIGURATOR.virtualMode = false;
|
CONFIGURATOR.virtualMode = false;
|
||||||
|
@ -258,17 +295,58 @@ class Serial extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get devices from the current protocol
|
* Get devices from a specific protocol type or current protocol
|
||||||
|
* @param {string} protocolType - Optional protocol type ('serial', 'bluetooth', 'websocket', 'virtual')
|
||||||
|
* @returns {Promise<Array>} - List of devices
|
||||||
*/
|
*/
|
||||||
async getDevices() {
|
async getDevices(protocolType = null) {
|
||||||
return this._protocol?.getDevices() || [];
|
try {
|
||||||
|
// Get the appropriate protocol
|
||||||
|
const targetProtocol = this._getProtocolByType(protocolType);
|
||||||
|
|
||||||
|
if (!targetProtocol) {
|
||||||
|
console.warn(`${this.logHead} No valid protocol for getting devices`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof targetProtocol.getDevices !== "function") {
|
||||||
|
console.error(`${this.logHead} Selected protocol does not implement getDevices`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetProtocol.getDevices() || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${this.logHead} Error getting devices:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request permission for a device
|
* Request permission to access a device
|
||||||
|
* @param {boolean} showAllDevices - Whether to show all devices or only those with filters
|
||||||
|
* @param {string} protocolType - Optional protocol type ('serial', 'bluetooth', etc.)
|
||||||
|
* @returns {Promise<Object>} - Promise resolving to the selected device
|
||||||
*/
|
*/
|
||||||
async requestPermissionDevice(showAllSerialDevices = false) {
|
async requestPermissionDevice(showAllDevices = false, protocolType = null) {
|
||||||
return this._protocol?.requestPermissionDevice(showAllSerialDevices) || null;
|
try {
|
||||||
|
// Get the appropriate protocol
|
||||||
|
const targetProtocol = this._getProtocolByType(protocolType);
|
||||||
|
|
||||||
|
if (!targetProtocol) {
|
||||||
|
console.warn(`${this.logHead} No valid protocol for permission request`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof targetProtocol.requestPermissionDevice !== "function") {
|
||||||
|
console.error(`${this.logHead} Selected protocol does not support permission requests`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetProtocol.requestPermissionDevice(showAllDevices);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${this.logHead} Error requesting device permission:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -810,12 +810,16 @@ function startLiveDataRefreshTimer() {
|
||||||
|
|
||||||
export function reinitializeConnection(callback) {
|
export function reinitializeConnection(callback) {
|
||||||
// In virtual mode reconnect when autoconnect is enabled
|
// In virtual mode reconnect when autoconnect is enabled
|
||||||
if (CONFIGURATOR.virtualMode && PortHandler.portPicker.autoConnect) {
|
if (CONFIGURATOR.virtualMode) {
|
||||||
|
connectDisconnect();
|
||||||
|
if (PortHandler.portPicker.autoConnect) {
|
||||||
return setTimeout(function () {
|
return setTimeout(function () {
|
||||||
$("a.connection_button__link").trigger("click");
|
$("a.connection_button__link").trigger("click");
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send reboot command to the flight controller
|
||||||
rebootTimestamp = Date.now();
|
rebootTimestamp = Date.now();
|
||||||
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false);
|
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false);
|
||||||
|
|
||||||
|
@ -824,14 +828,131 @@ export function reinitializeConnection(callback) {
|
||||||
connectDisconnect();
|
connectDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show reboot progress modal except for presets tab
|
||||||
|
if (GUI.active_tab === "presets") {
|
||||||
|
console.log("Rebooting in presets tab, skipping reboot dialog", GUI.active_tab);
|
||||||
gui_log(i18n.getMessage("deviceRebooting"));
|
gui_log(i18n.getMessage("deviceRebooting"));
|
||||||
|
|
||||||
// wait for the device to reboot
|
|
||||||
setTimeout(function () {
|
|
||||||
gui_log(i18n.getMessage("deviceReady"));
|
gui_log(i18n.getMessage("deviceReady"));
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
if (callback && typeof callback === "function") {
|
if (callback && typeof callback === "function") {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Show reboot progress modal
|
||||||
|
showRebootDialog(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRebootDialog(callback) {
|
||||||
|
gui_log(i18n.getMessage("deviceRebooting"));
|
||||||
|
|
||||||
|
// Show reboot progress modal
|
||||||
|
const rebootDialog = document.getElementById("rebootProgressDialog") || createRebootProgressDialog();
|
||||||
|
rebootDialog.querySelector(".reboot-progress-bar").style.width = "0%";
|
||||||
|
rebootDialog.querySelector(".reboot-status").textContent = i18n.getMessage("rebootFlightController");
|
||||||
|
rebootDialog.showModal();
|
||||||
|
|
||||||
|
// Update progress during reboot
|
||||||
|
let progress = 0;
|
||||||
|
const progressInterval = setInterval(() => {
|
||||||
|
progress += 5;
|
||||||
|
if (progress <= 100) {
|
||||||
|
rebootDialog.querySelector(".reboot-progress-bar").style.width = `${progress}%`;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Check for successful connection every 100ms
|
||||||
|
const connectionCheckInterval = setInterval(() => {
|
||||||
|
if (CONFIGURATOR.connectionValid) {
|
||||||
|
// Connection established, device is ready
|
||||||
|
clearInterval(connectionCheckInterval);
|
||||||
|
clearInterval(progressInterval);
|
||||||
|
|
||||||
|
rebootDialog.querySelector(".reboot-progress-bar").style.width = "100%";
|
||||||
|
rebootDialog.querySelector(".reboot-status").textContent = i18n.getMessage("rebootFlightControllerReady");
|
||||||
|
|
||||||
|
// Close the dialog after showing "ready" message briefly
|
||||||
|
setTimeout(() => {
|
||||||
|
rebootDialog.close();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
gui_log(i18n.getMessage("deviceReady"));
|
||||||
|
if (callback && typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Set a maximum timeout for the reboot process (5 seconds)
|
||||||
|
setTimeout(function () {
|
||||||
|
clearInterval(connectionCheckInterval);
|
||||||
|
clearInterval(progressInterval);
|
||||||
|
|
||||||
|
rebootDialog.querySelector(".reboot-progress-bar").style.width = "100%";
|
||||||
|
rebootDialog.querySelector(".reboot-status").textContent = i18n.getMessage("rebootFlightControllerReady");
|
||||||
|
|
||||||
|
// Close the dialog after showing "ready" message briefly
|
||||||
|
setTimeout(() => {
|
||||||
|
rebootDialog.close();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
gui_log(i18n.getMessage("deviceReady"));
|
||||||
|
if (callback && typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
// Helper function to create the reboot dialog if it doesn't exist
|
||||||
|
function createRebootProgressDialog() {
|
||||||
|
const dialog = document.createElement("dialog");
|
||||||
|
dialog.id = "rebootProgressDialog";
|
||||||
|
dialog.className = "dialogReboot";
|
||||||
|
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<div class="content">
|
||||||
|
<div class="reboot-status">${i18n.getMessage("rebootFlightController")}</div>
|
||||||
|
<div class="reboot-progress-container">
|
||||||
|
<div class="reboot-progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(dialog);
|
||||||
|
|
||||||
|
// Add styles if not already defined
|
||||||
|
if (!document.getElementById("rebootProgressStyle")) {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.id = "rebootProgressStyle";
|
||||||
|
style.textContent = `
|
||||||
|
.dialogReboot {
|
||||||
|
border: 1px solid #3f4241;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #2d3233;
|
||||||
|
color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
.reboot-progress-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #424546;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 15px 0 5px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
.reboot-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffbb00;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.1s ease-in-out;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
.reboot-status {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import AutoBackup from "../utils/AutoBackup.js";
|
||||||
import AutoDetect from "../utils/AutoDetect.js";
|
import AutoDetect from "../utils/AutoDetect.js";
|
||||||
import { EventBus } from "../../components/eventBus";
|
import { EventBus } from "../../components/eventBus";
|
||||||
import { ispConnected } from "../utils/connection.js";
|
import { ispConnected } from "../utils/connection.js";
|
||||||
|
import FC from "../fc";
|
||||||
|
|
||||||
const firmware_flasher = {
|
const firmware_flasher = {
|
||||||
targets: null,
|
targets: null,
|
||||||
|
@ -255,6 +256,9 @@ firmware_flasher.initialize = function (callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract osd protocols from general options and add to osdProtocols
|
// extract osd protocols from general options and add to osdProtocols
|
||||||
|
console.log(`${self.logHead} buildOptions`, FC.CONFIG.buildOptions);
|
||||||
|
self.cloudBuildOptions = FC.CONFIG.buildOptions || [];
|
||||||
|
console.log(`${self.logHead} generalOptions`, self.cloudBuildOptions);
|
||||||
data.osdProtocols = data.generalOptions
|
data.osdProtocols = data.generalOptions
|
||||||
.filter((option) => option.group === "OSD")
|
.filter((option) => option.group === "OSD")
|
||||||
.map((option) => {
|
.map((option) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue