@@ -559,7 +559,8 @@ async def get(self, remote_path, local_path=None):
559559NUS_TX_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
560560
561561
562- class PybricksHub ():
562+ class PybricksHub :
563+ EOL = b"\r \n " # MicroPython EOL
563564
564565 def __init__ (self ):
565566 self .logger = logging .getLogger ('Pybricks Hub' )
@@ -571,26 +572,33 @@ def __init__(self):
571572 self .logger .addHandler (handler )
572573 self .logger .setLevel (logging .WARNING )
573574
574- self .EOL = b"\r \n "
575575 self .stream_buf = bytearray ()
576576 self .output = []
577577 self .print_output = True
578578
579+ # indicates that the hub is currently connected via BLE
580+ self .connected = False
581+
582+ # stores the next expected checksum or -1 when not expecting a checksum
579583 self .expected_checksum = - 1
584+ # indicates when a valid checksum was received
580585 self .checksum_ready = asyncio .Event ()
581586
587+ # indicates is we are currently downloading a program
588+ self .loading = False
589+ # indicates that the user program is running
582590 self .program_running = False
583- self .program_state_changed = asyncio .Event ()
591+ # used to notify when the user program has ended
592+ self .user_program_stopped = asyncio .Event ()
584593
585594 def line_handler (self , line ):
586595 self .output .append (line )
587596 if self .print_output :
588597 print (line .decode ())
589598
590599 def nus_handler (self , sender , data ):
591-
592- # If no program is running, read checksum bytes.
593- if not self .program_running and self .expected_checksum >= 0 :
600+ # If we are currently downloading a program, treat incoming data as checksum.
601+ if self .loading :
594602 if data [0 ] == self .expected_checksum :
595603 self .checksum_ready .set ()
596604 self .logger .debug ("Correct checksum: {0}" .format (data [0 ]))
@@ -621,20 +629,25 @@ def nus_handler(self, sender, data):
621629 for line in lines :
622630 self .line_handler (line )
623631
624- def pybricks_service_handler (self , sender , data ):
632+ def pybricks_service_handler (self , _ : int , data : bytearray ):
625633 if data [0 ] == 0 :
626634
627635 # Get new state
628636 msg = data [1 ]
629637
630638 # Get new program state
631639 program_running_now = bool (msg & (1 << 6 ))
632- self .logger .info ("Program running: " + str (program_running_now ))
633640
634- # If program state changed, notifiy
635- if self .program_running != program_running_now :
636- self .program_state_changed .set ()
637- self .program_running = program_running_now
641+ # If we are currently downloading a program, we must ignore user
642+ # program running state changes, otherwise the checksum will be
643+ # sent to the terminal instead of being handled by the download
644+ # algorithm
645+ if not self .loading :
646+ if self .program_running != program_running_now :
647+ self .logger .info (f"Program running: { program_running_now } " )
648+ self .program_running = program_running_now
649+ if not program_running_now :
650+ self .user_program_stopped .set ()
638651
639652 def disconnected_handler (self , client : BleakClient ):
640653 self .logger .info ("Disconnected!" )
@@ -649,11 +662,12 @@ async def connect(self, device):
649662 self .connected = True
650663
651664 async def disconnect (self ):
652- await self .client .stop_notify (NUS_TX_UUID )
653- await self .client .stop_notify (PYBRICKS_UUID )
654665 if self .connected :
655666 self .logger .info ("Disconnecting..." )
656667 await self .client .disconnect ()
668+ self .connected = False
669+ else :
670+ self .logger .debug ("already disconnected" )
657671
658672 def get_checksum (self , block ):
659673 checksum = 0
@@ -664,13 +678,11 @@ def get_checksum(self, block):
664678 async def send_block (self , data ):
665679 self .checksum_ready .clear ()
666680 self .expected_checksum = self .get_checksum (data )
667- await self .client .write_gatt_char (NUS_RX_UUID , bytearray (data ), False )
668681 try :
682+ await self .client .write_gatt_char (NUS_RX_UUID , bytearray (data ), False )
669683 await asyncio .wait_for (self .checksum_ready .wait (), timeout = 0.5 )
670- except asyncio .TimeoutError :
671- self .logger .warning ("Error during program download." )
672- return
673- self .expected_checksum = - 1
684+ finally :
685+ self .expected_checksum = - 1
674686
675687 async def run (self , py_path , wait = True , print_output = True ):
676688
@@ -681,31 +693,25 @@ async def run(self, py_path, wait=True, print_output=True):
681693 # Compile the script to mpy format
682694 mpy = await compile_file (py_path )
683695
684- # Get length of file and send it as bytes to hub
685- length = len ( mpy ). to_bytes ( 4 , byteorder = 'little' )
686- await self .send_block ( length )
696+ try :
697+ self . loading = True
698+ self .user_program_stopped . clear ( )
687699
688- # Divide script in chunks of bytes
689- n = 100
690- chunks = [ mpy [ i : i + n ] for i in range ( 0 , len ( mpy ), n )]
700+ # Get length of file and send it as bytes to hub
701+ length = len ( mpy ). to_bytes ( 4 , byteorder = 'little' )
702+ await self . send_block ( length )
691703
692- # Send the data chunk by chunk
693- print ("Downloading {0} bytes in {1} steps." .format (len (mpy ), len (chunks )))
694- for i , chunk in enumerate (chunks ):
695- print ("Progress: {0}%" .format (
696- round ((i + 1 ) / len (chunks ) * 100 ))
697- )
698- await self .send_block (chunk )
704+ # Divide script in chunks of bytes
705+ n = 100
706+ chunks = [mpy [i : i + n ] for i in range (0 , len (mpy ), n )]
699707
700- # Wait for program to start.
701- try :
702- await asyncio . wait_for ( self . program_state_changed . wait (), timeout = 0.5 )
703- self . program_state_changed . clear ( )
704- except asyncio . TimeoutError :
705- self . logger . warning ( "Unable to start program." )
706- return
708+ # Send the data chunk by chunk
709+ print ( f"Downloading { len ( mpy ) } bytes in { len ( chunks ) } steps." )
710+ for i , chunk in enumerate ( chunks ):
711+ print ( f"Progress: { round (( i + 1 ) / len ( chunks ) * 100 ) } %" )
712+ await self . send_block ( chunk )
713+ finally :
714+ self . loading = False
707715
708716 if wait :
709- # Wait for program to stop
710- await self .program_state_changed .wait ()
711- self .program_state_changed .clear ()
717+ await self .user_program_stopped .wait ()
0 commit comments