Skip to content

Commit

Permalink
pybricks.connections.pybricks: split out download_user_program()
Browse files Browse the repository at this point in the history
This splits out code from the run() method to make is possible to
download a user program without starting it right away.

Fixes: pybricks/support#284
  • Loading branch information
dlech committed Apr 28, 2023
1 parent abd6b66 commit edb4a1e
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 43 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Added `PybricksHub.download_user_program()` method ([support#284]).

[support#284]: https://github.com/pybricks/support/issues/284

## [1.0.0-alpha.45] - 2023-04-21

### Added
Expand All @@ -14,14 +19,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed endline in `PybricksHub.write_line()`.

[support#1038]: https://github.com/orgs/pybricks/discussions/1038
[support#1038]: https://github.com/pybricks/support/issues/1038

## [1.0.0-alpha.44] - 2023-04-20

### Fixed
- Restored `PybricksHub.output` attribute ([support#1037]).

[support#1037]: https://github.com/orgs/pybricks/discussions/1037
[support#1037]: https://github.com/pybricks/support/issues/1037

## [1.0.0-alpha.43] - 2023-04-19

Expand Down
99 changes: 58 additions & 41 deletions pybricksdev/connections/pybricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,63 @@ async def read_line(self) -> str:

return await self.race_disconnect(self._stdout_line_queue.get())

async def download_user_program(self, program: bytes) -> None:
"""
Downloads user program to user RAM on the hub and indicates progress
using tqdm.
This is a somewhat low-level function. It verifies that the size of the
program is not too big but it does not verify that the program data
is valid or can be run on the hub. Also see :meth:`run`.
Requires hub with Pybricks Profile >= v1.2.0.
Args:
program: The raw program data.
Raises:
ValueError: if program is too large to fit on the hub
"""
# the hub tells us the max size of program that is allowed, so we can fail early
if len(program) > self._max_user_program_size:
raise ValueError(
f"program is too big ({len(program)} bytes). Hub has limit of {self._max_user_program_size} bytes."
)

# clear user program meta so hub doesn't try to run invalid program
await self.client.write_gatt_char(
PYBRICKS_COMMAND_EVENT_UUID,
struct.pack("<BI", Command.WRITE_USER_PROGRAM_META, 0),
response=True,
)

# payload is max size minus header size
payload_size = self._max_write_size - 5

# write program data with progress bar
with logging_redirect_tqdm(), tqdm(
total=len(program), unit="B", unit_scale=True
) as pbar:
for i, c in enumerate(chunk(program, payload_size)):
await self.client.write_gatt_char(
PYBRICKS_COMMAND_EVENT_UUID,
struct.pack(
f"<BI{len(c)}s",
Command.COMMAND_WRITE_USER_RAM,
i * payload_size,
c,
),
response=True,
)
pbar.update(len(c))

# set the metadata to notify that writing was successful
await self.client.write_gatt_char(
PYBRICKS_COMMAND_EVENT_UUID,
struct.pack("<BI", Command.WRITE_USER_PROGRAM_META, len(program)),
response=True,
)

async def start_user_program(self) -> None:
"""
Starts the user program that is already in RAM on the hub.
Expand Down Expand Up @@ -491,47 +548,7 @@ async def run(

mpy = await compile_multi_file(py_path, 6)

# the hub also tells us the max size of program that is allowed, so we can fail early
if len(mpy) > self._max_user_program_size:
raise ValueError(
f"Compiled program is too big ({len(mpy)} bytes). Hub has limit of {self._max_user_program_size} bytes."
)

# clear user program meta so hub doesn't try to run invalid program
await self.client.write_gatt_char(
PYBRICKS_COMMAND_EVENT_UUID,
struct.pack("<BI", Command.WRITE_USER_PROGRAM_META, 0),
response=True,
)

# payload is max size minus header size
payload_size = self._max_write_size - 5

# write program data with progress bar
with logging_redirect_tqdm(), tqdm(
total=len(mpy), unit="B", unit_scale=True
) as pbar:
for i, c in enumerate(chunk(mpy, payload_size)):
await self.client.write_gatt_char(
PYBRICKS_COMMAND_EVENT_UUID,
struct.pack(
f"<BI{len(c)}s",
Command.COMMAND_WRITE_USER_RAM,
i * payload_size,
c,
),
response=True,
)
pbar.update(len(c))

# set the metadata to notify that writing was successful
await self.client.write_gatt_char(
PYBRICKS_COMMAND_EVENT_UUID,
struct.pack("<BI", Command.WRITE_USER_PROGRAM_META, len(mpy)),
response=True,
)

# now we can run the program
await self.download_user_program(mpy)
await self.start_user_program()

if wait:
Expand Down

0 comments on commit edb4a1e

Please sign in to comment.