@@ -118,6 +118,15 @@ class Device {
118118 subscribe ( ( ) => this . onChanged ( ) ) ;
119119
120120 this . busy . subscribe ( ( val ) => this . log . debugShort ( val ? "busy..." : "idle." ) ) ;
121+ this . busy . subscribe (
122+ ( isBusy ) =>
123+ ! isBusy &&
124+ this . adapter . __proxyMeta . isBusy &&
125+ this . log . error (
126+ "Device is not busy, but the adapter queue is active. Current task:" ,
127+ [ ...this . adapter . __proxyMeta . history ] . pop ( )
128+ )
129+ ) ;
121130
122131 this . readUntil = createReadUntil ( ) ;
123132 this . readUntil ( / \n > > > [ ^ \n ] * $ / , ( matches ) => this . busy . set ( ! matches ) , { callOnFalse : true } ) ;
@@ -187,7 +196,7 @@ class Device {
187196 }
188197 } ) ;
189198 // resetting the device should also reset the waiting calls
190- this . adapter . __proxyMeta . reset ( ) ;
199+ [ ... this . adapter . __proxyMeta . history ] . pop ( ) . promise . finally ( ( ) => this . adapter . __proxyMeta . reset ( ) ) ;
191200 this . log . info ( "send \\x06 (safeboot)" ) ;
192201 this . adapter . sendData ( "\x06" ) ; // safeboot
193202 } ) ;
@@ -231,7 +240,7 @@ class Device {
231240 */
232241 async runScript ( script , options ) {
233242 options = Object . assign ( { } , runScriptDefaults , options ) ;
234- this . log . debug ( `runScript:\n\n${ script } \n\n` ) ;
243+ this . log . debug ( `queued runScript:\n\n${ script } \n\n` ) ;
235244 this . busy . set ( true ) ;
236245 const start = Date . now ( ) ;
237246 const result = await this . adapter . runScript ( script + "\n\r\n\r\n" , options ) ;
@@ -244,6 +253,17 @@ class Device {
244253 return result ;
245254 }
246255
256+ /**
257+ * Run a Python user script on this device
258+ * Running user scripts sets device.action to null. This ensures that they're shown correctly in the GUI.
259+ * @param {string } script
260+ * @param {import("micropython-ctl-cont").RunScriptOptions= } options
261+ */
262+ async runUserScript ( script , options ) {
263+ script = "# user script\n" + script ;
264+ this . runScript ( script , options ) ;
265+ }
266+
247267 /**
248268 * Creates a MicroPythonDevice
249269 */
@@ -252,10 +272,11 @@ class Device {
252272
253273 // We need to wrap the rawAdapter in a blocking proxy to make sure commands
254274 // run in sequence rather in in parallel. See JSDoc comment for more info.
255- const adapter = createBlockingProxy ( rawAdapter , { exceptions : [ "sendData" , "connectSerial" ] } ) ;
275+ const adapter = createBlockingProxy ( rawAdapter , { exceptions : [ "sendData" , "connectSerial" , "getState" ] } ) ;
256276 adapter . __proxyMeta . beforeEachCall ( ( { item } ) => {
257277 this . action . set ( item . field . toString ( ) ) ;
258278 this . busy . set ( true ) ;
279+ if ( item . field . toString ( ) === "runScript" && item . args [ 0 ] . startsWith ( "# user script\n" ) ) this . action . set ( null ) ;
259280 } ) ;
260281
261282 // emit line break to trigger a `>>>`. This triggers the `busyStatusUpdater`
@@ -382,8 +403,8 @@ class Device {
382403 if ( ! this . config . rootPath ) this . config = { ...this . config , rootPath : await this . getRootPath ( ) } ;
383404 await this . updatePymakrConf ( ) ;
384405 this . state . stale = false ;
385- this . busy . set ( false ) ;
386- resolve ( ) ;
406+ // busy could be truish as vscode could be trying to access a file
407+ this . busy . when ( false ) . then ( resolve ) ;
387408 }
388409 } ) ;
389410
@@ -423,20 +444,26 @@ class Device {
423444 }
424445
425446 /**
426- * @param {number } retries
447+ *
448+ * @param {number } safeBootAfterNumRetries attempt to safe boot on each retry after n failed ctrl + c attempts
449+ * @param {number } retries how many times to attempt to send ctrl + c and ctrl + f
450+ * @param {number } retryInterval how long to wait between each retry
451+ * @returns
427452 */
428- stopScript ( retries = 10 , retryInterval = 500 ) {
429- this . log . debug ( "stop script" ) ;
453+ stopScript ( safeBootAfterNumRetries = 2 , retries = 10 , retryInterval = 500 ) {
454+ this . log . debug ( "stop script" , { safeBootAfterNumRetries , retries , retryInterval } ) ;
430455 return new Promise ( ( resolve , reject ) => {
431456 if ( this . busy . get ( ) ) {
432457 let counter = 0 ;
433458 const intervalHandle = setInterval ( ( ) => {
434459 if ( counter >= retries ) {
435460 clearInterval ( intervalHandle ) ;
461+ this . log . debug ( "ctrl + c failed. Attempting soft reboot (ctrl + f)" ) ;
436462 reject ( `timed out after ${ retries } retries in ${ ( retries * retryInterval ) / 1000 } s` ) ;
437463 } else {
438464 counter ++ ;
439- this . adapter . sendData ( "\x03" ) ;
465+ const cmd = counter > safeBootAfterNumRetries ? "\x06\x03" : "\x03" ;
466+ this . adapter . sendData ( cmd ) ;
440467 this . log . log ( `retry stop script (${ counter } )` ) ;
441468 }
442469 } , retryInterval ) ;
@@ -447,9 +474,9 @@ class Device {
447474 clearInterval ( intervalHandle ) ;
448475 }
449476 } ) ;
450- this . adapter . sendData ( "\x03" ) ;
477+ const cmd = safeBootAfterNumRetries == 0 ? "\x06\x03" : "\x03" ;
478+ this . adapter . sendData ( cmd ) ;
451479 } else {
452- this . adapter . sendData ( "\x03" ) ;
453480 resolve ( ) ;
454481 }
455482 } ) ;
@@ -475,10 +502,14 @@ class Device {
475502 * @param {{
476503 * onScanComplete: (files: string[]) => void,
477504 * onUpload: (file: string) => void,
505+ * transform: (id: string, body: string) => string
478506 * }} options
479507 */
480508 async upload ( source , destination , options ) {
481- destination = posix . join ( this . config . rootPath , `/${ destination } ` . replace ( / \/ + / g, "/" ) ) ;
509+ this . log . debug ( "upload" , source , "to" , destination ) ;
510+ destination = posix . join ( this . config . rootPath , destination . replace ( / [ \/ \\ ] + / g, "/" ) ) ;
511+ await this . adapter . mkdirs ( dirname ( destination ) ) ;
512+
482513 const root = source ;
483514 const ignores = [ ...this . pymakr . config . get ( ) . get ( "ignore" ) ] ;
484515 const pymakrConfig = getNearestPymakrConfig ( source ) ;
@@ -491,7 +522,11 @@ class Device {
491522
492523 const _uploadFile = async ( file , destination ) => {
493524 this . log . traceShort ( "uploadFile" , file , "to" , destination ) ;
494- const data = Buffer . from ( readFileSync ( file ) ) ;
525+
526+ const data = options . transform
527+ ? Buffer . from ( options . transform ( file , readFileSync ( file , "utf-8" ) ) )
528+ : Buffer . from ( readFileSync ( file ) ) ;
529+
495530 return this . adapter . putFile ( destination , data , { checkIfSimilarBeforeUpload : true } ) ;
496531 } ;
497532
0 commit comments