Skip to content

fix(obd): keep one ELM327 prompt to one response - #64

Merged
eigger merged 2 commits into
mainfrom
fix/obd-rx-buffer-desync
Jul 30, 2026
Merged

fix(obd): keep one ELM327 prompt to one response#64
eigger merged 2 commits into
mainfrom
fix/obd-rx-buffer-desync

Conversation

@eigger

@eigger eigger commented Jul 30, 2026

Copy link
Copy Markdown
Owner

The RX buffer was drained with a single check and then cleared outright:

if (responseBuffer.contains(">")) {
    val fullResponse = responseBuffer.toString().trim()
    responseBuffer.clear()
    pendingDeferreds[device.id]?.complete(fullResponse)
}

A BLE notification can carry more than one prompt. When it does, both replies were handed over as one string and everything after the first prompt was thrown away, so the next command received the previous command's text and every reply after it was off by one. A partial reply left over from a command that had already timed out was prepended to the next one the same way.

Drain the buffer the way the ESPHome ble_elm327 component does: cut at each prompt in a loop, keep what follows for the next notification, and hand each reply to one waiting command. A reply with no command waiting is a late arrival and is dropped. The buffer is also reset before each write and on teardown so a stale fragment cannot leak into a fresh command.

Also stop choosing the read timeout by command length. cmd.length >= 6 was standing in for "this might be multi-frame", which mode 21 block requests are too — 21 03 answers with more than 60 bytes but is only four characters long. Give AT commands the short timeout and every data request the long one.

eigger and others added 2 commits July 30, 2026 13:01
The RX buffer was drained with a single check and then cleared outright:

    if (responseBuffer.contains(">")) {
        val fullResponse = responseBuffer.toString().trim()
        responseBuffer.clear()
        pendingDeferreds[device.id]?.complete(fullResponse)
    }

A BLE notification can carry more than one prompt. When it does, both replies
were handed over as one string and everything after the first prompt was
thrown away, so the next command received the previous command's text and
every reply after it was off by one. A partial reply left over from a command
that had already timed out was prepended to the next one the same way.

Drain the buffer the way the ESPHome ble_elm327 component does: cut at each
prompt in a loop, keep what follows for the next notification, and hand each
reply to one waiting command. A reply with no command waiting is a late
arrival and is dropped. The buffer is also reset before each write and on
teardown so a stale fragment cannot leak into a fresh command.

Also stop choosing the read timeout by command length. `cmd.length >= 6` was
standing in for "this might be multi-frame", which mode 21 block requests are
too — 21 03 answers with more than 60 bytes but is only four characters long.
Give AT commands the short timeout and every data request the long one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The chunks concatenate directly, so "0:6103AA" followed by "BBCC\r1:DDEE\r>"
gives "0:6103AABBCC\r1:DDEE" — the assertion expected a carriage return
between AA and BBCC that was never in the input. Split the response at a
clearer boundary instead, so the case reads as one reply arriving in two
notifications.

The production logic was correct; only the assertion was wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@eigger
eigger merged commit 77abc5b into main Jul 30, 2026
3 checks passed
@eigger
eigger deleted the fix/obd-rx-buffer-desync branch July 30, 2026 04:22
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.

1 participant