@@ -25,6 +25,7 @@ class USBWorkflow extends Workflow {
2525 this . _messageCallback = null ;
2626 this . _btnSelectHostFolderCallback = null ;
2727 this . _btnUseHostFolderCallback = null ;
28+ this . _usbDriveDisabled = false ;
2829 this . buttonStates = [
2930 { request : false , select : false } ,
3031 { request : true , select : false } ,
@@ -63,6 +64,12 @@ class USBWorkflow extends Workflow {
6364 }
6465 this . _isDisconnecting = true ;
6566
67+ // Newer CircuitPython firmware lets the editor temporarily make the
68+ // filesystem writable over the REPL by taking the CIRCUITPY USB drive
69+ // offline. Put the drive back before closing the serial connection so
70+ // it is available to the host again after a clean disconnect.
71+ await this . _restoreUsbDrive ( ) ;
72+
6673 // If we got here because the underlying device was lost (NetworkError),
6774 // the writable stream is errored and any further writes will throw
6875 // unhandled NetworkErrors. Force reconnect=false so we don't loop
@@ -321,6 +328,40 @@ class USBWorkflow extends Workflow {
321328 }
322329 }
323330
331+ async _trySerialFileTransfer ( ) {
332+ try {
333+ if ( await this . showBusy ( this . repl . tryDisableUsbDrive ( ) ) ) {
334+ this . _usbDriveDisabled = true ;
335+ console . log ( "CIRCUITPY USB drive disabled; using serial file transfer" ) ;
336+ return true ;
337+ }
338+ } catch ( error ) {
339+ console . warn ( "Unable to disable the CIRCUITPY USB drive:" , error ) ;
340+ }
341+ return false ;
342+ }
343+
344+ async _restoreUsbDrive ( ) {
345+ if ( ! this . _usbDriveDisabled ) {
346+ return ;
347+ }
348+
349+ try {
350+ if ( await this . repl . enableUsbDrive ( ) ) {
351+ console . log ( "CIRCUITPY USB drive restored" ) ;
352+ } else {
353+ console . warn ( "CircuitPython could not restore the CIRCUITPY USB drive" ) ;
354+ }
355+ } catch ( error ) {
356+ // A physical disconnect makes the REPL unavailable before this
357+ // cleanup can run. The next hard reset restores the firmware's
358+ // default USB drive setting.
359+ console . warn ( "Unable to restore the CIRCUITPY USB drive:" , error ) ;
360+ } finally {
361+ this . _usbDriveDisabled = false ;
362+ }
363+ }
364+
324365 // Workflow specific Functions
325366 async _switchToDevice ( device ) {
326367 device . removeEventListener ( "message" , this . _messageCallback ) ;
@@ -366,6 +407,12 @@ class USBWorkflow extends Workflow {
366407 // At this point we should see if we should init the file client and check if have a saved dir handle
367408 let fileops = new FileOps ( this . repl , false ) ;
368409 if ( await this . showBusy ( fileops . isReadOnly ( ) ) ) {
410+ if ( await this . _trySerialFileTransfer ( ) ) {
411+ this . initFileClient ( new ReplFileTransferClient ( this . connectionStatus . bind ( this ) , this . repl ) ) ;
412+ this . onConnected ( ) ;
413+ return ;
414+ }
415+
369416 // UID Only needed for matching the CIRCUITPY drive with the Serial Terminal
370417 await this . showBusy ( this . _getDeviceUid ( ) ) ;
371418 let modal = this . connectDialog . getModal ( ) ;
0 commit comments