22# Copyright (c) 2024 The Pybricks Authors
33
44import asyncio
5- import struct
65from enum import IntEnum
6+ from typing import AsyncGenerator
77
88from bleak import BleakClient
99
10- from ._common import oad_uuid
10+ from ._common import OADReturn , SoftwareVersion , oad_uuid
1111
1212__all__ = ["OADControlPoint" ]
1313
@@ -31,45 +31,11 @@ class CmdId(IntEnum):
3131 ERASE_ALL_BONDS = 0x13
3232
3333
34- class OADReturn (IntEnum ):
35- SUCCESS = 0
36- """OAD succeeded"""
37- CRC_ERR = 1
38- """The downloaded image’s CRC doesn’t match the one expected from the metadata"""
39- FLASH_ERR = 2
40- """Flash function failure such as flashOpen/flashRead/flash write/flash erase"""
41- BUFFER_OFL = 3
42- """The block number of the received packet doesn’t match the one requested, an overflow has occurred."""
43- ALREADY_STARTED = 4
44- """OAD start command received, while OAD is already is progress"""
45- NOT_STARTED = 5
46- """OAD data block received with OAD start process"""
47- DL_NOT_COMPLETE = 6
48- """OAD enable command received without complete OAD image download"""
49- NO_RESOURCES = 7
50- """Memory allocation fails/ used only for backward compatibility"""
51- IMAGE_TOO_BIG = 8
52- """Image is too big"""
53- INCOMPATIBLE_IMAGE = 9
54- """Stack and flash boundary mismatch, program entry mismatch"""
55- INVALID_FILE = 10
56- """Invalid image ID received"""
57- INCOMPATIBLE_FILE = 11
58- """BIM/image header/firmware version mismatch"""
59- AUTH_FAIL = 12
60- """Start OAD process / Image Identify message/image payload authentication/validation fail"""
61- EXT_NOT_SUPPORTED = 13
62- """Data length extension or OAD control point characteristic not supported"""
63- DL_COMPLETE = 14
64- """OAD image payload download complete"""
65- CCCD_NOT_ENABLED = 15
66- """Internal (target side) error code used to halt the process if a CCCD has not been enabled"""
67- IMG_ID_TIMEOUT = 16
68- """OAD Image ID has been tried too many times and has timed out. Device will disconnect."""
69-
70-
71- def _decode_version (v : int ) -> int :
72- return (v >> 4 ) * 10 + (v & 0x0F )
34+ OAD_LEGO_MARIO_DEVICE_TYPE = 0xFF150409
35+ """Device type for LEGO Mario and friends."""
36+
37+ OAD_LEGO_TECHNIC_MOVE_DEVICE_TYPE = 0xFF160409
38+ """Device type for LEGO Technic Move Hub."""
7339
7440
7541class OADControlPoint :
@@ -91,7 +57,7 @@ def _notification_handler(self, sender, data):
9157
9258 async def _send_command (self , cmd_id : CmdId , payload : bytes = b"" ):
9359 await self ._client .write_gatt_char (
94- OAD_CONTROL_POINT_CHAR_UUID , bytes ([cmd_id ]) + payload
60+ OAD_CONTROL_POINT_CHAR_UUID , bytes ([cmd_id ]) + payload , response = False
9561 )
9662 rsp = await self ._queue .get ()
9763
@@ -129,18 +95,28 @@ async def set_image_count(self, count: int) -> OADReturn:
12995
13096 return OADReturn (rsp [0 ])
13197
132- async def start_oad_process (self ) -> int :
98+ async def start_oad_process (self ) -> AsyncGenerator [ tuple [ OADReturn , int ], None ] :
13399 """
134100 Start the OAD process.
135101
136102 Returns: Block Number
137103 """
138- rsp = await self ._send_command (CmdId .START_OAD_PROCESS )
104+ await self ._client .write_gatt_char (
105+ OAD_CONTROL_POINT_CHAR_UUID ,
106+ bytes ([CmdId .START_OAD_PROCESS ]),
107+ response = False ,
108+ )
139109
140- if len ( rsp ) != 4 :
141- raise RuntimeError ( f"Unexpected response: { rsp . hex ( ':' ) } " )
110+ while True :
111+ rsp = await self . _queue . get ( )
142112
143- return int .from_bytes (rsp , "little" )
113+ if len (rsp ) != 6 or rsp [0 ] != CmdId .IMAGE_BLOCK_WRITE_CHAR :
114+ raise RuntimeError (f"Unexpected response: { rsp .hex (':' )} " )
115+
116+ status = OADReturn (rsp [1 ])
117+ block_num = int .from_bytes (rsp [2 :], "little" )
118+
119+ yield status , block_num
144120
145121 async def enable_oad_image (self ) -> OADReturn :
146122 """
@@ -182,7 +158,7 @@ async def disable_oad_image_block_write(self) -> OADReturn:
182158
183159 return OADReturn (rsp [0 ])
184160
185- async def get_software_version (self ) -> tuple [ tuple [ int , int ], tuple [ int , int ]] :
161+ async def get_software_version (self ) -> SoftwareVersion :
186162 """
187163 Get the software version.
188164
@@ -193,10 +169,7 @@ async def get_software_version(self) -> tuple[tuple[int, int], tuple[int, int]]:
193169 if len (rsp ) != 4 :
194170 raise RuntimeError (f"Unexpected response: { rsp .hex (':' )} " )
195171
196- return (
197- (_decode_version (rsp [0 ]), _decode_version (rsp [1 ])),
198- (_decode_version (rsp [2 ]), _decode_version (rsp [3 ])),
199- )
172+ return SoftwareVersion .from_bytes (rsp )
200173
201174 async def get_oad_image_status (self ) -> OADReturn :
202175 """
@@ -237,21 +210,6 @@ async def get_device_type(self) -> int:
237210
238211 return int .from_bytes (rsp , "little" )
239212
240- async def image_block_write (self , prev_status : int , block_num : int ) -> None :
241- """
242- Write an image block.
243-
244- Args:
245- prev_status: Status of the previous block received
246- block_num: Block number
247- """
248- rsp = await self ._send_command (
249- CmdId .IMAGE_BLOCK_WRITE_CHAR , struct .pack ("<BI" , prev_status , block_num )
250- )
251-
252- if len (rsp ) != 0 :
253- raise RuntimeError (f"Unexpected response: { rsp .hex (':' )} " )
254-
255213 async def erase_all_bonds (self ) -> OADReturn :
256214 """
257215 Erase all bonds.
0 commit comments