node-switchbot 3.5.0-beta.0 → 3.5.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/npm-version-script-esm.js +97 -0
- package/.github/workflows/beta-release.yml +1 -1
- package/.github/workflows/release.yml +1 -1
- package/BLE.md +14 -13
- package/CHANGELOG.md +68 -1
- package/OpenAPI.md +13 -12
- package/branding/Node_x_SwitchBot.svg +2 -3
- package/dist/device.d.ts +1193 -3
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +2478 -2
- package/dist/device.js.map +1 -1
- package/dist/index.d.ts +0 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -17
- package/dist/index.js.map +1 -1
- package/dist/{test/index.test.d.ts.map → index.test.d.ts.map} +1 -1
- package/dist/{test/index.test.js → index.test.js} +1 -1
- package/dist/index.test.js.map +1 -0
- package/dist/parameter-checker.d.ts +1 -1
- package/dist/parameter-checker.d.ts.map +1 -1
- package/dist/settings.d.ts +12 -28
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +24 -22
- package/dist/settings.js.map +1 -1
- package/dist/{test/settings.test.d.ts.map → settings.test.d.ts.map} +1 -1
- package/dist/{test/settings.test.js → settings.test.js} +6 -6
- package/dist/{test/settings.test.js.map → settings.test.js.map} +1 -1
- package/dist/switchbot-ble.d.ts +1 -1
- package/dist/switchbot-ble.d.ts.map +1 -1
- package/dist/switchbot-ble.js +8 -27
- package/dist/switchbot-ble.js.map +1 -1
- package/dist/switchbot-openapi.d.ts +33 -22
- package/dist/switchbot-openapi.d.ts.map +1 -1
- package/dist/switchbot-openapi.js +104 -64
- package/dist/switchbot-openapi.js.map +1 -1
- package/dist/types/bledevicestatus.d.ts +32 -8
- package/dist/types/bledevicestatus.d.ts.map +1 -1
- package/dist/types/devicestatus.d.ts +35 -1
- package/dist/types/devicestatus.d.ts.map +1 -1
- package/dist/types/devicewebhookstatus.d.ts +38 -1
- package/dist/types/devicewebhookstatus.d.ts.map +1 -1
- package/dist/types/types.d.ts +0 -223
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +1 -144
- package/dist/types/types.js.map +1 -1
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/icons.js +1 -1
- package/docs/assets/icons.svg +1 -1
- package/docs/assets/main.js +5 -5
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/assets/style.css +1405 -1288
- package/docs/classes/Advertising.html +10 -10
- package/docs/classes/SwitchBotBLE.html +13 -13
- package/docs/classes/SwitchBotOpenAPI.html +38 -28
- package/docs/classes/SwitchbotDevice.html +26 -23
- package/docs/classes/WoBlindTilt.html +45 -42
- package/docs/classes/WoBulb.html +42 -39
- package/docs/classes/WoCeilingLight.html +50 -47
- package/docs/classes/WoContact.html +30 -27
- package/docs/classes/WoCurtain.html +40 -37
- package/docs/classes/WoHand.html +37 -34
- package/docs/classes/WoHub2.html +30 -27
- package/docs/classes/WoHumi.html +45 -40
- package/docs/classes/WoHumi2.html +67 -0
- package/docs/classes/WoIOSensorTH.html +30 -27
- package/docs/classes/WoKeypad.html +52 -0
- package/docs/classes/WoLeak.html +53 -0
- package/docs/classes/WoPlugMiniJP.html +78 -0
- package/docs/classes/WoPlugMiniUS.html +39 -36
- package/docs/classes/WoPresence.html +30 -27
- package/docs/classes/WoRelaySwitch1.html +57 -0
- package/docs/classes/WoRelaySwitch1PM.html +57 -0
- package/docs/classes/WoRemote.html +52 -0
- package/docs/classes/WoSensorTH.html +27 -24
- package/docs/classes/WoSensorTHPlus.html +46 -0
- package/docs/classes/WoSensorTHPro.html +46 -0
- package/docs/classes/WoSensorTHProCO2.html +46 -0
- package/docs/classes/WoSmartLock.html +49 -46
- package/docs/classes/WoSmartLockPro.html +49 -46
- package/docs/classes/WoStrip.html +44 -41
- package/docs/enums/LogLevel.html +10 -10
- package/docs/enums/SwitchBotBLEModel.html +29 -27
- package/docs/enums/SwitchBotBLEModelFriendlyName.html +30 -26
- package/docs/enums/SwitchBotBLEModelName.html +28 -26
- package/docs/enums/SwitchBotModel.html +47 -43
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +13 -13
- package/docs/interfaces/AdvertisementData.html +3 -3
- package/docs/interfaces/Chars.html +2 -2
- package/docs/interfaces/ErrorObject.html +2 -2
- package/docs/interfaces/NobleTypes.html +2 -2
- package/docs/interfaces/Params.html +2 -2
- package/docs/interfaces/Rule.html +4 -4
- package/docs/interfaces/ServiceData.html +2 -2
- package/docs/interfaces/SwitchBotBLEDevice.html +23 -23
- package/docs/interfaces/WebhookDetail.html +4 -4
- package/docs/interfaces/ad.html +3 -3
- package/docs/interfaces/body.html +3 -3
- package/docs/interfaces/bodyChange.html +3 -3
- package/docs/interfaces/deleteWebhookResponse.html +3 -3
- package/docs/interfaces/device.html +6 -6
- package/docs/interfaces/deviceList.html +2 -2
- package/docs/interfaces/deviceStatus.html +6 -6
- package/docs/interfaces/deviceStatusRequest.html +3 -3
- package/docs/interfaces/deviceWebhook.html +4 -4
- package/docs/interfaces/deviceWebhookContext.html +4 -4
- package/docs/interfaces/devices.html +3 -3
- package/docs/interfaces/infraredRemoteList.html +2 -2
- package/docs/interfaces/irdevice.html +5 -5
- package/docs/interfaces/pushResponse.html +3 -3
- package/docs/interfaces/queryWebhookResponse.html +3 -3
- package/docs/interfaces/setupWebhookResponse.html +3 -3
- package/docs/interfaces/switchbot.html +2 -2
- package/docs/interfaces/updateWebhookResponse.html +3 -3
- package/docs/interfaces/webhookRequest.html +3 -3
- package/docs/media/BLE.md +14 -13
- package/docs/media/OpenAPI.md +13 -12
- package/docs/modules.html +1 -170
- package/docs/types/MacAddress.html +1 -1
- package/docs/types/batteryCirculatorFan.html +1 -1
- package/docs/types/batteryCirculatorFanServiceData.html +1 -1
- package/docs/types/batteryCirculatorFanStatus.html +1 -1
- package/docs/types/batteryCirculatorFanWebhookContext.html +1 -1
- package/docs/types/blindTilt.html +1 -1
- package/docs/types/blindTiltServiceData.html +1 -1
- package/docs/types/blindTiltStatus.html +1 -1
- package/docs/types/blindTiltWebhookContext.html +1 -1
- package/docs/types/bot.html +1 -1
- package/docs/types/botServiceData.html +1 -1
- package/docs/types/botStatus.html +1 -1
- package/docs/types/botWebhookContext.html +1 -1
- package/docs/types/ceilingLight.html +1 -1
- package/docs/types/ceilingLightPro.html +1 -1
- package/docs/types/ceilingLightProServiceData.html +1 -1
- package/docs/types/ceilingLightProStatus.html +1 -1
- package/docs/types/ceilingLightProWebhookContext.html +1 -1
- package/docs/types/ceilingLightServiceData.html +1 -1
- package/docs/types/ceilingLightStatus.html +1 -1
- package/docs/types/ceilingLightWebhookContext.html +1 -1
- package/docs/types/circulatorFanStatus.html +1 -0
- package/docs/types/circulatorFanWebhookContext.html +1 -0
- package/docs/types/colorBulb.html +1 -1
- package/docs/types/colorBulbServiceData.html +1 -1
- package/docs/types/colorBulbStatus.html +1 -1
- package/docs/types/colorBulbWebhookContext.html +1 -1
- package/docs/types/contactSensor.html +1 -1
- package/docs/types/contactSensorServiceData.html +1 -1
- package/docs/types/contactSensorStatus.html +1 -1
- package/docs/types/contactSensorWebhookContext.html +1 -1
- package/docs/types/curtain.html +1 -1
- package/docs/types/curtain3.html +1 -1
- package/docs/types/curtain3ServiceData.html +1 -1
- package/docs/types/curtain3WebhookContext.html +1 -1
- package/docs/types/curtainServiceData.html +1 -1
- package/docs/types/curtainStatus.html +1 -1
- package/docs/types/curtainWebhookContext.html +1 -1
- package/docs/types/floorCleaningRobotS10.html +1 -1
- package/docs/types/floorCleaningRobotS10Status.html +1 -1
- package/docs/types/floorCleaningRobotS10WebhookContext.html +1 -1
- package/docs/types/hub2.html +1 -1
- package/docs/types/hub2ServiceData.html +1 -1
- package/docs/types/hub2Status.html +1 -1
- package/docs/types/hub2WebhookContext.html +1 -1
- package/docs/types/humidifier.html +1 -1
- package/docs/types/humidifier2ServiceData.html +1 -0
- package/docs/types/humidifier2Status.html +1 -0
- package/docs/types/humidifier2WebhookContext.html +1 -0
- package/docs/types/humidifierServiceData.html +1 -1
- package/docs/types/humidifierStatus.html +1 -1
- package/docs/types/humidifierWebhookContext.html +1 -1
- package/docs/types/indoorCam.html +1 -1
- package/docs/types/indoorCameraWebhookContext.html +1 -1
- package/docs/types/keypad.html +1 -1
- package/docs/types/keypadDetectorServiceData.html +1 -1
- package/docs/types/keypadTouch.html +1 -1
- package/docs/types/keypadTouchWebhookContext.html +1 -1
- package/docs/types/keypadWebhookContext.html +1 -1
- package/docs/types/lock.html +1 -1
- package/docs/types/lockPro.html +1 -1
- package/docs/types/lockProServiceData.html +1 -1
- package/docs/types/lockProStatus.html +1 -1
- package/docs/types/lockProWebhookContext.html +1 -1
- package/docs/types/lockServiceData.html +1 -1
- package/docs/types/lockStatus.html +1 -1
- package/docs/types/lockWebhookContext.html +1 -1
- package/docs/types/meter.html +1 -1
- package/docs/types/meterPlus.html +1 -1
- package/docs/types/meterPlusServiceData.html +1 -1
- package/docs/types/meterPlusStatus.html +1 -1
- package/docs/types/meterPlusWebhookContext.html +1 -1
- package/docs/types/meterPro.html +1 -1
- package/docs/types/meterProCO2ServiceData.html +1 -1
- package/docs/types/meterProCO2Status.html +1 -1
- package/docs/types/meterProCO2WebhookContext.html +1 -1
- package/docs/types/meterProServiceData.html +1 -1
- package/docs/types/meterProStatus.html +1 -1
- package/docs/types/meterProWebhookContext.html +1 -1
- package/docs/types/meterServiceData.html +1 -1
- package/docs/types/meterStatus.html +1 -1
- package/docs/types/meterWebhookContext.html +1 -1
- package/docs/types/motionSensor.html +1 -1
- package/docs/types/motionSensorServiceData.html +1 -1
- package/docs/types/motionSensorStatus.html +1 -1
- package/docs/types/motionSensorWebhookContext.html +1 -1
- package/docs/types/onadvertisement.html +1 -1
- package/docs/types/ondiscover.html +1 -1
- package/docs/types/outdoorMeter.html +1 -1
- package/docs/types/outdoorMeterServiceData.html +1 -1
- package/docs/types/outdoorMeterStatus.html +1 -1
- package/docs/types/outdoorMeterWebhookContext.html +1 -1
- package/docs/types/panTiltCamWebhookContext.html +1 -1
- package/docs/types/pantiltCam.html +1 -1
- package/docs/types/pantiltCam2k.html +1 -1
- package/docs/types/plug.html +1 -1
- package/docs/types/plugMini.html +1 -1
- package/docs/types/plugMiniJPServiceData.html +1 -1
- package/docs/types/plugMiniJPWebhookContext.html +1 -1
- package/docs/types/plugMiniStatus.html +1 -1
- package/docs/types/plugMiniUSServiceData.html +1 -1
- package/docs/types/plugMiniUSWebhookContext.html +1 -1
- package/docs/types/plugStatus.html +1 -1
- package/docs/types/plugWebhookContext.html +1 -1
- package/docs/types/relaySwitch1Context.html +1 -0
- package/docs/types/relaySwitch1PMContext.html +1 -0
- package/docs/types/relaySwitch1PMServiceData.html +1 -1
- package/docs/types/relaySwitch1PMStatus.html +1 -0
- package/docs/types/relaySwitch1ServiceData.html +1 -0
- package/docs/types/relaySwitch1Status.html +1 -0
- package/docs/types/remote.html +1 -1
- package/docs/types/remoteServiceData.html +1 -0
- package/docs/types/robotVacuumCleanerS1.html +1 -1
- package/docs/types/robotVacuumCleanerS1Plus.html +1 -1
- package/docs/types/robotVacuumCleanerS1PlusStatus.html +1 -1
- package/docs/types/robotVacuumCleanerS1PlusWebhookContext.html +1 -1
- package/docs/types/robotVacuumCleanerS1Status.html +1 -1
- package/docs/types/robotVacuumCleanerS1WebhookContext.html +1 -1
- package/docs/types/robotVacuumCleanerServiceData.html +1 -1
- package/docs/types/stripLight.html +1 -1
- package/docs/types/stripLightServiceData.html +1 -1
- package/docs/types/stripLightStatus.html +1 -1
- package/docs/types/stripLightWebhookContext.html +1 -1
- package/docs/types/waterLeakDetector.html +1 -1
- package/docs/types/waterLeakDetectorServiceData.html +1 -1
- package/docs/types/waterLeakDetectorStatus.html +1 -1
- package/docs/types/waterLeakDetectorWebhookContext.html +1 -1
- package/package.json +16 -19
- package/.github/npm-version-script.cjs +0 -81
- package/dist/advertising.d.ts +0 -44
- package/dist/advertising.d.ts.map +0 -1
- package/dist/advertising.js +0 -163
- package/dist/advertising.js.map +0 -1
- package/dist/device/woblindtilt.d.ts +0 -78
- package/dist/device/woblindtilt.d.ts.map +0 -1
- package/dist/device/woblindtilt.js +0 -181
- package/dist/device/woblindtilt.js.map +0 -1
- package/dist/device/wobulb.d.ts +0 -70
- package/dist/device/wobulb.d.ts.map +0 -1
- package/dist/device/wobulb.js +0 -136
- package/dist/device/wobulb.js.map +0 -1
- package/dist/device/woceilinglight.d.ts +0 -74
- package/dist/device/woceilinglight.d.ts.map +0 -1
- package/dist/device/woceilinglight.js +0 -161
- package/dist/device/woceilinglight.js.map +0 -1
- package/dist/device/wocontact.d.ts +0 -19
- package/dist/device/wocontact.d.ts.map +0 -1
- package/dist/device/wocontact.js +0 -48
- package/dist/device/wocontact.js.map +0 -1
- package/dist/device/wocurtain.d.ts +0 -52
- package/dist/device/wocurtain.d.ts.map +0 -1
- package/dist/device/wocurtain.js +0 -135
- package/dist/device/wocurtain.js.map +0 -1
- package/dist/device/wohand.d.ts +0 -50
- package/dist/device/wohand.d.ts.map +0 -1
- package/dist/device/wohand.js +0 -82
- package/dist/device/wohand.js.map +0 -1
- package/dist/device/wohub2.d.ts +0 -19
- package/dist/device/wohub2.d.ts.map +0 -1
- package/dist/device/wohub2.js +0 -40
- package/dist/device/wohub2.js.map +0 -1
- package/dist/device/wohumi.d.ts +0 -50
- package/dist/device/wohumi.d.ts.map +0 -1
- package/dist/device/wohumi.js +0 -89
- package/dist/device/wohumi.js.map +0 -1
- package/dist/device/woiosensorth.d.ts +0 -20
- package/dist/device/woiosensorth.d.ts.map +0 -1
- package/dist/device/woiosensorth.js +0 -49
- package/dist/device/woiosensorth.js.map +0 -1
- package/dist/device/wokeypad.d.ts +0 -19
- package/dist/device/wokeypad.d.ts.map +0 -1
- package/dist/device/wokeypad.js +0 -51
- package/dist/device/wokeypad.js.map +0 -1
- package/dist/device/woleak.d.ts +0 -20
- package/dist/device/woleak.d.ts.map +0 -1
- package/dist/device/woleak.js +0 -52
- package/dist/device/woleak.js.map +0 -1
- package/dist/device/woplugmini.d.ts +0 -52
- package/dist/device/woplugmini.d.ts.map +0 -1
- package/dist/device/woplugmini.js +0 -110
- package/dist/device/woplugmini.js.map +0 -1
- package/dist/device/woplugmini_jp.d.ts +0 -52
- package/dist/device/woplugmini_jp.d.ts.map +0 -1
- package/dist/device/woplugmini_jp.js +0 -110
- package/dist/device/woplugmini_jp.js.map +0 -1
- package/dist/device/wopresence.d.ts +0 -19
- package/dist/device/wopresence.d.ts.map +0 -1
- package/dist/device/wopresence.js +0 -39
- package/dist/device/wopresence.js.map +0 -1
- package/dist/device/worelayswitch1plus.d.ts +0 -36
- package/dist/device/worelayswitch1plus.d.ts.map +0 -1
- package/dist/device/worelayswitch1plus.js +0 -141
- package/dist/device/worelayswitch1plus.js.map +0 -1
- package/dist/device/worelayswitch1pm.d.ts +0 -36
- package/dist/device/worelayswitch1pm.d.ts.map +0 -1
- package/dist/device/worelayswitch1pm.js +0 -144
- package/dist/device/worelayswitch1pm.js.map +0 -1
- package/dist/device/wosensorth.d.ts +0 -13
- package/dist/device/wosensorth.d.ts.map +0 -1
- package/dist/device/wosensorth.js +0 -38
- package/dist/device/wosensorth.js.map +0 -1
- package/dist/device/wosensorthplus.d.ts +0 -13
- package/dist/device/wosensorthplus.d.ts.map +0 -1
- package/dist/device/wosensorthplus.js +0 -38
- package/dist/device/wosensorthplus.js.map +0 -1
- package/dist/device/wosensorthpro.d.ts +0 -13
- package/dist/device/wosensorthpro.d.ts.map +0 -1
- package/dist/device/wosensorthpro.js +0 -38
- package/dist/device/wosensorthpro.js.map +0 -1
- package/dist/device/wosensorthproco2.d.ts +0 -13
- package/dist/device/wosensorthproco2.d.ts.map +0 -1
- package/dist/device/wosensorthproco2.js +0 -40
- package/dist/device/wosensorthproco2.js.map +0 -1
- package/dist/device/wosmartlock.d.ts +0 -86
- package/dist/device/wosmartlock.d.ts.map +0 -1
- package/dist/device/wosmartlock.js +0 -198
- package/dist/device/wosmartlock.js.map +0 -1
- package/dist/device/wosmartlockpro.d.ts +0 -86
- package/dist/device/wosmartlockpro.d.ts.map +0 -1
- package/dist/device/wosmartlockpro.js +0 -200
- package/dist/device/wosmartlockpro.js.map +0 -1
- package/dist/device/wostrip.d.ts +0 -63
- package/dist/device/wostrip.d.ts.map +0 -1
- package/dist/device/wostrip.js +0 -131
- package/dist/device/wostrip.js.map +0 -1
- package/dist/test/advertising.test.d.ts +0 -2
- package/dist/test/advertising.test.d.ts.map +0 -1
- package/dist/test/advertising.test.js +0 -99
- package/dist/test/advertising.test.js.map +0 -1
- package/dist/test/device.test.d.ts +0 -2
- package/dist/test/device.test.d.ts.map +0 -1
- package/dist/test/device.test.js +0 -38
- package/dist/test/device.test.js.map +0 -1
- package/dist/test/index.test.js.map +0 -1
- package/dist/test/parameter-checker.test.d.ts +0 -2
- package/dist/test/parameter-checker.test.d.ts.map +0 -1
- package/dist/test/parameter-checker.test.js +0 -108
- package/dist/test/parameter-checker.test.js.map +0 -1
- package/dist/test/switchbot-ble.test.d.ts +0 -2
- package/dist/test/switchbot-ble.test.d.ts.map +0 -1
- package/dist/test/switchbot-ble.test.js +0 -45
- package/dist/test/switchbot-ble.test.js.map +0 -1
- package/dist/test/switchbot-openapi.test.d.ts +0 -2
- package/dist/test/switchbot-openapi.test.d.ts.map +0 -1
- package/dist/test/switchbot-openapi.test.js +0 -96
- package/dist/test/switchbot-openapi.test.js.map +0 -1
- package/dist/test/woblindtilt.test.d.ts +0 -2
- package/dist/test/woblindtilt.test.d.ts.map +0 -1
- package/dist/test/woblindtilt.test.js +0 -76
- package/dist/test/woblindtilt.test.js.map +0 -1
- package/dist/test/wobulb.test.d.ts +0 -2
- package/dist/test/wobulb.test.d.ts.map +0 -1
- package/dist/test/wobulb.test.js +0 -95
- package/dist/test/wobulb.test.js.map +0 -1
- package/dist/test/woceilinglight.test.d.ts +0 -2
- package/dist/test/woceilinglight.test.d.ts.map +0 -1
- package/dist/test/woceilinglight.test.js +0 -81
- package/dist/test/woceilinglight.test.js.map +0 -1
- package/dist/test/wocontact.test.d.ts +0 -2
- package/dist/test/wocontact.test.d.ts.map +0 -1
- package/dist/test/wocontact.test.js +0 -65
- package/dist/test/wocontact.test.js.map +0 -1
- package/dist/test/wocurtain.test.d.ts +0 -2
- package/dist/test/wocurtain.test.d.ts.map +0 -1
- package/dist/test/wocurtain.test.js +0 -75
- package/dist/test/wocurtain.test.js.map +0 -1
- package/dist/test/wohand.test.d.ts +0 -2
- package/dist/test/wohand.test.d.ts.map +0 -1
- package/dist/test/wohand.test.js +0 -56
- package/dist/test/wohand.test.js.map +0 -1
- package/dist/test/wohub2.test.d.ts +0 -2
- package/dist/test/wohub2.test.d.ts.map +0 -1
- package/dist/test/wohub2.test.js +0 -80
- package/dist/test/wohub2.test.js.map +0 -1
- package/dist/test/wohumi.test.d.ts +0 -2
- package/dist/test/wohumi.test.d.ts.map +0 -1
- package/dist/test/wohumi.test.js +0 -82
- package/dist/test/wohumi.test.js.map +0 -1
- package/dist/test/woiosensorth.test.d.ts +0 -2
- package/dist/test/woiosensorth.test.d.ts.map +0 -1
- package/dist/test/woiosensorth.test.js +0 -40
- package/dist/test/woiosensorth.test.js.map +0 -1
- package/dist/test/wokeypad.test.d.ts +0 -2
- package/dist/test/wokeypad.test.d.ts.map +0 -1
- package/dist/test/wokeypad.test.js +0 -46
- package/dist/test/wokeypad.test.js.map +0 -1
- package/dist/test/woleak.test.d.ts +0 -2
- package/dist/test/woleak.test.d.ts.map +0 -1
- package/dist/test/woleak.test.js +0 -51
- package/dist/test/woleak.test.js.map +0 -1
- package/dist/test/woplugmini.test.d.ts +0 -2
- package/dist/test/woplugmini.test.d.ts.map +0 -1
- package/dist/test/woplugmini.test.js +0 -90
- package/dist/test/woplugmini.test.js.map +0 -1
- package/dist/test/woplugmini_jp.test.d.ts +0 -2
- package/dist/test/woplugmini_jp.test.d.ts.map +0 -1
- package/dist/test/woplugmini_jp.test.js +0 -90
- package/dist/test/woplugmini_jp.test.js.map +0 -1
- package/dist/test/wopresence.test.d.ts +0 -2
- package/dist/test/wopresence.test.d.ts.map +0 -1
- package/dist/test/wopresence.test.js +0 -68
- package/dist/test/wopresence.test.js.map +0 -1
- package/dist/test/wosensorth.test.d.ts +0 -2
- package/dist/test/wosensorth.test.d.ts.map +0 -1
- package/dist/test/wosensorth.test.js +0 -29
- package/dist/test/wosensorth.test.js.map +0 -1
- package/dist/test/wosensorthplus.test.d.ts +0 -2
- package/dist/test/wosensorthplus.test.d.ts.map +0 -1
- package/dist/test/wosensorthplus.test.js +0 -29
- package/dist/test/wosensorthplus.test.js.map +0 -1
- package/dist/test/wosensorthpro.test.d.ts +0 -2
- package/dist/test/wosensorthpro.test.d.ts.map +0 -1
- package/dist/test/wosensorthpro.test.js +0 -29
- package/dist/test/wosensorthpro.test.js.map +0 -1
- package/dist/test/wosensorthproco2.test.d.ts +0 -2
- package/dist/test/wosensorthproco2.test.d.ts.map +0 -1
- package/dist/test/wosensorthproco2.test.js +0 -32
- package/dist/test/wosensorthproco2.test.js.map +0 -1
- package/dist/test/wosmartlock.test.d.ts +0 -2
- package/dist/test/wosmartlock.test.d.ts.map +0 -1
- package/dist/test/wosmartlock.test.js +0 -151
- package/dist/test/wosmartlock.test.js.map +0 -1
- package/dist/test/wosmartlockpro.test.d.ts +0 -2
- package/dist/test/wosmartlockpro.test.d.ts.map +0 -1
- package/dist/test/wosmartlockpro.test.js +0 -150
- package/dist/test/wosmartlockpro.test.js.map +0 -1
- package/dist/test/wostrip.test.d.ts +0 -2
- package/dist/test/wostrip.test.d.ts.map +0 -1
- package/dist/test/wostrip.test.js +0 -102
- package/dist/test/wostrip.test.js.map +0 -1
- package/docs/types/relaySwitch1PlusServiceData.html +0 -1
- /package/dist/{test/index.test.d.ts → index.test.d.ts} +0 -0
- /package/dist/{test/settings.test.d.ts → settings.test.d.ts} +0 -0
package/dist/device.js
CHANGED
|
@@ -1,8 +1,171 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
+
import * as Crypto from 'node:crypto';
|
|
2
3
|
import { EventEmitter } from 'node:events';
|
|
3
|
-
import { Advertising } from './advertising.js';
|
|
4
4
|
import { parameterChecker } from './parameter-checker.js';
|
|
5
|
-
import { CHAR_UUID_DEVICE, CHAR_UUID_NOTIFY, CHAR_UUID_WRITE, READ_TIMEOUT_MSEC, SERV_UUID_PRIMARY, WRITE_TIMEOUT_MSEC } from './settings.js';
|
|
5
|
+
import { CHAR_UUID_DEVICE, CHAR_UUID_NOTIFY, CHAR_UUID_WRITE, READ_TIMEOUT_MSEC, SERV_UUID_PRIMARY, WoSmartLockCommands, WoSmartLockProCommands, WRITE_TIMEOUT_MSEC } from './settings.js';
|
|
6
|
+
const HUMIDIFIER_COMMAND_HEADER = '5701';
|
|
7
|
+
const TURN_ON_KEY = `${HUMIDIFIER_COMMAND_HEADER}0101`;
|
|
8
|
+
const TURN_OFF_KEY = `${HUMIDIFIER_COMMAND_HEADER}0102`;
|
|
9
|
+
const INCREASE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0103`;
|
|
10
|
+
const DECREASE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0104`;
|
|
11
|
+
const SET_AUTO_MODE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0105`;
|
|
12
|
+
const SET_MANUAL_MODE_KEY = `${HUMIDIFIER_COMMAND_HEADER}0106`;
|
|
13
|
+
export var SwitchBotModel;
|
|
14
|
+
(function (SwitchBotModel) {
|
|
15
|
+
SwitchBotModel["HubMini"] = "W0202200";
|
|
16
|
+
SwitchBotModel["HubPlus"] = "SwitchBot Hub S1";
|
|
17
|
+
SwitchBotModel["Hub2"] = "W3202100";
|
|
18
|
+
SwitchBotModel["Bot"] = "SwitchBot S1";
|
|
19
|
+
SwitchBotModel["Curtain"] = "W0701600";
|
|
20
|
+
SwitchBotModel["Curtain3"] = "W2400000";
|
|
21
|
+
SwitchBotModel["Humidifier"] = "W0801800";
|
|
22
|
+
SwitchBotModel["Humidifier2"] = "WXXXXXXX";
|
|
23
|
+
SwitchBotModel["Plug"] = "SP11";
|
|
24
|
+
SwitchBotModel["Meter"] = "SwitchBot MeterTH S1";
|
|
25
|
+
SwitchBotModel["MeterPlusJP"] = "W2201500";
|
|
26
|
+
SwitchBotModel["MeterPlusUS"] = "W2301500";
|
|
27
|
+
SwitchBotModel["MeterPro"] = "W4900000";
|
|
28
|
+
SwitchBotModel["MeterProCO2"] = "W4900010";
|
|
29
|
+
SwitchBotModel["OutdoorMeter"] = "W3400010";
|
|
30
|
+
SwitchBotModel["MotionSensor"] = "W1101500";
|
|
31
|
+
SwitchBotModel["ContactSensor"] = "W1201500";
|
|
32
|
+
SwitchBotModel["ColorBulb"] = "W1401400";
|
|
33
|
+
SwitchBotModel["StripLight"] = "W1701100";
|
|
34
|
+
SwitchBotModel["PlugMiniUS"] = "W1901400/W1901401";
|
|
35
|
+
SwitchBotModel["PlugMiniJP"] = "W2001400/W2001401";
|
|
36
|
+
SwitchBotModel["Lock"] = "W1601700";
|
|
37
|
+
SwitchBotModel["LockPro"] = "W3500000";
|
|
38
|
+
SwitchBotModel["Keypad"] = "W2500010";
|
|
39
|
+
SwitchBotModel["KeypadTouch"] = "W2500020";
|
|
40
|
+
SwitchBotModel["K10"] = "K10+";
|
|
41
|
+
SwitchBotModel["K10Pro"] = "K10+ Pro";
|
|
42
|
+
SwitchBotModel["WoSweeper"] = "WoSweeper";
|
|
43
|
+
SwitchBotModel["WoSweeperMini"] = "WoSweeperMini";
|
|
44
|
+
SwitchBotModel["RobotVacuumCleanerS1"] = "W3011000";
|
|
45
|
+
SwitchBotModel["RobotVacuumCleanerS1Plus"] = "W3011010";
|
|
46
|
+
SwitchBotModel["RobotVacuumCleanerS10"] = "W3211800";
|
|
47
|
+
SwitchBotModel["Remote"] = "Remote";
|
|
48
|
+
SwitchBotModel["UniversalRemote"] = "UniversalRemote";
|
|
49
|
+
SwitchBotModel["CeilingLight"] = "W2612230/W2612240";
|
|
50
|
+
SwitchBotModel["CeilingLightPro"] = "W2612210/W2612220";
|
|
51
|
+
SwitchBotModel["IndoorCam"] = "W1301200";
|
|
52
|
+
SwitchBotModel["PanTiltCam"] = "W1801200";
|
|
53
|
+
SwitchBotModel["PanTiltCam2K"] = "W3101100";
|
|
54
|
+
SwitchBotModel["BlindTilt"] = "W2701600";
|
|
55
|
+
SwitchBotModel["BatteryCirculatorFan"] = "W3800510";
|
|
56
|
+
SwitchBotModel["CirculatorFan"] = "W3800511";
|
|
57
|
+
SwitchBotModel["WaterDetector"] = "W4402000";
|
|
58
|
+
SwitchBotModel["RelaySwitch1"] = "W5502300";
|
|
59
|
+
SwitchBotModel["RelaySwitch1PM"] = "W5502310";
|
|
60
|
+
SwitchBotModel["Unknown"] = "Unknown";
|
|
61
|
+
})(SwitchBotModel || (SwitchBotModel = {}));
|
|
62
|
+
export var SwitchBotBLEModel;
|
|
63
|
+
(function (SwitchBotBLEModel) {
|
|
64
|
+
SwitchBotBLEModel["Bot"] = "H";
|
|
65
|
+
SwitchBotBLEModel["Curtain"] = "c";
|
|
66
|
+
SwitchBotBLEModel["Curtain3"] = "{";
|
|
67
|
+
SwitchBotBLEModel["Humidifier"] = "e";
|
|
68
|
+
SwitchBotBLEModel["Humidifier2"] = "#";
|
|
69
|
+
SwitchBotBLEModel["Meter"] = "T";
|
|
70
|
+
SwitchBotBLEModel["MeterPlus"] = "i";
|
|
71
|
+
SwitchBotBLEModel["MeterPro"] = "4";
|
|
72
|
+
SwitchBotBLEModel["MeterProCO2"] = "5";
|
|
73
|
+
SwitchBotBLEModel["Hub2"] = "v";
|
|
74
|
+
SwitchBotBLEModel["OutdoorMeter"] = "w";
|
|
75
|
+
SwitchBotBLEModel["MotionSensor"] = "s";
|
|
76
|
+
SwitchBotBLEModel["ContactSensor"] = "d";
|
|
77
|
+
SwitchBotBLEModel["ColorBulb"] = "u";
|
|
78
|
+
SwitchBotBLEModel["StripLight"] = "r";
|
|
79
|
+
SwitchBotBLEModel["PlugMiniUS"] = "g";
|
|
80
|
+
SwitchBotBLEModel["PlugMiniJP"] = "j";
|
|
81
|
+
SwitchBotBLEModel["Lock"] = "o";
|
|
82
|
+
SwitchBotBLEModel["LockPro"] = "$";
|
|
83
|
+
SwitchBotBLEModel["CeilingLight"] = "q";
|
|
84
|
+
SwitchBotBLEModel["CeilingLightPro"] = "n";
|
|
85
|
+
SwitchBotBLEModel["BlindTilt"] = "x";
|
|
86
|
+
SwitchBotBLEModel["Leak"] = "&";
|
|
87
|
+
SwitchBotBLEModel["Keypad"] = "y";
|
|
88
|
+
SwitchBotBLEModel["RelaySwitch1"] = ";";
|
|
89
|
+
SwitchBotBLEModel["RelaySwitch1PM"] = "<";
|
|
90
|
+
SwitchBotBLEModel["Remote"] = "b";
|
|
91
|
+
SwitchBotBLEModel["Unknown"] = "Unknown";
|
|
92
|
+
})(SwitchBotBLEModel || (SwitchBotBLEModel = {}));
|
|
93
|
+
export var SwitchBotBLEModelName;
|
|
94
|
+
(function (SwitchBotBLEModelName) {
|
|
95
|
+
SwitchBotBLEModelName["Bot"] = "WoHand";
|
|
96
|
+
SwitchBotBLEModelName["Hub2"] = "WoHub2";
|
|
97
|
+
SwitchBotBLEModelName["ColorBulb"] = "WoBulb";
|
|
98
|
+
SwitchBotBLEModelName["Curtain"] = "WoCurtain";
|
|
99
|
+
SwitchBotBLEModelName["Curtain3"] = "WoCurtain3";
|
|
100
|
+
SwitchBotBLEModelName["Humidifier"] = "WoHumi";
|
|
101
|
+
SwitchBotBLEModelName["Humidifier2"] = "WoHumi2";
|
|
102
|
+
SwitchBotBLEModelName["Meter"] = "WoSensorTH";
|
|
103
|
+
SwitchBotBLEModelName["MeterPlus"] = "WoSensorTHPlus";
|
|
104
|
+
SwitchBotBLEModelName["MeterPro"] = "WoSensorTHP";
|
|
105
|
+
SwitchBotBLEModelName["MeterProCO2"] = "WoSensorTHPc";
|
|
106
|
+
SwitchBotBLEModelName["Lock"] = "WoSmartLock";
|
|
107
|
+
SwitchBotBLEModelName["LockPro"] = "WoSmartLockPro";
|
|
108
|
+
SwitchBotBLEModelName["PlugMini"] = "WoPlugMini";
|
|
109
|
+
SwitchBotBLEModelName["StripLight"] = "WoStrip";
|
|
110
|
+
SwitchBotBLEModelName["OutdoorMeter"] = "WoIOSensorTH";
|
|
111
|
+
SwitchBotBLEModelName["ContactSensor"] = "WoContact";
|
|
112
|
+
SwitchBotBLEModelName["MotionSensor"] = "WoMotion";
|
|
113
|
+
SwitchBotBLEModelName["BlindTilt"] = "WoBlindTilt";
|
|
114
|
+
SwitchBotBLEModelName["CeilingLight"] = "WoCeilingLight";
|
|
115
|
+
SwitchBotBLEModelName["CeilingLightPro"] = "WoCeilingLightPro";
|
|
116
|
+
SwitchBotBLEModelName["Leak"] = "WoLeakDetector";
|
|
117
|
+
SwitchBotBLEModelName["Keypad"] = "WoKeypad";
|
|
118
|
+
SwitchBotBLEModelName["RelaySwitch1"] = "WoRelaySwitch1Plus";
|
|
119
|
+
SwitchBotBLEModelName["RelaySwitch1PM"] = "WoRelaySwitch1PM";
|
|
120
|
+
SwitchBotBLEModelName["Remote"] = "WoRemote";
|
|
121
|
+
SwitchBotBLEModelName["Unknown"] = "Unknown";
|
|
122
|
+
})(SwitchBotBLEModelName || (SwitchBotBLEModelName = {}));
|
|
123
|
+
export var SwitchBotBLEModelFriendlyName;
|
|
124
|
+
(function (SwitchBotBLEModelFriendlyName) {
|
|
125
|
+
SwitchBotBLEModelFriendlyName["Bot"] = "Bot";
|
|
126
|
+
SwitchBotBLEModelFriendlyName["Hub2"] = "Hub 2";
|
|
127
|
+
SwitchBotBLEModelFriendlyName["ColorBulb"] = "Color Bulb";
|
|
128
|
+
SwitchBotBLEModelFriendlyName["Curtain"] = "Curtain";
|
|
129
|
+
SwitchBotBLEModelFriendlyName["Curtain3"] = "Curtain 3";
|
|
130
|
+
SwitchBotBLEModelFriendlyName["Humidifier"] = "Humidifier";
|
|
131
|
+
SwitchBotBLEModelFriendlyName["Humidifier2"] = "Humidifier2";
|
|
132
|
+
SwitchBotBLEModelFriendlyName["Meter"] = "Meter";
|
|
133
|
+
SwitchBotBLEModelFriendlyName["Lock"] = "Lock";
|
|
134
|
+
SwitchBotBLEModelFriendlyName["LockPro"] = "Lock Pro";
|
|
135
|
+
SwitchBotBLEModelFriendlyName["PlugMini"] = "Plug Mini";
|
|
136
|
+
SwitchBotBLEModelFriendlyName["StripLight"] = "Strip Light";
|
|
137
|
+
SwitchBotBLEModelFriendlyName["MeterPlus"] = "Meter Plus";
|
|
138
|
+
SwitchBotBLEModelFriendlyName["MeterPro"] = "Meter Pro";
|
|
139
|
+
SwitchBotBLEModelFriendlyName["MeterProCO2"] = "Meter Pro CO2";
|
|
140
|
+
SwitchBotBLEModelFriendlyName["BatteryCirculatorFan"] = "Battery Circulator Fan";
|
|
141
|
+
SwitchBotBLEModelFriendlyName["CirculatorFan"] = "Circulator Fan";
|
|
142
|
+
SwitchBotBLEModelFriendlyName["OutdoorMeter"] = "Outdoor Meter";
|
|
143
|
+
SwitchBotBLEModelFriendlyName["ContactSensor"] = "Contact Sensor";
|
|
144
|
+
SwitchBotBLEModelFriendlyName["MotionSensor"] = "Motion Sensor";
|
|
145
|
+
SwitchBotBLEModelFriendlyName["BlindTilt"] = "Blind Tilt";
|
|
146
|
+
SwitchBotBLEModelFriendlyName["CeilingLight"] = "Ceiling Light";
|
|
147
|
+
SwitchBotBLEModelFriendlyName["CeilingLightPro"] = "Ceiling Light Pro";
|
|
148
|
+
SwitchBotBLEModelFriendlyName["Leak"] = "Water Detector";
|
|
149
|
+
SwitchBotBLEModelFriendlyName["Keypad"] = "Keypad";
|
|
150
|
+
SwitchBotBLEModelFriendlyName["RelaySwitch1"] = "Relay Switch 1";
|
|
151
|
+
SwitchBotBLEModelFriendlyName["RelaySwitch1PM"] = "Relay Switch 1PM";
|
|
152
|
+
SwitchBotBLEModelFriendlyName["Remote"] = "Remote";
|
|
153
|
+
SwitchBotBLEModelFriendlyName["Unknown"] = "Unknown";
|
|
154
|
+
})(SwitchBotBLEModelFriendlyName || (SwitchBotBLEModelFriendlyName = {}));
|
|
155
|
+
/**
|
|
156
|
+
* Enum for log levels.
|
|
157
|
+
*/
|
|
158
|
+
export var LogLevel;
|
|
159
|
+
(function (LogLevel) {
|
|
160
|
+
LogLevel["SUCCESS"] = "success";
|
|
161
|
+
LogLevel["DEBUGSUCCESS"] = "debugsuccess";
|
|
162
|
+
LogLevel["WARN"] = "warn";
|
|
163
|
+
LogLevel["DEBUGWARN"] = "debugwarn";
|
|
164
|
+
LogLevel["ERROR"] = "error";
|
|
165
|
+
LogLevel["DEBUGERROR"] = "debugerror";
|
|
166
|
+
LogLevel["DEBUG"] = "debug";
|
|
167
|
+
LogLevel["INFO"] = "info";
|
|
168
|
+
})(LogLevel || (LogLevel = {}));
|
|
6
169
|
/**
|
|
7
170
|
* Represents a Switchbot Device.
|
|
8
171
|
*/
|
|
@@ -343,4 +506,2317 @@ export class SwitchbotDevice extends EventEmitter {
|
|
|
343
506
|
}
|
|
344
507
|
}
|
|
345
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Represents the advertising data parser for SwitchBot devices.
|
|
511
|
+
*/
|
|
512
|
+
export class Advertising {
|
|
513
|
+
constructor() { }
|
|
514
|
+
/**
|
|
515
|
+
* Parses the advertisement data coming from SwitchBot device.
|
|
516
|
+
*
|
|
517
|
+
* This function processes advertising packets received from SwitchBot devices
|
|
518
|
+
* and extracts relevant information based on the device type.
|
|
519
|
+
*
|
|
520
|
+
* @param {NobleTypes['peripheral']} peripheral - The peripheral device object from noble.
|
|
521
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
522
|
+
* @returns {Promise<Ad | null>} - An object containing parsed data specific to the SwitchBot device type, or `null` if the device is not recognized.
|
|
523
|
+
*/
|
|
524
|
+
static async parse(peripheral, emitLog) {
|
|
525
|
+
const ad = peripheral.advertisement;
|
|
526
|
+
if (!ad || !ad.serviceData) {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
const serviceData = ad.serviceData[0]?.data;
|
|
530
|
+
const manufacturerData = ad.manufacturerData;
|
|
531
|
+
if (!Advertising.validateBuffer(serviceData) || !Advertising.validateBuffer(manufacturerData)) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
const model = serviceData.subarray(0, 1).toString('utf8');
|
|
535
|
+
const sd = await Advertising.parseServiceData(model, serviceData, manufacturerData, emitLog);
|
|
536
|
+
if (!sd) {
|
|
537
|
+
// emitLog('debugerror', `[parseAdvertising.${peripheral.id}.${model}] return null, parsed serviceData empty!`)
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const address = Advertising.formatAddress(peripheral);
|
|
541
|
+
const data = {
|
|
542
|
+
id: peripheral.id,
|
|
543
|
+
address,
|
|
544
|
+
rssi: peripheral.rssi,
|
|
545
|
+
serviceData: {
|
|
546
|
+
model,
|
|
547
|
+
modelName: sd.modelName || '',
|
|
548
|
+
modelFriendlyName: sd.modelFriendlyName || '',
|
|
549
|
+
...sd,
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
emitLog('debug', `[parseAdvertising.${peripheral.id}.${model}] return ${JSON.stringify(data)}`);
|
|
553
|
+
return data;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Validates if the buffer is a valid Buffer object with a minimum length.
|
|
557
|
+
*
|
|
558
|
+
* @param {any} buffer - The buffer to validate.
|
|
559
|
+
* @returns {boolean} - True if the buffer is valid, false otherwise.
|
|
560
|
+
*/
|
|
561
|
+
static validateBuffer(buffer) {
|
|
562
|
+
return buffer && Buffer.isBuffer(buffer) && buffer.length >= 3;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Parses the service data based on the device model.
|
|
566
|
+
*
|
|
567
|
+
* @param {string} model - The device model.
|
|
568
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
569
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
570
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
571
|
+
* @returns {Promise<any>} - The parsed service data.
|
|
572
|
+
*/
|
|
573
|
+
static async parseServiceData(model, serviceData, manufacturerData, emitLog) {
|
|
574
|
+
switch (model) {
|
|
575
|
+
case SwitchBotBLEModel.Bot:
|
|
576
|
+
return WoHand.parseServiceData(serviceData, emitLog);
|
|
577
|
+
case SwitchBotBLEModel.Curtain:
|
|
578
|
+
case SwitchBotBLEModel.Curtain3:
|
|
579
|
+
return WoCurtain.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
580
|
+
case SwitchBotBLEModel.Humidifier:
|
|
581
|
+
return WoHumi.parseServiceData(serviceData, emitLog);
|
|
582
|
+
case SwitchBotBLEModel.Humidifier2:
|
|
583
|
+
return WoHumi2.parseServiceData(serviceData, emitLog);
|
|
584
|
+
case SwitchBotBLEModel.Meter:
|
|
585
|
+
return WoSensorTH.parseServiceData(serviceData, emitLog);
|
|
586
|
+
case SwitchBotBLEModel.MeterPlus:
|
|
587
|
+
return WoSensorTHPlus.parseServiceData(serviceData, emitLog);
|
|
588
|
+
case SwitchBotBLEModel.MeterPro:
|
|
589
|
+
return WoSensorTHPro.parseServiceData(serviceData, emitLog);
|
|
590
|
+
case SwitchBotBLEModel.MeterProCO2:
|
|
591
|
+
return WoSensorTHProCO2.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
592
|
+
case SwitchBotBLEModel.Hub2:
|
|
593
|
+
return WoHub2.parseServiceData(manufacturerData, emitLog);
|
|
594
|
+
case SwitchBotBLEModel.OutdoorMeter:
|
|
595
|
+
return WoIOSensorTH.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
596
|
+
case SwitchBotBLEModel.MotionSensor:
|
|
597
|
+
return WoPresence.parseServiceData(serviceData, emitLog);
|
|
598
|
+
case SwitchBotBLEModel.ContactSensor:
|
|
599
|
+
return WoContact.parseServiceData(serviceData, emitLog);
|
|
600
|
+
case SwitchBotBLEModel.Remote:
|
|
601
|
+
return WoRemote.parseServiceData(serviceData, emitLog);
|
|
602
|
+
case SwitchBotBLEModel.ColorBulb:
|
|
603
|
+
return WoBulb.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
604
|
+
case SwitchBotBLEModel.CeilingLight:
|
|
605
|
+
return WoCeilingLight.parseServiceData(manufacturerData, emitLog);
|
|
606
|
+
case SwitchBotBLEModel.CeilingLightPro:
|
|
607
|
+
return WoCeilingLight.parseServiceData_Pro(manufacturerData, emitLog);
|
|
608
|
+
case SwitchBotBLEModel.StripLight:
|
|
609
|
+
return WoStrip.parseServiceData(serviceData, emitLog);
|
|
610
|
+
case SwitchBotBLEModel.PlugMiniUS:
|
|
611
|
+
return WoPlugMiniUS.parseServiceData(manufacturerData, emitLog);
|
|
612
|
+
case SwitchBotBLEModel.PlugMiniJP:
|
|
613
|
+
return WoPlugMiniJP.parseServiceData(manufacturerData, emitLog);
|
|
614
|
+
case SwitchBotBLEModel.Lock:
|
|
615
|
+
return WoSmartLock.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
616
|
+
case SwitchBotBLEModel.LockPro:
|
|
617
|
+
return WoSmartLockPro.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
618
|
+
case SwitchBotBLEModel.BlindTilt:
|
|
619
|
+
return WoBlindTilt.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
620
|
+
case SwitchBotBLEModel.Leak:
|
|
621
|
+
return WoLeak.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
622
|
+
case SwitchBotBLEModel.RelaySwitch1:
|
|
623
|
+
return WoRelaySwitch1.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
624
|
+
case SwitchBotBLEModel.RelaySwitch1PM:
|
|
625
|
+
return WoRelaySwitch1PM.parseServiceData(serviceData, manufacturerData, emitLog);
|
|
626
|
+
default:
|
|
627
|
+
emitLog('debug', `[parseAdvertising.${model}] return null, model "${model}" not available!`);
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Formats the address of the peripheral.
|
|
633
|
+
*
|
|
634
|
+
* @param {NobleTypes['peripheral']} peripheral - The peripheral device object from noble.
|
|
635
|
+
* @returns {string} - The formatted address.
|
|
636
|
+
*/
|
|
637
|
+
static formatAddress(peripheral) {
|
|
638
|
+
let address = peripheral.address || '';
|
|
639
|
+
if (address === '') {
|
|
640
|
+
const str = peripheral.advertisement.manufacturerData?.toString('hex').slice(4, 16) || '';
|
|
641
|
+
if (str !== '') {
|
|
642
|
+
address = str.match(/.{1,2}/g)?.join(':') || '';
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
address = address.replace(/-/g, ':');
|
|
647
|
+
}
|
|
648
|
+
return address;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Class representing a WoBlindTilt device.
|
|
653
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain.md
|
|
654
|
+
*/
|
|
655
|
+
export class WoBlindTilt extends SwitchbotDevice {
|
|
656
|
+
reverse = false;
|
|
657
|
+
/**
|
|
658
|
+
* Parses the service data and manufacturer data for the WoBlindTilt device.
|
|
659
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
660
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
661
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
662
|
+
* @param {boolean} [reverse] - Whether to reverse the tilt percentage.
|
|
663
|
+
* @returns {Promise<blindTiltServiceData | null>} - The parsed data object or null if the data is invalid.
|
|
664
|
+
*/
|
|
665
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog, reverse = false) {
|
|
666
|
+
if (![5, 6].includes(manufacturerData.length)) {
|
|
667
|
+
emitLog('debugerror', `[parseServiceDataForWoBlindTilt] Buffer length ${manufacturerData.length} !== 5 or 6!`);
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
const byte2 = serviceData.readUInt8(2);
|
|
671
|
+
const byte6 = manufacturerData.subarray(6);
|
|
672
|
+
const tilt = Math.max(Math.min(byte6.readUInt8(2) & 0b01111111, 100), 0);
|
|
673
|
+
const inMotion = !!(byte2 & 0b10000000);
|
|
674
|
+
const lightLevel = (byte6.readUInt8(1) >> 4) & 0b00001111;
|
|
675
|
+
const calibration = !!(byte6.readUInt8(1) & 0b00000001);
|
|
676
|
+
const sequenceNumber = byte6.readUInt8(0);
|
|
677
|
+
const battery = serviceData.length > 2 ? byte2 & 0b01111111 : 0;
|
|
678
|
+
const data = {
|
|
679
|
+
model: SwitchBotBLEModel.BlindTilt,
|
|
680
|
+
modelName: SwitchBotBLEModelName.BlindTilt,
|
|
681
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.BlindTilt,
|
|
682
|
+
calibration,
|
|
683
|
+
battery,
|
|
684
|
+
inMotion,
|
|
685
|
+
tilt: reverse ? 100 - tilt : tilt,
|
|
686
|
+
lightLevel,
|
|
687
|
+
sequenceNumber,
|
|
688
|
+
};
|
|
689
|
+
return data;
|
|
690
|
+
}
|
|
691
|
+
constructor(peripheral, noble) {
|
|
692
|
+
super(peripheral, noble);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Opens the blind tilt to the fully open position.
|
|
696
|
+
* @returns {Promise<void>}
|
|
697
|
+
*/
|
|
698
|
+
async open() {
|
|
699
|
+
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x32]);
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Closes the blind tilt up to the nearest endpoint.
|
|
703
|
+
* @returns {Promise<void>}
|
|
704
|
+
*/
|
|
705
|
+
async closeUp() {
|
|
706
|
+
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x64]);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Closes the blind tilt down to the nearest endpoint.
|
|
710
|
+
* @returns {Promise<void>}
|
|
711
|
+
*/
|
|
712
|
+
async closeDown() {
|
|
713
|
+
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x00]);
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Closes the blind tilt to the nearest endpoint.
|
|
717
|
+
* @returns {Promise<void>}
|
|
718
|
+
*/
|
|
719
|
+
async close() {
|
|
720
|
+
const position = await this.getPosition();
|
|
721
|
+
if (position > 50) {
|
|
722
|
+
await this.closeUp();
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
await this.closeDown();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Retrieves the current position of the blind tilt.
|
|
730
|
+
* @returns {Promise<number>} - The current position of the blind tilt (0-100).
|
|
731
|
+
*/
|
|
732
|
+
async getPosition() {
|
|
733
|
+
const tiltPosition = await this._getAdvValue('tilt');
|
|
734
|
+
return Math.max(0, Math.min(tiltPosition, 100));
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Retrieves the advertised value for a given key.
|
|
738
|
+
* @param {string} key - The key for the advertised value.
|
|
739
|
+
* @returns {Promise<number>} - The advertised value.
|
|
740
|
+
* @private
|
|
741
|
+
*/
|
|
742
|
+
async _getAdvValue(key) {
|
|
743
|
+
if (key === 'tilt') {
|
|
744
|
+
return 50; // Example value
|
|
745
|
+
}
|
|
746
|
+
throw new Error(`Unknown key: ${key}`);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Retrieves the basic information of the blind tilt.
|
|
750
|
+
* @returns {Promise<object | null>} - A promise that resolves to an object containing the basic information of the blind tilt.
|
|
751
|
+
*/
|
|
752
|
+
async getBasicInfo() {
|
|
753
|
+
const data = await this.getBasicInfo();
|
|
754
|
+
if (!data) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
const tilt = Math.max(Math.min(data[6], 100), 0);
|
|
758
|
+
const moving = Boolean(data[5] & 0b00000011);
|
|
759
|
+
let opening = false;
|
|
760
|
+
let closing = false;
|
|
761
|
+
let up = false;
|
|
762
|
+
if (moving) {
|
|
763
|
+
opening = Boolean(data[5] & 0b00000010);
|
|
764
|
+
closing = !opening && Boolean(data[5] & 0b00000001);
|
|
765
|
+
if (opening) {
|
|
766
|
+
const flag = Boolean(data[5] & 0b00000001);
|
|
767
|
+
up = flag ? this.reverse : !flag;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
up = tilt < 50 ? this.reverse : tilt > 50;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
battery: data[1],
|
|
775
|
+
firmware: data[2] / 10.0,
|
|
776
|
+
light: Boolean(data[4] & 0b00100000),
|
|
777
|
+
fault: Boolean(data[4] & 0b00001000),
|
|
778
|
+
solarPanel: Boolean(data[5] & 0b00001000),
|
|
779
|
+
calibration: Boolean(data[5] & 0b00000100),
|
|
780
|
+
calibrated: Boolean(data[5] & 0b00000100),
|
|
781
|
+
inMotion: moving,
|
|
782
|
+
motionDirection: {
|
|
783
|
+
opening: moving && opening,
|
|
784
|
+
closing: moving && closing,
|
|
785
|
+
up: moving && up,
|
|
786
|
+
down: moving && !up,
|
|
787
|
+
},
|
|
788
|
+
tilt: this.reverse ? 100 - tilt : tilt,
|
|
789
|
+
timers: data[7],
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Pauses the blind tilt operation.
|
|
794
|
+
* @returns {Promise<void>}
|
|
795
|
+
*/
|
|
796
|
+
async pause() {
|
|
797
|
+
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x00, 0xFF]);
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Runs the blind tilt to the specified position.
|
|
801
|
+
* @param {number} percent - The target position percentage (0-100).
|
|
802
|
+
* @param {number} mode - The running mode (0 or 1).
|
|
803
|
+
* @returns {Promise<void>}
|
|
804
|
+
*/
|
|
805
|
+
async runToPos(percent, mode) {
|
|
806
|
+
if (typeof percent !== 'number' || percent < 0 || percent > 100) {
|
|
807
|
+
throw new RangeError('Percent must be a number between 0 and 100');
|
|
808
|
+
}
|
|
809
|
+
if (typeof mode !== 'number' || mode < 0 || mode > 1) {
|
|
810
|
+
throw new RangeError('Mode must be a number between 0 and 1');
|
|
811
|
+
}
|
|
812
|
+
await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, mode, percent]);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Sends a command to operate the blind tilt and handles the response.
|
|
816
|
+
* @param {number[]} bytes - The byte array representing the command to be sent to the device.
|
|
817
|
+
* @returns {Promise<void>}
|
|
818
|
+
* @private
|
|
819
|
+
*/
|
|
820
|
+
async operateBlindTilt(bytes) {
|
|
821
|
+
const reqBuf = Buffer.from(bytes);
|
|
822
|
+
const resBuf = await this.command(reqBuf);
|
|
823
|
+
if (resBuf.length !== 3 || resBuf.readUInt8(0) !== 0x01) {
|
|
824
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Class representing a WoBulb device.
|
|
830
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/colorbulb.md
|
|
831
|
+
*/
|
|
832
|
+
export class WoBulb extends SwitchbotDevice {
|
|
833
|
+
/**
|
|
834
|
+
* Parses the service data for WoBulb.
|
|
835
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
836
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
837
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
838
|
+
* @returns {Promise<colorBulbServiceData | null>} - Parsed service data or null if invalid.
|
|
839
|
+
*/
|
|
840
|
+
static async parseServiceData(serviceData, manufacturerData,
|
|
841
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
842
|
+
emitLog) {
|
|
843
|
+
if (serviceData.length !== 18) {
|
|
844
|
+
// emitLog('debugerror', `[parseServiceDataForWoBulb] Buffer length ${serviceData.length} !== 18!`)
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
if (manufacturerData.length !== 13) {
|
|
848
|
+
// emitLog('debugerror', `[parseServiceDataForWoBulb] Buffer length ${manufacturerData.length} !== 13!`)
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
const [, byte1, , byte3, byte4, byte5, byte6, byte7, byte8, byte9, byte10,] = manufacturerData;
|
|
852
|
+
const data = {
|
|
853
|
+
model: SwitchBotBLEModel.ColorBulb,
|
|
854
|
+
modelName: SwitchBotBLEModelName.ColorBulb,
|
|
855
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.ColorBulb,
|
|
856
|
+
power: !!byte1,
|
|
857
|
+
red: byte3,
|
|
858
|
+
green: byte4,
|
|
859
|
+
blue: byte5,
|
|
860
|
+
color_temperature: byte6,
|
|
861
|
+
state: !!(byte7 & 0b01111111),
|
|
862
|
+
brightness: byte7 & 0b01111111,
|
|
863
|
+
delay: (byte8 & 0b10000000) >> 7,
|
|
864
|
+
preset: (byte8 & 0b00001000) >> 3,
|
|
865
|
+
color_mode: byte8 & 0b00000111,
|
|
866
|
+
speed: byte9 & 0b01111111,
|
|
867
|
+
loop_index: byte10 & 0b11111110,
|
|
868
|
+
};
|
|
869
|
+
return data;
|
|
870
|
+
}
|
|
871
|
+
constructor(peripheral, noble) {
|
|
872
|
+
super(peripheral, noble);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Reads the state of the bulb.
|
|
876
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is ON (true) or OFF (false).
|
|
877
|
+
*/
|
|
878
|
+
async readState() {
|
|
879
|
+
return this.operateBulb([0x57, 0x0F, 0x48, 0x01]);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Sets the state of the bulb.
|
|
883
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
884
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
885
|
+
* @private
|
|
886
|
+
*/
|
|
887
|
+
async setState(reqByteArray) {
|
|
888
|
+
const base = [0x57, 0x0F, 0x47, 0x01];
|
|
889
|
+
return this.operateBulb(base.concat(reqByteArray));
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Turns on the bulb.
|
|
893
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is ON (true).
|
|
894
|
+
*/
|
|
895
|
+
async turnOn() {
|
|
896
|
+
return this.setState([0x01, 0x01]);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Turns off the bulb.
|
|
900
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is OFF (false).
|
|
901
|
+
*/
|
|
902
|
+
async turnOff() {
|
|
903
|
+
return this.setState([0x01, 0x02]);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Sets the brightness of the bulb.
|
|
907
|
+
* @param {number} brightness - The brightness percentage (0-100).
|
|
908
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
909
|
+
*/
|
|
910
|
+
async setBrightness(brightness) {
|
|
911
|
+
if (brightness < 0 || brightness > 100) {
|
|
912
|
+
throw new RangeError('Brightness must be between 0 and 100');
|
|
913
|
+
}
|
|
914
|
+
return this.setState([0x02, 0x14, brightness]);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Sets the color temperature of the bulb.
|
|
918
|
+
* @param {number} color_temperature - The color temperature percentage (0-100).
|
|
919
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
920
|
+
*/
|
|
921
|
+
async setColorTemperature(color_temperature) {
|
|
922
|
+
if (color_temperature < 0 || color_temperature > 100) {
|
|
923
|
+
throw new RangeError('Color temperature must be between 0 and 100');
|
|
924
|
+
}
|
|
925
|
+
return this.setState([0x02, 0x17, color_temperature]);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Sets the RGB color of the bulb.
|
|
929
|
+
* @param {number} brightness - The brightness percentage (0-100).
|
|
930
|
+
* @param {number} red - The red color value (0-255).
|
|
931
|
+
* @param {number} green - The green color value (0-255).
|
|
932
|
+
* @param {number} blue - The blue color value (0-255).
|
|
933
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
934
|
+
*/
|
|
935
|
+
async setRGB(brightness, red, green, blue) {
|
|
936
|
+
if (brightness < 0 || brightness > 100 || red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) {
|
|
937
|
+
throw new RangeError('Invalid RGB or brightness values');
|
|
938
|
+
}
|
|
939
|
+
return this.setState([0x02, 0x12, brightness, red, green, blue]);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Sends a command to the bulb.
|
|
943
|
+
* @param {number[]} bytes - The command bytes.
|
|
944
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
945
|
+
* @private
|
|
946
|
+
*/
|
|
947
|
+
async operateBulb(bytes) {
|
|
948
|
+
const reqBuf = Buffer.from(bytes);
|
|
949
|
+
const resBuf = await this.command(reqBuf);
|
|
950
|
+
if (resBuf.length === 2) {
|
|
951
|
+
const code = resBuf.readUInt8(1);
|
|
952
|
+
if (code === 0x00 || code === 0x80) {
|
|
953
|
+
return code === 0x80;
|
|
954
|
+
}
|
|
955
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
956
|
+
}
|
|
957
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Class representing a WoCeilingLight device.
|
|
962
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/colorbulb.md
|
|
963
|
+
*/
|
|
964
|
+
export class WoCeilingLight extends SwitchbotDevice {
|
|
965
|
+
/**
|
|
966
|
+
* Parses the service data for WoCeilingLight.
|
|
967
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
968
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
969
|
+
* @returns {Promise<ceilingLightServiceData | null>} - Parsed service data or null if invalid.
|
|
970
|
+
*/
|
|
971
|
+
static async parseServiceData(manufacturerData, emitLog) {
|
|
972
|
+
if (manufacturerData.length !== 13) {
|
|
973
|
+
emitLog('debugerror', `[parseServiceDataForWoCeilingLight] Buffer length ${manufacturerData.length} !== 13!`);
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
const [, byte1, , byte3, byte4, byte5, byte6, byte7, byte8, byte9, byte10,] = manufacturerData;
|
|
977
|
+
const data = {
|
|
978
|
+
model: SwitchBotBLEModel.CeilingLight,
|
|
979
|
+
modelName: SwitchBotBLEModelName.CeilingLight,
|
|
980
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLight,
|
|
981
|
+
power: !!byte1,
|
|
982
|
+
red: byte3,
|
|
983
|
+
green: byte4,
|
|
984
|
+
blue: byte5,
|
|
985
|
+
color_temperature: byte6,
|
|
986
|
+
state: !!(byte7 & 0b01111111),
|
|
987
|
+
brightness: byte7 & 0b01111111,
|
|
988
|
+
delay: (byte8 & 0b10000000) ? 1 : 0,
|
|
989
|
+
preset: (byte8 & 0b00001000) ? 1 : 0,
|
|
990
|
+
color_mode: byte8 & 0b00000111,
|
|
991
|
+
speed: byte9 & 0b01111111,
|
|
992
|
+
loop_index: byte10 & 0b11111110,
|
|
993
|
+
};
|
|
994
|
+
return data;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Parses the service data for WoCeilingLight Pro.
|
|
998
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
999
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1000
|
+
* @returns {Promise<ceilingLightProServiceData | null>} - Parsed service data or null if invalid.
|
|
1001
|
+
*/
|
|
1002
|
+
static async parseServiceData_Pro(manufacturerData, emitLog) {
|
|
1003
|
+
if (manufacturerData.length !== 13) {
|
|
1004
|
+
emitLog('debugerror', `[parseServiceDataForWoCeilingLightPro] Buffer length ${manufacturerData.length} !== 13!`);
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
const [, byte1, , byte3, byte4, byte5, byte6, byte7, byte8, byte9, byte10,] = manufacturerData;
|
|
1008
|
+
const data = {
|
|
1009
|
+
model: SwitchBotBLEModel.CeilingLightPro,
|
|
1010
|
+
modelName: SwitchBotBLEModelName.CeilingLightPro,
|
|
1011
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLightPro,
|
|
1012
|
+
power: !!byte1,
|
|
1013
|
+
red: byte3,
|
|
1014
|
+
green: byte4,
|
|
1015
|
+
blue: byte5,
|
|
1016
|
+
color_temperature: byte6,
|
|
1017
|
+
state: !!(byte7 & 0b01111111),
|
|
1018
|
+
brightness: byte7 & 0b01111111,
|
|
1019
|
+
delay: (byte8 & 0b10000000) ? 1 : 0,
|
|
1020
|
+
preset: (byte8 & 0b00001000) ? 1 : 0,
|
|
1021
|
+
color_mode: byte8 & 0b00000111,
|
|
1022
|
+
speed: byte9 & 0b01111111,
|
|
1023
|
+
loop_index: byte10 & 0b11111110,
|
|
1024
|
+
};
|
|
1025
|
+
return data;
|
|
1026
|
+
}
|
|
1027
|
+
constructor(peripheral, noble) {
|
|
1028
|
+
super(peripheral, noble);
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Reads the state of the ceiling light.
|
|
1032
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the light is ON (true) or OFF (false).
|
|
1033
|
+
*/
|
|
1034
|
+
async readState() {
|
|
1035
|
+
return this.operateCeilingLight([0x57, 0x0F, 0x48, 0x01]);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Sets the state of the ceiling light.
|
|
1039
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
1040
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
1041
|
+
*/
|
|
1042
|
+
async setState(reqByteArray) {
|
|
1043
|
+
const base = [0x57, 0x0F, 0x47, 0x01];
|
|
1044
|
+
return this.operateCeilingLight(base.concat(reqByteArray));
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Turns on the ceiling light.
|
|
1048
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the light is ON (true).
|
|
1049
|
+
*/
|
|
1050
|
+
async turnOn() {
|
|
1051
|
+
return this.setState([0x01, 0x01]);
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Turns off the ceiling light.
|
|
1055
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the light is OFF (false).
|
|
1056
|
+
*/
|
|
1057
|
+
async turnOff() {
|
|
1058
|
+
return this.setState([0x01, 0x02]);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Sets the brightness of the ceiling light.
|
|
1062
|
+
* @param {number} brightness - The brightness percentage (0-100).
|
|
1063
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
1064
|
+
*/
|
|
1065
|
+
async setBrightness(brightness) {
|
|
1066
|
+
if (typeof brightness !== 'number' || brightness < 0 || brightness > 100) {
|
|
1067
|
+
throw new TypeError(`Invalid brightness value: ${brightness}`);
|
|
1068
|
+
}
|
|
1069
|
+
return this.setState([0x02, 0x14, brightness]);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Sets the color temperature of the ceiling light.
|
|
1073
|
+
* @param {number} color_temperature - The color temperature percentage (0-100).
|
|
1074
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
1075
|
+
*/
|
|
1076
|
+
async setColorTemperature(color_temperature) {
|
|
1077
|
+
if (typeof color_temperature !== 'number' || color_temperature < 0 || color_temperature > 100) {
|
|
1078
|
+
throw new TypeError(`Invalid color temperature value: ${color_temperature}`);
|
|
1079
|
+
}
|
|
1080
|
+
return this.setState([0x02, 0x17, color_temperature]);
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Sets the RGB color of the ceiling light.
|
|
1084
|
+
* @param {number} brightness - The brightness percentage (0-100).
|
|
1085
|
+
* @param {number} red - The red color value (0-255).
|
|
1086
|
+
* @param {number} green - The green color value (0-255).
|
|
1087
|
+
* @param {number} blue - The blue color value (0-255).
|
|
1088
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
1089
|
+
*/
|
|
1090
|
+
async setRGB(brightness, red, green, blue) {
|
|
1091
|
+
if (typeof brightness !== 'number' || brightness < 0 || brightness > 100
|
|
1092
|
+
|| typeof red !== 'number' || red < 0 || red > 255
|
|
1093
|
+
|| typeof green !== 'number' || green < 0 || green > 255
|
|
1094
|
+
|| typeof blue !== 'number' || blue < 0 || blue > 255) {
|
|
1095
|
+
throw new TypeError('Invalid RGB or brightness values');
|
|
1096
|
+
}
|
|
1097
|
+
return this.setState([0x02, 0x12, brightness, red, green, blue]);
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Sends a command to the ceiling light.
|
|
1101
|
+
* @param {number[]} bytes - The command bytes.
|
|
1102
|
+
* @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
|
|
1103
|
+
*/
|
|
1104
|
+
async operateCeilingLight(bytes) {
|
|
1105
|
+
const reqBuf = Buffer.from(bytes);
|
|
1106
|
+
const resBuf = await this.command(reqBuf);
|
|
1107
|
+
if (resBuf.length === 2) {
|
|
1108
|
+
const code = resBuf.readUInt8(1);
|
|
1109
|
+
if (code === 0x00 || code === 0x80) {
|
|
1110
|
+
return code === 0x80;
|
|
1111
|
+
}
|
|
1112
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1113
|
+
}
|
|
1114
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Class representing a WoContact device.
|
|
1119
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/contactsensor.md
|
|
1120
|
+
*/
|
|
1121
|
+
export class WoContact extends SwitchbotDevice {
|
|
1122
|
+
/**
|
|
1123
|
+
* Parses the service data for WoContact.
|
|
1124
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1125
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1126
|
+
* @returns {Promise<contactSensorServiceData | null>} - Parsed service data or null if invalid.
|
|
1127
|
+
*/
|
|
1128
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
1129
|
+
if (serviceData.length !== 9) {
|
|
1130
|
+
emitLog('debugerror', `[parseServiceDataForWoContact] Buffer length ${serviceData.length} !== 9!`);
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
const [byte1, byte2, byte3, , , , , , byte8] = serviceData;
|
|
1134
|
+
const hallState = (byte3 >> 1) & 0b00000011;
|
|
1135
|
+
const tested = Boolean(byte1 & 0b10000000);
|
|
1136
|
+
const movement = Boolean(byte1 & 0b01000000);
|
|
1137
|
+
const battery = byte2 & 0b01111111;
|
|
1138
|
+
const contact_open = Boolean(byte3 & 0b00000010);
|
|
1139
|
+
const contact_timeout = Boolean(byte3 & 0b00000100);
|
|
1140
|
+
const lightLevel = byte3 & 0b00000001 ? 'bright' : 'dark';
|
|
1141
|
+
const button_count = byte8 & 0b00001111;
|
|
1142
|
+
const doorState = hallState === 0 ? 'close' : hallState === 1 ? 'open' : 'timeout no closed';
|
|
1143
|
+
const data = {
|
|
1144
|
+
model: SwitchBotBLEModel.ContactSensor,
|
|
1145
|
+
modelName: SwitchBotBLEModelName.ContactSensor,
|
|
1146
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.ContactSensor,
|
|
1147
|
+
movement,
|
|
1148
|
+
tested,
|
|
1149
|
+
battery,
|
|
1150
|
+
contact_open,
|
|
1151
|
+
contact_timeout,
|
|
1152
|
+
lightLevel,
|
|
1153
|
+
button_count,
|
|
1154
|
+
doorState,
|
|
1155
|
+
};
|
|
1156
|
+
return data;
|
|
1157
|
+
}
|
|
1158
|
+
constructor(peripheral, noble) {
|
|
1159
|
+
super(peripheral, noble);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Class representing a WoCurtain device.
|
|
1164
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain.md
|
|
1165
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain3.md
|
|
1166
|
+
*/
|
|
1167
|
+
export class WoCurtain extends SwitchbotDevice {
|
|
1168
|
+
/**
|
|
1169
|
+
* Parses the service data for WoCurtain.
|
|
1170
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1171
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1172
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1173
|
+
* @param {boolean} [reverse] - Whether to reverse the position.
|
|
1174
|
+
* @returns {Promise<curtainServiceData | curtain3ServiceData | null>} - Parsed service data or null if invalid.
|
|
1175
|
+
*/
|
|
1176
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog, reverse = false) {
|
|
1177
|
+
if (![5, 6].includes(serviceData.length)) {
|
|
1178
|
+
emitLog('debugerror', `[parseServiceDataForWoCurtain] Buffer length ${serviceData.length} !== 5 or 6!`);
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
const byte1 = serviceData.readUInt8(1);
|
|
1182
|
+
const byte2 = serviceData.readUInt8(2);
|
|
1183
|
+
let deviceData;
|
|
1184
|
+
let batteryData = null;
|
|
1185
|
+
if (manufacturerData.length >= 13) {
|
|
1186
|
+
deviceData = manufacturerData.subarray(8, 11);
|
|
1187
|
+
batteryData = manufacturerData.readUInt8(12);
|
|
1188
|
+
}
|
|
1189
|
+
else if (manufacturerData.length >= 11) {
|
|
1190
|
+
deviceData = manufacturerData.subarray(8, 11);
|
|
1191
|
+
batteryData = byte2;
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
deviceData = serviceData.subarray(3, 6);
|
|
1195
|
+
batteryData = byte2;
|
|
1196
|
+
}
|
|
1197
|
+
const model = serviceData.subarray(0, 1).toString('utf8') ? SwitchBotBLEModel.Curtain : SwitchBotBLEModel.Curtain3;
|
|
1198
|
+
const calibration = Boolean(byte1 & 0b01000000);
|
|
1199
|
+
if (model === SwitchBotBLEModel.Curtain) {
|
|
1200
|
+
const byte3 = serviceData.readUInt8(3);
|
|
1201
|
+
const byte4 = serviceData.readUInt8(4);
|
|
1202
|
+
const position = byte3 & 0b01111111;
|
|
1203
|
+
const inMotion = Boolean(byte3 & 0b10000000);
|
|
1204
|
+
const lightLevel = (byte4 >> 4) & 0b00001111;
|
|
1205
|
+
const deviceChain = byte4 & 0b00000111;
|
|
1206
|
+
const battery = byte2 & 0b01111111;
|
|
1207
|
+
const data = {
|
|
1208
|
+
model: SwitchBotBLEModel.Curtain,
|
|
1209
|
+
modelName: SwitchBotBLEModelName.Curtain,
|
|
1210
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain,
|
|
1211
|
+
calibration,
|
|
1212
|
+
battery: battery ?? 0,
|
|
1213
|
+
inMotion,
|
|
1214
|
+
position: reverse ? 100 - position : position,
|
|
1215
|
+
lightLevel,
|
|
1216
|
+
deviceChain,
|
|
1217
|
+
};
|
|
1218
|
+
return data;
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
const position = Math.max(Math.min(deviceData.readUInt8(0) & 0b01111111, 100), 0);
|
|
1222
|
+
const inMotion = Boolean(deviceData.readUInt8(0) & 0b10000000);
|
|
1223
|
+
const lightLevel = (deviceData.readUInt8(1) >> 4) & 0b00001111;
|
|
1224
|
+
const deviceChain = deviceData.readUInt8(1) & 0b00000111;
|
|
1225
|
+
const battery = batteryData !== null ? batteryData & 0b01111111 : 0;
|
|
1226
|
+
const data = {
|
|
1227
|
+
model: SwitchBotBLEModel.Curtain3,
|
|
1228
|
+
modelName: SwitchBotBLEModelName.Curtain3,
|
|
1229
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain3,
|
|
1230
|
+
calibration,
|
|
1231
|
+
battery,
|
|
1232
|
+
inMotion,
|
|
1233
|
+
position: reverse ? 100 - position : position,
|
|
1234
|
+
lightLevel,
|
|
1235
|
+
deviceChain,
|
|
1236
|
+
};
|
|
1237
|
+
return data;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
constructor(peripheral, noble) {
|
|
1241
|
+
super(peripheral, noble);
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Opens the curtain.
|
|
1245
|
+
* @param {number} [mode] - Running mode (0x01 = QuietDrift, 0xFF = Default).
|
|
1246
|
+
* @returns {Promise<void>}
|
|
1247
|
+
*/
|
|
1248
|
+
async open(mode = 0xFF) {
|
|
1249
|
+
await this.runToPos(0, mode);
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Closes the curtain.
|
|
1253
|
+
* @param {number} [mode] - Running mode (0x01 = QuietDrift, 0xFF = Default).
|
|
1254
|
+
* @returns {Promise<void>}
|
|
1255
|
+
*/
|
|
1256
|
+
async close(mode = 0xFF) {
|
|
1257
|
+
await this.runToPos(100, mode);
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Pauses the curtain.
|
|
1261
|
+
* @returns {Promise<void>}
|
|
1262
|
+
*/
|
|
1263
|
+
async pause() {
|
|
1264
|
+
await this.operateCurtain([0x57, 0x0F, 0x45, 0x01, 0x00, 0xFF]);
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Runs the curtain to the target position.
|
|
1268
|
+
* @param {number} percent - The percentage of the target position.
|
|
1269
|
+
* @param {number} [mode] - Running mode (0x01 = QuietDrift, 0xFF = Default).
|
|
1270
|
+
* @returns {Promise<void>}
|
|
1271
|
+
*/
|
|
1272
|
+
async runToPos(percent, mode = 0xFF) {
|
|
1273
|
+
if (typeof percent !== 'number' || typeof mode !== 'number') {
|
|
1274
|
+
throw new TypeError('Invalid type for percent or mode');
|
|
1275
|
+
}
|
|
1276
|
+
percent = Math.max(0, Math.min(100, percent));
|
|
1277
|
+
await this.operateCurtain([0x57, 0x0F, 0x45, 0x01, 0x05, mode, percent]);
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Sends a command to the curtain.
|
|
1281
|
+
* @param {number[]} bytes - The command bytes.
|
|
1282
|
+
* @returns {Promise<void>}
|
|
1283
|
+
*/
|
|
1284
|
+
async operateCurtain(bytes) {
|
|
1285
|
+
const reqBuf = Buffer.from(bytes);
|
|
1286
|
+
const resBuf = await this.command(reqBuf);
|
|
1287
|
+
const code = resBuf.readUInt8(0);
|
|
1288
|
+
if (resBuf.length !== 3 || code !== 0x01) {
|
|
1289
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Class representing a WoHand device.
|
|
1295
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/bot.md
|
|
1296
|
+
*/
|
|
1297
|
+
export class WoHand extends SwitchbotDevice {
|
|
1298
|
+
/**
|
|
1299
|
+
* Parses the service data for WoHand.
|
|
1300
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1301
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1302
|
+
* @returns {Promise<botServiceData | null>} - Parsed service data or null if invalid.
|
|
1303
|
+
*/
|
|
1304
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
1305
|
+
if (!serviceData || serviceData.length < 3) {
|
|
1306
|
+
emitLog('debugerror', `[parseServiceData] Service Data Buffer length ${serviceData?.length ?? 0} < 3!`);
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
const byte1 = serviceData.readUInt8(1);
|
|
1310
|
+
const byte2 = serviceData.readUInt8(2);
|
|
1311
|
+
const data = {
|
|
1312
|
+
model: SwitchBotBLEModel.Bot,
|
|
1313
|
+
modelName: SwitchBotBLEModelName.Bot,
|
|
1314
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Bot,
|
|
1315
|
+
mode: !!(byte1 & 0b10000000), // Whether the light switch Add-on is used or not. 0 = press, 1 = switch
|
|
1316
|
+
state: !!(byte1 & 0b01000000), // Whether the switch status is ON or OFF. 0 = on, 1 = off
|
|
1317
|
+
battery: byte2 & 0b01111111, // %
|
|
1318
|
+
};
|
|
1319
|
+
return data;
|
|
1320
|
+
}
|
|
1321
|
+
constructor(peripheral, noble) {
|
|
1322
|
+
super(peripheral, noble);
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Sends a command to the bot.
|
|
1326
|
+
* @param {Buffer} reqBuf - The command buffer.
|
|
1327
|
+
* @returns {Promise<void>}
|
|
1328
|
+
*/
|
|
1329
|
+
async sendCommand(reqBuf) {
|
|
1330
|
+
const resBuf = await this.command(reqBuf);
|
|
1331
|
+
const code = resBuf.readUInt8(0);
|
|
1332
|
+
if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
|
|
1333
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Presses the bot.
|
|
1338
|
+
* @returns {Promise<void>}
|
|
1339
|
+
*/
|
|
1340
|
+
async press() {
|
|
1341
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x00]));
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Turns on the bot.
|
|
1345
|
+
* @returns {Promise<void>}
|
|
1346
|
+
*/
|
|
1347
|
+
async turnOn() {
|
|
1348
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x01]));
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Turns off the bot.
|
|
1352
|
+
* @returns {Promise<void>}
|
|
1353
|
+
*/
|
|
1354
|
+
async turnOff() {
|
|
1355
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x02]));
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Moves the bot down.
|
|
1359
|
+
* @returns {Promise<void>}
|
|
1360
|
+
*/
|
|
1361
|
+
async down() {
|
|
1362
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x03]));
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Moves the bot up.
|
|
1366
|
+
* @returns {Promise<void>}
|
|
1367
|
+
*/
|
|
1368
|
+
async up() {
|
|
1369
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x04]));
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Class representing a WoHub2 device.
|
|
1374
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
1375
|
+
*/
|
|
1376
|
+
export class WoHub2 extends SwitchbotDevice {
|
|
1377
|
+
/**
|
|
1378
|
+
* Parses the service data for WoHub2.
|
|
1379
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1380
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1381
|
+
* @returns {Promise<hub2ServiceData | null>} - Parsed service data or null if invalid.
|
|
1382
|
+
*/
|
|
1383
|
+
static async parseServiceData(manufacturerData, emitLog) {
|
|
1384
|
+
if (manufacturerData.length !== 16) {
|
|
1385
|
+
emitLog('debugerror', `[parseServiceDataForWoHub2] Buffer length ${manufacturerData.length} !== 16!`);
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
const [byte0, byte1, byte2, , , , , , , , , , byte12] = manufacturerData;
|
|
1389
|
+
const tempSign = byte1 & 0b10000000 ? 1 : -1;
|
|
1390
|
+
const tempC = tempSign * ((byte1 & 0b01111111) + (byte0 & 0b00001111) / 10);
|
|
1391
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
1392
|
+
const lightLevel = byte12 & 0b11111;
|
|
1393
|
+
const data = {
|
|
1394
|
+
model: SwitchBotBLEModel.Hub2,
|
|
1395
|
+
modelName: SwitchBotBLEModelName.Hub2,
|
|
1396
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Hub2,
|
|
1397
|
+
celsius: tempC,
|
|
1398
|
+
fahrenheit: tempF,
|
|
1399
|
+
fahrenheit_mode: !!(byte2 & 0b10000000),
|
|
1400
|
+
humidity: byte2 & 0b01111111,
|
|
1401
|
+
lightLevel,
|
|
1402
|
+
};
|
|
1403
|
+
return data;
|
|
1404
|
+
}
|
|
1405
|
+
constructor(peripheral, noble) {
|
|
1406
|
+
super(peripheral, noble);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Class representing a WoHumi device.
|
|
1411
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/tree/latest/devicetypes
|
|
1412
|
+
*/
|
|
1413
|
+
export class WoHumi extends SwitchbotDevice {
|
|
1414
|
+
constructor(peripheral, noble) {
|
|
1415
|
+
super(peripheral, noble);
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Parses the service data for WoHumi.
|
|
1419
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1420
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1421
|
+
* @returns {Promise<humidifierServiceData | null>} - Parsed service data or null if invalid.
|
|
1422
|
+
*/
|
|
1423
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
1424
|
+
if (serviceData.length !== 8) {
|
|
1425
|
+
emitLog('debugerror', `[parseServiceDataForWoHumi] Buffer length ${serviceData.length} !== 8!`);
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
const byte1 = serviceData.readUInt8(1);
|
|
1429
|
+
const byte4 = serviceData.readUInt8(4);
|
|
1430
|
+
const onState = !!(byte1 & 0b10000000); // 1 - on
|
|
1431
|
+
const autoMode = !!(byte4 & 0b10000000); // 1 - auto
|
|
1432
|
+
const percentage = byte4 & 0b01111111; // 0-100%, 101/102/103 - Quick gear 1/2/3
|
|
1433
|
+
const humidity = autoMode ? 0 : percentage === 101 ? 33 : percentage === 102 ? 66 : percentage === 103 ? 100 : percentage;
|
|
1434
|
+
const data = {
|
|
1435
|
+
model: SwitchBotBLEModel.Humidifier,
|
|
1436
|
+
modelName: SwitchBotBLEModelName.Humidifier,
|
|
1437
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier,
|
|
1438
|
+
onState,
|
|
1439
|
+
autoMode,
|
|
1440
|
+
percentage: autoMode ? 0 : percentage,
|
|
1441
|
+
humidity,
|
|
1442
|
+
};
|
|
1443
|
+
return data;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Sends a command to the humidifier.
|
|
1447
|
+
* @param {Buffer} reqBuf - The command buffer.
|
|
1448
|
+
* @returns {Promise<void>}
|
|
1449
|
+
*/
|
|
1450
|
+
async operateHumi(reqBuf) {
|
|
1451
|
+
const resBuf = await this.command(reqBuf);
|
|
1452
|
+
const code = resBuf.readUInt8(0);
|
|
1453
|
+
if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
|
|
1454
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Turns on the humidifier.
|
|
1459
|
+
* @returns {Promise<void>}
|
|
1460
|
+
*/
|
|
1461
|
+
async turnOn() {
|
|
1462
|
+
await this.operateHumi(Buffer.from(TURN_ON_KEY, 'hex'));
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Turns off the humidifier.
|
|
1466
|
+
* @returns {Promise<void>}
|
|
1467
|
+
*/
|
|
1468
|
+
async turnOff() {
|
|
1469
|
+
await this.operateHumi(Buffer.from(TURN_OFF_KEY, 'hex'));
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Increases the humidifier setting.
|
|
1473
|
+
* @returns {Promise<void>}
|
|
1474
|
+
*/
|
|
1475
|
+
async increase() {
|
|
1476
|
+
await this.operateHumi(Buffer.from(INCREASE_KEY, 'hex'));
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Decreases the humidifier setting.
|
|
1480
|
+
* @returns {Promise<void>}
|
|
1481
|
+
*/
|
|
1482
|
+
async decrease() {
|
|
1483
|
+
await this.operateHumi(Buffer.from(DECREASE_KEY, 'hex'));
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Sets the humidifier to auto mode.
|
|
1487
|
+
* @returns {Promise<void>}
|
|
1488
|
+
*/
|
|
1489
|
+
async setAutoMode() {
|
|
1490
|
+
await this.operateHumi(Buffer.from(SET_AUTO_MODE_KEY, 'hex'));
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Sets the humidifier to manual mode.
|
|
1494
|
+
* @returns {Promise<void>}
|
|
1495
|
+
*/
|
|
1496
|
+
async setManualMode() {
|
|
1497
|
+
await this.operateHumi(Buffer.from(SET_MANUAL_MODE_KEY, 'hex'));
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Sets the humidifier level.
|
|
1501
|
+
* @param {number} level - The level to set (0-100).
|
|
1502
|
+
* @returns {Promise<void>}
|
|
1503
|
+
*/
|
|
1504
|
+
async percentage(level) {
|
|
1505
|
+
if (level < 0 || level > 100) {
|
|
1506
|
+
throw new Error('Level must be between 0 and 100');
|
|
1507
|
+
}
|
|
1508
|
+
const levelKey = `${HUMIDIFIER_COMMAND_HEADER}0107${level.toString(16).padStart(2, '0')}`;
|
|
1509
|
+
await this.operateHumi(Buffer.from(levelKey, 'hex'));
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Class representing a WoHumi device.
|
|
1514
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/tree/latest/devicetypes
|
|
1515
|
+
*/
|
|
1516
|
+
export class WoHumi2 extends SwitchbotDevice {
|
|
1517
|
+
constructor(peripheral, noble) {
|
|
1518
|
+
super(peripheral, noble);
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Parses the service data for WoHumi.
|
|
1522
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1523
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1524
|
+
* @returns {Promise<humidifier2ServiceData | null>} - Parsed service data or null if invalid.
|
|
1525
|
+
*/
|
|
1526
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
1527
|
+
if (serviceData.length !== 8) {
|
|
1528
|
+
emitLog('debugerror', `[parseServiceDataForWoHumi] Buffer length ${serviceData.length} !== 8!`);
|
|
1529
|
+
return null;
|
|
1530
|
+
}
|
|
1531
|
+
const byte1 = serviceData.readUInt8(1);
|
|
1532
|
+
const byte4 = serviceData.readUInt8(4);
|
|
1533
|
+
const byte8 = serviceData.readUInt8(8);
|
|
1534
|
+
const byte10 = serviceData.readUInt8(10);
|
|
1535
|
+
const byte11 = serviceData.readUInt8(11);
|
|
1536
|
+
const byte12 = serviceData.readUInt8(12);
|
|
1537
|
+
const onState = !!(byte1 & 0b10000000); // 1 - on
|
|
1538
|
+
const autoMode = !!(byte4 & 0b10000000); // 1 - auto
|
|
1539
|
+
const percentage = byte4 & 0b01111111; // 0-100%, 101/102/103 - Quick gear 1/2/3
|
|
1540
|
+
const humidity = autoMode ? 0 : percentage === 101 ? 33 : percentage === 102 ? 66 : percentage === 103 ? 100 : percentage;
|
|
1541
|
+
const childLock = !!(byte8 & 0b00100000);
|
|
1542
|
+
const overHumidifyProtection = !!(byte8 & 0b10000000);
|
|
1543
|
+
const tankRemoved = !!(byte8 & 0b00000100);
|
|
1544
|
+
const tiltedAlert = !!(byte8 & 0b00000010);
|
|
1545
|
+
const filterMissing = !!(byte8 & 0b00000001);
|
|
1546
|
+
const temperature = (byte10 & 0b01111111) + (byte11 >> 4) / 10;
|
|
1547
|
+
const filterRunTime = byte12;
|
|
1548
|
+
const filterAlert = filterRunTime >= 240;
|
|
1549
|
+
const waterLevel = byte11 & 0b00000011;
|
|
1550
|
+
const data = {
|
|
1551
|
+
model: SwitchBotBLEModel.Humidifier2,
|
|
1552
|
+
modelName: SwitchBotBLEModelName.Humidifier2,
|
|
1553
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier2,
|
|
1554
|
+
onState,
|
|
1555
|
+
autoMode,
|
|
1556
|
+
percentage: autoMode ? 0 : percentage,
|
|
1557
|
+
humidity,
|
|
1558
|
+
childLock,
|
|
1559
|
+
overHumidifyProtection,
|
|
1560
|
+
tankRemoved,
|
|
1561
|
+
tiltedAlert,
|
|
1562
|
+
filterMissing,
|
|
1563
|
+
temperature,
|
|
1564
|
+
filterRunTime,
|
|
1565
|
+
filterAlert,
|
|
1566
|
+
waterLevel,
|
|
1567
|
+
};
|
|
1568
|
+
return data;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Sends a command to the humidifier.
|
|
1572
|
+
* @param {Buffer} reqBuf - The command buffer.
|
|
1573
|
+
* @returns {Promise<void>}
|
|
1574
|
+
*/
|
|
1575
|
+
async operateHumi(reqBuf) {
|
|
1576
|
+
const resBuf = await this.command(reqBuf);
|
|
1577
|
+
const code = resBuf.readUInt8(0);
|
|
1578
|
+
if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
|
|
1579
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Turns on the humidifier.
|
|
1584
|
+
* @returns {Promise<void>}
|
|
1585
|
+
*/
|
|
1586
|
+
async turnOn() {
|
|
1587
|
+
await this.operateHumi(Buffer.from(TURN_ON_KEY, 'hex'));
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Turns off the humidifier.
|
|
1591
|
+
* @returns {Promise<void>}
|
|
1592
|
+
*/
|
|
1593
|
+
async turnOff() {
|
|
1594
|
+
await this.operateHumi(Buffer.from(TURN_OFF_KEY, 'hex'));
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Increases the humidifier setting.
|
|
1598
|
+
* @returns {Promise<void>}
|
|
1599
|
+
*/
|
|
1600
|
+
async increase() {
|
|
1601
|
+
await this.operateHumi(Buffer.from(INCREASE_KEY, 'hex'));
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Decreases the humidifier setting.
|
|
1605
|
+
* @returns {Promise<void>}
|
|
1606
|
+
*/
|
|
1607
|
+
async decrease() {
|
|
1608
|
+
await this.operateHumi(Buffer.from(DECREASE_KEY, 'hex'));
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Sets the humidifier to auto mode.
|
|
1612
|
+
* @returns {Promise<void>}
|
|
1613
|
+
*/
|
|
1614
|
+
async setAutoMode() {
|
|
1615
|
+
await this.operateHumi(Buffer.from(SET_AUTO_MODE_KEY, 'hex'));
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Sets the humidifier to manual mode.
|
|
1619
|
+
* @returns {Promise<void>}
|
|
1620
|
+
*/
|
|
1621
|
+
async setManualMode() {
|
|
1622
|
+
await this.operateHumi(Buffer.from(SET_MANUAL_MODE_KEY, 'hex'));
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Sets the humidifier level.
|
|
1626
|
+
* @param {number} level - The level to set (0-100).
|
|
1627
|
+
* @returns {Promise<void>}
|
|
1628
|
+
*/
|
|
1629
|
+
async percentage(level) {
|
|
1630
|
+
if (level < 0 || level > 100) {
|
|
1631
|
+
throw new Error('Level must be between 0 and 100');
|
|
1632
|
+
}
|
|
1633
|
+
const levelKey = `${HUMIDIFIER_COMMAND_HEADER}0107${level.toString(16).padStart(2, '0')}`;
|
|
1634
|
+
await this.operateHumi(Buffer.from(levelKey, 'hex'));
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Class representing a WoIOSensorTH device.
|
|
1639
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md#outdoor-temperaturehumidity-sensor
|
|
1640
|
+
*/
|
|
1641
|
+
export class WoIOSensorTH extends SwitchbotDevice {
|
|
1642
|
+
/**
|
|
1643
|
+
* Parses the service data for WoIOSensorTH.
|
|
1644
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1645
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1646
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1647
|
+
* @returns {Promise<outdoorMeterServiceData | null>} - Parsed service data or null if invalid.
|
|
1648
|
+
*/
|
|
1649
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
1650
|
+
if (serviceData.length !== 3) {
|
|
1651
|
+
emitLog('debugerror', `[parseServiceDataForWoIOSensorTH] Service Data Buffer length ${serviceData.length} !== 3!`);
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
if (manufacturerData.length !== 14) {
|
|
1655
|
+
emitLog('debugerror', `[parseServiceDataForWoIOSensorTH] Manufacturer Data Buffer length ${manufacturerData.length} !== 14!`);
|
|
1656
|
+
return null;
|
|
1657
|
+
}
|
|
1658
|
+
const [mdByte10, mdByte11, mdByte12] = [
|
|
1659
|
+
manufacturerData.readUInt8(10),
|
|
1660
|
+
manufacturerData.readUInt8(11),
|
|
1661
|
+
manufacturerData.readUInt8(12),
|
|
1662
|
+
];
|
|
1663
|
+
const sdByte2 = serviceData.readUInt8(2);
|
|
1664
|
+
const tempSign = mdByte11 & 0b10000000 ? 1 : -1;
|
|
1665
|
+
const tempC = tempSign * ((mdByte11 & 0b01111111) + (mdByte10 & 0b00001111) / 10);
|
|
1666
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
1667
|
+
const data = {
|
|
1668
|
+
model: SwitchBotBLEModel.OutdoorMeter,
|
|
1669
|
+
modelName: SwitchBotBLEModelName.OutdoorMeter,
|
|
1670
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.OutdoorMeter,
|
|
1671
|
+
celsius: tempC,
|
|
1672
|
+
fahrenheit: tempF,
|
|
1673
|
+
fahrenheit_mode: !!(mdByte12 & 0b10000000),
|
|
1674
|
+
humidity: mdByte12 & 0b01111111,
|
|
1675
|
+
battery: sdByte2 & 0b01111111,
|
|
1676
|
+
};
|
|
1677
|
+
return data;
|
|
1678
|
+
}
|
|
1679
|
+
constructor(peripheral, noble) {
|
|
1680
|
+
super(peripheral, noble);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Class representing a WoKeypad device.
|
|
1685
|
+
*/
|
|
1686
|
+
export class WoKeypad extends SwitchbotDevice {
|
|
1687
|
+
/**
|
|
1688
|
+
* Parses the service data for WoKeypad.
|
|
1689
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1690
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1691
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1692
|
+
* @returns {Promise<keypadDetectorServiceData | null>} - Parsed service data or null if invalid.
|
|
1693
|
+
*/
|
|
1694
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
1695
|
+
if (!serviceData || serviceData.length < 3) {
|
|
1696
|
+
emitLog('debugerror', `[parseServiceDataForWoKeypad] Service Data Buffer length ${serviceData?.length ?? 0} < 3!`);
|
|
1697
|
+
return null;
|
|
1698
|
+
}
|
|
1699
|
+
if (!manufacturerData || manufacturerData.length < 2) {
|
|
1700
|
+
emitLog('debugerror', `[parseServiceDataForWoKeypad] Manufacturer Data Buffer length ${manufacturerData?.length ?? 0} < 2!`);
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
const modelId = serviceData.readUInt8(0);
|
|
1704
|
+
if (modelId !== 0x26) {
|
|
1705
|
+
// Not a Keypad
|
|
1706
|
+
emitLog('debugerror', `[parseServiceDataForWoKeypad] Model ID ${modelId} !== 0x26!`);
|
|
1707
|
+
return null;
|
|
1708
|
+
}
|
|
1709
|
+
const eventFlags = serviceData.readUInt8(1);
|
|
1710
|
+
const keypadEventDetected = !!(eventFlags & 0b00000001); // Bit 0
|
|
1711
|
+
const deviceTampered = !!(eventFlags & 0b00000010); // Bit 1
|
|
1712
|
+
const batteryInfo = serviceData.readUInt8(2);
|
|
1713
|
+
const batteryLevel = batteryInfo & 0b01111111; // Bits 0-6
|
|
1714
|
+
const lowBattery = !!(batteryInfo & 0b10000000); // Bit 7
|
|
1715
|
+
// Manufacturer data can be processed here if needed
|
|
1716
|
+
const data = {
|
|
1717
|
+
model: SwitchBotBLEModel.Keypad,
|
|
1718
|
+
modelName: SwitchBotBLEModelName.Keypad,
|
|
1719
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Keypad,
|
|
1720
|
+
event: keypadEventDetected,
|
|
1721
|
+
tampered: deviceTampered,
|
|
1722
|
+
battery: batteryLevel,
|
|
1723
|
+
low_battery: lowBattery,
|
|
1724
|
+
};
|
|
1725
|
+
return data;
|
|
1726
|
+
}
|
|
1727
|
+
constructor(peripheral, noble) {
|
|
1728
|
+
super(peripheral, noble);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Class representing a WoLeak device.
|
|
1733
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md#outdoor-temperaturehumidity-sensor
|
|
1734
|
+
*/
|
|
1735
|
+
export class WoLeak extends SwitchbotDevice {
|
|
1736
|
+
/**
|
|
1737
|
+
* Parses the service data for WoLeak.
|
|
1738
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1739
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1740
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1741
|
+
* @returns {Promise<leakServiceData | null>} - Parsed service data or null if invalid.
|
|
1742
|
+
*/
|
|
1743
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
1744
|
+
if (!serviceData || serviceData.length < 3) {
|
|
1745
|
+
emitLog('debugerror', `[parseServiceDataForWoLeakDetector] Service Data Buffer length ${serviceData?.length ?? 0} < 3!`);
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
if (!manufacturerData || manufacturerData.length < 2) {
|
|
1749
|
+
emitLog('debugerror', `[parseServiceDataForWoLeakDetector] Manufacturer Data Buffer length ${manufacturerData?.length ?? 0} < 2!`);
|
|
1750
|
+
return null;
|
|
1751
|
+
}
|
|
1752
|
+
const waterLeakDetected = !!(manufacturerData.readUInt8(8) & 0b00000001); // Bit 0
|
|
1753
|
+
const deviceTampered = !!(manufacturerData.readUInt8(8) & 0b00000010); // Bit 1
|
|
1754
|
+
const batteryLevel = manufacturerData.readUInt8(7) & 0b01111111; // Bits 0-6
|
|
1755
|
+
const lowBattery = !!(manufacturerData.readUInt8(7) & 0b10000000); // Bit 7
|
|
1756
|
+
// Manufacturer data can be processed here if needed
|
|
1757
|
+
const data = {
|
|
1758
|
+
model: SwitchBotBLEModel.Leak,
|
|
1759
|
+
modelName: SwitchBotBLEModelName.Leak,
|
|
1760
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Leak,
|
|
1761
|
+
leak: waterLeakDetected,
|
|
1762
|
+
tampered: deviceTampered,
|
|
1763
|
+
battery: batteryLevel,
|
|
1764
|
+
low_battery: lowBattery,
|
|
1765
|
+
};
|
|
1766
|
+
return data;
|
|
1767
|
+
}
|
|
1768
|
+
constructor(peripheral, noble) {
|
|
1769
|
+
super(peripheral, noble);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Class representing a WoPlugMini device.
|
|
1774
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/plugmini.md
|
|
1775
|
+
*/
|
|
1776
|
+
export class WoPlugMiniJP extends SwitchbotDevice {
|
|
1777
|
+
constructor(peripheral, noble) {
|
|
1778
|
+
super(peripheral, noble);
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Parses the service data for WoPlugMini JP.
|
|
1782
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1783
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1784
|
+
* @returns {Promise<plugMiniJPServiceData | null>} - Parsed service data or null if invalid.
|
|
1785
|
+
*/
|
|
1786
|
+
static async parseServiceData(manufacturerData, emitLog) {
|
|
1787
|
+
if (manufacturerData.length !== 14) {
|
|
1788
|
+
emitLog('debugerror', `[parseServiceDataForWoPlugMiniJP] Buffer length ${manufacturerData.length} should be 14`);
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
const [byte9, byte10, byte11, byte12, byte13] = [
|
|
1792
|
+
manufacturerData.readUInt8(9),
|
|
1793
|
+
manufacturerData.readUInt8(10),
|
|
1794
|
+
manufacturerData.readUInt8(11),
|
|
1795
|
+
manufacturerData.readUInt8(12),
|
|
1796
|
+
manufacturerData.readUInt8(13),
|
|
1797
|
+
];
|
|
1798
|
+
const state = byte9 === 0x00 ? 'off' : byte9 === 0x80 ? 'on' : null;
|
|
1799
|
+
const delay = !!(byte10 & 0b00000001);
|
|
1800
|
+
const timer = !!(byte10 & 0b00000010);
|
|
1801
|
+
const syncUtcTime = !!(byte10 & 0b00000100);
|
|
1802
|
+
const wifiRssi = byte11;
|
|
1803
|
+
const overload = !!(byte12 & 0b10000000);
|
|
1804
|
+
const currentPower = (((byte12 & 0b01111111) << 8) + byte13) / 10; // in watt
|
|
1805
|
+
const data = {
|
|
1806
|
+
model: SwitchBotBLEModel.PlugMiniJP,
|
|
1807
|
+
modelName: SwitchBotBLEModelName.PlugMini,
|
|
1808
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini,
|
|
1809
|
+
state: state ?? 'unknown',
|
|
1810
|
+
delay,
|
|
1811
|
+
timer,
|
|
1812
|
+
syncUtcTime,
|
|
1813
|
+
wifiRssi,
|
|
1814
|
+
overload,
|
|
1815
|
+
currentPower,
|
|
1816
|
+
};
|
|
1817
|
+
return data;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Reads the state of the plug.
|
|
1821
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1822
|
+
*/
|
|
1823
|
+
async readState() {
|
|
1824
|
+
return this.operatePlug([0x57, 0x0F, 0x51, 0x01]);
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Sets the state of the plug.
|
|
1828
|
+
* @private
|
|
1829
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
1830
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1831
|
+
*/
|
|
1832
|
+
async setState(reqByteArray) {
|
|
1833
|
+
const base = [0x57, 0x0F, 0x50, 0x01];
|
|
1834
|
+
return this.operatePlug([...base, ...reqByteArray]);
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Turns on the plug.
|
|
1838
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1839
|
+
*/
|
|
1840
|
+
async turnOn() {
|
|
1841
|
+
return this.setState([0x01, 0x80]);
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Turns off the plug.
|
|
1845
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1846
|
+
*/
|
|
1847
|
+
async turnOff() {
|
|
1848
|
+
return this.setState([0x01, 0x00]);
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* Toggles the state of the plug.
|
|
1852
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1853
|
+
*/
|
|
1854
|
+
async toggle() {
|
|
1855
|
+
return this.setState([0x02, 0x80]);
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Operates the plug with the given bytes.
|
|
1859
|
+
* @param {number[]} bytes - The byte array to send to the plug.
|
|
1860
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1861
|
+
*/
|
|
1862
|
+
async operatePlug(bytes) {
|
|
1863
|
+
const reqBuf = Buffer.from(bytes);
|
|
1864
|
+
const resBytes = await this.command(reqBuf);
|
|
1865
|
+
const resBuf = Buffer.from(resBytes);
|
|
1866
|
+
if (resBuf.length !== 2) {
|
|
1867
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
|
|
1868
|
+
}
|
|
1869
|
+
const code = resBuf.readUInt8(1);
|
|
1870
|
+
if (code === 0x00 || code === 0x80) {
|
|
1871
|
+
return code === 0x80;
|
|
1872
|
+
}
|
|
1873
|
+
else {
|
|
1874
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Class representing a WoPlugMini device.
|
|
1880
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/plugmini.md
|
|
1881
|
+
*/
|
|
1882
|
+
export class WoPlugMiniUS extends SwitchbotDevice {
|
|
1883
|
+
constructor(peripheral, noble) {
|
|
1884
|
+
super(peripheral, noble);
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Parses the service data for WoPlugMini US.
|
|
1888
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
1889
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1890
|
+
* @returns {Promise<plugMiniUSServiceData | null>} - Parsed service data or null if invalid.
|
|
1891
|
+
*/
|
|
1892
|
+
static async parseServiceData(manufacturerData, emitLog) {
|
|
1893
|
+
if (manufacturerData.length !== 14) {
|
|
1894
|
+
emitLog('debugerror', `[parseServiceDataForWoPlugMini] Buffer length ${manufacturerData.length} should be 14`);
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
const [byte9, byte10, byte11, byte12, byte13] = [
|
|
1898
|
+
manufacturerData.readUInt8(9),
|
|
1899
|
+
manufacturerData.readUInt8(10),
|
|
1900
|
+
manufacturerData.readUInt8(11),
|
|
1901
|
+
manufacturerData.readUInt8(12),
|
|
1902
|
+
manufacturerData.readUInt8(13),
|
|
1903
|
+
];
|
|
1904
|
+
const state = byte9 === 0x00 ? 'off' : byte9 === 0x80 ? 'on' : null;
|
|
1905
|
+
const delay = !!(byte10 & 0b00000001);
|
|
1906
|
+
const timer = !!(byte10 & 0b00000010);
|
|
1907
|
+
const syncUtcTime = !!(byte10 & 0b00000100);
|
|
1908
|
+
const wifiRssi = byte11;
|
|
1909
|
+
const overload = !!(byte12 & 0b10000000);
|
|
1910
|
+
const currentPower = (((byte12 & 0b01111111) << 8) + byte13) / 10; // in watt
|
|
1911
|
+
const data = {
|
|
1912
|
+
model: SwitchBotBLEModel.PlugMiniUS,
|
|
1913
|
+
modelName: SwitchBotBLEModelName.PlugMini,
|
|
1914
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini,
|
|
1915
|
+
state: state ?? 'unknown',
|
|
1916
|
+
delay,
|
|
1917
|
+
timer,
|
|
1918
|
+
syncUtcTime,
|
|
1919
|
+
wifiRssi,
|
|
1920
|
+
overload,
|
|
1921
|
+
currentPower,
|
|
1922
|
+
};
|
|
1923
|
+
return data;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Reads the state of the plug.
|
|
1927
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1928
|
+
*/
|
|
1929
|
+
async readState() {
|
|
1930
|
+
return this.operatePlug([0x57, 0x0F, 0x51, 0x01]);
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Sets the state of the plug.
|
|
1934
|
+
* @private
|
|
1935
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
1936
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1937
|
+
*/
|
|
1938
|
+
async setState(reqByteArray) {
|
|
1939
|
+
const base = [0x57, 0x0F, 0x50, 0x01];
|
|
1940
|
+
return this.operatePlug([...base, ...reqByteArray]);
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Turns on the plug.
|
|
1944
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1945
|
+
*/
|
|
1946
|
+
async turnOn() {
|
|
1947
|
+
return this.setState([0x01, 0x80]);
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Turns off the plug.
|
|
1951
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1952
|
+
*/
|
|
1953
|
+
async turnOff() {
|
|
1954
|
+
return this.setState([0x01, 0x00]);
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Toggles the state of the plug.
|
|
1958
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1959
|
+
*/
|
|
1960
|
+
async toggle() {
|
|
1961
|
+
return this.setState([0x02, 0x80]);
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Operates the plug with the given bytes.
|
|
1965
|
+
* @param {number[]} bytes - The byte array to send to the plug.
|
|
1966
|
+
* @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
|
|
1967
|
+
*/
|
|
1968
|
+
async operatePlug(bytes) {
|
|
1969
|
+
const reqBuf = Buffer.from(bytes);
|
|
1970
|
+
const resBytes = await this.command(reqBuf);
|
|
1971
|
+
const resBuf = Buffer.from(resBytes);
|
|
1972
|
+
if (resBuf.length !== 2) {
|
|
1973
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
|
|
1974
|
+
}
|
|
1975
|
+
const code = resBuf.readUInt8(1);
|
|
1976
|
+
if (code === 0x00 || code === 0x80) {
|
|
1977
|
+
return code === 0x80;
|
|
1978
|
+
}
|
|
1979
|
+
else {
|
|
1980
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Class representing a WoPresence device.
|
|
1986
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
1987
|
+
*/
|
|
1988
|
+
export class WoPresence extends SwitchbotDevice {
|
|
1989
|
+
/**
|
|
1990
|
+
* Parses the service data for WoPresence.
|
|
1991
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
1992
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
1993
|
+
* @returns {Promise<motionSensorServiceData | null>} - Parsed service data or null if invalid.
|
|
1994
|
+
*/
|
|
1995
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
1996
|
+
if (serviceData.length !== 6) {
|
|
1997
|
+
emitLog('debugerror', `[parseServiceDataForWoPresence] Buffer length ${serviceData.length} !== 6!`);
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
const [byte1, byte2, , , , byte5] = serviceData;
|
|
2001
|
+
const data = {
|
|
2002
|
+
model: SwitchBotBLEModel.MotionSensor,
|
|
2003
|
+
modelName: SwitchBotBLEModelName.MotionSensor,
|
|
2004
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.MotionSensor,
|
|
2005
|
+
tested: !!(byte1 & 0b10000000),
|
|
2006
|
+
movement: !!(byte1 & 0b01000000),
|
|
2007
|
+
battery: byte2 & 0b01111111,
|
|
2008
|
+
led: (byte5 & 0b00100000) >> 5,
|
|
2009
|
+
iot: (byte5 & 0b00010000) >> 4,
|
|
2010
|
+
sense_distance: (byte5 & 0b00001100) >> 2,
|
|
2011
|
+
lightLevel: (byte5 & 0b00000011) === 1 ? 'dark' : (byte5 & 0b00000011) === 2 ? 'bright' : 'unknown',
|
|
2012
|
+
is_light: !!(byte5 & 0b00000010),
|
|
2013
|
+
};
|
|
2014
|
+
return data;
|
|
2015
|
+
}
|
|
2016
|
+
constructor(peripheral, noble) {
|
|
2017
|
+
super(peripheral, noble);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Class representing a WoRelaySwitch1 device.
|
|
2022
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/
|
|
2023
|
+
*/
|
|
2024
|
+
export class WoRelaySwitch1 extends SwitchbotDevice {
|
|
2025
|
+
constructor(peripheral, noble) {
|
|
2026
|
+
super(peripheral, noble);
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Parses the service data for WoRelaySwitch1.
|
|
2030
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
2031
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
2032
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
2033
|
+
* @returns {Promise<relaySwitch1ServiceData | null>} - Parsed service data or null if invalid.
|
|
2034
|
+
*/
|
|
2035
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
2036
|
+
if (serviceData.length < 8 || manufacturerData.length === null) {
|
|
2037
|
+
emitLog('debugerror', `[parseServiceDataForWoRelaySwitch1Plus] Buffer length ${serviceData.length} < 8!`);
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
const data = {
|
|
2041
|
+
model: SwitchBotBLEModel.RelaySwitch1,
|
|
2042
|
+
modelName: SwitchBotBLEModelName.RelaySwitch1,
|
|
2043
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.RelaySwitch1,
|
|
2044
|
+
mode: true, // for compatibility, useless
|
|
2045
|
+
state: !!(manufacturerData[7] & 0b10000000),
|
|
2046
|
+
sequence_number: manufacturerData[6],
|
|
2047
|
+
};
|
|
2048
|
+
return data;
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Sends a command to the bot.
|
|
2052
|
+
* @param {Buffer} reqBuf - The command buffer.
|
|
2053
|
+
* @returns {Promise<void>}
|
|
2054
|
+
*/
|
|
2055
|
+
async sendCommand(reqBuf) {
|
|
2056
|
+
const resBuf = await this.command(reqBuf);
|
|
2057
|
+
const code = resBuf.readUInt8(0);
|
|
2058
|
+
if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
|
|
2059
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Turns on the bot.
|
|
2064
|
+
* @returns {Promise<void>}
|
|
2065
|
+
*/
|
|
2066
|
+
async turnOn() {
|
|
2067
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x01]));
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Turns off the bot.
|
|
2071
|
+
* @returns {Promise<void>}
|
|
2072
|
+
*/
|
|
2073
|
+
async turnOff() {
|
|
2074
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x02]));
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Class representing a WoRelaySwitch1PM device.
|
|
2079
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/
|
|
2080
|
+
*/
|
|
2081
|
+
export class WoRelaySwitch1PM extends SwitchbotDevice {
|
|
2082
|
+
constructor(peripheral, noble) {
|
|
2083
|
+
super(peripheral, noble);
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Parses the service data for WoRelaySwitch1PM.
|
|
2087
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
2088
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
2089
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
2090
|
+
* @returns {Promise<relaySwitch1PMServiceData | null>} - Parsed service data or null if invalid.
|
|
2091
|
+
*/
|
|
2092
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
2093
|
+
if (serviceData.length < 8 || manufacturerData.length === 0) {
|
|
2094
|
+
emitLog('debugerror', `[parseServiceDataForWoRelaySwitch1PM] Buffer length ${serviceData.length} < 8!`);
|
|
2095
|
+
return null;
|
|
2096
|
+
}
|
|
2097
|
+
const data = {
|
|
2098
|
+
model: SwitchBotBLEModel.RelaySwitch1PM,
|
|
2099
|
+
modelName: SwitchBotBLEModelName.RelaySwitch1PM,
|
|
2100
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.RelaySwitch1PM,
|
|
2101
|
+
mode: true, // for compatibility, useless
|
|
2102
|
+
state: !!(manufacturerData[7] & 0b10000000),
|
|
2103
|
+
sequence_number: manufacturerData[6],
|
|
2104
|
+
power: ((manufacturerData[10] << 8) + manufacturerData[11]) / 10,
|
|
2105
|
+
voltage: 0,
|
|
2106
|
+
current: 0,
|
|
2107
|
+
};
|
|
2108
|
+
return data;
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Sends a command to the bot.
|
|
2112
|
+
* @param {Buffer} reqBuf - The command buffer.
|
|
2113
|
+
* @returns {Promise<void>}
|
|
2114
|
+
*/
|
|
2115
|
+
async sendCommand(reqBuf) {
|
|
2116
|
+
const resBuf = await this.command(reqBuf);
|
|
2117
|
+
const code = resBuf.readUInt8(0);
|
|
2118
|
+
if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
|
|
2119
|
+
throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* Turns on the bot.
|
|
2124
|
+
* @returns {Promise<void>}
|
|
2125
|
+
*/
|
|
2126
|
+
async turnOn() {
|
|
2127
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x01]));
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Turns off the bot.
|
|
2131
|
+
* @returns {Promise<void>}
|
|
2132
|
+
*/
|
|
2133
|
+
async turnOff() {
|
|
2134
|
+
await this.sendCommand(Buffer.from([0x57, 0x01, 0x02]));
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Class representing a WoRemote device.
|
|
2139
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/
|
|
2140
|
+
*/
|
|
2141
|
+
export class WoRemote extends SwitchbotDevice {
|
|
2142
|
+
/**
|
|
2143
|
+
* Parses the service data for WoRemote.
|
|
2144
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
2145
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
2146
|
+
* @returns {Promise<remoteServiceData | null>} - Parsed service data or null if invalid.
|
|
2147
|
+
*/
|
|
2148
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
2149
|
+
if (serviceData.length !== 9) {
|
|
2150
|
+
emitLog('debugerror', `[parseServiceDataForWoRemote] Buffer length ${serviceData.length} !== 9!`);
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
const [byte2] = serviceData;
|
|
2154
|
+
const battery = byte2 & 0b01111111;
|
|
2155
|
+
const data = {
|
|
2156
|
+
model: SwitchBotBLEModel.Remote,
|
|
2157
|
+
modelName: SwitchBotBLEModelName.Remote,
|
|
2158
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Remote,
|
|
2159
|
+
battery,
|
|
2160
|
+
};
|
|
2161
|
+
return data;
|
|
2162
|
+
}
|
|
2163
|
+
constructor(peripheral, noble) {
|
|
2164
|
+
super(peripheral, noble);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Class representing a WoSensorTH device.
|
|
2169
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
2170
|
+
*/
|
|
2171
|
+
export class WoSensorTH extends SwitchbotDevice {
|
|
2172
|
+
constructor(peripheral, noble) {
|
|
2173
|
+
super(peripheral, noble);
|
|
2174
|
+
}
|
|
2175
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
2176
|
+
if (serviceData.length !== 6) {
|
|
2177
|
+
emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 6!`);
|
|
2178
|
+
return null;
|
|
2179
|
+
}
|
|
2180
|
+
const [byte2, byte3, byte4, byte5] = [
|
|
2181
|
+
serviceData.readUInt8(2),
|
|
2182
|
+
serviceData.readUInt8(3),
|
|
2183
|
+
serviceData.readUInt8(4),
|
|
2184
|
+
serviceData.readUInt8(5),
|
|
2185
|
+
];
|
|
2186
|
+
const tempSign = byte4 & 0b10000000 ? 1 : -1;
|
|
2187
|
+
const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
|
|
2188
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
2189
|
+
const data = {
|
|
2190
|
+
model: SwitchBotBLEModel.Meter,
|
|
2191
|
+
modelName: SwitchBotBLEModelName.Meter,
|
|
2192
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Meter,
|
|
2193
|
+
celsius: tempC,
|
|
2194
|
+
fahrenheit: tempF,
|
|
2195
|
+
fahrenheit_mode: !!(byte5 & 0b10000000),
|
|
2196
|
+
humidity: byte5 & 0b01111111,
|
|
2197
|
+
battery: byte2 & 0b01111111,
|
|
2198
|
+
};
|
|
2199
|
+
return data;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Class representing a WoSensorTH device.
|
|
2204
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
2205
|
+
*/
|
|
2206
|
+
export class WoSensorTHPlus extends SwitchbotDevice {
|
|
2207
|
+
constructor(peripheral, noble) {
|
|
2208
|
+
super(peripheral, noble);
|
|
2209
|
+
}
|
|
2210
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
2211
|
+
if (serviceData.length !== 6) {
|
|
2212
|
+
emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 6 or 7!`);
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2215
|
+
const [byte2, byte3, byte4, byte5] = [
|
|
2216
|
+
serviceData.readUInt8(2),
|
|
2217
|
+
serviceData.readUInt8(3),
|
|
2218
|
+
serviceData.readUInt8(4),
|
|
2219
|
+
serviceData.readUInt8(5),
|
|
2220
|
+
];
|
|
2221
|
+
const tempSign = byte4 & 0b10000000 ? 1 : -1;
|
|
2222
|
+
const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
|
|
2223
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
2224
|
+
const data = {
|
|
2225
|
+
model: SwitchBotBLEModel.MeterPlus,
|
|
2226
|
+
modelName: SwitchBotBLEModelName.MeterPlus,
|
|
2227
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPlus,
|
|
2228
|
+
celsius: tempC,
|
|
2229
|
+
fahrenheit: tempF,
|
|
2230
|
+
fahrenheit_mode: !!(byte5 & 0b10000000),
|
|
2231
|
+
humidity: byte5 & 0b01111111,
|
|
2232
|
+
battery: byte2 & 0b01111111,
|
|
2233
|
+
};
|
|
2234
|
+
return data;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Class representing a WoSensorTH device.
|
|
2239
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
2240
|
+
*/
|
|
2241
|
+
export class WoSensorTHPro extends SwitchbotDevice {
|
|
2242
|
+
constructor(peripheral, noble) {
|
|
2243
|
+
super(peripheral, noble);
|
|
2244
|
+
}
|
|
2245
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
2246
|
+
if (serviceData.length !== 6) {
|
|
2247
|
+
emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 6 or 7!`);
|
|
2248
|
+
return null;
|
|
2249
|
+
}
|
|
2250
|
+
const [byte2, byte3, byte4, byte5] = [
|
|
2251
|
+
serviceData.readUInt8(2),
|
|
2252
|
+
serviceData.readUInt8(3),
|
|
2253
|
+
serviceData.readUInt8(4),
|
|
2254
|
+
serviceData.readUInt8(5),
|
|
2255
|
+
];
|
|
2256
|
+
const tempSign = byte4 & 0b10000000 ? 1 : -1;
|
|
2257
|
+
const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
|
|
2258
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
2259
|
+
const data = {
|
|
2260
|
+
model: SwitchBotBLEModel.MeterPro,
|
|
2261
|
+
modelName: SwitchBotBLEModelName.MeterPro,
|
|
2262
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPro,
|
|
2263
|
+
celsius: tempC,
|
|
2264
|
+
fahrenheit: tempF,
|
|
2265
|
+
fahrenheit_mode: !!(byte5 & 0b10000000),
|
|
2266
|
+
humidity: byte5 & 0b01111111,
|
|
2267
|
+
battery: byte2 & 0b01111111,
|
|
2268
|
+
};
|
|
2269
|
+
return data;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Class representing a WoSensorTH device.
|
|
2274
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
|
|
2275
|
+
*/
|
|
2276
|
+
export class WoSensorTHProCO2 extends SwitchbotDevice {
|
|
2277
|
+
constructor(peripheral, noble) {
|
|
2278
|
+
super(peripheral, noble);
|
|
2279
|
+
}
|
|
2280
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
2281
|
+
if (serviceData.length !== 7) {
|
|
2282
|
+
emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 7!`);
|
|
2283
|
+
return null;
|
|
2284
|
+
}
|
|
2285
|
+
const [byte2, byte3, byte4, byte5, byte6] = [
|
|
2286
|
+
serviceData.readUInt8(2),
|
|
2287
|
+
serviceData.readUInt8(3),
|
|
2288
|
+
serviceData.readUInt8(4),
|
|
2289
|
+
serviceData.readUInt8(5),
|
|
2290
|
+
manufacturerData.readUInt16BE(6),
|
|
2291
|
+
];
|
|
2292
|
+
const tempSign = byte4 & 0b10000000 ? 1 : -1;
|
|
2293
|
+
const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
|
|
2294
|
+
const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
|
|
2295
|
+
const data = {
|
|
2296
|
+
model: SwitchBotBLEModel.MeterProCO2,
|
|
2297
|
+
modelName: SwitchBotBLEModelName.MeterProCO2,
|
|
2298
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterProCO2,
|
|
2299
|
+
celsius: tempC,
|
|
2300
|
+
fahrenheit: tempF,
|
|
2301
|
+
fahrenheit_mode: !!(byte5 & 0b10000000),
|
|
2302
|
+
humidity: byte5 & 0b01111111,
|
|
2303
|
+
battery: byte2 & 0b01111111,
|
|
2304
|
+
co2: byte6,
|
|
2305
|
+
};
|
|
2306
|
+
return data;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Class representing a WoSmartLock device.
|
|
2311
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/lock.md
|
|
2312
|
+
*/
|
|
2313
|
+
export class WoSmartLock extends SwitchbotDevice {
|
|
2314
|
+
iv = null;
|
|
2315
|
+
key_id = '';
|
|
2316
|
+
encryption_key = null;
|
|
2317
|
+
static Result = {
|
|
2318
|
+
ERROR: 0x00,
|
|
2319
|
+
SUCCESS: 0x01,
|
|
2320
|
+
SUCCESS_LOW_BATTERY: 0x06,
|
|
2321
|
+
};
|
|
2322
|
+
static async validateResponse(res) {
|
|
2323
|
+
if (res.length >= 3) {
|
|
2324
|
+
const result = res.readUInt8(0);
|
|
2325
|
+
if (result === WoSmartLock.Result.SUCCESS || result === WoSmartLock.Result.SUCCESS_LOW_BATTERY) {
|
|
2326
|
+
return result;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
return WoSmartLock.Result.ERROR;
|
|
2330
|
+
}
|
|
2331
|
+
static getLockStatus(code) {
|
|
2332
|
+
const statusMap = {
|
|
2333
|
+
0b0000000: 'LOCKED',
|
|
2334
|
+
0b0010000: 'UNLOCKED',
|
|
2335
|
+
0b0100000: 'LOCKING',
|
|
2336
|
+
0b0110000: 'UNLOCKING',
|
|
2337
|
+
0b1000000: 'LOCKING_STOP',
|
|
2338
|
+
0b1010000: 'UNLOCKING_STOP',
|
|
2339
|
+
0b1100000: 'NOT_FULLY_LOCKED', // Only EU lock type
|
|
2340
|
+
};
|
|
2341
|
+
return statusMap[code] || 'UNKNOWN';
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Parses the service data from the SwitchBot Strip Light.
|
|
2345
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
2346
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
2347
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
2348
|
+
* @returns {Promise<lockServiceData | null>} - Parsed service data or null if invalid.
|
|
2349
|
+
*/
|
|
2350
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
2351
|
+
if (manufacturerData.length < 11) {
|
|
2352
|
+
emitLog('debugerror', `[parseServiceDataForWoSmartLock] Buffer length ${manufacturerData.length} is too short!`);
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
const byte2 = serviceData.readUInt8(2);
|
|
2356
|
+
const byte15 = manufacturerData.readUInt8(9);
|
|
2357
|
+
const byte16 = manufacturerData.readUInt8(10);
|
|
2358
|
+
const data = {
|
|
2359
|
+
model: SwitchBotBLEModel.Lock,
|
|
2360
|
+
modelName: SwitchBotBLEModelName.Lock,
|
|
2361
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.Lock,
|
|
2362
|
+
battery: byte2 & 0b01111111,
|
|
2363
|
+
calibration: !!(byte15 & 0b10000000),
|
|
2364
|
+
status: WoSmartLock.getLockStatus(byte15 & 0b01110000),
|
|
2365
|
+
update_from_secondary_lock: !!(byte15 & 0b00001000),
|
|
2366
|
+
door_open: !!(byte15 & 0b00000100),
|
|
2367
|
+
double_lock_mode: !!(byte16 & 0b10000000),
|
|
2368
|
+
unclosed_alarm: !!(byte16 & 0b00100000),
|
|
2369
|
+
unlocked_alarm: !!(byte16 & 0b00010000),
|
|
2370
|
+
auto_lock_paused: !!(byte16 & 0b00000010),
|
|
2371
|
+
night_latch: !!(manufacturerData.length > 11 && manufacturerData.readUInt8(11) & 0b00000001),
|
|
2372
|
+
};
|
|
2373
|
+
return data;
|
|
2374
|
+
}
|
|
2375
|
+
constructor(peripheral, noble) {
|
|
2376
|
+
super(peripheral, noble);
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Initializes the encryption key info for valid lock communication.
|
|
2380
|
+
* @param {string} keyId - The key ID.
|
|
2381
|
+
* @param {string} encryptionKey - The encryption key.
|
|
2382
|
+
*/
|
|
2383
|
+
async setKey(keyId, encryptionKey) {
|
|
2384
|
+
this.iv = null;
|
|
2385
|
+
this.key_id = keyId;
|
|
2386
|
+
this.encryption_key = Buffer.from(encryptionKey, 'hex');
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Unlocks the Smart Lock.
|
|
2390
|
+
* @returns {Promise<number>} - The result of the unlock operation.
|
|
2391
|
+
*/
|
|
2392
|
+
async unlock() {
|
|
2393
|
+
const resBuf = await this.operateLock(WoSmartLockCommands.UNLOCK);
|
|
2394
|
+
return resBuf ? WoSmartLock.validateResponse(resBuf) : WoSmartLock.Result.ERROR;
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Unlocks the Smart Lock without unlatching the door.
|
|
2398
|
+
* @returns {Promise<number>} - The result of the unlock operation.
|
|
2399
|
+
*/
|
|
2400
|
+
async unlockNoUnlatch() {
|
|
2401
|
+
const resBuf = await this.operateLock(WoSmartLockCommands.UNLOCK_NO_UNLATCH);
|
|
2402
|
+
return resBuf ? WoSmartLock.validateResponse(resBuf) : WoSmartLock.Result.ERROR;
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Locks the Smart Lock.
|
|
2406
|
+
* @returns {Promise<number>} - The result of the lock operation.
|
|
2407
|
+
*/
|
|
2408
|
+
async lock() {
|
|
2409
|
+
const resBuf = await this.operateLock(WoSmartLockCommands.LOCK);
|
|
2410
|
+
return resBuf ? WoSmartLock.validateResponse(resBuf) : WoSmartLock.Result.ERROR;
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Gets general state info from the Smart Lock.
|
|
2414
|
+
* @returns {Promise<object | null>} - The state object or null if an error occurred.
|
|
2415
|
+
*/
|
|
2416
|
+
async info() {
|
|
2417
|
+
const resBuf = await this.operateLock(WoSmartLockCommands.LOCK_INFO);
|
|
2418
|
+
if (resBuf) {
|
|
2419
|
+
return {
|
|
2420
|
+
calibration: Boolean(resBuf[1] & 0b10000000),
|
|
2421
|
+
status: WoSmartLock.getLockStatus((resBuf[1] & 0b01110000)),
|
|
2422
|
+
door_open: Boolean(resBuf[1] & 0b00000100),
|
|
2423
|
+
unclosed_alarm: Boolean(resBuf[2] & 0b00100000),
|
|
2424
|
+
unlocked_alarm: Boolean(resBuf[2] & 0b00010000),
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Encrypts a string using AES-128-CTR.
|
|
2431
|
+
* @param {string} str - The string to encrypt.
|
|
2432
|
+
* @returns {Promise<string>} - The encrypted string in hex format.
|
|
2433
|
+
*/
|
|
2434
|
+
async encrypt(str) {
|
|
2435
|
+
const cipher = Crypto.createCipheriv('aes-128-ctr', this.encryption_key, this.iv);
|
|
2436
|
+
return Buffer.concat([cipher.update(str, 'hex'), cipher.final()]).toString('hex');
|
|
2437
|
+
}
|
|
2438
|
+
/**
|
|
2439
|
+
* Decrypts a buffer using AES-128-CTR.
|
|
2440
|
+
* @param {Buffer} data - The data to decrypt.
|
|
2441
|
+
* @returns {Promise<Buffer>} - The decrypted data.
|
|
2442
|
+
*/
|
|
2443
|
+
async decrypt(data) {
|
|
2444
|
+
const decipher = Crypto.createDecipheriv('aes-128-ctr', this.encryption_key, this.iv);
|
|
2445
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Retrieves the IV from the device.
|
|
2449
|
+
* @returns {Promise<Buffer>} - The IV buffer.
|
|
2450
|
+
*/
|
|
2451
|
+
async getIv() {
|
|
2452
|
+
if (!this.iv) {
|
|
2453
|
+
const res = await this.operateLock(WoSmartLockCommands.GET_CKIV + this.key_id, false);
|
|
2454
|
+
if (res) {
|
|
2455
|
+
this.iv = res.subarray(4);
|
|
2456
|
+
}
|
|
2457
|
+
else {
|
|
2458
|
+
throw new Error('Failed to retrieve IV from the device.');
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return this.iv;
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Sends an encrypted command to the device.
|
|
2465
|
+
* @param {string} key - The command key.
|
|
2466
|
+
* @returns {Promise<Buffer>} - The response buffer.
|
|
2467
|
+
*/
|
|
2468
|
+
async encryptedCommand(key) {
|
|
2469
|
+
const iv = await this.getIv();
|
|
2470
|
+
const req = Buffer.from(key.substring(0, 2) + this.key_id + Buffer.from(iv.subarray(0, 2)).toString('hex') + await this.encrypt(key.substring(2)), 'hex');
|
|
2471
|
+
const bytes = await this.command(req);
|
|
2472
|
+
const buf = Buffer.from(bytes);
|
|
2473
|
+
const code = WoSmartLock.validateResponse(buf);
|
|
2474
|
+
if (await code !== WoSmartLock.Result.ERROR) {
|
|
2475
|
+
return Buffer.concat([buf.subarray(0, 1), await this.decrypt(buf.subarray(4))]);
|
|
2476
|
+
}
|
|
2477
|
+
else {
|
|
2478
|
+
throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Operates the lock with the given command.
|
|
2483
|
+
* @param {string} key - The command key.
|
|
2484
|
+
* @param {boolean} [encrypt] - Whether to encrypt the command.
|
|
2485
|
+
* @returns {Promise<Buffer>} - The response buffer.
|
|
2486
|
+
*/
|
|
2487
|
+
async operateLock(key, encrypt = true) {
|
|
2488
|
+
if (encrypt) {
|
|
2489
|
+
return this.encryptedCommand(key);
|
|
2490
|
+
}
|
|
2491
|
+
const req = Buffer.from(`${key.substring(0, 2)}000000${key.substring(2)}`, 'hex');
|
|
2492
|
+
const bytes = await this.command(req);
|
|
2493
|
+
const buf = Buffer.from(bytes);
|
|
2494
|
+
const code = WoSmartLock.validateResponse(buf);
|
|
2495
|
+
if (await code === WoSmartLock.Result.ERROR) {
|
|
2496
|
+
throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
|
|
2497
|
+
}
|
|
2498
|
+
return buf;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Class representing a WoSmartLockPro device.
|
|
2503
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/lock.md
|
|
2504
|
+
*/
|
|
2505
|
+
export class WoSmartLockPro extends SwitchbotDevice {
|
|
2506
|
+
iv = null;
|
|
2507
|
+
key_id = '';
|
|
2508
|
+
encryption_key = null;
|
|
2509
|
+
static Result = {
|
|
2510
|
+
ERROR: 0x00,
|
|
2511
|
+
SUCCESS: 0x01,
|
|
2512
|
+
SUCCESS_LOW_BATTERY: 0x06,
|
|
2513
|
+
};
|
|
2514
|
+
static async validateResponse(res) {
|
|
2515
|
+
if (res.length >= 3) {
|
|
2516
|
+
const result = res.readUInt8(0);
|
|
2517
|
+
if (result === WoSmartLockPro.Result.SUCCESS || result === WoSmartLockPro.Result.SUCCESS_LOW_BATTERY) {
|
|
2518
|
+
return result;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
return WoSmartLockPro.Result.ERROR;
|
|
2522
|
+
}
|
|
2523
|
+
static getLockStatus(code) {
|
|
2524
|
+
const statusMap = {
|
|
2525
|
+
0b0000000: 'LOCKED',
|
|
2526
|
+
0b0010000: 'UNLOCKED',
|
|
2527
|
+
0b0100000: 'LOCKING',
|
|
2528
|
+
0b0110000: 'UNLOCKING',
|
|
2529
|
+
0b1000000: 'LOCKING_STOP',
|
|
2530
|
+
0b1010000: 'UNLOCKING_STOP',
|
|
2531
|
+
0b01100000: 'NOT_FULLY_LOCKED', // Only EU lock type
|
|
2532
|
+
};
|
|
2533
|
+
return statusMap[code] || 'UNKNOWN';
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* Parses the service data from the SwitchBot Strip Light.
|
|
2537
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
2538
|
+
* @param {Buffer} manufacturerData - The manufacturer data buffer.
|
|
2539
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
2540
|
+
* @returns {Promise<lockProServiceData | null>} - Parsed service data or null if invalid.
|
|
2541
|
+
*/
|
|
2542
|
+
static async parseServiceData(serviceData, manufacturerData, emitLog) {
|
|
2543
|
+
if (manufacturerData.length < 11) {
|
|
2544
|
+
emitLog('debugerror', `[parseServiceDataForWoSmartLockPro] Buffer length ${manufacturerData.length} is too short!`);
|
|
2545
|
+
return null;
|
|
2546
|
+
}
|
|
2547
|
+
const byte2 = serviceData.readUInt8(2);
|
|
2548
|
+
const byte7 = manufacturerData.readUInt8(7);
|
|
2549
|
+
const byte8 = manufacturerData.readUInt8(8);
|
|
2550
|
+
const byte9 = manufacturerData.readUInt8(9);
|
|
2551
|
+
const byte11 = manufacturerData.readUInt8(11);
|
|
2552
|
+
const data = {
|
|
2553
|
+
model: SwitchBotBLEModel.LockPro,
|
|
2554
|
+
modelName: SwitchBotBLEModelName.LockPro,
|
|
2555
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro,
|
|
2556
|
+
battery: byte2 & 0b01111111,
|
|
2557
|
+
calibration: !!(byte7 & 0b10000000),
|
|
2558
|
+
status: WoSmartLockPro.getLockStatus((byte7 & 0b00111000) >> 3),
|
|
2559
|
+
door_open: !!(byte8 & 0b01100000),
|
|
2560
|
+
update_from_secondary_lock: false,
|
|
2561
|
+
double_lock_mode: false,
|
|
2562
|
+
unclosed_alarm: !!(byte11 & 0b10000000),
|
|
2563
|
+
unlocked_alarm: !!(byte11 & 0b01000000),
|
|
2564
|
+
auto_lock_paused: !!(byte8 & 0b100000),
|
|
2565
|
+
night_latch: !!(byte9 & 0b00000001),
|
|
2566
|
+
};
|
|
2567
|
+
return data;
|
|
2568
|
+
}
|
|
2569
|
+
constructor(peripheral, noble) {
|
|
2570
|
+
super(peripheral, noble);
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Initializes the encryption key info for valid lock communication.
|
|
2574
|
+
* @param {string} keyId - The key ID.
|
|
2575
|
+
* @param {string} encryptionKey - The encryption key.
|
|
2576
|
+
*/
|
|
2577
|
+
async setKey(keyId, encryptionKey) {
|
|
2578
|
+
this.iv = null;
|
|
2579
|
+
this.key_id = keyId;
|
|
2580
|
+
this.encryption_key = Buffer.from(encryptionKey, 'hex');
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Unlocks the Smart Lock.
|
|
2584
|
+
* @returns {Promise<number>} - The result of the unlock operation.
|
|
2585
|
+
*/
|
|
2586
|
+
async unlock() {
|
|
2587
|
+
const resBuf = await this.operateLockPro(WoSmartLockProCommands.UNLOCK);
|
|
2588
|
+
return resBuf ? WoSmartLockPro.validateResponse(resBuf) : WoSmartLockPro.Result.ERROR;
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Unlocks the Smart Lock without unlatching the door.
|
|
2592
|
+
* @returns {Promise<number>} - The result of the unlock operation.
|
|
2593
|
+
*/
|
|
2594
|
+
async unlockNoUnlatch() {
|
|
2595
|
+
const resBuf = await this.operateLockPro(WoSmartLockProCommands.UNLOCK_NO_UNLATCH);
|
|
2596
|
+
return resBuf ? WoSmartLockPro.validateResponse(resBuf) : WoSmartLockPro.Result.ERROR;
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Locks the Smart Lock.
|
|
2600
|
+
* @returns {Promise<number>} - The result of the lock operation.
|
|
2601
|
+
*/
|
|
2602
|
+
async lock() {
|
|
2603
|
+
const resBuf = await this.operateLockPro(WoSmartLockProCommands.LOCK);
|
|
2604
|
+
return resBuf ? WoSmartLockPro.validateResponse(resBuf) : WoSmartLockPro.Result.ERROR;
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Gets general state info from the Smart Lock.
|
|
2608
|
+
* @returns {Promise<object | null>} - The state object or null if an error occurred.
|
|
2609
|
+
*/
|
|
2610
|
+
async info() {
|
|
2611
|
+
const resBuf = await this.operateLockPro(WoSmartLockProCommands.LOCK_INFO);
|
|
2612
|
+
if (resBuf) {
|
|
2613
|
+
return {
|
|
2614
|
+
calibration: Boolean(resBuf[0] & 0b10000000),
|
|
2615
|
+
status: WoSmartLockPro.getLockStatus((resBuf[0] & 0b01110000) >> 4),
|
|
2616
|
+
door_open: Boolean(resBuf[0] & 0b00000100),
|
|
2617
|
+
unclosed_alarm: Boolean(resBuf[1] & 0b00100000),
|
|
2618
|
+
unlocked_alarm: Boolean(resBuf[1] & 0b00010000),
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
return null;
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Encrypts a string using AES-128-CTR.
|
|
2625
|
+
* @param {string} str - The string to encrypt.
|
|
2626
|
+
* @returns {Promise<string>} - The encrypted string in hex format.
|
|
2627
|
+
*/
|
|
2628
|
+
async encrypt(str) {
|
|
2629
|
+
const cipher = Crypto.createCipheriv('aes-128-ctr', this.encryption_key, this.iv);
|
|
2630
|
+
return Buffer.concat([cipher.update(str, 'hex'), cipher.final()]).toString('hex');
|
|
2631
|
+
}
|
|
2632
|
+
/**
|
|
2633
|
+
* Decrypts a buffer using AES-128-CTR.
|
|
2634
|
+
* @param {Buffer} data - The data to decrypt.
|
|
2635
|
+
* @returns {Promise<Buffer>} - The decrypted data.
|
|
2636
|
+
*/
|
|
2637
|
+
async decrypt(data) {
|
|
2638
|
+
const decipher = Crypto.createDecipheriv('aes-128-ctr', this.encryption_key, this.iv);
|
|
2639
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Retrieves the IV from the device.
|
|
2643
|
+
* @returns {Promise<Buffer>} - The IV buffer.
|
|
2644
|
+
*/
|
|
2645
|
+
async getIv() {
|
|
2646
|
+
if (!this.iv) {
|
|
2647
|
+
const res = await this.operateLockPro(WoSmartLockProCommands.GET_CKIV + this.key_id, false);
|
|
2648
|
+
if (res) {
|
|
2649
|
+
this.iv = res.subarray(4);
|
|
2650
|
+
}
|
|
2651
|
+
else {
|
|
2652
|
+
throw new Error('Failed to retrieve IV from the device.');
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
return this.iv;
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Sends an encrypted command to the device.
|
|
2659
|
+
* @param {string} key - The command key.
|
|
2660
|
+
* @returns {Promise<Buffer>} - The response buffer.
|
|
2661
|
+
*/
|
|
2662
|
+
async encryptedCommand(key) {
|
|
2663
|
+
const iv = await this.getIv();
|
|
2664
|
+
const req = Buffer.from(key.substring(0, 2) + this.key_id + Buffer.from(iv.subarray(0, 2)).toString('hex') + await this.encrypt(key.substring(2)), 'hex');
|
|
2665
|
+
const bytes = await this.command(req);
|
|
2666
|
+
const buf = Buffer.from(bytes);
|
|
2667
|
+
const code = WoSmartLockPro.validateResponse(buf);
|
|
2668
|
+
if (await code !== WoSmartLockPro.Result.ERROR) {
|
|
2669
|
+
return Buffer.concat([buf.subarray(0, 1), await this.decrypt(buf.subarray(4))]);
|
|
2670
|
+
}
|
|
2671
|
+
else {
|
|
2672
|
+
throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Operates the lock with the given command.
|
|
2677
|
+
* @param {string} key - The command key.
|
|
2678
|
+
* @param {boolean} [encrypt] - Whether to encrypt the command.
|
|
2679
|
+
* @returns {Promise<Buffer>} - The response buffer.
|
|
2680
|
+
*/
|
|
2681
|
+
async operateLockPro(key, encrypt = true) {
|
|
2682
|
+
if (encrypt) {
|
|
2683
|
+
return this.encryptedCommand(key);
|
|
2684
|
+
}
|
|
2685
|
+
const req = Buffer.from(`${key.substring(0, 2)}000000${key.substring(2)}`, 'hex');
|
|
2686
|
+
const bytes = await this.command(req);
|
|
2687
|
+
const buf = Buffer.from(bytes);
|
|
2688
|
+
const code = WoSmartLockPro.validateResponse(buf);
|
|
2689
|
+
if (await code === WoSmartLockPro.Result.ERROR) {
|
|
2690
|
+
throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
|
|
2691
|
+
}
|
|
2692
|
+
return buf;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Class representing a WoStrip device.
|
|
2697
|
+
* @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/ledstriplight.md
|
|
2698
|
+
*/
|
|
2699
|
+
export class WoStrip extends SwitchbotDevice {
|
|
2700
|
+
/**
|
|
2701
|
+
* Parses the service data from the SwitchBot Strip Light.
|
|
2702
|
+
* @param {Buffer} serviceData - The service data buffer.
|
|
2703
|
+
* @param {Function} emitLog - The function to emit log messages.
|
|
2704
|
+
* @returns {Promise<stripLightServiceData | null>} - Parsed service data or null if invalid.
|
|
2705
|
+
*/
|
|
2706
|
+
static async parseServiceData(serviceData, emitLog) {
|
|
2707
|
+
if (serviceData.length !== 18) {
|
|
2708
|
+
emitLog('debugerror', `[parseServiceDataForWoStrip] Buffer length ${serviceData.length} !== 18!`);
|
|
2709
|
+
return null;
|
|
2710
|
+
}
|
|
2711
|
+
const [byte3, byte4, byte5, byte7, byte8, byte9, byte10] = [
|
|
2712
|
+
serviceData.readUInt8(3),
|
|
2713
|
+
serviceData.readUInt8(4),
|
|
2714
|
+
serviceData.readUInt8(5),
|
|
2715
|
+
serviceData.readUInt8(7),
|
|
2716
|
+
serviceData.readUInt8(8),
|
|
2717
|
+
serviceData.readUInt8(9),
|
|
2718
|
+
serviceData.readUInt8(10),
|
|
2719
|
+
];
|
|
2720
|
+
const data = {
|
|
2721
|
+
model: SwitchBotBLEModel.StripLight,
|
|
2722
|
+
modelName: SwitchBotBLEModelName.StripLight,
|
|
2723
|
+
modelFriendlyName: SwitchBotBLEModelFriendlyName.StripLight,
|
|
2724
|
+
power: !!(byte7 & 0b10000000),
|
|
2725
|
+
state: !!(byte7 & 0b10000000),
|
|
2726
|
+
brightness: byte7 & 0b01111111,
|
|
2727
|
+
red: byte3,
|
|
2728
|
+
green: byte4,
|
|
2729
|
+
blue: byte5,
|
|
2730
|
+
delay: byte8 & 0b10000000,
|
|
2731
|
+
preset: byte8 & 0b00001000,
|
|
2732
|
+
color_mode: byte8 & 0b00000111,
|
|
2733
|
+
speed: byte9 & 0b01111111,
|
|
2734
|
+
loop_index: byte10 & 0b11111110,
|
|
2735
|
+
};
|
|
2736
|
+
return data;
|
|
2737
|
+
}
|
|
2738
|
+
constructor(peripheral, noble) {
|
|
2739
|
+
super(peripheral, noble);
|
|
2740
|
+
}
|
|
2741
|
+
/**
|
|
2742
|
+
* Reads the state of the strip light.
|
|
2743
|
+
* @returns {Promise<boolean>} - Resolves with true if the strip light is ON, false otherwise.
|
|
2744
|
+
*/
|
|
2745
|
+
async readState() {
|
|
2746
|
+
return this.operateStripLight([0x57, 0x0F, 0x4A, 0x01]);
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* Sets the state of the strip light.
|
|
2750
|
+
* @public
|
|
2751
|
+
* @param {number[]} reqByteArray - The request byte array.
|
|
2752
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
2753
|
+
*/
|
|
2754
|
+
async setState(reqByteArray) {
|
|
2755
|
+
const base = [0x57, 0x0F, 0x49, 0x01];
|
|
2756
|
+
return this.operateStripLight([...base, ...reqByteArray]);
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Turns the strip light on.
|
|
2760
|
+
* @returns {Promise<boolean>} - Resolves with true if the strip light is ON.
|
|
2761
|
+
*/
|
|
2762
|
+
async turnOn() {
|
|
2763
|
+
return this.setState([0x01, 0x01]);
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Turns the strip light off.
|
|
2767
|
+
* @returns {Promise<boolean>} - Resolves with true if the strip light is OFF.
|
|
2768
|
+
*/
|
|
2769
|
+
async turnOff() {
|
|
2770
|
+
return this.setState([0x01, 0x02]);
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Sets the brightness of the strip light.
|
|
2774
|
+
* @param {number} brightness - The brightness percentage (0-100).
|
|
2775
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
2776
|
+
*/
|
|
2777
|
+
async setBrightness(brightness) {
|
|
2778
|
+
if (typeof brightness !== 'number' || brightness < 0 || brightness > 100) {
|
|
2779
|
+
throw new TypeError(`Invalid brightness value: ${brightness}`);
|
|
2780
|
+
}
|
|
2781
|
+
return this.setState([0x02, 0x14, brightness]);
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Sets the RGB values of the strip light.
|
|
2785
|
+
* @param {number} brightness - The brightness percentage (0-100).
|
|
2786
|
+
* @param {number} red - The red value (0-255).
|
|
2787
|
+
* @param {number} green - The green value (0-255).
|
|
2788
|
+
* @param {number} blue - The blue value (0-255).
|
|
2789
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
2790
|
+
*/
|
|
2791
|
+
async setRGB(brightness, red, green, blue) {
|
|
2792
|
+
if (![brightness, red, green, blue].every(val => typeof val === 'number')) {
|
|
2793
|
+
throw new TypeError('Invalid RGB or brightness value');
|
|
2794
|
+
}
|
|
2795
|
+
brightness = Math.max(0, Math.min(100, brightness));
|
|
2796
|
+
red = Math.max(0, Math.min(255, red));
|
|
2797
|
+
green = Math.max(0, Math.min(255, green));
|
|
2798
|
+
blue = Math.max(0, Math.min(255, blue));
|
|
2799
|
+
return this.setState([0x02, 0x12, brightness, red, green, blue]);
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Operates the strip light with the given byte array.
|
|
2803
|
+
* @public
|
|
2804
|
+
* @param {number[]} bytes - The byte array to send.
|
|
2805
|
+
* @returns {Promise<boolean>} - Resolves with true if the operation was successful.
|
|
2806
|
+
*/
|
|
2807
|
+
async operateStripLight(bytes) {
|
|
2808
|
+
const req_buf = Buffer.from(bytes);
|
|
2809
|
+
const res_buf = await this.command(req_buf);
|
|
2810
|
+
if (res_buf.length !== 2) {
|
|
2811
|
+
throw new Error(`Expecting a 2-byte response, got instead: 0x${res_buf.toString('hex')}`);
|
|
2812
|
+
}
|
|
2813
|
+
const code = res_buf.readUInt8(1);
|
|
2814
|
+
if (code === 0x00 || code === 0x80) {
|
|
2815
|
+
return code === 0x80;
|
|
2816
|
+
}
|
|
2817
|
+
else {
|
|
2818
|
+
throw new Error(`The device returned an error: 0x${res_buf.toString('hex')}`);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
346
2822
|
//# sourceMappingURL=device.js.map
|