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

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 (578) 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 +44 -0
  9. package/README.md +56 -3
  10. package/config.schema.json +170 -14787
  11. package/dist/device-types.d.ts +31 -0
  12. package/dist/device-types.d.ts.map +1 -0
  13. package/dist/device-types.js +216 -0
  14. package/dist/device-types.js.map +1 -0
  15. package/dist/deviceFactory.d.ts +13 -0
  16. package/dist/deviceFactory.d.ts.map +1 -0
  17. package/dist/deviceFactory.js +120 -0
  18. package/dist/deviceFactory.js.map +1 -0
  19. package/dist/devices/deviceBase.d.ts +50 -0
  20. package/dist/devices/deviceBase.d.ts.map +1 -0
  21. package/dist/devices/deviceBase.js +119 -0
  22. package/dist/devices/deviceBase.js.map +1 -0
  23. package/dist/devices/genericDevice.d.ts +298 -0
  24. package/dist/devices/genericDevice.d.ts.map +1 -0
  25. package/dist/devices/genericDevice.js +1067 -0
  26. package/dist/devices/genericDevice.js.map +1 -0
  27. package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
  28. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  29. package/dist/homebridge-ui/endpoints/config.js +104 -0
  30. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  31. package/dist/homebridge-ui/endpoints/credentials.d.ts +6 -0
  32. package/dist/homebridge-ui/endpoints/credentials.d.ts.map +1 -0
  33. package/dist/homebridge-ui/endpoints/credentials.js +65 -0
  34. package/dist/homebridge-ui/endpoints/credentials.js.map +1 -0
  35. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  36. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  37. package/dist/homebridge-ui/endpoints/devices.js +351 -0
  38. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  39. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  40. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  41. package/dist/homebridge-ui/endpoints/discovery.js +171 -0
  42. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  43. package/dist/homebridge-ui/public/css/styles.css +254 -0
  44. package/dist/homebridge-ui/public/index.html +112 -247
  45. package/dist/homebridge-ui/public/js/api.d.ts +23 -0
  46. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  47. package/dist/homebridge-ui/public/js/api.js +188 -0
  48. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  49. package/dist/homebridge-ui/public/js/api.ts +215 -0
  50. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  51. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  52. package/dist/homebridge-ui/public/js/app.js +1815 -0
  53. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  54. package/dist/homebridge-ui/public/js/app.ts +22 -0
  55. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  56. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  57. package/dist/homebridge-ui/public/js/constants.js +2 -0
  58. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  59. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  60. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  61. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  62. package/dist/homebridge-ui/public/js/credentials.js +96 -0
  63. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  64. package/dist/homebridge-ui/public/js/credentials.ts +100 -0
  65. package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
  66. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  67. package/dist/homebridge-ui/public/js/devices-delete.js +206 -0
  68. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  69. package/dist/homebridge-ui/public/js/devices-delete.ts +236 -0
  70. package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
  71. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  72. package/dist/homebridge-ui/public/js/devices.js +90 -0
  73. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  74. package/dist/homebridge-ui/public/js/devices.ts +98 -0
  75. package/dist/homebridge-ui/public/js/discovery.d.ts +3 -0
  76. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  77. package/dist/homebridge-ui/public/js/discovery.js +244 -0
  78. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  79. package/dist/homebridge-ui/public/js/discovery.ts +290 -0
  80. package/dist/homebridge-ui/public/js/logger.d.ts +6 -0
  81. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  82. package/dist/homebridge-ui/public/js/logger.js +12 -0
  83. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  84. package/dist/homebridge-ui/public/js/logger.ts +11 -0
  85. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  86. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  87. package/dist/homebridge-ui/public/js/modal.js +26 -0
  88. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  89. package/dist/homebridge-ui/public/js/modal.ts +28 -0
  90. package/dist/homebridge-ui/public/js/modals.d.ts +7 -0
  91. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  92. package/dist/homebridge-ui/public/js/modals.js +323 -0
  93. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  94. package/dist/homebridge-ui/public/js/modals.ts +367 -0
  95. package/dist/homebridge-ui/public/js/render.d.ts +65 -0
  96. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  97. package/dist/homebridge-ui/public/js/render.js +499 -0
  98. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  99. package/dist/homebridge-ui/public/js/render.ts +579 -0
  100. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  101. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  102. package/dist/homebridge-ui/public/js/toast.js +29 -0
  103. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  104. package/dist/homebridge-ui/public/js/toast.ts +37 -0
  105. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  106. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  107. package/dist/homebridge-ui/public/js/types.js +2 -0
  108. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  109. package/dist/homebridge-ui/public/js/types.ts +26 -0
  110. package/dist/homebridge-ui/server.js +11 -41
  111. package/dist/homebridge-ui/server.js.map +1 -1
  112. package/dist/homebridge-ui/utils/config-parser.d.ts +35 -0
  113. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  114. package/dist/homebridge-ui/utils/config-parser.js +87 -0
  115. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  116. package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
  117. package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
  118. package/dist/homebridge-ui/utils/device-migration.js +111 -0
  119. package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
  120. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  121. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  122. package/dist/homebridge-ui/utils/logger.js +17 -0
  123. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +4 -65
  126. package/dist/index.js.map +1 -1
  127. package/dist/platform.d.ts +35 -0
  128. package/dist/platform.d.ts.map +1 -0
  129. package/dist/platform.js +1527 -0
  130. package/dist/platform.js.map +1 -0
  131. package/dist/settings.d.ts +10 -249
  132. package/dist/settings.d.ts.map +1 -1
  133. package/dist/settings.js +5 -30
  134. package/dist/settings.js.map +1 -1
  135. package/dist/switchbotClient.d.ts +32 -0
  136. package/dist/switchbotClient.d.ts.map +1 -0
  137. package/dist/switchbotClient.js +207 -0
  138. package/dist/switchbotClient.js.map +1 -0
  139. package/dist/utils.d.ts +39 -50
  140. package/dist/utils.d.ts.map +1 -1
  141. package/dist/utils.js +40 -688
  142. package/dist/utils.js.map +1 -1
  143. package/docs/assets/highlight.css +14 -0
  144. package/docs/assets/icons.js +1 -1
  145. package/docs/assets/icons.svg +1 -1
  146. package/docs/assets/main.js +2 -2
  147. package/docs/assets/style.css +3 -3
  148. package/docs/index.html +77 -13
  149. package/docs/variables/default.html +1 -1
  150. package/eslint.config.js +7 -8
  151. package/nodemon.json +2 -2
  152. package/package.json +35 -32
  153. package/scripts/build-ui.js +36 -0
  154. package/scripts/e2e/README.md +25 -0
  155. package/scripts/e2e/curtain-e2e.sh +70 -0
  156. package/scripts/e2e/fan-e2e.sh +75 -0
  157. package/scripts/e2e/light-advanced-e2e.sh +97 -0
  158. package/scripts/e2e/light-e2e.sh +75 -0
  159. package/scripts/e2e/list-accessories.sh +19 -0
  160. package/scripts/e2e/lock-e2e.sh +65 -0
  161. package/scripts/free-dev-ports.mjs +105 -0
  162. package/scripts/generate-matter-maps.js +60 -0
  163. package/scripts/run-e2e-local.sh +14 -0
  164. package/scripts/sync-device-types.mjs +28 -0
  165. package/src/device-types.ts +230 -0
  166. package/src/deviceFactory.ts +163 -0
  167. package/src/devices/deviceBase.ts +141 -0
  168. package/src/devices/genericDevice.ts +998 -0
  169. package/src/homebridge-ui/endpoints/config.ts +123 -0
  170. package/src/homebridge-ui/endpoints/credentials.ts +74 -0
  171. package/src/homebridge-ui/endpoints/devices.ts +383 -0
  172. package/src/homebridge-ui/endpoints/discovery.ts +191 -0
  173. package/src/homebridge-ui/public/css/styles.css +254 -0
  174. package/src/homebridge-ui/public/index.html +112 -247
  175. package/src/homebridge-ui/public/js/api.ts +215 -0
  176. package/src/homebridge-ui/public/js/app.ts +22 -0
  177. package/src/homebridge-ui/public/js/constants.ts +1 -0
  178. package/src/homebridge-ui/public/js/credentials.ts +100 -0
  179. package/src/homebridge-ui/public/js/devices-delete.ts +236 -0
  180. package/src/homebridge-ui/public/js/devices.ts +98 -0
  181. package/src/homebridge-ui/public/js/discovery.ts +290 -0
  182. package/src/homebridge-ui/public/js/logger.ts +11 -0
  183. package/src/homebridge-ui/public/js/modal.ts +28 -0
  184. package/src/homebridge-ui/public/js/modals.ts +367 -0
  185. package/src/homebridge-ui/public/js/render.ts +579 -0
  186. package/src/homebridge-ui/public/js/toast.ts +37 -0
  187. package/src/homebridge-ui/public/js/types.ts +26 -0
  188. package/src/homebridge-ui/server.ts +11 -43
  189. package/src/homebridge-ui/utils/config-parser.ts +108 -0
  190. package/src/homebridge-ui/utils/device-migration.ts +144 -0
  191. package/src/homebridge-ui/utils/logger.ts +17 -0
  192. package/src/index.ts +4 -66
  193. package/src/platform.ts +1539 -0
  194. package/src/settings.ts +12 -277
  195. package/src/switchbotClient.ts +216 -0
  196. package/src/utils.ts +46 -713
  197. package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
  198. package/test/accessory-restore.spec.ts +73 -0
  199. package/test/device-mapping.spec.ts +37 -0
  200. package/test/deviceFactory.spec.ts +18 -0
  201. package/test/e2e/run-e2e.spec.ts +50 -0
  202. package/test/fan-swing.spec.ts +29 -0
  203. package/test/helpers/matter-harness.ts +53 -0
  204. package/test/lock-users.spec.ts +44 -0
  205. package/test/matter-childbridge.spec.ts +59 -0
  206. package/test/matter-descriptors.spec.ts +97 -0
  207. package/test/matter-device-state.spec.ts +101 -0
  208. package/test/matter-integration.spec.ts +111 -0
  209. package/test/platform.integration.spec.ts +55 -0
  210. package/test/switchbot-client-debounce.spec.ts +131 -0
  211. package/test/switchbot-client-openapi.spec.ts +56 -0
  212. package/test/switchbotClient.spec.ts +10 -0
  213. package/test/utils.spec.ts +36 -0
  214. package/todo/COMPLETED.md +229 -0
  215. package/todo/README.md +59 -0
  216. package/todo/TODO.md +176 -0
  217. package/todo/mockups/filtering-sorting.svg +112 -0
  218. package/todo/mockups/modal-switchbot-theme.svg +66 -0
  219. package/todo/mockups/signal-strength-visualization.svg +94 -0
  220. package/vitest.config.ts +7 -0
  221. package/coverage/base.css +0 -224
  222. package/coverage/block-navigation.js +0 -87
  223. package/coverage/clover.xml +0 -15847
  224. package/coverage/coverage-final.json +0 -42
  225. package/coverage/docs/assets/dmt/dmt-component-data.js.html +0 -85
  226. package/coverage/docs/assets/dmt/dmt-components.js.html +0 -286
  227. package/coverage/docs/assets/dmt/index.html +0 -131
  228. package/coverage/docs/assets/hierarchy.js.html +0 -85
  229. package/coverage/docs/assets/icons.js.html +0 -136
  230. package/coverage/docs/assets/index.html +0 -146
  231. package/coverage/docs/assets/main.js.html +0 -265
  232. package/coverage/favicon.png +0 -0
  233. package/coverage/index.html +0 -191
  234. package/coverage/prettify.css +0 -1
  235. package/coverage/prettify.js +0 -2
  236. package/coverage/sort-arrow-sprite.png +0 -0
  237. package/coverage/sorter.js +0 -196
  238. package/coverage/src/device/blindtilt.ts.html +0 -3238
  239. package/coverage/src/device/bot.ts.html +0 -2803
  240. package/coverage/src/device/ceilinglight.ts.html +0 -2338
  241. package/coverage/src/device/colorbulb.ts.html +0 -2824
  242. package/coverage/src/device/contact.ts.html +0 -1465
  243. package/coverage/src/device/curtain.ts.html +0 -2869
  244. package/coverage/src/device/device.ts.html +0 -2500
  245. package/coverage/src/device/fan.ts.html +0 -2242
  246. package/coverage/src/device/hub.ts.html +0 -1408
  247. package/coverage/src/device/humidifier.ts.html +0 -2116
  248. package/coverage/src/device/index.html +0 -416
  249. package/coverage/src/device/iosensor.ts.html +0 -1375
  250. package/coverage/src/device/lightstrip.ts.html +0 -2617
  251. package/coverage/src/device/lock.ts.html +0 -1963
  252. package/coverage/src/device/meter.ts.html +0 -1372
  253. package/coverage/src/device/meterplus.ts.html +0 -1384
  254. package/coverage/src/device/meterpro.ts.html +0 -1618
  255. package/coverage/src/device/motion.ts.html +0 -1264
  256. package/coverage/src/device/plug.ts.html +0 -1372
  257. package/coverage/src/device/relayswitch.ts.html +0 -2284
  258. package/coverage/src/device/robotvacuumcleaner.ts.html +0 -1810
  259. package/coverage/src/device/waterdetector.ts.html +0 -1294
  260. package/coverage/src/homebridge-ui/index.html +0 -116
  261. package/coverage/src/homebridge-ui/server.ts.html +0 -229
  262. package/coverage/src/index.html +0 -161
  263. package/coverage/src/index.ts.html +0 -124
  264. package/coverage/src/irdevice/airconditioner.ts.html +0 -1687
  265. package/coverage/src/irdevice/airpurifier.ts.html +0 -844
  266. package/coverage/src/irdevice/camera.ts.html +0 -475
  267. package/coverage/src/irdevice/fan.ts.html +0 -766
  268. package/coverage/src/irdevice/index.html +0 -251
  269. package/coverage/src/irdevice/irdevice.ts.html +0 -1117
  270. package/coverage/src/irdevice/light.ts.html +0 -826
  271. package/coverage/src/irdevice/other.ts.html +0 -2458
  272. package/coverage/src/irdevice/tv.ts.html +0 -1222
  273. package/coverage/src/irdevice/vacuumcleaner.ts.html +0 -466
  274. package/coverage/src/irdevice/waterheater.ts.html +0 -469
  275. package/coverage/src/platform.ts.html +0 -8776
  276. package/coverage/src/settings.ts.html +0 -934
  277. package/coverage/src/utils.ts.html +0 -2092
  278. package/dist/devices-hap/airpurifier.d.ts +0 -54
  279. package/dist/devices-hap/airpurifier.d.ts.map +0 -1
  280. package/dist/devices-hap/airpurifier.js +0 -527
  281. package/dist/devices-hap/airpurifier.js.map +0 -1
  282. package/dist/devices-hap/blindtilt.d.ts +0 -90
  283. package/dist/devices-hap/blindtilt.d.ts.map +0 -1
  284. package/dist/devices-hap/blindtilt.js +0 -974
  285. package/dist/devices-hap/blindtilt.js.map +0 -1
  286. package/dist/devices-hap/bot.d.ts +0 -102
  287. package/dist/devices-hap/bot.d.ts.map +0 -1
  288. package/dist/devices-hap/bot.js +0 -811
  289. package/dist/devices-hap/bot.js.map +0 -1
  290. package/dist/devices-hap/ceilinglight.d.ts +0 -85
  291. package/dist/devices-hap/ceilinglight.d.ts.map +0 -1
  292. package/dist/devices-hap/ceilinglight.js +0 -701
  293. package/dist/devices-hap/ceilinglight.js.map +0 -1
  294. package/dist/devices-hap/colorbulb.d.ts +0 -88
  295. package/dist/devices-hap/colorbulb.d.ts.map +0 -1
  296. package/dist/devices-hap/colorbulb.js +0 -881
  297. package/dist/devices-hap/colorbulb.js.map +0 -1
  298. package/dist/devices-hap/contact.d.ts +0 -44
  299. package/dist/devices-hap/contact.d.ts.map +0 -1
  300. package/dist/devices-hap/contact.js +0 -409
  301. package/dist/devices-hap/contact.js.map +0 -1
  302. package/dist/devices-hap/curtain.d.ts +0 -73
  303. package/dist/devices-hap/curtain.d.ts.map +0 -1
  304. package/dist/devices-hap/curtain.js +0 -869
  305. package/dist/devices-hap/curtain.js.map +0 -1
  306. package/dist/devices-hap/device.d.ts +0 -98
  307. package/dist/devices-hap/device.d.ts.map +0 -1
  308. package/dist/devices-hap/device.js +0 -749
  309. package/dist/devices-hap/device.js.map +0 -1
  310. package/dist/devices-hap/fan.d.ts +0 -69
  311. package/dist/devices-hap/fan.d.ts.map +0 -1
  312. package/dist/devices-hap/fan.js +0 -649
  313. package/dist/devices-hap/fan.js.map +0 -1
  314. package/dist/devices-hap/hub.d.ts +0 -37
  315. package/dist/devices-hap/hub.d.ts.map +0 -1
  316. package/dist/devices-hap/hub.js +0 -392
  317. package/dist/devices-hap/hub.js.map +0 -1
  318. package/dist/devices-hap/humidifier.d.ts +0 -68
  319. package/dist/devices-hap/humidifier.d.ts.map +0 -1
  320. package/dist/devices-hap/humidifier.js +0 -628
  321. package/dist/devices-hap/humidifier.js.map +0 -1
  322. package/dist/devices-hap/iosensor.d.ts +0 -42
  323. package/dist/devices-hap/iosensor.d.ts.map +0 -1
  324. package/dist/devices-hap/iosensor.js +0 -382
  325. package/dist/devices-hap/iosensor.js.map +0 -1
  326. package/dist/devices-hap/lightstrip.d.ts +0 -79
  327. package/dist/devices-hap/lightstrip.d.ts.map +0 -1
  328. package/dist/devices-hap/lightstrip.js +0 -797
  329. package/dist/devices-hap/lightstrip.js.map +0 -1
  330. package/dist/devices-hap/lock.d.ts +0 -53
  331. package/dist/devices-hap/lock.d.ts.map +0 -1
  332. package/dist/devices-hap/lock.js +0 -561
  333. package/dist/devices-hap/lock.js.map +0 -1
  334. package/dist/devices-hap/meter.d.ts +0 -37
  335. package/dist/devices-hap/meter.d.ts.map +0 -1
  336. package/dist/devices-hap/meter.js +0 -379
  337. package/dist/devices-hap/meter.js.map +0 -1
  338. package/dist/devices-hap/meterplus.d.ts +0 -42
  339. package/dist/devices-hap/meterplus.d.ts.map +0 -1
  340. package/dist/devices-hap/meterplus.js +0 -384
  341. package/dist/devices-hap/meterplus.js.map +0 -1
  342. package/dist/devices-hap/meterpro.d.ts +0 -43
  343. package/dist/devices-hap/meterpro.d.ts.map +0 -1
  344. package/dist/devices-hap/meterpro.js +0 -468
  345. package/dist/devices-hap/meterpro.js.map +0 -1
  346. package/dist/devices-hap/motion.d.ts +0 -42
  347. package/dist/devices-hap/motion.d.ts.map +0 -1
  348. package/dist/devices-hap/motion.js +0 -345
  349. package/dist/devices-hap/motion.js.map +0 -1
  350. package/dist/devices-hap/plug.d.ts +0 -49
  351. package/dist/devices-hap/plug.d.ts.map +0 -1
  352. package/dist/devices-hap/plug.js +0 -395
  353. package/dist/devices-hap/plug.js.map +0 -1
  354. package/dist/devices-hap/relayswitch.d.ts +0 -96
  355. package/dist/devices-hap/relayswitch.d.ts.map +0 -1
  356. package/dist/devices-hap/relayswitch.js +0 -642
  357. package/dist/devices-hap/relayswitch.js.map +0 -1
  358. package/dist/devices-hap/robotvacuumcleaner.d.ts +0 -54
  359. package/dist/devices-hap/robotvacuumcleaner.d.ts.map +0 -1
  360. package/dist/devices-hap/robotvacuumcleaner.js +0 -523
  361. package/dist/devices-hap/robotvacuumcleaner.js.map +0 -1
  362. package/dist/devices-hap/waterdetector.d.ts +0 -41
  363. package/dist/devices-hap/waterdetector.d.ts.map +0 -1
  364. package/dist/devices-hap/waterdetector.js +0 -356
  365. package/dist/devices-hap/waterdetector.js.map +0 -1
  366. package/dist/devices-matter/BaseMatterAccessory.d.ts +0 -63
  367. package/dist/devices-matter/BaseMatterAccessory.d.ts.map +0 -1
  368. package/dist/devices-matter/BaseMatterAccessory.js +0 -100
  369. package/dist/devices-matter/BaseMatterAccessory.js.map +0 -1
  370. package/dist/devices-matter/ColorLightAccessory.d.ts +0 -20
  371. package/dist/devices-matter/ColorLightAccessory.d.ts.map +0 -1
  372. package/dist/devices-matter/ColorLightAccessory.js +0 -95
  373. package/dist/devices-matter/ColorLightAccessory.js.map +0 -1
  374. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts +0 -18
  375. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +0 -1
  376. package/dist/devices-matter/ColorTemperatureLightAccessory.js +0 -78
  377. package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +0 -1
  378. package/dist/devices-matter/ContactSensorAccessory.d.ts +0 -12
  379. package/dist/devices-matter/ContactSensorAccessory.d.ts.map +0 -1
  380. package/dist/devices-matter/ContactSensorAccessory.js +0 -34
  381. package/dist/devices-matter/ContactSensorAccessory.js.map +0 -1
  382. package/dist/devices-matter/DimmableLightAccessory.d.ts +0 -58
  383. package/dist/devices-matter/DimmableLightAccessory.d.ts.map +0 -1
  384. package/dist/devices-matter/DimmableLightAccessory.js +0 -167
  385. package/dist/devices-matter/DimmableLightAccessory.js.map +0 -1
  386. package/dist/devices-matter/DoorLockAccessory.d.ts +0 -14
  387. package/dist/devices-matter/DoorLockAccessory.d.ts.map +0 -1
  388. package/dist/devices-matter/DoorLockAccessory.js +0 -50
  389. package/dist/devices-matter/DoorLockAccessory.js.map +0 -1
  390. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts +0 -21
  391. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +0 -1
  392. package/dist/devices-matter/ExtendedColorLightAccessory.js +0 -107
  393. package/dist/devices-matter/ExtendedColorLightAccessory.js.map +0 -1
  394. package/dist/devices-matter/FanAccessory.d.ts +0 -16
  395. package/dist/devices-matter/FanAccessory.d.ts.map +0 -1
  396. package/dist/devices-matter/FanAccessory.js +0 -81
  397. package/dist/devices-matter/FanAccessory.js.map +0 -1
  398. package/dist/devices-matter/HumiditySensorAccessory.d.ts +0 -12
  399. package/dist/devices-matter/HumiditySensorAccessory.d.ts.map +0 -1
  400. package/dist/devices-matter/HumiditySensorAccessory.js +0 -34
  401. package/dist/devices-matter/HumiditySensorAccessory.js.map +0 -1
  402. package/dist/devices-matter/LeakSensorAccessory.d.ts +0 -12
  403. package/dist/devices-matter/LeakSensorAccessory.d.ts.map +0 -1
  404. package/dist/devices-matter/LeakSensorAccessory.js +0 -33
  405. package/dist/devices-matter/LeakSensorAccessory.js.map +0 -1
  406. package/dist/devices-matter/LightSensorAccessory.d.ts +0 -12
  407. package/dist/devices-matter/LightSensorAccessory.d.ts.map +0 -1
  408. package/dist/devices-matter/LightSensorAccessory.js +0 -34
  409. package/dist/devices-matter/LightSensorAccessory.js.map +0 -1
  410. package/dist/devices-matter/OccupancySensorAccessory.d.ts +0 -12
  411. package/dist/devices-matter/OccupancySensorAccessory.d.ts.map +0 -1
  412. package/dist/devices-matter/OccupancySensorAccessory.js +0 -39
  413. package/dist/devices-matter/OccupancySensorAccessory.js.map +0 -1
  414. package/dist/devices-matter/OnOffLightAccessory.d.ts +0 -38
  415. package/dist/devices-matter/OnOffLightAccessory.d.ts.map +0 -1
  416. package/dist/devices-matter/OnOffLightAccessory.js +0 -118
  417. package/dist/devices-matter/OnOffLightAccessory.js.map +0 -1
  418. package/dist/devices-matter/OnOffOutletAccessory.d.ts +0 -12
  419. package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +0 -1
  420. package/dist/devices-matter/OnOffOutletAccessory.js +0 -40
  421. package/dist/devices-matter/OnOffOutletAccessory.js.map +0 -1
  422. package/dist/devices-matter/OnOffSwitchAccessory.d.ts +0 -14
  423. package/dist/devices-matter/OnOffSwitchAccessory.d.ts.map +0 -1
  424. package/dist/devices-matter/OnOffSwitchAccessory.js +0 -42
  425. package/dist/devices-matter/OnOffSwitchAccessory.js.map +0 -1
  426. package/dist/devices-matter/RoboticVacuumAccessory.d.ts +0 -68
  427. package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +0 -1
  428. package/dist/devices-matter/RoboticVacuumAccessory.js +0 -334
  429. package/dist/devices-matter/RoboticVacuumAccessory.js.map +0 -1
  430. package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts +0 -11
  431. package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts.map +0 -1
  432. package/dist/devices-matter/SmokeCOAlarmAccessory.js +0 -49
  433. package/dist/devices-matter/SmokeCOAlarmAccessory.js.map +0 -1
  434. package/dist/devices-matter/TemperatureSensorAccessory.d.ts +0 -12
  435. package/dist/devices-matter/TemperatureSensorAccessory.d.ts.map +0 -1
  436. package/dist/devices-matter/TemperatureSensorAccessory.js +0 -36
  437. package/dist/devices-matter/TemperatureSensorAccessory.js.map +0 -1
  438. package/dist/devices-matter/ThermostatAccessory.d.ts +0 -19
  439. package/dist/devices-matter/ThermostatAccessory.d.ts.map +0 -1
  440. package/dist/devices-matter/ThermostatAccessory.js +0 -95
  441. package/dist/devices-matter/ThermostatAccessory.js.map +0 -1
  442. package/dist/devices-matter/VenetianBlindAccessory.d.ts +0 -19
  443. package/dist/devices-matter/VenetianBlindAccessory.d.ts.map +0 -1
  444. package/dist/devices-matter/VenetianBlindAccessory.js +0 -99
  445. package/dist/devices-matter/VenetianBlindAccessory.js.map +0 -1
  446. package/dist/devices-matter/WindowBlindAccessory.d.ts +0 -17
  447. package/dist/devices-matter/WindowBlindAccessory.d.ts.map +0 -1
  448. package/dist/devices-matter/WindowBlindAccessory.js +0 -80
  449. package/dist/devices-matter/WindowBlindAccessory.js.map +0 -1
  450. package/dist/devices-matter/custom/PowerStripAccessory.d.ts +0 -97
  451. package/dist/devices-matter/custom/PowerStripAccessory.d.ts.map +0 -1
  452. package/dist/devices-matter/custom/PowerStripAccessory.js +0 -265
  453. package/dist/devices-matter/custom/PowerStripAccessory.js.map +0 -1
  454. package/dist/devices-matter/custom/index.d.ts +0 -8
  455. package/dist/devices-matter/custom/index.d.ts.map +0 -1
  456. package/dist/devices-matter/custom/index.js +0 -8
  457. package/dist/devices-matter/custom/index.js.map +0 -1
  458. package/dist/devices-matter/index.d.ts +0 -29
  459. package/dist/devices-matter/index.d.ts.map +0 -1
  460. package/dist/devices-matter/index.js +0 -28
  461. package/dist/devices-matter/index.js.map +0 -1
  462. package/dist/index.test.d.ts +0 -2
  463. package/dist/index.test.d.ts.map +0 -1
  464. package/dist/index.test.js +0 -19
  465. package/dist/index.test.js.map +0 -1
  466. package/dist/irdevice/airconditioner.d.ts +0 -61
  467. package/dist/irdevice/airconditioner.d.ts.map +0 -1
  468. package/dist/irdevice/airconditioner.js +0 -472
  469. package/dist/irdevice/airconditioner.js.map +0 -1
  470. package/dist/irdevice/airpurifier.d.ts +0 -50
  471. package/dist/irdevice/airpurifier.d.ts.map +0 -1
  472. package/dist/irdevice/airpurifier.js +0 -213
  473. package/dist/irdevice/airpurifier.js.map +0 -1
  474. package/dist/irdevice/camera.d.ts +0 -32
  475. package/dist/irdevice/camera.d.ts.map +0 -1
  476. package/dist/irdevice/camera.js +0 -107
  477. package/dist/irdevice/camera.js.map +0 -1
  478. package/dist/irdevice/fan.d.ts +0 -36
  479. package/dist/irdevice/fan.d.ts.map +0 -1
  480. package/dist/irdevice/fan.js +0 -200
  481. package/dist/irdevice/fan.js.map +0 -1
  482. package/dist/irdevice/irdevice.d.ts +0 -68
  483. package/dist/irdevice/irdevice.d.ts.map +0 -1
  484. package/dist/irdevice/irdevice.js +0 -298
  485. package/dist/irdevice/irdevice.js.map +0 -1
  486. package/dist/irdevice/light.d.ts +0 -36
  487. package/dist/irdevice/light.d.ts.map +0 -1
  488. package/dist/irdevice/light.js +0 -206
  489. package/dist/irdevice/light.js.map +0 -1
  490. package/dist/irdevice/other.d.ts +0 -57
  491. package/dist/irdevice/other.d.ts.map +0 -1
  492. package/dist/irdevice/other.js +0 -778
  493. package/dist/irdevice/other.js.map +0 -1
  494. package/dist/irdevice/tv.d.ts +0 -45
  495. package/dist/irdevice/tv.d.ts.map +0 -1
  496. package/dist/irdevice/tv.js +0 -327
  497. package/dist/irdevice/tv.js.map +0 -1
  498. package/dist/irdevice/vacuumcleaner.d.ts +0 -28
  499. package/dist/irdevice/vacuumcleaner.d.ts.map +0 -1
  500. package/dist/irdevice/vacuumcleaner.js +0 -104
  501. package/dist/irdevice/vacuumcleaner.js.map +0 -1
  502. package/dist/irdevice/waterheater.d.ts +0 -30
  503. package/dist/irdevice/waterheater.d.ts.map +0 -1
  504. package/dist/irdevice/waterheater.js +0 -105
  505. package/dist/irdevice/waterheater.js.map +0 -1
  506. package/dist/platform-hap.d.ts +0 -149
  507. package/dist/platform-hap.d.ts.map +0 -1
  508. package/dist/platform-hap.js +0 -2861
  509. package/dist/platform-hap.js.map +0 -1
  510. package/dist/platform-matter.d.ts +0 -120
  511. package/dist/platform-matter.d.ts.map +0 -1
  512. package/dist/platform-matter.js +0 -984
  513. package/dist/platform-matter.js.map +0 -1
  514. package/dist/verifyconfig.test.d.ts +0 -2
  515. package/dist/verifyconfig.test.d.ts.map +0 -1
  516. package/dist/verifyconfig.test.js +0 -167
  517. package/dist/verifyconfig.test.js.map +0 -1
  518. package/src/custom.d.ts +0 -7
  519. package/src/devices-hap/airpurifier.ts +0 -563
  520. package/src/devices-hap/blindtilt.ts +0 -1049
  521. package/src/devices-hap/bot.ts +0 -900
  522. package/src/devices-hap/ceilinglight.ts +0 -742
  523. package/src/devices-hap/colorbulb.ts +0 -904
  524. package/src/devices-hap/contact.ts +0 -457
  525. package/src/devices-hap/curtain.ts +0 -944
  526. package/src/devices-hap/device.ts +0 -811
  527. package/src/devices-hap/fan.ts +0 -711
  528. package/src/devices-hap/hub.ts +0 -439
  529. package/src/devices-hap/humidifier.ts +0 -669
  530. package/src/devices-hap/iosensor.ts +0 -427
  531. package/src/devices-hap/lightstrip.ts +0 -836
  532. package/src/devices-hap/lock.ts +0 -620
  533. package/src/devices-hap/meter.ts +0 -426
  534. package/src/devices-hap/meterplus.ts +0 -430
  535. package/src/devices-hap/meterpro.ts +0 -522
  536. package/src/devices-hap/motion.ts +0 -390
  537. package/src/devices-hap/plug.ts +0 -423
  538. package/src/devices-hap/relayswitch.ts +0 -727
  539. package/src/devices-hap/robotvacuumcleaner.ts +0 -568
  540. package/src/devices-hap/waterdetector.ts +0 -400
  541. package/src/devices-matter/BaseMatterAccessory.ts +0 -131
  542. package/src/devices-matter/ColorLightAccessory.ts +0 -110
  543. package/src/devices-matter/ColorTemperatureLightAccessory.ts +0 -92
  544. package/src/devices-matter/ContactSensorAccessory.ts +0 -41
  545. package/src/devices-matter/DimmableLightAccessory.ts +0 -192
  546. package/src/devices-matter/DoorLockAccessory.ts +0 -60
  547. package/src/devices-matter/ExtendedColorLightAccessory.ts +0 -123
  548. package/src/devices-matter/FanAccessory.ts +0 -95
  549. package/src/devices-matter/HumiditySensorAccessory.ts +0 -41
  550. package/src/devices-matter/LeakSensorAccessory.ts +0 -40
  551. package/src/devices-matter/LightSensorAccessory.ts +0 -41
  552. package/src/devices-matter/OccupancySensorAccessory.ts +0 -48
  553. package/src/devices-matter/OnOffLightAccessory.ts +0 -133
  554. package/src/devices-matter/OnOffOutletAccessory.ts +0 -46
  555. package/src/devices-matter/OnOffSwitchAccessory.ts +0 -51
  556. package/src/devices-matter/RoboticVacuumAccessory.ts +0 -407
  557. package/src/devices-matter/SmokeCOAlarmAccessory.ts +0 -59
  558. package/src/devices-matter/TemperatureSensorAccessory.ts +0 -43
  559. package/src/devices-matter/ThermostatAccessory.ts +0 -110
  560. package/src/devices-matter/VenetianBlindAccessory.ts +0 -115
  561. package/src/devices-matter/WindowBlindAccessory.ts +0 -92
  562. package/src/devices-matter/custom/PowerStripAccessory.ts +0 -309
  563. package/src/devices-matter/custom/index.ts +0 -8
  564. package/src/devices-matter/index.ts +0 -29
  565. package/src/index.test.ts +0 -24
  566. package/src/irdevice/airconditioner.ts +0 -533
  567. package/src/irdevice/airpurifier.ts +0 -252
  568. package/src/irdevice/camera.ts +0 -129
  569. package/src/irdevice/fan.ts +0 -226
  570. package/src/irdevice/irdevice.ts +0 -344
  571. package/src/irdevice/light.ts +0 -246
  572. package/src/irdevice/other.ts +0 -790
  573. package/src/irdevice/tv.ts +0 -378
  574. package/src/irdevice/vacuumcleaner.ts +0 -126
  575. package/src/irdevice/waterheater.ts +0 -127
  576. package/src/platform-hap.ts +0 -2997
  577. package/src/platform-matter.ts +0 -1110
  578. package/src/verifyconfig.test.ts +0 -197
@@ -0,0 +1,111 @@
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('normalizes Color Bulb and Motion Sensor to Matter-supported categories', async () => {
10
+ const matterApi = makeFakeMatterApi()
11
+ const api = makeFakeApiWithMatter(matterApi)
12
+
13
+ const platform = new SwitchBotMatterPlatform(console as any, {
14
+ devices: [
15
+ { id: 'cb-1', type: 'Color Bulb', name: 'Color Bulb 1' },
16
+ { id: 'ms-1', type: 'Motion Sensor', name: 'Motion Sensor 1' },
17
+ ],
18
+ enableMatter: true,
19
+ preferMatter: true,
20
+ } as any, api as any)
21
+
22
+ await (platform as any).registerMatterAccessories()
23
+
24
+ const regs = matterApi.getRegistered()
25
+ expect(regs.length).toBeGreaterThanOrEqual(2)
26
+ const ids = regs.map((r: any) => r?.context?.deviceId)
27
+ expect(ids).toContain('cb-1')
28
+ expect(ids).toContain('ms-1')
29
+ })
30
+
31
+ it('uses conformant window covering type for curtain Matter registration', async () => {
32
+ const matterApi = makeFakeMatterApi()
33
+ const api = makeFakeApiWithMatter(matterApi)
34
+
35
+ const platform = new SwitchBotMatterPlatform(console as any, {
36
+ devices: [{ id: 'curtain-1', type: 'curtain', name: 'Curtain 1' }],
37
+ enableMatter: true,
38
+ preferMatter: true,
39
+ } as any, api as any)
40
+
41
+ await (platform as any).registerMatterAccessories()
42
+
43
+ const regs = matterApi.getRegistered()
44
+ expect(regs.length).toBeGreaterThanOrEqual(1)
45
+ const wcType = regs[0]?.clusters?.windowCovering?.type
46
+ expect(wcType).toBe(0)
47
+ expect(wcType).not.toBe(4)
48
+ })
49
+
50
+ it('registers Matter accessories when Matter API available', async () => {
51
+ // Instantiate the real Matter platform directly to test its registration path
52
+ const matterApi = makeFakeMatterApi()
53
+ const api = makeFakeApiWithMatter(matterApi)
54
+
55
+ const platform = new SwitchBotMatterPlatform(console as any, { devices: [{ id: 'm-1', type: 'light', name: 'L1' }], enableMatter: true, preferMatter: true } as any, api as any)
56
+
57
+ // Call register path directly
58
+ if (typeof (platform as any).registerMatterAccessories === 'function') {
59
+ await (platform as any).registerMatterAccessories()
60
+ } else if (typeof (platform as any).loadDevices === 'function') {
61
+ await (platform as any).loadDevices()
62
+ }
63
+
64
+ const regs = matterApi.getRegistered()
65
+ expect(regs.length).toBeGreaterThanOrEqual(1)
66
+ // The platform registers serialized accessories; ensure deviceId is present in context
67
+ expect(regs[0]).toHaveProperty('context')
68
+ expect(regs[0].context).toHaveProperty('deviceId', 'm-1')
69
+ })
70
+
71
+ it('falls back to HAP when Matter API unavailable', async () => {
72
+ // Directly instantiate the HAP platform to exercise HAP fallback
73
+ const api = makeFakeApiWithoutMatter()
74
+ const platform = new SwitchBotHAPPlatform(console as any, { devices: [{ id: 'h-1', type: 'light', name: 'H1' }], enableMatter: true, preferMatter: true } as any, api as any)
75
+
76
+ // call loadDevices for HAP flow
77
+ if (typeof (platform as any).loadDevices === 'function') await (platform as any).loadDevices()
78
+
79
+ // Platform should have an accessories map populated
80
+ const accMap = (platform as any).accessories
81
+ expect(accMap).toBeDefined()
82
+ const keys = Array.from(accMap.keys())
83
+ expect(keys.length).toBeGreaterThanOrEqual(1)
84
+ })
85
+
86
+ it('reuses restored Matter accessories instead of registering duplicates', async () => {
87
+ const matterApi = makeFakeMatterApi()
88
+ const api = makeFakeApiWithMatter(matterApi)
89
+
90
+ const platform = new SwitchBotMatterPlatform(console as any, { devices: [{ id: 'r-1', type: 'light', name: 'R1' }], enableMatter: true, preferMatter: true } as any, api as any)
91
+
92
+ // Simulate a restored accessory present before registration
93
+ const restored = { uuid: matterApi.uuid.generate('r-1'), displayName: 'Restored R1', context: { deviceId: 'r-1' } }
94
+ if (typeof (platform as any).configureMatterAccessory === 'function') {
95
+ await (platform as any).configureMatterAccessory(restored)
96
+ } else if (typeof (platform as any).configureAccessory === 'function') {
97
+ await (platform as any).configureAccessory(restored)
98
+ }
99
+
100
+ // Now call register path
101
+ if (typeof (platform as any).registerMatterAccessories === 'function') await (platform as any).registerMatterAccessories()
102
+ else if (typeof (platform as any).loadDevices === 'function') {
103
+ await (platform as any).loadDevices()
104
+ if (typeof (platform as any).registerMatterAccessories === 'function') await (platform as any).registerMatterAccessories()
105
+ }
106
+
107
+ const accs = (platform as any).accessories
108
+ const found = Array.from(accs.values()).some((a: any) => a && (a.uuid === restored.uuid || a.UUID === restored.uuid))
109
+ expect(found).toBeTruthy()
110
+ })
111
+ })
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { SwitchBotHAPPlatform } from '../src/platform'
3
+
4
+ // Minimal mock logger
5
+ const log = {
6
+ info: () => {},
7
+ warn: () => {},
8
+ error: () => {},
9
+ debug: () => {},
10
+ }
11
+
12
+ describe('platform integration (HAP)', () => {
13
+ it('registers HAP accessories using Homebridge API', async () => {
14
+ const registered: any[] = []
15
+
16
+ // Minimal hap stub
17
+ const hap = {
18
+ uuid: { generate: (s: string) => `uuid-${s}` },
19
+ Service: { Switch: 'Switch', Lightbulb: 'Lightbulb' },
20
+ Characteristic: { On: 'On', Brightness: 'Brightness' },
21
+ }
22
+
23
+ // Simple platformAccessory constructor
24
+ function PlatformAccessory(name: string, uuid: string) {
25
+ this.displayName = name
26
+ this.UUID = uuid
27
+ this.services = []
28
+ this.getService = (type: any) => this.services.find((s: any) => s.type === type)
29
+ this.addService = (type: any) => {
30
+ const s: any = { type, characteristics: {} }
31
+ this.services.push(s)
32
+ return s
33
+ }
34
+ }
35
+
36
+ const api: any = {
37
+ hap,
38
+ platformAccessory: PlatformAccessory as any,
39
+ registerPlatformAccessories: (_p: string, _n: string, accs: any[]) => registered.push(...accs),
40
+ on: (_ev: string, cb: Function) => { /* do not auto-run */ },
41
+ }
42
+
43
+ const config: any = { devices: [{ id: 'dev1', type: 'light', name: 'L1' }] }
44
+
45
+ const platform = new SwitchBotHAPPlatform(log as any, config, api)
46
+
47
+ // call loadDevices directly to control flow
48
+ await (platform as any).loadDevices()
49
+
50
+ // Expect accessory created and registered
51
+ expect(registered.length).toBeGreaterThanOrEqual(1)
52
+ const keys = Array.from((platform as any).accessories.keys())
53
+ expect(keys.some((k: string) => k.includes('dev1'))).toBeTruthy()
54
+ })
55
+ })
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { SwitchBotClient } from '../src/switchbotClient'
3
+
4
+ describe('SwitchBotClient debounce/coalesce', () => {
5
+ let origFetch: any
6
+
7
+ beforeEach(() => {
8
+ origFetch = (global as any).fetch
9
+ vi.useFakeTimers()
10
+ })
11
+
12
+ afterEach(() => {
13
+ (global as any).fetch = origFetch
14
+ vi.restoreAllMocks()
15
+ vi.useRealTimers()
16
+ })
17
+
18
+ it('coalesces rapid writes for the same device (last write wins)', async () => {
19
+ const respBody = { status: 'ok' }
20
+ const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
21
+ ;(global as any).fetch = fetchMock
22
+
23
+ // Set a short debounce in config to keep the test fast
24
+ const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
25
+ await c.init()
26
+
27
+ const body1 = { command: 'one' }
28
+ const body2 = { command: 'two' }
29
+
30
+ const p1 = c.setDeviceState('d-debounce', body1)
31
+ const p2 = c.setDeviceState('d-debounce', body2)
32
+
33
+ // Fast-forward timers past debounce window
34
+ vi.advanceTimersByTime(20)
35
+ // Allow pending microtasks to resolve
36
+ await Promise.resolve()
37
+ await Promise.resolve()
38
+
39
+ const [r1, r2] = await Promise.all([p1, p2])
40
+ expect(r1).toEqual(respBody)
41
+ expect(r2).toEqual(respBody)
42
+
43
+ // Only one network call should have been made
44
+ expect(fetchMock).toHaveBeenCalledTimes(1)
45
+ const calledBody = JSON.parse(fetchMock.mock.calls[0][1].body)
46
+ expect(calledBody).toEqual(body2)
47
+ })
48
+
49
+ it('does not coalesce writes for different devices', async () => {
50
+ const respBody = { status: 'ok' }
51
+ const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
52
+ ;(global as any).fetch = fetchMock
53
+
54
+ const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
55
+ await c.init()
56
+
57
+ const p1 = c.setDeviceState('d-A', { command: 'a' })
58
+ const p2 = c.setDeviceState('d-B', { command: 'b' })
59
+
60
+ vi.advanceTimersByTime(20)
61
+ await Promise.resolve()
62
+ await Promise.resolve()
63
+
64
+ await Promise.all([p1, p2])
65
+
66
+ expect(fetchMock).toHaveBeenCalledTimes(2)
67
+ const b1 = JSON.parse(fetchMock.mock.calls[0][1].body)
68
+ const b2 = JSON.parse(fetchMock.mock.calls[1][1].body)
69
+ expect(b1).toEqual({ command: 'a' })
70
+ expect(b2).toEqual({ command: 'b' })
71
+ })
72
+
73
+ it('coalesces multiple rapid writes (three) and resolves all promises with last response', async () => {
74
+ const respBody = { status: 'ok', val: 'last' }
75
+ const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
76
+ ;(global as any).fetch = fetchMock
77
+
78
+ const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
79
+ await c.init()
80
+
81
+ const p1 = c.setDeviceState('d-debounce', { command: 'one' })
82
+ const p2 = c.setDeviceState('d-debounce', { command: 'two' })
83
+ const p3 = c.setDeviceState('d-debounce', { command: 'three' })
84
+
85
+ vi.advanceTimersByTime(20)
86
+ await Promise.resolve()
87
+ await Promise.resolve()
88
+
89
+ const [r1, r2, r3] = await Promise.all([p1, p2, p3])
90
+ expect(r1).toEqual(respBody)
91
+ expect(r2).toEqual(respBody)
92
+ expect(r3).toEqual(respBody)
93
+
94
+ expect(fetchMock).toHaveBeenCalledTimes(1)
95
+ const calledBody = JSON.parse(fetchMock.mock.calls[0][1].body)
96
+ expect(calledBody).toEqual({ command: 'three' })
97
+ })
98
+
99
+ it('separate writes outside debounce window produce multiple network calls', async () => {
100
+ const respBody1 = { status: 'ok', seq: 1 }
101
+ const respBody2 = { status: 'ok', seq: 2 }
102
+ const fetchMock = vi.fn()
103
+ .mockImplementationOnce(async () => ({ json: async () => respBody1 }))
104
+ .mockImplementationOnce(async () => ({ json: async () => respBody2 }))
105
+ ;(global as any).fetch = fetchMock
106
+
107
+ const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
108
+ await c.init()
109
+
110
+ const p1 = c.setDeviceState('d-debounce', { command: 'one' })
111
+ vi.advanceTimersByTime(20)
112
+ await Promise.resolve()
113
+ await Promise.resolve()
114
+
115
+ const p2 = c.setDeviceState('d-debounce', { command: 'two' })
116
+ vi.advanceTimersByTime(20)
117
+ await Promise.resolve()
118
+ await Promise.resolve()
119
+
120
+ const r1 = await p1
121
+ const r2 = await p2
122
+
123
+ expect(fetchMock).toHaveBeenCalledTimes(2)
124
+ const b1 = JSON.parse(fetchMock.mock.calls[0][1].body)
125
+ const b2 = JSON.parse(fetchMock.mock.calls[1][1].body)
126
+ expect(b1).toEqual({ command: 'one' })
127
+ expect(b2).toEqual({ command: 'two' })
128
+ expect(r1).toEqual(respBody1)
129
+ expect(r2).toEqual(respBody2)
130
+ })
131
+ })
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { SwitchBotClient } from '../src/switchbotClient'
3
+
4
+ describe('SwitchBotClient OpenAPI fallback', () => {
5
+ let origFetch: any
6
+
7
+ beforeEach(() => {
8
+ origFetch = (global as any).fetch
9
+ })
10
+
11
+ afterEach(() => {
12
+ (global as any).fetch = origFetch
13
+ vi.restoreAllMocks()
14
+ })
15
+
16
+ it('throws when no client and no token', async () => {
17
+ const c = new SwitchBotClient({} as any)
18
+ await c.init()
19
+ await expect(c.getDevice('nope')).rejects.toThrow('No SwitchBot client available')
20
+ })
21
+
22
+ it('uses OpenAPI when token provided for getDevice', async () => {
23
+ const fakeResp = { body: { device: { id: 'd1', state: 'ok' } } }
24
+ const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => fakeResp }))
25
+ ;(global as any).fetch = fetchMock
26
+
27
+ const c = new SwitchBotClient({ openApiToken: 'token-123' } as any)
28
+ await c.init()
29
+ const res = await c.getDevice('d1')
30
+ expect(res).toEqual(fakeResp)
31
+ expect(fetchMock).toHaveBeenCalledWith('https://api.switch-bot.com/v1.0/devices/d1', { headers: { Authorization: 'token-123' } })
32
+ })
33
+
34
+ it('posts commands to OpenAPI for setDeviceState', async () => {
35
+ const respBody = { status: 'ok' }
36
+ const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
37
+ ;(global as any).fetch = fetchMock
38
+
39
+ const c = new SwitchBotClient({ openApiToken: 't' } as any)
40
+ await c.init()
41
+ const body = { command: 'turnOn', parameter: 'default', commandType: 'command' }
42
+ const out = await c.setDeviceState('d2', body)
43
+ expect(out).toEqual(respBody)
44
+ expect(fetchMock).toHaveBeenCalledWith('https://api.switch-bot.com/v1.0/devices/d2/commands', expect.objectContaining({ method: 'POST' }))
45
+ const calledBody = JSON.parse((fetchMock.mock.calls[0][1].body))
46
+ expect(calledBody).toEqual(body)
47
+ })
48
+
49
+ it('propagates fetch errors', async () => {
50
+ const fetchMock = vi.fn(async () => { throw new Error('network') })
51
+ ;(global as any).fetch = fetchMock
52
+ const c = new SwitchBotClient({ openApiToken: 't' } as any)
53
+ await c.init()
54
+ await expect(c.getDevice('x')).rejects.toThrow('network')
55
+ })
56
+ })
@@ -0,0 +1,10 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { SwitchBotClient } from '../src/switchbotClient'
3
+
4
+ describe('SwitchBotClient', () => {
5
+ it('init does not throw and getDevice throws when no token/client', async () => {
6
+ const c = new SwitchBotClient({})
7
+ await c.init()
8
+ await expect(c.getDevice('nope')).rejects.toThrow()
9
+ })
10
+ })
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createPlatformProxy } from '../src/utils'
3
+
4
+ describe('createPlatformProxy', () => {
5
+ it('returns Matter platform when preferMatter, enabled, and Matter API is available', () => {
6
+ class HAP {}
7
+ class Matter {}
8
+ const ProxyCtor: any = createPlatformProxy(HAP, Matter)
9
+ const api = {
10
+ isMatterAvailable: () => true,
11
+ isMatterEnabled: () => true,
12
+ }
13
+ const impl = new (ProxyCtor as any)(console, { preferMatter: true, enableMatter: true }, api)
14
+ expect(impl).toBeInstanceOf(Matter)
15
+ })
16
+
17
+ it('returns HAP platform when preferMatter is false', () => {
18
+ class HAP {}
19
+ class Matter {}
20
+ const ProxyCtor: any = createPlatformProxy(HAP, Matter)
21
+ const impl = new (ProxyCtor as any)(console, { preferMatter: false, enableMatter: true }, undefined)
22
+ expect(impl).toBeInstanceOf(HAP)
23
+ })
24
+
25
+ it('returns HAP platform when Matter API is unavailable', () => {
26
+ class HAP {}
27
+ class Matter {}
28
+ const ProxyCtor: any = createPlatformProxy(HAP, Matter)
29
+ const api = {
30
+ isMatterAvailable: () => false,
31
+ isMatterEnabled: () => false,
32
+ }
33
+ const impl = new (ProxyCtor as any)(console, { preferMatter: true, enableMatter: true }, api)
34
+ expect(impl).toBeInstanceOf(HAP)
35
+ })
36
+ })
@@ -0,0 +1,229 @@
1
+ # Completed TODOs
2
+
3
+ This file tracks all completed features and improvements for the SwitchBot Homebridge Plugin.
4
+
5
+ ---
6
+
7
+ ## Code Refactoring & Architecture
8
+
9
+ ### Server-Side Refactoring ✅ COMPLETED
10
+
11
+ - [x] **Split `server.ts` into modular structure**
12
+ - [x] Created `src/homebridge-ui/endpoints/discovery.ts` - Discovery endpoint
13
+ - [x] Created `src/homebridge-ui/endpoints/config.ts` - Config management endpoint
14
+ - [x] Created `src/homebridge-ui/endpoints/devices.ts` - Device CRUD operations
15
+ - [x] Created `src/homebridge-ui/endpoints/credentials.ts` - Credentials endpoint
16
+ - [x] Created `src/homebridge-ui/utils/config-parser.ts` - Config parsing helpers
17
+ - [x] Created `src/homebridge-ui/utils/logger.ts` - Logging utility
18
+ - [x] Updated `src/homebridge-ui/server.ts` - Main entry point (imports and registers endpoints)
19
+
20
+ **Status:** Server endpoints are fully modularized and organized by concern.
21
+
22
+ ---
23
+
24
+ ## Bug Fixes
25
+
26
+ ### Matter Window Covering Conformance ✅ COMPLETED
27
+
28
+ - [x] **Fix Matter window covering Drapery enum validation error**
29
+ - [x] **Issue:** Matter accessory registration failed for Curtain devices
30
+ - [x] **Error:** `Conformance "LF & !TL": Matter does not allow enum value Drapery (ID 4) here`
31
+ - [x] **Related Runtime Failure:** `[Matter/Server] Failed to register Matter accessory ... [endpoint-behaviors] Behaviors have errors` with rollback from Matter transaction initialization
32
+ - [x] **Root Cause:** `windowCovering.type` used `Drapery` for lift-only curtain mappings
33
+ - [x] **Fix:** Updated curtain Matter default to a compliant lift-only covering enum and added regression coverage
34
+
35
+ **Status:** Curtain-type Matter accessories now register without the Drapery conformance failure.
36
+
37
+ ### Invalid Device Type Validation ✅ COMPLETED
38
+
39
+ - [x] **Fix invalid device type in config schema validation**
40
+ - [x] **Issue:** Config validation rejects devices with invalid `configDeviceType` values (e.g., "Lock Vision Pro" is not in valid enum)
41
+ - [x] **Error:** "Value is not accepted. Valid values: ..." error shown in Homebridge UI
42
+ - [x] **Root Cause:** Device types in existing configs may not match current DEVICE_TYPES enum (legacy names, typos, or renamed devices)
43
+ - [x] **Affected Devices:** Any device with `configDeviceType` not matching DEVICE_TYPES enum
44
+ - [x] **Solution:** Implemented device type validation migration and auto-correction
45
+
46
+ **Implementation:**
47
+ - Added `normalizeDeviceType()` — Validates and maps invalid types to valid equivalents
48
+ - Added `isValidDeviceType()` — Checks if device type is in DEVICE_TYPES enum
49
+ - Added `getValidDeviceTypes()` — Returns set of all valid device types
50
+ - Created `src/homebridge-ui/utils/device-migration.ts` — Migration utilities for batch validation
51
+ - Updated `src/homebridge-ui/endpoints/config.ts` — Device loading now validates types and warns about invalid ones
52
+ - Added migration mapping: `"Lock Vision Pro"` → `"Keypad Vision Pro"` (and others)
53
+ - Config endpoint returns `validationWarnings` with invalid device types and suggestions
54
+
55
+ **Status:** Device type validation is now automatic. Invalid types are detected and suggestions provided to users. Auto-correction mappings handle legacy/typo device names.
56
+
57
+ ---
58
+
59
+ ### Client-Side Refactoring
60
+
61
+ #### Phase 1: Quick Wins
62
+
63
+ ##### Extract DEVICE_TYPES constant ✅ COMPLETED
64
+
65
+ - [x] Created `src/device-types.ts` as single source of truth
66
+ - [x] Updated `src/homebridge-ui/public/js/constants.ts` to import from shared source
67
+ - [x] Added `DEVICE_TYPE_NORMALIZATION_MAP` for plugin type lookups
68
+ - [x] Updated `src/deviceFactory.ts` to use normalized type mappings
69
+ - [x] Created `scripts/sync-device-types.mjs` to auto-sync config.schema.json
70
+ - [x] Integrated sync into build process (runs before tsc)
71
+
72
+ **Status:** Device types now single-sourced with auto-sync. Config schema, UI dropdown, and plugin type handling all stay aligned automatically.
73
+
74
+ ##### Extract CSS styles ✅ COMPLETED
75
+
76
+ ![Modal Theme Mockup](mockups/modal-switchbot-theme.svg)
77
+
78
+ - [x] Created `src/homebridge-ui/public/css/styles.css`
79
+ - [x] Moved all inline styles from `src/homebridge-ui/public/index.html` (2.3kb)
80
+ - [x] Updated HTML to `<link rel="stylesheet" href="css/styles.css">`
81
+ - [x] Verified build copies CSS to dist/
82
+ - [x] Tested in browser to confirm styles still work
83
+ - [x] Added SwitchBot red color variables and theme
84
+ - [x] Enhanced modal styling with red accent borders
85
+ - [x] Improved button styling with hover animations
86
+
87
+ **Status:** CSS now externalized and properly linked. Build copies files correctly to dist/. Modal uses SwitchBot red theme with Homebridge-like appearance.
88
+
89
+ #### Phase 2: JavaScript Modularization ✅ COMPLETED
90
+
91
+ - [x] Created `src/homebridge-ui/public/js/api.ts` - API client functions
92
+ - [x] Created `src/homebridge-ui/public/js/discovery.ts` - Discovery UI rendering
93
+ - [x] Created `src/homebridge-ui/public/js/modals.ts` - Modal management
94
+ - [x] Created `src/homebridge-ui/public/js/credentials.ts` - Credentials UI
95
+ - [x] Created `src/homebridge-ui/public/js/toast.ts` & `modal.ts` - UI helpers
96
+ - [x] Created `src/homebridge-ui/public/js/app.ts` - Main app init
97
+ - [x] Updated `src/homebridge-ui/public/index.html` to use bundled app.js
98
+
99
+ **Status:** UI is modularized in TypeScript. Build bundles into app.js (47.0kb). Direct TypeScript imports provide type safety.
100
+
101
+ ---
102
+
103
+ ## Discovery System Enhancements
104
+
105
+ ### Signal Strength Visualization ✅ COMPLETED
106
+
107
+ ![Signal Strength Mockup](mockups/signal-strength-visualization.svg)
108
+
109
+ - [x] Implement RSSI color-coding system:
110
+ - [x] Green: > -60 dBm (excellent)
111
+ - [x] Yellow: -60 to -75 dBm (good)
112
+ - [x] Orange: -75 to -85 dBm (fair)
113
+ - [x] Red: < -85 dBm (poor/unreliable)
114
+ - [x] Add signal strength bars visual indicator (4-bar system)
115
+ - [x] Add tooltip explaining signal quality
116
+
117
+ **Implementation:**
118
+ - Added `getRssiSignalQuality()` — Determines RSSI level, color, description, and bar count
119
+ - Added `renderSignalBars()` — Visual 4-bar indicator (filled based on signal strength)
120
+ - Added `renderSignalQualityBadge()` — Color-coded badge showing quality level (Excellent/Good/Fair/Poor)
121
+ - Updated `renderDiscoveredDevices()` — Displays bars and badges next to device names for BLE/wireless devices
122
+ - Tooltips show detailed signal info on hover
123
+
124
+ **Status:** Signal visualization now displays for all discovered devices with RSSI data. Color-coded bars and badges provide instant visual feedback about connection quality.
125
+
126
+ ### Filtering & Sorting ✅ COMPLETED
127
+
128
+ ![Filtering & Sorting Mockup](mockups/filtering-sorting.svg)
129
+
130
+ - [x] Add filter buttons: "All | BLE Only | API Only | Both | IR Devices"
131
+ - [x] Add sort dropdown: "Signal Strength | Name | Type | Connection"
132
+ - [x] Add search box to filter by name/ID/type (real-time filtering)
133
+ - [x] Persist filter/sort preferences in localStorage
134
+
135
+ **Implementation:**
136
+ - Added `filterDevices()` — Filters by connection type (BLE/API/Both/IR) and search query (matches name/id/type/model)
137
+ - Added `sortDevices()` — Sorts by name (alphabetical), signal strength (RSSI descending), device type, or connection type
138
+ - Added `getDiscoveryPreferences()` & `setDiscoveryPreferences()` — Persist user preferences in localStorage
139
+ - Updated `discovery.ts` — Creates filter buttons, sort dropdown, and search input dynamically
140
+ - All filtering/sorting is client-side (instant, no API calls)
141
+ - Search is debounced (300ms) for smooth typing
142
+ - Filter buttons highlight active selection
143
+ - Status shows "Showing X of Y device(s)" when filtered
144
+
145
+ **Status:** Full filtering and sorting implemented with localStorage persistence. Discovery page now shows:
146
+ - Filter bar with 5 buttons (All/BLE/API/Both/IR)
147
+ - Sort dropdown with 4 options (Name/Signal Strength/Type/Connection)
148
+ - Real-time search box
149
+ - All preferences saved and restored on next discovery
150
+
151
+ ### One-Click Device Import ✅ COMPLETED
152
+
153
+ - [x] Added "Add to Config" button for each discovered device
154
+ - [x] Added auto-populated import modal with discovered name/type defaults
155
+ - [x] Added BLE MAC pre-fill in import flow and persistence to config on single add
156
+ - [x] Added confirmation toast/status messaging on successful import
157
+
158
+ **Implementation:**
159
+ - Updated discovery row action in `render.ts` to use explicit "Add to Config" CTA
160
+ - Added `importDiscoveredDevice()` modal in `modals.ts` for prefilled import settings
161
+ - Extended single-add client payload in `api.ts` to include optional `address`, `model`, and `rssi`
162
+ - Extended `/add-device` endpoint in `devices.ts` to persist discovered metadata (MAC/model/RSSI)
163
+
164
+ **Status:** One-click import now supports prefilled configuration and BLE MAC carry-through for direct connection setup.
165
+
166
+ ### Duplicate Detection ✅ COMPLETED
167
+
168
+ - [x] Check if discovered device ID already exists in config
169
+ - [x] Show badge: "✓ Already Added" (green) or "➕ New Device" (blue)
170
+ - [x] Disable/hide "Add" button for already-added devices
171
+ - [x] Add "View in Config" link for existing devices
172
+
173
+ **Implementation:**
174
+ - Discovery view now fetches configured device IDs and tags discovered rows as existing/new
175
+ - Existing devices show `Already Added` state with disabled add action
176
+ - Existing devices include a `View in Config` action that scrolls and highlights the configured entry
177
+
178
+ **Status:** Duplicate devices are clearly identified at discovery time, preventing accidental re-adds and improving navigation to existing config entries.
179
+
180
+ ---
181
+
182
+ ## Bug Fixes & Technical Debt
183
+
184
+ ### Fix lint & lint:fix scripts ✅ COMPLETED
185
+
186
+ - [x] Update ESLint configuration to include `src/homebridge-ui/**/*.ts` files
187
+ - [x] Add HTML linting for `src/homebridge-ui/**/*.html` files
188
+ - [x] Installed `eslint-plugin-html` for inline JavaScript in HTML
189
+ - [x] Update `npm run lint` script to lint both plugin and UI code
190
+ - [x] Update `npm run lint:fix` script to auto-fix both plugin and UI code
191
+ - [x] Fixed existing ESLint errors (regex patterns, multi-statement lines)
192
+ - [x] Lint now passes successfully
193
+ - [x] Test that `npm run lint` runs successfully without errors
194
+
195
+ **Changes Made:**
196
+ - Installed `eslint-plugin-html` package
197
+ - Updated `eslint.config.js` to import and configure HTML plugin
198
+ - Added `html: true` to formatters in ESLint config
199
+ - Updated package.json scripts: `lint` and `lint:fix` now include `src/homebridge-ui/**/*.html`
200
+ - Moved regex patterns to module scope in `server.ts` and `genericDevice.ts`
201
+ - Split multi-statement lines to comply with `style/max-statements-per-line` rule
202
+ - All lint errors fixed - `npm run lint` passes with 0 errors
203
+
204
+ ### Fix TypeScript compilation errors in discovery endpoint ✅ COMPLETED
205
+
206
+ - [x] Fixed: Changed `discover({ duration: 5000 })` to `discover({ timeout: 5000 })`
207
+ - [x] Fixed: Changed `switchbot.getDevices()` to use `switchbot.getAPIClient()?.getDevices()`
208
+ - [x] Reviewed node-switchbot v4.0.0-beta.3 API documentation and type definitions
209
+ - [x] Updated discovery implementation to use correct API types (APIDevice, APIInfraredRemote)
210
+ - [x] Fixed property names: `deviceId`, `deviceName`, `deviceType`, `remoteType`
211
+ - [x] Removed references to non-existent properties (`id`, `name`, `type`, `model`, `deviceModel`)
212
+
213
+ **Changes Made:**
214
+ - Updated BLE discovery to use `timeout` parameter instead of `duration`
215
+ - Changed OpenAPI access to use `switchbot.getAPIClient()?.getDevices()`
216
+ - Simplified response parsing: `apiData.deviceList` and `apiData.infraredRemoteList` (no `body` wrapper)
217
+ - Fixed all property accesses to match APIDevice and APIInfraredRemote interfaces
218
+ - Build now succeeds with 0 TypeScript errors
219
+ - All 39 tests still pass
220
+
221
+ ---
222
+
223
+ ## Notes
224
+
225
+ - All completed features have been tested and verified working
226
+ - Bundle size increased from 35.8kb → 47.0kb with new features
227
+ - Zero TypeScript compilation errors
228
+ - All lint checks passing
229
+ - UI mockups available in `todo/mockups/` directory