Skip to content

fix(ble): only report disconnects the device starts as unexpected - #349

Merged
donavanbecker merged 1 commit into
latestfrom
fix/ble-disconnect-logging
Oct 25, 2026
Merged

donavanbecker merged 1 commit into
latestfrom
fix/ble-disconnect-logging

Conversation

@donavanbecker

Copy link
Copy Markdown
Contributor

What

Since 5.x, every BLE command is followed a few seconds later by [BLEConnection] WARN: Unexpected disconnect from … (reported in #348).

disconnect() warned unless the MAC had been passed to markExpectedDisconnect(), and nothing in the library ever called that. So the idle auto-disconnect after each command, disconnectAll(), and cleanup after a failed connection all warned.

The opposite case was not handled at all. When the device drops the link (out of range, powered off, rebooted), the dead peripheral stayed in connections, and connect() returned "already connected". The next command was then written over a dead link.

Changes (src/ble.ts)

  • disconnect() is always started by us, so it logs at debug level. Calling it for a device that isn't connected is no longer a warning.
  • connect() attaches a disconnect listener to the peripheral. If the device drops the link, the listener:
    • warns Unexpected disconnect;
    • drops the connection state (characteristics, encryption session, notification handler);
    • rejects any pending reply, so the next command reconnects.
  • markExpectedDisconnect() now means "the device is about to drop the link, e.g. after a restart command; don't warn".
  • Characteristic-discovery failure cleanup goes through disconnect().

Why

This supersedes #348 by @dnicolson, which marked the idle and disconnectAll paths as expected. That fixed the warning but still logged at info level on every command, and left the dead-link case unhandled. @dnicolson is credited as co-author.

Tests

test/apis/ble-disconnect.test.ts covers:

  • disconnects we start (single, all, not connected) don't warn;
  • a device disconnect warns and clears state;
  • the next connect() reconnects;
  • a marked device disconnect doesn't warn but still clears state.

408 tests pass. Build and lint pass.

No breaking changes. I'll carry this into beta-6.0.0 after merge.


Generated by Claude Code

BLEConnection logged "Unexpected disconnect" for every disconnect it started
itself (the idle timer after each command, disconnectAll, a failed
connection), because nothing marked them as expected. Meanwhile a disconnect
started by the device (out of range, powered off) was never noticed: the dead
peripheral stayed in the connection map and the next command reused it.

- disconnect() is always intentional: it logs at debug level.
- A 'disconnect' listener on the peripheral drops the connection state
  (characteristics, encryption, pending reply) and warns only when the device
  dropped the link; markExpectedDisconnect() silences that warning.
- The characteristic-discovery failure path uses disconnect().

Supersedes #348.

Co-authored-by: dnicolson <dnicolson@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s
@donavanbecker
donavanbecker merged commit d36216d into latest Oct 25, 2026
2 checks passed
@donavanbecker
donavanbecker deleted the fix/ble-disconnect-logging branch October 25, 2026 00:51
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