Skip to content

daikin_madoka: retry SET_SETTING_STATUS when BRC1H silently drops the chunk - #14

Closed
pridmen wants to merge 1 commit into
Petapton:madokafrom
pridmen:madoka-status-retry-guard
Closed

pridmen wants to merge 1 commit into
Petapton:madokafrom
pridmen:madoka-status-retry-guard

Conversation

@pridmen

@pridmen pridmen commented May 16, 2026

Copy link
Copy Markdown

When one ESP32 proxies two BRC1H pairs over BLE, the back-to-back SET_OPERATION_MODE (600 ms) and SET_SETTING_STATUS (200 ms) emitted from control() occasionally have one of the two chunks silently dropped by one of the pulses. In the case this PR addresses, the dropped chunk is SET_SETTING_STATUS: SET_OPERATION_MODE goes through (LCD switches to the requested mode icon), but the on/off state does not — the unit stays physically off. Polling 5–15 s later reports cur_status_.status = 0 and flips climate::mode to OFF in HA, surprising the user who just turned it on. The other paired BRC1H on the same ESP works fine in the same window.

The dropped chunk can be either of the two, not specifically the second one. The symmetric case — SET_OPERATION_MODE itself dropped while SET_SETTING_STATUS lands — produces a different symptom (mode silently reverts to the previous one within ~24 s of a HomeKit/HA mode change). That case is handled by the companion PR #15, which adds the same retry pattern for CMD_GET_OPERATION_MODE. Both retries share STATUS_RETRY_WINDOW_MS / MAX_STATUS_RETRIES.

The existing BLE_SEND_MAX_RETRIES loop in query_() only retries the raw chunk write; if all 5 writes succeed at the link layer but the pulse-side parser silently rejects them, we never know. This PR adds a higher-level status retry:

  • Stash last_set_status_byte_ + last_set_ms_ + status_retry_count_ whenever control() emits a SET_SETTING_STATUS.
  • In parse_cb_, on CMD_GET_SETTING_STATUS readback within STATUS_RETRY_WINDOW_MS (30 s after control()) — if cur_status_.status disagrees with our intent, re-send SET_SETTING_STATUS, up to MAX_STATUS_RETRIES (2).
  • Patch the local cur_status_.status so the downstream switch doesn't flip climate::mode to OFF based on the stale readback.

Independent of #10 (sticky fan_only) / #11 (control cooldown) / #12 (notify gate) / #13 (poll delay). Companion to #15 (symmetric SET_OPERATION_MODE retry).

Caveats

  • If the user toggled the unit on the BRC1H buttons within the same 30 s window, we'd fight back briefly. Acceptable: the user usually notices, the second retry settles, or a fresh control() resets the guard.
  • 30 s window is conservative; in practice one retry resolves the disagreement within the same poll cycle.

Test plan

  • Reproduced the "one of two fan coils stays off" case on two BRC1H over one ESP32; with this patch the second poll fires SET_SETTING_STATUS again and the unit catches up — no manual "press fan_only twice" needed.
  • Single BRC1H setup unaffected (last_set_ms_ stays 0 until first control()).
  • Manual OFF from BRC1H buttons within window: HA shows the unit ON for ~10 s while retries fire, then accepts the off after retries exhaust. Tolerable trade-off vs. the symptom we fix.

@pridmen
pridmen requested a review from Petapton as a code owner May 16, 2026 23:19
@github-actions

Copy link
Copy Markdown

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://Petapton/esphome@pull/14/head
    components: [daikin_madoka]
    refresh: 1h

(Added by the PR bot)

…lently

When one ESP32 proxies two BRC1H pairs over BLE, the back-to-back
SET_OPERATION_MODE (600 ms) and SET_SETTING_STATUS (200 ms) we emit
from control() occasionally have the second chunk silently dropped by
one of the pulses. SET_OPERATION_MODE goes through (the LCD switches
to the requested mode icon), but SET_SETTING_STATUS does not, so the
unit stays physically off. Polling 5–15 s later reports status=0 and
flips climate::mode to OFF in HA, surprising the user who just turned
it on. The other paired BRC1H on the same ESP works fine in the same
window.

The existing BLE_SEND_MAX_RETRIES loop in query_() only retries the
raw chunk write; if all 5 writes succeed at the link layer but the
pulse-side parser silently rejects, we never know. Add a higher-level
status retry: stash the last requested status byte in last_set_status_byte_
on every control() that emits SET_SETTING_STATUS. In parse_cb_, on
CMD_GET_SETTING_STATUS readback within STATUS_RETRY_WINDOW_MS
(30 s after control()) — if cur_status_.status disagrees with our
intent, re-send SET_SETTING_STATUS once more, up to MAX_STATUS_RETRIES
(2). Patch the local cur_status_.status so the downstream switch
doesn't flip climate to OFF based on the stale readback.

Caveats:
- If the user toggled the unit on the BRC1H buttons within the same
  30 s window, we'd fight back briefly. Acceptable: the user usually
  notices and either the second retry settles or a fresh control()
  resets the guard.
- 30 s window is conservative; in practice a single retry resolves
  it within the same poll cycle.
@Petapton

Petapton commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Can't reproduce. Please at least provide logs.

@pridmen

pridmen commented Jun 26, 2026

Copy link
Copy Markdown
Author

Fair — and I can't hand you a fresh log right now, because the setup this reproduced on (two BRC1H on one ESP32) has been dismantled. I split them onto two ESP32s placed near each panel after fighting persistent 0x3e/133 establish failures plus a weak link to the second unit, and on the split setup the silent-chunk-drop symptom no longer occurs — so there's nothing live to capture at the moment.

How it showed up when it did: one ESP, two BRC1H, back-to-back SET_OPERATION_MODE (600 ms) + SET_SETTING_STATUS (200 ms) from control(). Occasionally one of the two chunks was silently dropped at the pulse — the link-layer write succeeded (so BLE_SEND_MAX_RETRIES didn't catch it) but the on/off state didn't change. 5–15 s later the GET_SETTING_STATUS poll returned status=0 and parse_cb_ flipped climate::mode to OFF, surprising whoever had just turned it on.

How to reproduce / catch it on a two-on-one-ESP rig: logger: VERBOSE on daikin_madoka; toggle on/off from HA repeatedly while the second device is also connected and being polled; watch for a CMD_GET_SETTING_STATUS readback that disagrees with the last control() within ~30 s. This PR re-sends SET_SETTING_STATUS (up to MAX_STATUS_RETRIES) on that mismatch and patches the local cur_status_.status so the downstream switch doesn't snap to OFF on the stale read.

I plan to revisit two-on-one-ESP later; when I do, I'll capture the readback-vs-intent mismatch and post it. Keeping it open until then — close if you'd rather.

@Petapton Petapton closed this Jun 26, 2026
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