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
47 changes: 47 additions & 0 deletions js/workflows/usb.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class USBWorkflow extends Workflow {
this._messageCallback = null;
this._btnSelectHostFolderCallback = null;
this._btnUseHostFolderCallback = null;
this._usbDriveDisabled = false;
this.buttonStates = [
{request: false, select: false},
{request: true, select: false},
Expand Down Expand Up @@ -63,6 +64,12 @@ class USBWorkflow extends Workflow {
}
this._isDisconnecting = true;

// Newer CircuitPython firmware lets the editor temporarily make the
// filesystem writable over the REPL by taking the CIRCUITPY USB drive
// offline. Put the drive back before closing the serial connection so
// it is available to the host again after a clean disconnect.
await this._restoreUsbDrive();

// If we got here because the underlying device was lost (NetworkError),
// the writable stream is errored and any further writes will throw
// unhandled NetworkErrors. Force reconnect=false so we don't loop
Expand Down Expand Up @@ -321,6 +328,40 @@ class USBWorkflow extends Workflow {
}
}

async _trySerialFileTransfer() {
try {
if (await this.showBusy(this.repl.tryDisableUsbDrive())) {
this._usbDriveDisabled = true;
console.log("CIRCUITPY USB drive disabled; using serial file transfer");
return true;
}
} catch (error) {
console.warn("Unable to disable the CIRCUITPY USB drive:", error);
}
return false;
}

async _restoreUsbDrive() {
if (!this._usbDriveDisabled) {
return;
}

try {
if (await this.repl.enableUsbDrive()) {
console.log("CIRCUITPY USB drive restored");
} else {
console.warn("CircuitPython could not restore the CIRCUITPY USB drive");
}
} catch (error) {
// A physical disconnect makes the REPL unavailable before this
// cleanup can run. The next hard reset restores the firmware's
// default USB drive setting.
console.warn("Unable to restore the CIRCUITPY USB drive:", error);
} finally {
this._usbDriveDisabled = false;
}
}

// Workflow specific Functions
async _switchToDevice(device) {
device.removeEventListener("message", this._messageCallback);
Expand Down Expand Up @@ -366,6 +407,12 @@ class USBWorkflow extends Workflow {
// At this point we should see if we should init the file client and check if have a saved dir handle
let fileops = new FileOps(this.repl, false);
if (await this.showBusy(fileops.isReadOnly())) {
if (await this._trySerialFileTransfer()) {
this.initFileClient(new ReplFileTransferClient(this.connectionStatus.bind(this), this.repl));
this.onConnected();
return;
}

// UID Only needed for matching the CIRCUITPY drive with the Serial Terminal
await this.showBusy(this._getDeviceUid());
let modal = this.connectDialog.getModal();
Expand Down
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"dependencies": {
"@adafruit/ble-file-transfer-js": "adafruit/ble-file-transfer-js#1.0.5",
"@adafruit/circuitpython-repl-js": "adafruit/circuitpython-repl-js#3.3.0",
"@adafruit/circuitpython-repl-js": "adafruit/circuitpython-repl-js#3.4.0",
"@astral-sh/ruff-wasm-web": "^0.15.21",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.11",
Expand Down