@@ -381,6 +381,47 @@ async def run(self, py_path, wait=True, print_output=True):
381381 await asyncio .sleep (0.3 )
382382
383383
384+ FILE_PACKET_SIZE = 1024
385+ FILE_TRANSFER_SCRIPT = f"""
386+ import sys
387+ import micropython
388+ import utime
389+
390+ PACKETSIZE = { FILE_PACKET_SIZE }
391+
392+ def receive_file(filename, filesize):
393+
394+ micropython.kbd_intr(-1)
395+
396+ with open(filename, "wb") as f:
397+
398+ # Initialize buffers
399+ done = 0
400+ buf = bytearray(PACKETSIZE)
401+ sys.stdin.buffer.read(1)
402+
403+ while done < filesize:
404+
405+ # Size of last package
406+ if filesize - done < PACKETSIZE:
407+ buf = bytearray(filesize - done)
408+
409+ # Read one packet from standard in.
410+ time_now = utime.ticks_ms()
411+ bytes_read = sys.stdin.buffer.readinto(buf)
412+
413+ # If transmission took a long time, something bad happened.
414+ if utime.ticks_ms() - time_now > 5000:
415+ print("transfer timed out")
416+ return
417+
418+ # Write the data and say we're ready for more.
419+ f.write(buf)
420+ done += bytes_read
421+ print("ACK")
422+ """
423+
424+
384425class REPLHub :
385426 """Run scripts on generic MicroPython boards with a REPL over USB."""
386427
@@ -455,6 +496,12 @@ async def reset_hub(self):
455496 # Clear all buffers
456497 self .reset_buffers ()
457498
499+ # Load file transfer function
500+ await self .exec_paste_mode (FILE_TRANSFER_SCRIPT , print_output = False )
501+ self .reset_buffers ()
502+
503+ print ("Hub is ready." )
504+
458505 async def exec_line (self , line , wait = True ):
459506 """Executes one line on the REPL."""
460507
@@ -467,10 +514,6 @@ async def exec_line(self, line, wait=True):
467514 echo = encoded + b"\r \n "
468515 self .serial .write (echo )
469516
470- # We are done if we don't want to wait for the result.
471- if not wait :
472- return
473-
474517 # Wait until the echo has been read.
475518 while len (self .buffer ) < start_len + len (echo ):
476519 await asyncio .sleep (0.05 )
@@ -480,6 +523,10 @@ async def exec_line(self, line, wait=True):
480523 print (start_len , self .buffer , self .buffer [start_len - 1 :], echo )
481524 raise ValueError ("Failed to execute line: {0}." .format (line ))
482525
526+ # We are done if we don't want to wait for the result.
527+ if not wait :
528+ return
529+
483530 # Wait for MicroPython to execute the command.
484531 while not self .is_idle ():
485532 await asyncio .sleep (0.1 )
@@ -556,3 +603,38 @@ async def run(self, py_path, wait=True, print_output=True):
556603 self .script_dir , _ = os .path .split (py_path )
557604 await self .reset_hub ()
558605 await self .exec_paste_mode (script , wait , print_output )
606+
607+ async def upload_file (self , destination , contents ):
608+ """Uploads a file to the hub."""
609+
610+ # Print upload info.
611+ size = len (contents )
612+ print (f"Uploading { destination } ({ size } bytes)" )
613+ self .reset_buffers ()
614+
615+ # Prepare hub to receive file
616+ await self .exec_line (f"receive_file('{ destination } ', { size } )" , wait = False )
617+
618+ ACK = b"ACK" + self .EOL
619+ progress = 0
620+
621+ # Write file chunk by chunk.
622+ for data in chunk (contents , FILE_PACKET_SIZE ):
623+
624+ # Send a chunk and wait for acknowledgement of receipt
625+ buffer_now = len (self .buffer )
626+ progress += self .serial .write (data )
627+ while len (self .buffer ) < buffer_now + len (ACK ):
628+ await asyncio .sleep (0.01 )
629+ self .parse_input ()
630+
631+ # Raise error if we didn't get acknowledgement
632+ if self .buffer [buffer_now : buffer_now + len (ACK )] != ACK :
633+ print (self .buffer [buffer_now :])
634+ raise ValueError ("Did not get expected response from the hub." )
635+
636+ # Print progress
637+ print (f"Progress: { int (progress / size * 100 )} %" , end = "\r " )
638+
639+ # Get REPL back in normal state
640+ await self .exec_line ("# File transfer complete" )
0 commit comments