Repository navigation
feat!: notification replies, pySwitchbot encryption and corrected BLE model codes - #347
Merged
Merged
Conversation
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
This was referenced Oct 24, 2026
Merged
Merged
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>
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.
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:
expectNotificationpath wrote first, which was a race.subscribeNotifications()handlers.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.sendEncryptedCommandnow implementsSwitchbotEncryptedDevice:57 000000 0F2103 <keyId>returns the AES mode (CTR or GCM) and the device's IV.57 <keyId> <2-byte header> <AES(rest)>.Also:
setKey({ encryptionKey, keyId }). homebridge-switchbot already calls it, but it didn't exist, so encrypted devices never received their key.Tests use a simulated device implementing the protocol in both CTR and GCM, plus pySwitchbot's init and mode cases.
3.
SwitchBotBLEModelvalues —feat!Thirteen values were codes belonging to other products. Some examples:
MeterPro'o'was the Lock's code.WaterLeakDetector'y'was the Keypad's.Remote0x05was HubMini Matter's.RelaySwitch1PM0x0Dwas Lock Lite's.Every value is now the product's primary pySwitchbot code. New members include
RelaySwitch2PM,GarageDoorOpener,LockLite,RollerShadeandPlugMiniEU.Breaking changes / migration
keyIdalongsideencryptionKey(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.SwitchBotBLEModelvalues changed.KeypadTouch,RobotVacuumCleanerS1,RobotVacuumCleanerS1PlusandPresenceSensorare 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 intobeta-6.0.0publishes6.0.0-beta.x; promotingbeta-6.0.0tolatestreleases 6.0.0.https://claude.ai/code/session_01TYWa2RXoWsaUTWh76oNv8s
Generated by Claude Code