Repository navigation
fix(ble_elm327): tx_delay/timeout fixes, opt-in auto recovery and diagnostic sensors - #328
Merged
Merged
Conversation
…nsors - Measure tx_delay from the end of the previous exchange (prompt received, timeout or failed write) instead of the write time, so slow adapters get idle time between commands (#327). - Add configurable response_timeout (default 1500 ms, measured from the last received byte); init commands, a manual ATZ and the first PID request after init use at least 5000 ms. - On timeout drop the partial reply and clear stale RX data before the next write so a late prompt cannot be glued to the next command. - Check write_value() results; a failed write is not waited on. - After 3 consecutive timeouts/write failures re-initialize the adapter (queue cleared, init sequence re-sent, tracked ATSH header reset); if the next streak happens before any PID is answered, disconnect so ble_client reconnects. - Detect ELM327 status replies (NO DATA, STOPPED, ?, BUFFER FULL, CAN ERROR, UNABLE TO CONNECT, ERRxx, ...), log and count them instead of parsing them. - Add optional diagnostic entities: timeout/error/NO DATA/write-failure/ re-init/reconnect counters (sensor) and last_error (text_sensor). - README: fix the state machine description, document response_timeout, recovery and diagnostics. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Keep the long (init) timeout until a PID request gets a normal reply after (re)init, a timeout or an error reply, and while the partial reply shows SEARCHING / BUS INIT, so an ATSP0 protocol search is never cut short. - Count adapter-level error replies (STOPPED, BUFFER FULL, ERRxx, LV RESET, ACT ALERT, '?' to non-AT commands) as failures; reset the streak only on a normal reply to a PID request or init command, never on late replies. Bus errors (NO DATA, CAN ERROR, UNABLE TO CONNECT, ...) are neutral so an ignition-off car does not cause re-init/reconnect churn. - Clear the tracked pre_commands header when a pre-command times out, fails to write or gets an error reply. - Rate-limit diagnostic publishes (5 s, on change); NO DATA no longer overwrites last_error. - Pause traffic after a recovery disconnect until the disconnect event, and retry the disconnect if it never arrives. - Add an absolute response limit and a 4 KB RX buffer cap. - Require 5 normal PID replies after a re-init before resetting escalation. - Only handle WRITE_CHAR_EVT for the TX handle and end the exchange there instead of also counting a timeout. - Classify BUS INIT ERROR and ACT ALERT; native tests for categories. - README: timeout rules, error categories, recovery, publish policy, auto_connect note and exact log messages. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Treat WRITE_CHAR_EVT status ESP_GATT_CONGESTED as accepted (Bluedroid att_protocol.c returns GATT_CONGESTED for "message accepted" and gatt_cl.c ends NO_RSP writes with it); log at DEBUG, only other statuses fail. - Allow a 20 s idle timeout while the adapter reports SEARCHING / BUS INIT. - Do not count STOPPED in reply to the first command after a timeout (the adapter aborted the timed-out request). - Exponential backoff (30 s .. 10 min) between recovery reconnects; keep reinit_attempted_ across our own recovery disconnect so streaks after a reconnect do not restart the re-init -> reconnect cycle. Recovery state and backoff are cleared after 5 normal PID replies. - Enter READY only when the last init command is answered; re-send it on timeout / failed write. - Count "not connected / TX characteristic missing" as a failed write instead of silently dropping the command. - README: remove leftover of the old recovery table, document search timeout, STOPPED exception, backoff, READY rule, CONGESTED handling and ATSP0 / ignition-off guidance. - Native tests for the STOPPED exception and the backoff schedule. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- New auto_recovery option (default false). Without it, timeouts and other failures are only logged and counted and the next command is sent, as before: no re-init, no reconnect, and the last init command entering READY even without a reply. - New recovery_threshold (1-20, default 3), only valid with auto_recovery. - response_timeout default back to 5000 ms (the previous fixed value). With auto_recovery off the timeout is measured from the write; the activity-based timer, the 20 s SEARCHING extension and the hard limit only apply with auto_recovery. - Bus-side error replies (CAN ERROR, UNABLE TO CONNECT, ...) are logged at DEBUG to keep ignition-off logs quiet. - Move the timeout decision into check_response_timeout() and test that the default path matches the previous behaviour. - README: option table, default vs auto_recovery tables, tx_delay timing note, recommended auto_recovery setup with diagnostics. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- New diagnostic sensor key response_time (ms, measurement, duration): successful write -> '>' of the most recent normal PID reply. AT / init / pre_commands, error replies (incl. NO DATA), timeouts, failed writes and late replies are not sampled. - Published with the existing diagnostics policy (at most every 5 s, on change; latest sample only). Averaging is left to ESPHome filters. - README: key description, sliding_window_moving_average example and tuning guidance for response_timeout / tx_delay; added to the recommended set. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Keep the default of 50 ms; document the effect versus the previous write-based timing (slow adapters +50 ms per cycle, fast adapters about the same), that tx_delay: 0 removes the wait, when to raise it, and that ATZ needs no extra delay because the next init command waits for its prompt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Only clear the tracked pre_commands header when a pre-command times out, fails to write, or gets an adapter/link error reply (STOPPED, BUFFER FULL, ERRxx, ...). A '?' (unsupported) or vehicle-side reply keeps the header sticky as before, so it is not re-sent on every poll. Logic moved to pre_command_reply_invalidates_header() with native tests. - README/PR: fast adapters go from max(50, response) to response + 50 per cycle (e.g. 30 ms reply: 50 -> 80 ms); tx_delay: 0 for master-like throughput. Hard limit is at least 40 s during SEARCHING / BUS INIT. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- When the tracked header was cleared after a device's pre_commands and PID were queued (failed pre-command, adapter reset), hold the PID back and re-queue the device so pre_commands + PID are queued together at the next collection. Never triggers on the normal path, so traffic is unchanged. - Clear header tracking on an LV RESET reply (any command) and when ATZ, ATWS or ATD is sent, e.g. via send_command. - Native tests for pre_commands_applied() and command_resets_adapter(). - README/PR: document the above and the '?' sticky trade-off. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Only clear header tracking for ATZ / ATWS / ATD sent on their own (init or send_command), not when they are one of a device's own pre_commands: the rest of that list re-applies the header, so e.g. ["ATD","ATSP6","ATSH 7E4"] is sent once and the PID follows, as before. - A PID whose header was lost is skipped for that poll instead of being re-polled immediately; its next update() queues pre_commands + PID again, so a failing ATSH cannot cause extra traffic beyond update_interval. - Move the dequeue decision into decide_dequeue() and test full poll cycles for pre_commands containing ATD / ATZ / ATWS. - README/PR: correct the description of both behaviours. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Native test: ATSH fails (timeout / STOPPED / BUFFER FULL) -> tracked header cleared -> PID held back -> next poll re-sends ATSH + PID -> PID only; '?' keeps the header sticky. Uses the same decide_dequeue() / pre_command_reply_invalidates_header() as loop(). No component code change. - README/PR: describe the trade-off that a device whose pre-command keeps failing (e.g. slow ECU timing out, alternating ATSH headers) skips polls instead of publishing another ECU's value, and the remedies (response_timeout, auto_recovery). Co-Authored-By: Claude Sonnet 5.5 <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.
Addresses #327 (iCar Pro: updates stop after ~30 s, no recovery until reboot).
With the default settings the component behaves like
master: there is no new re-init or reconnect, and the response timeout is still 5000 ms from the write. Recovery is opt-in withauto_recovery: true. The only default-on changes are the bug fixes listed below.Options
response_timeout5000ms (same as master's fixed value)auto_recoveryfalseATZre-init, then a BLE reconnect with backoff. Also enables the activity-based timeout.recovery_threshold3auto_recovery: true(rejected otherwise).Default behavior changes (no new options set) vs
mastertx_delay(default still50) is now an extra wait after the reply / timeout / failed write instead of being measured from the writeresponse + 50instead ofresponse). Fast adapters: slightly slower per cycle,response + 50instead ofmax(50, response)(e.g. a 30 ms reply: 50 → 80 ms). Settx_delay: 0for master-like throughput. The README explains thattx_delay: 0removes the wait and that slow or unstable adapters should raise it.NO DATA,STOPPED,?,CAN ERROR,UNABLE TO CONNECT, …) are no longer parsed as hex dataUNABLE TO CONNECTused to produce bogus bytesAB EC CE.NO DATAand bus errors at DEBUGwrite_valueresult checked: a failed write is not waited onWRITE_CHAR_EVT: only our TX handle;ESP_GATT_CONGESTEDis not a failure (DEBUG log); other statuses end the exchange immediatelyatt_protocol.c: "ATT congested, message accepted"). Master logged all non-OK statuses as failures.pre_commandsheader cleared when a pre-command times out, fails to write, or gets an adapter/link error reply (STOPPED,BUFFER FULL,ERRxx, …)ATSHafter such a failure; otherwise a PID could be sent with the wrong header. A?(unsupported) or vehicle-side error reply does not clear it, so the header stays sticky as on master and is not re-sent every poll.pre_commandsheader was cleared after it was queued is skipped for that poll; its nextupdate()queuespre_commands+ PID againATSHthe PID could go to the wrong ECU and that ECU's reply could be published. It only happens after a failure (failed pre-command / adapter reset). There is no extra polling beyondupdate_interval, and without failures the traffic is the same as master. Trade-off: if the pre-command keeps failing, that device can miss updates. Example: two devices with differentATSH, and a slow ECU whose request times out every time, so the nextATSHgetsSTOPPED. Master sent the PID with the wrong header, which gave a wrong value or none. Now the poll is skipped, so nothing wrong is published, but the sensor may not update. There is no retry logic. Remedies: a largerresponse_timeout(enough for slow multi-frame ECUs), orauto_recovery: true. Documented in the README.LV RESETreply (any command, even a late reply) and whenATZ/ATWS/ATDis sent on its own (init orsend_command)ATSH. Inside a device's ownpre_commands(e.g.["ATD","ATSP6","ATSH 7E4"]) they do not clear it, so that list is sent once and stays sticky, as on master (native test covers this).auto_recovery: falsea timeout still proceeds to READY, as before.Timeout (5000ms) waiting for response to '010c' (n failures in a row)Only with
auto_recovery: true:SEARCHING/BUS INITis reported, and a hard limit ofmax(30 s, 2× timeout)(at least 40 s during a search);ATZre-init → BLE disconnect/reconnect, with exponential backoff (30 s … 10 min) and reconnect retry;Review of the issue's points
tx_delayfrom the write timeresponse_timeoutoption (default 5000) + opt-inauto_recovery.rx_buffer_not cleared on timeoutwrite_valueresult ignoredcurrent_pre_commands_NO DATAis a fast answer and doesn't stall the queue, and ignition-off makes every PID return it. Counted instead.ATH1/ negative-response setups.Error categories (used for counting, and for escalation with
auto_recovery)NO DATA,CAN ERROR,BUS ERROR,BUS BUSY,BUS INIT ERROR,UNABLE TO CONNECT,DATA ERROR,FB ERROR,RX ERROR.STOPPED(except right after a timeout),BUFFER FULL,ERRxx,LV RESET,ACT ALERT, and?to non-AT commands (?to an AT command only means "unsupported").Diagnostics (optional,
entity_category: diagnostic)timeout_count,error_count,no_data_count,write_failure_count,reinit_count,reconnect_count. The last two stay 0 withoutauto_recovery.response_time(ms,measurement): time from a successful write to the>of the most recent normal PID reply. AT / init /pre_commands, error replies (incl.NO DATA), timeouts, failed writes and late replies are not sampled. Only the latest sample is published per 5 s interval. Averaging is left to ESPHomefilters(the README shows asliding_window_moving_averageexample), so there is no avg/max logic in the component.last_error.NO DATAis excluded from it.README
tx_delaytiming note, with the effect vs master and when to use 0 or raise it.ATZneeds no extra delay: the next init command waits for the>the ELM327 prints after its reset, so no internal post-ATZ delay was added.response_timeinterpretation and tuning guide.auto_recovery: trueexample withlast_error+reconnect_count.auto_recoverytimeout table.ATSP0guidance, plus anauto_connect: falsecaveat.Verification
check_response_timeout()test shows the default path matches master (timeout from the write, no activity / SEARCHING extension). Also covers the auto_recovery timeout rules, the error categories, the STOPPED-after-timeout rule, and the backoff schedule. All pass.esphome config(local): the test YAML (withauto_recovery: true,recovery_threshold: 3) is valid. A config with no options getsresponse_timeout: 5000,auto_recovery: false.recovery_thresholdwithoutauto_recoveryis rejected.🤖 Generated with Claude Code