matterbridge-example-dynamic-platform 1.2.1 → 1.2.3-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,39 @@ If you like this project and find it useful, please consider giving it a star on
8
8
  <img src="bmc-button.svg" alt="Buy me a coffee" width="120">
9
9
  </a>
10
10
 
11
+ ## [1.2.3] - 2025-05-25
12
+
13
+ ### Added
14
+
15
+ - [platform]: Added a cover device with both lift and tilt (supported by the Home app).
16
+
17
+ ### Changed
18
+
19
+ - [package]: Require matterbridge 3.0.4.
20
+ - [package]: Updated package.
21
+ - [package]: Updated dependencies.
22
+
23
+ <a href="https://www.buymeacoffee.com/luligugithub">
24
+ <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
25
+ </a>
26
+
27
+ ## [1.2.2] - 2025-05-19
28
+
29
+ ### Added
30
+
31
+ - [package]: Added waterHeater device type (not supported by Alexa and Apple Home)
32
+
33
+ ### Changed
34
+
35
+ - [package]: Changed the RVC from local implementation to the new RoboticVacuumCleaner class from matterbridge.
36
+ - [package]: Require matterbridge 3.0.3.
37
+ - [package]: Updated package.
38
+ - [package]: Updated dependencies.
39
+
40
+ <a href="https://www.buymeacoffee.com/luligugithub">
41
+ <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
42
+ </a>
43
+
11
44
  ## [1.2.1] - 2025-05-15
12
45
 
13
46
  ### Changed
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  Matterbridge dynamic platform example plugin is a template to develop your own plugin using the dynamic platform.
18
18
 
19
- It exposes 38 devices:
19
+ It exposes 40 virtual devices:
20
20
 
21
21
  - a switch with onOff cluster
22
22
  - a light with onOff
@@ -26,7 +26,8 @@ It exposes 38 devices:
26
26
  - a light with onOff, levelControl and colorControl (with XY and CT) clusters
27
27
  - a light with onOff, levelControl and colorControl (with CT only) clusters
28
28
  - an outlet (plug) with onOff cluster
29
- - a cover with windowCovering cluster
29
+ - a cover with windowCovering cluster and lift feature
30
+ - a cover with windowCovering cluster and both lift and tilt features
30
31
  - a lock with doorLock cluster
31
32
  - a thermo autoMode (i.e. with Auto Heat and Cool features) with thermostat cluster and 3 sub endpoints with flowMeasurement cluster, temperatureMeasurement cluster
32
33
  and relativeHumidityMeasurement cluster (to show how to create a composed device with sub endpoints)
@@ -57,6 +58,7 @@ It exposes 38 devices:
57
58
  - a microwave Oven device (supported by SmartThings, Alexa and Home Assistant)
58
59
  - an extractor Hood device (supported by SmartThings, Alexa and Home Assistant)
59
60
  - a cooktop device (supported by SmartThings, Alexa and Home Assistant)
61
+ - a water heater device (supported by SmartThings and Home Assistant)
60
62
 
61
63
  All these devices continuously change state and position. The plugin also shows how to use all the command handlers (you can control all the devices), how to subscribe to attributes and how to trigger events.
62
64
 
@@ -1,7 +1,7 @@
1
1
  import { MatterbridgeEndpoint, MatterbridgeServer, MatterbridgeOnOffServer, laundryWasher, laundryDryer, dishwasher, refrigerator, temperatureControlledCabinetCooler, oven, temperatureControlledCabinetHeater, microwaveOven, extractorHood, cooktop, cookSurface, powerSource, } from 'matterbridge';
2
- import { ClusterBehavior, PositionTag, RefrigeratorTag } from 'matterbridge/matter';
3
- import { OperationalState, TemperatureControl, DishwasherMode, LaundryWasherControls, LaundryWasherMode, LaundryDryerControls, OvenMode, ModeBase, RefrigeratorAndTemperatureControlledCabinetMode, MicrowaveOvenMode, MicrowaveOvenControl, OvenCavityOperationalState, } from 'matterbridge/matter/clusters';
4
- import { DishwasherAlarmServer, LaundryDryerControlsServer, LaundryWasherControlsServer, MicrowaveOvenControlBehavior, MicrowaveOvenModeServer, TemperatureControlBehavior, } from 'matterbridge/matter/behaviors';
2
+ import { PositionTag, RefrigeratorTag } from 'matterbridge/matter';
3
+ import { OperationalState, TemperatureControl, DishwasherMode, LaundryWasherControls, LaundryWasherMode, LaundryDryerControls, OvenMode, ModeBase, RefrigeratorAndTemperatureControlledCabinetMode, MicrowaveOvenMode, MicrowaveOvenControl, } from 'matterbridge/matter/clusters';
4
+ import { DishwasherAlarmServer, DishwasherModeBehavior, LaundryDryerControlsServer, LaundryWasherControlsServer, LaundryWasherModeBehavior, MicrowaveOvenControlBehavior, MicrowaveOvenModeServer, OvenCavityOperationalStateBehavior, OvenModeBehavior, RefrigeratorAndTemperatureControlledCabinetModeBehavior, TemperatureControlBehavior, } from 'matterbridge/matter/behaviors';
5
5
  export class Appliances extends MatterbridgeEndpoint {
6
6
  constructor(deviceType, name, serial) {
7
7
  super([deviceType, powerSource], { uniqueStorageKey: `${name}-${serial}` }, true);
@@ -112,7 +112,7 @@ export class Appliances extends MatterbridgeEndpoint {
112
112
  }
113
113
  }
114
114
  createDefaultOvenCavityOperationalStateClusterServer(operationalState = OperationalState.OperationalStateEnum.Stopped) {
115
- this.behaviors.require(OvenCavityOperationalStateServer, {
115
+ this.behaviors.require(MatterbridgeOvenCavityOperationalStateServer, {
116
116
  phaseList: [],
117
117
  currentPhase: null,
118
118
  operationalStateList: [
@@ -126,7 +126,7 @@ export class Appliances extends MatterbridgeEndpoint {
126
126
  return this;
127
127
  }
128
128
  static createDefaultRefrigeratorAndTemperatureControlledCabinetModeClusterServer(endpoint, currentMode) {
129
- endpoint.behaviors.require(RefrigeratorAndTemperatureControlledCabinetModeServer, {
129
+ endpoint.behaviors.require(MatterbridgeRefrigeratorAndTemperatureControlledCabinetModeServer, {
130
130
  supportedModes: [
131
131
  { label: 'Auto', mode: 0, modeTags: [{ value: RefrigeratorAndTemperatureControlledCabinetMode.ModeTag.Auto }] },
132
132
  { label: 'RapidCool', mode: 1, modeTags: [{ value: RefrigeratorAndTemperatureControlledCabinetMode.ModeTag.RapidCool }] },
@@ -137,7 +137,7 @@ export class Appliances extends MatterbridgeEndpoint {
137
137
  return endpoint;
138
138
  }
139
139
  static createDefaultOvenModeClusterServer(endpoint, currentMode) {
140
- endpoint.behaviors.require(OvenModeServer, {
140
+ endpoint.behaviors.require(MatterbridgeOvenModeServer, {
141
141
  supportedModes: [
142
142
  { label: 'Bake', mode: 1, modeTags: [{ value: OvenMode.ModeTag.Bake }] },
143
143
  { label: 'Convection', mode: 2, modeTags: [{ value: OvenMode.ModeTag.Convection }] },
@@ -155,7 +155,7 @@ export class Appliances extends MatterbridgeEndpoint {
155
155
  return endpoint;
156
156
  }
157
157
  createDefaultDishwasherModeClusterServer(currentMode) {
158
- this.behaviors.require(DishwasherModeServer, {
158
+ this.behaviors.require(MatterbridgeDishwasherModeServer, {
159
159
  supportedModes: [
160
160
  { label: 'Light', mode: 1, modeTags: [{ value: DishwasherMode.ModeTag.Light }] },
161
161
  { label: 'Normal', mode: 2, modeTags: [{ value: DishwasherMode.ModeTag.Normal }] },
@@ -166,7 +166,7 @@ export class Appliances extends MatterbridgeEndpoint {
166
166
  return this;
167
167
  }
168
168
  createDefaultLaundryWasherModeClusterServer(currentMode) {
169
- this.behaviors.require(LaundryWasherModeServer, {
169
+ this.behaviors.require(MatterbridgeLaundryWasherModeServer, {
170
170
  supportedModes: [
171
171
  { label: 'Delicate', mode: 1, modeTags: [{ value: LaundryWasherMode.ModeTag.Delicate }] },
172
172
  { label: 'Normal', mode: 2, modeTags: [{ value: LaundryWasherMode.ModeTag.Normal }] },
@@ -350,8 +350,7 @@ class MatterbridgeMicrowaveOvenControlServer extends MicrowaveOvenControlBehavio
350
350
  }
351
351
  }
352
352
  }
353
- export const OvenCavityOperationalStateBehavior = ClusterBehavior.withInterface().for(OvenCavityOperationalState.Cluster);
354
- export class OvenCavityOperationalStateServer extends OvenCavityOperationalStateBehavior {
353
+ export class MatterbridgeOvenCavityOperationalStateServer extends OvenCavityOperationalStateBehavior {
355
354
  initialize() {
356
355
  const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
357
356
  device.log.info('OvenCavityOperationalStateServer initialized: setting operational state to Stopped and operational error to No error');
@@ -377,8 +376,7 @@ export class OvenCavityOperationalStateServer extends OvenCavityOperationalState
377
376
  };
378
377
  }
379
378
  }
380
- export const RefrigeratorAndTemperatureControlledCabinetModeBehavior = ClusterBehavior.withInterface().for(RefrigeratorAndTemperatureControlledCabinetMode.Cluster);
381
- class RefrigeratorAndTemperatureControlledCabinetModeServer extends RefrigeratorAndTemperatureControlledCabinetModeBehavior {
379
+ class MatterbridgeRefrigeratorAndTemperatureControlledCabinetModeServer extends RefrigeratorAndTemperatureControlledCabinetModeBehavior {
382
380
  initialize() {
383
381
  const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
384
382
  device.log.info('MatterbridgeRefrigeratorAndTemperatureControlledCabinetModeServer initialized: setting currentMode to 1');
@@ -398,8 +396,7 @@ class RefrigeratorAndTemperatureControlledCabinetModeServer extends Refrigerator
398
396
  }
399
397
  }
400
398
  }
401
- export const OvenModeBehavior = ClusterBehavior.withInterface().for(OvenMode.Cluster);
402
- class OvenModeServer extends OvenModeBehavior {
399
+ class MatterbridgeOvenModeServer extends OvenModeBehavior {
403
400
  initialize() {
404
401
  const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
405
402
  device.log.info('OvenModeServer initialized: setting currentMode to 3');
@@ -419,8 +416,7 @@ class OvenModeServer extends OvenModeBehavior {
419
416
  }
420
417
  }
421
418
  }
422
- export const DishwasherModeBehavior = ClusterBehavior.withInterface().for(DishwasherMode.Cluster);
423
- class DishwasherModeServer extends DishwasherModeBehavior {
419
+ class MatterbridgeDishwasherModeServer extends DishwasherModeBehavior {
424
420
  initialize() {
425
421
  const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
426
422
  device.log.info('DishwasherModeServer initialized: setting currentMode to 3');
@@ -448,8 +444,7 @@ class DishwasherModeServer extends DishwasherModeBehavior {
448
444
  }
449
445
  }
450
446
  }
451
- export const LaundryWasherModeBehavior = ClusterBehavior.withInterface().for(LaundryWasherMode.Cluster);
452
- class LaundryWasherModeServer extends LaundryWasherModeBehavior {
447
+ class MatterbridgeLaundryWasherModeServer extends LaundryWasherModeBehavior {
453
448
  initialize() {
454
449
  const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
455
450
  device.log.info('LaundryWasherModeServer initialized: setting currentMode to 3');
package/dist/platform.js CHANGED
@@ -1,9 +1,8 @@
1
- import { MatterbridgeEndpoint, MatterbridgeDynamicPlatform, airQualitySensor, bridgedNode, colorTemperatureLight, coverDevice, dimmableLight, doorLockDevice, fanDevice, flowSensor, humiditySensor, onOffLight, onOffOutlet, onOffSwitch, powerSource, rainSensor, smokeCoAlarm, temperatureSensor, thermostatDevice, waterFreezeDetector, waterLeakDetector, airPurifier, pumpDevice, waterValve, genericSwitch, airConditioner, laundryWasher, cooktop, extractorHood, microwaveOven, oven, refrigerator, dishwasher, laundryDryer, onOffMountedSwitch, dimmableMountedSwitch, extendedColorLight, } from 'matterbridge';
1
+ import { MatterbridgeEndpoint, MatterbridgeDynamicPlatform, airQualitySensor, bridgedNode, colorTemperatureLight, coverDevice, dimmableLight, doorLockDevice, fanDevice, flowSensor, humiditySensor, onOffLight, onOffOutlet, onOffSwitch, powerSource, rainSensor, smokeCoAlarm, temperatureSensor, thermostatDevice, waterFreezeDetector, waterLeakDetector, airPurifier, pumpDevice, waterValve, genericSwitch, airConditioner, laundryWasher, cooktop, extractorHood, microwaveOven, oven, refrigerator, dishwasher, laundryDryer, onOffMountedSwitch, dimmableMountedSwitch, extendedColorLight, RoboticVacuumCleaner, WaterHeater, } from 'matterbridge';
2
2
  import { isValidBoolean, isValidNumber } from 'matterbridge/utils';
3
3
  import { LocationTag } from 'matterbridge/matter';
4
4
  import { PowerSource, BooleanState, OnOff, LevelControl, AirQuality, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FlowMeasurement, ColorControl, DoorLock, FanControl, FormaldehydeConcentrationMeasurement, NitrogenDioxideConcentrationMeasurement, OzoneConcentrationMeasurement, Pm10ConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, RadonConcentrationMeasurement, RelativeHumidityMeasurement, RelativeHumidityMeasurementCluster, SmokeCoAlarm, TemperatureMeasurement, Thermostat, ThermostatCluster, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
5
5
  import { Appliances } from './appliances.js';
6
- import { Robot } from './robot.js';
7
6
  export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatform {
8
7
  switch;
9
8
  mountedOnOffSwitch;
@@ -15,7 +14,8 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
15
14
  lightHS;
16
15
  lightCT;
17
16
  outlet;
18
- cover;
17
+ coverLift;
18
+ coverLiftTilt;
19
19
  lock;
20
20
  thermoAuto;
21
21
  thermoHeat;
@@ -35,6 +35,7 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
35
35
  momentarySwitch;
36
36
  latchingSwitch;
37
37
  vacuum;
38
+ heater;
38
39
  switchInterval;
39
40
  lightInterval;
40
41
  outletInterval;
@@ -57,8 +58,8 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
57
58
  fanModeLookup = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Smart'];
58
59
  constructor(matterbridge, log, config) {
59
60
  super(matterbridge, log, config);
60
- if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.0.1')) {
61
- throw new Error(`This plugin requires Matterbridge version >= "3.0.1". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
61
+ if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.0.4')) {
62
+ throw new Error(`This plugin requires Matterbridge version >= "3.0.4". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
62
63
  }
63
64
  this.log.info('Initializing platform:', this.config.name);
64
65
  if (config.whiteList === undefined)
@@ -432,41 +433,76 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
432
433
  await this.outlet?.setAttribute(OnOff.Cluster.id, 'onOff', false, this.outlet?.log);
433
434
  this.outlet?.log.info('Command off called');
434
435
  });
435
- this.cover = new MatterbridgeEndpoint([coverDevice, bridgedNode, powerSource], { uniqueStorageKey: 'Cover' }, this.config.debug)
436
+ this.coverLift = new MatterbridgeEndpoint([coverDevice, bridgedNode, powerSource], { uniqueStorageKey: 'CoverLift' }, this.config.debug)
436
437
  .createDefaultIdentifyClusterServer()
437
438
  .createDefaultGroupsClusterServer()
438
- .createDefaultBridgedDeviceBasicInformationClusterServer('Cover', '0x01020564', 0xfff1, 'Matterbridge', 'Matterbridge Cover', parseInt(this.version.replace(/\D/g, '')), this.version === '' ? 'Unknown' : this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion)
439
+ .createDefaultBridgedDeviceBasicInformationClusterServer('Cover lift', 'CL01020564', 0xfff1, 'Matterbridge', 'Matterbridge Cover', parseInt(this.version.replace(/\D/g, '')), this.version === '' ? 'Unknown' : this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion)
439
440
  .createDefaultWindowCoveringClusterServer()
440
441
  .createDefaultPowerSourceRechargeableBatteryClusterServer(86);
441
- this.setSelectDevice(this.cover.serialNumber ?? '', this.cover.deviceName ?? '', undefined, 'hub');
442
- if (this.validateDevice(this.cover.deviceName ?? '')) {
443
- await this.registerDevice(this.cover);
444
- this.bridgedDevices.set(this.cover.deviceName ?? '', this.cover);
442
+ this.setSelectDevice(this.coverLift.serialNumber ?? '', this.coverLift.deviceName ?? '', undefined, 'hub');
443
+ if (this.validateDevice(this.coverLift.deviceName ?? '')) {
444
+ await this.registerDevice(this.coverLift);
445
+ this.bridgedDevices.set(this.coverLift.deviceName ?? '', this.coverLift);
445
446
  }
446
447
  else {
447
- this.cover = undefined;
448
+ this.coverLift = undefined;
448
449
  }
449
- this.cover?.subscribeAttribute(WindowCovering.Cluster.id, 'mode', (newValue, oldValue) => {
450
- this.cover?.log.info(`Attribute mode changed from ${oldValue} to ${newValue}. Reverse: ${newValue.motorDirectionReversed}. Calibration: ${newValue.calibrationMode}. Maintenance: ${newValue.maintenanceMode}. LED: ${newValue.ledFeedback}`);
451
- }, this.cover.log);
452
- this.cover?.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
453
- this.cover?.log.info(`Command identify called identifyTime:${identifyTime}`);
450
+ this.coverLift?.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
451
+ this.coverLift?.log.info(`Command identify called identifyTime:${identifyTime}`);
454
452
  });
455
- this.cover?.addCommandHandler('stopMotion', async () => {
456
- await this.cover?.setWindowCoveringTargetAsCurrentAndStopped();
457
- this.cover?.log.info(`Command stopMotion called`);
453
+ this.coverLift?.addCommandHandler('stopMotion', async () => {
454
+ await this.coverLift?.setWindowCoveringTargetAsCurrentAndStopped();
455
+ this.coverLift?.log.info(`Command stopMotion called`);
458
456
  });
459
- this.cover?.addCommandHandler('downOrClose', async () => {
460
- await this.cover?.setWindowCoveringCurrentTargetStatus(10000, 10000, WindowCovering.MovementStatus.Stopped);
461
- this.cover?.log.info(`Command downOrClose called`);
457
+ this.coverLift?.addCommandHandler('downOrClose', async () => {
458
+ await this.coverLift?.setWindowCoveringCurrentTargetStatus(10000, 10000, WindowCovering.MovementStatus.Stopped);
459
+ this.coverLift?.log.info(`Command downOrClose called`);
462
460
  });
463
- this.cover?.addCommandHandler('upOrOpen', async () => {
464
- await this.cover?.setWindowCoveringCurrentTargetStatus(0, 0, WindowCovering.MovementStatus.Stopped);
465
- this.cover?.log.info(`Command upOrOpen called`);
461
+ this.coverLift?.addCommandHandler('upOrOpen', async () => {
462
+ await this.coverLift?.setWindowCoveringCurrentTargetStatus(0, 0, WindowCovering.MovementStatus.Stopped);
463
+ this.coverLift?.log.info(`Command upOrOpen called`);
466
464
  });
467
- this.cover?.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue } }) => {
468
- await this.cover?.setWindowCoveringCurrentTargetStatus(liftPercent100thsValue, liftPercent100thsValue, WindowCovering.MovementStatus.Stopped);
469
- this.cover?.log.info(`Command goToLiftPercentage ${liftPercent100thsValue} called`);
465
+ this.coverLift?.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue } }) => {
466
+ await this.coverLift?.setWindowCoveringCurrentTargetStatus(liftPercent100thsValue, liftPercent100thsValue, WindowCovering.MovementStatus.Stopped);
467
+ this.coverLift?.log.info(`Command goToLiftPercentage ${liftPercent100thsValue} called`);
468
+ });
469
+ this.coverLiftTilt = new MatterbridgeEndpoint([coverDevice, bridgedNode, powerSource], { uniqueStorageKey: 'CoverLiftTilt' }, this.config.debug)
470
+ .createDefaultIdentifyClusterServer()
471
+ .createDefaultGroupsClusterServer()
472
+ .createDefaultBridgedDeviceBasicInformationClusterServer('Cover lift and tilt', 'CLT01020554', 0xfff1, 'Matterbridge', 'Matterbridge Cover', parseInt(this.version.replace(/\D/g, '')), this.version === '' ? 'Unknown' : this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion)
473
+ .createDefaultLiftTiltWindowCoveringClusterServer()
474
+ .createDefaultPowerSourceRechargeableBatteryClusterServer(86);
475
+ this.setSelectDevice(this.coverLiftTilt.serialNumber ?? '', this.coverLiftTilt.deviceName ?? '', undefined, 'hub');
476
+ if (this.validateDevice(this.coverLiftTilt.deviceName ?? '')) {
477
+ await this.registerDevice(this.coverLiftTilt);
478
+ this.bridgedDevices.set(this.coverLiftTilt.deviceName ?? '', this.coverLiftTilt);
479
+ }
480
+ else {
481
+ this.coverLiftTilt = undefined;
482
+ }
483
+ this.coverLiftTilt?.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
484
+ this.coverLiftTilt?.log.info(`Command identify called identifyTime:${identifyTime}`);
485
+ });
486
+ this.coverLiftTilt?.addCommandHandler('stopMotion', async () => {
487
+ await this.coverLiftTilt?.setWindowCoveringTargetAsCurrentAndStopped();
488
+ this.coverLiftTilt?.log.info(`Command stopMotion called`);
489
+ });
490
+ this.coverLiftTilt?.addCommandHandler('downOrClose', async () => {
491
+ await this.coverLiftTilt?.setWindowCoveringCurrentTargetStatus(10000, 10000, WindowCovering.MovementStatus.Stopped);
492
+ this.coverLiftTilt?.log.info(`Command downOrClose called`);
493
+ });
494
+ this.coverLiftTilt?.addCommandHandler('upOrOpen', async () => {
495
+ await this.coverLiftTilt?.setWindowCoveringCurrentTargetStatus(0, 0, WindowCovering.MovementStatus.Stopped);
496
+ this.coverLiftTilt?.log.info(`Command upOrOpen called`);
497
+ });
498
+ this.coverLiftTilt?.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue } }) => {
499
+ await this.coverLiftTilt?.setWindowCoveringCurrentTargetStatus(liftPercent100thsValue, liftPercent100thsValue, WindowCovering.MovementStatus.Stopped);
500
+ this.coverLiftTilt?.log.info(`Command goToLiftPercentage ${liftPercent100thsValue} called`);
501
+ });
502
+ this.coverLiftTilt?.addCommandHandler('goToTiltPercentage', async ({ request: { tiltPercent100thsValue } }) => {
503
+ const position = this.coverLiftTilt?.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.coverLiftTilt?.log);
504
+ await this.coverLiftTilt?.setWindowCoveringTargetAndCurrentPosition(position, tiltPercent100thsValue);
505
+ this.coverLiftTilt?.log.info(`Command goToTiltPercentage ${tiltPercent100thsValue} called`);
470
506
  });
471
507
  this.lock = new MatterbridgeEndpoint([doorLockDevice, bridgedNode, powerSource], { uniqueStorageKey: 'Lock' }, this.config.debug)
472
508
  .createDefaultIdentifyClusterServer()
@@ -538,14 +574,14 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
538
574
  this.thermoAuto?.log.info('Set occupiedCoolingSetpoint:', setpoint);
539
575
  }
540
576
  });
541
- this.thermoAuto?.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
577
+ await this.thermoAuto?.subscribeAttribute(ThermostatCluster.id, 'systemMode', (value) => {
542
578
  const lookupSystemMode = ['Off', 'Auto', '', 'Cool', 'Heat', 'EmergencyHeat', 'Precooling', 'FanOnly', 'Dry', 'Sleep'];
543
579
  this.thermoAuto?.log.info('Subscribe systemMode called with:', lookupSystemMode[value]);
544
580
  }, this.thermoAuto.log);
545
- this.thermoAuto?.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', async (value) => {
581
+ await this.thermoAuto?.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', (value) => {
546
582
  this.thermoAuto?.log.info('Subscribe occupiedHeatingSetpoint called with:', value / 100);
547
583
  }, this.thermoAuto.log);
548
- this.thermoAuto?.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
584
+ await this.thermoAuto?.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', (value) => {
549
585
  this.thermoAuto?.log.info('Subscribe occupiedCoolingSetpoint called with:', value / 100);
550
586
  }, this.thermoAuto.log);
551
587
  this.thermoHeat = new MatterbridgeEndpoint([thermostatDevice, bridgedNode, powerSource], { uniqueStorageKey: 'Thermostat (Heat)' }, this.config.debug)
@@ -580,11 +616,11 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
580
616
  this.thermoHeat?.addCommandHandler('triggerEffect', async ({ request: { effectIdentifier, effectVariant } }) => {
581
617
  this.thermoHeat?.log.info(`Command identify called effectIdentifier ${effectIdentifier} effectVariant ${effectVariant}`);
582
618
  });
583
- this.thermoHeat?.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
619
+ await this.thermoHeat?.subscribeAttribute(ThermostatCluster.id, 'systemMode', (value) => {
584
620
  const lookupSystemMode = ['Off', 'Auto', '', 'Cool', 'Heat', 'EmergencyHeat', 'Precooling', 'FanOnly', 'Dry', 'Sleep'];
585
621
  this.thermoHeat?.log.info('Subscribe systemMode called with:', lookupSystemMode[value]);
586
622
  }, this.thermoHeat.log);
587
- this.thermoHeat?.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', async (value) => {
623
+ await this.thermoHeat?.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', (value) => {
588
624
  this.thermoHeat?.log.info('Subscribe occupiedHeatingSetpoint called with:', value / 100);
589
625
  }, this.thermoHeat.log);
590
626
  this.thermoCool = new MatterbridgeEndpoint([thermostatDevice, bridgedNode, powerSource], { uniqueStorageKey: 'Thermostat (Cool)' }, this.config.debug)
@@ -607,11 +643,11 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
607
643
  this.thermoCool?.addCommandHandler('triggerEffect', async ({ request: { effectIdentifier, effectVariant } }) => {
608
644
  this.thermoCool?.log.info(`Command identify called effectIdentifier ${effectIdentifier} effectVariant ${effectVariant}`);
609
645
  });
610
- this.thermoCool?.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
646
+ await this.thermoCool?.subscribeAttribute(ThermostatCluster.id, 'systemMode', (value) => {
611
647
  const lookupSystemMode = ['Off', 'Auto', '', 'Cool', 'Heat', 'EmergencyHeat', 'Precooling', 'FanOnly', 'Dry', 'Sleep'];
612
648
  this.thermoCool?.log.info('Subscribe systemMode called with:', lookupSystemMode[value]);
613
649
  }, this.thermoCool.log);
614
- this.thermoCool?.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
650
+ await this.thermoCool?.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', (value) => {
615
651
  this.thermoCool?.log.info('Subscribe occupiedCoolingSetpoint called with:', value / 100);
616
652
  }, this.thermoCool.log);
617
653
  this.airPurifier = new MatterbridgeEndpoint([airPurifier, temperatureSensor, humiditySensor, bridgedNode, powerSource], { uniqueStorageKey: 'Air purifier' }, this.config.debug)
@@ -634,31 +670,28 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
634
670
  this.airPurifier?.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
635
671
  this.airPurifier?.log.info(`Command identify called identifyTime:${identifyTime}`);
636
672
  });
637
- this.airPurifier?.subscribeAttribute(FanControl.Cluster.id, 'fanMode', async (newValue, oldValue) => {
638
- this.fan?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]}`);
673
+ await this.airPurifier?.subscribeAttribute(FanControl.Cluster.id, 'fanMode', (newValue, oldValue, context) => {
674
+ this.airPurifier?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.offline === true ? 'offline' : 'online'}`);
639
675
  if (newValue === FanControl.FanMode.Off) {
640
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 0, this.airPurifier?.log);
676
+ this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 0, this.airPurifier?.log);
641
677
  }
642
678
  else if (newValue === FanControl.FanMode.Low) {
643
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 33, this.airPurifier?.log);
679
+ this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 33, this.airPurifier?.log);
644
680
  }
645
681
  else if (newValue === FanControl.FanMode.Medium) {
646
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 66, this.airPurifier?.log);
682
+ this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 66, this.airPurifier?.log);
647
683
  }
648
684
  else if (newValue === FanControl.FanMode.High) {
649
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.airPurifier?.log);
685
+ this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.airPurifier?.log);
650
686
  }
651
687
  else if (newValue === FanControl.FanMode.On) {
652
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.airPurifier?.log);
653
- }
654
- else if (newValue === FanControl.FanMode.Auto) {
655
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 50, this.airPurifier?.log);
688
+ this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.airPurifier?.log);
656
689
  }
657
690
  }, this.airPurifier.log);
658
- this.airPurifier?.subscribeAttribute(FanControl.Cluster.id, 'percentSetting', async (newValue, oldValue) => {
659
- this.fan?.log.info(`Percent setting changed from ${oldValue} to ${newValue}`);
691
+ await this.airPurifier?.subscribeAttribute(FanControl.Cluster.id, 'percentSetting', (newValue, oldValue, context) => {
692
+ this.airPurifier?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.offline === true ? 'offline' : 'online'}`);
660
693
  if (isValidNumber(newValue, 0, 100))
661
- await this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', newValue, this.airPurifier?.log);
694
+ this.airPurifier?.setAttribute(FanControl.Cluster.id, 'percentCurrent', newValue, this.airPurifier?.log);
662
695
  }, this.airPurifier.log);
663
696
  this.airConditioner = new MatterbridgeEndpoint([airConditioner, bridgedNode, powerSource], { uniqueStorageKey: 'Air Conditioner' }, this.config.debug)
664
697
  .createDefaultBridgedDeviceBasicInformationClusterServer('Air Conditioner', '0x96382864AC', 0xfff1, 'Matterbridge', 'Matterbridge Air Conditioner', parseInt(this.version.replace(/\D/g, '')), this.version === '' ? 'Unknown' : this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion)
@@ -718,11 +751,15 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
718
751
  });
719
752
  this.pump?.addCommandHandler('on', async () => {
720
753
  this.pump?.log.info('Command on called');
721
- await this.pump?.setAttribute(OnOff.Cluster.id, 'onOff', true, this.pump?.log);
722
754
  });
723
755
  this.pump?.addCommandHandler('off', async () => {
724
756
  this.pump?.log.info('Command off called');
725
- await this.pump?.setAttribute(OnOff.Cluster.id, 'onOff', false, this.pump?.log);
757
+ });
758
+ this.pump?.addCommandHandler('moveToLevel', async ({ request: { level } }) => {
759
+ this.pump?.log.info(`Command moveToLevel called request: ${level}`);
760
+ });
761
+ this.pump?.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level } }) => {
762
+ this.pump?.log.info(`Command moveToLevelWithOnOff called request: ${level}`);
726
763
  });
727
764
  this.valve = new MatterbridgeEndpoint([waterValve, bridgedNode, powerSource], { uniqueStorageKey: 'Water valve' }, this.config.debug)
728
765
  .createDefaultBridgedDeviceBasicInformationClusterServer('Water valve', '0x96382864WV', 0xfff1, 'Matterbridge', 'Matterbridge Water valve', parseInt(this.version.replace(/\D/g, '')), this.version === '' ? 'Unknown' : this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion)
@@ -752,42 +789,42 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
752
789
  else {
753
790
  this.fan = undefined;
754
791
  }
755
- this.fan?.subscribeAttribute(FanControl.Cluster.id, 'fanMode', async (newValue, oldValue) => {
756
- this.fan?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]}`);
792
+ await this.fan?.subscribeAttribute(FanControl.Cluster.id, 'fanMode', (newValue, oldValue, context) => {
793
+ this.fan?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.offline === true ? 'offline' : 'online'}`);
757
794
  if (newValue === FanControl.FanMode.Off) {
758
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 0, this.fan?.log);
759
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 0, this.fan?.log);
795
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 0, this.fan?.log);
796
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 0, this.fan?.log);
760
797
  }
761
798
  else if (newValue === FanControl.FanMode.Low) {
762
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 33, this.fan?.log);
763
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 33, this.fan?.log);
799
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 33, this.fan?.log);
800
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 33, this.fan?.log);
764
801
  }
765
802
  else if (newValue === FanControl.FanMode.Medium) {
766
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 66, this.fan?.log);
767
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 66, this.fan?.log);
803
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 66, this.fan?.log);
804
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 66, this.fan?.log);
768
805
  }
769
806
  else if (newValue === FanControl.FanMode.High) {
770
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fan?.log);
771
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fan?.log);
807
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fan?.log);
808
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fan?.log);
772
809
  }
773
810
  else if (newValue === FanControl.FanMode.On) {
774
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fan?.log);
775
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fan?.log);
811
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fan?.log);
812
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fan?.log);
776
813
  }
777
814
  else if (newValue === FanControl.FanMode.Auto) {
778
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 50, this.fan?.log);
779
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 50, this.fan?.log);
815
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentSetting', 50, this.fan?.log);
816
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 50, this.fan?.log);
780
817
  }
781
818
  }, this.fan.log);
782
- this.fan?.subscribeAttribute(FanControl.Cluster.id, 'percentSetting', async (newValue, oldValue) => {
783
- this.fan?.log.info(`Percent setting changed from ${oldValue} to ${newValue}`);
819
+ await this.fan?.subscribeAttribute(FanControl.Cluster.id, 'percentSetting', (newValue, oldValue, context) => {
820
+ this.fan?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.offline === true ? 'offline' : 'online'}`);
784
821
  if (isValidNumber(newValue, 0, 100))
785
- await this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', newValue, this.fan?.log);
822
+ this.fan?.setAttribute(FanControl.Cluster.id, 'percentCurrent', newValue, this.fan?.log);
786
823
  }, this.fan.log);
787
- this.fan?.subscribeAttribute(FanControl.Cluster.id, 'speedSetting', async (newValue, oldValue) => {
788
- this.fan?.log.info(`Speed setting changed from ${oldValue} to ${newValue}`);
824
+ await this.fan?.subscribeAttribute(FanControl.Cluster.id, 'speedSetting', (newValue, oldValue, context) => {
825
+ this.fan?.log.info(`Speed setting changed from ${oldValue} to ${newValue} context: ${context.offline === true ? 'offline' : 'online'}`);
789
826
  if (isValidNumber(newValue, 0, 100))
790
- await this.fan?.setAttribute(FanControl.Cluster.id, 'speedCurrent', newValue, this.fan?.log);
827
+ this.fan?.setAttribute(FanControl.Cluster.id, 'speedCurrent', newValue, this.fan?.log);
791
828
  }, this.fan.log);
792
829
  this.waterLeak = new MatterbridgeEndpoint([waterLeakDetector, bridgedNode, powerSource], { uniqueStorageKey: 'Water leak detector' }, this.config.debug)
793
830
  .createDefaultBridgedDeviceBasicInformationClusterServer('Water leak detector', 'serial_98745631222', 0xfff1, 'Matterbridge', 'Matterbridge WaterLeakDetector', parseInt(this.version.replace(/\D/g, '')), this.version === '' ? 'Unknown' : this.version, parseInt(this.matterbridge.matterbridgeVersion.replace(/\D/g, '')), this.matterbridge.matterbridgeVersion)
@@ -922,13 +959,19 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
922
959
  this.latchingSwitch = undefined;
923
960
  }
924
961
  if (this.config.enableRVC === true) {
925
- const robot = new Robot('Robot Vacuum', '1238777820');
962
+ const robot = new RoboticVacuumCleaner('Robot Vacuum', '1238777820');
926
963
  this.setSelectDevice(robot.serialNumber ?? '', robot.deviceName ?? '', undefined, 'hub');
927
964
  if (this.validateDevice(robot.deviceName ?? '')) {
928
965
  await this.registerDevice(robot);
929
966
  this.bridgedDevices.set(robot.deviceName ?? '', robot);
930
967
  }
931
968
  }
969
+ const heater = new WaterHeater('Water Heater', '3456177820');
970
+ this.setSelectDevice(heater.serialNumber ?? '', heater.deviceName ?? '', undefined, 'hub');
971
+ if (this.validateDevice(heater.deviceName ?? '')) {
972
+ await this.registerDevice(heater);
973
+ this.bridgedDevices.set(heater.deviceName ?? '', heater);
974
+ }
932
975
  const laundryWasherDevice = new Appliances(laundryWasher, 'Laundry Washer', '1234567890');
933
976
  this.setSelectDevice(laundryWasherDevice.serialNumber ?? '', laundryWasherDevice.deviceName ?? '', undefined, 'hub');
934
977
  if (this.validateDevice(laundryWasherDevice.deviceName ?? '')) {
@@ -1081,17 +1124,17 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1081
1124
  }
1082
1125
  }, 60 * 1000 + 300);
1083
1126
  }
1084
- await this.cover?.setWindowCoveringTargetAsCurrentAndStopped();
1085
- this.cover?.log.info('Set cover initial targetPositionLiftPercent100ths = currentPositionLiftPercent100ths and operationalStatus to Stopped.');
1127
+ await this.coverLift?.setWindowCoveringTargetAsCurrentAndStopped();
1128
+ this.coverLift?.log.info('Set cover initial targetPositionLiftPercent100ths = currentPositionLiftPercent100ths and operationalStatus to Stopped.');
1086
1129
  if (this.config.useInterval) {
1087
1130
  this.coverInterval = setInterval(async () => {
1088
- let position = this.cover?.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.cover.log);
1131
+ let position = this.coverLift?.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.coverLift.log);
1089
1132
  if (isValidNumber(position, 0, 10000)) {
1090
1133
  position = position > 9000 ? 0 : position + 1000;
1091
- await this.cover?.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position, this.cover.log);
1092
- await this.cover?.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', position, this.cover.log);
1093
- await this.cover?.setAttribute(WindowCovering.Cluster.id, 'operationalStatus', { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped }, this.cover.log);
1094
- this.cover?.log.info(`Set cover current and target positionLiftPercent100ths to ${position} and operationalStatus to Stopped`);
1134
+ await this.coverLift?.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position, this.coverLift.log);
1135
+ await this.coverLift?.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', position, this.coverLift.log);
1136
+ await this.coverLift?.setAttribute(WindowCovering.Cluster.id, 'operationalStatus', { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped }, this.coverLift.log);
1137
+ this.coverLift?.log.info(`Set cover current and target positionLiftPercent100ths to ${position} and operationalStatus to Stopped`);
1095
1138
  }
1096
1139
  }, 60 * 1000 + 400);
1097
1140
  }
@@ -1266,26 +1309,27 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1266
1309
  await surface2?.setAttribute(OnOff.Cluster.id, 'onOff', true, surface2.log);
1267
1310
  surface2?.log.info(`Set Surface 2 onOff only OnOff to on`);
1268
1311
  if (this.config.useInterval) {
1312
+ this.genericSwitchLastEvent = 'Release';
1269
1313
  this.genericSwitchInterval = setInterval(async () => {
1270
1314
  if (this.genericSwitchLastEvent === 'Release') {
1271
- await this.momentarySwitch?.triggerSwitchEvent('Single', this.momentarySwitch?.log);
1272
1315
  this.genericSwitchLastEvent = 'Single';
1316
+ await this.momentarySwitch?.triggerSwitchEvent('Single', this.momentarySwitch?.log);
1273
1317
  }
1274
1318
  else if (this.genericSwitchLastEvent === 'Single') {
1275
- await this.momentarySwitch?.triggerSwitchEvent('Double', this.momentarySwitch?.log);
1276
1319
  this.genericSwitchLastEvent = 'Double';
1320
+ await this.momentarySwitch?.triggerSwitchEvent('Double', this.momentarySwitch?.log);
1277
1321
  }
1278
1322
  else if (this.genericSwitchLastEvent === 'Double') {
1279
- await this.momentarySwitch?.triggerSwitchEvent('Long', this.momentarySwitch?.log);
1280
1323
  this.genericSwitchLastEvent = 'Long';
1324
+ await this.momentarySwitch?.triggerSwitchEvent('Long', this.momentarySwitch?.log);
1281
1325
  }
1282
1326
  else if (this.genericSwitchLastEvent === 'Long') {
1283
- await this.latchingSwitch?.triggerSwitchEvent('Press', this.latchingSwitch?.log);
1284
1327
  this.genericSwitchLastEvent = 'Press';
1328
+ await this.latchingSwitch?.triggerSwitchEvent('Press', this.latchingSwitch?.log);
1285
1329
  }
1286
1330
  else if (this.genericSwitchLastEvent === 'Press') {
1287
- await this.latchingSwitch?.triggerSwitchEvent('Release', this.latchingSwitch?.log);
1288
1331
  this.genericSwitchLastEvent = 'Release';
1332
+ await this.latchingSwitch?.triggerSwitchEvent('Release', this.latchingSwitch?.log);
1289
1333
  }
1290
1334
  }, 60 * 1000 + 1900);
1291
1335
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge-example-dynamic-platform",
3
- "version": "1.2.1",
3
+ "version": "1.2.3-rc.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-example-dynamic-platform",
9
- "version": "1.2.1",
9
+ "version": "1.2.3-rc.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "node-ansi-logger": "3.0.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-example-dynamic-platform",
3
- "version": "1.2.1",
3
+ "version": "1.2.3-rc.1",
4
4
  "description": "Matterbridge dynamic plugin",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "MIT",
@@ -24,7 +24,11 @@
24
24
  "matter",
25
25
  "matter.js",
26
26
  "example",
27
- "plugin"
27
+ "plugin",
28
+ "dynamic",
29
+ "platform",
30
+ "virtual device",
31
+ "virtual devices"
28
32
  ],
29
33
  "engines": {
30
34
  "node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0 || >=22.0.0"
package/dist/robot.js DELETED
@@ -1,210 +0,0 @@
1
- import { Matterbridge, MatterbridgeServer, MatterbridgeEndpoint, roboticVacuumCleaner, dishwasher } from 'matterbridge';
2
- import { LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, DeviceTypeId, VendorId } from 'matterbridge/matter';
3
- import { ModeBase, OperationalState, PowerSource, RvcRunMode, RvcCleanMode, RvcOperationalState, ServiceArea } from 'matterbridge/matter/clusters';
4
- import { ActionsServer, RvcCleanModeBehavior, RvcOperationalStateBehavior, RvcRunModeBehavior, ServiceAreaBehavior } from 'matterbridge/matter/behaviors';
5
- import { AnsiLogger } from 'matterbridge/logger';
6
- export class Robot extends MatterbridgeEndpoint {
7
- constructor(name, serial) {
8
- super(roboticVacuumCleaner, { uniqueStorageKey: `${name}-${serial}` }, true);
9
- this.createDefaultIdentifyClusterServer()
10
- .createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Robot Vacuum Cleaner')
11
- .createDefaultRvcRunModeClusterServer()
12
- .createDefaultRvcOperationalStateClusterServer()
13
- .createDefaultRvcCleanModeClusterServer()
14
- .createDefaultServiceAreaClusterServer()
15
- .createDefaultPowerSourceRechargeableBatteryClusterServer(80, PowerSource.BatChargeLevel.Ok, 5900);
16
- }
17
- createDefaultRvcRunModeClusterServer(currentMode, supportedModes) {
18
- this.behaviors.require(MatterbridgeRvcRunModeServer, {
19
- supportedModes: supportedModes ?? [
20
- { label: 'Idle', mode: 1, modeTags: [{ value: RvcRunMode.ModeTag.Idle }] },
21
- { label: 'Cleaning', mode: 2, modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }] },
22
- { label: 'Mapping', mode: 3, modeTags: [{ value: RvcRunMode.ModeTag.Mapping }] },
23
- { label: 'SpotCleaning', mode: 4, modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }, { value: RvcRunMode.ModeTag.Max }] },
24
- ],
25
- currentMode: currentMode ?? 1,
26
- });
27
- return this;
28
- }
29
- createDefaultRvcCleanModeClusterServer(currentMode, supportedModes) {
30
- this.behaviors.require(MatterbridgeRvcCleanModeServer, {
31
- supportedModes: supportedModes ?? [
32
- { label: 'Vacuum', mode: 1, modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }] },
33
- { label: 'Mop', mode: 2, modeTags: [{ value: RvcCleanMode.ModeTag.Mop }] },
34
- { label: 'Clean', mode: 3, modeTags: [{ value: RvcCleanMode.ModeTag.DeepClean }] },
35
- ],
36
- currentMode: currentMode ?? 1,
37
- });
38
- return this;
39
- }
40
- createDefaultServiceAreaClusterServer(supportedAreas, selectedAreas) {
41
- this.behaviors.require(MatterbridgeServiceAreaServer, {
42
- supportedAreas: supportedAreas ?? [
43
- {
44
- areaId: 1,
45
- mapId: null,
46
- areaInfo: { locationInfo: { locationName: 'Living', floorNumber: null, areaType: null }, landmarkInfo: null },
47
- },
48
- {
49
- areaId: 2,
50
- mapId: null,
51
- areaInfo: { locationInfo: { locationName: 'Kitchen', floorNumber: null, areaType: null }, landmarkInfo: null },
52
- },
53
- {
54
- areaId: 3,
55
- mapId: null,
56
- areaInfo: { locationInfo: { locationName: 'Bedroom', floorNumber: null, areaType: null }, landmarkInfo: null },
57
- },
58
- {
59
- areaId: 4,
60
- mapId: null,
61
- areaInfo: { locationInfo: { locationName: 'Bathroom', floorNumber: null, areaType: null }, landmarkInfo: null },
62
- },
63
- ],
64
- selectedAreas: selectedAreas ?? [],
65
- currentArea: 1,
66
- estimatedEndTime: null,
67
- });
68
- return this;
69
- }
70
- createDefaultRvcOperationalStateClusterServer(phaseList = null, currentPhase = null, operationalStateList, operationalState, operationalError) {
71
- this.behaviors.require(MatterbridgeRvcOperationalStateServer, {
72
- phaseList,
73
- currentPhase,
74
- operationalStateList: operationalStateList ?? [
75
- { operationalStateId: RvcOperationalState.OperationalState.Stopped, operationalStateLabel: 'Stopped' },
76
- { operationalStateId: RvcOperationalState.OperationalState.Running, operationalStateLabel: 'Running' },
77
- { operationalStateId: RvcOperationalState.OperationalState.Paused, operationalStateLabel: 'Paused' },
78
- { operationalStateId: RvcOperationalState.OperationalState.Error, operationalStateLabel: 'Error' },
79
- { operationalStateId: RvcOperationalState.OperationalState.SeekingCharger, operationalStateLabel: 'SeekingCharger' },
80
- { operationalStateId: RvcOperationalState.OperationalState.Charging, operationalStateLabel: 'Charging' },
81
- { operationalStateId: RvcOperationalState.OperationalState.Docked, operationalStateLabel: 'Docked' },
82
- ],
83
- operationalState: operationalState ?? RvcOperationalState.OperationalState.Docked,
84
- operationalError: operationalError ?? { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' },
85
- });
86
- return this;
87
- }
88
- }
89
- export class MatterbridgeServiceAreaServer extends ServiceAreaBehavior {
90
- initialize() {
91
- }
92
- selectAreas({ newAreas }) {
93
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
94
- for (const area of newAreas) {
95
- const supportedArea = this.state.supportedAreas.find((supportedArea) => supportedArea.areaId === area);
96
- if (!supportedArea) {
97
- device.log.error('MatterbridgeServiceAreaServer selectAreas called with unsupported area:', area);
98
- return { status: ServiceArea.SelectAreasStatus.UnsupportedArea, statusText: 'Unsupported areas' };
99
- }
100
- }
101
- this.state.selectedAreas = newAreas;
102
- this.state.currentArea = newAreas[0];
103
- device.log.info(`***MatterbridgeServiceAreaServer selectAreas called with: ${newAreas.map((area) => area.toString()).join(', ')}`);
104
- return { status: ServiceArea.SelectAreasStatus.Success, statusText: 'Succesfully selected new areas' };
105
- }
106
- }
107
- export class MatterbridgeRvcRunModeServer extends RvcRunModeBehavior {
108
- initialize() {
109
- this.state.currentMode = 1;
110
- }
111
- changeToMode({ newMode }) {
112
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
113
- const changedMode = this.state.supportedModes.find((mode) => mode.mode === newMode);
114
- if (!changedMode) {
115
- device.log.error('MatterbridgeRvcRunModeServer changeToMode called with unsupported newMode:', newMode);
116
- return { status: ModeBase.ModeChangeStatus.InvalidInMode, statusText: 'Invalid mode' };
117
- }
118
- device.changeToMode({ newMode });
119
- this.state.currentMode = newMode;
120
- if (changedMode.modeTags.find((tag) => tag.value === RvcRunMode.ModeTag.Cleaning)) {
121
- device.log.info('***MatterbridgeRvcRunModeServer changeToMode called with newMode Cleaning => Running');
122
- this.agent.get(MatterbridgeRvcOperationalStateServer).state.operationalState = RvcOperationalState.OperationalState.Running;
123
- return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Running' };
124
- }
125
- else if (changedMode.modeTags.find((tag) => tag.value === RvcRunMode.ModeTag.Idle)) {
126
- device.log.info('***MatterbridgeRvcRunModeServer changeToMode called with newMode Idle => Docked');
127
- this.agent.get(MatterbridgeRvcOperationalStateServer).state.operationalState = RvcOperationalState.OperationalState.Docked;
128
- return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Docked' };
129
- }
130
- device.log.info(`***MatterbridgeRvcRunModeServer changeToMode called with newMode ${newMode} => ${changedMode.label}`);
131
- this.agent.get(MatterbridgeRvcOperationalStateServer).state.operationalState = RvcOperationalState.OperationalState.Running;
132
- return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Success' };
133
- }
134
- }
135
- export class MatterbridgeRvcCleanModeServer extends RvcCleanModeBehavior {
136
- initialize() {
137
- this.state.currentMode = 1;
138
- }
139
- changeToMode({ newMode }) {
140
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
141
- const supported = this.state.supportedModes.find((mode) => mode.mode === newMode);
142
- if (!supported) {
143
- device.log.error('***MatterbridgeRvcCleanModeServer changeToMode called with unsupported newMode:', newMode);
144
- return { status: ModeBase.ModeChangeStatus.InvalidInMode, statusText: 'Invalid mode' };
145
- }
146
- device.changeToMode({ newMode });
147
- this.state.currentMode = newMode;
148
- device.log.info(`***MatterbridgeRvcCleanModeServer changeToMode called with newMode ${newMode} => ${supported.label}`);
149
- return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Success' };
150
- }
151
- }
152
- export class MatterbridgeRvcOperationalStateServer extends RvcOperationalStateBehavior {
153
- initialize() {
154
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
155
- device.log.info('***MatterbridgeRvcOperationalStateServer initialized: setting operational state to Docked');
156
- this.state.operationalState = RvcOperationalState.OperationalState.Docked;
157
- this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
158
- }
159
- pause() {
160
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
161
- device.log.info('MatterbridgeRvcOperationalStateServer: pause called setting operational state to Paused and currentMode to Idle');
162
- this.agent.get(MatterbridgeRvcRunModeServer).state.currentMode = 1;
163
- this.state.operationalState = RvcOperationalState.OperationalState.Paused;
164
- this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
165
- return {
166
- commandResponseState: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
167
- };
168
- }
169
- resume() {
170
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
171
- device.log.info('MatterbridgeRvcOperationalStateServer: resume called setting operational state to Running and currentMode to Cleaning');
172
- this.agent.get(MatterbridgeRvcRunModeServer).state.currentMode = 2;
173
- this.state.operationalState = RvcOperationalState.OperationalState.Running;
174
- this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
175
- return {
176
- commandResponseState: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
177
- };
178
- }
179
- goHome() {
180
- const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
181
- device.log.info('MatterbridgeRvcOperationalStateServer: goHome called setting operational state to Docked and currentMode to Idle');
182
- this.agent.get(MatterbridgeRvcRunModeServer).state.currentMode = 1;
183
- this.state.operationalState = RvcOperationalState.OperationalState.Docked;
184
- this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
185
- return {
186
- commandResponseState: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
187
- };
188
- }
189
- }
190
- function createEndpointActionsClusterServer(endpoint, endpointLists) {
191
- endpoint.behaviors.require(ActionsServer, {
192
- actionList: [],
193
- endpointLists,
194
- });
195
- return endpoint;
196
- }
197
- if (process.argv.includes('-testRobot')) {
198
- const matterbridge = await Matterbridge.loadInstance(false);
199
- matterbridge.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: "debug" });
200
- matterbridge.environment.vars.set('log.level', MatterLogLevel.DEBUG);
201
- matterbridge.environment.vars.set('log.format', MatterLogFormat.ANSI);
202
- matterbridge.environment.vars.set('path.root', 'matterstorage');
203
- matterbridge.environment.vars.set('runtime.signals', true);
204
- matterbridge.environment.vars.set('runtime.exitcode', true);
205
- matterbridge.environment.vars.set('mdns.networkInterface', 'Wi-Fi');
206
- await matterbridge.startMatterStorage();
207
- const deviceType = dishwasher;
208
- const context = await matterbridge.createServerNodeContext('Matterbridge', deviceType.name, DeviceTypeId(deviceType.code), VendorId(0xfff1), 'Matterbridge', 0x8000, 'Matterbridge device');
209
- const server = (await matterbridge.createServerNode(context));
210
- }