matterbridge-example-dynamic-platform 2.0.14 → 2.0.15

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
@@ -26,6 +26,29 @@ If you like this project and find it useful, please consider giving it a star on
26
26
 
27
27
  <a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/assets/bmc-button.svg" alt="Buy me a coffee" width="120"></a>
28
28
 
29
+ ## [2.0.15] - 2026-04-08
30
+
31
+ ### Added
32
+
33
+ - [DoorLock]: Add a doorLock device with User and Pin features.
34
+ - [Fan]: Implement Auto >>> percentSetting = null rule. Thanks Ludovic BOUÉ (https://github.com/Luligu/matterbridge-example-dynamic-platform/issues/54).
35
+
36
+ ### Changed
37
+
38
+ - [package]: Bump package to `automator` v.3.1.5.
39
+ - [package]: Bump `eslint` to v.10.2.0.
40
+ - [package]: Bump `typescript` to v.6.0.2.
41
+ - [package]: Bump `typescript-eslint` to v.8.58.1.
42
+ - [devcontainer]: Fix pull of new image.
43
+ - [devcontainer]: Update VS Code settings.
44
+ - [devcontainer]: Leave matterbridge scripts in the cloned repo.
45
+ - [scripts]: Update mb-run script.
46
+ - [scripts]: Update package watch script.
47
+ - [scripts]: Add prune-releases script.
48
+ - [package]: Add `CODE_OF_CONDUCT.md`.
49
+
50
+ <a href="https://www.buymeacoffee.com/luligugithub"><img src="https://matterbridge.io/assets/bmc-button.svg" alt="Buy me a coffee" width="80"></a>
51
+
29
52
  ## [2.0.14] - 2026-03-20
30
53
 
31
54
  ### Changed
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
 
25
25
  Matterbridge dynamic platform example plugin is a template to develop your own plugin using the dynamic platform.
26
26
 
27
- It exposes 68 virtual devices:
27
+ It exposes 70 virtual devices:
28
28
 
29
29
  - a door contact sensor
30
30
  - a motion sensor
@@ -95,6 +95,7 @@ It exposes 68 virtual devices:
95
95
  - a speaker device (supported by SmartThings)
96
96
  - a soil sensor (Matter 1.5.0)
97
97
  - an irrigation system (Matter 1.5.0)
98
+ - an irrigation system with four zones (Matter 1.5.0)
98
99
 
99
100
  All these devices continuously change their state and position. The plugin also shows how to use all the command handlers (so you can control all the devices), how to subscribe to attributes, and how to trigger events.
100
101
 
package/dist/module.js CHANGED
@@ -3,7 +3,7 @@ import { AirConditioner, BasicVideoPlayer, BatteryStorage, Cooktop, Dishwasher,
3
3
  import { debugStringify } from 'matterbridge/logger';
4
4
  import { AreaNamespaceTag, LocationTag, NumberTag, PositionTag, RefrigeratorTag, SwitchesTag, UINT16_MAX, UINT32_MAX } from 'matterbridge/matter';
5
5
  import { AirQuality, BooleanState, BridgedDeviceBasicInformation, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, ColorControl, Descriptor, DeviceEnergyManagement, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, EnergyEvse, EnergyEvseMode, FanControl, FlowMeasurement, FormaldehydeConcentrationMeasurement, Identify, IlluminanceMeasurement, LevelControl, NitrogenDioxideConcentrationMeasurement, OccupancySensing, OnOff, OnOffCluster, OperationalState, OvenMode, OzoneConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PowerSource, PressureMeasurement, RadonConcentrationMeasurement, RelativeHumidityMeasurement, RelativeHumidityMeasurementCluster, RvcCleanMode, RvcOperationalState, RvcRunMode, SmokeCoAlarm, TemperatureMeasurement, Thermostat, ThermostatCluster, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
6
- import { isValidBoolean, isValidNumber, isValidObject, isValidString } from 'matterbridge/utils';
6
+ import { getEnumDescription, isValidBoolean, isValidNumber, isValidObject, isValidString } from 'matterbridge/utils';
7
7
  function luxToMatter(lux) {
8
8
  if (!Number.isFinite(lux) || lux <= 0)
9
9
  return 0;
@@ -55,6 +55,7 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
55
55
  coverLift;
56
56
  coverLiftTilt;
57
57
  lock;
58
+ userPinLock;
58
59
  thermoAuto;
59
60
  thermoAutoOccupancy;
60
61
  thermoAutoPresets;
@@ -105,8 +106,8 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
105
106
  constructor(matterbridge, log, config) {
106
107
  super(matterbridge, log, config);
107
108
  this.config = config;
108
- if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.7.0')) {
109
- throw new Error(`This plugin requires Matterbridge version >= "3.7.0". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
109
+ if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.7.2')) {
110
+ throw new Error(`This plugin requires Matterbridge version >= "3.7.2". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
110
111
  }
111
112
  this.log.info('Initializing platform:', this.config.name);
112
113
  }
@@ -700,22 +701,44 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
700
701
  .createDefaultPowerSourceRechargeableBatteryClusterServer(30)
701
702
  .addRequiredClusterServers();
702
703
  this.lock = await this.addDevice(this.lock);
703
- this.lock?.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
704
+ this.lock?.addCommandHandler('Identify.identify', async ({ request: { identifyTime } }) => {
704
705
  this.lock?.log.info(`Command identify called identifyTime:${identifyTime}`);
705
706
  });
706
- this.lock?.addCommandHandler('lockDoor', async () => {
707
+ this.lock?.addCommandHandler('DoorLock.lockDoor', async () => {
707
708
  this.lock?.log.info('Command lockDoor called');
708
709
  });
709
- this.lock?.addCommandHandler('unlockDoor', async () => {
710
+ this.lock?.addCommandHandler('DoorLock.unlockDoor', async () => {
710
711
  this.lock?.log.info('Command unlockDoor called');
711
712
  });
712
713
  await this.lock?.subscribeAttribute(DoorLock.Cluster.id, 'operatingMode', (value) => {
713
- const lookupOperatingMode = ['Normal', 'Vacation', 'Privacy', 'NoRemoteLockUnlock', 'Passage'];
714
- this.lock?.log.info('Subscribe operatingMode called with:', lookupOperatingMode[value]);
715
- const actuatorEnabled = value !== DoorLock.OperatingMode.NoRemoteLockUnlock;
716
- this.lock?.setAttribute(DoorLock.Cluster.id, 'actuatorEnabled', actuatorEnabled);
717
- this.lock?.log.info(`actuatorEnabled set to ${actuatorEnabled}`);
714
+ this.lock?.log.info(`Subscribe operatingMode called with: ${getEnumDescription(DoorLock.OperatingMode, value)}`);
718
715
  }, this.lock.log);
716
+ this.userPinLock = new MatterbridgeEndpoint([doorLockDevice, bridgedNode, powerSource], { id: 'UserPinLock' }, this.config.debug)
717
+ .createDefaultIdentifyClusterServer()
718
+ .createDefaultBridgedDeviceBasicInformationClusterServer('Lock with User and Pin', 'LUP00070', 0xfff1, 'Matterbridge', 'Matterbridge Lock')
719
+ .createUserPinDoorLockClusterServer()
720
+ .createDefaultPowerSourceRechargeableBatteryClusterServer(95)
721
+ .addRequiredClusterServers();
722
+ this.userPinLock = await this.addDevice(this.userPinLock);
723
+ await this.userPinLock?.setAttribute(PowerSource.Cluster.with(PowerSource.Feature.Rechargeable, PowerSource.Feature.Battery), 'batChargeState', PowerSource.BatChargeState.IsCharging);
724
+ this.userPinLock?.addCommandHandler('Identify.identify', async ({ request: { identifyTime } }) => {
725
+ this.userPinLock?.log.info(`Command identify called identifyTime:${identifyTime}`);
726
+ });
727
+ this.userPinLock?.addCommandHandler('DoorLock.lockDoor', async () => {
728
+ this.userPinLock?.log.info('Command lockDoor called');
729
+ });
730
+ this.userPinLock?.addCommandHandler('DoorLock.unlockDoor', async () => {
731
+ this.userPinLock?.log.info('Command unlockDoor called');
732
+ });
733
+ await this.userPinLock?.subscribeAttribute(DoorLock.Cluster, 'operatingMode', (value) => {
734
+ this.userPinLock?.log.info(`Subscribe operatingMode called with: ${getEnumDescription(DoorLock.OperatingMode, value)}`);
735
+ }, this.userPinLock.log);
736
+ await this.userPinLock?.subscribeAttribute(DoorLock.Complete, 'wrongCodeEntryLimit', (value) => {
737
+ this.userPinLock?.log.info(`Subscribe wrongCodeEntryLimit called with: ${value}`);
738
+ }, this.userPinLock.log);
739
+ await this.userPinLock?.subscribeAttribute(DoorLock.Complete, 'userCodeTemporaryDisableTime', (value) => {
740
+ this.userPinLock?.log.info(`Subscribe userCodeTemporaryDisableTime called with: ${value}`);
741
+ }, this.userPinLock.log);
719
742
  this.thermoAuto = new MatterbridgeEndpoint([thermostatDevice, bridgedNode, powerSource], { id: 'Thermostat (AutoMode)' }, this.config.debug)
720
743
  .createDefaultIdentifyClusterServer()
721
744
  .createDefaultBridgedDeviceBasicInformationClusterServer('Thermostat (Auto)', 'TAU00023', 0xfff1, 'Matterbridge', 'Matterbridge Thermostat')
@@ -1093,11 +1116,12 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1093
1116
  this.fanDefault?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanDefault?.log);
1094
1117
  }
1095
1118
  else if (newValue === FanControl.FanMode.On) {
1119
+ this.fanDefault?.setAttribute(FanControl.Cluster.id, 'fanMode', FanControl.FanMode.High, this.fanDefault?.log);
1096
1120
  this.fanDefault?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fanDefault?.log);
1097
1121
  this.fanDefault?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanDefault?.log);
1098
1122
  }
1099
1123
  else if (newValue === FanControl.FanMode.Auto) {
1100
- this.fanDefault?.setAttribute(FanControl.Cluster.id, 'percentSetting', 50, this.fanDefault?.log);
1124
+ this.fanDefault?.setAttribute(FanControl.Cluster.id, 'percentSetting', null, this.fanDefault?.log);
1101
1125
  this.fanDefault?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 50, this.fanDefault?.log);
1102
1126
  }
1103
1127
  }, this.fanDefault.log);
@@ -1135,11 +1159,12 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1135
1159
  this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanBase?.log);
1136
1160
  }
1137
1161
  else if (newValue === FanControl.FanMode.On) {
1162
+ this.fanBase?.setAttribute(FanControl.Cluster.id, 'fanMode', FanControl.FanMode.High, this.fanBase?.log);
1138
1163
  this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fanBase?.log);
1139
1164
  this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanBase?.log);
1140
1165
  }
1141
1166
  else if (newValue === FanControl.FanMode.Auto) {
1142
- this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentSetting', 50, this.fanBase?.log);
1167
+ this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentSetting', null, this.fanBase?.log);
1143
1168
  this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 50, this.fanBase?.log);
1144
1169
  }
1145
1170
  }, this.fanBase.log);
@@ -1168,6 +1193,11 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1168
1193
  this.fanOnHigh?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fanOnHigh?.log);
1169
1194
  this.fanOnHigh?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanOnHigh?.log);
1170
1195
  }
1196
+ else if (newValue === FanControl.FanMode.On) {
1197
+ this.fanBase?.setAttribute(FanControl.Cluster.id, 'fanMode', FanControl.FanMode.High, this.fanBase?.log);
1198
+ this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fanBase?.log);
1199
+ this.fanBase?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanBase?.log);
1200
+ }
1171
1201
  }, this.fanOnHigh.log);
1172
1202
  await this.fanOnHigh?.subscribeAttribute(FanControl.Cluster.id, 'percentSetting', (newValue, oldValue, context) => {
1173
1203
  this.fanOnHigh?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.offline === true ? 'offline' : 'online'}`);
@@ -1207,11 +1237,12 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1207
1237
  this.fanComplete?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanComplete?.log);
1208
1238
  }
1209
1239
  else if (newValue === FanControl.FanMode.On) {
1240
+ this.fanComplete?.setAttribute(FanControl.Cluster.id, 'fanMode', FanControl.FanMode.High, this.fanComplete?.log);
1210
1241
  this.fanComplete?.setAttribute(FanControl.Cluster.id, 'percentSetting', 100, this.fanComplete?.log);
1211
1242
  this.fanComplete?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 100, this.fanComplete?.log);
1212
1243
  }
1213
1244
  else if (newValue === FanControl.FanMode.Auto) {
1214
- this.fanComplete?.setAttribute(FanControl.Cluster.id, 'percentSetting', 50, this.fanComplete?.log);
1245
+ this.fanComplete?.setAttribute(FanControl.Cluster.id, 'percentSetting', null, this.fanComplete?.log);
1215
1246
  this.fanComplete?.setAttribute(FanControl.Cluster.id, 'percentCurrent', 50, this.fanComplete?.log);
1216
1247
  }
1217
1248
  }, this.fanComplete?.log);
@@ -5,13 +5,13 @@
5
5
  "required": [],
6
6
  "properties": {
7
7
  "name": {
8
- "description": "Plugin name",
8
+ "description": "Plugin Name",
9
9
  "type": "string",
10
10
  "readOnly": true,
11
11
  "ui:widget": "hidden"
12
12
  },
13
13
  "type": {
14
- "description": "Plugin type",
14
+ "description": "Plugin Type",
15
15
  "type": "string",
16
16
  "readOnly": true,
17
17
  "ui:widget": "hidden"
@@ -49,13 +49,13 @@
49
49
  "default": true
50
50
  },
51
51
  "debug": {
52
- "title": "Debug",
52
+ "title": "Enable Debug",
53
53
  "description": "Enable the debug for the plugin (development only)",
54
54
  "type": "boolean",
55
55
  "default": false
56
56
  },
57
57
  "unregisterOnShutdown": {
58
- "title": "Unregister on Shutdown",
58
+ "title": "Unregister On Shutdown",
59
59
  "description": "Unregister all devices on shutdown (development only)",
60
60
  "type": "boolean",
61
61
  "default": false
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "matterbridge-example-dynamic-platform",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-example-dynamic-platform",
9
- "version": "2.0.14",
9
+ "version": "2.0.15",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "node-ansi-logger": "3.2.0",
13
13
  "node-persist-manager": "2.0.1"
14
14
  },
15
15
  "engines": {
16
- "node": ">=20.0.0 <21.0.0 || >=22.0.0 <23.0.0 || >=24.0.0 <25.0.0"
16
+ "node": ">=20.19.0 <21.0.0 || >=22.13.0 <23.0.0 || >=24.0.0 <25.0.0"
17
17
  },
18
18
  "funding": {
19
19
  "type": "buymeacoffee",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-example-dynamic-platform",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "Matterbridge dynamic plugin",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",
@@ -56,7 +56,7 @@
56
56
  "ewelink"
57
57
  ],
58
58
  "engines": {
59
- "node": ">=20.0.0 <21.0.0 || >=22.0.0 <23.0.0 || >=24.0.0 <25.0.0"
59
+ "node": ">=20.19.0 <21.0.0 || >=22.13.0 <23.0.0 || >=24.0.0 <25.0.0"
60
60
  },
61
61
  "files": [
62
62
  "bin",
@@ -71,7 +71,7 @@
71
71
  "node-persist-manager": "2.0.1"
72
72
  },
73
73
  "overrides": {
74
- "eslint": "10.1.0",
74
+ "eslint": "10.2.0",
75
75
  "@eslint/js": "10.0.1"
76
76
  }
77
77
  }