Skip to content

Commit c719eb4

Browse files
committed
ble.pybricks: add Pybricks Profile v1.2.0 support
This updates the pybricksdev package to allow downloading and running programs via BLE using Pybricks Profile v1.2.0.
1 parent 4db8987 commit c719eb4

File tree

5 files changed

+318
-73
lines changed

5 files changed

+318
-73
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Added
10+
- Added support for Pybricks Profile v1.2.0 (BLE).
11+
912
## [1.0.0-alpha.31] - 2022-09-14
1013

1114
### Added

pybricksdev/ble/pybricks.py

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
After connecting, programs SHOULD read the Software Revision characteristic of
1515
the Device Information Service to determine the Pybricks protocol version. This
1616
version can be used to determine the capabilities of the device.
17+
18+
Additional information about the Pybricks Profile can be found at
19+
_`https://github.com/pybricks/technical-info/blob/master/pybricks-ble-profile.md`.
1720
"""
1821

1922
from enum import IntEnum, IntFlag
@@ -30,12 +33,22 @@ def _pybricks_uuid(short: int) -> str:
3033
PYBRICKS_SERVICE_UUID = _pybricks_uuid(0x0001)
3134
"""The Pybricks GATT service UUID."""
3235

33-
PYBRICKS_CONTROL_UUID = _pybricks_uuid(0x0002)
34-
"""The Pybricks control GATT characteristic UUID.
36+
PYBRICKS_COMMAND_EVENT_UUID = _pybricks_uuid(0x0002)
37+
"""The Pybricks command/event GATT characteristic UUID.
38+
39+
Commands are written to this characteristic and events are received via notifications.
40+
41+
See :class:`Command` and :class:`Event`.
3542
3643
.. availability:: Since Pybricks protocol v1.0.0.
3744
"""
3845

46+
PYBRICKS_HUB_CAPABILITIES_UUID = _pybricks_uuid(0x0003)
47+
"""The Pybricks hub capabilities GATT characteristic UUID.
48+
49+
.. availability:: Since Pybricks protocol v1.2.0.
50+
"""
51+
3952
PYBRICKS_PROTOCOL_VERSION = semver.VersionInfo(1, 1, 0)
4053
"""The minimum required Pybricks protocol version supported by this library."""
4154

@@ -55,6 +68,89 @@ class Command(IntEnum):
5568
.. availability:: Since Pybricks protocol v1.0.0.
5669
"""
5770

71+
START_USER_PROGRAM = 1
72+
"""
73+
Requests that the user program should be started.
74+
75+
Hub responds with :attr:`CommandError.BUSY` if a user program is currently running.
76+
77+
.. availability:: Since Pybricks protocol v1.2.0.
78+
"""
79+
80+
START_REPL = 2
81+
"""
82+
Requests that the interactive REPL should be started.
83+
84+
Hub responds with :attr:`CommandError.INVALID_COMMAND` if the hub does not support the REPL.
85+
Hub responds with :attr:`CommandError.BUSY` if a user program is currently running.
86+
87+
.. availability:: Since Pybricks protocol v1.2.0.
88+
"""
89+
90+
WRITE_USER_PROGRAM_META = 3
91+
"""
92+
Requests to write user program metadata.
93+
94+
Parameters:
95+
size: The size of the user program in bytes (32-bit unsigned integer).
96+
97+
Hub responds with :attr:`CommandError.BUSY` if a user program is currently running.
98+
99+
.. availability:: Since Pybricks protocol v1.2.0.
100+
"""
101+
102+
COMMAND_WRITE_USER_RAM = 4
103+
"""
104+
Requests to write data to user RAM.
105+
106+
Parameters:
107+
offset: The offset from the base user RAM address (32-bit unsigned integer).
108+
payload: The data to write to this address (0 to ``max_char_size`` - 5 bytes).
109+
110+
Hub responds with :attr:`CommandError.VALUE_NOT_ALLOWED` if the offset and size exceeded the user RAM area.
111+
Hub responds with :attr:`CommandError.BUSY` if a user program is currently running.
112+
113+
.. availability:: Since Pybricks protocol v1.2.0.
114+
"""
115+
116+
117+
class CommandError(IntEnum):
118+
"""
119+
GATT Attribute error codes that can be sent in response to command write requests.
120+
"""
121+
122+
# NB: these values are standard BLE protocol values (i.e. Table 3.4 in v5.3 core specification)
123+
124+
INVALID_HANDLE = 0x01
125+
"""
126+
The attribute handle given was not valid on this server.
127+
"""
128+
NOT_SUPPORTED = 0x06
129+
"""
130+
Attribute server does not support the request received from the client.
131+
"""
132+
VALUE_NOT_ALLOWED = 0x13
133+
"""
134+
The attribute parameter value was not allowed.
135+
"""
136+
137+
# NB: these values are limited to 0x80 – 0x9F as required by the Bluetooth
138+
# spec (i.e. Table 3.4 in v5.3 core specification)
139+
140+
INVALID_COMMAND = 0x80
141+
"""
142+
An invalid command was requested.
143+
144+
.. availability:: Since Pybricks protocol v1.2.0.
145+
"""
146+
147+
BUSY = 0x81
148+
"""
149+
The command cannot be completed now because the required resources are busy.
150+
151+
.. availability:: Since Pybricks protocol v1.2.0.
152+
"""
153+
58154

59155
class Event(IntEnum):
60156
"""Event for Pybricks BLE protocol.
@@ -118,11 +214,52 @@ class StatusFlag(IntFlag):
118214
"""
119215

120216
SHUTDOWN = 1 << 7
121-
"""Hub shutdown was requested.
217+
"""Hub has entered shutdown state.
122218
123219
.. availability:: Since Pybricks protocol v1.1.0.
124220
"""
125221

222+
SHUTDOWN_REQUESTED = 1 << 8
223+
"""Hub shutdown was requested.
224+
225+
.. availability:: Since Pybricks protocol v1.2.0.
226+
"""
227+
228+
229+
class HubCapabilityFlag(IntFlag):
230+
"""
231+
Hub capability flags.
232+
"""
233+
234+
HAS_REPL = 1 << 0
235+
"""
236+
Indicates that the hub has an interactive REPL.
237+
238+
.. availability:: Since Pybricks protocol v1.2.0.
239+
"""
240+
241+
USER_PROG_MULTI_FILE_MPY6 = 1 << 1
242+
"""
243+
Hub supports user programs using a multi-file blob with MicroPython MPY (ABI V6) files.
244+
245+
.. availability:: Since Pybricks protocol v1.2.0.
246+
"""
247+
248+
249+
def unpack_hub_capabilities(data: bytes) -> Tuple[int, HubCapabilityFlag, int]:
250+
"""
251+
Unpacks the value read from the hub capabilities characteristic.
252+
253+
Args:
254+
data: The raw characteristic value.
255+
256+
Returns:
257+
A tuple of the maximum characteristic write size in bytes, the hub capability
258+
flags and the max user program size in bytes.
259+
"""
260+
max_char_size, flags, max_user_prog_size = unpack("<HII", data)
261+
return max_char_size, HubCapabilityFlag(flags), max_user_prog_size
262+
126263

127264
# The Pybricks Protocol version also implies certain other services and
128265
# and characteristics are present.

pybricksdev/compile.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@ async def compile_file(path: str, abi: int, compile_args: Optional[List[str]] =
7878
return mpy
7979

8080

81-
async def compile_multi_file(
82-
path: str, abi: int, compile_args: Optional[List[str]] = None
83-
):
81+
async def compile_multi_file(path: str, abi: int):
8482
"""Compiles a Python file and its dependencies with ``mpy-cross``.
8583
8684
On the hub, all dependencies behave as independent modules. Any (leading)
@@ -106,8 +104,6 @@ async def compile_multi_file(
106104
Path to script that is to be compiled.
107105
abi:
108106
Expected MPY ABI version.
109-
compile_args:
110-
Extra arguments for ``mpy-cross``.
111107
112108
Returns:
113109
Concatenation of all compiled files in the format given above.
@@ -160,15 +156,7 @@ def find_dependencies(module_name):
160156
# Subtract the (builtin or missing) modules we won't upload.
161157
dependencies = dependencies.difference(not_found)
162158

163-
print("Uploading:", path)
164-
# If there are no dependencies, it is an old-style single file script.
165-
# For backwards compatibility, upload just the mpy data. Once the new
166-
# firmware is stable, we can remove this special case.
167-
if not dependencies:
168-
return await compile_file(path, abi)
169-
170159
# Get the total tuple of main programs and module
171-
print("Included modules:", dependencies)
172160
modules = [main_module] + sorted(tuple(dependencies))
173161

174162
# Get a data blob with all scripts.

0 commit comments

Comments
 (0)