Skip to content

Commit c1c3580

Browse files
committed
cli.oad: work around BlueZ disconnect race condition
BlueZ can get in a bad state if the hub disconnects at the same time as the host. Ideally, this should be fixed in BlueZ but to avoid triggering the bug, we can wait for the disconnect.
1 parent 41254b5 commit c1c3580

File tree

3 files changed

+25
-4
lines changed

3 files changed

+25
-4
lines changed

pybricksdev/ble/oad/control_point.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import AsyncGenerator
77

88
from bleak import BleakClient
9+
from bleak.exc import BleakError
910

1011
from ._common import OADReturn, SoftwareVersion, oad_uuid
1112

@@ -50,7 +51,11 @@ async def __aenter__(self):
5051
return self
5152

5253
async def __aexit__(self, *exc_info):
53-
await self._client.stop_notify(OAD_CONTROL_POINT_CHAR_UUID)
54+
try:
55+
await self._client.stop_notify(OAD_CONTROL_POINT_CHAR_UUID)
56+
except BleakError:
57+
# ignore if already disconnected
58+
pass
5459

5560
def _notification_handler(self, sender, data):
5661
self._queue.put_nowait(data)

pybricksdev/ble/oad/image_identify.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import struct
1212

1313
from bleak import BleakClient
14+
from bleak.exc import BleakError
1415

1516
from ._common import ImageInfo, OADReturn, SoftwareVersion, oad_uuid
1617

@@ -35,7 +36,11 @@ async def __aenter__(self):
3536
return self
3637

3738
async def __aexit__(self, *exc_info):
38-
await self._client.stop_notify(OAD_IMAGE_IDENTIFY_CHAR_UUID)
39+
try:
40+
await self._client.stop_notify(OAD_IMAGE_IDENTIFY_CHAR_UUID)
41+
except BleakError:
42+
# ignore if already disconnected
43+
pass
3944

4045
async def validate(
4146
self,

pybricksdev/cli/oad.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,17 @@ async def flash_oad_image(firmware: BinaryIO) -> None:
6262
print("No OAD device found")
6363
return
6464

65+
disconnect_event = asyncio.Event()
66+
67+
def on_disconnect(_):
68+
disconnect_event.set()
69+
6570
# long timeout in case pairing is needed
66-
async with asyncio.timeout(60), BleakClient(device) as client, OADImageIdentify(
71+
async with asyncio.timeout(60), BleakClient(
72+
device, on_disconnect
73+
) as client, OADImageIdentify(client) as image_identify, OADControlPoint(
6774
client
68-
) as image_identify, OADControlPoint(client) as control_point:
75+
) as control_point:
6976
image_block = OADImageBlock(client)
7077

7178
print(f"Connected to {device.name}")
@@ -133,6 +140,10 @@ async def flash_oad_image(firmware: BinaryIO) -> None:
133140
await control_point.enable_oad_image()
134141
print("Done.")
135142

143+
# avoid race condition of requesting disconnect while hub is initiating
144+
# disconnect itself - this can leave BlueZ in a a bad state
145+
await disconnect_event.wait()
146+
136147

137148
async def dump_oad_info():
138149
"""

0 commit comments

Comments
 (0)