@switchbot/homebridge-switchbot 5.0.0-beta.98 → 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 -450
  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 -526
  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 -850
  267. package/dist/platform.js.map +0 -1
  268. package/src/platform.ts +0 -867
  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,850 +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
- function resolveMatterDeviceType(matterApi, type, createdDeviceType, clusters) {
222
- if (createdDeviceType && typeof createdDeviceType === 'object' && typeof createdDeviceType.with === 'function') {
223
- return createdDeviceType;
224
- }
225
- const lowerType = (typeof createdDeviceType === 'string' && createdDeviceType) ? createdDeviceType.toLowerCase() : (type || '').toLowerCase();
226
- // Cluster-based upgrade for color lights if descriptor omitted device type.
227
- const hasColorControl = !!clusters?.colorControl;
228
- const inferredType = hasColorControl && lowerType === 'light'
229
- ? 'lightstrip'
230
- : lowerType;
231
- const mappedKey = DEVICE_MATTER_DEVICE_TYPE_KEYS[inferredType] || 'OnOffSwitch';
232
- return matterApi?.deviceTypes?.[mappedKey] || matterApi?.deviceTypes?.OnOffSwitch;
233
- }
234
- export class SwitchBotHAPPlatform {
235
- api;
236
- log;
237
- config;
238
- devices = [];
239
- // cached accessories restored by Homebridge
240
- accessories;
241
- // Track last loaded config to detect changes
242
- lastConfigHash = '';
243
- configReloadInterval = null;
244
- constructor(log, config, api) {
245
- this.log = log;
246
- this.config = config ?? {};
247
- this.api = api;
248
- this.accessories = new Map();
249
- this.log.info('SwitchBot HAP platform initialized');
250
- // Create/shared SwitchBot client and attach to config so child devices reuse it.
251
- try {
252
- const client = new SwitchBotClient(this.config);
253
- void client.init();
254
- this.config._client = client;
255
- }
256
- catch (e) {
257
- this.log.debug('Failed to create shared SwitchBot client', e);
258
- }
259
- // Create/shared SwitchBot client and attach to config so child devices reuse it.
260
- try {
261
- const client = new SwitchBotClient(this.config);
262
- void client.init();
263
- this.config._client = client;
264
- }
265
- catch (e) {
266
- this.log.debug('Failed to create shared SwitchBot client', e);
267
- }
268
- // Wait for Homebridge to finish launching to create/register accessories
269
- if (this.api && typeof this.api.on === 'function') {
270
- ;
271
- this.api.on('didFinishLaunching', () => {
272
- void this.loadDevices();
273
- // Start periodic config reload to pick up UI changes
274
- this.configReloadInterval = setInterval(() => {
275
- void this.checkAndReloadDevices();
276
- }, 10000); // Check every 10 seconds
277
- });
278
- }
279
- else {
280
- void this.loadDevices();
281
- // Start periodic config reload to pick up UI changes
282
- this.configReloadInterval = setInterval(() => {
283
- void this.checkAndReloadDevices();
284
- }, 10000); // Check every 10 seconds
285
- }
286
- }
287
- getConfigHash() {
288
- // Create a simple hash of current device config to detect changes
289
- const devices = this.config?.devices ?? [];
290
- return JSON.stringify(devices.map((d) => ({
291
- id: d.deviceId ?? d.id,
292
- type: d.configDeviceType ?? d.type,
293
- name: d.configDeviceName ?? d.name,
294
- })));
295
- }
296
- async checkAndReloadDevices() {
297
- const currentHash = this.getConfigHash();
298
- if (currentHash !== this.lastConfigHash) {
299
- this.log.info('[SwitchBot] Detected config changes, reloading devices...');
300
- // Clear existing devices
301
- this.devices = [];
302
- await this.loadDevices();
303
- }
304
- }
305
- async loadDevices() {
306
- const devices = this.config?.devices ?? [];
307
- for (const raw of devices) {
308
- // Normalize config keys from UI schema to internal shape
309
- const d = {
310
- id: raw.deviceId ?? raw.id,
311
- name: raw.configDeviceName ?? raw.name,
312
- type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
313
- _raw: raw,
314
- };
315
- let type = d.type;
316
- // Normalize device type variants for consistent Matter cluster lookup
317
- // Vacuum variants
318
- if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
319
- type = 'vacuum';
320
- }
321
- // Window covering variants
322
- if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
323
- type = 'curtain';
324
- }
325
- // Plug variants
326
- if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
327
- type = 'plug';
328
- }
329
- // Meter variants
330
- if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
331
- type = 'meter';
332
- }
333
- // Relay switch variants
334
- if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
335
- type = 'relay';
336
- }
337
- // Water detector variants
338
- if (['water detector'].includes((type || '').toLowerCase())) {
339
- type = 'waterdetector';
340
- }
341
- // Fan variants
342
- if (['smart fan'].includes((type || '').toLowerCase())) {
343
- type = 'fan';
344
- }
345
- // Light variants
346
- if (['strip light'].includes((type || '').toLowerCase())) {
347
- type = 'lightstrip';
348
- }
349
- const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
350
- // Auto-detect Matter from Homebridge API, allow manual override via config
351
- const matterAvailable = !!(this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.());
352
- const matterEnabled = matterAvailable || !!this.config.enableMatter;
353
- const useMatter = !!(matterEnabled && matterSupported && (!!this.config.preferMatter || matterAvailable));
354
- try {
355
- const created = await createDevice({ id: d.id, type, name: d.name }, this.config, useMatter);
356
- this.devices.push(created);
357
- // Prefer Matter: try registering to the Matter child bridge first.
358
- let matterRegistered = false;
359
- if (useMatter) {
360
- this.log.info(`Attempting Matter registration for ${d.id} (${type})`);
361
- // If Homebridge Matter APIs are available, register the accessory
362
- try {
363
- const matterApi = this.api?.matter;
364
- if (this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.() && matterApi && typeof matterApi.registerPlatformAccessories === 'function') {
365
- const createdDesc = created.createAccessory?.(this.api) || { id: d.id, name: d.name || type };
366
- const uuid = matterApi.uuid.generate(`${d.id}`);
367
- const defaultClusters = DEVICE_MATTER_CLUSTERS[type.toLowerCase()] || { onOff: { onOff: false } };
368
- const clusters = createdDesc.clusters || defaultClusters;
369
- const deviceType = resolveMatterDeviceType(matterApi, type, createdDesc.deviceType, clusters);
370
- const accessory = {
371
- UUID: uuid,
372
- displayName: createdDesc.name || d.name || type,
373
- deviceType,
374
- manufacturer: createdDesc.manufacturer || 'SwitchBot',
375
- model: createdDesc.model || type,
376
- serialNumber: createdDesc.serialNumber || d.id,
377
- reachable: createdDesc.reachable !== false,
378
- firmwareRevision: createdDesc.firmwareRevision || '1.0.0',
379
- hardwareRevision: createdDesc.hardwareRevision || '',
380
- clusters,
381
- context: { deviceId: d.id, type, _created: true },
382
- };
383
- try {
384
- await matterApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
385
- this.accessories.set(uuid, accessory);
386
- matterRegistered = true;
387
- this.log.info(`Registered Matter accessory ${d.id} (${type}) with uuid=${uuid}`);
388
- }
389
- catch (e) {
390
- this.log.warn('Failed to register Matter accessory with Homebridge matter API', e);
391
- }
392
- }
393
- else {
394
- this.log.info('Homebridge Matter API not available or not enabled; will fallback to HAP');
395
- }
396
- }
397
- catch (e) {
398
- this.log.warn('Matter registration attempt failed', e);
399
- }
400
- }
401
- // If Matter wasn't registered (either not supported, API missing, or registration failed), fall back to HAP registration.
402
- if (!matterRegistered && this.api && this.api.hap) {
403
- // Basic HAP accessory creation using homebridge API when available
404
- try {
405
- const hap = this.api.hap;
406
- const uuid = hap.uuid.generate(`${d.id}`);
407
- // Reuse cached accessory if available by uuid
408
- let accessory = this.accessories.get(uuid);
409
- // If not found by uuid, attempt to find by stored deviceId in accessory.context
410
- if (!accessory) {
411
- for (const [, a] of Array.from(this.accessories.entries())) {
412
- try {
413
- if (a && a.context && a.context.deviceId === d.id) {
414
- accessory = a;
415
- break;
416
- }
417
- }
418
- catch (e) {
419
- // ignore
420
- }
421
- }
422
- }
423
- if (!accessory) {
424
- accessory = new this.api.platformAccessory(d.name || type, uuid);
425
- // Store device metadata on accessory.context for persistence across restarts
426
- try {
427
- accessory.context = accessory.context || {};
428
- accessory.context.deviceId = d.id;
429
- accessory.context.type = type;
430
- }
431
- catch (e) {
432
- // ignore context failures
433
- }
434
- // Register new accessory with Homebridge so it's cached
435
- try {
436
- ;
437
- this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
438
- }
439
- catch (e) {
440
- // older API variations may require different registration; ignore if unavailable
441
- }
442
- this.accessories.set(uuid, accessory);
443
- }
444
- else {
445
- // ensure context includes deviceId (in case restored accessory lacked it)
446
- try {
447
- accessory.context = accessory.context || {};
448
- accessory.context.deviceId = accessory.context.deviceId || d.id;
449
- accessory.context.type = accessory.context.type || type;
450
- }
451
- catch (e) {
452
- // ignore
453
- }
454
- }
455
- // Add basic service descriptor from device
456
- const accDesc = created.createAccessory?.(this.api);
457
- if (accDesc && accDesc.services) {
458
- for (const s of accDesc.services) {
459
- const Service = hap.Service[s.type] || hap.Service[s.type];
460
- if (!Service) {
461
- continue;
462
- }
463
- const service = accessory.getService(Service) || accessory.addService(Service);
464
- for (const [charName, getterSetterRaw] of Object.entries(s.characteristics || {})) {
465
- const getterSetter = getterSetterRaw;
466
- const Characteristic = hap.Characteristic[charName];
467
- if (!Characteristic) {
468
- continue;
469
- }
470
- // Apply characteristic props if provided (min/max/step)
471
- if (getterSetter && getterSetter.props) {
472
- try {
473
- service.getCharacteristic(Characteristic).setProps(getterSetter.props);
474
- }
475
- catch (e) {
476
- // ignore setProps failures on older HAP implementations
477
- }
478
- }
479
- // Wire simple get/set handlers if provided
480
- if (getterSetter && typeof getterSetter.get === 'function') {
481
- service.getCharacteristic(Characteristic).onGet(getterSetter.get);
482
- }
483
- if (getterSetter && typeof getterSetter.set === 'function') {
484
- service.getCharacteristic(Characteristic).onSet(getterSetter.set);
485
- }
486
- }
487
- }
488
- }
489
- this.log.info(`Created/updated HAP accessory ${d.id} (${type})`);
490
- }
491
- catch (e) {
492
- this.log.warn('HAP accessory creation failed', e);
493
- }
494
- }
495
- else if (!matterRegistered) {
496
- this.log.info(`Created HAP descriptor for ${d.id} (${type}) (API not available to register)`);
497
- }
498
- }
499
- catch (e) {
500
- this.log.error(`Failed to create device ${d.id}:`, e);
501
- }
502
- }
503
- // Update hash after successfully loading devices
504
- this.lastConfigHash = this.getConfigHash();
505
- }
506
- // Example lifecycle method called by Homebridge
507
- async configureAccessory(accessory) {
508
- // Homebridge calls this for restored cached accessories — keep a reference.
509
- try {
510
- const uuid = accessory.UUID || accessory.UUID;
511
- this.accessories.set(uuid, accessory);
512
- this.log.info(`Restored cached accessory ${accessory.displayName || uuid}`);
513
- }
514
- catch (e) {
515
- this.log.warn('configureAccessory failed to restore accessory', e);
516
- }
517
- }
518
- // Called by Homebridge when a cached Matter accessory is restored
519
- configureMatterAccessory(accessory) {
520
- try {
521
- const uuid = accessory.uuid || accessory.UUID || accessory.uuid;
522
- this.accessories.set(uuid, accessory);
523
- this.log.info(`Restored cached Matter accessory ${accessory.displayName || uuid}`);
524
- }
525
- catch (e) {
526
- this.log.warn('configureMatterAccessory failed to restore accessory', e);
527
- }
528
- }
529
- }
530
- // Matter platform implementation (placeholder)
531
- export class SwitchBotMatterPlatform {
532
- api;
533
- log;
534
- config;
535
- devices = [];
536
- accessories;
537
- // Track last loaded config to detect changes
538
- lastConfigHash = '';
539
- configReloadInterval = null;
540
- constructor(log, config, api) {
541
- this.log = log;
542
- this.config = config ?? {};
543
- this.api = api;
544
- this.accessories = new Map();
545
- this.log.info('SwitchBot Matter platform initialized');
546
- if (this.api && typeof this.api.on === 'function') {
547
- ;
548
- this.api.on('didFinishLaunching', () => {
549
- ;
550
- (async () => {
551
- // After launch, perform discovery (if any) and register Matter accessories
552
- try {
553
- await this.loadDevices();
554
- if (this.api.isMatterAvailable?.() && this.api.isMatterEnabled?.() && this.api.matter && typeof this.api.matter.registerPlatformAccessories === 'function') {
555
- try {
556
- await this.registerMatterAccessories?.();
557
- }
558
- catch (e) {
559
- this.log.warn('registerMatterAccessories failed', e);
560
- }
561
- }
562
- }
563
- catch (e) {
564
- this.log.warn('Error during Matter platform startup', e);
565
- }
566
- })();
567
- // Start periodic config reload to pick up UI changes
568
- this.configReloadInterval = setInterval(() => {
569
- void this.checkAndReloadDevices();
570
- }, 10000); // Check every 10 seconds
571
- });
572
- }
573
- else {
574
- void this.loadDevices();
575
- // Start periodic config reload to pick up UI changes
576
- this.configReloadInterval = setInterval(() => {
577
- void this.checkAndReloadDevices();
578
- }, 10000); // Check every 10 seconds
579
- }
580
- // Create/shared SwitchBot client and attach to config so child devices reuse it.
581
- try {
582
- const client = new SwitchBotClient(this.config);
583
- void client.init();
584
- this.config._client = client;
585
- }
586
- catch (e) {
587
- this.log.debug('Failed to create shared SwitchBot client', e);
588
- }
589
- }
590
- async loadDevices() {
591
- const devices = this.config?.devices ?? [];
592
- for (const raw of devices) {
593
- // Normalize config keys produced by the UI schema
594
- const d = {
595
- id: raw.deviceId ?? raw.id,
596
- name: raw.configDeviceName ?? raw.name,
597
- type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
598
- _raw: raw,
599
- };
600
- let type = d.type;
601
- // Normalize device type variants for consistent Matter cluster lookup
602
- // Vacuum variants
603
- if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
604
- type = 'vacuum';
605
- }
606
- // Window covering variants
607
- if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
608
- type = 'curtain';
609
- }
610
- // Plug variants
611
- if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
612
- type = 'plug';
613
- }
614
- // Meter variants
615
- if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
616
- type = 'meter';
617
- }
618
- // Relay switch variants
619
- if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
620
- type = 'relay';
621
- }
622
- // Water detector variants
623
- if (['water detector'].includes((type || '').toLowerCase())) {
624
- type = 'waterdetector';
625
- }
626
- // Fan variants
627
- if (['smart fan'].includes((type || '').toLowerCase())) {
628
- type = 'fan';
629
- }
630
- // Light variants
631
- if (['strip light'].includes((type || '').toLowerCase())) {
632
- type = 'lightstrip';
633
- }
634
- const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
635
- // Auto-detect Matter from Homebridge API, allow manual override via config
636
- const matterAvailable = this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.();
637
- const matterEnabled = matterAvailable || !!this.config.enableMatter;
638
- const useMatter = matterEnabled && matterSupported;
639
- try {
640
- const created = await createDevice({ id: d.id, type, name: d.name }, this.config, useMatter);
641
- this.devices.push(created);
642
- if (useMatter) {
643
- this.log.info(`Prepared Matter accessory for ${d.id} (${type})${matterAvailable ? ' (auto-detected)' : ' (manually enabled)'}`);
644
- }
645
- else {
646
- if (!matterEnabled) {
647
- this.log.info(`Skipping Matter for ${d.id} (${type}) - Matter not available on this bridge`);
648
- }
649
- else if (!matterSupported) {
650
- this.log.info(`Skipping Matter for ${d.id} (${type}) - device type not supported`);
651
- }
652
- else {
653
- this.log.info(`Skipping Matter for ${d.id} (${type}) - not supported`);
654
- }
655
- }
656
- }
657
- catch (e) {
658
- this.log.error(`Failed to create Matter device ${d.id}:`, e);
659
- }
660
- }
661
- // Update hash after successfully loading devices
662
- this.lastConfigHash = this.getConfigHash();
663
- }
664
- getConfigHash() {
665
- // Create a simple hash of current device config to detect changes
666
- const devices = this.config?.devices ?? [];
667
- return JSON.stringify(devices.map((d) => ({
668
- id: d.deviceId ?? d.id,
669
- type: d.configDeviceType ?? d.type,
670
- name: d.configDeviceName ?? d.name,
671
- })));
672
- }
673
- async checkAndReloadDevices() {
674
- const currentHash = this.getConfigHash();
675
- if (currentHash !== this.lastConfigHash) {
676
- this.log.info('[SwitchBot] Detected config changes, reloading devices...');
677
- // Clear existing devices
678
- this.devices = [];
679
- await this.loadDevices();
680
- }
681
- }
682
- async configureAccessory(accessory) {
683
- try {
684
- const uuid = accessory.UUID || accessory.UUID;
685
- this.accessories.set(uuid, accessory);
686
- this.log.info(`Restored cached Matter accessory ${accessory.displayName || uuid}`);
687
- }
688
- catch (e) {
689
- this.log.warn('configureAccessory failed to restore Matter accessory', e);
690
- }
691
- }
692
- // Homebridge calls this when restoring cached Matter accessories
693
- configureMatterAccessory(accessory) {
694
- try {
695
- const uuid = accessory.uuid || accessory.UUID || accessory.uuid;
696
- this.accessories.set(uuid, accessory);
697
- this.log.info(`Restored cached Matter accessory ${accessory.displayName || uuid}`);
698
- }
699
- catch (e) {
700
- this.log.warn('configureMatterAccessory failed to restore Matter accessory', e);
701
- }
702
- }
703
- // Register serialized Matter accessories via Homebridge Matter API
704
- async registerMatterAccessories() {
705
- if (!this.api) {
706
- return;
707
- }
708
- const matterApi = this.api.matter;
709
- if (!matterApi || typeof matterApi.registerPlatformAccessories !== 'function') {
710
- this.log.info('Homebridge Matter API not available; skipping Matter accessory registration');
711
- return;
712
- }
713
- const devices = this.config?.devices ?? [];
714
- const accessoriesToRegister = [];
715
- // Auto-detect Matter from Homebridge API
716
- const matterAvailable = this.api?.isMatterAvailable?.() && this.api?.isMatterEnabled?.();
717
- const matterEnabled = matterAvailable || !!this.config.enableMatter;
718
- for (const raw of devices) {
719
- const d = {
720
- id: raw.deviceId ?? raw.id,
721
- name: raw.configDeviceName ?? raw.name,
722
- type: raw.configDeviceType ?? raw.type ?? raw.deviceType ?? 'unknown',
723
- };
724
- if (!d.id) {
725
- continue;
726
- }
727
- let type = d.type;
728
- // Normalize device type variants for consistent Matter cluster lookup
729
- // Vacuum variants
730
- if (['wosweeper', 'wosweepermini', 'wosweeperminipro', 'k10+', 'k10+ pro'].includes((type || '').toLowerCase())) {
731
- type = 'vacuum';
732
- }
733
- // Window covering variants
734
- if (['blindtilt', 'blind tilt', 'curtain3', 'rollershade', 'roller shade', 'worollershade', 'wo rollershade'].includes((type || '').toLowerCase())) {
735
- type = 'curtain';
736
- }
737
- // Plug variants
738
- if (['plug mini (jp)', 'plug mini (us)'].includes((type || '').toLowerCase())) {
739
- type = 'plug';
740
- }
741
- // Meter variants
742
- if (['meterplus', 'meter plus (jp)', 'meterpro', 'meterpro(co2)'].includes((type || '').toLowerCase())) {
743
- type = 'meter';
744
- }
745
- // Relay switch variants
746
- if (['relay switch 1', 'relay switch 1pm'].includes((type || '').toLowerCase())) {
747
- type = 'relay';
748
- }
749
- // Water detector variants
750
- if (['water detector'].includes((type || '').toLowerCase())) {
751
- type = 'waterdetector';
752
- }
753
- // Fan variants
754
- if (['smart fan'].includes((type || '').toLowerCase())) {
755
- type = 'fan';
756
- }
757
- // Light variants
758
- if (['strip light'].includes((type || '').toLowerCase())) {
759
- type = 'lightstrip';
760
- }
761
- const matterSupported = !!DEVICE_MATTER_SUPPORTED[(type || '').toLowerCase()];
762
- const useMatter = matterEnabled && matterSupported;
763
- if (!useMatter) {
764
- continue;
765
- }
766
- try {
767
- const created = await createDevice({ id: d.id, type, name: d.name }, this.config, true);
768
- const createdDesc = created.createAccessory?.(this.api) || { id: d.id, name: d.name || type };
769
- const uuid = matterApi.uuid.generate(`${d.id}`);
770
- // Try to find existing restored accessory by deviceId
771
- let existing;
772
- for (const [, a] of Array.from(this.accessories.entries())) {
773
- try {
774
- if (a && a.context && a.context.deviceId === d.id) {
775
- existing = a;
776
- break;
777
- }
778
- }
779
- catch (e) {
780
- // ignore
781
- }
782
- }
783
- if (existing) {
784
- // Ensure context and displayName are up to date
785
- // Prioritize device-specific Matter clusters (e.g., RVC for vacuum) over generic HAP-derived clusters
786
- let clusters = DEVICE_MATTER_CLUSTERS[type.toLowerCase()];
787
- if (!clusters) {
788
- clusters = existing.clusters || createdDesc.clusters || { onOff: { onOff: false } };
789
- }
790
- const deviceType = resolveMatterDeviceType(matterApi, type, existing.deviceType || createdDesc.deviceType, clusters);
791
- existing.context = existing.context || {};
792
- existing.context.deviceId = existing.context.deviceId || d.id;
793
- existing.context.type = existing.context.type || type;
794
- existing.deviceType = deviceType;
795
- existing.manufacturer = existing.manufacturer || createdDesc.manufacturer || 'SwitchBot';
796
- existing.model = existing.model || createdDesc.model || type;
797
- existing.serialNumber = existing.serialNumber || createdDesc.serialNumber || d.id;
798
- existing.reachable = existing.reachable !== false;
799
- existing.firmwareRevision = existing.firmwareRevision || createdDesc.firmwareRevision || '1.0.0';
800
- existing.hardwareRevision = existing.hardwareRevision || createdDesc.hardwareRevision || '';
801
- existing.clusters = clusters;
802
- existing.displayName = createdDesc.name || d.name || type;
803
- existing.UUID = existing.UUID || existing.uuid || uuid;
804
- accessoriesToRegister.push(existing);
805
- this.accessories.set(existing.UUID || uuid, existing);
806
- }
807
- else {
808
- // Prioritize device-specific Matter clusters (e.g., RVC for vacuum) over generic HAP-derived clusters
809
- let clusters = DEVICE_MATTER_CLUSTERS[type.toLowerCase()];
810
- if (!clusters) {
811
- clusters = createdDesc.clusters || { onOff: { onOff: false } };
812
- }
813
- const deviceType = resolveMatterDeviceType(matterApi, type, createdDesc.deviceType, clusters);
814
- const serialized = {
815
- UUID: uuid,
816
- displayName: createdDesc.name || d.name || type,
817
- deviceType,
818
- manufacturer: createdDesc.manufacturer || 'SwitchBot',
819
- model: createdDesc.model || type,
820
- serialNumber: createdDesc.serialNumber || d.id,
821
- reachable: createdDesc.reachable !== false,
822
- firmwareRevision: createdDesc.firmwareRevision || '1.0.0',
823
- hardwareRevision: createdDesc.hardwareRevision || '',
824
- clusters,
825
- context: { deviceId: d.id, type, created: true },
826
- };
827
- accessoriesToRegister.push(serialized);
828
- this.accessories.set(uuid, serialized);
829
- }
830
- }
831
- catch (e) {
832
- this.log.warn(`Failed to prepare Matter accessory for ${d.id} (${type})`, e);
833
- }
834
- }
835
- if (accessoriesToRegister.length > 0) {
836
- try {
837
- await matterApi.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessoriesToRegister);
838
- this.log.info(`Registered ${accessoriesToRegister.length} Matter accessory(ies) with Homebridge`);
839
- }
840
- catch (e) {
841
- this.log.warn('Failed to register Matter accessories', e);
842
- }
843
- }
844
- else {
845
- this.log.info('No Matter accessories to register');
846
- }
847
- }
848
- }
849
- export default SwitchBotHAPPlatform;
850
- //# sourceMappingURL=platform.js.map