matterbridge-zigbee2mqtt 2.8.1 → 2.9.0-dev-20251101-be5ef3b
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 +28 -0
- package/dist/entity.js +28 -17
- package/dist/{platform.js → module.js} +8 -4
- package/matterbridge-zigbee2mqtt.config.json +28 -0
- package/npm-shrinkwrap.json +16 -16
- package/package.json +3 -3
- package/dist/index.js +0 -4
- package/dist/jestHelpers.js +0 -231
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,34 @@ 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
|
+
## [2.9.0] - 2025-11-02
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- [tvoc]: Added voc_index to the converter. Thanks Funca (https://github.com/Luligu/matterbridge-zigbee2mqtt/issues/129).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- [package]: Updated dependencies.
|
|
20
|
+
- [package]: Bumped platform to v.1.0.0.
|
|
21
|
+
- [package]: Bumped package to automator v.2.0.10.
|
|
22
|
+
- [jest]: Bumped jestHelpers to v.1.0.11.
|
|
23
|
+
- [package]: Require matterbridge v.3.3.0.
|
|
24
|
+
- [package]: Added default config.
|
|
25
|
+
- [package]: Added typed ZigbeePlatformConfig.
|
|
26
|
+
- [platform]: Updated to new signature PlatformMatterbridge.
|
|
27
|
+
- [workflows]: Improved speed on Node CI.
|
|
28
|
+
- [devcontainer]: Added the plugin name to the container.
|
|
29
|
+
- [devcontainer]: Improved performance of first build with shallow clone.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- [platform]: Fixed specific zbminir2 device case for all devices. Thanks subst4nc3 (https://github.com/Luligu/matterbridge-zigbee2mqtt/issues/126).
|
|
34
|
+
|
|
35
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
36
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
37
|
+
</a>
|
|
38
|
+
|
|
11
39
|
## [2.8.1] - 2025-10-02
|
|
12
40
|
|
|
13
41
|
### Automations and scenes
|
package/dist/entity.js
CHANGED
|
@@ -326,8 +326,9 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
326
326
|
return;
|
|
327
327
|
}
|
|
328
328
|
this.log.debug(`Command on called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber}`);
|
|
329
|
-
|
|
330
|
-
this.
|
|
329
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
330
|
+
this.setCachePublishAttributes(data.endpoint, isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : undefined);
|
|
331
|
+
this.cachePublish('on', { ['state' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: 'ON' });
|
|
331
332
|
}
|
|
332
333
|
async offCommandHandler(data) {
|
|
333
334
|
if (data.endpoint.getAttribute(OnOff.Cluster.id, 'onOff') === false) {
|
|
@@ -335,16 +336,18 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
335
336
|
return;
|
|
336
337
|
}
|
|
337
338
|
this.log.debug(`Command off called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber}`);
|
|
338
|
-
|
|
339
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
340
|
+
this.cachePublish('off', { ['state' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: 'OFF' });
|
|
339
341
|
}
|
|
340
342
|
async toggleCommandHandler(data) {
|
|
341
343
|
this.log.debug(`Command toggle called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber}`);
|
|
344
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
342
345
|
if (data.endpoint.getAttribute(OnOff.Cluster.id, 'onOff') === false) {
|
|
343
|
-
this.setCachePublishAttributes(data.endpoint,
|
|
344
|
-
this.cachePublish('toggle', { ['state' + (
|
|
346
|
+
this.setCachePublishAttributes(data.endpoint, isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : undefined);
|
|
347
|
+
this.cachePublish('toggle', { ['state' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: 'ON' });
|
|
345
348
|
}
|
|
346
349
|
else {
|
|
347
|
-
this.cachePublish('toggle', { ['state' + (
|
|
350
|
+
this.cachePublish('toggle', { ['state' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: 'OFF' });
|
|
348
351
|
}
|
|
349
352
|
}
|
|
350
353
|
async moveToLevelCommandHandler(data) {
|
|
@@ -353,25 +356,27 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
353
356
|
return;
|
|
354
357
|
}
|
|
355
358
|
this.log.debug(`Command moveToLevel called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: ${data.request.level} transition: ${data.request.transitionTime}`);
|
|
356
|
-
|
|
359
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
360
|
+
this.cachePublish('moveToLevel', { ['brightness' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: data.request.level }, data.request.transitionTime);
|
|
357
361
|
}
|
|
358
362
|
async moveToLevelWithOnOffCommandHandler(data) {
|
|
359
363
|
this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: ${data.request.level} transition: ${data.request.transitionTime}`);
|
|
364
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
360
365
|
if (data.request['level'] <= (data.endpoint.getAttribute(LevelControl.Cluster.id, 'minLevel') ?? 1)) {
|
|
361
366
|
if (data.endpoint.getAttribute(OnOff.Cluster.id, 'onOff') === false) {
|
|
362
367
|
this.log.debug(`*Command moveToLevelWithOnOff ignored for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} light OFF`);
|
|
363
368
|
return;
|
|
364
369
|
}
|
|
365
370
|
data.endpoint.log.debug(`***Command moveToLevelWithOnOff received with level <= minLevel(${data.endpoint.getAttribute(LevelControl.Cluster.id, 'minLevel')}) => turn off the light`);
|
|
366
|
-
this.cachePublish('moveToLevelWithOnOff', { ['state' + (
|
|
371
|
+
this.cachePublish('moveToLevelWithOnOff', { ['state' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: 'OFF' }, data.request.transitionTime);
|
|
367
372
|
}
|
|
368
373
|
else {
|
|
369
374
|
if (data.endpoint.getAttribute(OnOff.Cluster.id, 'onOff') === false) {
|
|
370
375
|
data.endpoint.log.debug(`***Command moveToLevelWithOnOff received with level > minLevel(${data.endpoint.getAttribute(LevelControl.Cluster.id, 'minLevel')}) and light is off => turn on the light with attributes`);
|
|
371
|
-
this.cachePayload['state' + (
|
|
372
|
-
this.setCachePublishAttributes(data.endpoint,
|
|
376
|
+
this.cachePayload['state' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')] = 'ON';
|
|
377
|
+
this.setCachePublishAttributes(data.endpoint, isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '');
|
|
373
378
|
}
|
|
374
|
-
this.cachePublish('moveToLevelWithOnOff', { ['brightness' + (
|
|
379
|
+
this.cachePublish('moveToLevelWithOnOff', { ['brightness' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: data.request.level }, data.request.transitionTime);
|
|
375
380
|
}
|
|
376
381
|
}
|
|
377
382
|
async moveToColorTemperatureCommandHandler(data) {
|
|
@@ -381,12 +386,13 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
381
386
|
return;
|
|
382
387
|
}
|
|
383
388
|
this.log.debug(`Command moveToColorTemperature called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: ${data.request.colorTemperatureMireds} transition: ${data.request.transitionTime}`);
|
|
389
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
384
390
|
if (this.propertyMap.get('color_temp')) {
|
|
385
|
-
this.cachePublish('moveToColorTemperature', { ['color_temp' + (
|
|
391
|
+
this.cachePublish('moveToColorTemperature', { ['color_temp' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: data.request.colorTemperatureMireds }, data.request.transitionTime);
|
|
386
392
|
}
|
|
387
393
|
else {
|
|
388
394
|
const rgb = kelvinToRGB(miredToKelvin(data.request.colorTemperatureMireds));
|
|
389
|
-
this.cachePublish('moveToColorTemperature', { ['color' + (
|
|
395
|
+
this.cachePublish('moveToColorTemperature', { ['color' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: { r: rgb.r, g: rgb.g, b: rgb.b } }, data.request.transitionTime);
|
|
390
396
|
this.log.debug(`***Command moveToColorTemperature called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} but color_temp property is not available. Converting ${data.request.colorTemperatureMireds} to RGB ${debugStringify(rgb)}.`);
|
|
391
397
|
}
|
|
392
398
|
}
|
|
@@ -397,7 +403,8 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
397
403
|
return;
|
|
398
404
|
}
|
|
399
405
|
this.log.debug(`Command moveToColor called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: X: ${data.request.colorX} Y: ${data.request.colorY} transition: ${data.request.transitionTime}`);
|
|
400
|
-
|
|
406
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
407
|
+
this.cachePublish('moveToColor', { ['color' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: { x: Math.round(data.request.colorX / 65536 * 10000) / 10000, y: Math.round(data.request.colorY / 65536 * 10000) / 10000 } }, data.request.transitionTime);
|
|
401
408
|
}
|
|
402
409
|
async moveToHueCommandHandler(data) {
|
|
403
410
|
delete this.cachePayload['color_temp'];
|
|
@@ -406,7 +413,8 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
406
413
|
return;
|
|
407
414
|
}
|
|
408
415
|
this.log.debug(`Command moveToHue called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: ${data.request.hue} transition: ${data.request.transitionTime}`);
|
|
409
|
-
|
|
416
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
417
|
+
this.cachePublish('moveToHue', { ['color' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: { h: Math.round(data.request.hue / 254 * 360), s: Math.round(data.endpoint.getAttribute(ColorControlCluster.id, 'currentSaturation') / 254 * 100) } }, data.request.transitionTime);
|
|
410
418
|
}
|
|
411
419
|
async moveToSaturationCommandHandler(data) {
|
|
412
420
|
delete this.cachePayload['color_temp'];
|
|
@@ -415,7 +423,8 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
415
423
|
return;
|
|
416
424
|
}
|
|
417
425
|
this.log.debug(`Command moveToSaturation called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: ${data.request.saturation} transition: ${data.request.transitionTime}`);
|
|
418
|
-
|
|
426
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
427
|
+
this.cachePublish('moveToSaturation', { ['color' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: { h: Math.round(data.endpoint.getAttribute(ColorControlCluster.id, 'currentHue') / 254 * 360), s: Math.round(data.request.saturation / 254 * 100) } }, data.request.transitionTime);
|
|
419
428
|
}
|
|
420
429
|
async moveToHueAndSaturationCommandHandler(data) {
|
|
421
430
|
delete this.cachePayload['color_temp'];
|
|
@@ -424,7 +433,8 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
424
433
|
return;
|
|
425
434
|
}
|
|
426
435
|
this.log.debug(`Command moveToHueAndSaturation called for ${this.ien}${this.isGroup ? this.group?.friendly_name : this.device?.friendly_name}${rs}${db} endpoint: ${data.endpoint?.maybeId}:${data.endpoint?.maybeNumber} request: ${data.request.hue} - ${data.request.saturation} transition: ${data.request.transitionTime}`);
|
|
427
|
-
|
|
436
|
+
const isChildEndpoint = data.endpoint.deviceName !== this.entityName;
|
|
437
|
+
this.cachePublish('moveToHueAndSaturation', { ['color' + (isChildEndpoint ? '_' + data.endpoint.uniqueStorageKey : '')]: { h: Math.round(data.request.hue / 254 * 360), s: Math.round(data.request.saturation / 254 * 100) } }, data.request.transitionTime);
|
|
428
438
|
}
|
|
429
439
|
addBridgedDeviceBasicInformation() {
|
|
430
440
|
if (!this.bridgedDevice)
|
|
@@ -839,6 +849,7 @@ const z2ms = [
|
|
|
839
849
|
{ type: '', name: 'pressure', property: 'pressure', deviceType: pressureSensor, cluster: PressureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return value; } },
|
|
840
850
|
{ type: '', name: 'air_quality', property: 'air_quality', deviceType: airQualitySensor, cluster: AirQuality.Cluster.id, attribute: 'airQuality', valueLookup: ['unknown', 'excellent', 'good', 'moderate', 'poor', 'unhealthy', 'out_of_range'] },
|
|
841
851
|
{ type: '', name: 'voc', property: 'voc', deviceType: airQualitySensor, cluster: TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.min(65535, value); } },
|
|
852
|
+
{ type: '', name: 'voc_index', property: 'voc_index', deviceType: airQualitySensor, cluster: TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.min(65535, value); } },
|
|
842
853
|
{ type: '', name: 'co', property: 'co', deviceType: airQualitySensor, cluster: CarbonMonoxideConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
|
|
843
854
|
{ type: '', name: 'co2', property: 'co2', deviceType: airQualitySensor, cluster: CarbonDioxideConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
|
|
844
855
|
{ type: '', name: 'formaldehyd', property: 'formaldehyd', deviceType: airQualitySensor, cluster: FormaldehydeConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
|
|
@@ -5,6 +5,9 @@ import { isValidNumber, isValidString, waiter } from 'matterbridge/utils';
|
|
|
5
5
|
import { BridgedDeviceBasicInformation, DoorLock } from 'matterbridge/matter/clusters';
|
|
6
6
|
import { ZigbeeDevice, ZigbeeGroup } from './entity.js';
|
|
7
7
|
import { Zigbee2MQTT } from './zigbee2mqtt.js';
|
|
8
|
+
export default function initializePlugin(matterbridge, log, config) {
|
|
9
|
+
return new ZigbeePlatform(matterbridge, log, config);
|
|
10
|
+
}
|
|
8
11
|
export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
|
|
9
12
|
bridgedDevices = [];
|
|
10
13
|
zigbeeEntities = [];
|
|
@@ -39,8 +42,8 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
|
|
|
39
42
|
availabilityTimer;
|
|
40
43
|
constructor(matterbridge, log, config) {
|
|
41
44
|
super(matterbridge, log, config);
|
|
42
|
-
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.0
|
|
43
|
-
throw new Error(`This plugin requires Matterbridge version >= "3.0
|
|
45
|
+
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.3.0')) {
|
|
46
|
+
throw new Error(`This plugin requires Matterbridge version >= "3.3.0". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend."`);
|
|
44
47
|
}
|
|
45
48
|
this.debugEnabled = config.debug;
|
|
46
49
|
this.shouldStart = false;
|
|
@@ -81,8 +84,8 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
|
|
|
81
84
|
config.port = this.mqttPort;
|
|
82
85
|
config.protocolVersion = this.mqttProtocol;
|
|
83
86
|
config.topic = this.mqttTopic;
|
|
84
|
-
config.username = this.mqttUsername;
|
|
85
|
-
config.password = this.mqttPassword;
|
|
87
|
+
config.username = this.mqttUsername ?? '';
|
|
88
|
+
config.password = this.mqttPassword ?? '';
|
|
86
89
|
config.postfix = this.postfix;
|
|
87
90
|
if (config.postfixHostname !== undefined)
|
|
88
91
|
delete config.postfixHostname;
|
|
@@ -96,6 +99,7 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
|
|
|
96
99
|
config.scenesPrefix = true;
|
|
97
100
|
this.log.info(`Initializing platform: ${CYAN}${this.config.name}${nf} version: ${CYAN}${this.config.version}${rs}`);
|
|
98
101
|
this.log.info(`Loaded zigbee2mqtt parameters from ${CYAN}${path.join(matterbridge.matterbridgeDirectory, 'matterbridge-zigbee2mqtt.config.json')}${rs}`);
|
|
102
|
+
this.log.debug(`Config:\n${rs}${JSON.stringify(config, null, 2)}${rs}`);
|
|
99
103
|
this.z2m = new Zigbee2MQTT(this.mqttHost, this.mqttPort, this.mqttTopic, this.mqttUsername, this.mqttPassword, this.mqttProtocol, this.config.ca, this.config.rejectUnauthorized, this.config.cert, this.config.key, this.debugEnabled);
|
|
100
104
|
this.z2m.setLogDebug(this.debugEnabled);
|
|
101
105
|
this.z2m.setDataPath(path.join(matterbridge.matterbridgePluginDirectory, 'matterbridge-zigbee2mqtt'));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "matterbridge-zigbee2mqtt",
|
|
3
|
+
"type": "DynamicPlatform",
|
|
4
|
+
"version": "2.9.0",
|
|
5
|
+
"host": "mqtt://localhost",
|
|
6
|
+
"port": 1883,
|
|
7
|
+
"protocolVersion": 5,
|
|
8
|
+
"username": "",
|
|
9
|
+
"password": "",
|
|
10
|
+
"ca": "",
|
|
11
|
+
"rejectUnauthorized": true,
|
|
12
|
+
"cert": "",
|
|
13
|
+
"key": "",
|
|
14
|
+
"topic": "zigbee2mqtt",
|
|
15
|
+
"zigbeeFrontend": "http://localhost:8080",
|
|
16
|
+
"whiteList": [],
|
|
17
|
+
"blackList": [],
|
|
18
|
+
"switchList": [],
|
|
19
|
+
"lightList": [],
|
|
20
|
+
"outletList": [],
|
|
21
|
+
"featureBlackList": [],
|
|
22
|
+
"deviceFeatureBlackList": {},
|
|
23
|
+
"scenesType": "outlet",
|
|
24
|
+
"scenesPrefix": true,
|
|
25
|
+
"postfix": "",
|
|
26
|
+
"debug": false,
|
|
27
|
+
"unregisterOnShutdown": false
|
|
28
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-zigbee2mqtt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0-dev-20251101-be5ef3b",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge-zigbee2mqtt",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.9.0-dev-20251101-be5ef3b",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"moment": "2.30.1",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"node-persist-manager": "2.0.0"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=
|
|
18
|
+
"node": ">=20.0.0 <21.0.0 || >=22.0.0 <23.0.0 || >=24.0.0 <25.0.0"
|
|
19
19
|
},
|
|
20
20
|
"funding": {
|
|
21
21
|
"type": "buymeacoffee",
|
|
@@ -32,18 +32,18 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"node_modules/@types/node": {
|
|
35
|
-
"version": "24.
|
|
36
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.
|
|
37
|
-
"integrity": "sha512-
|
|
35
|
+
"version": "24.9.2",
|
|
36
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
|
|
37
|
+
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"undici-types": "~7.
|
|
40
|
+
"undici-types": "~7.16.0"
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"node_modules/@types/readable-stream": {
|
|
44
|
-
"version": "4.0.
|
|
45
|
-
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.
|
|
46
|
-
"integrity": "sha512
|
|
44
|
+
"version": "4.0.22",
|
|
45
|
+
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.22.tgz",
|
|
46
|
+
"integrity": "sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg==",
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@types/node": "*"
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
"license": "MIT"
|
|
92
92
|
},
|
|
93
93
|
"node_modules/bl": {
|
|
94
|
-
"version": "6.1.
|
|
95
|
-
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.
|
|
96
|
-
"integrity": "sha512-
|
|
94
|
+
"version": "6.1.4",
|
|
95
|
+
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.4.tgz",
|
|
96
|
+
"integrity": "sha512-ZV/9asSuknOExbM/zPPA8z00lc1ihPKWaStHkkQrxHNeYx+yY+TmF+v80dpv2G0mv3HVXBu7ryoAsxbFFhf4eg==",
|
|
97
97
|
"license": "MIT",
|
|
98
98
|
"dependencies": {
|
|
99
99
|
"@types/readable-stream": "^4.0.0",
|
|
@@ -530,9 +530,9 @@
|
|
|
530
530
|
"license": "MIT"
|
|
531
531
|
},
|
|
532
532
|
"node_modules/undici-types": {
|
|
533
|
-
"version": "7.
|
|
534
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.
|
|
535
|
-
"integrity": "sha512-
|
|
533
|
+
"version": "7.16.0",
|
|
534
|
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
|
535
|
+
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
|
536
536
|
"license": "MIT"
|
|
537
537
|
},
|
|
538
538
|
"node_modules/util-deprecate": {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-zigbee2mqtt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0-dev-20251101-be5ef3b",
|
|
4
4
|
"description": "Matterbridge zigbee2mqtt plugin",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "dist/
|
|
8
|
+
"main": "dist/module.js",
|
|
9
9
|
"homepage": "https://www.npmjs.com/package/matterbridge-zigbee2mqtt",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"zigbee2mqtt"
|
|
39
39
|
],
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
41
|
+
"node": ">=20.0.0 <21.0.0 || >=22.0.0 <23.0.0 || >=24.0.0 <25.0.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"moment": "2.30.1",
|
package/dist/index.js
DELETED
package/dist/jestHelpers.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { rmSync } from 'node:fs';
|
|
2
|
-
import { inspect } from 'node:util';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { jest } from '@jest/globals';
|
|
5
|
-
import { DeviceTypeId, Endpoint, Environment, MdnsService, ServerNode, ServerNodeStore, VendorId, LogFormat as MatterLogFormat, LogLevel as MatterLogLevel, Lifecycle, } from 'matterbridge/matter';
|
|
6
|
-
import { RootEndpoint, AggregatorEndpoint } from 'matterbridge/matter/endpoints';
|
|
7
|
-
import { AnsiLogger } from 'matterbridge/logger';
|
|
8
|
-
export let loggerLogSpy;
|
|
9
|
-
export let consoleLogSpy;
|
|
10
|
-
export let consoleDebugSpy;
|
|
11
|
-
export let consoleInfoSpy;
|
|
12
|
-
export let consoleWarnSpy;
|
|
13
|
-
export let consoleErrorSpy;
|
|
14
|
-
export function setupTest(name, debug = false) {
|
|
15
|
-
expect(name).toBeDefined();
|
|
16
|
-
expect(typeof name).toBe('string');
|
|
17
|
-
expect(name.length).toBeGreaterThanOrEqual(4);
|
|
18
|
-
rmSync(path.join('jest', name), { recursive: true, force: true });
|
|
19
|
-
if (debug) {
|
|
20
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log');
|
|
21
|
-
consoleLogSpy = jest.spyOn(console, 'log');
|
|
22
|
-
consoleDebugSpy = jest.spyOn(console, 'debug');
|
|
23
|
-
consoleInfoSpy = jest.spyOn(console, 'info');
|
|
24
|
-
consoleWarnSpy = jest.spyOn(console, 'warn');
|
|
25
|
-
consoleErrorSpy = jest.spyOn(console, 'error');
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log').mockImplementation(() => { });
|
|
29
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
30
|
-
consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(() => { });
|
|
31
|
-
consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(() => { });
|
|
32
|
-
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
33
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
export function setDebug(debug) {
|
|
37
|
-
if (debug) {
|
|
38
|
-
loggerLogSpy.mockRestore();
|
|
39
|
-
consoleLogSpy.mockRestore();
|
|
40
|
-
consoleDebugSpy.mockRestore();
|
|
41
|
-
consoleInfoSpy.mockRestore();
|
|
42
|
-
consoleWarnSpy.mockRestore();
|
|
43
|
-
consoleErrorSpy.mockRestore();
|
|
44
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log');
|
|
45
|
-
consoleLogSpy = jest.spyOn(console, 'log');
|
|
46
|
-
consoleDebugSpy = jest.spyOn(console, 'debug');
|
|
47
|
-
consoleInfoSpy = jest.spyOn(console, 'info');
|
|
48
|
-
consoleWarnSpy = jest.spyOn(console, 'warn');
|
|
49
|
-
consoleErrorSpy = jest.spyOn(console, 'error');
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log').mockImplementation(() => { });
|
|
53
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
54
|
-
consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(() => { });
|
|
55
|
-
consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(() => { });
|
|
56
|
-
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
57
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
export function createTestEnvironment(homeDir) {
|
|
61
|
-
expect(homeDir).toBeDefined();
|
|
62
|
-
expect(typeof homeDir).toBe('string');
|
|
63
|
-
expect(homeDir.length).toBeGreaterThanOrEqual(4);
|
|
64
|
-
rmSync(homeDir, { recursive: true, force: true });
|
|
65
|
-
const environment = Environment.default;
|
|
66
|
-
environment.vars.set('log.level', MatterLogLevel.DEBUG);
|
|
67
|
-
environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
68
|
-
environment.vars.set('path.root', homeDir);
|
|
69
|
-
environment.vars.set('runtime.signals', false);
|
|
70
|
-
environment.vars.set('runtime.exitcode', false);
|
|
71
|
-
return environment;
|
|
72
|
-
}
|
|
73
|
-
export async function flushAsync(ticks = 3, microTurns = 10, pause = 100) {
|
|
74
|
-
for (let i = 0; i < ticks; i++)
|
|
75
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
76
|
-
for (let i = 0; i < microTurns; i++)
|
|
77
|
-
await Promise.resolve();
|
|
78
|
-
if (pause)
|
|
79
|
-
await new Promise((resolve) => setTimeout(resolve, pause));
|
|
80
|
-
}
|
|
81
|
-
export async function flushAllEndpointNumberPersistence(targetServer, rounds = 2) {
|
|
82
|
-
const nodeStore = targetServer.env.get(ServerNodeStore);
|
|
83
|
-
for (let i = 0; i < rounds; i++) {
|
|
84
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
85
|
-
await nodeStore.endpointStores.close();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function collectAllEndpoints(root) {
|
|
89
|
-
const list = [];
|
|
90
|
-
const walk = (ep) => {
|
|
91
|
-
list.push(ep);
|
|
92
|
-
if (ep.parts) {
|
|
93
|
-
for (const child of ep.parts) {
|
|
94
|
-
walk(child);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
walk(root);
|
|
99
|
-
return list;
|
|
100
|
-
}
|
|
101
|
-
export async function assertAllEndpointNumbersPersisted(targetServer) {
|
|
102
|
-
const nodeStore = targetServer.env.get(ServerNodeStore);
|
|
103
|
-
await nodeStore.endpointStores.close();
|
|
104
|
-
const all = collectAllEndpoints(targetServer);
|
|
105
|
-
for (const ep of all) {
|
|
106
|
-
const store = nodeStore.storeForEndpoint(ep);
|
|
107
|
-
if (ep.maybeNumber === 0) {
|
|
108
|
-
expect(store.number ?? 0).toBe(0);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
expect(store.number).toBeGreaterThan(0);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return all.length;
|
|
115
|
-
}
|
|
116
|
-
export async function startServerNode(name, port) {
|
|
117
|
-
const server = await ServerNode.create({
|
|
118
|
-
id: name + 'ServerNode',
|
|
119
|
-
productDescription: {
|
|
120
|
-
name: name + 'ServerNode',
|
|
121
|
-
deviceType: DeviceTypeId(RootEndpoint.deviceType),
|
|
122
|
-
vendorId: VendorId(0xfff1),
|
|
123
|
-
productId: 0x8000,
|
|
124
|
-
},
|
|
125
|
-
basicInformation: {
|
|
126
|
-
vendorId: VendorId(0xfff1),
|
|
127
|
-
vendorName: 'Matterbridge',
|
|
128
|
-
productId: 0x8000,
|
|
129
|
-
productName: 'Matterbridge ' + name,
|
|
130
|
-
nodeLabel: name + 'ServerNode',
|
|
131
|
-
hardwareVersion: 1,
|
|
132
|
-
softwareVersion: 1,
|
|
133
|
-
reachable: true,
|
|
134
|
-
},
|
|
135
|
-
network: {
|
|
136
|
-
port,
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
expect(server).toBeDefined();
|
|
140
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
141
|
-
const aggregator = new Endpoint(AggregatorEndpoint, {
|
|
142
|
-
id: name + 'AggregatorNode',
|
|
143
|
-
});
|
|
144
|
-
expect(aggregator).toBeDefined();
|
|
145
|
-
await server.add(aggregator);
|
|
146
|
-
expect(server.parts.has(aggregator.id)).toBeTruthy();
|
|
147
|
-
expect(server.parts.has(aggregator)).toBeTruthy();
|
|
148
|
-
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
149
|
-
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
150
|
-
await new Promise((resolve) => {
|
|
151
|
-
server.lifecycle.online.on(async () => {
|
|
152
|
-
resolve();
|
|
153
|
-
});
|
|
154
|
-
server.start();
|
|
155
|
-
});
|
|
156
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
157
|
-
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
158
|
-
expect(server.lifecycle.isCommissioned).toBeFalsy();
|
|
159
|
-
expect(server.lifecycle.isPartsReady).toBeTruthy();
|
|
160
|
-
expect(server.lifecycle.hasId).toBeTruthy();
|
|
161
|
-
expect(server.lifecycle.hasNumber).toBeTruthy();
|
|
162
|
-
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
163
|
-
expect(aggregator.lifecycle.isInstalled).toBeTruthy();
|
|
164
|
-
expect(aggregator.lifecycle.isPartsReady).toBeTruthy();
|
|
165
|
-
expect(aggregator.lifecycle.hasId).toBeTruthy();
|
|
166
|
-
expect(aggregator.lifecycle.hasNumber).toBeTruthy();
|
|
167
|
-
await flushAsync(undefined, undefined, 200);
|
|
168
|
-
return [server, aggregator];
|
|
169
|
-
}
|
|
170
|
-
export async function stopServerNode(server) {
|
|
171
|
-
await flushAllEndpointNumberPersistence(server);
|
|
172
|
-
await assertAllEndpointNumbersPersisted(server);
|
|
173
|
-
expect(server).toBeDefined();
|
|
174
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
175
|
-
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
176
|
-
await server.close();
|
|
177
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
178
|
-
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
179
|
-
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
180
|
-
await flushAsync(undefined, undefined, 200);
|
|
181
|
-
}
|
|
182
|
-
export async function addDevice(owner, device, pause = 10) {
|
|
183
|
-
expect(owner).toBeDefined();
|
|
184
|
-
expect(device).toBeDefined();
|
|
185
|
-
expect(owner.lifecycle.isReady).toBeTruthy();
|
|
186
|
-
expect(owner.construction.status).toBe(Lifecycle.Status.Active);
|
|
187
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
188
|
-
try {
|
|
189
|
-
await owner.add(device);
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
const errorMessage = error instanceof Error ? error.message : error;
|
|
193
|
-
const errorInspect = inspect(error, { depth: 10 });
|
|
194
|
-
console.error(`Error adding device ${device.maybeId}.${device.maybeNumber}: ${errorMessage}\nstack: ${errorInspect}`);
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
expect(owner.parts.has(device)).toBeTruthy();
|
|
198
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
199
|
-
expect(device.lifecycle.isReady).toBeTruthy();
|
|
200
|
-
expect(device.lifecycle.isInstalled).toBeTruthy();
|
|
201
|
-
expect(device.lifecycle.hasId).toBeTruthy();
|
|
202
|
-
expect(device.lifecycle.hasNumber).toBeTruthy();
|
|
203
|
-
expect(device.construction.status).toBe(Lifecycle.Status.Active);
|
|
204
|
-
await flushAsync(1, 1, pause);
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
export async function deleteDevice(owner, device, pause = 10) {
|
|
208
|
-
expect(owner).toBeDefined();
|
|
209
|
-
expect(device).toBeDefined();
|
|
210
|
-
expect(owner.lifecycle.isReady).toBeTruthy();
|
|
211
|
-
expect(owner.construction.status).toBe(Lifecycle.Status.Active);
|
|
212
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
213
|
-
try {
|
|
214
|
-
await device.delete();
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
const errorMessage = error instanceof Error ? error.message : error;
|
|
218
|
-
const errorInspect = inspect(error, { depth: 10 });
|
|
219
|
-
console.error(`Error deleting device ${device.maybeId}.${device.maybeNumber}: ${errorMessage}\nstack: ${errorInspect}`);
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
expect(owner.parts.has(device)).toBeFalsy();
|
|
223
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
224
|
-
expect(device.lifecycle.isReady).toBeFalsy();
|
|
225
|
-
expect(device.lifecycle.isInstalled).toBeFalsy();
|
|
226
|
-
expect(device.lifecycle.hasId).toBeTruthy();
|
|
227
|
-
expect(device.lifecycle.hasNumber).toBeTruthy();
|
|
228
|
-
expect(device.construction.status).toBe(Lifecycle.Status.Destroyed);
|
|
229
|
-
await flushAsync(1, 1, pause);
|
|
230
|
-
return true;
|
|
231
|
-
}
|