Conversation
jens62
commented
Mar 12, 2026
Owner
- Implement YAML-based issue form
- Include mandatory fields for software version and OS
- Add drag-and-drop log upload section with confirmation checkbox
MQTT: on_disconnect called self.reconnect() which didn't exist, causing a silent AttributeError in the asyncio task. Added reconnect() coroutine calling mqttc.reconnect(), wired via run_coroutine_threadsafe (matching the pattern used by all other MQTT callbacks). Circuit breaker: _polling_loop now tracks _consecutive_poll_failures. After 5 consecutive failures the circuit opens — each subsequent iteration sleeps 60s before the probe attempt instead of the normal poll interval. Logs "Circuit open" once at threshold. On first success after failures, logs "Poll recovered", resets the counter, and resets _identification_fetched so identification is re-fetched in case the device was power-cycled during the outage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
configuration.md: add esphome_api_connection key; replace branch-diff table with startup config-logging note on-demand-ble.md: expand timing fields (ESP32/BLE/cached); add circuit breaker section; fix outdated note about _connect_ms including ident mqtt.md: add centralDevice/timings, error JSON format, esphomeProxy/* topics (connected, error, control/connect|disconnect, config/apiConnection) esphome.md: add esphome_api_connection config; document persistent vs on-demand API connection modes and invalid combination rule webapp.md: document ESP32 proxy panel, updated timing field labels, BLE: Reconnect/Disconnect button labels, — (cached) query display CLAUDE.md: reframe branch summary as merged feature summary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…E scan In persistent ESPHome API mode, disconnect_ble_only() intentionally skips calling _esphome_unsub_adv() to avoid the ESP32 closing the TCP connection. This left the old subscription active on the ESP32, causing it to log "Only one API subscription is allowed at a time" on the next request. Fix: call the stored _esphome_unsub_adv at the START of _connect_via_esphome(), before any BLE connection is active. If the unsubscribe causes TCP to close, _ensure_esphome_api_connected() reconnects it immediately after. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ly() The previous fix called _esphome_unsub_adv() at the start of _connect_via_esphome(), but disconnect_ble_only() was nulling the reference first — so the cleanup block was always skipped (dead code on the happy path). Fix: stop nulling _esphome_unsub_adv in disconnect_ble_only(). The reference is now kept alive so _connect_via_esphome() can actually call it at the start of the next request, before any BLE connection is active. If the unsubscribe causes the ESP32 to close TCP, _ensure_esphome_api_connected() reconnects it. Whether TCP closes is firmware-dependent and observable in logs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AquaCleanBaseClient logs the raw error message but the error code mapping (e.g. E7002) only reaches MQTT/SSE — never the Python log file. Noted as a TODO item to add logger.error with the error code at the mapping point in _on_demand_inner's finally block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After 6 hours of debugging the 'Only one API subscription is allowed at a time' ESP32 firmware error, persistent mode is removed entirely. on-demand TCP per request is proven stable for hours in production. To restore persistent mode: git checkout esphome-persistent-api Removes: esphome_api_connection config option, _ensure_esphome_api_connected(), disconnect_ble_only(), disconnect_esp32_api(), _get_esphome_connector(), set_esphome_api_connection(), _on_mqtt_set_esphome_api_connection(), ESP32 mode toggle and connect/disconnect UI buttons, MQTT topic esphomeProxy/config/apiConnection, REST endpoint /config/esphome-api-connection, close_api param from ESPHomeAPIClient.disconnect(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove esphome_api_connection option, ESP32 API connection modes table, and _ensure_esphome_api_connected() mentions from all documentation. Update known issues sections to reflect on-demand-only architecture. Note esphome-persistent-api git tag for reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The handler was removed in e002e24 but the subscribe call was missed, causing the broker to deliver messages to a topic with no handler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calling unsub_adv() before bluetooth_device_connect() sends UnsubscribeBluetoothLEAdvertisementsRequest, which clears api_connection_ on the ESP32. The ESP32 then disconnects any BLE client in CONNECTING state immediately → "Disconnect before connected, disconnect scheduled" (reason 0x16). Fix: keep the advertisement subscription alive during BLE connect; call unsub_adv() only after await self.client.connect() returns successfully (or after all attempts are exhausted). Probe script esphome/esphome-aioesphomeapi-probe-v3.py confirmed all 4 has_cache × address_type combinations work when the subscription is held across the connect. CLAUDE.md updated with findings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lifetime
unsub_adv() is synchronous — it only queues the UnsubscribeBluetoothLEAdvertisementsRequest
frame in aioesphomeapi's send buffer. The frame is flushed at the next await (e.g. inside
_post_connect()). Calling unsub_adv() at any point while BLE is active causes the ESP32
to disconnect the BLE client; the symptom depends on timing:
- if the frame is flushed during CONNECT phase: "Disconnect before connected" (reason 0x16)
- if the frame is flushed after connected but during notify setup: immediate disconnect
→ BluetoothGATTNotifyResponse timeout after 10s
Fix: store the callable as self._esphome_unsub_adv instead of calling it on success.
Call it in disconnect() only after await self.client.disconnect() has torn down the BLE
link, making it safe to send the unsubscribe request to the ESP32.
CLAUDE.md trap 7 updated to reflect the actual constraint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kali production run 2026-02-21 18:01 confirms the advertisement unsubscribe fix works: all BLE connects succeed, all ESP32 disconnects are reason=0x00 (clean), full data flow with no errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…home_api_connection refs _update_esphome_proxy_state now auto-clears error_hint to "" whenever error_code is set to "E0000". Previously a connection failure (E1002) set the hint and subsequent successful polls never cleared it, so the webapp kept showing "Cannot reach the ESP32 proxy" long after recovery. CLAUDE.md: removed all dead esphome_api_connection references (the persistent TCP feature was removed; only on-demand TCP exists). Cleaned up device_state table, config sections, traps 7–8, feature summary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds opt-in persistent TCP mode for the ESPHome BLE proxy. When esphome_api_connection = persistent, the ESP32 API TCP connection is kept alive between on-demand BLE requests. Only the BLE link to the Geberit is connected/disconnected per request (~150ms TCP handshake saved on every request after the first). Config (config.ini [ESPHOME]): esphome_api_connection = persistent | on-demand (default: on-demand) Implementation: - ESPHomeAPIClient.disconnect(close_api=False): skip TCP teardown - BluetoothLeConnector._ensure_esphome_api_connected(): reuse or reconnect - BluetoothLeConnector.disconnect_ble_only(): BLE down, TCP stays alive - ApiMode._get_esphome_connector(): cached connector for persistent mode - Runtime toggle: POST /config/esphome-api-connection, MQTT esphomeProxy/config/apiConnection - Webui: ESP32: Switch to Persistent / On-Demand button Naming conventions documented in CLAUDE.md and .clauderc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt mode In persistent_api mode the ESP32 TCP connection stays alive after each BLE disconnect. The proxy panel was incorrectly set to connected=False in the finally block, making the webapp show DISCONNECTED between polls even though ESP32: 0 ms proved the TCP was being reused. Fix: only set connected=False in on-demand mode (TCP truly torn down). In persistent mode, keep connected=True so the webapp reflects reality. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… data_received_handlers Each poll in persistent mode was creating a new AquaCleanBaseClient which registers frame_service.process_data on the shared connector's data_received_handlers. After N polls, N handlers were registered, causing N 'receive complete' events per BLE notification and ever-slower queries. Fix: create the client once alongside the connector in _get_esphome_connector() and reuse it across polls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t client caching Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndpoints Previously _on_demand_inner re-raised BLE/ESPHome exceptions, which propagated to FastAPI as unhandled HTTP 500. Now exceptions are stored, cleanup and BLE status update run in finally, then _http_error(503) raises a structured HTTPException. Also adds BLEPeripheralTimeoutError as an explicit case mapped to E0003 (consistent with ServiceMode handling). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nses - configuration.md: add esphome_api_connection option to [ESPHOME] section - rest-api.md: add POST /config/esphome-api-connection, /esphome/connect, /esphome/disconnect; document split timing fields; document HTTP 503 error format - mqtt.md: add esphomeProxy/enabled outbound topic; add esphomeProxy/config/apiConnection inbound topic - esphome.md: replace outdated "fresh TCP per request" paragraph with esphome_api_connection table and runtime toggle info Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When aioesphomeapi drops the TCP connection after its 90-second ping timeout, it sets api._connection = None internally but the cached self._esphome_api reference stays non-None. _ensure_esphome_api_connected() was returning the dead client unconditionally, causing every subsequent poll to fail with E7002 "Not connected to aquaclean-proxy" until the app was restarted. Add a liveness check via getattr(api, '_connection', None): if None, log a warning, clear the cached client, and fall through to open a fresh TCP connection. The normal "Reusing existing ESP32 API connection" fast path is preserved when the connection is still alive. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…meout Documents the root cause (aioesphomeapi sets _connection=None after 90s ping timeout, cached self._esphome_api stays non-None) and the fix (liveness check via getattr before reusing the cached client). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nd patch notes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add pyproject.toml with all dependencies including haggis from private repo - Fix all local imports to be fully qualified (aquaclean_console_app.xxx) across 20+ files in aquaclean_core, bluetooth_le, and top-level modules - Add missing __init__.py to aquaclean_console_app/, aquaclean_core/, and aquaclean_core/Message/ - Add __main__.py entry point enabling python -m aquaclean_console_app - Console script: aquaclean-bridge Install with: pip install git+https://github.com/jens62/geberit-aquaclean.git Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setuptools.backends.legacy:build requires setuptools >= 68. Debian systems with older setuptools fail with BackendUnavailable. setuptools.build_meta is the standard backend supported since setuptools 40+ and works with PIP_NO_BUILD_ISOLATION=1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lazy (inside-function) imports were missed during the pip-installable import qualification pass: - bluetooth_le.LE.ESPHomeAPIClient → aquaclean_console_app.bluetooth_le... - ErrorCodes.ErrorCode → aquaclean_console_app.ErrorCodes... - aquaclean_core.Api.CallClasses.GetSystemParameterList → aquaclean_console_app... Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace manual pip install with pip install git+https://github.com/... - Add collapsible dependency table - Show how to find config.ini after pip install - Update run commands from 'python main.py' to 'aquaclean-bridge' - Remove Python 3.13 haggis workaround note (handled by haggis-patched) - Add Debian 12 / Python 3.11.2 to tested environments Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was a stub returning no errors. Now validates: - [BLE] device_id: required, MAC address format - [SERVICE] ble_connection: must be 'persistent' or 'on-demand' - [ESPHOME] esphome_api_connection: must be 'persistent' or 'on-demand' - [ESPHOME] port: integer, 1–65535 - [API] port: integer, 1–65535 - [POLL] interval: non-negative float - [LOGGING] log_level: known log level - [ESPHOME] log_level: known log level Usage: aquaclean-bridge --mode cli --command check-config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
persistent is the intended mode for ESP32 proxy usage: reuses the TCP connection between BLE requests, avoiding per-request TCP handshake and device_info fetch overhead. on-demand was the cautious original default. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace empty options string with placeholder text - Ensure 'options' input is technically "supplied" for the @main action - Maintain github-releases strategy for final injection
- Switch from 'github-releases' to 'github-tags' to capture all versions - Ensure pre-release and tagged versions are included in the dropdown
- Update 'options' to a single list item to satisfy validation - Maintain 'strategy: github-tags' for dynamic injection - Prevent the action from overwriting the entire dropdown block
- Re-add # start-release-choices and # end-release-choices - Reset options to include 'Please select' header - Ensure markers are present for the automation to target
- Set options to "{{...}}" to satisfy required input
- Maintain strategy: github-tags for automated version fetching
- Preserve 'Please select' and markers during workflow execution
- Add "{{...}}" placeholder to software version dropdown
- Ensure markers are present for surgical tag injection
- Replace block scalar with inline array '["{{...}}"]'
- Satisfy required input while enabling dynamic strategy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.