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
donavanbecker added a commit that referenced this pull request Oct 30, 2026
Merges `latest` (5.2.1 to 5.2.4) into `beta-6.0.0` before the 6.0.0
release, so 6.0.0 keeps dnicolson's BLE logging changes (#351, #352,
#353).

There was one conflict: the disconnect fix landed on both branches (#349
to `latest`, #350 to `beta-6.0.0`). I kept the `beta-6.0.0` version,
which also clears the encrypted session on disconnect.

I checked that no log line from `latest` was dropped. 435 tests pass.
Build and lint pass.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s)_
@donavanbecker donavanbecker mentioned this pull request Oct 30, 2026
donavanbecker added a commit that referenced this pull request Oct 30, 2026
## node-switchbot 6.0.0

### What's new
- **Encrypted BLE devices** (#347): pySwitchbot's
`SwitchbotEncryptedDevice` protocol, supporting both CTR and GCM
sessions. Call `device.setKey({ keyId, encryptionKey })` to use it with
relay switches, locks, air purifiers and bulbs.
- **BLE replies from notifications** (#347): replies are read from the
notify characteristic, as in pySwitchbot, with a 5 s timeout. A
disconnect rejects a pending reply.
- **Disconnect handling** (#349, #350):
- Disconnects the library starts no longer log `Unexpected disconnect`.
- When a device drops the link, the connection and encrypted session are
cleared, so the next command reconnects.
- **BLE logging** from 5.2.x (#351, #352, #353, dnicolson).

### Breaking changes
- **`SwitchBotBLEModel` values** now use pySwitchbot's primary
advertisement codes:
  - Meter Pro `4`
  - Lock Pro `$`
  - Keypad `y`
  - Strip Light `r`
  - Hub 2 `v`
  - Air Purifier `*`
  - Air Purifier Table `7`
  - Remote `b`
  - Relay Switch 1 `;`
  - Relay Switch 1PM `<`
  - Water Leak Detector `&`
  - Humidifier 2 `#`
  - Battery Circulator Fan `~`
  - K10+ Pro Combo / K10 Pro `3`
  - K10+ `}`
- **Removed enum members:** `KeypadTouch`, `RobotVacuumCleanerS1`,
`RobotVacuumCleanerS1Plus` and `PresenceSensor`.

### Migration (homebridge-switchbot)
- Code that compares `SwitchBotBLEModel` values must use the new codes.
homebridge-switchbot doesn't switch on these values, so it needs no
change there.
- Pass keys with `setKey`. homebridge-switchbot does this as of #1432,
and #1433 moves it to 6.0.0-beta.

Tested as `6.0.0-beta.0` / `beta.1` on npm. 435 tests pass. Build and
lint pass.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: dnicolson <dnicolson@users.noreply.github.com>
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