Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,23 @@ <h1>Web Bluetooth not available!</h1>
<div class="step-number"></div>
<div class="step-content">
<h1>Request Bluetooth Device</h1>
<p>CircuitPython boards with <a href="https://circuitpython.org/downloads?features=Bluetooth%2FBTLE">nrf chips need
CircuitPython 7.0.0 or newer</a>. 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.</p>
<p>
<p> See the <a href="https://learn.adafruit.com/wirelessly-code-your-bluetooth-device-with-circuitpython/device-setup">online documentation</a>
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
CircuitPython 7.0.0 or newer</a>.
</p>
<p> 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.</p>
<button class="purple-button" id="requestBluetoothDevice">Request Bluetooth Device</button>
</p>
</div>
</section>
<section>
<div class="connect-status" hidden>
<div class="connect-status-content"></div>
</div>
</section>
</div>
</div>
<div class="popup-modal shadow connect-dialog closable" data-popup-modal="web-connect">
Expand Down Expand Up @@ -262,6 +269,14 @@ <h1>Navigate to your Device</h1>
<p>Once your device is connected to your Local Area Network, you can navigate to
<a id="device-link" href="http://circuitpython.local/code/">http://circuitpython.local/code/</a>. This opens
a page on your device that loads this website onto the device and to avoid any cross domain security issues.</p>
<p> 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</p>
</p>
</div>
</section>
<section>
<div class="connect-status" hidden>
<div class="connect-status-content"></div>
</div>
</section>
</div>
Expand Down Expand Up @@ -301,6 +316,11 @@ <h1>Select USB Host Folder</h1>
</p>
</div>
</section>
<section>
<div class="connect-status" hidden>
<div class="connect-status-content"></div>
</div>
</section>
</div>
</div>
<div class="popup-modal shadow closable" data-popup-modal="device-discovery">
Expand Down
42 changes: 25 additions & 17 deletions js/workflows/ble.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -128,7 +130,7 @@ class BLEWorkflow extends Workflow {
}
catch (error) {
console.error(error);
await this._showMessage(error);
this.showConnectStatus(this._suggestBLEConnectActions(error));
}
}
}
Expand All @@ -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);
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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};
67 changes: 47 additions & 20 deletions js/workflows/usb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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');
Expand All @@ -166,22 +166,27 @@ 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);
btnRequestSerialDevice.addEventListener('click', serialConnect);

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);

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
Expand Down
22 changes: 22 additions & 0 deletions js/workflows/workflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class Workflow {
}

async connect() {
this.clearConnectStatus();
return await this.available();
}

Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions sass/layout/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@
}
}

.connect-status-content {
background-color: #eebbbb;
color: #222222;
border-radius: 10px;
padding: 10px;
margin: 10px;
}

.popup-modal {
#message {
a {
Expand Down