CircuitPython boards with nrf chips need
- CircuitPython 7.0.0 or newer. The first time a device is connected to your host,
- you'll need to enable public broadcasting by pressing reset when the faster, blue blink
- is happening on start up. The device will reset and the second, blue blink will be solid
- when done successfully.
The first time a device is connected to your host, you'll need to enable
+ public broadcasting by pressing reset (or bootsel on some devices) when the faster, blue blink
+ is happening on start up. The device will reset and the second, blue blink will be solid
+ when done successfully.
+
+
+
+
+
@@ -262,6 +269,14 @@
Navigate to your Device
Once your device is connected to your Local Area Network, you can navigate to
http://circuitpython.local/code/. This opens
a page on your device that loads this website onto the device and to avoid any cross domain security issues.
+
If your device doesn't support the .local domain or the connection times out, connect with serial to read the
+ IP address assigned to your device, then open http://device-ip-address:80/code/ from another tab in your browser
+
+
+
+
+
+
@@ -301,6 +316,11 @@
Select USB Host Folder
+
+
+
+
+
diff --git a/js/workflows/ble.js b/js/workflows/ble.js
index c92d932..0c2a3fb 100644
--- a/js/workflows/ble.js
+++ b/js/workflows/ble.js
@@ -71,11 +71,13 @@ class BLEWorkflow extends Workflow {
stepOne.classList.add("hidden");
}
try {
+ this.clearConnectStatus();
const devices = await bluetooth.getDevices();
console.log(devices);
this.connectionStep(devices.length > 0 ? 2 : 1);
- } catch (e) {
- console.log("New Permissions backend for Web Bluetooth not enabled. Go to chrome://flags/#enable-web-bluetooth-new-permissions-backend to enable.", e);
+ } catch (error) {
+ console.error(error);
+ this.showConnectStatus(this._suggestBLEConnectActions(error));
}
} else {
modal.querySelectorAll('.step:not(:first-of-type)').forEach((stepItem) => {
@@ -128,7 +130,7 @@ class BLEWorkflow extends Workflow {
}
catch (error) {
console.error(error);
- await this._showMessage(error);
+ this.showConnectStatus(this._suggestBLEConnectActions(error));
}
}
}
@@ -152,7 +154,9 @@ class BLEWorkflow extends Workflow {
try {
this.bleServer = await device.gatt.connect();
} catch (error) {
- await this._showMessage("Failed to connect to device. Try forgetting device from OS bluetooth devices and try again.");
+ console.log(error);
+ // TODO(ericzundel): Add to suggestBLEConnectAction if we can determine the exception type
+ this.showConnectStatus("Failed to connect to device. Try forgetting device from OS bluetooth devices and try again.");
// Disable the reconnect button
this.connectionStep(1);
}
@@ -169,31 +173,25 @@ class BLEWorkflow extends Workflow {
this.debugLog("connecting to " + device.name);
try {
+ this.clearConnectStatus();
console.log('Watching advertisements from "' + device.name + '"...');
console.log('If no advertisements are received, make sure the device is powered on and in range. You can also try resetting the device');
await device.watchAdvertisements({signal: abortController.signal});
}
catch (error) {
console.error(error);
- await this._showMessage(error);
+ this.showConnectStatus(this._suggestBLEConnectActions(error));
}
}
// Request Bluetooth Device
async onRequestBluetoothDeviceButtonClick(e) {
- //try {
- console.log('Requesting any Bluetooth device...');
- this.debugLog("Requesting device. Cancel if empty and try existing");
- let device = await this.requestDevice();
+ console.log('Requesting any Bluetooth device...');
+ this.debugLog("Requesting device. Cancel if empty and try existing");
+ let device = await this.requestDevice();
- console.log('> Requested ' + device.name);
- await this.connectToBluetoothDevice(device);
- /*}
- catch (error) {
- console.error(error);
- await this._showMessage(error);
- this.debugLog('No device selected. Try to connect to existing.');
- }*/
+ console.log('> Requested ' + device.name);
+ await this.connectToBluetoothDevice(device);
}
async switchToDevice(device) {
@@ -279,6 +277,16 @@ class BLEWorkflow extends Workflow {
async showInfo(documentState) {
return await this.infoDialog.open(this, documentState);
}
+
+ // Analyze an exception and make user friendly suggestions
+ _suggestBLEConnectActions(error) {
+ if (error.name == "TypeError" &&
+ (error.message.includes("getDevices is not a function")
+ || error.message.includes("watchAdvertisements is not a function"))) {
+ 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.";
+ }
+ return `Connect via Bluetooth returned error: ${error}`;
+ }
}
export {BLEWorkflow};
diff --git a/js/workflows/usb.js b/js/workflows/usb.js
index 7c65f63..5d5d0de 100644
--- a/js/workflows/usb.js
+++ b/js/workflows/usb.js
@@ -101,6 +101,7 @@ class USBWorkflow extends Workflow {
// the device on the stored port is currently connected by checking if the
// readable and writable properties are null.
+ // Can throw a Security Error if permissions are not granted
let allDevices = await navigator.serial.getPorts();
let connectedDevices = [];
for (let device of allDevices) {
@@ -113,7 +114,8 @@ class USBWorkflow extends Workflow {
if (connectedDevices.length == 1) {
device = connectedDevices[0];
- console.log(await device.getInfo());
+ deviceInfo = await device.getInfo()
+ console.log(`Got previously connected device: ${deviceInfo}`);
try {
// Attempt to connect to the saved device. If it's not found, this will fail.
await this._switchToDevice(device);
@@ -122,37 +124,35 @@ class USBWorkflow extends Workflow {
await device.forget();
console.log("Failed to automatically connect to saved device. Prompting user to select a device.");
+ // If the user doesn't select a port, an exception is thrown
device = await navigator.serial.requestPort();
- console.log(device);
}
} else {
- console.log('Requesting any serial device...');
- try {
- device = await navigator.serial.requestPort();
- } catch (e) {
- console.log(e);
- return false;
- }
+ console.log('No previously connected device. Prompting user to select a device.');
+ // If the user doesn't select a port, an exception is thrown
+ device = await navigator.serial.requestPort();
}
+ console.log(`Selected device: ${device}`);
+
// If we didn't automatically use a saved device
if (!this._serialDevice) {
console.log('> Requested ', device);
await this._switchToDevice(device);
}
- console.log(this._serialDevice);
+
if (this._serialDevice != null) {
+ console.log(`Current serial device is: ${this._serialDevice}. Proceeding to step 2.`);
this.connectionStep(2);
return true;
}
-
+ console.log("Couldn't connect to serial port");
return false;
}
async showConnect(documentState) {
let p = this.connectDialog.open();
let modal = this.connectDialog.getModal();
-
btnRequestSerialDevice = modal.querySelector('#requestSerialDevice');
btnSelectHostFolder = modal.querySelector('#selectHostFolder');
btnUseHostFolder = modal.querySelector('#useHostFolder');
@@ -166,14 +166,14 @@ class USBWorkflow extends Workflow {
btnRequestSerialDevice.disabled = true;
btnSelectHostFolder.disabled = true;
+ this.clearConnectStatus();
let serialConnect = async (event) => {
try {
+ this.clearConnectStatus();
await this.connectToSerial();
} catch (e) {
- //console.log(e);
- //alert(e.message);
- //alert("Unable to connect to device. Make sure it is not already in use.");
- // TODO: I think this also occurs if the user cancels the requestPort dialog
+ console.log('connectToSerial() returned error: ', e);
+ this.showConnectStatus(this._suggestSerialConnectActions(e));
}
};
btnRequestSerialDevice.removeEventListener('click', serialConnect);
@@ -181,7 +181,12 @@ class USBWorkflow extends Workflow {
btnSelectHostFolder.removeEventListener('click', this._btnSelectHostFolderCallback)
this._btnSelectHostFolderCallback = async (event) => {
- await this._selectHostFolder();
+ try {
+ this.clearConnectStatus();
+ await this._selectHostFolder();
+ } catch (e) {
+ this.showConnectStatus(this._suggestFileConnectActions(e));
+ }
};
btnSelectHostFolder.addEventListener('click', this._btnSelectHostFolderCallback);
@@ -274,9 +279,8 @@ class USBWorkflow extends Workflow {
this._serialDevice = device;
console.log("switch to", this._serialDevice);
- await this._serialDevice.open({baudRate: 115200}); // TODO: Will fail if something else is already connected or it isn't found.
-
- // Start the read loop
+ await this._serialDevice.open({baudRate: 115200}); // Throws if something else is already connected or it isn't found.
+ console.log("Starting Read Loop");
this._readLoopPromise = this._readSerialLoop().catch(
async function(error) {
await this.onDisconnected();
@@ -373,6 +377,29 @@ print(binascii.hexlify(microcontroller.cpu.uid).decode('ascii').upper())`
console.log("Read Loop Stopped. Closing Serial Port.");
}
+ // Analyzes the error returned from the WebSerial API and returns human readable feedback.
+ _suggestSerialConnectActions(error) {
+ if (error.name == "NetworkError" && error.message.includes("Failed to open serial port")) {
+ 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.";
+ } else if (error.name == "NotFoundError" && error.message.includes("No port selected")) {
+ return "No serial port was selected. Press the 'Connect to Device' button to try again.";
+ } else if (error.name == "SecurityError") {
+ return "Permissions to access the serial port were not granted. Please check your browser settings and try again.";
+ }
+ return `Connect to Serial Port returned error: ${error}`;
+ }
+
+ // Analyzes the error from the FSAPI and returns human readable feedback
+ _suggestFileConnectActions(error) {
+ if (error.name == "SecurityError") {
+ return "Permissions to access the filesystem were not granted. Please check your browser settings and try again.";
+ } else if (error.name == "AbortError") {
+ return "No folder selected. Press the 'Select New Folder' button to try again.";
+ } else if (error.name == "TypeError")
+ return `Connect to Filesystem returned error: ${error}`;
+
+ }
+
async showInfo(documentState) {
return await this.infoDialog.open(this, documentState);
}
diff --git a/js/workflows/workflow.js b/js/workflows/workflow.js
index 661acb0..cfc04d0 100644
--- a/js/workflows/workflow.js
+++ b/js/workflows/workflow.js
@@ -86,6 +86,7 @@ class Workflow {
}
async connect() {
+ this.clearConnectStatus();
return await this.available();
}
@@ -337,6 +338,27 @@ class Workflow {
}
}
}
+
+ clearConnectStatus(modal) {
+ try {
+ const modal = this.connectDialog.getModal();
+ modal.querySelector('.connect-status').hidden = true;
+ } catch (e) {
+ console.log("Modal not active on clearStatus()", e);
+ }
+ }
+
+ showConnectStatus(message) {
+ try {
+ const modal = this.connectDialog.getModal();
+ const statusBox = modal.querySelector('.connect-status');
+ statusBox.hidden = false;
+ let statusContentBox = statusBox.querySelector('.connect-status-content');
+ statusContentBox.innerHTML = message;
+ } catch (e) {
+ console.log("Modal not active on showStatus()", e);
+ }
+ }
}
export {
diff --git a/sass/layout/_layout.scss b/sass/layout/_layout.scss
index ef987e4..d66b06f 100644
--- a/sass/layout/_layout.scss
+++ b/sass/layout/_layout.scss
@@ -151,6 +151,14 @@
}
}
+.connect-status-content {
+ background-color: #eebbbb;
+ color: #222222;
+ border-radius: 10px;
+ padding: 10px;
+ margin: 10px;
+}
+
.popup-modal {
#message {
a {