467
467
}
468
468
}
469
469
470
+ /**
471
+ * Packet types mapped to their wire values
472
+ * @typedef {Object } PacketTypes
473
+ * @property {number } RESPONSE - Response to an EVAL packet
474
+ * @property {number } EVAL - Execute and return the result as RESPONSE packet
475
+ * @property {number } EVENT - Parse as JSON and create `E.on('packet', ...)` event
476
+ * @property {number } FILE_SEND - Called before DATA, with {fn:"filename",s:123}
477
+ * @property {number } DATA - Sent after FILE_SEND with blocks of data for the file
478
+ * @property {number } FILE_RECV - Receive a file - returns a series of PT_TYPE_DATA packets, with a final zero length packet to end
479
+ */
480
+ const pkTypes = Object . freeze ( {
481
+ RESPONSE : 0 ,
482
+ EVAL : 0x2000 ,
483
+ EVENT : 0x4000 ,
484
+ FILE_SEND : 0x6000 ,
485
+ DATA : 0x8000 ,
486
+ FILE_RECV : 0xA000
487
+ } )
488
+
489
+ /**
490
+ * Creates a new packet for transfer using the packet protocol
491
+ * @param {number } pkType The packet type being sent, from `PacketTypes`
492
+ * @param {string } data Data to be appended to the end of the packet (max length 8191 bytes)
493
+ * @returns {string }
494
+ */
495
+ function createPacket ( pkType , data ) {
496
+
497
+ // Check the packet type is one of the known types
498
+ if ( ! Object . hasOwn ( pkTypes , pkType ) ) throw new Error ( `'pkType' '${ pkType } ' not one of ${ Object . keys ( pkTypes ) } ` ) ;
499
+
500
+ // Check the data is a string type and length is in bounds
501
+ if ( typeof data !== 'string' ) throw new Error ( "data must be a String" ) ;
502
+ if ( data . length <= 0 || data . length > 0x1FFF ) throw new Error ( 'data length is out of bounds, max 8191 bytes' ) ;
503
+
504
+ // Create packet heading using packet type and data length
505
+ const heading = pkTypes [ pkType ] | data . length
506
+
507
+ return String . fromCharCode (
508
+ 16 , // DLE (Data Link Escape)
509
+ 1 , // SOH (Start of Heading)
510
+ ( heading >> 8 ) & 0xFF , // Upper byte of heading
511
+ heading & 0xFF // Lower byte of heading
512
+ ) + data ; // Data blob
513
+ }
514
+
515
+ /**
516
+ * Take an input buffer and look for the initial control characters and then attempt to parse a
517
+ * complete data packet from the buffer. Any complete packet is sent via `emit("packet")` and then
518
+ * stripped from `buffer` modifiying it.
519
+ * @param {Uint8Array } buffer
520
+ * @returns {Uint8Array }
521
+ */
522
+ function parsePacketsFromBuffer ( buffer ) {
523
+
524
+ // Find DLE
525
+ const dle = buffer . findIndex ( v => v === 0x10 )
526
+ if ( dle < 0 ) return buffer
527
+
528
+ // Check for SOH
529
+ if ( buffer . at ( dle + 1 ) !== 0x1 ) {
530
+ // console.warn("DLE not followed by SOH")
531
+ // TODO: Not stripping out this invalid control will cause a loop
532
+ buffer . set ( [ undefined ] , dle ) // Remove this DLE
533
+ return buffer
534
+ }
535
+
536
+ // Check there's still space for headers
537
+ if ( buffer . at ( dle + 2 ) === undefined || buffer . at ( dle + 3 ) === undefined ) {
538
+ console . warn ( "NO SPACE FOR HEADERS" )
539
+ return buffer
540
+ }
541
+ const upper = buffer . at ( dle + 2 )
542
+ const lower = buffer . at ( dle + 3 )
543
+
544
+ // Parse heading from 2 bytes after control headers
545
+ const heading = new Number ( upper << 8 ) | new Number ( lower )
546
+ const pkLen = heading & 0x1FFF
547
+ const pkTyp = heading & 0xE000
548
+
549
+ // Ignoring heading bytes, check if there's enough bytes in the buffer to satisfy pkLen
550
+ if ( buffer . length < dle + 4 + pkLen ) {
551
+ return buffer
552
+ }
553
+
554
+ // Pick out a packet from the buffer and emit it via the event handler
555
+ const packet = buffer . subarray ( dle , dle + 4 + pkLen )
556
+ console . log ( "Packet recieved... type:" , pkTyp , "length:" , pkLen )
557
+ Espruino . Core . Serial . emit ( 'packet' , pkTyp , packet . subarray ( 4 , packet . length ) )
558
+
559
+ // Fill the buffer region of the packet that was sent with undefined
560
+ buffer . fill ( undefined , 0 , dle + packet . length )
561
+
562
+ // Return the input buffer but with the stripped packet filtered out
563
+ return buffer . filter ( v => v !== undefined )
564
+ }
565
+
566
+ /**
567
+ * Send a packet
568
+ * @param {number } pkType
569
+ * @param {string } data
570
+ * @param {() => void } callback
571
+ */
572
+ function sendPacket ( pkType , data , callback ) {
573
+
574
+ function onAck ( ) {
575
+ // TODO: What do we actually need to do in the event of an ack
576
+ // tidy()
577
+ // callback()
578
+ }
579
+
580
+ function onNack ( err ) {
581
+ tidy ( )
582
+ callback ( err )
583
+ }
584
+
585
+ let allData
586
+ function onPacket ( rxPkType , data ) {
587
+ tidy ( )
588
+ const packetData = String . fromCharCode ( ...data )
589
+
590
+ // TODO: Depending on the rx type and tx type match up packet types, wait for x number of data
591
+ if ( pkTypes [ pkType ] === pkTypes . EVAL && rxPkType === pkTypes . RESPONSE ) {
592
+ callback ( packetData )
593
+
594
+ // If the packet type is data, we need to wait for the 0 length `DATA` packet and then send all of the data joined together
595
+ } else if ( pkTypes [ pkType ] === pkTypes . FILE_RECV && rxPkType === pkTypes . DATA ) {
596
+ if ( data . length === 0 ) {
597
+ callback ( allData )
598
+ console . log ( "zero packet" )
599
+ } else {
600
+ console . log ( "appending data" , String . fromCharCode ( ...data ) )
601
+ allData += String . fromCharCode ( ...data )
602
+ }
603
+ } else {
604
+ callback ( "nodata" )
605
+ }
606
+ }
607
+
608
+ // Tidy up the event listeners from this packet task
609
+ function tidy ( ) {
610
+ Espruino . Core . Serial . removeListener ( "ack" , onAck )
611
+ Espruino . Core . Serial . removeListener ( "nack" , onNack )
612
+ Espruino . Core . Serial . removeListener ( "packet" , onPacket )
613
+ }
614
+
615
+ // Attach event handlers for this packet event
616
+ Espruino . Core . Serial . on ( "ack" , onAck )
617
+ Espruino . Core . Serial . on ( "nack" , onNack )
618
+ Espruino . Core . Serial . on ( "packet" , onPacket )
619
+
620
+ // Write packet to serial port
621
+ Espruino . Core . Serial . write ( createPacket ( pkType , data ) , undefined , function ( ) {
622
+ // TODO: Add 1 sec timeout
623
+
624
+ let dataBuffer = new Uint8Array ( )
625
+
626
+ // Each time data comes in, expand the buffer and add the new data to it
627
+ // TODO: This seems problematic if there are subsequent/concurrent calls
628
+ Espruino . Core . Serial . startListening ( ( data ) => {
629
+ const newBuffer = new Uint8Array ( data )
630
+
631
+ const tempBuffer = new Uint8Array ( dataBuffer . length + newBuffer . length )
632
+ tempBuffer . set ( dataBuffer , 0 )
633
+ tempBuffer . set ( newBuffer , dataBuffer . length )
634
+
635
+ dataBuffer = tempBuffer
636
+
637
+ // Now we've added more data to the buffer, try to parse out any packets
638
+ dataBuffer = parsePacketsFromBuffer ( dataBuffer )
639
+ } )
640
+ } )
641
+ }
642
+
470
643
/**
644
+ * Download a file - storageFile or normal file
471
645
* @param {string } fileName Path to file to download
472
646
* @param {(content?: string) => void } callback Call back with contents of file, or undefined if no content
473
647
*/
@@ -484,6 +658,10 @@ while (d!==undefined) {console.log(btoa(d));d=f.read(${CHUNKSIZE});}
484
658
} , options ) ;
485
659
}
486
660
661
+ function downloadFileV2 ( fileName , fs , callback ) {
662
+ sendPacket ( "FILE_RECV" , JSON . stringify ( { fn : fileName , fs } ) , callback )
663
+ }
664
+
487
665
/**
488
666
* Get the JS needed to upload a file
489
667
* @param {string } fileName Path to file to upload
@@ -1116,8 +1294,10 @@ while (d!==undefined) {console.log(btoa(d));d=f.read(${CHUNKSIZE});}
1116
1294
countBrackets : countBrackets ,
1117
1295
getEspruinoPrompt : getEspruinoPrompt ,
1118
1296
executeExpression : function ( expr , callback ) { executeExpression ( expr , callback , { exprPrintsResult :false } ) ; } ,
1297
+ executeExpressionV2 : function ( expr , callback ) { sendPacket ( "EVAL" , expr , callback ) ; /* TODO: Callback and parseRJSON */ } ,
1119
1298
executeStatement : function ( statement , callback ) { executeExpression ( statement , callback , { exprPrintsResult :true } ) ; } ,
1120
1299
downloadFile : downloadFile , // (fileName, callback)
1300
+ downloadFileV2 : downloadFileV2 ,
1121
1301
getUploadFileCode : getUploadFileCode , //(fileName, contents);
1122
1302
uploadFile : uploadFile , // (fileName, contents, callback)
1123
1303
versionToFloat : versionToFloat ,
@@ -1143,6 +1323,7 @@ while (d!==undefined) {console.log(btoa(d));d=f.read(${CHUNKSIZE});}
1143
1323
asUTF8Bytes : asUTF8Bytes ,
1144
1324
isASCII : isASCII ,
1145
1325
btoa : btoa ,
1146
- atob : atob
1326
+ atob : atob ,
1327
+ createPacket : createPacket
1147
1328
} ;
1148
1329
} ( ) ) ;
0 commit comments