Skip to content

Performance Improvements, Auto-Disconnect+Polling, Bugfix.#52

Open
henfri wants to merge 11 commits into
digaus:mainfrom
henfri:fix_ble_connection_hang
Open

Performance Improvements, Auto-Disconnect+Polling, Bugfix.#52
henfri wants to merge 11 commits into
digaus:mainfrom
henfri:fix_ble_connection_hang

Conversation

@henfri

@henfri henfri commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

This PR contains three categories of improvements:

Key Improvements:

  1. Performance: Direct Connect & GATT Caching: in reference to In reference to Faster connect - no permanent connection #47 :
    Bypasses passive scanning and utilizes 5ms local GATT caching. This drops the connection latency from ~4.5s down to ~1.4s on average, making the lock feel incredibly responsive.
    More details on this below.
  2. Feature: Auto-Disconnect & Polling: Added disconnect_timeout (default 10s) and status_update_interval (default 2h) to the component. The lock will automatically disconnect to save battery and will poll in the background without needing external YAML sntp cron jobs.
  3. Bugfix: MAC-Swapping Resolves an ESP-IDF core panic where the GATT cache retains stale bonding data when dynamically swapping target MAC addresses at runtime. This issue appeared when (by mistake) switching two ESP boards. After this, the boards would not connect to the locks anymore.

Details on Performance improvements.

By optimizing the BLE connection pipeline (bypassing MTU exchange, implementing local GATT caching, initiating direct connection, and introducing an idle-timeout hysteresis with automatic cache recovery), we reduced the total latency from triggering the command in Home Assistant to the physical motor starting from ~3.14 seconds to ~1.97 seconds (~37% faster).


Key Improvements & Features

1,1. MTU Negotiation Bypass

  • Action: Bypasses the MTU exchange request on BLE connection establishment.
  • Benefit: Prevents connection timeouts with the Eqiva lock (which doesn't support MTU negotiation) and saves ~200 ms.

1.2. Class-Level & NVS GATT Handle Caching

  • Action: Stores the write and read characteristic handles locally in the ESP32 RAM (and persists them in NVS flash). On subsequent connection attempts, it bypasses the over-the-air (OTA) service discovery phase completely and starts communicating immediately.
  • Benefit: Saves ~200 ms of radio search time.

1.3. Direct-Connect Scan Bypass

  • Action: Bypasses ESPHome's active BLE scanner wait time by immediately triggering the connect() sequence to the target MAC address.
  • Benefit: Reduces idle connection establishment time by ~216 ms.

1.4. 10-Second Idle Timeout Hysteresis (Hold Connection)

  • Action: Instead of disconnecting immediately, the BLE connection is held open for 10 seconds after the last activity.
  • Benefit: If multiple commands are triggered in sequence (e.g., Unlock followed immediately by Open), the subsequent command is executed instantly (in milliseconds). If no activity occurs for 10 seconds, the connection is closed to preserve the lock's battery (which would otherwise drain within a month).

1.5. Automatic GATT-Cache Recovery Fallback

  • Action: If a cached connection attempt fails before the cryptographic handshake (Case 0x03 nonce exchange) completes, the component automatically invalidates and clears the cached handles.
  • Benefit: Prevents deadlock reconnection loops if the lock is reset or firmware handles change. The next connection attempt will automatically fall back to a fresh OTA service discovery to rebuild the cache, requiring no manual re-flashes or reboots.

Performance Timings Comparison

These Tests are conducted with the help of the newly implmemented run_test function.

Below is a chronological breakdown of the latency optimizations compared to the benchmarks:

Phase / Metric 1. ESPHome Stock (Vanilla) [1] 2. Standalone NimBLE (Ideal-Benchmark) 3. Python keyble (BlueZ Benchmark) [2] 4. Step 1: MTU Bypass & Auto-Disconnect 5. Step 2: Class GATT Caching 6. Step 3: Direct Connect (Final Optimization) 7. Step 4: disconnect_wifi (Optional)
Physical Connect ~2735 ms ~1191 ms ~2000 ms ~1834 ms ~1820 ms ~1701 ms ~1825 ms
Handshake / Service Search ~104 ms ~45 ms ~300 ms ~203 ms (OTA) ~7 ms (Cached Handles) ~10 ms (Cached Handles) ~307 ms (Nonce exchanged)
Motor Start (from Connect) ~404 ms ~196 ms ~500 ms ~484 ms ~371 ms ~264 ms (Instant send) ~256 ms (Befehl gesendet)
Total Time to Motor Start ~3138 ms ~1387 ms ~2500 ms ~2318 ms ~2191 ms ~1975 ms (Under 2s!) ~2388 ms (Max stability)
BLE Stack Bluedroid NimBLE (Optimized) BlueZ (Linux) Bluedroid Bluedroid Bluedroid Bluedroid
Wi-Fi / Network Activity Active (HA API, Web) Disabled (No Wi-Fi overhead) Active (Host Wi-Fi) Active (HA API, Web) Active (HA API, Web) Active (HA API, Web) Disabled (during connect)
Scanning Duty Cycle 50% (Bluedroid default) 100% (NimBLE default) Managed by BlueZ 50% (Bluedroid default) 50% (Bluedroid default) 100% (Direct Connect) 100% (Wi-Fi stopped)
Task Jitter & Concurrency High (mDNS, API, Web) Minimal (Pure BLE firmware) Medium (Linux OS scheduler) High (mDNS, API, Web) High (mDNS, API, Web) High (mDNS, API, Web) Minimal (Wi-Fi tasks paused)
Primary Benefit / Action Baseline Shows absolute hardware-level limits. Python CLI on Raspberry Pi / PC Prevents MTU-Timeout; closes connection to save battery. Bypasses ~200 ms OTA service discovery. Bypasses active scan window delay (~216 ms). Eliminates RF coexistence delay completely

Footnotes:

  • [1] ESPHome Stock Values: Obtained as average values from the original latency logs. The physical connection duration fluctuates depending on signal strength and the advertising interval of the lock.
  • [2] Python keyble Values: Estimated based on syslog timestamps (seconds resolution) of a typical run: Command received at 12:51:14, BLE connected and command sent at 12:51:16, status updated to UNLOCKED and disconnected at 12:51:20. Total time to motor start is estimated at ~2.5s.

Configuration Setup

ESPHome SDK Configuration

To enable persistent GATT caching across ESP32 reboots, ensure your sdkconfig_options inside the YAML contains the following:

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_BOOTLOADER_WDT_TIME_MS: "60000"
      CONFIG_BT_GATTC_CACHE_NVS_FLASH: "y" # <-- Enables flash caching of handles

The attached file includes the Benchmarks.

walkthrough.md

@henfri henfri changed the title Fix ble connection hang Performance Improvements, Auto-Disconnect+Polling, Bugfix. Jun 5, 2026
@dlbogdan

dlbogdan commented Jul 2, 2026

Copy link
Copy Markdown

thank you for this work.

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