@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/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( /* accessory: PlatformAccessory */) {
32
- // Note this is not used for Matter accessories, as they are registered dynamically
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
- // this.registerSwitchesAndOutlets();
58
+ this.registerSwitchesAndOutlets();
42
59
  // this.registerSensors();
43
- // this.registerHVAC();
44
- // this.registerSecurity();
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
- this.api.matter.registerAccessory({
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
- this.api.matter.registerAccessory({
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
- this.api.matter.registerAccessory({
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
- this.api.matter.registerAccessory({
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
- this.api.matter.registerAccessory({
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