matterbridge 3.3.8-dev-20251117-e3fb774 → 3.3.9-dev-20251118-930cfdb
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 +18 -0
- package/README-DEV.md +20 -25
- package/dist/frontend.js +4 -0
- package/dist/jestutils/jestHelpers.js +100 -3
- package/dist/matterbridge.js +6 -5
- package/dist/matterbridgeEndpoint.js +30 -14
- package/dist/matterbridgeEndpointHelpers.js +1 -0
- package/dist/pluginManager.js +7 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,24 @@ Advantages:
|
|
|
28
28
|
- individual plugin isolation in childbridge mode;
|
|
29
29
|
- ability to update the plugin in childbridge mode without restarting matterbridge;
|
|
30
30
|
|
|
31
|
+
## [3.3.9] - Not published
|
|
32
|
+
|
|
33
|
+
### Development Breaking Changes
|
|
34
|
+
|
|
35
|
+
Removed the following long deprecated elements:
|
|
36
|
+
|
|
37
|
+
- [platform]: Matterbridge instead of PlatformMatterbridge in the platform constructor (deprecated since 3.0.0).
|
|
38
|
+
- [endpoint]: uniqueStorageKey instead of id in MatterbridgeEndpointOptions (deprecated since months).
|
|
39
|
+
- [endpoint]: endpointId instead of number in MatterbridgeEndpointOptions (deprecated since months).
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- [package]: Updated dependencies.
|
|
44
|
+
|
|
45
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
46
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
47
|
+
</a>
|
|
48
|
+
|
|
31
49
|
## [3.3.8] - 2025-11-15
|
|
32
50
|
|
|
33
51
|
### Development Breaking Changes
|
package/README-DEV.md
CHANGED
|
@@ -199,7 +199,7 @@ The plugin platform type.
|
|
|
199
199
|
The plugin config (loaded before the platform constructor is called and saved after onShutdown() is called).
|
|
200
200
|
Here you can store your plugin configuration (see matterbridge-zigbee2mqtt for example)
|
|
201
201
|
|
|
202
|
-
### constructor(matterbridge:
|
|
202
|
+
### constructor(matterbridge: PlatformMatterbridge, log: AnsiLogger, config: PlatformConfig)
|
|
203
203
|
|
|
204
204
|
The contructor is called when is plugin is loaded.
|
|
205
205
|
|
|
@@ -282,7 +282,7 @@ You create a Matter device with a new instance of MatterbridgeEndpoint(definitio
|
|
|
282
282
|
|
|
283
283
|
In the above example we create a contact sensor device type with also a power source device type feature replaceble battery.
|
|
284
284
|
|
|
285
|
-
All device types are defined in src\matterbridgeDeviceTypes.ts and taken from the 'Matter-1.4-Device-Library-Specification.pdf'.
|
|
285
|
+
All device types are defined in src\matterbridgeDeviceTypes.ts and taken from the 'Matter-1.4.1-Device-Library-Specification.pdf'.
|
|
286
286
|
|
|
287
287
|
All default cluster helpers are available as methods of MatterbridgeEndpoint.
|
|
288
288
|
|
|
@@ -293,12 +293,6 @@ All default cluster helpers are available as methods of MatterbridgeEndpoint.
|
|
|
293
293
|
- @param {string} [id] - The unique storage key for the endpoint. If not provided, a default key will be used.
|
|
294
294
|
- @param {EndpointNumber} [number] - The endpoint number for the endpoint. If not provided, the endpoint will be created with the next available endpoint number.
|
|
295
295
|
|
|
296
|
-
```typescript
|
|
297
|
-
const robot = new RoboticVacuumCleaner('Robot Vacuum', 'RVC1238777820', 'server');
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
In the above example we create a Rvc device type with its own server node.
|
|
301
|
-
|
|
302
296
|
The mode=`server` property of MatterbridgeEndpointOptions, allows to create an independent (not bridged) Matter device with its server node. In this case the bridge mode is not relevant.
|
|
303
297
|
|
|
304
298
|
The mode=`matter` property of MatterbridgeEndpointOptions, allows to create a (not bridged) Matter device that is added to the Matterbridge server node alongside the aggregator.
|
|
@@ -318,50 +312,50 @@ const robot = new RoboticVacuumCleaner('Robot Vacuum', 'RVC1238777820', 'server'
|
|
|
318
312
|
### Chapter 13. Appliances Device Types - Single class device types
|
|
319
313
|
|
|
320
314
|
```typescript
|
|
321
|
-
|
|
315
|
+
const laundryWasher = new LaundryWasher('Laundry Washer', 'LW1234567890');
|
|
322
316
|
```
|
|
323
317
|
|
|
324
318
|
```typescript
|
|
325
|
-
|
|
319
|
+
const laundryDryer = new LaundryDryer('Laundry Dryer', 'LDW1235227890');
|
|
326
320
|
```
|
|
327
321
|
|
|
328
322
|
```typescript
|
|
329
|
-
|
|
323
|
+
const dishwasher = new Dishwasher('Dishwasher', 'DW1234567890');
|
|
330
324
|
```
|
|
331
325
|
|
|
332
326
|
```typescript
|
|
333
|
-
|
|
327
|
+
const extractorHood = new ExtractorHood('Extractor Hood', 'EH1234567893');
|
|
334
328
|
```
|
|
335
329
|
|
|
336
330
|
```typescript
|
|
337
|
-
|
|
331
|
+
const microwaveOven = new MicrowaveOven('Microwave Oven', 'MO1234567893');
|
|
338
332
|
```
|
|
339
333
|
|
|
340
334
|
The Oven is always a composed device. You create the Oven and add one or more cabinet.
|
|
341
335
|
|
|
342
336
|
```typescript
|
|
343
|
-
|
|
344
|
-
|
|
337
|
+
const oven = new Oven('Oven', 'OV1234567890');
|
|
338
|
+
oven.addCabinet('Upper Cabinet', [{ mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: PositionTag.Top.label }]);
|
|
345
339
|
```
|
|
346
340
|
|
|
347
341
|
The Cooktop is always a composed device. You create the Cooktop and add one or more surface.
|
|
348
342
|
|
|
349
343
|
```typescript
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
344
|
+
const cooktop = new Cooktop('Cooktop', 'CT1234567890');
|
|
345
|
+
cooktop.addSurface('Surface Top Left', [
|
|
346
|
+
{ mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: PositionTag.Top.label },
|
|
347
|
+
{ mfgCode: null, namespaceId: PositionTag.Left.namespaceId, tag: PositionTag.Left.tag, label: PositionTag.Left.label },
|
|
348
|
+
]);
|
|
355
349
|
```
|
|
356
350
|
|
|
357
351
|
The Refrigerator is always a composed device. You create the Refrigerator and add one or more cabinet.
|
|
358
352
|
|
|
359
353
|
```typescript
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
354
|
+
const refrigerator = new Refrigerator('Refrigerator', 'RE1234567890');
|
|
355
|
+
refrigerator.addCabinet('Refrigerator Top', [
|
|
356
|
+
{ mfgCode: null, namespaceId: PositionTag.Top.namespaceId, tag: PositionTag.Top.tag, label: 'Refrigerator Top' },
|
|
357
|
+
{ mfgCode: null, namespaceId: RefrigeratorTag.Refrigerator.namespaceId, tag: RefrigeratorTag.Refrigerator.tag, label: RefrigeratorTag.Refrigerator.label },
|
|
358
|
+
]);
|
|
365
359
|
```
|
|
366
360
|
|
|
367
361
|
### Chapter 14. Energy Device Types - Single class device types
|
|
@@ -394,6 +388,7 @@ Each plugin has a minimal default config file injected by Matterbridge when it i
|
|
|
394
388
|
{
|
|
395
389
|
name: plugin.name, // i.e. matterbridge-test
|
|
396
390
|
type: plugin.type, // i.e. AccessoryPlatform or DynamicPlatform (on the first run is AnyPlatform cause it is still unknown)
|
|
391
|
+
version: plugin.version,
|
|
397
392
|
debug: false,
|
|
398
393
|
unregisterOnShutdown: false
|
|
399
394
|
}
|
package/dist/frontend.js
CHANGED
|
@@ -214,6 +214,7 @@ export class Frontend extends EventEmitter {
|
|
|
214
214
|
this.httpServer.on('upgrade', async (req, socket, head) => {
|
|
215
215
|
try {
|
|
216
216
|
if ((req.headers.upgrade || '').toLowerCase() !== 'websocket') {
|
|
217
|
+
this.log.error(`WebSocket upgrade error: Invalid upgrade header ${req.headers.upgrade}`);
|
|
217
218
|
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
|
|
218
219
|
return socket.destroy();
|
|
219
220
|
}
|
|
@@ -1140,6 +1141,9 @@ export class Frontend extends EventEmitter {
|
|
|
1140
1141
|
}
|
|
1141
1142
|
client.send(JSON.stringify(data));
|
|
1142
1143
|
}
|
|
1144
|
+
else {
|
|
1145
|
+
this.log.error('Cannot send api response, client not connected');
|
|
1146
|
+
}
|
|
1143
1147
|
};
|
|
1144
1148
|
try {
|
|
1145
1149
|
data = JSON.parse(message.toString());
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { rmSync } from 'node:fs';
|
|
2
2
|
import { inspect } from 'node:util';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { AnsiLogger, er, rs } from 'node-ansi-logger';
|
|
4
|
+
import { AnsiLogger, er, rs, UNDERLINE, UNDERLINEOFF } from 'node-ansi-logger';
|
|
5
5
|
import { LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Environment, Lifecycle } from '@matter/general';
|
|
6
6
|
import { Endpoint, ServerNode, ServerNodeStore } from '@matter/node';
|
|
7
7
|
import { DeviceTypeId, VendorId } from '@matter/types/datatype';
|
|
8
8
|
import { AggregatorEndpoint } from '@matter/node/endpoints';
|
|
9
9
|
import { MdnsService } from '@matter/main/protocol';
|
|
10
|
+
import { NodeStorageManager } from 'node-persist-manager';
|
|
10
11
|
import { Matterbridge } from '../matterbridge.js';
|
|
11
|
-
import { MATTER_STORAGE_NAME } from '../matterbridgeTypes.js';
|
|
12
|
+
import { MATTER_STORAGE_NAME, NODE_STORAGE_DIR } from '../matterbridgeTypes.js';
|
|
12
13
|
import { bridge } from '../matterbridgeDeviceTypes.js';
|
|
14
|
+
export const originalProcessArgv = Object.freeze([...process.argv]);
|
|
15
|
+
export const originalProcessEnv = Object.freeze({ ...process.env });
|
|
13
16
|
export let loggerLogSpy;
|
|
14
17
|
export let loggerDebugSpy;
|
|
15
18
|
export let loggerInfoSpy;
|
|
@@ -25,7 +28,12 @@ export let consoleErrorSpy;
|
|
|
25
28
|
export let addBridgedEndpointSpy;
|
|
26
29
|
export let removeBridgedEndpointSpy;
|
|
27
30
|
export let removeAllBridgedEndpointsSpy;
|
|
31
|
+
export let NAME;
|
|
32
|
+
export let HOMEDIR;
|
|
28
33
|
export let matterbridge;
|
|
34
|
+
export let frontend;
|
|
35
|
+
export let plugins;
|
|
36
|
+
export let devices;
|
|
29
37
|
export let environment;
|
|
30
38
|
export let server;
|
|
31
39
|
export let aggregator;
|
|
@@ -34,7 +42,9 @@ export async function setupTest(name, debug = false) {
|
|
|
34
42
|
expect(name).toBeDefined();
|
|
35
43
|
expect(typeof name).toBe('string');
|
|
36
44
|
expect(name.length).toBeGreaterThanOrEqual(4);
|
|
37
|
-
|
|
45
|
+
NAME = name;
|
|
46
|
+
HOMEDIR = path.join('jest', name);
|
|
47
|
+
rmSync(HOMEDIR, { recursive: true, force: true });
|
|
38
48
|
const { jest } = await import('@jest/globals');
|
|
39
49
|
loggerDebugSpy = jest.spyOn(AnsiLogger.prototype, 'debug');
|
|
40
50
|
loggerInfoSpy = jest.spyOn(AnsiLogger.prototype, 'info');
|
|
@@ -87,6 +97,89 @@ export async function setDebug(debug) {
|
|
|
87
97
|
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
88
98
|
}
|
|
89
99
|
}
|
|
100
|
+
export async function startMatterbridge(bridgeMode = 'bridge', frontendPort = 8283, matterPort = 5540, passcode = 20252026, discriminator = 3840, pluginSize = 0, devicesSize = 0) {
|
|
101
|
+
process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS'] = '100';
|
|
102
|
+
process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS'] = '100';
|
|
103
|
+
process.argv.length = 0;
|
|
104
|
+
process.argv.push(...originalProcessArgv, '-novirtual', '-debug', '-verbose', '-logger', 'debug', '-matterlogger', 'debug', bridgeMode === '' ? '-test' : '-' + bridgeMode, '-homedir', HOMEDIR, '-frontend', frontendPort.toString(), '-port', matterPort.toString(), '-passcode', passcode.toString(), '-discriminator', discriminator.toString());
|
|
105
|
+
matterbridge = await Matterbridge.loadInstance(true);
|
|
106
|
+
expect(matterbridge).toBeDefined();
|
|
107
|
+
expect(matterbridge.profile).toBeUndefined();
|
|
108
|
+
expect(matterbridge.bridgeMode).toBe(bridgeMode);
|
|
109
|
+
frontend = matterbridge.frontend;
|
|
110
|
+
plugins = matterbridge.plugins;
|
|
111
|
+
devices = matterbridge.devices;
|
|
112
|
+
expect(matterbridge.initialized).toBeTruthy();
|
|
113
|
+
expect(matterbridge.log).toBeDefined();
|
|
114
|
+
expect(matterbridge.rootDirectory).toBe(path.resolve('./'));
|
|
115
|
+
expect(matterbridge.homeDirectory).toBe(path.join('jest', NAME));
|
|
116
|
+
expect(matterbridge.matterbridgeDirectory).toBe(path.join('jest', NAME, '.matterbridge'));
|
|
117
|
+
expect(matterbridge.matterbridgePluginDirectory).toBe(path.join('jest', NAME, 'Matterbridge'));
|
|
118
|
+
expect(matterbridge.matterbridgeCertDirectory).toBe(path.join('jest', NAME, '.mattercert'));
|
|
119
|
+
expect(plugins).toBeDefined();
|
|
120
|
+
expect(plugins.size).toBe(pluginSize);
|
|
121
|
+
expect(devices).toBeDefined();
|
|
122
|
+
expect(devices.size).toBe(devicesSize);
|
|
123
|
+
expect(frontend).toBeDefined();
|
|
124
|
+
expect(frontend.listening).toBeTruthy();
|
|
125
|
+
expect(frontend.httpServer).toBeDefined();
|
|
126
|
+
expect(frontend.httpsServer).toBeUndefined();
|
|
127
|
+
expect(frontend.expressApp).toBeDefined();
|
|
128
|
+
expect(frontend.webSocketServer).toBeDefined();
|
|
129
|
+
expect(matterbridge.nodeStorage).toBeDefined();
|
|
130
|
+
expect(matterbridge.nodeContext).toBeDefined();
|
|
131
|
+
expect(Environment.default.vars.get('path.root')).toBe(path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
132
|
+
expect(matterbridge.matterStorageService).toBeDefined();
|
|
133
|
+
expect(matterbridge.matterStorageManager).toBeDefined();
|
|
134
|
+
expect(matterbridge.matterbridgeContext).toBeDefined();
|
|
135
|
+
expect(matterbridge.controllerContext).toBeUndefined();
|
|
136
|
+
if (bridgeMode === 'bridge') {
|
|
137
|
+
expect(matterbridge.serverNode).toBeDefined();
|
|
138
|
+
expect(matterbridge.aggregatorNode).toBeDefined();
|
|
139
|
+
}
|
|
140
|
+
expect(matterbridge.mdnsInterface).toBe(undefined);
|
|
141
|
+
expect(matterbridge.port).toBe(matterPort + (bridgeMode === 'bridge' ? 1 : 0));
|
|
142
|
+
expect(matterbridge.passcode).toBe(passcode + (bridgeMode === 'bridge' ? 1 : 0));
|
|
143
|
+
expect(matterbridge.discriminator).toBe(discriminator + (bridgeMode === 'bridge' ? 1 : 0));
|
|
144
|
+
if (bridgeMode === 'bridge') {
|
|
145
|
+
const started = new Promise((resolve) => {
|
|
146
|
+
matterbridge.once('bridge_started', () => {
|
|
147
|
+
resolve();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
const online = new Promise((resolve) => {
|
|
151
|
+
matterbridge.once('online', (name) => {
|
|
152
|
+
if (name === 'Matterbridge')
|
|
153
|
+
resolve();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
await Promise.all([started, online]);
|
|
157
|
+
}
|
|
158
|
+
else if (bridgeMode === 'childbridge') {
|
|
159
|
+
await new Promise((resolve) => {
|
|
160
|
+
matterbridge.once('childbridge_started', () => {
|
|
161
|
+
resolve();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("info", `The frontend http server is listening on ${UNDERLINE}http://${matterbridge.systemInformation.ipv4Address}:${frontendPort}${UNDERLINEOFF}${rs}`);
|
|
166
|
+
if (bridgeMode === 'bridge') {
|
|
167
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Starting Matterbridge server node`);
|
|
168
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Server node for Matterbridge is online`);
|
|
169
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Starting start matter interval in bridge mode...`);
|
|
170
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Cleared startMatterInterval interval in bridge mode`);
|
|
171
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Matterbridge bridge started successfully`);
|
|
172
|
+
}
|
|
173
|
+
else if (bridgeMode === 'childbridge') {
|
|
174
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Starting start matter interval in childbridge mode...`);
|
|
175
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Cleared startMatterInterval interval in childbridge mode`);
|
|
176
|
+
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Matterbridge childbridge started successfully`);
|
|
177
|
+
}
|
|
178
|
+
return matterbridge;
|
|
179
|
+
}
|
|
180
|
+
export async function stopMatterbridge(cleanupPause = 10, destroyPause = 250) {
|
|
181
|
+
await destroyMatterbridgeEnvironment(cleanupPause, destroyPause);
|
|
182
|
+
}
|
|
90
183
|
export async function createMatterbridgeEnvironment(name) {
|
|
91
184
|
matterbridge = await Matterbridge.loadInstance(false);
|
|
92
185
|
expect(matterbridge).toBeDefined();
|
|
@@ -106,6 +199,8 @@ export async function createMatterbridgeEnvironment(name) {
|
|
|
106
199
|
return matterbridge;
|
|
107
200
|
}
|
|
108
201
|
export async function startMatterbridgeEnvironment(port = 5540) {
|
|
202
|
+
matterbridge.nodeStorage = new NodeStorageManager({ dir: path.join(matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
203
|
+
matterbridge.nodeContext = await matterbridge.nodeStorage.createStorage('matterbridge');
|
|
109
204
|
await matterbridge.startMatterStorage();
|
|
110
205
|
expect(matterbridge.matterStorageService).toBeDefined();
|
|
111
206
|
expect(matterbridge.matterStorageManager).toBeDefined();
|
|
@@ -180,6 +275,8 @@ export async function stopMatterbridgeEnvironment() {
|
|
|
180
275
|
expect(matterbridge.matterStorageService).not.toBeDefined();
|
|
181
276
|
expect(matterbridge.matterStorageManager).not.toBeDefined();
|
|
182
277
|
expect(matterbridge.matterbridgeContext).not.toBeDefined();
|
|
278
|
+
await matterbridge.nodeContext?.close();
|
|
279
|
+
await matterbridge.nodeStorage?.close();
|
|
183
280
|
await flushAsync();
|
|
184
281
|
}
|
|
185
282
|
export async function destroyMatterbridgeEnvironment(cleanupPause = 10, destroyPause = 250) {
|
package/dist/matterbridge.js
CHANGED
|
@@ -1128,7 +1128,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1128
1128
|
await this.serverNode.add(this.aggregatorNode);
|
|
1129
1129
|
await addVirtualDevices(this, this.aggregatorNode);
|
|
1130
1130
|
await this.startPlugins();
|
|
1131
|
-
this.log.debug('Starting start matter interval in bridge mode');
|
|
1131
|
+
this.log.debug('Starting start matter interval in bridge mode...');
|
|
1132
1132
|
let failCount = 0;
|
|
1133
1133
|
this.startMatterInterval = setInterval(async () => {
|
|
1134
1134
|
for (const plugin of this.plugins) {
|
|
@@ -1156,7 +1156,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1156
1156
|
}
|
|
1157
1157
|
clearInterval(this.startMatterInterval);
|
|
1158
1158
|
this.startMatterInterval = undefined;
|
|
1159
|
-
this.log.debug('Cleared startMatterInterval interval
|
|
1159
|
+
this.log.debug('Cleared startMatterInterval interval in bridge mode');
|
|
1160
1160
|
this.startServerNode(this.serverNode);
|
|
1161
1161
|
for (const device of this.devices.array()) {
|
|
1162
1162
|
if (device.mode === 'server' && device.serverNode) {
|
|
@@ -1190,7 +1190,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1190
1190
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1191
1191
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1192
1192
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1193
|
-
}, this.startMatterIntervalMs);
|
|
1193
|
+
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1194
1194
|
}
|
|
1195
1195
|
async startChildbridge(delay = 1000) {
|
|
1196
1196
|
if (!this.matterStorageManager)
|
|
@@ -1236,7 +1236,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1236
1236
|
clearInterval(this.startMatterInterval);
|
|
1237
1237
|
this.startMatterInterval = undefined;
|
|
1238
1238
|
if (delay > 0)
|
|
1239
|
-
await wait(delay);
|
|
1239
|
+
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
|
|
1240
1240
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1241
1241
|
this.configureTimeout = setTimeout(async () => {
|
|
1242
1242
|
for (const plugin of this.plugins.array()) {
|
|
@@ -1291,7 +1291,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1291
1291
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1292
1292
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1293
1293
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1294
|
-
}, this.startMatterIntervalMs);
|
|
1294
|
+
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1295
1295
|
}
|
|
1296
1296
|
async startController() {
|
|
1297
1297
|
}
|
|
@@ -1373,6 +1373,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1373
1373
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1374
1374
|
const serverNode = await ServerNode.create({
|
|
1375
1375
|
id: storeId,
|
|
1376
|
+
environment: this.environment,
|
|
1376
1377
|
network: {
|
|
1377
1378
|
listeningAddressIpv4: this.ipv4Address,
|
|
1378
1379
|
listeningAddressIpv6: this.ipv6Address,
|
|
@@ -63,6 +63,7 @@ import { ThermostatUserInterfaceConfigurationServer } from '@matter/node/behavio
|
|
|
63
63
|
import { isValidNumber, isValidObject, isValidString } from './utils/isvalid.js';
|
|
64
64
|
import { MatterbridgeServer, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeLiftTiltWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeHepaFilterMonitoringServer, MatterbridgeEnhancedColorControlServer, MatterbridgePowerSourceServer, } from './matterbridgeBehaviors.js';
|
|
65
65
|
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getApparentElectricalPowerMeasurementClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, getDefaultPowerSourceWiredClusterServer, getDefaultPowerSourceReplaceableBatteryClusterServer, getDefaultPowerSourceRechargeableBatteryClusterServer, getDefaultDeviceEnergyManagementClusterServer, getDefaultDeviceEnergyManagementModeClusterServer, getDefaultPowerSourceBatteryClusterServer, } from './matterbridgeEndpointHelpers.js';
|
|
66
|
+
import { inspectError } from './utils/error.js';
|
|
66
67
|
export class MatterbridgeEndpoint extends Endpoint {
|
|
67
68
|
static logLevel = "info";
|
|
68
69
|
mode = undefined;
|
|
@@ -84,7 +85,6 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
84
85
|
productUrl = 'https://www.npmjs.com/package/matterbridge';
|
|
85
86
|
tagList = undefined;
|
|
86
87
|
originalId = undefined;
|
|
87
|
-
uniqueStorageKey = undefined;
|
|
88
88
|
name = undefined;
|
|
89
89
|
deviceType = undefined;
|
|
90
90
|
deviceTypes = new Map();
|
|
@@ -122,20 +122,16 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
122
122
|
behaviors: options.tagList ? SupportedBehaviors(DescriptorServer.with(Descriptor.Feature.TagList)) : SupportedBehaviors(DescriptorServer),
|
|
123
123
|
};
|
|
124
124
|
const endpointV8 = MutableEndpoint(deviceTypeDefinitionV8);
|
|
125
|
-
if (options.uniqueStorageKey && checkNotLatinCharacters(options.uniqueStorageKey)) {
|
|
126
|
-
options.uniqueStorageKey = generateUniqueId(options.uniqueStorageKey);
|
|
127
|
-
}
|
|
128
125
|
if (options.id && checkNotLatinCharacters(options.id)) {
|
|
129
126
|
options.id = generateUniqueId(options.id);
|
|
130
127
|
}
|
|
131
128
|
const optionsV8 = {
|
|
132
|
-
id: options.id?.replace(/[ .]/g, '')
|
|
133
|
-
number: options.number
|
|
129
|
+
id: options.id?.replace(/[ .]/g, ''),
|
|
130
|
+
number: options.number,
|
|
134
131
|
descriptor: options.tagList ? { tagList: options.tagList, deviceTypeList } : { deviceTypeList },
|
|
135
132
|
};
|
|
136
133
|
super(endpointV8, optionsV8);
|
|
137
134
|
this.mode = options.mode;
|
|
138
|
-
this.uniqueStorageKey = options.id ?? options.uniqueStorageKey;
|
|
139
135
|
this.originalId = originalId;
|
|
140
136
|
this.name = firstDefinition.name;
|
|
141
137
|
this.deviceType = firstDefinition.code;
|
|
@@ -147,7 +143,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
147
143
|
}
|
|
148
144
|
else
|
|
149
145
|
this.deviceTypes.set(firstDefinition.code, firstDefinition);
|
|
150
|
-
this.log = new AnsiLogger({ logName: this.originalId ??
|
|
146
|
+
this.log = new AnsiLogger({ logName: this.originalId ?? 'MatterbridgeEndpoint', logTimestampFormat: 4, logLevel: debug === true ? "debug" : MatterbridgeEndpoint.logLevel });
|
|
151
147
|
this.log.debug(`${YELLOW}new${db} MatterbridgeEndpoint: ${zb}${'0x' + firstDefinition.code.toString(16).padStart(4, '0')}${db}-${zb}${firstDefinition.name}${db} mode: ${CYAN}${this.mode}${db} id: ${CYAN}${optionsV8.id}${db} number: ${CYAN}${optionsV8.number}${db} taglist: ${CYAN}${options.tagList ? debugStringify(options.tagList) : 'undefined'}${db}`);
|
|
152
148
|
this.behaviors.require(MatterbridgeServer, { log: this.log, commandHandler: this.commandHandler });
|
|
153
149
|
}
|
|
@@ -279,11 +275,21 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
279
275
|
return child;
|
|
280
276
|
if (this.lifecycle.isInstalled) {
|
|
281
277
|
this.log.debug(`- with lifecycle installed`);
|
|
282
|
-
|
|
278
|
+
try {
|
|
279
|
+
this.add(child);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
inspectError(this.log, `addChildDeviceType: error adding (with lifecycle installed) child endpoint ${CYAN}${endpointName}${db}`, error);
|
|
283
|
+
}
|
|
283
284
|
}
|
|
284
285
|
else {
|
|
285
286
|
this.log.debug(`- with lifecycle NOT installed`);
|
|
286
|
-
|
|
287
|
+
try {
|
|
288
|
+
this.parts.add(child);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
inspectError(this.log, `addChildDeviceType: error adding (with lifecycle NOT installed) child endpoint ${CYAN}${endpointName}${db}`, error);
|
|
292
|
+
}
|
|
287
293
|
}
|
|
288
294
|
return child;
|
|
289
295
|
}
|
|
@@ -341,11 +347,21 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
341
347
|
return child;
|
|
342
348
|
if (this.lifecycle.isInstalled) {
|
|
343
349
|
this.log.debug(`- with lifecycle installed`);
|
|
344
|
-
|
|
350
|
+
try {
|
|
351
|
+
this.add(child);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
inspectError(this.log, `addChildDeviceType: error adding (with lifecycle installed) child endpoint ${CYAN}${endpointName}${db}`, error);
|
|
355
|
+
}
|
|
345
356
|
}
|
|
346
357
|
else {
|
|
347
358
|
this.log.debug(`- with lifecycle NOT installed`);
|
|
348
|
-
|
|
359
|
+
try {
|
|
360
|
+
this.parts.add(child);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
inspectError(this.log, `addChildDeviceType: error adding (with lifecycle NOT installed) child endpoint ${CYAN}${endpointName}${db}`, error);
|
|
364
|
+
}
|
|
349
365
|
}
|
|
350
366
|
return child;
|
|
351
367
|
}
|
|
@@ -1007,7 +1023,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
1007
1023
|
}
|
|
1008
1024
|
createSmokeOnlySmokeCOAlarmClusterServer(smokeState = SmokeCoAlarm.AlarmState.Normal) {
|
|
1009
1025
|
this.behaviors.require(MatterbridgeSmokeCoAlarmServer.with(SmokeCoAlarm.Feature.SmokeAlarm).enable({
|
|
1010
|
-
events: { smokeAlarm: true, interconnectSmokeAlarm: false,
|
|
1026
|
+
events: { smokeAlarm: true, interconnectSmokeAlarm: false, lowBattery: true, hardwareFault: true, endOfService: true, selfTestComplete: true, alarmMuted: true, muteEnded: true, allClear: true },
|
|
1011
1027
|
}), {
|
|
1012
1028
|
smokeState,
|
|
1013
1029
|
expressedState: SmokeCoAlarm.ExpressedState.Normal,
|
|
@@ -1021,7 +1037,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
1021
1037
|
}
|
|
1022
1038
|
createCoOnlySmokeCOAlarmClusterServer(coState = SmokeCoAlarm.AlarmState.Normal) {
|
|
1023
1039
|
this.behaviors.require(MatterbridgeSmokeCoAlarmServer.with(SmokeCoAlarm.Feature.CoAlarm).enable({
|
|
1024
|
-
events: {
|
|
1040
|
+
events: { coAlarm: true, interconnectCoAlarm: false, lowBattery: true, hardwareFault: true, endOfService: true, selfTestComplete: true, alarmMuted: true, muteEnded: true, allClear: true },
|
|
1025
1041
|
}), {
|
|
1026
1042
|
coState,
|
|
1027
1043
|
expressedState: SmokeCoAlarm.ExpressedState.Normal,
|
|
@@ -792,6 +792,7 @@ export function getDefaultOccupancySensingClusterServer(occupied = false, holdTi
|
|
|
792
792
|
occupancySensorTypeBitmap: { pir: true, ultrasonic: false, physicalContact: false },
|
|
793
793
|
pirOccupiedToUnoccupiedDelay: holdTime,
|
|
794
794
|
pirUnoccupiedToOccupiedDelay: holdTime,
|
|
795
|
+
pirUnoccupiedToOccupiedThreshold: 1,
|
|
795
796
|
holdTime,
|
|
796
797
|
holdTimeLimits: { holdTimeMin, holdTimeMax, holdTimeDefault: holdTime },
|
|
797
798
|
});
|
package/dist/pluginManager.js
CHANGED
|
@@ -288,19 +288,19 @@ export class PluginManager extends EventEmitter {
|
|
|
288
288
|
this.log.debug(`Saved ${BLUE}${plugins.length}${db} plugins to storage`);
|
|
289
289
|
return plugins.length;
|
|
290
290
|
}
|
|
291
|
-
async resolve(
|
|
291
|
+
async resolve(nameOrPath) {
|
|
292
292
|
const { default: path } = await import('node:path');
|
|
293
293
|
const { promises } = await import('node:fs');
|
|
294
|
-
if (!
|
|
295
|
-
|
|
296
|
-
let packageJsonPath = path.resolve(
|
|
294
|
+
if (!nameOrPath.endsWith('package.json'))
|
|
295
|
+
nameOrPath = path.join(nameOrPath, 'package.json');
|
|
296
|
+
let packageJsonPath = path.resolve(nameOrPath);
|
|
297
297
|
this.log.debug(`Resolving plugin path ${plg}${packageJsonPath}${db}`);
|
|
298
298
|
try {
|
|
299
299
|
await promises.access(packageJsonPath);
|
|
300
300
|
}
|
|
301
301
|
catch {
|
|
302
302
|
this.log.debug(`Package.json not found at ${plg}${packageJsonPath}${db}`);
|
|
303
|
-
packageJsonPath = path.join(this.matterbridge.globalModulesDirectory,
|
|
303
|
+
packageJsonPath = path.join(this.matterbridge.globalModulesDirectory, nameOrPath);
|
|
304
304
|
this.log.debug(`Trying at ${plg}${packageJsonPath}${db}`);
|
|
305
305
|
}
|
|
306
306
|
try {
|
|
@@ -359,11 +359,11 @@ export class PluginManager extends EventEmitter {
|
|
|
359
359
|
this.log.error(`Please open an issue on the plugin repository to remove them.`);
|
|
360
360
|
return null;
|
|
361
361
|
}
|
|
362
|
-
this.log.debug(`Resolved plugin path ${plg}${
|
|
362
|
+
this.log.debug(`Resolved plugin path ${plg}${nameOrPath}${db}: ${packageJsonPath}`);
|
|
363
363
|
return packageJsonPath;
|
|
364
364
|
}
|
|
365
365
|
catch (err) {
|
|
366
|
-
logError(this.log, `Failed to resolve plugin path ${plg}${
|
|
366
|
+
logError(this.log, `Failed to resolve plugin path ${plg}${nameOrPath}${er}`, err);
|
|
367
367
|
return null;
|
|
368
368
|
}
|
|
369
369
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.9-dev-20251118-930cfdb",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.3.
|
|
9
|
+
"version": "3.3.9-dev-20251118-930cfdb",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.15.6",
|
package/package.json
CHANGED