matterbridge 1.1.12 → 1.2.1
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 +22 -3
- package/README.md +52 -11
- package/dist/AirQualityCluster.d.ts.map +1 -1
- package/dist/AirQualityCluster.js +1 -10
- package/dist/AirQualityCluster.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/matterbridge.d.ts +89 -13
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +279 -53
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +3 -2
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.js +3 -2
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
- package/dist/matterbridgeDevice.d.ts +60 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +60 -0
- package/dist/matterbridgeDevice.js.map +1 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +3 -2
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
- package/dist/matterbridgeDynamicPlatform.js +3 -2
- package/dist/matterbridgeDynamicPlatform.js.map +1 -1
- package/dist/matterbridgePlatform.d.ts +10 -11
- package/dist/matterbridgePlatform.d.ts.map +1 -1
- package/dist/matterbridgePlatform.js +10 -58
- package/dist/matterbridgePlatform.js.map +1 -1
- package/frontend/build/asset-manifest.json +6 -6
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/css/{main.70102d98.css → main.61f6cf42.css} +2 -2
- package/frontend/build/static/css/main.61f6cf42.css.map +1 -0
- package/frontend/build/static/js/main.e3553a4d.js +3 -0
- package/frontend/build/static/js/main.e3553a4d.js.map +1 -0
- package/package.json +8 -5
- package/Screenshot devices page.png +0 -0
- package/Screenshot home page.png +0 -0
- package/dist/ColorControlServer.d.ts +0 -86
- package/dist/ColorControlServer.d.ts.map +0 -1
- package/dist/ColorControlServer.js +0 -102
- package/dist/ColorControlServer.js.map +0 -1
- package/frontend/build/static/css/main.70102d98.css.map +0 -1
- package/frontend/build/static/js/main.5d39b100.js +0 -3
- package/frontend/build/static/js/main.5d39b100.js.map +0 -1
- /package/frontend/build/static/js/{main.5d39b100.js.LICENSE.txt → main.e3553a4d.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file matterbridge.ts
|
|
5
5
|
* @author Luca Liguori
|
|
6
6
|
* @date 2023-12-29
|
|
7
|
-
* @version 1.
|
|
7
|
+
* @version 1.2.0
|
|
8
8
|
*
|
|
9
9
|
* Copyright 2023, 2024 Luca Liguori.
|
|
10
10
|
*
|
|
@@ -102,20 +102,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
102
102
|
/**
|
|
103
103
|
* Loads an instance of the Matterbridge class.
|
|
104
104
|
* If an instance already exists, return that instance.
|
|
105
|
-
*
|
|
105
|
+
*
|
|
106
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
107
|
+
* @returns The loaded Matterbridge instance.
|
|
106
108
|
*/
|
|
107
109
|
static async loadInstance(initialize = false) {
|
|
108
110
|
if (!Matterbridge.instance) {
|
|
109
111
|
// eslint-disable-next-line no-console
|
|
110
|
-
console.log('Matterbridge instance does not exists');
|
|
112
|
+
console.log(wr + 'Matterbridge instance does not exists!', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
111
113
|
Matterbridge.instance = new Matterbridge();
|
|
112
114
|
if (initialize)
|
|
113
115
|
await Matterbridge.instance.initialize();
|
|
114
116
|
}
|
|
115
|
-
else {
|
|
116
|
-
// eslint-disable-next-line no-console
|
|
117
|
-
console.log('Matterbridge instance already exists');
|
|
118
|
-
}
|
|
119
117
|
return Matterbridge.instance;
|
|
120
118
|
}
|
|
121
119
|
/**
|
|
@@ -129,10 +127,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
129
127
|
* @returns A Promise that resolves when the initialization is complete.
|
|
130
128
|
*/
|
|
131
129
|
async initialize() {
|
|
132
|
-
/*
|
|
133
|
-
const wtf = await import('wtfnode');
|
|
134
|
-
wtf.dump();
|
|
135
|
-
*/
|
|
136
130
|
// Display the help
|
|
137
131
|
if (hasParameter('help')) {
|
|
138
132
|
// eslint-disable-next-line no-console
|
|
@@ -154,28 +148,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
154
148
|
- disable [plugin name]: disable the globally installed plugin with the given name\n`);
|
|
155
149
|
process.exit(0);
|
|
156
150
|
}
|
|
157
|
-
//
|
|
151
|
+
// Set Matterbridge logger
|
|
158
152
|
if (hasParameter('debug'))
|
|
159
153
|
this.debugEnabled = true;
|
|
160
154
|
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
|
|
161
155
|
this.log.debug('Matterbridge is starting...');
|
|
162
|
-
// log system info and create .matterbridge directory
|
|
163
|
-
await this.logNodeAndSystemInfo();
|
|
164
|
-
this.log.info(
|
|
165
|
-
// eslint-disable-next-line max-len
|
|
166
|
-
`Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
|
|
167
|
-
// check node version and throw error
|
|
168
|
-
requireMinNodeVersion(18);
|
|
169
|
-
// register SIGINT SIGTERM signal handlers
|
|
170
|
-
this.registerSignalHandlers();
|
|
171
|
-
// set matter.js logger level and format
|
|
172
|
-
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
173
|
-
Logger.format = Format.ANSI;
|
|
174
156
|
// Initialize NodeStorage
|
|
157
|
+
this.homeDirectory = os.homedir();
|
|
158
|
+
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
175
159
|
this.log.debug('Creating node storage manager');
|
|
176
160
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
|
|
177
161
|
this.log.debug('Creating node storage context for matterbridge');
|
|
178
162
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
163
|
+
// Get the plugins from node storage
|
|
179
164
|
this.registeredPlugins = await this.nodeContext.get('plugins', []);
|
|
180
165
|
for (const plugin of this.registeredPlugins) {
|
|
181
166
|
this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
|
|
@@ -187,6 +172,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
187
172
|
await plugin.nodeContext.set('description', plugin.description);
|
|
188
173
|
await plugin.nodeContext.set('author', plugin.author);
|
|
189
174
|
}
|
|
175
|
+
if (hasParameter('logstorage')) {
|
|
176
|
+
await this.nodeContext.logStorage();
|
|
177
|
+
for (const plugin of this.registeredPlugins) {
|
|
178
|
+
await plugin.nodeContext?.logStorage();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Log system info and create .matterbridge directory
|
|
182
|
+
await this.logNodeAndSystemInfo();
|
|
183
|
+
this.log.info(
|
|
184
|
+
// eslint-disable-next-line max-len
|
|
185
|
+
`Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
|
|
186
|
+
// Check node version and throw error
|
|
187
|
+
requireMinNodeVersion(18);
|
|
188
|
+
// Register SIGINT SIGTERM signal handlers
|
|
189
|
+
this.registerSignalHandlers();
|
|
190
|
+
// Set matter.js logger level and format
|
|
191
|
+
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
192
|
+
Logger.format = Format.ANSI;
|
|
193
|
+
// Initialize NodeStorage
|
|
194
|
+
/*
|
|
195
|
+
this.log.debug('Creating node storage manager');
|
|
196
|
+
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
|
|
197
|
+
this.log.debug('Creating node storage context for matterbridge');
|
|
198
|
+
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
199
|
+
// Get the plugins from node storage
|
|
200
|
+
this.registeredPlugins = await this.nodeContext.get<RegisteredPlugin[]>('plugins', []);
|
|
201
|
+
for (const plugin of this.registeredPlugins) {
|
|
202
|
+
this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
|
|
203
|
+
plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
|
|
204
|
+
await plugin.nodeContext.set<string>('name', plugin.name);
|
|
205
|
+
await plugin.nodeContext.set<string>('type', plugin.type);
|
|
206
|
+
await plugin.nodeContext.set<string>('path', plugin.path);
|
|
207
|
+
await plugin.nodeContext.set<string>('version', plugin.version);
|
|
208
|
+
await plugin.nodeContext.set<string>('description', plugin.description);
|
|
209
|
+
await plugin.nodeContext.set<string>('author', plugin.author);
|
|
210
|
+
}
|
|
211
|
+
*/
|
|
190
212
|
// Parse command line
|
|
191
213
|
this.parseCommandLine();
|
|
192
214
|
}
|
|
@@ -282,12 +304,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
282
304
|
this.bridgeMode = 'childbridge';
|
|
283
305
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
284
306
|
await this.testStartMatterBridge(); // No await do it asyncronously
|
|
307
|
+
return;
|
|
285
308
|
}
|
|
286
309
|
if (hasParameter('controller')) {
|
|
287
310
|
this.bridgeMode = 'controller';
|
|
288
311
|
this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
|
|
289
312
|
this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
|
|
290
313
|
await this.startMatterBridge();
|
|
314
|
+
return;
|
|
291
315
|
}
|
|
292
316
|
if (hasParameter('bridge')) {
|
|
293
317
|
this.bridgeMode = 'bridge';
|
|
@@ -306,6 +330,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
306
330
|
this.loadPlugin(plugin); // No await do it asyncronously
|
|
307
331
|
}
|
|
308
332
|
await this.startMatterBridge();
|
|
333
|
+
return;
|
|
309
334
|
}
|
|
310
335
|
if (hasParameter('childbridge')) {
|
|
311
336
|
this.bridgeMode = 'childbridge';
|
|
@@ -324,8 +349,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
324
349
|
this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
325
350
|
}
|
|
326
351
|
await this.startMatterBridge();
|
|
352
|
+
return;
|
|
327
353
|
}
|
|
328
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Resolves the name of a plugin by loading and parsing its package.json file.
|
|
357
|
+
* @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
|
|
358
|
+
* @returns The path to the resolved package.json file, or null if the package.json file is not found or does not contain a name.
|
|
359
|
+
*/
|
|
329
360
|
async resolvePluginName(pluginPath) {
|
|
330
361
|
if (!pluginPath.endsWith('package.json'))
|
|
331
362
|
pluginPath = path.join(pluginPath, 'package.json');
|
|
@@ -451,14 +482,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
451
482
|
* Restarts the process by spawning a new process and exiting the current process.
|
|
452
483
|
*/
|
|
453
484
|
async restartProcess() {
|
|
454
|
-
//this.log.info('Restarting still not implemented');
|
|
455
|
-
//return;
|
|
456
485
|
await this.cleanup('restarting...', true);
|
|
457
486
|
this.hasCleanupStarted = false;
|
|
458
487
|
}
|
|
459
488
|
/**
|
|
460
|
-
*
|
|
461
|
-
* @param message - The
|
|
489
|
+
* Cleans up the Matterbridge instance.
|
|
490
|
+
* @param message - The cleanup message.
|
|
491
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
492
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
462
493
|
*/
|
|
463
494
|
async cleanup(message, restart = false) {
|
|
464
495
|
if (!this.hasCleanupStarted) {
|
|
@@ -468,10 +499,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
468
499
|
process.removeAllListeners('SIGTERM');
|
|
469
500
|
// Calling the shutdown functions with a reason
|
|
470
501
|
for (const plugin of this.registeredPlugins) {
|
|
471
|
-
if (plugin.platform)
|
|
502
|
+
if (plugin.platform) {
|
|
472
503
|
await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
|
|
473
|
-
|
|
474
|
-
|
|
504
|
+
await this.savePluginConfig(plugin);
|
|
505
|
+
}
|
|
475
506
|
}
|
|
476
507
|
// Set reachability to false
|
|
477
508
|
/*
|
|
@@ -631,6 +662,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
631
662
|
this.log.info(`Registered bridged device ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
632
663
|
}
|
|
633
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* Removes a bridged device from the Matterbridge.
|
|
667
|
+
* @param pluginName - The name of the plugin.
|
|
668
|
+
* @param device - The device to be removed.
|
|
669
|
+
* @returns A Promise that resolves when the device is successfully removed.
|
|
670
|
+
*/
|
|
634
671
|
async removeBridgedDevice(pluginName, device) {
|
|
635
672
|
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
636
673
|
this.log.error(`Removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -647,12 +684,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
647
684
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${dev}${device.name}${er}) plugin ${plg}${pluginName}${er} aggregator not found`);
|
|
648
685
|
return;
|
|
649
686
|
}
|
|
650
|
-
/*
|
|
651
687
|
if (this.bridgeMode === 'childbridge' && !plugin.connected) {
|
|
652
|
-
|
|
653
|
-
|
|
688
|
+
this.log.warn(`Error removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) plugin ${plg}${pluginName}${wr} not connected`);
|
|
689
|
+
return;
|
|
654
690
|
}
|
|
655
|
-
*/
|
|
656
691
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
657
692
|
if (this.bridgeMode === 'bridge') {
|
|
658
693
|
this.matterAggregator.removeBridgedDevice(device);
|
|
@@ -662,11 +697,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
662
697
|
return;
|
|
663
698
|
}
|
|
664
699
|
});
|
|
700
|
+
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
665
701
|
if (plugin.registeredDevices !== undefined)
|
|
666
702
|
plugin.registeredDevices--;
|
|
667
703
|
if (plugin.addedDevices !== undefined)
|
|
668
704
|
plugin.addedDevices--;
|
|
669
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
670
705
|
}
|
|
671
706
|
// Only register the device in childbridge mode
|
|
672
707
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -830,6 +865,92 @@ export class Matterbridge extends EventEmitter {
|
|
|
830
865
|
}
|
|
831
866
|
*/
|
|
832
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Loads the configuration for a plugin.
|
|
870
|
+
* If the configuration file exists, it reads the file and returns the parsed JSON data.
|
|
871
|
+
* If the configuration file does not exist, it creates a new file with default configuration and returns it.
|
|
872
|
+
* If any error occurs during file access or creation, it logs an error and rejects the promise with the error.
|
|
873
|
+
*
|
|
874
|
+
* @param plugin - The plugin for which to load the configuration.
|
|
875
|
+
* @returns A promise that resolves to the loaded or created configuration.
|
|
876
|
+
*/
|
|
877
|
+
async loadPluginConfig(plugin) {
|
|
878
|
+
const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
|
|
879
|
+
try {
|
|
880
|
+
await fs.access(configFile);
|
|
881
|
+
const data = await fs.readFile(configFile, 'utf8');
|
|
882
|
+
this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, JSON.parse(data));
|
|
883
|
+
return JSON.parse(data);
|
|
884
|
+
}
|
|
885
|
+
catch (err) {
|
|
886
|
+
if (err instanceof Error) {
|
|
887
|
+
const nodeErr = err;
|
|
888
|
+
if (nodeErr.code === 'ENOENT') {
|
|
889
|
+
try {
|
|
890
|
+
await this.writeFile(configFile, JSON.stringify({ name: plugin.name, type: plugin.type }, null, 2));
|
|
891
|
+
this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, { name: plugin.name, type: plugin.type });
|
|
892
|
+
return { name: plugin.name, type: plugin.type };
|
|
893
|
+
}
|
|
894
|
+
catch (err) {
|
|
895
|
+
this.log.error(`Error creating config file ${configFile}: ${err}`);
|
|
896
|
+
return Promise.reject(err);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
this.log.error(`Error accessing config file ${configFile}: ${err}`);
|
|
901
|
+
return Promise.reject(err);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return Promise.reject(err);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Saves the configuration of a registered plugin.
|
|
909
|
+
* @param {RegisteredPlugin} plugin - The plugin whose configuration needs to be saved.
|
|
910
|
+
* @returns {Promise<void>} - A promise that resolves when the configuration is successfully saved.
|
|
911
|
+
* @throws {Error} - If the plugin's configuration is not found or if there is an error while saving the configuration.
|
|
912
|
+
*/
|
|
913
|
+
async savePluginConfig(plugin) {
|
|
914
|
+
if (!plugin.platform?.config) {
|
|
915
|
+
this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`);
|
|
916
|
+
return Promise.reject(new Error(`Error saving plugin ${plg}${plugin.name}${er} config: config not found`));
|
|
917
|
+
}
|
|
918
|
+
const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
|
|
919
|
+
try {
|
|
920
|
+
await this.writeFile(configFile, JSON.stringify(plugin.platform.config, null, 2));
|
|
921
|
+
this.log.debug(`Saved config file: ${configFile}.\nConfig:${rs}\n`, plugin.platform.config);
|
|
922
|
+
}
|
|
923
|
+
catch (err) {
|
|
924
|
+
this.log.error(`Error saving plugin ${plg}${plugin.name}${er} config: ${err}`);
|
|
925
|
+
return Promise.reject(err);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Writes data to a file.
|
|
930
|
+
*
|
|
931
|
+
* @param {string} filePath - The path of the file to write to.
|
|
932
|
+
* @param {string} data - The data to write to the file.
|
|
933
|
+
* @returns {Promise<void>} - A promise that resolves when the data is successfully written to the file.
|
|
934
|
+
*/
|
|
935
|
+
async writeFile(filePath, data) {
|
|
936
|
+
// Write the data to a file
|
|
937
|
+
await fs
|
|
938
|
+
.writeFile(`${filePath}`, data, 'utf8')
|
|
939
|
+
.then(() => {
|
|
940
|
+
this.log.debug(`Successfully wrote to ${filePath}`);
|
|
941
|
+
})
|
|
942
|
+
.catch((error) => {
|
|
943
|
+
this.log.error(`Error writing to ${filePath}:`, error);
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Starts a plugin.
|
|
948
|
+
*
|
|
949
|
+
* @param {RegisteredPlugin} plugin - The plugin to start.
|
|
950
|
+
* @param {string} [message] - Optional message to pass to the plugin's onStart method.
|
|
951
|
+
* @param {boolean} [configure=false] - Indicates whether to configure the plugin after starting.
|
|
952
|
+
* @returns {Promise<void>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
|
|
953
|
+
*/
|
|
833
954
|
async startPlugin(plugin, message, configure = false) {
|
|
834
955
|
if (!plugin.loaded || !plugin.platform) {
|
|
835
956
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`);
|
|
@@ -860,6 +981,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
860
981
|
return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
861
982
|
}
|
|
862
983
|
}
|
|
984
|
+
/**
|
|
985
|
+
* Configures a plugin.
|
|
986
|
+
*
|
|
987
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
988
|
+
* @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
|
|
989
|
+
*/
|
|
863
990
|
async configurePlugin(plugin) {
|
|
864
991
|
if (!plugin.loaded || !plugin.started || !plugin.platform) {
|
|
865
992
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`);
|
|
@@ -888,6 +1015,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
888
1015
|
return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
889
1016
|
}
|
|
890
1017
|
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Loads a plugin and returns the corresponding MatterbridgePlatform instance.
|
|
1020
|
+
* @param plugin - The plugin to load.
|
|
1021
|
+
* @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
|
|
1022
|
+
* @param message - Optional message to pass to the plugin when starting.
|
|
1023
|
+
* @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
|
|
1024
|
+
* @throws An error if the plugin is not enabled, already loaded, or fails to load.
|
|
1025
|
+
*/
|
|
891
1026
|
async loadPlugin(plugin, start = false, message = '') {
|
|
892
1027
|
if (!plugin.enabled) {
|
|
893
1028
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
|
|
@@ -910,8 +1045,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
910
1045
|
this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
|
|
911
1046
|
// Call the default export function of the plugin, passing this MatterBridge instance
|
|
912
1047
|
if (pluginInstance.default) {
|
|
913
|
-
const
|
|
1048
|
+
const config = await this.loadPluginConfig(plugin);
|
|
1049
|
+
const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }), config);
|
|
914
1050
|
platform.name = packageJson.name;
|
|
1051
|
+
platform.config = config;
|
|
915
1052
|
plugin.name = packageJson.name;
|
|
916
1053
|
plugin.description = packageJson.description;
|
|
917
1054
|
plugin.version = packageJson.version;
|
|
@@ -922,7 +1059,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
922
1059
|
plugin.registeredDevices = 0;
|
|
923
1060
|
plugin.addedDevices = 0;
|
|
924
1061
|
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
925
|
-
await platform.loadConfig();
|
|
926
1062
|
this.log.info(`Loaded plugin ${plg}${plugin.name}${db} type ${typ}${platform.type}${db} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
|
|
927
1063
|
if (start)
|
|
928
1064
|
this.startPlugin(plugin, message); // No await do it asyncronously
|
|
@@ -1113,6 +1249,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1113
1249
|
}, 1000);
|
|
1114
1250
|
}
|
|
1115
1251
|
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Starts the Matter server.
|
|
1254
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
1255
|
+
*/
|
|
1116
1256
|
async startMatterServer() {
|
|
1117
1257
|
if (!this.matterServer) {
|
|
1118
1258
|
this.log.error('No matter server initialized');
|
|
@@ -1182,15 +1322,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1182
1322
|
return storageContext;
|
|
1183
1323
|
}
|
|
1184
1324
|
/**
|
|
1185
|
-
* Shows the commissioning QR code for a given
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
*
|
|
1189
|
-
*
|
|
1190
|
-
*
|
|
1191
|
-
* @param commissioningServer - The commissioning server to show the QR code for.
|
|
1192
|
-
* @param storageContext - The storage context to store the pairing codes.
|
|
1193
|
-
* @param pluginName - The name of the commissioning server.
|
|
1325
|
+
* Shows the commissioning QR code for a given plugin.
|
|
1326
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
1327
|
+
* @param {StorageContext} storageContext - The storage context instance.
|
|
1328
|
+
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
1329
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1330
|
+
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
1194
1331
|
*/
|
|
1195
1332
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
1196
1333
|
if (!commissioningServer || !storageContext || !pluginName)
|
|
@@ -1207,6 +1344,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1207
1344
|
if (pluginName !== 'Matterbridge') {
|
|
1208
1345
|
const plugin = this.findPlugin(pluginName);
|
|
1209
1346
|
if (plugin) {
|
|
1347
|
+
plugin.qrPairingCode = qrPairingCode;
|
|
1348
|
+
plugin.manualPairingCode = manualPairingCode;
|
|
1210
1349
|
plugin.paired = false;
|
|
1211
1350
|
}
|
|
1212
1351
|
}
|
|
@@ -1528,9 +1667,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1528
1667
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
1529
1668
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1530
1669
|
// Global node_modules directory
|
|
1670
|
+
/*
|
|
1531
1671
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
1532
1672
|
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1533
1673
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1674
|
+
*/
|
|
1675
|
+
if (this.nodeContext)
|
|
1676
|
+
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1677
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1678
|
+
this.getGlobalNodeModules()
|
|
1679
|
+
.then(async (globalModulesDirectory) => {
|
|
1680
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
1681
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1682
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1683
|
+
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
1684
|
+
})
|
|
1685
|
+
.catch((error) => {
|
|
1686
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1687
|
+
});
|
|
1534
1688
|
// Create the data directory .matterbridge in the home directory
|
|
1535
1689
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
1536
1690
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
@@ -1584,12 +1738,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1584
1738
|
this.matterbridgeVersion = packageJson.version;
|
|
1585
1739
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
1586
1740
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1741
|
+
// Matterbridge latest version
|
|
1742
|
+
/*
|
|
1587
1743
|
this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
|
|
1588
1744
|
this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
|
|
1589
1745
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1746
|
+
*/
|
|
1747
|
+
if (this.nodeContext)
|
|
1748
|
+
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
1749
|
+
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1750
|
+
this.getLatestVersion('matterbridge')
|
|
1751
|
+
.then(async (matterbridgeLatestVersion) => {
|
|
1752
|
+
this.matterbridgeLatestVersion = matterbridgeLatestVersion;
|
|
1753
|
+
this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
|
|
1754
|
+
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1755
|
+
await this.nodeContext?.set('matterbridgeLatestVersion', this.matterbridgeLatestVersion);
|
|
1756
|
+
if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
|
|
1757
|
+
this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
|
|
1758
|
+
}
|
|
1759
|
+
})
|
|
1760
|
+
.catch((error) => {
|
|
1761
|
+
this.log.error(`Error getting Matterbridge latest version: ${error}`);
|
|
1762
|
+
});
|
|
1593
1763
|
// Current working directory
|
|
1594
1764
|
const currentDir = process.cwd();
|
|
1595
1765
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
@@ -1597,6 +1767,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1597
1767
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1598
1768
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1599
1769
|
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Retrieves an array of base registered plugins.
|
|
1772
|
+
*
|
|
1773
|
+
* @returns {BaseRegisteredPlugin[]} An array of base registered plugins.
|
|
1774
|
+
*/
|
|
1600
1775
|
getBaseRegisteredPlugins() {
|
|
1601
1776
|
const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
|
|
1602
1777
|
path: plugin.path,
|
|
@@ -1617,6 +1792,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1617
1792
|
}));
|
|
1618
1793
|
return baseRegisteredPlugins;
|
|
1619
1794
|
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Retrieves an array of base registered devices from the registered plugins.
|
|
1797
|
+
* @returns {BaseRegisteredPlugin[]} An array of base registered devices.
|
|
1798
|
+
*/
|
|
1620
1799
|
getBaseRegisteredDevices() {
|
|
1621
1800
|
const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
|
|
1622
1801
|
path: plugin.path,
|
|
@@ -1648,6 +1827,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1648
1827
|
this.expressApp.get('/api/qr-code', (req, res) => {
|
|
1649
1828
|
this.log.debug('The frontend sent /api/qr-code');
|
|
1650
1829
|
if (!this.matterbridgeContext) {
|
|
1830
|
+
this.log.error('/api/qr-code matterbridgeContext not found');
|
|
1651
1831
|
res.json([]);
|
|
1652
1832
|
return;
|
|
1653
1833
|
}
|
|
@@ -1751,7 +1931,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1751
1931
|
// Endpoint to receive commands
|
|
1752
1932
|
this.expressApp.post('/api/command/:command/:param', async (req, res) => {
|
|
1753
1933
|
const command = req.params.command;
|
|
1754
|
-
|
|
1934
|
+
let param = req.params.param;
|
|
1755
1935
|
this.log.debug(`The frontend sent /api/command/${command}/${param}`);
|
|
1756
1936
|
if (!command) {
|
|
1757
1937
|
res.status(400).json({ error: 'No command provided' });
|
|
@@ -1787,9 +1967,55 @@ export class Matterbridge extends EventEmitter {
|
|
|
1787
1967
|
if (command === 'update') {
|
|
1788
1968
|
this.log.warn(`The /api/command/${command} is not yet implemented`);
|
|
1789
1969
|
}
|
|
1790
|
-
// Handle the command addplugin from
|
|
1970
|
+
// Handle the command addplugin from Home C:\Users\lligu\GitHub\matterbridge-example-accessory-platform
|
|
1791
1971
|
if (command === 'addplugin') {
|
|
1792
|
-
|
|
1972
|
+
param = param.replace(/\*/g, '\\');
|
|
1973
|
+
if (this.registeredPlugins.find((plugin) => plugin.name === param)) {
|
|
1974
|
+
this.log.warn(`Plugin ${plg}${param}${wr} already added to matterbridge`);
|
|
1975
|
+
res.json({ message: 'Command received' });
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
const packageJsonPath = await this.resolvePluginName(param);
|
|
1979
|
+
if (!packageJsonPath) {
|
|
1980
|
+
this.log.error(`Error resolving plugin ${plg}${param}${er}`);
|
|
1981
|
+
res.json({ message: 'Command received' });
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
this.log.debug(`Loading plugin from ${plg}${packageJsonPath}${db}`);
|
|
1985
|
+
try {
|
|
1986
|
+
// Load the package.json of the plugin
|
|
1987
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
1988
|
+
const plugin = { path: packageJsonPath, type: '', name: packageJson.name, version: packageJson.version, description: packageJson.description, author: packageJson.author, enabled: true };
|
|
1989
|
+
if (await this.loadPlugin(plugin)) {
|
|
1990
|
+
this.registeredPlugins.push(plugin);
|
|
1991
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
1992
|
+
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge. Restart required.`);
|
|
1993
|
+
}
|
|
1994
|
+
else {
|
|
1995
|
+
this.log.error(`Error adding plugin ${plg}${packageJsonPath}${er}`);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
catch (error) {
|
|
1999
|
+
this.log.error(`Error adding plugin ${plg}${packageJsonPath}${er}`);
|
|
2000
|
+
res.json({ message: 'Command received' });
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
// Handle the command removeplugin from Home
|
|
2005
|
+
if (command === 'removeplugin') {
|
|
2006
|
+
const index = this.registeredPlugins.findIndex((registeredPlugin) => registeredPlugin.name === param);
|
|
2007
|
+
if (index !== -1) {
|
|
2008
|
+
if (this.registeredPlugins[index].platform) {
|
|
2009
|
+
await this.registeredPlugins[index].platform?.onShutdown('The plugin has been removed.');
|
|
2010
|
+
await this.savePluginConfig(this.registeredPlugins[index]);
|
|
2011
|
+
}
|
|
2012
|
+
this.registeredPlugins.splice(index, 1);
|
|
2013
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
2014
|
+
this.log.info(`Plugin ${plg}${param}${nf} removed from matterbridge`);
|
|
2015
|
+
}
|
|
2016
|
+
else {
|
|
2017
|
+
this.log.warn(`Plugin ${plg}${param}${wr} not registered in matterbridge`);
|
|
2018
|
+
}
|
|
1793
2019
|
}
|
|
1794
2020
|
// Handle the command enableplugin from Home
|
|
1795
2021
|
if (command === 'enableplugin') {
|