Skip to content

Fix Battery Guard BM6 empty realtime replies - #412

Merged
fl4p merged 1 commit into
fl4p:masterfrom
travel-and-cache:fix/bm6-battery-guard-fallback
Sep 16, 2026
Merged

fl4p merged 1 commit into
fl4p:masterfrom
travel-and-cache:fix/bm6-battery-guard-fallback

Conversation

@travel-and-cache

@travel-and-cache travel-and-cache commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • recognize and ignore the exact empty BM6 realtime sentinel emitted by Battery Guard devices
  • retry affected devices with write-before-notify ordering
  • preserve the upstream notify-before-write behavior for all other BM6/BM2 variants

Why

Battery Guard devices can answer the normal notify-before-write flow with an empty d15507ff... realtime frame and subsequently expose 0 V / 0% samples. The fallback is activated only after that exact sentinel because BM6 is a shared decoder used by other device variants.

Tests

  • sentinel rejection and retry behavior covered by unit tests
  • normal and unknown non-empty status variants retain the upstream request order
  • verified live by the reporter on two Battery Guard devices

Fixes #410

@fl4p
fl4p merged commit f7239dd into fl4p:master Sep 16, 2026
5 checks passed
fl4p added a commit that referenced this pull request Sep 16, 2026
The fallback ordering from #412 was latched for good by any sentinel
frame and never retried the default one:

* _empty_realtime was set from any notification, including one arriving
  outside a pending request — connect() subscribes before the first
  write, so a single stray sentinel pinned a working BM6 onto the
  untested path until restart. It is now cleared at the start of every
  realtime request and on connect.
* If the fallback ordering also stops answering, every poll re-entered
  it and timed out. A timeout there now clears the preference, so the
  next poll uses the default ordering again.
* _q_write_before_notify unsubscribed, wrote, then re-subscribed with no
  try/finally, so a raising write left FFF4 unsubscribed for the rest of
  the connection.
* On the sentinel, _q cancelled the response waiter immediately and
  discarded a real frame arriving a few tens of ms later; it now keeps
  waiting for EMPTY_REALTIME_GRACE before falling back.
@fl4p

fl4p commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Merged, with a follow-up in a999a5b that makes the fallback recoverable. The sentinel detection and the ordering switch are yours, unchanged — what I changed is the state around them:

  • _empty_realtime was set by any notification, including one arriving with no request pending. connect() subscribes before the first write, so one stray sentinel pinned a working BM6 onto the write-before-notify path until a restart. It's now cleared at the start of every realtime request and on connect.
  • The switch was one-way. If the fallback ordering also stops answering, every poll re-entered it and timed out at 8 s forever. A timeout there now clears the preference, so the next poll tries the default ordering again.
  • _q_write_before_notify did stop_notify → write → start_notify with no try/finally; a raising write left FFE4/FFF4 unsubscribed for the rest of the connection.
  • On the sentinel, _q cancelled the response waiter immediately and dropped a real frame arriving tens of ms later. It now waits EMPTY_REALTIME_GRACE (0.5 s) before falling back.

Your two state-machine tests still pass as written; three more cover the stray sentinel, the revert, and the failed write. Ships in 2.21.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bm6 decoder fails on intAct Battery Guard in v2.21

2 participants