Repository navigation
fix(ble): reconnect a dropped peripheral at once, forever, and resume a Maimu train - #202
Merged
Merged
Conversation
… a Maimu train A Maimu worn by a mouse in an RTPP arena dropped its link whenever the animal left the stimulation zone, and each outage lasted 15-35 s: - the reconnect loop waited 5 s before its first attempt, backed off to a minute, and gave up after twelve attempts into ERROR; - every attempt first spent a full scan looking for the address that had just dropped, but a peripheral using BLE privacy re-advertises at a new address after every disconnect, so that scan could never succeed; - on reconnect the Maimu always wrote 'off', so a 1200 s train cut off by a drop never came back; - zone commands issued during the outage blocked the flow behind the reconnect, then replayed in a burst, oldest first. BLEDevice now retries straight away, then after 0.5, 1 and every 2 s, and never gives up by default (a finite MAX_RECONNECT_ATTEMPTS still lands in ERROR). On a reconnect it looks for the advertised name first, when that name has been heard on this host. Attempt logging thins out after the first five so an endless retry does not bury the log. MaimuDevice remembers its last command and its reconnect hook resends it: a train for whatever is left of its duration, 'on' as 'on', and 'off' if nothing was asked or the train has run out. Commands sent while the link is down return immediately and only the newest is sent on reconnect. A train(on_ms, off_ms, duration_s) action replaces the node's raw "p<on>,<off>,<s>" write so the device knows a train is running.
…ink that bounces Unplugging and replugging a DK running the Maimu firmware showed the link dropping again within 0.25 s of a reconnect (a bouncing USB plug). The reconnect hook's resend then went through write()'s reconnect-once-and-retry path while holding the Maimu's command lock: - the flow's next command waited 2.4 s behind that in-band reconnect; - the resend had been worked out before the second drop, so 'p10,40,2' went out 2.5 s late and ran the light past the end of that train; - the device read "disconnected" rather than "reconnecting" meanwhile, because the loop that should have been retrying had already finished. BLEDevice.write() takes retry=False for a caller that owns its recovery: it sends once over the link as it stands and raises ConnectionError without connecting. The Maimu uses it for every command and for the reconnect resend; a command whose write dies with the link is remembered and sent by the reconnect. The reconnect loop now checks the link after its hook and, if it died meanwhile, carries on retrying and runs the hook again, so the resend works out the time left at that later moment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In an RTPP run a Maimu's link dropped whenever the mouse left the stimulation zone, and every outage lasted 15�35 s with the light off. The run log showed four separate problems:
adv.cclearsBT_DEV_RPA_VALIDon each legacy advertising start), so that address was always gone.off, so a 1200 s train cut off by a drop stayed off.Changes
BLEDevice(core):MAX_RECONNECT_ATTEMPTSnow defaults toNone, meaning never give up. A finite value still ends inERROR.MaimuDevice(plugin):onason, andoffif nothing was asked or the train has run out. The log says when a train was resumed.train(on_ms, off_ms, duration_s)action, declared inACTION_ARGS_SCHEMA. The node's duty-cycle path uses it instead of a rawwrite("p�"), so the device knows a train is running.Docs: the CHANGELOG,
docs-site/reference/devices.mdand the plugin README are updated, plus the ERROR wording comment inmain_window.py.Follow-up (3a19351): a link that bounces. On the bench, replugging a DK dropped the link again within 0.25 s of a reconnect, and the resend fell back on write()'s retry-once-inside-the-write path. That held the command lock and stalled the flow for 2.4 s, sent a resend computed before the second drop (2.5 s late, overrunning the train), and showed "disconnected" instead of "reconnecting".
BLEDevice.write()now takesretry=False, which sends once over the link as it stands. The Maimu uses it for every command and for the resend. The reconnect loop checks the link after its hook and keeps retrying if it died meanwhile, then runs the hook again with the time left at that moment.Related firmware (outside this repo)
Firmware 0.1.1 for the units goes after the drops themselves: a 2 s supervision timeout instead of the auto-requested 420 ms, +8 dBm TX instead of 0 dBm, and privacy off for a stable address. Units that haven't been updated get everything above except the stable address.
Test plan
pytest tests/unit/hal plugins/glider-maimu/tests: 599 passed. New tests cover:offon/offrestored as they werepytest tests/unit/core tests/unit/gui/shell/test_main_window_shell.py tests/unit/gui/test_camera_panel_rehearsal.py: everything passes excepttest_flow_clock.py::test_the_timing_thread_wakes_within_a_couple_of_ms. That test also fails on this machine without this change and doesn't touch the changed code (5.4 ms wake against a 2 ms bound).ruff check .andblack --check .clean