Skip to main content

ESPHome BLE proxy watchdog and self-recovery

In this article, we are going to talk about a failure mode I hit on an ESP32 BLE proxy used with Home Assistant.

The issue was tricky because the device still looked online:

  • WiFi was normal
  • ESPHome API was reachable
  • but BLE scanning silently stopped

So from Home Assistant it looked half healthy, but in reality the proxy had stopped doing the job it was supposed to do.

Problem symptoms​

I found one of my BLE proxies had these symptoms:

  • scanning: false
  • discovered_devices: 0
  • last_detection had been stale for more than 11 hours

Another BLE proxy in the same home was still working normally, which helped confirm this was not a general Home Assistant problem.

Environment​

My setup at the time was:

  • Home Assistant 2026.3.0
  • Home Assistant OS 17.1
  • ESPHome 2026.2.4
  • ESP32 framework: esp-idf

The affected devices were ESP32 Bluetooth proxies managed through ESPHome.

If you are starting from scratch with ESPHome Bluetooth proxy setup, check this first:

First confirmation​

The first useful check was Home Assistant Bluetooth diagnostics.

The affected proxy showed:

scanning=False
devices=0

while another proxy and the built-in Bluetooth scanner on the Home Assistant host were still healthy.

That told me:

  • Home Assistant itself was still processing Bluetooth data
  • WiFi connectivity was not the main problem
  • the ESP32 BLE radio or BLE stack was likely stuck

What I tried​

I tried a few recovery paths first:

MethodResult
reload Home Assistant config entryonly reloads HA side, ESP32 not really recovered
disable/enable integrationlooked better briefly, but BLE devices still stayed at 0
direct restart attempt through APInot available at that moment
OTA port retrytimed out
compile + OTA through ESPHome dashboard websocketworked

The successful recovery path was doing a real ESPHome compile and OTA upload.

That brought the proxy back, but the more important conclusion was:

I should not rely on manual recovery for this kind of failure.

Better fix: add a watchdog in ESPHome itself​

The real fix was to let the ESP32 detect the problem and reboot itself.

The idea is simple:

  • whenever BLE advertisements are seen, update a timestamp
  • every 2 minutes, check how long it has been since the last BLE advertisement
  • if it has been too long, reboot the device

ESPHome config​

1. Track the last BLE activity time​

bluetooth-proxy-livingroom.yaml
globals:
- id: last_ble_adv_time
type: uint32_t
restore_value: no
initial_value: "0"

esp32_ble_tracker:
scan_parameters:
active: false
window: 15ms
interval: 320ms
on_ble_advertise:
- lambda: |-
id(last_ble_adv_time) = millis();

Every BLE advertisement refreshes last_ble_adv_time.

2. Add the watchdog check​

bluetooth-proxy-livingroom.yaml
interval:
- interval: 2min
then:
- lambda: |-
uint32_t now_ms = millis();
uint32_t last = id(last_ble_adv_time);

if (now_ms < 180000) return;

if (last == 0) {
ESP_LOGW("ble_watchdog", "No BLE adv since boot, restarting");
App.safe_reboot();
return;
}

uint32_t elapsed = now_ms - last;
if (elapsed > 600000) {
ESP_LOGW("ble_watchdog", "No BLE adv for %u ms, restarting", elapsed);
App.safe_reboot();
}

Why these numbers?​

  • 180000 ms = do not check during the first 3 minutes after boot
  • 600000 ms = if there is no BLE advertisement for 10 minutes, reboot

This keeps the rule simple and practical.

Add manual recovery helpers too​

Besides the watchdog, I also added two small helpers.

Restart button​

bluetooth-proxy-livingroom.yaml
button:
- platform: restart
name: "Restart"

This creates entities such as:

  • button.bluetooth_proxy_livingroom_restart
  • button.bluetooth_proxy_corridor_restart

Uptime sensor​

bluetooth-proxy-livingroom.yaml
sensor:
- platform: uptime
name: "Uptime"
update_interval: 60s

This makes it easier to tell whether the proxy recently rebooted.

Why I put the watchdog in firmware instead of Home Assistant automation​

This is the most important design decision in this article.

At first, it is tempting to think:

  • Home Assistant can monitor diagnostics
  • if the scanner stops, Home Assistant can trigger a restart

But for this kind of issue, that approach is weaker.

Problems with a Home Assistant automation approach​

  • it depends on Home Assistant being healthy
  • it depends on network calls and diagnostics parsing
  • it adds extra sensors or REST polling logic
  • detection is slower and the logic is spread across multiple places

Benefits of a firmware watchdog​

  • the device is self-healing
  • no Home Assistant dependency is required for the recovery decision
  • no network round trip is needed
  • the logic stays next to the BLE config itself

So the principle here is:

if a device can detect and fix its own health problem, prefer doing it on the device.

Result after OTA update​

After applying the updated ESPHome config and uploading it to both proxies:

  • both BLE proxies returned to normal scanning
  • restart button entities became available
  • uptime sensors became available

The system is now much easier to operate because:

  • there is a manual restart option
  • transient BLE lockups should recover automatically

When this pattern is useful​

This same pattern is useful for other ESPHome devices too, not only BLE proxies.

For example:

  • a sensor that gets stuck and stops reporting
  • a radio stack that silently hangs
  • a device that stays online but stops doing useful work