matterbridge-example-dynamic-platform 3.0.3-dev-20260821-8d97cc8 → 3.0.3-dev-20260822-8bd7fd5

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
@@ -34,6 +34,13 @@ If you like this project and find it useful, please consider giving it a star on
34
34
  ### Breaking changes
35
35
 
36
36
  - [matterbridge]: Require matterbridge v.3.10.6 with matter v.1.6.0.
37
+ - [fanControl]: Matterbridge v.3.10.6 now manages the compliant FanControl `fanMode` and `percentSetting` synchronization; plugin examples now only update `percentCurrent` as physical device feedback.
38
+
39
+ ### Added
40
+
41
+ - [platform]: Add `closureSlidingGate` device with pedestrian opening support. Thanks Ludovic BOUÉ.
42
+ - [platform]: Add `closureRoofWindow` device with ventilation opening support. Thanks Ludovic BOUÉ.
43
+ - [devcontainer]: Upgrade the `Dev Container` to v.2.0.0, using the Node.js and Bun stack.
37
44
 
38
45
  ### Fixed
39
46
 
package/README.md CHANGED
@@ -23,7 +23,7 @@
23
23
 
24
24
  Matterbridge dynamic platform example plugin is a template to develop your own plugin using the dynamic platform.
25
25
 
26
- It exposes 74 virtual devices:
26
+ It exposes 76 virtual devices:
27
27
 
28
28
  - a door contact sensor
29
29
  - a motion sensor
@@ -99,6 +99,8 @@ It exposes 74 virtual devices:
99
99
  - an irrigation system with four zones (Matter 1.5.0)
100
100
  - a closure device (Matter 1.5.0, supported by SmartThings)
101
101
  - a closure device with Lift and Tilt panels (Matter 1.5.0, supported by SmartThings)
102
+ - a sliding gate closure device with pedestrian opening support (Matter 1.5.0, supported by SmartThings)
103
+ - a roof window closure device with ventilation opening support (Matter 1.5.0, supported by SmartThings)
102
104
 
103
105
  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.
104
106
 
package/dist/module.js CHANGED
@@ -1,8 +1,9 @@
1
- import { aggregator, airPurifier, airQualitySensor, bridgedNode, colorTemperatureLight, contactSensor, windowCovering, dimmableLight, mountedDimmableLoadControl, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, flowSensor, genericSwitch, getSemtag, humiditySensor, lightSensor, MatterbridgeDynamicPlatform, MatterbridgeEndpoint, modeSelect, occupancySensor, onOffLight, mountedOnOffControl, onOffPlugInUnit, onOffLightSwitch, powerSource, pressureSensor, pump, rainSensor, smokeCoAlarm, temperatureSensor, thermostat, waterFreezeDetector, waterLeakDetector, waterValve, } from 'matterbridge';
2
- import { AirConditioner, BasicVideoPlayer, BatteryStorage, Closure, Cooktop, Dishwasher, Evse, ExtractorHood, HeatPump, IrrigationSystem, LaundryDryer, LaundryWasher, MicrowaveOven, Oven, Refrigerator, RoboticVacuumCleaner, SoilSensor, SolarPower, Speaker, WaterHeater, } from 'matterbridge/devices';
1
+ import { aggregator, airPurifier, airQualitySensor, bridgedNode, closure, colorTemperatureLight, contactSensor, windowCovering, dimmableLight, mountedDimmableLoadControl, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, flowSensor, genericSwitch, getSemtag, humiditySensor, lightSensor, MatterbridgeDynamicPlatform, MatterbridgeEndpoint, modeSelect, occupancySensor, onOffLight, mountedOnOffControl, onOffPlugInUnit, onOffLightSwitch, powerSource, pressureSensor, pump, rainSensor, smokeCoAlarm, temperatureSensor, thermostat, waterFreezeDetector, waterLeakDetector, waterValve, } from 'matterbridge';
2
+ import { AirConditioner, BasicVideoPlayer, BatteryStorage, Closure, Cooktop, Dishwasher, Evse, ExtractorHood, HeatPump, IrrigationSystem, LaundryDryer, LaundryWasher, MatterbridgeClosureControlServer, MicrowaveOven, Oven, Refrigerator, RoboticVacuumCleaner, SoilSensor, SolarPower, Speaker, WaterHeater, } from 'matterbridge/devices';
3
3
  import { debugStringify } from 'matterbridge/logger';
4
- import { ClosureCoveringTag, ClosurePanelTag, ClosureTag, CommonAreaNamespaceTag, CommonLocationTag, CommonNumberTag, CommonPositionTag, RefrigeratorTag, SwitchesTag, UINT16_MAX, UINT32_MAX, } from 'matterbridge/matter';
4
+ import { ClosureCoveringTag, ClosurePanelTag, ClosureTag, ClosureWindowTag, CommonAreaNamespaceTag, CommonLocationTag, CommonNumberTag, CommonPositionTag, RefrigeratorTag, SwitchesTag, UINT16_MAX, UINT32_MAX, } from 'matterbridge/matter';
5
5
  import { AirQuality, BooleanState, BridgedDeviceBasicInformation, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, ClosureControl, ClosureDimension, ColorControl, Descriptor, DeviceEnergyManagement, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, EnergyEvse, EnergyEvseMode, FanControl, FlowMeasurement, FormaldehydeConcentrationMeasurement, Identify, IlluminanceMeasurement, LevelControl, NitrogenDioxideConcentrationMeasurement, OccupancySensing, OnOff, OperationalState, OvenMode, OzoneConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PowerSource, PressureMeasurement, RadonConcentrationMeasurement, RelativeHumidityMeasurement, RvcCleanMode, RvcOperationalState, RvcRunMode, SmokeCoAlarm, TemperatureMeasurement, Thermostat, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
6
+ import { ThreeLevelAuto } from 'matterbridge/matter/types';
6
7
  import { fireAndForget, getEnumDescription, isValidBoolean, isValidNumber, isValidObject, isValidString } from 'matterbridge/utils';
7
8
  function luxToMatter(lux) {
8
9
  if (!Number.isFinite(lux) || lux <= 0)
@@ -36,6 +37,8 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
36
37
  irrigationSystem;
37
38
  closureGarageDoor;
38
39
  closureVenetianBlind;
40
+ closureSlidingGate;
41
+ closureRoofWindow;
39
42
  select;
40
43
  climate;
41
44
  switch;
@@ -307,6 +310,96 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
307
310
  };
308
311
  addClosurePanelStepSimulation(closureVenetianBlindLift);
309
312
  addClosurePanelStepSimulation(closureVenetianBlindTilt);
313
+ this.closureSlidingGate = new MatterbridgeEndpoint([closure, powerSource], {
314
+ id: 'SlidingGate-GAT000073',
315
+ tagList: [getSemtag(ClosureTag.Gate)],
316
+ }, this.config.debug)
317
+ .createDefaultIdentifyClusterServer()
318
+ .createDefaultBasicInformationClusterServer('Sliding Gate', 'GAT000073', 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Closure')
319
+ .createDefaultPowerSourceWiredClusterServer();
320
+ this.closureSlidingGate.behaviors.require(MatterbridgeClosureControlServer.with(ClosureControl.Feature.Positioning, ClosureControl.Feature.MotionLatching, ClosureControl.Feature.Speed, ClosureControl.Feature.Pedestrian), {
321
+ countdownTime: 0,
322
+ mainState: ClosureControl.MainState.Stopped,
323
+ currentErrorList: [],
324
+ overallCurrentState: { position: ClosureControl.CurrentPosition.FullyClosed, latch: true, speed: ThreeLevelAuto.Auto, secureState: true },
325
+ overallTargetState: { position: ClosureControl.TargetPosition.MoveToFullyClosed, latch: true, speed: ThreeLevelAuto.Auto },
326
+ latchControlModes: { remoteLatching: true, remoteUnlatching: true },
327
+ });
328
+ this.closureSlidingGate = await this.addDevice(this.closureSlidingGate);
329
+ let closureSlidingGateMoveTimeout;
330
+ this.closureSlidingGate?.addCommandHandler('ClosureControl.moveTo', ({ request: { position, latch, speed }, attributes }) => {
331
+ this.closureSlidingGate?.log.info(`Command moveTo called position:${position} latch:${latch} speed:${speed}`);
332
+ attributes.countdownTime = 1;
333
+ closureSlidingGateMoveTimeout = setTimeout(() => {
334
+ void (async () => {
335
+ const targetState = this.closureSlidingGate?.getAttribute(ClosureControl, 'overallTargetState');
336
+ if (targetState === undefined || targetState === null || targetState.position === undefined || targetState.position === null)
337
+ return;
338
+ await this.closureSlidingGate?.setAttribute(ClosureControl, 'countdownTime', 0, this.closureSlidingGate.log);
339
+ await this.closureSlidingGate?.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Stopped, this.closureSlidingGate.log);
340
+ await this.closureSlidingGate?.setAttribute(ClosureControl, 'currentErrorList', [], this.closureSlidingGate.log);
341
+ await this.closureSlidingGate?.setAttribute(ClosureControl, 'overallCurrentState', {
342
+ position: closureTargetToCurrentPosition(targetState.position),
343
+ latch: targetState.latch,
344
+ speed: targetState.speed,
345
+ secureState: targetState.position === ClosureControl.TargetPosition.MoveToFullyClosed && targetState.latch === true,
346
+ }, this.closureSlidingGate.log);
347
+ await this.closureSlidingGate?.setAttribute(ClosureControl, 'overallTargetState', targetState, this.closureSlidingGate.log);
348
+ await this.closureSlidingGate?.triggerEvent(ClosureControl, 'movementCompleted', undefined, this.closureSlidingGate.log);
349
+ })();
350
+ }, 1000).unref();
351
+ });
352
+ this.closureSlidingGate?.addCommandHandler('ClosureControl.stop', ({ attributes }) => {
353
+ this.closureSlidingGate?.log.info('Command stop called');
354
+ attributes.countdownTime = 0;
355
+ clearTimeout(closureSlidingGateMoveTimeout);
356
+ closureSlidingGateMoveTimeout = undefined;
357
+ });
358
+ this.closureRoofWindow = new MatterbridgeEndpoint([closure, powerSource], {
359
+ id: 'RoofWindow-ROO000074',
360
+ tagList: [getSemtag(ClosureTag.Window), getSemtag(ClosureWindowTag.Roof)],
361
+ }, this.config.debug)
362
+ .createDefaultIdentifyClusterServer()
363
+ .createDefaultBasicInformationClusterServer('Roof Window', 'ROO000074', 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Closure')
364
+ .createDefaultPowerSourceWiredClusterServer();
365
+ this.closureRoofWindow.behaviors.require(MatterbridgeClosureControlServer.with(ClosureControl.Feature.Positioning, ClosureControl.Feature.MotionLatching, ClosureControl.Feature.Speed, ClosureControl.Feature.Ventilation), {
366
+ countdownTime: 0,
367
+ mainState: ClosureControl.MainState.Stopped,
368
+ currentErrorList: [],
369
+ overallCurrentState: { position: ClosureControl.CurrentPosition.FullyClosed, latch: true, speed: ThreeLevelAuto.Auto, secureState: true },
370
+ overallTargetState: { position: ClosureControl.TargetPosition.MoveToFullyClosed, latch: true, speed: ThreeLevelAuto.Auto },
371
+ latchControlModes: { remoteLatching: true, remoteUnlatching: true },
372
+ });
373
+ this.closureRoofWindow = await this.addDevice(this.closureRoofWindow);
374
+ let closureRoofWindowMoveTimeout;
375
+ this.closureRoofWindow?.addCommandHandler('ClosureControl.moveTo', ({ request: { position, latch, speed }, attributes }) => {
376
+ this.closureRoofWindow?.log.info(`Command moveTo called position:${position} latch:${latch} speed:${speed}`);
377
+ attributes.countdownTime = 1;
378
+ closureRoofWindowMoveTimeout = setTimeout(() => {
379
+ void (async () => {
380
+ const targetState = this.closureRoofWindow?.getAttribute(ClosureControl, 'overallTargetState');
381
+ if (targetState === undefined || targetState === null || targetState.position === undefined || targetState.position === null)
382
+ return;
383
+ await this.closureRoofWindow?.setAttribute(ClosureControl, 'countdownTime', 0, this.closureRoofWindow.log);
384
+ await this.closureRoofWindow?.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Stopped, this.closureRoofWindow.log);
385
+ await this.closureRoofWindow?.setAttribute(ClosureControl, 'currentErrorList', [], this.closureRoofWindow.log);
386
+ await this.closureRoofWindow?.setAttribute(ClosureControl, 'overallCurrentState', {
387
+ position: closureTargetToCurrentPosition(targetState.position),
388
+ latch: targetState.latch,
389
+ speed: targetState.speed,
390
+ secureState: targetState.position === ClosureControl.TargetPosition.MoveToFullyClosed && targetState.latch === true,
391
+ }, this.closureRoofWindow.log);
392
+ await this.closureRoofWindow?.setAttribute(ClosureControl, 'overallTargetState', targetState, this.closureRoofWindow.log);
393
+ await this.closureRoofWindow?.triggerEvent(ClosureControl, 'movementCompleted', undefined, this.closureRoofWindow.log);
394
+ })();
395
+ }, 1000).unref();
396
+ });
397
+ this.closureRoofWindow?.addCommandHandler('ClosureControl.stop', ({ attributes }) => {
398
+ this.closureRoofWindow?.log.info('Command stop called');
399
+ attributes.countdownTime = 0;
400
+ clearTimeout(closureRoofWindowMoveTimeout);
401
+ closureRoofWindowMoveTimeout = undefined;
402
+ });
310
403
  this.climate = new MatterbridgeEndpoint([bridgedNode, powerSource], { id: 'Climate' }, this.config.debug)
311
404
  .createDefaultBridgedDeviceBasicInformationClusterServer('Climate', 'CLI00008', 0xfff1, 'Matterbridge', 'Matterbridge Climate')
312
405
  .createDefaultPowerSourceReplaceableBatteryClusterServer(90, PowerSource.BatChargeLevel.Ok, 2990, '2 x AA', 2, PowerSource.BatReplaceability.UserReplaceable)
@@ -1315,33 +1408,15 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1315
1408
  this.airPurifier?.addCommandHandler('identify', ({ request: { identifyTime } }) => {
1316
1409
  this.airPurifier?.log.info(`Command identify called identifyTime:${identifyTime}`);
1317
1410
  });
1318
- this.airPurifier?.subscribeAttribute(FanControl.id, 'fanMode', (newValue, oldValue, context) => void (async () => {
1411
+ this.airPurifier?.subscribeAttribute(FanControl.id, 'fanMode', (newValue, oldValue, context) => {
1319
1412
  this.airPurifier?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1320
- if (context.fabric === undefined)
1321
- return;
1322
- if (newValue === FanControl.FanMode.Off) {
1323
- await this.airPurifier?.setAttribute(FanControl.id, 'percentSetting', 0, this.airPurifier?.log);
1324
- await this.airPurifier?.setAttribute(FanControl.id, 'percentCurrent', 0, this.airPurifier?.log);
1325
- }
1326
- else if (newValue === FanControl.FanMode.Low) {
1327
- await this.airPurifier?.setAttribute(FanControl.id, 'percentSetting', 33, this.airPurifier?.log);
1328
- await this.airPurifier?.setAttribute(FanControl.id, 'percentCurrent', 33, this.airPurifier?.log);
1329
- }
1330
- else if (newValue === FanControl.FanMode.Medium) {
1331
- await this.airPurifier?.setAttribute(FanControl.id, 'percentSetting', 66, this.airPurifier?.log);
1332
- await this.airPurifier?.setAttribute(FanControl.id, 'percentCurrent', 66, this.airPurifier?.log);
1333
- }
1334
- else if (newValue === FanControl.FanMode.High || newValue === FanControl.FanMode.On || newValue === FanControl.FanMode.Auto) {
1335
- await this.airPurifier?.setAttribute(FanControl.id, 'percentSetting', 100, this.airPurifier?.log);
1336
- await this.airPurifier?.setAttribute(FanControl.id, 'percentCurrent', 100, this.airPurifier?.log);
1337
- }
1338
- })(), this.airPurifier.log);
1413
+ }, this.airPurifier.log);
1339
1414
  this.airPurifier?.subscribeAttribute(FanControl.id, 'percentSetting', (newValue, oldValue, context) => void (async () => {
1340
1415
  this.airPurifier?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1341
1416
  if (context.fabric === undefined)
1342
1417
  return;
1343
- if (isValidNumber(newValue, 0, 100))
1344
- await this.airPurifier?.setAttribute(FanControl.id, 'percentCurrent', newValue, this.airPurifier?.log);
1418
+ if (newValue === null || isValidNumber(newValue, 0, 100))
1419
+ await this.airPurifier?.setAttribute(FanControl, 'percentCurrent', newValue ?? 50, this.airPurifier?.log);
1345
1420
  })(), this.airPurifier.log);
1346
1421
  this.pump = new MatterbridgeEndpoint([pump, bridgedNode, powerSource], { id: 'Pump' }, this.config.debug)
1347
1422
  .createDefaultBridgedDeviceBasicInformationClusterServer('Pump', 'PUM00028', 0xfff1, 'Matterbridge', 'Matterbridge Pump')
@@ -1383,52 +1458,15 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1383
1458
  .createDefaultFanControlClusterServer()
1384
1459
  .addRequiredClusterServers();
1385
1460
  this.fanDefault = await this.addDevice(this.fanDefault);
1386
- this.fanDefault?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => void (async () => {
1461
+ this.fanDefault?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => {
1387
1462
  this.fanDefault?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1388
- if (context.fabric === undefined)
1389
- return;
1390
- if (newValue === FanControl.FanMode.Off) {
1391
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', 0, this.fanDefault?.log);
1392
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', 0, this.fanDefault?.log);
1393
- }
1394
- else if (newValue === FanControl.FanMode.Low) {
1395
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', 33, this.fanDefault?.log);
1396
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', 33, this.fanDefault?.log);
1397
- }
1398
- else if (newValue === FanControl.FanMode.Medium) {
1399
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', 66, this.fanDefault?.log);
1400
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', 66, this.fanDefault?.log);
1401
- }
1402
- else if (newValue === FanControl.FanMode.High) {
1403
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', 100, this.fanDefault?.log);
1404
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', 100, this.fanDefault?.log);
1405
- }
1406
- else if (newValue === FanControl.FanMode.On) {
1407
- await this.fanDefault?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanDefault?.log);
1408
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', 100, this.fanDefault?.log);
1409
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', 100, this.fanDefault?.log);
1410
- }
1411
- else if (newValue === FanControl.FanMode.Auto) {
1412
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', null, this.fanDefault?.log);
1413
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', 50, this.fanDefault?.log);
1414
- }
1415
- })(), this.fanDefault.log);
1463
+ }, this.fanDefault.log);
1416
1464
  this.fanDefault?.subscribeAttribute(FanControl, 'percentSetting', (newValue, oldValue, context) => void (async () => {
1417
1465
  this.fanDefault?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1418
1466
  if (context.fabric === undefined)
1419
1467
  return;
1420
- if (isValidNumber(newValue, 0, 100))
1421
- await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', newValue, this.fanDefault?.log);
1422
- if (isValidNumber(newValue, 0, 0))
1423
- await this.fanDefault?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Off, this.fanDefault?.log);
1424
- if (isValidNumber(newValue, 1, 33))
1425
- await this.fanDefault?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Low, this.fanDefault?.log);
1426
- if (isValidNumber(newValue, 34, 66))
1427
- await this.fanDefault?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Medium, this.fanDefault?.log);
1428
- if (isValidNumber(newValue, 67, 100))
1429
- await this.fanDefault?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanDefault?.log);
1430
- if (newValue === null)
1431
- await this.fanDefault?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Auto, this.fanDefault?.log);
1468
+ if (newValue === null || isValidNumber(newValue, 0, 100))
1469
+ await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', newValue ?? 50, this.fanDefault?.log);
1432
1470
  })(), this.fanDefault.log);
1433
1471
  this.fanBase = new MatterbridgeEndpoint([fan, bridgedNode, powerSource], { id: 'Fan off low medium high' }, this.config.debug)
1434
1472
  .createDefaultBridgedDeviceBasicInformationClusterServer('Fan base', 'FBA00031', 0xfff1, 'Matterbridge', 'Matterbridge Fan')
@@ -1436,50 +1474,15 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1436
1474
  .createBaseFanControlClusterServer()
1437
1475
  .addRequiredClusterServers();
1438
1476
  this.fanBase = await this.addDevice(this.fanBase);
1439
- this.fanBase?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => void (async () => {
1477
+ this.fanBase?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => {
1440
1478
  this.fanBase?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1441
- if (context.fabric === undefined)
1442
- return;
1443
- if (newValue === FanControl.FanMode.Off) {
1444
- await this.fanBase?.setAttribute(FanControl, 'percentSetting', 0, this.fanBase?.log);
1445
- await this.fanBase?.setAttribute(FanControl, 'percentCurrent', 0, this.fanBase?.log);
1446
- }
1447
- else if (newValue === FanControl.FanMode.Low) {
1448
- await this.fanBase?.setAttribute(FanControl, 'percentSetting', 33, this.fanBase?.log);
1449
- await this.fanBase?.setAttribute(FanControl, 'percentCurrent', 33, this.fanBase?.log);
1450
- }
1451
- else if (newValue === FanControl.FanMode.Medium) {
1452
- await this.fanBase?.setAttribute(FanControl, 'percentSetting', 66, this.fanBase?.log);
1453
- await this.fanBase?.setAttribute(FanControl, 'percentCurrent', 66, this.fanBase?.log);
1454
- }
1455
- else if (newValue === FanControl.FanMode.High) {
1456
- await this.fanBase?.setAttribute(FanControl, 'percentSetting', 100, this.fanBase?.log);
1457
- await this.fanBase?.setAttribute(FanControl, 'percentCurrent', 100, this.fanBase?.log);
1458
- }
1459
- else if (newValue === FanControl.FanMode.On) {
1460
- await this.fanBase?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanBase?.log);
1461
- await this.fanBase?.setAttribute(FanControl, 'percentSetting', 100, this.fanBase?.log);
1462
- await this.fanBase?.setAttribute(FanControl, 'percentCurrent', 100, this.fanBase?.log);
1463
- }
1464
- else if (newValue === FanControl.FanMode.Auto) {
1465
- await this.fanBase?.setAttribute(FanControl, 'percentSetting', null, this.fanBase?.log);
1466
- await this.fanBase?.setAttribute(FanControl, 'percentCurrent', 50, this.fanBase?.log);
1467
- }
1468
- })(), this.fanBase.log);
1479
+ }, this.fanBase.log);
1469
1480
  this.fanBase?.subscribeAttribute(FanControl, 'percentSetting', (newValue, oldValue, context) => void (async () => {
1470
1481
  this.fanBase?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1471
1482
  if (context.fabric === undefined)
1472
1483
  return;
1473
1484
  if (isValidNumber(newValue, 0, 100))
1474
1485
  await this.fanBase?.setAttribute(FanControl, 'percentCurrent', newValue, this.fanBase?.log);
1475
- if (isValidNumber(newValue, 0, 0))
1476
- await this.fanBase?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Off, this.fanBase?.log);
1477
- if (isValidNumber(newValue, 1, 33))
1478
- await this.fanBase?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Low, this.fanBase?.log);
1479
- if (isValidNumber(newValue, 34, 66))
1480
- await this.fanBase?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Medium, this.fanBase?.log);
1481
- if (isValidNumber(newValue, 67, 100))
1482
- await this.fanBase?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanBase?.log);
1483
1486
  })(), this.fanBase.log);
1484
1487
  this.fanOnHigh = new MatterbridgeEndpoint([fan, bridgedNode, powerSource], { id: 'Fan off high' }, this.config.debug)
1485
1488
  .createDefaultBridgedDeviceBasicInformationClusterServer('Fan off high', 'FOH00032', 0xfff1, 'Matterbridge', 'Matterbridge Fan')
@@ -1487,33 +1490,16 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1487
1490
  .createOnOffFanControlClusterServer()
1488
1491
  .addRequiredClusterServers();
1489
1492
  this.fanOnHigh = await this.addDevice(this.fanOnHigh);
1490
- this.fanOnHigh?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => void (async () => {
1493
+ this.fanOnHigh?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => {
1491
1494
  this.fanOnHigh?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1492
- if (context.fabric === undefined)
1493
- return;
1494
- if (newValue === FanControl.FanMode.Off) {
1495
- await this.fanOnHigh?.setAttribute(FanControl, 'percentSetting', 0, this.fanOnHigh?.log);
1496
- await this.fanOnHigh?.setAttribute(FanControl, 'percentCurrent', 0, this.fanOnHigh?.log);
1497
- }
1498
- else if (newValue === FanControl.FanMode.High) {
1499
- await this.fanOnHigh?.setAttribute(FanControl, 'percentSetting', 100, this.fanOnHigh?.log);
1500
- await this.fanOnHigh?.setAttribute(FanControl, 'percentCurrent', 100, this.fanOnHigh?.log);
1501
- }
1502
- else if (newValue === FanControl.FanMode.On) {
1503
- await this.fanOnHigh?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanOnHigh?.log);
1504
- await this.fanOnHigh?.setAttribute(FanControl, 'percentSetting', 100, this.fanOnHigh?.log);
1505
- await this.fanOnHigh?.setAttribute(FanControl, 'percentCurrent', 100, this.fanOnHigh?.log);
1506
- }
1507
- })(), this.fanOnHigh.log);
1495
+ }, this.fanOnHigh.log);
1508
1496
  this.fanOnHigh?.subscribeAttribute(FanControl, 'percentSetting', (newValue, oldValue, context) => void (async () => {
1509
1497
  this.fanOnHigh?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1510
1498
  if (context.fabric === undefined)
1511
1499
  return;
1512
1500
  if (isValidNumber(newValue, 0, 100)) {
1513
- if (newValue > 0)
1514
- newValue = 100;
1515
- await this.fanOnHigh?.setAttribute(FanControl, 'percentCurrent', newValue, this.fanOnHigh?.log);
1516
- await this.fanOnHigh?.setAttribute(FanControl, 'percentSetting', newValue, this.fanOnHigh?.log);
1501
+ await this.fanOnHigh?.setAttribute(FanControl, 'percentCurrent', newValue > 0 ? 100 : 0, this.fanOnHigh?.log);
1502
+ await this.fanOnHigh?.setAttribute(FanControl, 'percentSetting', newValue > 0 ? 100 : 0, this.fanOnHigh?.log);
1517
1503
  await this.fanOnHigh?.setAttribute(FanControl, 'fanMode', newValue === 0 ? FanControl.FanMode.Off : FanControl.FanMode.High, this.fanOnHigh?.log);
1518
1504
  }
1519
1505
  })(), this.fanOnHigh.log);
@@ -1523,52 +1509,15 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1523
1509
  .createCompleteFanControlClusterServer()
1524
1510
  .addRequiredClusterServers();
1525
1511
  this.fanComplete = await this.addDevice(this.fanComplete);
1526
- this.fanComplete?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => void (async () => {
1512
+ this.fanComplete?.subscribeAttribute(FanControl, 'fanMode', (newValue, oldValue, context) => {
1527
1513
  this.fanComplete?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1528
- if (context.fabric === undefined)
1529
- return;
1530
- if (newValue === FanControl.FanMode.Off) {
1531
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', 0, this.fanComplete?.log);
1532
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', 0, this.fanComplete?.log);
1533
- }
1534
- else if (newValue === FanControl.FanMode.Low) {
1535
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', 33, this.fanComplete?.log);
1536
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', 33, this.fanComplete?.log);
1537
- }
1538
- else if (newValue === FanControl.FanMode.Medium) {
1539
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', 66, this.fanComplete?.log);
1540
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', 66, this.fanComplete?.log);
1541
- }
1542
- else if (newValue === FanControl.FanMode.High) {
1543
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', 100, this.fanComplete?.log);
1544
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', 100, this.fanComplete?.log);
1545
- }
1546
- else if (newValue === FanControl.FanMode.On) {
1547
- await this.fanComplete?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanComplete?.log);
1548
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', 100, this.fanComplete?.log);
1549
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', 100, this.fanComplete?.log);
1550
- }
1551
- else if (newValue === FanControl.FanMode.Auto) {
1552
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', null, this.fanComplete?.log);
1553
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', 50, this.fanComplete?.log);
1554
- }
1555
- })(), this.fanComplete?.log);
1514
+ }, this.fanComplete?.log);
1556
1515
  this.fanComplete?.subscribeAttribute(FanControl, 'percentSetting', (newValue, oldValue, context) => void (async () => {
1557
1516
  this.fanComplete?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1558
1517
  if (context.fabric === undefined)
1559
1518
  return;
1560
- if (isValidNumber(newValue, 0, 100))
1561
- await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', newValue, this.fanComplete?.log);
1562
- if (isValidNumber(newValue, 0, 0))
1563
- await this.fanComplete?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Off, this.fanComplete?.log);
1564
- if (isValidNumber(newValue, 1, 33))
1565
- await this.fanComplete?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Low, this.fanComplete?.log);
1566
- if (isValidNumber(newValue, 34, 66))
1567
- await this.fanComplete?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Medium, this.fanComplete?.log);
1568
- if (isValidNumber(newValue, 67, 100))
1569
- await this.fanComplete?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.High, this.fanComplete?.log);
1570
- if (newValue === null)
1571
- await this.fanComplete?.setAttribute(FanControl, 'fanMode', FanControl.FanMode.Auto, this.fanComplete?.log);
1519
+ if (newValue === null || isValidNumber(newValue, 0, 100))
1520
+ await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', newValue ?? 50, this.fanComplete?.log);
1572
1521
  })(), this.fanComplete?.log);
1573
1522
  this.fanComplete?.subscribeAttribute(FanControl, 'rockSetting', (newValue, oldValue, context) => {
1574
1523
  this.fanComplete?.log.info(`Rock setting changed from ${debugStringify(oldValue)} to ${debugStringify(newValue)} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
@@ -1881,41 +1830,15 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
1881
1830
  await this.airConditioner?.setAttribute(RelativeHumidityMeasurement.id, 'measuredValue', null, this.airConditioner?.log);
1882
1831
  await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 0, this.airConditioner?.log);
1883
1832
  });
1884
- this.airConditioner?.subscribeAttribute(FanControl.id, 'fanMode', (newValue, oldValue, context) => void (async () => {
1833
+ this.airConditioner?.subscribeAttribute(FanControl.id, 'fanMode', (newValue, oldValue, context) => {
1885
1834
  this.airConditioner?.log.info(`Fan mode changed from ${this.fanModeLookup[oldValue]} to ${this.fanModeLookup[newValue]} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1886
- if (context.fabric === undefined)
1887
- return;
1888
- if (newValue === FanControl.FanMode.Off) {
1889
- await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 0, this.airConditioner?.log);
1890
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', 0, this.airConditioner?.log);
1891
- }
1892
- else if (newValue === FanControl.FanMode.Low) {
1893
- await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 33, this.airConditioner?.log);
1894
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', 33, this.airConditioner?.log);
1895
- }
1896
- else if (newValue === FanControl.FanMode.Medium) {
1897
- await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 66, this.airConditioner?.log);
1898
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', 66, this.airConditioner?.log);
1899
- }
1900
- else if (newValue === FanControl.FanMode.High) {
1901
- await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 100, this.airConditioner?.log);
1902
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', 100, this.airConditioner?.log);
1903
- }
1904
- else if (newValue === FanControl.FanMode.On) {
1905
- await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 100, this.airConditioner?.log);
1906
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', 100, this.airConditioner?.log);
1907
- }
1908
- else if (newValue === FanControl.FanMode.Auto) {
1909
- await this.airConditioner?.setAttribute(FanControl.id, 'percentSetting', 50, this.airConditioner?.log);
1910
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', 50, this.airConditioner?.log);
1911
- }
1912
- })(), this.airConditioner?.log);
1835
+ }, this.airConditioner?.log);
1913
1836
  this.airConditioner?.subscribeAttribute(FanControl.id, 'percentSetting', (newValue, oldValue, context) => void (async () => {
1914
1837
  this.airConditioner?.log.info(`Percent setting changed from ${oldValue} to ${newValue} context: ${context.fabric === undefined ? 'offline' : 'online'}`);
1915
1838
  if (context.fabric === undefined)
1916
1839
  return;
1917
- if (isValidNumber(newValue, 0, 100))
1918
- await this.airConditioner?.setAttribute(FanControl.id, 'percentCurrent', newValue, this.airConditioner?.log);
1840
+ if (newValue === null || isValidNumber(newValue, 0, 100))
1841
+ await this.airConditioner?.setAttribute(FanControl, 'percentCurrent', newValue ?? 50, this.airConditioner?.log);
1919
1842
  })(), this.airConditioner?.log);
1920
1843
  this.basicVideoPlayer = new BasicVideoPlayer('BasicVideoPlayer', 'BVP00062');
1921
1844
  this.basicVideoPlayer = await this.addDevice(this.basicVideoPlayer);
@@ -2518,31 +2441,30 @@ export class ExampleMatterbridgeDynamicPlatform extends MatterbridgeDynamicPlatf
2518
2441
  await this.fanBase?.setAttribute(FanControl.id, 'fanMode', FanControl.FanMode.Off, this.fanBase.log);
2519
2442
  await this.fanBase?.setAttribute(FanControl.id, 'percentCurrent', 0, this.fanBase.log);
2520
2443
  await this.fanBase?.setAttribute(FanControl.id, 'percentSetting', 0, this.fanBase.log);
2444
+ await this.fanOnHigh?.setAttribute(FanControl.id, 'fanMode', FanControl.FanMode.Off, this.fanOnHigh.log);
2445
+ await this.fanOnHigh?.setAttribute(FanControl.id, 'percentCurrent', 0, this.fanOnHigh.log);
2446
+ await this.fanOnHigh?.setAttribute(FanControl.id, 'percentSetting', 0, this.fanOnHigh.log);
2447
+ this.fanDefault?.log.info('Set fan initial fanMode to Auto and percentCurrent to 0');
2521
2448
  await this.fanDefault?.setAttribute(FanControl.id, 'fanMode', FanControl.FanMode.Auto, this.fanDefault.log);
2522
2449
  await this.fanDefault?.setAttribute(FanControl.id, 'percentCurrent', 0, this.fanDefault.log);
2523
- await this.fanDefault?.setAttribute(FanControl.id, 'percentSetting', 0, this.fanDefault.log);
2450
+ this.fanComplete?.log.info('Set fan initial fanMode to Auto and percentCurrent to 0');
2524
2451
  await this.fanComplete?.setAttribute(FanControl.id, 'fanMode', FanControl.FanMode.Auto, this.fanComplete.log);
2525
2452
  await this.fanComplete?.setAttribute(FanControl.id, 'percentCurrent', 0, this.fanComplete.log);
2526
- await this.fanComplete?.setAttribute(FanControl.id, 'percentSetting', 0, this.fanComplete.log);
2527
2453
  if (this.config.useInterval) {
2528
2454
  this.addInterval(async () => {
2529
- let mode = this.fanBase?.getAttribute(FanControl, 'fanMode', this.fanBase.log);
2530
- let value = this.fanBase?.getAttribute(FanControl, 'percentCurrent', this.fanBase.log);
2531
- mode = this.fanDefault?.getAttribute(FanControl, 'fanMode', this.fanDefault.log);
2532
- value = this.fanDefault?.getAttribute(FanControl, 'percentCurrent', this.fanDefault.log);
2455
+ let mode = this.fanDefault?.getAttribute(FanControl, 'fanMode', this.fanDefault.log);
2456
+ let value = this.fanDefault?.getAttribute(FanControl, 'percentCurrent', this.fanDefault.log);
2533
2457
  if (isValidNumber(mode, FanControl.FanMode.Off, FanControl.FanMode.Auto) && mode === FanControl.FanMode.Auto && isValidNumber(value, 0, 100)) {
2534
2458
  value = value + 10 >= 100 ? 0 : value + 10;
2535
2459
  await this.fanDefault?.setAttribute(FanControl, 'percentCurrent', value, this.fanDefault.log);
2536
- await this.fanDefault?.setAttribute(FanControl, 'percentSetting', value, this.fanDefault.log);
2537
- this.fanDefault?.log.info(`Set fan percentCurrent and percentSetting to ${value}`);
2460
+ this.fanDefault?.log.info(`Set fan percentCurrent to ${value}`);
2538
2461
  }
2539
2462
  mode = this.fanComplete?.getAttribute(FanControl, 'fanMode', this.fanComplete.log);
2540
2463
  value = this.fanComplete?.getAttribute(FanControl, 'percentCurrent', this.fanComplete.log);
2541
2464
  if (isValidNumber(mode, FanControl.FanMode.Off, FanControl.FanMode.Auto) && mode === FanControl.FanMode.Auto && isValidNumber(value, 0, 100)) {
2542
2465
  value = value + 10 >= 100 ? 0 : value + 10;
2543
2466
  await this.fanComplete?.setAttribute(FanControl, 'percentCurrent', value, this.fanComplete.log);
2544
- await this.fanComplete?.setAttribute(FanControl, 'percentSetting', value, this.fanComplete.log);
2545
- this.fanComplete?.log.info(`Set fan percentCurrent and percentSetting to ${value}`);
2467
+ this.fanComplete?.log.info(`Set fan percentCurrent to ${value}`);
2546
2468
  }
2547
2469
  }, 60 * 1000 + 700);
2548
2470
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge-example-dynamic-platform",
3
- "version": "3.0.3-dev-20260821-8d97cc8",
3
+ "version": "3.0.3-dev-20260822-8bd7fd5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-example-dynamic-platform",
9
- "version": "3.0.3-dev-20260821-8d97cc8",
9
+ "version": "3.0.3-dev-20260822-8bd7fd5",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "node-ansi-logger": "3.3.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-example-dynamic-platform",
3
- "version": "3.0.3-dev-20260821-8d97cc8",
3
+ "version": "3.0.3-dev-20260822-8bd7fd5",
4
4
  "description": "Matterbridge dynamic plugin",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",