@homebridge-plugins/homebridge-matter 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +67 -33
- package/config.schema.json +222 -2
- package/dist/devices/index.d.ts +13 -0
- package/dist/devices/index.js +22 -0
- package/dist/devices/index.js.map +1 -0
- package/dist/devices/section-12-robotic/index.d.ts +6 -0
- package/dist/devices/section-12-robotic/index.js +7 -0
- package/dist/devices/section-12-robotic/index.js.map +1 -0
- package/dist/devices/section-12-robotic/robotic-vacuum-cleaner.d.ts +63 -0
- package/dist/devices/section-12-robotic/robotic-vacuum-cleaner.js +318 -0
- package/dist/devices/section-12-robotic/robotic-vacuum-cleaner.js.map +1 -0
- package/dist/devices/section-4-lighting/color-temperature-light.d.ts +7 -0
- package/dist/devices/section-4-lighting/color-temperature-light.js +62 -0
- package/dist/devices/section-4-lighting/color-temperature-light.js.map +1 -0
- package/dist/devices/section-4-lighting/dimmable-light.d.ts +7 -0
- package/dist/devices/section-4-lighting/dimmable-light.js +48 -0
- package/dist/devices/section-4-lighting/dimmable-light.js.map +1 -0
- package/dist/devices/section-4-lighting/extended-color-light.d.ts +12 -0
- package/dist/devices/section-4-lighting/extended-color-light.js +142 -0
- package/dist/devices/section-4-lighting/extended-color-light.js.map +1 -0
- package/dist/devices/section-4-lighting/index.d.ts +9 -0
- package/dist/devices/section-4-lighting/index.js +10 -0
- package/dist/devices/section-4-lighting/index.js.map +1 -0
- package/dist/devices/section-4-lighting/on-off-light.d.ts +7 -0
- package/dist/devices/section-4-lighting/on-off-light.js +37 -0
- package/dist/devices/section-4-lighting/on-off-light.js.map +1 -0
- package/dist/devices/section-5-smart-plugs/dimmable-plug-in-unit.d.ts +7 -0
- package/dist/devices/section-5-smart-plugs/dimmable-plug-in-unit.js +48 -0
- package/dist/devices/section-5-smart-plugs/dimmable-plug-in-unit.js.map +1 -0
- package/dist/devices/section-5-smart-plugs/index.d.ts +7 -0
- package/dist/devices/section-5-smart-plugs/index.js +8 -0
- package/dist/devices/section-5-smart-plugs/index.js.map +1 -0
- package/dist/devices/section-5-smart-plugs/on-off-plug-in-unit.d.ts +7 -0
- package/dist/devices/section-5-smart-plugs/on-off-plug-in-unit.js +37 -0
- package/dist/devices/section-5-smart-plugs/on-off-plug-in-unit.js.map +1 -0
- package/dist/devices/section-6-switches/index.d.ts +6 -0
- package/dist/devices/section-6-switches/index.js +7 -0
- package/dist/devices/section-6-switches/index.js.map +1 -0
- package/dist/devices/section-6-switches/on-off-light-switch.d.ts +7 -0
- package/dist/devices/section-6-switches/on-off-light-switch.js +30 -0
- package/dist/devices/section-6-switches/on-off-light-switch.js.map +1 -0
- package/dist/devices/section-7-sensors/contact-sensor.d.ts +7 -0
- package/dist/devices/section-7-sensors/contact-sensor.js +27 -0
- package/dist/devices/section-7-sensors/contact-sensor.js.map +1 -0
- package/dist/devices/section-7-sensors/humidity-sensor.d.ts +7 -0
- package/dist/devices/section-7-sensors/humidity-sensor.js +29 -0
- package/dist/devices/section-7-sensors/humidity-sensor.js.map +1 -0
- package/dist/devices/section-7-sensors/index.d.ts +12 -0
- package/dist/devices/section-7-sensors/index.js +13 -0
- package/dist/devices/section-7-sensors/index.js.map +1 -0
- package/dist/devices/section-7-sensors/light-sensor.d.ts +7 -0
- package/dist/devices/section-7-sensors/light-sensor.js +29 -0
- package/dist/devices/section-7-sensors/light-sensor.js.map +1 -0
- package/dist/devices/section-7-sensors/occupancy-sensor.d.ts +8 -0
- package/dist/devices/section-7-sensors/occupancy-sensor.js +33 -0
- package/dist/devices/section-7-sensors/occupancy-sensor.js.map +1 -0
- package/dist/devices/section-7-sensors/smoke-co-alarm.d.ts +7 -0
- package/dist/devices/section-7-sensors/smoke-co-alarm.js +37 -0
- package/dist/devices/section-7-sensors/smoke-co-alarm.js.map +1 -0
- package/dist/devices/section-7-sensors/temperature-sensor.d.ts +7 -0
- package/dist/devices/section-7-sensors/temperature-sensor.js +29 -0
- package/dist/devices/section-7-sensors/temperature-sensor.js.map +1 -0
- package/dist/devices/section-7-sensors/water-leak-detector.d.ts +7 -0
- package/dist/devices/section-7-sensors/water-leak-detector.js +27 -0
- package/dist/devices/section-7-sensors/water-leak-detector.js.map +1 -0
- package/dist/devices/section-8-closure/door-lock.d.ts +7 -0
- package/dist/devices/section-8-closure/door-lock.js +48 -0
- package/dist/devices/section-8-closure/door-lock.js.map +1 -0
- package/dist/devices/section-8-closure/index.d.ts +7 -0
- package/dist/devices/section-8-closure/index.js +8 -0
- package/dist/devices/section-8-closure/index.js.map +1 -0
- package/dist/devices/section-8-closure/window-covering.d.ts +9 -0
- package/dist/devices/section-8-closure/window-covering.js +154 -0
- package/dist/devices/section-8-closure/window-covering.js.map +1 -0
- package/dist/devices/section-9-hvac/fan.d.ts +7 -0
- package/dist/devices/section-9-hvac/fan.js +56 -0
- package/dist/devices/section-9-hvac/fan.js.map +1 -0
- package/dist/devices/section-9-hvac/index.d.ts +7 -0
- package/dist/devices/section-9-hvac/index.js +8 -0
- package/dist/devices/section-9-hvac/index.js.map +1 -0
- package/dist/devices/section-9-hvac/thermostat.d.ts +7 -0
- package/dist/devices/section-9-hvac/thermostat.js +61 -0
- package/dist/devices/section-9-hvac/thermostat.js.map +1 -0
- package/dist/devices/types.d.ts +16 -0
- package/dist/devices/types.js +5 -0
- package/dist/devices/types.js.map +1 -0
- package/dist/homebridge-ui/public/index.html +269 -0
- package/dist/homebridge-ui/server.js +47 -0
- package/dist/platform.d.ts +28 -20
- package/dist/platform.js +187 -973
- package/dist/platform.js.map +1 -1
- package/package.json +9 -9
- package/plugin-header.png +0 -0
- package/.claude/settings.local.json +0 -28
package/dist/platform.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { registerColorTemperatureLight, registerContactSensor, registerDimmableLight, registerDimmablePlugInUnit, registerDoorLock, registerExtendedColorLight, registerFan, registerHumiditySensor, registerLightSensor, registerOccupancySensor, registerOnOffLight, registerOnOffLightSwitch, registerOnOffPlugInUnit, registerRoboticVacuumCleaner, registerSmokeCoAlarm, registerTemperatureSensor, registerThermostat, registerWaterLeakDetector, registerWindowCovering, } from './devices/index.js';
|
|
1
2
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
2
3
|
/**
|
|
3
4
|
* MatterPlatform
|
|
4
5
|
* Demonstrates all available Matter device types in Homebridge
|
|
6
|
+
*
|
|
7
|
+
* Organized by official Matter Specification v1.4.1 categories
|
|
5
8
|
*/
|
|
6
9
|
export class ExampleHomebridgePlatform {
|
|
7
10
|
log;
|
|
8
11
|
config;
|
|
9
12
|
api;
|
|
10
13
|
// Track restored HAP cached accessories (required for DynamicPlatformPlugin)
|
|
11
|
-
|
|
14
|
+
// This is commented out here as this plugin does not have any HAP accessories
|
|
15
|
+
// public readonly accessories: Map<string, PlatformAccessory> = new Map()
|
|
12
16
|
// Track restored Matter cached accessories
|
|
13
17
|
matterAccessories = new Map();
|
|
14
18
|
constructor(log, config, api) {
|
|
@@ -16,7 +20,15 @@ export class ExampleHomebridgePlatform {
|
|
|
16
20
|
this.config = config;
|
|
17
21
|
this.api = api;
|
|
18
22
|
this.log.debug('Finished initializing platform:', this.config.name);
|
|
19
|
-
//
|
|
23
|
+
// Does the user have a version of Homebridge that is compatible with matter?
|
|
24
|
+
if (!this.api.isMatterAvailable?.()) {
|
|
25
|
+
this.log.warn('Matter is not available in this version of Homebridge. Please update Homebridge to use this plugin.');
|
|
26
|
+
}
|
|
27
|
+
// Check if the user has matter enabled, this means:
|
|
28
|
+
// - If the plugin is running on the main bridge, then the user must have enabled matter in the Homebridge settings page in the UI
|
|
29
|
+
// - If the plugin is running on a child bridge, then the user must have enabled matter on the plugin bridge settings section in the UI
|
|
30
|
+
// In reality, only the below check is needed, but they are both included here for completeness
|
|
31
|
+
// Remember to use a '?.' optional chaining operator in case the user is running an older version of Homebridge that does not have these APIs
|
|
20
32
|
if (!this.api.isMatterEnabled?.()) {
|
|
21
33
|
this.log.warn('Matter is not enabled in Homebridge. Please enable Matter in the Homebridge settings to use this plugin.');
|
|
22
34
|
return;
|
|
@@ -31,1025 +43,227 @@ export class ExampleHomebridgePlatform {
|
|
|
31
43
|
* Required for DynamicPlatformPlugin
|
|
32
44
|
* Called when homebridge restores cached accessories from disk at startup
|
|
33
45
|
*/
|
|
34
|
-
configureAccessory(accessory) {
|
|
46
|
+
configureAccessory( /* accessory: PlatformAccessory */) {
|
|
35
47
|
// Note this is not used for Matter accessories - use configureMatterAccessory instead
|
|
36
|
-
|
|
48
|
+
// This plugin does not have any hap accessories, so here we can comment this out
|
|
49
|
+
// this.accessories.set(accessory.UUID, accessory)
|
|
37
50
|
}
|
|
38
51
|
/**
|
|
39
|
-
* Called
|
|
40
|
-
* Track these so we can determine which accessories to remove in didFinishLaunching
|
|
52
|
+
* Called when homebridge restores cached Matter accessories from disk at startup
|
|
41
53
|
*/
|
|
42
54
|
configureMatterAccessory(accessory) {
|
|
43
|
-
this.log.
|
|
44
|
-
// Track cached Matter accessories (in real plugin, compare with cloud devices and remove orphans)
|
|
55
|
+
this.log.debug('Loading cached Matter accessory:', accessory.displayName);
|
|
45
56
|
this.matterAccessories.set(accessory.uuid, accessory);
|
|
46
57
|
}
|
|
47
58
|
/**
|
|
48
|
-
* Register all Matter
|
|
59
|
+
* Register all Matter accessories
|
|
49
60
|
*/
|
|
50
61
|
registerMatterAccessories() {
|
|
51
|
-
this.log.info('
|
|
52
|
-
this.log.info('
|
|
53
|
-
this.log.info('
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
// Register
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
62
|
-
this.
|
|
63
|
-
this.
|
|
64
|
-
|
|
62
|
+
this.log.info('═'.repeat(80));
|
|
63
|
+
this.log.info('Homebridge Matter Plugin');
|
|
64
|
+
this.log.info('═'.repeat(80));
|
|
65
|
+
// Remove accessories that are disabled in config
|
|
66
|
+
this.removeDisabledAccessories();
|
|
67
|
+
// Register devices by Matter specification sections
|
|
68
|
+
this.registerSection4Lighting();
|
|
69
|
+
this.registerSection5SmartPlugs();
|
|
70
|
+
this.registerSection6Switches();
|
|
71
|
+
this.registerSection7Sensors();
|
|
72
|
+
this.registerSection8Closure();
|
|
73
|
+
this.registerSection9HVAC();
|
|
74
|
+
this.registerSection12Robotic();
|
|
75
|
+
this.log.info('═'.repeat(80));
|
|
65
76
|
this.log.info('Finished registering Matter accessories');
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.
|
|
77
|
+
this.log.info('═'.repeat(80));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Remove accessories that are disabled in config
|
|
81
|
+
*/
|
|
82
|
+
removeDisabledAccessories() {
|
|
83
|
+
const configMap = [
|
|
84
|
+
{ enabled: this.config.enableOnOffLight, uuid: this.api.matter.uuid.generate('matter-onoff-light'), name: 'On/Off Light' },
|
|
85
|
+
{ enabled: this.config.enableDimmableLight, uuid: this.api.matter.uuid.generate('matter-dimmable-light'), name: 'Dimmable Light' },
|
|
86
|
+
{ enabled: this.config.enableColourTemperatureLight, uuid: this.api.matter.uuid.generate('matter-colour-temp-light'), name: 'Colour Temperature Light' },
|
|
87
|
+
{ enabled: this.config.enableColourLight, uuid: this.api.matter.uuid.generate('matter-colour-light'), name: 'Colour Light (HS)' },
|
|
88
|
+
{ enabled: this.config.enableExtendedColourLight, uuid: this.api.matter.uuid.generate('matter-extended-colour-light'), name: 'Extended Colour Light' },
|
|
89
|
+
{ enabled: this.config.enableOnOffOutlet, uuid: this.api.matter.uuid.generate('matter-onoff-outlet'), name: 'On/Off Outlet' },
|
|
90
|
+
{ enabled: this.config.enableDimmableOutlet, uuid: this.api.matter.uuid.generate('matter-dimmable-outlet'), name: 'Dimmable Outlet' },
|
|
91
|
+
{ enabled: this.config.enableOnOffSwitch, uuid: this.api.matter.uuid.generate('matter-onoff-switch'), name: 'On/Off Switch' },
|
|
92
|
+
{ enabled: this.config.enableTemperatureSensor, uuid: this.api.hap.uuid.generate('matter-temperature-sensor'), name: 'Temperature Sensor' },
|
|
93
|
+
{ enabled: this.config.enableHumiditySensor, uuid: this.api.hap.uuid.generate('matter-humidity-sensor'), name: 'Humidity Sensor' },
|
|
94
|
+
{ enabled: this.config.enableLightSensor, uuid: this.api.hap.uuid.generate('matter-light-sensor'), name: 'Light Sensor' },
|
|
95
|
+
{ enabled: this.config.enableMotionSensor, uuid: this.api.hap.uuid.generate('matter-motion-sensor'), name: 'Motion Sensor' },
|
|
96
|
+
{ enabled: this.config.enableContactSensor, uuid: this.api.hap.uuid.generate('matter-contact-sensor'), name: 'Contact Sensor' },
|
|
97
|
+
{ enabled: this.config.enableLeakSensor, uuid: this.api.hap.uuid.generate('matter-leak-sensor'), name: 'Leak Sensor' },
|
|
98
|
+
{ enabled: this.config.enableSmokeSensor, uuid: this.api.hap.uuid.generate('matter-smoke-sensor'), name: 'Smoke Sensor' },
|
|
99
|
+
{ enabled: this.config.enableDoorLock, uuid: this.api.matter.uuid.generate('matter-door-lock'), name: 'Door Lock' },
|
|
100
|
+
{ enabled: this.config.enableWindowBlind, uuid: this.api.matter.uuid.generate('matter-window-blind'), name: 'Window Blind' },
|
|
101
|
+
{ enabled: this.config.enableVenetianBlind, uuid: this.api.matter.uuid.generate('matter-venetian-blind'), name: 'Venetian Blind' },
|
|
102
|
+
{ enabled: this.config.enableThermostat, uuid: this.api.matter.uuid.generate('matter-thermostat'), name: 'Thermostat' },
|
|
103
|
+
{ enabled: this.config.enableFan, uuid: this.api.matter.uuid.generate('matter-fan'), name: 'Fan' },
|
|
104
|
+
{ enabled: this.config.enableRobotVacuum, uuid: this.api.matter.uuid.generate('matter-robot-vacuum'), name: 'Robot Vacuum' },
|
|
105
|
+
];
|
|
106
|
+
for (const { enabled, uuid, name } of configMap) {
|
|
107
|
+
if (enabled === false) {
|
|
108
|
+
const existingAccessory = this.matterAccessories.get(uuid);
|
|
109
|
+
if (existingAccessory) {
|
|
110
|
+
this.log.info(`Removing accessory '${name}' (disabled in config)`);
|
|
111
|
+
this.api.matter.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
|
|
112
|
+
this.matterAccessories.delete(uuid);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
74
115
|
}
|
|
75
|
-
// // ═══════════════════════════════════════════════════════════════
|
|
76
|
-
// // PATTERN 2 DEMONSTRATION: Update Dimmable Light WITHOUT handler
|
|
77
|
-
// // ═══════════════════════════════════════════════════════════════
|
|
78
|
-
// // Simulating: Dimmable light state changed externally (like via native app)
|
|
79
|
-
// // This will update the Home app WITHOUT triggering the dimmable light's handler
|
|
80
|
-
// // Note: Homebridge automatically defers the update to avoid transaction conflicts
|
|
81
|
-
// this.log.info('[On/Off Light] → Updating Dimmable Light state using updateMatterAccessoryState (no handler!)')
|
|
82
|
-
//
|
|
83
|
-
// this.api.matter.updateAccessoryState(uuidLightDimmable, this.api.matter.clusterNames.OnOff, {
|
|
84
|
-
// onOff: true,
|
|
85
|
-
// })
|
|
86
116
|
}
|
|
87
117
|
/**
|
|
88
|
-
* Lighting Devices
|
|
118
|
+
* Section 4: Lighting Devices (Matter Spec § 4)
|
|
89
119
|
*/
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
// These are called when the user controls the accessory via the Home app
|
|
111
|
-
handlers: {
|
|
112
|
-
onOff: {
|
|
113
|
-
on: async ( /* no args */) => {
|
|
114
|
-
this.log.info('[On/Off Light] ✓ Handler `on` called (user controlled via Home app)');
|
|
115
|
-
},
|
|
116
|
-
off: async ( /* no args */) => {
|
|
117
|
-
this.log.info('[On/Off Light] ✓ Handler `off` called (user controlled via Home app)');
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
// 2. Dimmable Light
|
|
123
|
-
accessories.push({
|
|
124
|
-
uuid: uuidLightDimmable,
|
|
125
|
-
displayName: 'Dimmable Light',
|
|
126
|
-
deviceType: this.api.matter.deviceTypes.DimmableLight,
|
|
127
|
-
serialNumber: 'LIGHT-002',
|
|
128
|
-
manufacturer: 'Matter Examples',
|
|
129
|
-
model: 'DimmableLight v1',
|
|
130
|
-
clusters: {
|
|
131
|
-
onOff: {
|
|
132
|
-
onOff: false,
|
|
133
|
-
},
|
|
134
|
-
levelControl: {
|
|
135
|
-
currentLevel: 127,
|
|
136
|
-
minLevel: 1,
|
|
137
|
-
maxLevel: 254,
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
// These are called when the user controls the accessory via the Home app
|
|
141
|
-
handlers: {
|
|
142
|
-
onOff: {
|
|
143
|
-
on: async () => {
|
|
144
|
-
this.log.info('[Dimmable Light] ✓ Handler `on` called (user controlled via Home app)');
|
|
145
|
-
},
|
|
146
|
-
off: async () => {
|
|
147
|
-
this.log.info('[Dimmable Light] ✓ Handler `off` called (user controlled via Home app)');
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
levelControl: {
|
|
151
|
-
moveToLevelWithOnOff: async (request) => {
|
|
152
|
-
const { level } = request;
|
|
153
|
-
this.log.info(`[Dimmable Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
// 3. Colour Temperature Light
|
|
159
|
-
accessories.push({
|
|
160
|
-
uuid: uuidLightColourTemp,
|
|
161
|
-
displayName: 'Colour Temperature Light',
|
|
162
|
-
deviceType: this.api.matter.deviceTypes.ColorTemperatureLight,
|
|
163
|
-
serialNumber: 'LIGHT-003',
|
|
164
|
-
manufacturer: 'Matter Examples',
|
|
165
|
-
model: 'ColourTempLight v1',
|
|
166
|
-
clusters: {
|
|
167
|
-
onOff: {
|
|
168
|
-
onOff: false,
|
|
169
|
-
},
|
|
170
|
-
levelControl: {
|
|
171
|
-
currentLevel: 127,
|
|
172
|
-
minLevel: 1,
|
|
173
|
-
maxLevel: 254,
|
|
174
|
-
},
|
|
175
|
-
colorControl: {
|
|
176
|
-
colorMode: 2, // Colour temperature mode
|
|
177
|
-
colorTemperatureMireds: 250, // ~4000K
|
|
178
|
-
colorTempPhysicalMinMireds: 147, // 6800K (coolest)
|
|
179
|
-
colorTempPhysicalMaxMireds: 454, // 2200K (warmest)
|
|
180
|
-
coupleColorTempToLevelMinMireds: 147,
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
// These are called when the user controls the accessory via the Home app
|
|
184
|
-
handlers: {
|
|
185
|
-
onOff: {
|
|
186
|
-
on: async ( /* no args */) => {
|
|
187
|
-
this.log.info('[Colour Temp Light] handler `on` called (user controlled via Home app)');
|
|
188
|
-
},
|
|
189
|
-
off: async ( /* no args */) => {
|
|
190
|
-
this.log.info('[Colour Temp Light] Turned `off` called (user controlled via Home app)');
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
levelControl: {
|
|
194
|
-
moveToLevelWithOnOff: async (request) => {
|
|
195
|
-
const { level } = request;
|
|
196
|
-
this.log.info(`[Colour Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
colorControl: {
|
|
200
|
-
moveToColorTemperatureLogic: async (request) => {
|
|
201
|
-
const { targetMireds, transitionTime } = request;
|
|
202
|
-
const kelvin = Math.round(1000000 / targetMireds);
|
|
203
|
-
this.log.info(`[Colour Temp Light] ✓ Handler \`moveToColorTemperatureLogic\` called with ${targetMireds} mireds (~${kelvin}K), transition: ${transitionTime}s`);
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
// 4. Colour Light (Hue/Saturation ONLY - no CCT)
|
|
209
|
-
accessories.push({
|
|
210
|
-
uuid: uuidLightColour,
|
|
211
|
-
displayName: 'Colour Light (HS)',
|
|
212
|
-
deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
|
|
213
|
-
serialNumber: 'LIGHT-004',
|
|
214
|
-
manufacturer: 'Matter Examples',
|
|
215
|
-
model: 'ColorLight v1',
|
|
216
|
-
clusters: {
|
|
217
|
-
onOff: {
|
|
218
|
-
onOff: false,
|
|
219
|
-
},
|
|
220
|
-
levelControl: {
|
|
221
|
-
currentLevel: 127,
|
|
222
|
-
minLevel: 1,
|
|
223
|
-
maxLevel: 254,
|
|
224
|
-
},
|
|
225
|
-
colorControl: {
|
|
226
|
-
colorMode: 0, // Hue/Saturation mode
|
|
227
|
-
currentHue: 0, // Red (0 degrees)
|
|
228
|
-
currentSaturation: 254, // Full saturation
|
|
229
|
-
currentX: 41942, // Also provide XY for compatibility
|
|
230
|
-
currentY: 21626,
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
// These are called when the user controls the accessory via the Home app
|
|
234
|
-
handlers: {
|
|
235
|
-
onOff: {
|
|
236
|
-
on: async () => {
|
|
237
|
-
this.log.info('[Colour Light HS] ✓ Handler `on` called (user controlled via Home app)');
|
|
238
|
-
},
|
|
239
|
-
off: async () => {
|
|
240
|
-
this.log.info('[Colour Light HS] ✓ Handler `off` called (user controlled via Home app)');
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
levelControl: {
|
|
244
|
-
moveToLevelWithOnOff: async (request) => {
|
|
245
|
-
const { level } = request;
|
|
246
|
-
this.log.info(`[Colour Light HS] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
colorControl: {
|
|
250
|
-
moveToColorLogic: async (request) => {
|
|
251
|
-
const { targetX, targetY, transitionTime } = request;
|
|
252
|
-
const xFloat = (targetX / 65535).toFixed(4);
|
|
253
|
-
const yFloat = (targetY / 65535).toFixed(4);
|
|
254
|
-
this.log.info(`[Colour Light HS] ✓ Handler \`moveToColorLogic\` called with x=${targetX} (~${xFloat}), y=${targetY} (~${yFloat}), transition: ${transitionTime}s`);
|
|
255
|
-
},
|
|
256
|
-
moveToHueAndSaturationLogic: async (request) => {
|
|
257
|
-
const { targetHue, targetSaturation, transitionTime } = request;
|
|
258
|
-
const hueDegrees = Math.round((targetHue / 254) * 360);
|
|
259
|
-
const saturationPercent = Math.round((targetSaturation / 254) * 100);
|
|
260
|
-
this.log.info(`[Colour Light HS] ✓ Handler \`moveToHueAndSaturationLogic\` called with hue=${targetHue} (~${hueDegrees}°), saturation=${targetSaturation} (~${saturationPercent}%), transition: ${transitionTime}s`);
|
|
261
|
-
},
|
|
262
|
-
// NOTE: No moveToColorTemperatureLogic handler - this light only supports color, not CCT
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
// 5. Extended Colour Light (Hue/Saturation + CCT)
|
|
267
|
-
accessories.push({
|
|
268
|
-
uuid: uuidLightExtendedColour,
|
|
269
|
-
displayName: 'Extended Colour Light (HS+CCT)',
|
|
270
|
-
deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
|
|
271
|
-
serialNumber: 'LIGHT-005',
|
|
272
|
-
manufacturer: 'Matter Examples',
|
|
273
|
-
model: 'ExtendedColorLight v1',
|
|
274
|
-
clusters: {
|
|
275
|
-
onOff: {
|
|
276
|
-
onOff: false,
|
|
277
|
-
},
|
|
278
|
-
levelControl: {
|
|
279
|
-
currentLevel: 127,
|
|
280
|
-
minLevel: 1,
|
|
281
|
-
maxLevel: 254,
|
|
282
|
-
},
|
|
283
|
-
colorControl: {
|
|
284
|
-
colorMode: 0, // Hue/Saturation mode
|
|
285
|
-
currentHue: 0, // Red (0 degrees)
|
|
286
|
-
currentSaturation: 254, // Full saturation
|
|
287
|
-
currentX: 41942, // Also provide XY for compatibility
|
|
288
|
-
currentY: 21626,
|
|
289
|
-
colorTemperatureMireds: 250, // ~4000K (for CCT mode)
|
|
290
|
-
colorTempPhysicalMinMireds: 147, // 6800K (coolest)
|
|
291
|
-
colorTempPhysicalMaxMireds: 454, // 2200K (warmest)
|
|
292
|
-
coupleColorTempToLevelMinMireds: 147,
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
// These are called when the user controls the accessory via the Home app
|
|
296
|
-
handlers: {
|
|
297
|
-
onOff: {
|
|
298
|
-
on: async () => {
|
|
299
|
-
this.log.info('[Extended Colour Light] ✓ Handler `on` called (user controlled via Home app)');
|
|
300
|
-
},
|
|
301
|
-
off: async () => {
|
|
302
|
-
this.log.info('[Extended Colour Light] ✓ Handler `off` called (user controlled via Home app)');
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
levelControl: {
|
|
306
|
-
moveToLevelWithOnOff: async (request) => {
|
|
307
|
-
const { level } = request;
|
|
308
|
-
this.log.info(`[Extended Colour Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
colorControl: {
|
|
312
|
-
moveToColorLogic: async (request) => {
|
|
313
|
-
const { targetX, targetY, transitionTime } = request;
|
|
314
|
-
const xFloat = (targetX / 65535).toFixed(4);
|
|
315
|
-
const yFloat = (targetY / 65535).toFixed(4);
|
|
316
|
-
this.log.info(`[Extended Colour Light] ✓ Handler \`moveToColorLogic\` called with x=${targetX} (~${xFloat}), y=${targetY} (~${yFloat}), transition: ${transitionTime}s`);
|
|
317
|
-
},
|
|
318
|
-
moveToHueAndSaturationLogic: async (request) => {
|
|
319
|
-
const { targetHue, targetSaturation, transitionTime } = request;
|
|
320
|
-
const hueDegrees = Math.round((targetHue / 254) * 360);
|
|
321
|
-
const saturationPercent = Math.round((targetSaturation / 254) * 100);
|
|
322
|
-
this.log.info(`[Extended Colour Light] ✓ Handler \`moveToHueAndSaturationLogic\` called with hue=${targetHue} (~${hueDegrees}°), saturation=${targetSaturation} (~${saturationPercent}%), transition: ${transitionTime}s`);
|
|
323
|
-
},
|
|
324
|
-
moveToColorTemperatureLogic: async (request) => {
|
|
325
|
-
const { targetMireds, transitionTime } = request;
|
|
326
|
-
const kelvin = Math.round(1000000 / targetMireds);
|
|
327
|
-
this.log.info(`[Extended Colour Light] ✓ Handler \`moveToColorTemperatureLogic\` called with ${targetMireds} mireds (~${kelvin}K), transition: ${transitionTime}s`);
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
// Register all lighting accessories
|
|
333
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
120
|
+
registerSection4Lighting() {
|
|
121
|
+
this.log.info('═'.repeat(80));
|
|
122
|
+
this.log.info('Section 4: Lighting Devices (Matter Spec § 4)');
|
|
123
|
+
this.log.info('═'.repeat(80));
|
|
124
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
125
|
+
const accessories = [
|
|
126
|
+
...registerOnOffLight(context),
|
|
127
|
+
...registerDimmableLight(context),
|
|
128
|
+
...registerColorTemperatureLight(context),
|
|
129
|
+
...registerExtendedColorLight(context),
|
|
130
|
+
];
|
|
131
|
+
if (accessories.length > 0) {
|
|
132
|
+
this.log.info(`✓ Registered ${accessories.length} lighting device(s)`);
|
|
133
|
+
for (const acc of accessories) {
|
|
134
|
+
this.log.info(` - ${acc.displayName}`);
|
|
135
|
+
}
|
|
136
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
137
|
+
}
|
|
334
138
|
}
|
|
335
139
|
/**
|
|
336
|
-
*
|
|
140
|
+
* Section 5: Smart Plugs/Actuators (Matter Spec § 5)
|
|
337
141
|
*/
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
handlers: {
|
|
357
|
-
onOff: {
|
|
358
|
-
on: async () => {
|
|
359
|
-
this.log.info('[On/Off Switch] ✓ Handler `on` called (user controlled via Home app)');
|
|
360
|
-
},
|
|
361
|
-
off: async () => {
|
|
362
|
-
this.log.info('[On/Off Switch] ✓ Handler `off` called (user controlled via Home app)');
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
// 2. On/Off Outlet (Smart Plug)
|
|
368
|
-
accessories.push({
|
|
369
|
-
uuid: uuidOutlet,
|
|
370
|
-
displayName: 'On/Off Outlet',
|
|
371
|
-
deviceType: this.api.matter.deviceTypes.OnOffOutlet,
|
|
372
|
-
serialNumber: 'OUTLET-001',
|
|
373
|
-
manufacturer: 'Matter Examples',
|
|
374
|
-
model: 'OnOffOutlet v1',
|
|
375
|
-
clusters: {
|
|
376
|
-
onOff: {
|
|
377
|
-
onOff: false,
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
handlers: {
|
|
381
|
-
onOff: {
|
|
382
|
-
on: async () => {
|
|
383
|
-
this.log.info('[On/Off Outlet] ✓ Handler `on` called (user controlled via Home app)');
|
|
384
|
-
},
|
|
385
|
-
off: async () => {
|
|
386
|
-
this.log.info('[On/Off Outlet] ✓ Handler `off` called (user controlled via Home app)');
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
// 3. Dimmable Outlet
|
|
392
|
-
accessories.push({
|
|
393
|
-
uuid: uuidDimmableOutlet,
|
|
394
|
-
displayName: 'Dimmable Outlet',
|
|
395
|
-
deviceType: this.api.matter.deviceTypes.DimmableOutlet,
|
|
396
|
-
serialNumber: 'OUTLET-002',
|
|
397
|
-
manufacturer: 'Matter Examples',
|
|
398
|
-
model: 'DimmableOutlet v1',
|
|
399
|
-
clusters: {
|
|
400
|
-
onOff: {
|
|
401
|
-
onOff: false,
|
|
402
|
-
},
|
|
403
|
-
levelControl: {
|
|
404
|
-
currentLevel: 127,
|
|
405
|
-
minLevel: 1,
|
|
406
|
-
maxLevel: 254,
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
handlers: {
|
|
410
|
-
onOff: {
|
|
411
|
-
on: async () => {
|
|
412
|
-
this.log.info('[Dimmable Outlet] ✓ Handler `on` called (user controlled via Home app)');
|
|
413
|
-
},
|
|
414
|
-
off: async () => {
|
|
415
|
-
this.log.info('[Dimmable Outlet] ✓ Handler `off` called (user controlled via Home app)');
|
|
416
|
-
},
|
|
417
|
-
},
|
|
418
|
-
levelControl: {
|
|
419
|
-
moveToLevelWithOnOff: async (request) => {
|
|
420
|
-
const { level } = request;
|
|
421
|
-
this.log.info(`[Dimmable Outlet] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
},
|
|
425
|
-
});
|
|
426
|
-
// Register all switch/outlet accessories
|
|
427
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
428
|
-
// Register sensor devices
|
|
429
|
-
this.registerSensors();
|
|
142
|
+
registerSection5SmartPlugs() {
|
|
143
|
+
this.log.info('═'.repeat(80));
|
|
144
|
+
this.log.info('Section 5: Smart Plugs/Actuators (Matter Spec § 5)');
|
|
145
|
+
this.log.info('═'.repeat(80));
|
|
146
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
147
|
+
const accessories = [
|
|
148
|
+
...registerOnOffPlugInUnit(context),
|
|
149
|
+
...registerDimmablePlugInUnit(context),
|
|
150
|
+
];
|
|
151
|
+
if (accessories.length > 0) {
|
|
152
|
+
this.log.info(`✓ Registered ${accessories.length} smart plug/actuator device(s)`);
|
|
153
|
+
for (const acc of accessories) {
|
|
154
|
+
this.log.info(` - ${acc.displayName}`);
|
|
155
|
+
}
|
|
156
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
157
|
+
}
|
|
430
158
|
}
|
|
431
159
|
/**
|
|
432
|
-
*
|
|
160
|
+
* Section 6: Switches & Controllers (Matter Spec § 6)
|
|
433
161
|
*/
|
|
434
|
-
|
|
162
|
+
registerSection6Switches() {
|
|
435
163
|
this.log.info('═'.repeat(80));
|
|
436
|
-
this.log.info('
|
|
164
|
+
this.log.info('Section 6: Switches & Controllers (Matter Spec § 6)');
|
|
437
165
|
this.log.info('═'.repeat(80));
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
temperatureMeasurement: {
|
|
449
|
-
measuredValue: 2100, // 21.00°C (in hundredths of a degree Celsius)
|
|
450
|
-
minMeasuredValue: -5000, // -50°C
|
|
451
|
-
maxMeasuredValue: 10000, // 100°C
|
|
452
|
-
},
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
// 2. Humidity Sensor
|
|
456
|
-
accessories.push({
|
|
457
|
-
uuid: this.api.hap.uuid.generate('matter-humidity-sensor'),
|
|
458
|
-
displayName: 'Humidity Sensor',
|
|
459
|
-
deviceType: this.api.matter.deviceTypes.HumiditySensor,
|
|
460
|
-
serialNumber: 'HUM-001',
|
|
461
|
-
manufacturer: 'Homebridge',
|
|
462
|
-
model: 'Humidity Sensor Example',
|
|
463
|
-
clusters: {
|
|
464
|
-
relativeHumidityMeasurement: {
|
|
465
|
-
measuredValue: 5500, // 55% (in hundredths of a percent)
|
|
466
|
-
minMeasuredValue: 0,
|
|
467
|
-
maxMeasuredValue: 10000, // 100%
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
});
|
|
471
|
-
// 3. Light Sensor
|
|
472
|
-
accessories.push({
|
|
473
|
-
uuid: this.api.hap.uuid.generate('matter-light-sensor'),
|
|
474
|
-
displayName: 'Light Sensor',
|
|
475
|
-
deviceType: this.api.matter.deviceTypes.LightSensor,
|
|
476
|
-
serialNumber: 'LIGHT-001',
|
|
477
|
-
manufacturer: 'Homebridge',
|
|
478
|
-
model: 'Light Sensor Example',
|
|
479
|
-
clusters: {
|
|
480
|
-
illuminanceMeasurement: {
|
|
481
|
-
measuredValue: 5000, // 500 lux (in 10,000 * log10(lux) format)
|
|
482
|
-
minMeasuredValue: 1,
|
|
483
|
-
maxMeasuredValue: 65534,
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
});
|
|
487
|
-
// 4. Motion Sensor (Occupancy)
|
|
488
|
-
// Note: OccupancySensorDevice requires specifying features (PIR, Ultrasonic, or PhysicalContact)
|
|
489
|
-
const OccupancySensingServer = this.api.matter.deviceTypes.MotionSensor.requirements.OccupancySensingServer;
|
|
490
|
-
const MotionSensorWithPIR = this.api.matter.deviceTypes.MotionSensor.with(OccupancySensingServer.with('PassiveInfrared'));
|
|
491
|
-
accessories.push({
|
|
492
|
-
uuid: this.api.hap.uuid.generate('matter-motion-sensor'),
|
|
493
|
-
displayName: 'Motion Sensor',
|
|
494
|
-
deviceType: MotionSensorWithPIR,
|
|
495
|
-
serialNumber: 'MOTION-001',
|
|
496
|
-
manufacturer: 'Homebridge',
|
|
497
|
-
model: 'Motion Sensor Example',
|
|
498
|
-
clusters: {
|
|
499
|
-
occupancySensing: {
|
|
500
|
-
occupancy: {
|
|
501
|
-
occupied: false, // No motion detected
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
|
-
});
|
|
506
|
-
// 5. Contact Sensor
|
|
507
|
-
accessories.push({
|
|
508
|
-
uuid: this.api.hap.uuid.generate('matter-contact-sensor'),
|
|
509
|
-
displayName: 'Contact Sensor',
|
|
510
|
-
deviceType: this.api.matter.deviceTypes.ContactSensor,
|
|
511
|
-
serialNumber: 'CONTACT-001',
|
|
512
|
-
manufacturer: 'Homebridge',
|
|
513
|
-
model: 'Contact Sensor Example',
|
|
514
|
-
clusters: {
|
|
515
|
-
booleanState: {
|
|
516
|
-
stateValue: false, // Contact closed (false = closed, true = open)
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
});
|
|
520
|
-
// 6. Leak Sensor
|
|
521
|
-
accessories.push({
|
|
522
|
-
uuid: this.api.hap.uuid.generate('matter-leak-sensor'),
|
|
523
|
-
displayName: 'Leak Sensor',
|
|
524
|
-
deviceType: this.api.matter.deviceTypes.LeakSensor,
|
|
525
|
-
serialNumber: 'LEAK-001',
|
|
526
|
-
manufacturer: 'Homebridge',
|
|
527
|
-
model: 'Leak Sensor Example',
|
|
528
|
-
clusters: {
|
|
529
|
-
booleanState: {
|
|
530
|
-
stateValue: false, // No leak detected (false = dry, true = leak)
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
});
|
|
534
|
-
// 7. Smoke Sensor
|
|
535
|
-
// Note: SmokeCoAlarmDevice requires specifying features (SmokeAlarm and/or CoAlarm)
|
|
536
|
-
const SmokeCoAlarmServer = this.api.matter.deviceTypes.SmokeSensor.requirements.SmokeCoAlarmServer;
|
|
537
|
-
const SmokeSensorWithBoth = this.api.matter.deviceTypes.SmokeSensor.with(SmokeCoAlarmServer.with('SmokeAlarm', 'CoAlarm'));
|
|
538
|
-
accessories.push({
|
|
539
|
-
uuid: this.api.hap.uuid.generate('matter-smoke-sensor'),
|
|
540
|
-
displayName: 'Smoke Sensor',
|
|
541
|
-
deviceType: SmokeSensorWithBoth,
|
|
542
|
-
serialNumber: 'SMOKE-001',
|
|
543
|
-
manufacturer: 'Homebridge',
|
|
544
|
-
model: 'Smoke Sensor Example',
|
|
545
|
-
clusters: {
|
|
546
|
-
smokeCoAlarm: {
|
|
547
|
-
smokeState: 0, // 0 = Normal, 1 = Warning, 2 = Critical
|
|
548
|
-
coState: 0, // 0 = Normal, 1 = Warning, 2 = Critical
|
|
549
|
-
batteryAlert: 0, // 0 = Normal
|
|
550
|
-
testInProgress: false,
|
|
551
|
-
hardwareFaultAlert: false,
|
|
552
|
-
endOfServiceAlert: 0, // 0 = Normal
|
|
553
|
-
interconnectSmokeAlarm: 0, // 0 = Normal
|
|
554
|
-
interconnectCoAlarm: 0, // 0 = Normal
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
});
|
|
558
|
-
this.log.info(`✓ Registered ${accessories.length} sensor accessories`);
|
|
559
|
-
for (const acc of accessories) {
|
|
560
|
-
this.log.info(` - ${acc.displayName}`);
|
|
166
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
167
|
+
const accessories = [
|
|
168
|
+
...registerOnOffLightSwitch(context),
|
|
169
|
+
];
|
|
170
|
+
if (accessories.length > 0) {
|
|
171
|
+
this.log.info(`✓ Registered ${accessories.length} switch/controller device(s)`);
|
|
172
|
+
for (const acc of accessories) {
|
|
173
|
+
this.log.info(` - ${acc.displayName}`);
|
|
174
|
+
}
|
|
175
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
561
176
|
}
|
|
562
|
-
// Register all sensor accessories
|
|
563
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
564
177
|
}
|
|
565
178
|
/**
|
|
566
|
-
*
|
|
179
|
+
* Section 7: Sensors (Matter Spec § 7)
|
|
567
180
|
*/
|
|
568
|
-
|
|
181
|
+
registerSection7Sensors() {
|
|
569
182
|
this.log.info('═'.repeat(80));
|
|
570
|
-
this.log.info('
|
|
183
|
+
this.log.info('Section 7: Sensors (Matter Spec § 7)');
|
|
571
184
|
this.log.info('═'.repeat(80));
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
maxHeatSetpointLimit: 3000, // 30°C maximum
|
|
589
|
-
// Cooling setpoint (target temperature in cool mode)
|
|
590
|
-
occupiedCoolingSetpoint: 2400, // 24.00°C
|
|
591
|
-
minCoolSetpointLimit: 1600, // 16°C minimum
|
|
592
|
-
maxCoolSetpointLimit: 3200, // 32°C maximum
|
|
593
|
-
// System mode: 0=Off, 1=Auto, 3=Cool, 4=Heat
|
|
594
|
-
systemMode: 4, // Heat mode
|
|
595
|
-
// Control sequence: what modes are available (mandatory field)
|
|
596
|
-
// 4 = CoolingAndHeating (correct value when both Heating & Cooling features are present)
|
|
597
|
-
controlSequenceOfOperation: 4,
|
|
598
|
-
},
|
|
599
|
-
},
|
|
600
|
-
handlers: {
|
|
601
|
-
thermostat: {
|
|
602
|
-
// Called when user changes heating setpoint
|
|
603
|
-
setOccupiedHeatingSetpoint: async (request) => {
|
|
604
|
-
const tempC = (request.targetSetpoint / 100).toFixed(1);
|
|
605
|
-
this.log.info(`[Thermostat] ✓ Handler \`setOccupiedHeatingSetpoint\` called: ${request.targetSetpoint} (${tempC}°C)`);
|
|
606
|
-
},
|
|
607
|
-
// Called when user changes cooling setpoint
|
|
608
|
-
setOccupiedCoolingSetpoint: async (request) => {
|
|
609
|
-
const tempC = (request.targetSetpoint / 100).toFixed(1);
|
|
610
|
-
this.log.info(`[Thermostat] ✓ Handler \`setOccupiedCoolingSetpoint\` called: ${request.targetSetpoint} (${tempC}°C)`);
|
|
611
|
-
},
|
|
612
|
-
// Called when user changes mode (Off, Auto, Cool, Heat)
|
|
613
|
-
setSystemMode: async (request) => {
|
|
614
|
-
const modes = ['Off', 'Auto', 'Reserved', 'Cool', 'Heat', 'Emergency Heating', 'Precooling', 'Fan Only'];
|
|
615
|
-
const modeName = modes[request.systemMode] || `Unknown (${request.systemMode})`;
|
|
616
|
-
this.log.info(`[Thermostat] ✓ Handler \`setSystemMode\` called: ${request.systemMode} (${modeName})`);
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
});
|
|
621
|
-
// 2. Fan
|
|
622
|
-
accessories.push({
|
|
623
|
-
uuid: this.api.matter.uuid.generate('matter-fan'),
|
|
624
|
-
displayName: 'Fan',
|
|
625
|
-
deviceType: this.api.matter.deviceTypes.Fan,
|
|
626
|
-
serialNumber: 'FAN-001',
|
|
627
|
-
manufacturer: 'Matter Examples',
|
|
628
|
-
model: 'Fan v1',
|
|
629
|
-
clusters: {
|
|
630
|
-
fanControl: {
|
|
631
|
-
// Fan mode: 0=Off, 1=Low, 2=Medium, 3=High, 4=On, 5=Auto, 6=Smart
|
|
632
|
-
fanMode: 0, // Off
|
|
633
|
-
// Fan mode sequence: indicates which modes are supported
|
|
634
|
-
// 0=OffLowMedHigh, 1=OffLowHigh, 2=OffLowMedHighAuto, 3=OffLowHighAuto, 4=OffOnAuto, 5=OffOn
|
|
635
|
-
fanModeSequence: 0, // OffLowMedHigh
|
|
636
|
-
// Percent setting (0-100)
|
|
637
|
-
percentSetting: 0,
|
|
638
|
-
percentCurrent: 0,
|
|
639
|
-
// Speed setting (0-100, some fans use this instead of percent)
|
|
640
|
-
speedSetting: 0,
|
|
641
|
-
speedCurrent: 0,
|
|
642
|
-
},
|
|
643
|
-
},
|
|
644
|
-
handlers: {
|
|
645
|
-
fanControl: {
|
|
646
|
-
// Called when user changes fan speed via percent slider
|
|
647
|
-
setPercentSetting: async (request) => {
|
|
648
|
-
this.log.info(`[Fan] ✓ Handler \`setPercentSetting\` called: ${request.percentSetting}%`);
|
|
649
|
-
},
|
|
650
|
-
// Called when user changes fan mode
|
|
651
|
-
setFanMode: async (request) => {
|
|
652
|
-
const modes = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Smart'];
|
|
653
|
-
const modeName = modes[request.fanMode] || `Unknown (${request.fanMode})`;
|
|
654
|
-
this.log.info(`[Fan] ✓ Handler \`setFanMode\` called: ${request.fanMode} (${modeName})`);
|
|
655
|
-
},
|
|
656
|
-
// Called when user presses up/down buttons to adjust speed
|
|
657
|
-
step: async (request) => {
|
|
658
|
-
const dir = request.direction === 0 ? 'Up' : 'Down';
|
|
659
|
-
this.log.info(`[Fan] ✓ Handler \`step\` called: direction=${dir}, wrap=${request.wrap}, lowestOff=${request.lowestOff}`);
|
|
660
|
-
},
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
});
|
|
664
|
-
this.log.info(`✓ Registered ${accessories.length} HVAC accessories`);
|
|
665
|
-
for (const acc of accessories) {
|
|
666
|
-
this.log.info(` - ${acc.displayName}`);
|
|
185
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
186
|
+
const accessories = [
|
|
187
|
+
...registerContactSensor(context),
|
|
188
|
+
...registerLightSensor(context),
|
|
189
|
+
...registerOccupancySensor(context),
|
|
190
|
+
...registerTemperatureSensor(context),
|
|
191
|
+
...registerHumiditySensor(context),
|
|
192
|
+
...registerSmokeCoAlarm(context),
|
|
193
|
+
...registerWaterLeakDetector(context),
|
|
194
|
+
];
|
|
195
|
+
if (accessories.length > 0) {
|
|
196
|
+
this.log.info(`✓ Registered ${accessories.length} sensor device(s)`);
|
|
197
|
+
for (const acc of accessories) {
|
|
198
|
+
this.log.info(` - ${acc.displayName}`);
|
|
199
|
+
}
|
|
200
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
667
201
|
}
|
|
668
|
-
// Register all HVAC accessories
|
|
669
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
670
202
|
}
|
|
671
203
|
/**
|
|
672
|
-
*
|
|
204
|
+
* Section 8: Closure Devices (Matter Spec § 8)
|
|
673
205
|
*/
|
|
674
|
-
|
|
206
|
+
registerSection8Closure() {
|
|
675
207
|
this.log.info('═'.repeat(80));
|
|
676
|
-
this.log.info('
|
|
208
|
+
this.log.info('Section 8: Closure Devices (Matter Spec § 8)');
|
|
677
209
|
this.log.info('═'.repeat(80));
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
// Lock state: 0=NotFullyLocked, 1=Locked, 2=Unlocked
|
|
690
|
-
lockState: 2, // Unlocked
|
|
691
|
-
// Lock type: 0=Deadbolt, 1=Magnetic, 2=Other, etc.
|
|
692
|
-
lockType: 0, // Deadbolt
|
|
693
|
-
// Actuator enabled (can be locked/unlocked)
|
|
694
|
-
actuatorEnabled: true,
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
handlers: {
|
|
698
|
-
doorLock: {
|
|
699
|
-
// Called when user locks the door
|
|
700
|
-
lockDoor: async () => {
|
|
701
|
-
this.log.info('[Door Lock] ✓ Handler `lockDoor` called - Locking door');
|
|
702
|
-
// Update the lock state to "Locked" (1)
|
|
703
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-door-lock'), 'doorLock', { lockState: 1 });
|
|
704
|
-
},
|
|
705
|
-
// Called when user unlocks the door
|
|
706
|
-
unlockDoor: async () => {
|
|
707
|
-
this.log.info('[Door Lock] ✓ Handler `unlockDoor` called - Unlocking door');
|
|
708
|
-
// Update the lock state to "Unlocked" (2)
|
|
709
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-door-lock'), 'doorLock', { lockState: 2 });
|
|
710
|
-
},
|
|
711
|
-
},
|
|
712
|
-
},
|
|
713
|
-
});
|
|
714
|
-
// 2. Garage Door Opener
|
|
715
|
-
// Note: Matter uses WindowCovering device type for garage doors
|
|
716
|
-
accessories.push({
|
|
717
|
-
uuid: this.api.matter.uuid.generate('matter-garage-door'),
|
|
718
|
-
displayName: 'Garage Door',
|
|
719
|
-
deviceType: this.api.matter.deviceTypes.WindowCovering,
|
|
720
|
-
serialNumber: 'GARAGE-001',
|
|
721
|
-
manufacturer: 'Matter Examples',
|
|
722
|
-
model: 'GarageDoor v1',
|
|
723
|
-
clusters: {
|
|
724
|
-
windowCovering: {
|
|
725
|
-
// Target position (0 = fully closed, 10000 = fully open)
|
|
726
|
-
targetPositionLiftPercent100ths: 0, // Closed
|
|
727
|
-
// Current position
|
|
728
|
-
currentPositionLiftPercent100ths: 0, // Closed
|
|
729
|
-
// Operational status
|
|
730
|
-
operationalStatus: {
|
|
731
|
-
global: 0, // Not moving
|
|
732
|
-
lift: 0,
|
|
733
|
-
tilt: 0,
|
|
734
|
-
},
|
|
735
|
-
// End product type
|
|
736
|
-
endProductType: 7, // Garage door
|
|
737
|
-
// Configuration: supports lift positioning
|
|
738
|
-
configStatus: {
|
|
739
|
-
operational: true,
|
|
740
|
-
onlineReserved: true,
|
|
741
|
-
liftMovementReversed: false,
|
|
742
|
-
liftPositionAware: true,
|
|
743
|
-
tiltPositionAware: false,
|
|
744
|
-
liftEncoderControlled: true,
|
|
745
|
-
tiltEncoderControlled: false,
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
},
|
|
749
|
-
handlers: {
|
|
750
|
-
windowCovering: {
|
|
751
|
-
// Called when user opens/closes garage door
|
|
752
|
-
goToLiftPercentage: async (request) => {
|
|
753
|
-
const percent = (request.targetPercent / 100).toFixed(0);
|
|
754
|
-
this.log.info(`[Garage Door] ✓ Handler \`goToLiftPercentage\` called: ${request.targetPercent} (${percent}% open)`);
|
|
755
|
-
// Update position
|
|
756
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-garage-door'), 'windowCovering', {
|
|
757
|
-
currentPositionLiftPercent100ths: request.targetPercent,
|
|
758
|
-
targetPositionLiftPercent100ths: request.targetPercent,
|
|
759
|
-
});
|
|
760
|
-
},
|
|
761
|
-
// Called when user presses "up" (open)
|
|
762
|
-
upOrOpen: async () => {
|
|
763
|
-
this.log.info('[Garage Door] ✓ Handler `upOrOpen` called - Opening garage door');
|
|
764
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-garage-door'), 'windowCovering', {
|
|
765
|
-
currentPositionLiftPercent100ths: 10000, // Fully open
|
|
766
|
-
targetPositionLiftPercent100ths: 10000,
|
|
767
|
-
});
|
|
768
|
-
},
|
|
769
|
-
// Called when user presses "down" (close)
|
|
770
|
-
downOrClose: async () => {
|
|
771
|
-
this.log.info('[Garage Door] ✓ Handler `downOrClose` called - Closing garage door');
|
|
772
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-garage-door'), 'windowCovering', {
|
|
773
|
-
currentPositionLiftPercent100ths: 0, // Fully closed
|
|
774
|
-
targetPositionLiftPercent100ths: 0,
|
|
775
|
-
});
|
|
776
|
-
},
|
|
777
|
-
// Called when user presses "stop"
|
|
778
|
-
stopMotion: async () => {
|
|
779
|
-
this.log.info('[Garage Door] ✓ Handler `stopMotion` called - Stopping garage door');
|
|
780
|
-
},
|
|
781
|
-
},
|
|
782
|
-
},
|
|
783
|
-
});
|
|
784
|
-
this.log.info(`✓ Registered ${accessories.length} security & access accessories`);
|
|
785
|
-
for (const acc of accessories) {
|
|
786
|
-
this.log.info(` - ${acc.displayName}`);
|
|
210
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
211
|
+
const accessories = [
|
|
212
|
+
...registerDoorLock(context),
|
|
213
|
+
...registerWindowCovering(context),
|
|
214
|
+
];
|
|
215
|
+
if (accessories.length > 0) {
|
|
216
|
+
this.log.info(`✓ Registered ${accessories.length} closure device(s)`);
|
|
217
|
+
for (const acc of accessories) {
|
|
218
|
+
this.log.info(` - ${acc.displayName}`);
|
|
219
|
+
}
|
|
220
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
787
221
|
}
|
|
788
|
-
// Register all security accessories
|
|
789
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
790
222
|
}
|
|
791
223
|
/**
|
|
792
|
-
*
|
|
224
|
+
* Section 9: HVAC (Matter Spec § 9)
|
|
793
225
|
*/
|
|
794
|
-
|
|
226
|
+
registerSection9HVAC() {
|
|
795
227
|
this.log.info('═'.repeat(80));
|
|
796
|
-
this.log.info('
|
|
228
|
+
this.log.info('Section 9: HVAC (Matter Spec § 9)');
|
|
797
229
|
this.log.info('═'.repeat(80));
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
// Target position (0 = fully closed, 10000 = fully open, in hundredths of percent)
|
|
810
|
-
targetPositionLiftPercent100ths: 5000, // 50% open
|
|
811
|
-
// Current position
|
|
812
|
-
currentPositionLiftPercent100ths: 5000, // 50% open
|
|
813
|
-
// Operational status
|
|
814
|
-
operationalStatus: {
|
|
815
|
-
global: 0, // Not moving
|
|
816
|
-
lift: 0,
|
|
817
|
-
tilt: 0,
|
|
818
|
-
},
|
|
819
|
-
// End product type
|
|
820
|
-
endProductType: 0, // Rollershade
|
|
821
|
-
// Configuration
|
|
822
|
-
configStatus: {
|
|
823
|
-
operational: true,
|
|
824
|
-
onlineReserved: true,
|
|
825
|
-
liftMovementReversed: false,
|
|
826
|
-
liftPositionAware: true,
|
|
827
|
-
tiltPositionAware: false,
|
|
828
|
-
liftEncoderControlled: true,
|
|
829
|
-
tiltEncoderControlled: false,
|
|
830
|
-
},
|
|
831
|
-
},
|
|
832
|
-
},
|
|
833
|
-
handlers: {
|
|
834
|
-
windowCovering: {
|
|
835
|
-
// Called when user sets position via slider
|
|
836
|
-
goToLiftPercentage: async (request) => {
|
|
837
|
-
const percent = (request.targetPercent / 100).toFixed(0);
|
|
838
|
-
this.log.info(`[Window Blind] ✓ Handler \`goToLiftPercentage\` called: ${request.targetPercent} (${percent}% open)`);
|
|
839
|
-
// Update position
|
|
840
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-window-blind'), 'windowCovering', {
|
|
841
|
-
currentPositionLiftPercent100ths: request.targetPercent,
|
|
842
|
-
targetPositionLiftPercent100ths: request.targetPercent,
|
|
843
|
-
});
|
|
844
|
-
},
|
|
845
|
-
// Called when user presses "up" (open)
|
|
846
|
-
upOrOpen: async () => {
|
|
847
|
-
this.log.info('[Window Blind] ✓ Handler `upOrOpen` called - Opening blind');
|
|
848
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-window-blind'), 'windowCovering', {
|
|
849
|
-
currentPositionLiftPercent100ths: 10000, // Fully open
|
|
850
|
-
targetPositionLiftPercent100ths: 10000,
|
|
851
|
-
});
|
|
852
|
-
},
|
|
853
|
-
// Called when user presses "down" (close)
|
|
854
|
-
downOrClose: async () => {
|
|
855
|
-
this.log.info('[Window Blind] ✓ Handler `downOrClose` called - Closing blind');
|
|
856
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-window-blind'), 'windowCovering', {
|
|
857
|
-
currentPositionLiftPercent100ths: 0, // Fully closed
|
|
858
|
-
targetPositionLiftPercent100ths: 0,
|
|
859
|
-
});
|
|
860
|
-
},
|
|
861
|
-
// Called when user presses "stop"
|
|
862
|
-
stopMotion: async () => {
|
|
863
|
-
this.log.info('[Window Blind] ✓ Handler `stopMotion` called - Stopping blind');
|
|
864
|
-
},
|
|
865
|
-
},
|
|
866
|
-
},
|
|
867
|
-
});
|
|
868
|
-
// 2. Window Covering with Tilt (Venetian Blind)
|
|
869
|
-
accessories.push({
|
|
870
|
-
uuid: this.api.matter.uuid.generate('matter-venetian-blind'),
|
|
871
|
-
displayName: 'Venetian Blind (Tilt)',
|
|
872
|
-
deviceType: this.api.matter.deviceTypes.WindowCovering,
|
|
873
|
-
serialNumber: 'BLIND-002',
|
|
874
|
-
manufacturer: 'Matter Examples',
|
|
875
|
-
model: 'VenetianBlind v1',
|
|
876
|
-
clusters: {
|
|
877
|
-
windowCovering: {
|
|
878
|
-
// Lift position (vertical position)
|
|
879
|
-
targetPositionLiftPercent100ths: 5000, // 50% open
|
|
880
|
-
currentPositionLiftPercent100ths: 5000,
|
|
881
|
-
// Tilt position (slat angle: 0 = closed, 10000 = fully open)
|
|
882
|
-
targetPositionTiltPercent100ths: 5000, // 50% tilted
|
|
883
|
-
currentPositionTiltPercent100ths: 5000,
|
|
884
|
-
// Operational status
|
|
885
|
-
operationalStatus: {
|
|
886
|
-
global: 0,
|
|
887
|
-
lift: 0,
|
|
888
|
-
tilt: 0,
|
|
889
|
-
},
|
|
890
|
-
// End product type
|
|
891
|
-
endProductType: 8, // Venetian blind
|
|
892
|
-
// Configuration: supports both lift and tilt
|
|
893
|
-
configStatus: {
|
|
894
|
-
operational: true,
|
|
895
|
-
onlineReserved: true,
|
|
896
|
-
liftMovementReversed: false,
|
|
897
|
-
liftPositionAware: true,
|
|
898
|
-
tiltPositionAware: true,
|
|
899
|
-
liftEncoderControlled: true,
|
|
900
|
-
tiltEncoderControlled: true,
|
|
901
|
-
},
|
|
902
|
-
},
|
|
903
|
-
},
|
|
904
|
-
handlers: {
|
|
905
|
-
windowCovering: {
|
|
906
|
-
// Called when user sets lift position
|
|
907
|
-
goToLiftPercentage: async (request) => {
|
|
908
|
-
const percent = (request.targetPercent / 100).toFixed(0);
|
|
909
|
-
this.log.info(`[Venetian Blind] ✓ Handler \`goToLiftPercentage\` called: ${request.targetPercent} (${percent}% open)`);
|
|
910
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-venetian-blind'), 'windowCovering', {
|
|
911
|
-
currentPositionLiftPercent100ths: request.targetPercent,
|
|
912
|
-
targetPositionLiftPercent100ths: request.targetPercent,
|
|
913
|
-
});
|
|
914
|
-
},
|
|
915
|
-
// Called when user sets tilt angle
|
|
916
|
-
goToTiltPercentage: async (request) => {
|
|
917
|
-
const percent = (request.targetPercent / 100).toFixed(0);
|
|
918
|
-
this.log.info(`[Venetian Blind] ✓ Handler \`goToTiltPercentage\` called: ${request.targetPercent} (${percent}% tilted)`);
|
|
919
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-venetian-blind'), 'windowCovering', {
|
|
920
|
-
currentPositionTiltPercent100ths: request.targetPercent,
|
|
921
|
-
targetPositionTiltPercent100ths: request.targetPercent,
|
|
922
|
-
});
|
|
923
|
-
},
|
|
924
|
-
upOrOpen: async () => {
|
|
925
|
-
this.log.info('[Venetian Blind] ✓ Handler `upOrOpen` called');
|
|
926
|
-
},
|
|
927
|
-
downOrClose: async () => {
|
|
928
|
-
this.log.info('[Venetian Blind] ✓ Handler `downOrClose` called');
|
|
929
|
-
},
|
|
930
|
-
stopMotion: async () => {
|
|
931
|
-
this.log.info('[Venetian Blind] ✓ Handler `stopMotion` called');
|
|
932
|
-
},
|
|
933
|
-
},
|
|
934
|
-
},
|
|
935
|
-
});
|
|
936
|
-
this.log.info(`✓ Registered ${accessories.length} window covering accessories`);
|
|
937
|
-
for (const acc of accessories) {
|
|
938
|
-
this.log.info(` - ${acc.displayName}`);
|
|
230
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
231
|
+
const accessories = [
|
|
232
|
+
...registerThermostat(context),
|
|
233
|
+
...registerFan(context),
|
|
234
|
+
];
|
|
235
|
+
if (accessories.length > 0) {
|
|
236
|
+
this.log.info(`✓ Registered ${accessories.length} HVAC device(s)`);
|
|
237
|
+
for (const acc of accessories) {
|
|
238
|
+
this.log.info(` - ${acc.displayName}`);
|
|
239
|
+
}
|
|
240
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
939
241
|
}
|
|
940
|
-
// Register all window covering accessories
|
|
941
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
942
242
|
}
|
|
943
243
|
/**
|
|
944
|
-
*
|
|
244
|
+
* Section 12: Robotic Devices (Matter Spec § 12)
|
|
245
|
+
*
|
|
246
|
+
* ⚠️ IMPORTANT: RVC devices are published as external accessories
|
|
247
|
+
* Apple Home requires RVC devices to be on their own dedicated Matter bridge.
|
|
248
|
+
* Using publishExternalAccessories ensures each RVC device gets its own bridge.
|
|
945
249
|
*/
|
|
946
|
-
|
|
250
|
+
registerSection12Robotic() {
|
|
947
251
|
this.log.info('═'.repeat(80));
|
|
948
|
-
this.log.info('
|
|
252
|
+
this.log.info('Section 12: Robotic Devices (Matter Spec § 12)');
|
|
949
253
|
this.log.info('═'.repeat(80));
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
supportedModes: [
|
|
963
|
-
{ label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] }, // 16384 = Idle tag
|
|
964
|
-
{ label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] }, // 16385 = Cleaning tag
|
|
965
|
-
{ label: 'Mapping', mode: 2, modeTags: [{ value: 16386 }] }, // 16386 = Mapping tag
|
|
966
|
-
],
|
|
967
|
-
// Current mode
|
|
968
|
-
currentMode: 0, // Idle
|
|
969
|
-
},
|
|
970
|
-
rvcOperationalState: {
|
|
971
|
-
// Operational state list (must include at least an error state)
|
|
972
|
-
operationalStateList: [
|
|
973
|
-
{ operationalStateId: 0 }, // Stopped
|
|
974
|
-
{ operationalStateId: 1 }, // Running
|
|
975
|
-
{ operationalStateId: 2 }, // Paused
|
|
976
|
-
{ operationalStateId: 3 }, // Error (required)
|
|
977
|
-
{ operationalStateId: 64 }, // SeekingCharger
|
|
978
|
-
{ operationalStateId: 65 }, // Charging
|
|
979
|
-
{ operationalStateId: 66 }, // Docked
|
|
980
|
-
],
|
|
981
|
-
// Current operational state (just the ID, not an object)
|
|
982
|
-
operationalState: 66, // Docked
|
|
983
|
-
// Error state
|
|
984
|
-
operationalError: {
|
|
985
|
-
errorStateId: 0, // No error
|
|
986
|
-
},
|
|
987
|
-
},
|
|
988
|
-
rvcCleanMode: {
|
|
989
|
-
// Supported clean modes (0=Vacuum, 1=Mop, 2=Vacuum+Mop)
|
|
990
|
-
supportedModes: [
|
|
991
|
-
{ label: 'Vacuum', mode: 0, modeTags: [] },
|
|
992
|
-
{ label: 'Mop', mode: 1, modeTags: [] },
|
|
993
|
-
{ label: 'Vacuum & Mop', mode: 2, modeTags: [] },
|
|
994
|
-
],
|
|
995
|
-
// Current clean mode
|
|
996
|
-
currentMode: 0, // Vacuum
|
|
997
|
-
},
|
|
998
|
-
},
|
|
999
|
-
handlers: {
|
|
1000
|
-
rvcOperationalState: {
|
|
1001
|
-
// Called when user presses "pause" in Home app
|
|
1002
|
-
pause: async () => {
|
|
1003
|
-
this.log.info('[Robot Vacuum] ✓ Handler `pause` called - Pausing cleaning');
|
|
1004
|
-
// Update state to Paused (2)
|
|
1005
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 2 });
|
|
1006
|
-
},
|
|
1007
|
-
// Called when user presses "resume" or "start" in Home app
|
|
1008
|
-
resume: async () => {
|
|
1009
|
-
this.log.info('[Robot Vacuum] ✓ Handler `resume` called - Resuming cleaning');
|
|
1010
|
-
// Update state to Running (1)
|
|
1011
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 1 });
|
|
1012
|
-
},
|
|
1013
|
-
// Called when user sends robot to charging dock
|
|
1014
|
-
goHome: async () => {
|
|
1015
|
-
this.log.info('[Robot Vacuum] ✓ Handler `goHome` called - Returning to dock');
|
|
1016
|
-
// Update state to SeekingCharger (64)
|
|
1017
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 64 });
|
|
1018
|
-
// Simulate arriving at dock after 3 seconds
|
|
1019
|
-
setTimeout(async () => {
|
|
1020
|
-
this.log.info('[Robot Vacuum] → Arrived at dock, now docked');
|
|
1021
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 66 });
|
|
1022
|
-
}, 3000);
|
|
1023
|
-
},
|
|
1024
|
-
},
|
|
1025
|
-
rvcRunMode: {
|
|
1026
|
-
// Called when user changes run mode (Idle, Cleaning, Mapping)
|
|
1027
|
-
changeToMode: async (request) => {
|
|
1028
|
-
const modes = ['Idle', 'Cleaning', 'Mapping'];
|
|
1029
|
-
const modeName = modes[request.newMode] || `Unknown (${request.newMode})`;
|
|
1030
|
-
this.log.info(`[Robot Vacuum] ✓ Handler \`changeToMode\` called: ${request.newMode} (${modeName})`);
|
|
1031
|
-
// Update the current mode
|
|
1032
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcRunMode', { currentMode: request.newMode });
|
|
1033
|
-
},
|
|
1034
|
-
},
|
|
1035
|
-
rvcCleanMode: {
|
|
1036
|
-
// Called when user changes clean mode (Vacuum, Mop, Vacuum+Mop)
|
|
1037
|
-
changeToMode: async (request) => {
|
|
1038
|
-
const modes = ['Vacuum', 'Mop', 'Vacuum & Mop'];
|
|
1039
|
-
const modeName = modes[request.newMode] || `Unknown (${request.newMode})`;
|
|
1040
|
-
this.log.info(`[Robot Vacuum] ✓ Handler \`changeToMode\` called: ${request.newMode} (${modeName})`);
|
|
1041
|
-
// Update the current clean mode
|
|
1042
|
-
await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcCleanMode', { currentMode: request.newMode });
|
|
1043
|
-
},
|
|
1044
|
-
},
|
|
1045
|
-
},
|
|
1046
|
-
});
|
|
1047
|
-
this.log.info(`✓ Registered ${accessories.length} appliance accessories`);
|
|
1048
|
-
for (const acc of accessories) {
|
|
1049
|
-
this.log.info(` - ${acc.displayName}`);
|
|
254
|
+
const context = { api: this.api, log: this.log, config: this.config };
|
|
255
|
+
const accessories = [
|
|
256
|
+
...registerRoboticVacuumCleaner(context),
|
|
257
|
+
];
|
|
258
|
+
if (accessories.length > 0) {
|
|
259
|
+
this.log.info(`✓ Publishing ${accessories.length} robotic device(s) as external accessories`);
|
|
260
|
+
for (const acc of accessories) {
|
|
261
|
+
this.log.info(` - ${acc.displayName} (dedicated bridge for Apple Home compatibility)`);
|
|
262
|
+
}
|
|
263
|
+
// Use publishExternalAccessories to give each RVC device its own bridge
|
|
264
|
+
// This is required for Apple Home compatibility
|
|
265
|
+
this.api.matter.publishExternalAccessories(PLUGIN_NAME, accessories);
|
|
1050
266
|
}
|
|
1051
|
-
// Register all appliance accessories
|
|
1052
|
-
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
1053
267
|
}
|
|
1054
268
|
}
|
|
1055
269
|
//# sourceMappingURL=platform.js.map
|