node-switchbot 3.6.5 → 4.0.0-beta.0

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.
Files changed (548) hide show
  1. package/.github/npm-version-script-esm.js +97 -0
  2. package/CHANGELOG.md +586 -0
  3. package/PRODUCTION_READY.md +135 -0
  4. package/README.md +92 -0
  5. package/dist/api.d.ts +130 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +286 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/ble.d.ts +116 -0
  10. package/dist/ble.d.ts.map +1 -0
  11. package/dist/ble.js +430 -0
  12. package/dist/ble.js.map +1 -0
  13. package/dist/devices/base.d.ts +146 -0
  14. package/dist/devices/base.d.ts.map +1 -0
  15. package/dist/devices/base.js +324 -0
  16. package/dist/devices/base.js.map +1 -0
  17. package/dist/devices/index.d.ts +30 -0
  18. package/dist/devices/index.d.ts.map +1 -0
  19. package/dist/devices/index.js +34 -0
  20. package/dist/devices/index.js.map +1 -0
  21. package/dist/devices/wo-air-purifier-table.d.ts +8 -0
  22. package/dist/devices/wo-air-purifier-table.d.ts.map +1 -0
  23. package/dist/devices/wo-air-purifier-table.js +12 -0
  24. package/dist/devices/wo-air-purifier-table.js.map +1 -0
  25. package/dist/devices/wo-air-purifier.d.ts +28 -0
  26. package/dist/devices/wo-air-purifier.d.ts.map +1 -0
  27. package/dist/devices/wo-air-purifier.js +105 -0
  28. package/dist/devices/wo-air-purifier.js.map +1 -0
  29. package/dist/devices/wo-blind-tilt.d.ts +36 -0
  30. package/dist/devices/wo-blind-tilt.d.ts.map +1 -0
  31. package/dist/devices/wo-blind-tilt.js +95 -0
  32. package/dist/devices/wo-blind-tilt.js.map +1 -0
  33. package/dist/devices/wo-bulb.d.ts +32 -0
  34. package/dist/devices/wo-bulb.d.ts.map +1 -0
  35. package/dist/devices/wo-bulb.js +109 -0
  36. package/dist/devices/wo-bulb.js.map +1 -0
  37. package/dist/devices/wo-ceiling-light.d.ts +8 -0
  38. package/dist/devices/wo-ceiling-light.d.ts.map +1 -0
  39. package/dist/devices/wo-ceiling-light.js +12 -0
  40. package/dist/devices/wo-ceiling-light.js.map +1 -0
  41. package/dist/devices/wo-contact.d.ts +12 -0
  42. package/dist/devices/wo-contact.d.ts.map +1 -0
  43. package/dist/devices/wo-contact.js +50 -0
  44. package/dist/devices/wo-contact.js.map +1 -0
  45. package/dist/devices/wo-curtain.d.ts +28 -0
  46. package/dist/devices/wo-curtain.d.ts.map +1 -0
  47. package/dist/devices/wo-curtain.js +82 -0
  48. package/dist/devices/wo-curtain.js.map +1 -0
  49. package/dist/devices/wo-hand.d.ts +24 -0
  50. package/dist/devices/wo-hand.d.ts.map +1 -0
  51. package/dist/devices/wo-hand.js +70 -0
  52. package/dist/devices/wo-hand.js.map +1 -0
  53. package/dist/devices/wo-hub2.d.ts +12 -0
  54. package/dist/devices/wo-hub2.d.ts.map +1 -0
  55. package/dist/devices/wo-hub2.js +48 -0
  56. package/dist/devices/wo-hub2.js.map +1 -0
  57. package/dist/devices/wo-hub3.d.ts +8 -0
  58. package/dist/devices/wo-hub3.d.ts.map +1 -0
  59. package/dist/devices/wo-hub3.js +12 -0
  60. package/dist/devices/wo-hub3.js.map +1 -0
  61. package/dist/devices/wo-humi.d.ts +28 -0
  62. package/dist/devices/wo-humi.d.ts.map +1 -0
  63. package/dist/devices/wo-humi.js +91 -0
  64. package/dist/devices/wo-humi.js.map +1 -0
  65. package/dist/devices/wo-humi2.d.ts +8 -0
  66. package/dist/devices/wo-humi2.d.ts.map +1 -0
  67. package/dist/devices/wo-humi2.js +12 -0
  68. package/dist/devices/wo-humi2.js.map +1 -0
  69. package/dist/devices/wo-io-sensor-th.d.ts +8 -0
  70. package/dist/devices/wo-io-sensor-th.d.ts.map +1 -0
  71. package/dist/devices/wo-io-sensor-th.js +12 -0
  72. package/dist/devices/wo-io-sensor-th.js.map +1 -0
  73. package/dist/devices/wo-keypad.d.ts +13 -0
  74. package/dist/devices/wo-keypad.d.ts.map +1 -0
  75. package/dist/devices/wo-keypad.js +46 -0
  76. package/dist/devices/wo-keypad.js.map +1 -0
  77. package/dist/devices/wo-leak.d.ts +12 -0
  78. package/dist/devices/wo-leak.d.ts.map +1 -0
  79. package/dist/devices/wo-leak.js +46 -0
  80. package/dist/devices/wo-leak.js.map +1 -0
  81. package/dist/devices/wo-lock-pro.d.ts +24 -0
  82. package/dist/devices/wo-lock-pro.d.ts.map +1 -0
  83. package/dist/devices/wo-lock-pro.js +72 -0
  84. package/dist/devices/wo-lock-pro.js.map +1 -0
  85. package/dist/devices/wo-lock.d.ts +20 -0
  86. package/dist/devices/wo-lock.d.ts.map +1 -0
  87. package/dist/devices/wo-lock.js +64 -0
  88. package/dist/devices/wo-lock.js.map +1 -0
  89. package/dist/devices/wo-plug-mini-jp.d.ts +8 -0
  90. package/dist/devices/wo-plug-mini-jp.d.ts.map +1 -0
  91. package/dist/devices/wo-plug-mini-jp.js +12 -0
  92. package/dist/devices/wo-plug-mini-jp.js.map +1 -0
  93. package/dist/devices/wo-plug-mini-us.d.ts +24 -0
  94. package/dist/devices/wo-plug-mini-us.d.ts.map +1 -0
  95. package/dist/devices/wo-plug-mini-us.js +70 -0
  96. package/dist/devices/wo-plug-mini-us.js.map +1 -0
  97. package/dist/devices/wo-presence.d.ts +12 -0
  98. package/dist/devices/wo-presence.d.ts.map +1 -0
  99. package/dist/devices/wo-presence.js +48 -0
  100. package/dist/devices/wo-presence.js.map +1 -0
  101. package/dist/devices/wo-relay-switch-1.d.ts +24 -0
  102. package/dist/devices/wo-relay-switch-1.d.ts.map +1 -0
  103. package/dist/devices/wo-relay-switch-1.js +70 -0
  104. package/dist/devices/wo-relay-switch-1.js.map +1 -0
  105. package/dist/devices/wo-relay-switch-1pm.d.ts +8 -0
  106. package/dist/devices/wo-relay-switch-1pm.d.ts.map +1 -0
  107. package/dist/devices/wo-relay-switch-1pm.js +12 -0
  108. package/dist/devices/wo-relay-switch-1pm.js.map +1 -0
  109. package/dist/devices/wo-remote.d.ts +13 -0
  110. package/dist/devices/wo-remote.d.ts.map +1 -0
  111. package/dist/devices/wo-remote.js +45 -0
  112. package/dist/devices/wo-remote.js.map +1 -0
  113. package/dist/devices/wo-sensor-th-plus.d.ts +8 -0
  114. package/dist/devices/wo-sensor-th-plus.d.ts.map +1 -0
  115. package/dist/devices/wo-sensor-th-plus.js +12 -0
  116. package/dist/devices/wo-sensor-th-plus.js.map +1 -0
  117. package/dist/devices/wo-sensor-th-pro-co2.d.ts +8 -0
  118. package/dist/devices/wo-sensor-th-pro-co2.d.ts.map +1 -0
  119. package/dist/devices/wo-sensor-th-pro-co2.js +12 -0
  120. package/dist/devices/wo-sensor-th-pro-co2.js.map +1 -0
  121. package/dist/devices/wo-sensor-th-pro.d.ts +8 -0
  122. package/dist/devices/wo-sensor-th-pro.d.ts.map +1 -0
  123. package/dist/devices/wo-sensor-th-pro.js +12 -0
  124. package/dist/devices/wo-sensor-th-pro.js.map +1 -0
  125. package/dist/devices/wo-sensor-th.d.ts +12 -0
  126. package/dist/devices/wo-sensor-th.d.ts.map +1 -0
  127. package/dist/devices/wo-sensor-th.js +49 -0
  128. package/dist/devices/wo-sensor-th.js.map +1 -0
  129. package/dist/devices/wo-strip.d.ts +8 -0
  130. package/dist/devices/wo-strip.d.ts.map +1 -0
  131. package/dist/devices/wo-strip.js +12 -0
  132. package/dist/devices/wo-strip.js.map +1 -0
  133. package/dist/errors.d.ts +63 -0
  134. package/dist/errors.d.ts.map +1 -0
  135. package/dist/errors.js +103 -0
  136. package/dist/errors.js.map +1 -0
  137. package/dist/index.d.ts +10 -6
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +9 -10
  140. package/dist/index.js.map +1 -1
  141. package/dist/settings.d.ts +126 -47
  142. package/dist/settings.d.ts.map +1 -1
  143. package/dist/settings.js +214 -64
  144. package/dist/settings.js.map +1 -1
  145. package/dist/switchbot.d.ts +80 -0
  146. package/dist/switchbot.d.ts.map +1 -0
  147. package/dist/switchbot.js +376 -0
  148. package/dist/switchbot.js.map +1 -0
  149. package/dist/types/api.d.ts +187 -0
  150. package/dist/types/api.d.ts.map +1 -0
  151. package/dist/types/api.js +6 -0
  152. package/dist/types/api.js.map +1 -0
  153. package/dist/types/ble.d.ts +246 -343
  154. package/dist/types/ble.d.ts.map +1 -1
  155. package/dist/types/ble.js +94 -1
  156. package/dist/types/ble.js.map +1 -1
  157. package/dist/types/device.d.ts +209 -0
  158. package/dist/types/device.d.ts.map +1 -0
  159. package/dist/types/device.js +6 -0
  160. package/dist/types/device.js.map +1 -0
  161. package/dist/types/index.d.ts +115 -0
  162. package/dist/types/index.d.ts.map +1 -0
  163. package/dist/types/index.js +19 -0
  164. package/dist/types/index.js.map +1 -0
  165. package/dist/utils/index.d.ts +97 -0
  166. package/dist/utils/index.d.ts.map +1 -0
  167. package/dist/utils/index.js +206 -0
  168. package/dist/utils/index.js.map +1 -0
  169. package/docs/assets/hierarchy.js +1 -1
  170. package/docs/assets/highlight.css +20 -6
  171. package/docs/assets/icons.js +1 -1
  172. package/docs/assets/icons.svg +1 -1
  173. package/docs/assets/main.js +2 -2
  174. package/docs/assets/navigation.js +1 -1
  175. package/docs/assets/search.js +1 -1
  176. package/docs/assets/style.css +3 -3
  177. package/docs/classes/APIError.html +6 -0
  178. package/docs/classes/APINotAvailableError.html +4 -0
  179. package/docs/classes/BLEConnection.html +17 -0
  180. package/docs/classes/BLENotAvailableError.html +4 -0
  181. package/docs/classes/BLEScanner.html +15 -0
  182. package/docs/classes/CommandFailedError.html +6 -0
  183. package/docs/classes/ConnectionTimeoutError.html +5 -0
  184. package/docs/classes/DeviceManager.html +25 -0
  185. package/docs/classes/DeviceNotFoundError.html +4 -0
  186. package/docs/classes/DiscoveryError.html +5 -0
  187. package/docs/classes/OpenAPIClient.html +66 -0
  188. package/docs/classes/SwitchBot.html +21 -0
  189. package/docs/classes/SwitchBotDevice.html +28 -0
  190. package/docs/classes/SwitchBotError.html +4 -0
  191. package/docs/classes/ValidationError.html +5 -0
  192. package/docs/classes/WoAirPurifier.html +32 -80
  193. package/docs/classes/WoAirPurifierTable.html +36 -83
  194. package/docs/classes/WoBlindTilt.html +34 -73
  195. package/docs/classes/WoBulb.html +33 -85
  196. package/docs/classes/WoCeilingLight.html +38 -107
  197. package/docs/classes/WoContact.html +27 -52
  198. package/docs/classes/WoCurtain.html +32 -69
  199. package/docs/classes/WoHand.html +30 -59
  200. package/docs/classes/WoHub2.html +27 -52
  201. package/docs/classes/WoHub3.html +28 -52
  202. package/docs/classes/WoHumi.html +33 -65
  203. package/docs/classes/WoHumi2.html +36 -67
  204. package/docs/classes/WoIOSensorTH.html +28 -53
  205. package/docs/classes/WoKeypad.html +28 -52
  206. package/docs/classes/WoLeak.html +27 -53
  207. package/docs/classes/WoPlugMiniJP.html +34 -78
  208. package/docs/classes/WoPlugMiniUS.html +30 -75
  209. package/docs/classes/WoPresence.html +27 -60
  210. package/docs/classes/WoRelaySwitch1.html +31 -55
  211. package/docs/classes/WoRelaySwitch1PM.html +34 -57
  212. package/docs/classes/WoRemote.html +28 -52
  213. package/docs/classes/WoSensorTH.html +27 -46
  214. package/docs/classes/WoSensorTHPlus.html +28 -46
  215. package/docs/classes/WoSensorTHPro.html +28 -46
  216. package/docs/classes/WoSensorTHProCO2.html +28 -46
  217. package/docs/classes/WoSmartLock.html +29 -111
  218. package/docs/classes/WoSmartLockPro.html +31 -111
  219. package/docs/classes/WoStrip.html +38 -94
  220. package/docs/enums/LogLevel.html +4 -7
  221. package/docs/enums/SwitchBotBLEModel.html +11 -5
  222. package/docs/enums/SwitchBotBLEModelName.html +13 -6
  223. package/docs/functions/updateBaseURL.html +3 -3
  224. package/docs/hierarchy.html +1 -1
  225. package/docs/index.html +40 -6
  226. package/docs/interfaces/APICommandRequest.html +5 -0
  227. package/docs/interfaces/APICommandResponse.html +5 -0
  228. package/docs/interfaces/APIDevice.html +18 -0
  229. package/docs/interfaces/APIDeviceStatus.html +36 -0
  230. package/docs/interfaces/APIErrorResponse.html +5 -0
  231. package/docs/interfaces/APIResponse.html +5 -0
  232. package/docs/interfaces/AirPurifierCommands.html +5 -0
  233. package/docs/interfaces/AirPurifierServiceData.html +14 -0
  234. package/docs/interfaces/AirPurifierStatus.html +17 -0
  235. package/docs/interfaces/BLEAdvertisement.html +6 -0
  236. package/docs/interfaces/BLEScanOptions.html +10 -0
  237. package/docs/interfaces/BLEServiceData.html +10 -0
  238. package/docs/interfaces/BlindTiltCommands.html +7 -0
  239. package/docs/interfaces/BlindTiltServiceData.html +14 -0
  240. package/docs/interfaces/BlindTiltStatus.html +15 -0
  241. package/docs/interfaces/BotCommands.html +4 -0
  242. package/docs/interfaces/BotServiceData.html +12 -0
  243. package/docs/interfaces/BotStatus.html +14 -0
  244. package/docs/interfaces/BulbCommands.html +6 -0
  245. package/docs/interfaces/BulbServiceData.html +19 -0
  246. package/docs/interfaces/BulbStatus.html +16 -0
  247. package/docs/interfaces/CeilingLightCommands.html +6 -0
  248. package/docs/interfaces/CeilingLightServiceData.html +19 -0
  249. package/docs/interfaces/CeilingLightStatus.html +16 -0
  250. package/docs/interfaces/CommandResult.html +12 -0
  251. package/docs/interfaces/ContactServiceData.html +13 -0
  252. package/docs/interfaces/ContactStatus.html +15 -0
  253. package/docs/interfaces/CurtainCommands.html +5 -0
  254. package/docs/interfaces/CurtainServiceData.html +14 -0
  255. package/docs/interfaces/CurtainStatus.html +17 -0
  256. package/docs/interfaces/DeviceInfo.html +26 -0
  257. package/docs/interfaces/DeviceListResponse.html +4 -0
  258. package/docs/interfaces/DeviceStatus.html +12 -0
  259. package/docs/interfaces/DiscoveryOptions.html +14 -0
  260. package/docs/interfaces/HubServiceData.html +14 -0
  261. package/docs/interfaces/HubStatus.html +15 -0
  262. package/docs/interfaces/HumidifierCommands.html +5 -0
  263. package/docs/interfaces/HumidifierServiceData.html +14 -0
  264. package/docs/interfaces/HumidifierStatus.html +18 -0
  265. package/docs/interfaces/KeypadStatus.html +13 -0
  266. package/docs/interfaces/LeakServiceData.html +11 -0
  267. package/docs/interfaces/LeakStatus.html +13 -0
  268. package/docs/interfaces/LockCommands.html +3 -0
  269. package/docs/interfaces/LockServiceData.html +15 -0
  270. package/docs/interfaces/LockStatus.html +15 -0
  271. package/docs/interfaces/MeterServiceData.html +13 -0
  272. package/docs/interfaces/MeterStatus.html +15 -0
  273. package/docs/interfaces/MotionServiceData.html +13 -0
  274. package/docs/interfaces/MotionStatus.html +14 -0
  275. package/docs/interfaces/PlugCommands.html +4 -0
  276. package/docs/interfaces/PlugServiceData.html +17 -0
  277. package/docs/interfaces/PlugStatus.html +16 -0
  278. package/docs/interfaces/PresenceServiceData.html +12 -0
  279. package/docs/interfaces/PresenceStatus.html +14 -0
  280. package/docs/interfaces/RelaySwitchCommands.html +4 -0
  281. package/docs/interfaces/RelaySwitchServiceData.html +14 -0
  282. package/docs/interfaces/RelaySwitchStatus.html +16 -0
  283. package/docs/interfaces/RemoteStatus.html +12 -0
  284. package/docs/interfaces/SceneListResponse.html +3 -0
  285. package/docs/interfaces/StripCommands.html +6 -0
  286. package/docs/interfaces/StripServiceData.html +19 -0
  287. package/docs/interfaces/StripStatus.html +16 -0
  288. package/docs/interfaces/SwitchBotConfig.html +18 -0
  289. package/docs/interfaces/WebhookConfig.html +4 -0
  290. package/docs/interfaces/WebhookDetails.html +7 -0
  291. package/docs/interfaces/WebhookQueryResponse.html +5 -0
  292. package/docs/interfaces/WebhookSetupResponse.html +5 -0
  293. package/docs/modules.html +1 -1
  294. package/docs/types/ConnectionType.html +2 -0
  295. package/docs/types/PhysicalDeviceType.html +2 -0
  296. package/docs/types/VirtualDeviceType.html +2 -0
  297. package/docs/variables/urls.html +1 -1
  298. package/package.json +11 -20
  299. package/.github/scripts/npm-version-script-esm-auto.js +0 -207
  300. package/dist/device.d.ts +0 -1577
  301. package/dist/device.d.ts.map +0 -1
  302. package/dist/device.js +0 -3493
  303. package/dist/device.js.map +0 -1
  304. package/dist/device.test.d.ts +0 -2
  305. package/dist/device.test.d.ts.map +0 -1
  306. package/dist/device.test.js +0 -255
  307. package/dist/device.test.js.map +0 -1
  308. package/dist/index.test.d.ts +0 -2
  309. package/dist/index.test.d.ts.map +0 -1
  310. package/dist/index.test.js +0 -14
  311. package/dist/index.test.js.map +0 -1
  312. package/dist/parameter-checker.d.ts +0 -91
  313. package/dist/parameter-checker.d.ts.map +0 -1
  314. package/dist/parameter-checker.js +0 -262
  315. package/dist/parameter-checker.js.map +0 -1
  316. package/dist/parameter-checker.test.d.ts +0 -2
  317. package/dist/parameter-checker.test.d.ts.map +0 -1
  318. package/dist/parameter-checker.test.js +0 -56
  319. package/dist/parameter-checker.test.js.map +0 -1
  320. package/dist/settings.test.d.ts +0 -2
  321. package/dist/settings.test.d.ts.map +0 -1
  322. package/dist/settings.test.js +0 -61
  323. package/dist/settings.test.js.map +0 -1
  324. package/dist/switchbot-ble.d.ts +0 -89
  325. package/dist/switchbot-ble.d.ts.map +0 -1
  326. package/dist/switchbot-ble.js +0 -319
  327. package/dist/switchbot-ble.js.map +0 -1
  328. package/dist/switchbot-ble.test.d.ts +0 -2
  329. package/dist/switchbot-ble.test.d.ts.map +0 -1
  330. package/dist/switchbot-ble.test.js +0 -32
  331. package/dist/switchbot-ble.test.js.map +0 -1
  332. package/dist/switchbot-openapi.d.ts +0 -139
  333. package/dist/switchbot-openapi.d.ts.map +0 -1
  334. package/dist/switchbot-openapi.js +0 -375
  335. package/dist/switchbot-openapi.js.map +0 -1
  336. package/dist/switchbot-openapi.test.d.ts +0 -2
  337. package/dist/switchbot-openapi.test.d.ts.map +0 -1
  338. package/dist/switchbot-openapi.test.js +0 -36
  339. package/dist/switchbot-openapi.test.js.map +0 -1
  340. package/dist/types/ble-guards.d.ts +0 -12
  341. package/dist/types/ble-guards.d.ts.map +0 -1
  342. package/dist/types/ble-guards.js +0 -10
  343. package/dist/types/ble-guards.js.map +0 -1
  344. package/dist/types/ble-guards.test.d.ts +0 -2
  345. package/dist/types/ble-guards.test.d.ts.map +0 -1
  346. package/dist/types/ble-guards.test.js +0 -62
  347. package/dist/types/ble-guards.test.js.map +0 -1
  348. package/dist/types/openapi.d.ts +0 -635
  349. package/dist/types/openapi.d.ts.map +0 -1
  350. package/dist/types/openapi.js +0 -3
  351. package/dist/types/openapi.js.map +0 -1
  352. package/docs/classes/Advertising.html +0 -21
  353. package/docs/classes/ErrorUtils.html +0 -25
  354. package/docs/classes/ParameterChecker.html +0 -69
  355. package/docs/classes/SwitchBotBLE.html +0 -39
  356. package/docs/classes/SwitchBotOpenAPI.html +0 -57
  357. package/docs/classes/SwitchbotDevice.html +0 -47
  358. package/docs/classes/ValidationUtils.html +0 -51
  359. package/docs/classes/WoSmartLockUltra.html +0 -113
  360. package/docs/enums/SwitchBotBLEModelFriendlyName.html +0 -39
  361. package/docs/enums/SwitchBotModel.html +0 -52
  362. package/docs/interfaces/AdvertisementData.html +0 -3
  363. package/docs/interfaces/Chars.html +0 -4
  364. package/docs/interfaces/ColorLightServiceDataBase.html +0 -17
  365. package/docs/interfaces/ErrorObject.html +0 -3
  366. package/docs/interfaces/LockBaseServiceData.html +0 -15
  367. package/docs/interfaces/NobleTypes.html +0 -3
  368. package/docs/interfaces/Params.html +0 -6
  369. package/docs/interfaces/PlugMiniServiceDataBase.html +0 -12
  370. package/docs/interfaces/Rule.html +0 -9
  371. package/docs/interfaces/ServiceData.html +0 -2
  372. package/docs/interfaces/SwitchBotBLEDevice.html +0 -27
  373. package/docs/interfaces/SwitchBotScanner.html +0 -6
  374. package/docs/interfaces/TemperatureServiceDataBase.html +0 -10
  375. package/docs/interfaces/WebhookDetail.html +0 -6
  376. package/docs/interfaces/ad.html +0 -5
  377. package/docs/interfaces/body.html +0 -3
  378. package/docs/interfaces/bodyChange.html +0 -4
  379. package/docs/interfaces/deleteWebhookResponse.html +0 -4
  380. package/docs/interfaces/device.html +0 -7
  381. package/docs/interfaces/deviceList.html +0 -2
  382. package/docs/interfaces/deviceStatus.html +0 -7
  383. package/docs/interfaces/deviceStatusRequest.html +0 -4
  384. package/docs/interfaces/deviceWebhook.html +0 -4
  385. package/docs/interfaces/deviceWebhookContext.html +0 -4
  386. package/docs/interfaces/devices.html +0 -4
  387. package/docs/interfaces/infraredRemoteList.html +0 -2
  388. package/docs/interfaces/irdevice.html +0 -5
  389. package/docs/interfaces/pushRequest.html +0 -5
  390. package/docs/interfaces/pushResponse.html +0 -5
  391. package/docs/interfaces/pushResponseBody.html +0 -3
  392. package/docs/interfaces/queryWebhookResponse.html +0 -4
  393. package/docs/interfaces/setupWebhookResponse.html +0 -4
  394. package/docs/interfaces/updateWebhookResponse.html +0 -4
  395. package/docs/interfaces/webhookRequest.html +0 -4
  396. package/docs/types/BLEDeviceServiceData.html +0 -1
  397. package/docs/types/IndoorCam.html +0 -1
  398. package/docs/types/MacAddress.html +0 -1
  399. package/docs/types/airPurifier.html +0 -1
  400. package/docs/types/airPurifierPM25WebhookContext.html +0 -1
  401. package/docs/types/airPurifierServiceData.html +0 -1
  402. package/docs/types/airPurifierStatus.html +0 -1
  403. package/docs/types/airPurifierTable.html +0 -1
  404. package/docs/types/airPurifierTablePM25WebhookContext.html +0 -1
  405. package/docs/types/airPurifierTableServiceData.html +0 -1
  406. package/docs/types/airPurifierTableStatus.html +0 -1
  407. package/docs/types/airPurifierTableVOC.html +0 -1
  408. package/docs/types/airPurifierTableVOCStatus.html +0 -1
  409. package/docs/types/airPurifierTableVOCWebhookContext.html +0 -1
  410. package/docs/types/airPurifierTableWebhookContext.html +0 -1
  411. package/docs/types/airPurifierVOC.html +0 -1
  412. package/docs/types/airPurifierVOCStatus.html +0 -1
  413. package/docs/types/airPurifierVOCWebhookContext.html +0 -1
  414. package/docs/types/airPurifierWebhookContext.html +0 -1
  415. package/docs/types/batteryCirculatorFan.html +0 -1
  416. package/docs/types/batteryCirculatorFanServiceData.html +0 -1
  417. package/docs/types/batteryCirculatorFanStatus.html +0 -1
  418. package/docs/types/batteryCirculatorFanWebhookContext.html +0 -1
  419. package/docs/types/blindTilt.html +0 -1
  420. package/docs/types/blindTiltServiceData.html +0 -1
  421. package/docs/types/blindTiltStatus.html +0 -1
  422. package/docs/types/blindTiltWebhookContext.html +0 -1
  423. package/docs/types/bot.html +0 -1
  424. package/docs/types/botServiceData.html +0 -1
  425. package/docs/types/botStatus.html +0 -1
  426. package/docs/types/botWebhookContext.html +0 -1
  427. package/docs/types/ceilingLight.html +0 -1
  428. package/docs/types/ceilingLightPro.html +0 -1
  429. package/docs/types/ceilingLightProServiceData.html +0 -1
  430. package/docs/types/ceilingLightProStatus.html +0 -1
  431. package/docs/types/ceilingLightProWebhookContext.html +0 -1
  432. package/docs/types/ceilingLightServiceData.html +0 -1
  433. package/docs/types/ceilingLightStatus.html +0 -1
  434. package/docs/types/ceilingLightWebhookContext.html +0 -1
  435. package/docs/types/circulatorFanStatus.html +0 -1
  436. package/docs/types/circulatorFanWebhookContext.html +0 -1
  437. package/docs/types/colorBulb.html +0 -1
  438. package/docs/types/colorBulbServiceData.html +0 -1
  439. package/docs/types/colorBulbStatus.html +0 -1
  440. package/docs/types/colorBulbWebhookContext.html +0 -1
  441. package/docs/types/commandType.html +0 -2
  442. package/docs/types/contactSensor.html +0 -1
  443. package/docs/types/contactSensorServiceData.html +0 -1
  444. package/docs/types/contactSensorStatus.html +0 -1
  445. package/docs/types/contactSensorWebhookContext.html +0 -1
  446. package/docs/types/curtain.html +0 -1
  447. package/docs/types/curtain3.html +0 -1
  448. package/docs/types/curtain3ServiceData.html +0 -1
  449. package/docs/types/curtain3WebhookContext.html +0 -1
  450. package/docs/types/curtainServiceData.html +0 -1
  451. package/docs/types/curtainStatus.html +0 -1
  452. package/docs/types/curtainWebhookContext.html +0 -1
  453. package/docs/types/floorCleaningRobotS10.html +0 -1
  454. package/docs/types/floorCleaningRobotS10Status.html +0 -1
  455. package/docs/types/floorCleaningRobotS10WebhookContext.html +0 -1
  456. package/docs/types/hub2.html +0 -1
  457. package/docs/types/hub2ServiceData.html +0 -1
  458. package/docs/types/hub2Status.html +0 -1
  459. package/docs/types/hub2WebhookContext.html +0 -1
  460. package/docs/types/hub3ServiceData.html +0 -1
  461. package/docs/types/humidifier.html +0 -1
  462. package/docs/types/humidifier2ServiceData.html +0 -1
  463. package/docs/types/humidifier2Status.html +0 -1
  464. package/docs/types/humidifier2WebhookContext.html +0 -1
  465. package/docs/types/humidifierServiceData.html +0 -1
  466. package/docs/types/humidifierStatus.html +0 -1
  467. package/docs/types/humidifierWebhookContext.html +0 -1
  468. package/docs/types/indoorCameraWebhookContext.html +0 -1
  469. package/docs/types/keypad.html +0 -1
  470. package/docs/types/keypadDetectorServiceData.html +0 -1
  471. package/docs/types/keypadTouch.html +0 -1
  472. package/docs/types/keypadTouchWebhookContext.html +0 -1
  473. package/docs/types/keypadWebhookContext.html +0 -1
  474. package/docs/types/lock.html +0 -1
  475. package/docs/types/lockPro.html +0 -1
  476. package/docs/types/lockProServiceData.html +0 -1
  477. package/docs/types/lockProStatus.html +0 -1
  478. package/docs/types/lockProWebhookContext.html +0 -1
  479. package/docs/types/lockServiceData.html +0 -1
  480. package/docs/types/lockStatus.html +0 -1
  481. package/docs/types/lockWebhookContext.html +0 -1
  482. package/docs/types/meter.html +0 -1
  483. package/docs/types/meterPlus.html +0 -1
  484. package/docs/types/meterPlusServiceData.html +0 -1
  485. package/docs/types/meterPlusStatus.html +0 -1
  486. package/docs/types/meterPlusWebhookContext.html +0 -1
  487. package/docs/types/meterPro.html +0 -1
  488. package/docs/types/meterProCO2ServiceData.html +0 -1
  489. package/docs/types/meterProCO2Status.html +0 -1
  490. package/docs/types/meterProCO2WebhookContext.html +0 -1
  491. package/docs/types/meterProServiceData.html +0 -1
  492. package/docs/types/meterProStatus.html +0 -1
  493. package/docs/types/meterProWebhookContext.html +0 -1
  494. package/docs/types/meterServiceData.html +0 -1
  495. package/docs/types/meterStatus.html +0 -1
  496. package/docs/types/meterWebhookContext.html +0 -1
  497. package/docs/types/motionSensor.html +0 -1
  498. package/docs/types/motionSensorServiceData.html +0 -1
  499. package/docs/types/motionSensorStatus.html +0 -1
  500. package/docs/types/motionSensorWebhookContext.html +0 -1
  501. package/docs/types/onadvertisement.html +0 -1
  502. package/docs/types/ondiscover.html +0 -1
  503. package/docs/types/outdoorMeter.html +0 -1
  504. package/docs/types/outdoorMeterServiceData.html +0 -1
  505. package/docs/types/outdoorMeterStatus.html +0 -1
  506. package/docs/types/outdoorMeterWebhookContext.html +0 -1
  507. package/docs/types/panTiltCamWebhookContext.html +0 -1
  508. package/docs/types/pantiltCam.html +0 -1
  509. package/docs/types/pantiltCam2k.html +0 -1
  510. package/docs/types/plug.html +0 -1
  511. package/docs/types/plugMini.html +0 -1
  512. package/docs/types/plugMiniJPServiceData.html +0 -1
  513. package/docs/types/plugMiniJPWebhookContext.html +0 -1
  514. package/docs/types/plugMiniStatus.html +0 -1
  515. package/docs/types/plugMiniUSServiceData.html +0 -1
  516. package/docs/types/plugMiniUSWebhookContext.html +0 -1
  517. package/docs/types/plugStatus.html +0 -1
  518. package/docs/types/plugWebhookContext.html +0 -1
  519. package/docs/types/presenceSensor.html +0 -1
  520. package/docs/types/presenceSensorServiceData.html +0 -1
  521. package/docs/types/presenceSensorStatus.html +0 -1
  522. package/docs/types/presenceSensorWebhookContext.html +0 -1
  523. package/docs/types/relaySwitch1Context.html +0 -1
  524. package/docs/types/relaySwitch1PMContext.html +0 -1
  525. package/docs/types/relaySwitch1PMServiceData.html +0 -1
  526. package/docs/types/relaySwitch1PMStatus.html +0 -1
  527. package/docs/types/relaySwitch1ServiceData.html +0 -1
  528. package/docs/types/relaySwitch1Status.html +0 -1
  529. package/docs/types/remote.html +0 -1
  530. package/docs/types/remoteServiceData.html +0 -1
  531. package/docs/types/robotVacuumCleanerS1.html +0 -1
  532. package/docs/types/robotVacuumCleanerS1Plus.html +0 -1
  533. package/docs/types/robotVacuumCleanerS1PlusStatus.html +0 -1
  534. package/docs/types/robotVacuumCleanerS1PlusWebhookContext.html +0 -1
  535. package/docs/types/robotVacuumCleanerS1Status.html +0 -1
  536. package/docs/types/robotVacuumCleanerS1WebhookContext.html +0 -1
  537. package/docs/types/robotVacuumCleanerServiceData.html +0 -1
  538. package/docs/types/stripLight.html +0 -1
  539. package/docs/types/stripLightServiceData.html +0 -1
  540. package/docs/types/stripLightStatus.html +0 -1
  541. package/docs/types/stripLightWebhookContext.html +0 -1
  542. package/docs/types/waterLeakDetector.html +0 -1
  543. package/docs/types/waterLeakDetectorServiceData.html +0 -1
  544. package/docs/types/waterLeakDetectorStatus.html +0 -1
  545. package/docs/types/waterLeakDetectorWebhookContext.html +0 -1
  546. package/docs/variables/parameterChecker.html +0 -1
  547. package/eslint.config.js +0 -48
  548. package/typedoc.json +0 -17
package/dist/device.js DELETED
@@ -1,3493 +0,0 @@
1
- import { Buffer } from 'node:buffer';
2
- import * as Crypto from 'node:crypto';
3
- import { EventEmitter } from 'node:events';
4
- import { CHAR_UUID_DEVICE, CHAR_UUID_NOTIFY, CHAR_UUID_WRITE, READ_TIMEOUT_MSEC, SERV_UUID_PRIMARY, WoSmartLockCommands, WoSmartLockProCommands, WRITE_TIMEOUT_MSEC } from './settings.js';
5
- /**
6
- * Command constants for various SwitchBot devices.
7
- * Using readonly arrays to ensure immutability and better type safety.
8
- */
9
- const DEVICE_COMMANDS = {
10
- BLIND_TILT: {
11
- OPEN: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x32],
12
- CLOSE_UP: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x64],
13
- CLOSE_DOWN: [0x57, 0x0F, 0x45, 0x01, 0x05, 0xFF, 0x00],
14
- PAUSE: [0x57, 0x0F, 0x45, 0x01, 0x00, 0xFF],
15
- },
16
- BULB: {
17
- BASE: [0x57, 0x0F, 0x47, 0x01],
18
- READ_STATE: [0x57, 0x0F, 0x48, 0x01],
19
- TURN_ON: [0x01, 0x01],
20
- TURN_OFF: [0x01, 0x02],
21
- SET_BRIGHTNESS: [0x02, 0x14],
22
- SET_COLOR_TEMP: [0x02, 0x17],
23
- SET_RGB: [0x02, 0x12],
24
- },
25
- HUMIDIFIER: {
26
- HEADER: '5701',
27
- TURN_ON: '570101',
28
- TURN_OFF: '570102',
29
- INCREASE: '570103',
30
- DECREASE: '570104',
31
- SET_AUTO_MODE: '570105',
32
- SET_MANUAL_MODE: '570106',
33
- },
34
- AIR_PURIFIER: {
35
- TURN_ON: [0x57, 0x01, 0x01],
36
- TURN_OFF: [0x57, 0x01, 0x02],
37
- SET_MODE: [0x57, 0x02],
38
- SET_SPEED: [0x57, 0x03],
39
- },
40
- // Common commands used across multiple devices
41
- COMMON: {
42
- POWER_ON: [0x57, 0x01, 0x01],
43
- POWER_OFF: [0x57, 0x01, 0x02],
44
- },
45
- };
46
- /**
47
- * Air quality level constants for air purifier devices.
48
- */
49
- const AIR_QUALITY_LEVELS = {
50
- EXCELLENT: 'excellent',
51
- GOOD: 'good',
52
- FAIR: 'fair',
53
- POOR: 'poor',
54
- };
55
- /**
56
- * Air purifier mode constants.
57
- */
58
- const AIR_PURIFIER_MODES = {
59
- MANUAL: 'manual',
60
- AUTO: 'auto',
61
- SLEEP: 'sleep',
62
- LEVEL_1: 'level_1',
63
- LEVEL_2: 'level_2',
64
- LEVEL_3: 'level_3',
65
- };
66
- // Legacy constants for backward compatibility
67
- const BLIND_TILT_COMMANDS = DEVICE_COMMANDS.BLIND_TILT;
68
- const BULB_COMMANDS = DEVICE_COMMANDS.BULB;
69
- const HUMIDIFIER_COMMAND_HEADER = DEVICE_COMMANDS.HUMIDIFIER.HEADER;
70
- const TURN_ON_KEY = DEVICE_COMMANDS.HUMIDIFIER.TURN_ON;
71
- const TURN_OFF_KEY = DEVICE_COMMANDS.HUMIDIFIER.TURN_OFF;
72
- const INCREASE_KEY = DEVICE_COMMANDS.HUMIDIFIER.INCREASE;
73
- const DECREASE_KEY = DEVICE_COMMANDS.HUMIDIFIER.DECREASE;
74
- const SET_AUTO_MODE_KEY = DEVICE_COMMANDS.HUMIDIFIER.SET_AUTO_MODE;
75
- const SET_MANUAL_MODE_KEY = DEVICE_COMMANDS.HUMIDIFIER.SET_MANUAL_MODE;
76
- export var SwitchBotModel;
77
- (function (SwitchBotModel) {
78
- SwitchBotModel["HubMini"] = "W0202200";
79
- SwitchBotModel["HubPlus"] = "SwitchBot Hub S1";
80
- SwitchBotModel["Hub2"] = "W3202100";
81
- SwitchBotModel["Hub3"] = "W3302100";
82
- SwitchBotModel["Bot"] = "SwitchBot S1";
83
- SwitchBotModel["Curtain"] = "W0701600";
84
- SwitchBotModel["Curtain3"] = "W2400000";
85
- SwitchBotModel["Humidifier"] = "W0801800";
86
- SwitchBotModel["Humidifier2"] = "WXXXXXXX";
87
- SwitchBotModel["Plug"] = "SP11";
88
- SwitchBotModel["Meter"] = "SwitchBot MeterTH S1";
89
- SwitchBotModel["MeterPlusJP"] = "W2201500";
90
- SwitchBotModel["MeterPlusUS"] = "W2301500";
91
- SwitchBotModel["MeterPro"] = "W4900000";
92
- SwitchBotModel["MeterProCO2"] = "W4900010";
93
- SwitchBotModel["OutdoorMeter"] = "W3400010";
94
- SwitchBotModel["MotionSensor"] = "W1101500";
95
- SwitchBotModel["PresenceSensor"] = "W8200000";
96
- SwitchBotModel["ContactSensor"] = "W1201500";
97
- SwitchBotModel["ColorBulb"] = "W1401400";
98
- SwitchBotModel["StripLight"] = "W1701100";
99
- SwitchBotModel["PlugMiniUS"] = "W1901400/W1901401";
100
- SwitchBotModel["PlugMiniJP"] = "W2001400/W2001401";
101
- SwitchBotModel["Lock"] = "W1601700";
102
- SwitchBotModel["LockPro"] = "W3500000";
103
- SwitchBotModel["LockUltra"] = "W3600000";
104
- SwitchBotModel["Keypad"] = "W2500010";
105
- SwitchBotModel["KeypadTouch"] = "W2500020";
106
- SwitchBotModel["K10"] = "K10+";
107
- SwitchBotModel["K10Pro"] = "K10+ Pro";
108
- SwitchBotModel["WoSweeper"] = "WoSweeper";
109
- SwitchBotModel["WoSweeperMini"] = "WoSweeperMini";
110
- SwitchBotModel["RobotVacuumCleanerS1"] = "W3011000";
111
- SwitchBotModel["RobotVacuumCleanerS1Plus"] = "W3011010";
112
- SwitchBotModel["RobotVacuumCleanerS10"] = "W3211800";
113
- SwitchBotModel["Remote"] = "Remote";
114
- SwitchBotModel["UniversalRemote"] = "UniversalRemote";
115
- SwitchBotModel["CeilingLight"] = "W2612230/W2612240";
116
- SwitchBotModel["CeilingLightPro"] = "W2612210/W2612220";
117
- SwitchBotModel["IndoorCam"] = "W1301200";
118
- SwitchBotModel["PanTiltCam"] = "W1801200";
119
- SwitchBotModel["PanTiltCam2K"] = "W3101100";
120
- SwitchBotModel["BlindTilt"] = "W2701600";
121
- SwitchBotModel["BatteryCirculatorFan"] = "W3800510";
122
- SwitchBotModel["CirculatorFan"] = "W3800511";
123
- SwitchBotModel["WaterDetector"] = "W4402000";
124
- SwitchBotModel["RelaySwitch1"] = "W5502300";
125
- SwitchBotModel["RelaySwitch1PM"] = "W5502310";
126
- SwitchBotModel["Unknown"] = "Unknown";
127
- SwitchBotModel["AirPurifier"] = "W5302300";
128
- SwitchBotModel["AirPurifierTable"] = "W5302310";
129
- })(SwitchBotModel || (SwitchBotModel = {}));
130
- export var SwitchBotBLEModel;
131
- (function (SwitchBotBLEModel) {
132
- SwitchBotBLEModel["Bot"] = "H";
133
- SwitchBotBLEModel["Curtain"] = "c";
134
- SwitchBotBLEModel["Curtain3"] = "{";
135
- SwitchBotBLEModel["Humidifier"] = "e";
136
- SwitchBotBLEModel["Humidifier2"] = "#";
137
- SwitchBotBLEModel["Meter"] = "T";
138
- SwitchBotBLEModel["MeterPlus"] = "i";
139
- SwitchBotBLEModel["MeterPro"] = "4";
140
- SwitchBotBLEModel["MeterProCO2"] = "5";
141
- SwitchBotBLEModel["Hub2"] = "v";
142
- SwitchBotBLEModel["Hub3"] = "V";
143
- SwitchBotBLEModel["OutdoorMeter"] = "w";
144
- SwitchBotBLEModel["MotionSensor"] = "s";
145
- SwitchBotBLEModel["PresenceSensor"] = "p";
146
- SwitchBotBLEModel["ContactSensor"] = "d";
147
- SwitchBotBLEModel["ColorBulb"] = "u";
148
- SwitchBotBLEModel["StripLight"] = "r";
149
- SwitchBotBLEModel["PlugMiniUS"] = "g";
150
- SwitchBotBLEModel["PlugMiniJP"] = "j";
151
- SwitchBotBLEModel["Lock"] = "o";
152
- SwitchBotBLEModel["LockPro"] = "$";
153
- SwitchBotBLEModel["LockUltra"] = "U";
154
- SwitchBotBLEModel["CeilingLight"] = "q";
155
- SwitchBotBLEModel["CeilingLightPro"] = "n";
156
- SwitchBotBLEModel["BlindTilt"] = "x";
157
- SwitchBotBLEModel["Leak"] = "&";
158
- SwitchBotBLEModel["Keypad"] = "y";
159
- SwitchBotBLEModel["RelaySwitch1"] = ";";
160
- SwitchBotBLEModel["RelaySwitch1PM"] = "<";
161
- SwitchBotBLEModel["Remote"] = "b";
162
- SwitchBotBLEModel["Unknown"] = "Unknown";
163
- SwitchBotBLEModel["AirPurifier"] = "+";
164
- SwitchBotBLEModel["AirPurifierTable"] = "7";
165
- })(SwitchBotBLEModel || (SwitchBotBLEModel = {}));
166
- export var SwitchBotBLEModelName;
167
- (function (SwitchBotBLEModelName) {
168
- SwitchBotBLEModelName["Bot"] = "WoHand";
169
- SwitchBotBLEModelName["Hub2"] = "WoHub2";
170
- SwitchBotBLEModelName["Hub3"] = "WoHub3";
171
- SwitchBotBLEModelName["ColorBulb"] = "WoBulb";
172
- SwitchBotBLEModelName["Curtain"] = "WoCurtain";
173
- SwitchBotBLEModelName["Curtain3"] = "WoCurtain3";
174
- SwitchBotBLEModelName["Humidifier"] = "WoHumi";
175
- SwitchBotBLEModelName["Humidifier2"] = "WoHumi2";
176
- SwitchBotBLEModelName["Meter"] = "WoSensorTH";
177
- SwitchBotBLEModelName["MeterPlus"] = "WoSensorTHPlus";
178
- SwitchBotBLEModelName["MeterPro"] = "WoSensorTHP";
179
- SwitchBotBLEModelName["MeterProCO2"] = "WoSensorTHPc";
180
- SwitchBotBLEModelName["Lock"] = "WoSmartLock";
181
- SwitchBotBLEModelName["LockPro"] = "WoSmartLockPro";
182
- SwitchBotBLEModelName["LockUltra"] = "WoSmartLockUltra";
183
- SwitchBotBLEModelName["PresenceSensor"] = "WoPresence";
184
- SwitchBotBLEModelName["PlugMini"] = "WoPlugMini";
185
- SwitchBotBLEModelName["StripLight"] = "WoStrip";
186
- SwitchBotBLEModelName["OutdoorMeter"] = "WoIOSensorTH";
187
- SwitchBotBLEModelName["ContactSensor"] = "WoContact";
188
- SwitchBotBLEModelName["MotionSensor"] = "WoMotion";
189
- SwitchBotBLEModelName["BlindTilt"] = "WoBlindTilt";
190
- SwitchBotBLEModelName["CeilingLight"] = "WoCeilingLight";
191
- SwitchBotBLEModelName["CeilingLightPro"] = "WoCeilingLightPro";
192
- SwitchBotBLEModelName["Leak"] = "WoLeakDetector";
193
- SwitchBotBLEModelName["Keypad"] = "WoKeypad";
194
- SwitchBotBLEModelName["RelaySwitch1"] = "WoRelaySwitch1Plus";
195
- SwitchBotBLEModelName["RelaySwitch1PM"] = "WoRelaySwitch1PM";
196
- SwitchBotBLEModelName["Remote"] = "WoRemote";
197
- SwitchBotBLEModelName["AirPurifier"] = "WoAirPurifier";
198
- SwitchBotBLEModelName["AirPurifierTable"] = "WoAirPurifierTable";
199
- SwitchBotBLEModelName["Unknown"] = "Unknown";
200
- })(SwitchBotBLEModelName || (SwitchBotBLEModelName = {}));
201
- export var SwitchBotBLEModelFriendlyName;
202
- (function (SwitchBotBLEModelFriendlyName) {
203
- SwitchBotBLEModelFriendlyName["Bot"] = "Bot";
204
- SwitchBotBLEModelFriendlyName["Hub2"] = "Hub 2";
205
- SwitchBotBLEModelFriendlyName["Hub3"] = "Hub 3";
206
- SwitchBotBLEModelFriendlyName["ColorBulb"] = "Color Bulb";
207
- SwitchBotBLEModelFriendlyName["Curtain"] = "Curtain";
208
- SwitchBotBLEModelFriendlyName["Curtain3"] = "Curtain 3";
209
- SwitchBotBLEModelFriendlyName["Humidifier"] = "Humidifier";
210
- SwitchBotBLEModelFriendlyName["Humidifier2"] = "Humidifier2";
211
- SwitchBotBLEModelFriendlyName["Meter"] = "Meter";
212
- SwitchBotBLEModelFriendlyName["Lock"] = "Lock";
213
- SwitchBotBLEModelFriendlyName["LockPro"] = "Lock Pro";
214
- SwitchBotBLEModelFriendlyName["LockUltra"] = "Lock Ultra";
215
- SwitchBotBLEModelFriendlyName["PlugMini"] = "Plug Mini";
216
- SwitchBotBLEModelFriendlyName["StripLight"] = "Strip Light";
217
- SwitchBotBLEModelFriendlyName["MeterPlus"] = "Meter Plus";
218
- SwitchBotBLEModelFriendlyName["MeterPro"] = "Meter Pro";
219
- SwitchBotBLEModelFriendlyName["MeterProCO2"] = "Meter Pro CO2";
220
- SwitchBotBLEModelFriendlyName["BatteryCirculatorFan"] = "Battery Circulator Fan";
221
- SwitchBotBLEModelFriendlyName["CirculatorFan"] = "Circulator Fan";
222
- SwitchBotBLEModelFriendlyName["OutdoorMeter"] = "Outdoor Meter";
223
- SwitchBotBLEModelFriendlyName["ContactSensor"] = "Contact Sensor";
224
- SwitchBotBLEModelFriendlyName["MotionSensor"] = "Motion Sensor";
225
- SwitchBotBLEModelFriendlyName["PresenceSensor"] = "Presence Sensor";
226
- SwitchBotBLEModelFriendlyName["BlindTilt"] = "Blind Tilt";
227
- SwitchBotBLEModelFriendlyName["CeilingLight"] = "Ceiling Light";
228
- SwitchBotBLEModelFriendlyName["CeilingLightPro"] = "Ceiling Light Pro";
229
- SwitchBotBLEModelFriendlyName["Leak"] = "Water Detector";
230
- SwitchBotBLEModelFriendlyName["Keypad"] = "Keypad";
231
- SwitchBotBLEModelFriendlyName["RelaySwitch1"] = "Relay Switch 1";
232
- SwitchBotBLEModelFriendlyName["RelaySwitch1PM"] = "Relay Switch 1PM";
233
- SwitchBotBLEModelFriendlyName["Remote"] = "Remote";
234
- SwitchBotBLEModelFriendlyName["AirPurifier"] = "Air Purifier";
235
- SwitchBotBLEModelFriendlyName["AirPurifierTable"] = "Air Purifier Table";
236
- SwitchBotBLEModelFriendlyName["Unknown"] = "Unknown";
237
- SwitchBotBLEModelFriendlyName["AirPurifierVOC"] = "Air Purifier VOC";
238
- SwitchBotBLEModelFriendlyName["AirPurifierTableVOC"] = "Air Purifier Table VOC";
239
- SwitchBotBLEModelFriendlyName["AirPurifierPM2_5"] = "Air Purifier PM2.5";
240
- SwitchBotBLEModelFriendlyName["AirPurifierTablePM2_5"] = "Air Purifier Table PM2.5";
241
- })(SwitchBotBLEModelFriendlyName || (SwitchBotBLEModelFriendlyName = {}));
242
- /**
243
- * Enum for log levels.
244
- */
245
- export var LogLevel;
246
- (function (LogLevel) {
247
- LogLevel["SUCCESS"] = "success";
248
- LogLevel["DEBUGSUCCESS"] = "debugsuccess";
249
- LogLevel["WARN"] = "warn";
250
- LogLevel["DEBUGWARN"] = "debugwarn";
251
- LogLevel["ERROR"] = "error";
252
- LogLevel["DEBUGERROR"] = "debugerror";
253
- LogLevel["DEBUG"] = "debug";
254
- LogLevel["INFO"] = "info";
255
- })(LogLevel || (LogLevel = {}));
256
- /**
257
- * Utility class for comprehensive input validation with improved error messages.
258
- */
259
- export class ValidationUtils {
260
- /**
261
- * Validates percentage value (0-100).
262
- * @param value - The value to validate
263
- * @param paramName - The parameter name for error reporting
264
- * @throws {RangeError} When value is not within valid range
265
- * @throws {TypeError} When value is not a number
266
- */
267
- static validatePercentage(value, paramName = 'value') {
268
- if (typeof value !== 'number' || Number.isNaN(value)) {
269
- throw new TypeError(`${paramName} must be a valid number, got: ${value}`);
270
- }
271
- if (value < 0 || value > 100) {
272
- throw new RangeError(`${paramName} must be between 0 and 100 inclusive, got: ${value}`);
273
- }
274
- }
275
- /**
276
- * Validates RGB color value (0-255).
277
- * @param value - The color value to validate
278
- * @param colorName - The color name for error reporting
279
- * @throws {RangeError} When value is not within valid range
280
- * @throws {TypeError} When value is not a number
281
- */
282
- static validateRGB(value, colorName = 'color') {
283
- if (typeof value !== 'number' || Number.isNaN(value)) {
284
- throw new TypeError(`${colorName} must be a valid number, got: ${value}`);
285
- }
286
- if (!Number.isInteger(value) || value < 0 || value > 255) {
287
- throw new RangeError(`${colorName} must be an integer between 0 and 255 inclusive, got: ${value}`);
288
- }
289
- }
290
- /**
291
- * Validates buffer and throws descriptive error.
292
- * @param buffer - The buffer to validate
293
- * @param expectedLength - Optional expected length
294
- * @param paramName - The parameter name for error reporting
295
- * @throws {TypeError} When buffer is not a Buffer
296
- * @throws {RangeError} When buffer length doesn't match expected
297
- */
298
- static validateBuffer(buffer, expectedLength, paramName = 'buffer') {
299
- if (!Buffer.isBuffer(buffer)) {
300
- throw new TypeError(`${paramName} must be a Buffer instance, got: ${typeof buffer}`);
301
- }
302
- if (expectedLength !== undefined && buffer.length !== expectedLength) {
303
- throw new RangeError(`${paramName} must have exactly ${expectedLength} bytes, got: ${buffer.length} bytes`);
304
- }
305
- }
306
- /**
307
- * Validates string input with comprehensive checks.
308
- * @param value - The value to validate
309
- * @param paramName - The parameter name for error reporting
310
- * @param minLength - Minimum required length
311
- * @param maxLength - Optional maximum length
312
- * @throws {TypeError} When value is not a string
313
- * @throws {RangeError} When string length is invalid
314
- */
315
- static validateString(value, paramName = 'value', minLength = 1, maxLength) {
316
- if (typeof value !== 'string') {
317
- throw new TypeError(`${paramName} must be a string, got: ${typeof value}`);
318
- }
319
- if (value.length < minLength) {
320
- throw new RangeError(`${paramName} must have at least ${minLength} character(s), got: ${value.length}`);
321
- }
322
- if (maxLength !== undefined && value.length > maxLength) {
323
- throw new RangeError(`${paramName} must have at most ${maxLength} character(s), got: ${value.length}`);
324
- }
325
- }
326
- /**
327
- * Validates numeric range with enhanced checks.
328
- * @param value - The value to validate
329
- * @param min - Minimum allowed value
330
- * @param max - Maximum allowed value
331
- * @param paramName - The parameter name for error reporting
332
- * @param mustBeInteger - Whether the value must be an integer
333
- * @throws {TypeError} When value is not a number
334
- * @throws {RangeError} When value is outside valid range
335
- */
336
- static validateRange(value, min, max, paramName = 'value', mustBeInteger = false) {
337
- if (typeof value !== 'number' || Number.isNaN(value)) {
338
- throw new TypeError(`${paramName} must be a valid number, got: ${value}`);
339
- }
340
- if (mustBeInteger && !Number.isInteger(value)) {
341
- throw new TypeError(`${paramName} must be an integer, got: ${value}`);
342
- }
343
- if (value < min || value > max) {
344
- throw new RangeError(`${paramName} must be between ${min} and ${max} inclusive, got: ${value}`);
345
- }
346
- }
347
- /**
348
- * Validates MAC address format.
349
- * @param address - The MAC address to validate
350
- * @param paramName - The parameter name for error reporting
351
- * @throws {TypeError} When address is not a string
352
- * @throws {Error} When address format is invalid
353
- */
354
- static validateMacAddress(address, paramName = 'address') {
355
- if (typeof address !== 'string') {
356
- throw new TypeError(`${paramName} must be a string`);
357
- }
358
- const macRegex = /^(?:[0-9A-F]{2}[:-]){5}[0-9A-F]{2}$|^[0-9A-F]{12}$/i;
359
- if (!macRegex.test(address)) {
360
- throw new Error(`${paramName} must be a valid MAC address format, got: ${address}`);
361
- }
362
- }
363
- /**
364
- * Validates that a value is one of the allowed enum values.
365
- * @param value - The value to validate
366
- * @param allowedValues - Array of allowed values
367
- * @param paramName - The parameter name for error reporting
368
- * @throws {Error} When value is not in allowed values
369
- */
370
- static validateEnum(value, allowedValues, paramName = 'value') {
371
- if (!allowedValues.includes(value)) {
372
- throw new Error(`${paramName} must be one of: ${allowedValues.join(', ')}, got: ${value}`);
373
- }
374
- }
375
- }
376
- /**
377
- * Enhanced error handling utilities.
378
- */
379
- export class ErrorUtils {
380
- /**
381
- * Creates a timeout error with context.
382
- * @param operation - The operation that timed out
383
- * @param timeoutMs - The timeout duration in milliseconds
384
- * @returns A descriptive timeout error
385
- */
386
- static createTimeoutError(operation, timeoutMs) {
387
- return new Error(`Operation '${operation}' timed out after ${timeoutMs}ms`);
388
- }
389
- /**
390
- * Creates a connection error with context.
391
- * @param deviceId - The device ID that failed to connect
392
- * @param cause - The underlying cause of the connection failure
393
- * @returns A descriptive connection error
394
- */
395
- static createConnectionError(deviceId, cause) {
396
- const message = `Failed to connect to device ${deviceId}`;
397
- return cause ? new Error(`${message}: ${cause.message}`) : new Error(message);
398
- }
399
- /**
400
- * Creates a command error with context.
401
- * @param command - The command that failed
402
- * @param deviceId - The device ID
403
- * @param cause - The underlying cause
404
- * @returns A descriptive command error
405
- */
406
- static createCommandError(command, deviceId, cause) {
407
- const message = `Command '${command}' failed for device ${deviceId}`;
408
- return cause ? new Error(`${message}: ${cause.message}`) : new Error(message);
409
- }
410
- /**
411
- * Wraps an async operation with timeout and enhanced error handling.
412
- * @param operation - The async operation to wrap
413
- * @param timeoutMs - Timeout in milliseconds
414
- * @param operationName - Name of the operation for error messages
415
- * @returns Promise that resolves with the operation result or rejects with timeout
416
- */
417
- static async withTimeout(operation, timeoutMs, operationName) {
418
- const timeoutPromise = new Promise((_, reject) => {
419
- setTimeout(() => {
420
- reject(this.createTimeoutError(operationName, timeoutMs));
421
- }, timeoutMs);
422
- });
423
- return Promise.race([operation, timeoutPromise]);
424
- }
425
- }
426
- /**
427
- * Represents a Switchbot Device.
428
- */
429
- export class SwitchbotDevice extends EventEmitter {
430
- noble;
431
- peripheral;
432
- characteristics = null;
433
- deviceId;
434
- deviceAddress;
435
- deviceModel;
436
- deviceModelName;
437
- deviceFriendlyName;
438
- explicitlyConnected = false;
439
- isConnected = false;
440
- onNotify = () => { };
441
- onDisconnect = async () => { };
442
- onConnect = async () => { };
443
- /**
444
- * Initializes a new instance of the SwitchbotDevice class.
445
- * @param peripheral The peripheral object from noble.
446
- * @param noble The Noble object.
447
- */
448
- constructor(peripheral, noble) {
449
- super();
450
- this.peripheral = peripheral;
451
- this.noble = noble;
452
- Advertising.parse(peripheral, this.log.bind(this)).then((ad) => {
453
- this.deviceId = ad?.id ?? '';
454
- this.deviceAddress = ad?.address ?? '';
455
- this.deviceModel = ad?.serviceData.model ?? '';
456
- this.deviceModelName = ad?.serviceData.modelName ?? '';
457
- this.deviceFriendlyName = ad?.serviceData.modelFriendlyName ?? '';
458
- });
459
- }
460
- /**
461
- * Logs a message with the specified log level.
462
- * @param level The severity level of the log (e.g., 'info', 'warn', 'error').
463
- * @param message The log message to be emitted.
464
- */
465
- async log(level, message) {
466
- this.emit('log', { level, message });
467
- }
468
- // Getters
469
- get id() {
470
- return this.deviceId;
471
- }
472
- get address() {
473
- return this.deviceAddress;
474
- }
475
- get model() {
476
- return this.deviceModel;
477
- }
478
- get modelName() {
479
- return this.deviceModelName;
480
- }
481
- get friendlyName() {
482
- return this.deviceFriendlyName;
483
- }
484
- get connectionState() {
485
- return this.isConnected ? 'connected' : this.peripheral.state;
486
- }
487
- get onConnectHandler() {
488
- return this.onConnect;
489
- }
490
- set onConnectHandler(func) {
491
- if (typeof func !== 'function') {
492
- throw new TypeError('The `onConnectHandler` must be a function that returns a Promise<void>.');
493
- }
494
- this.onConnect = async () => {
495
- await func();
496
- };
497
- }
498
- get onDisconnectHandler() {
499
- return this.onDisconnect;
500
- }
501
- set onDisconnectHandler(func) {
502
- if (typeof func !== 'function') {
503
- throw new TypeError('The `onDisconnectHandler` must be a function that returns a Promise<void>.');
504
- }
505
- this.onDisconnect = async () => {
506
- await func();
507
- };
508
- }
509
- /**
510
- * Connects to the device.
511
- * @returns A Promise that resolves when the connection is complete.
512
- */
513
- async connect() {
514
- this.explicitlyConnected = true;
515
- await this.internalConnect();
516
- }
517
- /**
518
- * Internal method to handle the connection process.
519
- * @returns A Promise that resolves when the connection is complete.
520
- */
521
- async internalConnect() {
522
- if (this.noble.state !== 'poweredOn') {
523
- throw new Error(`The Bluetooth status is ${this.noble.state}, not poweredOn.`);
524
- }
525
- const state = this.connectionState;
526
- if (state === 'connected') {
527
- return;
528
- }
529
- if (state === 'connecting' || state === 'disconnecting') {
530
- throw new Error(`Now ${state}. Wait for a few seconds then try again.`);
531
- }
532
- this.peripheral.once('connect', async () => {
533
- this.isConnected = true;
534
- await this.onConnect();
535
- });
536
- this.peripheral.once('disconnect', async () => {
537
- this.isConnected = false;
538
- this.characteristics = null;
539
- this.peripheral.removeAllListeners();
540
- await this.onDisconnect();
541
- });
542
- await this.peripheral.connectAsync();
543
- this.characteristics = await this.getCharacteristics();
544
- await this.subscribeToNotify();
545
- }
546
- /**
547
- * Retrieves the device characteristics.
548
- * @returns A Promise that resolves with the device characteristics.
549
- */
550
- async getCharacteristics() {
551
- const TIMEOUT_DURATION = 5000;
552
- const timeoutPromise = new Promise((_, reject) => {
553
- setTimeout(() => {
554
- reject(new Error('Failed to discover services and characteristics: TIMEOUT'));
555
- }, TIMEOUT_DURATION);
556
- });
557
- try {
558
- const services = await Promise.race([this.discoverServices(), timeoutPromise]);
559
- const chars = { write: null, notify: null, device: null };
560
- for (const service of services) {
561
- const characteristics = await this.discoverCharacteristics(service);
562
- for (const char of characteristics) {
563
- if (char.uuid === CHAR_UUID_WRITE) {
564
- chars.write = char;
565
- }
566
- if (char.uuid === CHAR_UUID_NOTIFY) {
567
- chars.notify = char;
568
- }
569
- if (char.uuid === CHAR_UUID_DEVICE) {
570
- chars.device = char;
571
- }
572
- }
573
- }
574
- if (!chars.write || !chars.notify) {
575
- throw new Error('No characteristic was found.');
576
- }
577
- return chars;
578
- }
579
- catch (error) {
580
- throw new Error(error.message || 'An error occurred while discovering characteristics.');
581
- }
582
- }
583
- /**
584
- * Discovers the device services.
585
- * @returns A Promise that resolves with the list of services.
586
- */
587
- async discoverServices() {
588
- try {
589
- const services = await this.peripheral.discoverServicesAsync([]);
590
- const primaryServices = services.filter(s => s.uuid === SERV_UUID_PRIMARY);
591
- if (primaryServices.length === 0) {
592
- throw new Error('No service was found.');
593
- }
594
- return primaryServices;
595
- }
596
- catch (e) {
597
- throw new Error(`Failed to discover services, Error: ${e.message ?? e}`);
598
- }
599
- }
600
- /**
601
- * Discovers the characteristics of a service.
602
- * @param service The service to discover characteristics for.
603
- * @returns A Promise that resolves with the list of characteristics.
604
- */
605
- // Discover characteristics without extra async/await
606
- discoverCharacteristics(service) {
607
- return service.discoverCharacteristicsAsync([]);
608
- }
609
- /**
610
- * Subscribes to the notify characteristic.
611
- * @returns A Promise that resolves when the subscription is complete.
612
- */
613
- async subscribeToNotify() {
614
- const char = this.characteristics?.notify;
615
- if (!char) {
616
- throw new Error('No notify characteristic was found.');
617
- }
618
- await char.subscribeAsync();
619
- char.on('data', (buf) => this.onNotify(buf));
620
- }
621
- /**
622
- * Unsubscribes from the notify characteristic.
623
- * @returns A Promise that resolves when the unsubscription is complete.
624
- */
625
- async unsubscribeFromNotify() {
626
- const char = this.characteristics?.notify;
627
- if (!char) {
628
- return;
629
- }
630
- char.removeAllListeners();
631
- await char.unsubscribeAsync();
632
- }
633
- /**
634
- * Disconnects from the device.
635
- * @returns A Promise that resolves when the disconnection is complete.
636
- */
637
- async disconnect() {
638
- this.explicitlyConnected = false;
639
- const state = this.peripheral.state;
640
- if (state === 'disconnected') {
641
- return;
642
- }
643
- if (state === 'connecting' || state === 'disconnecting') {
644
- throw new Error(`Now ${state}. Wait for a few seconds then try again.`);
645
- }
646
- await this.unsubscribeFromNotify();
647
- await this.peripheral.disconnectAsync();
648
- }
649
- /**
650
- * Internal method to handle disconnection if not explicitly initiated.
651
- * @returns A Promise that resolves when the disconnection is complete.
652
- */
653
- async internalDisconnect() {
654
- if (!this.explicitlyConnected) {
655
- await this.disconnect();
656
- this.explicitlyConnected = true;
657
- }
658
- }
659
- /**
660
- * Retrieves the device name.
661
- * @returns A Promise that resolves with the device name.
662
- */
663
- async getDeviceName() {
664
- await this.internalConnect();
665
- try {
666
- if (!this.characteristics?.device) {
667
- throw new Error(`Characteristic ${CHAR_UUID_DEVICE} not supported`);
668
- }
669
- const buf = await this.readCharacteristic(this.characteristics.device);
670
- return buf.toString('utf8');
671
- }
672
- catch (error) {
673
- const deviceContext = `device ${this.deviceId || 'unknown'}`;
674
- throw ErrorUtils.createCommandError('getDeviceName', deviceContext, error);
675
- }
676
- finally {
677
- await this.internalDisconnect();
678
- }
679
- }
680
- /**
681
- * Sets the device name.
682
- * @param name The new device name.
683
- * @returns A Promise that resolves when the name is set.
684
- */
685
- async setDeviceName(name) {
686
- ValidationUtils.validateString(name, 'name', 1);
687
- // Additional validation for device name length
688
- const nameBuffer = Buffer.from(name, 'utf8');
689
- if (nameBuffer.length > 100) {
690
- throw new RangeError('Device name cannot exceed 100 bytes when encoded as UTF-8');
691
- }
692
- await this.internalConnect();
693
- try {
694
- if (!this.characteristics?.device) {
695
- throw new Error(`Characteristic ${CHAR_UUID_DEVICE} not supported`);
696
- }
697
- await this.writeCharacteristic(this.characteristics.device, nameBuffer);
698
- }
699
- catch (error) {
700
- const deviceContext = `device ${this.deviceId || 'unknown'}`;
701
- throw ErrorUtils.createCommandError('setDeviceName', deviceContext, error);
702
- }
703
- finally {
704
- await this.internalDisconnect();
705
- }
706
- }
707
- /**
708
- * Sends a command to the device and awaits a response.
709
- * @param reqBuf The command buffer.
710
- * @returns A Promise that resolves with the response buffer.
711
- */
712
- async command(reqBuf) {
713
- ValidationUtils.validateBuffer(reqBuf, undefined, 'reqBuf');
714
- await this.internalConnect();
715
- if (!this.characteristics?.write) {
716
- throw new Error('No write characteristic available for command execution');
717
- }
718
- try {
719
- await this.writeCharacteristic(this.characteristics.write, reqBuf);
720
- const resBuf = await this.waitForCommandResponse();
721
- return resBuf;
722
- }
723
- catch (error) {
724
- const deviceContext = `device ${this.deviceId || 'unknown'}`;
725
- // Use ErrorUtils for enriched error context
726
- throw ErrorUtils.createCommandError('execute command', deviceContext, error);
727
- }
728
- finally {
729
- await this.internalDisconnect();
730
- }
731
- }
732
- /**
733
- * Waits for a response from the device after sending a command.
734
- * @returns A Promise that resolves with the response buffer.
735
- */
736
- async waitForCommandResponse() {
737
- const timeout = READ_TIMEOUT_MSEC;
738
- let timer = null;
739
- const timeoutPromise = new Promise((_, reject) => {
740
- timer = setTimeout(() => reject(new Error('READ_TIMEOUT')), timeout);
741
- });
742
- const readPromise = new Promise((resolve) => {
743
- this.onNotify = (buf) => {
744
- if (timer) {
745
- clearTimeout(timer);
746
- }
747
- resolve(buf);
748
- };
749
- });
750
- return await Promise.race([readPromise, timeoutPromise]);
751
- }
752
- /**
753
- * Reads data from a characteristic with enhanced timeout and error handling.
754
- * @param char The characteristic to read from.
755
- * @returns A Promise that resolves with the data buffer.
756
- */
757
- async readCharacteristic(char) {
758
- try {
759
- return await ErrorUtils.withTimeout(char.readAsync(), READ_TIMEOUT_MSEC, `read characteristic ${char.uuid}`);
760
- }
761
- catch (error) {
762
- const deviceContext = `device ${this.deviceId || 'unknown'}`;
763
- throw ErrorUtils.createCommandError(`read characteristic ${char.uuid}`, deviceContext, error);
764
- }
765
- }
766
- /**
767
- * Writes data to a characteristic with enhanced timeout and error handling.
768
- * @param char The characteristic to write to.
769
- * @param buf The data buffer.
770
- * @returns A Promise that resolves when the write is complete.
771
- */
772
- async writeCharacteristic(char, buf) {
773
- ValidationUtils.validateBuffer(buf, undefined, 'write buffer');
774
- try {
775
- return await ErrorUtils.withTimeout(char.writeAsync(buf, false), WRITE_TIMEOUT_MSEC, `write to characteristic ${char.uuid}`);
776
- }
777
- catch (error) {
778
- const deviceContext = `device ${this.deviceId || 'unknown'}`;
779
- throw ErrorUtils.createCommandError(`write to characteristic ${char.uuid}`, deviceContext, error);
780
- }
781
- }
782
- }
783
- /**
784
- * Represents the advertising data parser for SwitchBot devices.
785
- */
786
- export class Advertising {
787
- constructor() { }
788
- /**
789
- * Parses the advertisement data coming from SwitchBot device.
790
- *
791
- * This function processes advertising packets received from SwitchBot devices
792
- * and extracts relevant information based on the device type.
793
- *
794
- * @param {NobleTypes['peripheral']} peripheral - The peripheral device object from noble.
795
- * @param {Function} emitLog - The function to emit log messages.
796
- * @returns {Promise<Ad | null>} - An object containing parsed data specific to the SwitchBot device type, or `null` if the device is not recognized.
797
- */
798
- static async parse(peripheral, emitLog) {
799
- const ad = peripheral.advertisement;
800
- if (!ad || !ad.serviceData) {
801
- return null;
802
- }
803
- const serviceData = ad.serviceData[0]?.data;
804
- const manufacturerData = ad.manufacturerData;
805
- // Service data must exist and contain at least the model byte (1 byte minimum)
806
- // At least one of serviceData or manufacturerData should have sufficient data (3+ bytes)
807
- if (!Advertising.validateBuffer(serviceData, 1)) {
808
- return null;
809
- }
810
- if (!Advertising.validateBuffer(serviceData, 3) && !Advertising.validateBuffer(manufacturerData, 3)) {
811
- return null;
812
- }
813
- const model = serviceData.subarray(0, 1).toString('utf8');
814
- const sd = await Advertising.parseServiceData(model, serviceData, manufacturerData, emitLog);
815
- if (!sd) {
816
- // emitLog('debugerror', `[parseAdvertising.${peripheral.id}.${model}] return null, parsed serviceData empty!`)
817
- return null;
818
- }
819
- const address = Advertising.formatAddress(peripheral);
820
- const data = {
821
- id: peripheral.id,
822
- address,
823
- rssi: peripheral.rssi,
824
- serviceData: {
825
- model,
826
- modelName: sd.modelName || '',
827
- modelFriendlyName: sd.modelFriendlyName || '',
828
- ...sd,
829
- },
830
- };
831
- emitLog('debug', `[parseAdvertising.${peripheral.id}.${model}] return ${JSON.stringify(data)}`);
832
- return data;
833
- }
834
- /**
835
- * Validates if the buffer is a valid Buffer object with a minimum length.
836
- *
837
- * @param {any} buffer - The buffer to validate.
838
- * @param {number} minLength - The minimum required length.
839
- * @returns {boolean} - True if the buffer is valid, false otherwise.
840
- */
841
- static validateBuffer(buffer, minLength = 3) {
842
- return buffer && Buffer.isBuffer(buffer) && buffer.length >= minLength;
843
- }
844
- /**
845
- * Parses the service data based on the device model.
846
- *
847
- * @param {string} model - The device model.
848
- * @param {Buffer} serviceData - The service data buffer.
849
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
850
- * @param {Function} emitLog - The function to emit log messages.
851
- * @returns {Promise<any>} - The parsed service data.
852
- */
853
- static async parseServiceData(model, serviceData, manufacturerData, emitLog) {
854
- switch (model) {
855
- case SwitchBotBLEModel.Bot:
856
- return WoHand.parseServiceData(serviceData, emitLog);
857
- case SwitchBotBLEModel.Curtain:
858
- case SwitchBotBLEModel.Curtain3:
859
- return WoCurtain.parseServiceData(serviceData, manufacturerData, emitLog);
860
- case SwitchBotBLEModel.Humidifier:
861
- return WoHumi.parseServiceData(serviceData, emitLog);
862
- case SwitchBotBLEModel.Humidifier2:
863
- return WoHumi2.parseServiceData(serviceData, emitLog);
864
- case SwitchBotBLEModel.Meter:
865
- return WoSensorTH.parseServiceData(serviceData, emitLog);
866
- case SwitchBotBLEModel.MeterPlus:
867
- return WoSensorTHPlus.parseServiceData(serviceData, emitLog);
868
- case SwitchBotBLEModel.MeterPro:
869
- return WoSensorTHPro.parseServiceData(serviceData, emitLog);
870
- case SwitchBotBLEModel.MeterProCO2:
871
- return WoSensorTHProCO2.parseServiceData(serviceData, manufacturerData, emitLog);
872
- case SwitchBotBLEModel.Hub2:
873
- return WoHub2.parseServiceData(manufacturerData, emitLog);
874
- case SwitchBotBLEModel.Hub3:
875
- return WoHub3.parseServiceData(manufacturerData, emitLog);
876
- case SwitchBotBLEModel.OutdoorMeter:
877
- return WoIOSensorTH.parseServiceData(serviceData, manufacturerData, emitLog);
878
- case SwitchBotBLEModel.AirPurifier:
879
- return WoAirPurifier.parseServiceData(serviceData, manufacturerData, emitLog);
880
- case SwitchBotBLEModel.AirPurifierTable:
881
- return WoAirPurifierTable.parseServiceData(serviceData, manufacturerData, emitLog);
882
- case SwitchBotBLEModel.MotionSensor:
883
- return WoPresence.parseServiceData(serviceData, emitLog);
884
- case SwitchBotBLEModel.PresenceSensor:
885
- return WoPresence.parsePresenceSensorServiceData(serviceData, manufacturerData, emitLog);
886
- case SwitchBotBLEModel.ContactSensor:
887
- return WoContact.parseServiceData(serviceData, emitLog);
888
- case SwitchBotBLEModel.Remote:
889
- return WoRemote.parseServiceData(serviceData, emitLog);
890
- case SwitchBotBLEModel.ColorBulb:
891
- return WoBulb.parseServiceData(serviceData, manufacturerData, emitLog);
892
- case SwitchBotBLEModel.CeilingLight:
893
- return WoCeilingLight.parseServiceData(manufacturerData, emitLog);
894
- case SwitchBotBLEModel.CeilingLightPro:
895
- return WoCeilingLight.parseServiceData_Pro(manufacturerData, emitLog);
896
- case SwitchBotBLEModel.StripLight:
897
- return WoStrip.parseServiceData(serviceData, emitLog);
898
- case SwitchBotBLEModel.PlugMiniUS:
899
- return WoPlugMiniUS.parseServiceData(manufacturerData, emitLog);
900
- case SwitchBotBLEModel.PlugMiniJP:
901
- return WoPlugMiniJP.parseServiceData(manufacturerData, emitLog);
902
- case SwitchBotBLEModel.Lock:
903
- return WoSmartLock.parseServiceData(serviceData, manufacturerData, emitLog);
904
- case SwitchBotBLEModel.LockPro:
905
- return WoSmartLockPro.parseServiceData(serviceData, manufacturerData, emitLog);
906
- case SwitchBotBLEModel.LockUltra:
907
- return WoSmartLockUltra.parseServiceData(serviceData, manufacturerData, emitLog);
908
- case SwitchBotBLEModel.BlindTilt:
909
- return WoBlindTilt.parseServiceData(serviceData, manufacturerData, emitLog);
910
- case SwitchBotBLEModel.Leak:
911
- return WoLeak.parseServiceData(serviceData, manufacturerData, emitLog);
912
- case SwitchBotBLEModel.RelaySwitch1:
913
- return WoRelaySwitch1.parseServiceData(serviceData, manufacturerData, emitLog);
914
- case SwitchBotBLEModel.RelaySwitch1PM:
915
- return WoRelaySwitch1PM.parseServiceData(serviceData, manufacturerData, emitLog);
916
- default:
917
- emitLog('debug', `[parseAdvertising.${model}] return null, model "${model}" not available!`);
918
- return null;
919
- }
920
- }
921
- /**
922
- * Formats the address of the peripheral.
923
- *
924
- * @param {NobleTypes['peripheral']} peripheral - The peripheral device object from noble.
925
- * @returns {string} - The formatted address.
926
- */
927
- static formatAddress(peripheral) {
928
- let address = peripheral.address || '';
929
- if (address === '') {
930
- const str = peripheral.advertisement.manufacturerData?.toString('hex').slice(4, 16) || '';
931
- if (str !== '') {
932
- address = str.match(/.{1,2}/g)?.join(':') || '';
933
- }
934
- }
935
- else {
936
- address = address.replace(/-/g, ':');
937
- }
938
- return address;
939
- }
940
- }
941
- /**
942
- * Class representing a WoBlindTilt device.
943
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain.md
944
- */
945
- export class WoBlindTilt extends SwitchbotDevice {
946
- reverse = false;
947
- /**
948
- * Parses the service data and manufacturer data for the WoBlindTilt device.
949
- * @param {Buffer} serviceData - The service data buffer.
950
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
951
- * @param {Function} emitLog - The function to emit log messages.
952
- * @param {boolean} [reverse] - Whether to reverse the tilt percentage.
953
- * @returns {Promise<blindTiltServiceData | null>} - The parsed data object or null if the data is invalid.
954
- */
955
- static async parseServiceData(serviceData, manufacturerData, emitLog, reverse = false) {
956
- if (![5, 6].includes(manufacturerData.length)) {
957
- emitLog('debugerror', `[parseServiceDataForWoBlindTilt] Buffer length ${manufacturerData.length} !== 5 or 6!`);
958
- return null;
959
- }
960
- const byte2 = serviceData.readUInt8(2);
961
- const byte6 = manufacturerData.subarray(6);
962
- const tilt = Math.max(Math.min(byte6.readUInt8(2) & 0b01111111, 100), 0);
963
- const inMotion = !!(byte2 & 0b10000000);
964
- const lightLevel = (byte6.readUInt8(1) >> 4) & 0b00001111;
965
- const calibration = !!(byte6.readUInt8(1) & 0b00000001);
966
- const sequenceNumber = byte6.readUInt8(0);
967
- const battery = serviceData.length > 2 ? byte2 & 0b01111111 : 0;
968
- const data = {
969
- model: SwitchBotBLEModel.BlindTilt,
970
- modelName: SwitchBotBLEModelName.BlindTilt,
971
- modelFriendlyName: SwitchBotBLEModelFriendlyName.BlindTilt,
972
- calibration,
973
- battery,
974
- inMotion,
975
- tilt: reverse ? 100 - tilt : tilt,
976
- lightLevel,
977
- sequenceNumber,
978
- };
979
- return data;
980
- }
981
- constructor(peripheral, noble) {
982
- super(peripheral, noble);
983
- }
984
- /**
985
- * Opens the blind tilt to the fully open position.
986
- * @returns {Promise<void>}
987
- */
988
- async open() {
989
- await this.operateBlindTilt([...BLIND_TILT_COMMANDS.OPEN]);
990
- }
991
- /**
992
- * Closes the blind tilt up to the nearest endpoint.
993
- * @returns {Promise<void>}
994
- */
995
- async closeUp() {
996
- await this.operateBlindTilt([...BLIND_TILT_COMMANDS.CLOSE_UP]);
997
- }
998
- /**
999
- * Closes the blind tilt down to the nearest endpoint.
1000
- * @returns {Promise<void>}
1001
- */
1002
- async closeDown() {
1003
- await this.operateBlindTilt([...BLIND_TILT_COMMANDS.CLOSE_DOWN]);
1004
- }
1005
- /**
1006
- * Closes the blind tilt to the nearest endpoint.
1007
- * @returns {Promise<void>}
1008
- */
1009
- async close() {
1010
- const position = await this.getPosition();
1011
- if (position > 50) {
1012
- await this.closeUp();
1013
- }
1014
- else {
1015
- await this.closeDown();
1016
- }
1017
- }
1018
- /**
1019
- * Retrieves the current position of the blind tilt.
1020
- * @returns {Promise<number>} - The current position of the blind tilt (0-100).
1021
- */
1022
- async getPosition() {
1023
- const tiltPosition = await this._getAdvValue('tilt');
1024
- return Math.max(0, Math.min(tiltPosition, 100));
1025
- }
1026
- /**
1027
- * Retrieves the advertised value for a given key.
1028
- * @param {string} key - The key for the advertised value.
1029
- * @returns {Promise<number>} - The advertised value.
1030
- * @private
1031
- */
1032
- async _getAdvValue(key) {
1033
- if (key === 'tilt') {
1034
- return 50; // Example value
1035
- }
1036
- throw new Error(`Unknown key: ${key}`);
1037
- }
1038
- /**
1039
- * Retrieves the basic information of the blind tilt.
1040
- * @returns {Promise<object | null>} - A promise that resolves to an object containing the basic information of the blind tilt.
1041
- */
1042
- async getBasicInfo() {
1043
- const data = await this.getBasicInfo();
1044
- if (!data) {
1045
- return null;
1046
- }
1047
- const tilt = Math.max(Math.min(data[6], 100), 0);
1048
- const moving = Boolean(data[5] & 0b00000011);
1049
- let opening = false;
1050
- let closing = false;
1051
- let up = false;
1052
- if (moving) {
1053
- opening = Boolean(data[5] & 0b00000010);
1054
- closing = !opening && Boolean(data[5] & 0b00000001);
1055
- if (opening) {
1056
- const flag = Boolean(data[5] & 0b00000001);
1057
- up = flag ? this.reverse : !flag;
1058
- }
1059
- else {
1060
- up = tilt < 50 ? this.reverse : tilt > 50;
1061
- }
1062
- }
1063
- return {
1064
- battery: data[1],
1065
- firmware: data[2] / 10.0,
1066
- light: Boolean(data[4] & 0b00100000),
1067
- fault: Boolean(data[4] & 0b00001000),
1068
- solarPanel: Boolean(data[5] & 0b00001000),
1069
- calibration: Boolean(data[5] & 0b00000100),
1070
- calibrated: Boolean(data[5] & 0b00000100),
1071
- inMotion: moving,
1072
- motionDirection: {
1073
- opening: moving && opening,
1074
- closing: moving && closing,
1075
- up: moving && up,
1076
- down: moving && !up,
1077
- },
1078
- tilt: this.reverse ? 100 - tilt : tilt,
1079
- timers: data[7],
1080
- };
1081
- }
1082
- /**
1083
- * Pauses the blind tilt operation.
1084
- * @returns {Promise<void>}
1085
- */
1086
- async pause() {
1087
- await this.operateBlindTilt([...BLIND_TILT_COMMANDS.PAUSE]);
1088
- }
1089
- /**
1090
- * Runs the blind tilt to the specified position.
1091
- * @param {number} percent - The target position percentage (0-100).
1092
- * @param {number} mode - The running mode (0 or 1).
1093
- * @returns {Promise<void>}
1094
- */
1095
- async runToPos(percent, mode) {
1096
- ValidationUtils.validatePercentage(percent, 'percent');
1097
- ValidationUtils.validateRange(mode, 0, 1, 'mode', true);
1098
- const adjustedPercent = this.reverse ? 100 - percent : percent;
1099
- await this.operateBlindTilt([0x57, 0x0F, 0x45, 0x01, 0x05, mode, adjustedPercent]);
1100
- }
1101
- /**
1102
- * Sends a command to operate the blind tilt and handles the response.
1103
- * @param {number[]} bytes - The byte array representing the command to be sent to the device.
1104
- * @returns {Promise<void>}
1105
- * @private
1106
- */
1107
- async operateBlindTilt(bytes) {
1108
- const reqBuf = Buffer.from(bytes);
1109
- const resBuf = await this.command(reqBuf);
1110
- if (resBuf.length !== 3 || resBuf.readUInt8(0) !== 0x01) {
1111
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1112
- }
1113
- }
1114
- }
1115
- /**
1116
- * Class representing a WoBulb device.
1117
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/colorbulb.md
1118
- */
1119
- export class WoBulb extends SwitchbotDevice {
1120
- /**
1121
- * Parses the service data for WoBulb.
1122
- * @param {Buffer} serviceData - The service data buffer.
1123
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1124
- * @param {Function} emitLog - The function to emit log messages.
1125
- * @returns {Promise<colorBulbServiceData | null>} - Parsed service data or null if invalid.
1126
- */
1127
- static async parseServiceData(serviceData, manufacturerData,
1128
- // eslint-disable-next-line unused-imports/no-unused-vars
1129
- emitLog) {
1130
- if (serviceData.length !== 18) {
1131
- // emitLog('debugerror', `[parseServiceDataForWoBulb] Buffer length ${serviceData.length} !== 18!`)
1132
- return null;
1133
- }
1134
- if (manufacturerData.length !== 13) {
1135
- // emitLog('debugerror', `[parseServiceDataForWoBulb] Buffer length ${manufacturerData.length} !== 13!`)
1136
- return null;
1137
- }
1138
- const [, byte1, , byte3, byte4, byte5, byte6, byte7, byte8, byte9, byte10,] = manufacturerData;
1139
- const data = {
1140
- model: SwitchBotBLEModel.ColorBulb,
1141
- modelName: SwitchBotBLEModelName.ColorBulb,
1142
- modelFriendlyName: SwitchBotBLEModelFriendlyName.ColorBulb,
1143
- power: !!byte1,
1144
- red: byte3,
1145
- green: byte4,
1146
- blue: byte5,
1147
- color_temperature: byte6,
1148
- state: !!(byte7 & 0b01111111),
1149
- brightness: byte7 & 0b01111111,
1150
- delay: (byte8 & 0b10000000) >> 7,
1151
- preset: (byte8 & 0b00001000) >> 3,
1152
- color_mode: byte8 & 0b00000111,
1153
- speed: byte9 & 0b01111111,
1154
- loop_index: byte10 & 0b11111110,
1155
- };
1156
- return data;
1157
- }
1158
- constructor(peripheral, noble) {
1159
- super(peripheral, noble);
1160
- }
1161
- /**
1162
- * Reads the state of the bulb.
1163
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is ON (true) or OFF (false).
1164
- */
1165
- async readState() {
1166
- return this.operateBulb([...BULB_COMMANDS.READ_STATE]);
1167
- }
1168
- /**
1169
- * Sets the state of the bulb.
1170
- * @param {number[]} reqByteArray - The request byte array.
1171
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1172
- * @private
1173
- */
1174
- async setState(reqByteArray) {
1175
- return this.operateBulb([...BULB_COMMANDS.BASE, ...reqByteArray]);
1176
- }
1177
- /**
1178
- * Turns on the bulb.
1179
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is ON (true).
1180
- */
1181
- async turnOn() {
1182
- return this.setState([...BULB_COMMANDS.TURN_ON]);
1183
- }
1184
- /**
1185
- * Turns off the bulb.
1186
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the bulb is OFF (false).
1187
- */
1188
- async turnOff() {
1189
- return this.setState([...BULB_COMMANDS.TURN_OFF]);
1190
- }
1191
- /**
1192
- * Sets the brightness of the bulb.
1193
- * @param {number} brightness - The brightness percentage (0-100).
1194
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1195
- */
1196
- async setBrightness(brightness) {
1197
- ValidationUtils.validatePercentage(brightness, 'brightness');
1198
- return this.setState([...BULB_COMMANDS.SET_BRIGHTNESS, brightness]);
1199
- }
1200
- /**
1201
- * Sets the color temperature of the bulb.
1202
- * @param {number} color_temperature - The color temperature percentage (0-100).
1203
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1204
- */
1205
- async setColorTemperature(color_temperature) {
1206
- ValidationUtils.validatePercentage(color_temperature, 'color_temperature');
1207
- return this.setState([...BULB_COMMANDS.SET_COLOR_TEMP, color_temperature]);
1208
- }
1209
- /**
1210
- * Sets the RGB color of the bulb.
1211
- * @param {number} brightness - The brightness percentage (0-100).
1212
- * @param {number} red - The red color value (0-255).
1213
- * @param {number} green - The green color value (0-255).
1214
- * @param {number} blue - The blue color value (0-255).
1215
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1216
- */
1217
- async setRGB(brightness, red, green, blue) {
1218
- ValidationUtils.validatePercentage(brightness, 'brightness');
1219
- ValidationUtils.validateRGB(red, 'red');
1220
- ValidationUtils.validateRGB(green, 'green');
1221
- ValidationUtils.validateRGB(blue, 'blue');
1222
- return this.setState([...BULB_COMMANDS.SET_RGB, brightness, red, green, blue]);
1223
- }
1224
- /**
1225
- * Sends a command to the bulb.
1226
- * @param {number[]} bytes - The command bytes.
1227
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1228
- * @private
1229
- */
1230
- async operateBulb(bytes) {
1231
- const reqBuf = Buffer.from(bytes);
1232
- const resBuf = await this.command(reqBuf);
1233
- if (resBuf.length === 2) {
1234
- const code = resBuf.readUInt8(1);
1235
- if (code === 0x00 || code === 0x80) {
1236
- return code === 0x80;
1237
- }
1238
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1239
- }
1240
- throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
1241
- }
1242
- }
1243
- /**
1244
- * Class representing a WoCeilingLight device.
1245
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/colorbulb.md
1246
- */
1247
- export class WoCeilingLight extends SwitchbotDevice {
1248
- /**
1249
- * Parses the service data for WoCeilingLight.
1250
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1251
- * @param {Function} emitLog - The function to emit log messages.
1252
- * @returns {Promise<ceilingLightServiceData | null>} - Parsed service data or null if invalid.
1253
- */
1254
- static async parseServiceData(manufacturerData, emitLog) {
1255
- if (manufacturerData.length !== 13) {
1256
- emitLog('debugerror', `[parseServiceDataForWoCeilingLight] Buffer length ${manufacturerData.length} !== 13!`);
1257
- return null;
1258
- }
1259
- const [, byte1, , byte3, byte4, byte5, byte6, byte7, byte8, byte9, byte10,] = manufacturerData;
1260
- const data = {
1261
- model: SwitchBotBLEModel.CeilingLight,
1262
- modelName: SwitchBotBLEModelName.CeilingLight,
1263
- modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLight,
1264
- power: !!byte1,
1265
- red: byte3,
1266
- green: byte4,
1267
- blue: byte5,
1268
- color_temperature: byte6,
1269
- state: !!(byte7 & 0b01111111),
1270
- brightness: byte7 & 0b01111111,
1271
- delay: (byte8 & 0b10000000) ? 1 : 0,
1272
- preset: (byte8 & 0b00001000) ? 1 : 0,
1273
- color_mode: byte8 & 0b00000111,
1274
- speed: byte9 & 0b01111111,
1275
- loop_index: byte10 & 0b11111110,
1276
- };
1277
- return data;
1278
- }
1279
- /**
1280
- * Parses the service data for WoCeilingLight Pro.
1281
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1282
- * @param {Function} emitLog - The function to emit log messages.
1283
- * @returns {Promise<ceilingLightProServiceData | null>} - Parsed service data or null if invalid.
1284
- */
1285
- static async parseServiceData_Pro(manufacturerData, emitLog) {
1286
- if (manufacturerData.length !== 13) {
1287
- emitLog('debugerror', `[parseServiceDataForWoCeilingLightPro] Buffer length ${manufacturerData.length} !== 13!`);
1288
- return null;
1289
- }
1290
- const [, byte1, , byte3, byte4, byte5, byte6, byte7, byte8, byte9, byte10,] = manufacturerData;
1291
- const data = {
1292
- model: SwitchBotBLEModel.CeilingLightPro,
1293
- modelName: SwitchBotBLEModelName.CeilingLightPro,
1294
- modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLightPro,
1295
- power: !!byte1,
1296
- red: byte3,
1297
- green: byte4,
1298
- blue: byte5,
1299
- color_temperature: byte6,
1300
- state: !!(byte7 & 0b01111111),
1301
- brightness: byte7 & 0b01111111,
1302
- delay: (byte8 & 0b10000000) ? 1 : 0,
1303
- preset: (byte8 & 0b00001000) ? 1 : 0,
1304
- color_mode: byte8 & 0b00000111,
1305
- speed: byte9 & 0b01111111,
1306
- loop_index: byte10 & 0b11111110,
1307
- };
1308
- return data;
1309
- }
1310
- constructor(peripheral, noble) {
1311
- super(peripheral, noble);
1312
- }
1313
- /**
1314
- * Reads the state of the ceiling light.
1315
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the light is ON (true) or OFF (false).
1316
- */
1317
- async readState() {
1318
- return this.operateCeilingLight([0x57, 0x0F, 0x48, 0x01]);
1319
- }
1320
- /**
1321
- * Sets the state of the ceiling light.
1322
- * @param {number[]} reqByteArray - The request byte array.
1323
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1324
- */
1325
- async setState(reqByteArray) {
1326
- const base = [0x57, 0x0F, 0x47, 0x01];
1327
- return this.operateCeilingLight(base.concat(reqByteArray));
1328
- }
1329
- /**
1330
- * Turns on the ceiling light.
1331
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the light is ON (true).
1332
- */
1333
- async turnOn() {
1334
- return this.setState([0x01, 0x01]);
1335
- }
1336
- /**
1337
- * Turns off the ceiling light.
1338
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the light is OFF (false).
1339
- */
1340
- async turnOff() {
1341
- return this.setState([0x01, 0x02]);
1342
- }
1343
- /**
1344
- * Sets the brightness of the ceiling light.
1345
- * @param {number} brightness - The brightness percentage (0-100).
1346
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1347
- */
1348
- async setBrightness(brightness) {
1349
- if (typeof brightness !== 'number' || brightness < 0 || brightness > 100) {
1350
- throw new TypeError(`Invalid brightness value: ${brightness}`);
1351
- }
1352
- return this.setState([0x02, 0x14, brightness]);
1353
- }
1354
- /**
1355
- * Sets the color temperature of the ceiling light.
1356
- * @param {number} color_temperature - The color temperature percentage (0-100).
1357
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1358
- */
1359
- async setColorTemperature(color_temperature) {
1360
- if (typeof color_temperature !== 'number' || color_temperature < 0 || color_temperature > 100) {
1361
- throw new TypeError(`Invalid color temperature value: ${color_temperature}`);
1362
- }
1363
- return this.setState([0x02, 0x17, color_temperature]);
1364
- }
1365
- /**
1366
- * Sets the RGB color of the ceiling light.
1367
- * @param {number} brightness - The brightness percentage (0-100).
1368
- * @param {number} red - The red color value (0-255).
1369
- * @param {number} green - The green color value (0-255).
1370
- * @param {number} blue - The blue color value (0-255).
1371
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1372
- */
1373
- async setRGB(brightness, red, green, blue) {
1374
- if (typeof brightness !== 'number' || brightness < 0 || brightness > 100
1375
- || typeof red !== 'number' || red < 0 || red > 255
1376
- || typeof green !== 'number' || green < 0 || green > 255
1377
- || typeof blue !== 'number' || blue < 0 || blue > 255) {
1378
- throw new TypeError('Invalid RGB or brightness values');
1379
- }
1380
- return this.setState([0x02, 0x12, brightness, red, green, blue]);
1381
- }
1382
- /**
1383
- * Sends a command to the ceiling light.
1384
- * @param {number[]} bytes - The command bytes.
1385
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
1386
- */
1387
- async operateCeilingLight(bytes) {
1388
- const reqBuf = Buffer.from(bytes);
1389
- const resBuf = await this.command(reqBuf);
1390
- if (resBuf.length === 2) {
1391
- const code = resBuf.readUInt8(1);
1392
- if (code === 0x00 || code === 0x80) {
1393
- return code === 0x80;
1394
- }
1395
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1396
- }
1397
- throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
1398
- }
1399
- }
1400
- /**
1401
- * Class representing a WoContact device.
1402
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/contactsensor.md
1403
- */
1404
- export class WoContact extends SwitchbotDevice {
1405
- /**
1406
- * Parses the service data for WoContact.
1407
- * @param {Buffer} serviceData - The service data buffer.
1408
- * @param {Function} emitLog - The function to emit log messages.
1409
- * @returns {Promise<contactSensorServiceData | null>} - Parsed service data or null if invalid.
1410
- */
1411
- static async parseServiceData(serviceData, emitLog) {
1412
- if (serviceData.length !== 9) {
1413
- emitLog('debugerror', `[parseServiceDataForWoContact] Buffer length ${serviceData.length} !== 9!`);
1414
- return null;
1415
- }
1416
- const [byte1, byte2, byte3, , , , , , byte8] = serviceData;
1417
- const hallState = (byte3 >> 1) & 0b00000011;
1418
- const tested = Boolean(byte1 & 0b10000000);
1419
- const movement = Boolean(byte1 & 0b01000000);
1420
- const battery = byte2 & 0b01111111;
1421
- const contact_open = Boolean(byte3 & 0b00000010);
1422
- const contact_timeout = Boolean(byte3 & 0b00000100);
1423
- const lightLevel = byte3 & 0b00000001 ? 'bright' : 'dark';
1424
- const button_count = byte8 & 0b00001111;
1425
- const doorState = hallState === 0 ? 'close' : hallState === 1 ? 'open' : 'timeout no closed';
1426
- const data = {
1427
- model: SwitchBotBLEModel.ContactSensor,
1428
- modelName: SwitchBotBLEModelName.ContactSensor,
1429
- modelFriendlyName: SwitchBotBLEModelFriendlyName.ContactSensor,
1430
- movement,
1431
- tested,
1432
- battery,
1433
- contact_open,
1434
- contact_timeout,
1435
- lightLevel,
1436
- button_count,
1437
- doorState,
1438
- };
1439
- return data;
1440
- }
1441
- constructor(peripheral, noble) {
1442
- super(peripheral, noble);
1443
- }
1444
- }
1445
- /**
1446
- * Class representing a WoCurtain device.
1447
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain.md
1448
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/curtain3.md
1449
- */
1450
- export class WoCurtain extends SwitchbotDevice {
1451
- /**
1452
- * Parses the service data for WoCurtain.
1453
- * @param {Buffer} serviceData - The service data buffer.
1454
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1455
- * @param {Function} emitLog - The function to emit log messages.
1456
- * @param {boolean} [reverse] - Whether to reverse the position.
1457
- * @returns {Promise<curtainServiceData | curtain3ServiceData | null>} - Parsed service data or null if invalid.
1458
- */
1459
- static async parseServiceData(serviceData, manufacturerData, emitLog, reverse = false) {
1460
- if (![5, 6].includes(serviceData.length)) {
1461
- emitLog('debugerror', `[parseServiceDataForWoCurtain] Buffer length ${serviceData.length} !== 5 or 6!`);
1462
- return null;
1463
- }
1464
- const byte1 = serviceData.readUInt8(1);
1465
- const byte2 = serviceData.readUInt8(2);
1466
- let deviceData;
1467
- let batteryData = null;
1468
- if (manufacturerData.length >= 13) {
1469
- deviceData = manufacturerData.subarray(8, 11);
1470
- batteryData = manufacturerData.readUInt8(12);
1471
- }
1472
- else if (manufacturerData.length >= 11) {
1473
- deviceData = manufacturerData.subarray(8, 11);
1474
- batteryData = byte2;
1475
- }
1476
- else {
1477
- deviceData = serviceData.subarray(3, 6);
1478
- batteryData = byte2;
1479
- }
1480
- const model = serviceData.subarray(0, 1).toString('utf8') ? SwitchBotBLEModel.Curtain : SwitchBotBLEModel.Curtain3;
1481
- const calibration = Boolean(byte1 & 0b01000000);
1482
- if (model === SwitchBotBLEModel.Curtain) {
1483
- const byte3 = serviceData.readUInt8(3);
1484
- const byte4 = serviceData.readUInt8(4);
1485
- const position = byte3 & 0b01111111;
1486
- const inMotion = Boolean(byte3 & 0b10000000);
1487
- const lightLevel = (byte4 >> 4) & 0b00001111;
1488
- const deviceChain = byte4 & 0b00000111;
1489
- const battery = byte2 & 0b01111111;
1490
- const data = {
1491
- model: SwitchBotBLEModel.Curtain,
1492
- modelName: SwitchBotBLEModelName.Curtain,
1493
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain,
1494
- calibration,
1495
- battery: battery ?? 0,
1496
- inMotion,
1497
- position: reverse ? 100 - position : position,
1498
- lightLevel,
1499
- deviceChain,
1500
- };
1501
- return data;
1502
- }
1503
- else {
1504
- const position = Math.max(Math.min(deviceData.readUInt8(0) & 0b01111111, 100), 0);
1505
- const inMotion = Boolean(deviceData.readUInt8(0) & 0b10000000);
1506
- const lightLevel = (deviceData.readUInt8(1) >> 4) & 0b00001111;
1507
- const deviceChain = deviceData.readUInt8(1) & 0b00000111;
1508
- const battery = batteryData !== null ? batteryData & 0b01111111 : 0;
1509
- const data = {
1510
- model: SwitchBotBLEModel.Curtain3,
1511
- modelName: SwitchBotBLEModelName.Curtain3,
1512
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain3,
1513
- calibration,
1514
- battery,
1515
- inMotion,
1516
- position: reverse ? 100 - position : position,
1517
- lightLevel,
1518
- deviceChain,
1519
- };
1520
- return data;
1521
- }
1522
- }
1523
- constructor(peripheral, noble) {
1524
- super(peripheral, noble);
1525
- }
1526
- /**
1527
- * Opens the curtain.
1528
- * @param {number} [mode] - Running mode (0x01 = QuietDrift, 0xFF = Default).
1529
- * @returns {Promise<void>}
1530
- */
1531
- async open(mode = 0xFF) {
1532
- await this.runToPos(0, mode);
1533
- }
1534
- /**
1535
- * Closes the curtain.
1536
- * @param {number} [mode] - Running mode (0x01 = QuietDrift, 0xFF = Default).
1537
- * @returns {Promise<void>}
1538
- */
1539
- async close(mode = 0xFF) {
1540
- await this.runToPos(100, mode);
1541
- }
1542
- /**
1543
- * Pauses the curtain.
1544
- * @returns {Promise<void>}
1545
- */
1546
- async pause() {
1547
- await this.operateCurtain([0x57, 0x0F, 0x45, 0x01, 0x00, 0xFF]);
1548
- }
1549
- /**
1550
- * Runs the curtain to the target position.
1551
- * @param {number} percent - The percentage of the target position.
1552
- * @param {number} [mode] - Running mode (0x01 = QuietDrift, 0xFF = Default).
1553
- * @returns {Promise<void>}
1554
- */
1555
- async runToPos(percent, mode = 0xFF) {
1556
- if (typeof percent !== 'number' || typeof mode !== 'number') {
1557
- throw new TypeError('Invalid type for percent or mode');
1558
- }
1559
- percent = Math.max(0, Math.min(100, percent));
1560
- await this.operateCurtain([0x57, 0x0F, 0x45, 0x01, 0x05, mode, percent]);
1561
- }
1562
- /**
1563
- * Sends a command to the curtain.
1564
- * @param {number[]} bytes - The command bytes.
1565
- * @returns {Promise<void>}
1566
- */
1567
- async operateCurtain(bytes) {
1568
- const reqBuf = Buffer.from(bytes);
1569
- const resBuf = await this.command(reqBuf);
1570
- const code = resBuf.readUInt8(0);
1571
- if (resBuf.length !== 3 || code !== 0x01) {
1572
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1573
- }
1574
- }
1575
- }
1576
- /**
1577
- * Class representing a WoHand device.
1578
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/bot.md
1579
- */
1580
- export class WoHand extends SwitchbotDevice {
1581
- /**
1582
- * Parses the service data for WoHand.
1583
- * @param {Buffer} serviceData - The service data buffer.
1584
- * @param {Function} emitLog - The function to emit log messages.
1585
- * @returns {Promise<botServiceData | null>} - Parsed service data or null if invalid.
1586
- */
1587
- static async parseServiceData(serviceData, emitLog) {
1588
- if (!serviceData || serviceData.length < 3) {
1589
- emitLog('debugerror', `[parseServiceData] Service Data Buffer length ${serviceData?.length ?? 0} < 3!`);
1590
- return null;
1591
- }
1592
- const byte1 = serviceData.readUInt8(1);
1593
- const byte2 = serviceData.readUInt8(2);
1594
- const data = {
1595
- model: SwitchBotBLEModel.Bot,
1596
- modelName: SwitchBotBLEModelName.Bot,
1597
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Bot,
1598
- mode: !!(byte1 & 0b10000000), // Whether the light switch Add-on is used or not. 0 = press, 1 = switch
1599
- state: !!(byte1 & 0b01000000), // Whether the switch status is ON or OFF. 0 = on, 1 = off
1600
- battery: byte2 & 0b01111111, // %
1601
- };
1602
- return data;
1603
- }
1604
- constructor(peripheral, noble) {
1605
- super(peripheral, noble);
1606
- }
1607
- /**
1608
- * Sends a command to the bot.
1609
- * @param {Buffer} reqBuf - The command buffer.
1610
- * @returns {Promise<void>}
1611
- */
1612
- async sendCommand(reqBuf) {
1613
- const resBuf = await this.command(reqBuf);
1614
- const code = resBuf.readUInt8(0);
1615
- if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
1616
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1617
- }
1618
- }
1619
- /**
1620
- * Presses the bot.
1621
- * @returns {Promise<void>}
1622
- */
1623
- async press() {
1624
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x00]));
1625
- }
1626
- /**
1627
- * Turns on the bot.
1628
- * @returns {Promise<void>}
1629
- */
1630
- async turnOn() {
1631
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x01]));
1632
- }
1633
- /**
1634
- * Turns off the bot.
1635
- * @returns {Promise<void>}
1636
- */
1637
- async turnOff() {
1638
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x02]));
1639
- }
1640
- /**
1641
- * Moves the bot down.
1642
- * @returns {Promise<void>}
1643
- */
1644
- async down() {
1645
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x03]));
1646
- }
1647
- /**
1648
- * Moves the bot up.
1649
- * @returns {Promise<void>}
1650
- */
1651
- async up() {
1652
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x04]));
1653
- }
1654
- }
1655
- /**
1656
- * Class representing a WoHub2 device.
1657
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
1658
- */
1659
- export class WoHub2 extends SwitchbotDevice {
1660
- /**
1661
- * Parses the service data for WoHub2.
1662
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1663
- * @param {Function} emitLog - The function to emit log messages.
1664
- * @returns {Promise<hub2ServiceData | null>} - Parsed service data or null if invalid.
1665
- */
1666
- static async parseServiceData(manufacturerData, emitLog) {
1667
- if (manufacturerData.length !== 16) {
1668
- emitLog('debugerror', `[parseServiceDataForWoHub2] Buffer length ${manufacturerData.length} !== 16!`);
1669
- return null;
1670
- }
1671
- const [byte0, byte1, byte2, , , , , , , , , , byte12] = manufacturerData;
1672
- const tempSign = byte1 & 0b10000000 ? 1 : -1;
1673
- const tempC = tempSign * ((byte1 & 0b01111111) + (byte0 & 0b00001111) / 10);
1674
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
1675
- const lightLevel = byte12 & 0b11111;
1676
- const data = {
1677
- model: SwitchBotBLEModel.Hub2,
1678
- modelName: SwitchBotBLEModelName.Hub2,
1679
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Hub2,
1680
- celsius: tempC,
1681
- fahrenheit: tempF,
1682
- fahrenheit_mode: !!(byte2 & 0b10000000),
1683
- humidity: byte2 & 0b01111111,
1684
- lightLevel,
1685
- };
1686
- return data;
1687
- }
1688
- constructor(peripheral, noble) {
1689
- super(peripheral, noble);
1690
- }
1691
- }
1692
- /**
1693
- * Class representing a WoHub3 device.
1694
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
1695
- */
1696
- export class WoHub3 extends SwitchbotDevice {
1697
- /**
1698
- * Parses the service data for WoHub3.
1699
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1700
- * @param {Function} emitLog - The function to emit log messages.
1701
- * @returns {Promise<hub3ServiceData | null>} - Parsed service data or null if invalid.
1702
- */
1703
- static async parseServiceData(manufacturerData, emitLog) {
1704
- if (manufacturerData.length !== 16) {
1705
- emitLog('debugerror', `[parseServiceDataForWoHub3] Buffer length ${manufacturerData.length} !== 16!`);
1706
- return null;
1707
- }
1708
- const [byte0, byte1, byte2, , , , , , , , , , byte12] = manufacturerData;
1709
- const tempSign = byte1 & 0b10000000 ? 1 : -1;
1710
- const tempC = tempSign * ((byte1 & 0b01111111) + (byte0 & 0b00001111) / 10);
1711
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
1712
- const lightLevel = byte12 & 0b11111;
1713
- const data = {
1714
- model: SwitchBotBLEModel.Hub3,
1715
- modelName: SwitchBotBLEModelName.Hub3,
1716
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Hub3,
1717
- celsius: tempC,
1718
- fahrenheit: tempF,
1719
- fahrenheit_mode: !!(byte2 & 0b10000000),
1720
- humidity: byte2 & 0b01111111,
1721
- lightLevel,
1722
- };
1723
- return data;
1724
- }
1725
- constructor(peripheral, noble) {
1726
- super(peripheral, noble);
1727
- }
1728
- }
1729
- /**
1730
- * Class representing a WoHumi device.
1731
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/tree/latest/devicetypes
1732
- */
1733
- export class WoHumi extends SwitchbotDevice {
1734
- constructor(peripheral, noble) {
1735
- super(peripheral, noble);
1736
- }
1737
- /**
1738
- * Parses the service data for WoHumi.
1739
- * @param {Buffer} serviceData - The service data buffer.
1740
- * @param {Function} emitLog - The function to emit log messages.
1741
- * @returns {Promise<humidifierServiceData | null>} - Parsed service data or null if invalid.
1742
- */
1743
- static async parseServiceData(serviceData, emitLog) {
1744
- if (serviceData.length !== 8) {
1745
- emitLog('debugerror', `[parseServiceDataForWoHumi] Buffer length ${serviceData.length} !== 8!`);
1746
- return null;
1747
- }
1748
- const byte1 = serviceData.readUInt8(1);
1749
- const byte4 = serviceData.readUInt8(4);
1750
- const onState = !!(byte1 & 0b10000000); // 1 - on
1751
- const autoMode = !!(byte4 & 0b10000000); // 1 - auto
1752
- const percentage = byte4 & 0b01111111; // 0-100%, 101/102/103 - Quick gear 1/2/3
1753
- const humidity = autoMode ? 0 : percentage === 101 ? 33 : percentage === 102 ? 66 : percentage === 103 ? 100 : percentage;
1754
- const data = {
1755
- model: SwitchBotBLEModel.Humidifier,
1756
- modelName: SwitchBotBLEModelName.Humidifier,
1757
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier,
1758
- onState,
1759
- autoMode,
1760
- percentage: autoMode ? 0 : percentage,
1761
- humidity,
1762
- };
1763
- return data;
1764
- }
1765
- /**
1766
- * Sends a command to the humidifier.
1767
- * @param {Buffer} reqBuf - The command buffer.
1768
- * @returns {Promise<void>}
1769
- */
1770
- async operateHumi(reqBuf) {
1771
- const resBuf = await this.command(reqBuf);
1772
- const code = resBuf.readUInt8(0);
1773
- if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
1774
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1775
- }
1776
- }
1777
- /**
1778
- * Turns on the humidifier.
1779
- * @returns {Promise<void>}
1780
- */
1781
- async turnOn() {
1782
- await this.operateHumi(Buffer.from(TURN_ON_KEY, 'hex'));
1783
- }
1784
- /**
1785
- * Turns off the humidifier.
1786
- * @returns {Promise<void>}
1787
- */
1788
- async turnOff() {
1789
- await this.operateHumi(Buffer.from(TURN_OFF_KEY, 'hex'));
1790
- }
1791
- /**
1792
- * Increases the humidifier setting.
1793
- * @returns {Promise<void>}
1794
- */
1795
- async increase() {
1796
- await this.operateHumi(Buffer.from(INCREASE_KEY, 'hex'));
1797
- }
1798
- /**
1799
- * Decreases the humidifier setting.
1800
- * @returns {Promise<void>}
1801
- */
1802
- async decrease() {
1803
- await this.operateHumi(Buffer.from(DECREASE_KEY, 'hex'));
1804
- }
1805
- /**
1806
- * Sets the humidifier to auto mode.
1807
- * @returns {Promise<void>}
1808
- */
1809
- async setAutoMode() {
1810
- await this.operateHumi(Buffer.from(SET_AUTO_MODE_KEY, 'hex'));
1811
- }
1812
- /**
1813
- * Sets the humidifier to manual mode.
1814
- * @returns {Promise<void>}
1815
- */
1816
- async setManualMode() {
1817
- await this.operateHumi(Buffer.from(SET_MANUAL_MODE_KEY, 'hex'));
1818
- }
1819
- /**
1820
- * Sets the humidifier level.
1821
- * @param {number} level - The level to set (0-100).
1822
- * @returns {Promise<void>}
1823
- */
1824
- async percentage(level) {
1825
- if (level < 0 || level > 100) {
1826
- throw new Error('Level must be between 0 and 100');
1827
- }
1828
- const levelKey = `${HUMIDIFIER_COMMAND_HEADER}0107${level.toString(16).padStart(2, '0')}`;
1829
- await this.operateHumi(Buffer.from(levelKey, 'hex'));
1830
- }
1831
- }
1832
- /**
1833
- * Class representing a WoHumi device.
1834
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/tree/latest/devicetypes
1835
- */
1836
- export class WoHumi2 extends SwitchbotDevice {
1837
- constructor(peripheral, noble) {
1838
- super(peripheral, noble);
1839
- }
1840
- /**
1841
- * Parses the service data for WoHumi.
1842
- * @param {Buffer} serviceData - The service data buffer.
1843
- * @param {Function} emitLog - The function to emit log messages.
1844
- * @returns {Promise<humidifier2ServiceData | null>} - Parsed service data or null if invalid.
1845
- */
1846
- static async parseServiceData(serviceData, emitLog) {
1847
- if (serviceData.length !== 8) {
1848
- emitLog('debugerror', `[parseServiceDataForWoHumi] Buffer length ${serviceData.length} !== 8!`);
1849
- return null;
1850
- }
1851
- const byte1 = serviceData.readUInt8(1);
1852
- const byte4 = serviceData.readUInt8(4);
1853
- const byte8 = serviceData.readUInt8(8);
1854
- const byte10 = serviceData.readUInt8(10);
1855
- const byte11 = serviceData.readUInt8(11);
1856
- const byte12 = serviceData.readUInt8(12);
1857
- const onState = !!(byte1 & 0b10000000); // 1 - on
1858
- const autoMode = !!(byte4 & 0b10000000); // 1 - auto
1859
- const percentage = byte4 & 0b01111111; // 0-100%, 101/102/103 - Quick gear 1/2/3
1860
- const humidity = autoMode ? 0 : percentage === 101 ? 33 : percentage === 102 ? 66 : percentage === 103 ? 100 : percentage;
1861
- const childLock = !!(byte8 & 0b00100000);
1862
- const overHumidifyProtection = !!(byte8 & 0b10000000);
1863
- const tankRemoved = !!(byte8 & 0b00000100);
1864
- const tiltedAlert = !!(byte8 & 0b00000010);
1865
- const filterMissing = !!(byte8 & 0b00000001);
1866
- const temperature = (byte10 & 0b01111111) + (byte11 >> 4) / 10;
1867
- const filterRunTime = byte12;
1868
- const filterAlert = filterRunTime >= 240;
1869
- const waterLevel = byte11 & 0b00000011;
1870
- const data = {
1871
- model: SwitchBotBLEModel.Humidifier2,
1872
- modelName: SwitchBotBLEModelName.Humidifier2,
1873
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier2,
1874
- onState,
1875
- autoMode,
1876
- percentage: autoMode ? 0 : percentage,
1877
- humidity,
1878
- childLock,
1879
- overHumidifyProtection,
1880
- tankRemoved,
1881
- tiltedAlert,
1882
- filterMissing,
1883
- temperature,
1884
- filterRunTime,
1885
- filterAlert,
1886
- waterLevel,
1887
- };
1888
- return data;
1889
- }
1890
- /**
1891
- * Sends a command to the humidifier.
1892
- * @param {Buffer} reqBuf - The command buffer.
1893
- * @returns {Promise<void>}
1894
- */
1895
- async operateHumi(reqBuf) {
1896
- const resBuf = await this.command(reqBuf);
1897
- const code = resBuf.readUInt8(0);
1898
- if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
1899
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
1900
- }
1901
- }
1902
- /**
1903
- * Turns on the humidifier.
1904
- * @returns {Promise<void>}
1905
- */
1906
- async turnOn() {
1907
- await this.operateHumi(Buffer.from(TURN_ON_KEY, 'hex'));
1908
- }
1909
- /**
1910
- * Turns off the humidifier.
1911
- * @returns {Promise<void>}
1912
- */
1913
- async turnOff() {
1914
- await this.operateHumi(Buffer.from(TURN_OFF_KEY, 'hex'));
1915
- }
1916
- /**
1917
- * Increases the humidifier setting.
1918
- * @returns {Promise<void>}
1919
- */
1920
- async increase() {
1921
- await this.operateHumi(Buffer.from(INCREASE_KEY, 'hex'));
1922
- }
1923
- /**
1924
- * Decreases the humidifier setting.
1925
- * @returns {Promise<void>}
1926
- */
1927
- async decrease() {
1928
- await this.operateHumi(Buffer.from(DECREASE_KEY, 'hex'));
1929
- }
1930
- /**
1931
- * Sets the humidifier to auto mode.
1932
- * @returns {Promise<void>}
1933
- */
1934
- async setAutoMode() {
1935
- await this.operateHumi(Buffer.from(SET_AUTO_MODE_KEY, 'hex'));
1936
- }
1937
- /**
1938
- * Sets the humidifier to manual mode.
1939
- * @returns {Promise<void>}
1940
- */
1941
- async setManualMode() {
1942
- await this.operateHumi(Buffer.from(SET_MANUAL_MODE_KEY, 'hex'));
1943
- }
1944
- /**
1945
- * Sets the humidifier level.
1946
- * @param {number} level - The level to set (0-100).
1947
- * @returns {Promise<void>}
1948
- */
1949
- async percentage(level) {
1950
- if (level < 0 || level > 100) {
1951
- throw new Error('Level must be between 0 and 100');
1952
- }
1953
- const levelKey = `${HUMIDIFIER_COMMAND_HEADER}0107${level.toString(16).padStart(2, '0')}`;
1954
- await this.operateHumi(Buffer.from(levelKey, 'hex'));
1955
- }
1956
- }
1957
- /**
1958
- * Class representing a WoIOSensorTH device.
1959
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md#outdoor-temperaturehumidity-sensor
1960
- */
1961
- export class WoIOSensorTH extends SwitchbotDevice {
1962
- /**
1963
- * Parses the service data for WoIOSensorTH.
1964
- * @param {Buffer} serviceData - The service data buffer.
1965
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
1966
- * @param {Function} emitLog - The function to emit log messages.
1967
- * @returns {Promise<outdoorMeterServiceData | null>} - Parsed service data or null if invalid.
1968
- */
1969
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
1970
- if (serviceData.length !== 3) {
1971
- emitLog('debugerror', `[parseServiceDataForWoIOSensorTH] Service Data Buffer length ${serviceData.length} !== 3!`);
1972
- return null;
1973
- }
1974
- if (manufacturerData.length !== 14) {
1975
- emitLog('debugerror', `[parseServiceDataForWoIOSensorTH] Manufacturer Data Buffer length ${manufacturerData.length} !== 14!`);
1976
- return null;
1977
- }
1978
- const [mdByte10, mdByte11, mdByte12] = [
1979
- manufacturerData.readUInt8(10),
1980
- manufacturerData.readUInt8(11),
1981
- manufacturerData.readUInt8(12),
1982
- ];
1983
- const sdByte2 = serviceData.readUInt8(2);
1984
- const tempSign = mdByte11 & 0b10000000 ? 1 : -1;
1985
- const tempC = tempSign * ((mdByte11 & 0b01111111) + (mdByte10 & 0b00001111) / 10);
1986
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
1987
- const data = {
1988
- model: SwitchBotBLEModel.OutdoorMeter,
1989
- modelName: SwitchBotBLEModelName.OutdoorMeter,
1990
- modelFriendlyName: SwitchBotBLEModelFriendlyName.OutdoorMeter,
1991
- celsius: tempC,
1992
- fahrenheit: tempF,
1993
- fahrenheit_mode: !!(mdByte12 & 0b10000000),
1994
- humidity: mdByte12 & 0b01111111,
1995
- battery: sdByte2 & 0b01111111,
1996
- };
1997
- return data;
1998
- }
1999
- constructor(peripheral, noble) {
2000
- super(peripheral, noble);
2001
- }
2002
- }
2003
- /**
2004
- * Class representing a WoKeypad device.
2005
- */
2006
- export class WoKeypad extends SwitchbotDevice {
2007
- /**
2008
- * Parses the service data for WoKeypad.
2009
- * @param {Buffer} serviceData - The service data buffer.
2010
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2011
- * @param {Function} emitLog - The function to emit log messages.
2012
- * @returns {Promise<keypadDetectorServiceData | null>} - Parsed service data or null if invalid.
2013
- */
2014
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2015
- if (!serviceData || serviceData.length < 3) {
2016
- emitLog('debugerror', `[parseServiceDataForWoKeypad] Service Data Buffer length ${serviceData?.length ?? 0} < 3!`);
2017
- return null;
2018
- }
2019
- if (!manufacturerData || manufacturerData.length < 2) {
2020
- emitLog('debugerror', `[parseServiceDataForWoKeypad] Manufacturer Data Buffer length ${manufacturerData?.length ?? 0} < 2!`);
2021
- return null;
2022
- }
2023
- const modelId = serviceData.readUInt8(0);
2024
- if (modelId !== 0x26) {
2025
- // Not a Keypad
2026
- emitLog('debugerror', `[parseServiceDataForWoKeypad] Model ID ${modelId} !== 0x26!`);
2027
- return null;
2028
- }
2029
- const eventFlags = serviceData.readUInt8(1);
2030
- const keypadEventDetected = !!(eventFlags & 0b00000001); // Bit 0
2031
- const deviceTampered = !!(eventFlags & 0b00000010); // Bit 1
2032
- const batteryInfo = serviceData.readUInt8(2);
2033
- const batteryLevel = batteryInfo & 0b01111111; // Bits 0-6
2034
- const lowBattery = !!(batteryInfo & 0b10000000); // Bit 7
2035
- // Manufacturer data can be processed here if needed
2036
- const data = {
2037
- model: SwitchBotBLEModel.Keypad,
2038
- modelName: SwitchBotBLEModelName.Keypad,
2039
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Keypad,
2040
- event: keypadEventDetected,
2041
- tampered: deviceTampered,
2042
- battery: batteryLevel,
2043
- low_battery: lowBattery,
2044
- };
2045
- return data;
2046
- }
2047
- constructor(peripheral, noble) {
2048
- super(peripheral, noble);
2049
- }
2050
- }
2051
- /**
2052
- * Class representing a WoLeak device.
2053
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md#outdoor-temperaturehumidity-sensor
2054
- */
2055
- export class WoLeak extends SwitchbotDevice {
2056
- /**
2057
- * Parses the service data for WoLeak.
2058
- * @param {Buffer} serviceData - The service data buffer.
2059
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2060
- * @param {Function} emitLog - The function to emit log messages.
2061
- * @returns {Promise<waterLeakDetectorServiceData | null>} - Parsed service data or null if invalid.
2062
- */
2063
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2064
- if (!serviceData || serviceData.length < 3) {
2065
- emitLog('debugerror', `[parseServiceDataForWoLeakDetector] Service Data Buffer length ${serviceData?.length ?? 0} < 3!`);
2066
- return null;
2067
- }
2068
- if (!manufacturerData || manufacturerData.length < 2) {
2069
- emitLog('debugerror', `[parseServiceDataForWoLeakDetector] Manufacturer Data Buffer length ${manufacturerData?.length ?? 0} < 2!`);
2070
- return null;
2071
- }
2072
- const waterLeakDetected = manufacturerData.length > 8 && !!(manufacturerData.readUInt8(8) & 0b00000001); // Bit 0
2073
- const deviceTampered = !!(manufacturerData.readUInt8(8) & 0b00000010); // Bit 1
2074
- const batteryLevel = manufacturerData.readUInt8(7) & 0b01111111; // Bits 0-6
2075
- const lowBattery = !!(manufacturerData.readUInt8(7) & 0b10000000); // Bit 7
2076
- // Manufacturer data can be processed here if needed
2077
- const data = {
2078
- model: SwitchBotBLEModel.Leak,
2079
- modelName: SwitchBotBLEModelName.Leak,
2080
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Leak,
2081
- leak: waterLeakDetected,
2082
- tampered: deviceTampered,
2083
- battery: batteryLevel,
2084
- low_battery: lowBattery,
2085
- };
2086
- return data;
2087
- }
2088
- constructor(peripheral, noble) {
2089
- super(peripheral, noble);
2090
- }
2091
- }
2092
- /**
2093
- * Class representing a WoPlugMini device.
2094
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/plugmini.md
2095
- */
2096
- export class WoPlugMiniJP extends SwitchbotDevice {
2097
- constructor(peripheral, noble) {
2098
- super(peripheral, noble);
2099
- }
2100
- /**
2101
- * Parses the service data for WoPlugMini JP.
2102
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2103
- * @param {Function} emitLog - The function to emit log messages.
2104
- * @returns {Promise<plugMiniJPServiceData | null>} - Parsed service data or null if invalid.
2105
- */
2106
- static async parseServiceData(manufacturerData, emitLog) {
2107
- if (manufacturerData.length !== 14) {
2108
- emitLog('debugerror', `[parseServiceDataForWoPlugMiniJP] Buffer length ${manufacturerData.length} should be 14`);
2109
- return null;
2110
- }
2111
- const [byte9, byte10, byte11, byte12, byte13] = [
2112
- manufacturerData.readUInt8(9),
2113
- manufacturerData.readUInt8(10),
2114
- manufacturerData.readUInt8(11),
2115
- manufacturerData.readUInt8(12),
2116
- manufacturerData.readUInt8(13),
2117
- ];
2118
- const state = byte9 === 0x00 ? 'off' : byte9 === 0x80 ? 'on' : null;
2119
- const delay = !!(byte10 & 0b00000001);
2120
- const timer = !!(byte10 & 0b00000010);
2121
- const syncUtcTime = !!(byte10 & 0b00000100);
2122
- const wifiRssi = byte11;
2123
- const overload = !!(byte12 & 0b10000000);
2124
- const currentPower = (((byte12 & 0b01111111) << 8) + byte13) / 10; // in watt
2125
- const data = {
2126
- model: SwitchBotBLEModel.PlugMiniJP,
2127
- modelName: SwitchBotBLEModelName.PlugMini,
2128
- modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini,
2129
- state: state ?? 'unknown',
2130
- delay,
2131
- timer,
2132
- syncUtcTime,
2133
- wifiRssi,
2134
- overload,
2135
- currentPower,
2136
- };
2137
- return data;
2138
- }
2139
- /**
2140
- * Reads the state of the plug.
2141
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2142
- */
2143
- async readState() {
2144
- return this.operatePlug([0x57, 0x0F, 0x51, 0x01]);
2145
- }
2146
- /**
2147
- * Sets the state of the plug.
2148
- * @private
2149
- * @param {number[]} reqByteArray - The request byte array.
2150
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2151
- */
2152
- async setState(reqByteArray) {
2153
- const base = [0x57, 0x0F, 0x50, 0x01];
2154
- return this.operatePlug([...base, ...reqByteArray]);
2155
- }
2156
- /**
2157
- * Turns on the plug.
2158
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2159
- */
2160
- async turnOn() {
2161
- return this.setState([0x01, 0x80]);
2162
- }
2163
- /**
2164
- * Turns off the plug.
2165
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2166
- */
2167
- async turnOff() {
2168
- return this.setState([0x01, 0x00]);
2169
- }
2170
- /**
2171
- * Toggles the state of the plug.
2172
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2173
- */
2174
- async toggle() {
2175
- return this.setState([0x02, 0x80]);
2176
- }
2177
- /**
2178
- * Operates the plug with the given bytes.
2179
- * @param {number[]} bytes - The byte array to send to the plug.
2180
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2181
- */
2182
- async operatePlug(bytes) {
2183
- const reqBuf = Buffer.from(bytes);
2184
- const resBytes = await this.command(reqBuf);
2185
- const resBuf = Buffer.from(resBytes);
2186
- if (resBuf.length !== 2) {
2187
- throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
2188
- }
2189
- const code = resBuf.readUInt8(1);
2190
- if (code === 0x00 || code === 0x80) {
2191
- return code === 0x80;
2192
- }
2193
- else {
2194
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
2195
- }
2196
- }
2197
- }
2198
- /**
2199
- * Class representing a WoPlugMini device.
2200
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/plugmini.md
2201
- */
2202
- export class WoPlugMiniUS extends SwitchbotDevice {
2203
- constructor(peripheral, noble) {
2204
- super(peripheral, noble);
2205
- }
2206
- /**
2207
- * Parses the service data for WoPlugMini US.
2208
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2209
- * @param {Function} emitLog - The function to emit log messages.
2210
- * @returns {Promise<plugMiniUSServiceData | null>} - Parsed service data or null if invalid.
2211
- */
2212
- static async parseServiceData(manufacturerData, emitLog) {
2213
- if (manufacturerData.length !== 14) {
2214
- emitLog('debugerror', `[parseServiceDataForWoPlugMini] Buffer length ${manufacturerData.length} should be 14`);
2215
- return null;
2216
- }
2217
- const [byte9, byte10, byte11, byte12, byte13] = [
2218
- manufacturerData.readUInt8(9),
2219
- manufacturerData.readUInt8(10),
2220
- manufacturerData.readUInt8(11),
2221
- manufacturerData.readUInt8(12),
2222
- manufacturerData.readUInt8(13),
2223
- ];
2224
- const state = byte9 === 0x00 ? 'off' : byte9 === 0x80 ? 'on' : null;
2225
- const delay = !!(byte10 & 0b00000001);
2226
- const timer = !!(byte10 & 0b00000010);
2227
- const syncUtcTime = !!(byte10 & 0b00000100);
2228
- const wifiRssi = byte11;
2229
- const overload = !!(byte12 & 0b10000000);
2230
- const currentPower = (((byte12 & 0b01111111) << 8) + byte13) / 10; // in watt
2231
- const data = {
2232
- model: SwitchBotBLEModel.PlugMiniUS,
2233
- modelName: SwitchBotBLEModelName.PlugMini,
2234
- modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini,
2235
- state: state ?? 'unknown',
2236
- delay,
2237
- timer,
2238
- syncUtcTime,
2239
- wifiRssi,
2240
- overload,
2241
- currentPower,
2242
- };
2243
- return data;
2244
- }
2245
- /**
2246
- * Reads the state of the plug.
2247
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2248
- */
2249
- async readState() {
2250
- return this.operatePlug([0x57, 0x0F, 0x51, 0x01]);
2251
- }
2252
- /**
2253
- * Sets the state of the plug.
2254
- * @private
2255
- * @param {number[]} reqByteArray - The request byte array.
2256
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2257
- */
2258
- async setState(reqByteArray) {
2259
- const base = [0x57, 0x0F, 0x50, 0x01];
2260
- return this.operatePlug([...base, ...reqByteArray]);
2261
- }
2262
- /**
2263
- * Turns on the plug.
2264
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2265
- */
2266
- async turnOn() {
2267
- return this.setState([0x01, 0x80]);
2268
- }
2269
- /**
2270
- * Turns off the plug.
2271
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2272
- */
2273
- async turnOff() {
2274
- return this.setState([0x01, 0x00]);
2275
- }
2276
- /**
2277
- * Toggles the state of the plug.
2278
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2279
- */
2280
- async toggle() {
2281
- return this.setState([0x02, 0x80]);
2282
- }
2283
- /**
2284
- * Operates the plug with the given bytes.
2285
- * @param {number[]} bytes - The byte array to send to the plug.
2286
- * @returns {Promise<boolean>} - Resolves with a boolean that tells whether the plug is ON (true) or OFF (false).
2287
- */
2288
- async operatePlug(bytes) {
2289
- const reqBuf = Buffer.from(bytes);
2290
- const resBytes = await this.command(reqBuf);
2291
- const resBuf = Buffer.from(resBytes);
2292
- if (resBuf.length !== 2) {
2293
- throw new Error(`Expecting a 2-byte response, got instead: 0x${resBuf.toString('hex')}`);
2294
- }
2295
- const code = resBuf.readUInt8(1);
2296
- if (code === 0x00 || code === 0x80) {
2297
- return code === 0x80;
2298
- }
2299
- else {
2300
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
2301
- }
2302
- }
2303
- }
2304
- const PRESENCE_SENSOR_BATTERY_RANGE_MAP = ['<10%', '10-19%', '20-59%', '>=60%'];
2305
- /**
2306
- * Class representing a WoPresence device.
2307
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
2308
- */
2309
- export class WoPresence extends SwitchbotDevice {
2310
- /**
2311
- * Parses the service data for WoPresence.
2312
- * @param {Buffer} serviceData - The service data buffer.
2313
- * @param {Function} emitLog - The function to emit log messages.
2314
- * @returns {Promise<motionSensorServiceData | null>} - Parsed service data or null if invalid.
2315
- */
2316
- static async parseServiceData(serviceData, emitLog) {
2317
- if (serviceData.length !== 6) {
2318
- emitLog('debugerror', `[parseServiceDataForWoPresence] Buffer length ${serviceData.length} !== 6!`);
2319
- return null;
2320
- }
2321
- const [byte1, byte2, , , , byte5] = serviceData;
2322
- const data = {
2323
- model: SwitchBotBLEModel.MotionSensor,
2324
- modelName: SwitchBotBLEModelName.MotionSensor,
2325
- modelFriendlyName: SwitchBotBLEModelFriendlyName.MotionSensor,
2326
- tested: !!(byte1 & 0b10000000),
2327
- movement: !!(byte1 & 0b01000000),
2328
- battery: byte2 & 0b01111111,
2329
- led: (byte5 & 0b00100000) >> 5,
2330
- iot: (byte5 & 0b00010000) >> 4,
2331
- sense_distance: (byte5 & 0b00001100) >> 2,
2332
- lightLevel: (byte5 & 0b00000011) === 1 ? 'dark' : (byte5 & 0b00000011) === 2 ? 'bright' : 'unknown',
2333
- is_light: !!(byte5 & 0b00000010),
2334
- };
2335
- return data;
2336
- }
2337
- /**
2338
- * Parses the manufacturer data for presence sensors.
2339
- * @param {Buffer | null} serviceData - The optional service data buffer.
2340
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2341
- * @param {Function} emitLog - The function to emit log messages.
2342
- * @returns {Promise<presenceSensorServiceData | null>} - Parsed service data or null if invalid.
2343
- */
2344
- static async parsePresenceSensorServiceData(serviceData, manufacturerData, emitLog) {
2345
- if (!manufacturerData || manufacturerData.length < 12) {
2346
- emitLog('debugerror', `[parsePresenceSensorServiceData] Manufacturer buffer length ${manufacturerData?.length ?? 0} < 12!`);
2347
- return null;
2348
- }
2349
- const statusByte = manufacturerData[7];
2350
- const batteryBits = (statusByte >> 2) & 0b11;
2351
- const data = {
2352
- model: SwitchBotBLEModel.PresenceSensor,
2353
- modelName: SwitchBotBLEModelName.PresenceSensor,
2354
- modelFriendlyName: SwitchBotBLEModelFriendlyName.PresenceSensor,
2355
- sequenceNumber: manufacturerData[6],
2356
- adaptiveState: !!(statusByte & 0b10000000),
2357
- motionDetected: !!(statusByte & 0b01000000),
2358
- batteryRange: PRESENCE_SENSOR_BATTERY_RANGE_MAP[batteryBits] ?? 'Unknown',
2359
- triggerFlag: manufacturerData[10],
2360
- ledState: !!(manufacturerData[11] & 0b10000000),
2361
- lightLevel: manufacturerData[11] & 0x0F,
2362
- };
2363
- if (serviceData && serviceData.length >= 3) {
2364
- data.battery = serviceData[2] & 0x7F;
2365
- }
2366
- return data;
2367
- }
2368
- constructor(peripheral, noble) {
2369
- super(peripheral, noble);
2370
- }
2371
- }
2372
- /**
2373
- * Class representing a WoRelaySwitch1 device.
2374
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/
2375
- */
2376
- export class WoRelaySwitch1 extends SwitchbotDevice {
2377
- constructor(peripheral, noble) {
2378
- super(peripheral, noble);
2379
- }
2380
- /**
2381
- * Parses the service data for WoRelaySwitch1.
2382
- * @param {Buffer} serviceData - The service data buffer.
2383
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2384
- * @param {Function} emitLog - The function to emit log messages.
2385
- * @returns {Promise<relaySwitch1ServiceData | null>} - Parsed service data or null if invalid.
2386
- */
2387
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2388
- if (serviceData.length < 8 || manufacturerData.length === null) {
2389
- emitLog('debugerror', `[parseServiceDataForWoRelaySwitch1Plus] Buffer length ${serviceData.length} < 8!`);
2390
- return null;
2391
- }
2392
- const data = {
2393
- model: SwitchBotBLEModel.RelaySwitch1,
2394
- modelName: SwitchBotBLEModelName.RelaySwitch1,
2395
- modelFriendlyName: SwitchBotBLEModelFriendlyName.RelaySwitch1,
2396
- mode: true, // for compatibility, useless
2397
- state: !!(manufacturerData[7] & 0b10000000),
2398
- sequence_number: manufacturerData[6],
2399
- };
2400
- return data;
2401
- }
2402
- /**
2403
- * Sends a command to the bot.
2404
- * @param {Buffer} reqBuf - The command buffer.
2405
- * @returns {Promise<void>}
2406
- */
2407
- async sendCommand(reqBuf) {
2408
- const resBuf = await this.command(reqBuf);
2409
- const code = resBuf.readUInt8(0);
2410
- if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
2411
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
2412
- }
2413
- }
2414
- /**
2415
- * Turns on the bot.
2416
- * @returns {Promise<void>}
2417
- */
2418
- async turnOn() {
2419
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x01]));
2420
- }
2421
- /**
2422
- * Turns off the bot.
2423
- * @returns {Promise<void>}
2424
- */
2425
- async turnOff() {
2426
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x02]));
2427
- }
2428
- }
2429
- /**
2430
- * Class representing a WoRelaySwitch1PM device.
2431
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/
2432
- */
2433
- export class WoRelaySwitch1PM extends SwitchbotDevice {
2434
- constructor(peripheral, noble) {
2435
- super(peripheral, noble);
2436
- }
2437
- /**
2438
- * Parses the service data for WoRelaySwitch1PM.
2439
- * @param {Buffer} serviceData - The service data buffer.
2440
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2441
- * @param {Function} emitLog - The function to emit log messages.
2442
- * @returns {Promise<relaySwitch1PMServiceData | null>} - Parsed service data or null if invalid.
2443
- */
2444
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2445
- if (serviceData.length < 8 || manufacturerData.length === 0) {
2446
- emitLog('debugerror', `[parseServiceDataForWoRelaySwitch1PM] Buffer length ${serviceData.length} < 8!`);
2447
- return null;
2448
- }
2449
- const data = {
2450
- model: SwitchBotBLEModel.RelaySwitch1PM,
2451
- modelName: SwitchBotBLEModelName.RelaySwitch1PM,
2452
- modelFriendlyName: SwitchBotBLEModelFriendlyName.RelaySwitch1PM,
2453
- mode: true, // for compatibility, useless
2454
- state: !!(manufacturerData[7] & 0b10000000),
2455
- sequence_number: manufacturerData[6],
2456
- power: ((manufacturerData[10] << 8) + manufacturerData[11]) / 10,
2457
- voltage: 0,
2458
- current: 0,
2459
- };
2460
- return data;
2461
- }
2462
- /**
2463
- * Sends a command to the bot.
2464
- * @param {Buffer} reqBuf - The command buffer.
2465
- * @returns {Promise<void>}
2466
- */
2467
- async sendCommand(reqBuf) {
2468
- const resBuf = await this.command(reqBuf);
2469
- const code = resBuf.readUInt8(0);
2470
- if (resBuf.length !== 3 || (code !== 0x01 && code !== 0x05)) {
2471
- throw new Error(`The device returned an error: 0x${resBuf.toString('hex')}`);
2472
- }
2473
- }
2474
- /**
2475
- * Turns on the bot.
2476
- * @returns {Promise<void>}
2477
- */
2478
- async turnOn() {
2479
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x01]));
2480
- }
2481
- /**
2482
- * Turns off the bot.
2483
- * @returns {Promise<void>}
2484
- */
2485
- async turnOff() {
2486
- await this.sendCommand(Buffer.from([0x57, 0x01, 0x02]));
2487
- }
2488
- }
2489
- /**
2490
- * Class representing a WoRemote device.
2491
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/
2492
- */
2493
- export class WoRemote extends SwitchbotDevice {
2494
- /**
2495
- * Parses the service data for WoRemote.
2496
- * @param {Buffer} serviceData - The service data buffer.
2497
- * @param {Function} emitLog - The function to emit log messages.
2498
- * @returns {Promise<remoteServiceData | null>} - Parsed service data or null if invalid.
2499
- */
2500
- static async parseServiceData(serviceData, emitLog) {
2501
- if (serviceData.length !== 9) {
2502
- emitLog('debugerror', `[parseServiceDataForWoRemote] Buffer length ${serviceData.length} !== 9!`);
2503
- return null;
2504
- }
2505
- const [byte2] = serviceData;
2506
- const battery = byte2 & 0b01111111;
2507
- const data = {
2508
- model: SwitchBotBLEModel.Remote,
2509
- modelName: SwitchBotBLEModelName.Remote,
2510
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Remote,
2511
- battery,
2512
- };
2513
- return data;
2514
- }
2515
- constructor(peripheral, noble) {
2516
- super(peripheral, noble);
2517
- }
2518
- }
2519
- /**
2520
- * Class representing a WoSensorTH device.
2521
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
2522
- */
2523
- export class WoSensorTH extends SwitchbotDevice {
2524
- constructor(peripheral, noble) {
2525
- super(peripheral, noble);
2526
- }
2527
- static async parseServiceData(serviceData, emitLog) {
2528
- if (serviceData.length !== 6) {
2529
- emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 6!`);
2530
- return null;
2531
- }
2532
- const [byte2, byte3, byte4, byte5] = [
2533
- serviceData.readUInt8(2),
2534
- serviceData.readUInt8(3),
2535
- serviceData.readUInt8(4),
2536
- serviceData.readUInt8(5),
2537
- ];
2538
- const tempSign = byte4 & 0b10000000 ? 1 : -1;
2539
- const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
2540
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
2541
- const data = {
2542
- model: SwitchBotBLEModel.Meter,
2543
- modelName: SwitchBotBLEModelName.Meter,
2544
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Meter,
2545
- celsius: tempC,
2546
- fahrenheit: tempF,
2547
- fahrenheit_mode: !!(byte5 & 0b10000000),
2548
- humidity: byte5 & 0b01111111,
2549
- battery: byte2 & 0b01111111,
2550
- };
2551
- return data;
2552
- }
2553
- }
2554
- /**
2555
- * Class representing a WoSensorTH device.
2556
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
2557
- */
2558
- export class WoSensorTHPlus extends SwitchbotDevice {
2559
- constructor(peripheral, noble) {
2560
- super(peripheral, noble);
2561
- }
2562
- static async parseServiceData(serviceData, emitLog) {
2563
- if (serviceData.length !== 6) {
2564
- emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 6 or 7!`);
2565
- return null;
2566
- }
2567
- const [byte2, byte3, byte4, byte5] = [
2568
- serviceData.readUInt8(2),
2569
- serviceData.readUInt8(3),
2570
- serviceData.readUInt8(4),
2571
- serviceData.readUInt8(5),
2572
- ];
2573
- const tempSign = byte4 & 0b10000000 ? 1 : -1;
2574
- const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
2575
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
2576
- const data = {
2577
- model: SwitchBotBLEModel.MeterPlus,
2578
- modelName: SwitchBotBLEModelName.MeterPlus,
2579
- modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPlus,
2580
- celsius: tempC,
2581
- fahrenheit: tempF,
2582
- fahrenheit_mode: !!(byte5 & 0b10000000),
2583
- humidity: byte5 & 0b01111111,
2584
- battery: byte2 & 0b01111111,
2585
- };
2586
- return data;
2587
- }
2588
- }
2589
- /**
2590
- * Class representing a WoSensorTH device.
2591
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
2592
- */
2593
- export class WoSensorTHPro extends SwitchbotDevice {
2594
- constructor(peripheral, noble) {
2595
- super(peripheral, noble);
2596
- }
2597
- static async parseServiceData(serviceData, emitLog) {
2598
- if (serviceData.length !== 6) {
2599
- emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 6 or 7!`);
2600
- return null;
2601
- }
2602
- const [byte2, byte3, byte4, byte5] = [
2603
- serviceData.readUInt8(2),
2604
- serviceData.readUInt8(3),
2605
- serviceData.readUInt8(4),
2606
- serviceData.readUInt8(5),
2607
- ];
2608
- const tempSign = byte4 & 0b10000000 ? 1 : -1;
2609
- const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
2610
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
2611
- const data = {
2612
- model: SwitchBotBLEModel.MeterPro,
2613
- modelName: SwitchBotBLEModelName.MeterPro,
2614
- modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPro,
2615
- celsius: tempC,
2616
- fahrenheit: tempF,
2617
- fahrenheit_mode: !!(byte5 & 0b10000000),
2618
- humidity: byte5 & 0b01111111,
2619
- battery: byte2 & 0b01111111,
2620
- };
2621
- return data;
2622
- }
2623
- }
2624
- /**
2625
- * Class representing a WoSensorTH device.
2626
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/meter.md
2627
- */
2628
- export class WoSensorTHProCO2 extends SwitchbotDevice {
2629
- constructor(peripheral, noble) {
2630
- super(peripheral, noble);
2631
- }
2632
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2633
- if (serviceData.length !== 7 && serviceData.length !== 3) {
2634
- emitLog('debugerror', `[parseServiceData] Buffer length ${serviceData.length} !== 3 or 7!`);
2635
- return null;
2636
- }
2637
- if (serviceData.length === 7) {
2638
- const [byte2, byte3, byte4, byte5, byte6] = [
2639
- serviceData.readUInt8(2),
2640
- serviceData.readUInt8(3),
2641
- serviceData.readUInt8(4),
2642
- serviceData.readUInt8(5),
2643
- manufacturerData.readUInt16BE(6),
2644
- ];
2645
- const tempSign = byte4 & 0b10000000 ? 1 : -1;
2646
- const tempC = tempSign * ((byte4 & 0b01111111) + (byte3 & 0b00001111) / 10);
2647
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
2648
- return {
2649
- model: SwitchBotBLEModel.MeterProCO2,
2650
- modelName: SwitchBotBLEModelName.MeterProCO2,
2651
- modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterProCO2,
2652
- celsius: tempC,
2653
- fahrenheit: tempF,
2654
- fahrenheit_mode: !!(byte5 & 0b10000000),
2655
- humidity: byte5 & 0b01111111,
2656
- battery: byte2 & 0b01111111,
2657
- co2: byte6,
2658
- };
2659
- }
2660
- else {
2661
- const [mdByte10, mdByte11, mdByte12] = [
2662
- manufacturerData.readUInt8(10),
2663
- manufacturerData.readUInt8(11),
2664
- manufacturerData.readUInt8(12),
2665
- ];
2666
- const sdByte2 = serviceData.readUInt8(2);
2667
- const tempSign = mdByte11 & 0b10000000 ? 1 : -1;
2668
- const tempC = tempSign * ((mdByte11 & 0b01111111) + (mdByte10 & 0b00001111) / 10);
2669
- const tempF = Math.round(((tempC * 9) / 5 + 32) * 10) / 10;
2670
- return {
2671
- model: SwitchBotBLEModel.MeterProCO2,
2672
- modelName: SwitchBotBLEModelName.MeterProCO2,
2673
- modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterProCO2,
2674
- celsius: tempC,
2675
- fahrenheit: tempF,
2676
- fahrenheit_mode: !!(mdByte12 & 0b10000000),
2677
- humidity: mdByte12 & 0b01111111,
2678
- battery: sdByte2 & 0b01111111,
2679
- co2: manufacturerData.readUInt16BE(15),
2680
- };
2681
- }
2682
- }
2683
- }
2684
- /**
2685
- * Class representing a WoSmartLock device.
2686
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/lock.md
2687
- */
2688
- export class WoSmartLock extends SwitchbotDevice {
2689
- iv = null;
2690
- key_id = '';
2691
- encryption_key = null;
2692
- static Result = {
2693
- ERROR: 0x00,
2694
- SUCCESS: 0x01,
2695
- SUCCESS_LOW_BATTERY: 0x06,
2696
- };
2697
- static async validateResponse(res) {
2698
- if (res.length >= 3) {
2699
- const result = res.readUInt8(0);
2700
- if (result === WoSmartLock.Result.SUCCESS || result === WoSmartLock.Result.SUCCESS_LOW_BATTERY) {
2701
- return result;
2702
- }
2703
- }
2704
- return WoSmartLock.Result.ERROR;
2705
- }
2706
- static getLockStatus(code) {
2707
- const statusMap = {
2708
- 0b0000000: 'LOCKED',
2709
- 0b0010000: 'UNLOCKED',
2710
- 0b0100000: 'LOCKING',
2711
- 0b0110000: 'UNLOCKING',
2712
- 0b1000000: 'LOCKING_STOP',
2713
- 0b1010000: 'UNLOCKING_STOP',
2714
- 0b1100000: 'NOT_FULLY_LOCKED', // Only EU lock type
2715
- };
2716
- return statusMap[code] || 'UNKNOWN';
2717
- }
2718
- /**
2719
- * Parses the service data from the SwitchBot Strip Light.
2720
- * @param {Buffer} serviceData - The service data buffer.
2721
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2722
- * @param {Function} emitLog - The function to emit log messages.
2723
- * @returns {Promise<lockServiceData | null>} - Parsed service data or null if invalid.
2724
- */
2725
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2726
- if (manufacturerData.length < 11) {
2727
- emitLog('debugerror', `[parseServiceDataForWoSmartLock] Buffer length ${manufacturerData.length} is too short!`);
2728
- return null;
2729
- }
2730
- const byte2 = serviceData.readUInt8(2);
2731
- const byte15 = manufacturerData.readUInt8(9);
2732
- const byte16 = manufacturerData.readUInt8(10);
2733
- const data = {
2734
- model: SwitchBotBLEModel.Lock,
2735
- modelName: SwitchBotBLEModelName.Lock,
2736
- modelFriendlyName: SwitchBotBLEModelFriendlyName.Lock,
2737
- battery: byte2 & 0b01111111,
2738
- calibration: !!(byte15 & 0b10000000),
2739
- status: WoSmartLock.getLockStatus(byte15 & 0b01110000),
2740
- update_from_secondary_lock: !!(byte15 & 0b00001000),
2741
- door_open: !!(byte15 & 0b00000100),
2742
- double_lock_mode: !!(byte16 & 0b10000000),
2743
- unclosed_alarm: !!(byte16 & 0b00100000),
2744
- unlocked_alarm: !!(byte16 & 0b00010000),
2745
- auto_lock_paused: !!(byte16 & 0b00000010),
2746
- night_latch: !!(manufacturerData.length > 11 && manufacturerData.readUInt8(11) & 0b00000001),
2747
- };
2748
- return data;
2749
- }
2750
- constructor(peripheral, noble) {
2751
- super(peripheral, noble);
2752
- }
2753
- /**
2754
- * Initializes the encryption key info for valid lock communication.
2755
- * @param {string} keyId - The key ID.
2756
- * @param {string} encryptionKey - The encryption key.
2757
- */
2758
- async setKey(keyId, encryptionKey) {
2759
- this.iv = null;
2760
- this.key_id = keyId;
2761
- this.encryption_key = Buffer.from(encryptionKey, 'hex');
2762
- }
2763
- /**
2764
- * Unlocks the Smart Lock.
2765
- * @returns {Promise<number>} - The result of the unlock operation.
2766
- */
2767
- async unlock() {
2768
- const resBuf = await this.operateLock(WoSmartLockCommands.UNLOCK);
2769
- return resBuf ? WoSmartLock.validateResponse(resBuf) : WoSmartLock.Result.ERROR;
2770
- }
2771
- /**
2772
- * Unlocks the Smart Lock without unlatching the door.
2773
- * @returns {Promise<number>} - The result of the unlock operation.
2774
- */
2775
- async unlockNoUnlatch() {
2776
- const resBuf = await this.operateLock(WoSmartLockCommands.UNLOCK_NO_UNLATCH);
2777
- return resBuf ? WoSmartLock.validateResponse(resBuf) : WoSmartLock.Result.ERROR;
2778
- }
2779
- /**
2780
- * Locks the Smart Lock.
2781
- * @returns {Promise<number>} - The result of the lock operation.
2782
- */
2783
- async lock() {
2784
- const resBuf = await this.operateLock(WoSmartLockCommands.LOCK);
2785
- return resBuf ? WoSmartLock.validateResponse(resBuf) : WoSmartLock.Result.ERROR;
2786
- }
2787
- /**
2788
- * Gets general state info from the Smart Lock.
2789
- * @returns {Promise<object | null>} - The state object or null if an error occurred.
2790
- */
2791
- async info() {
2792
- const resBuf = await this.operateLock(WoSmartLockCommands.LOCK_INFO);
2793
- if (resBuf) {
2794
- return {
2795
- calibration: Boolean(resBuf[1] & 0b10000000),
2796
- status: WoSmartLock.getLockStatus((resBuf[1] & 0b01110000)),
2797
- door_open: Boolean(resBuf[1] & 0b00000100),
2798
- unclosed_alarm: Boolean(resBuf[2] & 0b00100000),
2799
- unlocked_alarm: Boolean(resBuf[2] & 0b00010000),
2800
- };
2801
- }
2802
- return null;
2803
- }
2804
- /**
2805
- * Encrypts a string using AES-128-CTR.
2806
- * @param {string} str - The string to encrypt.
2807
- * @returns {Promise<string>} - The encrypted string in hex format.
2808
- */
2809
- async encrypt(str) {
2810
- const cipher = Crypto.createCipheriv('aes-128-ctr', this.encryption_key, this.iv);
2811
- return Buffer.concat([cipher.update(str, 'hex'), cipher.final()]).toString('hex');
2812
- }
2813
- /**
2814
- * Decrypts a buffer using AES-128-CTR.
2815
- * @param {Buffer} data - The data to decrypt.
2816
- * @returns {Promise<Buffer>} - The decrypted data.
2817
- */
2818
- async decrypt(data) {
2819
- const decipher = Crypto.createDecipheriv('aes-128-ctr', this.encryption_key, this.iv);
2820
- return Buffer.concat([decipher.update(data), decipher.final()]);
2821
- }
2822
- /**
2823
- * Retrieves the IV from the device.
2824
- * @returns {Promise<Buffer>} - The IV buffer.
2825
- */
2826
- async getIv() {
2827
- if (!this.iv) {
2828
- const res = await this.operateLock(WoSmartLockCommands.GET_CKIV + this.key_id, false);
2829
- if (res) {
2830
- this.iv = res.subarray(4);
2831
- }
2832
- else {
2833
- throw new Error('Failed to retrieve IV from the device.');
2834
- }
2835
- }
2836
- return this.iv;
2837
- }
2838
- /**
2839
- * Sends an encrypted command to the device.
2840
- * @param {string} key - The command key.
2841
- * @returns {Promise<Buffer>} - The response buffer.
2842
- */
2843
- async encryptedCommand(key) {
2844
- const iv = await this.getIv();
2845
- 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');
2846
- const bytes = await this.command(req);
2847
- const buf = Buffer.from(bytes);
2848
- const code = WoSmartLock.validateResponse(buf);
2849
- if (await code !== WoSmartLock.Result.ERROR) {
2850
- return Buffer.concat([buf.subarray(0, 1), await this.decrypt(buf.subarray(4))]);
2851
- }
2852
- else {
2853
- throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
2854
- }
2855
- }
2856
- /**
2857
- * Operates the lock with the given command.
2858
- * @param {string} key - The command key.
2859
- * @param {boolean} [encrypt] - Whether to encrypt the command.
2860
- * @returns {Promise<Buffer>} - The response buffer.
2861
- */
2862
- async operateLock(key, encrypt = true) {
2863
- if (encrypt) {
2864
- return this.encryptedCommand(key);
2865
- }
2866
- const req = Buffer.from(`${key.substring(0, 2)}000000${key.substring(2)}`, 'hex');
2867
- const bytes = await this.command(req);
2868
- const buf = Buffer.from(bytes);
2869
- const code = WoSmartLock.validateResponse(buf);
2870
- if (await code === WoSmartLock.Result.ERROR) {
2871
- throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
2872
- }
2873
- return buf;
2874
- }
2875
- }
2876
- /**
2877
- * Class representing a WoSmartLockPro device.
2878
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/lock.md
2879
- */
2880
- export class WoSmartLockPro extends SwitchbotDevice {
2881
- iv = null;
2882
- key_id = '';
2883
- encryption_key = null;
2884
- static Result = {
2885
- ERROR: 0x00,
2886
- SUCCESS: 0x01,
2887
- SUCCESS_LOW_BATTERY: 0x06,
2888
- };
2889
- static async validateResponse(res) {
2890
- if (res.length >= 3) {
2891
- const result = res.readUInt8(0);
2892
- if (result === WoSmartLockPro.Result.SUCCESS || result === WoSmartLockPro.Result.SUCCESS_LOW_BATTERY) {
2893
- return result;
2894
- }
2895
- }
2896
- return WoSmartLockPro.Result.ERROR;
2897
- }
2898
- static getLockStatus(code) {
2899
- const statusMap = {
2900
- 0b0000000: 'LOCKED',
2901
- 0b0010000: 'UNLOCKED',
2902
- 0b0100000: 'LOCKING',
2903
- 0b0110000: 'UNLOCKING',
2904
- 0b1000000: 'LOCKING_STOP',
2905
- 0b1010000: 'UNLOCKING_STOP',
2906
- 0b01100000: 'NOT_FULLY_LOCKED', // Only EU lock type
2907
- };
2908
- return statusMap[code] || 'UNKNOWN';
2909
- }
2910
- /**
2911
- * Parses the service data from the SwitchBot Strip Light.
2912
- * @param {Buffer} serviceData - The service data buffer.
2913
- * @param {Buffer} manufacturerData - The manufacturer data buffer.
2914
- * @param {Function} emitLog - The function to emit log messages.
2915
- * @returns {Promise<lockProServiceData | null>} - Parsed service data or null if invalid.
2916
- */
2917
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
2918
- if (manufacturerData.length < 11) {
2919
- emitLog('debugerror', `[parseServiceDataForWoSmartLockPro] Buffer length ${manufacturerData.length} is too short!`);
2920
- return null;
2921
- }
2922
- const byte2 = serviceData.readUInt8(2);
2923
- const byte7 = manufacturerData.readUInt8(7);
2924
- const byte8 = manufacturerData.readUInt8(8);
2925
- const byte9 = manufacturerData.readUInt8(9);
2926
- const byte11 = manufacturerData.readUInt8(11);
2927
- const data = {
2928
- model: SwitchBotBLEModel.LockPro,
2929
- modelName: SwitchBotBLEModelName.LockPro,
2930
- modelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro,
2931
- battery: byte2 & 0b01111111,
2932
- calibration: !!(byte7 & 0b10000000),
2933
- status: WoSmartLockPro.getLockStatus((byte7 & 0b00111000) >> 3),
2934
- door_open: !!(byte8 & 0b01100000),
2935
- update_from_secondary_lock: false,
2936
- double_lock_mode: false,
2937
- unclosed_alarm: !!(byte11 & 0b10000000),
2938
- unlocked_alarm: !!(byte11 & 0b01000000),
2939
- auto_lock_paused: !!(byte8 & 0b100000),
2940
- night_latch: !!(byte9 & 0b00000001),
2941
- };
2942
- return data;
2943
- }
2944
- constructor(peripheral, noble) {
2945
- super(peripheral, noble);
2946
- }
2947
- /**
2948
- * Initializes the encryption key info for valid lock communication.
2949
- * @param {string} keyId - The key ID.
2950
- * @param {string} encryptionKey - The encryption key.
2951
- */
2952
- async setKey(keyId, encryptionKey) {
2953
- this.iv = null;
2954
- this.key_id = keyId;
2955
- this.encryption_key = Buffer.from(encryptionKey, 'hex');
2956
- }
2957
- /**
2958
- * Unlocks the Smart Lock.
2959
- * @returns {Promise<number>} - The result of the unlock operation.
2960
- */
2961
- async unlock() {
2962
- const resBuf = await this.operateLockPro(WoSmartLockProCommands.UNLOCK);
2963
- return resBuf ? WoSmartLockPro.validateResponse(resBuf) : WoSmartLockPro.Result.ERROR;
2964
- }
2965
- /**
2966
- * Unlocks the Smart Lock without unlatching the door.
2967
- * @returns {Promise<number>} - The result of the unlock operation.
2968
- */
2969
- async unlockNoUnlatch() {
2970
- const resBuf = await this.operateLockPro(WoSmartLockProCommands.UNLOCK_NO_UNLATCH);
2971
- return resBuf ? WoSmartLockPro.validateResponse(resBuf) : WoSmartLockPro.Result.ERROR;
2972
- }
2973
- /**
2974
- * Locks the Smart Lock.
2975
- * @returns {Promise<number>} - The result of the lock operation.
2976
- */
2977
- async lock() {
2978
- const resBuf = await this.operateLockPro(WoSmartLockProCommands.LOCK);
2979
- return resBuf ? WoSmartLockPro.validateResponse(resBuf) : WoSmartLockPro.Result.ERROR;
2980
- }
2981
- /**
2982
- * Gets general state info from the Smart Lock.
2983
- * @returns {Promise<object | null>} - The state object or null if an error occurred.
2984
- */
2985
- async info() {
2986
- const resBuf = await this.operateLockPro(WoSmartLockProCommands.LOCK_INFO);
2987
- if (resBuf) {
2988
- return {
2989
- calibration: Boolean(resBuf[0] & 0b10000000),
2990
- status: WoSmartLockPro.getLockStatus((resBuf[0] & 0b01110000) >> 4),
2991
- door_open: Boolean(resBuf[0] & 0b00000100),
2992
- unclosed_alarm: Boolean(resBuf[1] & 0b00100000),
2993
- unlocked_alarm: Boolean(resBuf[1] & 0b00010000),
2994
- };
2995
- }
2996
- return null;
2997
- }
2998
- /**
2999
- * Encrypts a string using AES-128-CTR.
3000
- * @param {string} str - The string to encrypt.
3001
- * @returns {Promise<string>} - The encrypted string in hex format.
3002
- */
3003
- async encrypt(str) {
3004
- const cipher = Crypto.createCipheriv('aes-128-ctr', this.encryption_key, this.iv);
3005
- return Buffer.concat([cipher.update(str, 'hex'), cipher.final()]).toString('hex');
3006
- }
3007
- /**
3008
- * Decrypts a buffer using AES-128-CTR.
3009
- * @param {Buffer} data - The data to decrypt.
3010
- * @returns {Promise<Buffer>} - The decrypted data.
3011
- */
3012
- async decrypt(data) {
3013
- const decipher = Crypto.createDecipheriv('aes-128-ctr', this.encryption_key, this.iv);
3014
- return Buffer.concat([decipher.update(data), decipher.final()]);
3015
- }
3016
- /**
3017
- * Retrieves the IV from the device.
3018
- * @returns {Promise<Buffer>} - The IV buffer.
3019
- */
3020
- async getIv() {
3021
- if (!this.iv) {
3022
- const res = await this.operateLockPro(WoSmartLockProCommands.GET_CKIV + this.key_id, false);
3023
- if (res) {
3024
- this.iv = res.subarray(4);
3025
- }
3026
- else {
3027
- throw new Error('Failed to retrieve IV from the device.');
3028
- }
3029
- }
3030
- return this.iv;
3031
- }
3032
- /**
3033
- * Sends an encrypted command to the device.
3034
- * @param {string} key - The command key.
3035
- * @returns {Promise<Buffer>} - The response buffer.
3036
- */
3037
- async encryptedCommand(key) {
3038
- const iv = await this.getIv();
3039
- 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');
3040
- const bytes = await this.command(req);
3041
- const buf = Buffer.from(bytes);
3042
- const code = WoSmartLockPro.validateResponse(buf);
3043
- if (await code !== WoSmartLockPro.Result.ERROR) {
3044
- return Buffer.concat([buf.subarray(0, 1), await this.decrypt(buf.subarray(4))]);
3045
- }
3046
- else {
3047
- throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
3048
- }
3049
- }
3050
- /**
3051
- * Operates the lock with the given command.
3052
- * @param {string} key - The command key.
3053
- * @param {boolean} [encrypt] - Whether to encrypt the command.
3054
- * @returns {Promise<Buffer>} - The response buffer.
3055
- */
3056
- async operateLockPro(key, encrypt = true) {
3057
- if (encrypt) {
3058
- return this.encryptedCommand(key);
3059
- }
3060
- const req = Buffer.from(`${key.substring(0, 2)}000000${key.substring(2)}`, 'hex');
3061
- const bytes = await this.command(req);
3062
- const buf = Buffer.from(bytes);
3063
- const code = WoSmartLockPro.validateResponse(buf);
3064
- if (await code === WoSmartLockPro.Result.ERROR) {
3065
- throw new Error(`The device returned an error: 0x${buf.toString('hex')}`);
3066
- }
3067
- return buf;
3068
- }
3069
- }
3070
- /**
3071
- * Class representing a WoSmartLockUltra device.
3072
- * Reuses the LockPro parsing and encrypted command behavior but reports a distinct model.
3073
- */
3074
- export class WoSmartLockUltra extends WoSmartLockPro {
3075
- constructor(peripheral, noble) {
3076
- super(peripheral, noble);
3077
- }
3078
- static async parseServiceData(serviceData, manufacturerData, emitLog) {
3079
- const data = await WoSmartLockPro.parseServiceData(serviceData, manufacturerData, emitLog);
3080
- if (!data)
3081
- return null;
3082
- const out = data;
3083
- out.model = SwitchBotBLEModel.LockUltra;
3084
- out.modelName = SwitchBotBLEModelName.LockUltra;
3085
- out.modelFriendlyName = SwitchBotBLEModelFriendlyName.LockUltra;
3086
- return out;
3087
- }
3088
- }
3089
- /**
3090
- * Class representing a WoStrip device.
3091
- * @see https://github.com/OpenWonderLabs/SwitchBotAPI-BLE/blob/latest/devicetypes/ledstriplight.md
3092
- */
3093
- export class WoStrip extends SwitchbotDevice {
3094
- /**
3095
- * Parses the service data from the SwitchBot Strip Light.
3096
- * @param {Buffer} serviceData - The service data buffer.
3097
- * @param {Function} emitLog - The function to emit log messages.
3098
- * @returns {Promise<stripLightServiceData | null>} - Parsed service data or null if invalid.
3099
- */
3100
- static async parseServiceData(serviceData, emitLog) {
3101
- if (serviceData.length !== 18) {
3102
- emitLog('debugerror', `[parseServiceDataForWoStrip] Buffer length ${serviceData.length} !== 18!`);
3103
- return null;
3104
- }
3105
- const [byte3, byte4, byte5, byte7, byte8, byte9, byte10] = [
3106
- serviceData.readUInt8(3),
3107
- serviceData.readUInt8(4),
3108
- serviceData.readUInt8(5),
3109
- serviceData.readUInt8(7),
3110
- serviceData.readUInt8(8),
3111
- serviceData.readUInt8(9),
3112
- serviceData.readUInt8(10),
3113
- ];
3114
- const data = {
3115
- model: SwitchBotBLEModel.StripLight,
3116
- modelName: SwitchBotBLEModelName.StripLight,
3117
- modelFriendlyName: SwitchBotBLEModelFriendlyName.StripLight,
3118
- power: !!(byte7 & 0b10000000),
3119
- state: !!(byte7 & 0b10000000),
3120
- brightness: byte7 & 0b01111111,
3121
- red: byte3,
3122
- green: byte4,
3123
- blue: byte5,
3124
- color_temperature: 0, // Add a default value or extract from serviceData if available
3125
- delay: byte8 & 0b10000000,
3126
- preset: byte8 & 0b00001000,
3127
- color_mode: byte8 & 0b00000111,
3128
- speed: byte9 & 0b01111111,
3129
- loop_index: byte10 & 0b11111110,
3130
- };
3131
- return data;
3132
- }
3133
- constructor(peripheral, noble) {
3134
- super(peripheral, noble);
3135
- }
3136
- /**
3137
- * Reads the state of the strip light.
3138
- * @returns {Promise<boolean>} - Resolves with true if the strip light is ON, false otherwise.
3139
- */
3140
- async readState() {
3141
- return this.operateStripLight([0x57, 0x0F, 0x4A, 0x01]);
3142
- }
3143
- /**
3144
- * Sets the state of the strip light.
3145
- * @public
3146
- * @param {number[]} reqByteArray - The request byte array.
3147
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3148
- */
3149
- async setState(reqByteArray) {
3150
- const base = [0x57, 0x0F, 0x49, 0x01];
3151
- return this.operateStripLight([...base, ...reqByteArray]);
3152
- }
3153
- /**
3154
- * Turns the strip light on.
3155
- * @returns {Promise<boolean>} - Resolves with true if the strip light is ON.
3156
- */
3157
- async turnOn() {
3158
- return this.setState([0x01, 0x01]);
3159
- }
3160
- /**
3161
- * Turns the strip light off.
3162
- * @returns {Promise<boolean>} - Resolves with true if the strip light is OFF.
3163
- */
3164
- async turnOff() {
3165
- return this.setState([0x01, 0x02]);
3166
- }
3167
- /**
3168
- * Sets the brightness of the strip light.
3169
- * @param {number} brightness - The brightness percentage (0-100).
3170
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3171
- */
3172
- async setBrightness(brightness) {
3173
- if (typeof brightness !== 'number' || brightness < 0 || brightness > 100) {
3174
- throw new TypeError(`Invalid brightness value: ${brightness}`);
3175
- }
3176
- return this.setState([0x02, 0x14, brightness]);
3177
- }
3178
- /**
3179
- * Sets the RGB values of the strip light.
3180
- * @param {number} brightness - The brightness percentage (0-100).
3181
- * @param {number} red - The red value (0-255).
3182
- * @param {number} green - The green value (0-255).
3183
- * @param {number} blue - The blue value (0-255).
3184
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3185
- */
3186
- async setRGB(brightness, red, green, blue) {
3187
- if (![brightness, red, green, blue].every(val => typeof val === 'number')) {
3188
- throw new TypeError('Invalid RGB or brightness value');
3189
- }
3190
- brightness = Math.max(0, Math.min(100, brightness));
3191
- red = Math.max(0, Math.min(255, red));
3192
- green = Math.max(0, Math.min(255, green));
3193
- blue = Math.max(0, Math.min(255, blue));
3194
- return this.setState([0x02, 0x12, brightness, red, green, blue]);
3195
- }
3196
- /**
3197
- * Operates the strip light with the given byte array.
3198
- * @public
3199
- * @param {number[]} bytes - The byte array to send.
3200
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3201
- */
3202
- async operateStripLight(bytes) {
3203
- const req_buf = Buffer.from(bytes);
3204
- const res_buf = await this.command(req_buf);
3205
- if (res_buf.length !== 2) {
3206
- throw new Error(`Expecting a 2-byte response, got instead: 0x${res_buf.toString('hex')}`);
3207
- }
3208
- const code = res_buf.readUInt8(1);
3209
- if (code === 0x00 || code === 0x80) {
3210
- return code === 0x80;
3211
- }
3212
- else {
3213
- throw new Error(`The device returned an error: 0x${res_buf.toString('hex')}`);
3214
- }
3215
- }
3216
- }
3217
- /**
3218
- * Class representing a SwitchBot Air Purifier device.
3219
- * @extends SwitchbotDevice
3220
- */
3221
- export class WoAirPurifier extends SwitchbotDevice {
3222
- /**
3223
- * Parses service data for air purifier devices.
3224
- * @param {Buffer | null} serviceData - The service data buffer.
3225
- * @param {Buffer | null} manufacturerData - The manufacturer data buffer.
3226
- * @param {Function} emitLog - The function to emit log messages.
3227
- * @returns {airPurifierServiceData | null} - The parsed service data or null.
3228
- */
3229
- static parseServiceData(serviceData, manufacturerData, emitLog) {
3230
- if (!manufacturerData || manufacturerData.length < 14) {
3231
- return null;
3232
- }
3233
- const deviceData = manufacturerData.subarray(6);
3234
- if (deviceData.length < 8) {
3235
- return null;
3236
- }
3237
- const sequenceNumber = deviceData[0];
3238
- const isOn = Boolean(deviceData[1] & 0b10000000);
3239
- const mode = deviceData[1] & 0b00000111;
3240
- const isAqiValid = Boolean(deviceData[2] & 0b00000100);
3241
- const childLock = Boolean(deviceData[2] & 0b00000010);
3242
- const speed = deviceData[3] & 0b01111111;
3243
- const aqiLevelRaw = (deviceData[4] & 0b00000110) >> 1;
3244
- const workTime = (deviceData[5] << 8) | deviceData[6];
3245
- const errCode = deviceData[7];
3246
- // Map AQI level to string using the defined constant
3247
- const aqiLevelValues = [
3248
- AIR_QUALITY_LEVELS.EXCELLENT,
3249
- AIR_QUALITY_LEVELS.GOOD,
3250
- AIR_QUALITY_LEVELS.FAIR,
3251
- AIR_QUALITY_LEVELS.POOR,
3252
- ];
3253
- const aqiLevel = aqiLevelValues[aqiLevelRaw] || 'unknown';
3254
- // Determine mode based on mode value and speed
3255
- let modeString = null;
3256
- if (mode === 1) {
3257
- if (speed >= 0 && speed <= 33) {
3258
- modeString = AIR_PURIFIER_MODES.LEVEL_1;
3259
- }
3260
- else if (speed >= 34 && speed <= 66) {
3261
- modeString = AIR_PURIFIER_MODES.LEVEL_2;
3262
- }
3263
- else {
3264
- modeString = AIR_PURIFIER_MODES.LEVEL_3;
3265
- }
3266
- }
3267
- else if (mode > 1 && mode <= 4) {
3268
- const modeMap = [null, null, 'auto', 'sleep', 'manual'];
3269
- modeString = modeMap[mode + 2] || null;
3270
- }
3271
- if (emitLog) {
3272
- emitLog('debug', `Air Purifier Service Data: isOn=${isOn}, mode=${modeString}, speed=${speed}, AQI=${aqiLevel}`);
3273
- }
3274
- return {
3275
- model: SwitchBotBLEModel.AirPurifier,
3276
- modelName: SwitchBotBLEModelName.AirPurifier,
3277
- modelFriendlyName: SwitchBotBLEModelFriendlyName.AirPurifier,
3278
- isOn,
3279
- mode: modeString,
3280
- isAqiValid,
3281
- child_lock: childLock,
3282
- speed,
3283
- aqi_level: aqiLevel,
3284
- filter_element_working_time: workTime,
3285
- err_code: errCode,
3286
- sequence_number: sequenceNumber,
3287
- };
3288
- }
3289
- /**
3290
- * Sets the state of the air purifier.
3291
- * @param {number[]} reqByteArray - The request byte array.
3292
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
3293
- * @private
3294
- */
3295
- async setState(reqByteArray) {
3296
- return this.operateAirPurifier(reqByteArray);
3297
- }
3298
- /**
3299
- * Turns the air purifier on.
3300
- * @returns {Promise<boolean>} - Resolves with true if the air purifier is turned on.
3301
- */
3302
- async turnOn() {
3303
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_ON]);
3304
- }
3305
- /**
3306
- * Turns the air purifier off.
3307
- * @returns {Promise<boolean>} - Resolves with true if the air purifier is turned off.
3308
- */
3309
- async turnOff() {
3310
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_OFF]);
3311
- }
3312
- /**
3313
- * Sets the speed of the air purifier.
3314
- * @param {number} speed - The speed value (0-100).
3315
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3316
- */
3317
- async setSpeed(speed) {
3318
- if (typeof speed !== 'number' || speed < 0 || speed > 100) {
3319
- throw new TypeError(`Invalid speed value: ${speed}`);
3320
- }
3321
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_SPEED, speed]);
3322
- }
3323
- /**
3324
- * Sets the mode of the air purifier.
3325
- * @param {number} mode - The mode value (1-4).
3326
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3327
- */
3328
- async setMode(mode) {
3329
- if (typeof mode !== 'number' || mode < 1 || mode > 4) {
3330
- throw new TypeError(`Invalid mode value: ${mode}`);
3331
- }
3332
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_MODE, mode]);
3333
- }
3334
- /**
3335
- * Operates the air purifier with the given byte array.
3336
- * @public
3337
- * @param {number[]} bytes - The byte array to send.
3338
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3339
- */
3340
- async operateAirPurifier(bytes) {
3341
- const req_buf = Buffer.from(bytes);
3342
- const res_buf = await this.command(req_buf);
3343
- if (res_buf.length !== 2) {
3344
- throw new Error(`Expecting a 2-byte response, got instead: 0x${res_buf.toString('hex')}`);
3345
- }
3346
- const code = res_buf.readUInt8(1);
3347
- if (code === 0x00 || code === 0x80) {
3348
- return code === 0x80;
3349
- }
3350
- else {
3351
- throw new Error(`The device returned an error: 0x${res_buf.toString('hex')}`);
3352
- }
3353
- }
3354
- }
3355
- /**
3356
- * Class representing a SwitchBot Air Purifier Table device.
3357
- * @extends SwitchbotDevice
3358
- */
3359
- export class WoAirPurifierTable extends SwitchbotDevice {
3360
- /**
3361
- * Parses service data for air purifier table devices.
3362
- * @param {Buffer | null} serviceData - The service data buffer.
3363
- * @param {Buffer | null} manufacturerData - The manufacturer data buffer.
3364
- * @param {Function} emitLog - The function to emit log messages.
3365
- * @returns {airPurifierTableServiceData | null} - The parsed service data or null.
3366
- */
3367
- static parseServiceData(serviceData, manufacturerData, emitLog) {
3368
- if (!manufacturerData || manufacturerData.length < 14) {
3369
- return null;
3370
- }
3371
- const deviceData = manufacturerData.subarray(6);
3372
- if (deviceData.length < 8) {
3373
- return null;
3374
- }
3375
- const sequenceNumber = deviceData[0];
3376
- const isOn = Boolean(deviceData[1] & 0b10000000);
3377
- const mode = deviceData[1] & 0b00000111;
3378
- const isAqiValid = Boolean(deviceData[2] & 0b00000100);
3379
- const childLock = Boolean(deviceData[2] & 0b00000010);
3380
- const speed = deviceData[3] & 0b01111111;
3381
- const aqiLevelRaw = (deviceData[4] & 0b00000110) >> 1;
3382
- const workTime = (deviceData[5] << 8) | deviceData[6];
3383
- const errCode = deviceData[7];
3384
- // Map AQI level to string using the defined constant
3385
- const aqiLevelValues = [
3386
- AIR_QUALITY_LEVELS.EXCELLENT,
3387
- AIR_QUALITY_LEVELS.GOOD,
3388
- AIR_QUALITY_LEVELS.FAIR,
3389
- AIR_QUALITY_LEVELS.POOR,
3390
- ];
3391
- const aqiLevel = aqiLevelValues[aqiLevelRaw] || 'unknown';
3392
- // Determine mode based on mode value and speed
3393
- let modeString = null;
3394
- if (mode === 1) {
3395
- if (speed >= 0 && speed <= 33) {
3396
- modeString = AIR_PURIFIER_MODES.LEVEL_1;
3397
- }
3398
- else if (speed >= 34 && speed <= 66) {
3399
- modeString = AIR_PURIFIER_MODES.LEVEL_2;
3400
- }
3401
- else {
3402
- modeString = AIR_PURIFIER_MODES.LEVEL_3;
3403
- }
3404
- }
3405
- else if (mode > 1 && mode <= 4) {
3406
- const modeMap = [null, null, 'auto', 'sleep', 'manual'];
3407
- modeString = modeMap[mode + 2] || null;
3408
- }
3409
- if (emitLog) {
3410
- emitLog('debug', `Air Purifier Table Service Data: isOn=${isOn}, mode=${modeString}, speed=${speed}, AQI=${aqiLevel}`);
3411
- }
3412
- return {
3413
- model: SwitchBotBLEModel.AirPurifierTable,
3414
- modelName: SwitchBotBLEModelName.AirPurifierTable,
3415
- modelFriendlyName: SwitchBotBLEModelFriendlyName.AirPurifierTable,
3416
- isOn,
3417
- mode: modeString,
3418
- isAqiValid,
3419
- child_lock: childLock,
3420
- speed,
3421
- aqi_level: aqiLevel,
3422
- filter_element_working_time: workTime,
3423
- err_code: errCode,
3424
- sequence_number: sequenceNumber,
3425
- };
3426
- }
3427
- /**
3428
- * Sets the state of the air purifier table.
3429
- * @param {number[]} reqByteArray - The request byte array.
3430
- * @returns {Promise<boolean>} - Resolves with a boolean indicating whether the operation was successful.
3431
- * @private
3432
- */
3433
- async setState(reqByteArray) {
3434
- return this.operateAirPurifierTable(reqByteArray);
3435
- }
3436
- /**
3437
- * Turns the air purifier table on.
3438
- * @returns {Promise<boolean>} - Resolves with true if the air purifier table is turned on.
3439
- */
3440
- async turnOn() {
3441
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_ON]);
3442
- }
3443
- /**
3444
- * Turns the air purifier table off.
3445
- * @returns {Promise<boolean>} - Resolves with true if the air purifier table is turned off.
3446
- */
3447
- async turnOff() {
3448
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.TURN_OFF]);
3449
- }
3450
- /**
3451
- * Sets the speed of the air purifier table.
3452
- * @param {number} speed - The speed value (0-100).
3453
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3454
- */
3455
- async setSpeed(speed) {
3456
- if (typeof speed !== 'number' || speed < 0 || speed > 100) {
3457
- throw new TypeError(`Invalid speed value: ${speed}`);
3458
- }
3459
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_SPEED, speed]);
3460
- }
3461
- /**
3462
- * Sets the mode of the air purifier table.
3463
- * @param {number} mode - The mode value (1-4).
3464
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3465
- */
3466
- async setMode(mode) {
3467
- if (typeof mode !== 'number' || mode < 1 || mode > 4) {
3468
- throw new TypeError(`Invalid mode value: ${mode}`);
3469
- }
3470
- return this.setState([...DEVICE_COMMANDS.AIR_PURIFIER.SET_MODE, mode]);
3471
- }
3472
- /**
3473
- * Operates the air purifier table with the given byte array.
3474
- * @public
3475
- * @param {number[]} bytes - The byte array to send.
3476
- * @returns {Promise<boolean>} - Resolves with true if the operation was successful.
3477
- */
3478
- async operateAirPurifierTable(bytes) {
3479
- const req_buf = Buffer.from(bytes);
3480
- const res_buf = await this.command(req_buf);
3481
- if (res_buf.length !== 2) {
3482
- throw new Error(`Expecting a 2-byte response, got instead: 0x${res_buf.toString('hex')}`);
3483
- }
3484
- const code = res_buf.readUInt8(1);
3485
- if (code === 0x00 || code === 0x80) {
3486
- return code === 0x80;
3487
- }
3488
- else {
3489
- throw new Error(`The device returned an error: 0x${res_buf.toString('hex')}`);
3490
- }
3491
- }
3492
- }
3493
- //# sourceMappingURL=device.js.map