matterbridge 1.1.9 → 1.1.10

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +20 -4
  3. package/dist/cli.js +20 -4
  4. package/dist/cli.js.map +1 -1
  5. package/dist/matterbridge.d.ts +23 -1
  6. package/dist/matterbridge.d.ts.map +1 -1
  7. package/dist/matterbridge.js +217 -37
  8. package/dist/matterbridge.js.map +1 -1
  9. package/dist/matterbridgeAccessoryPlatform.d.ts +9 -0
  10. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  11. package/dist/matterbridgeAccessoryPlatform.js +13 -0
  12. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  13. package/dist/matterbridgeDevice.d.ts +1 -0
  14. package/dist/matterbridgeDevice.d.ts.map +1 -1
  15. package/dist/matterbridgeDevice.js +69 -11
  16. package/dist/matterbridgeDevice.js.map +1 -1
  17. package/dist/matterbridgeDynamicPlatform.d.ts +9 -0
  18. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  19. package/dist/matterbridgeDynamicPlatform.js +13 -0
  20. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  21. package/frontend/build/asset-manifest.json +6 -6
  22. package/frontend/build/index.html +1 -1
  23. package/frontend/build/static/css/main.3804969f.css +2 -0
  24. package/frontend/build/static/css/main.3804969f.css.map +1 -0
  25. package/frontend/build/static/js/main.82822a11.js +3 -0
  26. package/frontend/build/static/js/main.82822a11.js.map +1 -0
  27. package/package.json +2 -1
  28. package/frontend/build/static/css/main.ce1ee9e7.css +0 -2
  29. package/frontend/build/static/css/main.ce1ee9e7.css.map +0 -1
  30. package/frontend/build/static/js/main.cc840fb3.js +0 -3
  31. package/frontend/build/static/js/main.cc840fb3.js.map +0 -1
  32. /package/frontend/build/static/js/{main.cc840fb3.js.LICENSE.txt → main.82822a11.js.LICENSE.txt} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,11 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.10] - 2024-03-15
6
+
7
+ ### Added
8
+
9
+ - [matterbridge]: added unregisterAllDevices() to the platforms
10
+ - [matterbridge]: added unregisterDevice(device: MatterbridgeDevice) to the platforms
11
+ - [frontend]: Enable and disable plugin are now available. Restart Matteerbridge after.
12
+ - [frontend]: Frontend got updated to 0.8.2.
13
+
5
14
  ## [1.1.9] - 2024-03-15
6
15
 
7
16
  ### Added
8
17
 
18
+ - [frontend]: Selecting a plugin in the home page show the corresponding QR code.
9
19
  - [frontend]: Settings page now controll the global logger level.
20
+ - [frontend]: Restart from the header is available.
10
21
  - [frontend]: Frontend got updated to 0.8.1.
11
22
 
12
23
  ## [1.1.8] - 2024-03-15
package/README.md CHANGED
@@ -174,21 +174,37 @@ The easiest way is to clone:
174
174
 
175
175
  Then change the name, version, description and author in the package.json.
176
176
 
177
- Add your plugin logic in platform.ts:
177
+ Add your plugin logic in platform.ts.
178
178
 
179
- ### onStart(reason?: string)
179
+ ## MatterbridgeDynamicPlatform and MatterbridgeAccessoryPlatform api
180
+
181
+ ### async onStart(reason?: string)
180
182
  The method onStart() is where you have to create your MatterbridgeDevice and add all needed clusters and command handlers.
181
183
 
182
184
  The MatterbridgeDevice class has the create cluster methods already done and all command handlers needed (see plugin examples).
183
185
 
184
186
  The method is called when Matterbridge load the plugin.
185
187
 
186
- ### onConfigure()
188
+ ### async onConfigure()
187
189
  The method onConfigure() is where you can configure or initialize your device.
188
190
 
189
191
  The method is called when the platform is commissioned.
190
192
 
191
- ### onShutdown(reason?: string)
193
+ ### async onShutdown(reason?: string)
192
194
  The method onShutdown() is where you have to eventually cleanup some resources.
193
195
 
194
196
  The method is called when Matterbridge is shutting down.
197
+
198
+ ### async registerDevice(device: MatterbridgeDevice)
199
+ After you created your device, add it to the platform.
200
+
201
+ ### async unregisterDevice(device: MatterbridgeDevice)
202
+ You can unregister one or more device.
203
+
204
+ ### async unregisterAllDevices()
205
+ You can unregister all devices you added.
206
+
207
+ It can me be useful to call this method from onShutdown if you don't want to keep all the devices.
208
+
209
+ ## MatterbridgeDevice api
210
+
package/dist/cli.js CHANGED
@@ -21,17 +21,33 @@
21
21
  * See the License for the specific language governing permissions and
22
22
  * limitations under the License. *
23
23
  */
24
+ /* eslint-disable no-console */
25
+ //import wtf from 'wtfnode';
24
26
  import { Matterbridge } from './matterbridge.js';
27
+ let instance;
25
28
  async function main() {
26
- // eslint-disable-next-line no-console
27
29
  console.log('CLI: Matterbridge.loadInstance() called');
28
- await Matterbridge.loadInstance(true);
29
- // eslint-disable-next-line no-console
30
+ instance = await Matterbridge.loadInstance(true);
31
+ registerHandlers();
30
32
  console.log('CLI: Matterbridge.loadInstance() exited');
31
33
  }
34
+ function registerHandlers() {
35
+ instance.on('shutdown', async () => shutdown());
36
+ instance.on('restart', async () => restart());
37
+ }
38
+ async function shutdown() {
39
+ console.log('CLI: received shutdown event, exiting...');
40
+ //wtf.dump();
41
+ process.exit(0);
42
+ }
43
+ async function restart() {
44
+ console.log('CLI: received restart event, loading...');
45
+ //wtf.dump();
46
+ instance = await Matterbridge.loadInstance(true);
47
+ registerHandlers();
48
+ }
32
49
  process.title = 'matterbridge';
33
50
  main().catch((error) => {
34
- // eslint-disable-next-line no-console
35
51
  console.error(`CLI: Matterbridge.loadInstance() failed with error: ${error}`);
36
52
  });
37
53
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,KAAK,UAAU,IAAI;IACjB,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACvD,MAAM,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACtC,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;AACzD,CAAC;AAED,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC;AAE/B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,uDAAuD,KAAK,EAAE,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,+BAA+B;AAC/B,4BAA4B;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,IAAI,QAAkC,CAAC;AAEvC,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACvD,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACjD,gBAAgB,EAAE,CAAC;IACnB,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,gBAAgB;IACvB,QAAS,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjD,QAAS,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,QAAQ;IACrB,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IACxD,aAAa;IACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACvD,aAAa;IACb,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACjD,gBAAgB,EAAE,CAAC;AACrB,CAAC;AAED,OAAO,CAAC,KAAK,GAAG,cAAc,CAAC;AAE/B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,uDAAuD,KAAK,EAAE,CAAC,CAAC;AAChF,CAAC,CAAC,CAAC"}
@@ -20,7 +20,9 @@
20
20
  * See the License for the specific language governing permissions and
21
21
  * limitations under the License. *
22
22
  */
23
+ /// <reference types="node" resolution-mode="require"/>
23
24
  import { MatterbridgeDevice } from './matterbridgeDevice.js';
25
+ import EventEmitter from 'events';
24
26
  interface SystemInformation {
25
27
  ipv4Address: string;
26
28
  ipv6Address: string;
@@ -37,7 +39,7 @@ interface SystemInformation {
37
39
  /**
38
40
  * Represents the Matterbridge application.
39
41
  */
40
- export declare class Matterbridge {
42
+ export declare class Matterbridge extends EventEmitter {
41
43
  systemInformation: SystemInformation;
42
44
  homeDirectory: string;
43
45
  rootDirectory: string;
@@ -82,6 +84,13 @@ export declare class Matterbridge {
82
84
  * @returns A Promise that resolves when the initialization is complete.
83
85
  */
84
86
  initialize(): Promise<void>;
87
+ /**
88
+ * Spawns a child process with the given command and arguments.
89
+ * @param command - The command to execute.
90
+ * @param args - The arguments to pass to the command (default: []).
91
+ * @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
92
+ */
93
+ private spawnCommand;
85
94
  /**
86
95
  * Parses the command line arguments and performs the corresponding actions.
87
96
  * @private
@@ -131,6 +140,14 @@ export declare class Matterbridge {
131
140
  * @returns {Promise<void>} - A promise that resolves when the storage process is started.
132
141
  */
133
142
  addBridgedDevice(pluginName: string, device: MatterbridgeDevice): Promise<void>;
143
+ removeBridgedDevice(pluginName: string, device: MatterbridgeDevice): Promise<void>;
144
+ /**
145
+ * Removes all bridged devices associated with a specific plugin.
146
+ *
147
+ * @param pluginName - The name of the plugin.
148
+ * @returns A promise that resolves when all devices have been removed.
149
+ */
150
+ removeAllBridgedDevices(pluginName: string): Promise<void>;
134
151
  /**
135
152
  * Starts the storage process based on the specified storage type and name.
136
153
  * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
@@ -241,6 +258,11 @@ export declare class Matterbridge {
241
258
  * @returns A Promise that resolves to the latest version of the package.
242
259
  */
243
260
  private getLatestVersion;
261
+ /**
262
+ * Retrieves the path to the global Node.js modules directory.
263
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
264
+ */
265
+ private getGlobalNodeModules;
244
266
  /**
245
267
  * Logs the node and system information.
246
268
  */
@@ -1 +1 @@
1
- {"version":3,"file":"matterbridge.d.ts","sourceRoot":"","sources":["../src/matterbridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"matterbridge.d.ts","sourceRoot":"","sources":["../src/matterbridge.ts"],"names":[],"mappings":"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"}
@@ -25,7 +25,8 @@ import { NodeStorageManager } from 'node-persist-manager';
25
25
  import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr } from 'node-ansi-logger';
26
26
  import { fileURLToPath, pathToFileURL } from 'url';
27
27
  import { promises as fs } from 'fs';
28
- import { exec, execSync } from 'child_process';
28
+ import { exec, spawn } from 'child_process';
29
+ import EventEmitter from 'events';
29
30
  import express from 'express';
30
31
  import os from 'os';
31
32
  import path from 'path';
@@ -44,7 +45,7 @@ const typ = '\u001B[38;5;207m';
44
45
  /**
45
46
  * Represents the Matterbridge application.
46
47
  */
47
- export class Matterbridge {
48
+ export class Matterbridge extends EventEmitter {
48
49
  systemInformation = {
49
50
  ipv4Address: '',
50
51
  ipv6Address: '',
@@ -84,6 +85,7 @@ export class Matterbridge {
84
85
  commissioningController;
85
86
  static instance;
86
87
  constructor() {
88
+ super();
87
89
  // We load asyncronously
88
90
  }
89
91
  /**
@@ -116,6 +118,10 @@ export class Matterbridge {
116
118
  * @returns A Promise that resolves when the initialization is complete.
117
119
  */
118
120
  async initialize() {
121
+ /*
122
+ const wtf = await import('wtfnode');
123
+ wtf.dump();
124
+ */
119
125
  // Display the help
120
126
  if (hasParameter('help')) {
121
127
  // eslint-disable-next-line no-console
@@ -173,6 +179,43 @@ export class Matterbridge {
173
179
  // Parse command line
174
180
  this.parseCommandLine();
175
181
  }
182
+ /**
183
+ * Spawns a child process with the given command and arguments.
184
+ * @param command - The command to execute.
185
+ * @param args - The arguments to pass to the command (default: []).
186
+ * @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
187
+ */
188
+ async spawnCommand(command, args = []) {
189
+ /*
190
+ npm > npm.cmd on windows
191
+ */
192
+ return new Promise((resolve, reject) => {
193
+ const childProcess = spawn(command, args, {
194
+ stdio: 'inherit',
195
+ });
196
+ childProcess.on('error', (err) => {
197
+ this.log.error(`Failed to start child process: ${err.message}`);
198
+ reject(err); // Reject the promise on error
199
+ });
200
+ childProcess.on('close', (code) => {
201
+ if (code === 0) {
202
+ this.log.info(`Child process stdio streams have closed with code ${code}`);
203
+ resolve();
204
+ }
205
+ else {
206
+ this.log.error(`Child process stdio streams have closed with code ${code}`);
207
+ reject(new Error(`Process exited with code ${code}`));
208
+ }
209
+ });
210
+ // The 'exit' event might be redundant here since 'close' is also being handled
211
+ childProcess.on('exit', (code, signal) => {
212
+ this.log.info(`Child process exited with code ${code} and signal ${signal}`);
213
+ });
214
+ childProcess.on('disconnect', () => {
215
+ this.log.info('Child process has been disconnected from the parent');
216
+ });
217
+ });
218
+ }
176
219
  /**
177
220
  * Parses the command line arguments and performs the corresponding actions.
178
221
  * @private
@@ -191,26 +234,31 @@ export class Matterbridge {
191
234
  this.log.info(`│ └─ ${db}${plugin.path}${db}`);
192
235
  }
193
236
  });
237
+ this.emit('shutdown');
194
238
  process.exit(0);
195
239
  }
196
240
  if (getParameter('add')) {
197
241
  this.log.debug(`Registering plugin ${getParameter('add')}`);
198
242
  await this.executeCommandLine(getParameter('add'), 'add');
243
+ this.emit('shutdown');
199
244
  process.exit(0);
200
245
  }
201
246
  if (getParameter('remove')) {
202
247
  this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
203
248
  await this.executeCommandLine(getParameter('remove'), 'remove');
249
+ this.emit('shutdown');
204
250
  process.exit(0);
205
251
  }
206
252
  if (getParameter('enable')) {
207
253
  this.log.debug(`Enable plugin ${getParameter('enable')}`);
208
254
  await this.executeCommandLine(getParameter('enable'), 'enable');
255
+ this.emit('shutdown');
209
256
  process.exit(0);
210
257
  }
211
258
  if (getParameter('disable')) {
212
259
  this.log.debug(`Disable plugin ${getParameter('disable')}`);
213
260
  await this.executeCommandLine(getParameter('disable'), 'disable');
261
+ this.emit('shutdown');
214
262
  process.exit(0);
215
263
  }
216
264
  // Start the storage (we need it now for frontend and later for matterbridge)
@@ -228,8 +276,10 @@ export class Matterbridge {
228
276
  this.bridgeMode = 'bridge';
229
277
  MatterbridgeDevice.bridgeMode = 'bridge';
230
278
  for (const plugin of this.registeredPlugins) {
231
- if (!plugin.enabled)
279
+ if (!plugin.enabled) {
280
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
232
281
  continue;
282
+ }
233
283
  plugin.loaded = false;
234
284
  plugin.started = false;
235
285
  plugin.configured = false;
@@ -244,8 +294,10 @@ export class Matterbridge {
244
294
  this.bridgeMode = 'childbridge';
245
295
  MatterbridgeDevice.bridgeMode = 'childbridge';
246
296
  for (const plugin of this.registeredPlugins) {
247
- if (!plugin.enabled)
297
+ if (!plugin.enabled) {
298
+ this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
248
299
  continue;
300
+ }
249
301
  plugin.loaded = false;
250
302
  plugin.started = false;
251
303
  plugin.configured = false;
@@ -384,7 +436,7 @@ export class Matterbridge {
384
436
  async restartProcess() {
385
437
  //this.log.info('Restarting still not implemented');
386
438
  //return;
387
- await this.cleanup('Matterbridge is restarting...', true);
439
+ await this.cleanup('restarting...', true);
388
440
  this.hasCleanupStarted = false;
389
441
  }
390
442
  /**
@@ -429,7 +481,7 @@ export class Matterbridge {
429
481
  this.expressApp.removeAllListeners();
430
482
  this.expressApp = undefined;
431
483
  }
432
- setTimeout(async () => {
484
+ const cleanupTimeout1 = setTimeout(async () => {
433
485
  // Closing matter
434
486
  await this.stopMatter();
435
487
  // Closing storage
@@ -453,15 +505,21 @@ export class Matterbridge {
453
505
  }
454
506
  this.registeredPlugins = [];
455
507
  this.registeredDevices = [];
456
- setTimeout(async () => {
457
- this.log.info('Cleanup completed.');
458
- //if (restart) console.log(this);
459
- if (restart)
460
- await this.initialize();
461
- else
462
- process.exit(0);
508
+ const cleanupTimeout2 = setTimeout(async () => {
509
+ if (restart) {
510
+ this.log.info('Cleanup completed. Restarting...');
511
+ Matterbridge.instance = undefined;
512
+ this.emit('restart');
513
+ }
514
+ else {
515
+ this.log.info('Cleanup completed. Shutting down...');
516
+ Matterbridge.instance = undefined;
517
+ this.emit('shutdown');
518
+ }
463
519
  }, 2 * 1000);
520
+ cleanupTimeout2.unref();
464
521
  }, 3 * 1000);
522
+ cleanupTimeout1.unref();
465
523
  }
466
524
  }
467
525
  /**
@@ -486,14 +544,14 @@ export class Matterbridge {
486
544
  */
487
545
  async addDevice(pluginName, device) {
488
546
  if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
489
- this.log.error(`Adding device ${dev}${device.name}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
547
+ this.log.error(`Adding device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
490
548
  return;
491
549
  }
492
- this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
550
+ this.log.debug(`Adding device ${dev}${device.name}-${device.deviceName}${db} for plugin ${plg}${pluginName}${db}`);
493
551
  // Check if the plugin is registered
494
552
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
495
553
  if (!plugin) {
496
- this.log.error(`Error adding device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
554
+ this.log.error(`Error adding device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
497
555
  return;
498
556
  }
499
557
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -504,14 +562,14 @@ export class Matterbridge {
504
562
  plugin.registeredDevices++;
505
563
  if (plugin.addedDevices !== undefined)
506
564
  plugin.addedDevices++;
507
- this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
565
+ this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
508
566
  }
509
567
  // Only register the device in childbridge mode
510
568
  if (this.bridgeMode === 'childbridge') {
511
569
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
512
570
  if (plugin.registeredDevices !== undefined)
513
571
  plugin.registeredDevices++;
514
- this.log.info(`Registered device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
572
+ this.log.info(`Registered device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
515
573
  }
516
574
  }
517
575
  /**
@@ -522,14 +580,14 @@ export class Matterbridge {
522
580
  */
523
581
  async addBridgedDevice(pluginName, device) {
524
582
  if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
525
- this.log.error(`Adding bridged device ${dev}${device.name}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
583
+ this.log.error(`Adding bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
526
584
  return;
527
585
  }
528
- this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
586
+ this.log.debug(`Adding bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
529
587
  // Check if the plugin is registered
530
588
  const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
531
589
  if (!plugin) {
532
- this.log.error(`Error adding bridged device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
590
+ this.log.error(`Error adding bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
533
591
  return;
534
592
  }
535
593
  // Register and add the device to matterbridge aggregator in bridge mode
@@ -540,14 +598,92 @@ export class Matterbridge {
540
598
  plugin.registeredDevices++;
541
599
  if (plugin.addedDevices !== undefined)
542
600
  plugin.addedDevices++;
543
- this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
601
+ this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
544
602
  }
545
603
  // Only register the device in childbridge mode
546
604
  if (this.bridgeMode === 'childbridge') {
547
605
  this.registeredDevices.push({ plugin: pluginName, device, added: false });
548
606
  if (plugin.registeredDevices !== undefined)
549
607
  plugin.registeredDevices++;
550
- this.log.info(`Registered bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
608
+ this.log.info(`Registered bridged device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
609
+ }
610
+ }
611
+ async removeBridgedDevice(pluginName, device) {
612
+ if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
613
+ this.log.error(`Removing bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
614
+ return;
615
+ }
616
+ this.log.debug(`Removing bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
617
+ // Check if the plugin is registered
618
+ const plugin = this.findPlugin(pluginName);
619
+ if (!plugin) {
620
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
621
+ return;
622
+ }
623
+ if (this.bridgeMode === 'childbridge' && !plugin.aggregator) {
624
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} aggregator not found`);
625
+ return;
626
+ }
627
+ if (this.bridgeMode === 'childbridge' && !plugin.connected) {
628
+ this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not connected`);
629
+ return;
630
+ }
631
+ // Register and add the device to matterbridge aggregator in bridge mode
632
+ if (this.bridgeMode === 'bridge') {
633
+ this.matterAggregator.removeBridgedDevice(device);
634
+ this.registeredDevices.forEach((registeredDevice, index) => {
635
+ if (registeredDevice.device === device) {
636
+ this.registeredDevices.splice(index, 1);
637
+ return;
638
+ }
639
+ });
640
+ if (plugin.registeredDevices !== undefined)
641
+ plugin.registeredDevices--;
642
+ if (plugin.addedDevices !== undefined)
643
+ plugin.addedDevices--;
644
+ this.log.info(`Rmoved bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
645
+ }
646
+ // Only register the device in childbridge mode
647
+ if (this.bridgeMode === 'childbridge') {
648
+ if (plugin.type === 'AccessoryPlatform') {
649
+ this.log.warn(`Removing bridged device ${dev}${device.name}-${device.deviceName}${wr} for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
650
+ }
651
+ else if (plugin.type === 'DynamicPlatform') {
652
+ this.registeredDevices.forEach((registeredDevice, index) => {
653
+ if (registeredDevice.device === device) {
654
+ this.registeredDevices.splice(index, 1);
655
+ return;
656
+ }
657
+ });
658
+ plugin.aggregator.removeBridgedDevice(device);
659
+ }
660
+ if (plugin.registeredDevices !== undefined)
661
+ plugin.registeredDevices--;
662
+ if (plugin.addedDevices !== undefined)
663
+ plugin.addedDevices--;
664
+ this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
665
+ }
666
+ }
667
+ /**
668
+ * Removes all bridged devices associated with a specific plugin.
669
+ *
670
+ * @param pluginName - The name of the plugin.
671
+ * @returns A promise that resolves when all devices have been removed.
672
+ */
673
+ async removeAllBridgedDevices(pluginName) {
674
+ const plugin = this.findPlugin(pluginName);
675
+ if (this.bridgeMode === 'childbridge' && plugin?.type === 'AccessoryPlatform') {
676
+ this.log.warn(`Removing devices for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
677
+ return;
678
+ }
679
+ const devicesToRemove = [];
680
+ for (const registeredDevice of this.registeredDevices) {
681
+ if (registeredDevice.plugin === pluginName) {
682
+ devicesToRemove.push(registeredDevice);
683
+ }
684
+ }
685
+ for (const registeredDevice of devicesToRemove) {
686
+ this.removeBridgedDevice(pluginName, registeredDevice.device);
551
687
  }
552
688
  }
553
689
  /**
@@ -846,10 +982,6 @@ export class Matterbridge {
846
982
  .forEach((registeredDevice) => {
847
983
  if (!plugin.storageContext)
848
984
  plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
849
- if (!plugin.storageContext) {
850
- this.log.error(`Error importing storage context for plugin ${plg}${plugin.name}${er}`);
851
- return;
852
- }
853
985
  if (!plugin.commissioningServer)
854
986
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
855
987
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
@@ -860,10 +992,6 @@ export class Matterbridge {
860
992
  }
861
993
  if (plugin.type === 'DynamicPlatform') {
862
994
  plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
863
- if (!plugin.storageContext) {
864
- this.log.error(`Error creating storage context for plugin ${plg}${plugin.name}${er}`);
865
- return;
866
- }
867
995
  plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
868
996
  this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
869
997
  plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
@@ -1130,7 +1258,7 @@ export class Matterbridge {
1130
1258
  if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
1131
1259
  for (const registeredDevice of this.registeredDevices) {
1132
1260
  if (registeredDevice.plugin === name) {
1133
- this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1261
+ this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
1134
1262
  if (!plugin.aggregator) {
1135
1263
  this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
1136
1264
  continue;
@@ -1138,7 +1266,9 @@ export class Matterbridge {
1138
1266
  plugin.aggregator.addBridgedDevice(registeredDevice.device);
1139
1267
  if (plugin.addedDevices !== undefined)
1140
1268
  plugin.addedDevices++;
1141
- this.log.info(`Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}${nf} for plugin ${plg}${plugin.name}${nf}`);
1269
+ this.log.info(
1270
+ // eslint-disable-next-line max-len
1271
+ `Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} for plugin ${plg}${plugin.name}${nf}`);
1142
1272
  registeredDevice.added = true;
1143
1273
  }
1144
1274
  }
@@ -1244,6 +1374,22 @@ export class Matterbridge {
1244
1374
  });
1245
1375
  });
1246
1376
  }
1377
+ /**
1378
+ * Retrieves the path to the global Node.js modules directory.
1379
+ * @returns A promise that resolves to the path of the global Node.js modules directory.
1380
+ */
1381
+ async getGlobalNodeModules() {
1382
+ return new Promise((resolve, reject) => {
1383
+ exec('npm root -g', (error, stdout) => {
1384
+ if (error) {
1385
+ reject(error);
1386
+ }
1387
+ else {
1388
+ resolve(stdout.trim());
1389
+ }
1390
+ });
1391
+ });
1392
+ }
1247
1393
  /**
1248
1394
  * Logs the node and system information.
1249
1395
  */
@@ -1304,7 +1450,7 @@ export class Matterbridge {
1304
1450
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
1305
1451
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
1306
1452
  // Global node_modules directory
1307
- this.globalModulesDir = execSync('npm root -g').toString().trim();
1453
+ this.globalModulesDir = await this.getGlobalNodeModules();
1308
1454
  this.log.debug(`Global node_modules Directory: ${this.globalModulesDir}`);
1309
1455
  // Create the data directory .matterbridge in the home directory
1310
1456
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
@@ -1513,7 +1659,7 @@ export class Matterbridge {
1513
1659
  res.json(data);
1514
1660
  });
1515
1661
  // Endpoint to receive commands
1516
- this.expressApp.post('/api/command/:command/:param', (req, res) => {
1662
+ this.expressApp.post('/api/command/:command/:param', async (req, res) => {
1517
1663
  const command = req.params.command;
1518
1664
  const param = req.params.param;
1519
1665
  this.log.debug(`The frontend sent /api/command/${command}/${param}`);
@@ -1522,7 +1668,7 @@ export class Matterbridge {
1522
1668
  return;
1523
1669
  }
1524
1670
  this.log.info(`***Received command: ${command}:${param}`);
1525
- // Handle the command debugLevel
1671
+ // Handle the command debugLevel from Settings
1526
1672
  if (command === 'setloglevel') {
1527
1673
  if (param === 'Debug') {
1528
1674
  this.log.setLogDebug(true);
@@ -1543,10 +1689,44 @@ export class Matterbridge {
1543
1689
  plugin.platform?.log.setLogDebug(this.debugEnabled);
1544
1690
  });
1545
1691
  }
1546
- // Handle the command debugLevel
1692
+ // Handle the command debugLevel from Header
1547
1693
  if (command === 'restart') {
1548
1694
  this.restartProcess();
1549
1695
  }
1696
+ // Handle the command update from Header
1697
+ if (command === 'update') {
1698
+ this.log.warn(`The /api/command/${command} is not yet implemented`);
1699
+ }
1700
+ // Handle the command addplugin from Header
1701
+ if (command === 'addplugin') {
1702
+ this.log.warn(`The /api/command/${command}/${param} is not yet implemented`);
1703
+ }
1704
+ // Handle the command enableplugin from Home
1705
+ if (command === 'enableplugin') {
1706
+ const plugin = this.findPlugin(param);
1707
+ if (plugin) {
1708
+ plugin.enabled = true;
1709
+ plugin.loaded = undefined;
1710
+ plugin.started = undefined;
1711
+ plugin.configured = undefined;
1712
+ plugin.connected = undefined;
1713
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1714
+ this.log.info(`Enabled plugin ${plg}${param}${nf}`);
1715
+ }
1716
+ }
1717
+ // Handle the command disableplugin from Home
1718
+ if (command === 'disableplugin') {
1719
+ const plugin = this.findPlugin(param);
1720
+ if (plugin) {
1721
+ plugin.enabled = false;
1722
+ plugin.loaded = undefined;
1723
+ plugin.started = undefined;
1724
+ plugin.configured = undefined;
1725
+ plugin.connected = undefined;
1726
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
1727
+ this.log.info(`Disabled plugin ${plg}${param}${nf}`);
1728
+ }
1729
+ }
1550
1730
  res.json({ message: 'Command received' });
1551
1731
  });
1552
1732
  // Fallback for routing