@switchbot/homebridge-switchbot 5.0.0-beta.13 → 5.0.0-beta.131

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 (593) hide show
  1. package/.github/ISSUE_TEMPLATE/e2e-verification.md +36 -0
  2. package/.github/copilot-instructions.md +39 -0
  3. package/.github/workflows/ci.yml +32 -0
  4. package/.github/workflows/manual-e2e.yml +115 -0
  5. package/.github/workflows/release.yml +0 -4
  6. package/CHANGELOG.md +35 -0
  7. package/E2E-VERIFICATION.md +121 -0
  8. package/MIGRATION.md +54 -0
  9. package/README.md +99 -3
  10. package/TODO.md +263 -0
  11. package/config.schema.json +198 -14787
  12. package/dist/device-types.d.ts +31 -0
  13. package/dist/device-types.d.ts.map +1 -0
  14. package/dist/device-types.js +246 -0
  15. package/dist/device-types.js.map +1 -0
  16. package/dist/deviceFactory.d.ts +13 -0
  17. package/dist/deviceFactory.d.ts.map +1 -0
  18. package/dist/deviceFactory.js +165 -0
  19. package/dist/deviceFactory.js.map +1 -0
  20. package/dist/devices/deviceBase.d.ts +50 -0
  21. package/dist/devices/deviceBase.d.ts.map +1 -0
  22. package/dist/devices/deviceBase.js +119 -0
  23. package/dist/devices/deviceBase.js.map +1 -0
  24. package/dist/devices/genericDevice.d.ts +277 -0
  25. package/dist/devices/genericDevice.d.ts.map +1 -0
  26. package/dist/devices/genericDevice.js +1396 -0
  27. package/dist/devices/genericDevice.js.map +1 -0
  28. package/dist/errors.d.ts +5 -0
  29. package/dist/errors.d.ts.map +1 -0
  30. package/dist/errors.js +51 -0
  31. package/dist/errors.js.map +1 -0
  32. package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
  33. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  34. package/dist/homebridge-ui/endpoints/config.js +107 -0
  35. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  36. package/dist/homebridge-ui/endpoints/credentials.d.ts +6 -0
  37. package/dist/homebridge-ui/endpoints/credentials.d.ts.map +1 -0
  38. package/dist/homebridge-ui/endpoints/credentials.js +65 -0
  39. package/dist/homebridge-ui/endpoints/credentials.js.map +1 -0
  40. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  41. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  42. package/dist/homebridge-ui/endpoints/devices.js +430 -0
  43. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  44. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  45. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  46. package/dist/homebridge-ui/endpoints/discovery.js +212 -0
  47. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  48. package/dist/homebridge-ui/public/css/styles.css +447 -0
  49. package/dist/homebridge-ui/public/index.html +144 -248
  50. package/dist/homebridge-ui/public/js/api.d.ts +62 -0
  51. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  52. package/dist/homebridge-ui/public/js/api.js +225 -0
  53. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  54. package/dist/homebridge-ui/public/js/api.ts +279 -0
  55. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  56. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  57. package/dist/homebridge-ui/public/js/app.js +3600 -0
  58. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  59. package/dist/homebridge-ui/public/js/app.ts +23 -0
  60. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  61. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  62. package/dist/homebridge-ui/public/js/constants.js +2 -0
  63. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  64. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  65. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  66. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  67. package/dist/homebridge-ui/public/js/credentials.js +96 -0
  68. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  69. package/dist/homebridge-ui/public/js/credentials.ts +100 -0
  70. package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
  71. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  72. package/dist/homebridge-ui/public/js/devices-delete.js +206 -0
  73. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  74. package/dist/homebridge-ui/public/js/devices-delete.ts +236 -0
  75. package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
  76. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  77. package/dist/homebridge-ui/public/js/devices.js +94 -0
  78. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  79. package/dist/homebridge-ui/public/js/devices.ts +114 -0
  80. package/dist/homebridge-ui/public/js/discovery.d.ts +4 -0
  81. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  82. package/dist/homebridge-ui/public/js/discovery.js +1364 -0
  83. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  84. package/dist/homebridge-ui/public/js/discovery.ts +1492 -0
  85. package/dist/homebridge-ui/public/js/logger.d.ts +6 -0
  86. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  87. package/dist/homebridge-ui/public/js/logger.js +12 -0
  88. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  89. package/dist/homebridge-ui/public/js/logger.ts +11 -0
  90. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  91. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  92. package/dist/homebridge-ui/public/js/modal.js +26 -0
  93. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  94. package/dist/homebridge-ui/public/js/modal.ts +28 -0
  95. package/dist/homebridge-ui/public/js/modals.d.ts +7 -0
  96. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  97. package/dist/homebridge-ui/public/js/modals.js +494 -0
  98. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  99. package/dist/homebridge-ui/public/js/modals.ts +546 -0
  100. package/dist/homebridge-ui/public/js/render.d.ts +68 -0
  101. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  102. package/dist/homebridge-ui/public/js/render.js +914 -0
  103. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  104. package/dist/homebridge-ui/public/js/render.ts +1026 -0
  105. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  106. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  107. package/dist/homebridge-ui/public/js/toast.js +29 -0
  108. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  109. package/dist/homebridge-ui/public/js/toast.ts +37 -0
  110. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  111. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  112. package/dist/homebridge-ui/public/js/types.js +2 -0
  113. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  114. package/dist/homebridge-ui/public/js/types.ts +26 -0
  115. package/dist/homebridge-ui/server.js +11 -41
  116. package/dist/homebridge-ui/server.js.map +1 -1
  117. package/dist/homebridge-ui/utils/config-parser.d.ts +35 -0
  118. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  119. package/dist/homebridge-ui/utils/config-parser.js +87 -0
  120. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  121. package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
  122. package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
  123. package/dist/homebridge-ui/utils/device-migration.js +111 -0
  124. package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
  125. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  126. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  127. package/dist/homebridge-ui/utils/logger.js +17 -0
  128. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +1 -3
  131. package/dist/index.js.map +1 -1
  132. package/dist/platform.d.ts +39 -0
  133. package/dist/platform.d.ts.map +1 -0
  134. package/dist/platform.js +1587 -0
  135. package/dist/platform.js.map +1 -0
  136. package/dist/settings.d.ts +10 -249
  137. package/dist/settings.d.ts.map +1 -1
  138. package/dist/settings.js +5 -30
  139. package/dist/settings.js.map +1 -1
  140. package/dist/switchbotClient.d.ts +32 -0
  141. package/dist/switchbotClient.d.ts.map +1 -0
  142. package/dist/switchbotClient.js +253 -0
  143. package/dist/switchbotClient.js.map +1 -0
  144. package/dist/utils.d.ts +36 -82
  145. package/dist/utils.d.ts.map +1 -1
  146. package/dist/utils.js +40 -786
  147. package/dist/utils.js.map +1 -1
  148. package/docs/assets/highlight.css +21 -0
  149. package/docs/assets/icons.js +1 -1
  150. package/docs/assets/icons.svg +1 -1
  151. package/docs/assets/main.js +2 -2
  152. package/docs/assets/style.css +3 -3
  153. package/docs/index.html +103 -13
  154. package/docs/variables/default.html +1 -1
  155. package/eslint.config.js +7 -8
  156. package/nodemon.json +2 -2
  157. package/package.json +36 -32
  158. package/scripts/build-ui.js +36 -0
  159. package/scripts/e2e/README.md +25 -0
  160. package/scripts/e2e/curtain-e2e.sh +70 -0
  161. package/scripts/e2e/fan-e2e.sh +75 -0
  162. package/scripts/e2e/light-advanced-e2e.sh +97 -0
  163. package/scripts/e2e/light-e2e.sh +75 -0
  164. package/scripts/e2e/list-accessories.sh +19 -0
  165. package/scripts/e2e/lock-e2e.sh +65 -0
  166. package/scripts/free-dev-ports.mjs +105 -0
  167. package/scripts/generate-matter-maps.js +60 -0
  168. package/scripts/run-e2e-local.sh +14 -0
  169. package/scripts/sync-device-types.mjs +28 -0
  170. package/src/device-types.ts +261 -0
  171. package/src/deviceFactory.ts +208 -0
  172. package/src/devices/deviceBase.ts +141 -0
  173. package/src/devices/genericDevice.ts +1345 -0
  174. package/src/errors.ts +43 -0
  175. package/src/homebridge-ui/endpoints/config.ts +126 -0
  176. package/src/homebridge-ui/endpoints/credentials.ts +74 -0
  177. package/src/homebridge-ui/endpoints/devices.ts +464 -0
  178. package/src/homebridge-ui/endpoints/discovery.ts +233 -0
  179. package/src/homebridge-ui/public/css/styles.css +447 -0
  180. package/src/homebridge-ui/public/index.html +144 -248
  181. package/src/homebridge-ui/public/js/api.ts +279 -0
  182. package/src/homebridge-ui/public/js/app.ts +23 -0
  183. package/src/homebridge-ui/public/js/constants.ts +1 -0
  184. package/src/homebridge-ui/public/js/credentials.ts +100 -0
  185. package/src/homebridge-ui/public/js/devices-delete.ts +236 -0
  186. package/src/homebridge-ui/public/js/devices.ts +114 -0
  187. package/src/homebridge-ui/public/js/discovery.ts +1492 -0
  188. package/src/homebridge-ui/public/js/logger.ts +11 -0
  189. package/src/homebridge-ui/public/js/modal.ts +28 -0
  190. package/src/homebridge-ui/public/js/modals.ts +546 -0
  191. package/src/homebridge-ui/public/js/render.ts +1026 -0
  192. package/src/homebridge-ui/public/js/toast.ts +37 -0
  193. package/src/homebridge-ui/public/js/types.ts +26 -0
  194. package/src/homebridge-ui/server.ts +11 -43
  195. package/src/homebridge-ui/utils/config-parser.ts +108 -0
  196. package/src/homebridge-ui/utils/device-migration.ts +144 -0
  197. package/src/homebridge-ui/utils/logger.ts +17 -0
  198. package/src/index.ts +1 -3
  199. package/src/platform.ts +1601 -0
  200. package/src/settings.ts +12 -277
  201. package/src/switchbotClient.ts +251 -0
  202. package/src/utils.ts +48 -812
  203. package/test/accessory-restore.spec.ts +73 -0
  204. package/test/device-mapping.spec.ts +37 -0
  205. package/test/deviceFactory.spec.ts +18 -0
  206. package/test/e2e/run-e2e.spec.ts +52 -0
  207. package/test/fan-swing.spec.ts +29 -0
  208. package/test/helpers/matter-harness.ts +53 -0
  209. package/test/lock-users.spec.ts +44 -0
  210. package/test/matter-childbridge.spec.ts +59 -0
  211. package/test/matter-descriptors.spec.ts +110 -0
  212. package/test/matter-device-state.spec.ts +101 -0
  213. package/test/matter-integration.spec.ts +136 -0
  214. package/test/platform.integration.spec.ts +55 -0
  215. package/test/switchbot-client-debounce.spec.ts +131 -0
  216. package/test/switchbot-client-openapi.spec.ts +56 -0
  217. package/test/switchbotClient.spec.ts +10 -0
  218. package/test/utils.spec.ts +36 -0
  219. package/todo/COMPLETED.md +542 -0
  220. package/todo/README.md +59 -0
  221. package/todo/TODO.md +190 -0
  222. package/todo/mockups/filtering-sorting.svg +112 -0
  223. package/todo/mockups/modal-switchbot-theme.svg +66 -0
  224. package/todo/mockups/signal-strength-visualization.svg +94 -0
  225. package/vitest.config.ts +7 -0
  226. package/coverage/base.css +0 -224
  227. package/coverage/block-navigation.js +0 -87
  228. package/coverage/clover.xml +0 -15847
  229. package/coverage/coverage-final.json +0 -42
  230. package/coverage/docs/assets/dmt/dmt-component-data.js.html +0 -85
  231. package/coverage/docs/assets/dmt/dmt-components.js.html +0 -286
  232. package/coverage/docs/assets/dmt/index.html +0 -131
  233. package/coverage/docs/assets/hierarchy.js.html +0 -85
  234. package/coverage/docs/assets/icons.js.html +0 -136
  235. package/coverage/docs/assets/index.html +0 -146
  236. package/coverage/docs/assets/main.js.html +0 -265
  237. package/coverage/favicon.png +0 -0
  238. package/coverage/index.html +0 -191
  239. package/coverage/prettify.css +0 -1
  240. package/coverage/prettify.js +0 -2
  241. package/coverage/sort-arrow-sprite.png +0 -0
  242. package/coverage/sorter.js +0 -196
  243. package/coverage/src/device/blindtilt.ts.html +0 -3238
  244. package/coverage/src/device/bot.ts.html +0 -2803
  245. package/coverage/src/device/ceilinglight.ts.html +0 -2338
  246. package/coverage/src/device/colorbulb.ts.html +0 -2824
  247. package/coverage/src/device/contact.ts.html +0 -1465
  248. package/coverage/src/device/curtain.ts.html +0 -2869
  249. package/coverage/src/device/device.ts.html +0 -2500
  250. package/coverage/src/device/fan.ts.html +0 -2242
  251. package/coverage/src/device/hub.ts.html +0 -1408
  252. package/coverage/src/device/humidifier.ts.html +0 -2116
  253. package/coverage/src/device/index.html +0 -416
  254. package/coverage/src/device/iosensor.ts.html +0 -1375
  255. package/coverage/src/device/lightstrip.ts.html +0 -2617
  256. package/coverage/src/device/lock.ts.html +0 -1963
  257. package/coverage/src/device/meter.ts.html +0 -1372
  258. package/coverage/src/device/meterplus.ts.html +0 -1384
  259. package/coverage/src/device/meterpro.ts.html +0 -1618
  260. package/coverage/src/device/motion.ts.html +0 -1264
  261. package/coverage/src/device/plug.ts.html +0 -1372
  262. package/coverage/src/device/relayswitch.ts.html +0 -2284
  263. package/coverage/src/device/robotvacuumcleaner.ts.html +0 -1810
  264. package/coverage/src/device/waterdetector.ts.html +0 -1294
  265. package/coverage/src/homebridge-ui/index.html +0 -116
  266. package/coverage/src/homebridge-ui/server.ts.html +0 -229
  267. package/coverage/src/index.html +0 -161
  268. package/coverage/src/index.ts.html +0 -124
  269. package/coverage/src/irdevice/airconditioner.ts.html +0 -1687
  270. package/coverage/src/irdevice/airpurifier.ts.html +0 -844
  271. package/coverage/src/irdevice/camera.ts.html +0 -475
  272. package/coverage/src/irdevice/fan.ts.html +0 -766
  273. package/coverage/src/irdevice/index.html +0 -251
  274. package/coverage/src/irdevice/irdevice.ts.html +0 -1117
  275. package/coverage/src/irdevice/light.ts.html +0 -826
  276. package/coverage/src/irdevice/other.ts.html +0 -2458
  277. package/coverage/src/irdevice/tv.ts.html +0 -1222
  278. package/coverage/src/irdevice/vacuumcleaner.ts.html +0 -466
  279. package/coverage/src/irdevice/waterheater.ts.html +0 -469
  280. package/coverage/src/platform.ts.html +0 -8776
  281. package/coverage/src/settings.ts.html +0 -934
  282. package/coverage/src/utils.ts.html +0 -2092
  283. package/dist/baseMatterAccessory.test.d.ts +0 -2
  284. package/dist/baseMatterAccessory.test.d.ts.map +0 -1
  285. package/dist/baseMatterAccessory.test.js +0 -71
  286. package/dist/baseMatterAccessory.test.js.map +0 -1
  287. package/dist/devices-hap/airpurifier.d.ts +0 -54
  288. package/dist/devices-hap/airpurifier.d.ts.map +0 -1
  289. package/dist/devices-hap/airpurifier.js +0 -527
  290. package/dist/devices-hap/airpurifier.js.map +0 -1
  291. package/dist/devices-hap/blindtilt.d.ts +0 -90
  292. package/dist/devices-hap/blindtilt.d.ts.map +0 -1
  293. package/dist/devices-hap/blindtilt.js +0 -974
  294. package/dist/devices-hap/blindtilt.js.map +0 -1
  295. package/dist/devices-hap/bot.d.ts +0 -102
  296. package/dist/devices-hap/bot.d.ts.map +0 -1
  297. package/dist/devices-hap/bot.js +0 -811
  298. package/dist/devices-hap/bot.js.map +0 -1
  299. package/dist/devices-hap/ceilinglight.d.ts +0 -85
  300. package/dist/devices-hap/ceilinglight.d.ts.map +0 -1
  301. package/dist/devices-hap/ceilinglight.js +0 -701
  302. package/dist/devices-hap/ceilinglight.js.map +0 -1
  303. package/dist/devices-hap/colorbulb.d.ts +0 -88
  304. package/dist/devices-hap/colorbulb.d.ts.map +0 -1
  305. package/dist/devices-hap/colorbulb.js +0 -881
  306. package/dist/devices-hap/colorbulb.js.map +0 -1
  307. package/dist/devices-hap/contact.d.ts +0 -44
  308. package/dist/devices-hap/contact.d.ts.map +0 -1
  309. package/dist/devices-hap/contact.js +0 -409
  310. package/dist/devices-hap/contact.js.map +0 -1
  311. package/dist/devices-hap/curtain.d.ts +0 -73
  312. package/dist/devices-hap/curtain.d.ts.map +0 -1
  313. package/dist/devices-hap/curtain.js +0 -869
  314. package/dist/devices-hap/curtain.js.map +0 -1
  315. package/dist/devices-hap/device.d.ts +0 -98
  316. package/dist/devices-hap/device.d.ts.map +0 -1
  317. package/dist/devices-hap/device.js +0 -749
  318. package/dist/devices-hap/device.js.map +0 -1
  319. package/dist/devices-hap/fan.d.ts +0 -69
  320. package/dist/devices-hap/fan.d.ts.map +0 -1
  321. package/dist/devices-hap/fan.js +0 -649
  322. package/dist/devices-hap/fan.js.map +0 -1
  323. package/dist/devices-hap/hub.d.ts +0 -37
  324. package/dist/devices-hap/hub.d.ts.map +0 -1
  325. package/dist/devices-hap/hub.js +0 -392
  326. package/dist/devices-hap/hub.js.map +0 -1
  327. package/dist/devices-hap/humidifier.d.ts +0 -68
  328. package/dist/devices-hap/humidifier.d.ts.map +0 -1
  329. package/dist/devices-hap/humidifier.js +0 -628
  330. package/dist/devices-hap/humidifier.js.map +0 -1
  331. package/dist/devices-hap/iosensor.d.ts +0 -42
  332. package/dist/devices-hap/iosensor.d.ts.map +0 -1
  333. package/dist/devices-hap/iosensor.js +0 -382
  334. package/dist/devices-hap/iosensor.js.map +0 -1
  335. package/dist/devices-hap/lightstrip.d.ts +0 -79
  336. package/dist/devices-hap/lightstrip.d.ts.map +0 -1
  337. package/dist/devices-hap/lightstrip.js +0 -797
  338. package/dist/devices-hap/lightstrip.js.map +0 -1
  339. package/dist/devices-hap/lock.d.ts +0 -53
  340. package/dist/devices-hap/lock.d.ts.map +0 -1
  341. package/dist/devices-hap/lock.js +0 -561
  342. package/dist/devices-hap/lock.js.map +0 -1
  343. package/dist/devices-hap/meter.d.ts +0 -37
  344. package/dist/devices-hap/meter.d.ts.map +0 -1
  345. package/dist/devices-hap/meter.js +0 -379
  346. package/dist/devices-hap/meter.js.map +0 -1
  347. package/dist/devices-hap/meterplus.d.ts +0 -42
  348. package/dist/devices-hap/meterplus.d.ts.map +0 -1
  349. package/dist/devices-hap/meterplus.js +0 -384
  350. package/dist/devices-hap/meterplus.js.map +0 -1
  351. package/dist/devices-hap/meterpro.d.ts +0 -43
  352. package/dist/devices-hap/meterpro.d.ts.map +0 -1
  353. package/dist/devices-hap/meterpro.js +0 -468
  354. package/dist/devices-hap/meterpro.js.map +0 -1
  355. package/dist/devices-hap/motion.d.ts +0 -42
  356. package/dist/devices-hap/motion.d.ts.map +0 -1
  357. package/dist/devices-hap/motion.js +0 -345
  358. package/dist/devices-hap/motion.js.map +0 -1
  359. package/dist/devices-hap/plug.d.ts +0 -49
  360. package/dist/devices-hap/plug.d.ts.map +0 -1
  361. package/dist/devices-hap/plug.js +0 -395
  362. package/dist/devices-hap/plug.js.map +0 -1
  363. package/dist/devices-hap/relayswitch.d.ts +0 -96
  364. package/dist/devices-hap/relayswitch.d.ts.map +0 -1
  365. package/dist/devices-hap/relayswitch.js +0 -642
  366. package/dist/devices-hap/relayswitch.js.map +0 -1
  367. package/dist/devices-hap/robotvacuumcleaner.d.ts +0 -54
  368. package/dist/devices-hap/robotvacuumcleaner.d.ts.map +0 -1
  369. package/dist/devices-hap/robotvacuumcleaner.js +0 -523
  370. package/dist/devices-hap/robotvacuumcleaner.js.map +0 -1
  371. package/dist/devices-hap/waterdetector.d.ts +0 -41
  372. package/dist/devices-hap/waterdetector.d.ts.map +0 -1
  373. package/dist/devices-hap/waterdetector.js +0 -356
  374. package/dist/devices-hap/waterdetector.js.map +0 -1
  375. package/dist/devices-matter/BaseMatterAccessory.d.ts +0 -78
  376. package/dist/devices-matter/BaseMatterAccessory.d.ts.map +0 -1
  377. package/dist/devices-matter/BaseMatterAccessory.js +0 -220
  378. package/dist/devices-matter/BaseMatterAccessory.js.map +0 -1
  379. package/dist/devices-matter/ColorLightAccessory.d.ts +0 -20
  380. package/dist/devices-matter/ColorLightAccessory.d.ts.map +0 -1
  381. package/dist/devices-matter/ColorLightAccessory.js +0 -95
  382. package/dist/devices-matter/ColorLightAccessory.js.map +0 -1
  383. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts +0 -18
  384. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +0 -1
  385. package/dist/devices-matter/ColorTemperatureLightAccessory.js +0 -76
  386. package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +0 -1
  387. package/dist/devices-matter/ContactSensorAccessory.d.ts +0 -12
  388. package/dist/devices-matter/ContactSensorAccessory.d.ts.map +0 -1
  389. package/dist/devices-matter/ContactSensorAccessory.js +0 -34
  390. package/dist/devices-matter/ContactSensorAccessory.js.map +0 -1
  391. package/dist/devices-matter/DimmableLightAccessory.d.ts +0 -58
  392. package/dist/devices-matter/DimmableLightAccessory.d.ts.map +0 -1
  393. package/dist/devices-matter/DimmableLightAccessory.js +0 -167
  394. package/dist/devices-matter/DimmableLightAccessory.js.map +0 -1
  395. package/dist/devices-matter/DoorLockAccessory.d.ts +0 -14
  396. package/dist/devices-matter/DoorLockAccessory.d.ts.map +0 -1
  397. package/dist/devices-matter/DoorLockAccessory.js +0 -50
  398. package/dist/devices-matter/DoorLockAccessory.js.map +0 -1
  399. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts +0 -21
  400. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +0 -1
  401. package/dist/devices-matter/ExtendedColorLightAccessory.js +0 -106
  402. package/dist/devices-matter/ExtendedColorLightAccessory.js.map +0 -1
  403. package/dist/devices-matter/FanAccessory.d.ts +0 -16
  404. package/dist/devices-matter/FanAccessory.d.ts.map +0 -1
  405. package/dist/devices-matter/FanAccessory.js +0 -81
  406. package/dist/devices-matter/FanAccessory.js.map +0 -1
  407. package/dist/devices-matter/HumiditySensorAccessory.d.ts +0 -12
  408. package/dist/devices-matter/HumiditySensorAccessory.d.ts.map +0 -1
  409. package/dist/devices-matter/HumiditySensorAccessory.js +0 -34
  410. package/dist/devices-matter/HumiditySensorAccessory.js.map +0 -1
  411. package/dist/devices-matter/LeakSensorAccessory.d.ts +0 -12
  412. package/dist/devices-matter/LeakSensorAccessory.d.ts.map +0 -1
  413. package/dist/devices-matter/LeakSensorAccessory.js +0 -33
  414. package/dist/devices-matter/LeakSensorAccessory.js.map +0 -1
  415. package/dist/devices-matter/LightSensorAccessory.d.ts +0 -12
  416. package/dist/devices-matter/LightSensorAccessory.d.ts.map +0 -1
  417. package/dist/devices-matter/LightSensorAccessory.js +0 -34
  418. package/dist/devices-matter/LightSensorAccessory.js.map +0 -1
  419. package/dist/devices-matter/OccupancySensorAccessory.d.ts +0 -12
  420. package/dist/devices-matter/OccupancySensorAccessory.d.ts.map +0 -1
  421. package/dist/devices-matter/OccupancySensorAccessory.js +0 -39
  422. package/dist/devices-matter/OccupancySensorAccessory.js.map +0 -1
  423. package/dist/devices-matter/OnOffLightAccessory.d.ts +0 -38
  424. package/dist/devices-matter/OnOffLightAccessory.d.ts.map +0 -1
  425. package/dist/devices-matter/OnOffLightAccessory.js +0 -110
  426. package/dist/devices-matter/OnOffLightAccessory.js.map +0 -1
  427. package/dist/devices-matter/OnOffOutletAccessory.d.ts +0 -14
  428. package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +0 -1
  429. package/dist/devices-matter/OnOffOutletAccessory.js +0 -43
  430. package/dist/devices-matter/OnOffOutletAccessory.js.map +0 -1
  431. package/dist/devices-matter/OnOffSwitchAccessory.d.ts +0 -14
  432. package/dist/devices-matter/OnOffSwitchAccessory.d.ts.map +0 -1
  433. package/dist/devices-matter/OnOffSwitchAccessory.js +0 -42
  434. package/dist/devices-matter/OnOffSwitchAccessory.js.map +0 -1
  435. package/dist/devices-matter/RoboticVacuumAccessory.d.ts +0 -68
  436. package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +0 -1
  437. package/dist/devices-matter/RoboticVacuumAccessory.js +0 -334
  438. package/dist/devices-matter/RoboticVacuumAccessory.js.map +0 -1
  439. package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts +0 -11
  440. package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts.map +0 -1
  441. package/dist/devices-matter/SmokeCOAlarmAccessory.js +0 -49
  442. package/dist/devices-matter/SmokeCOAlarmAccessory.js.map +0 -1
  443. package/dist/devices-matter/TemperatureSensorAccessory.d.ts +0 -12
  444. package/dist/devices-matter/TemperatureSensorAccessory.d.ts.map +0 -1
  445. package/dist/devices-matter/TemperatureSensorAccessory.js +0 -36
  446. package/dist/devices-matter/TemperatureSensorAccessory.js.map +0 -1
  447. package/dist/devices-matter/ThermostatAccessory.d.ts +0 -19
  448. package/dist/devices-matter/ThermostatAccessory.d.ts.map +0 -1
  449. package/dist/devices-matter/ThermostatAccessory.js +0 -95
  450. package/dist/devices-matter/ThermostatAccessory.js.map +0 -1
  451. package/dist/devices-matter/VenetianBlindAccessory.d.ts +0 -19
  452. package/dist/devices-matter/VenetianBlindAccessory.d.ts.map +0 -1
  453. package/dist/devices-matter/VenetianBlindAccessory.js +0 -99
  454. package/dist/devices-matter/VenetianBlindAccessory.js.map +0 -1
  455. package/dist/devices-matter/WindowBlindAccessory.d.ts +0 -17
  456. package/dist/devices-matter/WindowBlindAccessory.d.ts.map +0 -1
  457. package/dist/devices-matter/WindowBlindAccessory.js +0 -80
  458. package/dist/devices-matter/WindowBlindAccessory.js.map +0 -1
  459. package/dist/devices-matter/custom/PowerStripAccessory.d.ts +0 -97
  460. package/dist/devices-matter/custom/PowerStripAccessory.d.ts.map +0 -1
  461. package/dist/devices-matter/custom/PowerStripAccessory.js +0 -265
  462. package/dist/devices-matter/custom/PowerStripAccessory.js.map +0 -1
  463. package/dist/devices-matter/custom/index.d.ts +0 -8
  464. package/dist/devices-matter/custom/index.d.ts.map +0 -1
  465. package/dist/devices-matter/custom/index.js +0 -8
  466. package/dist/devices-matter/custom/index.js.map +0 -1
  467. package/dist/devices-matter/index.d.ts +0 -29
  468. package/dist/devices-matter/index.d.ts.map +0 -1
  469. package/dist/devices-matter/index.js +0 -28
  470. package/dist/devices-matter/index.js.map +0 -1
  471. package/dist/index.test.d.ts +0 -2
  472. package/dist/index.test.d.ts.map +0 -1
  473. package/dist/index.test.js +0 -19
  474. package/dist/index.test.js.map +0 -1
  475. package/dist/irdevice/airconditioner.d.ts +0 -61
  476. package/dist/irdevice/airconditioner.d.ts.map +0 -1
  477. package/dist/irdevice/airconditioner.js +0 -472
  478. package/dist/irdevice/airconditioner.js.map +0 -1
  479. package/dist/irdevice/airpurifier.d.ts +0 -50
  480. package/dist/irdevice/airpurifier.d.ts.map +0 -1
  481. package/dist/irdevice/airpurifier.js +0 -213
  482. package/dist/irdevice/airpurifier.js.map +0 -1
  483. package/dist/irdevice/camera.d.ts +0 -32
  484. package/dist/irdevice/camera.d.ts.map +0 -1
  485. package/dist/irdevice/camera.js +0 -107
  486. package/dist/irdevice/camera.js.map +0 -1
  487. package/dist/irdevice/fan.d.ts +0 -36
  488. package/dist/irdevice/fan.d.ts.map +0 -1
  489. package/dist/irdevice/fan.js +0 -200
  490. package/dist/irdevice/fan.js.map +0 -1
  491. package/dist/irdevice/irdevice.d.ts +0 -68
  492. package/dist/irdevice/irdevice.d.ts.map +0 -1
  493. package/dist/irdevice/irdevice.js +0 -298
  494. package/dist/irdevice/irdevice.js.map +0 -1
  495. package/dist/irdevice/light.d.ts +0 -36
  496. package/dist/irdevice/light.d.ts.map +0 -1
  497. package/dist/irdevice/light.js +0 -206
  498. package/dist/irdevice/light.js.map +0 -1
  499. package/dist/irdevice/other.d.ts +0 -57
  500. package/dist/irdevice/other.d.ts.map +0 -1
  501. package/dist/irdevice/other.js +0 -778
  502. package/dist/irdevice/other.js.map +0 -1
  503. package/dist/irdevice/tv.d.ts +0 -45
  504. package/dist/irdevice/tv.d.ts.map +0 -1
  505. package/dist/irdevice/tv.js +0 -327
  506. package/dist/irdevice/tv.js.map +0 -1
  507. package/dist/irdevice/vacuumcleaner.d.ts +0 -28
  508. package/dist/irdevice/vacuumcleaner.d.ts.map +0 -1
  509. package/dist/irdevice/vacuumcleaner.js +0 -104
  510. package/dist/irdevice/vacuumcleaner.js.map +0 -1
  511. package/dist/irdevice/waterheater.d.ts +0 -30
  512. package/dist/irdevice/waterheater.d.ts.map +0 -1
  513. package/dist/irdevice/waterheater.js +0 -105
  514. package/dist/irdevice/waterheater.js.map +0 -1
  515. package/dist/platform-hap.d.ts +0 -149
  516. package/dist/platform-hap.d.ts.map +0 -1
  517. package/dist/platform-hap.js +0 -2861
  518. package/dist/platform-hap.js.map +0 -1
  519. package/dist/platform-matter.d.ts +0 -120
  520. package/dist/platform-matter.d.ts.map +0 -1
  521. package/dist/platform-matter.js +0 -999
  522. package/dist/platform-matter.js.map +0 -1
  523. package/dist/utils.test.d.ts +0 -2
  524. package/dist/utils.test.d.ts.map +0 -1
  525. package/dist/utils.test.js +0 -95
  526. package/dist/utils.test.js.map +0 -1
  527. package/dist/verifyconfig.test.d.ts +0 -2
  528. package/dist/verifyconfig.test.d.ts.map +0 -1
  529. package/dist/verifyconfig.test.js +0 -167
  530. package/dist/verifyconfig.test.js.map +0 -1
  531. package/src/baseMatterAccessory.test.ts +0 -88
  532. package/src/custom.d.ts +0 -7
  533. package/src/devices-hap/airpurifier.ts +0 -563
  534. package/src/devices-hap/blindtilt.ts +0 -1049
  535. package/src/devices-hap/bot.ts +0 -900
  536. package/src/devices-hap/ceilinglight.ts +0 -742
  537. package/src/devices-hap/colorbulb.ts +0 -904
  538. package/src/devices-hap/contact.ts +0 -457
  539. package/src/devices-hap/curtain.ts +0 -944
  540. package/src/devices-hap/device.ts +0 -811
  541. package/src/devices-hap/fan.ts +0 -711
  542. package/src/devices-hap/hub.ts +0 -439
  543. package/src/devices-hap/humidifier.ts +0 -669
  544. package/src/devices-hap/iosensor.ts +0 -427
  545. package/src/devices-hap/lightstrip.ts +0 -836
  546. package/src/devices-hap/lock.ts +0 -620
  547. package/src/devices-hap/meter.ts +0 -426
  548. package/src/devices-hap/meterplus.ts +0 -430
  549. package/src/devices-hap/meterpro.ts +0 -522
  550. package/src/devices-hap/motion.ts +0 -390
  551. package/src/devices-hap/plug.ts +0 -423
  552. package/src/devices-hap/relayswitch.ts +0 -727
  553. package/src/devices-hap/robotvacuumcleaner.ts +0 -568
  554. package/src/devices-hap/waterdetector.ts +0 -400
  555. package/src/devices-matter/BaseMatterAccessory.ts +0 -249
  556. package/src/devices-matter/ColorLightAccessory.ts +0 -110
  557. package/src/devices-matter/ColorTemperatureLightAccessory.ts +0 -90
  558. package/src/devices-matter/ContactSensorAccessory.ts +0 -41
  559. package/src/devices-matter/DimmableLightAccessory.ts +0 -192
  560. package/src/devices-matter/DoorLockAccessory.ts +0 -60
  561. package/src/devices-matter/ExtendedColorLightAccessory.ts +0 -122
  562. package/src/devices-matter/FanAccessory.ts +0 -95
  563. package/src/devices-matter/HumiditySensorAccessory.ts +0 -41
  564. package/src/devices-matter/LeakSensorAccessory.ts +0 -40
  565. package/src/devices-matter/LightSensorAccessory.ts +0 -41
  566. package/src/devices-matter/OccupancySensorAccessory.ts +0 -48
  567. package/src/devices-matter/OnOffLightAccessory.ts +0 -125
  568. package/src/devices-matter/OnOffOutletAccessory.ts +0 -51
  569. package/src/devices-matter/OnOffSwitchAccessory.ts +0 -51
  570. package/src/devices-matter/RoboticVacuumAccessory.ts +0 -407
  571. package/src/devices-matter/SmokeCOAlarmAccessory.ts +0 -59
  572. package/src/devices-matter/TemperatureSensorAccessory.ts +0 -43
  573. package/src/devices-matter/ThermostatAccessory.ts +0 -110
  574. package/src/devices-matter/VenetianBlindAccessory.ts +0 -115
  575. package/src/devices-matter/WindowBlindAccessory.ts +0 -92
  576. package/src/devices-matter/custom/PowerStripAccessory.ts +0 -309
  577. package/src/devices-matter/custom/index.ts +0 -8
  578. package/src/devices-matter/index.ts +0 -29
  579. package/src/index.test.ts +0 -24
  580. package/src/irdevice/airconditioner.ts +0 -533
  581. package/src/irdevice/airpurifier.ts +0 -252
  582. package/src/irdevice/camera.ts +0 -129
  583. package/src/irdevice/fan.ts +0 -226
  584. package/src/irdevice/irdevice.ts +0 -344
  585. package/src/irdevice/light.ts +0 -246
  586. package/src/irdevice/other.ts +0 -790
  587. package/src/irdevice/tv.ts +0 -378
  588. package/src/irdevice/vacuumcleaner.ts +0 -126
  589. package/src/irdevice/waterheater.ts +0 -127
  590. package/src/platform-hap.ts +0 -2997
  591. package/src/platform-matter.ts +0 -1124
  592. package/src/utils.test.ts +0 -96
  593. package/src/verifyconfig.test.ts +0 -197
@@ -0,0 +1,1396 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { MATTER_ATTRIBUTE_IDS, MATTER_CLUSTER_IDS } from '../utils.js';
3
+ import { DeviceBase } from './deviceBase.js';
4
+ function validateBLEResponseLength(buf, expected, context = '') {
5
+ if (!buf || typeof buf.length !== 'number' || buf.length !== expected) {
6
+ console.warn(`[BLE] Invalid response length${context ? ` for ${context}` : ''}: expected ${expected}, got ${buf?.length}`);
7
+ return false;
8
+ }
9
+ return true;
10
+ }
11
+ // BLE notification handling: per-command notification futures and unsolicited notification logging
12
+ const BLE_NOTIFICATION_HANDLERS = new Map();
13
+ // Module-scope regex pattern to avoid recompilation
14
+ const HEX_COLOR_REGEX = /^#?[0-9A-F]{6}$/i;
15
+ export class GenericDevice extends DeviceBase {
16
+ log;
17
+ constructor(opts, cfg) {
18
+ super(opts, cfg);
19
+ // Prefer logger from opts, then cfg, fallback to console
20
+ this.log = opts?.log || cfg?.log || console;
21
+ // If BLE encryptionKey/keyId provided, set on node-switchbot device instance if possible
22
+ if (opts.encryptionKey && this.client && typeof this.client.devices?.get === 'function') {
23
+ try {
24
+ const dev = this.client.devices.get(opts.id);
25
+ if (dev && typeof dev.setKey === 'function') {
26
+ dev.setKey({
27
+ encryptionKey: opts.encryptionKey,
28
+ keyId: opts.keyId || undefined,
29
+ });
30
+ }
31
+ }
32
+ catch (e) {
33
+ // ignore if device not found or setKey not available
34
+ }
35
+ }
36
+ // Ensure log is always set
37
+ if (!this.log) {
38
+ this.log = console;
39
+ }
40
+ // Subscribe to BLE notifications if supported (node-switchbot v4+)
41
+ this._subscribeBLENotifications();
42
+ }
43
+ /**
44
+ * Subscribe to BLE notifications for this device (if supported by node-switchbot)
45
+ * Logs unsolicited notifications and enables per-command notification futures.
46
+ */
47
+ async _subscribeBLENotifications() {
48
+ if (!this.client || typeof this.client.devices?.get !== 'function') {
49
+ return;
50
+ }
51
+ const dev = this.client.devices.get(this.opts.id);
52
+ if (!dev || typeof dev.mac !== 'string' || !dev.mac) {
53
+ return;
54
+ }
55
+ // Only subscribe once per device
56
+ if (BLE_NOTIFICATION_HANDLERS.has(dev.mac)) {
57
+ return;
58
+ }
59
+ if (typeof dev.subscribeNotifications === 'function') {
60
+ const handler = (payload) => {
61
+ // If a per-command notification future is waiting, let node-switchbot handle it
62
+ // Otherwise, log unsolicited notification
63
+ if (payload && payload.length > 0) {
64
+ // Unsolicited notification logging
65
+ // (node-switchbot will resolve per-command futures internally)
66
+ this.log.debug(`[BLE] Unsolicited notification from ${dev.mac}: ${payload.toString('hex')}`);
67
+ }
68
+ };
69
+ try {
70
+ // Subscribe and remember handler for possible cleanup
71
+ await dev.subscribeNotifications(handler);
72
+ BLE_NOTIFICATION_HANDLERS.set(dev.mac, handler);
73
+ }
74
+ catch (e) {
75
+ // ignore if subscription fails
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Await a BLE notification for this device (for advanced use in subclasses)
81
+ * Returns the notification payload or throws on timeout.
82
+ */
83
+ async _awaitBLENotification(timeoutMs = 5000) {
84
+ if (!this.client || typeof this.client.devices?.get !== 'function') {
85
+ throw new Error('No BLE client/device');
86
+ }
87
+ const dev = this.client.devices.get(this.opts.id);
88
+ if (!dev || typeof dev.mac !== 'string' || !dev.mac) {
89
+ throw new Error('No BLE MAC for device');
90
+ }
91
+ if (typeof dev.bleConnection?.sendCommand !== 'function') {
92
+ throw new TypeError('BLE connection does not support sendCommand');
93
+ }
94
+ // This is a low-level utility; in most cases, node-switchbot handles notification futures for commands
95
+ // Here, we expose a direct await for advanced use
96
+ return new Promise((resolve, reject) => {
97
+ let timer;
98
+ const handler = (payload) => {
99
+ clearTimeout(timer);
100
+ dev.bleConnection?.unsubscribeNotifications(dev.mac, handler);
101
+ resolve(payload);
102
+ };
103
+ dev.bleConnection?.subscribeNotifications(dev.mac, handler).then(() => {
104
+ timer = setTimeout(() => {
105
+ dev.bleConnection?.unsubscribeNotifications(dev.mac, handler);
106
+ reject(new Error('BLE notification timeout'));
107
+ }, timeoutMs);
108
+ }).catch(reject);
109
+ });
110
+ }
111
+ async getState() {
112
+ // Default: return minimal info; implementations should override
113
+ if (this.client && typeof this.client.getDevice === 'function') {
114
+ try {
115
+ const raw = await this.client.getDevice(this.opts.id);
116
+ // If this is a BLE buffer/array, validate length (common BLE status: 12 bytes, but may vary by device)
117
+ if (raw && (raw instanceof Buffer || Array.isArray(raw) || raw instanceof Uint8Array)) {
118
+ // Default to 12, override per device if needed
119
+ if (!validateBLEResponseLength(raw, 12, this.opts.type)) {
120
+ return { id: this.opts.id, type: this.opts.type, error: 'invalid_ble_response_length', raw };
121
+ }
122
+ }
123
+ // Normalize common response shapes
124
+ try {
125
+ const device = raw?.body ?? raw;
126
+ return device;
127
+ }
128
+ catch (e) {
129
+ return raw;
130
+ }
131
+ }
132
+ catch (e) {
133
+ // ignore and fallback
134
+ }
135
+ }
136
+ return { id: this.opts.id, type: this.opts.type };
137
+ }
138
+ async setState(change) {
139
+ // Apply change via SwitchBot API in real implementation
140
+ // Translate common high-level changes into SwitchBot OpenAPI commands
141
+ if (!this.client) {
142
+ return { success: false, reason: 'no client', change };
143
+ }
144
+ const cmdBody = {};
145
+ if (typeof change.on === 'boolean') {
146
+ cmdBody.command = change.on ? 'turnOn' : 'turnOff';
147
+ cmdBody.parameter = 'default';
148
+ cmdBody.commandType = 'command';
149
+ }
150
+ else if (typeof change.brightness === 'number') {
151
+ const v = Math.max(0, Math.min(100, Number(change.brightness)));
152
+ cmdBody.command = 'setBrightness';
153
+ cmdBody.parameter = String(v);
154
+ cmdBody.commandType = 'command';
155
+ }
156
+ else if (typeof change.speed === 'number') {
157
+ const v = Math.max(0, Math.min(100, Number(change.speed)));
158
+ cmdBody.command = 'setFanSpeed';
159
+ cmdBody.parameter = String(v);
160
+ cmdBody.commandType = 'command';
161
+ }
162
+ else if (typeof change.position === 'number') {
163
+ const v = Math.max(0, Math.min(100, Number(change.position)));
164
+ cmdBody.command = 'setPosition';
165
+ cmdBody.parameter = String(v);
166
+ cmdBody.commandType = 'command';
167
+ }
168
+ else if (typeof change.locked === 'boolean') {
169
+ cmdBody.command = change.locked ? 'lock' : 'unlock';
170
+ cmdBody.parameter = 'default';
171
+ cmdBody.commandType = 'command';
172
+ }
173
+ else if (typeof change.start === 'boolean') {
174
+ cmdBody.command = change.start ? 'start' : 'stop';
175
+ cmdBody.parameter = 'default';
176
+ cmdBody.commandType = 'command';
177
+ }
178
+ else {
179
+ // If caller supplied an explicit command body, pass through
180
+ if (change && typeof change.command === 'string') {
181
+ Object.assign(cmdBody, change);
182
+ }
183
+ else {
184
+ // Fallback: send raw change to client setDeviceState
185
+ try {
186
+ if (typeof this.client.setDeviceState === 'function') {
187
+ return await this.client.setDeviceState(this.opts.id, change);
188
+ }
189
+ if (typeof this.client.sendCommand === 'function') {
190
+ return await this.client.sendCommand(this.opts.id, change);
191
+ }
192
+ }
193
+ catch (err) {
194
+ const e = err;
195
+ return { success: false, reason: e?.message ?? String(e) };
196
+ }
197
+ return { success: false, reason: 'unsupported change', change };
198
+ }
199
+ }
200
+ try {
201
+ return await this.client.setDeviceState(this.opts.id, cmdBody);
202
+ }
203
+ catch (err) {
204
+ // try alternative client API if available
205
+ try {
206
+ if (typeof this.client.sendCommand === 'function') {
207
+ return await this.client.sendCommand(this.opts.id, cmdBody);
208
+ }
209
+ }
210
+ catch (e2) {
211
+ // ignore
212
+ }
213
+ const e = err;
214
+ return { success: false, reason: e?.message ?? String(e) };
215
+ }
216
+ }
217
+ createHAPAccessory(api) {
218
+ // Default HAP descriptor: a Switch service with On characteristic
219
+ return {
220
+ services: [
221
+ {
222
+ type: 'Switch',
223
+ characteristics: {
224
+ On: {
225
+ get: async () => {
226
+ const s = await this.getState();
227
+ return !!(s && (s.on === true || s.state === 'on' || s.power === 'on'));
228
+ },
229
+ set: async (v) => {
230
+ await this.setState({ on: !!v });
231
+ },
232
+ },
233
+ },
234
+ },
235
+ ],
236
+ };
237
+ }
238
+ // Default Matter descriptor mirrors HAP descriptor structure so the
239
+ // platform can construct a Matter accessory representation when
240
+ // Homebridge Matter APIs are available. Device subclasses may override
241
+ // this to provide Matter-specific clusters/attributes if desired.
242
+ async createMatterAccessory(api) {
243
+ // Dynamically detect features from getState()
244
+ const state = await this.getState();
245
+ const clusters = [];
246
+ // On/Off (Switch/Plug/Generic)
247
+ if ('on' in state || 'power' in state || 'state' in state) {
248
+ clusters.push({
249
+ type: 'OnOff',
250
+ clusterId: MATTER_CLUSTER_IDS.OnOff,
251
+ attributes: {
252
+ onOff: { read: async () => !!(await this.getState()).on || (await this.getState()).power === 'on' || (await this.getState()).state === 'on', write: async (v) => this.setState({ on: !!v }) },
253
+ [MATTER_ATTRIBUTE_IDS.OnOff.OnOff]: { read: async () => !!(await this.getState()).on || (await this.getState()).power === 'on' || (await this.getState()).state === 'on', write: async (v) => this.setState({ on: !!v }) },
254
+ },
255
+ });
256
+ }
257
+ // Brightness (Light)
258
+ if ('brightness' in state) {
259
+ clusters.push({
260
+ type: 'LevelControl',
261
+ clusterId: MATTER_CLUSTER_IDS.LevelControl,
262
+ attributes: {
263
+ currentLevel: { read: async () => (await this.getState()).brightness ?? 100, write: async (v) => this.setState({ brightness: Number(v) }) },
264
+ [MATTER_ATTRIBUTE_IDS.LevelControl.CurrentLevel]: { read: async () => (await this.getState()).brightness ?? 100, write: async (v) => this.setState({ brightness: Number(v) }) },
265
+ },
266
+ });
267
+ }
268
+ // Color (Light)
269
+ if ('hue' in state && 'saturation' in state) {
270
+ clusters.push({
271
+ type: 'ColorControl',
272
+ clusterId: MATTER_CLUSTER_IDS.ColorControl,
273
+ attributes: {
274
+ colorMode: { read: async () => 0 },
275
+ colorHue: { read: async () => (await this.getState()).hue ?? 0, write: async (v) => this.setState({ hue: Number(v) }) },
276
+ [MATTER_ATTRIBUTE_IDS.ColorControl.CurrentHue]: { read: async () => (await this.getState()).hue ?? 0, write: async (v) => this.setState({ hue: Number(v) }) },
277
+ colorSaturation: { read: async () => (await this.getState()).saturation ?? 0, write: async (v) => this.setState({ saturation: Number(v) }) },
278
+ [MATTER_ATTRIBUTE_IDS.ColorControl.CurrentSaturation]: { read: async () => (await this.getState()).saturation ?? 0, write: async (v) => this.setState({ saturation: Number(v) }) },
279
+ ...(typeof state.colorTemperature === 'number' || typeof state.kelvin === 'number'
280
+ ? {
281
+ colorTemperature: { read: async () => typeof (await this.getState()).colorTemperature === 'number' ? (await this.getState()).colorTemperature : Math.round(1000000 / ((await this.getState()).kelvin ?? 2500)), write: async (v) => this.setState({ colorTemperature: Number(v) }) },
282
+ [MATTER_ATTRIBUTE_IDS.ColorControl.ColorTemperatureMireds]: { read: async () => typeof (await this.getState()).colorTemperature === 'number' ? (await this.getState()).colorTemperature : Math.round(1000000 / ((await this.getState()).kelvin ?? 2500)), write: async (v) => this.setState({ colorTemperature: Number(v) }) },
283
+ }
284
+ : {}),
285
+ },
286
+ });
287
+ }
288
+ // Temperature sensor
289
+ if ('temperature' in state) {
290
+ clusters.push({
291
+ type: 'TemperatureMeasurement',
292
+ // No clusterId, not present in MATTER_CLUSTER_IDS
293
+ attributes: {
294
+ measuredValue: { read: async () => (await this.getState()).temperature ?? 0 },
295
+ },
296
+ });
297
+ }
298
+ // Humidity sensor
299
+ if ('humidity' in state) {
300
+ clusters.push({
301
+ type: 'RelativeHumidityMeasurement',
302
+ clusterId: MATTER_CLUSTER_IDS.RelativeHumidityMeasurement,
303
+ attributes: {
304
+ measuredValue: { read: async () => (await this.getState()).humidity ?? 0 },
305
+ },
306
+ });
307
+ }
308
+ // CO2 sensor
309
+ if ('CO2' in state) {
310
+ clusters.push({
311
+ type: 'AirQuality',
312
+ attributes: {
313
+ CO2: { read: async () => (await this.getState()).CO2 ?? 0 },
314
+ },
315
+ });
316
+ }
317
+ // Lock
318
+ if ('lockState' in state || 'locked' in state) {
319
+ clusters.push({
320
+ type: 'DoorLock',
321
+ clusterId: MATTER_CLUSTER_IDS.DoorLock,
322
+ attributes: {
323
+ lockState: { read: async () => (await this.getState()).lockState ?? (await this.getState()).locked ? 1 : 0, write: async (v) => this.setState({ locked: !!v }) },
324
+ },
325
+ });
326
+ }
327
+ // Motion sensor
328
+ if ('moveDetected' in state || 'motion' in state) {
329
+ clusters.push({
330
+ type: 'OccupancySensing',
331
+ // No clusterId, not present in MATTER_CLUSTER_IDS
332
+ attributes: {
333
+ occupancy: { read: async () => (await this.getState()).moveDetected === true || (await this.getState()).motion === true ? 1 : 0 },
334
+ },
335
+ });
336
+ }
337
+ // Contact sensor
338
+ if ('openState' in state || 'contact' in state || 'open' in state) {
339
+ clusters.push({
340
+ type: 'BooleanState',
341
+ // No clusterId, not present in MATTER_CLUSTER_IDS
342
+ attributes: {
343
+ stateValue: { read: async () => (await this.getState()).openState === 'open' || (await this.getState()).open === true ? 1 : 0 },
344
+ },
345
+ });
346
+ }
347
+ // Leak sensor
348
+ if ('leak' in state || 'status' in state) {
349
+ clusters.push({
350
+ type: 'LeakSensor',
351
+ attributes: {
352
+ leakDetected: { read: async () => (await this.getState()).leak === true || (await this.getState()).status === 1 ? 1 : 0 },
353
+ },
354
+ });
355
+ }
356
+ // Energy monitoring (Plug)
357
+ if ('voltage' in state || 'power' in state || 'electricCurrent' in state) {
358
+ clusters.push({
359
+ type: 'ElectricalMeasurement',
360
+ // No clusterId, not present in MATTER_CLUSTER_IDS
361
+ attributes: {
362
+ voltage: { read: async () => (await this.getState()).voltage ?? 0 },
363
+ power: { read: async () => (await this.getState()).power ?? 0 },
364
+ electricCurrent: { read: async () => (await this.getState()).electricCurrent ?? 0 },
365
+ },
366
+ });
367
+ }
368
+ // Fan
369
+ if ('speed' in state || 'fanSpeed' in state) {
370
+ clusters.push({
371
+ type: 'FanControl',
372
+ clusterId: MATTER_CLUSTER_IDS.FanControl,
373
+ attributes: {
374
+ speedCurrent: { read: async () => (await this.getState()).speed ?? (await this.getState()).fanSpeed ?? 0, write: async (v) => this.setState({ speed: Number(v) }) },
375
+ },
376
+ });
377
+ }
378
+ // Vacuum
379
+ if ('workingStatus' in state) {
380
+ clusters.push({
381
+ type: 'RobotVacuumCleaner',
382
+ attributes: {
383
+ workingStatus: { read: async () => (await this.getState()).workingStatus ?? 'StandBy' },
384
+ },
385
+ });
386
+ }
387
+ return {
388
+ id: this.opts.id,
389
+ name: this.opts.name ?? this.opts.type,
390
+ protocol: 'matter',
391
+ clusters,
392
+ };
393
+ }
394
+ }
395
+ // Specific device classes can extend GenericDevice for custom behavior.
396
+ export class BotDevice extends GenericDevice {
397
+ }
398
+ export class CurtainDevice extends GenericDevice {
399
+ createHAPAccessory(api) {
400
+ return {
401
+ services: [
402
+ {
403
+ type: 'WindowCovering',
404
+ characteristics: {
405
+ CurrentPosition: {
406
+ get: async () => {
407
+ const s = await this.getState();
408
+ return typeof s.position === 'number' ? s.position : 0;
409
+ },
410
+ },
411
+ TargetPosition: {
412
+ get: async () => {
413
+ const s = await this.getState();
414
+ return typeof s.position === 'number' ? s.position : 0;
415
+ },
416
+ set: async (v) => {
417
+ await this.setState({ position: Number(v) });
418
+ },
419
+ },
420
+ },
421
+ },
422
+ ],
423
+ };
424
+ }
425
+ // Matter-specific descriptor for Curtain (Shade cluster) with conformance type
426
+ async createMatterAccessory(api) {
427
+ // Determine device type and features
428
+ const typeKey = (this.opts.type || '').toLowerCase();
429
+ // Dynamic detection: check config/state for lift/tilt support
430
+ const hasConfigLift = !!this.opts.lift || typeof this.opts.lift === 'number';
431
+ const hasConfigTilt = !!this.opts.tilt || typeof this.opts.tilt === 'number';
432
+ let hasLiftAttrs = false;
433
+ let hasTiltAttrs = false;
434
+ if (typeof this.getState === 'function') {
435
+ let state;
436
+ try {
437
+ state = await this.getState();
438
+ }
439
+ catch (e) {
440
+ state = undefined;
441
+ }
442
+ if (state) {
443
+ hasLiftAttrs = typeof state.position === 'number';
444
+ hasTiltAttrs = typeof state.tilt === 'number';
445
+ }
446
+ }
447
+ // For SwitchBot Curtain, always set supportsLift = true
448
+ let supportsLift = hasConfigLift || hasLiftAttrs;
449
+ if (typeKey.includes('curtain')) {
450
+ supportsLift = true;
451
+ }
452
+ else if (!supportsLift && (typeKey.includes('rollershade') || typeKey.includes('blindtilt'))) {
453
+ supportsLift = true;
454
+ }
455
+ const supportsTilt = hasConfigTilt || hasTiltAttrs || typeKey.includes('blindtilt') || typeKey.includes('blind tilt');
456
+ // Debug: Log feature detection variables
457
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} feature detection: typeKey='${typeKey}', hasConfigLift=${hasConfigLift}, hasLiftAttrs=${hasLiftAttrs}, hasConfigTilt=${hasConfigTilt}, hasTiltAttrs=${hasTiltAttrs}, supportsLift=${supportsLift}, supportsTilt=${supportsTilt}`);
458
+ // If neither lift nor tilt is supported, do not register as Matter window covering
459
+ if (!supportsLift && !supportsTilt) {
460
+ this.log.warn(`[Matter] Skipping Matter registration for window covering device '${this.opts.name ?? this.opts.id}' (id: ${this.opts.id}): no lift or tilt support detected.`);
461
+ return undefined;
462
+ }
463
+ // Allow user to override Matter type via config (opts.matterType)
464
+ if (typeof this.opts.matterType === 'number') {
465
+ const matterType = this.opts.matterType;
466
+ this.log.info(`[Matter/Config] Using user-specified Matter type for ${this.opts.name ?? this.opts.id}: matterType=${matterType}`);
467
+ // Build attributes for Matter WindowCovering
468
+ const attributes = {
469
+ type: {
470
+ read: async () => {
471
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} returning config-overridden type attribute value:`, matterType);
472
+ return matterType;
473
+ },
474
+ write: undefined,
475
+ },
476
+ currentPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0; }, write: undefined },
477
+ targetPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0; }, write: async (v) => this.setState({ position: Number(v) }) },
478
+ };
479
+ if (supportsTilt) {
480
+ attributes.currentTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0; }, write: undefined };
481
+ attributes.targetTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0; }, write: async (v) => this.setState({ tilt: Number(v) }) };
482
+ }
483
+ try {
484
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes:`, JSON.stringify(attributes, null, 2));
485
+ }
486
+ catch (e) {
487
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes: [unserializable]`);
488
+ }
489
+ const windowCoveringCluster = {
490
+ type: 'WindowCovering', // for test compatibility
491
+ matterType, // actual Matter type number
492
+ clusterId: MATTER_CLUSTER_IDS.WindowCovering,
493
+ attributes,
494
+ };
495
+ const clustersArr = [windowCoveringCluster];
496
+ // Always set clusters.windowCovering to the WindowCovering cluster by clusterId
497
+ const foundWC = clustersArr.find((c) => c && c.clusterId === MATTER_CLUSTER_IDS.WindowCovering);
498
+ clustersArr.windowCovering = foundWC || null;
499
+ return {
500
+ id: this.opts.id,
501
+ name: this.opts.name ?? this.opts.type,
502
+ protocol: 'matter',
503
+ clusters: clustersArr,
504
+ };
505
+ }
506
+ const combos = [];
507
+ if (supportsLift && supportsTilt) {
508
+ // Both supported: allow RollerShade (0), optionally Drapery (4) if typeKey includes 'drapery'
509
+ combos.push({ matterType: 0, supportsLift: true, supportsTilt: true });
510
+ if (typeKey.includes('drapery')) {
511
+ combos.push({ matterType: 4, supportsLift: true, supportsTilt: true });
512
+ }
513
+ }
514
+ else if (supportsLift && !supportsTilt) {
515
+ // Only lift: for SwitchBot Curtain, use Drapery (4) for conformance
516
+ if (typeKey.includes('curtain')) {
517
+ combos.push({ matterType: 4, supportsLift: true, supportsTilt: false });
518
+ }
519
+ else {
520
+ combos.push({ matterType: 0, supportsLift: true, supportsTilt: false });
521
+ }
522
+ }
523
+ else if (!supportsLift && supportsTilt) {
524
+ // Only tilt: allow BlindTilt (8)
525
+ combos.push({ matterType: 8, supportsLift: false, supportsTilt: true });
526
+ }
527
+ let lastError;
528
+ for (const combo of combos) {
529
+ try {
530
+ this.log.info(`[Matter/Retry] Trying combo for ${this.opts.name ?? this.opts.id}: matterType=${combo.matterType}, supportsLift=${combo.supportsLift}, supportsTilt=${combo.supportsTilt}`);
531
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} trying combo: matterType=${combo.matterType}, supportsLift=${combo.supportsLift}, supportsTilt=${combo.supportsTilt}`);
532
+ // Build attributes for Matter WindowCovering (string keys only)
533
+ const attributes = {
534
+ type: {
535
+ read: async () => {
536
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} returning type attribute value:`, combo.matterType);
537
+ return combo.matterType;
538
+ },
539
+ write: undefined,
540
+ },
541
+ currentPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0; }, write: undefined },
542
+ targetPosition: { read: async () => { const s = await this.getState(); return typeof s.position === 'number' ? s.position : 0; }, write: async (v) => this.setState({ position: Number(v) }) },
543
+ };
544
+ if (combo.supportsTilt) {
545
+ attributes.currentTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0; }, write: undefined };
546
+ attributes.targetTilt = { read: async () => { const s = await this.getState(); return typeof s.tilt === 'number' ? s.tilt : 0; }, write: async (v) => this.setState({ tilt: Number(v) }) };
547
+ }
548
+ // Log the final attributes object
549
+ try {
550
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes:`, JSON.stringify(attributes, null, 2));
551
+ }
552
+ catch (e) {
553
+ this.log.debug(`[Matter/Debug] ${this.opts.name ?? this.opts.id} attributes: [unserializable]`);
554
+ }
555
+ // Try to register with this combo
556
+ const windowCoveringCluster = {
557
+ type: 'WindowCovering', // for test compatibility
558
+ matterType: combo.matterType, // actual Matter type number
559
+ clusterId: MATTER_CLUSTER_IDS.WindowCovering,
560
+ attributes,
561
+ };
562
+ const clustersArr = [windowCoveringCluster];
563
+ // Always set clusters.windowCovering to the WindowCovering cluster by clusterId
564
+ const foundWC = clustersArr.find((c) => c && c.clusterId === MATTER_CLUSTER_IDS.WindowCovering);
565
+ clustersArr.windowCovering = foundWC || null;
566
+ return {
567
+ id: this.opts.id,
568
+ name: this.opts.name ?? this.opts.type,
569
+ protocol: 'matter',
570
+ clusters: clustersArr,
571
+ };
572
+ }
573
+ catch (err) {
574
+ lastError = err;
575
+ this.log.warn(`[Matter/Retry] Failed combo matterType=${combo.matterType}, supportsLift=${combo.supportsLift}, supportsTilt=${combo.supportsTilt}: ${err}`);
576
+ }
577
+ }
578
+ // If all combos fail, throw last error
579
+ throw lastError || new Error('All Matter window covering type combos failed');
580
+ }
581
+ }
582
+ export class FanDevice extends GenericDevice {
583
+ createHAPAccessory(api) {
584
+ return {
585
+ services: [
586
+ {
587
+ type: 'Fan',
588
+ characteristics: {
589
+ On: {
590
+ get: async () => {
591
+ const s = await this.getState();
592
+ return !!(s && (s.on === true || s.state === 'on'));
593
+ },
594
+ set: async (v) => {
595
+ await this.setState({ on: !!v });
596
+ },
597
+ },
598
+ RotationSpeed: {
599
+ get: async () => {
600
+ const s = await this.getState();
601
+ return typeof s.speed === 'number' ? s.speed : 0;
602
+ },
603
+ set: async (v) => {
604
+ await this.setState({ speed: Number(v) });
605
+ },
606
+ },
607
+ },
608
+ },
609
+ ],
610
+ };
611
+ }
612
+ async setState(change) {
613
+ if (!this.client) {
614
+ return { success: false, reason: 'no client' };
615
+ }
616
+ // Oscillation support
617
+ if (typeof change.oscillate === 'boolean') {
618
+ const body = { command: 'setOscillation', parameter: change.oscillate ? 'on' : 'off', commandType: 'command' };
619
+ try {
620
+ return await this.client.setDeviceState(this.opts.id, body);
621
+ }
622
+ catch (err) {
623
+ try {
624
+ if (typeof this.client.sendCommand === 'function') {
625
+ return await this.client.sendCommand(this.opts.id, body);
626
+ }
627
+ }
628
+ catch (e) { }
629
+ const e = err;
630
+ return { success: false, reason: e?.message ?? String(e) };
631
+ }
632
+ }
633
+ // Swing / sweep support (angle or mode)
634
+ if (change && (typeof change.swing === 'boolean' || typeof change.swingAngle === 'number' || typeof change.swingMode === 'string')) {
635
+ let param = 'default';
636
+ if (typeof change.swingMode === 'string') {
637
+ param = change.swingMode;
638
+ }
639
+ else if (typeof change.swingAngle === 'number') {
640
+ param = String(Number(change.swingAngle));
641
+ }
642
+ else {
643
+ param = change.swing ? 'on' : 'off';
644
+ }
645
+ const body = { command: 'setSwing', parameter: param, commandType: 'command' };
646
+ try {
647
+ return await this.client.setDeviceState(this.opts.id, body);
648
+ }
649
+ catch (err) {
650
+ try {
651
+ if (typeof this.client.sendCommand === 'function') {
652
+ return await this.client.sendCommand(this.opts.id, body);
653
+ }
654
+ }
655
+ catch (e) { }
656
+ const e = err;
657
+ return { success: false, reason: e?.message ?? String(e) };
658
+ }
659
+ }
660
+ return super.setState(change);
661
+ }
662
+ // Matter-specific descriptor for Fan
663
+ createMatterAccessory(api) {
664
+ return {
665
+ id: this.opts.id,
666
+ name: this.opts.name ?? this.opts.type,
667
+ protocol: 'matter',
668
+ clusters: [
669
+ {
670
+ // OnOff cluster
671
+ type: 'OnOff',
672
+ clusterId: MATTER_CLUSTER_IDS.OnOff,
673
+ attributes: {
674
+ onOff: { read: async () => { const s = await this.getState(); return !!(s && (s.on === true || s.state === 'on')); }, write: async (v) => this.setState({ on: !!v }) },
675
+ // numeric attribute id for onOff
676
+ [MATTER_ATTRIBUTE_IDS.OnOff.OnOff]: { read: async () => { const s = await this.getState(); return !!(s && (s.on === true || s.state === 'on')); }, write: async (v) => this.setState({ on: !!v }) },
677
+ },
678
+ },
679
+ {
680
+ // Fan Control cluster
681
+ type: 'FanControl',
682
+ clusterId: MATTER_CLUSTER_IDS.FanControl,
683
+ attributes: {
684
+ rotationSpeed: { read: async () => { const s = await this.getState(); return typeof s.speed === 'number' ? s.speed : 0; }, write: async (v) => this.setState({ speed: Number(v) }) },
685
+ [MATTER_ATTRIBUTE_IDS.FanControl.SpeedCurrent]: { read: async () => { const s = await this.getState(); return typeof s.speed === 'number' ? s.speed : 0; }, write: async (v) => this.setState({ speed: Number(v) }) },
686
+ oscillation: { read: async () => { const s = await this.getState(); return !!s?.oscillating; }, write: async (v) => this.setState({ oscillate: !!v }) },
687
+ swingMode: { read: async () => { const s = await this.getState(); return s?.swingMode ?? null; }, write: async (v) => this.setState({ swingMode: v }) },
688
+ },
689
+ },
690
+ ],
691
+ };
692
+ }
693
+ }
694
+ export class LightDevice extends GenericDevice {
695
+ createHAPAccessory(api) {
696
+ return {
697
+ services: [
698
+ {
699
+ type: 'Lightbulb',
700
+ characteristics: {
701
+ On: {
702
+ get: async () => {
703
+ const s = await this.getState();
704
+ return !!(s && (s.on === true || s.state === 'on' || s.power === 'on'));
705
+ },
706
+ set: async (v) => {
707
+ await this.setState({ on: !!v });
708
+ },
709
+ },
710
+ Brightness: {
711
+ props: { minValue: 0, maxValue: 100, minStep: 1 },
712
+ get: async () => {
713
+ const s = await this.getState();
714
+ return typeof s.brightness === 'number' ? s.brightness : 100;
715
+ },
716
+ set: async (v) => {
717
+ await this.setState({ brightness: Number(v) });
718
+ },
719
+ },
720
+ Hue: {
721
+ props: { minValue: 0, maxValue: 360, minStep: 1 },
722
+ get: async () => {
723
+ const s = await this.getState();
724
+ // prefer explicit hue if provided
725
+ if (s && typeof s.hue === 'number') {
726
+ return s.hue;
727
+ }
728
+ // try HSV from color hex
729
+ const hex = s?.color || s?.colorHex || s?.colour;
730
+ if (typeof hex === 'string' && HEX_COLOR_REGEX.test(hex)) {
731
+ const h = (() => {
732
+ const hsl = (h, s, l) => ({ h, s, l });
733
+ // convert hex -> rgb -> hsv
734
+ const cleaned = hex.replace('#', '');
735
+ const r = Number.parseInt(cleaned.substr(0, 2), 16) / 255;
736
+ const g = Number.parseInt(cleaned.substr(2, 2), 16) / 255;
737
+ const b = Number.parseInt(cleaned.substr(4, 2), 16) / 255;
738
+ const mx = Math.max(r, g, b);
739
+ const mn = Math.min(r, g, b);
740
+ const d = mx - mn;
741
+ if (d === 0) {
742
+ return 0;
743
+ }
744
+ let hue = 0;
745
+ switch (mx) {
746
+ case r:
747
+ hue = ((g - b) / d) % 6;
748
+ break;
749
+ case g:
750
+ hue = (b - r) / d + 2;
751
+ break;
752
+ case b:
753
+ hue = (r - g) / d + 4;
754
+ break;
755
+ }
756
+ hue = Math.round(hue * 60);
757
+ if (hue < 0) {
758
+ hue += 360;
759
+ }
760
+ return hue;
761
+ })();
762
+ return h;
763
+ }
764
+ return 0;
765
+ },
766
+ set: async (v) => {
767
+ await this.setState({ hue: Number(v) });
768
+ },
769
+ },
770
+ Saturation: {
771
+ props: { minValue: 0, maxValue: 100, minStep: 1 },
772
+ get: async () => {
773
+ const s = await this.getState();
774
+ if (s && typeof s.saturation === 'number') {
775
+ return s.saturation;
776
+ }
777
+ // if color hex is available, derive saturation from rgb
778
+ const hex = s?.color || s?.colorHex || s?.colour;
779
+ if (typeof hex === 'string' && HEX_COLOR_REGEX.test(hex)) {
780
+ const cleaned = hex.replace('#', '');
781
+ const r = Number.parseInt(cleaned.substr(0, 2), 16) / 255;
782
+ const g = Number.parseInt(cleaned.substr(2, 2), 16) / 255;
783
+ const b = Number.parseInt(cleaned.substr(4, 2), 16) / 255;
784
+ const mx = Math.max(r, g, b);
785
+ const mn = Math.min(r, g, b);
786
+ const d = mx - mn;
787
+ const sat = mx === 0 ? 0 : Math.round((d / mx) * 100);
788
+ return sat;
789
+ }
790
+ return 0;
791
+ },
792
+ set: async (v) => {
793
+ await this.setState({ saturation: Number(v) });
794
+ },
795
+ },
796
+ ColorTemperature: {
797
+ props: { minValue: 153, maxValue: 500, minStep: 1 },
798
+ get: async () => {
799
+ const s = await this.getState();
800
+ // prefer mired if provided
801
+ if (s && typeof s.colorTemperature === 'number') {
802
+ return s.colorTemperature;
803
+ }
804
+ if (s && typeof s.color_temp === 'number') {
805
+ return s.color_temp;
806
+ }
807
+ // some devices provide kelvin
808
+ if (s && typeof s.kelvin === 'number' && s.kelvin > 0) {
809
+ return Math.round(1000000 / s.kelvin);
810
+ }
811
+ return 400;
812
+ },
813
+ set: async (v) => {
814
+ await this.setState({ colorTemperature: Number(v) });
815
+ },
816
+ },
817
+ },
818
+ },
819
+ ],
820
+ };
821
+ }
822
+ async setState(change) {
823
+ if (!this.client) {
824
+ return { success: false, reason: 'no client' };
825
+ }
826
+ // Color temperature (mired) or brightness/hue/sat
827
+ if (typeof change.colorTemperature === 'number' || typeof change.color_temp === 'number') {
828
+ const v = String(Number(change.colorTemperature ?? change.color_temp));
829
+ const body = { command: 'setColorTemperature', parameter: v, commandType: 'command' };
830
+ try {
831
+ return await this.client.setDeviceState(this.opts.id, body);
832
+ }
833
+ catch (err) {
834
+ try {
835
+ if (typeof this.client.sendCommand === 'function') {
836
+ return await this.client.sendCommand(this.opts.id, body);
837
+ }
838
+ }
839
+ catch (e) { }
840
+ const e = err;
841
+ return { success: false, reason: e?.message ?? String(e) };
842
+ }
843
+ }
844
+ if (typeof change.hue === 'number' && typeof change.saturation === 'number') {
845
+ const body = { command: 'setColor', parameter: `${Number(change.hue)},${Number(change.saturation)}`, commandType: 'command' };
846
+ try {
847
+ return await this.client.setDeviceState(this.opts.id, body);
848
+ }
849
+ catch (err) {
850
+ try {
851
+ if (typeof this.client.sendCommand === 'function') {
852
+ return await this.client.sendCommand(this.opts.id, body);
853
+ }
854
+ }
855
+ catch (e) { }
856
+ const e = err;
857
+ return { success: false, reason: e?.message ?? String(e) };
858
+ }
859
+ }
860
+ if (change && typeof change.color === 'string') {
861
+ const body = { command: 'setColor', parameter: change.color, commandType: 'command' };
862
+ try {
863
+ return await this.client.setDeviceState(this.opts.id, body);
864
+ }
865
+ catch (err) {
866
+ try {
867
+ if (typeof this.client.sendCommand === 'function') {
868
+ return await this.client.sendCommand(this.opts.id, body);
869
+ }
870
+ }
871
+ catch (e) { }
872
+ const e = err;
873
+ return { success: false, reason: e?.message ?? String(e) };
874
+ }
875
+ }
876
+ // Fallback to generic handler (brightness/on)
877
+ return super.setState(change);
878
+ }
879
+ // Matter-specific descriptor for lights (OnOff + Level + Color)
880
+ createMatterAccessory(api) {
881
+ return {
882
+ id: this.opts.id,
883
+ name: this.opts.name ?? this.opts.type,
884
+ protocol: 'matter',
885
+ clusters: [
886
+ {
887
+ // OnOff cluster
888
+ type: 'OnOff',
889
+ clusterId: MATTER_CLUSTER_IDS.OnOff,
890
+ attributes: {
891
+ onOff: { read: async () => { const s = await this.getState(); return !!(s && (s.on === true || s.state === 'on' || s.power === 'on')); }, write: async (v) => this.setState({ on: !!v }) },
892
+ [MATTER_ATTRIBUTE_IDS.OnOff.OnOff]: { read: async () => { const s = await this.getState(); return !!(s && (s.on === true || s.state === 'on' || s.power === 'on')); }, write: async (v) => this.setState({ on: !!v }) },
893
+ },
894
+ },
895
+ {
896
+ // Level Control cluster
897
+ type: 'LevelControl',
898
+ clusterId: MATTER_CLUSTER_IDS.LevelControl,
899
+ attributes: {
900
+ currentLevel: { read: async () => { const s = await this.getState(); return typeof s.brightness === 'number' ? s.brightness : 100; }, write: async (v) => this.setState({ brightness: Number(v) }) },
901
+ [MATTER_ATTRIBUTE_IDS.LevelControl.CurrentLevel]: { read: async () => { const s = await this.getState(); return typeof s.brightness === 'number' ? s.brightness : 100; }, write: async (v) => this.setState({ brightness: Number(v) }) },
902
+ },
903
+ },
904
+ {
905
+ // Color Control cluster
906
+ type: 'ColorControl',
907
+ clusterId: MATTER_CLUSTER_IDS.ColorControl,
908
+ attributes: {
909
+ // Required colorMode attribute for Matter conformance (0 = currentHueAndSaturation)
910
+ colorMode: { read: async () => 0 },
911
+ colorHue: { read: async () => { const s = await this.getState(); return typeof s.hue === 'number' ? s.hue : 0; }, write: async (v) => this.setState({ hue: Number(v) }) },
912
+ [MATTER_ATTRIBUTE_IDS.ColorControl.CurrentHue]: { read: async () => { const s = await this.getState(); return typeof s.hue === 'number' ? s.hue : 0; }, write: async (v) => this.setState({ hue: Number(v) }) },
913
+ colorSaturation: { read: async () => { const s = await this.getState(); return typeof s.saturation === 'number' ? s.saturation : 0; }, write: async (v) => this.setState({ saturation: Number(v) }) },
914
+ [MATTER_ATTRIBUTE_IDS.ColorControl.CurrentSaturation]: { read: async () => { const s = await this.getState(); return typeof s.saturation === 'number' ? s.saturation : 0; }, write: async (v) => this.setState({ saturation: Number(v) }) },
915
+ colorTemperature: { read: async () => {
916
+ const s = await this.getState();
917
+ if (typeof s.colorTemperature === 'number') {
918
+ return s.colorTemperature;
919
+ }
920
+ if (typeof s.kelvin === 'number') {
921
+ return Math.round(1000000 / s.kelvin);
922
+ }
923
+ return 400;
924
+ }, write: async (v) => this.setState({ colorTemperature: Number(v) }) },
925
+ [MATTER_ATTRIBUTE_IDS.ColorControl.ColorTemperatureMireds]: { read: async () => {
926
+ const s = await this.getState();
927
+ if (typeof s.colorTemperature === 'number') {
928
+ return s.colorTemperature;
929
+ }
930
+ if (typeof s.kelvin === 'number') {
931
+ return Math.round(1000000 / s.kelvin);
932
+ }
933
+ return 400;
934
+ }, write: async (v) => this.setState({ colorTemperature: Number(v) }) },
935
+ },
936
+ },
937
+ ],
938
+ };
939
+ }
940
+ }
941
+ export class LightStripDevice extends LightDevice {
942
+ }
943
+ export class MotionSensorDevice extends GenericDevice {
944
+ createHAPAccessory(api) {
945
+ return {
946
+ services: [
947
+ {
948
+ type: 'MotionSensor',
949
+ characteristics: {
950
+ MotionDetected: {
951
+ get: async () => {
952
+ const s = await this.getState();
953
+ return !!(s && s.motion === true);
954
+ },
955
+ },
956
+ },
957
+ },
958
+ ],
959
+ };
960
+ }
961
+ async setState(change) {
962
+ if (!this.client) {
963
+ return { success: false, reason: 'no client' };
964
+ }
965
+ // Oscillation support
966
+ if (typeof change.oscillate === 'boolean') {
967
+ const body = { command: 'setOscillation', parameter: change.oscillate ? 'on' : 'off', commandType: 'command' };
968
+ try {
969
+ return await this.client.setDeviceState(this.opts.id, body);
970
+ }
971
+ catch (err) {
972
+ try {
973
+ if (typeof this.client.sendCommand === 'function') {
974
+ return await this.client.sendCommand(this.opts.id, body);
975
+ }
976
+ }
977
+ catch (e) { }
978
+ const e = err;
979
+ return { success: false, reason: e?.message ?? String(e) };
980
+ }
981
+ }
982
+ // Swing / sweep support (angle or mode)
983
+ if (change && (typeof change.swing === 'boolean' || typeof change.swingAngle === 'number' || typeof change.swingMode === 'string')) {
984
+ let param = 'default';
985
+ if (typeof change.swingMode === 'string') {
986
+ param = change.swingMode;
987
+ }
988
+ else if (typeof change.swingAngle === 'number') {
989
+ param = String(Number(change.swingAngle));
990
+ }
991
+ else {
992
+ param = change.swing ? 'on' : 'off';
993
+ }
994
+ const body = { command: 'setSwing', parameter: param, commandType: 'command' };
995
+ try {
996
+ return await this.client.setDeviceState(this.opts.id, body);
997
+ }
998
+ catch (err) {
999
+ try {
1000
+ if (typeof this.client.sendCommand === 'function') {
1001
+ return await this.client.sendCommand(this.opts.id, body);
1002
+ }
1003
+ }
1004
+ catch (e) { }
1005
+ const e = err;
1006
+ return { success: false, reason: e?.message ?? String(e) };
1007
+ }
1008
+ }
1009
+ return super.setState(change);
1010
+ }
1011
+ }
1012
+ export class ContactSensorDevice extends GenericDevice {
1013
+ createHAPAccessory(api) {
1014
+ return {
1015
+ services: [
1016
+ {
1017
+ type: 'ContactSensor',
1018
+ characteristics: {
1019
+ ContactSensorState: {
1020
+ get: async () => {
1021
+ const s = await this.getState();
1022
+ return s && s.open ? 1 : 0;
1023
+ },
1024
+ },
1025
+ },
1026
+ },
1027
+ ],
1028
+ };
1029
+ }
1030
+ }
1031
+ export class VacuumDevice extends GenericDevice {
1032
+ // Use DeviceBase defaults (Switch-style) — no override needed
1033
+ createHAPAccessory(api) {
1034
+ return super.createHAPAccessory(api);
1035
+ }
1036
+ }
1037
+ export class LockDevice extends GenericDevice {
1038
+ createHAPAccessory(api) {
1039
+ return {
1040
+ services: [
1041
+ {
1042
+ type: 'LockMechanism',
1043
+ characteristics: {
1044
+ LockCurrentState: {
1045
+ get: async () => {
1046
+ const s = await this.getState();
1047
+ return s && s.locked ? 1 : 0;
1048
+ },
1049
+ },
1050
+ LockTargetState: {
1051
+ get: async () => {
1052
+ const s = await this.getState();
1053
+ return s && s.locked ? 1 : 0;
1054
+ },
1055
+ set: async (v) => {
1056
+ await this.setState({ locked: !!v });
1057
+ },
1058
+ },
1059
+ },
1060
+ },
1061
+ ],
1062
+ };
1063
+ }
1064
+ async setState(change) {
1065
+ if (!this.client) {
1066
+ return { success: false, reason: 'no client' };
1067
+ }
1068
+ // User management actions: add/remove/list users, unlock with pin
1069
+ if (change && typeof change.action === 'string') {
1070
+ const action = change.action;
1071
+ try {
1072
+ if (action === 'addUser' && (change.user || change.userId) && (change.pin || change.code)) {
1073
+ const user = change.user ?? change.userId;
1074
+ const p = String(change.pin ?? change.code);
1075
+ const body = { command: 'addUserCode', parameter: `${user}:${p}`, commandType: 'command' };
1076
+ return await this.client.setDeviceState(this.opts.id, body);
1077
+ }
1078
+ if (action === 'removeUser' && (change.user || change.userId)) {
1079
+ const user = change.user ?? change.userId;
1080
+ const body = { command: 'removeUserCode', parameter: String(user), commandType: 'command' };
1081
+ return await this.client.setDeviceState(this.opts.id, body);
1082
+ }
1083
+ if (action === 'listUsers') {
1084
+ const body = { command: 'listUsers', parameter: 'default', commandType: 'command' };
1085
+ return await this.client.setDeviceState(this.opts.id, body);
1086
+ }
1087
+ if (action === 'unlockWithPin' && (change.pin || change.code)) {
1088
+ const p = String(change.pin ?? change.code);
1089
+ const body = { command: 'unlockWithPin', parameter: p, commandType: 'command' };
1090
+ return await this.client.setDeviceState(this.opts.id, body);
1091
+ }
1092
+ }
1093
+ catch (err) {
1094
+ try {
1095
+ if (typeof this.client.sendCommand === 'function') {
1096
+ return await this.client.sendCommand(this.opts.id, { command: action, parameter: change.parameter ?? 'default', commandType: 'command' });
1097
+ }
1098
+ }
1099
+ catch (e) { }
1100
+ const e = err;
1101
+ return { success: false, reason: e?.message ?? String(e) };
1102
+ }
1103
+ }
1104
+ // Support setting lock PIN/passcode via `pin` or `passcode` (fallback)
1105
+ const pin = change?.pin ?? change?.passcode ?? change?.code;
1106
+ if (typeof pin === 'string' || typeof pin === 'number') {
1107
+ const body = { command: 'setLockPin', parameter: String(pin), commandType: 'command' };
1108
+ try {
1109
+ return await this.client.setDeviceState(this.opts.id, body);
1110
+ }
1111
+ catch (err) {
1112
+ try {
1113
+ if (typeof this.client.sendCommand === 'function') {
1114
+ return await this.client.sendCommand(this.opts.id, body);
1115
+ }
1116
+ }
1117
+ catch (e) { }
1118
+ const e = err;
1119
+ return { success: false, reason: e?.message ?? String(e) };
1120
+ }
1121
+ }
1122
+ return super.setState(change);
1123
+ }
1124
+ // Matter DoorLock descriptor including simple user-management actions
1125
+ createMatterAccessory(api) {
1126
+ return {
1127
+ id: this.opts.id,
1128
+ name: this.opts.name ?? this.opts.type,
1129
+ protocol: 'matter',
1130
+ clusters: [
1131
+ {
1132
+ // DoorLock cluster
1133
+ type: 'DoorLock',
1134
+ clusterId: MATTER_CLUSTER_IDS.DoorLock,
1135
+ attributes: {
1136
+ lockState: { read: async () => { const s = await this.getState(); return !!(s && s.locked); }, write: async (v) => this.setState({ locked: !!v }) },
1137
+ [MATTER_ATTRIBUTE_IDS.DoorLock.LockState]: { read: async () => { const s = await this.getState(); return !!(s && s.locked); }, write: async (v) => this.setState({ locked: !!v }) },
1138
+ },
1139
+ },
1140
+ {
1141
+ // DoorLock user mgmt cluster (conceptual)
1142
+ type: 'DoorLockUserManagement',
1143
+ clusterId: 0x0301,
1144
+ attributes: {
1145
+ addUser: { write: async (v) => this.setState({ action: 'addUser', user: v?.user, pin: v?.pin }) },
1146
+ removeUser: { write: async (v) => this.setState({ action: 'removeUser', user: v?.user }) },
1147
+ listUsers: { read: async () => { const r = await this.setState({ action: 'listUsers' }); return r; }, write: undefined },
1148
+ unlockWithPin: { write: async (v) => this.setState({ action: 'unlockWithPin', pin: v?.pin }) },
1149
+ },
1150
+ },
1151
+ ],
1152
+ };
1153
+ }
1154
+ }
1155
+ export class HumidifierDevice extends GenericDevice {
1156
+ createHAPAccessory(api) {
1157
+ return {
1158
+ services: [
1159
+ {
1160
+ type: 'HumidifierDehumidifier',
1161
+ characteristics: {
1162
+ Active: {
1163
+ get: async () => {
1164
+ const s = await this.getState();
1165
+ return s && s.on ? 1 : 0;
1166
+ },
1167
+ set: async (v) => {
1168
+ await this.setState({ on: v === 1 });
1169
+ },
1170
+ },
1171
+ CurrentHumidifierDehumidifierState: {
1172
+ get: async () => {
1173
+ const s = await this.getState();
1174
+ return s && s.on ? 2 : 0; // 0 = Inactive, 2 = Humidifying
1175
+ },
1176
+ },
1177
+ TargetHumidifierDehumidifierState: {
1178
+ get: async () => 1, // 1 = Humidifier
1179
+ set: async (v) => {
1180
+ // Support humidifier mode
1181
+ },
1182
+ },
1183
+ CurrentRelativeHumidity: {
1184
+ get: async () => {
1185
+ const s = await this.getState();
1186
+ return typeof s.humidity === 'number' ? s.humidity : 0;
1187
+ },
1188
+ },
1189
+ RelativeHumidityHumidifierThreshold: {
1190
+ get: async () => {
1191
+ const s = await this.getState();
1192
+ return typeof s.targetHumidity === 'number' ? s.targetHumidity : 50;
1193
+ },
1194
+ set: async (v) => {
1195
+ await this.setState({ humidity: Number(v) });
1196
+ },
1197
+ },
1198
+ },
1199
+ },
1200
+ ],
1201
+ };
1202
+ }
1203
+ async setState(change) {
1204
+ if (!this.client) {
1205
+ return { success: false, reason: 'no client' };
1206
+ }
1207
+ if (typeof change.humidity === 'number') {
1208
+ const v = String(Number(change.humidity));
1209
+ const body = { command: 'setHumidity', parameter: v, commandType: 'command' };
1210
+ try {
1211
+ return await this.client.setDeviceState(this.opts.id, body);
1212
+ }
1213
+ catch (err) {
1214
+ try {
1215
+ if (typeof this.client.sendCommand === 'function') {
1216
+ return await this.client.sendCommand(this.opts.id, body);
1217
+ }
1218
+ }
1219
+ catch (e) { }
1220
+ const e = err;
1221
+ return { success: false, reason: e?.message ?? String(e) };
1222
+ }
1223
+ }
1224
+ if (typeof change.dry === 'boolean') {
1225
+ const body = { command: 'setDry', parameter: change.dry ? 'on' : 'off', commandType: 'command' };
1226
+ try {
1227
+ return await this.client.setDeviceState(this.opts.id, body);
1228
+ }
1229
+ catch (err) {
1230
+ try {
1231
+ if (typeof this.client.sendCommand === 'function') {
1232
+ return await this.client.sendCommand(this.opts.id, body);
1233
+ }
1234
+ }
1235
+ catch (e) { }
1236
+ const e = err;
1237
+ return { success: false, reason: e?.message ?? String(e) };
1238
+ }
1239
+ }
1240
+ return super.setState(change);
1241
+ }
1242
+ }
1243
+ // Provide Matter descriptor for humidifier (humidity and on/off)
1244
+ export class HumidifierMatterDevice extends HumidifierDevice {
1245
+ async createMatterAccessory(api) {
1246
+ return {
1247
+ id: this.opts.id,
1248
+ name: this.opts.name ?? this.opts.type,
1249
+ protocol: 'matter',
1250
+ clusters: [
1251
+ {
1252
+ // Relative Humidity Sensor cluster
1253
+ type: 'RelativeHumiditySensor',
1254
+ clusterId: MATTER_CLUSTER_IDS.RelativeHumidityMeasurement,
1255
+ attributes: {
1256
+ currentRelativeHumidity: { read: async () => { const s = await this.getState(); return typeof s.humidity === 'number' ? s.humidity : 0; }, write: undefined },
1257
+ [MATTER_ATTRIBUTE_IDS.RelativeHumidityMeasurement.MeasuredValue]: { read: async () => { const s = await this.getState(); return typeof s.humidity === 'number' ? s.humidity : 0; }, write: undefined },
1258
+ },
1259
+ },
1260
+ {
1261
+ type: 'OnOff',
1262
+ clusterId: MATTER_CLUSTER_IDS.OnOff,
1263
+ attributes: {
1264
+ onOff: { read: async () => { const s = await this.getState(); return !!s?.on; }, write: async (v) => this.setState({ on: !!v }) },
1265
+ [MATTER_ATTRIBUTE_IDS.OnOff.OnOff]: { read: async () => { const s = await this.getState(); return !!s?.on; }, write: async (v) => this.setState({ on: !!v }) },
1266
+ },
1267
+ },
1268
+ ],
1269
+ };
1270
+ }
1271
+ }
1272
+ export class TemperatureSensorDevice extends GenericDevice {
1273
+ createHAPAccessory(api) {
1274
+ return {
1275
+ services: [
1276
+ {
1277
+ type: 'TemperatureSensor',
1278
+ characteristics: {
1279
+ CurrentTemperature: {
1280
+ get: async () => {
1281
+ const s = await this.getState();
1282
+ return typeof s.temperature === 'number' ? s.temperature : 0;
1283
+ },
1284
+ },
1285
+ },
1286
+ },
1287
+ ],
1288
+ };
1289
+ }
1290
+ }
1291
+ // Additional device classes (aliases / specialized variants)
1292
+ export class RelaySwitchDevice extends GenericDevice {
1293
+ }
1294
+ export class RelaySwitch1PMDevice extends GenericDevice {
1295
+ }
1296
+ export class PlugDevice extends GenericDevice {
1297
+ createHAPAccessory(api) {
1298
+ return {
1299
+ services: [
1300
+ {
1301
+ type: 'Outlet',
1302
+ characteristics: {
1303
+ On: {
1304
+ get: async () => {
1305
+ const s = await this.getState();
1306
+ return !!(s && (s.on === true || s.state === 'on'));
1307
+ },
1308
+ set: async (v) => {
1309
+ await this.setState({ on: !!v });
1310
+ },
1311
+ },
1312
+ OutletInUse: {
1313
+ get: async () => {
1314
+ const s = await this.getState();
1315
+ return s && s.inUse ? 1 : 0;
1316
+ },
1317
+ },
1318
+ },
1319
+ },
1320
+ ],
1321
+ };
1322
+ }
1323
+ }
1324
+ export class PlugMiniDevice extends PlugDevice {
1325
+ }
1326
+ export class BlindTiltDevice extends CurtainDevice {
1327
+ }
1328
+ export class Curtain3Device extends CurtainDevice {
1329
+ }
1330
+ export class RollerShadeDevice extends CurtainDevice {
1331
+ }
1332
+ export class Hub2Device extends GenericDevice {
1333
+ }
1334
+ export class MeterDevice extends GenericDevice {
1335
+ createHAPAccessory(api) {
1336
+ return {
1337
+ services: [
1338
+ {
1339
+ type: 'TemperatureSensor',
1340
+ characteristics: {
1341
+ CurrentTemperature: {
1342
+ get: async () => {
1343
+ const s = await this.getState();
1344
+ return typeof s.temperature === 'number' ? s.temperature : 0;
1345
+ },
1346
+ },
1347
+ },
1348
+ },
1349
+ {
1350
+ type: 'HumiditySensor',
1351
+ characteristics: {
1352
+ CurrentRelativeHumidity: {
1353
+ get: async () => {
1354
+ const s = await this.getState();
1355
+ return typeof s.humidity === 'number' ? s.humidity : 0;
1356
+ },
1357
+ },
1358
+ },
1359
+ },
1360
+ ],
1361
+ };
1362
+ }
1363
+ }
1364
+ export class WaterDetectorDevice extends GenericDevice {
1365
+ createHAPAccessory(api) {
1366
+ return {
1367
+ services: [
1368
+ {
1369
+ type: 'LeakSensor',
1370
+ characteristics: {
1371
+ LeakDetected: {
1372
+ get: async () => {
1373
+ const s = await this.getState();
1374
+ return s && s.leak ? 1 : 0;
1375
+ },
1376
+ },
1377
+ },
1378
+ },
1379
+ ],
1380
+ };
1381
+ }
1382
+ }
1383
+ export class SmartFanDevice extends FanDevice {
1384
+ }
1385
+ export class StripLightDevice extends LightStripDevice {
1386
+ }
1387
+ export class WalletFinderDevice extends GenericDevice {
1388
+ }
1389
+ // K10 / WoSweeper variants map to VacuumDevice
1390
+ export class WoSweeperDevice extends VacuumDevice {
1391
+ }
1392
+ export class WoSweeperMiniDevice extends VacuumDevice {
1393
+ }
1394
+ export class WoSweeperMiniProDevice extends VacuumDevice {
1395
+ }
1396
+ //# sourceMappingURL=genericDevice.js.map