@switchbot/homebridge-switchbot 5.0.0-beta.99 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/.changeset/config.json +14 -0
  2. package/.github/copilot-instructions.md +39 -0
  3. package/.github/workflows/ci.yml +4 -1
  4. package/.github/workflows/manual-e2e.yml +6 -3
  5. package/.github/workflows/release.yml +64 -15
  6. package/.github/workflows/stale.yml +2 -4
  7. package/.husky/pre-push +15 -0
  8. package/CHANGELOG.md +126 -134
  9. package/MIGRATION.md +16 -6
  10. package/README.md +84 -3
  11. package/TODO.md +263 -0
  12. package/config.schema.json +229 -36
  13. package/dist/SwitchBotHAPPlatform.d.ts +133 -0
  14. package/dist/SwitchBotHAPPlatform.d.ts.map +1 -0
  15. package/dist/SwitchBotHAPPlatform.js +555 -0
  16. package/dist/SwitchBotHAPPlatform.js.map +1 -0
  17. package/dist/SwitchBotMatterPlatform.d.ts +141 -0
  18. package/dist/SwitchBotMatterPlatform.d.ts.map +1 -0
  19. package/dist/SwitchBotMatterPlatform.js +536 -0
  20. package/dist/SwitchBotMatterPlatform.js.map +1 -0
  21. package/dist/device-types.d.ts +31 -0
  22. package/dist/device-types.d.ts.map +1 -0
  23. package/dist/device-types.js +246 -0
  24. package/dist/device-types.js.map +1 -0
  25. package/dist/deviceCommandMapper.d.ts +10 -0
  26. package/dist/deviceCommandMapper.d.ts.map +1 -0
  27. package/dist/deviceCommandMapper.js +319 -0
  28. package/dist/deviceCommandMapper.js.map +1 -0
  29. package/dist/deviceFactory.d.ts +3 -2
  30. package/dist/deviceFactory.d.ts.map +1 -1
  31. package/dist/deviceFactory.js +107 -29
  32. package/dist/deviceFactory.js.map +1 -1
  33. package/dist/devices/genericDevice.d.ts +59 -37
  34. package/dist/devices/genericDevice.d.ts.map +1 -1
  35. package/dist/devices/genericDevice.js +376 -78
  36. package/dist/devices/genericDevice.js.map +1 -1
  37. package/dist/errors.d.ts +38 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +32 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/homebridge-ui/device-types.js +246 -0
  42. package/dist/homebridge-ui/device-types.js.map +1 -0
  43. package/dist/homebridge-ui/deviceCommandMapper.js +319 -0
  44. package/dist/homebridge-ui/deviceCommandMapper.js.map +1 -0
  45. package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
  46. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  47. package/dist/homebridge-ui/endpoints/config.js +90 -0
  48. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  49. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  50. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  51. package/dist/homebridge-ui/endpoints/devices.js +144 -0
  52. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  53. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  54. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  55. package/dist/homebridge-ui/endpoints/discovery.js +219 -0
  56. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  57. package/dist/homebridge-ui/errors.js +32 -0
  58. package/dist/homebridge-ui/errors.js.map +1 -0
  59. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +90 -0
  60. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -0
  61. package/dist/homebridge-ui/homebridge-ui/endpoints/devices.js +144 -0
  62. package/dist/homebridge-ui/homebridge-ui/endpoints/devices.js.map +1 -0
  63. package/dist/homebridge-ui/homebridge-ui/endpoints/discovery.js +219 -0
  64. package/dist/homebridge-ui/homebridge-ui/endpoints/discovery.js.map +1 -0
  65. package/dist/homebridge-ui/homebridge-ui/server.js +11 -0
  66. package/dist/homebridge-ui/homebridge-ui/server.js.map +1 -0
  67. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +108 -0
  68. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -0
  69. package/dist/homebridge-ui/homebridge-ui/utils/device-migration.js +111 -0
  70. package/dist/homebridge-ui/homebridge-ui/utils/device-migration.js.map +1 -0
  71. package/dist/homebridge-ui/homebridge-ui/utils/logger.js +17 -0
  72. package/dist/homebridge-ui/homebridge-ui/utils/logger.js.map +1 -0
  73. package/dist/homebridge-ui/public/css/styles.css +483 -0
  74. package/dist/homebridge-ui/public/index.html +197 -621
  75. package/dist/homebridge-ui/public/js/advanced-settings.d.ts +3 -0
  76. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -0
  77. package/dist/homebridge-ui/public/js/advanced-settings.js +95 -0
  78. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -0
  79. package/dist/homebridge-ui/public/js/advanced-settings.ts +94 -0
  80. package/dist/homebridge-ui/public/js/api.d.ts +66 -0
  81. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  82. package/dist/homebridge-ui/public/js/api.js +295 -0
  83. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  84. package/dist/homebridge-ui/public/js/api.ts +355 -0
  85. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  86. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  87. package/dist/homebridge-ui/public/js/app.js +3722 -0
  88. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  89. package/dist/homebridge-ui/public/js/app.ts +22 -0
  90. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  91. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  92. package/dist/homebridge-ui/public/js/constants.js +2 -0
  93. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  94. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  95. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  96. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  97. package/dist/homebridge-ui/public/js/credentials.js +99 -0
  98. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  99. package/dist/homebridge-ui/public/js/credentials.ts +105 -0
  100. package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
  101. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  102. package/dist/homebridge-ui/public/js/devices-delete.js +199 -0
  103. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  104. package/dist/homebridge-ui/public/js/devices-delete.ts +227 -0
  105. package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
  106. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  107. package/dist/homebridge-ui/public/js/devices.js +98 -0
  108. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  109. package/dist/homebridge-ui/public/js/devices.ts +106 -0
  110. package/dist/homebridge-ui/public/js/discovery.d.ts +9 -0
  111. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  112. package/dist/homebridge-ui/public/js/discovery.js +1201 -0
  113. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  114. package/dist/homebridge-ui/public/js/discovery.ts +1335 -0
  115. package/dist/homebridge-ui/public/js/logger.d.ts +7 -0
  116. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  117. package/dist/homebridge-ui/public/js/logger.js +17 -0
  118. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  119. package/dist/homebridge-ui/public/js/logger.ts +17 -0
  120. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  121. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  122. package/dist/homebridge-ui/public/js/modal.js +35 -0
  123. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  124. package/dist/homebridge-ui/public/js/modal.ts +35 -0
  125. package/dist/homebridge-ui/public/js/modals.d.ts +15 -0
  126. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  127. package/dist/homebridge-ui/public/js/modals.js +675 -0
  128. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  129. package/dist/homebridge-ui/public/js/modals.ts +765 -0
  130. package/dist/homebridge-ui/public/js/render.d.ts +71 -0
  131. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  132. package/dist/homebridge-ui/public/js/render.js +960 -0
  133. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  134. package/dist/homebridge-ui/public/js/render.ts +1084 -0
  135. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  136. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  137. package/dist/homebridge-ui/public/js/toast.js +38 -0
  138. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  139. package/dist/homebridge-ui/public/js/toast.ts +44 -0
  140. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  141. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  142. package/dist/homebridge-ui/public/js/types.js +2 -0
  143. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  144. package/dist/homebridge-ui/public/js/types.ts +26 -0
  145. package/dist/homebridge-ui/server.d.ts +1 -3
  146. package/dist/homebridge-ui/server.d.ts.map +1 -1
  147. package/dist/homebridge-ui/server.js +8 -471
  148. package/dist/homebridge-ui/server.js.map +1 -1
  149. package/dist/homebridge-ui/settings.js +8 -0
  150. package/dist/homebridge-ui/settings.js.map +1 -0
  151. package/dist/homebridge-ui/switchbotClient.js +247 -0
  152. package/dist/homebridge-ui/switchbotClient.js.map +1 -0
  153. package/dist/homebridge-ui/utils/config-parser.d.ts +39 -0
  154. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  155. package/dist/homebridge-ui/utils/config-parser.js +108 -0
  156. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  157. package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
  158. package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
  159. package/dist/homebridge-ui/utils/device-migration.js +111 -0
  160. package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
  161. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  162. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  163. package/dist/homebridge-ui/utils/logger.js +17 -0
  164. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  165. package/dist/index.d.ts +10 -0
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +12 -2
  168. package/dist/index.js.map +1 -1
  169. package/dist/settings.d.ts +1 -0
  170. package/dist/settings.d.ts.map +1 -1
  171. package/dist/settings.js +1 -0
  172. package/dist/settings.js.map +1 -1
  173. package/dist/switchbotClient.d.ts +12 -10
  174. package/dist/switchbotClient.d.ts.map +1 -1
  175. package/dist/switchbotClient.js +156 -103
  176. package/dist/switchbotClient.js.map +1 -1
  177. package/dist/utils.d.ts +76 -1
  178. package/dist/utils.d.ts.map +1 -1
  179. package/dist/utils.js +1121 -4
  180. package/dist/utils.js.map +1 -1
  181. package/docs/assets/highlight.css +16 -2
  182. package/docs/assets/main.js +1 -1
  183. package/docs/index.html +82 -5
  184. package/docs/variables/default.html +3 -1
  185. package/eslint.config.js +9 -5
  186. package/nodemon.json +2 -2
  187. package/package.json +34 -21
  188. package/scripts/build-ui.js +37 -0
  189. package/scripts/free-dev-ports.mjs +105 -0
  190. package/scripts/generate-matter-maps.js +34 -17
  191. package/scripts/sync-device-types.mjs +31 -0
  192. package/src/SwitchBotHAPPlatform.ts +558 -0
  193. package/src/SwitchBotMatterPlatform.ts +538 -0
  194. package/src/device-types.js +246 -0
  195. package/src/device-types.js.map +1 -0
  196. package/src/device-types.ts +261 -0
  197. package/src/deviceCommandMapper.js +319 -0
  198. package/src/deviceCommandMapper.js.map +1 -0
  199. package/src/deviceCommandMapper.ts +333 -0
  200. package/src/deviceFactory.ts +125 -45
  201. package/src/devices/genericDevice.ts +411 -69
  202. package/src/errors.js +32 -0
  203. package/src/errors.js.map +1 -0
  204. package/src/errors.ts +35 -0
  205. package/src/homebridge-ui/endpoints/config.ts +110 -0
  206. package/src/homebridge-ui/endpoints/devices.ts +153 -0
  207. package/src/homebridge-ui/endpoints/discovery.ts +240 -0
  208. package/src/homebridge-ui/public/css/styles.css +483 -0
  209. package/src/homebridge-ui/public/index.html +197 -621
  210. package/src/homebridge-ui/public/js/advanced-settings.ts +94 -0
  211. package/src/homebridge-ui/public/js/api.ts +355 -0
  212. package/src/homebridge-ui/public/js/app.ts +22 -0
  213. package/src/homebridge-ui/public/js/constants.ts +1 -0
  214. package/src/homebridge-ui/public/js/credentials.ts +105 -0
  215. package/src/homebridge-ui/public/js/devices-delete.ts +227 -0
  216. package/src/homebridge-ui/public/js/devices.ts +106 -0
  217. package/src/homebridge-ui/public/js/discovery.ts +1335 -0
  218. package/src/homebridge-ui/public/js/logger.ts +17 -0
  219. package/src/homebridge-ui/public/js/modal.ts +35 -0
  220. package/src/homebridge-ui/public/js/modals.ts +765 -0
  221. package/src/homebridge-ui/public/js/render.ts +1084 -0
  222. package/src/homebridge-ui/public/js/toast.ts +44 -0
  223. package/src/homebridge-ui/public/js/types.ts +26 -0
  224. package/src/homebridge-ui/server.ts +9 -554
  225. package/src/homebridge-ui/utils/config-parser.ts +125 -0
  226. package/src/homebridge-ui/utils/device-migration.ts +144 -0
  227. package/src/homebridge-ui/utils/logger.ts +17 -0
  228. package/src/index.ts +12 -2
  229. package/src/settings.js +8 -0
  230. package/src/settings.js.map +1 -0
  231. package/src/settings.ts +2 -0
  232. package/src/switchbotClient.js +247 -0
  233. package/src/switchbotClient.js.map +1 -0
  234. package/src/switchbotClient.ts +177 -114
  235. package/src/utils.ts +1133 -5
  236. package/test/client/switchbot-client-debounce.spec.ts +35 -0
  237. package/test/client/switchbot-client-openapi.spec.ts +19 -0
  238. package/test/client/switchbotClient.spec.ts +64 -0
  239. package/test/device/device-mapping.spec.ts +23 -0
  240. package/test/device/deviceBase.spec.ts +26 -0
  241. package/test/device/deviceFactory-edge.spec.ts +15 -0
  242. package/test/device/deviceFactory.spec.ts +33 -0
  243. package/test/device/fan-swing.spec.ts +34 -0
  244. package/test/device/genericDevice-blepoll.spec.ts +47 -0
  245. package/test/device/irdevice.spec.ts +9 -0
  246. package/test/device/lock-users.spec.ts +35 -0
  247. package/test/device/matter-descriptors.spec.ts +22 -0
  248. package/test/device/matter-device-state.spec.ts +37 -0
  249. package/test/e2e/run-e2e.spec.ts +18 -19
  250. package/test/errors/errors.spec.ts +10 -0
  251. package/test/helpers/matter-harness.ts +20 -9
  252. package/test/homebridge-ui/server.spec.ts +9 -0
  253. package/test/platform/accessory-restore.spec.ts +37 -0
  254. package/test/platform/matter-childbridge.spec.ts +34 -0
  255. package/test/platform/matter-integration.spec.ts +33 -0
  256. package/test/platform/platform-edge.spec.ts +73 -0
  257. package/test/platform/platform.integration.spec.ts +34 -0
  258. package/test/utils/utils-extra.spec.ts +10 -0
  259. package/test/utils/utils.spec.ts +53 -0
  260. package/todo/TODO.md +80 -0
  261. package/tsconfig.ui.json +11 -0
  262. package/.github/npm-version-script-esm.js +0 -97
  263. package/.github/workflows/beta-release.yml +0 -52
  264. package/dist/platform.d.ts +0 -35
  265. package/dist/platform.d.ts.map +0 -1
  266. package/dist/platform.js +0 -945
  267. package/dist/platform.js.map +0 -1
  268. package/src/platform.ts +0 -963
  269. package/test/accessory-restore.spec.ts +0 -73
  270. package/test/device-mapping.spec.ts +0 -37
  271. package/test/deviceFactory.spec.ts +0 -18
  272. package/test/fan-swing.spec.ts +0 -29
  273. package/test/lock-users.spec.ts +0 -44
  274. package/test/matter-childbridge.spec.ts +0 -55
  275. package/test/matter-descriptors.spec.ts +0 -97
  276. package/test/matter-device-state.spec.ts +0 -101
  277. package/test/matter-integration.spec.ts +0 -70
  278. package/test/platform.integration.spec.ts +0 -55
  279. package/test/switchbot-client-debounce.spec.ts +0 -131
  280. package/test/switchbot-client-openapi.spec.ts +0 -56
  281. package/test/switchbotClient.spec.ts +0 -10
  282. package/test/utils.spec.ts +0 -20
package/dist/platform.js DELETED
@@ -1,945 +0,0 @@
1
- import { createDevice } from './deviceFactory.js';
2
- import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
3
- import { SwitchBotClient } from './switchbotClient.js';
4
- // Which device types should prefer Matter if available
5
- // Based on HAP service mappings: device implementations use specific HomeKit services
6
- // that map to corresponding Matter clusters when Matter is enabled
7
- const DEVICE_MATTER_SUPPORTED = {
8
- // Core devices
9
- 'bot': true, // Switch → OnOff
10
- 'curtain': true, // WindowCovering → WindowCovering
11
- 'fan': true, // Fan → FanControl
12
- 'light': true, // Lightbulb → OnOff + LevelControl
13
- 'lightstrip': true, // Lightbulb (color) → OnOff + LevelControl + ColorControl
14
- 'motion': true, // MotionSensor → OccupancySensing
15
- 'contact': true, // ContactSensor → BooleanState
16
- 'vacuum': true, // Switch → RobotVacuumCleaner
17
- 'lock': true, // LockMechanism → DoorLock
18
- 'humidifier': true, // Fan + Humidity → OnOff + FanControl + RelativeHumidityMeasurement
19
- 'temperature': true, // TemperatureSensor → TemperatureMeasurement
20
- // Switch devices
21
- 'relay': true, // Switch → OnOff
22
- 'relay switch 1': true, // Switch → OnOff
23
- 'relay switch 1pm': true, // Switch → OnOff
24
- 'plug': true, // Outlet → OnOff
25
- 'plug mini (jp)': true, // Outlet → OnOff
26
- 'plug mini (us)': true, // Outlet → OnOff
27
- // Window covering variants
28
- 'blindtilt': true, // WindowCovering → WindowCovering
29
- 'blind tilt': true, // WindowCovering → WindowCovering
30
- 'curtain3': true, // WindowCovering → WindowCovering
31
- 'rollershade': true, // WindowCovering → WindowCovering
32
- 'roller shade': true, // WindowCovering → WindowCovering
33
- 'worollershade': true, // WindowCovering → WindowCovering
34
- 'wo rollershade': true, // WindowCovering → WindowCovering
35
- // Vacuum variants (normalized to 'vacuum' before lookup)
36
- 'wosweeper': true, // VacuumDevice → RobotVacuumCleaner
37
- 'wosweepermini': true, // VacuumDevice → RobotVacuumCleaner
38
- 'wosweeperminipro': true, // VacuumDevice → RobotVacuumCleaner
39
- 'k10+': true, // VacuumDevice → RobotVacuumCleaner
40
- 'k10+ pro': true, // VacuumDevice → RobotVacuumCleaner
41
- // Sensors
42
- 'meter': true, // TemperatureSensor + HumiditySensor → TemperatureMeasurement + RelativeHumidityMeasurement
43
- 'meterplus': true, // TemperatureSensor + HumiditySensor → TemperatureMeasurement + RelativeHumidityMeasurement
44
- 'meter plus (jp)': true, // TemperatureSensor + HumiditySensor → TemperatureMeasurement + RelativeHumidityMeasurement
45
- 'meterpro': true, // TemperatureSensor + HumiditySensor → TemperatureMeasurement + RelativeHumidityMeasurement
46
- 'meterpro(co2)': true, // TemperatureSensor + HumiditySensor → TemperatureMeasurement + RelativeHumidityMeasurement
47
- 'waterdetector': true, // LeakSensor → BooleanState
48
- 'water detector': true, // LeakSensor → BooleanState
49
- // Other devices
50
- 'smart fan': true, // Fan → FanControl
51
- 'strip light': true, // Lightbulb (color) → OnOff + LevelControl + ColorControl
52
- 'hub 2': false, // Hub device - not exposed as accessory
53
- 'walletfinder': false, // Button device - Matter support TBD
54
- };
55
- // Default Matter cluster configurations by device type
56
- // Maps device types to their Matter cluster states (used when device doesn't provide clusters)
57
- // Note: wosweeper/curtain/plug variants are normalized before cluster lookup (see loadDevices)
58
- const DEVICE_MATTER_CLUSTERS = {
59
- // Core devices - aligned with HAP service implementations
60
- bot: { onOff: { onOff: false } }, // Switch → OnOff
61
- vacuum: {
62
- rvcRunMode: {
63
- supportedModes: [
64
- { label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] },
65
- { label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] },
66
- ],
67
- currentMode: 0,
68
- },
69
- rvcCleanMode: {
70
- supportedModes: [
71
- { label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] },
72
- ],
73
- currentMode: 0,
74
- },
75
- rvcOperationalState: {
76
- operationalStateList: [
77
- { operationalStateId: 0 }, // Stopped
78
- { operationalStateId: 1 }, // Running
79
- { operationalStateId: 2 }, // Paused
80
- { operationalStateId: 3 }, // Error (required)
81
- { operationalStateId: 64 }, // Seeking charger
82
- { operationalStateId: 65 }, // Charging
83
- { operationalStateId: 66 }, // Docked
84
- ],
85
- operationalState: 66,
86
- },
87
- }, // Switch in HAP, RobotVacuumCleaner in Matter
88
- curtain: {
89
- windowCovering: {
90
- currentPositionLiftPercent100ths: 0,
91
- targetPositionLiftPercent100ths: 0,
92
- operationalStatus: 0,
93
- },
94
- }, // WindowCovering → WindowCovering (includes curtain3, rollershade variants via normalization)
95
- blindtilt: {
96
- windowCovering: {
97
- currentPositionLiftPercent100ths: 0,
98
- targetPositionLiftPercent100ths: 0,
99
- currentPositionTiltPercent100ths: 0,
100
- targetPositionTiltPercent100ths: 0,
101
- operationalStatus: 0,
102
- },
103
- }, // WindowCovering with tilt → WindowCovering
104
- fan: {
105
- onOff: { onOff: false },
106
- fanControl: {
107
- fanMode: 0,
108
- percentCurrent: 0,
109
- percentSetting: 0,
110
- speedCurrent: 0,
111
- speedMax: 100,
112
- },
113
- }, // Fan → OnOff + FanControl
114
- light: {
115
- onOff: { onOff: false },
116
- levelControl: {
117
- currentLevel: 0,
118
- minLevel: 0,
119
- maxLevel: 254,
120
- },
121
- }, // Lightbulb → OnOff + LevelControl
122
- lightstrip: {
123
- onOff: { onOff: false },
124
- levelControl: {
125
- currentLevel: 0,
126
- minLevel: 0,
127
- maxLevel: 254,
128
- },
129
- colorControl: {
130
- currentHue: 0,
131
- currentSaturation: 0,
132
- colorTemperatureMireds: 400,
133
- colorMode: 0,
134
- },
135
- }, // Lightbulb with color → OnOff + LevelControl + ColorControl
136
- lock: {
137
- doorLock: {
138
- lockState: 0,
139
- lockType: 0,
140
- actuatorEnabled: true,
141
- operatingMode: 0,
142
- },
143
- }, // LockMechanism → DoorLock
144
- motion: {
145
- occupancySensing: {
146
- occupancy: 0,
147
- occupancySensorType: 0,
148
- },
149
- }, // MotionSensor → OccupancySensing
150
- contact: {
151
- booleanState: {
152
- stateValue: false,
153
- },
154
- }, // ContactSensor → BooleanState
155
- humidifier: {
156
- onOff: { onOff: false },
157
- fanControl: {
158
- fanMode: 0,
159
- percentCurrent: 0,
160
- },
161
- relativeHumidityMeasurement: {
162
- measuredValue: 0,
163
- minMeasuredValue: 0,
164
- maxMeasuredValue: 100,
165
- },
166
- }, // HumidifierDehumidifier → OnOff + FanControl + RelativeHumidityMeasurement
167
- temperature: {
168
- temperatureMeasurement: {
169
- measuredValue: 0,
170
- minMeasuredValue: -27315,
171
- maxMeasuredValue: 32767,
172
- },
173
- }, // TemperatureSensor → TemperatureMeasurement
174
- // Switch/Outlet devices
175
- relay: { onOff: { onOff: false } }, // Switch → OnOff
176
- plug: {
177
- onOff: { onOff: false },
178
- electricalMeasurement: {
179
- activePower: 0,
180
- rmsCurrent: 0,
181
- rmsVoltage: 0,
182
- },
183
- }, // Outlet → OnOff + ElectricalMeasurement (for PM models)
184
- // Sensors
185
- meter: {
186
- temperatureMeasurement: {
187
- measuredValue: 0,
188
- minMeasuredValue: -27315,
189
- maxMeasuredValue: 32767,
190
- },
191
- relativeHumidityMeasurement: {
192
- measuredValue: 0,
193
- minMeasuredValue: 0,
194
- maxMeasuredValue: 100,
195
- },
196
- }, // TemperatureSensor + HumiditySensor → TemperatureMeasurement + RelativeHumidityMeasurement
197
- waterdetector: {
198
- booleanState: {
199
- stateValue: false,
200
- },
201
- }, // LeakSensor → BooleanState
202
- };
203
- const DEVICE_MATTER_DEVICE_TYPE_KEYS = {
204
- bot: 'OnOffSwitch',
205
- vacuum: 'RoboticVacuumCleaner',
206
- curtain: 'WindowCovering',
207
- blindtilt: 'WindowCovering',
208
- fan: 'Fan',
209
- light: 'DimmableLight',
210
- lightstrip: 'ExtendedColorLight',
211
- lock: 'DoorLock',
212
- motion: 'MotionSensor',
213
- contact: 'ContactSensor',
214
- humidifier: 'Fan',
215
- temperature: 'TemperatureSensor',
216
- relay: 'OnOffSwitch',
217
- plug: 'OnOffOutlet',
218
- meter: 'TemperatureSensor',
219
- waterdetector: 'LeakSensor',
220
- };
221
- // Default Matter cluster handlers by device type
222
- // Provides handler stubs for devices with interactive clusters
223
- const DEVICE_MATTER_HANDLERS = {
224
- vacuum: {
225
- rvcRunMode: {
226
- // Handle changing vacuum run mode (Idle, Cleaning, Mapping)
227
- changeToMode: async (request) => {
228
- const modeNames = ['Idle', 'Cleaning', 'Mapping'];
229
- const modeName = modeNames[request?.newMode] || `Unknown (${request?.newMode})`;
230
- console.log('[SwitchBot] RVC run mode change requested:', modeName);
231
- // TODO: Send API command to SwitchBot device to change run mode
232
- // await this.client.setDeviceState(deviceId, { command: 'setMode', parameter: modeName })
233
- },
234
- },
235
- rvcCleanMode: {
236
- // Handle changing vacuum clean mode (Vacuum, Mop, etc.)
237
- changeToMode: async (request) => {
238
- const modeName = request?.newMode !== undefined ? `Mode ${request.newMode}` : 'Unknown';
239
- console.log('[SwitchBot] RVC clean mode change requested:', modeName);
240
- // TODO: Send API command to SwitchBot device to change clean mode
241
- },
242
- },
243
- rvcOperationalState: {
244
- // Handle pause command
245
- pause: async () => {
246
- console.log('[SwitchBot] RVC pause command received');
247
- // TODO: Send pause command to SwitchBot device
248
- },
249
- // Handle resume command
250
- resume: async () => {
251
- console.log('[SwitchBot] RVC resume command received');
252
- // TODO: Send resume command to SwitchBot device
253
- },
254
- // Handle return to dock command
255
- goHome: async () => {
256
- console.log('[SwitchBot] RVC goHome command received');
257
- // TODO: Send return-to-dock command to SwitchBot device
258
- },
259
- },
260
- },
261
- bot: {
262
- onOff: {
263
- // Handle on/off switch commands
264
- on: async () => {
265
- console.log('[SwitchBot] Bot ON command received');
266
- // TODO: Send ON command to SwitchBot device
267
- },
268
- off: async () => {
269
- console.log('[SwitchBot] Bot OFF command received');
270
- // TODO: Send OFF command to SwitchBot device
271
- },
272
- },
273
- },
274
- curtain: {
275
- windowCovering: {
276
- // Handle curtain position changes
277
- goToLiftPercentage: async (request) => {
278
- console.log('[SwitchBot] Curtain position change requested:', request?.liftPercent100thsValue);
279
- // TODO: Send position command to SwitchBot device
280
- },
281
- upOrOpen: async () => {
282
- console.log('[SwitchBot] Curtain open command received');
283
- // TODO: Send open command to SwitchBot device
284
- },
285
- downOrClose: async () => {
286
- console.log('[SwitchBot] Curtain close command received');
287
- // TODO: Send close command to SwitchBot device
288
- },
289
- stopMotion: async () => {
290
- console.log('[SwitchBot] Curtain stop command received');
291
- // TODO: Send stop command to SwitchBot device
292
- },
293
- },
294
- },
295
- plug: {
296
- onOff: {
297
- on: async () => {
298
- console.log('[SwitchBot] Plug ON command received');
299
- },
300
- off: async () => {
301
- console.log('[SwitchBot] Plug OFF command received');
302
- },
303
- },
304
- },
305
- lock: {
306
- doorLock: {
307
- setLockState: async (request) => {
308
- const state = request?.lockState === 1 ? 'LOCKED' : 'UNLOCKED';
309
- console.log('[SwitchBot] Lock state change requested:', state);
310
- },
311
- },
312
- },
313
- };
314
- function resolveMatterDeviceType(matterApi, type, createdDeviceType, clusters) {
315
- if (createdDeviceType && typeof createdDeviceType === 'object' && typeof createdDeviceType.with === 'function') {
316
- return createdDeviceType;
317
- }
318
- const lowerType = (typeof createdDeviceType === 'string' && createdDeviceType) ? createdDeviceType.toLowerCase() : (type || '').toLowerCase();
319
- // Cluster-based upgrade for color lights if descriptor omitted device type.
320
- const hasColorControl = !!clusters?.colorControl;
321
- const inferredType = hasColorControl && lowerType === 'light'
322
- ? 'lightstrip'
323
- : lowerType;
324
- const mappedKey = DEVICE_MATTER_DEVICE_TYPE_KEYS[inferredType] || 'OnOffSwitch';
325
- return matterApi?.deviceTypes?.[mappedKey] || matterApi?.deviceTypes?.OnOffSwitch;
326
- }
327
- export class SwitchBotHAPPlatform {
328
- api;
329
- log;
330
- config;
331
- devices = [];
332
- // cached accessories restored by Homebridge
333
- accessories;
334
- // Track last loaded config to detect changes
335
- lastConfigHash = '';
336
- configReloadInterval = null;
337
- constructor(log, config, api) {
338
- this.log = log;
339
- this.config = config ?? {};
340
- this.api = api;
341
- this.accessories = new Map();
342
- this.log.info('SwitchBot HAP platform initialized');
343
- // Create/shared SwitchBot client and attach to config so child devices reuse it.
344
- try {
345
- const client = new SwitchBotClient(this.config);
346
- void client.init();
347
- this.config._client = client;
348
- }
349
- catch (e) {
350
- this.log.debug('Failed to create shared SwitchBot client', e);
351
- }
352
- // Create/shared SwitchBot client and attach to config so child devices reuse it.
353
- try {
354
- const client = new SwitchBotClient(this.config);
355
- void client.init();
356
- this.config._client = client;
357
- }
358
- catch (e) {
359
- this.log.debug('Failed to create shared SwitchBot client', e);
360
- }
361
- // Wait for Homebridge to finish launching to create/register accessories
362
- if (this.api && typeof this.api.on === 'function') {
363
- ;
364
- this.api.on('didFinishLaunching', () => {
365
- void this.loadDevices();
366
- // Start periodic config reload to pick up UI changes
367
- this.configReloadInterval = setInterval(() => {
368
- void this.checkAndReloadDevices();
369
- }, 10000); // Check every 10 seconds
370
- });
371
- }
372
- else {
373
- void this.loadDevices();
374
- // Start periodic config reload to pick up UI changes
375
- this.configReloadInterval = setInterval(() => {
376
- void this.checkAndReloadDevices();
377
- }, 10000); // Check every 10 seconds
378
- }
379
- }
380
- getConfigHash() {
381
- // Create a simple hash of current device config to detect changes
382
- const devices = this.config?.devices ?? [];
383
- return JSON.stringify(devices.map((d) => ({
384
- id: d.deviceId ?? d.id,
385
- type: d.configDeviceType ?? d.type,
386
- name: d.configDeviceName ?? d.name,
387
- })));
388
- }
389
- async checkAndReloadDevices() {
390
- const currentHash = this.getConfigHash();
391
- if (currentHash !== this.lastConfigHash) {
392
- this.log.info('[SwitchBot] Detected config changes, reloading devices...');
393
- // Clear existing devices
394
- this.devices = [];
395
- await this.loadDevices();
396
- }
397
- }
398
- async loadDevices() {
399
- const devices = this.config?.devices ?? [];
400
- for (const raw of devices) {
401
- // Normalize config keys from UI schema to internal shape
402
- const d = {
403
- id: raw.deviceId ?? raw.id,
404
- name: raw.configDeviceName ?? raw.name,
405
- type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
406
- _raw: raw,
407
- };
408
- let type = d.type;
409
- // Normalize device type variants for consistent Matter cluster lookup
410
- // Vacuum variants
411
- if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
412
- type = 'vacuum';
413
- }
414
- // Window covering variants
415
- if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
416
- type = 'curtain';
417
- }
418
- // Plug variants
419
- if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
420
- type = 'plug';
421
- }
422
- // Meter variants
423
- if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
424
- type = 'meter';
425
- }
426
- // Relay switch variants
427
- if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
428
- type = 'relay';
429
- }
430
- // Water detector variants
431
- if (['water detector'].includes((type || '').toLowerCase())) {
432
- type = 'waterdetector';
433
- }
434
- // Fan variants
435
- if (['smart fan'].includes((type || '').toLowerCase())) {
436
- type = 'fan';
437
- }
438
- // Light variants
439
- if (['strip light'].includes((type || '').toLowerCase())) {
440
- type = 'lightstrip';
441
- }
442
- const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
443
- // Auto-detect Matter from Homebridge API, allow manual override via config
444
- const matterAvailable = !!(this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.());
445
- const matterEnabled = matterAvailable || !!this.config.enableMatter;
446
- const useMatter = !!(matterEnabled && matterSupported && (!!this.config.preferMatter || matterAvailable));
447
- try {
448
- const created = await createDevice({ id: d.id, type, name: d.name }, this.config, useMatter);
449
- this.devices.push(created);
450
- // Prefer Matter: try registering to the Matter child bridge first.
451
- let matterRegistered = false;
452
- if (useMatter) {
453
- this.log.info(`Attempting Matter registration for ${d.id} (${type})`);
454
- // If Homebridge Matter APIs are available, register the accessory
455
- try {
456
- const matterApi = this.api?.matter;
457
- if (this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.() && matterApi && typeof matterApi.registerPlatformAccessories === 'function') {
458
- const createdDesc = created.createAccessory?.(this.api) || { id: d.id, name: d.name || type };
459
- const uuid = matterApi.uuid.generate(`${d.id}`);
460
- const defaultClusters = DEVICE_MATTER_CLUSTERS[type.toLowerCase()] || { onOff: { onOff: false } };
461
- const clusters = createdDesc.clusters || defaultClusters;
462
- const deviceType = resolveMatterDeviceType(matterApi, type, createdDesc.deviceType, clusters);
463
- const accessory = {
464
- UUID: uuid,
465
- displayName: createdDesc.name || d.name || type,
466
- deviceType,
467
- manufacturer: createdDesc.manufacturer || 'SwitchBot',
468
- model: createdDesc.model || type,
469
- serialNumber: createdDesc.serialNumber || d.id,
470
- reachable: createdDesc.reachable !== false,
471
- firmwareRevision: createdDesc.firmwareRevision || '1.0.0',
472
- hardwareRevision: createdDesc.hardwareRevision || '',
473
- clusters,
474
- context: { deviceId: d.id, type, _created: true },
475
- };
476
- try {
477
- await matterApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
478
- this.accessories.set(uuid, accessory);
479
- matterRegistered = true;
480
- this.log.info(`Registered Matter accessory ${d.id} (${type}) with uuid=${uuid}`);
481
- }
482
- catch (e) {
483
- this.log.warn('Failed to register Matter accessory with Homebridge matter API', e);
484
- }
485
- }
486
- else {
487
- this.log.info('Homebridge Matter API not available or not enabled; will fallback to HAP');
488
- }
489
- }
490
- catch (e) {
491
- this.log.warn('Matter registration attempt failed', e);
492
- }
493
- }
494
- // If Matter wasn't registered (either not supported, API missing, or registration failed), fall back to HAP registration.
495
- if (!matterRegistered && this.api && this.api.hap) {
496
- // Basic HAP accessory creation using homebridge API when available
497
- try {
498
- const hap = this.api.hap;
499
- const uuid = hap.uuid.generate(`${d.id}`);
500
- // Reuse cached accessory if available by uuid
501
- let accessory = this.accessories.get(uuid);
502
- // If not found by uuid, attempt to find by stored deviceId in accessory.context
503
- if (!accessory) {
504
- for (const [, a] of Array.from(this.accessories.entries())) {
505
- try {
506
- if (a && a.context && a.context.deviceId === d.id) {
507
- accessory = a;
508
- break;
509
- }
510
- }
511
- catch (e) {
512
- // ignore
513
- }
514
- }
515
- }
516
- if (!accessory) {
517
- accessory = new this.api.platformAccessory(d.name || type, uuid);
518
- // Store device metadata on accessory.context for persistence across restarts
519
- try {
520
- accessory.context = accessory.context || {};
521
- accessory.context.deviceId = d.id;
522
- accessory.context.type = type;
523
- }
524
- catch (e) {
525
- // ignore context failures
526
- }
527
- // Register new accessory with Homebridge so it's cached
528
- try {
529
- ;
530
- this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
531
- }
532
- catch (e) {
533
- // older API variations may require different registration; ignore if unavailable
534
- }
535
- this.accessories.set(uuid, accessory);
536
- }
537
- else {
538
- // ensure context includes deviceId (in case restored accessory lacked it)
539
- try {
540
- accessory.context = accessory.context || {};
541
- accessory.context.deviceId = accessory.context.deviceId || d.id;
542
- accessory.context.type = accessory.context.type || type;
543
- }
544
- catch (e) {
545
- // ignore
546
- }
547
- }
548
- // Add basic service descriptor from device
549
- const accDesc = created.createAccessory?.(this.api);
550
- if (accDesc && accDesc.services) {
551
- for (const s of accDesc.services) {
552
- const Service = hap.Service[s.type] || hap.Service[s.type];
553
- if (!Service) {
554
- continue;
555
- }
556
- const service = accessory.getService(Service) || accessory.addService(Service);
557
- for (const [charName, getterSetterRaw] of Object.entries(s.characteristics || {})) {
558
- const getterSetter = getterSetterRaw;
559
- const Characteristic = hap.Characteristic[charName];
560
- if (!Characteristic) {
561
- continue;
562
- }
563
- // Apply characteristic props if provided (min/max/step)
564
- if (getterSetter && getterSetter.props) {
565
- try {
566
- service.getCharacteristic(Characteristic).setProps(getterSetter.props);
567
- }
568
- catch (e) {
569
- // ignore setProps failures on older HAP implementations
570
- }
571
- }
572
- // Wire simple get/set handlers if provided
573
- if (getterSetter && typeof getterSetter.get === 'function') {
574
- service.getCharacteristic(Characteristic).onGet(getterSetter.get);
575
- }
576
- if (getterSetter && typeof getterSetter.set === 'function') {
577
- service.getCharacteristic(Characteristic).onSet(getterSetter.set);
578
- }
579
- }
580
- }
581
- }
582
- this.log.info(`Created/updated HAP accessory ${d.id} (${type})`);
583
- }
584
- catch (e) {
585
- this.log.warn('HAP accessory creation failed', e);
586
- }
587
- }
588
- else if (!matterRegistered) {
589
- this.log.info(`Created HAP descriptor for ${d.id} (${type}) (API not available to register)`);
590
- }
591
- }
592
- catch (e) {
593
- this.log.error(`Failed to create device ${d.id}:`, e);
594
- }
595
- }
596
- // Update hash after successfully loading devices
597
- this.lastConfigHash = this.getConfigHash();
598
- }
599
- // Example lifecycle method called by Homebridge
600
- async configureAccessory(accessory) {
601
- // Homebridge calls this for restored cached accessories — keep a reference.
602
- try {
603
- const uuid = accessory.UUID || accessory.UUID;
604
- this.accessories.set(uuid, accessory);
605
- this.log.info(`Restored cached accessory ${accessory.displayName || uuid}`);
606
- }
607
- catch (e) {
608
- this.log.warn('configureAccessory failed to restore accessory', e);
609
- }
610
- }
611
- // Called by Homebridge when a cached Matter accessory is restored
612
- configureMatterAccessory(accessory) {
613
- try {
614
- const uuid = accessory.uuid || accessory.UUID || accessory.uuid;
615
- this.accessories.set(uuid, accessory);
616
- this.log.info(`Restored cached Matter accessory ${accessory.displayName || uuid}`);
617
- }
618
- catch (e) {
619
- this.log.warn('configureMatterAccessory failed to restore accessory', e);
620
- }
621
- }
622
- }
623
- // Matter platform implementation (placeholder)
624
- export class SwitchBotMatterPlatform {
625
- api;
626
- log;
627
- config;
628
- devices = [];
629
- accessories;
630
- // Track last loaded config to detect changes
631
- lastConfigHash = '';
632
- configReloadInterval = null;
633
- constructor(log, config, api) {
634
- this.log = log;
635
- this.config = config ?? {};
636
- this.api = api;
637
- this.accessories = new Map();
638
- this.log.info('SwitchBot Matter platform initialized');
639
- if (this.api && typeof this.api.on === 'function') {
640
- ;
641
- this.api.on('didFinishLaunching', () => {
642
- ;
643
- (async () => {
644
- // After launch, perform discovery (if any) and register Matter accessories
645
- try {
646
- await this.loadDevices();
647
- if (this.api.isMatterAvailable?.() && this.api.isMatterEnabled?.() && this.api.matter && typeof this.api.matter.registerPlatformAccessories === 'function') {
648
- try {
649
- await this.registerMatterAccessories?.();
650
- }
651
- catch (e) {
652
- this.log.warn('registerMatterAccessories failed', e);
653
- }
654
- }
655
- }
656
- catch (e) {
657
- this.log.warn('Error during Matter platform startup', e);
658
- }
659
- })();
660
- // Start periodic config reload to pick up UI changes
661
- this.configReloadInterval = setInterval(() => {
662
- void this.checkAndReloadDevices();
663
- }, 10000); // Check every 10 seconds
664
- });
665
- }
666
- else {
667
- void this.loadDevices();
668
- // Start periodic config reload to pick up UI changes
669
- this.configReloadInterval = setInterval(() => {
670
- void this.checkAndReloadDevices();
671
- }, 10000); // Check every 10 seconds
672
- }
673
- // Create/shared SwitchBot client and attach to config so child devices reuse it.
674
- try {
675
- const client = new SwitchBotClient(this.config);
676
- void client.init();
677
- this.config._client = client;
678
- }
679
- catch (e) {
680
- this.log.debug('Failed to create shared SwitchBot client', e);
681
- }
682
- }
683
- async loadDevices() {
684
- const devices = this.config?.devices ?? [];
685
- for (const raw of devices) {
686
- // Normalize config keys produced by the UI schema
687
- const d = {
688
- id: raw.deviceId ?? raw.id,
689
- name: raw.configDeviceName ?? raw.name,
690
- type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
691
- _raw: raw,
692
- };
693
- let type = d.type;
694
- // Normalize device type variants for consistent Matter cluster lookup
695
- // Vacuum variants
696
- if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
697
- type = 'vacuum';
698
- }
699
- // Window covering variants
700
- if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
701
- type = 'curtain';
702
- }
703
- // Plug variants
704
- if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
705
- type = 'plug';
706
- }
707
- // Meter variants
708
- if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
709
- type = 'meter';
710
- }
711
- // Relay switch variants
712
- if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
713
- type = 'relay';
714
- }
715
- // Water detector variants
716
- if (['water detector'].includes((type || '').toLowerCase())) {
717
- type = 'waterdetector';
718
- }
719
- // Fan variants
720
- if (['smart fan'].includes((type || '').toLowerCase())) {
721
- type = 'fan';
722
- }
723
- // Light variants
724
- if (['strip light'].includes((type || '').toLowerCase())) {
725
- type = 'lightstrip';
726
- }
727
- const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
728
- // Auto-detect Matter from Homebridge API, allow manual override via config
729
- const matterAvailable = this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.();
730
- const matterEnabled = matterAvailable || !!this.config.enableMatter;
731
- const useMatter = matterEnabled && matterSupported;
732
- try {
733
- const created = await createDevice({ id: d.id, type, name: d.name }, this.config, useMatter);
734
- this.devices.push(created);
735
- if (useMatter) {
736
- this.log.info(`Prepared Matter accessory for ${d.id} (${type})${matterAvailable ? ' (auto-detected)' : ' (manually enabled)'}`);
737
- }
738
- else {
739
- if (!matterEnabled) {
740
- this.log.info(`Skipping Matter for ${d.id} (${type}) - Matter not available on this bridge`);
741
- }
742
- else if (!matterSupported) {
743
- this.log.info(`Skipping Matter for ${d.id} (${type}) - device type not supported`);
744
- }
745
- else {
746
- this.log.info(`Skipping Matter for ${d.id} (${type}) - not supported`);
747
- }
748
- }
749
- }
750
- catch (e) {
751
- this.log.error(`Failed to create Matter device ${d.id}:`, e);
752
- }
753
- }
754
- // Update hash after successfully loading devices
755
- this.lastConfigHash = this.getConfigHash();
756
- }
757
- getConfigHash() {
758
- // Create a simple hash of current device config to detect changes
759
- const devices = this.config?.devices ?? [];
760
- return JSON.stringify(devices.map((d) => ({
761
- id: d.deviceId ?? d.id,
762
- type: d.configDeviceType ?? d.type,
763
- name: d.configDeviceName ?? d.name,
764
- })));
765
- }
766
- async checkAndReloadDevices() {
767
- const currentHash = this.getConfigHash();
768
- if (currentHash !== this.lastConfigHash) {
769
- this.log.info('[SwitchBot] Detected config changes, reloading devices...');
770
- // Clear existing devices
771
- this.devices = [];
772
- await this.loadDevices();
773
- }
774
- }
775
- async configureAccessory(accessory) {
776
- try {
777
- const uuid = accessory.UUID || accessory.UUID;
778
- this.accessories.set(uuid, accessory);
779
- this.log.info(`Restored cached Matter accessory ${accessory.displayName || uuid}`);
780
- }
781
- catch (e) {
782
- this.log.warn('configureAccessory failed to restore Matter accessory', e);
783
- }
784
- }
785
- // Homebridge calls this when restoring cached Matter accessories
786
- configureMatterAccessory(accessory) {
787
- try {
788
- const uuid = accessory.uuid || accessory.UUID || accessory.uuid;
789
- this.accessories.set(uuid, accessory);
790
- this.log.info(`Restored cached Matter accessory ${accessory.displayName || uuid}`);
791
- }
792
- catch (e) {
793
- this.log.warn('configureMatterAccessory failed to restore Matter accessory', e);
794
- }
795
- }
796
- // Register serialized Matter accessories via Homebridge Matter API
797
- async registerMatterAccessories() {
798
- if (!this.api) {
799
- return;
800
- }
801
- const matterApi = this.api.matter;
802
- if (!matterApi || typeof matterApi.registerPlatformAccessories !== 'function') {
803
- this.log.info('Homebridge Matter API not available; skipping Matter accessory registration');
804
- return;
805
- }
806
- const devices = this.config?.devices ?? [];
807
- const accessoriesToRegister = [];
808
- // Auto-detect Matter from Homebridge API
809
- const matterAvailable = this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.();
810
- const matterEnabled = matterAvailable || !!this.config.enableMatter;
811
- for (const raw of devices) {
812
- const d = {
813
- id: raw.deviceId ?? raw.id,
814
- name: raw.configDeviceName ?? raw.name,
815
- type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
816
- };
817
- if (!d.id) {
818
- continue;
819
- }
820
- let type = d.type;
821
- // Normalize device type variants for consistent Matter cluster lookup
822
- // Vacuum variants
823
- if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
824
- type = 'vacuum';
825
- }
826
- // Window covering variants
827
- if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
828
- type = 'curtain';
829
- }
830
- // Plug variants
831
- if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
832
- type = 'plug';
833
- }
834
- // Meter variants
835
- if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
836
- type = 'meter';
837
- }
838
- // Relay switch variants
839
- if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
840
- type = 'relay';
841
- }
842
- // Water detector variants
843
- if (['water detector'].includes((type || '').toLowerCase())) {
844
- type = 'waterdetector';
845
- }
846
- // Fan variants
847
- if (['smart fan'].includes((type || '').toLowerCase())) {
848
- type = 'fan';
849
- }
850
- // Light variants
851
- if (['strip light'].includes((type || '').toLowerCase())) {
852
- type = 'lightstrip';
853
- }
854
- const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
855
- const useMatter = matterEnabled && matterSupported;
856
- if (!useMatter) {
857
- continue;
858
- }
859
- try {
860
- const created = await createDevice({ id: d.id, type, name: d.name }, this.config, true);
861
- const createdDesc = created.createAccessory?.(this.api) || { id: d.id, name: d.name || type };
862
- const uuid = matterApi.uuid.generate(`${d.id}`);
863
- // Try to find existing restored accessory by deviceId
864
- let existing;
865
- for (const [, a] of Array.from(this.accessories.entries())) {
866
- try {
867
- if (a && a.context && a.context.deviceId === d.id) {
868
- existing = a;
869
- break;
870
- }
871
- }
872
- catch (e) {
873
- // ignore
874
- }
875
- }
876
- if (existing) {
877
- // Ensure context and displayName are up to date
878
- // Prioritize device-specific Matter clusters (e.g., RVC for vacuum) over generic HAP-derived clusters
879
- let clusters = DEVICE_MATTER_CLUSTERS[type.toLowerCase()];
880
- if (!clusters) {
881
- clusters = existing.clusters || createdDesc.clusters || { onOff: { onOff: false } };
882
- }
883
- const deviceType = resolveMatterDeviceType(matterApi, type, existing.deviceType || createdDesc.deviceType, clusters);
884
- existing.context = existing.context || {};
885
- existing.context.deviceId = existing.context.deviceId || d.id;
886
- existing.context.type = existing.context.type || type;
887
- existing.deviceType = deviceType;
888
- existing.manufacturer = existing.manufacturer || createdDesc.manufacturer || 'SwitchBot';
889
- existing.model = existing.model || createdDesc.model || type;
890
- existing.serialNumber = existing.serialNumber || createdDesc.serialNumber || d.id;
891
- existing.reachable = existing.reachable !== false;
892
- existing.firmwareRevision = existing.firmwareRevision || createdDesc.firmwareRevision || '1.0.0';
893
- existing.hardwareRevision = existing.hardwareRevision || createdDesc.hardwareRevision || '';
894
- existing.clusters = clusters;
895
- existing.handlers = createdDesc.handlers || DEVICE_MATTER_HANDLERS[type.toLowerCase()] || undefined;
896
- existing.displayName = createdDesc.name || d.name || type;
897
- existing.UUID = existing.UUID || existing.uuid || uuid;
898
- accessoriesToRegister.push(existing);
899
- this.accessories.set(existing.UUID || uuid, existing);
900
- }
901
- else {
902
- // Prioritize device-specific Matter clusters (e.g., RVC for vacuum) over generic HAP-derived clusters
903
- let clusters = DEVICE_MATTER_CLUSTERS[type.toLowerCase()];
904
- if (!clusters) {
905
- clusters = createdDesc.clusters || { onOff: { onOff: false } };
906
- }
907
- const deviceType = resolveMatterDeviceType(matterApi, type, createdDesc.deviceType, clusters);
908
- const serialized = {
909
- UUID: uuid,
910
- displayName: createdDesc.name || d.name || type,
911
- deviceType,
912
- manufacturer: createdDesc.manufacturer || 'SwitchBot',
913
- model: createdDesc.model || type,
914
- serialNumber: createdDesc.serialNumber || d.id,
915
- reachable: createdDesc.reachable !== false,
916
- firmwareRevision: createdDesc.firmwareRevision || '1.0.0',
917
- hardwareRevision: createdDesc.hardwareRevision || '',
918
- clusters,
919
- handlers: createdDesc.handlers || DEVICE_MATTER_HANDLERS[type.toLowerCase()] || undefined,
920
- context: { deviceId: d.id, type, created: true },
921
- };
922
- accessoriesToRegister.push(serialized);
923
- this.accessories.set(uuid, serialized);
924
- }
925
- }
926
- catch (e) {
927
- this.log.warn(`Failed to prepare Matter accessory for ${d.id} (${type})`, e);
928
- }
929
- }
930
- if (accessoriesToRegister.length > 0) {
931
- try {
932
- await matterApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessoriesToRegister);
933
- this.log.info(`Registered ${accessoriesToRegister.length} Matter accessory(ies) with Homebridge`);
934
- }
935
- catch (e) {
936
- this.log.warn('Failed to register Matter accessories', e);
937
- }
938
- }
939
- else {
940
- this.log.info('No Matter accessories to register');
941
- }
942
- }
943
- }
944
- export default SwitchBotHAPPlatform;
945
- //# sourceMappingURL=platform.js.map