Skip to content

Commit 2212605

Browse files
committed
connections.pybricks: add support for Pybricks Profile v1.5.0
Implement support for Pybricks Profile v1.5.0. Also add some missing items from v1.4.0.
1 parent 8362a5b commit 2212605

File tree

3 files changed

+91
-5
lines changed

3 files changed

+91
-5
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.5.0.
11+
912
## [1.1.0] - 2025-05-28
1013

1114
### Added

pybricksdev/ble/pybricks.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ class StatusFlag(IntFlag):
255255
.. availability:: Since Pybricks protocol v1.2.0.
256256
"""
257257

258+
BLE_HOST_CONNECTED = 1 << 9
259+
"""Bluetooth Low Energy host is connected.
260+
261+
.. availability:: Since Pybricks protocol v1.4.0.
262+
"""
263+
258264

259265
class HubCapabilityFlag(IntFlag):
260266
"""
@@ -277,14 +283,36 @@ class HubCapabilityFlag(IntFlag):
277283
.. availability:: Since Pybricks protocol v1.2.0.
278284
"""
279285

280-
USER_PROG_MULTI_FILE_MPY6_1_NATIVE = 2 << 1
286+
USER_PROG_MULTI_FILE_MPY6_1_NATIVE = 1 << 2
281287
"""
282288
Hub supports user programs using a multi-file blob with MicroPython MPY (ABI V6.1) files
283289
including native module support.
284290
285291
.. availability:: Since Pybricks protocol v1.3.0.
286292
"""
287293

294+
BUILTIN_USER_PROGRAM_PORT_VIEW = 1 << 3
295+
"""
296+
Hub has the built-in user program "port view" feature.
297+
298+
.. availability:: Since Pybricks protocol v1.4.0.
299+
"""
300+
301+
BUILTIN_USER_PROGRAM_IMU_CALIBRATION = 1 << 4
302+
"""
303+
Hub has the built-in user program "IMU calibration" feature.
304+
305+
.. availability:: Since Pybricks protocol v1.4.0.
306+
"""
307+
308+
USER_PROG_FORMAT_MULTI_MPY_V6_3_NATIVE = 1 << 5
309+
"""
310+
Hub supports user programs using a multi-file blob with MicroPython MPY (ABI V6.3) files
311+
including native module support.
312+
313+
.. availability:: Since Pybricks protocol v1.5.0.
314+
"""
315+
288316

289317
def unpack_hub_capabilities(data: bytes) -> Tuple[int, HubCapabilityFlag, int]:
290318
"""
@@ -295,10 +323,32 @@ def unpack_hub_capabilities(data: bytes) -> Tuple[int, HubCapabilityFlag, int]:
295323
296324
Returns:
297325
A tuple of the maximum characteristic write size in bytes, the hub capability
298-
flags and the max user program size in bytes.
326+
flags and the max user program size in bytes and the number of slots.
327+
"""
328+
if len(data) == 11:
329+
# Pybricks protocol v1.5
330+
max_char_size, flags, max_user_prog_size, num_of_slots = unpack("<HIIB", data)
331+
else:
332+
# Pybricks protocol v1.2
333+
max_char_size, flags, max_user_prog_size = unpack("<HII", data)
334+
num_of_slots = 0
335+
336+
return max_char_size, HubCapabilityFlag(flags), max_user_prog_size, num_of_slots
337+
338+
339+
def unpack_hub_capabilities_v15(data: bytes) -> Tuple[int, HubCapabilityFlag, int, int]:
340+
"""
341+
Unpacks the value read from the hub capabilities characteristic. (Pybricks protocol v1.5)
342+
343+
Args:
344+
data: The raw characteristic value.
345+
346+
Returns:
347+
A tuple of the maximum characteristic write size in bytes, the hub capability
348+
flags and the max user program size in bytes and the number of slots.
299349
"""
300-
max_char_size, flags, max_user_prog_size = unpack("<HII", data)
301-
return max_char_size, HubCapabilityFlag(flags), max_user_prog_size
350+
max_char_size, flags, max_user_prog_size, num_of_slots = unpack("<HIIB", data)
351+
return max_char_size, HubCapabilityFlag(flags), max_user_prog_size, num_of_slots
302352

303353

304354
# The Pybricks Protocol version also implies certain other services and

pybricksdev/connections/pybricks.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ class PybricksHub:
8787
has not been connected yet or the connected hub has Pybricks profile < v1.2.0.
8888
"""
8989

90+
_running_program: int
91+
"""
92+
The ID number of the currently running user program on the connected hub.
93+
Only valid if a user program is running and the hub supports Pybricks
94+
profile >= v1.4.
95+
"""
96+
97+
_num_of_slots: int
98+
"""
99+
The number of user program slots available on the connected hub. ``0`` if
100+
the hub has not been connected yet or the hub does not support slots.
101+
"""
102+
103+
_selected_slot: int
104+
"""
105+
The currently selected user program slot on the connected hub. Only valid
106+
if ``_num_of_slots`` is greater than ``0``.
107+
"""
108+
90109
def __init__(self):
91110
self.connection_state_observable = BehaviorSubject(ConnectionState.DISCONNECTED)
92111
self.status_observable = BehaviorSubject(StatusFlag(0))
@@ -97,6 +116,9 @@ def __init__(self):
97116
self._mpy_abi_version = 0
98117
self._capability_flags = HubCapabilityFlag(0)
99118
self._max_user_program_size = 0
119+
self._running_program = 0
120+
self._num_of_slots = 0
121+
self._selected_slot = 0
100122

101123
# whether to enable line handler features or not
102124
self._enable_line_handler = False
@@ -229,6 +251,11 @@ def _pybricks_service_handler(self, _: int, data: bytes) -> None:
229251
# decode the payload
230252
(flags,) = struct.unpack_from("<I", data, 1)
231253
self.status_observable.on_next(StatusFlag(flags))
254+
255+
if len(data) >= 6:
256+
self._running_program = data[5]
257+
if len(data) >= 7:
258+
self._selected_slot = data[6]
232259
elif data[0] == Event.WRITE_STDOUT:
233260
payload = data[1:]
234261
self._stdout_subject.on_next(payload)
@@ -452,7 +479,11 @@ async def start_user_program(self) -> None:
452479
"""
453480
await self.write_gatt_char(
454481
PYBRICKS_COMMAND_EVENT_UUID,
455-
struct.pack("<B", Command.START_USER_PROGRAM),
482+
(
483+
struct.pack("<B", Command.START_USER_PROGRAM)
484+
if self._num_of_slots == 0
485+
else struct.pack("<BB", Command.START_USER_PROGRAM, self._selected_slot)
486+
),
456487
response=True,
457488
)
458489

@@ -718,6 +749,7 @@ async def _client_connect(self) -> bool:
718749
self._max_write_size,
719750
self._capability_flags,
720751
self._max_user_program_size,
752+
self._num_of_slots,
721753
) = unpack_hub_capabilities(caps)
722754
else:
723755
# HACK: prior to profile v1.2.0 isn't a proper way to get the
@@ -812,6 +844,7 @@ async def _client_connect(self) -> bool:
812844
_,
813845
self._capability_flags,
814846
self._max_user_program_size,
847+
self._num_of_slots,
815848
) = unpack_hub_capabilities(caps)
816849

817850
ofst += size

0 commit comments

Comments
 (0)