@switchbot/homebridge-switchbot 5.0.0-beta.11 → 5.0.0-beta.110

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 (577) hide show
  1. package/.github/ISSUE_TEMPLATE/e2e-verification.md +36 -0
  2. package/.github/workflows/ci.yml +32 -0
  3. package/.github/workflows/manual-e2e.yml +115 -0
  4. package/.github/workflows/release.yml +0 -4
  5. package/CHANGELOG.md +35 -0
  6. package/E2E-VERIFICATION.md +121 -0
  7. package/MIGRATION.md +44 -0
  8. package/README.md +56 -3
  9. package/config.schema.json +170 -14787
  10. package/dist/device-types.d.ts +31 -0
  11. package/dist/device-types.d.ts.map +1 -0
  12. package/dist/device-types.js +216 -0
  13. package/dist/device-types.js.map +1 -0
  14. package/dist/deviceFactory.d.ts +13 -0
  15. package/dist/deviceFactory.d.ts.map +1 -0
  16. package/dist/deviceFactory.js +120 -0
  17. package/dist/deviceFactory.js.map +1 -0
  18. package/dist/devices/deviceBase.d.ts +50 -0
  19. package/dist/devices/deviceBase.d.ts.map +1 -0
  20. package/dist/devices/deviceBase.js +119 -0
  21. package/dist/devices/deviceBase.js.map +1 -0
  22. package/dist/devices/genericDevice.d.ts +298 -0
  23. package/dist/devices/genericDevice.d.ts.map +1 -0
  24. package/dist/devices/genericDevice.js +1067 -0
  25. package/dist/devices/genericDevice.js.map +1 -0
  26. package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
  27. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  28. package/dist/homebridge-ui/endpoints/config.js +104 -0
  29. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  30. package/dist/homebridge-ui/endpoints/credentials.d.ts +6 -0
  31. package/dist/homebridge-ui/endpoints/credentials.d.ts.map +1 -0
  32. package/dist/homebridge-ui/endpoints/credentials.js +65 -0
  33. package/dist/homebridge-ui/endpoints/credentials.js.map +1 -0
  34. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  35. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  36. package/dist/homebridge-ui/endpoints/devices.js +342 -0
  37. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  38. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  39. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  40. package/dist/homebridge-ui/endpoints/discovery.js +171 -0
  41. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  42. package/dist/homebridge-ui/public/css/styles.css +254 -0
  43. package/dist/homebridge-ui/public/index.html +112 -247
  44. package/dist/homebridge-ui/public/js/api.d.ts +19 -0
  45. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  46. package/dist/homebridge-ui/public/js/api.js +178 -0
  47. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  48. package/dist/homebridge-ui/public/js/api.ts +205 -0
  49. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  50. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  51. package/dist/homebridge-ui/public/js/app.js +1581 -0
  52. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  53. package/dist/homebridge-ui/public/js/app.ts +22 -0
  54. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  55. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  56. package/dist/homebridge-ui/public/js/constants.js +2 -0
  57. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  58. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  59. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  60. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  61. package/dist/homebridge-ui/public/js/credentials.js +96 -0
  62. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  63. package/dist/homebridge-ui/public/js/credentials.ts +100 -0
  64. package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
  65. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  66. package/dist/homebridge-ui/public/js/devices-delete.js +206 -0
  67. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  68. package/dist/homebridge-ui/public/js/devices-delete.ts +236 -0
  69. package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
  70. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  71. package/dist/homebridge-ui/public/js/devices.js +81 -0
  72. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  73. package/dist/homebridge-ui/public/js/devices.ts +88 -0
  74. package/dist/homebridge-ui/public/js/discovery.d.ts +3 -0
  75. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  76. package/dist/homebridge-ui/public/js/discovery.js +233 -0
  77. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  78. package/dist/homebridge-ui/public/js/discovery.ts +277 -0
  79. package/dist/homebridge-ui/public/js/logger.d.ts +6 -0
  80. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  81. package/dist/homebridge-ui/public/js/logger.js +12 -0
  82. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  83. package/dist/homebridge-ui/public/js/logger.ts +11 -0
  84. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  85. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  86. package/dist/homebridge-ui/public/js/modal.js +26 -0
  87. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  88. package/dist/homebridge-ui/public/js/modal.ts +28 -0
  89. package/dist/homebridge-ui/public/js/modals.d.ts +2 -0
  90. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  91. package/dist/homebridge-ui/public/js/modals.js +177 -0
  92. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  93. package/dist/homebridge-ui/public/js/modals.ts +200 -0
  94. package/dist/homebridge-ui/public/js/render.d.ts +63 -0
  95. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  96. package/dist/homebridge-ui/public/js/render.js +439 -0
  97. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  98. package/dist/homebridge-ui/public/js/render.ts +510 -0
  99. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  100. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  101. package/dist/homebridge-ui/public/js/toast.js +29 -0
  102. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  103. package/dist/homebridge-ui/public/js/toast.ts +37 -0
  104. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  105. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  106. package/dist/homebridge-ui/public/js/types.js +2 -0
  107. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  108. package/dist/homebridge-ui/public/js/types.ts +26 -0
  109. package/dist/homebridge-ui/server.js +11 -41
  110. package/dist/homebridge-ui/server.js.map +1 -1
  111. package/dist/homebridge-ui/utils/config-parser.d.ts +35 -0
  112. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  113. package/dist/homebridge-ui/utils/config-parser.js +87 -0
  114. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  115. package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
  116. package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
  117. package/dist/homebridge-ui/utils/device-migration.js +111 -0
  118. package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
  119. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  120. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  121. package/dist/homebridge-ui/utils/logger.js +17 -0
  122. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +4 -65
  125. package/dist/index.js.map +1 -1
  126. package/dist/platform.d.ts +35 -0
  127. package/dist/platform.d.ts.map +1 -0
  128. package/dist/platform.js +1567 -0
  129. package/dist/platform.js.map +1 -0
  130. package/dist/settings.d.ts +10 -249
  131. package/dist/settings.d.ts.map +1 -1
  132. package/dist/settings.js +5 -30
  133. package/dist/settings.js.map +1 -1
  134. package/dist/switchbotClient.d.ts +32 -0
  135. package/dist/switchbotClient.d.ts.map +1 -0
  136. package/dist/switchbotClient.js +207 -0
  137. package/dist/switchbotClient.js.map +1 -0
  138. package/dist/utils.d.ts +39 -50
  139. package/dist/utils.d.ts.map +1 -1
  140. package/dist/utils.js +39 -688
  141. package/dist/utils.js.map +1 -1
  142. package/docs/assets/highlight.css +14 -0
  143. package/docs/assets/icons.js +1 -1
  144. package/docs/assets/icons.svg +1 -1
  145. package/docs/assets/main.js +2 -2
  146. package/docs/assets/style.css +3 -3
  147. package/docs/index.html +77 -13
  148. package/docs/variables/default.html +1 -1
  149. package/eslint.config.js +7 -8
  150. package/nodemon.json +2 -2
  151. package/package.json +35 -32
  152. package/scripts/build-ui.js +36 -0
  153. package/scripts/e2e/README.md +25 -0
  154. package/scripts/e2e/curtain-e2e.sh +70 -0
  155. package/scripts/e2e/fan-e2e.sh +75 -0
  156. package/scripts/e2e/light-advanced-e2e.sh +97 -0
  157. package/scripts/e2e/light-e2e.sh +75 -0
  158. package/scripts/e2e/list-accessories.sh +19 -0
  159. package/scripts/e2e/lock-e2e.sh +65 -0
  160. package/scripts/free-dev-ports.mjs +105 -0
  161. package/scripts/generate-matter-maps.js +60 -0
  162. package/scripts/run-e2e-local.sh +14 -0
  163. package/scripts/sync-device-types.mjs +28 -0
  164. package/src/device-types.ts +230 -0
  165. package/src/deviceFactory.ts +163 -0
  166. package/src/devices/deviceBase.ts +141 -0
  167. package/src/devices/genericDevice.ts +998 -0
  168. package/src/homebridge-ui/endpoints/config.ts +123 -0
  169. package/src/homebridge-ui/endpoints/credentials.ts +74 -0
  170. package/src/homebridge-ui/endpoints/devices.ts +380 -0
  171. package/src/homebridge-ui/endpoints/discovery.ts +191 -0
  172. package/src/homebridge-ui/public/css/styles.css +254 -0
  173. package/src/homebridge-ui/public/index.html +112 -247
  174. package/src/homebridge-ui/public/js/api.ts +205 -0
  175. package/src/homebridge-ui/public/js/app.ts +22 -0
  176. package/src/homebridge-ui/public/js/constants.ts +1 -0
  177. package/src/homebridge-ui/public/js/credentials.ts +100 -0
  178. package/src/homebridge-ui/public/js/devices-delete.ts +236 -0
  179. package/src/homebridge-ui/public/js/devices.ts +88 -0
  180. package/src/homebridge-ui/public/js/discovery.ts +277 -0
  181. package/src/homebridge-ui/public/js/logger.ts +11 -0
  182. package/src/homebridge-ui/public/js/modal.ts +28 -0
  183. package/src/homebridge-ui/public/js/modals.ts +200 -0
  184. package/src/homebridge-ui/public/js/render.ts +510 -0
  185. package/src/homebridge-ui/public/js/toast.ts +37 -0
  186. package/src/homebridge-ui/public/js/types.ts +26 -0
  187. package/src/homebridge-ui/server.ts +11 -43
  188. package/src/homebridge-ui/utils/config-parser.ts +108 -0
  189. package/src/homebridge-ui/utils/device-migration.ts +144 -0
  190. package/src/homebridge-ui/utils/logger.ts +17 -0
  191. package/src/index.ts +4 -66
  192. package/src/platform.ts +1566 -0
  193. package/src/settings.ts +12 -277
  194. package/src/switchbotClient.ts +216 -0
  195. package/src/utils.ts +45 -713
  196. package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
  197. package/test/accessory-restore.spec.ts +73 -0
  198. package/test/device-mapping.spec.ts +37 -0
  199. package/test/deviceFactory.spec.ts +18 -0
  200. package/test/e2e/run-e2e.spec.ts +50 -0
  201. package/test/fan-swing.spec.ts +29 -0
  202. package/test/helpers/matter-harness.ts +53 -0
  203. package/test/lock-users.spec.ts +44 -0
  204. package/test/matter-childbridge.spec.ts +55 -0
  205. package/test/matter-descriptors.spec.ts +97 -0
  206. package/test/matter-device-state.spec.ts +101 -0
  207. package/test/matter-integration.spec.ts +70 -0
  208. package/test/platform.integration.spec.ts +55 -0
  209. package/test/switchbot-client-debounce.spec.ts +131 -0
  210. package/test/switchbot-client-openapi.spec.ts +56 -0
  211. package/test/switchbotClient.spec.ts +10 -0
  212. package/test/utils.spec.ts +20 -0
  213. package/todo/COMPLETED.md +189 -0
  214. package/todo/README.md +59 -0
  215. package/todo/TODO.md +205 -0
  216. package/todo/mockups/filtering-sorting.svg +112 -0
  217. package/todo/mockups/modal-switchbot-theme.svg +66 -0
  218. package/todo/mockups/signal-strength-visualization.svg +94 -0
  219. package/vitest.config.ts +7 -0
  220. package/coverage/base.css +0 -224
  221. package/coverage/block-navigation.js +0 -87
  222. package/coverage/clover.xml +0 -15847
  223. package/coverage/coverage-final.json +0 -42
  224. package/coverage/docs/assets/dmt/dmt-component-data.js.html +0 -85
  225. package/coverage/docs/assets/dmt/dmt-components.js.html +0 -286
  226. package/coverage/docs/assets/dmt/index.html +0 -131
  227. package/coverage/docs/assets/hierarchy.js.html +0 -85
  228. package/coverage/docs/assets/icons.js.html +0 -136
  229. package/coverage/docs/assets/index.html +0 -146
  230. package/coverage/docs/assets/main.js.html +0 -265
  231. package/coverage/favicon.png +0 -0
  232. package/coverage/index.html +0 -191
  233. package/coverage/prettify.css +0 -1
  234. package/coverage/prettify.js +0 -2
  235. package/coverage/sort-arrow-sprite.png +0 -0
  236. package/coverage/sorter.js +0 -196
  237. package/coverage/src/device/blindtilt.ts.html +0 -3238
  238. package/coverage/src/device/bot.ts.html +0 -2803
  239. package/coverage/src/device/ceilinglight.ts.html +0 -2338
  240. package/coverage/src/device/colorbulb.ts.html +0 -2824
  241. package/coverage/src/device/contact.ts.html +0 -1465
  242. package/coverage/src/device/curtain.ts.html +0 -2869
  243. package/coverage/src/device/device.ts.html +0 -2500
  244. package/coverage/src/device/fan.ts.html +0 -2242
  245. package/coverage/src/device/hub.ts.html +0 -1408
  246. package/coverage/src/device/humidifier.ts.html +0 -2116
  247. package/coverage/src/device/index.html +0 -416
  248. package/coverage/src/device/iosensor.ts.html +0 -1375
  249. package/coverage/src/device/lightstrip.ts.html +0 -2617
  250. package/coverage/src/device/lock.ts.html +0 -1963
  251. package/coverage/src/device/meter.ts.html +0 -1372
  252. package/coverage/src/device/meterplus.ts.html +0 -1384
  253. package/coverage/src/device/meterpro.ts.html +0 -1618
  254. package/coverage/src/device/motion.ts.html +0 -1264
  255. package/coverage/src/device/plug.ts.html +0 -1372
  256. package/coverage/src/device/relayswitch.ts.html +0 -2284
  257. package/coverage/src/device/robotvacuumcleaner.ts.html +0 -1810
  258. package/coverage/src/device/waterdetector.ts.html +0 -1294
  259. package/coverage/src/homebridge-ui/index.html +0 -116
  260. package/coverage/src/homebridge-ui/server.ts.html +0 -229
  261. package/coverage/src/index.html +0 -161
  262. package/coverage/src/index.ts.html +0 -124
  263. package/coverage/src/irdevice/airconditioner.ts.html +0 -1687
  264. package/coverage/src/irdevice/airpurifier.ts.html +0 -844
  265. package/coverage/src/irdevice/camera.ts.html +0 -475
  266. package/coverage/src/irdevice/fan.ts.html +0 -766
  267. package/coverage/src/irdevice/index.html +0 -251
  268. package/coverage/src/irdevice/irdevice.ts.html +0 -1117
  269. package/coverage/src/irdevice/light.ts.html +0 -826
  270. package/coverage/src/irdevice/other.ts.html +0 -2458
  271. package/coverage/src/irdevice/tv.ts.html +0 -1222
  272. package/coverage/src/irdevice/vacuumcleaner.ts.html +0 -466
  273. package/coverage/src/irdevice/waterheater.ts.html +0 -469
  274. package/coverage/src/platform.ts.html +0 -8776
  275. package/coverage/src/settings.ts.html +0 -934
  276. package/coverage/src/utils.ts.html +0 -2092
  277. package/dist/devices-hap/airpurifier.d.ts +0 -54
  278. package/dist/devices-hap/airpurifier.d.ts.map +0 -1
  279. package/dist/devices-hap/airpurifier.js +0 -527
  280. package/dist/devices-hap/airpurifier.js.map +0 -1
  281. package/dist/devices-hap/blindtilt.d.ts +0 -90
  282. package/dist/devices-hap/blindtilt.d.ts.map +0 -1
  283. package/dist/devices-hap/blindtilt.js +0 -974
  284. package/dist/devices-hap/blindtilt.js.map +0 -1
  285. package/dist/devices-hap/bot.d.ts +0 -102
  286. package/dist/devices-hap/bot.d.ts.map +0 -1
  287. package/dist/devices-hap/bot.js +0 -811
  288. package/dist/devices-hap/bot.js.map +0 -1
  289. package/dist/devices-hap/ceilinglight.d.ts +0 -85
  290. package/dist/devices-hap/ceilinglight.d.ts.map +0 -1
  291. package/dist/devices-hap/ceilinglight.js +0 -701
  292. package/dist/devices-hap/ceilinglight.js.map +0 -1
  293. package/dist/devices-hap/colorbulb.d.ts +0 -88
  294. package/dist/devices-hap/colorbulb.d.ts.map +0 -1
  295. package/dist/devices-hap/colorbulb.js +0 -881
  296. package/dist/devices-hap/colorbulb.js.map +0 -1
  297. package/dist/devices-hap/contact.d.ts +0 -44
  298. package/dist/devices-hap/contact.d.ts.map +0 -1
  299. package/dist/devices-hap/contact.js +0 -409
  300. package/dist/devices-hap/contact.js.map +0 -1
  301. package/dist/devices-hap/curtain.d.ts +0 -73
  302. package/dist/devices-hap/curtain.d.ts.map +0 -1
  303. package/dist/devices-hap/curtain.js +0 -869
  304. package/dist/devices-hap/curtain.js.map +0 -1
  305. package/dist/devices-hap/device.d.ts +0 -98
  306. package/dist/devices-hap/device.d.ts.map +0 -1
  307. package/dist/devices-hap/device.js +0 -749
  308. package/dist/devices-hap/device.js.map +0 -1
  309. package/dist/devices-hap/fan.d.ts +0 -69
  310. package/dist/devices-hap/fan.d.ts.map +0 -1
  311. package/dist/devices-hap/fan.js +0 -649
  312. package/dist/devices-hap/fan.js.map +0 -1
  313. package/dist/devices-hap/hub.d.ts +0 -37
  314. package/dist/devices-hap/hub.d.ts.map +0 -1
  315. package/dist/devices-hap/hub.js +0 -392
  316. package/dist/devices-hap/hub.js.map +0 -1
  317. package/dist/devices-hap/humidifier.d.ts +0 -68
  318. package/dist/devices-hap/humidifier.d.ts.map +0 -1
  319. package/dist/devices-hap/humidifier.js +0 -628
  320. package/dist/devices-hap/humidifier.js.map +0 -1
  321. package/dist/devices-hap/iosensor.d.ts +0 -42
  322. package/dist/devices-hap/iosensor.d.ts.map +0 -1
  323. package/dist/devices-hap/iosensor.js +0 -382
  324. package/dist/devices-hap/iosensor.js.map +0 -1
  325. package/dist/devices-hap/lightstrip.d.ts +0 -79
  326. package/dist/devices-hap/lightstrip.d.ts.map +0 -1
  327. package/dist/devices-hap/lightstrip.js +0 -797
  328. package/dist/devices-hap/lightstrip.js.map +0 -1
  329. package/dist/devices-hap/lock.d.ts +0 -53
  330. package/dist/devices-hap/lock.d.ts.map +0 -1
  331. package/dist/devices-hap/lock.js +0 -561
  332. package/dist/devices-hap/lock.js.map +0 -1
  333. package/dist/devices-hap/meter.d.ts +0 -37
  334. package/dist/devices-hap/meter.d.ts.map +0 -1
  335. package/dist/devices-hap/meter.js +0 -379
  336. package/dist/devices-hap/meter.js.map +0 -1
  337. package/dist/devices-hap/meterplus.d.ts +0 -42
  338. package/dist/devices-hap/meterplus.d.ts.map +0 -1
  339. package/dist/devices-hap/meterplus.js +0 -384
  340. package/dist/devices-hap/meterplus.js.map +0 -1
  341. package/dist/devices-hap/meterpro.d.ts +0 -43
  342. package/dist/devices-hap/meterpro.d.ts.map +0 -1
  343. package/dist/devices-hap/meterpro.js +0 -468
  344. package/dist/devices-hap/meterpro.js.map +0 -1
  345. package/dist/devices-hap/motion.d.ts +0 -42
  346. package/dist/devices-hap/motion.d.ts.map +0 -1
  347. package/dist/devices-hap/motion.js +0 -345
  348. package/dist/devices-hap/motion.js.map +0 -1
  349. package/dist/devices-hap/plug.d.ts +0 -49
  350. package/dist/devices-hap/plug.d.ts.map +0 -1
  351. package/dist/devices-hap/plug.js +0 -395
  352. package/dist/devices-hap/plug.js.map +0 -1
  353. package/dist/devices-hap/relayswitch.d.ts +0 -96
  354. package/dist/devices-hap/relayswitch.d.ts.map +0 -1
  355. package/dist/devices-hap/relayswitch.js +0 -642
  356. package/dist/devices-hap/relayswitch.js.map +0 -1
  357. package/dist/devices-hap/robotvacuumcleaner.d.ts +0 -54
  358. package/dist/devices-hap/robotvacuumcleaner.d.ts.map +0 -1
  359. package/dist/devices-hap/robotvacuumcleaner.js +0 -523
  360. package/dist/devices-hap/robotvacuumcleaner.js.map +0 -1
  361. package/dist/devices-hap/waterdetector.d.ts +0 -41
  362. package/dist/devices-hap/waterdetector.d.ts.map +0 -1
  363. package/dist/devices-hap/waterdetector.js +0 -356
  364. package/dist/devices-hap/waterdetector.js.map +0 -1
  365. package/dist/devices-matter/BaseMatterAccessory.d.ts +0 -63
  366. package/dist/devices-matter/BaseMatterAccessory.d.ts.map +0 -1
  367. package/dist/devices-matter/BaseMatterAccessory.js +0 -100
  368. package/dist/devices-matter/BaseMatterAccessory.js.map +0 -1
  369. package/dist/devices-matter/ColorLightAccessory.d.ts +0 -20
  370. package/dist/devices-matter/ColorLightAccessory.d.ts.map +0 -1
  371. package/dist/devices-matter/ColorLightAccessory.js +0 -95
  372. package/dist/devices-matter/ColorLightAccessory.js.map +0 -1
  373. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts +0 -18
  374. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +0 -1
  375. package/dist/devices-matter/ColorTemperatureLightAccessory.js +0 -78
  376. package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +0 -1
  377. package/dist/devices-matter/ContactSensorAccessory.d.ts +0 -12
  378. package/dist/devices-matter/ContactSensorAccessory.d.ts.map +0 -1
  379. package/dist/devices-matter/ContactSensorAccessory.js +0 -34
  380. package/dist/devices-matter/ContactSensorAccessory.js.map +0 -1
  381. package/dist/devices-matter/DimmableLightAccessory.d.ts +0 -58
  382. package/dist/devices-matter/DimmableLightAccessory.d.ts.map +0 -1
  383. package/dist/devices-matter/DimmableLightAccessory.js +0 -167
  384. package/dist/devices-matter/DimmableLightAccessory.js.map +0 -1
  385. package/dist/devices-matter/DoorLockAccessory.d.ts +0 -14
  386. package/dist/devices-matter/DoorLockAccessory.d.ts.map +0 -1
  387. package/dist/devices-matter/DoorLockAccessory.js +0 -50
  388. package/dist/devices-matter/DoorLockAccessory.js.map +0 -1
  389. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts +0 -21
  390. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +0 -1
  391. package/dist/devices-matter/ExtendedColorLightAccessory.js +0 -107
  392. package/dist/devices-matter/ExtendedColorLightAccessory.js.map +0 -1
  393. package/dist/devices-matter/FanAccessory.d.ts +0 -16
  394. package/dist/devices-matter/FanAccessory.d.ts.map +0 -1
  395. package/dist/devices-matter/FanAccessory.js +0 -81
  396. package/dist/devices-matter/FanAccessory.js.map +0 -1
  397. package/dist/devices-matter/HumiditySensorAccessory.d.ts +0 -12
  398. package/dist/devices-matter/HumiditySensorAccessory.d.ts.map +0 -1
  399. package/dist/devices-matter/HumiditySensorAccessory.js +0 -34
  400. package/dist/devices-matter/HumiditySensorAccessory.js.map +0 -1
  401. package/dist/devices-matter/LeakSensorAccessory.d.ts +0 -12
  402. package/dist/devices-matter/LeakSensorAccessory.d.ts.map +0 -1
  403. package/dist/devices-matter/LeakSensorAccessory.js +0 -33
  404. package/dist/devices-matter/LeakSensorAccessory.js.map +0 -1
  405. package/dist/devices-matter/LightSensorAccessory.d.ts +0 -12
  406. package/dist/devices-matter/LightSensorAccessory.d.ts.map +0 -1
  407. package/dist/devices-matter/LightSensorAccessory.js +0 -34
  408. package/dist/devices-matter/LightSensorAccessory.js.map +0 -1
  409. package/dist/devices-matter/OccupancySensorAccessory.d.ts +0 -12
  410. package/dist/devices-matter/OccupancySensorAccessory.d.ts.map +0 -1
  411. package/dist/devices-matter/OccupancySensorAccessory.js +0 -39
  412. package/dist/devices-matter/OccupancySensorAccessory.js.map +0 -1
  413. package/dist/devices-matter/OnOffLightAccessory.d.ts +0 -38
  414. package/dist/devices-matter/OnOffLightAccessory.d.ts.map +0 -1
  415. package/dist/devices-matter/OnOffLightAccessory.js +0 -118
  416. package/dist/devices-matter/OnOffLightAccessory.js.map +0 -1
  417. package/dist/devices-matter/OnOffOutletAccessory.d.ts +0 -12
  418. package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +0 -1
  419. package/dist/devices-matter/OnOffOutletAccessory.js +0 -40
  420. package/dist/devices-matter/OnOffOutletAccessory.js.map +0 -1
  421. package/dist/devices-matter/OnOffSwitchAccessory.d.ts +0 -14
  422. package/dist/devices-matter/OnOffSwitchAccessory.d.ts.map +0 -1
  423. package/dist/devices-matter/OnOffSwitchAccessory.js +0 -42
  424. package/dist/devices-matter/OnOffSwitchAccessory.js.map +0 -1
  425. package/dist/devices-matter/RoboticVacuumAccessory.d.ts +0 -68
  426. package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +0 -1
  427. package/dist/devices-matter/RoboticVacuumAccessory.js +0 -334
  428. package/dist/devices-matter/RoboticVacuumAccessory.js.map +0 -1
  429. package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts +0 -11
  430. package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts.map +0 -1
  431. package/dist/devices-matter/SmokeCOAlarmAccessory.js +0 -49
  432. package/dist/devices-matter/SmokeCOAlarmAccessory.js.map +0 -1
  433. package/dist/devices-matter/TemperatureSensorAccessory.d.ts +0 -12
  434. package/dist/devices-matter/TemperatureSensorAccessory.d.ts.map +0 -1
  435. package/dist/devices-matter/TemperatureSensorAccessory.js +0 -36
  436. package/dist/devices-matter/TemperatureSensorAccessory.js.map +0 -1
  437. package/dist/devices-matter/ThermostatAccessory.d.ts +0 -19
  438. package/dist/devices-matter/ThermostatAccessory.d.ts.map +0 -1
  439. package/dist/devices-matter/ThermostatAccessory.js +0 -95
  440. package/dist/devices-matter/ThermostatAccessory.js.map +0 -1
  441. package/dist/devices-matter/VenetianBlindAccessory.d.ts +0 -19
  442. package/dist/devices-matter/VenetianBlindAccessory.d.ts.map +0 -1
  443. package/dist/devices-matter/VenetianBlindAccessory.js +0 -99
  444. package/dist/devices-matter/VenetianBlindAccessory.js.map +0 -1
  445. package/dist/devices-matter/WindowBlindAccessory.d.ts +0 -17
  446. package/dist/devices-matter/WindowBlindAccessory.d.ts.map +0 -1
  447. package/dist/devices-matter/WindowBlindAccessory.js +0 -80
  448. package/dist/devices-matter/WindowBlindAccessory.js.map +0 -1
  449. package/dist/devices-matter/custom/PowerStripAccessory.d.ts +0 -97
  450. package/dist/devices-matter/custom/PowerStripAccessory.d.ts.map +0 -1
  451. package/dist/devices-matter/custom/PowerStripAccessory.js +0 -265
  452. package/dist/devices-matter/custom/PowerStripAccessory.js.map +0 -1
  453. package/dist/devices-matter/custom/index.d.ts +0 -8
  454. package/dist/devices-matter/custom/index.d.ts.map +0 -1
  455. package/dist/devices-matter/custom/index.js +0 -8
  456. package/dist/devices-matter/custom/index.js.map +0 -1
  457. package/dist/devices-matter/index.d.ts +0 -29
  458. package/dist/devices-matter/index.d.ts.map +0 -1
  459. package/dist/devices-matter/index.js +0 -28
  460. package/dist/devices-matter/index.js.map +0 -1
  461. package/dist/index.test.d.ts +0 -2
  462. package/dist/index.test.d.ts.map +0 -1
  463. package/dist/index.test.js +0 -19
  464. package/dist/index.test.js.map +0 -1
  465. package/dist/irdevice/airconditioner.d.ts +0 -61
  466. package/dist/irdevice/airconditioner.d.ts.map +0 -1
  467. package/dist/irdevice/airconditioner.js +0 -472
  468. package/dist/irdevice/airconditioner.js.map +0 -1
  469. package/dist/irdevice/airpurifier.d.ts +0 -50
  470. package/dist/irdevice/airpurifier.d.ts.map +0 -1
  471. package/dist/irdevice/airpurifier.js +0 -213
  472. package/dist/irdevice/airpurifier.js.map +0 -1
  473. package/dist/irdevice/camera.d.ts +0 -32
  474. package/dist/irdevice/camera.d.ts.map +0 -1
  475. package/dist/irdevice/camera.js +0 -107
  476. package/dist/irdevice/camera.js.map +0 -1
  477. package/dist/irdevice/fan.d.ts +0 -36
  478. package/dist/irdevice/fan.d.ts.map +0 -1
  479. package/dist/irdevice/fan.js +0 -200
  480. package/dist/irdevice/fan.js.map +0 -1
  481. package/dist/irdevice/irdevice.d.ts +0 -68
  482. package/dist/irdevice/irdevice.d.ts.map +0 -1
  483. package/dist/irdevice/irdevice.js +0 -298
  484. package/dist/irdevice/irdevice.js.map +0 -1
  485. package/dist/irdevice/light.d.ts +0 -36
  486. package/dist/irdevice/light.d.ts.map +0 -1
  487. package/dist/irdevice/light.js +0 -206
  488. package/dist/irdevice/light.js.map +0 -1
  489. package/dist/irdevice/other.d.ts +0 -57
  490. package/dist/irdevice/other.d.ts.map +0 -1
  491. package/dist/irdevice/other.js +0 -778
  492. package/dist/irdevice/other.js.map +0 -1
  493. package/dist/irdevice/tv.d.ts +0 -45
  494. package/dist/irdevice/tv.d.ts.map +0 -1
  495. package/dist/irdevice/tv.js +0 -327
  496. package/dist/irdevice/tv.js.map +0 -1
  497. package/dist/irdevice/vacuumcleaner.d.ts +0 -28
  498. package/dist/irdevice/vacuumcleaner.d.ts.map +0 -1
  499. package/dist/irdevice/vacuumcleaner.js +0 -104
  500. package/dist/irdevice/vacuumcleaner.js.map +0 -1
  501. package/dist/irdevice/waterheater.d.ts +0 -30
  502. package/dist/irdevice/waterheater.d.ts.map +0 -1
  503. package/dist/irdevice/waterheater.js +0 -105
  504. package/dist/irdevice/waterheater.js.map +0 -1
  505. package/dist/platform-hap.d.ts +0 -149
  506. package/dist/platform-hap.d.ts.map +0 -1
  507. package/dist/platform-hap.js +0 -2861
  508. package/dist/platform-hap.js.map +0 -1
  509. package/dist/platform-matter.d.ts +0 -120
  510. package/dist/platform-matter.d.ts.map +0 -1
  511. package/dist/platform-matter.js +0 -984
  512. package/dist/platform-matter.js.map +0 -1
  513. package/dist/verifyconfig.test.d.ts +0 -2
  514. package/dist/verifyconfig.test.d.ts.map +0 -1
  515. package/dist/verifyconfig.test.js +0 -167
  516. package/dist/verifyconfig.test.js.map +0 -1
  517. package/src/custom.d.ts +0 -7
  518. package/src/devices-hap/airpurifier.ts +0 -563
  519. package/src/devices-hap/blindtilt.ts +0 -1049
  520. package/src/devices-hap/bot.ts +0 -900
  521. package/src/devices-hap/ceilinglight.ts +0 -742
  522. package/src/devices-hap/colorbulb.ts +0 -904
  523. package/src/devices-hap/contact.ts +0 -457
  524. package/src/devices-hap/curtain.ts +0 -944
  525. package/src/devices-hap/device.ts +0 -811
  526. package/src/devices-hap/fan.ts +0 -711
  527. package/src/devices-hap/hub.ts +0 -439
  528. package/src/devices-hap/humidifier.ts +0 -669
  529. package/src/devices-hap/iosensor.ts +0 -427
  530. package/src/devices-hap/lightstrip.ts +0 -836
  531. package/src/devices-hap/lock.ts +0 -620
  532. package/src/devices-hap/meter.ts +0 -426
  533. package/src/devices-hap/meterplus.ts +0 -430
  534. package/src/devices-hap/meterpro.ts +0 -522
  535. package/src/devices-hap/motion.ts +0 -390
  536. package/src/devices-hap/plug.ts +0 -423
  537. package/src/devices-hap/relayswitch.ts +0 -727
  538. package/src/devices-hap/robotvacuumcleaner.ts +0 -568
  539. package/src/devices-hap/waterdetector.ts +0 -400
  540. package/src/devices-matter/BaseMatterAccessory.ts +0 -131
  541. package/src/devices-matter/ColorLightAccessory.ts +0 -110
  542. package/src/devices-matter/ColorTemperatureLightAccessory.ts +0 -92
  543. package/src/devices-matter/ContactSensorAccessory.ts +0 -41
  544. package/src/devices-matter/DimmableLightAccessory.ts +0 -192
  545. package/src/devices-matter/DoorLockAccessory.ts +0 -60
  546. package/src/devices-matter/ExtendedColorLightAccessory.ts +0 -123
  547. package/src/devices-matter/FanAccessory.ts +0 -95
  548. package/src/devices-matter/HumiditySensorAccessory.ts +0 -41
  549. package/src/devices-matter/LeakSensorAccessory.ts +0 -40
  550. package/src/devices-matter/LightSensorAccessory.ts +0 -41
  551. package/src/devices-matter/OccupancySensorAccessory.ts +0 -48
  552. package/src/devices-matter/OnOffLightAccessory.ts +0 -133
  553. package/src/devices-matter/OnOffOutletAccessory.ts +0 -46
  554. package/src/devices-matter/OnOffSwitchAccessory.ts +0 -51
  555. package/src/devices-matter/RoboticVacuumAccessory.ts +0 -407
  556. package/src/devices-matter/SmokeCOAlarmAccessory.ts +0 -59
  557. package/src/devices-matter/TemperatureSensorAccessory.ts +0 -43
  558. package/src/devices-matter/ThermostatAccessory.ts +0 -110
  559. package/src/devices-matter/VenetianBlindAccessory.ts +0 -115
  560. package/src/devices-matter/WindowBlindAccessory.ts +0 -92
  561. package/src/devices-matter/custom/PowerStripAccessory.ts +0 -309
  562. package/src/devices-matter/custom/index.ts +0 -8
  563. package/src/devices-matter/index.ts +0 -29
  564. package/src/index.test.ts +0 -24
  565. package/src/irdevice/airconditioner.ts +0 -533
  566. package/src/irdevice/airpurifier.ts +0 -252
  567. package/src/irdevice/camera.ts +0 -129
  568. package/src/irdevice/fan.ts +0 -226
  569. package/src/irdevice/irdevice.ts +0 -344
  570. package/src/irdevice/light.ts +0 -246
  571. package/src/irdevice/other.ts +0 -790
  572. package/src/irdevice/tv.ts +0 -378
  573. package/src/irdevice/vacuumcleaner.ts +0 -126
  574. package/src/irdevice/waterheater.ts +0 -127
  575. package/src/platform-hap.ts +0 -2997
  576. package/src/platform-matter.ts +0 -1110
  577. package/src/verifyconfig.test.ts +0 -197
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { SwitchBotHAPPlatform, SwitchBotMatterPlatform } from '../src/platform'
3
+
4
+ const log = { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }
5
+
6
+ describe('accessory restore', () => {
7
+ it('reuses restored HAP accessory by context.deviceId and avoids re-register', async () => {
8
+ const registered: any[] = []
9
+ const hap = { uuid: { generate: (s: string) => `uuid-${s}` }, Service: {}, Characteristic: {} }
10
+
11
+ function PlatformAccessory(name: string, uuid: string) {
12
+ this.displayName = name
13
+ this.UUID = uuid
14
+ this.context = {}
15
+ this.getService = () => undefined
16
+ this.addService = () => undefined
17
+ }
18
+
19
+ const api: any = {
20
+ hap,
21
+ platformAccessory: PlatformAccessory as any,
22
+ registerPlatformAccessories: (_p: string, _n: string, accs: any[]) => registered.push(...accs),
23
+ on: (_ev: string, cb: Function) => {},
24
+ }
25
+
26
+ const platform = new SwitchBotHAPPlatform(log as any, { devices: [{ id: 'dev1', type: 'light', name: 'NewName' }] }, api)
27
+
28
+ // Simulate restored accessory with different uuid but correct deviceId in context
29
+ const restored = new (api.platformAccessory)('OldName', 'old-uuid')
30
+ restored.context = { deviceId: 'dev1', type: 'light' }
31
+ platform.configureAccessory(restored)
32
+
33
+ // Load devices should reuse restored accessory by context.deviceId
34
+ await (platform as any).loadDevices()
35
+
36
+ // No new registration for this device because restored accessory was reused
37
+ expect(registered.length).toBe(0)
38
+ // The restored accessory should remain in the platform's accessory map
39
+ const keys = Array.from((platform as any).accessories.keys())
40
+ expect(keys).toContain('old-uuid')
41
+ const acc = (platform as any).accessories.get('old-uuid')
42
+ expect(acc.context.deviceId).toBe('dev1')
43
+ })
44
+
45
+ it('reuses restored Matter accessory by context.deviceId when registering', async () => {
46
+ const captured: any[] = []
47
+ const matter = { uuid: { generate: (s: string) => `uuid-${s}` }, clusterNames: {} }
48
+ const api: any = {
49
+ matter,
50
+ isMatterAvailable: () => true,
51
+ isMatterEnabled: () => true,
52
+ on: (_ev: string, cb: Function) => {},
53
+ }
54
+
55
+ const platform = new SwitchBotMatterPlatform(log as any, { devices: [{ id: 'm1', type: 'light', name: 'M1' }] }, api)
56
+
57
+ // Simulate restored Matter accessory with a different uuid but matching deviceId
58
+ const restored = { uuid: 'restored-uuid', displayName: 'OldM', context: { deviceId: 'm1', type: 'light' } }
59
+ ;(platform as any).accessories.set(restored.uuid, restored)
60
+
61
+ // stub matter.registerPlatformAccessories to capture what is registered
62
+ api.matter.registerPlatformAccessories = async (_p: string, _n: string, accs: any[]) => captured.push(...accs)
63
+
64
+ // call registerMatterAccessories
65
+ await (platform as any).registerMatterAccessories()
66
+
67
+ // The restored accessory should still be present and associated with deviceId
68
+ const keys = Array.from((platform as any).accessories.keys())
69
+ expect(keys).toContain(restored.uuid)
70
+ const acc = (platform as any).accessories.get(restored.uuid)
71
+ expect(acc.context.deviceId).toBe('m1')
72
+ })
73
+ })
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createDevice } from '../src/deviceFactory'
3
+
4
+ const DEVICE_TYPES = [
5
+ 'bot',
6
+ 'curtain',
7
+ 'fan',
8
+ 'light',
9
+ 'lightstrip',
10
+ 'motion',
11
+ 'contact',
12
+ 'vacuum',
13
+ 'lock',
14
+ 'humidifier',
15
+ 'temperature',
16
+ ]
17
+
18
+ describe('device mapping', () => {
19
+ it('maps known device types to device descriptors (HAP)', async () => {
20
+ for (const t of DEVICE_TYPES) {
21
+ const created = await createDevice({ id: `dev-${t}`, type: t, name: `Name-${t}` }, {}, false)
22
+ expect(created).toHaveProperty('instance')
23
+ expect(created).toHaveProperty('createAccessory')
24
+ const acc = created.createAccessory?.(undefined)
25
+ expect(acc).toBeDefined()
26
+ // descriptor should include services for HAP devices
27
+ if (created.protocol === 'hap') {
28
+ expect(acc).toHaveProperty('services')
29
+ }
30
+ }
31
+ })
32
+
33
+ it('returns matter protocol when requested', async () => {
34
+ const created = await createDevice({ id: 'm1', type: 'light', name: 'M1' }, { preferMatter: true, enableMatter: true }, true)
35
+ expect(created.protocol).toBe('matter')
36
+ })
37
+ })
@@ -0,0 +1,18 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createDevice } from '../src/deviceFactory'
3
+
4
+ describe('deviceFactory', () => {
5
+ it('creates HAP device descriptor for light', async () => {
6
+ const created = await createDevice({ id: 'dev1', type: 'light', name: 'L1' }, {}, false)
7
+ expect(created).toHaveProperty('instance')
8
+ expect(created).toHaveProperty('createAccessory')
9
+ expect(created.protocol).toBe('hap')
10
+ const acc = created.createAccessory?.(undefined)
11
+ expect(acc).toHaveProperty('services')
12
+ })
13
+
14
+ it('creates Matter descriptor when useMatter=true', async () => {
15
+ const created = await createDevice({ id: 'dev2', type: 'light', name: 'L2' }, { preferMatter: true, enableMatter: true }, true)
16
+ expect(created.protocol).toBe('matter')
17
+ })
18
+ })
@@ -0,0 +1,50 @@
1
+ import { describe, test } from 'vitest'
2
+ import { promisify } from 'util'
3
+ import { execFile } from 'child_process'
4
+ import path from 'path'
5
+
6
+ const execFileP = promisify(execFile)
7
+
8
+ describe('E2E scripts (manual-run harness)', () => {
9
+ if (!process.env.RUN_E2E) {
10
+ test.skip('E2E disabled - set RUN_E2E=true to enable', () => {})
11
+ return
12
+ }
13
+
14
+ test('run configured E2E scripts sequentially', async () => {
15
+ const repoRoot = path.resolve(__dirname, '../../')
16
+ const scriptsDir = path.join(repoRoot, 'scripts', 'e2e')
17
+
18
+ const scriptsToRun: { envVar: string; script: string }[] = [
19
+ { envVar: 'LIGHT_ACCESSORY_ID', script: 'light-e2e.sh' },
20
+ { envVar: 'FAN_ACCESSORY_ID', script: 'fan-e2e.sh' },
21
+ { envVar: 'CURTAIN_ACCESSORY_ID', script: 'curtain-e2e.sh' },
22
+ { envVar: 'LOCK_ACCESSORY_ID', script: 'lock-e2e.sh' },
23
+ ]
24
+
25
+ for (const s of scriptsToRun) {
26
+ if (!process.env[s.envVar]) {
27
+ // skip script if no accessory id configured
28
+ // eslint-disable-next-line no-console
29
+ console.log(`Skipping ${s.script} because ${s.envVar} not set`)
30
+ continue
31
+ }
32
+
33
+ const scriptPath = path.join(scriptsDir, s.script)
34
+ // Forward environment so scripts can read HB_URL / HB_TOKEN / <ACCESSORY_ID>
35
+ const env = Object.assign({}, process.env)
36
+
37
+ try {
38
+ // eslint-disable-next-line no-console
39
+ console.log(`Running ${s.script}...`)
40
+ const { stdout, stderr } = await execFileP('bash', [scriptPath], { env })
41
+ // eslint-disable-next-line no-console
42
+ console.log(stdout)
43
+ if (stderr) console.error(stderr)
44
+ } catch (e: any) {
45
+ // bubble up as test failure
46
+ throw new Error(`Script ${s.script} failed: ${e.message}`)
47
+ }
48
+ }
49
+ })
50
+ })
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { FanDevice } from '../src/devices/genericDevice'
3
+
4
+ describe('Fan swing commands', () => {
5
+ it('sends setSwing on boolean swing', async () => {
6
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
7
+ const d = new FanDevice({ id: 'fan1', type: 'fan', name: 'F' }, { _client: mockClient } as any)
8
+ await d.init()
9
+ const res = await d.setState({ swing: true })
10
+ expect(mockClient.setDeviceState).toHaveBeenCalled()
11
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'setSwing', parameter: 'on', commandType: 'command' })
12
+ })
13
+
14
+ it('sends setSwing with angle', async () => {
15
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
16
+ const d = new FanDevice({ id: 'fan2', type: 'fan', name: 'F2' }, { _client: mockClient } as any)
17
+ await d.init()
18
+ await d.setState({ swingAngle: 45 })
19
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'setSwing', parameter: '45', commandType: 'command' })
20
+ })
21
+
22
+ it('sends setSwing with mode', async () => {
23
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
24
+ const d = new FanDevice({ id: 'fan3', type: 'fan', name: 'F3' }, { _client: mockClient } as any)
25
+ await d.init()
26
+ await d.setState({ swingMode: 'vertical' })
27
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'setSwing', parameter: 'vertical', commandType: 'command' })
28
+ })
29
+ })
@@ -0,0 +1,53 @@
1
+ export function makeFakeMatterApi() {
2
+ const registered: any[] = []
3
+ return {
4
+ uuid: { generate: (s: string) => `m-${s}` },
5
+ registerPlatformAccessories: async (_plugin: string, _name: string, accs: any[]) => {
6
+ registered.push(...accs)
7
+ },
8
+ getRegistered: () => registered,
9
+ }
10
+ }
11
+
12
+ export function makeFakeHap() {
13
+ return {
14
+ uuid: { generate: (s: string) => `h-${s}` },
15
+ Service: { Switch: 'Switch', Lightbulb: 'Lightbulb', Fan: 'Fan', WindowCovering: 'WindowCovering', LockMechanism: 'LockMechanism', HumiditySensor: 'HumiditySensor' },
16
+ Characteristic: { On: 'On', Brightness: 'Brightness', RotationSpeed: 'RotationSpeed', CurrentPosition: 'CurrentPosition', TargetPosition: 'TargetPosition' },
17
+ }
18
+ }
19
+
20
+ export function makeFakeApiWithMatter(matterApi?: any) {
21
+ const matter = matterApi || makeFakeMatterApi()
22
+ const api: any = {
23
+ matter,
24
+ isMatterAvailable: () => true,
25
+ isMatterEnabled: () => true,
26
+ hap: makeFakeHap(),
27
+ }
28
+ return api
29
+ }
30
+
31
+ export function makeFakeApiWithoutMatter() {
32
+ // Provide a minimal platformAccessory constructor and registerPlatformAccessories
33
+ function PlatformAccessory(name: string, uuid: string) {
34
+ this.displayName = name
35
+ this.UUID = uuid
36
+ this.services = []
37
+ this.getService = (type: any) => this.services.find((s: any) => s.type === type)
38
+ this.addService = (type: any) => {
39
+ const s: any = { type, characteristics: {} }
40
+ this.services.push(s)
41
+ return s
42
+ }
43
+ }
44
+
45
+ return {
46
+ isMatterAvailable: () => false,
47
+ isMatterEnabled: () => false,
48
+ hap: makeFakeHap(),
49
+ platformAccessory: PlatformAccessory,
50
+ registerPlatformAccessories: (_p: string, _n: string, accs: any[]) => {},
51
+ on: (_ev: string, cb: Function) => {},
52
+ }
53
+ }
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { LockDevice } from '../src/devices/genericDevice'
3
+
4
+ describe('Lock user management', () => {
5
+ it('sets lock pin via pin field', async () => {
6
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
7
+ const d = new LockDevice({ id: 'lock1', type: 'lock', name: 'L1' }, { _client: mockClient } as any)
8
+ await d.init()
9
+ await d.setState({ pin: '1234' })
10
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'setLockPin', parameter: '1234', commandType: 'command' })
11
+ })
12
+
13
+ it('adds a user code with addUser action', async () => {
14
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
15
+ const d = new LockDevice({ id: 'lock2', type: 'lock', name: 'L2' }, { _client: mockClient } as any)
16
+ await d.init()
17
+ await d.setState({ action: 'addUser', user: 'user1', pin: '9999' })
18
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'addUserCode', parameter: 'user1:9999', commandType: 'command' })
19
+ })
20
+
21
+ it('removes a user code with removeUser action', async () => {
22
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
23
+ const d = new LockDevice({ id: 'lock3', type: 'lock', name: 'L3' }, { _client: mockClient } as any)
24
+ await d.init()
25
+ await d.setState({ action: 'removeUser', user: 'user1' })
26
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'removeUserCode', parameter: 'user1', commandType: 'command' })
27
+ })
28
+
29
+ it('lists users with listUsers action', async () => {
30
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
31
+ const d = new LockDevice({ id: 'lock4', type: 'lock', name: 'L4' }, { _client: mockClient } as any)
32
+ await d.init()
33
+ await d.setState({ action: 'listUsers' })
34
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'listUsers', parameter: 'default', commandType: 'command' })
35
+ })
36
+
37
+ it('unlocks with pin using unlockWithPin action', async () => {
38
+ const mockClient = { setDeviceState: vi.fn(async (id: string, body: any) => ({ id, body })) }
39
+ const d = new LockDevice({ id: 'lock5', type: 'lock', name: 'L5' }, { _client: mockClient } as any)
40
+ await d.init()
41
+ await d.setState({ action: 'unlockWithPin', pin: '5555' })
42
+ expect((mockClient.setDeviceState as any).mock.calls[0][1]).toEqual({ command: 'unlockWithPin', parameter: '5555', commandType: 'command' })
43
+ })
44
+ })
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
2
+ import SwitchBotPlatform from '../src/platform'
3
+ import { createPlatformProxy } from '../src/utils'
4
+
5
+ // This is a lightweight integration-style unit test that verifies the
6
+ // platform will attempt Matter registration when MatterPlatform is
7
+ // provided by the proxy and will still restore cached accessories.
8
+
9
+ describe('platform integration (Matter)', () => {
10
+ it('creates Matter descriptors when useMatter=true and restores accessories', async () => {
11
+ // Create a fake MatterPlatform class that records register calls
12
+ class FakeMatterPlatform {
13
+ public registered: any[] = []
14
+ constructor(public log: any, public cfg: any, public api: any) {}
15
+ async registerMatterAccessories(accessories: any[]) {
16
+ this.registered.push(...accessories)
17
+ return
18
+ }
19
+ // simulate configureAccessory hook for restored matter accessories
20
+ configureMatterAccessory(accessory: any) {
21
+ // no-op
22
+ }
23
+ // lightweight loadDevices implementation that uses an injected discover hook if present
24
+ async loadDevices() {
25
+ const devices = (this.cfg?.devices) || (this as any)._discoverDevices ? await (this as any)._discoverDevices() : []
26
+ // Simulate preparing descriptors and immediately registering them
27
+ const serialized = devices.map((d: any) => ({ id: d.id ?? d.deviceId ?? d, protocol: 'matter', name: d.name }))
28
+ if (serialized.length > 0) await this.registerMatterAccessories(serialized)
29
+ }
30
+ }
31
+
32
+ // Create proxy that would choose the fake matter platform when preferred
33
+ const Proxy = createPlatformProxy(function HAP() {}, FakeMatterPlatform)
34
+ const platform = new (Proxy as any)(console, { preferMatter: true, enableMatter: true }, {})
35
+
36
+ // Make a small device descriptor to load
37
+ const device = { id: 'dev-m-1', type: 'light', name: 'MatterLight' }
38
+ // Simulate platform method that loads devices (use same method name used in code)
39
+ if (typeof platform.loadDevices === 'function') {
40
+ // Add an internal method to return our single device for creation
41
+ platform._discoverDevices = async () => [device]
42
+ // Call loadDevices which should call the MatterPlatform.registerMatterAccessories
43
+ await platform.loadDevices()
44
+
45
+ // Verify FakeMatterPlatform received the accessory
46
+ expect(platform).toBeInstanceOf(FakeMatterPlatform)
47
+ expect((platform as any).registered.length).toBeGreaterThanOrEqual(1)
48
+ const registered = (platform as any).registered[0]
49
+ expect(registered).toHaveProperty('protocol', 'matter')
50
+ expect(registered).toHaveProperty('id', device.id)
51
+ } else {
52
+ throw new Error('platform.loadDevices not implemented')
53
+ }
54
+ })
55
+ })
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { FanDevice, LightDevice, CurtainDevice, LockDevice } from '../src/devices/genericDevice'
3
+
4
+ describe('Matter descriptors and translations (per-device)', () => {
5
+ beforeEach(() => {
6
+ vi.restoreAllMocks()
7
+ })
8
+
9
+ it('Fan: maps oscillation and swing to setDeviceState commands', async () => {
10
+ const calls: any[] = []
11
+ const fakeClient: any = { setDeviceState: async (id: string, body: any) => { calls.push({ id, body }); return { ok: true } } }
12
+ const d = new FanDevice({ id: 'f1', type: 'fan' } as any, { _client: fakeClient } as any)
13
+
14
+ // Oscillate
15
+ await d.setState({ oscillate: true })
16
+ expect(calls.pop()?.body.command).toBe('setOscillation')
17
+
18
+ // Swing boolean
19
+ await d.setState({ swing: true })
20
+ expect(calls.pop()?.body.command).toBe('setSwing')
21
+
22
+ // Swing angle
23
+ await d.setState({ swingAngle: 45 })
24
+ const last = calls.pop()
25
+ expect(last.body.command).toBe('setSwing')
26
+ expect(last.body.parameter).toBe('45')
27
+ })
28
+
29
+ it('Light: color/CT mapping and setState commands', async () => {
30
+ const calls: any[] = []
31
+ const fakeClient: any = { setDeviceState: async (id: string, body: any) => { calls.push(body); return { ok: true } } }
32
+ const d = new LightDevice({ id: 'l1', type: 'light' } as any, { _client: fakeClient } as any)
33
+
34
+ // color temperature (mired)
35
+ await d.setState({ colorTemperature: 300 })
36
+ expect(calls.pop()?.command).toBe('setColorTemperature')
37
+
38
+ // hue + saturation
39
+ await d.setState({ hue: 120, saturation: 50 })
40
+ const c = calls.pop()
41
+ expect(c.command).toBe('setColor')
42
+ expect(c.parameter).toBe('120,50')
43
+
44
+ // hex color string
45
+ await d.setState({ color: '#ff0000' })
46
+ expect(calls.pop()?.command).toBe('setColor')
47
+ })
48
+
49
+ it('Curtain: position mapping and Matter attribute writes', async () => {
50
+ const calls: any[] = []
51
+ const fakeClient: any = { setDeviceState: async (id: string, body: any) => { calls.push(body); return { ok: true } } }
52
+ const d = new CurtainDevice({ id: 'c1', type: 'curtain' } as any, { _client: fakeClient } as any)
53
+
54
+ // Setting position via HAP-style setState
55
+ await d.setState({ position: 25 })
56
+ expect(calls.pop()?.command).toBe('setPosition')
57
+
58
+ // Matter descriptor write: call targetPosition write handler
59
+ const matter = d.createMatterAccessory({})
60
+ const cluster = matter.clusters.find((c: any) => c.type === 'Shade')
61
+ // targetPosition attribute exists and has a write function
62
+ expect(cluster.attributes.targetPosition.write).toBeTypeOf('function')
63
+ await cluster.attributes.targetPosition.write(80)
64
+ expect(calls.pop()?.command).toBe('setPosition')
65
+ })
66
+
67
+ it('Lock: user management actions map to correct commands', async () => {
68
+ const calls: any[] = []
69
+ const fakeClient: any = { setDeviceState: async (id: string, body: any) => { calls.push(body); return { ok: true } } }
70
+ const d = new LockDevice({ id: 'lock1', type: 'lock' } as any, { _client: fakeClient } as any)
71
+
72
+ // addUser via setState
73
+ await d.setState({ action: 'addUser', user: 'u1', pin: '1234' })
74
+ let last = calls.pop()
75
+ expect(last.command).toBe('addUserCode')
76
+ expect(last.parameter).toBe('u1:1234')
77
+
78
+ // removeUser
79
+ await d.setState({ action: 'removeUser', user: 'u1' })
80
+ last = calls.pop()
81
+ expect(last.command).toBe('removeUserCode')
82
+
83
+ // unlockWithPin
84
+ await d.setState({ action: 'unlockWithPin', pin: '0000' })
85
+ last = calls.pop()
86
+ expect(last.command).toBe('unlockWithPin')
87
+
88
+ // Matter descriptor write for addUser
89
+ const matter = d.createMatterAccessory({})
90
+ const mgmt = matter.clusters.find((c: any) => c.type === 'DoorLockUserManagement')
91
+ expect(mgmt.attributes.addUser.write).toBeTypeOf('function')
92
+ await mgmt.attributes.addUser.write({ user: 'u2', pin: '4321' })
93
+ last = calls.pop()
94
+ expect(last.command).toBe('addUserCode')
95
+ expect(last.parameter).toBe('u2:4321')
96
+ })
97
+ })
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { LightDevice, FanDevice, CurtainDevice, LockDevice, HumidifierMatterDevice } from '../src/devices/genericDevice'
3
+ import { makeFakeApiWithMatter } from './helpers/matter-harness'
4
+
5
+ const api = makeFakeApiWithMatter()
6
+
7
+ describe('Matter device state translations', () => {
8
+ it('Light: reads and writes map to setState/getState', async () => {
9
+ const device = new LightDevice({ id: 'l-1', type: 'light', name: 'L1' }, {})
10
+ let lastSet: any = null
11
+ device.getState = async () => ({ on: true, brightness: 75, hue: 120, saturation: 50, colorTemperature: 350 })
12
+ device.setState = async (c: any) => { lastSet = c; return { success: true } }
13
+
14
+ const desc = device.createMatterAccessory(api)
15
+ const levelCluster = desc.clusters.find((c: any) => c.clusterId === 0x0008 || c.type === 'LevelControl')
16
+ expect(levelCluster).toBeDefined()
17
+ const levelAttr = levelCluster.attributes.currentLevel || levelCluster.attributes["0x0000"] || Object.values(levelCluster.attributes)[0]
18
+ const read = await levelAttr.read()
19
+ expect(read).toBe(75)
20
+
21
+ // write should call setState with brightness
22
+ await levelAttr.write(30)
23
+ expect(lastSet).toMatchObject({ brightness: 30 })
24
+ })
25
+
26
+ it('Fan: reads speed and writes speed/oscillation', async () => {
27
+ const device = new FanDevice({ id: 'f-1', type: 'fan', name: 'F1' }, {})
28
+ let last: any = null
29
+ device.getState = async () => ({ on: false, speed: 45, oscillating: true, swingMode: 'auto' })
30
+ device.setState = async (c: any) => { last = c; return { success: true } }
31
+
32
+ const desc = device.createMatterAccessory(api)
33
+ const fanCluster = desc.clusters.find((c: any) => c.clusterId === 0x0202 || c.type === 'FanControl')
34
+ expect(fanCluster).toBeDefined()
35
+ const speedAttr = fanCluster.attributes.rotationSpeed || Object.values(fanCluster.attributes)[0]
36
+ const speed = await speedAttr.read()
37
+ expect(speed).toBe(45)
38
+
39
+ await speedAttr.write(80)
40
+ expect(last).toMatchObject({ speed: 80 })
41
+
42
+ // oscillation write
43
+ const osc = fanCluster.attributes.oscillation
44
+ await osc.write(true)
45
+ expect(last).toMatchObject({ oscillate: true })
46
+ })
47
+
48
+ it('Curtain: reads and writes position', async () => {
49
+ const device = new CurtainDevice({ id: 'c-1', type: 'curtain', name: 'C1' }, {})
50
+ let last: any = null
51
+ device.getState = async () => ({ position: 42 })
52
+ device.setState = async (c: any) => { last = c; return { success: true } }
53
+
54
+ const desc = device.createMatterAccessory(api)
55
+ const shade = desc.clusters.find((c: any) => c.clusterId === 0x0102 || c.type === 'Shade')
56
+ expect(shade).toBeDefined()
57
+ const current = shade.attributes.currentPosition || shade.attributes["0x0000"]
58
+ const v = await current.read()
59
+ expect(v).toBe(42)
60
+
61
+ const target = shade.attributes.targetPosition || shade.attributes["0x0001"]
62
+ await target.write(10)
63
+ expect(last).toMatchObject({ position: 10 })
64
+ })
65
+
66
+ it('Lock: reads and writes locked state', async () => {
67
+ const device = new LockDevice({ id: 'k-1', type: 'lock', name: 'K1' }, {})
68
+ let last: any = null
69
+ device.getState = async () => ({ locked: true })
70
+ device.setState = async (c: any) => { last = c; return { success: true } }
71
+
72
+ const desc = device.createMatterAccessory(api)
73
+ const lock = desc.clusters.find((c: any) => c.clusterId === 0x0101 || c.type === 'DoorLock')
74
+ expect(lock).toBeDefined()
75
+ const lockAttr = lock.attributes.lockState || lock.attributes["0x0000"]
76
+ const v = await lockAttr.read()
77
+ expect(v).toBe(true)
78
+
79
+ await lockAttr.write(false)
80
+ expect(last).toMatchObject({ locked: false })
81
+ })
82
+
83
+ it('Humidifier: reads humidity and toggles on/off', async () => {
84
+ const device = new HumidifierMatterDevice({ id: 'h-1', type: 'humidifier', name: 'H1' }, {})
85
+ let last: any = null
86
+ device.getState = async () => ({ humidity: 55, on: false })
87
+ device.setState = async (c: any) => { last = c; return { success: true } }
88
+
89
+ const desc = device.createMatterAccessory(api)
90
+ const rh = desc.clusters.find((c: any) => c.clusterId === 0x0405 || c.type === 'RelativeHumiditySensor')
91
+ expect(rh).toBeDefined()
92
+ const humAttr = rh.attributes.currentRelativeHumidity || rh.attributes["0x0000"]
93
+ const hv = await humAttr.read()
94
+ expect(hv).toBe(55)
95
+
96
+ const onCluster = desc.clusters.find((c: any) => c.clusterId === 0x0006 || c.type === 'OnOff')
97
+ const onAttr = onCluster.attributes.onOff || onCluster.attributes["0x0000"]
98
+ await onAttr.write(true)
99
+ expect(last).toMatchObject({ on: true })
100
+ })
101
+ })
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createPlatformProxy } from '../src/utils'
3
+ import { makeFakeApiWithMatter, makeFakeApiWithoutMatter, makeFakeMatterApi } from './helpers/matter-harness'
4
+ import SwitchBotHAPPlatform, { SwitchBotMatterPlatform } from '../src/platform'
5
+
6
+ // Expanded Matter integration harness tests
7
+
8
+ describe('Matter integration harness', () => {
9
+ it('registers Matter accessories when Matter API available', async () => {
10
+ // Instantiate the real Matter platform directly to test its registration path
11
+ const matterApi = makeFakeMatterApi()
12
+ const api = makeFakeApiWithMatter(matterApi)
13
+
14
+ const platform = new SwitchBotMatterPlatform(console as any, { devices: [{ id: 'm-1', type: 'light', name: 'L1' }], enableMatter: true, preferMatter: true } as any, api as any)
15
+
16
+ // Call register path directly
17
+ if (typeof (platform as any).registerMatterAccessories === 'function') {
18
+ await (platform as any).registerMatterAccessories()
19
+ } else if (typeof (platform as any).loadDevices === 'function') {
20
+ await (platform as any).loadDevices()
21
+ }
22
+
23
+ const regs = matterApi.getRegistered()
24
+ expect(regs.length).toBeGreaterThanOrEqual(1)
25
+ // The platform registers serialized accessories; ensure deviceId is present in context
26
+ expect(regs[0]).toHaveProperty('context')
27
+ expect(regs[0].context).toHaveProperty('deviceId', 'm-1')
28
+ })
29
+
30
+ it('falls back to HAP when Matter API unavailable', async () => {
31
+ // Directly instantiate the HAP platform to exercise HAP fallback
32
+ const api = makeFakeApiWithoutMatter()
33
+ const platform = new SwitchBotHAPPlatform(console as any, { devices: [{ id: 'h-1', type: 'light', name: 'H1' }], enableMatter: true, preferMatter: true } as any, api as any)
34
+
35
+ // call loadDevices for HAP flow
36
+ if (typeof (platform as any).loadDevices === 'function') await (platform as any).loadDevices()
37
+
38
+ // Platform should have an accessories map populated
39
+ const accMap = (platform as any).accessories
40
+ expect(accMap).toBeDefined()
41
+ const keys = Array.from(accMap.keys())
42
+ expect(keys.length).toBeGreaterThanOrEqual(1)
43
+ })
44
+
45
+ it('reuses restored Matter accessories instead of registering duplicates', async () => {
46
+ const matterApi = makeFakeMatterApi()
47
+ const api = makeFakeApiWithMatter(matterApi)
48
+
49
+ const platform = new SwitchBotMatterPlatform(console as any, { devices: [{ id: 'r-1', type: 'light', name: 'R1' }], enableMatter: true, preferMatter: true } as any, api as any)
50
+
51
+ // Simulate a restored accessory present before registration
52
+ const restored = { uuid: matterApi.uuid.generate('r-1'), displayName: 'Restored R1', context: { deviceId: 'r-1' } }
53
+ if (typeof (platform as any).configureMatterAccessory === 'function') {
54
+ await (platform as any).configureMatterAccessory(restored)
55
+ } else if (typeof (platform as any).configureAccessory === 'function') {
56
+ await (platform as any).configureAccessory(restored)
57
+ }
58
+
59
+ // Now call register path
60
+ if (typeof (platform as any).registerMatterAccessories === 'function') await (platform as any).registerMatterAccessories()
61
+ else if (typeof (platform as any).loadDevices === 'function') {
62
+ await (platform as any).loadDevices()
63
+ if (typeof (platform as any).registerMatterAccessories === 'function') await (platform as any).registerMatterAccessories()
64
+ }
65
+
66
+ const accs = (platform as any).accessories
67
+ const found = Array.from(accs.values()).some((a: any) => a && (a.uuid === restored.uuid || a.UUID === restored.uuid))
68
+ expect(found).toBeTruthy()
69
+ })
70
+ })