@switchbot/homebridge-switchbot 5.0.0-beta.6 → 5.0.0-beta.61
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.
- package/CHANGELOG.md +15 -0
- package/README.md +45 -3
- package/config.schema.json +866 -13754
- package/dist/devices-hap/airpurifier.d.ts.map +1 -1
- package/dist/devices-hap/airpurifier.js +12 -6
- package/dist/devices-hap/airpurifier.js.map +1 -1
- package/dist/devices-hap/blindtilt.js +3 -3
- package/dist/devices-hap/bot.d.ts.map +1 -1
- package/dist/devices-hap/bot.js +16 -5
- package/dist/devices-hap/bot.js.map +1 -1
- package/dist/devices-hap/ceilinglight.d.ts.map +1 -1
- package/dist/devices-hap/ceilinglight.js +13 -7
- package/dist/devices-hap/ceilinglight.js.map +1 -1
- package/dist/devices-hap/colorbulb.d.ts.map +1 -1
- package/dist/devices-hap/colorbulb.js +49 -9
- package/dist/devices-hap/colorbulb.js.map +1 -1
- package/dist/devices-hap/contact.js +3 -3
- package/dist/devices-hap/curtain.js +2 -2
- package/dist/devices-hap/curtain.js.map +1 -1
- package/dist/devices-hap/device.d.ts +18 -8
- package/dist/devices-hap/device.d.ts.map +1 -1
- package/dist/devices-hap/device.js +141 -69
- package/dist/devices-hap/device.js.map +1 -1
- package/dist/devices-hap/fan.d.ts.map +1 -1
- package/dist/devices-hap/fan.js +12 -6
- package/dist/devices-hap/fan.js.map +1 -1
- package/dist/devices-hap/hub.d.ts.map +1 -1
- package/dist/devices-hap/hub.js +6 -5
- package/dist/devices-hap/hub.js.map +1 -1
- package/dist/devices-hap/humidifier.d.ts +5 -0
- package/dist/devices-hap/humidifier.d.ts.map +1 -1
- package/dist/devices-hap/humidifier.js +92 -4
- package/dist/devices-hap/humidifier.js.map +1 -1
- package/dist/devices-hap/iosensor.d.ts.map +1 -1
- package/dist/devices-hap/iosensor.js +36 -21
- package/dist/devices-hap/iosensor.js.map +1 -1
- package/dist/devices-hap/lightstrip.d.ts.map +1 -1
- package/dist/devices-hap/lightstrip.js +38 -8
- package/dist/devices-hap/lightstrip.js.map +1 -1
- package/dist/devices-hap/lock.d.ts.map +1 -1
- package/dist/devices-hap/lock.js +14 -6
- package/dist/devices-hap/lock.js.map +1 -1
- package/dist/devices-hap/meter.d.ts.map +1 -1
- package/dist/devices-hap/meter.js +6 -5
- package/dist/devices-hap/meter.js.map +1 -1
- package/dist/devices-hap/meterplus.d.ts.map +1 -1
- package/dist/devices-hap/meterplus.js +6 -5
- package/dist/devices-hap/meterplus.js.map +1 -1
- package/dist/devices-hap/meterpro.d.ts.map +1 -1
- package/dist/devices-hap/meterpro.js +7 -6
- package/dist/devices-hap/meterpro.js.map +1 -1
- package/dist/devices-hap/motion.js +3 -3
- package/dist/devices-hap/plug.d.ts.map +1 -1
- package/dist/devices-hap/plug.js +11 -6
- package/dist/devices-hap/plug.js.map +1 -1
- package/dist/devices-hap/relayswitch.js +3 -3
- package/dist/devices-hap/robotvacuumcleaner.d.ts.map +1 -1
- package/dist/devices-hap/robotvacuumcleaner.js +13 -6
- package/dist/devices-hap/robotvacuumcleaner.js.map +1 -1
- package/dist/devices-hap/waterdetector.js +3 -3
- package/dist/devices-matter/BaseMatterAccessory.d.ts +27 -0
- package/dist/devices-matter/BaseMatterAccessory.d.ts.map +1 -1
- package/dist/devices-matter/BaseMatterAccessory.js +169 -5
- package/dist/devices-matter/BaseMatterAccessory.js.map +1 -1
- package/dist/devices-matter/ColorLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ColorLightAccessory.js +12 -12
- package/dist/devices-matter/ColorLightAccessory.js.map +1 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.js +5 -7
- package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +1 -1
- package/dist/devices-matter/DimmableLightAccessory.js +9 -9
- package/dist/devices-matter/DimmableLightAccessory.js.map +1 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.js +14 -15
- package/dist/devices-matter/ExtendedColorLightAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffLightAccessory.d.ts.map +1 -1
- package/dist/devices-matter/OnOffLightAccessory.js +8 -16
- package/dist/devices-matter/OnOffLightAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffOutletAccessory.d.ts +2 -0
- package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +1 -1
- package/dist/devices-matter/OnOffOutletAccessory.js +10 -7
- package/dist/devices-matter/OnOffOutletAccessory.js.map +1 -1
- package/dist/devices-matter/OnOffSwitchAccessory.js +2 -2
- package/dist/devices-matter/OnOffSwitchAccessory.js.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts +29 -43
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.js +287 -262
- package/dist/devices-matter/RoboticVacuumAccessory.js.map +1 -1
- package/dist/homebridge-ui/public/index.html +200 -18
- package/dist/homebridge-ui/server.js +0 -31
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -7
- package/dist/index.js.map +1 -1
- package/dist/irdevice/irdevice.d.ts +11 -10
- package/dist/irdevice/irdevice.d.ts.map +1 -1
- package/dist/irdevice/irdevice.js +76 -35
- package/dist/irdevice/irdevice.js.map +1 -1
- package/dist/platform-hap.d.ts +26 -15
- package/dist/platform-hap.d.ts.map +1 -1
- package/dist/platform-hap.js +333 -153
- package/dist/platform-hap.js.map +1 -1
- package/dist/platform-matter.d.ts +93 -6
- package/dist/platform-matter.d.ts.map +1 -1
- package/dist/platform-matter.js +1822 -224
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +58 -7
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/test/apiRequestTracker.test.d.ts +2 -0
- package/dist/test/apiRequestTracker.test.d.ts.map +1 -0
- package/dist/test/apiRequestTracker.test.js +392 -0
- package/dist/test/apiRequestTracker.test.js.map +1 -0
- package/dist/test/hap/device-webhook-context.test.d.ts +2 -0
- package/dist/test/hap/device-webhook-context.test.d.ts.map +1 -0
- package/dist/test/hap/device-webhook-context.test.js +128 -0
- package/dist/test/hap/device-webhook-context.test.js.map +1 -0
- package/dist/test/hap/platform-hap.logging.test.d.ts +2 -0
- package/dist/test/hap/platform-hap.logging.test.d.ts.map +1 -0
- package/dist/test/hap/platform-hap.logging.test.js +33 -0
- package/dist/test/hap/platform-hap.logging.test.js.map +1 -0
- package/dist/test/hap/platform-hap.test.d.ts +2 -0
- package/dist/test/hap/platform-hap.test.d.ts.map +1 -0
- package/dist/test/hap/platform-hap.test.js +62 -0
- package/dist/test/hap/platform-hap.test.js.map +1 -0
- package/dist/test/helpers/platform-fixtures.d.ts +9 -0
- package/dist/test/helpers/platform-fixtures.d.ts.map +1 -0
- package/dist/test/helpers/platform-fixtures.js +30 -0
- package/dist/test/helpers/platform-fixtures.js.map +1 -0
- package/dist/test/homebridge-ui/server.test.d.ts +2 -0
- package/dist/test/homebridge-ui/server.test.d.ts.map +1 -0
- package/dist/test/homebridge-ui/server.test.js +445 -0
- package/dist/test/homebridge-ui/server.test.js.map +1 -0
- package/dist/{index.test.d.ts.map → test/index.test.d.ts.map} +1 -1
- package/dist/test/index.test.js +19 -0
- package/dist/test/index.test.js.map +1 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts +2 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.d.ts.map +1 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.js +71 -0
- package/dist/test/matter/devices-matter/baseMatterAccessory.test.js.map +1 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts +2 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts.map +1 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js +366 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js.map +1 -0
- package/dist/test/matter/platform-matter.additional.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.additional.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.additional.test.js +35 -0
- package/dist/test/matter/platform-matter.additional.test.js.map +1 -0
- package/dist/test/matter/platform-matter.bleparse.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.bleparse.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.bleparse.test.js +43 -0
- package/dist/test/matter/platform-matter.bleparse.test.js.map +1 -0
- package/dist/test/matter/platform-matter.cleanup.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.cleanup.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.cleanup.test.js +70 -0
- package/dist/test/matter/platform-matter.cleanup.test.js.map +1 -0
- package/dist/test/matter/platform-matter.keepstale.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.keepstale.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.keepstale.test.js +27 -0
- package/dist/test/matter/platform-matter.keepstale.test.js.map +1 -0
- package/dist/test/matter/platform-matter.logging.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.logging.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.logging.test.js +29 -0
- package/dist/test/matter/platform-matter.logging.test.js.map +1 -0
- package/dist/test/matter/platform-matter.mapping.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.mapping.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.mapping.test.js +43 -0
- package/dist/test/matter/platform-matter.mapping.test.js.map +1 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.js +84 -0
- package/dist/test/matter/platform-matter.openapi-mapping.test.js.map +1 -0
- package/dist/test/matter/platform-matter.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.test.js +117 -0
- package/dist/test/matter/platform-matter.test.js.map +1 -0
- package/dist/test/matter/platform-matter.unregister.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.unregister.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.unregister.test.js +30 -0
- package/dist/test/matter/platform-matter.unregister.test.js.map +1 -0
- package/dist/test/matter/platform-matter.webhook.test.d.ts +2 -0
- package/dist/test/matter/platform-matter.webhook.test.d.ts.map +1 -0
- package/dist/test/matter/platform-matter.webhook.test.js +46 -0
- package/dist/test/matter/platform-matter.webhook.test.js.map +1 -0
- package/dist/test/utils.test.d.ts +2 -0
- package/dist/test/utils.test.d.ts.map +1 -0
- package/dist/test/utils.test.js +95 -0
- package/dist/test/utils.test.js.map +1 -0
- package/dist/test/verifyconfig.test.d.ts.map +1 -0
- package/dist/{verifyconfig.test.js → test/verifyconfig.test.js} +2 -2
- package/dist/test/verifyconfig.test.js.map +1 -0
- package/dist/utils.d.ts +204 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +713 -33
- package/dist/utils.js.map +1 -1
- package/docs/assets/highlight.css +14 -0
- package/docs/assets/main.js +2 -2
- package/docs/index.html +31 -2
- package/docs/variables/default.html +1 -1
- package/package.json +15 -15
- package/src/devices-hap/airpurifier.ts +11 -6
- package/src/devices-hap/blindtilt.ts +3 -3
- package/src/devices-hap/bot.ts +15 -5
- package/src/devices-hap/ceilinglight.ts +12 -7
- package/src/devices-hap/colorbulb.ts +46 -10
- package/src/devices-hap/contact.ts +3 -3
- package/src/devices-hap/curtain.ts +2 -2
- package/src/devices-hap/device.ts +149 -70
- package/src/devices-hap/fan.ts +11 -6
- package/src/devices-hap/hub.ts +6 -5
- package/src/devices-hap/humidifier.ts +97 -4
- package/src/devices-hap/iosensor.ts +36 -21
- package/src/devices-hap/lightstrip.ts +35 -8
- package/src/devices-hap/lock.ts +13 -6
- package/src/devices-hap/meter.ts +6 -5
- package/src/devices-hap/meterplus.ts +6 -5
- package/src/devices-hap/meterpro.ts +7 -6
- package/src/devices-hap/motion.ts +3 -3
- package/src/devices-hap/plug.ts +10 -6
- package/src/devices-hap/relayswitch.ts +3 -3
- package/src/devices-hap/robotvacuumcleaner.ts +12 -6
- package/src/devices-hap/waterdetector.ts +3 -3
- package/src/devices-matter/BaseMatterAccessory.ts +176 -5
- package/src/devices-matter/ColorLightAccessory.ts +12 -12
- package/src/devices-matter/ColorTemperatureLightAccessory.ts +5 -7
- package/src/devices-matter/DimmableLightAccessory.ts +9 -9
- package/src/devices-matter/ExtendedColorLightAccessory.ts +14 -15
- package/src/devices-matter/OnOffLightAccessory.ts +8 -16
- package/src/devices-matter/OnOffOutletAccessory.ts +12 -7
- package/src/devices-matter/OnOffSwitchAccessory.ts +2 -2
- package/src/devices-matter/RoboticVacuumAccessory.ts +340 -313
- package/src/homebridge-ui/public/index.html +200 -18
- package/src/homebridge-ui/server.ts +0 -34
- package/src/index.ts +4 -7
- package/src/irdevice/irdevice.ts +74 -35
- package/src/platform-hap.ts +365 -169
- package/src/platform-matter.ts +1872 -229
- package/src/settings.ts +62 -3
- package/src/test/apiRequestTracker.test.ts +417 -0
- package/src/test/hap/device-webhook-context.test.ts +136 -0
- package/src/test/hap/platform-hap.logging.test.ts +36 -0
- package/src/test/hap/platform-hap.test.ts +70 -0
- package/src/test/helpers/platform-fixtures.ts +33 -0
- package/src/test/homebridge-ui/server.test.ts +486 -0
- package/src/test/index.test.ts +24 -0
- package/src/test/matter/devices-matter/baseMatterAccessory.test.ts +88 -0
- package/src/test/matter/devices-matter/roboticVacuumAccessory.test.ts +453 -0
- package/src/test/matter/platform-matter.additional.test.ts +44 -0
- package/src/test/matter/platform-matter.bleparse.test.ts +47 -0
- package/src/test/matter/platform-matter.cleanup.test.ts +86 -0
- package/src/test/matter/platform-matter.keepstale.test.ts +37 -0
- package/src/test/matter/platform-matter.logging.test.ts +33 -0
- package/src/test/matter/platform-matter.mapping.test.ts +57 -0
- package/src/test/matter/platform-matter.openapi-mapping.test.ts +109 -0
- package/src/test/matter/platform-matter.test.ts +144 -0
- package/src/test/matter/platform-matter.unregister.test.ts +39 -0
- package/src/test/matter/platform-matter.webhook.test.ts +54 -0
- package/src/test/utils.test.ts +96 -0
- package/src/{verifyconfig.test.ts → test/verifyconfig.test.ts} +12 -11
- package/src/utils.ts +777 -36
- package/dist/index.test.js +0 -14
- package/dist/index.test.js.map +0 -1
- package/dist/verifyconfig.test.d.ts.map +0 -1
- package/dist/verifyconfig.test.js.map +0 -1
- package/src/index.test.ts +0 -19
- /package/dist/{index.test.d.ts → test/index.test.d.ts} +0 -0
- /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
|
+
})
|