Skip to content

feat!: notification replies, pySwitchbot encryption and corrected BLE model codes - #347

Merged
donavanbecker merged 3 commits into
beta-6.0.0from
feat/ble-transport-6.0.0
Oct 24, 2026
Merged

donavanbecker merged 3 commits into
beta-6.0.0from
feat/ble-transport-6.0.0

Conversation

@donavanbecker

@donavanbecker donavanbecker commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

BLE transport changes to match pySwitchbot, shipped first as 6.0.0 betas (npm i node-switchbot@beta) so they can be tested on real devices before a stable release. Each change has tests; I couldn't test against hardware.

1. Replies come from notifications — fix(ble)

SwitchBot devices answer a command with a notification, which pySwitchbot waits for. node-switchbot read the notify characteristic after writing, which can return a stale value. Now:

  • The wait is registered before writing, so a fast reply isn't missed. The existing expectNotification path wrote first, which was a race.
  • One listener per notify characteristic: a notification answers the pending command, otherwise it goes to subscribeNotifications() handlers.
  • A pending reply fails on disconnect; the timeout is pySwitchbot's 5 s.
  • Backends without notification events still fall back to a read.

2. pySwitchbot's encryption protocol — feat(ble)!

node-switchbot's encryption didn't match the devices: it encrypted the whole command, prepended 0x11, and used an IV from config. BLEConnection.sendEncryptedCommand now implements SwitchbotEncryptedDevice:

  • Session setup per connection: 57 000000 0F2103 <keyId> returns the AES mode (CTR or GCM) and the device's IV.
  • Commands: sent as 57 <keyId> <2-byte header> <AES(rest)>.
  • Replies: decrypted to status plus payload. GCM increments the IV per command, and the session resets on disconnect.

Also:

  • Added setKey({ encryptionKey, keyId }). homebridge-switchbot already calls it, but it didn't exist, so encrypted devices never received their key.
  • Bulbs, relay switches and air purifiers no longer pre-encrypt when a key id is set.
  • The air purifier filter reset now works on encrypted devices, verified from the decrypted reply.

Tests use a simulated device implementing the protocol in both CTR and GCM, plus pySwitchbot's init and mode cases.

3. SwitchBotBLEModel values — feat!

Thirteen values were codes belonging to other products. Some examples:

  • MeterPro 'o' was the Lock's code.
  • WaterLeakDetector 'y' was the Keypad's.
  • Remote 0x05 was HubMini Matter's.
  • RelaySwitch1PM 0x0D was Lock Lite's.

Every value is now the product's primary pySwitchbot code. New members include RelaySwitch2PM, GarageDoorOpener, LockLite, RollerShade and PlugMiniEU.

Breaking changes / migration

  • Encrypted devices need keyId alongside encryptionKey (both from the SwitchBot account, as for pySwitchbot). Without a key id the old scheme is used, which didn't work with real devices anyway.
  • SwitchBotBLEModel values changed. KeypadTouch, RobotVacuumCleanerS1, RobotVacuumCleanerS1Plus and PresenceSensor are removed (no single-byte code; their values named other products). homebridge-switchbot doesn't use this enum.

Checks

Build, 429 tests (+27) and lint pass. Labelled major. Merging into beta-6.0.0 publishes 6.0.0-beta.x; promoting beta-6.0.0 to latest releases 6.0.0.

https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s


Generated by Claude Code

SwitchBot devices answer a command with a notification on the notify
characteristic, which pySwitchbot waits for. node-switchbot read the
characteristic after writing, which can return a stale value.

- Register the wait before writing, so a fast reply is not missed.
- Attach one notify listener per characteristic: a notification answers
  the pending command, otherwise it goes to subscribeNotifications()
  handlers.
- Fail a pending reply on disconnect, and use pySwitchbot's 5 s reply
  timeout.
- Backends without notification events still read the characteristic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s
Implement pySwitchbot's SwitchbotEncryptedDevice protocol in
BLEConnection.sendEncryptedCommand:

- Session setup per connection: 57 000000 0F2103 <keyId> returns the AES
  mode (CTR or GCM) and the device's IV.
- Commands are sent as 57 <keyId> <2-byte header> <AES(rest)>, and replies
  are decrypted (status byte plus payload). GCM increments the IV per
  command. The session resets on disconnect.

Devices use it when they have an encryption key and key id. Add
setKey({ encryptionKey, keyId }), which homebridge-switchbot already calls
but which did not exist, so encrypted devices never received their key.
Bulbs, relay switches and air purifiers no longer pre-encrypt commands
when a key id is set. Air purifier filter reset now works on encrypted
devices, verified from the decrypted reply.

BREAKING CHANGE: an encrypted device needs `keyId` (2 hex characters)
alongside `encryptionKey`, both from the SwitchBot account. The previous
scheme (encryptionKey plus a configured encryptionIV) is kept only when no
keyId is set; it does not match the device protocol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s
Several values were codes that now belong to other products: MeterPro 'o'
(Lock), WaterLeakDetector 'y' (Keypad), Remote 0x05 (HubMini Matter),
RelaySwitch1PM 0x0D (Lock Lite), BatteryCirculatorFan 0x04 (Lock Pro),
AirPurifier 0x08 (K10+ Pro), and others. Every value is now the
product's primary pySwitchbot code.

Add RollerShade, PlugMiniEU, RelaySwitch2PM, GarageDoorOpener, LockLite,
HubMiniMatter and robot vacuum members.

BREAKING CHANGE: SwitchBotBLEModel values changed to pySwitchbot's
primary codes. KeypadTouch, RobotVacuumCleanerS1,
RobotVacuumCleanerS1Plus and PresenceSensor are removed: they have no
single-byte code and their values named other products. Compare against
DEVICE_MODEL_MAP (all codes) rather than enum values where possible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s
@donavanbecker donavanbecker added the major major version change label Oct 24, 2026 — with Claude
@donavanbecker
donavanbecker merged commit 90bc606 into beta-6.0.0 Oct 24, 2026
2 checks passed
@donavanbecker
donavanbecker deleted the feat/ble-transport-6.0.0 branch October 24, 2026 15:04
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

major major version change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants