matterbridge 3.1.7-dev-20250724-c3522e6 → 3.1.8-dev-20250725-5cc0474
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 +9 -3
- package/README-SERVICE.md +1 -0
- package/dist/dgram/mdns.js +6 -2
- package/dist/matterbridge.js +45 -16
- package/dist/matterbridgeEndpoint.js +9 -0
- package/dist/matterbridgeEndpointHelpers.js +6 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,11 +8,12 @@ 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
|
-
## [3.1.
|
|
11
|
+
## [3.1.8] - 2025-07-??
|
|
12
12
|
|
|
13
13
|
### Added
|
|
14
14
|
|
|
15
|
-
- [
|
|
15
|
+
- [certification]: Improved certification management in pairing.json.
|
|
16
|
+
- [workflow]: Update permissions and change GitHub token for Docker build trigger
|
|
16
17
|
|
|
17
18
|
### Changed
|
|
18
19
|
|
|
@@ -22,7 +23,7 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
22
23
|
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
23
24
|
</a>
|
|
24
25
|
|
|
25
|
-
## [3.1.7] - 2025-07-
|
|
26
|
+
## [3.1.7] - 2025-07-25
|
|
26
27
|
|
|
27
28
|
### Added
|
|
28
29
|
|
|
@@ -32,6 +33,11 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
32
33
|
- [docker]: Added on demand trigger for Build Docker Image dev from other plugins workflows.
|
|
33
34
|
- [mdns]: Added bin mb_mdns.
|
|
34
35
|
- [coap]: Added bin mb_coap.
|
|
36
|
+
- [operationalState]: Improved documentation on createDefaultOperationalStateClusterServer() and added the optional attribute countdownTime. Thanks Ludovic BOUÉ (https://github.com/Luligu/matterbridge/pull/363).
|
|
37
|
+
- [momentarySwitch]: Added createDefaultMomentarySwitchClusterServer(). It creates a single click only switch. It is supported by the Home app.
|
|
38
|
+
- [fixedLabels]: Improved documentation and added character length check.
|
|
39
|
+
- [userLabels]: Improved documentation and added character length check.
|
|
40
|
+
- [certification]: Improved certification management in pairing.json with new properties.
|
|
35
41
|
|
|
36
42
|
### Changed
|
|
37
43
|
|
package/README-SERVICE.md
CHANGED
package/dist/dgram/mdns.js
CHANGED
|
@@ -128,8 +128,12 @@ export class Mdns extends Multicast {
|
|
|
128
128
|
else {
|
|
129
129
|
const ptr = result.answers?.find((record) => record.name === '_shelly._tcp.local' && record.type === 12) ||
|
|
130
130
|
result.answers?.find((record) => record.name === '_http._tcp.local' && record.type === 12) ||
|
|
131
|
-
result.answers?.find((record) => record.type === 12)
|
|
132
|
-
|
|
131
|
+
result.answers?.find((record) => record.type === 12) ||
|
|
132
|
+
result.answers?.find((record) => record.type === 16) ||
|
|
133
|
+
result.answers
|
|
134
|
+
? result.answers[0]
|
|
135
|
+
: undefined;
|
|
136
|
+
this.deviceResponses.set(rinfo.address, { rinfo, response: result, dataPTR: ptr?.type === 12 ? ptr?.data : ptr?.name });
|
|
133
137
|
this.onResponse(rinfo, result);
|
|
134
138
|
}
|
|
135
139
|
this.logMdnsMessage(result);
|
package/dist/matterbridge.js
CHANGED
|
@@ -136,6 +136,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
136
136
|
aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
|
|
137
137
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
138
138
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
139
|
+
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
140
|
+
aggregatorSerialNumber = getParameter('serialNumber');
|
|
141
|
+
aggregatorUniqueId = getParameter('uniqueId');
|
|
139
142
|
static instance;
|
|
140
143
|
constructor() {
|
|
141
144
|
super();
|
|
@@ -286,29 +289,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
286
289
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
287
290
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
288
291
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
289
|
-
if (isValidNumber(pairingFileJson.vendorId))
|
|
292
|
+
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
290
293
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
291
|
-
|
|
294
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
295
|
+
}
|
|
296
|
+
if (isValidString(pairingFileJson.vendorName, 3)) {
|
|
292
297
|
this.aggregatorVendorName = pairingFileJson.vendorName;
|
|
293
|
-
|
|
298
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorName ${CYAN}${this.aggregatorVendorName}${nf} from pairing file.`);
|
|
299
|
+
}
|
|
300
|
+
if (isValidNumber(pairingFileJson.productId)) {
|
|
294
301
|
this.aggregatorProductId = pairingFileJson.productId;
|
|
295
|
-
|
|
302
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using productId ${CYAN}${this.aggregatorProductId}${nf} from pairing file.`);
|
|
303
|
+
}
|
|
304
|
+
if (isValidString(pairingFileJson.productName, 3)) {
|
|
296
305
|
this.aggregatorProductName = pairingFileJson.productName;
|
|
306
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using productName ${CYAN}${this.aggregatorProductName}${nf} from pairing file.`);
|
|
307
|
+
}
|
|
308
|
+
if (isValidNumber(pairingFileJson.deviceType)) {
|
|
309
|
+
this.aggregatorDeviceType = DeviceTypeId(pairingFileJson.deviceType);
|
|
310
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using deviceType ${CYAN}${this.aggregatorDeviceType}(0x${this.aggregatorDeviceType.toString(16).padStart(4, '0')})${nf} from pairing file.`);
|
|
311
|
+
}
|
|
312
|
+
if (isValidString(pairingFileJson.serialNumber, 3)) {
|
|
313
|
+
this.aggregatorSerialNumber = pairingFileJson.serialNumber;
|
|
314
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using serialNumber ${CYAN}${this.aggregatorSerialNumber}${nf} from pairing file.`);
|
|
315
|
+
}
|
|
316
|
+
if (isValidString(pairingFileJson.uniqueId, 3)) {
|
|
317
|
+
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
318
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
319
|
+
}
|
|
297
320
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
298
321
|
this.passcode = pairingFileJson.passcode;
|
|
299
322
|
this.discriminator = pairingFileJson.discriminator;
|
|
300
323
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
301
324
|
}
|
|
302
325
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
303
|
-
const
|
|
304
|
-
const matches = hexString.match(/.{1,2}/g);
|
|
305
|
-
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
306
|
-
};
|
|
326
|
+
const { hexToBuffer } = await import('./utils/hex.js');
|
|
307
327
|
this.certification = {
|
|
308
|
-
privateKey:
|
|
309
|
-
certificate:
|
|
310
|
-
intermediateCertificate:
|
|
311
|
-
declaration:
|
|
328
|
+
privateKey: hexToBuffer(pairingFileJson.privateKey),
|
|
329
|
+
certificate: hexToBuffer(pairingFileJson.certificate),
|
|
330
|
+
intermediateCertificate: hexToBuffer(pairingFileJson.intermediateCertificate),
|
|
331
|
+
declaration: hexToBuffer(pairingFileJson.declaration),
|
|
312
332
|
};
|
|
313
333
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using privateKey, certificate, intermediateCertificate and declaration from pairing file.`);
|
|
314
334
|
}
|
|
@@ -647,6 +667,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
647
667
|
}
|
|
648
668
|
try {
|
|
649
669
|
await this.startMatterStorage();
|
|
670
|
+
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
671
|
+
const storageManager = await this.matterStorageService.open('Matterbridge');
|
|
672
|
+
const storageContext = storageManager?.createContext('persist');
|
|
673
|
+
if (this.aggregatorSerialNumber)
|
|
674
|
+
await storageContext?.set('serialNumber', this.aggregatorSerialNumber);
|
|
675
|
+
if (this.aggregatorUniqueId)
|
|
676
|
+
await storageContext?.set('uniqueId', this.aggregatorUniqueId);
|
|
677
|
+
this.matterbridgeInformation.matterbridgeSerialNumber = this.aggregatorSerialNumber;
|
|
678
|
+
}
|
|
650
679
|
}
|
|
651
680
|
catch (error) {
|
|
652
681
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
@@ -1200,7 +1229,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1200
1229
|
async createDynamicPlugin(plugin) {
|
|
1201
1230
|
if (!plugin.locked) {
|
|
1202
1231
|
plugin.locked = true;
|
|
1203
|
-
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge',
|
|
1232
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
|
|
1204
1233
|
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1205
1234
|
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1206
1235
|
plugin.serialNumber = await plugin.storageContext.get('serialNumber', '');
|
|
@@ -1381,7 +1410,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1381
1410
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
1382
1411
|
this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
|
|
1383
1412
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1384
|
-
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge',
|
|
1413
|
+
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1385
1414
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1386
1415
|
this.log.info('Matter node storage started');
|
|
1387
1416
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
@@ -1404,7 +1433,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1404
1433
|
this.matterbridgeContext = undefined;
|
|
1405
1434
|
this.log.info('Matter node storage closed');
|
|
1406
1435
|
}
|
|
1407
|
-
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1436
|
+
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1408
1437
|
const { randomBytes } = await import('node:crypto');
|
|
1409
1438
|
if (!this.matterStorageService)
|
|
1410
1439
|
throw new Error('No storage service initialized');
|
|
@@ -1422,7 +1451,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1422
1451
|
await storageContext.set('nodeLabel', productName.slice(0, 32));
|
|
1423
1452
|
await storageContext.set('productLabel', productName.slice(0, 32));
|
|
1424
1453
|
await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
|
|
1425
|
-
await storageContext.set('uniqueId', await storageContext.get('uniqueId', 'UI' + random));
|
|
1454
|
+
await storageContext.set('uniqueId', await storageContext.get('uniqueId', uniqueId ? uniqueId.slice(0, 32) : 'UI' + random));
|
|
1426
1455
|
await storageContext.set('softwareVersion', isValidNumber(parseVersionString(this.matterbridgeVersion), 0, UINT32_MAX) ? parseVersionString(this.matterbridgeVersion) : 1);
|
|
1427
1456
|
await storageContext.set('softwareVersionString', isValidString(this.matterbridgeVersion, 5, 64) ? this.matterbridgeVersion : '1.0.0');
|
|
1428
1457
|
await storageContext.set('hardwareVersion', isValidNumber(parseVersionString(this.systemInformation.osRelease), 0, UINT16_MAX) ? parseVersionString(this.systemInformation.osRelease) : 1);
|
|
@@ -822,6 +822,15 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
822
822
|
});
|
|
823
823
|
return this;
|
|
824
824
|
}
|
|
825
|
+
createOnOffFanControlClusterServer(fanMode = FanControl.FanMode.Off) {
|
|
826
|
+
this.behaviors.require(FanControlServer, {
|
|
827
|
+
fanMode,
|
|
828
|
+
fanModeSequence: FanControl.FanModeSequence.OffHigh,
|
|
829
|
+
percentSetting: 0,
|
|
830
|
+
percentCurrent: 0,
|
|
831
|
+
});
|
|
832
|
+
return this;
|
|
833
|
+
}
|
|
825
834
|
createBaseFanControlClusterServer(fanMode = FanControl.FanMode.Off, fanModeSequence = FanControl.FanModeSequence.OffLowMedHigh, percentSetting = 0, percentCurrent = 0) {
|
|
826
835
|
this.behaviors.require(FanControlServer, {
|
|
827
836
|
fanMode,
|
|
@@ -381,15 +381,15 @@ export async function addFixedLabel(endpoint, label, value) {
|
|
|
381
381
|
if (!endpoint.hasClusterServer(FixedLabel.Cluster.id)) {
|
|
382
382
|
endpoint.log.debug(`addFixedLabel: add cluster ${hk}FixedLabel${db}:${hk}fixedLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
|
|
383
383
|
endpoint.behaviors.require(FixedLabelServer, {
|
|
384
|
-
labelList: [{ label, value }],
|
|
384
|
+
labelList: [{ label: label.substring(0, 16), value: value.substring(0, 16) }],
|
|
385
385
|
});
|
|
386
386
|
return;
|
|
387
387
|
}
|
|
388
388
|
endpoint.log.debug(`addFixedLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
|
|
389
389
|
let labelList = endpoint.getAttribute(FixedLabel.Cluster.id, 'labelList', endpoint.log);
|
|
390
390
|
if (isValidArray(labelList)) {
|
|
391
|
-
labelList = labelList.filter((entry) => entry.label !== label);
|
|
392
|
-
labelList.push({ label, value });
|
|
391
|
+
labelList = labelList.filter((entry) => entry.label !== label.substring(0, 16));
|
|
392
|
+
labelList.push({ label: label.substring(0, 16), value: value.substring(0, 16) });
|
|
393
393
|
await endpoint.setAttribute(FixedLabel.Cluster.id, 'labelList', labelList, endpoint.log);
|
|
394
394
|
}
|
|
395
395
|
}
|
|
@@ -397,15 +397,15 @@ export async function addUserLabel(endpoint, label, value) {
|
|
|
397
397
|
if (!endpoint.hasClusterServer(UserLabel.Cluster.id)) {
|
|
398
398
|
endpoint.log.debug(`addUserLabel: add cluster ${hk}UserLabel${db}:${hk}userLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
|
|
399
399
|
endpoint.behaviors.require(UserLabelServer, {
|
|
400
|
-
labelList: [{ label, value }],
|
|
400
|
+
labelList: [{ label: label.substring(0, 16), value: value.substring(0, 16) }],
|
|
401
401
|
});
|
|
402
402
|
return;
|
|
403
403
|
}
|
|
404
404
|
endpoint.log.debug(`addUserLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
|
|
405
405
|
let labelList = endpoint.getAttribute(UserLabel.Cluster.id, 'labelList', endpoint.log);
|
|
406
406
|
if (isValidArray(labelList)) {
|
|
407
|
-
labelList = labelList.filter((entry) => entry.label !== label);
|
|
408
|
-
labelList.push({ label, value });
|
|
407
|
+
labelList = labelList.filter((entry) => entry.label !== label.substring(0, 16));
|
|
408
|
+
labelList.push({ label: label.substring(0, 16), value: value.substring(0, 16) });
|
|
409
409
|
await endpoint.setAttribute(UserLabel.Cluster.id, 'labelList', labelList, endpoint.log);
|
|
410
410
|
}
|
|
411
411
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.8-dev-20250725-5cc0474",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.1.
|
|
9
|
+
"version": "3.1.8-dev-20250725-5cc0474",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.15.1",
|
package/package.json
CHANGED