@homebridge-plugins/homebridge-matter 0.0.4 → 0.0.6

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/dist/platform.js CHANGED
@@ -53,13 +53,16 @@ export class ExampleHomebridgePlatform {
53
53
  this.log.info('Matter cached accessories:', this.matterAccessories.size);
54
54
  this.log.info('='.repeat(80));
55
55
  this.log.info('Registering Matter accessories...');
56
+ // Remove disabled accessories that are cached
57
+ this.removeDisabledAccessories();
56
58
  // Register each device type
57
59
  this.registerLightingDevices();
58
60
  this.registerSwitchesAndOutlets();
59
- // this.registerSensors();
60
- // this.registerHVAC();
61
- // this.registerSecurity();
62
- // this.registerOtherDevices();
61
+ this.registerSensors();
62
+ this.registerHVAC();
63
+ this.registerSecurity();
64
+ this.registerWindowCoverings();
65
+ this.registerAppliances();
63
66
  this.log.info('Finished registering Matter accessories');
64
67
  // You can read current state using the API
65
68
  const onOffState = this.api.matter.getAccessoryState('matter-dimmable-light', this.api.matter.clusterNames.OnOff);
@@ -82,6 +85,53 @@ export class ExampleHomebridgePlatform {
82
85
  // onOff: true,
83
86
  // })
84
87
  }
88
+ /**
89
+ * Remove disabled accessories from cache
90
+ */
91
+ removeDisabledAccessories() {
92
+ const accessoriesToRemove = [];
93
+ // Define mapping of config flags to UUIDs
94
+ const configMap = [
95
+ { enabled: this.config.enableOnOffLight, uuid: this.api.matter.uuid.generate('matter-onoff-light'), name: 'On/Off Light' },
96
+ { enabled: this.config.enableDimmableLight, uuid: this.api.matter.uuid.generate('matter-dimmable-light'), name: 'Dimmable Light' },
97
+ { enabled: this.config.enableColourTemperatureLight, uuid: this.api.matter.uuid.generate('matter-colour-temp-light'), name: 'Colour Temperature Light' },
98
+ { enabled: this.config.enableColourLight, uuid: this.api.matter.uuid.generate('matter-colour-light'), name: 'Colour Light' },
99
+ { enabled: this.config.enableExtendedColourLight, uuid: this.api.matter.uuid.generate('matter-extended-colour-light'), name: 'Extended Colour Light' },
100
+ { enabled: this.config.enableOnOffSwitch, uuid: this.api.matter.uuid.generate('matter-onoff-switch'), name: 'On/Off Switch' },
101
+ { enabled: this.config.enableOnOffOutlet, uuid: this.api.matter.uuid.generate('matter-onoff-outlet'), name: 'On/Off Outlet' },
102
+ { enabled: this.config.enableDimmableOutlet, uuid: this.api.matter.uuid.generate('matter-dimmable-outlet'), name: 'Dimmable Outlet' },
103
+ { enabled: this.config.enableTemperatureSensor, uuid: this.api.hap.uuid.generate('matter-temperature-sensor'), name: 'Temperature Sensor' },
104
+ { enabled: this.config.enableHumiditySensor, uuid: this.api.hap.uuid.generate('matter-humidity-sensor'), name: 'Humidity Sensor' },
105
+ { enabled: this.config.enableLightSensor, uuid: this.api.hap.uuid.generate('matter-light-sensor'), name: 'Light Sensor' },
106
+ { enabled: this.config.enableMotionSensor, uuid: this.api.hap.uuid.generate('matter-motion-sensor'), name: 'Motion Sensor' },
107
+ { enabled: this.config.enableContactSensor, uuid: this.api.hap.uuid.generate('matter-contact-sensor'), name: 'Contact Sensor' },
108
+ { enabled: this.config.enableLeakSensor, uuid: this.api.hap.uuid.generate('matter-leak-sensor'), name: 'Leak Sensor' },
109
+ { enabled: this.config.enableSmokeSensor, uuid: this.api.hap.uuid.generate('matter-smoke-sensor'), name: 'Smoke Sensor' },
110
+ { enabled: this.config.enableThermostat, uuid: this.api.matter.uuid.generate('matter-thermostat'), name: 'Thermostat' },
111
+ { enabled: this.config.enableFan, uuid: this.api.matter.uuid.generate('matter-fan'), name: 'Fan' },
112
+ { enabled: this.config.enableDoorLock, uuid: this.api.matter.uuid.generate('matter-door-lock'), name: 'Door Lock' },
113
+ { enabled: this.config.enableGarageDoor, uuid: this.api.matter.uuid.generate('matter-garage-door'), name: 'Garage Door' },
114
+ { enabled: this.config.enableWindowBlind, uuid: this.api.matter.uuid.generate('matter-window-blind'), name: 'Window Blind' },
115
+ { enabled: this.config.enableVenetianBlind, uuid: this.api.matter.uuid.generate('matter-venetian-blind'), name: 'Venetian Blind' },
116
+ { enabled: this.config.enableRobotVacuum, uuid: this.api.matter.uuid.generate('matter-robot-vacuum'), name: 'Robot Vacuum' },
117
+ ];
118
+ // Check each config entry
119
+ for (const item of configMap) {
120
+ // If disabled and exists in cache, mark for removal
121
+ if (!item.enabled && this.matterAccessories.has(item.uuid)) {
122
+ accessoriesToRemove.push(item);
123
+ }
124
+ }
125
+ // Remove disabled accessories
126
+ if (accessoriesToRemove.length > 0) {
127
+ this.log.info(`Removing ${accessoriesToRemove.length} disabled accessories...`);
128
+ for (const item of accessoriesToRemove) {
129
+ this.log.info(` - Removing: ${item.name}`);
130
+ this.api.matter.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [this.matterAccessories.get(item.uuid)]);
131
+ this.matterAccessories.delete(item.uuid);
132
+ }
133
+ }
134
+ }
85
135
  /**
86
136
  * Lighting Devices
87
137
  */
@@ -93,242 +143,254 @@ export class ExampleHomebridgePlatform {
93
143
  const uuidLightExtendedColour = this.api.matter.uuid.generate('matter-extended-colour-light');
94
144
  // 1. On/Off Light
95
145
  const accessories = [];
96
- accessories.push({
97
- uuid: uuidLightOnOff,
98
- displayName: 'On/Off Light',
99
- deviceType: this.api.matter.deviceTypes.OnOffLight,
100
- serialNumber: 'LIGHT-001',
101
- manufacturer: 'Matter Examples',
102
- model: 'OnOffLight v1',
103
- clusters: {
104
- onOff: {
105
- onOff: true,
106
- },
107
- },
108
- // These are called when the user controls the accessory via the Home app
109
- handlers: {
110
- onOff: {
111
- on: async ( /* no args */) => {
112
- this.log.info('[On/Off Light] ✓ Handler `on` called (user controlled via Home app)');
113
- },
114
- off: async ( /* no args */) => {
115
- this.log.info('[On/Off Light] ✓ Handler `off` called (user controlled via Home app)');
116
- },
117
- },
118
- },
119
- });
146
+ if (this.config.enableOnOffLight) {
147
+ accessories.push({
148
+ uuid: uuidLightOnOff,
149
+ displayName: 'On/Off Light',
150
+ deviceType: this.api.matter.deviceTypes.OnOffLight,
151
+ serialNumber: 'LIGHT-001',
152
+ manufacturer: 'Matter Examples',
153
+ model: 'OnOffLight v1',
154
+ clusters: {
155
+ onOff: {
156
+ onOff: true,
157
+ },
158
+ },
159
+ // These are called when the user controls the accessory via the Home app
160
+ handlers: {
161
+ onOff: {
162
+ on: async ( /* no args */) => {
163
+ this.log.info('[On/Off Light] ✓ Handler `on` called (user controlled via Home app)');
164
+ },
165
+ off: async ( /* no args */) => {
166
+ this.log.info('[On/Off Light] ✓ Handler `off` called (user controlled via Home app)');
167
+ },
168
+ },
169
+ },
170
+ });
171
+ }
120
172
  // 2. Dimmable Light
121
- accessories.push({
122
- uuid: uuidLightDimmable,
123
- displayName: 'Dimmable Light',
124
- deviceType: this.api.matter.deviceTypes.DimmableLight,
125
- serialNumber: 'LIGHT-002',
126
- manufacturer: 'Matter Examples',
127
- model: 'DimmableLight v1',
128
- clusters: {
129
- onOff: {
130
- onOff: false,
131
- },
132
- levelControl: {
133
- currentLevel: 127,
134
- minLevel: 1,
135
- maxLevel: 254,
136
- },
137
- },
138
- // These are called when the user controls the accessory via the Home app
139
- handlers: {
140
- onOff: {
141
- on: async () => {
142
- this.log.info('[Dimmable Light] ✓ Handler `on` called (user controlled via Home app)');
143
- },
144
- off: async () => {
145
- this.log.info('[Dimmable Light] ✓ Handler `off` called (user controlled via Home app)');
146
- },
147
- },
148
- levelControl: {
149
- moveToLevelWithOnOff: async (request) => {
150
- const { level } = request;
151
- this.log.info(`[Dimmable Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
152
- },
153
- },
154
- },
155
- });
173
+ if (this.config.enableDimmableLight) {
174
+ accessories.push({
175
+ uuid: uuidLightDimmable,
176
+ displayName: 'Dimmable Light',
177
+ deviceType: this.api.matter.deviceTypes.DimmableLight,
178
+ serialNumber: 'LIGHT-002',
179
+ manufacturer: 'Matter Examples',
180
+ model: 'DimmableLight v1',
181
+ clusters: {
182
+ onOff: {
183
+ onOff: false,
184
+ },
185
+ levelControl: {
186
+ currentLevel: 127,
187
+ minLevel: 1,
188
+ maxLevel: 254,
189
+ },
190
+ },
191
+ // These are called when the user controls the accessory via the Home app
192
+ handlers: {
193
+ onOff: {
194
+ on: async () => {
195
+ this.log.info('[Dimmable Light] ✓ Handler `on` called (user controlled via Home app)');
196
+ },
197
+ off: async () => {
198
+ this.log.info('[Dimmable Light] ✓ Handler `off` called (user controlled via Home app)');
199
+ },
200
+ },
201
+ levelControl: {
202
+ moveToLevelWithOnOff: async (request) => {
203
+ const { level } = request;
204
+ this.log.info(`[Dimmable Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
205
+ },
206
+ },
207
+ },
208
+ });
209
+ }
156
210
  // 3. Colour Temperature Light
157
- accessories.push({
158
- uuid: uuidLightColourTemp,
159
- displayName: 'Colour Temperature Light',
160
- deviceType: this.api.matter.deviceTypes.ColorTemperatureLight,
161
- serialNumber: 'LIGHT-003',
162
- manufacturer: 'Matter Examples',
163
- model: 'ColourTempLight v1',
164
- clusters: {
165
- onOff: {
166
- onOff: false,
167
- },
168
- levelControl: {
169
- currentLevel: 127,
170
- minLevel: 1,
171
- maxLevel: 254,
172
- },
173
- colorControl: {
174
- colorMode: 2, // Colour temperature mode
175
- colorTemperatureMireds: 250, // ~4000K
176
- colorTempPhysicalMinMireds: 147, // 6800K (coolest)
177
- colorTempPhysicalMaxMireds: 454, // 2200K (warmest)
178
- coupleColorTempToLevelMinMireds: 147,
179
- },
180
- },
181
- // These are called when the user controls the accessory via the Home app
182
- handlers: {
183
- onOff: {
184
- on: async ( /* no args */) => {
185
- this.log.info('[Colour Temp Light] handler `on` called (user controlled via Home app)');
186
- },
187
- off: async ( /* no args */) => {
188
- this.log.info('[Colour Temp Light] Turned `off` called (user controlled via Home app)');
189
- },
190
- },
191
- levelControl: {
192
- moveToLevelWithOnOff: async (request) => {
193
- const { level } = request;
194
- this.log.info(`[Colour Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
195
- },
196
- },
197
- colorControl: {
198
- moveToColorTemperatureLogic: async (request) => {
199
- const { targetMireds, transitionTime } = request;
200
- const kelvin = Math.round(1000000 / targetMireds);
201
- this.log.info(`[Colour Temp Light] Handler \`moveToColorTemperatureLogic\` called with ${targetMireds} mireds (~${kelvin}K), transition: ${transitionTime}s`);
202
- },
203
- },
204
- },
205
- });
211
+ if (this.config.enableColourTemperatureLight) {
212
+ accessories.push({
213
+ uuid: uuidLightColourTemp,
214
+ displayName: 'Colour Temperature Light',
215
+ deviceType: this.api.matter.deviceTypes.ColorTemperatureLight,
216
+ serialNumber: 'LIGHT-003',
217
+ manufacturer: 'Matter Examples',
218
+ model: 'ColourTempLight v1',
219
+ clusters: {
220
+ onOff: {
221
+ onOff: false,
222
+ },
223
+ levelControl: {
224
+ currentLevel: 127,
225
+ minLevel: 1,
226
+ maxLevel: 254,
227
+ },
228
+ colorControl: {
229
+ colorMode: 2, // Colour temperature mode
230
+ colorTemperatureMireds: 250, // ~4000K
231
+ colorTempPhysicalMinMireds: 147, // 6800K (coolest)
232
+ colorTempPhysicalMaxMireds: 454, // 2200K (warmest)
233
+ coupleColorTempToLevelMinMireds: 147,
234
+ },
235
+ },
236
+ // These are called when the user controls the accessory via the Home app
237
+ handlers: {
238
+ onOff: {
239
+ on: async ( /* no args */) => {
240
+ this.log.info('[Colour Temp Light] handler `on` called (user controlled via Home app)');
241
+ },
242
+ off: async ( /* no args */) => {
243
+ this.log.info('[Colour Temp Light] Turned `off` called (user controlled via Home app)');
244
+ },
245
+ },
246
+ levelControl: {
247
+ moveToLevelWithOnOff: async (request) => {
248
+ const { level } = request;
249
+ this.log.info(`[Colour Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
250
+ },
251
+ },
252
+ colorControl: {
253
+ moveToColorTemperatureLogic: async (request) => {
254
+ const { targetMireds, transitionTime } = request;
255
+ const kelvin = Math.round(1000000 / targetMireds);
256
+ this.log.info(`[Colour Temp Light] ✓ Handler \`moveToColorTemperatureLogic\` called with ${targetMireds} mireds (~${kelvin}K), transition: ${transitionTime}s`);
257
+ },
258
+ },
259
+ },
260
+ });
261
+ }
206
262
  // 4. Colour Light (Hue/Saturation ONLY - no CCT)
207
- accessories.push({
208
- uuid: uuidLightColour,
209
- displayName: 'Colour Light (HS)',
210
- deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
211
- serialNumber: 'LIGHT-004',
212
- manufacturer: 'Matter Examples',
213
- model: 'ColorLight v1',
214
- clusters: {
215
- onOff: {
216
- onOff: false,
217
- },
218
- levelControl: {
219
- currentLevel: 127,
220
- minLevel: 1,
221
- maxLevel: 254,
222
- },
223
- colorControl: {
224
- colorMode: 0, // Hue/Saturation mode
225
- currentHue: 0, // Red (0 degrees)
226
- currentSaturation: 254, // Full saturation
227
- currentX: 41942, // Also provide XY for compatibility
228
- currentY: 21626,
229
- },
230
- },
231
- // These are called when the user controls the accessory via the Home app
232
- handlers: {
233
- onOff: {
234
- on: async () => {
235
- this.log.info('[Colour Light HS] ✓ Handler `on` called (user controlled via Home app)');
236
- },
237
- off: async () => {
238
- this.log.info('[Colour Light HS] ✓ Handler `off` called (user controlled via Home app)');
239
- },
240
- },
241
- levelControl: {
242
- moveToLevelWithOnOff: async (request) => {
243
- const { level } = request;
244
- this.log.info(`[Colour Light HS] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
245
- },
246
- },
247
- colorControl: {
248
- moveToColorLogic: async (request) => {
249
- const { targetX, targetY, transitionTime } = request;
250
- const xFloat = (targetX / 65535).toFixed(4);
251
- const yFloat = (targetY / 65535).toFixed(4);
252
- this.log.info(`[Colour Light HS] Handler \`moveToColorLogic\` called with x=${targetX} (~${xFloat}), y=${targetY} (~${yFloat}), transition: ${transitionTime}s`);
253
- },
254
- moveToHueAndSaturationLogic: async (request) => {
255
- const { targetHue, targetSaturation, transitionTime } = request;
256
- const hueDegrees = Math.round((targetHue / 254) * 360);
257
- const saturationPercent = Math.round((targetSaturation / 254) * 100);
258
- this.log.info(`[Colour Light HS] ✓ Handler \`moveToHueAndSaturationLogic\` called with hue=${targetHue} (~${hueDegrees}°), saturation=${targetSaturation} (~${saturationPercent}%), transition: ${transitionTime}s`);
259
- },
260
- // NOTE: No moveToColorTemperatureLogic handler - this light only supports color, not CCT
261
- },
262
- },
263
- });
263
+ if (this.config.enableColourLight) {
264
+ accessories.push({
265
+ uuid: uuidLightColour,
266
+ displayName: 'Colour Light (HS)',
267
+ deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
268
+ serialNumber: 'LIGHT-004',
269
+ manufacturer: 'Matter Examples',
270
+ model: 'ColorLight v1',
271
+ clusters: {
272
+ onOff: {
273
+ onOff: false,
274
+ },
275
+ levelControl: {
276
+ currentLevel: 127,
277
+ minLevel: 1,
278
+ maxLevel: 254,
279
+ },
280
+ colorControl: {
281
+ colorMode: 0, // Hue/Saturation mode
282
+ currentHue: 0, // Red (0 degrees)
283
+ currentSaturation: 254, // Full saturation
284
+ currentX: 41942, // Also provide XY for compatibility
285
+ currentY: 21626,
286
+ },
287
+ },
288
+ // These are called when the user controls the accessory via the Home app
289
+ handlers: {
290
+ onOff: {
291
+ on: async () => {
292
+ this.log.info('[Colour Light HS] ✓ Handler `on` called (user controlled via Home app)');
293
+ },
294
+ off: async () => {
295
+ this.log.info('[Colour Light HS] ✓ Handler `off` called (user controlled via Home app)');
296
+ },
297
+ },
298
+ levelControl: {
299
+ moveToLevelWithOnOff: async (request) => {
300
+ const { level } = request;
301
+ this.log.info(`[Colour Light HS] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
302
+ },
303
+ },
304
+ colorControl: {
305
+ moveToColorLogic: async (request) => {
306
+ const { targetX, targetY, transitionTime } = request;
307
+ const xFloat = (targetX / 65535).toFixed(4);
308
+ const yFloat = (targetY / 65535).toFixed(4);
309
+ this.log.info(`[Colour Light HS] ✓ Handler \`moveToColorLogic\` called with x=${targetX} (~${xFloat}), y=${targetY} (~${yFloat}), transition: ${transitionTime}s`);
310
+ },
311
+ moveToHueAndSaturationLogic: async (request) => {
312
+ const { targetHue, targetSaturation, transitionTime } = request;
313
+ const hueDegrees = Math.round((targetHue / 254) * 360);
314
+ const saturationPercent = Math.round((targetSaturation / 254) * 100);
315
+ this.log.info(`[Colour Light HS] ✓ Handler \`moveToHueAndSaturationLogic\` called with hue=${targetHue} (~${hueDegrees}°), saturation=${targetSaturation} (~${saturationPercent}%), transition: ${transitionTime}s`);
316
+ },
317
+ // NOTE: No moveToColorTemperatureLogic handler - this light only supports color, not CCT
318
+ },
319
+ },
320
+ });
321
+ }
264
322
  // 5. Extended Colour Light (Hue/Saturation + CCT)
265
- accessories.push({
266
- uuid: uuidLightExtendedColour,
267
- displayName: 'Extended Colour Light (HS+CCT)',
268
- deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
269
- serialNumber: 'LIGHT-005',
270
- manufacturer: 'Matter Examples',
271
- model: 'ExtendedColorLight v1',
272
- clusters: {
273
- onOff: {
274
- onOff: false,
275
- },
276
- levelControl: {
277
- currentLevel: 127,
278
- minLevel: 1,
279
- maxLevel: 254,
280
- },
281
- colorControl: {
282
- colorMode: 0, // Hue/Saturation mode
283
- currentHue: 0, // Red (0 degrees)
284
- currentSaturation: 254, // Full saturation
285
- currentX: 41942, // Also provide XY for compatibility
286
- currentY: 21626,
287
- colorTemperatureMireds: 250, // ~4000K (for CCT mode)
288
- colorTempPhysicalMinMireds: 147, // 6800K (coolest)
289
- colorTempPhysicalMaxMireds: 454, // 2200K (warmest)
290
- coupleColorTempToLevelMinMireds: 147,
291
- },
292
- },
293
- // These are called when the user controls the accessory via the Home app
294
- handlers: {
295
- onOff: {
296
- on: async () => {
297
- this.log.info('[Extended Colour Light] ✓ Handler `on` called (user controlled via Home app)');
298
- },
299
- off: async () => {
300
- this.log.info('[Extended Colour Light] ✓ Handler `off` called (user controlled via Home app)');
301
- },
302
- },
303
- levelControl: {
304
- moveToLevelWithOnOff: async (request) => {
305
- const { level } = request;
306
- this.log.info(`[Extended Colour Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
307
- },
308
- },
309
- colorControl: {
310
- moveToColorLogic: async (request) => {
311
- const { targetX, targetY, transitionTime } = request;
312
- const xFloat = (targetX / 65535).toFixed(4);
313
- const yFloat = (targetY / 65535).toFixed(4);
314
- this.log.info(`[Extended Colour Light] Handler \`moveToColorLogic\` called with x=${targetX} (~${xFloat}), y=${targetY} (~${yFloat}), transition: ${transitionTime}s`);
315
- },
316
- moveToHueAndSaturationLogic: async (request) => {
317
- const { targetHue, targetSaturation, transitionTime } = request;
318
- const hueDegrees = Math.round((targetHue / 254) * 360);
319
- const saturationPercent = Math.round((targetSaturation / 254) * 100);
320
- this.log.info(`[Extended Colour Light] ✓ Handler \`moveToHueAndSaturationLogic\` called with hue=${targetHue} (~${hueDegrees}°), saturation=${targetSaturation} (~${saturationPercent}%), transition: ${transitionTime}s`);
321
- },
322
- moveToColorTemperatureLogic: async (request) => {
323
- const { targetMireds, transitionTime } = request;
324
- const kelvin = Math.round(1000000 / targetMireds);
325
- this.log.info(`[Extended Colour Light] Handler \`moveToColorTemperatureLogic\` called with ${targetMireds} mireds (~${kelvin}K), transition: ${transitionTime}s`);
326
- },
327
- },
328
- },
329
- });
323
+ if (this.config.enableExtendedColourLight) {
324
+ accessories.push({
325
+ uuid: uuidLightExtendedColour,
326
+ displayName: 'Extended Colour Light (HS+CCT)',
327
+ deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
328
+ serialNumber: 'LIGHT-005',
329
+ manufacturer: 'Matter Examples',
330
+ model: 'ExtendedColorLight v1',
331
+ clusters: {
332
+ onOff: {
333
+ onOff: false,
334
+ },
335
+ levelControl: {
336
+ currentLevel: 127,
337
+ minLevel: 1,
338
+ maxLevel: 254,
339
+ },
340
+ colorControl: {
341
+ colorMode: 0, // Hue/Saturation mode
342
+ currentHue: 0, // Red (0 degrees)
343
+ currentSaturation: 254, // Full saturation
344
+ currentX: 41942, // Also provide XY for compatibility
345
+ currentY: 21626,
346
+ colorTemperatureMireds: 250, // ~4000K (for CCT mode)
347
+ colorTempPhysicalMinMireds: 147, // 6800K (coolest)
348
+ colorTempPhysicalMaxMireds: 454, // 2200K (warmest)
349
+ coupleColorTempToLevelMinMireds: 147,
350
+ },
351
+ },
352
+ // These are called when the user controls the accessory via the Home app
353
+ handlers: {
354
+ onOff: {
355
+ on: async () => {
356
+ this.log.info('[Extended Colour Light] ✓ Handler `on` called (user controlled via Home app)');
357
+ },
358
+ off: async () => {
359
+ this.log.info('[Extended Colour Light] ✓ Handler `off` called (user controlled via Home app)');
360
+ },
361
+ },
362
+ levelControl: {
363
+ moveToLevelWithOnOff: async (request) => {
364
+ const { level } = request;
365
+ this.log.info(`[Extended Colour Light] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
366
+ },
367
+ },
368
+ colorControl: {
369
+ moveToColorLogic: async (request) => {
370
+ const { targetX, targetY, transitionTime } = request;
371
+ const xFloat = (targetX / 65535).toFixed(4);
372
+ const yFloat = (targetY / 65535).toFixed(4);
373
+ this.log.info(`[Extended Colour Light] ✓ Handler \`moveToColorLogic\` called with x=${targetX} (~${xFloat}), y=${targetY} (~${yFloat}), transition: ${transitionTime}s`);
374
+ },
375
+ moveToHueAndSaturationLogic: async (request) => {
376
+ const { targetHue, targetSaturation, transitionTime } = request;
377
+ const hueDegrees = Math.round((targetHue / 254) * 360);
378
+ const saturationPercent = Math.round((targetSaturation / 254) * 100);
379
+ this.log.info(`[Extended Colour Light] ✓ Handler \`moveToHueAndSaturationLogic\` called with hue=${targetHue} (~${hueDegrees}°), saturation=${targetSaturation} (~${saturationPercent}%), transition: ${transitionTime}s`);
380
+ },
381
+ moveToColorTemperatureLogic: async (request) => {
382
+ const { targetMireds, transitionTime } = request;
383
+ const kelvin = Math.round(1000000 / targetMireds);
384
+ this.log.info(`[Extended Colour Light] ✓ Handler \`moveToColorTemperatureLogic\` called with ${targetMireds} mireds (~${kelvin}K), transition: ${transitionTime}s`);
385
+ },
386
+ },
387
+ },
388
+ });
389
+ }
330
390
  // Register all lighting accessories
331
- this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
391
+ if (accessories.length > 0) {
392
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
393
+ }
332
394
  }
333
395
  /**
334
396
  * Switches and Outlets
@@ -339,92 +401,98 @@ export class ExampleHomebridgePlatform {
339
401
  const uuidDimmableOutlet = this.api.matter.uuid.generate('matter-dimmable-outlet');
340
402
  const accessories = [];
341
403
  // 1. On/Off Switch
342
- accessories.push({
343
- uuid: uuidSwitch,
344
- displayName: 'On/Off Switch',
345
- deviceType: this.api.matter.deviceTypes.OnOffSwitch,
346
- serialNumber: 'SWITCH-001',
347
- manufacturer: 'Matter Examples',
348
- model: 'OnOffSwitch v1',
349
- clusters: {
350
- onOff: {
351
- onOff: false,
352
- },
353
- },
354
- handlers: {
355
- onOff: {
356
- on: async () => {
357
- this.log.info('[On/Off Switch] ✓ Handler `on` called (user controlled via Home app)');
358
- },
359
- off: async () => {
360
- this.log.info('[On/Off Switch] ✓ Handler `off` called (user controlled via Home app)');
361
- },
362
- },
363
- },
364
- });
404
+ if (this.config.enableOnOffSwitch) {
405
+ accessories.push({
406
+ uuid: uuidSwitch,
407
+ displayName: 'On/Off Switch',
408
+ deviceType: this.api.matter.deviceTypes.OnOffSwitch,
409
+ serialNumber: 'SWITCH-001',
410
+ manufacturer: 'Matter Examples',
411
+ model: 'OnOffSwitch v1',
412
+ clusters: {
413
+ onOff: {
414
+ onOff: false,
415
+ },
416
+ },
417
+ handlers: {
418
+ onOff: {
419
+ on: async () => {
420
+ this.log.info('[On/Off Switch] ✓ Handler `on` called (user controlled via Home app)');
421
+ },
422
+ off: async () => {
423
+ this.log.info('[On/Off Switch] ✓ Handler `off` called (user controlled via Home app)');
424
+ },
425
+ },
426
+ },
427
+ });
428
+ }
365
429
  // 2. On/Off Outlet (Smart Plug)
366
- accessories.push({
367
- uuid: uuidOutlet,
368
- displayName: 'On/Off Outlet',
369
- deviceType: this.api.matter.deviceTypes.OnOffOutlet,
370
- serialNumber: 'OUTLET-001',
371
- manufacturer: 'Matter Examples',
372
- model: 'OnOffOutlet v1',
373
- clusters: {
374
- onOff: {
375
- onOff: false,
376
- },
377
- },
378
- handlers: {
379
- onOff: {
380
- on: async () => {
381
- this.log.info('[On/Off Outlet] ✓ Handler `on` called (user controlled via Home app)');
382
- },
383
- off: async () => {
384
- this.log.info('[On/Off Outlet] ✓ Handler `off` called (user controlled via Home app)');
385
- },
386
- },
387
- },
388
- });
430
+ if (this.config.enableOnOffOutlet) {
431
+ accessories.push({
432
+ uuid: uuidOutlet,
433
+ displayName: 'On/Off Outlet',
434
+ deviceType: this.api.matter.deviceTypes.OnOffOutlet,
435
+ serialNumber: 'OUTLET-001',
436
+ manufacturer: 'Matter Examples',
437
+ model: 'OnOffOutlet v1',
438
+ clusters: {
439
+ onOff: {
440
+ onOff: false,
441
+ },
442
+ },
443
+ handlers: {
444
+ onOff: {
445
+ on: async () => {
446
+ this.log.info('[On/Off Outlet] ✓ Handler `on` called (user controlled via Home app)');
447
+ },
448
+ off: async () => {
449
+ this.log.info('[On/Off Outlet] ✓ Handler `off` called (user controlled via Home app)');
450
+ },
451
+ },
452
+ },
453
+ });
454
+ }
389
455
  // 3. Dimmable Outlet
390
- accessories.push({
391
- uuid: uuidDimmableOutlet,
392
- displayName: 'Dimmable Outlet',
393
- deviceType: this.api.matter.deviceTypes.DimmableOutlet,
394
- serialNumber: 'OUTLET-002',
395
- manufacturer: 'Matter Examples',
396
- model: 'DimmableOutlet v1',
397
- clusters: {
398
- onOff: {
399
- onOff: false,
400
- },
401
- levelControl: {
402
- currentLevel: 127,
403
- minLevel: 1,
404
- maxLevel: 254,
405
- },
406
- },
407
- handlers: {
408
- onOff: {
409
- on: async () => {
410
- this.log.info('[Dimmable Outlet] ✓ Handler `on` called (user controlled via Home app)');
411
- },
412
- off: async () => {
413
- this.log.info('[Dimmable Outlet] ✓ Handler `off` called (user controlled via Home app)');
414
- },
415
- },
416
- levelControl: {
417
- moveToLevelWithOnOff: async (request) => {
418
- const { level } = request;
419
- this.log.info(`[Dimmable Outlet] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
420
- },
421
- },
422
- },
423
- });
456
+ if (this.config.enableDimmableOutlet) {
457
+ accessories.push({
458
+ uuid: uuidDimmableOutlet,
459
+ displayName: 'Dimmable Outlet',
460
+ deviceType: this.api.matter.deviceTypes.DimmableOutlet,
461
+ serialNumber: 'OUTLET-002',
462
+ manufacturer: 'Matter Examples',
463
+ model: 'DimmableOutlet v1',
464
+ clusters: {
465
+ onOff: {
466
+ onOff: false,
467
+ },
468
+ levelControl: {
469
+ currentLevel: 127,
470
+ minLevel: 1,
471
+ maxLevel: 254,
472
+ },
473
+ },
474
+ handlers: {
475
+ onOff: {
476
+ on: async () => {
477
+ this.log.info('[Dimmable Outlet] ✓ Handler `on` called (user controlled via Home app)');
478
+ },
479
+ off: async () => {
480
+ this.log.info('[Dimmable Outlet] ✓ Handler `off` called (user controlled via Home app)');
481
+ },
482
+ },
483
+ levelControl: {
484
+ moveToLevelWithOnOff: async (request) => {
485
+ const { level } = request;
486
+ this.log.info(`[Dimmable Outlet] ✓ Handler \`moveToLevel\` called with ${level} (${Math.round(level / 254 * 100)}%)`);
487
+ },
488
+ },
489
+ },
490
+ });
491
+ }
424
492
  // Register all switch/outlet accessories
425
- this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
426
- // Register sensor devices
427
- this.registerSensors();
493
+ if (accessories.length > 0) {
494
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
495
+ }
428
496
  }
429
497
  /**
430
498
  * Register Matter sensor accessories
@@ -435,130 +503,657 @@ export class ExampleHomebridgePlatform {
435
503
  this.log.info('═'.repeat(80));
436
504
  const accessories = [];
437
505
  // 1. Temperature Sensor
438
- accessories.push({
439
- uuid: this.api.hap.uuid.generate('matter-temperature-sensor'),
440
- displayName: 'Temperature Sensor',
441
- deviceType: this.api.matter.deviceTypes.TemperatureSensor,
442
- serialNumber: 'TEMP-001',
443
- manufacturer: 'Homebridge',
444
- model: 'Temperature Sensor Example',
445
- clusters: {
446
- temperatureMeasurement: {
447
- measuredValue: 2100, // 21.00°C (in hundredths of a degree Celsius)
448
- minMeasuredValue: -5000, // -50°C
449
- maxMeasuredValue: 10000, // 100°C
450
- },
451
- },
452
- });
506
+ if (this.config.enableTemperatureSensor) {
507
+ accessories.push({
508
+ uuid: this.api.hap.uuid.generate('matter-temperature-sensor'),
509
+ displayName: 'Temperature Sensor',
510
+ deviceType: this.api.matter.deviceTypes.TemperatureSensor,
511
+ serialNumber: 'TEMP-001',
512
+ manufacturer: 'Homebridge',
513
+ model: 'Temperature Sensor Example',
514
+ clusters: {
515
+ temperatureMeasurement: {
516
+ measuredValue: 2100, // 21.00°C (in hundredths of a degree Celsius)
517
+ minMeasuredValue: -5000, // -50°C
518
+ maxMeasuredValue: 10000, // 100°C
519
+ },
520
+ },
521
+ });
522
+ }
453
523
  // 2. Humidity Sensor
454
- accessories.push({
455
- uuid: this.api.hap.uuid.generate('matter-humidity-sensor'),
456
- displayName: 'Humidity Sensor',
457
- deviceType: this.api.matter.deviceTypes.HumiditySensor,
458
- serialNumber: 'HUM-001',
459
- manufacturer: 'Homebridge',
460
- model: 'Humidity Sensor Example',
461
- clusters: {
462
- relativeHumidityMeasurement: {
463
- measuredValue: 5500, // 55% (in hundredths of a percent)
464
- minMeasuredValue: 0,
465
- maxMeasuredValue: 10000, // 100%
466
- },
467
- },
468
- });
524
+ if (this.config.enableHumiditySensor) {
525
+ accessories.push({
526
+ uuid: this.api.hap.uuid.generate('matter-humidity-sensor'),
527
+ displayName: 'Humidity Sensor',
528
+ deviceType: this.api.matter.deviceTypes.HumiditySensor,
529
+ serialNumber: 'HUM-001',
530
+ manufacturer: 'Homebridge',
531
+ model: 'Humidity Sensor Example',
532
+ clusters: {
533
+ relativeHumidityMeasurement: {
534
+ measuredValue: 5500, // 55% (in hundredths of a percent)
535
+ minMeasuredValue: 0,
536
+ maxMeasuredValue: 10000, // 100%
537
+ },
538
+ },
539
+ });
540
+ }
469
541
  // 3. Light Sensor
470
- accessories.push({
471
- uuid: this.api.hap.uuid.generate('matter-light-sensor'),
472
- displayName: 'Light Sensor',
473
- deviceType: this.api.matter.deviceTypes.LightSensor,
474
- serialNumber: 'LIGHT-001',
475
- manufacturer: 'Homebridge',
476
- model: 'Light Sensor Example',
477
- clusters: {
478
- illuminanceMeasurement: {
479
- measuredValue: 5000, // 500 lux (in 10,000 * log10(lux) format)
480
- minMeasuredValue: 1,
481
- maxMeasuredValue: 65534,
482
- },
483
- },
484
- });
542
+ if (this.config.enableLightSensor) {
543
+ accessories.push({
544
+ uuid: this.api.hap.uuid.generate('matter-light-sensor'),
545
+ displayName: 'Light Sensor',
546
+ deviceType: this.api.matter.deviceTypes.LightSensor,
547
+ serialNumber: 'LIGHT-001',
548
+ manufacturer: 'Homebridge',
549
+ model: 'Light Sensor Example',
550
+ clusters: {
551
+ illuminanceMeasurement: {
552
+ measuredValue: 5000, // 500 lux (in 10,000 * log10(lux) format)
553
+ minMeasuredValue: 1,
554
+ maxMeasuredValue: 65534,
555
+ },
556
+ },
557
+ });
558
+ }
485
559
  // 4. Motion Sensor (Occupancy)
486
- // Note: OccupancySensorDevice requires specifying features (PIR, Ultrasonic, or PhysicalContact)
487
- const OccupancySensingServer = this.api.matter.deviceTypes.MotionSensor.requirements.OccupancySensingServer;
488
- const MotionSensorWithPIR = this.api.matter.deviceTypes.MotionSensor.with(OccupancySensingServer.with('PassiveInfrared'));
489
- accessories.push({
490
- uuid: this.api.hap.uuid.generate('matter-motion-sensor'),
491
- displayName: 'Motion Sensor',
492
- deviceType: MotionSensorWithPIR,
493
- serialNumber: 'MOTION-001',
494
- manufacturer: 'Homebridge',
495
- model: 'Motion Sensor Example',
496
- clusters: {
497
- occupancySensing: {
498
- occupancy: {
499
- occupied: false, // No motion detected
500
- },
501
- },
502
- },
503
- });
560
+ if (this.config.enableMotionSensor) {
561
+ // Note: OccupancySensorDevice requires specifying features (PIR, Ultrasonic, or PhysicalContact)
562
+ const OccupancySensingServer = this.api.matter.deviceTypes.MotionSensor.requirements.OccupancySensingServer;
563
+ const MotionSensorWithPIR = this.api.matter.deviceTypes.MotionSensor.with(OccupancySensingServer.with('PassiveInfrared'));
564
+ accessories.push({
565
+ uuid: this.api.hap.uuid.generate('matter-motion-sensor'),
566
+ displayName: 'Motion Sensor',
567
+ deviceType: MotionSensorWithPIR,
568
+ serialNumber: 'MOTION-001',
569
+ manufacturer: 'Homebridge',
570
+ model: 'Motion Sensor Example',
571
+ clusters: {
572
+ occupancySensing: {
573
+ occupancy: {
574
+ occupied: false, // No motion detected
575
+ },
576
+ },
577
+ },
578
+ });
579
+ }
504
580
  // 5. Contact Sensor
505
- accessories.push({
506
- uuid: this.api.hap.uuid.generate('matter-contact-sensor'),
507
- displayName: 'Contact Sensor',
508
- deviceType: this.api.matter.deviceTypes.ContactSensor,
509
- serialNumber: 'CONTACT-001',
510
- manufacturer: 'Homebridge',
511
- model: 'Contact Sensor Example',
512
- clusters: {
513
- booleanState: {
514
- stateValue: false, // Contact closed (false = closed, true = open)
515
- },
516
- },
517
- });
581
+ if (this.config.enableContactSensor) {
582
+ accessories.push({
583
+ uuid: this.api.hap.uuid.generate('matter-contact-sensor'),
584
+ displayName: 'Contact Sensor',
585
+ deviceType: this.api.matter.deviceTypes.ContactSensor,
586
+ serialNumber: 'CONTACT-001',
587
+ manufacturer: 'Homebridge',
588
+ model: 'Contact Sensor Example',
589
+ clusters: {
590
+ booleanState: {
591
+ stateValue: false, // Contact closed (false = closed, true = open)
592
+ },
593
+ },
594
+ });
595
+ }
518
596
  // 6. Leak Sensor
519
- accessories.push({
520
- uuid: this.api.hap.uuid.generate('matter-leak-sensor'),
521
- displayName: 'Leak Sensor',
522
- deviceType: this.api.matter.deviceTypes.LeakSensor,
523
- serialNumber: 'LEAK-001',
524
- manufacturer: 'Homebridge',
525
- model: 'Leak Sensor Example',
526
- clusters: {
527
- booleanState: {
528
- stateValue: false, // No leak detected (false = dry, true = leak)
529
- },
530
- },
531
- });
597
+ if (this.config.enableLeakSensor) {
598
+ accessories.push({
599
+ uuid: this.api.hap.uuid.generate('matter-leak-sensor'),
600
+ displayName: 'Leak Sensor',
601
+ deviceType: this.api.matter.deviceTypes.LeakSensor,
602
+ serialNumber: 'LEAK-001',
603
+ manufacturer: 'Homebridge',
604
+ model: 'Leak Sensor Example',
605
+ clusters: {
606
+ booleanState: {
607
+ stateValue: false, // No leak detected (false = dry, true = leak)
608
+ },
609
+ },
610
+ });
611
+ }
532
612
  // 7. Smoke Sensor
533
- // Note: SmokeCoAlarmDevice requires specifying features (SmokeAlarm and/or CoAlarm)
534
- const SmokeCoAlarmServer = this.api.matter.deviceTypes.SmokeSensor.requirements.SmokeCoAlarmServer;
535
- const SmokeSensorWithBoth = this.api.matter.deviceTypes.SmokeSensor.with(SmokeCoAlarmServer.with('SmokeAlarm', 'CoAlarm'));
536
- accessories.push({
537
- uuid: this.api.hap.uuid.generate('matter-smoke-sensor'),
538
- displayName: 'Smoke Sensor',
539
- deviceType: SmokeSensorWithBoth,
540
- serialNumber: 'SMOKE-001',
541
- manufacturer: 'Homebridge',
542
- model: 'Smoke Sensor Example',
543
- clusters: {
544
- smokeCoAlarm: {
545
- smokeState: 0, // 0 = Normal, 1 = Warning, 2 = Critical
546
- coState: 0, // 0 = Normal, 1 = Warning, 2 = Critical
547
- batteryAlert: 0, // 0 = Normal
548
- testInProgress: false,
549
- hardwareFaultAlert: false,
550
- endOfServiceAlert: 0, // 0 = Normal
551
- interconnectSmokeAlarm: 0, // 0 = Normal
552
- interconnectCoAlarm: 0, // 0 = Normal
553
- },
554
- },
555
- });
556
- this.log.info(`✓ Registered ${accessories.length} sensor accessories`);
557
- for (const acc of accessories) {
558
- this.log.info(` - ${acc.displayName}`);
613
+ if (this.config.enableSmokeSensor) {
614
+ // Note: SmokeCoAlarmDevice requires specifying features (SmokeAlarm and/or CoAlarm)
615
+ const SmokeCoAlarmServer = this.api.matter.deviceTypes.SmokeSensor.requirements.SmokeCoAlarmServer;
616
+ const SmokeSensorWithBoth = this.api.matter.deviceTypes.SmokeSensor.with(SmokeCoAlarmServer.with('SmokeAlarm', 'CoAlarm'));
617
+ accessories.push({
618
+ uuid: this.api.hap.uuid.generate('matter-smoke-sensor'),
619
+ displayName: 'Smoke Sensor',
620
+ deviceType: SmokeSensorWithBoth,
621
+ serialNumber: 'SMOKE-001',
622
+ manufacturer: 'Homebridge',
623
+ model: 'Smoke Sensor Example',
624
+ clusters: {
625
+ smokeCoAlarm: {
626
+ smokeState: 0, // 0 = Normal, 1 = Warning, 2 = Critical
627
+ coState: 0, // 0 = Normal, 1 = Warning, 2 = Critical
628
+ batteryAlert: 0, // 0 = Normal
629
+ testInProgress: false,
630
+ hardwareFaultAlert: false,
631
+ endOfServiceAlert: 0, // 0 = Normal
632
+ interconnectSmokeAlarm: 0, // 0 = Normal
633
+ interconnectCoAlarm: 0, // 0 = Normal
634
+ },
635
+ },
636
+ });
637
+ }
638
+ if (accessories.length > 0) {
639
+ this.log.info(`✓ Registered ${accessories.length} sensor accessories`);
640
+ for (const acc of accessories) {
641
+ this.log.info(` - ${acc.displayName}`);
642
+ }
643
+ // Register all sensor accessories
644
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
645
+ }
646
+ }
647
+ /**
648
+ * Register Matter HVAC accessories (Thermostats, Fans)
649
+ */
650
+ registerHVAC() {
651
+ this.log.info('═'.repeat(80));
652
+ this.log.info('Registering Matter HVAC Devices');
653
+ this.log.info('═'.repeat(80));
654
+ const accessories = [];
655
+ // 1. Thermostat
656
+ if (this.config.enableThermostat) {
657
+ accessories.push({
658
+ uuid: this.api.matter.uuid.generate('matter-thermostat'),
659
+ displayName: 'Thermostat',
660
+ deviceType: this.api.matter.deviceTypes.Thermostat,
661
+ serialNumber: 'THERMO-001',
662
+ manufacturer: 'Matter Examples',
663
+ model: 'Thermostat v1',
664
+ clusters: {
665
+ thermostat: {
666
+ // Current temperature (in hundredths of degrees Celsius)
667
+ localTemperature: 2100, // 21.00°C
668
+ // Heating setpoint (target temperature in heat mode)
669
+ occupiedHeatingSetpoint: 2000, // 20.00°C
670
+ minHeatSetpointLimit: 700, // 7°C minimum
671
+ maxHeatSetpointLimit: 3000, // 30°C maximum
672
+ // Cooling setpoint (target temperature in cool mode)
673
+ occupiedCoolingSetpoint: 2400, // 24.00°C
674
+ minCoolSetpointLimit: 1600, // 16°C minimum
675
+ maxCoolSetpointLimit: 3200, // 32°C maximum
676
+ // System mode: 0=Off, 1=Auto, 3=Cool, 4=Heat
677
+ systemMode: 4, // Heat mode
678
+ // Control sequence: what modes are available (mandatory field)
679
+ // 4 = CoolingAndHeating (correct value when both Heating & Cooling features are present)
680
+ controlSequenceOfOperation: 4,
681
+ },
682
+ },
683
+ handlers: {
684
+ thermostat: {
685
+ // Called when user changes heating setpoint
686
+ setOccupiedHeatingSetpoint: async (request) => {
687
+ const tempC = (request.targetSetpoint / 100).toFixed(1);
688
+ this.log.info(`[Thermostat] ✓ Handler \`setOccupiedHeatingSetpoint\` called: ${request.targetSetpoint} (${tempC}°C)`);
689
+ },
690
+ // Called when user changes cooling setpoint
691
+ setOccupiedCoolingSetpoint: async (request) => {
692
+ const tempC = (request.targetSetpoint / 100).toFixed(1);
693
+ this.log.info(`[Thermostat] ✓ Handler \`setOccupiedCoolingSetpoint\` called: ${request.targetSetpoint} (${tempC}°C)`);
694
+ },
695
+ // Called when user changes mode (Off, Auto, Cool, Heat)
696
+ setSystemMode: async (request) => {
697
+ const modes = ['Off', 'Auto', 'Reserved', 'Cool', 'Heat', 'Emergency Heating', 'Precooling', 'Fan Only'];
698
+ const modeName = modes[request.systemMode] || `Unknown (${request.systemMode})`;
699
+ this.log.info(`[Thermostat] ✓ Handler \`setSystemMode\` called: ${request.systemMode} (${modeName})`);
700
+ },
701
+ },
702
+ },
703
+ });
704
+ }
705
+ // 2. Fan
706
+ if (this.config.enableFan) {
707
+ accessories.push({
708
+ uuid: this.api.matter.uuid.generate('matter-fan'),
709
+ displayName: 'Fan',
710
+ deviceType: this.api.matter.deviceTypes.Fan,
711
+ serialNumber: 'FAN-001',
712
+ manufacturer: 'Matter Examples',
713
+ model: 'Fan v1',
714
+ clusters: {
715
+ fanControl: {
716
+ // Fan mode: 0=Off, 1=Low, 2=Medium, 3=High, 4=On, 5=Auto, 6=Smart
717
+ fanMode: 0, // Off
718
+ // Fan mode sequence: indicates which modes are supported
719
+ // 0=OffLowMedHigh, 1=OffLowHigh, 2=OffLowMedHighAuto, 3=OffLowHighAuto, 4=OffOnAuto, 5=OffOn
720
+ fanModeSequence: 0, // OffLowMedHigh
721
+ // Percent setting (0-100)
722
+ percentSetting: 0,
723
+ percentCurrent: 0,
724
+ // Speed setting (0-100, some fans use this instead of percent)
725
+ speedSetting: 0,
726
+ speedCurrent: 0,
727
+ },
728
+ },
729
+ handlers: {
730
+ fanControl: {
731
+ // Called when user changes fan speed via percent slider
732
+ setPercentSetting: async (request) => {
733
+ this.log.info(`[Fan] ✓ Handler \`setPercentSetting\` called: ${request.percentSetting}%`);
734
+ },
735
+ // Called when user changes fan mode
736
+ setFanMode: async (request) => {
737
+ const modes = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Smart'];
738
+ const modeName = modes[request.fanMode] || `Unknown (${request.fanMode})`;
739
+ this.log.info(`[Fan] ✓ Handler \`setFanMode\` called: ${request.fanMode} (${modeName})`);
740
+ },
741
+ // Called when user presses up/down buttons to adjust speed
742
+ step: async (request) => {
743
+ const dir = request.direction === 0 ? 'Up' : 'Down';
744
+ this.log.info(`[Fan] ✓ Handler \`step\` called: direction=${dir}, wrap=${request.wrap}, lowestOff=${request.lowestOff}`);
745
+ },
746
+ },
747
+ },
748
+ });
749
+ }
750
+ if (accessories.length > 0) {
751
+ this.log.info(`✓ Registered ${accessories.length} HVAC accessories`);
752
+ for (const acc of accessories) {
753
+ this.log.info(` - ${acc.displayName}`);
754
+ }
755
+ // Register all HVAC accessories
756
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
757
+ }
758
+ }
759
+ /**
760
+ * Register Matter Security & Access accessories (Door Locks, Garage Doors)
761
+ */
762
+ registerSecurity() {
763
+ this.log.info('═'.repeat(80));
764
+ this.log.info('Registering Matter Security & Access Devices');
765
+ this.log.info('═'.repeat(80));
766
+ const accessories = [];
767
+ // 1. Door Lock
768
+ if (this.config.enableDoorLock) {
769
+ accessories.push({
770
+ uuid: this.api.matter.uuid.generate('matter-door-lock'),
771
+ displayName: 'Door Lock',
772
+ deviceType: this.api.matter.deviceTypes.DoorLock,
773
+ serialNumber: 'LOCK-001',
774
+ manufacturer: 'Matter Examples',
775
+ model: 'DoorLock v1',
776
+ clusters: {
777
+ doorLock: {
778
+ // Lock state: 0=NotFullyLocked, 1=Locked, 2=Unlocked
779
+ lockState: 2, // Unlocked
780
+ // Lock type: 0=Deadbolt, 1=Magnetic, 2=Other, etc.
781
+ lockType: 0, // Deadbolt
782
+ // Actuator enabled (can be locked/unlocked)
783
+ actuatorEnabled: true,
784
+ },
785
+ },
786
+ handlers: {
787
+ doorLock: {
788
+ // Called when user locks the door
789
+ lockDoor: async () => {
790
+ this.log.info('[Door Lock] ✓ Handler `lockDoor` called - Locking door');
791
+ // Update the lock state to "Locked" (1)
792
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-door-lock'), 'doorLock', { lockState: 1 });
793
+ },
794
+ // Called when user unlocks the door
795
+ unlockDoor: async () => {
796
+ this.log.info('[Door Lock] ✓ Handler `unlockDoor` called - Unlocking door');
797
+ // Update the lock state to "Unlocked" (2)
798
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-door-lock'), 'doorLock', { lockState: 2 });
799
+ },
800
+ },
801
+ },
802
+ });
803
+ }
804
+ // 2. Garage Door Opener
805
+ if (this.config.enableGarageDoor) {
806
+ // Note: Matter uses WindowCovering device type for garage doors
807
+ accessories.push({
808
+ uuid: this.api.matter.uuid.generate('matter-garage-door'),
809
+ displayName: 'Garage Door',
810
+ deviceType: this.api.matter.deviceTypes.WindowCovering,
811
+ serialNumber: 'GARAGE-001',
812
+ manufacturer: 'Matter Examples',
813
+ model: 'GarageDoor v1',
814
+ clusters: {
815
+ windowCovering: {
816
+ // Target position (0 = fully closed, 10000 = fully open)
817
+ targetPositionLiftPercent100ths: 0, // Closed
818
+ // Current position
819
+ currentPositionLiftPercent100ths: 0, // Closed
820
+ // Operational status
821
+ operationalStatus: {
822
+ global: 0, // Not moving
823
+ lift: 0,
824
+ tilt: 0,
825
+ },
826
+ // End product type
827
+ endProductType: 7, // Garage door
828
+ // Configuration: supports lift positioning
829
+ configStatus: {
830
+ operational: true,
831
+ onlineReserved: true,
832
+ liftMovementReversed: false,
833
+ liftPositionAware: true,
834
+ tiltPositionAware: false,
835
+ liftEncoderControlled: true,
836
+ tiltEncoderControlled: false,
837
+ },
838
+ },
839
+ },
840
+ handlers: {
841
+ windowCovering: {
842
+ // Called when user opens/closes garage door
843
+ goToLiftPercentage: async (request) => {
844
+ const percent = (request.targetPercent / 100).toFixed(0);
845
+ this.log.info(`[Garage Door] ✓ Handler \`goToLiftPercentage\` called: ${request.targetPercent} (${percent}% open)`);
846
+ // Update position
847
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-garage-door'), 'windowCovering', {
848
+ currentPositionLiftPercent100ths: request.targetPercent,
849
+ targetPositionLiftPercent100ths: request.targetPercent,
850
+ });
851
+ },
852
+ // Called when user presses "up" (open)
853
+ upOrOpen: async () => {
854
+ this.log.info('[Garage Door] ✓ Handler `upOrOpen` called - Opening garage door');
855
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-garage-door'), 'windowCovering', {
856
+ currentPositionLiftPercent100ths: 10000, // Fully open
857
+ targetPositionLiftPercent100ths: 10000,
858
+ });
859
+ },
860
+ // Called when user presses "down" (close)
861
+ downOrClose: async () => {
862
+ this.log.info('[Garage Door] ✓ Handler `downOrClose` called - Closing garage door');
863
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-garage-door'), 'windowCovering', {
864
+ currentPositionLiftPercent100ths: 0, // Fully closed
865
+ targetPositionLiftPercent100ths: 0,
866
+ });
867
+ },
868
+ // Called when user presses "stop"
869
+ stopMotion: async () => {
870
+ this.log.info('[Garage Door] ✓ Handler `stopMotion` called - Stopping garage door');
871
+ },
872
+ },
873
+ },
874
+ });
875
+ }
876
+ if (accessories.length > 0) {
877
+ this.log.info(`✓ Registered ${accessories.length} security & access accessories`);
878
+ for (const acc of accessories) {
879
+ this.log.info(` - ${acc.displayName}`);
880
+ }
881
+ // Register all security accessories
882
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
883
+ }
884
+ }
885
+ /**
886
+ * Register Matter Window Covering accessories (Blinds, Shades)
887
+ */
888
+ registerWindowCoverings() {
889
+ this.log.info('═'.repeat(80));
890
+ this.log.info('Registering Matter Window Covering Devices');
891
+ this.log.info('═'.repeat(80));
892
+ const accessories = [];
893
+ // 1. Window Covering (Blind/Shade with position control)
894
+ if (this.config.enableWindowBlind) {
895
+ accessories.push({
896
+ uuid: this.api.matter.uuid.generate('matter-window-blind'),
897
+ displayName: 'Window Blind',
898
+ deviceType: this.api.matter.deviceTypes.WindowCovering,
899
+ serialNumber: 'BLIND-001',
900
+ manufacturer: 'Matter Examples',
901
+ model: 'WindowBlind v1',
902
+ clusters: {
903
+ windowCovering: {
904
+ // Target position (0 = fully closed, 10000 = fully open, in hundredths of percent)
905
+ targetPositionLiftPercent100ths: 5000, // 50% open
906
+ // Current position
907
+ currentPositionLiftPercent100ths: 5000, // 50% open
908
+ // Operational status
909
+ operationalStatus: {
910
+ global: 0, // Not moving
911
+ lift: 0,
912
+ tilt: 0,
913
+ },
914
+ // End product type
915
+ endProductType: 0, // Rollershade
916
+ // Configuration
917
+ configStatus: {
918
+ operational: true,
919
+ onlineReserved: true,
920
+ liftMovementReversed: false,
921
+ liftPositionAware: true,
922
+ tiltPositionAware: false,
923
+ liftEncoderControlled: true,
924
+ tiltEncoderControlled: false,
925
+ },
926
+ },
927
+ },
928
+ handlers: {
929
+ windowCovering: {
930
+ // Called when user sets position via slider
931
+ goToLiftPercentage: async (request) => {
932
+ const percent = (request.targetPercent / 100).toFixed(0);
933
+ this.log.info(`[Window Blind] ✓ Handler \`goToLiftPercentage\` called: ${request.targetPercent} (${percent}% open)`);
934
+ // Update position
935
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-window-blind'), 'windowCovering', {
936
+ currentPositionLiftPercent100ths: request.targetPercent,
937
+ targetPositionLiftPercent100ths: request.targetPercent,
938
+ });
939
+ },
940
+ // Called when user presses "up" (open)
941
+ upOrOpen: async () => {
942
+ this.log.info('[Window Blind] ✓ Handler `upOrOpen` called - Opening blind');
943
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-window-blind'), 'windowCovering', {
944
+ currentPositionLiftPercent100ths: 10000, // Fully open
945
+ targetPositionLiftPercent100ths: 10000,
946
+ });
947
+ },
948
+ // Called when user presses "down" (close)
949
+ downOrClose: async () => {
950
+ this.log.info('[Window Blind] ✓ Handler `downOrClose` called - Closing blind');
951
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-window-blind'), 'windowCovering', {
952
+ currentPositionLiftPercent100ths: 0, // Fully closed
953
+ targetPositionLiftPercent100ths: 0,
954
+ });
955
+ },
956
+ // Called when user presses "stop"
957
+ stopMotion: async () => {
958
+ this.log.info('[Window Blind] ✓ Handler `stopMotion` called - Stopping blind');
959
+ },
960
+ },
961
+ },
962
+ });
963
+ }
964
+ // 2. Window Covering with Tilt (Venetian Blind)
965
+ if (this.config.enableVenetianBlind) {
966
+ accessories.push({
967
+ uuid: this.api.matter.uuid.generate('matter-venetian-blind'),
968
+ displayName: 'Venetian Blind (Tilt)',
969
+ deviceType: this.api.matter.deviceTypes.WindowCovering,
970
+ serialNumber: 'BLIND-002',
971
+ manufacturer: 'Matter Examples',
972
+ model: 'VenetianBlind v1',
973
+ clusters: {
974
+ windowCovering: {
975
+ // Lift position (vertical position)
976
+ targetPositionLiftPercent100ths: 5000, // 50% open
977
+ currentPositionLiftPercent100ths: 5000,
978
+ // Tilt position (slat angle: 0 = closed, 10000 = fully open)
979
+ targetPositionTiltPercent100ths: 5000, // 50% tilted
980
+ currentPositionTiltPercent100ths: 5000,
981
+ // Operational status
982
+ operationalStatus: {
983
+ global: 0,
984
+ lift: 0,
985
+ tilt: 0,
986
+ },
987
+ // End product type
988
+ endProductType: 8, // Venetian blind
989
+ // Configuration: supports both lift and tilt
990
+ configStatus: {
991
+ operational: true,
992
+ onlineReserved: true,
993
+ liftMovementReversed: false,
994
+ liftPositionAware: true,
995
+ tiltPositionAware: true,
996
+ liftEncoderControlled: true,
997
+ tiltEncoderControlled: true,
998
+ },
999
+ },
1000
+ },
1001
+ handlers: {
1002
+ windowCovering: {
1003
+ // Called when user sets lift position
1004
+ goToLiftPercentage: async (request) => {
1005
+ const percent = (request.targetPercent / 100).toFixed(0);
1006
+ this.log.info(`[Venetian Blind] ✓ Handler \`goToLiftPercentage\` called: ${request.targetPercent} (${percent}% open)`);
1007
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-venetian-blind'), 'windowCovering', {
1008
+ currentPositionLiftPercent100ths: request.targetPercent,
1009
+ targetPositionLiftPercent100ths: request.targetPercent,
1010
+ });
1011
+ },
1012
+ // Called when user sets tilt angle
1013
+ goToTiltPercentage: async (request) => {
1014
+ const percent = (request.targetPercent / 100).toFixed(0);
1015
+ this.log.info(`[Venetian Blind] ✓ Handler \`goToTiltPercentage\` called: ${request.targetPercent} (${percent}% tilted)`);
1016
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-venetian-blind'), 'windowCovering', {
1017
+ currentPositionTiltPercent100ths: request.targetPercent,
1018
+ targetPositionTiltPercent100ths: request.targetPercent,
1019
+ });
1020
+ },
1021
+ upOrOpen: async () => {
1022
+ this.log.info('[Venetian Blind] ✓ Handler `upOrOpen` called');
1023
+ },
1024
+ downOrClose: async () => {
1025
+ this.log.info('[Venetian Blind] ✓ Handler `downOrClose` called');
1026
+ },
1027
+ stopMotion: async () => {
1028
+ this.log.info('[Venetian Blind] ✓ Handler `stopMotion` called');
1029
+ },
1030
+ },
1031
+ },
1032
+ });
1033
+ }
1034
+ if (accessories.length > 0) {
1035
+ this.log.info(`✓ Registered ${accessories.length} window covering accessories`);
1036
+ for (const acc of accessories) {
1037
+ this.log.info(` - ${acc.displayName}`);
1038
+ }
1039
+ // Register all window covering accessories
1040
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Register Matter Appliance accessories (Robotic Vacuum Cleaners, etc.)
1045
+ */
1046
+ registerAppliances() {
1047
+ this.log.info('═'.repeat(80));
1048
+ this.log.info('Registering Matter Appliance Devices');
1049
+ this.log.info('═'.repeat(80));
1050
+ const accessories = [];
1051
+ // 1. Robotic Vacuum Cleaner
1052
+ if (this.config.enableRobotVacuum) {
1053
+ accessories.push({
1054
+ uuid: this.api.matter.uuid.generate('matter-robot-vacuum'),
1055
+ displayName: 'Robot Vacuum',
1056
+ deviceType: this.api.matter.deviceTypes.RoboticVacuumCleaner,
1057
+ serialNumber: 'VACUUM-001',
1058
+ manufacturer: 'Matter Examples',
1059
+ model: 'RobotVacuum v1',
1060
+ clusters: {
1061
+ rvcRunMode: {
1062
+ // Supported run modes (0=Idle, 1=Cleaning, 2=Mapping)
1063
+ supportedModes: [
1064
+ { label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] }, // 16384 = Idle tag
1065
+ { label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] }, // 16385 = Cleaning tag
1066
+ { label: 'Mapping', mode: 2, modeTags: [{ value: 16386 }] }, // 16386 = Mapping tag
1067
+ ],
1068
+ // Current mode
1069
+ currentMode: 0, // Idle
1070
+ },
1071
+ rvcOperationalState: {
1072
+ // Operational state list (must include at least an error state)
1073
+ operationalStateList: [
1074
+ { operationalStateId: 0 }, // Stopped
1075
+ { operationalStateId: 1 }, // Running
1076
+ { operationalStateId: 2 }, // Paused
1077
+ { operationalStateId: 3 }, // Error (required)
1078
+ { operationalStateId: 64 }, // SeekingCharger
1079
+ { operationalStateId: 65 }, // Charging
1080
+ { operationalStateId: 66 }, // Docked
1081
+ ],
1082
+ // Current operational state (just the ID, not an object)
1083
+ operationalState: 66, // Docked
1084
+ // Error state
1085
+ operationalError: {
1086
+ errorStateId: 0, // No error
1087
+ },
1088
+ },
1089
+ rvcCleanMode: {
1090
+ // Supported clean modes (0=Vacuum, 1=Mop, 2=Vacuum+Mop)
1091
+ supportedModes: [
1092
+ { label: 'Vacuum', mode: 0, modeTags: [] },
1093
+ { label: 'Mop', mode: 1, modeTags: [] },
1094
+ { label: 'Vacuum & Mop', mode: 2, modeTags: [] },
1095
+ ],
1096
+ // Current clean mode
1097
+ currentMode: 0, // Vacuum
1098
+ },
1099
+ },
1100
+ handlers: {
1101
+ rvcOperationalState: {
1102
+ // Called when user presses "pause" in Home app
1103
+ pause: async () => {
1104
+ this.log.info('[Robot Vacuum] ✓ Handler `pause` called - Pausing cleaning');
1105
+ // Update state to Paused (2)
1106
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 2 });
1107
+ },
1108
+ // Called when user presses "resume" or "start" in Home app
1109
+ resume: async () => {
1110
+ this.log.info('[Robot Vacuum] ✓ Handler `resume` called - Resuming cleaning');
1111
+ // Update state to Running (1)
1112
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 1 });
1113
+ },
1114
+ // Called when user sends robot to charging dock
1115
+ goHome: async () => {
1116
+ this.log.info('[Robot Vacuum] ✓ Handler `goHome` called - Returning to dock');
1117
+ // Update state to SeekingCharger (64)
1118
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 64 });
1119
+ // Simulate arriving at dock after 3 seconds
1120
+ setTimeout(async () => {
1121
+ this.log.info('[Robot Vacuum] → Arrived at dock, now docked');
1122
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcOperationalState', { operationalState: 66 });
1123
+ }, 3000);
1124
+ },
1125
+ },
1126
+ rvcRunMode: {
1127
+ // Called when user changes run mode (Idle, Cleaning, Mapping)
1128
+ changeToMode: async (request) => {
1129
+ const modes = ['Idle', 'Cleaning', 'Mapping'];
1130
+ const modeName = modes[request.newMode] || `Unknown (${request.newMode})`;
1131
+ this.log.info(`[Robot Vacuum] ✓ Handler \`changeToMode\` called: ${request.newMode} (${modeName})`);
1132
+ // Update the current mode
1133
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcRunMode', { currentMode: request.newMode });
1134
+ },
1135
+ },
1136
+ rvcCleanMode: {
1137
+ // Called when user changes clean mode (Vacuum, Mop, Vacuum+Mop)
1138
+ changeToMode: async (request) => {
1139
+ const modes = ['Vacuum', 'Mop', 'Vacuum & Mop'];
1140
+ const modeName = modes[request.newMode] || `Unknown (${request.newMode})`;
1141
+ this.log.info(`[Robot Vacuum] ✓ Handler \`changeToMode\` called: ${request.newMode} (${modeName})`);
1142
+ // Update the current clean mode
1143
+ await this.api.matter.updateAccessoryState(this.api.matter.uuid.generate('matter-robot-vacuum'), 'rvcCleanMode', { currentMode: request.newMode });
1144
+ },
1145
+ },
1146
+ },
1147
+ });
1148
+ }
1149
+ if (accessories.length > 0) {
1150
+ this.log.info(`✓ Registered ${accessories.length} appliance accessories`);
1151
+ for (const acc of accessories) {
1152
+ this.log.info(` - ${acc.displayName}`);
1153
+ }
1154
+ // Register all appliance accessories
1155
+ this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
559
1156
  }
560
- // Register all sensor accessories
561
- this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
562
1157
  }
563
1158
  }
564
1159
  //# sourceMappingURL=platform.js.map