@homebridge-plugins/homebridge-matter 0.0.3 → 0.0.5
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/.claude/settings.local.json +5 -1
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -176
- package/README.md +1 -196
- package/config.schema.json +1 -1
- package/dist/platform.d.ts +31 -1
- package/dist/platform.js +752 -11
- package/dist/platform.js.map +1 -1
- package/package.json +37 -8
package/dist/platform.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
1
2
|
/**
|
|
2
3
|
* MatterPlatform
|
|
3
4
|
* Demonstrates all available Matter device types in Homebridge
|
|
@@ -6,8 +7,10 @@ export class ExampleHomebridgePlatform {
|
|
|
6
7
|
log;
|
|
7
8
|
config;
|
|
8
9
|
api;
|
|
9
|
-
// Track restored cached accessories (required for DynamicPlatformPlugin)
|
|
10
|
+
// Track restored HAP cached accessories (required for DynamicPlatformPlugin)
|
|
10
11
|
accessories = new Map();
|
|
12
|
+
// Track restored Matter cached accessories
|
|
13
|
+
matterAccessories = new Map();
|
|
11
14
|
constructor(log, config, api) {
|
|
12
15
|
this.log = log;
|
|
13
16
|
this.config = config;
|
|
@@ -28,20 +31,36 @@ export class ExampleHomebridgePlatform {
|
|
|
28
31
|
* Required for DynamicPlatformPlugin
|
|
29
32
|
* Called when homebridge restores cached accessories from disk at startup
|
|
30
33
|
*/
|
|
31
|
-
configureAccessory(
|
|
32
|
-
// Note this is not used for Matter accessories
|
|
34
|
+
configureAccessory(accessory) {
|
|
35
|
+
// Note this is not used for Matter accessories - use configureMatterAccessory instead
|
|
36
|
+
this.accessories.set(accessory.UUID, accessory);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Called for each cached Matter accessory restored from disk
|
|
40
|
+
* Track these so we can determine which accessories to remove in didFinishLaunching
|
|
41
|
+
*/
|
|
42
|
+
configureMatterAccessory(accessory) {
|
|
43
|
+
this.log.info(`✓ Restored Matter accessory from cache: ${accessory.displayName}`);
|
|
44
|
+
// Track cached Matter accessories (in real plugin, compare with cloud devices and remove orphans)
|
|
45
|
+
this.matterAccessories.set(accessory.uuid, accessory);
|
|
33
46
|
}
|
|
34
47
|
/**
|
|
35
48
|
* Register all Matter accessory examples
|
|
36
49
|
*/
|
|
37
50
|
registerMatterAccessories() {
|
|
51
|
+
this.log.info('='.repeat(80));
|
|
52
|
+
this.log.info('HAP cached accessories:', this.accessories.size);
|
|
53
|
+
this.log.info('Matter cached accessories:', this.matterAccessories.size);
|
|
54
|
+
this.log.info('='.repeat(80));
|
|
38
55
|
this.log.info('Registering Matter accessories...');
|
|
39
56
|
// Register each device type
|
|
40
57
|
this.registerLightingDevices();
|
|
41
|
-
|
|
58
|
+
this.registerSwitchesAndOutlets();
|
|
42
59
|
// this.registerSensors();
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
this.registerHVAC();
|
|
61
|
+
this.registerSecurity();
|
|
62
|
+
this.registerWindowCoverings();
|
|
63
|
+
this.registerAppliances();
|
|
45
64
|
// this.registerOtherDevices();
|
|
46
65
|
this.log.info('Finished registering Matter accessories');
|
|
47
66
|
// You can read current state using the API
|
|
@@ -75,7 +94,8 @@ export class ExampleHomebridgePlatform {
|
|
|
75
94
|
const uuidLightColour = this.api.matter.uuid.generate('matter-colour-light');
|
|
76
95
|
const uuidLightExtendedColour = this.api.matter.uuid.generate('matter-extended-colour-light');
|
|
77
96
|
// 1. On/Off Light
|
|
78
|
-
|
|
97
|
+
const accessories = [];
|
|
98
|
+
accessories.push({
|
|
79
99
|
uuid: uuidLightOnOff,
|
|
80
100
|
displayName: 'On/Off Light',
|
|
81
101
|
deviceType: this.api.matter.deviceTypes.OnOffLight,
|
|
@@ -100,7 +120,7 @@ export class ExampleHomebridgePlatform {
|
|
|
100
120
|
},
|
|
101
121
|
});
|
|
102
122
|
// 2. Dimmable Light
|
|
103
|
-
|
|
123
|
+
accessories.push({
|
|
104
124
|
uuid: uuidLightDimmable,
|
|
105
125
|
displayName: 'Dimmable Light',
|
|
106
126
|
deviceType: this.api.matter.deviceTypes.DimmableLight,
|
|
@@ -136,7 +156,7 @@ export class ExampleHomebridgePlatform {
|
|
|
136
156
|
},
|
|
137
157
|
});
|
|
138
158
|
// 3. Colour Temperature Light
|
|
139
|
-
|
|
159
|
+
accessories.push({
|
|
140
160
|
uuid: uuidLightColourTemp,
|
|
141
161
|
displayName: 'Colour Temperature Light',
|
|
142
162
|
deviceType: this.api.matter.deviceTypes.ColorTemperatureLight,
|
|
@@ -186,7 +206,7 @@ export class ExampleHomebridgePlatform {
|
|
|
186
206
|
},
|
|
187
207
|
});
|
|
188
208
|
// 4. Colour Light (Hue/Saturation ONLY - no CCT)
|
|
189
|
-
|
|
209
|
+
accessories.push({
|
|
190
210
|
uuid: uuidLightColour,
|
|
191
211
|
displayName: 'Colour Light (HS)',
|
|
192
212
|
deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
|
|
@@ -244,7 +264,7 @@ export class ExampleHomebridgePlatform {
|
|
|
244
264
|
},
|
|
245
265
|
});
|
|
246
266
|
// 5. Extended Colour Light (Hue/Saturation + CCT)
|
|
247
|
-
|
|
267
|
+
accessories.push({
|
|
248
268
|
uuid: uuidLightExtendedColour,
|
|
249
269
|
displayName: 'Extended Colour Light (HS+CCT)',
|
|
250
270
|
deviceType: this.api.matter.deviceTypes.ExtendedColorLight,
|
|
@@ -309,6 +329,727 @@ export class ExampleHomebridgePlatform {
|
|
|
309
329
|
},
|
|
310
330
|
},
|
|
311
331
|
});
|
|
332
|
+
// Register all lighting accessories
|
|
333
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Switches and Outlets
|
|
337
|
+
*/
|
|
338
|
+
registerSwitchesAndOutlets() {
|
|
339
|
+
const uuidSwitch = this.api.matter.uuid.generate('matter-onoff-switch');
|
|
340
|
+
const uuidOutlet = this.api.matter.uuid.generate('matter-onoff-outlet');
|
|
341
|
+
const uuidDimmableOutlet = this.api.matter.uuid.generate('matter-dimmable-outlet');
|
|
342
|
+
const accessories = [];
|
|
343
|
+
// 1. On/Off Switch
|
|
344
|
+
accessories.push({
|
|
345
|
+
uuid: uuidSwitch,
|
|
346
|
+
displayName: 'On/Off Switch',
|
|
347
|
+
deviceType: this.api.matter.deviceTypes.OnOffSwitch,
|
|
348
|
+
serialNumber: 'SWITCH-001',
|
|
349
|
+
manufacturer: 'Matter Examples',
|
|
350
|
+
model: 'OnOffSwitch v1',
|
|
351
|
+
clusters: {
|
|
352
|
+
onOff: {
|
|
353
|
+
onOff: false,
|
|
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();
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Register Matter sensor accessories
|
|
433
|
+
*/
|
|
434
|
+
registerSensors() {
|
|
435
|
+
this.log.info('═'.repeat(80));
|
|
436
|
+
this.log.info('Registering Matter Sensor Devices');
|
|
437
|
+
this.log.info('═'.repeat(80));
|
|
438
|
+
const accessories = [];
|
|
439
|
+
// 1. Temperature Sensor
|
|
440
|
+
accessories.push({
|
|
441
|
+
uuid: this.api.hap.uuid.generate('matter-temperature-sensor'),
|
|
442
|
+
displayName: 'Temperature Sensor',
|
|
443
|
+
deviceType: this.api.matter.deviceTypes.TemperatureSensor,
|
|
444
|
+
serialNumber: 'TEMP-001',
|
|
445
|
+
manufacturer: 'Homebridge',
|
|
446
|
+
model: 'Temperature Sensor Example',
|
|
447
|
+
clusters: {
|
|
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}`);
|
|
561
|
+
}
|
|
562
|
+
// Register all sensor accessories
|
|
563
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Register Matter HVAC accessories (Thermostats, Fans)
|
|
567
|
+
*/
|
|
568
|
+
registerHVAC() {
|
|
569
|
+
this.log.info('═'.repeat(80));
|
|
570
|
+
this.log.info('Registering Matter HVAC Devices');
|
|
571
|
+
this.log.info('═'.repeat(80));
|
|
572
|
+
const accessories = [];
|
|
573
|
+
// 1. Thermostat
|
|
574
|
+
accessories.push({
|
|
575
|
+
uuid: this.api.matter.uuid.generate('matter-thermostat'),
|
|
576
|
+
displayName: 'Thermostat',
|
|
577
|
+
deviceType: this.api.matter.deviceTypes.Thermostat,
|
|
578
|
+
serialNumber: 'THERMO-001',
|
|
579
|
+
manufacturer: 'Matter Examples',
|
|
580
|
+
model: 'Thermostat v1',
|
|
581
|
+
clusters: {
|
|
582
|
+
thermostat: {
|
|
583
|
+
// Current temperature (in hundredths of degrees Celsius)
|
|
584
|
+
localTemperature: 2100, // 21.00°C
|
|
585
|
+
// Heating setpoint (target temperature in heat mode)
|
|
586
|
+
occupiedHeatingSetpoint: 2000, // 20.00°C
|
|
587
|
+
minHeatSetpointLimit: 700, // 7°C minimum
|
|
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}`);
|
|
667
|
+
}
|
|
668
|
+
// Register all HVAC accessories
|
|
669
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Register Matter Security & Access accessories (Door Locks, Garage Doors)
|
|
673
|
+
*/
|
|
674
|
+
registerSecurity() {
|
|
675
|
+
this.log.info('═'.repeat(80));
|
|
676
|
+
this.log.info('Registering Matter Security & Access Devices');
|
|
677
|
+
this.log.info('═'.repeat(80));
|
|
678
|
+
const accessories = [];
|
|
679
|
+
// 1. Door Lock
|
|
680
|
+
accessories.push({
|
|
681
|
+
uuid: this.api.matter.uuid.generate('matter-door-lock'),
|
|
682
|
+
displayName: 'Door Lock',
|
|
683
|
+
deviceType: this.api.matter.deviceTypes.DoorLock,
|
|
684
|
+
serialNumber: 'LOCK-001',
|
|
685
|
+
manufacturer: 'Matter Examples',
|
|
686
|
+
model: 'DoorLock v1',
|
|
687
|
+
clusters: {
|
|
688
|
+
doorLock: {
|
|
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}`);
|
|
787
|
+
}
|
|
788
|
+
// Register all security accessories
|
|
789
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Register Matter Window Covering accessories (Blinds, Shades)
|
|
793
|
+
*/
|
|
794
|
+
registerWindowCoverings() {
|
|
795
|
+
this.log.info('═'.repeat(80));
|
|
796
|
+
this.log.info('Registering Matter Window Covering Devices');
|
|
797
|
+
this.log.info('═'.repeat(80));
|
|
798
|
+
const accessories = [];
|
|
799
|
+
// 1. Window Covering (Blind/Shade with position control)
|
|
800
|
+
accessories.push({
|
|
801
|
+
uuid: this.api.matter.uuid.generate('matter-window-blind'),
|
|
802
|
+
displayName: 'Window Blind',
|
|
803
|
+
deviceType: this.api.matter.deviceTypes.WindowCovering,
|
|
804
|
+
serialNumber: 'BLIND-001',
|
|
805
|
+
manufacturer: 'Matter Examples',
|
|
806
|
+
model: 'WindowBlind v1',
|
|
807
|
+
clusters: {
|
|
808
|
+
windowCovering: {
|
|
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}`);
|
|
939
|
+
}
|
|
940
|
+
// Register all window covering accessories
|
|
941
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Register Matter Appliance accessories (Robotic Vacuum Cleaners, etc.)
|
|
945
|
+
*/
|
|
946
|
+
registerAppliances() {
|
|
947
|
+
this.log.info('═'.repeat(80));
|
|
948
|
+
this.log.info('Registering Matter Appliance Devices');
|
|
949
|
+
this.log.info('═'.repeat(80));
|
|
950
|
+
const accessories = [];
|
|
951
|
+
// 1. Robotic Vacuum Cleaner
|
|
952
|
+
accessories.push({
|
|
953
|
+
uuid: this.api.matter.uuid.generate('matter-robot-vacuum'),
|
|
954
|
+
displayName: 'Robot Vacuum',
|
|
955
|
+
deviceType: this.api.matter.deviceTypes.RoboticVacuumCleaner,
|
|
956
|
+
serialNumber: 'VACUUM-001',
|
|
957
|
+
manufacturer: 'Matter Examples',
|
|
958
|
+
model: 'RobotVacuum v1',
|
|
959
|
+
clusters: {
|
|
960
|
+
rvcRunMode: {
|
|
961
|
+
// Supported run modes (0=Idle, 1=Cleaning, 2=Mapping)
|
|
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}`);
|
|
1050
|
+
}
|
|
1051
|
+
// Register all appliance accessories
|
|
1052
|
+
this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories);
|
|
312
1053
|
}
|
|
313
1054
|
}
|
|
314
1055
|
//# sourceMappingURL=platform.js.map
|