Skip to content

Commit de34003

Browse files
authored
Merge pull request #334 from ericzundel/zundel/usb-readonly-update
Added status message area to USB connect workflow
2 parents 77e98ea + 10856e3 commit de34003

5 files changed

Lines changed: 128 additions & 43 deletions

File tree

index.html

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,23 @@ <h1>Web Bluetooth not available!</h1>
221221
<div class="step-number"></div>
222222
<div class="step-content">
223223
<h1>Request Bluetooth Device</h1>
224-
<p>CircuitPython boards with <a href="https://circuitpython.org/downloads?features=Bluetooth%2FBTLE">nrf chips need
225-
CircuitPython 7.0.0 or newer</a>. The first time a device is connected to your host,
226-
you'll need to enable public broadcasting by pressing reset when the faster, blue blink
227-
is happening on start up. The device will reset and the second, blue blink will be solid
228-
when done successfully.</p>
229-
<p>
224+
<p> See the <a href="https://learn.adafruit.com/wirelessly-code-your-bluetooth-device-with-circuitpython/device-setup">online documentation</a>
225+
for platform specific notes on how to use Bluetooth. Note that CircuitPython boards with <a href="https://circuitpython.org/downloads?features=Bluetooth%2FBTLE">nrf chips need
226+
CircuitPython 7.0.0 or newer</a>.
227+
</p>
228+
<p> The first time a device is connected to your host, you'll need to enable
229+
public broadcasting by pressing reset (or bootsel on some devices) when the faster, blue blink
230+
is happening on start up. The device will reset and the second, blue blink will be solid
231+
when done successfully.</p>
230232
<button class="purple-button" id="requestBluetoothDevice">Request Bluetooth Device</button>
231233
</p>
232234
</div>
233235
</section>
236+
<section>
237+
<div class="connect-status" hidden>
238+
<div class="connect-status-content"></div>
239+
</div>
240+
</section>
234241
</div>
235242
</div>
236243
<div class="popup-modal shadow connect-dialog closable" data-popup-modal="web-connect">
@@ -262,6 +269,14 @@ <h1>Navigate to your Device</h1>
262269
<p>Once your device is connected to your Local Area Network, you can navigate to
263270
<a id="device-link" href="http://circuitpython.local/code/">http://circuitpython.local/code/</a>. This opens
264271
a page on your device that loads this website onto the device and to avoid any cross domain security issues.</p>
272+
<p> If your device doesn't support the .local domain or the connection times out, connect with serial to read the
273+
IP address assigned to your device, then open http://device-ip-address:80/code/ from another tab in your browser</p>
274+
</p>
275+
</div>
276+
</section>
277+
<section>
278+
<div class="connect-status" hidden>
279+
<div class="connect-status-content"></div>
265280
</div>
266281
</section>
267282
</div>
@@ -301,6 +316,11 @@ <h1>Select USB Host Folder</h1>
301316
</p>
302317
</div>
303318
</section>
319+
<section>
320+
<div class="connect-status" hidden>
321+
<div class="connect-status-content"></div>
322+
</div>
323+
</section>
304324
</div>
305325
</div>
306326
<div class="popup-modal shadow closable" data-popup-modal="device-discovery">

js/workflows/ble.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ class BLEWorkflow extends Workflow {
7171
stepOne.classList.add("hidden");
7272
}
7373
try {
74+
this.clearConnectStatus();
7475
const devices = await bluetooth.getDevices();
7576
console.log(devices);
7677
this.connectionStep(devices.length > 0 ? 2 : 1);
77-
} catch (e) {
78-
console.log("New Permissions backend for Web Bluetooth not enabled. Go to chrome://flags/#enable-web-bluetooth-new-permissions-backend to enable.", e);
78+
} catch (error) {
79+
console.error(error);
80+
this.showConnectStatus(this._suggestBLEConnectActions(error));
7981
}
8082
} else {
8183
modal.querySelectorAll('.step:not(:first-of-type)').forEach((stepItem) => {
@@ -128,7 +130,7 @@ class BLEWorkflow extends Workflow {
128130
}
129131
catch (error) {
130132
console.error(error);
131-
await this._showMessage(error);
133+
this.showConnectStatus(this._suggestBLEConnectActions(error));
132134
}
133135
}
134136
}
@@ -152,7 +154,9 @@ class BLEWorkflow extends Workflow {
152154
try {
153155
this.bleServer = await device.gatt.connect();
154156
} catch (error) {
155-
await this._showMessage("Failed to connect to device. Try forgetting device from OS bluetooth devices and try again.");
157+
console.log(error);
158+
// TODO(ericzundel): Add to suggestBLEConnectAction if we can determine the exception type
159+
this.showConnectStatus("Failed to connect to device. Try forgetting device from OS bluetooth devices and try again.");
156160
// Disable the reconnect button
157161
this.connectionStep(1);
158162
}
@@ -169,31 +173,25 @@ class BLEWorkflow extends Workflow {
169173

170174
this.debugLog("connecting to " + device.name);
171175
try {
176+
this.clearConnectStatus();
172177
console.log('Watching advertisements from "' + device.name + '"...');
173178
console.log('If no advertisements are received, make sure the device is powered on and in range. You can also try resetting the device');
174179
await device.watchAdvertisements({signal: abortController.signal});
175180
}
176181
catch (error) {
177182
console.error(error);
178-
await this._showMessage(error);
183+
this.showConnectStatus(this._suggestBLEConnectActions(error));
179184
}
180185
}
181186

182187
// Request Bluetooth Device
183188
async onRequestBluetoothDeviceButtonClick(e) {
184-
//try {
185-
console.log('Requesting any Bluetooth device...');
186-
this.debugLog("Requesting device. Cancel if empty and try existing");
187-
let device = await this.requestDevice();
189+
console.log('Requesting any Bluetooth device...');
190+
this.debugLog("Requesting device. Cancel if empty and try existing");
191+
let device = await this.requestDevice();
188192

189-
console.log('> Requested ' + device.name);
190-
await this.connectToBluetoothDevice(device);
191-
/*}
192-
catch (error) {
193-
console.error(error);
194-
await this._showMessage(error);
195-
this.debugLog('No device selected. Try to connect to existing.');
196-
}*/
193+
console.log('> Requested ' + device.name);
194+
await this.connectToBluetoothDevice(device);
197195
}
198196

199197
async switchToDevice(device) {
@@ -279,6 +277,16 @@ class BLEWorkflow extends Workflow {
279277
async showInfo(documentState) {
280278
return await this.infoDialog.open(this, documentState);
281279
}
280+
281+
// Analyze an exception and make user friendly suggestions
282+
_suggestBLEConnectActions(error) {
283+
if (error.name == "TypeError" &&
284+
(error.message.includes("getDevices is not a function")
285+
|| error.message.includes("watchAdvertisements is not a function"))) {
286+
return "Bluetooth API not available. Make sure you are loading from a secure context (HTTPS), then go to chrome://flags/#enable-web-bluetooth-new-permissions-backend to enable.";
287+
}
288+
return `Connect via Bluetooth returned error: ${error}`;
289+
}
282290
}
283291

284292
export {BLEWorkflow};

js/workflows/usb.js

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class USBWorkflow extends Workflow {
101101
// the device on the stored port is currently connected by checking if the
102102
// readable and writable properties are null.
103103

104+
// Can throw a Security Error if permissions are not granted
104105
let allDevices = await navigator.serial.getPorts();
105106
let connectedDevices = [];
106107
for (let device of allDevices) {
@@ -113,7 +114,8 @@ class USBWorkflow extends Workflow {
113114

114115
if (connectedDevices.length == 1) {
115116
device = connectedDevices[0];
116-
console.log(await device.getInfo());
117+
deviceInfo = await device.getInfo()
118+
console.log(`Got previously connected device: ${deviceInfo}`);
117119
try {
118120
// Attempt to connect to the saved device. If it's not found, this will fail.
119121
await this._switchToDevice(device);
@@ -122,37 +124,35 @@ class USBWorkflow extends Workflow {
122124
await device.forget();
123125

124126
console.log("Failed to automatically connect to saved device. Prompting user to select a device.");
127+
// If the user doesn't select a port, an exception is thrown
125128
device = await navigator.serial.requestPort();
126-
console.log(device);
127129
}
128130
} else {
129-
console.log('Requesting any serial device...');
130-
try {
131-
device = await navigator.serial.requestPort();
132-
} catch (e) {
133-
console.log(e);
134-
return false;
135-
}
131+
console.log('No previously connected device. Prompting user to select a device.');
132+
// If the user doesn't select a port, an exception is thrown
133+
device = await navigator.serial.requestPort();
136134
}
135+
console.log(`Selected device: ${device}`);
136+
137137

138138
// If we didn't automatically use a saved device
139139
if (!this._serialDevice) {
140140
console.log('> Requested ', device);
141141
await this._switchToDevice(device);
142142
}
143-
console.log(this._serialDevice);
143+
144144
if (this._serialDevice != null) {
145+
console.log(`Current serial device is: ${this._serialDevice}. Proceeding to step 2.`);
145146
this.connectionStep(2);
146147
return true;
147148
}
148-
149+
console.log("Couldn't connect to serial port");
149150
return false;
150151
}
151152

152153
async showConnect(documentState) {
153154
let p = this.connectDialog.open();
154155
let modal = this.connectDialog.getModal();
155-
156156
btnRequestSerialDevice = modal.querySelector('#requestSerialDevice');
157157
btnSelectHostFolder = modal.querySelector('#selectHostFolder');
158158
btnUseHostFolder = modal.querySelector('#useHostFolder');
@@ -166,22 +166,27 @@ class USBWorkflow extends Workflow {
166166

167167
btnRequestSerialDevice.disabled = true;
168168
btnSelectHostFolder.disabled = true;
169+
this.clearConnectStatus();
169170
let serialConnect = async (event) => {
170171
try {
172+
this.clearConnectStatus();
171173
await this.connectToSerial();
172174
} catch (e) {
173-
//console.log(e);
174-
//alert(e.message);
175-
//alert("Unable to connect to device. Make sure it is not already in use.");
176-
// TODO: I think this also occurs if the user cancels the requestPort dialog
175+
console.log('connectToSerial() returned error: ', e);
176+
this.showConnectStatus(this._suggestSerialConnectActions(e));
177177
}
178178
};
179179
btnRequestSerialDevice.removeEventListener('click', serialConnect);
180180
btnRequestSerialDevice.addEventListener('click', serialConnect);
181181

182182
btnSelectHostFolder.removeEventListener('click', this._btnSelectHostFolderCallback)
183183
this._btnSelectHostFolderCallback = async (event) => {
184-
await this._selectHostFolder();
184+
try {
185+
this.clearConnectStatus();
186+
await this._selectHostFolder();
187+
} catch (e) {
188+
this.showConnectStatus(this._suggestFileConnectActions(e));
189+
}
185190
};
186191
btnSelectHostFolder.addEventListener('click', this._btnSelectHostFolderCallback);
187192

@@ -274,9 +279,8 @@ class USBWorkflow extends Workflow {
274279

275280
this._serialDevice = device;
276281
console.log("switch to", this._serialDevice);
277-
await this._serialDevice.open({baudRate: 115200}); // TODO: Will fail if something else is already connected or it isn't found.
278-
279-
// Start the read loop
282+
await this._serialDevice.open({baudRate: 115200}); // Throws if something else is already connected or it isn't found.
283+
console.log("Starting Read Loop");
280284
this._readLoopPromise = this._readSerialLoop().catch(
281285
async function(error) {
282286
await this.onDisconnected();
@@ -373,6 +377,29 @@ print(binascii.hexlify(microcontroller.cpu.uid).decode('ascii').upper())`
373377
console.log("Read Loop Stopped. Closing Serial Port.");
374378
}
375379

380+
// Analyzes the error returned from the WebSerial API and returns human readable feedback.
381+
_suggestSerialConnectActions(error) {
382+
if (error.name == "NetworkError" && error.message.includes("Failed to open serial port")) {
383+
return "The serial port could not be opened. Make sure the correct port is selected and no other program is using it. For more information, see the JavaScript console.";
384+
} else if (error.name == "NotFoundError" && error.message.includes("No port selected")) {
385+
return "No serial port was selected. Press the 'Connect to Device' button to try again.";
386+
} else if (error.name == "SecurityError") {
387+
return "Permissions to access the serial port were not granted. Please check your browser settings and try again.";
388+
}
389+
return `Connect to Serial Port returned error: ${error}`;
390+
}
391+
392+
// Analyzes the error from the FSAPI and returns human readable feedback
393+
_suggestFileConnectActions(error) {
394+
if (error.name == "SecurityError") {
395+
return "Permissions to access the filesystem were not granted. Please check your browser settings and try again.";
396+
} else if (error.name == "AbortError") {
397+
return "No folder selected. Press the 'Select New Folder' button to try again.";
398+
} else if (error.name == "TypeError")
399+
return `Connect to Filesystem returned error: ${error}`;
400+
401+
}
402+
376403
async showInfo(documentState) {
377404
return await this.infoDialog.open(this, documentState);
378405
}

js/workflows/workflow.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class Workflow {
8686
}
8787

8888
async connect() {
89+
this.clearConnectStatus();
8990
return await this.available();
9091
}
9192

@@ -337,6 +338,27 @@ class Workflow {
337338
}
338339
}
339340
}
341+
342+
clearConnectStatus(modal) {
343+
try {
344+
const modal = this.connectDialog.getModal();
345+
modal.querySelector('.connect-status').hidden = true;
346+
} catch (e) {
347+
console.log("Modal not active on clearStatus()", e);
348+
}
349+
}
350+
351+
showConnectStatus(message) {
352+
try {
353+
const modal = this.connectDialog.getModal();
354+
const statusBox = modal.querySelector('.connect-status');
355+
statusBox.hidden = false;
356+
let statusContentBox = statusBox.querySelector('.connect-status-content');
357+
statusContentBox.innerHTML = message;
358+
} catch (e) {
359+
console.log("Modal not active on showStatus()", e);
360+
}
361+
}
340362
}
341363

342364
export {

sass/layout/_layout.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@
151151
}
152152
}
153153

154+
.connect-status-content {
155+
background-color: #eebbbb;
156+
color: #222222;
157+
border-radius: 10px;
158+
padding: 10px;
159+
margin: 10px;
160+
}
161+
154162
.popup-modal {
155163
#message {
156164
a {

0 commit comments

Comments
 (0)