matterbridge-zigbee2mqtt 2.5.1 → 2.6.0-dev-20250608-0e2d35b

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
@@ -10,6 +10,8 @@ If you like this project and find it useful, please consider giving it a star on
10
10
 
11
11
  ### Breaking Changes
12
12
 
13
+ The host config option must now start with mqtt:// or mqtts:// to distinguish between unsecure and SSL/TLS connection with the MQTT broker.
14
+
13
15
  New device types:
14
16
 
15
17
  - extendedColorLight
@@ -19,10 +21,14 @@ New device types:
19
21
 
20
22
  If your controller has issues detecting the new device type, blacklist these devices, restart, wait 5 minutes that the controller removes them, remove the blacklist and restart again. This will create a new endpoint on the controller and the controllers will likely remove and recreate all the devices so make a backup of configurations (i.e. room assignements) and automations on the controller.
21
23
 
22
- ## [2.5.1] - 2025-06-04
24
+ ## [2.6.0] - 2025-06-07
23
25
 
24
26
  ### Added
25
27
 
28
+ - [npm]: The dev of matterbridge-zigbee2mqtt is published with tag **dev** on **npm** each day at 00:00 UTC if there is a new commit.
29
+ - [mqtt]: Added MQTT SSL/TLS server authentication. Prefix host with mqtts:// and provide the ca certificate for self-signed server certificates.
30
+ - [mqtt]: Added MQTT SSL/TLS client authentication. Prefix host with mqtts:// and provide the client certificate and key. Provide also the ca certificate for self-signed client certificates.
31
+
26
32
  ### Changed
27
33
 
28
34
  - [package]: Updated package.
@@ -30,6 +36,8 @@ If your controller has issues detecting the new device type, blacklist these dev
30
36
 
31
37
  ### Fixed
32
38
 
39
+ - [subscribe]: Removed async from handlers.
40
+
33
41
  <a href="https://www.buymeacoffee.com/luligugithub">
34
42
  <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
35
43
  </a>
package/README.md CHANGED
@@ -143,12 +143,16 @@ These are the default vules:
143
143
  {
144
144
  "name": "matterbridge-zigbee2mqtt",
145
145
  "type": "DynamicPlatform",
146
- "unregisterOnShutdown": false,
147
- "host": "localhost",
146
+ "host": "mqtt://localhost",
148
147
  "port": 1883,
148
+ "protocolVersion": 5,
149
149
  "topic": "zigbee2mqtt",
150
150
  "username": "",
151
151
  "password": "",
152
+ "ca": undefined,
153
+ "cert": undefined,
154
+ "key": undefined,
155
+ "rejectUnauthorized": true,
152
156
  "whiteList": [],
153
157
  "blackList": [],
154
158
  "switchList": [],
package/dist/entity.js CHANGED
@@ -1274,7 +1274,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1274
1274
  }
1275
1275
  }
1276
1276
  });
1277
- zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
1277
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', (value) => {
1278
1278
  if (isValidNumber(value, Thermostat.SystemMode.Off, Thermostat.SystemMode.FanOnly) && zigbeeDevice.thermostatSystemModeLookup[value] !== '') {
1279
1279
  const system_mode = zigbeeDevice.thermostatSystemModeLookup[value];
1280
1280
  zigbeeDevice.log.debug(`Subscribe systemMode called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with ${value} => ${system_mode}`);
@@ -1286,7 +1286,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1286
1286
  }
1287
1287
  }, zigbeeDevice.log);
1288
1288
  if (zigbeeDevice.bridgedDevice.hasAttributeServer(ThermostatCluster.id, 'occupiedHeatingSetpoint'))
1289
- zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', async (value) => {
1289
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', (value) => {
1290
1290
  zigbeeDevice.log.debug(`Subscribe occupiedHeatingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1291
1291
  if (zigbeeDevice.propertyMap.has('current_heating_setpoint'))
1292
1292
  zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
@@ -1298,7 +1298,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1298
1298
  }, 5 * 1000);
1299
1299
  }, zigbeeDevice.log);
1300
1300
  if (zigbeeDevice.bridgedDevice.hasAttributeServer(ThermostatCluster.id, 'occupiedCoolingSetpoint'))
1301
- zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
1301
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', (value) => {
1302
1302
  zigbeeDevice.log.debug(`Subscribe occupiedCoolingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1303
1303
  if (zigbeeDevice.propertyMap.has('current_heating_setpoint'))
1304
1304
  zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
package/dist/platform.js CHANGED
@@ -12,7 +12,7 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
12
12
  zigbeeEntities = [];
13
13
  injectTimer;
14
14
  namePostfix = 1;
15
- mqttHost = 'localhost';
15
+ mqttHost = 'mqtt://localhost';
16
16
  mqttPort = 1883;
17
17
  mqttTopic = 'zigbee2mqtt';
18
18
  mqttUsername = undefined;
@@ -45,8 +45,10 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
45
45
  this.debugEnabled = config.debug;
46
46
  this.shouldStart = false;
47
47
  this.shouldConfigure = false;
48
- if (config.host)
48
+ if (config.host && typeof config.host === 'string') {
49
49
  this.mqttHost = config.host;
50
+ this.mqttHost = !this.mqttHost.startsWith('mqtt://') && !this.mqttHost.startsWith('mqtts://') ? 'mqtt://' + this.mqttHost : this.mqttHost;
51
+ }
50
52
  if (config.port)
51
53
  this.mqttPort = config.port;
52
54
  if (config.topic)
@@ -55,8 +57,12 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
55
57
  this.mqttUsername = config.username;
56
58
  if (config.password)
57
59
  this.mqttPassword = config.password;
58
- if (config.protocolVersion && typeof config.protocolVersion === 'number' && config.protocolVersion >= 3 && config.protocolVersion <= 5)
60
+ if (config.protocolVersion && typeof config.protocolVersion === 'number' && config.protocolVersion >= 3 && config.protocolVersion <= 5) {
59
61
  this.mqttProtocol = config.protocolVersion;
62
+ }
63
+ else {
64
+ this.mqttProtocol = 5;
65
+ }
60
66
  if (config.switchList)
61
67
  this.switchList = config.switchList;
62
68
  if (config.lightList)
@@ -67,8 +73,9 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
67
73
  this.featureBlackList = config.featureBlackList;
68
74
  if (config.deviceFeatureBlackList)
69
75
  this.deviceFeatureBlackList = config.deviceFeatureBlackList;
70
- if (config.postfix)
76
+ if (config.postfix && typeof config.postfix === 'string') {
71
77
  this.postfix = config.postfix;
78
+ }
72
79
  this.postfix = this.postfix.trim().slice(0, 3);
73
80
  config.host = this.mqttHost;
74
81
  config.port = this.mqttPort;
@@ -87,19 +94,13 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform {
87
94
  config.scenesType = 'outlet';
88
95
  if (config.scenesPrefix === undefined)
89
96
  config.scenesPrefix = true;
90
- if (config.type === 'MatterbridgeExtension') {
91
- this.z2m = new Zigbee2MQTT(this.mqttHost, this.mqttPort, this.mqttTopic, this.mqttUsername, this.mqttPassword, this.mqttProtocol, this.debugEnabled);
92
- this.z2m.setLogDebug(this.debugEnabled);
93
- this.log.debug('Created ZigbeePlatform as Matterbridge extension');
94
- return;
95
- }
96
97
  this.log.info(`Initializing platform: ${CYAN}${this.config.name}${nf} version: ${CYAN}${this.config.version}${rs}`);
97
98
  this.log.info(`Loaded zigbee2mqtt parameters from ${CYAN}${path.join(matterbridge.matterbridgeDirectory, 'matterbridge-zigbee2mqtt.config.json')}${rs}`);
98
- this.z2m = new Zigbee2MQTT(this.mqttHost, this.mqttPort, this.mqttTopic, this.mqttUsername, this.mqttPassword, this.mqttProtocol, this.debugEnabled);
99
+ 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);
99
100
  this.z2m.setLogDebug(this.debugEnabled);
100
101
  this.z2m.setDataPath(path.join(matterbridge.matterbridgePluginDirectory, 'matterbridge-zigbee2mqtt'));
101
102
  if (isValidString(this.mqttHost) && isValidNumber(this.mqttPort, 1, 65535)) {
102
- this.log.info(`Connecting to MQTT broker: ${'mqtt://' + this.mqttHost + ':' + this.mqttPort.toString()}`);
103
+ this.log.info(`Connecting to MQTT broker: ${this.mqttHost + ':' + this.mqttPort.toString()}`);
103
104
  this.z2m.start();
104
105
  }
105
106
  else {
@@ -2,10 +2,10 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import * as util from 'node:util';
4
4
  import * as crypto from 'node:crypto';
5
- import { connectAsync } from 'mqtt';
6
5
  import { EventEmitter } from 'node:events';
7
- import { AnsiLogger, rs, db, dn, gn, er, zb, hk, id, idn, ign, REVERSE, REVERSEOFF } from 'node-ansi-logger';
8
6
  import { mkdir } from 'node:fs/promises';
7
+ import { connectAsync } from 'mqtt';
8
+ import { AnsiLogger, rs, db, dn, gn, er, zb, hk, id, idn, ign, REVERSE, REVERSEOFF } from 'node-ansi-logger';
9
9
  const writeFile = util.promisify(fs.writeFile);
10
10
  export class Zigbee2MQTT extends EventEmitter {
11
11
  log;
@@ -32,9 +32,9 @@ export class Zigbee2MQTT extends EventEmitter {
32
32
  z2mGroups;
33
33
  loggedEntries = 0;
34
34
  options = {
35
- clientId: 'classZigbee2MQTT_' + crypto.randomBytes(8).toString('hex'),
35
+ clientId: 'matterbridge_' + crypto.randomBytes(8).toString('hex'),
36
36
  keepalive: 60,
37
- protocolId: 'MQTT',
37
+ protocol: 'mqtt',
38
38
  protocolVersion: 5,
39
39
  reconnectPeriod: 5000,
40
40
  connectTimeout: 60 * 1000,
@@ -42,8 +42,9 @@ export class Zigbee2MQTT extends EventEmitter {
42
42
  password: undefined,
43
43
  clean: true,
44
44
  };
45
- constructor(mqttHost, mqttPort, mqttTopic, mqttUsername = undefined, mqttPassword = undefined, protocolVersion = 5, debug = false) {
45
+ constructor(mqttHost, mqttPort, mqttTopic, mqttUsername, mqttPassword, protocolVersion = 5, ca, rejectUnauthorized, cert, key, debug = false) {
46
46
  super();
47
+ this.log = new AnsiLogger({ logName: 'Zigbee2MQTT', logTimestampFormat: 4, logLevel: debug ? "debug" : "info" });
47
48
  this.mqttHost = mqttHost;
48
49
  this.mqttPort = mqttPort;
49
50
  this.mqttTopic = mqttTopic;
@@ -52,6 +53,54 @@ export class Zigbee2MQTT extends EventEmitter {
52
53
  this.options.username = mqttUsername !== undefined && mqttUsername !== '' ? mqttUsername : undefined;
53
54
  this.options.password = mqttPassword !== undefined && mqttPassword !== '' ? mqttPassword : undefined;
54
55
  this.options.protocolVersion = protocolVersion;
56
+ if (mqttHost.startsWith('mqtts://')) {
57
+ this.log.debug('Using mqtts:// protocol for secure MQTT connection');
58
+ if (!ca) {
59
+ this.log.info('When using mqtts:// protocol, you must provide the ca certificate for SSL/TLS connections with self-signed certificates.');
60
+ }
61
+ else {
62
+ try {
63
+ fs.accessSync(ca, fs.constants.R_OK);
64
+ this.options.ca = fs.readFileSync(ca);
65
+ }
66
+ catch (error) {
67
+ this.log.error(`Error reading the CA certificate from ${ca}:`, error);
68
+ }
69
+ }
70
+ this.options.rejectUnauthorized = rejectUnauthorized !== undefined ? rejectUnauthorized : true;
71
+ this.options.protocol = 'mqtts';
72
+ if (cert && key) {
73
+ try {
74
+ fs.accessSync(cert, fs.constants.R_OK);
75
+ this.options.cert = fs.readFileSync(cert);
76
+ }
77
+ catch (error) {
78
+ this.log.error(`Error reading the client certificate from ${cert}:`, error);
79
+ }
80
+ try {
81
+ fs.accessSync(key, fs.constants.R_OK);
82
+ this.options.key = fs.readFileSync(key);
83
+ }
84
+ catch (error) {
85
+ this.log.error(`Error reading the client key from ${key}:`, error);
86
+ }
87
+ }
88
+ }
89
+ else if (mqttHost.startsWith('mqtt://')) {
90
+ this.log.debug('Using mqtt:// protocol for non-secure MQTT connection');
91
+ if (ca) {
92
+ this.log.warn('You are using mqtt:// protocol, but you provided a CA certificate. It will be ignored.');
93
+ }
94
+ if (cert) {
95
+ this.log.warn('You are using mqtt:// protocol, but you provided a certificate. It will be ignored.');
96
+ }
97
+ if (key) {
98
+ this.log.warn('You are using mqtt:// protocol, but you provided a key. It will be ignored.');
99
+ }
100
+ }
101
+ else {
102
+ this.log.warn('You are using an unsupported MQTT protocol. Please use mqtt:// or mqtts://.');
103
+ }
55
104
  this.z2mIsAvailabilityEnabled = false;
56
105
  this.z2mIsOnline = false;
57
106
  this.z2mPermitJoin = false;
@@ -59,7 +108,6 @@ export class Zigbee2MQTT extends EventEmitter {
59
108
  this.z2mVersion = '';
60
109
  this.z2mDevices = [];
61
110
  this.z2mGroups = [];
62
- this.log = new AnsiLogger({ logName: 'Zigbee2MQTT', logTimestampFormat: 4, logLevel: debug ? "debug" : "info" });
63
111
  this.log.debug(`Created new instance with host: ${mqttHost} port: ${mqttPort} protocol ${protocolVersion} topic: ${mqttTopic} username: ${mqttUsername !== undefined && mqttUsername !== '' ? mqttUsername : 'undefined'} password: ${mqttPassword !== undefined && mqttPassword !== '' ? '*****' : 'undefined'}`);
64
112
  }
65
113
  setLogDebug(logDebug) {
@@ -92,7 +140,7 @@ export class Zigbee2MQTT extends EventEmitter {
92
140
  }
93
141
  }
94
142
  getUrl() {
95
- return 'mqtt://' + this.mqttHost + ':' + this.mqttPort.toString();
143
+ return this.mqttHost + ':' + this.mqttPort.toString();
96
144
  }
97
145
  async start() {
98
146
  this.log.debug(`Starting connection to ${this.getUrl()}...`);
@@ -156,7 +204,7 @@ export class Zigbee2MQTT extends EventEmitter {
156
204
  catch (error) {
157
205
  this.log.error('Error publishing keepalive MQTT message:', error);
158
206
  }
159
- }, (this.options.keepalive ?? 60) * 1000);
207
+ }, (this.options.keepalive ?? 60) * 1000).unref();
160
208
  })
161
209
  .catch((error) => {
162
210
  this.log.error(`Error connecting to ${this.getUrl()}: ${error.message}`);
@@ -17,36 +17,53 @@
17
17
  "ui:widget": "hidden"
18
18
  },
19
19
  "host": {
20
- "description": "MQTT server host (IP address or hostname without mqtt://)",
20
+ "description": "MQTT server host (IP address or hostname with mqtt:// or mqtts:// prefix). For secure connections, use the mqtts:// prefix and ensure your certificates are configured. If you use a hostname, make sure that the hostname is resolvable by the system running matterbridge.",
21
21
  "type": "string",
22
- "default": "localhost"
22
+ "default": "mqtt://localhost"
23
+ },
24
+ "port": {
25
+ "description": "MQTT server port (i.e. 1883 for mqtt:// and 8883 for mqtts://).",
26
+ "type": "number",
27
+ "default": 1883
28
+ },
29
+ "protocolVersion": {
30
+ "description": "MQTT server protocol version 3, 4 or 5 (default 5). Version 5 is recommended for most modern MQTT brokers.",
31
+ "type": "number",
32
+ "default": 5
23
33
  },
24
34
  "username": {
25
- "description": "MQTT server authentication user",
35
+ "description": "MQTT server authentication user. Only required if the MQTT server requires authentication (i.e allow_anonymous false)",
26
36
  "type": "string"
27
37
  },
28
38
  "password": {
29
- "description": "MQTT server authentication password",
39
+ "description": "MQTT server authentication password. Only required if the MQTT server requires authentication (i.e allow_anonymous false)",
30
40
  "type": "string",
31
41
  "ui:widget": "password"
32
42
  },
33
- "port": {
34
- "description": "MQTT server port (default 1883)",
35
- "type": "number",
36
- "default": 1883
43
+ "ca": {
44
+ "description": "Absolute path to the SSL/TLS CA certificate used to sign both the server and client certificates (PEM format). Required for secure connections (i.e. mqtts://) with self-signed certificates.",
45
+ "type": "string"
37
46
  },
38
- "protocolVersion": {
39
- "description": "MQTT server protocol version 3, 4 or 5 (default 5)",
40
- "type": "number",
41
- "default": 5
47
+ "rejectUnauthorized": {
48
+ "description": "Reject unauthorized MQTT server. Only used for secure connections (i.e. mqtts://).",
49
+ "type": "boolean",
50
+ "default": true
51
+ },
52
+ "cert": {
53
+ "description": "Absolute path to the SSL/TLS MQTT client certificate (PEM format). Only required if the MQTT server requires a client certificate for authentication (i.e. require_certificate true).",
54
+ "type": "string"
55
+ },
56
+ "key": {
57
+ "description": "Absolute path to the SSL/TLS MQTT client private key (PEM format). Only required if the MQTT server requires a client certificate for authentication (i.e. require_certificate true).",
58
+ "type": "string"
42
59
  },
43
60
  "topic": {
44
- "description": "MQTT base topic for Zigbee2MQTT MQTT messages",
61
+ "description": "MQTT base topic for Zigbee2MQTT MQTT messages. This should match the base_topic in your Zigbee2MQTT configuration.yaml file.",
45
62
  "type": "string",
46
63
  "default": "zigbee2mqtt"
47
64
  },
48
65
  "zigbeeFrontend": {
49
- "description": "Zigbee frontend host to prefix the configUrl (i.e. http://192.168.1.100:8080)",
66
+ "description": "Zigbee frontend host to prefix the configUrl from matterbridge frontend (i.e. http://192.168.1.100:8080)",
50
67
  "type": "string",
51
68
  "default": "http://localhost:8080"
52
69
  },
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge-zigbee2mqtt",
3
- "version": "2.5.1",
3
+ "version": "2.6.0-dev-20250608-0e2d35b",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-zigbee2mqtt",
9
- "version": "2.5.1",
9
+ "version": "2.6.0-dev-20250608-0e2d35b",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "moment": "2.30.1",
@@ -23,18 +23,18 @@
23
23
  }
24
24
  },
25
25
  "node_modules/@babel/runtime": {
26
- "version": "7.27.4",
27
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz",
28
- "integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
26
+ "version": "7.27.6",
27
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
28
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
29
29
  "license": "MIT",
30
30
  "engines": {
31
31
  "node": ">=6.9.0"
32
32
  }
33
33
  },
34
34
  "node_modules/@types/node": {
35
- "version": "22.15.29",
36
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
37
- "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
35
+ "version": "22.15.30",
36
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz",
37
+ "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
40
  "undici-types": "~6.21.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-zigbee2mqtt",
3
- "version": "2.5.1",
3
+ "version": "2.6.0-dev-20250608-0e2d35b",
4
4
  "description": "Matterbridge zigbee2mqtt plugin",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",