matterbridge 3.0.1-dev-20250507-a182295 → 3.0.2-dev-20250508-7214e17
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 +25 -0
- package/README.md +1 -1
- package/dist/index.js +1 -0
- package/dist/matterbridge.js +34 -0
- package/dist/matterbridgeBehaviors.js +108 -0
- package/dist/matterbridgeEndpoint.js +2 -13
- package/dist/matterbridgeEndpointHelpers.js +20 -6
- package/dist/roboticVacuumCleaner.js +87 -0
- package/dist/update.js +1 -1
- package/dist/utils/deepCopy.js +8 -3
- package/dist/utils/deepEqual.js +6 -4
- package/dist/utils/export.js +2 -1
- package/dist/utils/hex.js +27 -0
- package/dist/utils/isvalid.js +3 -0
- package/dist/utils/network.js +2 -2
- package/dist/utils/wait.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/utils/{parameter.js → commandLine.js} +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,23 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
13
13
|
If you want to run Matterbridge in Home Assistant please use the official add-on https://github.com/Luligu/matterbridge-home-assistant-addon that also has Ingress and side panel.
|
|
14
14
|
It is also available the official Matterbridge Home Assistant plugin https://github.com/Luligu/matterbridge-hass.
|
|
15
15
|
|
|
16
|
+
## [3.0.2] - 2025-05-??
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- [package]: Updated dependencies.
|
|
23
|
+
- [utils]: Refactor utils functions.
|
|
24
|
+
- [utils]: Updated Jest tests on utils functions.
|
|
25
|
+
- [devices]: Added RoboticVacuumCleaner class to create the Robotic Vacuum Cleaner device type.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
30
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
31
|
+
</a>
|
|
32
|
+
|
|
16
33
|
## [3.0.1] - 2025-05-06
|
|
17
34
|
|
|
18
35
|
### Added
|
|
@@ -39,6 +56,10 @@ It is also available the official Matterbridge Home Assistant plugin https://git
|
|
|
39
56
|
- [BasicInformation]: Fixed vulnerability in BasicInformation and BridgedDeviceBasicInformation cluster initialization attributes.
|
|
40
57
|
- [frontend]: Fixed refresh and postfix for select in HomeDevices.
|
|
41
58
|
|
|
59
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
60
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
61
|
+
</a>
|
|
62
|
+
|
|
42
63
|
## [3.0.0] - 2025-04-29
|
|
43
64
|
|
|
44
65
|
## Breaking changes
|
|
@@ -105,6 +126,10 @@ Modified clusters:
|
|
|
105
126
|
- [matterbridge]: Fixed wrong message when advertising stops and the node has been paired.
|
|
106
127
|
- [frontend]: Fixed download logs that broke with express v5.1.0.
|
|
107
128
|
|
|
129
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
130
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
131
|
+
</a>
|
|
132
|
+
|
|
108
133
|
## [2.2.9] - 2025-04-18
|
|
109
134
|
|
|
110
135
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://hub.docker.com/r/luligu/matterbridge)
|
|
6
6
|
[](https://hub.docker.com/r/luligu/matterbridge)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/matter-history)
|
|
11
11
|
[](https://www.npmjs.com/package/node-ansi-logger)
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export * from './matterbridgeDeviceTypes.js';
|
|
|
13
13
|
export * from './matterbridgePlatform.js';
|
|
14
14
|
export * from './matterbridgeAccessoryPlatform.js';
|
|
15
15
|
export * from './matterbridgeDynamicPlatform.js';
|
|
16
|
+
export * from './roboticVacuumCleaner.js';
|
|
16
17
|
const log = new AnsiLogger({ logName: 'Main', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
17
18
|
async function main() {
|
|
18
19
|
log.debug('***Matterbridge.loadInstance() called');
|
package/dist/matterbridge.js
CHANGED
|
@@ -132,6 +132,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
132
132
|
port;
|
|
133
133
|
passcode;
|
|
134
134
|
discriminator;
|
|
135
|
+
certification;
|
|
135
136
|
serverNode;
|
|
136
137
|
aggregatorNode;
|
|
137
138
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -267,6 +268,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
267
268
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
268
269
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
269
270
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
271
|
+
const pairingFilePath = path.join(this.homeDirectory, '.mattercert', 'pairing.json');
|
|
272
|
+
try {
|
|
273
|
+
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
274
|
+
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
275
|
+
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
276
|
+
if (pairingFileJson.passcode && pairingFileJson.discriminator) {
|
|
277
|
+
this.passcode = pairingFileJson.passcode;
|
|
278
|
+
this.discriminator = pairingFileJson.discriminator;
|
|
279
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf}`);
|
|
280
|
+
}
|
|
281
|
+
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
282
|
+
const hexStringToUint8Array = (hexString) => {
|
|
283
|
+
const matches = hexString.match(/.{1,2}/g);
|
|
284
|
+
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
285
|
+
};
|
|
286
|
+
this.certification = {
|
|
287
|
+
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
288
|
+
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
289
|
+
intermediateCertificate: hexStringToUint8Array(pairingFileJson.intermediateCertificate),
|
|
290
|
+
declaration: hexStringToUint8Array(pairingFileJson.declaration),
|
|
291
|
+
};
|
|
292
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using privateKey, certificate, intermediateCertificate and declaration from pairing file.`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
297
|
+
}
|
|
298
|
+
await this.nodeContext.set('matterport', this.port);
|
|
299
|
+
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
300
|
+
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
270
301
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
271
302
|
if (hasParameter('logger')) {
|
|
272
303
|
const level = getParameter('logger');
|
|
@@ -1438,6 +1469,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1438
1469
|
listeningAddressIpv6: this.ipv6address,
|
|
1439
1470
|
port,
|
|
1440
1471
|
},
|
|
1472
|
+
operationalCredentials: {
|
|
1473
|
+
certification: this.certification,
|
|
1474
|
+
},
|
|
1441
1475
|
commissioning: {
|
|
1442
1476
|
passcode,
|
|
1443
1477
|
discriminator,
|
|
@@ -8,6 +8,9 @@ import { ValveConfigurationAndControl } from '@matter/main/clusters/valve-config
|
|
|
8
8
|
import { SmokeCoAlarm } from '@matter/main/clusters/smoke-co-alarm';
|
|
9
9
|
import { BooleanStateConfigurationServer } from '@matter/main/behaviors/boolean-state-configuration';
|
|
10
10
|
import { OperationalState } from '@matter/main/clusters/operational-state';
|
|
11
|
+
import { ModeBase } from '@matter/main/clusters/mode-base';
|
|
12
|
+
import { RvcRunMode } from '@matter/main/clusters/rvc-run-mode';
|
|
13
|
+
import { RvcOperationalState } from '@matter/main/clusters/rvc-operational-state';
|
|
11
14
|
import { IdentifyServer } from '@matter/main/behaviors/identify';
|
|
12
15
|
import { OnOffServer } from '@matter/main/behaviors/on-off';
|
|
13
16
|
import { LevelControlServer } from '@matter/main/behaviors/level-control';
|
|
@@ -21,6 +24,11 @@ import { ModeSelectServer } from '@matter/main/behaviors/mode-select';
|
|
|
21
24
|
import { SmokeCoAlarmServer } from '@matter/main/behaviors/smoke-co-alarm';
|
|
22
25
|
import { SwitchServer } from '@matter/main/behaviors/switch';
|
|
23
26
|
import { OperationalStateServer } from '@matter/main/behaviors/operational-state';
|
|
27
|
+
import { RvcRunModeServer } from '@matter/main/behaviors/rvc-run-mode';
|
|
28
|
+
import { RvcCleanModeServer } from '@matter/main/behaviors/rvc-clean-mode';
|
|
29
|
+
import { RvcOperationalStateServer } from '@matter/main/behaviors/rvc-operational-state';
|
|
30
|
+
import { ServiceAreaServer } from '@matter/main/behaviors/service-area';
|
|
31
|
+
import { ServiceArea } from '@matter/main/clusters/service-area';
|
|
24
32
|
export class MatterbridgeServerDevice {
|
|
25
33
|
log;
|
|
26
34
|
commandHandler;
|
|
@@ -158,6 +166,14 @@ export class MatterbridgeServerDevice {
|
|
|
158
166
|
this.log.info(`Resume (endpoint ${this.endpointId}.${this.endpointNumber})`);
|
|
159
167
|
this.commandHandler.executeHandler('resume', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
|
|
160
168
|
}
|
|
169
|
+
goHome() {
|
|
170
|
+
this.log.info(`GoHome (endpoint ${this.endpointId}.${this.endpointNumber})`);
|
|
171
|
+
this.commandHandler.executeHandler('goHome', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
|
|
172
|
+
}
|
|
173
|
+
selectAreas({ newAreas }) {
|
|
174
|
+
this.log.info(`Selecting areas ${newAreas} (endpoint ${this.endpointId}.${this.endpointNumber})`);
|
|
175
|
+
this.commandHandler.executeHandler('selectAreas', { request: { newAreas }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
|
|
176
|
+
}
|
|
161
177
|
}
|
|
162
178
|
export class MatterbridgeServer extends Behavior {
|
|
163
179
|
static id = 'matterbridge';
|
|
@@ -430,3 +446,95 @@ export class MatterbridgeOperationalStateServer extends OperationalStateServer {
|
|
|
430
446
|
};
|
|
431
447
|
}
|
|
432
448
|
}
|
|
449
|
+
export class MatterbridgeRvcRunModeServer extends RvcRunModeServer {
|
|
450
|
+
changeToMode({ newMode }) {
|
|
451
|
+
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
452
|
+
const supported = this.state.supportedModes.find((mode) => mode.mode === newMode);
|
|
453
|
+
if (!supported) {
|
|
454
|
+
device.log.error(`MatterbridgeRvcRunModeServer changeToMode called with unsupported newMode: ${newMode}`);
|
|
455
|
+
return { status: ModeBase.ModeChangeStatus.UnsupportedMode, statusText: 'Unsupported mode' };
|
|
456
|
+
}
|
|
457
|
+
device.changeToMode({ newMode });
|
|
458
|
+
this.state.currentMode = newMode;
|
|
459
|
+
if (supported.modeTags.find((tag) => tag.value === RvcRunMode.ModeTag.Cleaning)) {
|
|
460
|
+
device.log.info('MatterbridgeRvcRunModeServer changeToMode called with newMode Cleaning => Running');
|
|
461
|
+
this.agent.get(MatterbridgeRvcOperationalStateServer).state.operationalState = RvcOperationalState.OperationalState.Running;
|
|
462
|
+
return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Running' };
|
|
463
|
+
}
|
|
464
|
+
else if (supported.modeTags.find((tag) => tag.value === RvcRunMode.ModeTag.Idle)) {
|
|
465
|
+
device.log.info('MatterbridgeRvcRunModeServer changeToMode called with newMode Idle => Docked');
|
|
466
|
+
this.agent.get(MatterbridgeRvcOperationalStateServer).state.operationalState = RvcOperationalState.OperationalState.Docked;
|
|
467
|
+
return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Docked' };
|
|
468
|
+
}
|
|
469
|
+
device.log.info(`MatterbridgeRvcRunModeServer changeToMode called with newMode ${newMode} => ${supported.label}`);
|
|
470
|
+
this.agent.get(MatterbridgeRvcOperationalStateServer).state.operationalState = RvcOperationalState.OperationalState.Running;
|
|
471
|
+
return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Success' };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
export class MatterbridgeRvcCleanModeServer extends RvcCleanModeServer {
|
|
475
|
+
changeToMode({ newMode }) {
|
|
476
|
+
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
477
|
+
const supported = this.state.supportedModes.find((mode) => mode.mode === newMode);
|
|
478
|
+
if (!supported) {
|
|
479
|
+
device.log.error(`MatterbridgeRvcCleanModeServer changeToMode called with unsupported newMode: ${newMode}`);
|
|
480
|
+
return { status: ModeBase.ModeChangeStatus.UnsupportedMode, statusText: 'Unsupported mode' };
|
|
481
|
+
}
|
|
482
|
+
device.changeToMode({ newMode });
|
|
483
|
+
this.state.currentMode = newMode;
|
|
484
|
+
device.log.info(`MatterbridgeRvcCleanModeServer changeToMode called with newMode ${newMode} => ${supported.label}`);
|
|
485
|
+
return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Success' };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
export class MatterbridgeRvcOperationalStateServer extends RvcOperationalStateServer {
|
|
489
|
+
pause() {
|
|
490
|
+
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
491
|
+
device.log.info('MatterbridgeRvcOperationalStateServer: pause called setting operational state to Paused and currentMode to Idle');
|
|
492
|
+
device.pause();
|
|
493
|
+
this.agent.get(MatterbridgeRvcRunModeServer).state.currentMode = 1;
|
|
494
|
+
this.state.operationalState = RvcOperationalState.OperationalState.Paused;
|
|
495
|
+
this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
|
|
496
|
+
return {
|
|
497
|
+
commandResponseState: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
resume() {
|
|
501
|
+
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
502
|
+
device.log.info('MatterbridgeRvcOperationalStateServer: resume called setting operational state to Running and currentMode to Cleaning');
|
|
503
|
+
device.resume();
|
|
504
|
+
this.agent.get(MatterbridgeRvcRunModeServer).state.currentMode = 2;
|
|
505
|
+
this.state.operationalState = RvcOperationalState.OperationalState.Running;
|
|
506
|
+
this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
|
|
507
|
+
return {
|
|
508
|
+
commandResponseState: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
goHome() {
|
|
512
|
+
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
513
|
+
device.log.info('MatterbridgeRvcOperationalStateServer: goHome called setting operational state to Docked and currentMode to Idle');
|
|
514
|
+
device.goHome();
|
|
515
|
+
this.agent.get(MatterbridgeRvcRunModeServer).state.currentMode = 1;
|
|
516
|
+
this.state.operationalState = RvcOperationalState.OperationalState.Docked;
|
|
517
|
+
this.state.operationalError = { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' };
|
|
518
|
+
return {
|
|
519
|
+
commandResponseState: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
export class MatterbridgeServiceAreaServer extends ServiceAreaServer {
|
|
524
|
+
selectAreas({ newAreas }) {
|
|
525
|
+
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
526
|
+
for (const area of newAreas) {
|
|
527
|
+
const supportedArea = this.state.supportedAreas.find((supportedArea) => supportedArea.areaId === area);
|
|
528
|
+
if (!supportedArea) {
|
|
529
|
+
device.log.error(`MatterbridgeServiceAreaServer selectAreas called with unsupported area: ${area}`);
|
|
530
|
+
return { status: ServiceArea.SelectAreasStatus.UnsupportedArea, statusText: 'Unsupported areas' };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
device.selectAreas({ newAreas });
|
|
534
|
+
this.state.selectedAreas = newAreas;
|
|
535
|
+
this.state.currentArea = newAreas[0];
|
|
536
|
+
device.log.info(`MatterbridgeServiceAreaServer selectAreas called with: ${newAreas.map((area) => area.toString()).join(', ')}`);
|
|
537
|
+
device.selectAreas({ newAreas });
|
|
538
|
+
return { status: ServiceArea.SelectAreasStatus.Success, statusText: 'Succesfully selected new areas' };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -2,7 +2,7 @@ import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } fr
|
|
|
2
2
|
import { bridgedNode } from './matterbridgeDeviceTypes.js';
|
|
3
3
|
import { isValidNumber, isValidObject, isValidString } from './utils/export.js';
|
|
4
4
|
import { MatterbridgeServer, MatterbridgeServerDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
|
|
5
|
-
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, } from './matterbridgeEndpointHelpers.js';
|
|
5
|
+
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, } from './matterbridgeEndpointHelpers.js';
|
|
6
6
|
import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, UINT16_MAX, UINT32_MAX, VendorId } from '@matter/main';
|
|
7
7
|
import { getClusterNameById, MeasurementType } from '@matter/main/types';
|
|
8
8
|
import { Descriptor } from '@matter/main/clusters/descriptor';
|
|
@@ -986,18 +986,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
986
986
|
return true;
|
|
987
987
|
}
|
|
988
988
|
createDefaultOperationalStateClusterServer(operationalState = OperationalState.OperationalStateEnum.Stopped) {
|
|
989
|
-
this.behaviors.require(MatterbridgeOperationalStateServer,
|
|
990
|
-
phaseList: [],
|
|
991
|
-
currentPhase: null,
|
|
992
|
-
operationalStateList: [
|
|
993
|
-
{ operationalStateId: OperationalState.OperationalStateEnum.Stopped, operationalStateLabel: 'Stopped' },
|
|
994
|
-
{ operationalStateId: OperationalState.OperationalStateEnum.Running, operationalStateLabel: 'Running' },
|
|
995
|
-
{ operationalStateId: OperationalState.OperationalStateEnum.Paused, operationalStateLabel: 'Paused' },
|
|
996
|
-
{ operationalStateId: OperationalState.OperationalStateEnum.Error, operationalStateLabel: 'Error' },
|
|
997
|
-
],
|
|
998
|
-
operationalState,
|
|
999
|
-
operationalError: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
|
|
1000
|
-
});
|
|
989
|
+
this.behaviors.require(MatterbridgeOperationalStateServer, getDefaultOperationalStateClusterServer(operationalState));
|
|
1001
990
|
return this;
|
|
1002
991
|
}
|
|
1003
992
|
createDefaultBooleanStateClusterServer(contact) {
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from './logger/export.js';
|
|
3
|
-
import { deepCopy, deepEqual, isValidArray } from './utils/export.js';
|
|
4
|
-
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
|
|
5
1
|
import { Lifecycle } from '@matter/main';
|
|
6
2
|
import { getClusterNameById } from '@matter/main/types';
|
|
7
3
|
import { PowerSource } from '@matter/main/clusters/power-source';
|
|
@@ -75,6 +71,10 @@ import { Pm25ConcentrationMeasurementServer } from '@matter/main/behaviors/pm25-
|
|
|
75
71
|
import { Pm10ConcentrationMeasurementServer } from '@matter/main/behaviors/pm10-concentration-measurement';
|
|
76
72
|
import { RadonConcentrationMeasurementServer } from '@matter/main/behaviors/radon-concentration-measurement';
|
|
77
73
|
import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/main/behaviors/total-volatile-organic-compounds-concentration-measurement';
|
|
74
|
+
import { createHash } from 'node:crypto';
|
|
75
|
+
import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from 'node-ansi-logger';
|
|
76
|
+
import { deepCopy, deepEqual, isValidArray } from './utils/export.js';
|
|
77
|
+
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeOperationalStateServer, } from './matterbridgeBehaviors.js';
|
|
78
78
|
export function capitalizeFirstLetter(name) {
|
|
79
79
|
if (!name)
|
|
80
80
|
return name;
|
|
@@ -115,7 +115,7 @@ export function getBehaviourTypesFromClusterServerIds(clusterServerList) {
|
|
|
115
115
|
}
|
|
116
116
|
export function getBehaviourTypesFromClusterClientIds(clusterClientList) {
|
|
117
117
|
const behaviorTypes = [];
|
|
118
|
-
clusterClientList.forEach((
|
|
118
|
+
clusterClientList.forEach((_clusterId) => {
|
|
119
119
|
});
|
|
120
120
|
return behaviorTypes;
|
|
121
121
|
}
|
|
@@ -206,7 +206,7 @@ export function getBehaviourTypeFromClusterServerId(clusterId) {
|
|
|
206
206
|
return TotalVolatileOrganicCompoundsConcentrationMeasurementServer.with('NumericMeasurement');
|
|
207
207
|
return MatterbridgeIdentifyServer;
|
|
208
208
|
}
|
|
209
|
-
export function getBehaviourTypeFromClusterClientId(
|
|
209
|
+
export function getBehaviourTypeFromClusterClientId(_clusterId) {
|
|
210
210
|
}
|
|
211
211
|
export function getBehavior(endpoint, cluster) {
|
|
212
212
|
let behavior;
|
|
@@ -518,6 +518,20 @@ export async function subscribeAttribute(endpoint, cluster, attribute, listener,
|
|
|
518
518
|
log?.info(`${db}Subscribed endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db}`);
|
|
519
519
|
return true;
|
|
520
520
|
}
|
|
521
|
+
export function getDefaultOperationalStateClusterServer(operationalState = OperationalState.OperationalStateEnum.Stopped) {
|
|
522
|
+
return optionsFor(MatterbridgeOperationalStateServer, {
|
|
523
|
+
phaseList: [],
|
|
524
|
+
currentPhase: null,
|
|
525
|
+
operationalStateList: [
|
|
526
|
+
{ operationalStateId: OperationalState.OperationalStateEnum.Stopped, operationalStateLabel: 'Stopped' },
|
|
527
|
+
{ operationalStateId: OperationalState.OperationalStateEnum.Running, operationalStateLabel: 'Running' },
|
|
528
|
+
{ operationalStateId: OperationalState.OperationalStateEnum.Paused, operationalStateLabel: 'Paused' },
|
|
529
|
+
{ operationalStateId: OperationalState.OperationalStateEnum.Error, operationalStateLabel: 'Error' },
|
|
530
|
+
],
|
|
531
|
+
operationalState,
|
|
532
|
+
operationalError: { errorStateId: OperationalState.ErrorState.NoError, errorStateLabel: 'No error', errorStateDetails: 'Fully operational' },
|
|
533
|
+
});
|
|
534
|
+
}
|
|
521
535
|
export function getDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
|
|
522
536
|
return optionsFor(TemperatureMeasurementServer, {
|
|
523
537
|
measuredValue,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
2
|
+
import { roboticVacuumCleaner } from './matterbridgeDeviceTypes.js';
|
|
3
|
+
import { MatterbridgeRvcCleanModeServer, MatterbridgeRvcOperationalStateServer, MatterbridgeRvcRunModeServer, MatterbridgeServiceAreaServer } from './matterbridgeBehaviors.js';
|
|
4
|
+
import { PowerSource, RvcRunMode, RvcCleanMode, RvcOperationalState } from '@matter/main/clusters';
|
|
5
|
+
export class RoboticVacuumCleaner extends MatterbridgeEndpoint {
|
|
6
|
+
constructor(name, serial) {
|
|
7
|
+
super(roboticVacuumCleaner, { uniqueStorageKey: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` }, true);
|
|
8
|
+
this.createDefaultIdentifyClusterServer()
|
|
9
|
+
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Robot Vacuum Cleaner')
|
|
10
|
+
.createDefaultPowerSourceRechargeableBatteryClusterServer(80, PowerSource.BatChargeLevel.Ok, 5900)
|
|
11
|
+
.createDefaultRvcRunModeClusterServer()
|
|
12
|
+
.createDefaultRvcCleanModeClusterServer()
|
|
13
|
+
.createDefaultRvcOperationalStateClusterServer()
|
|
14
|
+
.createDefaultServiceAreaClusterServer();
|
|
15
|
+
}
|
|
16
|
+
createDefaultRvcRunModeClusterServer(currentMode, supportedModes) {
|
|
17
|
+
this.behaviors.require(MatterbridgeRvcRunModeServer, {
|
|
18
|
+
supportedModes: supportedModes ?? [
|
|
19
|
+
{ label: 'Idle', mode: 1, modeTags: [{ value: RvcRunMode.ModeTag.Idle }] },
|
|
20
|
+
{ label: 'Cleaning', mode: 2, modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }] },
|
|
21
|
+
{ label: 'Mapping', mode: 3, modeTags: [{ value: RvcRunMode.ModeTag.Mapping }] },
|
|
22
|
+
{ label: 'SpotCleaning', mode: 4, modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }, { value: RvcRunMode.ModeTag.Max }] },
|
|
23
|
+
],
|
|
24
|
+
currentMode: currentMode ?? 1,
|
|
25
|
+
});
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
createDefaultRvcCleanModeClusterServer(currentMode, supportedModes) {
|
|
29
|
+
this.behaviors.require(MatterbridgeRvcCleanModeServer, {
|
|
30
|
+
supportedModes: supportedModes ?? [
|
|
31
|
+
{ label: 'Vacuum', mode: 1, modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }] },
|
|
32
|
+
{ label: 'Mop', mode: 2, modeTags: [{ value: RvcCleanMode.ModeTag.Mop }] },
|
|
33
|
+
{ label: 'Clean', mode: 3, modeTags: [{ value: RvcCleanMode.ModeTag.DeepClean }] },
|
|
34
|
+
],
|
|
35
|
+
currentMode: currentMode ?? 1,
|
|
36
|
+
});
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
createDefaultServiceAreaClusterServer(supportedAreas, selectedAreas, currentArea) {
|
|
40
|
+
this.behaviors.require(MatterbridgeServiceAreaServer, {
|
|
41
|
+
supportedAreas: supportedAreas ?? [
|
|
42
|
+
{
|
|
43
|
+
areaId: 1,
|
|
44
|
+
mapId: null,
|
|
45
|
+
areaInfo: { locationInfo: { locationName: 'Living', floorNumber: null, areaType: null }, landmarkInfo: null },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
areaId: 2,
|
|
49
|
+
mapId: null,
|
|
50
|
+
areaInfo: { locationInfo: { locationName: 'Kitchen', floorNumber: null, areaType: null }, landmarkInfo: null },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
areaId: 3,
|
|
54
|
+
mapId: null,
|
|
55
|
+
areaInfo: { locationInfo: { locationName: 'Bedroom', floorNumber: null, areaType: null }, landmarkInfo: null },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
areaId: 4,
|
|
59
|
+
mapId: null,
|
|
60
|
+
areaInfo: { locationInfo: { locationName: 'Bathroom', floorNumber: null, areaType: null }, landmarkInfo: null },
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
selectedAreas: selectedAreas ?? [],
|
|
64
|
+
currentArea: currentArea ?? 1,
|
|
65
|
+
estimatedEndTime: null,
|
|
66
|
+
});
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
createDefaultRvcOperationalStateClusterServer(phaseList = null, currentPhase = null, operationalStateList, operationalState, operationalError) {
|
|
70
|
+
this.behaviors.require(MatterbridgeRvcOperationalStateServer, {
|
|
71
|
+
phaseList,
|
|
72
|
+
currentPhase,
|
|
73
|
+
operationalStateList: operationalStateList ?? [
|
|
74
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Stopped, operationalStateLabel: 'Stopped' },
|
|
75
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Running, operationalStateLabel: 'Running' },
|
|
76
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Paused, operationalStateLabel: 'Paused' },
|
|
77
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Error, operationalStateLabel: 'Error' },
|
|
78
|
+
{ operationalStateId: RvcOperationalState.OperationalState.SeekingCharger, operationalStateLabel: 'SeekingCharger' },
|
|
79
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Charging, operationalStateLabel: 'Charging' },
|
|
80
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Docked, operationalStateLabel: 'Docked' },
|
|
81
|
+
],
|
|
82
|
+
operationalState: operationalState ?? RvcOperationalState.OperationalState.Docked,
|
|
83
|
+
operationalError: operationalError ?? { errorStateId: RvcOperationalState.ErrorState.NoError, errorStateLabel: 'No Error', errorStateDetails: 'Fully operational' },
|
|
84
|
+
});
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/update.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { plg } from './matterbridgeTypes.js';
|
|
2
2
|
import { db, nt, wr } from './logger/export.js';
|
|
3
3
|
export async function checkUpdates(matterbridge) {
|
|
4
|
-
const { hasParameter } = await import('./utils/
|
|
4
|
+
const { hasParameter } = await import('./utils/commandLine.js');
|
|
5
5
|
getMatterbridgeLatestVersion(matterbridge);
|
|
6
6
|
getMatterbridgeDevVersion(matterbridge);
|
|
7
7
|
for (const plugin of matterbridge.plugins) {
|
package/dist/utils/deepCopy.js
CHANGED
|
@@ -8,11 +8,16 @@ export function deepCopy(value) {
|
|
|
8
8
|
else if (value instanceof Date) {
|
|
9
9
|
return new Date(value.getTime());
|
|
10
10
|
}
|
|
11
|
+
else if (value instanceof RegExp) {
|
|
12
|
+
return new RegExp(value.source, value.flags);
|
|
13
|
+
}
|
|
11
14
|
else if (value instanceof Map) {
|
|
12
15
|
const mapCopy = new Map();
|
|
13
|
-
value.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
for (const [origKey, origVal] of value.entries()) {
|
|
17
|
+
const clonedKey = deepCopy(origKey);
|
|
18
|
+
const clonedVal = deepCopy(origVal);
|
|
19
|
+
mapCopy.set(clonedKey, clonedVal);
|
|
20
|
+
}
|
|
16
21
|
return mapCopy;
|
|
17
22
|
}
|
|
18
23
|
else if (value instanceof Set) {
|
package/dist/utils/deepEqual.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
export function deepEqual(a, b, excludeProperties = []) {
|
|
2
|
-
const debug = false;
|
|
3
2
|
const debugLog = (...messages) => {
|
|
4
|
-
if (debug) {
|
|
5
|
-
console.log(...messages);
|
|
6
|
-
}
|
|
7
3
|
};
|
|
8
4
|
if (a === b) {
|
|
9
5
|
return true;
|
|
@@ -16,6 +12,12 @@ export function deepEqual(a, b, excludeProperties = []) {
|
|
|
16
12
|
debugLog('deepEqual false for == null');
|
|
17
13
|
return false;
|
|
18
14
|
}
|
|
15
|
+
if (a instanceof Date && b instanceof Date) {
|
|
16
|
+
return a.getTime() === b.getTime();
|
|
17
|
+
}
|
|
18
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
19
|
+
return a.source === b.source && a.flags === b.flags;
|
|
20
|
+
}
|
|
19
21
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
20
22
|
if (a.length !== b.length) {
|
|
21
23
|
debugLog(`deepEqual false for array a.length(${a.length}) !== b.length(${b.length})`);
|
package/dist/utils/export.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './network.js';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './commandLine.js';
|
|
3
3
|
export * from './isvalid.js';
|
|
4
4
|
export * from './colorUtils.js';
|
|
5
5
|
export * from './deepCopy.js';
|
|
@@ -7,3 +7,4 @@ export * from './deepEqual.js';
|
|
|
7
7
|
export * from './copyDirectory.js';
|
|
8
8
|
export * from './createZip.js';
|
|
9
9
|
export * from './wait.js';
|
|
10
|
+
export * from './hex.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function bufferToHex(buffer) {
|
|
2
|
+
if (!(buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer))) {
|
|
3
|
+
throw new TypeError('Expected input to be an ArrayBuffer or ArrayBufferView');
|
|
4
|
+
}
|
|
5
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
6
|
+
return Array.from(bytes)
|
|
7
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
8
|
+
.join('');
|
|
9
|
+
}
|
|
10
|
+
export function hexToBuffer(hex) {
|
|
11
|
+
if (typeof hex !== 'string') {
|
|
12
|
+
throw new TypeError('Expected a string for hex input');
|
|
13
|
+
}
|
|
14
|
+
const cleaned = hex.trim();
|
|
15
|
+
if (cleaned.length % 2 !== 0) {
|
|
16
|
+
throw new Error('Invalid hex string length, must be even');
|
|
17
|
+
}
|
|
18
|
+
if (!/^[0-9a-fA-F]*$/.test(cleaned)) {
|
|
19
|
+
throw new Error('Invalid hex string, contains non-hex characters');
|
|
20
|
+
}
|
|
21
|
+
const length = cleaned.length / 2;
|
|
22
|
+
const result = new Uint8Array(length);
|
|
23
|
+
for (let i = 0; i < length; i++) {
|
|
24
|
+
result[i] = parseInt(cleaned.substr(i * 2, 2), 16);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
package/dist/utils/isvalid.js
CHANGED
|
@@ -23,6 +23,9 @@ export function isValidString(value, minLength, maxLength) {
|
|
|
23
23
|
return false;
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
|
+
export function isValidRegExp(value) {
|
|
27
|
+
return value !== undefined && value !== null && value instanceof RegExp;
|
|
28
|
+
}
|
|
26
29
|
export function isValidObject(value, minLength, maxLength) {
|
|
27
30
|
if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value))
|
|
28
31
|
return false;
|
package/dist/utils/network.js
CHANGED
|
@@ -44,7 +44,7 @@ export function getMacAddress() {
|
|
|
44
44
|
break;
|
|
45
45
|
}
|
|
46
46
|
for (const detail of interfaceDetails) {
|
|
47
|
-
if (
|
|
47
|
+
if (!detail.internal && macAddress === undefined) {
|
|
48
48
|
macAddress = detail.mac;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -83,7 +83,7 @@ export async function resolveHostname(hostname, family = 4) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
export async function getNpmPackageVersion(packageName, tag = 'latest', timeout = 10000) {
|
|
86
|
-
const https = await import('https');
|
|
86
|
+
const https = await import('node:https');
|
|
87
87
|
return new Promise((resolve, reject) => {
|
|
88
88
|
const url = `https://registry.npmjs.org/${packageName}`;
|
|
89
89
|
const controller = new AbortController();
|
package/dist/utils/wait.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnsiLogger } from '../logger/export.js';
|
|
2
|
-
const log = new AnsiLogger({ logName: 'MatterbridgeUtils', logTimestampFormat: 4, logLevel: "info" });
|
|
2
|
+
export const log = new AnsiLogger({ logName: 'MatterbridgeUtils', logTimestampFormat: 4, logLevel: "info" });
|
|
3
3
|
export async function waiter(name, check, exitWithReject = false, resolveTimeout = 5000, resolveInterval = 500, debug = false) {
|
|
4
4
|
if (check())
|
|
5
5
|
return true;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2-dev-20250508-7214e17",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.0.
|
|
9
|
+
"version": "3.0.2-dev-20250508-7214e17",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.13.0",
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isValidNumber } from './export.js';
|
|
1
2
|
export function hasParameter(name) {
|
|
2
3
|
const commandArguments = process.argv.slice(2);
|
|
3
4
|
let markerIncluded = commandArguments.includes(`-${name}`);
|
|
@@ -5,7 +6,6 @@ export function hasParameter(name) {
|
|
|
5
6
|
markerIncluded = commandArguments.includes(`--${name}`);
|
|
6
7
|
return markerIncluded;
|
|
7
8
|
}
|
|
8
|
-
import { isValidNumber } from './export.js';
|
|
9
9
|
export function getParameter(name) {
|
|
10
10
|
const commandArguments = process.argv.slice(2);
|
|
11
11
|
let markerIndex = commandArguments.indexOf(`-${name}`);
|