Skip to content

Commit 9baef76

Browse files
authored
Allow execution when either Web Serial, Bluetooth or USB API is present (#4470)
* Allow execution when either webserial, webbluetooth or webusb API is available * Fix todo * Add passing missing properties * Update browser detection * Remove commented code * Indentation * Update presentation of errorMessage * cleanup * Consistency * Nitpicks * Remove writeValueWithResponse * More nitpicks
1 parent 77a719d commit 9baef76

File tree

9 files changed

+188
-63
lines changed

9 files changed

+188
-63
lines changed

src/components/port-picker/PortPicker.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
:disabled="disabled"
1919
:show-virtual-option="showVirtualOption"
2020
:show-manual-option="showManualOption"
21+
:show-bluetooth-option="showBluetoothOption"
22+
:show-serial-option="showSerialOption"
23+
:show-usb-option="showUsbOption"
2124
@update:modelValue="updateModelValue(null, $event)"
2225
/>
2326
</div>
@@ -62,6 +65,18 @@ export default defineComponent({
6265
type: Boolean,
6366
default: true,
6467
},
68+
showBluetoothOption: {
69+
type: Boolean,
70+
default: true,
71+
},
72+
showSerialOption: {
73+
type: Boolean,
74+
default: true,
75+
},
76+
showUsbOption: {
77+
type: Boolean,
78+
default: true,
79+
},
6580
disabled: {
6681
type: Boolean,
6782
default: false,

src/components/port-picker/PortsInput.vue

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,36 @@
1919
{{ $t("portsSelectVirtual") }}
2020
</option>
2121
<option
22+
v-if="showBluetoothOption"
2223
v-for="connectedBluetoothDevice in connectedBluetoothDevices"
2324
:key="connectedBluetoothDevice.path"
2425
:value="connectedBluetoothDevice.path"
2526
>
2627
{{ connectedBluetoothDevice.displayName }}
2728
</option>
2829
<option
30+
v-if="showSerialOption"
2931
v-for="connectedSerialDevice in connectedSerialDevices"
3032
:key="connectedSerialDevice.path"
3133
:value="connectedSerialDevice.path"
3234
>
3335
{{ connectedSerialDevice.displayName }}
3436
</option>
3537
<option
38+
v-if="showUsbOption"
3639
v-for="connectedUsbDevice in connectedUsbDevices"
3740
:key="connectedUsbDevice.path"
3841
:value="connectedUsbDevice.path"
3942
>
4043
{{ connectedUsbDevice.displayName }}
4144
</option>
42-
<option value="requestpermission">
45+
<option v-if="showSerialOption" value="requestpermission">
4346
{{ $t("portsSelectPermission") }}
4447
</option>
45-
<option value="requestpermissionbluetooth">
48+
<option v-if="showBluetoothOption" value="requestpermissionbluetooth">
4649
{{ $t("portsSelectPermissionBluetooth") }}
4750
</option>
48-
<option value="requestpermissionusb">
51+
<option v-if="showUsbOption" value="requestpermissionusb">
4952
{{ $t("portsSelectPermissionDFU") }}
5053
</option>
5154
</select>
@@ -121,6 +124,18 @@ export default defineComponent({
121124
type: Boolean,
122125
default: true,
123126
},
127+
showBluetoothOption: {
128+
type: Boolean,
129+
default: true,
130+
},
131+
showSerialOption: {
132+
type: Boolean,
133+
default: true,
134+
},
135+
showUsbOption: {
136+
type: Boolean,
137+
default: true,
138+
},
124139
},
125140
emits: ["update:modelValue"],
126141
setup(props, { emit }) {

src/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
:connected-usb-devices="PortHandler.currentUsbPorts"
3434
:show-virtual-option="PortHandler.showVirtualMode"
3535
:show-manual-option="PortHandler.showManualMode"
36+
:show-bluetooth-option="PortHandler.showBluetoothOption"
37+
:show-serial-option="PortHandler.showSerialOption"
38+
:show-usb-option="PortHandler.showUsbOption"
3639
:disabled="PortHandler.portPickerDisabled"
3740
></port-picker>
3841
<div class="header-wrapper">

src/js/gui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import MSP from "./msp";
33
import Switchery from "switchery-latest";
44
import jBox from "jbox";
55
import $ from "jquery";
6-
import { getOS } from "./utils/checkBrowserCompatibilty";
6+
import { getOS } from "./utils/checkBrowserCompatibility";
77

88
const TABS = {};
99

src/js/port_handler.js

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { EventBus } from "../components/eventBus";
33
import { serial } from "./serial.js";
44
import WEBUSBDFU from "./protocols/webusbdfu";
55
import { reactive } from "vue";
6+
import {
7+
checkBrowserCompatibility,
8+
checkWebBluetoothSupport,
9+
checkWebSerialSupport,
10+
checkWebUSBSupport,
11+
} from "./utils/checkBrowserCompatibility.js";
612

713
const DEFAULT_PORT = "noselection";
814
const DEFAULT_BAUDS = 115200;
@@ -27,7 +33,17 @@ const PortHandler = new (function () {
2733
this.bluetoothAvailable = false;
2834
this.dfuAvailable = false;
2935
this.portAvailable = false;
30-
this.showAllSerialDevices = false;
36+
37+
checkBrowserCompatibility();
38+
39+
this.showBluetoothOption = checkWebBluetoothSupport();
40+
this.showSerialOption = checkWebSerialSupport();
41+
this.showUsbOption = checkWebUSBSupport();
42+
43+
console.log(`${this.logHead} Bluetooth available: ${this.showBluetoothOption}`);
44+
console.log(`${this.logHead} Serial available: ${this.showSerialOption}`);
45+
console.log(`${this.logHead} DFU available: ${this.showUsbOption}`);
46+
3147
this.showVirtualMode = getConfig("showVirtualMode", false).showVirtualMode;
3248
this.showManualMode = getConfig("showManualMode", false).showManualMode;
3349
this.showAllSerialDevices = getConfig("showAllSerialDevices", false).showAllSerialDevices;
@@ -183,21 +199,21 @@ PortHandler.selectActivePort = function (suggestedDevice = false) {
183199
}
184200

185201
// If there is a connection, return it
186-
// if (selectedPort) {
187-
// console.log(`${this.logHead} Using connected device: ${selectedPort.path}`);
188-
// selectedPort = selectedPort.path;
189-
// return selectedPort;
190-
// }
202+
if (selectedPort) {
203+
console.log(`${this.logHead} Using connected device: ${selectedPort.path}`);
204+
selectedPort = selectedPort.path;
205+
return selectedPort;
206+
}
191207

192208
// If there is no connection, check for the last used device
193209
// Check if the device is already connected
194-
// if (this.portPicker.selectedPort && this.portPicker.selectedPort !== DEFAULT_PORT) {
195-
// selectedPort = this.currentSerialPorts.find((device) => device.path === this.portPicker.selectedPort);
196-
// if (selectedPort) {
197-
// console.log(`${this.logHead} Using previously selected device: ${selectedPort.path}`);
198-
// return selectedPort.path;
199-
// }
200-
// }
210+
if (this.portPicker.selectedPort && this.portPicker.selectedPort !== DEFAULT_PORT) {
211+
selectedPort = this.currentSerialPorts.find((device) => device.path === this.portPicker.selectedPort);
212+
if (selectedPort) {
213+
console.log(`${this.logHead} Using previously selected device: ${selectedPort.path}`);
214+
return selectedPort.path;
215+
}
216+
}
201217

202218
// Return the suggested device (the new device that has been detected)
203219
if (!selectedPort && suggestedDevice) {
@@ -289,15 +305,23 @@ PortHandler.updateDeviceList = async function (deviceType) {
289305
try {
290306
switch (deviceType) {
291307
case "bluetooth":
292-
ports = await serial.getDevices("bluetooth");
308+
if (this.showBluetoothOption) {
309+
ports = await serial.getDevices("bluetooth");
310+
}
293311
break;
294312
case "usb":
295-
ports = await WEBUSBDFU.getDevices();
313+
if (this.showUsbOption) {
314+
ports = await WEBUSBDFU.getDevices();
315+
}
296316
break;
297317
case "serial":
298-
default:
299-
ports = await serial.getDevices("serial");
318+
if (this.showSerialOption) {
319+
ports = await serial.getDevices("serial");
320+
}
300321
break;
322+
default:
323+
console.warn(`${this.logHead} Unknown device type: ${deviceType}`);
324+
return [];
301325
}
302326

303327
// Sort the ports

src/js/protocols/WebBluetooth.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class WebBluetooth extends EventTarget {
2020
this.closeRequested = false;
2121
this.transmitting = false;
2222
this.connectionInfo = null;
23+
this.lastWrite = null;
2324

2425
this.bitrate = 0;
2526
this.bytesSent = 0;
@@ -32,10 +33,10 @@ class WebBluetooth extends EventTarget {
3233

3334
this.logHead = "[BLUETOOTH]";
3435

35-
if (!this.bluetooth && window && window.navigator && window.navigator.bluetooth) {
36-
this.bluetooth = navigator.bluetooth;
37-
} else {
38-
console.error(`${this.logHead} Bluetooth API not available`);
36+
this.bluetooth = navigator?.bluetooth;
37+
38+
if (!this.bluetooth) {
39+
console.error(`${this.logHead} Web Bluetooth API not supported`);
3940
return;
4041
}
4142

@@ -86,10 +87,14 @@ class WebBluetooth extends EventTarget {
8687
}
8788

8889
async loadDevices() {
89-
const devices = await this.getDevices();
90+
try {
91+
const devices = await this.getDevices();
9092

91-
this.portCounter = 1;
92-
this.devices = devices.map((device) => this.createPort(device));
93+
this.portCounter = 1;
94+
this.devices = devices.map((device) => this.createPort(device));
95+
} catch (error) {
96+
console.error(`${this.logHead} Failed to load devices:`, error);
97+
}
9398
}
9499

95100
async requestPermissionDevice() {
@@ -251,7 +256,11 @@ class WebBluetooth extends EventTarget {
251256

252257
this.readCharacteristic.addEventListener("characteristicvaluechanged", this.handleNotification.bind(this));
253258

254-
return await this.readCharacteristic.readValue();
259+
try {
260+
return await this.readCharacteristic.readValue();
261+
} catch (e) {
262+
console.error(`${this.logHead} Failed to read characteristic value:`, e);
263+
}
255264
}
256265

257266
handleNotification(event) {
@@ -261,7 +270,9 @@ class WebBluetooth extends EventTarget {
261270
buffer[i] = event.target.value.getUint8(i);
262271
}
263272

264-
this.dispatchEvent(new CustomEvent("receive", { detail: buffer }));
273+
setTimeout(() => {
274+
this.dispatchEvent(new CustomEvent("receive", { detail: buffer }));
275+
}, 0);
265276
}
266277

267278
startNotifications() {
@@ -342,7 +353,14 @@ class WebBluetooth extends EventTarget {
342353

343354
const dataBuffer = new Uint8Array(data);
344355

345-
await this.writeCharacteristic.writeValue(dataBuffer);
356+
try {
357+
if (this.lastWrite) {
358+
await this.lastWrite;
359+
}
360+
} catch (error) {
361+
console.error(error);
362+
}
363+
this.lastWrite = this.writeCharacteristic.writeValue(dataBuffer);
346364

347365
return {
348366
bytesSent: data.byteLength,

src/js/protocols/WebSerial.js

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { webSerialDevices, vendorIdNames } from "./devices";
2-
import { checkBrowserCompatibility } from "../utils/checkBrowserCompatibilty";
32
import GUI from "../gui";
43

54
const logHead = "[SERIAL]";
@@ -39,8 +38,6 @@ class WebSerial extends EventTarget {
3938
constructor() {
4039
super();
4140

42-
checkBrowserCompatibility();
43-
4441
this.connected = false;
4542
this.openRequested = false;
4643
this.openCanceled = false;
@@ -60,16 +57,20 @@ class WebSerial extends EventTarget {
6057
this.writer = null;
6158
this.reading = false;
6259

60+
if (!navigator?.serial) {
61+
console.error(`${logHead} Web Serial API not supported`);
62+
return;
63+
}
64+
6365
this.connect = this.connect.bind(this);
6466
this.disconnect = this.disconnect.bind(this);
6567
this.handleDisconnect = this.handleDisconnect.bind(this);
6668
this.handleReceiveBytes = this.handleReceiveBytes.bind(this);
6769

6870
// Initialize device connection/disconnection listeners
69-
if (navigator.serial) {
70-
navigator.serial.addEventListener("connect", (e) => this.handleNewDevice(e.target));
71-
navigator.serial.addEventListener("disconnect", (e) => this.handleRemovedDevice(e.target));
72-
}
71+
navigator.serial.addEventListener("connect", (e) => this.handleNewDevice(e.target));
72+
navigator.serial.addEventListener("disconnect", (e) => this.handleRemovedDevice(e.target));
73+
7374
this.isNeedBatchWrite = false;
7475
this.loadDevices();
7576
}
@@ -116,11 +117,6 @@ class WebSerial extends EventTarget {
116117
}
117118

118119
async loadDevices() {
119-
if (!navigator.serial) {
120-
console.error(`${logHead} Web Serial API not available`);
121-
return;
122-
}
123-
124120
try {
125121
const ports = await navigator.serial.getPorts();
126122
this.portCounter = 1;
@@ -131,11 +127,6 @@ class WebSerial extends EventTarget {
131127
}
132128

133129
async requestPermissionDevice(showAllSerialDevices = false) {
134-
if (!navigator.serial) {
135-
console.error(`${logHead} Web Serial API not available`);
136-
return null;
137-
}
138-
139130
let newPermissionPort = null;
140131

141132
try {

src/js/protocols/webusbdfu.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class WEBUSBDFU_protocol extends EventTarget {
7777
this.flash_layout = { start_address: 0, total_size: 0, sectors: [] };
7878
this.transferSize = 2048; // Default USB DFU transfer size for F3,F4 and F7
7979

80+
if (!navigator?.usb) {
81+
console.error(`${this.logHead} WebUSB API not supported`);
82+
return;
83+
}
84+
8085
navigator.usb.addEventListener("connect", (e) => this.handleNewDevice(e.device));
8186
navigator.usb.addEventListener("disconnect", (e) => this.handleNewDevice(e.device));
8287
}

0 commit comments

Comments
 (0)