matterbridge 3.4.0-dev-20251120-21b4f48 → 3.4.0-dev-20251120-5724e43

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
@@ -34,7 +34,7 @@ Advantages:
34
34
 
35
35
  Removed the following long deprecated elements:
36
36
 
37
- - [platform]: Matterbridge instead of PlatformMatterbridge in the platform constructor (deprecated since 3.0.0).
37
+ - [platform]: Matterbridge instead of PlatformMatterbridge in the platform constructor (deprecated since 3.3.0).
38
38
  - [endpoint]: uniqueStorageKey instead of id in MatterbridgeEndpointOptions (deprecated since months).
39
39
  - [endpoint]: endpointId instead of number in MatterbridgeEndpointOptions (deprecated since months).
40
40
 
@@ -43,6 +43,11 @@ Removed the following long deprecated elements:
43
43
  - [endpoint]: Added getChildEndpointById() and getChildEndpointByOriginalId().
44
44
  - [endpoint]: Deprecated getChildEndpointByName(). Use getChildEndpointById() or getChildEndpointByOriginalId().
45
45
  - [platform]: Added wssSendSnackbarMessage method to MatterbridgePlatform for sending snackbar notifications to the frontend.
46
+ - [doorLock]: Added autoRelockTime attribute with default 0.
47
+ - [DevContainer]: Added instructions for testing a plugin with a paired controller when using DevContainer.
48
+ - [platform]: Made internal use methods and properties hard-private.
49
+ - [platform]: Added size(), getDeviceByName(), getDeviceByUniqueId(), getDeviceBySerialNumber(), getDeviceById(), getDeviceByOriginalId(), getDeviceByNumber() and hasDeviceUniqueId().
50
+ - [platform]: Added isReady, isLoaded, isStarted and isConfigured.
46
51
 
47
52
  ### Changed
48
53
 
package/README-DEV.md CHANGED
@@ -54,6 +54,12 @@ To start the Dev Container, simply open the project folder in [Visual Studio Cod
54
54
 
55
55
  Since Dev Container doesn't run in network mode 'host', it is not possible to pair Mattebridge running inside the Dev Container.
56
56
 
57
+ When you want to test your plugin with a paired controller, you have several options:
58
+
59
+ - create a tgz (npm run npmPack) and upload it to a running instance of matterbridge.
60
+ - publish it with tag dev and install it (matterbridge-yourplugin@dev in Install plugins) in a running instance of matterbridge.
61
+ - use a local instance of matterbridge running outside the dev container and install (../matterbridge-yourplugin in Install plugins) or add (../matterbridge-yourplugin in Install plugins) your plugin to it (easiest way). Adjust the path if matterbridge dir and your plugin dir are not in the same parent directory.
62
+
57
63
  ## Guidelines on imports/exports
58
64
 
59
65
  Matterbridge exports from:
package/dist/frontend.js CHANGED
@@ -1648,6 +1648,7 @@ export class Frontend extends EventEmitter {
1648
1648
  else if (data.params.value === 'Fatal') {
1649
1649
  Logger.level = MatterLogLevel.FATAL;
1650
1650
  }
1651
+ this.matterbridge.matterLogLevel = MatterLogLevel.names[Logger.level];
1651
1652
  let callbackLogLevel = "notice";
1652
1653
  if (this.matterbridge.getLogLevel() === "info" || Logger.level === MatterLogLevel.INFO)
1653
1654
  callbackLogLevel = "info";
@@ -66,8 +66,10 @@ export class Matterbridge extends EventEmitter {
66
66
  virtualMode = 'outlet';
67
67
  profile = getParameter('profile');
68
68
  log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
69
+ logLevel = this.log.logLevel;
69
70
  fileLogger = false;
70
- matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
71
+ matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
72
+ matterLogLevel = this.matterLog.logLevel;
71
73
  matterFileLogger = false;
72
74
  readOnly = hasParameter('readonly') || hasParameter('shelly');
73
75
  shellyBoard = hasParameter('shelly');
@@ -320,6 +322,7 @@ export class Matterbridge extends EventEmitter {
320
322
  else {
321
323
  this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
322
324
  }
325
+ this.logLevel = this.log.logLevel;
323
326
  this.frontend.logLevel = this.log.logLevel;
324
327
  MatterbridgeEndpoint.logLevel = this.log.logLevel;
325
328
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
@@ -359,11 +362,12 @@ export class Matterbridge extends EventEmitter {
359
362
  Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
360
363
  }
361
364
  Logger.format = MatterLogFormat.ANSI;
365
+ this.matterLogLevel = MatterLogLevel.names[Logger.level];
362
366
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
363
367
  this.matterFileLogger = true;
364
368
  }
365
369
  Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
366
- this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
370
+ this.log.debug(`Matter logLevel: ${this.matterLogLevel} fileLoger: ${this.matterFileLogger}.`);
367
371
  const networkInterfaces = os.networkInterfaces();
368
372
  const availableAddresses = Object.entries(networkInterfaces);
369
373
  const availableInterfaceNames = Object.keys(networkInterfaces);
@@ -841,6 +845,7 @@ export class Matterbridge extends EventEmitter {
841
845
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
842
846
  }
843
847
  async setLogLevel(logLevel) {
848
+ this.logLevel = logLevel;
844
849
  this.log.logLevel = logLevel;
845
850
  this.frontend.logLevel = logLevel;
846
851
  MatterbridgeEndpoint.logLevel = logLevel;
@@ -20,14 +20,17 @@ export class MatterbridgePlatform {
20
20
  version = '1.0.0';
21
21
  storage;
22
22
  context;
23
- selectDevice = new Map();
24
- selectEntity = new Map();
25
- contextReady;
26
- selectDeviceContextReady;
27
- selectEntityContextReady;
23
+ isReady = false;
24
+ isLoaded = false;
25
+ isStarted = false;
26
+ isConfigured = false;
27
+ #selectDevices = new Map();
28
+ #selectEntities = new Map();
29
+ #contextReady;
30
+ #selectDeviceContextReady;
31
+ #selectEntityContextReady;
28
32
  ready;
29
- registeredEndpointsByUniqueId = new Map();
30
- registeredEndpointsByName = new Map();
33
+ #registeredEndpoints = new Map();
31
34
  #server;
32
35
  #debug = hasParameter('debug') || hasParameter('verbose');
33
36
  #verbose = hasParameter('verbose');
@@ -40,8 +43,10 @@ export class MatterbridgePlatform {
40
43
  this.log.debug(`Creating MatterbridgePlatform for plugin ${this.config.name}`);
41
44
  if (this.#verbose)
42
45
  this.log.debug(`Creating MatterbridgePlatform for plugin ${this.config.name} with config:\n${JSON.stringify(this.config, null, 2)}\n`);
43
- if (!isValidString(this.config.name, 1))
46
+ if (!isValidString(this.config.name, 1)) {
47
+ this.#server.close();
44
48
  throw new Error('Platform: the plugin name is missing or invalid.');
49
+ }
45
50
  this.log.debug(`Creating storage for plugin ${this.config.name} in ${path.join(this.matterbridge.matterbridgeDirectory, this.config.name)}`);
46
51
  this.storage = new NodeStorageManager({
47
52
  dir: path.join(this.matterbridge.matterbridgeDirectory, this.config.name),
@@ -51,32 +56,47 @@ export class MatterbridgePlatform {
51
56
  forgiveParseErrors: true,
52
57
  });
53
58
  this.log.debug(`Creating context for plugin ${this.config.name}`);
54
- this.contextReady = this.storage.createStorage('context').then((context) => {
59
+ this.#contextReady = this.storage.createStorage('context').then((context) => {
55
60
  this.context = context;
56
61
  this.log.debug(`Created context for plugin ${this.config.name}`);
57
62
  return;
58
63
  });
59
64
  this.log.debug(`Loading selectDevice for plugin ${this.config.name}`);
60
- this.selectDeviceContextReady = this.storage.createStorage('selectDevice').then(async (context) => {
65
+ this.#selectDeviceContextReady = this.storage.createStorage('selectDevice').then(async (context) => {
61
66
  const selectDevice = await context.get('selectDevice', []);
62
67
  for (const device of selectDevice)
63
- this.selectDevice.set(device.serial, device);
64
- this.log.debug(`Loaded ${this.selectDevice.size} selectDevice for plugin ${this.config.name}`);
68
+ this.#selectDevices.set(device.serial, device);
69
+ this.log.debug(`Loaded ${this.#selectDevices.size} selectDevice for plugin ${this.config.name}`);
65
70
  return;
66
71
  });
67
72
  this.log.debug(`Loading selectEntity for plugin ${this.config.name}`);
68
- this.selectEntityContextReady = this.storage.createStorage('selectEntity').then(async (context) => {
73
+ this.#selectEntityContextReady = this.storage.createStorage('selectEntity').then(async (context) => {
69
74
  const selectEntity = await context.get('selectEntity', []);
70
75
  for (const entity of selectEntity)
71
- this.selectEntity.set(entity.name, entity);
72
- this.log.debug(`Loaded ${this.selectEntity.size} selectEntity for plugin ${this.config.name}`);
76
+ this.#selectEntities.set(entity.name, entity);
77
+ this.log.debug(`Loaded ${this.#selectEntities.size} selectEntity for plugin ${this.config.name}`);
73
78
  return;
74
79
  });
75
- this.ready = Promise.all([this.contextReady, this.selectDeviceContextReady, this.selectEntityContextReady]).then(() => {
80
+ this.ready = Promise.all([this.#contextReady, this.#selectDeviceContextReady, this.#selectEntityContextReady]).then(() => {
76
81
  this.log.debug(`MatterbridgePlatform for plugin ${this.config.name} is fully initialized`);
82
+ this.isReady = true;
77
83
  return;
78
84
  });
79
85
  }
86
+ async destroy() {
87
+ if (this.#verbose)
88
+ this.log.debug(`Destroying MatterbridgePlatform for plugin ${this.config.name}`);
89
+ this.#selectDevices.clear();
90
+ this.#selectEntities.clear();
91
+ this.#registeredEndpoints.clear();
92
+ await this.context?.close();
93
+ this.context = undefined;
94
+ await this.storage?.close();
95
+ this.#server.close();
96
+ if (this.#verbose)
97
+ this.log.debug(`Destroyed MatterbridgePlatform for plugin ${this.config.name}`);
98
+ this.isReady = false;
99
+ }
80
100
  async onStart(reason) {
81
101
  this.log.error('Plugins must override onStart.', reason);
82
102
  throw new Error('Plugins must override onStart.');
@@ -90,14 +110,7 @@ export class MatterbridgePlatform {
90
110
  this.log.debug(`Shutting down platform ${this.name}`, reason);
91
111
  await this.saveSelects();
92
112
  await this.checkEndpointNumbers();
93
- this.selectDevice.clear();
94
- this.selectEntity.clear();
95
- this.registeredEndpointsByUniqueId.clear();
96
- this.registeredEndpointsByName.clear();
97
- await this.context?.close();
98
- this.context = undefined;
99
- await this.storage?.close();
100
- this.#server.close();
113
+ await this.destroy();
101
114
  }
102
115
  async onChangeLoggerLevel(logLevel) {
103
116
  this.log.debug(`The plugin doesn't override onChangeLoggerLevel. Logger level set to: ${logLevel}`);
@@ -135,11 +148,35 @@ export class MatterbridgePlatform {
135
148
  wssSendSnackbarMessage(message, timeout, severity) {
136
149
  this.#server.request({ type: 'frontend_snackbarmessage', src: 'platform', dst: 'frontend', params: { message, timeout, severity } });
137
150
  }
151
+ size() {
152
+ return this.#registeredEndpoints.size;
153
+ }
138
154
  getDevices() {
139
- return Array.from(this.registeredEndpointsByUniqueId.values());
155
+ return Array.from(this.#registeredEndpoints.values());
156
+ }
157
+ getDeviceByName(deviceName) {
158
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.deviceName === deviceName);
159
+ }
160
+ getDeviceByUniqueId(uniqueId) {
161
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.uniqueId === uniqueId);
162
+ }
163
+ getDeviceBySerialNumber(serialNumber) {
164
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.serialNumber === serialNumber);
165
+ }
166
+ getDeviceById(id) {
167
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.maybeId === id);
168
+ }
169
+ getDeviceByOriginalId(originalId) {
170
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.originalId === originalId);
171
+ }
172
+ getDeviceByNumber(number) {
173
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.maybeNumber === number);
140
174
  }
141
175
  hasDeviceName(deviceName) {
142
- return this.registeredEndpointsByName.has(deviceName);
176
+ return Array.from(this.#registeredEndpoints.values()).find((device) => device.deviceName === deviceName) !== undefined;
177
+ }
178
+ hasDeviceUniqueId(deviceUniqueId) {
179
+ return this.#registeredEndpoints.has(deviceUniqueId);
143
180
  }
144
181
  async registerVirtualDevice(name, type, callback) {
145
182
  let aggregator;
@@ -176,7 +213,7 @@ export class MatterbridgePlatform {
176
213
  this.log.error(`Device with uniqueId ${CYAN}${device.uniqueId}${er} has no serialNumber. The device will not be added.`);
177
214
  return;
178
215
  }
179
- if (this.registeredEndpointsByName.has(device.deviceName)) {
216
+ if (this.hasDeviceName(device.deviceName)) {
180
217
  this.log.error(`Device with name ${CYAN}${device.deviceName}${er} is already registered. The device will not be added. Please change the device name.`);
181
218
  return;
182
219
  }
@@ -201,48 +238,44 @@ export class MatterbridgePlatform {
201
238
  }
202
239
  }
203
240
  await this.matterbridge.addBridgedEndpoint(this.name, device);
204
- this.registeredEndpointsByUniqueId.set(device.uniqueId, device);
205
- this.registeredEndpointsByName.set(device.deviceName, device);
241
+ this.#registeredEndpoints.set(device.uniqueId, device);
206
242
  }
207
243
  async unregisterDevice(device) {
208
244
  await this.matterbridge.removeBridgedEndpoint(this.name, device);
209
245
  if (device.uniqueId)
210
- this.registeredEndpointsByUniqueId.delete(device.uniqueId);
211
- if (device.deviceName)
212
- this.registeredEndpointsByName.delete(device.deviceName);
246
+ this.#registeredEndpoints.delete(device.uniqueId);
213
247
  }
214
248
  async unregisterAllDevices(delay = 0) {
215
249
  await this.matterbridge.removeAllBridgedEndpoints(this.name, delay);
216
- this.registeredEndpointsByUniqueId.clear();
217
- this.registeredEndpointsByName.clear();
250
+ this.#registeredEndpoints.clear();
218
251
  }
219
252
  async saveSelects() {
220
253
  if (this.storage) {
221
- this.log.debug(`Saving ${this.selectDevice.size} selectDevice...`);
254
+ this.log.debug(`Saving ${this.#selectDevices.size} selectDevice...`);
222
255
  const selectDevice = await this.storage.createStorage('selectDevice');
223
- await selectDevice.set('selectDevice', Array.from(this.selectDevice.values()));
256
+ await selectDevice.set('selectDevice', Array.from(this.#selectDevices.values()));
224
257
  await selectDevice.close();
225
- this.log.debug(`Saving ${this.selectEntity.size} selectEntity...`);
258
+ this.log.debug(`Saving ${this.#selectEntities.size} selectEntity...`);
226
259
  const selectEntity = await this.storage.createStorage('selectEntity');
227
- await selectEntity.set('selectEntity', Array.from(this.selectEntity.values()));
260
+ await selectEntity.set('selectEntity', Array.from(this.#selectEntities.values()));
228
261
  await selectEntity.close();
229
262
  }
230
263
  }
231
264
  async clearSelect() {
232
- this.selectDevice.clear();
233
- this.selectEntity.clear();
265
+ this.#selectDevices.clear();
266
+ this.#selectEntities.clear();
234
267
  await this.saveSelects();
235
268
  }
236
269
  async clearDeviceSelect(serial) {
237
- this.selectDevice.delete(serial);
270
+ this.#selectDevices.delete(serial);
238
271
  await this.saveSelects();
239
272
  }
240
273
  async clearEntitySelect(name) {
241
- this.selectEntity.delete(name);
274
+ this.#selectEntities.delete(name);
242
275
  await this.saveSelects();
243
276
  }
244
277
  setSelectDevice(serial, name, configUrl, icon, entities) {
245
- const device = this.selectDevice.get(serial);
278
+ const device = this.#selectDevices.get(serial);
246
279
  if (device) {
247
280
  device.serial = serial;
248
281
  device.name = name;
@@ -254,11 +287,14 @@ export class MatterbridgePlatform {
254
287
  device.entities = entities;
255
288
  }
256
289
  else {
257
- this.selectDevice.set(serial, { serial, name, configUrl, icon, entities });
290
+ this.#selectDevices.set(serial, { serial, name, configUrl, icon, entities });
258
291
  }
259
292
  }
293
+ getSelectDevice(serial) {
294
+ return this.#selectDevices.get(serial);
295
+ }
260
296
  setSelectDeviceEntity(serial, entityName, entityDescription, entityIcon) {
261
- const device = this.selectDevice.get(serial);
297
+ const device = this.#selectDevices.get(serial);
262
298
  if (device) {
263
299
  if (!device.entities)
264
300
  device.entities = [];
@@ -268,17 +304,20 @@ export class MatterbridgePlatform {
268
304
  }
269
305
  getSelectDevices() {
270
306
  const selectDevices = [];
271
- for (const device of this.selectDevice.values()) {
307
+ for (const device of this.#selectDevices.values()) {
272
308
  selectDevices.push({ pluginName: this.name, ...device });
273
309
  }
274
310
  return selectDevices;
275
311
  }
276
312
  setSelectEntity(name, description, icon) {
277
- this.selectEntity.set(name, { name, description, icon });
313
+ this.#selectEntities.set(name, { name, description, icon });
314
+ }
315
+ getSelectEntity(name) {
316
+ return this.#selectEntities.get(name);
278
317
  }
279
318
  getSelectEntities() {
280
319
  const selectEntities = [];
281
- for (const entity of this.selectEntity.values()) {
320
+ for (const entity of this.#selectEntities.values()) {
282
321
  selectEntities.push({ pluginName: this.name, ...entity });
283
322
  }
284
323
  return selectEntities;
@@ -728,7 +728,7 @@ export class PluginManager extends EventEmitter {
728
728
  const { pathToFileURL } = await import('node:url');
729
729
  const pluginUrl = pathToFileURL(pluginEntry);
730
730
  this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
731
- const pluginInstance = await import(pluginUrl.href);
731
+ const pluginInstance = (await import(pluginUrl.href));
732
732
  this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
733
733
  if (pluginInstance.default) {
734
734
  const config = await this.loadConfig(plugin);
@@ -740,12 +740,13 @@ export class PluginManager extends EventEmitter {
740
740
  plugin.schemaJson = await this.loadSchema(plugin);
741
741
  config.name = packageJson.name;
742
742
  config.version = packageJson.version;
743
- const log = new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4, logLevel: config.debug ? "debug" : this.matterbridge.log.logLevel });
743
+ const log = new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4, logLevel: config.debug ? "debug" : this.matterbridge.logLevel });
744
744
  const platform = pluginInstance.default(this.matterbridge, log, config);
745
745
  config.type = platform.type;
746
746
  platform.name = packageJson.name;
747
747
  platform.config = config;
748
748
  platform.version = packageJson.version;
749
+ platform.isLoaded = true;
749
750
  plugin.name = packageJson.name;
750
751
  plugin.description = packageJson.description ?? 'No description';
751
752
  plugin.version = packageJson.version;
@@ -804,6 +805,7 @@ export class PluginManager extends EventEmitter {
804
805
  await plugin.platform.onStart(message);
805
806
  this.log.notice(`Started plugin ${plg}${plugin.name}${nt} type ${typ}${plugin.type}${nt}`);
806
807
  plugin.started = true;
808
+ plugin.platform.isStarted = true;
807
809
  await this.saveConfigFromPlugin(plugin);
808
810
  this.emit('started', plugin.name);
809
811
  if (configure)
@@ -846,6 +848,7 @@ export class PluginManager extends EventEmitter {
846
848
  await plugin.platform.onConfigure();
847
849
  this.log.notice(`Configured plugin ${plg}${plugin.name}${nt} type ${typ}${plugin.type}${nt}`);
848
850
  plugin.configured = true;
851
+ plugin.platform.isConfigured = true;
849
852
  this.emit('configured', plugin.name);
850
853
  return plugin;
851
854
  }
@@ -885,6 +888,10 @@ export class PluginManager extends EventEmitter {
885
888
  this.log.info(`Shutting down plugin ${plg}${plugin.name}${nf}: ${reason}...`);
886
889
  try {
887
890
  await plugin.platform.onShutdown(reason);
891
+ plugin.platform.isReady = false;
892
+ plugin.platform.isLoaded = false;
893
+ plugin.platform.isStarted = false;
894
+ plugin.platform.isConfigured = false;
888
895
  plugin.locked = undefined;
889
896
  plugin.error = undefined;
890
897
  plugin.loaded = undefined;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.4.0-dev-20251120-21b4f48",
3
+ "version": "3.4.0-dev-20251120-5724e43",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.4.0-dev-20251120-21b4f48",
9
+ "version": "3.4.0-dev-20251120-5724e43",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.15.6",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.4.0-dev-20251120-21b4f48",
3
+ "version": "3.4.0-dev-20251120-5724e43",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",