@@ -428,6 +428,63 @@ async def read_line(self) -> str:
428428
429429 return await self .race_disconnect (self ._stdout_line_queue .get ())
430430
431+ async def download_user_program (self , program : bytes ) -> None :
432+ """
433+ Downloads user program to user RAM on the hub and indicates progress
434+ using tqdm.
435+
436+ This is a somewhat low-level function. It verifies that the size of the
437+ program is not too big but it does not verify that the program data
438+ is valid or can be run on the hub. Also see :meth:`run`.
439+
440+ Requires hub with Pybricks Profile >= v1.2.0.
441+
442+ Args:
443+ program: The raw program data.
444+
445+ Raises:
446+ ValueError: if program is too large to fit on the hub
447+ """
448+ # the hub tells us the max size of program that is allowed, so we can fail early
449+ if len (program ) > self ._max_user_program_size :
450+ raise ValueError (
451+ f"program is too big ({ len (program )} bytes). Hub has limit of { self ._max_user_program_size } bytes."
452+ )
453+
454+ # clear user program meta so hub doesn't try to run invalid program
455+ await self .client .write_gatt_char (
456+ PYBRICKS_COMMAND_EVENT_UUID ,
457+ struct .pack ("<BI" , Command .WRITE_USER_PROGRAM_META , 0 ),
458+ response = True ,
459+ )
460+
461+ # payload is max size minus header size
462+ payload_size = self ._max_write_size - 5
463+
464+ # write program data with progress bar
465+ with logging_redirect_tqdm (), tqdm (
466+ total = len (program ), unit = "B" , unit_scale = True
467+ ) as pbar :
468+ for i , c in enumerate (chunk (program , payload_size )):
469+ await self .client .write_gatt_char (
470+ PYBRICKS_COMMAND_EVENT_UUID ,
471+ struct .pack (
472+ f"<BI{ len (c )} s" ,
473+ Command .COMMAND_WRITE_USER_RAM ,
474+ i * payload_size ,
475+ c ,
476+ ),
477+ response = True ,
478+ )
479+ pbar .update (len (c ))
480+
481+ # set the metadata to notify that writing was successful
482+ await self .client .write_gatt_char (
483+ PYBRICKS_COMMAND_EVENT_UUID ,
484+ struct .pack ("<BI" , Command .WRITE_USER_PROGRAM_META , len (program )),
485+ response = True ,
486+ )
487+
431488 async def start_user_program (self ) -> None :
432489 """
433490 Starts the user program that is already in RAM on the hub.
@@ -491,47 +548,7 @@ async def run(
491548
492549 mpy = await compile_multi_file (py_path , 6 )
493550
494- # the hub also tells us the max size of program that is allowed, so we can fail early
495- if len (mpy ) > self ._max_user_program_size :
496- raise ValueError (
497- f"Compiled program is too big ({ len (mpy )} bytes). Hub has limit of { self ._max_user_program_size } bytes."
498- )
499-
500- # clear user program meta so hub doesn't try to run invalid program
501- await self .client .write_gatt_char (
502- PYBRICKS_COMMAND_EVENT_UUID ,
503- struct .pack ("<BI" , Command .WRITE_USER_PROGRAM_META , 0 ),
504- response = True ,
505- )
506-
507- # payload is max size minus header size
508- payload_size = self ._max_write_size - 5
509-
510- # write program data with progress bar
511- with logging_redirect_tqdm (), tqdm (
512- total = len (mpy ), unit = "B" , unit_scale = True
513- ) as pbar :
514- for i , c in enumerate (chunk (mpy , payload_size )):
515- await self .client .write_gatt_char (
516- PYBRICKS_COMMAND_EVENT_UUID ,
517- struct .pack (
518- f"<BI{ len (c )} s" ,
519- Command .COMMAND_WRITE_USER_RAM ,
520- i * payload_size ,
521- c ,
522- ),
523- response = True ,
524- )
525- pbar .update (len (c ))
526-
527- # set the metadata to notify that writing was successful
528- await self .client .write_gatt_char (
529- PYBRICKS_COMMAND_EVENT_UUID ,
530- struct .pack ("<BI" , Command .WRITE_USER_PROGRAM_META , len (mpy )),
531- response = True ,
532- )
533-
534- # now we can run the program
551+ await self .download_user_program (mpy )
535552 await self .start_user_program ()
536553
537554 if wait :
0 commit comments