matterbridge-zigbee2mqtt 2.4.7 → 2.5.0-dev.3

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,42 @@ 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
+ ### Breaking Changes
12
+
13
+ New device types:
14
+
15
+ - extendedColorLight
16
+ - waterLeakDetector
17
+ - rainSensor
18
+ - smokeSensor
19
+
20
+ If your controller has issues detecting the new device type, blacklist these devices, restart, wait 5 minutes, remove the blacklist and restart again. This will create a new endpoint on the controller.
21
+
22
+ ## [2.5.0] - 2025-05-23
23
+
24
+ ### Added
25
+
26
+ - [scenes]: Added scenes support for groups and devices. See the README.md for explanations.
27
+ - [waterLeak]: Added waterLeakDetector device type for zigbee property "water_leak". Default to false (i.e. no alarm) since is not possible to get the property.
28
+ - [rainSensor]: Added rainSensor device type for zigbee property "rain". Default to false (i.e. no alarm) since is not possible to get the property.
29
+ - [smokeSensor]: Added smokeSensor device type for zigbee property "smoke". Default to false (i.e. no alarm) since is not possible to get the property.
30
+ - [colorTemp]: Added conversion from color temperature to rgb for the rgb devices that don't support color temperature.
31
+ - [battery]: Set batChargeLevel to warning if battery is less than 40% and the device doesn't expose battery_low.
32
+ - [battery]: Set batChargeLevel to critical if battery is less than 20% and the device doesn't expose battery_low.
33
+ - [retain]: Send retained mqtt states at startup if z2m has retain enabled.
34
+
35
+ ### Changed
36
+
37
+ - [package]: Updated package.
38
+ - [package]: Updated dependencies.
39
+ - [plugin]: Requires Matterbridge 3.0.3.
40
+ - [config]: As anticipated in the previous release, the parameter postfixHostname has been removed. Use postfix if needed.
41
+ - [colorRgb]: Changed the default device type from colorTemperatureLight to extendedColorLight to solve the SmartThings issue with colors.
42
+
43
+ <a href="https://www.buymeacoffee.com/luligugithub">
44
+ <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
45
+ </a>
46
+
11
47
  ## [2.4.7] - 2025-03-19
12
48
 
13
49
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@ Matterbridge zigbee2mqtt is a matterbridge production-level plugin that expose a
17
17
 
18
18
  No hub or dedicated hardware needed.
19
19
 
20
- Interested in super fast and autonomous automations for zigbee2mqtt? Try this: https://github.com/Luligu/zigbee2mqtt-automations
20
+ Interested in super fast and autonomous **[automations for zigbee2mqtt](https://github.com/Luligu/zigbee2mqtt-automations)**? Try this: https://github.com/Luligu/zigbee2mqtt-automations.
21
21
 
22
22
  If you like this project and find it useful, please consider giving it a star on GitHub at https://github.com/Luligu/matterbridge-zigbee2mqtt and sponsoring it.
23
23
 
@@ -150,8 +150,9 @@ These are the default vules:
150
150
  "outletList": [],
151
151
  "featureBlackList": [],
152
152
  "deviceFeatureBlackList": {},
153
- "postfix": "",
154
- "postfixHostname": true
153
+ "scenesType": "outlet",
154
+ "scenesPrefix: true,
155
+ "postfix": ""
155
156
  }
156
157
  ```
157
158
 
@@ -223,6 +224,14 @@ The latest release also supports all clusters in the multi endpoints devices (e.
223
224
 
224
225
  Since the Matter support in the available ecosystems (controllers) is sometimes limited and, when available, only covers Matter 1.1 specifications, some z2m devices cannot be exposed properly or cannot be exposed at all.
225
226
 
227
+ ## Availability
228
+
229
+ If the availability is enabled in zigbee2mqtt settings, it is exposed.
230
+
231
+ ## Retain
232
+
233
+ If the retain option is enabled in zigbee2mqtt settings or device setting, at restart all retained states are updated.
234
+
226
235
  ## Unsupported devices
227
236
 
228
237
  If one of your devices is not supported out of the box, open an issue and we will try to support it if possible.
@@ -250,6 +259,20 @@ If one of your devices is not supported out of the box, open an issue and we wil
250
259
 
251
260
  ![See the screenshot here](https://github.com/Luligu/matterbridge-zigbee2mqtt/blob/main/screenshot/Smart%20button.png)
252
261
 
262
+ ## Scenes in groups and devices
263
+
264
+ With release 2.5.0 has been added support for scenes in groups and devices.
265
+
266
+ In the config select what device type you want to use to expose the command that runs the scene: 'light' | 'outlet' | 'switch' | 'mounted_switch'.
267
+
268
+ Switch is not supported by Alexa. Mounted Switch is not supported by Apple Home.
269
+
270
+ The virtual device takes the name of the group or device it belongs to, with added the name of scene. If scenesPrefix is disabled, it takes only the name of the scene. Consider that in Matter the node name is 32 characters long.
271
+
272
+ The state of the virtual device is always reverted to off in a few seconds.
273
+
274
+ It is possibile to disable the feature globally with featureBlackList and on a per device/group base with deviceFeatureBlackList.
275
+
253
276
  # Known issues
254
277
 
255
278
  For general controller issues check the Matterbridge Known issues section
package/dist/entity.js CHANGED
@@ -1,12 +1,11 @@
1
- import { airQualitySensor, colorTemperatureSwitch, dimmableSwitch, onOffSwitch, powerSource, bridgedNode, electricalSensor, onOffLight, dimmableLight, colorTemperatureLight, onOffOutlet, coverDevice, thermostatDevice, MatterbridgeEndpoint, dimmableOutlet, doorLockDevice, occupancySensor, lightSensor, contactSensor, temperatureSensor, humiditySensor, pressureSensor, genericSwitch, } from 'matterbridge';
1
+ import { airQualitySensor, colorTemperatureSwitch, dimmableSwitch, onOffSwitch, powerSource, bridgedNode, electricalSensor, onOffLight, dimmableLight, colorTemperatureLight, onOffOutlet, coverDevice, thermostatDevice, MatterbridgeEndpoint, dimmableOutlet, doorLockDevice, occupancySensor, lightSensor, contactSensor, temperatureSensor, humiditySensor, pressureSensor, genericSwitch, waterLeakDetector, rainSensor, smokeCoAlarm, extendedColorLight, } from 'matterbridge';
2
2
  import { AnsiLogger, gn, dn, ign, idn, rs, db, debugStringify, hk, zb, or, nf, CYAN, er, YELLOW } from 'matterbridge/logger';
3
- import { deepCopy, deepEqual, isValidNumber } from 'matterbridge/utils';
3
+ import { deepCopy, deepEqual, isValidNumber, kelvinToRGB, miredToKelvin } from 'matterbridge/utils';
4
4
  import * as color from 'matterbridge/utils';
5
5
  import { SwitchesTag, NumberTag } from 'matterbridge/matter';
6
6
  import { getClusterNameById, ClusterId } from 'matterbridge/matter/types';
7
- import { ElectricalEnergyMeasurement, ElectricalPowerMeasurement, OnOffCluster, LevelControlCluster, WindowCoveringCluster, DoorLockCluster, BridgedDeviceBasicInformation, OnOff, LevelControl, ColorControl, ColorControlCluster, TemperatureMeasurement, BooleanState, RelativeHumidityMeasurement, PressureMeasurement, OccupancySensing, IlluminanceMeasurement, PowerSource, WindowCovering, DoorLock, ThermostatCluster, Thermostat, AirQuality, TotalVolatileOrganicCompoundsConcentrationMeasurement, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FormaldehydeConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, Pm10ConcentrationMeasurement, } from 'matterbridge/matter/clusters';
7
+ import { ElectricalEnergyMeasurement, ElectricalPowerMeasurement, OnOffCluster, LevelControlCluster, WindowCoveringCluster, DoorLockCluster, BridgedDeviceBasicInformation, OnOff, LevelControl, ColorControl, ColorControlCluster, TemperatureMeasurement, BooleanState, RelativeHumidityMeasurement, PressureMeasurement, OccupancySensing, IlluminanceMeasurement, PowerSource, WindowCovering, DoorLock, ThermostatCluster, Thermostat, AirQuality, TotalVolatileOrganicCompoundsConcentrationMeasurement, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FormaldehydeConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, Pm10ConcentrationMeasurement, SmokeCoAlarm, } from 'matterbridge/matter/clusters';
8
8
  import EventEmitter from 'node:events';
9
- import { hostname } from 'node:os';
10
9
  export class ZigbeeEntity extends EventEmitter {
11
10
  log;
12
11
  serial = '';
@@ -87,6 +86,17 @@ export class ZigbeeEntity extends EventEmitter {
87
86
  return;
88
87
  if (key === 'voltage' && this.isDevice && this.device?.power_source === 'Battery')
89
88
  key = 'battery_voltage';
89
+ if (key === 'battery' && !('battery_low' in payload) && isValidNumber(value, 0, 100) && this.isDevice && this.device?.power_source === 'Battery') {
90
+ if (value < 20) {
91
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, PowerSource.Cluster.id, 'batChargeLevel', PowerSource.BatChargeLevel.Critical);
92
+ }
93
+ else if (value < 40) {
94
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, PowerSource.Cluster.id, 'batChargeLevel', PowerSource.BatChargeLevel.Warning);
95
+ }
96
+ else {
97
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, PowerSource.Cluster.id, 'batChargeLevel', PowerSource.BatChargeLevel.Ok);
98
+ }
99
+ }
90
100
  const propertyMap = this.propertyMap.get(key);
91
101
  if (propertyMap) {
92
102
  this.log.debug(`Payload entry ${CYAN}${key}${db} => name: ${CYAN}${propertyMap.name}${db} type: ${CYAN}${propertyMap.type === '' ? 'generic' : propertyMap.type}${db} ` +
@@ -110,7 +120,7 @@ export class ZigbeeEntity extends EventEmitter {
110
120
  const propertyMap = this.propertyMap.get(('action_' + value));
111
121
  if (propertyMap) {
112
122
  const child = this.bridgedDevice.getChildEndpointByName(propertyMap.endpoint);
113
- if (child && child.number)
123
+ if (child && child.maybeNumber)
114
124
  child.triggerSwitchEvent(propertyMap.action, this.log);
115
125
  }
116
126
  else
@@ -294,11 +304,11 @@ export class ZigbeeEntity extends EventEmitter {
294
304
  }
295
305
  const localValue = deviceEndpoint.getAttribute(ClusterId(clusterId), attributeName, undefined);
296
306
  if (typeof value === 'object' ? deepEqual(value, localValue) : value === localValue) {
297
- this.log.debug(`Skip update endpoint ${deviceEndpoint.name}:${deviceEndpoint.number}${childEndpointName ? ' (' + childEndpointName + ')' : ''} ` +
307
+ this.log.debug(`Skip update endpoint ${deviceEndpoint.name}:${deviceEndpoint.maybeNumber}${childEndpointName ? ' (' + childEndpointName + ')' : ''} ` +
298
308
  `attribute ${getClusterNameById(ClusterId(clusterId))}.${attributeName} already ${typeof value === 'object' ? debugStringify(value) : value}`);
299
309
  return;
300
310
  }
301
- this.log.info(`${db}Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
311
+ this.log.info(`${db}Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.maybeNumber}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
302
312
  `attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${db}.${hk}${attributeName}${db} from ${YELLOW}${typeof localValue === 'object' ? debugStringify(localValue) : localValue}${db} to ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
303
313
  try {
304
314
  deviceEndpoint.setAttribute(ClusterId(clusterId), attributeName, value);
@@ -330,10 +340,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
330
340
  }
331
341
  static async create(platform, group) {
332
342
  const zigbeeGroup = new ZigbeeGroup(platform, group);
333
- if (zigbeeGroup.platform.postfixHostname) {
334
- zigbeeGroup.serial = `group-${group.id}_${hostname}`.slice(0, 32);
335
- }
336
- else if (zigbeeGroup.platform.postfix !== '') {
343
+ if (zigbeeGroup.platform.postfix !== '') {
337
344
  zigbeeGroup.serial = `group-${group.id}-${zigbeeGroup.platform.postfix}`.slice(0, 32);
338
345
  }
339
346
  else {
@@ -419,7 +426,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
419
426
  zigbeeGroup.propertyMap.set('color_temp', { name: 'color_temp', type: 'light', endpoint: '' });
420
427
  }
421
428
  if (useColor) {
422
- deviceType = colorTemperatureLight;
429
+ deviceType = extendedColorLight;
423
430
  zigbeeGroup.propertyMap.set('color', { name: 'color', type: 'light', endpoint: '' });
424
431
  }
425
432
  if (isCover) {
@@ -440,9 +447,14 @@ export class ZigbeeGroup extends ZigbeeEntity {
440
447
  return zigbeeGroup;
441
448
  zigbeeGroup.bridgedDevice = new MatterbridgeEndpoint([deviceType, bridgedNode, powerSource], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
442
449
  }
443
- if (platform.config.groupScenes === true) {
450
+ if (!platform.featureBlackList?.includes('scenes') && !platform.deviceFeatureBlackList[group.friendly_name]?.includes('scenes')) {
444
451
  group.scenes.forEach((scene) => {
445
452
  zigbeeGroup.log.debug(`***Group ${gn}${group.friendly_name}${rs}${db} scene ${CYAN}${scene.name}${db} id ${CYAN}${scene.id}${db}`);
453
+ platform.setSelectDeviceEntity(`group-${group.id}`, 'scenes', 'Scenes', 'component');
454
+ platform.registerVirtualDevice(`${platform.config.scenesPrefix ? group.friendly_name + ' ' : ''}${scene.name}`, async () => {
455
+ zigbeeGroup.log.info(`Triggered scene "${scene.name}" id ${scene.id} from group ${group.friendly_name}`);
456
+ zigbeeGroup.publishCommand('scene_recall', group.friendly_name, { 'scene_recall': scene.id });
457
+ });
446
458
  });
447
459
  }
448
460
  zigbeeGroup.addBridgedDeviceBasicInformation();
@@ -458,7 +470,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
458
470
  if (isLight)
459
471
  await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'light');
460
472
  zigbeeGroup.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
461
- zigbeeGroup.log.warn(`Command identify called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
473
+ zigbeeGroup.log.debug(`Command identify called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
462
474
  });
463
475
  zigbeeGroup.bridgedDevice.addCommandHandler('on', async () => {
464
476
  zigbeeGroup.log.debug(`Command on called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
@@ -520,6 +532,14 @@ export class ZigbeeGroup extends ZigbeeEntity {
520
532
  const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50);
521
533
  zigbeeGroup.publishCommand('moveToHueAndSaturation', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
522
534
  });
535
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToColor', async ({ request }) => {
536
+ zigbeeGroup.log.debug(`Command moveToColor called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: X: ${request.colorX} Y: ${request.colorY}`);
537
+ await zigbeeGroup.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY, zigbeeGroup.log);
538
+ const payload = { color: { x: request.colorX / 65536, y: request.colorY / 65536 } };
539
+ if (zigbeeGroup.transition && request.transitionTime && request.transitionTime / 10 >= 1)
540
+ payload['transition'] = Math.round(request.transitionTime / 10);
541
+ zigbeeGroup.publishCommand('moveToColor', group.friendly_name, payload);
542
+ });
523
543
  }
524
544
  }
525
545
  if (isCover) {
@@ -600,8 +620,8 @@ export const z2ms = [
600
620
  { type: 'outlet', name: 'brightness', property: 'brightness', deviceType: dimmableOutlet, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(1, Math.min(254, value)); } },
601
621
  { type: 'light', name: 'state', property: 'state', deviceType: onOffLight, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
602
622
  { type: 'light', name: 'brightness', property: 'brightness', deviceType: dimmableLight, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(1, Math.min(254, value)); } },
603
- { type: 'light', name: 'color_hs', property: 'color_hs', deviceType: colorTemperatureLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
604
- { type: 'light', name: 'color_xy', property: 'color_xy', deviceType: colorTemperatureLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
623
+ { type: 'light', name: 'color_hs', property: 'color_hs', deviceType: extendedColorLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
624
+ { type: 'light', name: 'color_xy', property: 'color_xy', deviceType: extendedColorLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
605
625
  { type: 'light', name: 'color_temp', property: 'color_temp', deviceType: colorTemperatureLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
606
626
  { type: 'cover', name: 'state', property: 'state', deviceType: coverDevice, cluster: WindowCovering.Cluster.id, attribute: 'targetPositionLiftPercent100ths' },
607
627
  { type: 'cover', name: 'moving', property: 'moving', deviceType: coverDevice, cluster: WindowCovering.Cluster.id, attribute: 'operationalStatus' },
@@ -623,9 +643,10 @@ export const z2ms = [
623
643
  { type: '', name: 'occupancy', property: 'occupancy', deviceType: occupancySensor, cluster: OccupancySensing.Cluster.id, attribute: 'occupancy', converter: (value) => { return { occupied: value }; } },
624
644
  { type: '', name: 'illuminance', property: 'illuminance', deviceType: lightSensor, cluster: IlluminanceMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(Math.max(Math.min(10000 * Math.log10(value), 0xfffe), 0)); } },
625
645
  { type: '', name: 'contact', property: 'contact', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return value; } },
626
- { type: '', name: 'water_leak', property: 'water_leak', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
646
+ { type: '', name: 'water_leak', property: 'water_leak', deviceType: waterLeakDetector, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return value; } },
647
+ { type: '', name: 'rain', property: 'rain', deviceType: rainSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return value; } },
627
648
  { type: '', name: 'vibration', property: 'vibration', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
628
- { type: '', name: 'smoke', property: 'smoke', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
649
+ { type: '', name: 'smoke', property: 'smoke', deviceType: smokeCoAlarm, cluster: SmokeCoAlarm.Cluster.id, attribute: 'smokeState', converter: (value) => { return value ? SmokeCoAlarm.AlarmState.Critical : SmokeCoAlarm.AlarmState.Normal; } },
629
650
  { type: '', name: 'carbon_monoxide', property: 'carbon_monoxide', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
630
651
  { type: '', name: 'temperature', property: 'temperature', deviceType: temperatureSensor, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
631
652
  { type: '', name: 'humidity', property: 'humidity', deviceType: humiditySensor, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
@@ -644,10 +665,10 @@ export const z2ms = [
644
665
  { type: '', name: '', property: 'battery', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batPercentRemaining', converter: (value) => { return Math.round(value * 2); } },
645
666
  { type: '', name: '', property: 'battery_low', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batChargeLevel', converter: (value) => { return value === true ? PowerSource.BatChargeLevel.Critical : PowerSource.BatChargeLevel.Ok; } },
646
667
  { type: '', name: '', property: 'battery_voltage', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batVoltage', converter: (value) => { return value; } },
647
- { type: '', name: 'energy', property: 'energy', deviceType: electricalSensor, cluster: ElectricalEnergyMeasurement.Cluster.id, attribute: 'cumulativeEnergyImported', converter: (value) => { return { energy: value * 1000000 }; } },
648
- { type: '', name: 'power', property: 'power', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activePower', converter: (value) => { return value * 1000; } },
649
- { type: '', name: 'voltage', property: 'voltage', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'voltage', converter: (value) => { return value * 1000; } },
650
- { type: '', name: 'current', property: 'current', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activeCurrent', converter: (value) => { return value * 1000; } },
668
+ { type: '', name: 'energy', property: 'energy', deviceType: electricalSensor, cluster: ElectricalEnergyMeasurement.Cluster.id, attribute: 'cumulativeEnergyImported', converter: (value) => { return { energy: Math.round(value * 1000000) }; } },
669
+ { type: '', name: 'power', property: 'power', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activePower', converter: (value) => { return Math.round(value * 1000); } },
670
+ { type: '', name: 'voltage', property: 'voltage', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'voltage', converter: (value) => { return Math.round(value * 1000); } },
671
+ { type: '', name: 'current', property: 'current', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activeCurrent', converter: (value) => { return Math.round(value * 1000); } },
651
672
  ];
652
673
  export class ZigbeeDevice extends ZigbeeEntity {
653
674
  constructor(platform, device) {
@@ -656,37 +677,42 @@ export class ZigbeeDevice extends ZigbeeEntity {
656
677
  static async create(platform, device) {
657
678
  const zigbeeDevice = new ZigbeeDevice(platform, device);
658
679
  zigbeeDevice.serial = `${device.ieee_address}`;
659
- if (zigbeeDevice.platform.postfixHostname) {
660
- zigbeeDevice.serial = `${zigbeeDevice.serial}_${hostname}`.slice(0, 32);
661
- }
662
- else if (zigbeeDevice.platform.postfix !== '') {
680
+ if (zigbeeDevice.platform.postfix !== '') {
663
681
  zigbeeDevice.serial = `${zigbeeDevice.serial}-${zigbeeDevice.platform.postfix}`.slice(0, 32);
664
682
  }
665
683
  if (device.friendly_name === 'Coordinator' || (device.model_id === 'ti.router' && device.manufacturer === 'TexasInstruments') || (device.model_id.startsWith('SLZB-') && device.manufacturer === 'SMLIGHT')) {
666
684
  zigbeeDevice.isRouter = true;
667
685
  platform.setSelectDevice(device.ieee_address, device.friendly_name, 'wifi');
668
- zigbeeDevice.bridgedDevice = new MatterbridgeEndpoint([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
686
+ zigbeeDevice.bridgedDevice = new MatterbridgeEndpoint([doorLockDevice, bridgedNode, powerSource], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
669
687
  zigbeeDevice.addBridgedDeviceBasicInformation();
670
688
  zigbeeDevice.addPowerSource();
671
689
  zigbeeDevice.bridgedDevice.addRequiredClusterServers();
672
690
  await zigbeeDevice.bridgedDevice.addFixedLabel('type', 'lock');
673
691
  zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice);
692
+ zigbeeDevice.bridgedDevice.addCommandHandler('identify', async (data) => {
693
+ zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request identifyTime:${data.request.identifyTime} `);
694
+ });
674
695
  zigbeeDevice.bridgedDevice.addCommandHandler('lockDoor', async () => {
675
- zigbeeDevice.log.debug(`Command permit_join: false called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
696
+ zigbeeDevice.log.debug(`Command permit_join false called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
676
697
  await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Locked, zigbeeDevice.log);
677
- zigbeeDevice.publishCommand('permit_join: false', 'bridge/request/permit_join', { value: false });
698
+ zigbeeDevice.publishCommand('permit_join false', 'bridge/request/permit_join', { value: false });
678
699
  });
679
700
  zigbeeDevice.bridgedDevice.addCommandHandler('unlockDoor', async () => {
680
- zigbeeDevice.log.debug(`Command permit_join: true called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
701
+ zigbeeDevice.log.debug(`Command permit_join true called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
681
702
  await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Unlocked, zigbeeDevice.log);
682
- zigbeeDevice.publishCommand('permit_join: true', 'bridge/request/permit_join', { value: true });
703
+ zigbeeDevice.publishCommand('permit_join true', 'bridge/request/permit_join', { value: true });
683
704
  });
684
705
  return zigbeeDevice;
685
706
  }
686
- if (platform.config.deviceScenes === true) {
707
+ if (!platform.featureBlackList?.includes('scenes') && !platform.deviceFeatureBlackList[device.friendly_name]?.includes('scenes')) {
687
708
  Object.entries(device.endpoints).forEach(([key, endpoint]) => {
688
709
  Object.values(endpoint.scenes).forEach((scene) => {
689
710
  zigbeeDevice.log.debug(`***Device ${dn}${device.friendly_name}${rs}${db} endpoint ${CYAN}${key}${db} scene ${CYAN}${scene.name}${db} id ${CYAN}${scene.id}${db}`);
711
+ platform.setSelectDeviceEntity(device.ieee_address, 'scenes', 'Scenes', 'component');
712
+ platform.registerVirtualDevice(`${platform.config.scenesPrefix ? device.friendly_name + ' ' : ''}${scene.name}`, async () => {
713
+ zigbeeDevice.log.info(`Triggered scene "${scene.name}" id ${scene.id} from device ${device.friendly_name}`);
714
+ zigbeeDevice.publishCommand('scene_recall', device.friendly_name, { 'scene_recall': scene.id });
715
+ });
690
716
  });
691
717
  });
692
718
  }
@@ -908,6 +934,11 @@ export class ZigbeeDevice extends ZigbeeEntity {
908
934
  deviceTypesMap.delete(onOffLight.code);
909
935
  if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(colorTemperatureLight.code))
910
936
  deviceTypesMap.delete(dimmableLight.code);
937
+ if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(extendedColorLight.code))
938
+ deviceTypesMap.delete(dimmableLight.code);
939
+ if (deviceTypesMap.has(colorTemperatureLight.code) && deviceTypesMap.has(extendedColorLight.code))
940
+ deviceTypesMap.delete(colorTemperatureLight.code);
941
+ deviceTypesMap.delete(bridgedNode.code);
911
942
  deviceTypesMap.delete(powerSource.code);
912
943
  device.deviceTypes = Array.from(deviceTypesMap.values());
913
944
  }
@@ -943,12 +974,26 @@ export class ZigbeeDevice extends ZigbeeEntity {
943
974
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${zigbeeDevice.device?.friendly_name}${rs}${db} endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => ` +
944
975
  `${nf}tagList: ${debugStringify(device.tagList)} deviceTypes: ${debugStringify(device.deviceTypes)} clusterServersIds: ${debugStringify(device.clusterServersIds)}`);
945
976
  }
977
+ if ((mainEndpoint.deviceTypes.find((dt) => dt.code === waterLeakDetector.code) || mainEndpoint.deviceTypes.find((dt) => dt.code === rainSensor.code)) && mainEndpoint.clusterServersIds.includes(BooleanState.Cluster.id)) {
978
+ zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} BooleanStateCluster cluster with`);
979
+ zigbeeDevice.bridgedDevice.createDefaultBooleanStateClusterServer(false);
980
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(BooleanState.Cluster.id), 1);
981
+ }
982
+ if (mainEndpoint.deviceTypes.find((dt) => dt.code === smokeCoAlarm.code) && mainEndpoint.clusterServersIds.includes(SmokeCoAlarm.Cluster.id)) {
983
+ zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} SmokeCoAlarmCluster cluster with`);
984
+ zigbeeDevice.bridgedDevice.createSmokeOnlySmokeCOAlarmClusterServer(SmokeCoAlarm.AlarmState.Normal);
985
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(SmokeCoAlarm.Cluster.id), 1);
986
+ }
946
987
  if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
947
- zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')}`);
948
988
  if (!names.includes('color_hs') && !names.includes('color_xy')) {
949
- zigbeeDevice.bridgedDevice.createCtColorControlClusterServer();
950
- mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(ColorControl.Cluster.id), 1);
989
+ zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with CT: ${names.includes('color_temp')} min: ${zigbeeDevice.propertyMap.get('color_temp')?.value_min} max: ${zigbeeDevice.propertyMap.get('color_temp')?.value_max}`);
990
+ zigbeeDevice.bridgedDevice.createCtColorControlClusterServer(zigbeeDevice.propertyMap.get('color_temp')?.value_max, zigbeeDevice.propertyMap.get('color_temp')?.value_min, zigbeeDevice.propertyMap.get('color_temp')?.value_max);
951
991
  }
992
+ else {
993
+ zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')} min: ${zigbeeDevice.propertyMap.get('color_temp')?.value_min} max: ${zigbeeDevice.propertyMap.get('color_temp')?.value_max}`);
994
+ zigbeeDevice.bridgedDevice.createDefaultColorControlClusterServer(undefined, undefined, undefined, undefined, zigbeeDevice.propertyMap.get('color_temp')?.value_max, zigbeeDevice.propertyMap.get('color_temp')?.value_min, zigbeeDevice.propertyMap.get('color_temp')?.value_max);
995
+ }
996
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(ColorControl.Cluster.id), 1);
952
997
  }
953
998
  if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
954
999
  const system_mode = zigbeeDevice.propertyMap.get('system_mode');
@@ -991,25 +1036,25 @@ export class ZigbeeDevice extends ZigbeeEntity {
991
1036
  zigbeeDevice.mutableDevice.clear();
992
1037
  zigbeeDevice.logPropertyMap();
993
1038
  zigbeeDevice.bridgedDevice.addCommandHandler('identify', async (data) => {
994
- zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} `);
1039
+ zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber} request identifyTime:${data.request.identifyTime} `);
995
1040
  });
996
1041
  if (zigbeeDevice.bridgedDevice.hasClusterServer(OnOffCluster.id) || zigbeeDevice.hasEndpoints) {
997
1042
  for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
998
1043
  if (child.hasClusterServer(OnOffCluster)) {
999
1044
  child.addCommandHandler('on', async (data) => {
1000
- zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1045
+ zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber}`);
1001
1046
  const payload = {};
1002
1047
  payload['state_' + data.endpoint.uniqueStorageKey] = 'ON';
1003
1048
  zigbeeDevice.publishCommand('on', device.friendly_name, payload);
1004
1049
  });
1005
1050
  child.addCommandHandler('off', async (data) => {
1006
- zigbeeDevice.log.debug(`Command off called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1051
+ zigbeeDevice.log.debug(`Command off called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber}`);
1007
1052
  const payload = {};
1008
1053
  payload['state_' + data.endpoint.uniqueStorageKey] = 'OFF';
1009
1054
  zigbeeDevice.publishCommand('off', device.friendly_name, payload);
1010
1055
  });
1011
1056
  child.addCommandHandler('toggle', async (data) => {
1012
- zigbeeDevice.log.debug(`Command toggle called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1057
+ zigbeeDevice.log.debug(`Command toggle called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber}`);
1013
1058
  const payload = {};
1014
1059
  payload['state_' + data.endpoint.uniqueStorageKey] = 'TOGGLE';
1015
1060
  zigbeeDevice.publishCommand('toggle', device.friendly_name, payload);
@@ -1017,41 +1062,23 @@ export class ZigbeeDevice extends ZigbeeEntity {
1017
1062
  }
1018
1063
  }
1019
1064
  zigbeeDevice.bridgedDevice.addCommandHandler('on', async (data) => {
1020
- zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1021
- const payload = {};
1022
- const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1023
- if (label === undefined)
1024
- payload['state'] = 'ON';
1025
- else
1026
- payload['state_' + label] = 'ON';
1027
- zigbeeDevice.publishCommand('on', device.friendly_name, payload);
1065
+ zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber}`);
1066
+ zigbeeDevice.publishCommand('on', device.friendly_name, { state: 'ON' });
1028
1067
  });
1029
1068
  zigbeeDevice.bridgedDevice.addCommandHandler('off', async (data) => {
1030
- zigbeeDevice.log.debug(`Command off called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1031
- const payload = {};
1032
- const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1033
- if (label === undefined)
1034
- payload['state'] = 'OFF';
1035
- else
1036
- payload['state_' + label] = 'OFF';
1037
- zigbeeDevice.publishCommand('off', device.friendly_name, payload);
1069
+ zigbeeDevice.log.debug(`Command off called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber}`);
1070
+ zigbeeDevice.publishCommand('off', device.friendly_name, { state: 'OFF' });
1038
1071
  });
1039
1072
  zigbeeDevice.bridgedDevice.addCommandHandler('toggle', async (data) => {
1040
- zigbeeDevice.log.debug(`Command toggle called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1041
- const payload = {};
1042
- const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1043
- if (label === undefined)
1044
- payload['state'] = 'TOGGLE';
1045
- else
1046
- payload['state_' + label] = 'TOGGLE';
1047
- zigbeeDevice.publishCommand('toggle', device.friendly_name, payload);
1073
+ zigbeeDevice.log.debug(`Command toggle called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber}`);
1074
+ zigbeeDevice.publishCommand('toggle', device.friendly_name, { state: 'TOGGLE' });
1048
1075
  });
1049
1076
  }
1050
1077
  if (zigbeeDevice.bridgedDevice.hasClusterServer(LevelControlCluster.id) || zigbeeDevice.hasEndpoints) {
1051
1078
  for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
1052
1079
  if (child.hasClusterServer(LevelControlCluster)) {
1053
1080
  child.addCommandHandler('moveToLevel', async (data) => {
1054
- zigbeeDevice.log.debug(`Command moveToLevel called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1081
+ zigbeeDevice.log.debug(`Command moveToLevel called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1055
1082
  const payload = {};
1056
1083
  payload['brightness_' + data.endpoint.uniqueStorageKey] = data.request.level;
1057
1084
  if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
@@ -1059,7 +1086,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1059
1086
  zigbeeDevice.publishCommand('moveToLevel', device.friendly_name, payload);
1060
1087
  });
1061
1088
  child.addCommandHandler('moveToLevelWithOnOff', async (data) => {
1062
- zigbeeDevice.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1089
+ zigbeeDevice.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1063
1090
  const payload = {};
1064
1091
  payload['brightness_' + data.endpoint.uniqueStorageKey] = data.request.level;
1065
1092
  if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
@@ -1069,25 +1096,15 @@ export class ZigbeeDevice extends ZigbeeEntity {
1069
1096
  }
1070
1097
  }
1071
1098
  zigbeeDevice.bridgedDevice.addCommandHandler('moveToLevel', async (data) => {
1072
- zigbeeDevice.log.debug(`Command moveToLevel called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1073
- const payload = {};
1074
- const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1075
- if (label === undefined)
1076
- payload['brightness'] = data.request.level;
1077
- else
1078
- payload['brightness_' + label] = data.request.level;
1099
+ zigbeeDevice.log.debug(`Command moveToLevel called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1100
+ const payload = { brightness: data.request.level };
1079
1101
  if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1080
1102
  payload['transition'] = Math.round(data.request.transitionTime / 10);
1081
1103
  zigbeeDevice.publishCommand('moveToLevel', device.friendly_name, payload);
1082
1104
  });
1083
1105
  zigbeeDevice.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async (data) => {
1084
- zigbeeDevice.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1085
- const payload = {};
1086
- const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1087
- if (label === undefined)
1088
- payload['brightness'] = data.request.level;
1089
- else
1090
- payload['brightness_' + label] = data.request.level;
1106
+ zigbeeDevice.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeNumber} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1107
+ const payload = { brightness: data.request.level };
1091
1108
  if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1092
1109
  payload['transition'] = Math.round(data.request.transitionTime / 10);
1093
1110
  zigbeeDevice.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload);
@@ -1097,7 +1114,15 @@ export class ZigbeeDevice extends ZigbeeEntity {
1097
1114
  zigbeeDevice.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request }) => {
1098
1115
  zigbeeDevice.log.debug(`Command moveToColorTemperature called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds}`);
1099
1116
  await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, zigbeeDevice.log);
1100
- const payload = { color_temp: request.colorTemperatureMireds };
1117
+ const payload = {};
1118
+ if (zigbeeDevice.propertyMap.get('color_temp')) {
1119
+ payload['color_temp'] = request.colorTemperatureMireds;
1120
+ }
1121
+ else {
1122
+ const rgb = kelvinToRGB(miredToKelvin(request.colorTemperatureMireds));
1123
+ payload['color'] = { r: rgb.r, g: rgb.g, b: rgb.b };
1124
+ zigbeeDevice.log.info(`Command moveToColorTemperature called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${nf} but color_temp property is not available. Converting ${request.colorTemperatureMireds} to RGB ${debugStringify(payload['color'])}.`);
1125
+ }
1101
1126
  if (zigbeeDevice.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1102
1127
  payload['transition'] = Math.round(request.transitionTime / 10);
1103
1128
  zigbeeDevice.publishCommand('moveToColorTemperature', device.friendly_name, payload);
@@ -1156,7 +1181,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1156
1181
  zigbeeDevice.bridgedDevice.addCommandHandler('upOrOpen', async () => {
1157
1182
  zigbeeDevice.log.debug(`Command upOrOpen called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
1158
1183
  if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
1159
- zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 0, zigbeeDevice.log);
1184
+ await zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 0, zigbeeDevice.log);
1160
1185
  else
1161
1186
  await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAndCurrentPosition(0);
1162
1187
  zigbeeDevice.publishCommand('upOrOpen', device.friendly_name, { state: 'OPEN' });
@@ -1164,7 +1189,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1164
1189
  zigbeeDevice.bridgedDevice.addCommandHandler('downOrClose', async () => {
1165
1190
  zigbeeDevice.log.debug(`Command downOrClose called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
1166
1191
  if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
1167
- zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 10000, zigbeeDevice.log);
1192
+ await zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 10000, zigbeeDevice.log);
1168
1193
  else
1169
1194
  await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAndCurrentPosition(10000);
1170
1195
  zigbeeDevice.publishCommand('downOrClose', device.friendly_name, { state: 'CLOSE' });
@@ -1177,7 +1202,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1177
1202
  zigbeeDevice.bridgedDevice.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue } }) => {
1178
1203
  zigbeeDevice.log.debug(`Command goToLiftPercentage called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request liftPercent100thsValue: ${liftPercent100thsValue}`);
1179
1204
  if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
1180
- zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', liftPercent100thsValue, zigbeeDevice.log);
1205
+ await zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', liftPercent100thsValue, zigbeeDevice.log);
1181
1206
  else
1182
1207
  await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAndCurrentPosition(liftPercent100thsValue);
1183
1208
  zigbeeDevice.publishCommand('goToLiftPercentage', device.friendly_name, { position: liftPercent100thsValue / 100 });
@@ -1202,7 +1227,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1202
1227
  const t = zigbeeDevice.bridgedDevice?.getAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', zigbeeDevice.log);
1203
1228
  const setpoint = Math.round(t / 100 + request.amount / 10);
1204
1229
  if (zigbeeDevice.propertyMap.has('current_heating_setpoint')) {
1205
- zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1230
+ zigbeeDevice.publishCommand('CurrentHeatingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1206
1231
  }
1207
1232
  else if (zigbeeDevice.propertyMap.has('occupied_heating_setpoint')) {
1208
1233
  zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: setpoint });
@@ -1212,7 +1237,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1212
1237
  const t = zigbeeDevice.bridgedDevice?.getAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', zigbeeDevice.log);
1213
1238
  const setpoint = Math.round(t / 100 + request.amount / 10);
1214
1239
  if (zigbeeDevice.propertyMap.has('current_heating_setpoint')) {
1215
- zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1240
+ zigbeeDevice.publishCommand('CurrentCoolingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1216
1241
  }
1217
1242
  else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint')) {
1218
1243
  zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: setpoint });
package/dist/platform.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MatterbridgeDynamicPlatform } from 'matterbridge';
1
+ import { addVirtualDevice, MatterbridgeDynamicPlatform } from 'matterbridge';
2
2
  import { dn, gn, db, wr, zb, payloadStringify, rs, debugStringify, CYAN, er, nf } from 'matterbridge/logger';
3
3
  import { isValidNumber, isValidString, waiter } from 'matterbridge/utils';
4
4
  import { BridgedDeviceBasicInformation, DoorLock } from 'matterbridge/matter/clusters';
@@ -11,6 +11,7 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
11
11
  bridgedDevices = [];
12
12
  zigbeeEntities = [];
13
13
  injectTimer;
14
+ namePostfix = 1;
14
15
  mqttHost = 'localhost';
15
16
  mqttPort = 1883;
16
17
  mqttTopic = 'zigbee2mqtt';
@@ -23,7 +24,6 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
23
24
  featureBlackList = [];
24
25
  deviceFeatureBlackList = {};
25
26
  postfix = '';
26
- postfixHostname = true;
27
27
  debugEnabled;
28
28
  shouldStart;
29
29
  shouldConfigure;
@@ -34,12 +34,13 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
34
34
  z2mBridgeInfo;
35
35
  z2mBridgeDevices;
36
36
  z2mBridgeGroups;
37
- z2mDeviceAvailability = new Map();
37
+ z2mEntityAvailability = new Map();
38
+ z2mEntityPayload = new Map();
38
39
  availabilityTimer;
39
40
  constructor(matterbridge, log, config) {
40
41
  super(matterbridge, log, config);
41
- if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('2.2.5')) {
42
- throw new Error(`This plugin requires Matterbridge version >= "2.2.5". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend."`);
42
+ if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.0.3')) {
43
+ throw new Error(`This plugin requires Matterbridge version >= "3.0.3". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend."`);
43
44
  }
44
45
  this.debugEnabled = config.debug;
45
46
  this.shouldStart = false;
@@ -69,7 +70,6 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
69
70
  if (config.postfix)
70
71
  this.postfix = config.postfix;
71
72
  this.postfix = this.postfix.trim().slice(0, 3);
72
- this.postfixHostname = config.postfixHostname ?? true;
73
73
  config.host = this.mqttHost;
74
74
  config.port = this.mqttPort;
75
75
  config.protocolVersion = this.mqttProtocol;
@@ -77,7 +77,16 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
77
77
  config.username = this.mqttUsername;
78
78
  config.password = this.mqttPassword;
79
79
  config.postfix = this.postfix;
80
- config.postfixHostname = this.postfixHostname;
80
+ if (config.postfixHostname !== undefined)
81
+ delete config.postfixHostname;
82
+ if (config.deviceScenes !== undefined)
83
+ delete config.deviceScenes;
84
+ if (config.groupScenes !== undefined)
85
+ delete config.groupScenes;
86
+ if (config.scenesType === undefined)
87
+ config.scenesType = 'outlet';
88
+ if (config.scenesPrefix === undefined)
89
+ config.scenesPrefix = true;
81
90
  if (config.type === 'MatterbridgeExtension') {
82
91
  this.z2m = new Zigbee2MQTT(this.mqttHost, this.mqttPort, this.mqttTopic, this.mqttUsername, this.mqttPassword, this.mqttProtocol, this.debugEnabled);
83
92
  this.z2m.setLogDebug(this.debugEnabled);
@@ -86,7 +95,6 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
86
95
  }
87
96
  this.log.info(`Initializing platform: ${CYAN}${this.config.name}${nf} version: ${CYAN}${this.config.version}${rs}`);
88
97
  this.log.info(`Loaded zigbee2mqtt parameters from ${CYAN}${path.join(matterbridge.matterbridgeDirectory, 'matterbridge-zigbee2mqtt.config.json')}${rs}`);
89
- this.clearSelect();
90
98
  this.z2m = new Zigbee2MQTT(this.mqttHost, this.mqttPort, this.mqttTopic, this.mqttUsername, this.mqttPassword, this.mqttProtocol, this.debugEnabled);
91
99
  this.z2m.setLogDebug(this.debugEnabled);
92
100
  this.z2m.setDataPath(path.join(matterbridge.matterbridgePluginDirectory, 'matterbridge-zigbee2mqtt'));
@@ -187,11 +195,14 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
187
195
  }
188
196
  });
189
197
  this.z2m.on('availability', (device, available) => {
190
- this.z2mDeviceAvailability.set(device, available);
198
+ this.z2mEntityAvailability.set(device, available);
191
199
  if (available)
192
- this.log.info(`zigbee2MQTT device ${device} is ${available ? 'online' : 'offline'}`);
200
+ this.log.info(`zigbee2MQTT entity ${device} is ${available ? 'online' : 'offline'}`);
193
201
  else
194
- this.log.warn(`zigbee2MQTT device ${device} is ${available ? 'online' : 'offline'}`);
202
+ this.log.warn(`zigbee2MQTT entity ${device} is ${available ? 'online' : 'offline'}`);
203
+ });
204
+ this.z2m.on('message', (device, payload) => {
205
+ this.z2mEntityPayload.set(device, payload);
195
206
  });
196
207
  this.z2m.on('permit_join', async (device, time, status) => {
197
208
  this.log.info(`zigbee2MQTT sent permit_join device: ${device} time: ${time} status: ${status}`);
@@ -292,6 +303,9 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
292
303
  }
293
304
  async onStart(reason) {
294
305
  this.log.info(`Starting zigbee2mqtt dynamic platform v${this.version}: ` + reason);
306
+ await this.ready;
307
+ await this.clearSelect();
308
+ this.setSelectEntity('scenes', 'Scenes', 'component');
295
309
  const hasOnline = await waiter('z2mBridgeOnline', () => this.z2mBridgeOnline !== undefined);
296
310
  const hasInfo = await waiter('z2mBridgeInfo', () => this.z2mBridgeInfo !== undefined);
297
311
  const hasDevices = await waiter('z2mBridgeDevices & z2mBridgeGroups', () => this.z2mBridgeDevices !== undefined || this.z2mBridgeGroups !== undefined);
@@ -324,33 +338,36 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
324
338
  await super.onConfigure();
325
339
  this.log.info(`Requesting update for ${this.zigbeeEntities.length} zigbee entities.`);
326
340
  for (const bridgedEntity of this.zigbeeEntities) {
327
- if (bridgedEntity.isDevice && bridgedEntity.device)
328
- await this.requestDeviceUpdate(bridgedEntity.device);
329
- if (bridgedEntity.isGroup && bridgedEntity.group)
330
- await this.requestGroupUpdate(bridgedEntity.group);
331
341
  await bridgedEntity.configure();
332
342
  if (bridgedEntity.isRouter && bridgedEntity.bridgedDevice) {
333
343
  this.log.info(`Configuring router ${bridgedEntity.bridgedDevice?.deviceName}.`);
334
344
  if (this.z2mBridgeInfo?.permit_join) {
335
345
  bridgedEntity.bridgedDevice?.setAttribute(DoorLock.Cluster.id, 'lockState', DoorLock.LockState.Unlocked, this.log);
336
- if (bridgedEntity.bridgedDevice.number)
346
+ if (bridgedEntity.bridgedDevice.maybeNumber)
337
347
  bridgedEntity.bridgedDevice?.triggerEvent(DoorLock.Cluster.id, 'lockOperation', { lockOperationType: DoorLock.LockOperationType.Unlock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null }, this.log);
338
348
  }
339
349
  else {
340
350
  bridgedEntity.bridgedDevice?.setAttribute(DoorLock.Cluster.id, 'lockState', DoorLock.LockState.Locked, this.log);
341
- if (bridgedEntity.bridgedDevice.number)
351
+ if (bridgedEntity.bridgedDevice.maybeNumber)
342
352
  bridgedEntity.bridgedDevice?.triggerEvent(DoorLock.Cluster.id, 'lockOperation', { lockOperationType: DoorLock.LockOperationType.Lock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null }, this.log);
343
353
  }
344
354
  }
355
+ if (bridgedEntity.isDevice && bridgedEntity.device)
356
+ await this.requestDeviceUpdate(bridgedEntity.device);
357
+ if (bridgedEntity.isGroup && bridgedEntity.group)
358
+ await this.requestGroupUpdate(bridgedEntity.group);
345
359
  }
346
360
  this.availabilityTimer = setTimeout(() => {
347
- for (const [device, available] of this.z2mDeviceAvailability) {
361
+ for (const [entity, available] of this.z2mEntityAvailability) {
348
362
  if (available)
349
- this.z2m.emit('ONLINE-' + device);
363
+ this.z2m.emit('ONLINE-' + entity);
350
364
  else
351
- this.z2m.emit('OFFLINE-' + device);
365
+ this.z2m.emit('OFFLINE-' + entity);
366
+ }
367
+ for (const [entity, payload] of this.z2mEntityPayload) {
368
+ this.z2m.emit('MESSAGE-' + entity, payload);
352
369
  }
353
- }, 60 * 1000);
370
+ }, 10 * 1000).unref();
354
371
  if (this.config.injectPayloads) {
355
372
  this.injectTimer = setInterval(() => {
356
373
  const data = this.z2m.readConfig(path.join(this.matterbridge.matterbridgeDirectory, this.config.injectPayloads));
@@ -481,6 +498,22 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
481
498
  }
482
499
  return matterGroup;
483
500
  }
501
+ registerVirtualDevice(name, callback) {
502
+ let aggregator;
503
+ if (this.matterbridge.bridgeMode === 'bridge') {
504
+ aggregator = this.matterbridge.aggregatorNode;
505
+ }
506
+ else if (this.matterbridge.bridgeMode === 'childbridge') {
507
+ aggregator = this.matterbridge.plugins.get(this.name)?.aggregatorNode;
508
+ }
509
+ if (aggregator) {
510
+ if (aggregator.parts.has(name.replaceAll(' ', '') + ':' + this.config.scenesType)) {
511
+ this.log.warn(`Scene name ${name} already registered. Please use a different name. Changed to ${name + ' ' + this.namePostfix}`);
512
+ name = name + ' ' + this.namePostfix++;
513
+ }
514
+ addVirtualDevice(aggregator, name.slice(0, 32), this.config.scenesType, callback);
515
+ }
516
+ }
484
517
  async unregisterZigbeeEntity(friendly_name) {
485
518
  const entity = this.zigbeeEntities.find((entity) => entity.entityName === friendly_name);
486
519
  if (entity) {
@@ -70,6 +70,8 @@ export class Zigbee2MQTT extends EventEmitter {
70
70
  await mkdir(dataPath, { recursive: true });
71
71
  this.mqttDataPath = dataPath;
72
72
  this.log.debug(`Data directory ${this.mqttDataPath} created successfully.`);
73
+ const filePath = path.join(this.mqttDataPath, 'bridge-payloads.txt');
74
+ fs.unlinkSync(filePath);
73
75
  }
74
76
  catch (e) {
75
77
  const error = e;
@@ -80,6 +82,13 @@ export class Zigbee2MQTT extends EventEmitter {
80
82
  this.log.error('Error creating data directory:', error);
81
83
  }
82
84
  }
85
+ try {
86
+ const filePath = path.join(this.mqttDataPath, 'bridge-payloads.txt');
87
+ fs.unlinkSync(filePath);
88
+ }
89
+ catch (error) {
90
+ this.log.debug(`Error deleting bridge-payloads.txt: ${error}`);
91
+ }
83
92
  }
84
93
  getUrl() {
85
94
  return 'mqtt://' + this.mqttHost + ':' + this.mqttPort.toString();
@@ -613,6 +622,7 @@ export class Zigbee2MQTT extends EventEmitter {
613
622
  else if (service === 'set') {
614
623
  }
615
624
  else if (service === undefined) {
625
+ this.emit('message', entity, data);
616
626
  this.emit('MESSAGE-' + entity, data);
617
627
  }
618
628
  else {
@@ -119,28 +119,39 @@
119
119
  "selectDeviceEntityFrom": "name"
120
120
  }
121
121
  },
122
- "deviceScenes": {
123
- "description": "Enable the devices scenes",
124
- "type": "boolean",
125
- "default": false,
126
- "ui:widget": "hidden"
127
- },
128
- "groupScenes": {
129
- "description": "Enable the groups scenes",
122
+ "scenesType": {
123
+ "description": "Device type to use to expose scenes",
124
+ "type": "string",
125
+ "oneOf": [
126
+ {
127
+ "title": "Light",
128
+ "enum": ["light"]
129
+ },
130
+ {
131
+ "title": "Outlet",
132
+ "enum": ["outlet"]
133
+ },
134
+ {
135
+ "title": "Switch",
136
+ "enum": ["switch"]
137
+ },
138
+ {
139
+ "title": "Mounted Switch",
140
+ "enum": ["mounted_switch"]
141
+ }
142
+ ],
143
+ "default": "outlet"
144
+ },
145
+ "scenesPrefix": {
146
+ "description": "Add the device/group friendly name before the scene name.",
130
147
  "type": "boolean",
131
- "default": false,
132
- "ui:widget": "hidden"
148
+ "default": true
133
149
  },
134
150
  "postfix": {
135
151
  "description": "Add this unique postfix (3 characters max) to each device serial to avoid collision with other instances (you may loose the configuration of the devices in your controller when changing this value).",
136
152
  "type": "string",
137
153
  "default": ""
138
154
  },
139
- "postfixHostname": {
140
- "description": "DEPRECATED: unique postfix added to each device identifier to avoid collision with other instances (you may loose the configuration of the devices in your controller when changing this value). Use postfix instead.",
141
- "type": "boolean",
142
- "default": true
143
- },
144
155
  "debug": {
145
156
  "description": "Enable the debug for the plugin (development only)",
146
157
  "type": "boolean",
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "matterbridge-zigbee2mqtt",
3
- "version": "2.4.7",
3
+ "version": "2.5.0-dev.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-zigbee2mqtt",
9
- "version": "2.4.7",
9
+ "version": "2.5.0-dev.3",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "moment": "2.30.1",
13
- "mqtt": "5.10.4",
13
+ "mqtt": "5.13.0",
14
14
  "node-ansi-logger": "3.0.1",
15
15
  "node-persist-manager": "1.0.8"
16
16
  },
@@ -23,40 +23,27 @@
23
23
  }
24
24
  },
25
25
  "node_modules/@babel/runtime": {
26
- "version": "7.26.10",
27
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
28
- "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
26
+ "version": "7.27.1",
27
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
28
+ "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
29
29
  "license": "MIT",
30
- "dependencies": {
31
- "regenerator-runtime": "^0.14.0"
32
- },
33
30
  "engines": {
34
31
  "node": ">=6.9.0"
35
32
  }
36
33
  },
37
34
  "node_modules/@types/node": {
38
- "version": "22.13.10",
39
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
40
- "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
35
+ "version": "22.15.21",
36
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
37
+ "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
41
38
  "license": "MIT",
42
39
  "dependencies": {
43
- "undici-types": "~6.20.0"
40
+ "undici-types": "~6.21.0"
44
41
  }
45
42
  },
46
43
  "node_modules/@types/readable-stream": {
47
- "version": "4.0.18",
48
- "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.18.tgz",
49
- "integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==",
50
- "license": "MIT",
51
- "dependencies": {
52
- "@types/node": "*",
53
- "safe-buffer": "~5.1.1"
54
- }
55
- },
56
- "node_modules/@types/ws": {
57
- "version": "8.18.0",
58
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
59
- "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
44
+ "version": "4.0.19",
45
+ "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.19.tgz",
46
+ "integrity": "sha512-6Tgd3lMocKwOul/kwAAgSebkhdMCLhRvcJ6CKHA6wdql2qNIwK6hw3Y4PZQxn9HcJogoC/1ZOmkFM7OZKH/VrA==",
60
47
  "license": "MIT",
61
48
  "dependencies": {
62
49
  "@types/node": "*"
@@ -172,9 +159,9 @@
172
159
  }
173
160
  },
174
161
  "node_modules/debug": {
175
- "version": "4.4.0",
176
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
177
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
162
+ "version": "4.4.1",
163
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
164
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
178
165
  "license": "MIT",
179
166
  "dependencies": {
180
167
  "ms": "^2.1.3"
@@ -251,6 +238,19 @@
251
238
  "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
252
239
  "license": "ISC"
253
240
  },
241
+ "node_modules/ip-address": {
242
+ "version": "9.0.5",
243
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
244
+ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
245
+ "license": "MIT",
246
+ "dependencies": {
247
+ "jsbn": "1.1.0",
248
+ "sprintf-js": "^1.1.3"
249
+ },
250
+ "engines": {
251
+ "node": ">= 12"
252
+ }
253
+ },
254
254
  "node_modules/js-sdsl": {
255
255
  "version": "4.3.0",
256
256
  "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
@@ -261,6 +261,12 @@
261
261
  "url": "https://opencollective.com/js-sdsl"
262
262
  }
263
263
  },
264
+ "node_modules/jsbn": {
265
+ "version": "1.1.0",
266
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
267
+ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
268
+ "license": "MIT"
269
+ },
264
270
  "node_modules/minimist": {
265
271
  "version": "1.2.8",
266
272
  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -280,24 +286,22 @@
280
286
  }
281
287
  },
282
288
  "node_modules/mqtt": {
283
- "version": "5.10.4",
284
- "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.10.4.tgz",
285
- "integrity": "sha512-wN+SuhT2/ZaG6NPxca0N6YtRivnMxk6VflxQUEeqDH4erKdj+wPAGhHmcTLzvqfE4sJRxrEJ+XJxUc0No0E7eQ==",
289
+ "version": "5.13.0",
290
+ "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.13.0.tgz",
291
+ "integrity": "sha512-pR+z+ChxFl3n8AKLQbTONVOOg/jl4KiKQRBAi78tjd6PksOWvl1nl9L8ZHOZ3MiavZfrUOjok2ddwc1VymGWRg==",
286
292
  "license": "MIT",
287
293
  "dependencies": {
288
- "@types/readable-stream": "^4.0.18",
289
- "@types/ws": "^8.5.14",
290
294
  "commist": "^3.2.0",
291
295
  "concat-stream": "^2.0.0",
292
296
  "debug": "^4.4.0",
293
297
  "help-me": "^5.0.0",
294
298
  "lru-cache": "^10.4.3",
295
299
  "minimist": "^1.2.8",
296
- "mqtt-packet": "^9.0.1",
300
+ "mqtt-packet": "^9.0.2",
297
301
  "number-allocator": "^1.0.14",
298
302
  "readable-stream": "^4.7.0",
299
- "reinterval": "^1.1.0",
300
303
  "rfdc": "^1.4.1",
304
+ "socks": "^2.8.3",
301
305
  "split2": "^4.2.0",
302
306
  "worker-timers": "^7.1.8",
303
307
  "ws": "^8.18.0"
@@ -431,18 +435,6 @@
431
435
  "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
432
436
  }
433
437
  },
434
- "node_modules/regenerator-runtime": {
435
- "version": "0.14.1",
436
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
437
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
438
- "license": "MIT"
439
- },
440
- "node_modules/reinterval": {
441
- "version": "1.1.0",
442
- "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
443
- "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
444
- "license": "MIT"
445
- },
446
438
  "node_modules/rfdc": {
447
439
  "version": "1.4.1",
448
440
  "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@@ -450,11 +442,49 @@
450
442
  "license": "MIT"
451
443
  },
452
444
  "node_modules/safe-buffer": {
453
- "version": "5.1.2",
454
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
455
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
445
+ "version": "5.2.1",
446
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
447
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
448
+ "funding": [
449
+ {
450
+ "type": "github",
451
+ "url": "https://github.com/sponsors/feross"
452
+ },
453
+ {
454
+ "type": "patreon",
455
+ "url": "https://www.patreon.com/feross"
456
+ },
457
+ {
458
+ "type": "consulting",
459
+ "url": "https://feross.org/support"
460
+ }
461
+ ],
456
462
  "license": "MIT"
457
463
  },
464
+ "node_modules/smart-buffer": {
465
+ "version": "4.2.0",
466
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
467
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
468
+ "license": "MIT",
469
+ "engines": {
470
+ "node": ">= 6.0.0",
471
+ "npm": ">= 3.0.0"
472
+ }
473
+ },
474
+ "node_modules/socks": {
475
+ "version": "2.8.4",
476
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
477
+ "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
478
+ "license": "MIT",
479
+ "dependencies": {
480
+ "ip-address": "^9.0.5",
481
+ "smart-buffer": "^4.2.0"
482
+ },
483
+ "engines": {
484
+ "node": ">= 10.0.0",
485
+ "npm": ">= 3.0.0"
486
+ }
487
+ },
458
488
  "node_modules/split2": {
459
489
  "version": "4.2.0",
460
490
  "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -464,6 +494,12 @@
464
494
  "node": ">= 10.x"
465
495
  }
466
496
  },
497
+ "node_modules/sprintf-js": {
498
+ "version": "1.1.3",
499
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
500
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
501
+ "license": "BSD-3-Clause"
502
+ },
467
503
  "node_modules/string_decoder": {
468
504
  "version": "1.3.0",
469
505
  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -473,26 +509,6 @@
473
509
  "safe-buffer": "~5.2.0"
474
510
  }
475
511
  },
476
- "node_modules/string_decoder/node_modules/safe-buffer": {
477
- "version": "5.2.1",
478
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
479
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
480
- "funding": [
481
- {
482
- "type": "github",
483
- "url": "https://github.com/sponsors/feross"
484
- },
485
- {
486
- "type": "patreon",
487
- "url": "https://www.patreon.com/feross"
488
- },
489
- {
490
- "type": "consulting",
491
- "url": "https://feross.org/support"
492
- }
493
- ],
494
- "license": "MIT"
495
- },
496
512
  "node_modules/tslib": {
497
513
  "version": "2.8.1",
498
514
  "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -506,9 +522,9 @@
506
522
  "license": "MIT"
507
523
  },
508
524
  "node_modules/undici-types": {
509
- "version": "6.20.0",
510
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
511
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
525
+ "version": "6.21.0",
526
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
527
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
512
528
  "license": "MIT"
513
529
  },
514
530
  "node_modules/util-deprecate": {
@@ -552,9 +568,9 @@
552
568
  }
553
569
  },
554
570
  "node_modules/ws": {
555
- "version": "8.18.1",
556
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
557
- "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
571
+ "version": "8.18.2",
572
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
573
+ "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
558
574
  "license": "MIT",
559
575
  "engines": {
560
576
  "node": ">=10.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-zigbee2mqtt",
3
- "version": "2.4.7",
3
+ "version": "2.5.0-dev.3",
4
4
  "description": "Matterbridge zigbee2mqtt plugin",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "moment": "2.30.1",
45
- "mqtt": "5.10.4",
45
+ "mqtt": "5.13.0",
46
46
  "node-ansi-logger": "3.0.1",
47
47
  "node-persist-manager": "1.0.8"
48
48
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ // enable isolatedModules just for ts-jest
5
+ "isolatedModules": true
6
+ },
7
+ "include": ["**/*.spec.ts", "**/*.test.ts", "**/__test__/*"]
8
+ }