@switchbot/homebridge-switchbot 5.0.0-beta.12 → 5.0.0-beta.121

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