diff --git a/js/workflows/usb.js b/js/workflows/usb.js index 728c17e..845211b 100644 --- a/js/workflows/usb.js +++ b/js/workflows/usb.js @@ -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}, @@ -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 @@ -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); @@ -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(); diff --git a/package-lock.json b/package-lock.json index fdd68eb..e2d790a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "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", @@ -46,8 +46,7 @@ }, "node_modules/@adafruit/circuitpython-repl-js": { "version": "3.3.0", - "resolved": "git+ssh://git@github.com/adafruit/circuitpython-repl-js.git#90b3f4e0856d9f355309b80457319fcce84524b2", - "integrity": "sha512-/pWFRIAwwwYXbHfNPw0v6YjX09EjC2yEXAZ98RnVhiQIGh6+N6W9hNKRyCPOBYJvIVXftlyLDHzHc2paV+Oefg==", + "resolved": "git+ssh://git@github.com/adafruit/circuitpython-repl-js.git#a36b73c07f28f447a4a54d7d03a85ef9b3c36be7", "license": "MIT" }, "node_modules/@astral-sh/ruff-wasm-web": { diff --git a/package.json b/package.json index cb6a9be..aebe1f3 100644 --- a/package.json +++ b/package.json @@ -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",