Skip to content

daikin_madoka: BLE watchdog + multi-client gattc/gap filters - #16

Closed
pridmen wants to merge 2 commits into
Petapton:madokafrom
pridmen:madoka-ble-watchdog
Closed

pridmen wants to merge 2 commits into
Petapton:madokafrom
pridmen:madoka-ble-watchdog

Conversation

@pridmen

@pridmen pridmen commented May 21, 2026

Copy link
Copy Markdown

Three orthogonal hardening fixes for ESP32 setups that pair more than one BRC1H and need to survive Wi-Fi flaps.

1. BLE watchdog

After a Wi-Fi flap (or a stray bluedroid race) the BLE stack occasionally reports ESTABLISHED but the pulse never replies again — parse_cb_() stops, current_temperature stays NAN, and HA's commands disappear into the void. Only a physical power-cycle of the ESP32 recovers.

Track last_response_ms_; if it exceeds 5 minutes while node_state is ESTABLISHED, issue a per-channel disconnect (the other BRC1H pair is left alone). After 3 consecutive failures, fall back to App.safe_reboot() as a safety net.

  • parse_cb_() refreshes the timestamp on every successful reply.
  • REG_FOR_NOTIFY_EVT refreshes the timestamp on (re)connect so a long disconnect interval does not immediately trip the watchdog.
  • The watchdog only runs once a reply has been seen at all (last_response_ms_ > 0), so a fresh flash with an unpaired pulse does not reboot-loop.

2. Multi-client gattc_if filter

Recent ESPHome versions dispatch the same gattc_event_handler to every BLEClientNode registered against the same esp32_ble interface, regardless of which BLE connection the callback actually belongs to. Without filtering, a DISCONNECT_EVT from the OTHER BRC1H wipes our current_temperature/target_temperature to NAN — observed on dual-pair setups where one pulse stays connected and the other is in a reconnect loop.

Filter at the top of the handler by gattc_if.

3. Multi-client remote_bda filter (GAP variant)

Same broadcast issue affects gap_event_handler too. An AUTH_CMPL_EVT from the OTHER BRC1H reaches our handler with a remote address that doesn't match this->parent_->get_remote_bda(), drives try_register_notifications_() against the wrong remote, and logs noisy No control service found at device, not a Daikin Madoka..? warnings. GAP events don't carry a gattc_if, so filter by remote_bda extracted from param->ble_security.{ble_req,auth_cmpl}.bd_addr.

Test plan

  • Two paired BRC1H pulses on a single ESP32 — watchdog triggered correctly after artificial Wi-Fi flap, per-channel disconnect cleanly restored BLE link.
  • gattc_if filter — DISCONNECT events from one BRC1H no longer wipe current_temperature of the other one (reproducible regression on ESPHome 2026.5).
  • remote_bda GAP filter — eliminates No control service found log noise during reconnect of the second pair.
  • Standalone single-pulse setup — no behaviour change (watchdog never trips while polling returns within 5 min).

🤖 Generated with Claude Code

Two orthogonal hardening fixes for ESP32 setups that pair more than
one BRC1H and need to survive Wi-Fi flaps.

1. BLE watchdog. After a Wi-Fi flap the BLE stack occasionally reports
   ESTABLISHED but the pulse never replies again — parse_cb_() stops,
   current_temperature stays NAN, and HA's commands disappear into the
   void. Only a physical power-cycle of the ESP32 recovers.

   Track last_response_ms_; if it exceeds 5 minutes while node_state
   is ESTABLISHED, issue a per-channel disconnect (the other BRC1H
   pair is left alone). After 3 consecutive failures, fall back to
   App.safe_reboot() as a safety net.

   - parse_cb_() refreshes the timestamp on every successful reply.
   - REG_FOR_NOTIFY_EVT refreshes the timestamp on (re)connect so a
     long disconnect interval does not immediately trip the watchdog.
   - The watchdog only runs once a reply has been seen at all
     (last_response_ms_ > 0), so a fresh flash with an unpaired pulse
     does not reboot-loop.

2. Multi-client gattc_if filter. Recent ESPHome versions dispatch the
   same gattc_event_handler to every BLEClientNode registered against
   the same esp32_ble interface, regardless of which BLE connection
   the callback actually belongs to. Without filtering, a
   DISCONNECT_EVT from the OTHER BRC1H wipes our
   current_temperature/target_temperature to NAN. Filter at the top
   of the handler by gattc_if.
@pridmen
pridmen requested a review from Petapton as a code owner May 21, 2026 10:44
@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/16/head
    components: [daikin_madoka]
    refresh: 1h

(Added by the PR bot)

The same broadcast issue that affected gattc_event_handler also affects
gap_event_handler — GAP security events are delivered to every
BLEClientNode regardless of ownership. With two BRC1H paired to one ESP,
an AUTH_CMPL_EVT from the OTHER pulse calls our handler with a remote
address that doesn't match this->parent_->get_remote_bda(), drives
try_register_notifications_() against the wrong remote, and logs a noisy
'No control service found at device, not a Daikin Madoka..?' warning.

GAP events don't carry a gattc_if so filter by remote_bda extracted from
whichever sub-struct of ble_security the specific event uses
(ble_req.bd_addr or auth_cmpl.bd_addr).
@pridmen pridmen changed the title daikin_madoka: BLE watchdog + multi-client gattc filter daikin_madoka: BLE watchdog + multi-client gattc/gap filters May 21, 2026
@Petapton

Copy link
Copy Markdown
Owner

Can't reproduce. Please at least provide logs.

@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