@switchbot/homebridge-switchbot 5.0.0-beta.7 → 5.0.0-beta.70
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 +871 -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 +36 -43
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.js +478 -268
- package/dist/devices-matter/RoboticVacuumAccessory.js.map +1 -1
- package/dist/devices-matter/VenetianBlindAccessory.d.ts +6 -6
- package/dist/devices-matter/VenetianBlindAccessory.d.ts.map +1 -1
- package/dist/devices-matter/VenetianBlindAccessory.js.map +1 -1
- package/dist/devices-matter/WindowBlindAccessory.d.ts +5 -5
- package/dist/devices-matter/WindowBlindAccessory.d.ts.map +1 -1
- package/dist/devices-matter/WindowBlindAccessory.js +57 -6
- package/dist/devices-matter/WindowBlindAccessory.js.map +1 -1
- package/dist/homebridge-ui/public/index.html +219 -19
- 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 -9
- 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 +1878 -229
- package/dist/platform-matter.js.map +1 -1
- package/dist/settings.d.ts +75 -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/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 +529 -315
- package/src/devices-matter/VenetianBlindAccessory.ts +5 -5
- package/src/devices-matter/WindowBlindAccessory.ts +53 -10
- package/src/homebridge-ui/public/index.html +219 -19
- package/src/homebridge-ui/server.ts +0 -34
- package/src/index.ts +4 -10
- package/src/irdevice/irdevice.ts +74 -35
- package/src/platform-hap.ts +365 -169
- package/src/platform-matter.ts +1923 -230
- package/src/settings.ts +78 -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/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
|
@@ -1,105 +1,167 @@
|
|
|
1
|
-
|
|
1
|
+
//
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Robotic Vacuum Cleaner Accessory
|
|
4
|
+
* Robotic Vacuum Cleaner Accessory (SwitchBot-aligned)
|
|
5
5
|
*
|
|
6
|
-
* This is
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - Operational state management with realistic transitions
|
|
11
|
-
* - Service area (room) support
|
|
6
|
+
* This implementation is intentionally limited to SwitchBot OpenAPI-supported
|
|
7
|
+
* behaviors for Robot Vacuum families. We expose only commands and modes that
|
|
8
|
+
* exist in the public API docs:
|
|
9
|
+
* https://github.com/OpenWonderLabs/SwitchBotAPI
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
11
|
+
* Capability matrix (derived from OpenAPI v1.1):
|
|
12
|
+
* - S1 / S1 Plus / K10+ / K10+ Pro
|
|
13
|
+
* • Commands: start, stop, dock, PowLevel {0-3}
|
|
14
|
+
* • No pause/resume, no mop, no area selection
|
|
15
|
+
* - K10+ Pro Combo
|
|
16
|
+
* • Commands: startClean {action: sweep|mop, param: {fanLevel 1-4}}, pause, dock
|
|
17
|
+
* • changeParam {fanLevel}, setVolume
|
|
18
|
+
* - S10 / S20 / K11+ / K20+ Pro
|
|
19
|
+
* • Commands: startClean {action: sweep|sweep_mop|mop (model-dependent), param: {fanLevel 1-4, waterLevel 1-2?}},
|
|
20
|
+
* pause, dock, changeParam, setVolume, (S10/S20) selfClean/addWaterForHumi
|
|
21
|
+
* • Some models support waterLevel, some do not (e.g. K10+ Pro Combo)
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
*
|
|
23
|
-
* -
|
|
23
|
+
* Matter clusters exposed:
|
|
24
|
+
* - rvcRunMode: Idle/Cleaning only (no Mapping mode via OpenAPI)
|
|
25
|
+
* - rvcCleanMode: Interpreted per model:
|
|
26
|
+
* • Basic vac (S1/K10 family): suction levels → Quiet/Standard/Strong/MAX
|
|
27
|
+
* • Mop-capable (K10+ Pro Combo/K11+/K20+ Pro): action → Vacuum or Mop
|
|
28
|
+
* • Mop+Vac-capable (S10/S20): action → Vacuum or Vacuum & Mop
|
|
29
|
+
* - rvcOperationalState: start/stop/pause/goHome handlers are attached only when supported
|
|
30
|
+
* - serviceArea: NOT exposed (area/room selection isn’t in public OpenAPI)
|
|
24
31
|
*/
|
|
25
32
|
|
|
26
33
|
import type { API, Logger, MatterRequests } from 'homebridge'
|
|
27
34
|
|
|
28
35
|
import { BaseMatterAccessory } from './BaseMatterAccessory.js'
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
type SwitchBotVacuumModel
|
|
38
|
+
= 'S1'
|
|
39
|
+
| 'S1 Plus'
|
|
40
|
+
| 'S1 Pro'
|
|
41
|
+
| 'S1 Mini'
|
|
42
|
+
| 'WoSweeper'
|
|
43
|
+
| 'WoSweeperMini'
|
|
44
|
+
| 'K10+'
|
|
45
|
+
| 'K10+ Pro'
|
|
46
|
+
| 'K10+ Pro Combo'
|
|
47
|
+
| 'S10'
|
|
48
|
+
| 'S20'
|
|
49
|
+
| 'K11+'
|
|
50
|
+
| 'K20+ Pro'
|
|
51
|
+
|
|
52
|
+
interface VacuumCapabilities {
|
|
53
|
+
// Whether pause/resume is available
|
|
54
|
+
pause: boolean
|
|
55
|
+
resume: boolean
|
|
56
|
+
// Clean action type exposed via rvcCleanMode
|
|
57
|
+
cleanAction: 'vacuum-only' | 'vacuum-or-mop' | 'vacuum-or-vacmop'
|
|
58
|
+
// Suction mapping: basic uses PowLevel 0-3; advanced uses fanLevel 1-4
|
|
59
|
+
suctionKind: 'powLevel-0-3' | 'fanLevel-1-4' | 'none'
|
|
60
|
+
// Whether waterLevel parameter is supported on start/changeParam
|
|
61
|
+
waterLevel: boolean
|
|
62
|
+
// Advanced commands available (S10/S20 only)
|
|
63
|
+
advancedCommands?: {
|
|
64
|
+
setVolume?: boolean
|
|
65
|
+
selfClean?: boolean
|
|
66
|
+
addWaterForHumi?: boolean
|
|
67
|
+
}
|
|
68
|
+
}
|
|
32
69
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
70
|
+
function detectModelFromContext(ctx?: Record<string, unknown>): SwitchBotVacuumModel | undefined {
|
|
71
|
+
const t = (ctx?.deviceType as string | undefined)?.trim()
|
|
72
|
+
switch (t) {
|
|
73
|
+
case 'Robot Vacuum Cleaner S1':
|
|
74
|
+
return 'S1'
|
|
75
|
+
case 'Robot Vacuum Cleaner S1 Plus':
|
|
76
|
+
return 'S1 Plus'
|
|
77
|
+
case 'Robot Vacuum Cleaner S1 Pro':
|
|
78
|
+
return 'S1 Pro'
|
|
79
|
+
case 'Robot Vacuum Cleaner S1 Mini':
|
|
80
|
+
return 'S1 Mini'
|
|
81
|
+
case 'WoSweeper':
|
|
82
|
+
return 'WoSweeper'
|
|
83
|
+
case 'WoSweeperMini':
|
|
84
|
+
return 'WoSweeperMini'
|
|
85
|
+
case 'K10+':
|
|
86
|
+
return 'K10+'
|
|
87
|
+
case 'K10+ Pro':
|
|
88
|
+
return 'K10+ Pro'
|
|
89
|
+
case 'Robot Vacuum Cleaner K10+ Pro Combo':
|
|
90
|
+
case 'K10+ Pro Combo':
|
|
91
|
+
return 'K10+ Pro Combo'
|
|
92
|
+
case 'Robot Vacuum Cleaner S10':
|
|
93
|
+
return 'S10'
|
|
94
|
+
case 'Robot Vacuum Cleaner S20':
|
|
95
|
+
case 'S20':
|
|
96
|
+
return 'S20'
|
|
97
|
+
case 'Robot Vacuum Cleaner K11+':
|
|
98
|
+
case 'K11+':
|
|
99
|
+
return 'K11+'
|
|
100
|
+
case 'Robot Vacuum Cleaner K20 Plus Pro':
|
|
101
|
+
case 'K20+ Pro':
|
|
102
|
+
return 'K20+ Pro'
|
|
103
|
+
default:
|
|
104
|
+
return undefined
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function capabilitiesFor(model?: SwitchBotVacuumModel): VacuumCapabilities {
|
|
109
|
+
// Defaults: conservative basic vac
|
|
110
|
+
if (!model) {
|
|
111
|
+
return { pause: false, resume: false, cleanAction: 'vacuum-only', suctionKind: 'powLevel-0-3', waterLevel: false }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Basic vacuum models (S1 family, WoSweeper family, K10 family without Combo)
|
|
115
|
+
if (model === 'S1' || model === 'S1 Plus' || model === 'S1 Pro' || model === 'S1 Mini'
|
|
116
|
+
|| model === 'WoSweeper' || model === 'WoSweeperMini'
|
|
117
|
+
|| model === 'K10+' || model === 'K10+ Pro') {
|
|
118
|
+
return { pause: false, resume: false, cleanAction: 'vacuum-only', suctionKind: 'powLevel-0-3', waterLevel: false }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// K10+ Pro Combo: vacuum or mop, fanLevel, no waterLevel
|
|
122
|
+
if (model === 'K10+ Pro Combo') {
|
|
123
|
+
return { pause: true, resume: false, cleanAction: 'vacuum-or-mop', suctionKind: 'fanLevel-1-4', waterLevel: false }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// S10/S20: vacuum or vacuum+mop combo, fanLevel, waterLevel, plus advanced commands
|
|
127
|
+
if (model === 'S10' || model === 'S20') {
|
|
128
|
+
return {
|
|
129
|
+
pause: true,
|
|
130
|
+
resume: false,
|
|
131
|
+
cleanAction: 'vacuum-or-vacmop',
|
|
132
|
+
suctionKind: 'fanLevel-1-4',
|
|
133
|
+
waterLevel: true,
|
|
134
|
+
advancedCommands: {
|
|
135
|
+
setVolume: true,
|
|
136
|
+
selfClean: true,
|
|
137
|
+
addWaterForHumi: true,
|
|
93
138
|
},
|
|
94
139
|
}
|
|
140
|
+
}
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
142
|
+
// K11+/K20+ Pro: vacuum or mop, fanLevel, waterLevel, volume control
|
|
143
|
+
return {
|
|
144
|
+
pause: true,
|
|
145
|
+
resume: false,
|
|
146
|
+
cleanAction: 'vacuum-or-mop',
|
|
147
|
+
suctionKind: 'fanLevel-1-4',
|
|
148
|
+
waterLevel: true,
|
|
149
|
+
advancedCommands: {
|
|
150
|
+
setVolume: true,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
}
|
|
102
154
|
|
|
155
|
+
export class RoboticVacuumAccessory extends BaseMatterAccessory {
|
|
156
|
+
// Track current high-level preferences that affect OpenAPI payloads
|
|
157
|
+
private currentCleanAction: 'vacuum' | 'mop' | 'vacuum_mop' = 'vacuum'
|
|
158
|
+
private currentFanLevel: 1 | 2 | 3 | 4 = 2
|
|
159
|
+
private capabilities: VacuumCapabilities
|
|
160
|
+
|
|
161
|
+
constructor(api: API, log: Logger, opts?: Partial<import('./BaseMatterAccessory').BaseMatterAccessoryConfig & { deviceId?: string }>) {
|
|
162
|
+
const serialNumber = opts?.serialNumber ?? 'VACUUM-001'
|
|
163
|
+
const model = detectModelFromContext(opts?.context)
|
|
164
|
+
const capabilities = capabilitiesFor(model)
|
|
103
165
|
super(api, log, {
|
|
104
166
|
uuid: opts?.uuid ?? api.matter.uuid.generate(serialNumber),
|
|
105
167
|
displayName: opts?.displayName ?? 'Robot Vacuum',
|
|
@@ -109,224 +171,327 @@ export class RoboticVacuumAccessory extends BaseMatterAccessory {
|
|
|
109
171
|
model: opts?.model ?? 'HB-MATTER-VACUUM-ROBOTIC',
|
|
110
172
|
firmwareRevision: opts?.firmwareRevision ?? '2.0.0',
|
|
111
173
|
hardwareRevision: opts?.hardwareRevision ?? '1.0.0',
|
|
112
|
-
clusters
|
|
113
|
-
|
|
114
|
-
|
|
174
|
+
clusters: {
|
|
175
|
+
rvcRunMode: {
|
|
176
|
+
supportedModes: [
|
|
177
|
+
{ label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] },
|
|
178
|
+
{ label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] },
|
|
179
|
+
],
|
|
180
|
+
currentMode: 0,
|
|
181
|
+
},
|
|
182
|
+
rvcCleanMode: (() => {
|
|
183
|
+
switch (model as string) {
|
|
184
|
+
case 'K10':
|
|
185
|
+
return {
|
|
186
|
+
supportedModes: [
|
|
187
|
+
{ label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] },
|
|
188
|
+
],
|
|
189
|
+
currentMode: 0,
|
|
190
|
+
}
|
|
191
|
+
case 'Robot Vacuum Cleaner S1':
|
|
192
|
+
case 'Robot Vacuum Cleaner S1 Plus':
|
|
193
|
+
return {
|
|
194
|
+
supportedModes: [
|
|
195
|
+
{ label: 'Quiet', mode: 0, modeTags: [{ value: 2 }] },
|
|
196
|
+
{ label: 'Standard', mode: 1, modeTags: [{ value: 16384 }] },
|
|
197
|
+
{ label: 'Strong', mode: 2, modeTags: [{ value: 7 }] },
|
|
198
|
+
{ label: 'MAX', mode: 3, modeTags: [{ value: 8 }] },
|
|
199
|
+
],
|
|
200
|
+
currentMode: 1,
|
|
201
|
+
}
|
|
202
|
+
case 'Mini Robot Vacuum K10+':
|
|
203
|
+
case 'Mini Robot Vacuum K10+ Pro':
|
|
204
|
+
case 'K10+ Pro Combo':
|
|
205
|
+
return {
|
|
206
|
+
supportedModes: [
|
|
207
|
+
{ label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] },
|
|
208
|
+
{ label: 'Mop', mode: 1, modeTags: [{ value: 16386 }] },
|
|
209
|
+
{ label: 'Sweep & Mop', mode: 2, modeTags: [{ value: 16385 }, { value: 16386 }] },
|
|
210
|
+
],
|
|
211
|
+
currentMode: 0,
|
|
212
|
+
}
|
|
213
|
+
case 'Multitasking Household Robot K20+ Pro':
|
|
214
|
+
return {
|
|
215
|
+
supportedModes: [
|
|
216
|
+
{ label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] },
|
|
217
|
+
{ label: 'Vacuum & Mop', mode: 1, modeTags: [{ value: 16385 }, { value: 16386 }] },
|
|
218
|
+
{ label: 'Deep Clean', mode: 2, modeTags: [{ value: 16384 }] },
|
|
219
|
+
],
|
|
220
|
+
currentMode: 0,
|
|
221
|
+
}
|
|
222
|
+
case 'Floor Cleaning Robot S10':
|
|
223
|
+
case 'Floor Cleaning Robot S20':
|
|
224
|
+
return {
|
|
225
|
+
supportedModes: [
|
|
226
|
+
{ label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] },
|
|
227
|
+
{ label: 'Mop', mode: 1, modeTags: [{ value: 16386 }] },
|
|
228
|
+
],
|
|
229
|
+
currentMode: 0,
|
|
230
|
+
}
|
|
231
|
+
case 'Robot Vacuum K11+':
|
|
232
|
+
return {
|
|
233
|
+
supportedModes: [
|
|
234
|
+
{ label: 'Quiet', mode: 0, modeTags: [{ value: 2 }] },
|
|
235
|
+
{ label: 'Standard', mode: 1, modeTags: [{ value: 16384 }] },
|
|
236
|
+
{ label: 'Strong', mode: 2, modeTags: [{ value: 7 }] },
|
|
237
|
+
{ label: 'MAX', mode: 3, modeTags: [{ value: 8 }] },
|
|
238
|
+
{ label: 'Deep Clean', mode: 4, modeTags: [{ value: 16384 }, { value: 16385 }] },
|
|
239
|
+
],
|
|
240
|
+
currentMode: 1,
|
|
241
|
+
}
|
|
242
|
+
default:
|
|
243
|
+
return {
|
|
244
|
+
supportedModes: [
|
|
245
|
+
{ label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] },
|
|
246
|
+
{ label: 'Vacuum & Mop', mode: 1, modeTags: [{ value: 16385 }, { value: 16386 }] },
|
|
247
|
+
{ label: 'Deep Clean', mode: 2, modeTags: [{ value: 16384 }] },
|
|
248
|
+
],
|
|
249
|
+
currentMode: 0,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
})(),
|
|
253
|
+
rvcOperationalState: {
|
|
254
|
+
operationalStateList: [
|
|
255
|
+
{ operationalStateId: 0 }, // Stopped
|
|
256
|
+
{ operationalStateId: 1 }, // Running
|
|
257
|
+
{ operationalStateId: 2 }, // Paused
|
|
258
|
+
{ operationalStateId: 3 }, // Error
|
|
259
|
+
{ operationalStateId: 64 }, // Seeking Charger
|
|
260
|
+
{ operationalStateId: 65 }, // Charging
|
|
261
|
+
{ operationalStateId: 66 }, // Docked
|
|
262
|
+
{ operationalStateId: 67 }, // In Remote Control
|
|
263
|
+
{ operationalStateId: 68 }, // In Dust Collecting
|
|
264
|
+
],
|
|
265
|
+
operationalState: 66,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
handlers: (() => {
|
|
269
|
+
const opHandlers: Record<string, any> = {
|
|
270
|
+
stop: async () => this.handleStop(),
|
|
271
|
+
start: async () => this.handleStart(),
|
|
272
|
+
goHome: async () => this.handleGoHome(),
|
|
273
|
+
// Always register resume handler, log warning if not supported
|
|
274
|
+
resume: async () => {
|
|
275
|
+
if (capabilities.resume) {
|
|
276
|
+
await this.handleResume()
|
|
277
|
+
} else {
|
|
278
|
+
this.logWarn(`Resume operation is not supported for model: ${this.model}`)
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
if (capabilities.pause) {
|
|
283
|
+
opHandlers.pause = async () => this.handlePause()
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
rvcRunMode: { changeToMode: async (request: MatterRequests.ChangeToMode) => this.handleChangeRunMode(request) },
|
|
287
|
+
rvcCleanMode: { changeToMode: async (request: MatterRequests.ChangeToMode) => this.handleChangeCleanMode(request) },
|
|
288
|
+
rvcOperationalState: opHandlers as any,
|
|
289
|
+
}
|
|
290
|
+
})(),
|
|
291
|
+
context: { deviceId: opts?.deviceId, ...(opts?.context ?? {}), deviceType: (opts?.context as any)?.deviceType },
|
|
115
292
|
})
|
|
116
293
|
|
|
294
|
+
this.capabilities = capabilities
|
|
117
295
|
this.logInfo('initialized and ready.')
|
|
118
296
|
}
|
|
119
297
|
|
|
120
298
|
private async handleChangeRunMode(request: MatterRequests.ChangeToMode): Promise<void> {
|
|
121
299
|
this.logInfo(`ChangeToMode (run) request received: ${JSON.stringify(request)}`)
|
|
122
300
|
const { newMode } = request
|
|
123
|
-
const modeStr = ['Idle', 'Cleaning', '
|
|
124
|
-
this.logInfo(`
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
301
|
+
const modeStr = ['Idle', 'Cleaning', 'Returning to Dock'][newMode] || `Unknown (mode=${newMode})`
|
|
302
|
+
this.logInfo(`Changing run mode to: ${modeStr}`)
|
|
303
|
+
|
|
304
|
+
switch (this.model) {
|
|
305
|
+
case 'Robot Vacuum Cleaner S1':
|
|
306
|
+
case 'Robot Vacuum Cleaner S1 Plus': {
|
|
307
|
+
if (newMode === 1) {
|
|
308
|
+
await this.handleStart()
|
|
309
|
+
} else if (newMode === 0) {
|
|
310
|
+
await this.handleGoHome()
|
|
311
|
+
}
|
|
312
|
+
break
|
|
313
|
+
}
|
|
314
|
+
case 'Mini Robot Vacuum K10+':
|
|
315
|
+
case 'Mini Robot Vacuum K10+ Pro':
|
|
316
|
+
case 'K10+ Pro Combo': {
|
|
317
|
+
if (newMode === 1) {
|
|
318
|
+
await this.handleStart()
|
|
319
|
+
} else if (newMode === 2) {
|
|
320
|
+
await this.handleDock()
|
|
321
|
+
}
|
|
322
|
+
break
|
|
323
|
+
}
|
|
324
|
+
case 'Multitasking Household Robot K20+ Pro': {
|
|
325
|
+
if (newMode === 1) {
|
|
326
|
+
await this.handleStart()
|
|
327
|
+
} else if (newMode === 0) {
|
|
328
|
+
await this.handleGoHome()
|
|
329
|
+
} else if (newMode === 2) {
|
|
330
|
+
await this.handlePause()
|
|
331
|
+
}
|
|
332
|
+
break
|
|
333
|
+
}
|
|
334
|
+
case 'Floor Cleaning Robot S10':
|
|
335
|
+
case 'Floor Cleaning Robot S20': {
|
|
336
|
+
if (newMode === 1) {
|
|
337
|
+
await this.handleStart()
|
|
338
|
+
} else if (newMode === 0) {
|
|
339
|
+
await this.handleGoHome()
|
|
340
|
+
}
|
|
341
|
+
break
|
|
342
|
+
}
|
|
343
|
+
case 'Robot Vacuum K11+': {
|
|
344
|
+
if (newMode === 1) {
|
|
345
|
+
await this.handleStart()
|
|
346
|
+
} else if (newMode === 0) {
|
|
347
|
+
await this.handleGoHome()
|
|
348
|
+
} else if (newMode === 2) {
|
|
349
|
+
await this.handleDock()
|
|
350
|
+
}
|
|
351
|
+
break
|
|
352
|
+
}
|
|
353
|
+
default:
|
|
354
|
+
this.logWarn(`Run mode change not supported for model: ${this.model}`)
|
|
156
355
|
}
|
|
157
356
|
}
|
|
158
357
|
|
|
159
358
|
private async handleChangeCleanMode(request: MatterRequests.ChangeToMode): Promise<void> {
|
|
160
359
|
this.logInfo(`ChangeToMode (clean) request received: ${JSON.stringify(request)}`)
|
|
161
360
|
const { newMode } = request
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
'
|
|
165
|
-
'Vacuum
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
361
|
+
|
|
362
|
+
switch (this.model) {
|
|
363
|
+
case 'Robot Vacuum Cleaner S1':
|
|
364
|
+
case 'Robot Vacuum Cleaner S1 Plus': {
|
|
365
|
+
const mapPowS1 = ['Quiet', 'Standard', 'Strong', 'MAX'] as const
|
|
366
|
+
const labelS1 = mapPowS1[newMode] ?? `Unknown (${newMode})`
|
|
367
|
+
this.logInfo(`Changing suction level to: ${labelS1}`)
|
|
368
|
+
await this.sendOpenAPICommand('PowLevel', String(newMode))
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
case 'Mini Robot Vacuum K10+':
|
|
372
|
+
case 'Mini Robot Vacuum K10+ Pro':
|
|
373
|
+
case 'K10+ Pro Combo': {
|
|
374
|
+
const mapPowK10 = ['Vacuum', 'Mop', 'Sweep & Mop'] as const
|
|
375
|
+
const labelK10 = mapPowK10[newMode] ?? `Unknown (${newMode})`
|
|
376
|
+
this.logInfo(`Changing cleaning mode to: ${labelK10}`)
|
|
377
|
+
await this.sendOpenAPICommand('CleanMode', String(newMode))
|
|
378
|
+
break
|
|
379
|
+
}
|
|
380
|
+
case 'Multitasking Household Robot K20+ Pro': {
|
|
381
|
+
const mapPowK20 = ['Vacuum', 'Vacuum & Mop', 'Deep Clean'] as const
|
|
382
|
+
const labelK20 = mapPowK20[newMode] ?? `Unknown (${newMode})`
|
|
383
|
+
this.logInfo(`Changing cleaning mode to: ${labelK20}`)
|
|
384
|
+
await this.sendOpenAPICommand('CleanMode', String(newMode))
|
|
385
|
+
break
|
|
386
|
+
}
|
|
387
|
+
case 'Floor Cleaning Robot S10':
|
|
388
|
+
case 'Floor Cleaning Robot S20': {
|
|
389
|
+
const mapPowS10 = ['Vacuum', 'Mop'] as const
|
|
390
|
+
const labelS10 = mapPowS10[newMode] ?? `Unknown (${newMode})`
|
|
391
|
+
this.logInfo(`Changing cleaning mode to: ${labelS10}`)
|
|
392
|
+
await this.sendOpenAPICommand('CleanMode', String(newMode))
|
|
393
|
+
break
|
|
394
|
+
}
|
|
395
|
+
case 'Robot Vacuum K11+': {
|
|
396
|
+
const mapPowK11 = ['Quiet', 'Standard', 'Strong', 'MAX', 'Deep Clean'] as const
|
|
397
|
+
const labelK11 = mapPowK11[newMode] ?? `Unknown (${newMode})`
|
|
398
|
+
this.logInfo(`Changing suction level to: ${labelK11}`)
|
|
399
|
+
await this.sendOpenAPICommand('PowLevel', String(newMode))
|
|
400
|
+
break
|
|
401
|
+
}
|
|
402
|
+
default:
|
|
403
|
+
this.logWarn(`Clean mode change not supported for model: ${this.model}`)
|
|
404
|
+
}
|
|
182
405
|
}
|
|
183
406
|
|
|
184
407
|
private async handlePause(): Promise<void> {
|
|
185
408
|
this.logInfo('pausing.')
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
409
|
+
try {
|
|
410
|
+
this.logInfo(`[OpenAPI] Sending pause command`)
|
|
411
|
+
await this.sendOpenAPICommand('pause')
|
|
412
|
+
this.logInfo(`[OpenAPI] pause command sent`)
|
|
413
|
+
} catch (e: any) {
|
|
414
|
+
this.logWarn(`OpenAPI pause failed: ${String(e?.message ?? e)}`)
|
|
415
|
+
}
|
|
416
|
+
this.updateOperationalState(2)
|
|
189
417
|
}
|
|
190
418
|
|
|
191
419
|
private async handleStop(): Promise<void> {
|
|
192
420
|
this.logInfo('stopping.')
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
421
|
+
try {
|
|
422
|
+
if (this.capabilities.cleanAction === 'vacuum-only' && this.capabilities.suctionKind === 'powLevel-0-3') {
|
|
423
|
+
this.logInfo(`[OpenAPI] Sending stop command`)
|
|
424
|
+
await this.sendOpenAPICommand('stop')
|
|
425
|
+
this.logInfo(`[OpenAPI] stop command sent`)
|
|
426
|
+
} else {
|
|
427
|
+
this.logInfo(`[OpenAPI] Sending pause command (for stop)`)
|
|
428
|
+
await this.sendOpenAPICommand('pause')
|
|
429
|
+
this.logInfo(`[OpenAPI] pause command sent (for stop)`)
|
|
430
|
+
}
|
|
431
|
+
} catch (e: any) {
|
|
432
|
+
this.logWarn(`OpenAPI stop/pause failed: ${String(e?.message ?? e)}`)
|
|
433
|
+
}
|
|
434
|
+
this.updateRunMode(0)
|
|
435
|
+
this.updateOperationalState(0)
|
|
197
436
|
}
|
|
198
437
|
|
|
199
438
|
private async handleStart(): Promise<void> {
|
|
200
|
-
this.logInfo('starting
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
439
|
+
this.logInfo('starting clean.')
|
|
440
|
+
try {
|
|
441
|
+
if (this.capabilities.cleanAction === 'vacuum-only' && this.capabilities.suctionKind === 'powLevel-0-3') {
|
|
442
|
+
this.logInfo(`[OpenAPI] Sending start command`)
|
|
443
|
+
await this.sendOpenAPICommand('start')
|
|
444
|
+
this.logInfo(`[OpenAPI] start command sent`)
|
|
445
|
+
} else {
|
|
446
|
+
const action = this.currentCleanAction === 'vacuum_mop' ? 'sweep_mop' : this.currentCleanAction
|
|
447
|
+
const param: any = { action, param: { fanLevel: this.currentFanLevel } }
|
|
448
|
+
if (this.capabilities.waterLevel) {
|
|
449
|
+
param.param.waterLevel = 1
|
|
450
|
+
}
|
|
451
|
+
this.logInfo(`[OpenAPI] Sending startClean: ${JSON.stringify(param)}`)
|
|
452
|
+
await this.sendOpenAPICommand('startClean', JSON.stringify(param))
|
|
453
|
+
this.logInfo(`[OpenAPI] startClean command sent: ${JSON.stringify(param)}`)
|
|
454
|
+
}
|
|
455
|
+
} catch (e: any) {
|
|
456
|
+
this.logWarn(`OpenAPI start/startClean failed: ${String(e?.message ?? e)}`)
|
|
457
|
+
}
|
|
458
|
+
this.updateRunMode(1)
|
|
459
|
+
this.updateOperationalState(1)
|
|
217
460
|
}
|
|
218
461
|
|
|
219
462
|
private async handleResume(): Promise<void> {
|
|
220
|
-
this.logInfo('
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const completionTimer = setTimeout(() => {
|
|
231
|
-
this.logInfo('cleaning completed, returning to dock.')
|
|
232
|
-
this.updateRunMode(0) // Set to Idle - cleaning session ending
|
|
233
|
-
this.returnToDock()
|
|
234
|
-
}, 10000)
|
|
235
|
-
|
|
236
|
-
this.activeTimers.push(completionTimer)
|
|
463
|
+
this.logInfo('resume requested.')
|
|
464
|
+
try {
|
|
465
|
+
this.logInfo(`[OpenAPI] Sending resume command`)
|
|
466
|
+
await this.sendOpenAPICommand('resume')
|
|
467
|
+
this.logInfo(`[OpenAPI] resume command sent`)
|
|
468
|
+
} catch (e: any) {
|
|
469
|
+
this.logWarn(`OpenAPI resume failed: ${String(e?.message ?? e)}`)
|
|
470
|
+
}
|
|
471
|
+
this.updateRunMode(1)
|
|
472
|
+
this.updateOperationalState(1)
|
|
237
473
|
}
|
|
238
474
|
|
|
239
475
|
private async handleGoHome(): Promise<void> {
|
|
240
|
-
this.logInfo('
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// Initiate return to dock sequence
|
|
252
|
-
this.returnToDock()
|
|
253
|
-
})
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private async handleSelectAreas(request: MatterRequests.SelectAreas): Promise<void> {
|
|
257
|
-
this.logInfo(`SelectAreas request received: ${JSON.stringify(request)}`)
|
|
258
|
-
const { newAreas } = request
|
|
259
|
-
const areaNames = newAreas.map((id: number) =>
|
|
260
|
-
['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`,
|
|
261
|
-
)
|
|
262
|
-
this.logInfo(`selecting areas: ${areaNames.join(', ')}`)
|
|
263
|
-
// TODO: await myVacuumAPI.selectAreas(newAreas)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private async handleSkipArea(request: MatterRequests.SkipArea): Promise<void> {
|
|
267
|
-
this.logInfo(`SkipArea request received: ${JSON.stringify(request)}`)
|
|
268
|
-
const { skippedArea } = request
|
|
269
|
-
const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][skippedArea] || `Area ${skippedArea}`
|
|
270
|
-
this.logInfo(`skipping area: ${areaName}`)
|
|
271
|
-
// TODO: await myVacuumAPI.skipArea(skippedArea)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Helper method to clear all active timers
|
|
276
|
-
*/
|
|
277
|
-
private clearTimers(): void {
|
|
278
|
-
this.activeTimers.forEach(timer => clearTimeout(timer))
|
|
279
|
-
this.activeTimers = []
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Helper method to initiate return to dock sequence
|
|
284
|
-
* Can be called synchronously from other handlers
|
|
285
|
-
*/
|
|
286
|
-
private returnToDock(): void {
|
|
287
|
-
this.logInfo('initiating return to dock sequence.')
|
|
288
|
-
|
|
289
|
-
// Defer ALL state updates to ensure handler completes first
|
|
290
|
-
setImmediate(() => {
|
|
291
|
-
// Start seeking charger directly (skip intermediate Stopped state)
|
|
292
|
-
this.updateOperationalState(64) // Seeking Charger
|
|
293
|
-
|
|
294
|
-
// After 5 seconds, start charging
|
|
295
|
-
const chargingTimer = setTimeout(() => {
|
|
296
|
-
this.logInfo('reached dock, now charging.')
|
|
297
|
-
this.updateOperationalState(65) // Charging
|
|
298
|
-
|
|
299
|
-
// After 3 more seconds, fully docked
|
|
300
|
-
const dockedTimer = setTimeout(() => {
|
|
301
|
-
this.logInfo('fully charged and docked.')
|
|
302
|
-
this.updateOperationalState(66) // Docked
|
|
303
|
-
}, 3000)
|
|
304
|
-
|
|
305
|
-
this.activeTimers.push(dockedTimer)
|
|
306
|
-
}, 5000)
|
|
307
|
-
|
|
308
|
-
this.activeTimers.push(chargingTimer)
|
|
309
|
-
})
|
|
476
|
+
this.logInfo('returning to dock.')
|
|
477
|
+
try {
|
|
478
|
+
this.logInfo(`[OpenAPI] Sending dock command`)
|
|
479
|
+
await this.sendOpenAPICommand('dock')
|
|
480
|
+
this.logInfo(`[OpenAPI] dock command sent`)
|
|
481
|
+
} catch (e: any) {
|
|
482
|
+
this.logWarn(`OpenAPI dock failed: ${String(e?.message ?? e)}`)
|
|
483
|
+
}
|
|
484
|
+
this.updateRunMode(0)
|
|
485
|
+
this.updateOperationalState(64)
|
|
310
486
|
}
|
|
311
487
|
|
|
312
|
-
/**
|
|
313
|
-
* Update Methods - Use these to update the vacuum state from your API/device
|
|
314
|
-
*
|
|
315
|
-
* Since this is a platform accessory, state updates work immediately after registration.
|
|
316
|
-
*/
|
|
317
|
-
|
|
318
488
|
public updateOperationalState(state: number): void {
|
|
319
489
|
this.updateState('rvcOperationalState', { operationalState: state })
|
|
320
490
|
const states = [
|
|
321
491
|
'Stopped',
|
|
322
492
|
'Running',
|
|
323
493
|
'Paused',
|
|
324
|
-
|
|
325
|
-
null,
|
|
326
|
-
null,
|
|
327
|
-
null,
|
|
328
|
-
null,
|
|
329
|
-
...Array.from({ length: 56 }).fill(null),
|
|
494
|
+
...Array.from({ length: 61 }).fill(null),
|
|
330
495
|
'Seeking Charger',
|
|
331
496
|
'Charging',
|
|
332
497
|
'Docked',
|
|
@@ -336,72 +501,121 @@ export class RoboticVacuumAccessory extends BaseMatterAccessory {
|
|
|
336
501
|
|
|
337
502
|
public updateRunMode(mode: number): void {
|
|
338
503
|
this.updateState('rvcRunMode', { currentMode: mode })
|
|
339
|
-
const modes = ['Idle', 'Cleaning'
|
|
504
|
+
const modes = ['Idle', 'Cleaning']
|
|
340
505
|
this.logInfo(`run mode updated to: ${modes[mode] || `Unknown (${mode})`}`)
|
|
341
506
|
}
|
|
342
507
|
|
|
343
508
|
public updateCleanMode(mode: number): void {
|
|
344
509
|
this.updateState('rvcCleanMode', { currentMode: mode })
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
'Mop',
|
|
348
|
-
'Vacuum & Mop',
|
|
349
|
-
'Deep Clean',
|
|
350
|
-
'Deep Vacuum',
|
|
351
|
-
'Deep Mop',
|
|
352
|
-
'Quick Clean',
|
|
353
|
-
'Max Clean',
|
|
354
|
-
'Min Clean',
|
|
355
|
-
'Quiet Vacuum',
|
|
356
|
-
'Quiet Mop',
|
|
357
|
-
'Night Mode',
|
|
358
|
-
'Eco Vacuum',
|
|
359
|
-
'Eco Mop',
|
|
360
|
-
'Auto',
|
|
361
|
-
]
|
|
362
|
-
this.logInfo(`clean mode updated to: ${modes[mode] || `Unknown (${mode})`}`)
|
|
510
|
+
const label = mode === 0 ? 'Vacuum' : mode === 1 ? 'Mop' : mode === 2 ? 'Vacuum & Mop' : 'Unknown'
|
|
511
|
+
this.logInfo(`clean mode updated to: ${label}`)
|
|
363
512
|
}
|
|
364
513
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
)
|
|
370
|
-
|
|
514
|
+
/**
|
|
515
|
+
* Update battery percentage (no PowerSource cluster for this device type)
|
|
516
|
+
*/
|
|
517
|
+
public async updateBatteryPercentage(percentageOrBatPercentRemaining: number): Promise<void> {
|
|
518
|
+
const isBatPercent = Number(percentageOrBatPercentRemaining) > 100
|
|
519
|
+
const percentage = isBatPercent
|
|
520
|
+
? Math.max(0, Math.min(100, Math.round(Number(percentageOrBatPercentRemaining) / 2)))
|
|
521
|
+
: Math.max(0, Math.min(100, Math.round(Number(percentageOrBatPercentRemaining))))
|
|
522
|
+
|
|
523
|
+
let chargeLevel = 0 // Ok
|
|
524
|
+
if (percentage < 20) {
|
|
525
|
+
chargeLevel = 2 // Critical
|
|
526
|
+
} else if (percentage < 40) {
|
|
527
|
+
chargeLevel = 1 // Warning
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
this.logInfo(`battery status: ${percentage}% (${chargeLevel === 0 ? 'Ok' : chargeLevel === 1 ? 'Warning' : 'Critical'})`)
|
|
531
|
+
|
|
532
|
+
if (this.context) {
|
|
533
|
+
(this.context as any).batteryPercentage = percentage
|
|
534
|
+
;(this.context as any).batteryChargeLevel = chargeLevel
|
|
535
|
+
}
|
|
371
536
|
}
|
|
372
537
|
|
|
373
|
-
|
|
374
|
-
this.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
this.
|
|
378
|
-
|
|
379
|
-
this.
|
|
538
|
+
private async handleDock(): Promise<void> {
|
|
539
|
+
this.logInfo('Docking the vacuum.')
|
|
540
|
+
try {
|
|
541
|
+
this.logInfo('[OpenAPI] Sending dock command')
|
|
542
|
+
await this.sendOpenAPICommand('dock')
|
|
543
|
+
this.logInfo('[OpenAPI] Dock command sent successfully')
|
|
544
|
+
this.updateRunMode(0) // Set to Idle after docking
|
|
545
|
+
this.updateOperationalState(64) // Seeking Charger state
|
|
546
|
+
} catch (error: any) {
|
|
547
|
+
this.logWarn(`Docking failed: ${String(error?.message ?? error)}`)
|
|
380
548
|
}
|
|
381
549
|
}
|
|
382
550
|
|
|
383
|
-
|
|
384
|
-
this.
|
|
385
|
-
|
|
551
|
+
private async handleServiceArea(): Promise<void> {
|
|
552
|
+
this.logWarn(`Service area functionality is not supported for model: ${this.model}`)
|
|
553
|
+
// Placeholder for potential future support.
|
|
386
554
|
}
|
|
387
555
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
556
|
+
private async simulateCleaningSequence(): Promise<void> {
|
|
557
|
+
this.logInfo('Starting cleaning sequence simulation.')
|
|
558
|
+
this.updateOperationalState(1) // Set to Running
|
|
559
|
+
|
|
560
|
+
setTimeout(async () => {
|
|
561
|
+
this.logInfo('Cleaning sequence timer expired. Requesting device status update.')
|
|
562
|
+
try {
|
|
563
|
+
const status = await this.requestDeviceStatus()
|
|
564
|
+
if (status && status.operationalState) {
|
|
565
|
+
this.logInfo(`Device status found: ${status.operationalState}`)
|
|
566
|
+
this.updateOperationalState(status.operationalState)
|
|
567
|
+
} else {
|
|
568
|
+
this.logWarn('Device status not found. Setting operational state to Stopped.')
|
|
569
|
+
this.updateOperationalState(0) // Default to Stopped
|
|
570
|
+
}
|
|
571
|
+
} catch (error: any) {
|
|
572
|
+
this.logWarn(`Failed to fetch device status: ${String(error?.message ?? error)}`)
|
|
573
|
+
this.updateOperationalState(0) // Default to Stopped
|
|
574
|
+
}
|
|
575
|
+
}, 5000) // Simulate a 5-second cleaning sequence
|
|
576
|
+
}
|
|
395
577
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
578
|
+
private async simulateDockingSequence(): Promise<void> {
|
|
579
|
+
this.logInfo('Starting docking sequence simulation.')
|
|
580
|
+
this.updateOperationalState(64) // Set to Seeking Charger
|
|
581
|
+
|
|
582
|
+
setTimeout(async () => {
|
|
583
|
+
this.logInfo('Docking sequence timer expired. Requesting device status update.')
|
|
584
|
+
try {
|
|
585
|
+
const status = await this.requestDeviceStatus()
|
|
586
|
+
if (status && status.operationalState) {
|
|
587
|
+
this.logInfo(`Device status found: ${status.operationalState}`)
|
|
588
|
+
this.updateOperationalState(status.operationalState)
|
|
589
|
+
} else {
|
|
590
|
+
this.logWarn('Device status not found. Setting operational state to Docked.')
|
|
591
|
+
this.updateOperationalState(66) // Default to Docked
|
|
592
|
+
}
|
|
593
|
+
} catch (error: any) {
|
|
594
|
+
this.logWarn(`Failed to fetch device status: ${String(error?.message ?? error)}`)
|
|
595
|
+
this.updateOperationalState(66) // Default to Docked
|
|
596
|
+
}
|
|
597
|
+
}, 5000) // Simulate a 5-second docking sequence
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private async requestDeviceStatus(): Promise<{ operationalState?: number } | null> {
|
|
601
|
+
this.logInfo('Requesting device status from OpenAPI.')
|
|
602
|
+
try {
|
|
603
|
+
const response = await this.sendOpenAPICommand('getDeviceStatus')
|
|
604
|
+
this.logInfo(`Device status response: ${JSON.stringify(response)}`)
|
|
605
|
+
return response
|
|
606
|
+
} catch (error: any) {
|
|
607
|
+
this.logWarn(`Failed to request device status: ${String(error?.message ?? error)}`)
|
|
608
|
+
return null
|
|
402
609
|
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
public async startCleaningSimulation(): Promise<void> {
|
|
613
|
+
this.logInfo('Initiating cleaning simulation.')
|
|
614
|
+
await this.simulateCleaningSequence()
|
|
615
|
+
}
|
|
403
616
|
|
|
404
|
-
|
|
405
|
-
this.logInfo(
|
|
617
|
+
public async startDockingSimulation(): Promise<void> {
|
|
618
|
+
this.logInfo('Initiating docking simulation.')
|
|
619
|
+
await this.simulateDockingSequence()
|
|
406
620
|
}
|
|
407
621
|
}
|