@switchbot/homebridge-switchbot 5.0.0-beta.6 → 5.0.0-beta.60

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 (267) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +45 -3
  3. package/config.schema.json +866 -13754
  4. package/dist/devices-hap/airpurifier.d.ts.map +1 -1
  5. package/dist/devices-hap/airpurifier.js +12 -6
  6. package/dist/devices-hap/airpurifier.js.map +1 -1
  7. package/dist/devices-hap/blindtilt.js +3 -3
  8. package/dist/devices-hap/bot.d.ts.map +1 -1
  9. package/dist/devices-hap/bot.js +16 -5
  10. package/dist/devices-hap/bot.js.map +1 -1
  11. package/dist/devices-hap/ceilinglight.d.ts.map +1 -1
  12. package/dist/devices-hap/ceilinglight.js +13 -7
  13. package/dist/devices-hap/ceilinglight.js.map +1 -1
  14. package/dist/devices-hap/colorbulb.d.ts.map +1 -1
  15. package/dist/devices-hap/colorbulb.js +49 -9
  16. package/dist/devices-hap/colorbulb.js.map +1 -1
  17. package/dist/devices-hap/contact.js +3 -3
  18. package/dist/devices-hap/curtain.js +2 -2
  19. package/dist/devices-hap/curtain.js.map +1 -1
  20. package/dist/devices-hap/device.d.ts +18 -8
  21. package/dist/devices-hap/device.d.ts.map +1 -1
  22. package/dist/devices-hap/device.js +141 -69
  23. package/dist/devices-hap/device.js.map +1 -1
  24. package/dist/devices-hap/fan.d.ts.map +1 -1
  25. package/dist/devices-hap/fan.js +12 -6
  26. package/dist/devices-hap/fan.js.map +1 -1
  27. package/dist/devices-hap/hub.d.ts.map +1 -1
  28. package/dist/devices-hap/hub.js +6 -5
  29. package/dist/devices-hap/hub.js.map +1 -1
  30. package/dist/devices-hap/humidifier.d.ts +5 -0
  31. package/dist/devices-hap/humidifier.d.ts.map +1 -1
  32. package/dist/devices-hap/humidifier.js +92 -4
  33. package/dist/devices-hap/humidifier.js.map +1 -1
  34. package/dist/devices-hap/iosensor.d.ts.map +1 -1
  35. package/dist/devices-hap/iosensor.js +36 -21
  36. package/dist/devices-hap/iosensor.js.map +1 -1
  37. package/dist/devices-hap/lightstrip.d.ts.map +1 -1
  38. package/dist/devices-hap/lightstrip.js +38 -8
  39. package/dist/devices-hap/lightstrip.js.map +1 -1
  40. package/dist/devices-hap/lock.d.ts.map +1 -1
  41. package/dist/devices-hap/lock.js +14 -6
  42. package/dist/devices-hap/lock.js.map +1 -1
  43. package/dist/devices-hap/meter.d.ts.map +1 -1
  44. package/dist/devices-hap/meter.js +6 -5
  45. package/dist/devices-hap/meter.js.map +1 -1
  46. package/dist/devices-hap/meterplus.d.ts.map +1 -1
  47. package/dist/devices-hap/meterplus.js +6 -5
  48. package/dist/devices-hap/meterplus.js.map +1 -1
  49. package/dist/devices-hap/meterpro.d.ts.map +1 -1
  50. package/dist/devices-hap/meterpro.js +7 -6
  51. package/dist/devices-hap/meterpro.js.map +1 -1
  52. package/dist/devices-hap/motion.js +3 -3
  53. package/dist/devices-hap/plug.d.ts.map +1 -1
  54. package/dist/devices-hap/plug.js +11 -6
  55. package/dist/devices-hap/plug.js.map +1 -1
  56. package/dist/devices-hap/relayswitch.js +3 -3
  57. package/dist/devices-hap/robotvacuumcleaner.d.ts.map +1 -1
  58. package/dist/devices-hap/robotvacuumcleaner.js +13 -6
  59. package/dist/devices-hap/robotvacuumcleaner.js.map +1 -1
  60. package/dist/devices-hap/waterdetector.js +3 -3
  61. package/dist/devices-matter/BaseMatterAccessory.d.ts +27 -0
  62. package/dist/devices-matter/BaseMatterAccessory.d.ts.map +1 -1
  63. package/dist/devices-matter/BaseMatterAccessory.js +169 -5
  64. package/dist/devices-matter/BaseMatterAccessory.js.map +1 -1
  65. package/dist/devices-matter/ColorLightAccessory.d.ts.map +1 -1
  66. package/dist/devices-matter/ColorLightAccessory.js +12 -12
  67. package/dist/devices-matter/ColorLightAccessory.js.map +1 -1
  68. package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +1 -1
  69. package/dist/devices-matter/ColorTemperatureLightAccessory.js +5 -7
  70. package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +1 -1
  71. package/dist/devices-matter/DimmableLightAccessory.js +9 -9
  72. package/dist/devices-matter/DimmableLightAccessory.js.map +1 -1
  73. package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +1 -1
  74. package/dist/devices-matter/ExtendedColorLightAccessory.js +14 -15
  75. package/dist/devices-matter/ExtendedColorLightAccessory.js.map +1 -1
  76. package/dist/devices-matter/OnOffLightAccessory.d.ts.map +1 -1
  77. package/dist/devices-matter/OnOffLightAccessory.js +8 -16
  78. package/dist/devices-matter/OnOffLightAccessory.js.map +1 -1
  79. package/dist/devices-matter/OnOffOutletAccessory.d.ts +2 -0
  80. package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +1 -1
  81. package/dist/devices-matter/OnOffOutletAccessory.js +10 -7
  82. package/dist/devices-matter/OnOffOutletAccessory.js.map +1 -1
  83. package/dist/devices-matter/OnOffSwitchAccessory.js +2 -2
  84. package/dist/devices-matter/OnOffSwitchAccessory.js.map +1 -1
  85. package/dist/devices-matter/RoboticVacuumAccessory.d.ts +29 -43
  86. package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +1 -1
  87. package/dist/devices-matter/RoboticVacuumAccessory.js +287 -262
  88. package/dist/devices-matter/RoboticVacuumAccessory.js.map +1 -1
  89. package/dist/homebridge-ui/public/index.html +200 -18
  90. package/dist/homebridge-ui/server.js +82 -9
  91. package/dist/homebridge-ui/server.js.map +1 -1
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +4 -7
  94. package/dist/index.js.map +1 -1
  95. package/dist/irdevice/irdevice.d.ts +11 -10
  96. package/dist/irdevice/irdevice.d.ts.map +1 -1
  97. package/dist/irdevice/irdevice.js +76 -35
  98. package/dist/irdevice/irdevice.js.map +1 -1
  99. package/dist/platform-hap.d.ts +26 -15
  100. package/dist/platform-hap.d.ts.map +1 -1
  101. package/dist/platform-hap.js +333 -153
  102. package/dist/platform-hap.js.map +1 -1
  103. package/dist/platform-matter.d.ts +93 -6
  104. package/dist/platform-matter.d.ts.map +1 -1
  105. package/dist/platform-matter.js +1822 -224
  106. package/dist/platform-matter.js.map +1 -1
  107. package/dist/settings.d.ts +58 -7
  108. package/dist/settings.d.ts.map +1 -1
  109. package/dist/settings.js.map +1 -1
  110. package/dist/test/apiRequestTracker.test.d.ts +2 -0
  111. package/dist/test/apiRequestTracker.test.d.ts.map +1 -0
  112. package/dist/test/apiRequestTracker.test.js +392 -0
  113. package/dist/test/apiRequestTracker.test.js.map +1 -0
  114. package/dist/test/hap/device-webhook-context.test.d.ts +2 -0
  115. package/dist/test/hap/device-webhook-context.test.d.ts.map +1 -0
  116. package/dist/test/hap/device-webhook-context.test.js +128 -0
  117. package/dist/test/hap/device-webhook-context.test.js.map +1 -0
  118. package/dist/test/hap/platform-hap.logging.test.d.ts +2 -0
  119. package/dist/test/hap/platform-hap.logging.test.d.ts.map +1 -0
  120. package/dist/test/hap/platform-hap.logging.test.js +33 -0
  121. package/dist/test/hap/platform-hap.logging.test.js.map +1 -0
  122. package/dist/test/hap/platform-hap.test.d.ts +2 -0
  123. package/dist/test/hap/platform-hap.test.d.ts.map +1 -0
  124. package/dist/test/hap/platform-hap.test.js +62 -0
  125. package/dist/test/hap/platform-hap.test.js.map +1 -0
  126. package/dist/test/helpers/platform-fixtures.d.ts +9 -0
  127. package/dist/test/helpers/platform-fixtures.d.ts.map +1 -0
  128. package/dist/test/helpers/platform-fixtures.js +30 -0
  129. package/dist/test/helpers/platform-fixtures.js.map +1 -0
  130. package/dist/test/homebridge-ui/server.test.d.ts +2 -0
  131. package/dist/test/homebridge-ui/server.test.d.ts.map +1 -0
  132. package/dist/test/homebridge-ui/server.test.js +445 -0
  133. package/dist/test/homebridge-ui/server.test.js.map +1 -0
  134. package/dist/{index.test.d.ts.map → test/index.test.d.ts.map} +1 -1
  135. package/dist/test/index.test.js +19 -0
  136. package/dist/test/index.test.js.map +1 -0
  137. package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts +2 -0
  138. package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
  139. package/dist/test/matter/devices-matter/baseMatterAccessory.test.js +71 -0
  140. package/dist/test/matter/devices-matter/baseMatterAccessory.test.js.map +1 -0
  141. package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts +2 -0
  142. package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts.map +1 -0
  143. package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js +366 -0
  144. package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js.map +1 -0
  145. package/dist/test/matter/platform-matter.additional.test.d.ts +2 -0
  146. package/dist/test/matter/platform-matter.additional.test.d.ts.map +1 -0
  147. package/dist/test/matter/platform-matter.additional.test.js +35 -0
  148. package/dist/test/matter/platform-matter.additional.test.js.map +1 -0
  149. package/dist/test/matter/platform-matter.bleparse.test.d.ts +2 -0
  150. package/dist/test/matter/platform-matter.bleparse.test.d.ts.map +1 -0
  151. package/dist/test/matter/platform-matter.bleparse.test.js +43 -0
  152. package/dist/test/matter/platform-matter.bleparse.test.js.map +1 -0
  153. package/dist/test/matter/platform-matter.cleanup.test.d.ts +2 -0
  154. package/dist/test/matter/platform-matter.cleanup.test.d.ts.map +1 -0
  155. package/dist/test/matter/platform-matter.cleanup.test.js +70 -0
  156. package/dist/test/matter/platform-matter.cleanup.test.js.map +1 -0
  157. package/dist/test/matter/platform-matter.keepstale.test.d.ts +2 -0
  158. package/dist/test/matter/platform-matter.keepstale.test.d.ts.map +1 -0
  159. package/dist/test/matter/platform-matter.keepstale.test.js +27 -0
  160. package/dist/test/matter/platform-matter.keepstale.test.js.map +1 -0
  161. package/dist/test/matter/platform-matter.logging.test.d.ts +2 -0
  162. package/dist/test/matter/platform-matter.logging.test.d.ts.map +1 -0
  163. package/dist/test/matter/platform-matter.logging.test.js +29 -0
  164. package/dist/test/matter/platform-matter.logging.test.js.map +1 -0
  165. package/dist/test/matter/platform-matter.mapping.test.d.ts +2 -0
  166. package/dist/test/matter/platform-matter.mapping.test.d.ts.map +1 -0
  167. package/dist/test/matter/platform-matter.mapping.test.js +43 -0
  168. package/dist/test/matter/platform-matter.mapping.test.js.map +1 -0
  169. package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts +2 -0
  170. package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts.map +1 -0
  171. package/dist/test/matter/platform-matter.openapi-mapping.test.js +84 -0
  172. package/dist/test/matter/platform-matter.openapi-mapping.test.js.map +1 -0
  173. package/dist/test/matter/platform-matter.test.d.ts +2 -0
  174. package/dist/test/matter/platform-matter.test.d.ts.map +1 -0
  175. package/dist/test/matter/platform-matter.test.js +117 -0
  176. package/dist/test/matter/platform-matter.test.js.map +1 -0
  177. package/dist/test/matter/platform-matter.unregister.test.d.ts +2 -0
  178. package/dist/test/matter/platform-matter.unregister.test.d.ts.map +1 -0
  179. package/dist/test/matter/platform-matter.unregister.test.js +30 -0
  180. package/dist/test/matter/platform-matter.unregister.test.js.map +1 -0
  181. package/dist/test/matter/platform-matter.webhook.test.d.ts +2 -0
  182. package/dist/test/matter/platform-matter.webhook.test.d.ts.map +1 -0
  183. package/dist/test/matter/platform-matter.webhook.test.js +46 -0
  184. package/dist/test/matter/platform-matter.webhook.test.js.map +1 -0
  185. package/dist/test/utils.test.d.ts +2 -0
  186. package/dist/test/utils.test.d.ts.map +1 -0
  187. package/dist/test/utils.test.js +95 -0
  188. package/dist/test/utils.test.js.map +1 -0
  189. package/dist/test/verifyconfig.test.d.ts.map +1 -0
  190. package/dist/{verifyconfig.test.js → test/verifyconfig.test.js} +2 -2
  191. package/dist/test/verifyconfig.test.js.map +1 -0
  192. package/dist/utils.d.ts +204 -3
  193. package/dist/utils.d.ts.map +1 -1
  194. package/dist/utils.js +713 -33
  195. package/dist/utils.js.map +1 -1
  196. package/docs/assets/highlight.css +14 -0
  197. package/docs/assets/main.js +2 -2
  198. package/docs/index.html +31 -2
  199. package/docs/variables/default.html +1 -1
  200. package/package.json +15 -15
  201. package/src/devices-hap/airpurifier.ts +11 -6
  202. package/src/devices-hap/blindtilt.ts +3 -3
  203. package/src/devices-hap/bot.ts +15 -5
  204. package/src/devices-hap/ceilinglight.ts +12 -7
  205. package/src/devices-hap/colorbulb.ts +46 -10
  206. package/src/devices-hap/contact.ts +3 -3
  207. package/src/devices-hap/curtain.ts +2 -2
  208. package/src/devices-hap/device.ts +149 -70
  209. package/src/devices-hap/fan.ts +11 -6
  210. package/src/devices-hap/hub.ts +6 -5
  211. package/src/devices-hap/humidifier.ts +97 -4
  212. package/src/devices-hap/iosensor.ts +36 -21
  213. package/src/devices-hap/lightstrip.ts +35 -8
  214. package/src/devices-hap/lock.ts +13 -6
  215. package/src/devices-hap/meter.ts +6 -5
  216. package/src/devices-hap/meterplus.ts +6 -5
  217. package/src/devices-hap/meterpro.ts +7 -6
  218. package/src/devices-hap/motion.ts +3 -3
  219. package/src/devices-hap/plug.ts +10 -6
  220. package/src/devices-hap/relayswitch.ts +3 -3
  221. package/src/devices-hap/robotvacuumcleaner.ts +12 -6
  222. package/src/devices-hap/waterdetector.ts +3 -3
  223. package/src/devices-matter/BaseMatterAccessory.ts +176 -5
  224. package/src/devices-matter/ColorLightAccessory.ts +12 -12
  225. package/src/devices-matter/ColorTemperatureLightAccessory.ts +5 -7
  226. package/src/devices-matter/DimmableLightAccessory.ts +9 -9
  227. package/src/devices-matter/ExtendedColorLightAccessory.ts +14 -15
  228. package/src/devices-matter/OnOffLightAccessory.ts +8 -16
  229. package/src/devices-matter/OnOffOutletAccessory.ts +12 -7
  230. package/src/devices-matter/OnOffSwitchAccessory.ts +2 -2
  231. package/src/devices-matter/RoboticVacuumAccessory.ts +340 -313
  232. package/src/homebridge-ui/public/index.html +200 -18
  233. package/src/homebridge-ui/server.ts +85 -9
  234. package/src/index.ts +4 -7
  235. package/src/irdevice/irdevice.ts +74 -35
  236. package/src/platform-hap.ts +365 -169
  237. package/src/platform-matter.ts +1872 -229
  238. package/src/settings.ts +62 -3
  239. package/src/test/apiRequestTracker.test.ts +417 -0
  240. package/src/test/hap/device-webhook-context.test.ts +136 -0
  241. package/src/test/hap/platform-hap.logging.test.ts +36 -0
  242. package/src/test/hap/platform-hap.test.ts +70 -0
  243. package/src/test/helpers/platform-fixtures.ts +33 -0
  244. package/src/test/homebridge-ui/server.test.ts +486 -0
  245. package/src/test/index.test.ts +24 -0
  246. package/src/test/matter/devices-matter/baseMatterAccessory.test.ts +88 -0
  247. package/src/test/matter/devices-matter/roboticVacuumAccessory.test.ts +453 -0
  248. package/src/test/matter/platform-matter.additional.test.ts +44 -0
  249. package/src/test/matter/platform-matter.bleparse.test.ts +47 -0
  250. package/src/test/matter/platform-matter.cleanup.test.ts +86 -0
  251. package/src/test/matter/platform-matter.keepstale.test.ts +37 -0
  252. package/src/test/matter/platform-matter.logging.test.ts +33 -0
  253. package/src/test/matter/platform-matter.mapping.test.ts +57 -0
  254. package/src/test/matter/platform-matter.openapi-mapping.test.ts +109 -0
  255. package/src/test/matter/platform-matter.test.ts +144 -0
  256. package/src/test/matter/platform-matter.unregister.test.ts +39 -0
  257. package/src/test/matter/platform-matter.webhook.test.ts +54 -0
  258. package/src/test/utils.test.ts +96 -0
  259. package/src/{verifyconfig.test.ts → test/verifyconfig.test.ts} +12 -11
  260. package/src/utils.ts +777 -36
  261. package/dist/index.test.js +0 -14
  262. package/dist/index.test.js.map +0 -1
  263. package/dist/verifyconfig.test.d.ts.map +0 -1
  264. package/dist/verifyconfig.test.js.map +0 -1
  265. package/src/index.test.ts +0 -19
  266. /package/dist/{index.test.d.ts → test/index.test.d.ts} +0 -0
  267. /package/dist/{verifyconfig.test.d.ts → test/verifyconfig.test.d.ts} +0 -0
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { BaseMatterAccessory } from '../../../devices-matter/BaseMatterAccessory.js'
4
+
5
+ // Minimal concrete subclass for testing
6
+ class TestAccessory extends BaseMatterAccessory {
7
+ constructor(api: any, log: any, opts?: any) {
8
+ super(api, log, {
9
+ uuid: opts?.uuid ?? api.matter.uuid.generate('test'),
10
+ displayName: opts?.displayName ?? 'Test',
11
+ deviceType: opts?.deviceType ?? ('OnOffLight' as any),
12
+ serialNumber: opts?.serialNumber ?? 'TEST-1',
13
+ manufacturer: opts?.manufacturer ?? 'TestCo',
14
+ model: opts?.model ?? 'T-1',
15
+ firmwareRevision: opts?.firmwareRevision ?? '1.0',
16
+ hardwareRevision: opts?.hardwareRevision ?? '1.0',
17
+ context: opts?.context ?? {},
18
+ clusters: opts?.clusters,
19
+ handlers: opts?.handlers,
20
+ })
21
+ }
22
+ }
23
+
24
+ describe('baseMatterAccessory helpers', () => {
25
+ it('sendOnCommand uses OpenAPI helper and updates state', async () => {
26
+ const update = vi.fn()
27
+ const sendOpenAPI = vi.fn(async () => ({ response: {}, statusCode: 200 }))
28
+ const api: any = { matter: { clusterNames: { OnOff: 'onOff', LevelControl: 'level', ColorControl: 'color' }, uuid: { generate: () => 'uuid-1' }, updateAccessoryState: update } }
29
+ const log: any = { info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn() }
30
+
31
+ const ctx = { deviceId: 'DEV-1', sendOpenAPI, connectionType: 'OpenAPI' }
32
+ const acc = new TestAccessory(api, log, { context: ctx })
33
+
34
+ await acc.sendOnCommand()
35
+
36
+ expect(sendOpenAPI).toHaveBeenCalledWith('turnOn', 'default')
37
+ expect(update).toHaveBeenCalledWith(acc.uuid, 'onOff', { onOff: true })
38
+ })
39
+
40
+ it('sendOnCommand uses BLE helper when connectionType is BLE', async () => {
41
+ const update = vi.fn()
42
+ const sendBLE = vi.fn(async () => true)
43
+ const api: any = { matter: { clusterNames: { OnOff: 'onOff', LevelControl: 'level', ColorControl: 'color' }, uuid: { generate: () => 'uuid-2' }, updateAccessoryState: update } }
44
+ const log: any = { info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn() }
45
+
46
+ const ctx = { deviceId: 'DEV-2', sendBLE, connectionType: 'BLE' }
47
+ const acc = new TestAccessory(api, log, { context: ctx })
48
+
49
+ await acc.sendOnCommand()
50
+
51
+ expect(sendBLE).toHaveBeenCalledWith('turnOn')
52
+ expect(update).toHaveBeenCalledWith(acc.uuid, 'onOff', { onOff: true })
53
+ })
54
+
55
+ it('sendSetBrightness sends percent and updates LevelControl', async () => {
56
+ const update = vi.fn()
57
+ const sendOpenAPI = vi.fn(async () => ({ response: {}, statusCode: 200 }))
58
+ const api: any = { matter: { clusterNames: { OnOff: 'onOff', LevelControl: 'level', ColorControl: 'color' }, uuid: { generate: () => 'uuid-3' }, updateAccessoryState: update } }
59
+ const log: any = { info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn() }
60
+
61
+ const ctx = { deviceId: 'DEV-3', sendOpenAPI, connectionType: 'OpenAPI' }
62
+ const acc = new TestAccessory(api, log, { context: ctx })
63
+
64
+ await acc.sendSetBrightness(50)
65
+
66
+ expect(sendOpenAPI).toHaveBeenCalledWith('setBrightness', '50')
67
+ const expectedLevel = Math.round((50 / 100) * 254)
68
+ expect(update).toHaveBeenCalledWith(acc.uuid, 'level', { currentLevel: expectedLevel })
69
+ })
70
+
71
+ it('sendSetColor sends RGB and updates ColorControl', async () => {
72
+ const update = vi.fn()
73
+ const sendOpenAPI = vi.fn(async () => ({ response: {}, statusCode: 200 }))
74
+ const api: any = { matter: { clusterNames: { OnOff: 'onOff', LevelControl: 'level', ColorControl: 'color' }, uuid: { generate: () => 'uuid-4' }, updateAccessoryState: update } }
75
+ const log: any = { info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn() }
76
+
77
+ const ctx = { deviceId: 'DEV-4', sendOpenAPI, connectionType: 'OpenAPI' }
78
+ const acc = new TestAccessory(api, log, { context: ctx })
79
+
80
+ await acc.sendSetColor(255, 128, 0)
81
+
82
+ expect(sendOpenAPI).toHaveBeenCalledWith('setColor', '255:128:0')
83
+ expect(update).toHaveBeenCalled()
84
+ // Ensure we updated ColorControl cluster with numeric attributes
85
+ const calledWith = (update.mock.calls[0] || update.mock.calls[1])
86
+ expect(calledWith[1] === 'color' || calledWith[1] === api.matter.clusterNames.ColorControl).toBeTruthy()
87
+ })
88
+ })
@@ -0,0 +1,453 @@
1
+ import type { API, Logger } from 'homebridge'
2
+
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
4
+
5
+ // Mock the base accessory
6
+ vi.mock('../../../devices-matter/BaseMatterAccessory.js', () => ({
7
+ BaseMatterAccessory: class {
8
+ api: any
9
+ log: any
10
+ config: any
11
+ context: any
12
+
13
+ constructor(api: API, log: Logger, config: any) {
14
+ this.api = api
15
+ this.log = log
16
+ this.config = config
17
+ this.context = config.context
18
+ }
19
+
20
+ logInfo = vi.fn()
21
+ logWarn = vi.fn()
22
+ logError = vi.fn()
23
+ sendOpenAPICommand = vi.fn(async () => {})
24
+ updateState = vi.fn()
25
+ },
26
+ }))
27
+
28
+ describe('roboticVacuumAccessory', () => {
29
+ let mockAPI: any
30
+ let mockLogger: Logger
31
+ let RoboticVacuumAccessory: any
32
+
33
+ beforeEach(async () => {
34
+ // Clear all mocks
35
+ vi.clearAllMocks()
36
+
37
+ // Mock API
38
+ mockAPI = {
39
+ matter: {
40
+ uuid: {
41
+ generate: vi.fn((serial: string) => `uuid-${serial}`),
42
+ },
43
+ deviceTypes: {
44
+ RoboticVacuumCleaner: 'RoboticVacuumCleaner',
45
+ },
46
+ },
47
+ }
48
+
49
+ // Mock Logger
50
+ mockLogger = {
51
+ info: vi.fn(),
52
+ warn: vi.fn(),
53
+ error: vi.fn(),
54
+ debug: vi.fn(),
55
+ } as any
56
+
57
+ // Dynamically import the class after mocks are set up
58
+ const module = await import('../../../devices-matter/RoboticVacuumAccessory.js')
59
+ RoboticVacuumAccessory = module.RoboticVacuumAccessory
60
+ })
61
+
62
+ describe('model detection', () => {
63
+ it('should detect S1 model from Robot Vacuum Cleaner S1', () => {
64
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
65
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
66
+ })
67
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
68
+ expect((vacuum as any).capabilities.suctionKind).toBe('powLevel-0-3')
69
+ expect((vacuum as any).capabilities.pause).toBe(false)
70
+ })
71
+
72
+ it('should detect S1 Plus model', () => {
73
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
74
+ context: { deviceType: 'Robot Vacuum Cleaner S1 Plus' },
75
+ })
76
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
77
+ expect((vacuum as any).capabilities.pause).toBe(false)
78
+ })
79
+
80
+ it('should detect S1 Pro model', () => {
81
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
82
+ context: { deviceType: 'Robot Vacuum Cleaner S1 Pro' },
83
+ })
84
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
85
+ })
86
+
87
+ it('should detect S1 Mini model', () => {
88
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
89
+ context: { deviceType: 'Robot Vacuum Cleaner S1 Mini' },
90
+ })
91
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
92
+ })
93
+
94
+ it('should detect WoSweeper model', () => {
95
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
96
+ context: { deviceType: 'WoSweeper' },
97
+ })
98
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
99
+ expect((vacuum as any).capabilities.suctionKind).toBe('powLevel-0-3')
100
+ })
101
+
102
+ it('should detect WoSweeperMini model', () => {
103
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
104
+ context: { deviceType: 'WoSweeperMini' },
105
+ })
106
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
107
+ })
108
+
109
+ it('should detect K10+ model', () => {
110
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
111
+ context: { deviceType: 'K10+' },
112
+ })
113
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
114
+ expect((vacuum as any).capabilities.pause).toBe(false)
115
+ })
116
+
117
+ it('should detect K10+ Pro model', () => {
118
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
119
+ context: { deviceType: 'K10+ Pro' },
120
+ })
121
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
122
+ expect((vacuum as any).capabilities.pause).toBe(false)
123
+ })
124
+
125
+ it('should detect K10+ Pro Combo model with mop capability', () => {
126
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
127
+ context: { deviceType: 'Robot Vacuum Cleaner K10+ Pro Combo' },
128
+ })
129
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-mop')
130
+ expect((vacuum as any).capabilities.suctionKind).toBe('fanLevel-1-4')
131
+ expect((vacuum as any).capabilities.pause).toBe(true)
132
+ expect((vacuum as any).capabilities.waterLevel).toBe(false)
133
+ })
134
+
135
+ it('should detect S10 model with vacuum+mop and advanced commands', () => {
136
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
137
+ context: { deviceType: 'Robot Vacuum Cleaner S10' },
138
+ })
139
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-vacmop')
140
+ expect((vacuum as any).capabilities.suctionKind).toBe('fanLevel-1-4')
141
+ expect((vacuum as any).capabilities.pause).toBe(true)
142
+ expect((vacuum as any).capabilities.waterLevel).toBe(true)
143
+ expect((vacuum as any).capabilities.advancedCommands?.selfClean).toBe(true)
144
+ expect((vacuum as any).capabilities.advancedCommands?.addWaterForHumi).toBe(true)
145
+ })
146
+
147
+ it('should detect S20 model with vacuum+mop and advanced commands', () => {
148
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
149
+ context: { deviceType: 'Robot Vacuum Cleaner S20' },
150
+ })
151
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-vacmop')
152
+ expect((vacuum as any).capabilities.advancedCommands?.selfClean).toBe(true)
153
+ })
154
+
155
+ it('should detect K11+ model', () => {
156
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
157
+ context: { deviceType: 'Robot Vacuum Cleaner K11+' },
158
+ })
159
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-mop')
160
+ expect((vacuum as any).capabilities.waterLevel).toBe(true)
161
+ expect((vacuum as any).capabilities.advancedCommands?.setVolume).toBe(true)
162
+ expect((vacuum as any).capabilities.advancedCommands?.selfClean).toBeUndefined()
163
+ })
164
+
165
+ it('should detect K20+ Pro model', () => {
166
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
167
+ context: { deviceType: 'Robot Vacuum Cleaner K20 Plus Pro' },
168
+ })
169
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-mop')
170
+ expect((vacuum as any).capabilities.pause).toBe(true)
171
+ })
172
+
173
+ it('should default to basic vacuum for unknown models', () => {
174
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
175
+ context: { deviceType: 'Unknown Model' },
176
+ })
177
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
178
+ expect((vacuum as any).capabilities.pause).toBe(false)
179
+ expect((vacuum as any).capabilities.suctionKind).toBe('powLevel-0-3')
180
+ })
181
+
182
+ it('should handle missing context', () => {
183
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {})
184
+ expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
185
+ expect((vacuum as any).capabilities.pause).toBe(false)
186
+ })
187
+ })
188
+
189
+ describe('capability matrix', () => {
190
+ it('should configure correct clusters for basic vacuum (S1)', () => {
191
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
192
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
193
+ })
194
+ const clusters = (vacuum as any).config.clusters
195
+
196
+ // Should have rvcRunMode with Idle and Cleaning only
197
+ expect(clusters.rvcRunMode.supportedModes).toHaveLength(2)
198
+ expect(clusters.rvcRunMode.supportedModes[0].label).toBe('Idle')
199
+ expect(clusters.rvcRunMode.supportedModes[1].label).toBe('Cleaning')
200
+
201
+ // Should have rvcCleanMode with suction levels
202
+ expect(clusters.rvcCleanMode.supportedModes).toHaveLength(4)
203
+ expect(clusters.rvcCleanMode.supportedModes.map((m: any) => m.label)).toEqual([
204
+ 'Quiet',
205
+ 'Standard',
206
+ 'Strong',
207
+ 'MAX',
208
+ ])
209
+
210
+ // Should not have serviceArea cluster
211
+ expect(clusters.serviceArea).toBeUndefined()
212
+ })
213
+
214
+ it('should configure correct clusters for K10+ Pro Combo', () => {
215
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
216
+ context: { deviceType: 'K10+ Pro Combo' },
217
+ })
218
+ const clusters = (vacuum as any).config.clusters
219
+
220
+ // Should have vacuum/mop action modes
221
+ expect(clusters.rvcCleanMode.supportedModes).toHaveLength(2)
222
+ expect(clusters.rvcCleanMode.supportedModes.map((m: any) => m.label)).toEqual(['Vacuum', 'Mop'])
223
+ })
224
+
225
+ it('should configure correct clusters for S10', () => {
226
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
227
+ context: { deviceType: 'Robot Vacuum Cleaner S10' },
228
+ })
229
+ const clusters = (vacuum as any).config.clusters
230
+
231
+ // Should have vacuum and vacuum+mop modes
232
+ expect(clusters.rvcCleanMode.supportedModes).toHaveLength(2)
233
+ expect(clusters.rvcCleanMode.supportedModes.map((m: any) => m.label)).toEqual(['Vacuum', 'Vacuum & Mop'])
234
+ })
235
+
236
+ it('should include pause handler for models that support it', () => {
237
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
238
+ context: { deviceType: 'K10+ Pro Combo' },
239
+ })
240
+ const handlers = (vacuum as any).config.handlers
241
+
242
+ expect(handlers.rvcOperationalState.pause).toBeDefined()
243
+ expect(handlers.rvcOperationalState.start).toBeDefined()
244
+ expect(handlers.rvcOperationalState.stop).toBeDefined()
245
+ expect(handlers.rvcOperationalState.goHome).toBeDefined()
246
+ })
247
+
248
+ it('should not include pause handler for basic models', () => {
249
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
250
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
251
+ })
252
+ const handlers = (vacuum as any).config.handlers
253
+
254
+ expect(handlers.rvcOperationalState.pause).toBeUndefined()
255
+ expect(handlers.rvcOperationalState.start).toBeDefined()
256
+ })
257
+ })
258
+
259
+ describe('openAPI command mapping', () => {
260
+ it('should send start command for basic vacuum', async () => {
261
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
262
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
263
+ })
264
+
265
+ await (vacuum as any).handleStart()
266
+
267
+ expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('start')
268
+ })
269
+
270
+ it('should send stop command for basic vacuum', async () => {
271
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
272
+ context: { deviceType: 'K10+' },
273
+ })
274
+
275
+ await (vacuum as any).handleStop()
276
+
277
+ expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('stop')
278
+ })
279
+
280
+ it('should send dock command on goHome', async () => {
281
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
282
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
283
+ })
284
+
285
+ await (vacuum as any).handleGoHome()
286
+
287
+ expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('dock')
288
+ })
289
+
290
+ it('should send PowLevel command for basic vacuum suction change', async () => {
291
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
292
+ context: { deviceType: 'WoSweeper' },
293
+ })
294
+
295
+ await (vacuum as any).handleChangeCleanMode({ newMode: 2 }) // Strong
296
+
297
+ expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('PowLevel', '2')
298
+ })
299
+
300
+ it('should send startClean with action for K10+ Pro Combo', async () => {
301
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
302
+ context: { deviceType: 'K10+ Pro Combo' },
303
+ })
304
+
305
+ // Set to vacuum mode first
306
+ await (vacuum as any).handleChangeCleanMode({ newMode: 0 })
307
+ expect((vacuum as any).currentCleanAction).toBe('vacuum')
308
+
309
+ await (vacuum as any).handleStart()
310
+
311
+ const expectedPayload = JSON.stringify({
312
+ action: 'vacuum',
313
+ param: { fanLevel: 2 },
314
+ })
315
+ expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('startClean', expectedPayload)
316
+ })
317
+
318
+ it('should send startClean with sweep_mop action for S10 vacuum+mop mode', async () => {
319
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
320
+ context: { deviceType: 'Robot Vacuum Cleaner S10' },
321
+ })
322
+
323
+ // Set to vacuum+mop mode
324
+ await (vacuum as any).handleChangeCleanMode({ newMode: 1 })
325
+ expect((vacuum as any).currentCleanAction).toBe('vacuum_mop')
326
+
327
+ await (vacuum as any).handleStart()
328
+
329
+ const calls = (vacuum as any).sendOpenAPICommand.mock.calls
330
+ const startCleanCall = calls.find((call: any) => call[0] === 'startClean')
331
+ expect(startCleanCall).toBeDefined()
332
+
333
+ const payload = JSON.parse(startCleanCall[1])
334
+ expect(payload.action).toBe('sweep_mop')
335
+ expect(payload.param.fanLevel).toBe(2)
336
+ expect(payload.param.waterLevel).toBe(1)
337
+ })
338
+
339
+ it('should include waterLevel for S20 models', async () => {
340
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
341
+ context: { deviceType: 'Robot Vacuum Cleaner S20' },
342
+ })
343
+
344
+ await (vacuum as any).handleStart()
345
+
346
+ const calls = (vacuum as any).sendOpenAPICommand.mock.calls
347
+ const startCleanCall = calls.find((call: any) => call[0] === 'startClean')
348
+ const payload = JSON.parse(startCleanCall[1])
349
+ expect(payload.param.waterLevel).toBe(1)
350
+ })
351
+
352
+ it('should not include waterLevel for K10+ Pro Combo', async () => {
353
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
354
+ context: { deviceType: 'K10+ Pro Combo' },
355
+ })
356
+
357
+ await (vacuum as any).handleStart()
358
+
359
+ const calls = (vacuum as any).sendOpenAPICommand.mock.calls
360
+ const startCleanCall = calls.find((call: any) => call[0] === 'startClean')
361
+ const payload = JSON.parse(startCleanCall[1])
362
+ expect(payload.param.waterLevel).toBeUndefined()
363
+ })
364
+
365
+ it('should send pause command for models that support it', async () => {
366
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
367
+ context: { deviceType: 'S10' },
368
+ })
369
+
370
+ await (vacuum as any).handlePause()
371
+
372
+ expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('pause')
373
+ })
374
+
375
+ it('should update local action state for clean mode changes on K10+ Pro Combo', async () => {
376
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
377
+ context: { deviceType: 'K10+ Pro Combo' },
378
+ })
379
+
380
+ // Change to mop mode
381
+ await (vacuum as any).handleChangeCleanMode({ newMode: 1 })
382
+ expect((vacuum as any).currentCleanAction).toBe('mop')
383
+
384
+ // Change back to vacuum mode
385
+ await (vacuum as any).handleChangeCleanMode({ newMode: 0 })
386
+ expect((vacuum as any).currentCleanAction).toBe('vacuum')
387
+
388
+ // Clean mode changes should just update state, not call OpenAPI
389
+ expect((vacuum as any).sendOpenAPICommand).not.toHaveBeenCalled()
390
+ })
391
+ })
392
+
393
+ describe('state updates', () => {
394
+ it('should update run mode correctly', () => {
395
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
396
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
397
+ })
398
+
399
+ vacuum.updateRunMode(1) // Cleaning
400
+
401
+ expect((vacuum as any).updateState).toHaveBeenCalledWith('rvcRunMode', { currentMode: 1 })
402
+ })
403
+
404
+ it('should update clean mode correctly', () => {
405
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
406
+ context: { deviceType: 'K10+' },
407
+ })
408
+
409
+ vacuum.updateCleanMode(2) // Strong
410
+
411
+ expect((vacuum as any).updateState).toHaveBeenCalledWith('rvcCleanMode', { currentMode: 2 })
412
+ })
413
+
414
+ it('should update operational state correctly', () => {
415
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
416
+ context: { deviceType: 'S10' },
417
+ })
418
+
419
+ vacuum.updateOperationalState(64) // Seeking Charger
420
+
421
+ expect((vacuum as any).updateState).toHaveBeenCalledWith('rvcOperationalState', { operationalState: 64 })
422
+ })
423
+
424
+ it('should update battery percentage without PowerSource cluster', async () => {
425
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
426
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
427
+ })
428
+
429
+ await vacuum.updateBatteryPercentage(75)
430
+
431
+ // Should log but not update any cluster
432
+ expect((vacuum as any).logInfo).toHaveBeenCalledWith(expect.stringContaining('75%'))
433
+ expect((vacuum as any).context.batteryPercentage).toBe(75)
434
+ })
435
+ })
436
+
437
+ describe('error handling', () => {
438
+ it('should handle OpenAPI command failures gracefully', async () => {
439
+ const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
440
+ context: { deviceType: 'Robot Vacuum Cleaner S1' },
441
+ })
442
+
443
+ // Mock failure
444
+ ;(vacuum as any).sendOpenAPICommand = vi.fn(async () => {
445
+ throw new Error('API Error')
446
+ })
447
+
448
+ await (vacuum as any).handleStart()
449
+
450
+ expect((vacuum as any).logWarn).toHaveBeenCalledWith(expect.stringContaining('failed'))
451
+ })
452
+ })
453
+ })
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
+
6
+ describe('additional platform-matter mapping tests', () => {
7
+ it('parses OpenAPI color strings and triggers an update', async () => {
8
+ const updateAccessoryState = vi.fn()
9
+ const api: any = makeApiStub({ updateAccessoryState })
10
+ const log = makeLogStub()
11
+ const platform = new SwitchBotMatterPlatform(log as any, {} as any, api)
12
+
13
+ // Some OpenAPI statuses provide color as comma/colon separated or hex
14
+ await (platform as any).applyStatusToAccessory('uuid-col', { deviceId: 'DEV-COL' } as any, { color: '255:128:64' })
15
+ await (platform as any).applyStatusToAccessory('uuid-col', { deviceId: 'DEV-COL' } as any, { color: '255,128,64' })
16
+ await (platform as any).applyStatusToAccessory('uuid-col', { deviceId: 'DEV-COL' } as any, { color: '#FF8040' })
17
+
18
+ expect(updateAccessoryState).toHaveBeenCalled()
19
+ })
20
+
21
+ it('maps MeterPro CO2 values to Matter updates', async () => {
22
+ const updateAccessoryState = vi.fn()
23
+ const api: any = makeApiStub({ updateAccessoryState })
24
+ const log = makeLogStub()
25
+ const platform = new SwitchBotMatterPlatform(log as any, {} as any, api)
26
+
27
+ await (platform as any).applyStatusToAccessory('uuid-co2', { deviceId: 'DEV-METERPRO' } as any, { co2: 420 })
28
+
29
+ expect(updateAccessoryState).toHaveBeenCalled()
30
+ })
31
+
32
+ it('handles curtain position synonyms (position / slidePosition)', async () => {
33
+ const updateAccessoryState = vi.fn()
34
+ const api: any = makeApiStub({ updateAccessoryState })
35
+ const log = makeLogStub()
36
+ const platform = new SwitchBotMatterPlatform(log as any, {} as any, api)
37
+
38
+ // Provide status with position and slidePosition synonyms
39
+ await (platform as any).applyStatusToAccessory('uuid-cur', { deviceId: 'DEV-CUR', deviceType: 'Curtain' } as any, { position: 30 })
40
+ await (platform as any).applyStatusToAccessory('uuid-cur', { deviceId: 'DEV-CUR', deviceType: 'Curtain' } as any, { slidePosition: 70 })
41
+
42
+ expect(updateAccessoryState).toHaveBeenCalled()
43
+ })
44
+ })
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { SwitchBotMatterPlatform } from '../../platform-matter.js'
4
+ import { makeApiStub, makeLogStub } from '../helpers/platform-fixtures.js'
5
+
6
+ describe('platform-matter BLE advertisement parser', () => {
7
+ it('parses extended serviceData fields correctly', () => {
8
+ const updateAccessoryState = vi.fn()
9
+ const api: any = makeApiStub({ updateAccessoryState })
10
+
11
+ const log: any = makeLogStub()
12
+ const platform = new SwitchBotMatterPlatform(log as any, {} as any, api)
13
+
14
+ const dev: any = { deviceId: 'DEV-TEST' }
15
+ const sd = {
16
+ temp: 22.3,
17
+ humid: 45,
18
+ pm25: 12,
19
+ pm10: 34,
20
+ voc: 120,
21
+ co2: 420,
22
+ motion: 1,
23
+ open: 0,
24
+ leak: 0,
25
+ position: 30,
26
+ fanSpeed: 75,
27
+ battery: 88,
28
+ rgb: '255:128:64',
29
+ }
30
+
31
+ const parsed = (platform as any).parseAdvertisementForDevice(dev, sd)
32
+ expect(parsed).toBeTruthy()
33
+ expect(parsed.temperature).toBeCloseTo(22.3)
34
+ expect(parsed.humidity).toBe(45)
35
+ expect(parsed.pm25).toBe(12)
36
+ expect(parsed.pm10).toBe(34)
37
+ expect(parsed.voc).toBe(120)
38
+ expect(parsed.co2).toBe(420)
39
+ expect(parsed.motion).toBeTruthy()
40
+ expect(parsed.contact).toBeDefined()
41
+ expect(parsed.leak).toBeFalsy()
42
+ expect(parsed.position).toBe(30)
43
+ expect(parsed.fanSpeed).toBe(75)
44
+ expect(parsed.battery).toBe(88)
45
+ expect(parsed.color).toEqual({ r: 255, g: 128, b: 64 })
46
+ })
47
+ })