Skip to content

Commit 192004a

Browse files
committed
cli.lwp3.repl: end program on disconnect
If the device disconnects (e.g. it is powered off), then the prompt is still waiting and will cause a crash on the next input. To work around this, we cancel the task from the disconnect callback and then catch the exception.
1 parent 93e10a4 commit 192004a

File tree

2 files changed

+53
-40
lines changed

2 files changed

+53
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9595
- Wait for some time to allow program output to be received before disconnecting
9696
in the `run` command.
9797
- Fixed spelling of `INPUT` in `pybricksdev.ble.lwp3.messages`.
98+
- Fixed `pybricksdev lwp3 repl` does not exit if device disconnects.
9899

99100
## [1.0.0-alpha.3] - 2021-04-09
100101
### Changed

pybricksdev/cli/lwp3/repl.py

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -104,50 +104,62 @@ def match_lwp3_uuid(dev: BLEDevice, adv: AdvertisementData) -> None:
104104

105105
logger.info("found device")
106106

107-
def handle_disconnect(client: BleakClient):
108-
logger.info("disconnected")
109-
110-
async with BleakClient(device, disconnected_callback=handle_disconnect) as client:
111-
logger.info("connected")
112-
113-
def handle_notify(handle, value):
114-
try:
115-
msg = parse_message(value)
116-
except Exception as ex:
117-
logger.warning("failed to parse message: %s", value, exc_info=ex)
118-
else:
119-
logger.info("received: %s", msg)
107+
repl_task = asyncio.current_task()
120108

121-
await client.start_notify(LWP3_HUB_CHARACTERISTIC_UUID, handle_notify)
122-
123-
# welcome is delayed to allow initial log messages to settle.
124-
async def welcome():
125-
await asyncio.sleep(1)
126-
print("Type message and press ENTER to send. Press CTRL+D to exit.")
109+
def handle_disconnect(client: BleakClient):
110+
repl_task.cancel()
127111

128-
asyncio.ensure_future(welcome())
112+
try:
113+
async with BleakClient(
114+
device, disconnected_callback=handle_disconnect
115+
) as client:
116+
logger.info("connected")
129117

130-
while True:
131-
with patch_stdout():
118+
def handle_notify(handle, value):
119+
try:
120+
msg = parse_message(value)
121+
except Exception as ex:
122+
logger.warning("failed to parse message: %s", value, exc_info=ex)
123+
else:
124+
logger.info("received: %s", msg)
125+
126+
await client.start_notify(LWP3_HUB_CHARACTERISTIC_UUID, handle_notify)
127+
128+
# welcome is delayed to allow initial log messages to settle.
129+
async def welcome():
130+
await asyncio.sleep(1)
131+
print("Type message and press ENTER to send. Press CTRL+D to exit.")
132+
133+
asyncio.ensure_future(welcome())
134+
135+
while True:
136+
with patch_stdout():
137+
try:
138+
result = await session.prompt_async(">>> ")
139+
except KeyboardInterrupt:
140+
# CTRL+C ignores the line
141+
continue
142+
except EOFError:
143+
# CTRL+D exits the program
144+
break
132145
try:
133-
result = await session.prompt_async(">>> ")
134-
except KeyboardInterrupt:
135-
# CTRL+C ignores the line
136-
continue
137-
except EOFError:
138-
# CTRL+D exits the program
139-
break
140-
try:
141-
msg = eval(result, _eval_pool)
142-
if not isinstance(msg, AbstractMessage):
143-
raise ValueError("not a message object")
144-
except Exception:
145-
logger.exception("bad input:")
146-
else:
147-
logger.info("sending: %s", msg)
148-
await client.write_gatt_char(LWP3_HUB_CHARACTERISTIC_UUID, bytes(msg))
149-
150-
logger.info("disconnecting...")
146+
msg = eval(result, _eval_pool)
147+
if not isinstance(msg, AbstractMessage):
148+
raise ValueError("not a message object")
149+
except Exception:
150+
logger.exception("bad input:")
151+
else:
152+
logger.info("sending: %s", msg)
153+
await client.write_gatt_char(
154+
LWP3_HUB_CHARACTERISTIC_UUID, bytes(msg)
155+
)
156+
157+
logger.info("disconnecting...")
158+
except asyncio.CancelledError:
159+
# happens on disconnect
160+
pass
161+
162+
logger.info("disconnected")
151163

152164

153165
def setup_repl_logging() -> None:

0 commit comments

Comments
 (0)