Skip to content

Fix bthome: emit measurement object ids in ascending order - #7

Merged
AToom74 merged 1 commit into
mainfrom
fix-object-id-ordering
Aug 7, 2026
Merged

Fix bthome: emit measurement object ids in ascending order#7
AToom74 merged 1 commit into
mainfrom
fix-object-id-ordering

Conversation

@AToom74

@AToom74 AToom74 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Problem

BTHome v2 requires object ids within a single advertisement payload to be in non-decreasing numerical order. build_advertisement_data_() wrote measurements in rotation-traversal order, which violated this. Home Assistant's bthome_ble parser warns on every update:

aq-cariene 8BCB: BTHome device is not sending object ids in numerical order
(from low to high object id). This can cause issues with your BTHome receiver,
payload: 00e212f9020124024209031815

Values still decode correctly today (each object is self-describing, and HA's parser continues normally after logging), but the warning is valid and some stricter receivers can mishandle out-of-order payloads.

Root cause

Two effects combined:

  1. Sensors aren't necessarily declared in object_id order. Even a single unsplit packet could come out unsorted depending on YAML declaration order.
  2. The rotation cursor starts mid-list and wraps ((start + i) % count), placing a high id before a low one whenever measurements are split across packets or a sensor is skipped (NaN / no state).

There was also a latent starvation issue: sensors and binary sensors were selected with independent rotation cursors, so when a payload overflowed one advertisement, binary sensors could be starved indefinitely.

Fix

Separate selection from write order:

  • Rotation still decides which measurements go in each packet (for fair coverage when split).
  • The selected set is sorted by object_id before encoding.
  • Sensors and binary sensors now share a single combined cursor, because their id ranges overlap (e.g. temperature 0x02 vs door 0x1A) and a single cursor also prevents either category from starving the other.

Verification

No ESPHome toolchain was available locally, so the selection/sort/rotation logic was extracted into a standalone C++ harness and checked to be non-decreasing across: every rotation start, forced packet-splitting, overlapping sensor+binary ids, NaN/absent sensors. A negative control reproduced the old out-of-order behavior to confirm the harness would have caught it.

Reported against a real device (aq-cariene, nRF52/xiao_ble) whose logs showed the warning firing repeatedly (52 occurrences over ~4 hours) with payloads like 00e212f9020124024209031815, 00e512f7020125024209031815, etc. — all with humidity (0x02... wait these are decrypted-post-parse ids) out of ascending order.

Related upstream: the same issue and fix exist as dz0ny/esphome-bthome#15 / dz0ny/esphome-bthome#17, which this PR's approach follows.

🤖 Generated with Claude Code

BTHome v2 requires object ids within a single advertisement payload to
be in non-decreasing numerical order. build_advertisement_data_()
wrote measurements in raw rotation-traversal order, which violated
this. Home Assistant's bthome_ble parser warns on every update:

  BTHome device is not sending object ids in numerical order (from
  low to high object id). This can cause issues with your BTHome
  receiver, payload: ...

Values still decoded correctly (each object is self-describing), but
the warning is valid and some stricter receivers can mishandle it.

Root cause was two combined effects:
1. Sensors aren't necessarily declared in object_id order, so even a
   single unsplit packet could come out unsorted.
2. The rotation cursor starts mid-list and wraps
   ((start + i) % count), placing a high id before a low one whenever
   measurements are split across packets or a sensor is skipped
   (NaN / no state).

There was also a latent starvation issue: sensors and binary sensors
were selected with independent rotation cursors, so when a payload
overflowed one advertisement, binary sensors could be starved
indefinitely.

Fix separates selection from write order:
- Rotation still decides which measurements go in each packet (for
  fair coverage when split).
- The selected set is sorted by object_id before encoding.
- Sensors and binary sensors now share a single combined cursor,
  since their id ranges overlap (e.g. temperature 0x02 vs door 0x1A)
  and a single cursor also prevents either category from starving
  the other.

Verified with a standalone harness (no ESPHome toolchain available
locally) that extracts the selection/sort/rotation logic and checks
non-decreasing output across: every rotation start, forced
packet-splitting, overlapping sensor+binary ids, NaN/absent sensors,
and a negative control that reproduces the old out-of-order behavior.
@AToom74
AToom74 merged commit 424a6b7 into main Aug 7, 2026
5 checks passed
@AToom74
AToom74 deleted the fix-object-id-ordering branch August 7, 2026 12:55
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.

2 participants