matterbridge 1.1.4 → 1.1.6
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 +20 -0
- package/README.md +12 -9
- package/dist/AirQualityCluster.d.ts.map +1 -1
- package/dist/AirQualityCluster.js +1 -7
- package/dist/AirQualityCluster.js.map +1 -1
- package/dist/TvocCluster.d.ts.map +1 -1
- package/dist/TvocCluster.js +1 -2
- package/dist/TvocCluster.js.map +1 -1
- package/dist/matterbridge.d.ts +14 -5
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +408 -236
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +7 -13
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.js +9 -18
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
- package/dist/matterbridgeController.d.ts +23 -1
- package/dist/matterbridgeController.d.ts.map +1 -1
- package/dist/matterbridgeController.js +364 -293
- package/dist/matterbridgeController.js.map +1 -1
- package/dist/matterbridgeDevice.d.ts +16 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +34 -13
- package/dist/matterbridgeDevice.js.map +1 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +7 -13
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
- package/dist/matterbridgeDynamicPlatform.js +9 -18
- package/dist/matterbridgeDynamicPlatform.js.map +1 -1
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/manifest.json +2 -2
- package/frontend/build/matterbridge 32x32.png +0 -0
- package/frontend/build/matterbridge 64x64.png +0 -0
- package/frontend/build/static/js/{main.b5a876cf.js → main.e5888ebb.js} +3 -3
- package/frontend/build/static/js/main.e5888ebb.js.map +1 -0
- package/package.json +71 -69
- package/frontend/build/static/js/main.b5a876cf.js.map +0 -1
- /package/frontend/build/static/js/{main.b5a876cf.js.LICENSE.txt → main.e5888ebb.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
* See the License for the specific language governing permissions and
|
|
21
21
|
* limitations under the License. *
|
|
22
22
|
*/
|
|
23
|
+
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
23
24
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
24
|
-
import { AnsiLogger, BRIGHT,
|
|
25
|
+
import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr } from 'node-ansi-logger';
|
|
25
26
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
26
27
|
import { promises as fs } from 'fs';
|
|
27
28
|
import express from 'express';
|
|
@@ -38,6 +39,7 @@ import { requireMinNodeVersion, getParameter, getIntParameter, hasParameter } fr
|
|
|
38
39
|
import { CryptoNode } from '@project-chip/matter-node.js/crypto';
|
|
39
40
|
const plg = '\u001B[38;5;33m';
|
|
40
41
|
const dev = '\u001B[38;5;79m';
|
|
42
|
+
const typ = '\u001B[38;5;207m';
|
|
41
43
|
/**
|
|
42
44
|
* Represents the Matterbridge application.
|
|
43
45
|
*/
|
|
@@ -55,16 +57,18 @@ export class Matterbridge {
|
|
|
55
57
|
freeMemory: '',
|
|
56
58
|
systemUptime: '',
|
|
57
59
|
};
|
|
58
|
-
homeDirectory;
|
|
59
|
-
rootDirectory;
|
|
60
|
-
matterbridgeDirectory;
|
|
60
|
+
homeDirectory = '';
|
|
61
|
+
rootDirectory = '';
|
|
62
|
+
matterbridgeDirectory = '';
|
|
63
|
+
matterbridgeVersion = '';
|
|
61
64
|
bridgeMode = '';
|
|
65
|
+
debugEnabled = false;
|
|
62
66
|
log;
|
|
63
67
|
hasCleanupStarted = false;
|
|
64
68
|
registeredPlugins = [];
|
|
65
69
|
registeredDevices = [];
|
|
66
|
-
nodeStorage
|
|
67
|
-
nodeContext
|
|
70
|
+
nodeStorage;
|
|
71
|
+
nodeContext;
|
|
68
72
|
app;
|
|
69
73
|
storageManager;
|
|
70
74
|
matterbridgeContext;
|
|
@@ -79,7 +83,7 @@ export class Matterbridge {
|
|
|
79
83
|
}
|
|
80
84
|
/**
|
|
81
85
|
* Loads an instance of the Matterbridge class.
|
|
82
|
-
* If an instance already exists,
|
|
86
|
+
* If an instance already exists, return that instance.
|
|
83
87
|
* @returns The loaded instance of the Matterbridge class.
|
|
84
88
|
*/
|
|
85
89
|
static async loadInstance(cli = false) {
|
|
@@ -118,6 +122,7 @@ export class Matterbridge {
|
|
|
118
122
|
- bridge: start Matterbridge in bridge mode
|
|
119
123
|
- childbridge: start Matterbridge in childbridge mode
|
|
120
124
|
- frontend [port]: start the frontend on the given port (default 3000)
|
|
125
|
+
- debug: enable debug mode (default false)
|
|
121
126
|
- list: list the registered plugins
|
|
122
127
|
- add [plugin path]: register the plugin
|
|
123
128
|
- remove [plugin path]: remove the plugin
|
|
@@ -126,29 +131,38 @@ export class Matterbridge {
|
|
|
126
131
|
process.exit(0);
|
|
127
132
|
}
|
|
128
133
|
// set Matterbridge logger
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
if (hasParameter('debug'))
|
|
135
|
+
this.debugEnabled = true;
|
|
136
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
|
|
137
|
+
this.log.debug('Matterbridge is starting...');
|
|
131
138
|
// log system info and create .matterbridge directory
|
|
132
139
|
await this.logNodeAndSystemInfo();
|
|
140
|
+
this.log.info(
|
|
141
|
+
// eslint-disable-next-line max-len
|
|
142
|
+
`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}`);
|
|
133
143
|
// check node version and throw error
|
|
134
144
|
requireMinNodeVersion(18);
|
|
135
145
|
// register SIGINT SIGTERM signal handlers
|
|
136
146
|
this.registerSignalHandlers();
|
|
137
147
|
// set matter.js logger level and format
|
|
138
|
-
Logger.defaultLogLevel = Level.DEBUG;
|
|
148
|
+
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
139
149
|
Logger.format = Format.ANSI;
|
|
140
150
|
// Initialize NodeStorage
|
|
141
151
|
this.log.debug('Creating node storage manager');
|
|
142
|
-
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage') });
|
|
152
|
+
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
|
|
143
153
|
this.log.debug('Creating node storage context for matterbridge');
|
|
144
154
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
145
155
|
this.registeredPlugins = await this.nodeContext.get('plugins', []);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
for (const plugin of this.registeredPlugins) {
|
|
157
|
+
this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
|
|
158
|
+
plugin.nodeContext = await this.nodeStorage?.createStorage(plugin.name);
|
|
159
|
+
await plugin.nodeContext?.set('name', plugin.name);
|
|
160
|
+
await plugin.nodeContext?.set('type', plugin.type);
|
|
161
|
+
await plugin.nodeContext?.set('path', plugin.path);
|
|
162
|
+
await plugin.nodeContext?.set('version', plugin.version);
|
|
163
|
+
await plugin.nodeContext?.set('description', plugin.description);
|
|
164
|
+
await plugin.nodeContext?.set('author', plugin.author);
|
|
165
|
+
}
|
|
152
166
|
// Parse command line
|
|
153
167
|
this.parseCommandLine();
|
|
154
168
|
}
|
|
@@ -160,31 +174,36 @@ export class Matterbridge {
|
|
|
160
174
|
async parseCommandLine() {
|
|
161
175
|
if (hasParameter('list')) {
|
|
162
176
|
this.log.info('Registered plugins:');
|
|
163
|
-
this.registeredPlugins.forEach((plugin) => {
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
this.registeredPlugins.forEach((plugin, index) => {
|
|
178
|
+
if (index === this.registeredPlugins.length - 1) {
|
|
179
|
+
this.log.info(`└─┬─ ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} ${YELLOW}${plugin.enabled ? 'enabled' : 'disabled'}${nf}`);
|
|
180
|
+
this.log.info(` └─ ${db}${plugin.path}${db}`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.log.info(`├─┬─ ${plg}${plugin.name}${nf}: "${plg}${BRIGHT}${plugin.description}${RESET}${nf}" type: ${typ}${plugin.type}${nf} ${YELLOW}${plugin.enabled ? 'enabled' : 'disabled'}${nf}`);
|
|
184
|
+
this.log.info(`│ └─ ${db}${plugin.path}${db}`);
|
|
185
|
+
}
|
|
167
186
|
});
|
|
168
187
|
process.exit(0);
|
|
169
188
|
}
|
|
170
189
|
if (getParameter('add')) {
|
|
171
190
|
this.log.debug(`Registering plugin ${getParameter('add')}`);
|
|
172
|
-
await this.
|
|
191
|
+
await this.executeCommandLine(getParameter('add'), 'add');
|
|
173
192
|
process.exit(0);
|
|
174
193
|
}
|
|
175
194
|
if (getParameter('remove')) {
|
|
176
195
|
this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
|
|
177
|
-
await this.
|
|
196
|
+
await this.executeCommandLine(getParameter('remove'), 'remove');
|
|
178
197
|
process.exit(0);
|
|
179
198
|
}
|
|
180
199
|
if (getParameter('enable')) {
|
|
181
200
|
this.log.debug(`Enable plugin ${getParameter('enable')}`);
|
|
182
|
-
await this.
|
|
201
|
+
await this.executeCommandLine(getParameter('enable'), 'enable');
|
|
183
202
|
process.exit(0);
|
|
184
203
|
}
|
|
185
204
|
if (getParameter('disable')) {
|
|
186
205
|
this.log.debug(`Disable plugin ${getParameter('disable')}`);
|
|
187
|
-
await this.
|
|
206
|
+
await this.executeCommandLine(getParameter('disable'), 'disable');
|
|
188
207
|
process.exit(0);
|
|
189
208
|
}
|
|
190
209
|
// Start the storage (we need it now for frontend and later for matterbridge)
|
|
@@ -193,128 +212,112 @@ export class Matterbridge {
|
|
|
193
212
|
this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
194
213
|
// Initialize frontend
|
|
195
214
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
196
|
-
if (hasParameter('
|
|
215
|
+
if (hasParameter('test')) {
|
|
197
216
|
this.bridgeMode = 'childbridge';
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return;
|
|
201
|
-
this.log.info(`Loading registered plugin ${plg}${plugin.name}${nf} type ${GREEN}${plugin.type}${nf}`);
|
|
202
|
-
await this.loadPlugin(plugin.path, 'load');
|
|
203
|
-
});
|
|
204
|
-
await this.startMatterBridge();
|
|
217
|
+
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
218
|
+
this.testStartMatterBridge(); // No await do it asyncronously
|
|
205
219
|
}
|
|
206
220
|
if (hasParameter('bridge')) {
|
|
207
221
|
this.bridgeMode = 'bridge';
|
|
208
|
-
|
|
222
|
+
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
223
|
+
for (const plugin of this.registeredPlugins) {
|
|
209
224
|
if (!plugin.enabled)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
continue;
|
|
226
|
+
plugin.loaded = false;
|
|
227
|
+
plugin.started = false;
|
|
228
|
+
plugin.configured = false;
|
|
229
|
+
plugin.connected = undefined;
|
|
230
|
+
this.loadPlugin(plugin); // No await do it asyncronously
|
|
231
|
+
}
|
|
232
|
+
await this.startMatterBridge();
|
|
233
|
+
}
|
|
234
|
+
if (hasParameter('childbridge')) {
|
|
235
|
+
this.bridgeMode = 'childbridge';
|
|
236
|
+
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
237
|
+
for (const plugin of this.registeredPlugins) {
|
|
238
|
+
if (!plugin.enabled)
|
|
239
|
+
continue;
|
|
240
|
+
plugin.loaded = false;
|
|
241
|
+
plugin.started = false;
|
|
242
|
+
plugin.configured = false;
|
|
243
|
+
plugin.connected = false;
|
|
244
|
+
this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
245
|
+
}
|
|
214
246
|
await this.startMatterBridge();
|
|
215
247
|
}
|
|
216
248
|
}
|
|
217
249
|
/**
|
|
218
250
|
* Loads a plugin from the specified package.json file path.
|
|
219
251
|
* @param packageJsonPath - The path to the package.json file of the plugin.
|
|
220
|
-
* @param mode - The mode of operation. Possible values are '
|
|
252
|
+
* @param mode - The mode of operation. Possible values are 'add', 'remove', 'enable', 'disable'.
|
|
221
253
|
* @returns A Promise that resolves when the plugin is loaded successfully, or rejects with an error if loading fails.
|
|
222
254
|
*/
|
|
223
|
-
async
|
|
255
|
+
async executeCommandLine(packageJsonPath, mode) {
|
|
224
256
|
if (!packageJsonPath.endsWith('package.json'))
|
|
225
257
|
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
258
|
+
// Resolve the package.json of the plugin
|
|
226
259
|
packageJsonPath = path.resolve(packageJsonPath);
|
|
227
260
|
this.log.debug(`Loading plugin from ${plg}${packageJsonPath}${db}`);
|
|
228
261
|
try {
|
|
229
262
|
// Load the package.json of the plugin
|
|
230
263
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const pluginUrl = pathToFileURL(pluginPath);
|
|
239
|
-
// Dynamically import the plugin
|
|
240
|
-
this.log.debug(`Importing plugin ${plg}${plugin?.name}${db} from ${pluginUrl.href}`);
|
|
241
|
-
const pluginInstance = await import(pluginUrl.href);
|
|
242
|
-
this.log.debug(`Imported plugin ${plg}${plugin?.name}${db} from ${pluginUrl.href}`);
|
|
243
|
-
// Call the default export function of the plugin, passing this MatterBridge instance
|
|
244
|
-
if (pluginInstance.default) {
|
|
245
|
-
const platform = pluginInstance.default(this, new AnsiLogger({ logName: packageJson.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */ }));
|
|
246
|
-
platform.name = packageJson.name;
|
|
247
|
-
if (mode === 'load') {
|
|
248
|
-
this.log.info(`Plugin ${plg}${plugin?.name}${nf} type ${GREEN}${platform.type}${nf} loaded (entrypoint ${UNDERLINE}${pluginPath}${UNDERLINEOFF})`);
|
|
249
|
-
// Update plugin info
|
|
250
|
-
if (plugin) {
|
|
251
|
-
plugin.path = packageJsonPath;
|
|
252
|
-
plugin.name = packageJson.name;
|
|
253
|
-
plugin.description = packageJson.description;
|
|
254
|
-
plugin.version = packageJson.version;
|
|
255
|
-
plugin.author = packageJson.author;
|
|
256
|
-
plugin.type = platform.type;
|
|
257
|
-
plugin.loaded = true;
|
|
258
|
-
plugin.platform = platform;
|
|
264
|
+
if (mode === 'add') {
|
|
265
|
+
if (!this.registeredPlugins.find((plugin) => plugin.name === packageJson.name)) {
|
|
266
|
+
const plugin = { path: packageJsonPath, type: '', name: packageJson.name, version: packageJson.version, description: packageJson.description, author: packageJson.author, enabled: true };
|
|
267
|
+
if (await this.loadPlugin(plugin)) {
|
|
268
|
+
this.registeredPlugins.push(plugin);
|
|
269
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
270
|
+
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge`);
|
|
259
271
|
}
|
|
260
272
|
else {
|
|
261
|
-
this.log.error(`Plugin ${plg}${
|
|
273
|
+
this.log.error(`Plugin ${plg}${packageJsonPath}${wr} not added to matterbridge error`);
|
|
262
274
|
}
|
|
263
275
|
}
|
|
264
|
-
else
|
|
265
|
-
|
|
266
|
-
this.registeredPlugins.push({
|
|
267
|
-
path: packageJsonPath,
|
|
268
|
-
type: platform.type,
|
|
269
|
-
name: packageJson.name,
|
|
270
|
-
version: packageJson.version,
|
|
271
|
-
description: packageJson.description,
|
|
272
|
-
author: packageJson.author,
|
|
273
|
-
enabled: true,
|
|
274
|
-
});
|
|
275
|
-
await this.nodeContext?.set('plugins', this.registeredPlugins);
|
|
276
|
-
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${platform.type} added to matterbridge`);
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} already added to matterbridge`);
|
|
280
|
-
}
|
|
276
|
+
else {
|
|
277
|
+
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} already added to matterbridge`);
|
|
281
278
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
290
|
-
}
|
|
279
|
+
}
|
|
280
|
+
else if (mode === 'remove') {
|
|
281
|
+
if (this.registeredPlugins.find((registeredPlugin) => registeredPlugin.name === packageJson.name)) {
|
|
282
|
+
this.registeredPlugins.splice(this.registeredPlugins.findIndex((registeredPlugin) => registeredPlugin.name === packageJson.name), 1);
|
|
283
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
284
|
+
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} removed from matterbridge`);
|
|
291
285
|
}
|
|
292
|
-
else
|
|
293
|
-
|
|
294
|
-
if (plugin) {
|
|
295
|
-
plugin.enabled = true;
|
|
296
|
-
await this.nodeContext?.set('plugins', this.registeredPlugins);
|
|
297
|
-
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} enabled`);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
301
|
-
}
|
|
286
|
+
else {
|
|
287
|
+
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
302
288
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
289
|
+
}
|
|
290
|
+
else if (mode === 'enable') {
|
|
291
|
+
const plugin = this.registeredPlugins.find((registeredPlugin) => registeredPlugin.name === packageJson.name);
|
|
292
|
+
if (plugin) {
|
|
293
|
+
plugin.enabled = true;
|
|
294
|
+
plugin.loaded = undefined;
|
|
295
|
+
plugin.started = undefined;
|
|
296
|
+
plugin.configured = undefined;
|
|
297
|
+
plugin.connected = undefined;
|
|
298
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
299
|
+
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} enabled`);
|
|
313
300
|
}
|
|
314
301
|
else {
|
|
315
|
-
this.log.
|
|
302
|
+
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
316
303
|
}
|
|
317
304
|
}
|
|
305
|
+
else if (mode === 'disable') {
|
|
306
|
+
const plugin = this.registeredPlugins.find((registeredPlugin) => registeredPlugin.name === packageJson.name);
|
|
307
|
+
if (plugin) {
|
|
308
|
+
plugin.enabled = false;
|
|
309
|
+
plugin.loaded = undefined;
|
|
310
|
+
plugin.started = undefined;
|
|
311
|
+
plugin.configured = undefined;
|
|
312
|
+
plugin.connected = undefined;
|
|
313
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
314
|
+
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} disabled`);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
//}
|
|
318
321
|
}
|
|
319
322
|
catch (err) {
|
|
320
323
|
this.log.error(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
|
|
@@ -339,13 +342,12 @@ export class Matterbridge {
|
|
|
339
342
|
async cleanup(message) {
|
|
340
343
|
if (!this.hasCleanupStarted) {
|
|
341
344
|
this.hasCleanupStarted = true;
|
|
342
|
-
this.log.
|
|
345
|
+
this.log.info(message);
|
|
343
346
|
// Callint the shutdown functions with a reason
|
|
344
|
-
this.registeredPlugins
|
|
345
|
-
if (plugin.platform)
|
|
346
|
-
plugin.platform.onShutdown('Matterbridge is closing: ' + message);
|
|
347
|
-
|
|
348
|
-
});
|
|
347
|
+
for (const plugin of this.registeredPlugins) {
|
|
348
|
+
if (plugin.platform)
|
|
349
|
+
await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
|
|
350
|
+
}
|
|
349
351
|
// Set reachability to false
|
|
350
352
|
/*
|
|
351
353
|
this.log.debug(`*Changing reachability to false for ${this.registeredDevices.length} devices (${this.bridgeMode} mode):`);
|
|
@@ -368,10 +370,18 @@ export class Matterbridge {
|
|
|
368
370
|
await this.stopMatter();
|
|
369
371
|
// Closing storage
|
|
370
372
|
await this.stopStorage();
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
373
|
+
// Serialize registeredDevices
|
|
374
|
+
const serializedRegisteredDevices = [];
|
|
375
|
+
this.registeredDevices.forEach((registeredDevice) => {
|
|
376
|
+
serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
|
|
377
|
+
});
|
|
378
|
+
//console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
|
|
379
|
+
await this.nodeContext?.set('devices', serializedRegisteredDevices);
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
this.log.info('Cleanup completed.');
|
|
382
|
+
process.exit(0);
|
|
383
|
+
}, 2 * 1000);
|
|
384
|
+
}, 3 * 1000);
|
|
375
385
|
}
|
|
376
386
|
}
|
|
377
387
|
/**
|
|
@@ -392,6 +402,7 @@ export class Matterbridge {
|
|
|
392
402
|
* Adds a device to the Matterbridge.
|
|
393
403
|
* @param pluginName - The name of the plugin.
|
|
394
404
|
* @param device - The device to be added.
|
|
405
|
+
* @returns A Promise that resolves when the device is added successfully.
|
|
395
406
|
*/
|
|
396
407
|
async addDevice(pluginName, device) {
|
|
397
408
|
this.log.info(`Adding device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
@@ -403,20 +414,19 @@ export class Matterbridge {
|
|
|
403
414
|
}
|
|
404
415
|
// Add and register the device to the matterbridge in bridge mode
|
|
405
416
|
if (this.bridgeMode === 'bridge') {
|
|
406
|
-
const basic = device.getClusterServerById(BasicInformationCluster.id);
|
|
407
|
-
if (!basic) {
|
|
408
|
-
this.log.error(`addDevice error: cannot find the BasicInformationCluster device ${dev}${device.name}${nf} plugin ${plg}${pluginName}${nf}`);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
device.createDefaultBridgedDeviceBasicInformationClusterServer(basic.getNodeLabelAttribute(), basic.getSerialNumberAttribute(), basic.getVendorIdAttribute(), basic.getVendorNameAttribute(), basic.getProductNameAttribute(), basic.getSoftwareVersionAttribute(), basic.getSoftwareVersionStringAttribute(), basic.getHardwareVersionAttribute(), basic.getHardwareVersionStringAttribute());
|
|
412
|
-
//console.log(basic.getSoftwareVersionAttribute(), basic.getSoftwareVersionStringAttribute());
|
|
413
417
|
this.matterAggregator.addBridgedDevice(device);
|
|
414
418
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
415
|
-
|
|
419
|
+
if (plugin.registeredDevices !== undefined)
|
|
420
|
+
plugin.registeredDevices++;
|
|
421
|
+
if (plugin.addedDevices !== undefined)
|
|
422
|
+
plugin.addedDevices++;
|
|
423
|
+
this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
416
424
|
}
|
|
417
425
|
// Only register the device in childbridge mode
|
|
418
426
|
if (this.bridgeMode === 'childbridge') {
|
|
419
427
|
this.registeredDevices.push({ plugin: pluginName, device, added: false });
|
|
428
|
+
if (plugin.registeredDevices !== undefined)
|
|
429
|
+
plugin.registeredDevices++;
|
|
420
430
|
this.log.info(`Registered device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
421
431
|
}
|
|
422
432
|
}
|
|
@@ -424,6 +434,7 @@ export class Matterbridge {
|
|
|
424
434
|
* Adds a bridged device to the Matterbridge.
|
|
425
435
|
* @param pluginName - The name of the plugin.
|
|
426
436
|
* @param device - The bridged device to add.
|
|
437
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
427
438
|
*/
|
|
428
439
|
async addBridgedDevice(pluginName, device) {
|
|
429
440
|
this.log.info(`Adding bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
@@ -437,14 +448,18 @@ export class Matterbridge {
|
|
|
437
448
|
if (this.bridgeMode === 'bridge') {
|
|
438
449
|
this.matterAggregator.addBridgedDevice(device);
|
|
439
450
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
440
|
-
|
|
451
|
+
if (plugin.registeredDevices !== undefined)
|
|
452
|
+
plugin.registeredDevices++;
|
|
453
|
+
if (plugin.addedDevices !== undefined)
|
|
454
|
+
plugin.addedDevices++;
|
|
455
|
+
this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
441
456
|
}
|
|
442
457
|
// Only register the device in childbridge mode
|
|
443
458
|
if (this.bridgeMode === 'childbridge') {
|
|
444
459
|
this.registeredDevices.push({ plugin: pluginName, device, added: false });
|
|
460
|
+
if (plugin.registeredDevices !== undefined)
|
|
461
|
+
plugin.registeredDevices++;
|
|
445
462
|
this.log.info(`Registered bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
446
|
-
//const basic = device.getClusterServerById(BridgedDeviceBasicInformationCluster.id);
|
|
447
|
-
//console.log(JSON.stringify(basic, null, 2));
|
|
448
463
|
}
|
|
449
464
|
}
|
|
450
465
|
/**
|
|
@@ -513,6 +528,146 @@ export class Matterbridge {
|
|
|
513
528
|
await this.storageManager.close();
|
|
514
529
|
this.log.debug('Storage closed');
|
|
515
530
|
}
|
|
531
|
+
async testStartMatterBridge() {
|
|
532
|
+
for (const plugin of this.registeredPlugins) {
|
|
533
|
+
if (!plugin.enabled)
|
|
534
|
+
continue;
|
|
535
|
+
// No await do it asyncronously
|
|
536
|
+
this.loadPlugin(plugin)
|
|
537
|
+
.then(() => {
|
|
538
|
+
// No await do it asyncronously
|
|
539
|
+
this.startPlugin(plugin)
|
|
540
|
+
.then(() => { })
|
|
541
|
+
.catch((err) => {
|
|
542
|
+
this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
543
|
+
});
|
|
544
|
+
})
|
|
545
|
+
.catch((err) => {
|
|
546
|
+
this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
for (const plugin of this.registeredPlugins) {
|
|
550
|
+
if (!plugin.enabled)
|
|
551
|
+
continue;
|
|
552
|
+
// Start the interval to check if the plugin is loaded and started
|
|
553
|
+
let times = 0;
|
|
554
|
+
const interval = setInterval(() => {
|
|
555
|
+
times++;
|
|
556
|
+
this.log.info(`Waiting ${times} secs for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) and send devices ...`);
|
|
557
|
+
if (!plugin.loaded || !plugin.started)
|
|
558
|
+
return;
|
|
559
|
+
this.log.info(`Plugin ${plg}${plugin.name}${db} sent ${plugin.registeredDevices} devices`);
|
|
560
|
+
clearInterval(interval);
|
|
561
|
+
}, 1000);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async startPlugin(plugin, message, configure = false) {
|
|
565
|
+
if (!plugin.loaded || !plugin.platform) {
|
|
566
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`);
|
|
567
|
+
return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`));
|
|
568
|
+
}
|
|
569
|
+
if (plugin.started) {
|
|
570
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} already started`);
|
|
571
|
+
return Promise.resolve();
|
|
572
|
+
}
|
|
573
|
+
this.log.info(`Starting plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
|
|
574
|
+
try {
|
|
575
|
+
plugin.platform
|
|
576
|
+
.onStart(message)
|
|
577
|
+
.then(() => {
|
|
578
|
+
plugin.started = true;
|
|
579
|
+
this.log.info(`Started plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
|
|
580
|
+
if (configure)
|
|
581
|
+
this.configurePlugin(plugin); // No await do it asyncronously
|
|
582
|
+
return Promise.resolve();
|
|
583
|
+
})
|
|
584
|
+
.catch((err) => {
|
|
585
|
+
this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
586
|
+
return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
591
|
+
return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async configurePlugin(plugin) {
|
|
595
|
+
if (!plugin.loaded || !plugin.started || !plugin.platform) {
|
|
596
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`);
|
|
597
|
+
return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or not started or not platform`));
|
|
598
|
+
}
|
|
599
|
+
if (plugin.configured) {
|
|
600
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} already configured`);
|
|
601
|
+
return Promise.resolve();
|
|
602
|
+
}
|
|
603
|
+
this.log.info(`Configuring plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
|
|
604
|
+
try {
|
|
605
|
+
plugin.platform
|
|
606
|
+
.onConfigure()
|
|
607
|
+
.then(() => {
|
|
608
|
+
plugin.configured = true;
|
|
609
|
+
this.log.info(`Configured plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
|
|
610
|
+
return Promise.resolve();
|
|
611
|
+
})
|
|
612
|
+
.catch((err) => {
|
|
613
|
+
this.log.error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
614
|
+
return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
this.log.error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
619
|
+
return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async loadPlugin(plugin, start = false, message = '') {
|
|
623
|
+
if (!plugin.enabled) {
|
|
624
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} not enabled`);
|
|
625
|
+
return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not enabled`));
|
|
626
|
+
}
|
|
627
|
+
if (plugin.platform) {
|
|
628
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} already loaded`);
|
|
629
|
+
return Promise.resolve(plugin.platform);
|
|
630
|
+
}
|
|
631
|
+
this.log.info(`Loading plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
|
|
632
|
+
try {
|
|
633
|
+
// Load the package.json of the plugin
|
|
634
|
+
const packageJson = JSON.parse(await fs.readFile(plugin.path, 'utf8'));
|
|
635
|
+
// Resolve the main module path relative to package.json
|
|
636
|
+
const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
|
|
637
|
+
// Dynamically import the plugin
|
|
638
|
+
const pluginUrl = pathToFileURL(pluginEntry);
|
|
639
|
+
this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
|
|
640
|
+
const pluginInstance = await import(pluginUrl.href);
|
|
641
|
+
this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
|
|
642
|
+
// Call the default export function of the plugin, passing this MatterBridge instance
|
|
643
|
+
if (pluginInstance.default) {
|
|
644
|
+
const platform = pluginInstance.default(this, new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled }));
|
|
645
|
+
platform.name = packageJson.name;
|
|
646
|
+
plugin.name = packageJson.name;
|
|
647
|
+
plugin.description = packageJson.description;
|
|
648
|
+
plugin.version = packageJson.version;
|
|
649
|
+
plugin.author = packageJson.author;
|
|
650
|
+
plugin.type = platform.type;
|
|
651
|
+
plugin.platform = platform;
|
|
652
|
+
plugin.loaded = true;
|
|
653
|
+
plugin.registeredDevices = 0;
|
|
654
|
+
plugin.addedDevices = 0;
|
|
655
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
656
|
+
this.log.info(`Loaded plugin ${plg}${plugin.name}${db} type ${typ}${platform.type}${db} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
|
|
657
|
+
if (start)
|
|
658
|
+
this.startPlugin(plugin, message); // No await do it asyncronously
|
|
659
|
+
return Promise.resolve(platform);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`);
|
|
663
|
+
return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
668
|
+
return Promise.reject(new Error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
516
671
|
/**
|
|
517
672
|
* Starts the Matterbridge based on the bridge mode.
|
|
518
673
|
* If the bridge mode is 'bridge', it creates a commissioning server, matter aggregator,
|
|
@@ -527,7 +682,7 @@ export class Matterbridge {
|
|
|
527
682
|
this.createMatterServer(this.storageManager);
|
|
528
683
|
if (this.bridgeMode === 'bridge') {
|
|
529
684
|
// Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
|
|
530
|
-
// Plugins are started by callback when Matterbridge is commissioned and plugin.started is set to true
|
|
685
|
+
// Plugins are started and configured by callback when Matterbridge is commissioned and plugin.started is set to true
|
|
531
686
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
532
687
|
this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
533
688
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
@@ -540,53 +695,37 @@ export class Matterbridge {
|
|
|
540
695
|
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
541
696
|
this.log.debug('Starting matter server');
|
|
542
697
|
await this.startMatterServer();
|
|
543
|
-
this.registeredPlugins.forEach(async (plugin) => {
|
|
544
|
-
if (plugin.platform)
|
|
545
|
-
await plugin.platform.onMatterStarted();
|
|
546
|
-
});
|
|
547
698
|
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
548
699
|
}
|
|
549
700
|
if (this.bridgeMode === 'childbridge') {
|
|
550
|
-
// Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
|
|
551
|
-
// Plugins are started here and plugin.started is set to true.
|
|
701
|
+
// Plugins are loaded and started by loadPlugin on startup and plugin.loaded is set to true and plugin.started is set to true
|
|
552
702
|
// addDevice and addBridgedDeevice just register the devices that are added here to the plugin commissioning server for Accessory Platform
|
|
553
703
|
// or to the plugin aggregator for Dynamic Platform after the commissioning is done
|
|
704
|
+
// Plugins are configured by callback when the plugin is commissioned
|
|
554
705
|
this.registeredPlugins.forEach(async (plugin) => {
|
|
555
706
|
if (!plugin.enabled)
|
|
556
707
|
return;
|
|
557
|
-
// Start the interval to check if the plugin is loaded
|
|
558
|
-
const loadedInterval = setInterval(async () => {
|
|
559
|
-
this.log.debug(`Waiting in load interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) and send devices ...`);
|
|
560
|
-
if (!plugin.loaded)
|
|
561
|
-
return;
|
|
562
|
-
plugin.started = true;
|
|
563
|
-
plugin.registeredDevices = 0;
|
|
564
|
-
clearInterval(loadedInterval);
|
|
565
|
-
}, 1000);
|
|
566
708
|
// Start the interval to check if the plugins is started
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
709
|
+
// TODO set a counter or a timeout
|
|
710
|
+
this.log.info(`**Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
711
|
+
const startInterval = setInterval(async () => {
|
|
712
|
+
if (!plugin.loaded || !plugin.started) {
|
|
713
|
+
this.log.info(`***Returning in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
570
714
|
return;
|
|
715
|
+
}
|
|
571
716
|
if (plugin.type === 'AccessoryPlatform') {
|
|
572
|
-
this.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
this.log.error(`Platform not found for plugin ${plg}${plugin.name}${er}`);
|
|
577
|
-
this.registeredDevices.forEach(async (registeredDevice) => {
|
|
578
|
-
if (registeredDevice.plugin === plugin.name) {
|
|
717
|
+
this.registeredDevices
|
|
718
|
+
.filter((registeredDevice) => registeredDevice.plugin === plugin.name)
|
|
719
|
+
.forEach((registeredDevice) => {
|
|
720
|
+
if (!plugin.storageContext)
|
|
579
721
|
plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device); // Generate serialNumber and uniqueId
|
|
722
|
+
if (!plugin.commissioningServer)
|
|
580
723
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (plugin.registeredDevices !== undefined)
|
|
584
|
-
plugin.registeredDevices++;
|
|
585
|
-
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db} `);
|
|
586
|
-
await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
724
|
+
this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
725
|
+
plugin.commissioningServer.addDevice(registeredDevice.device);
|
|
589
726
|
});
|
|
727
|
+
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
728
|
+
await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
590
729
|
}
|
|
591
730
|
if (plugin.type === 'DynamicPlatform') {
|
|
592
731
|
plugin.storageContext = this.createCommissioningServerContext(
|
|
@@ -595,31 +734,29 @@ export class Matterbridge {
|
|
|
595
734
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
596
735
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
597
736
|
plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
|
|
598
|
-
this.log.debug(`Starting dynamic platform for plugin ${plg}${plugin.name}${db}`);
|
|
599
|
-
if (plugin.platform)
|
|
600
|
-
await plugin.platform.onStart('Matterbridge Dynamic platform has started commissioning');
|
|
601
|
-
else
|
|
602
|
-
this.log.error(`Platform not found for plugin ${plg}${plugin.name}${er}`);
|
|
603
737
|
this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
604
738
|
plugin.commissioningServer.addDevice(plugin.aggregator);
|
|
605
739
|
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
606
740
|
await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
607
741
|
}
|
|
608
|
-
clearInterval(
|
|
742
|
+
clearInterval(startInterval);
|
|
609
743
|
}, 1000);
|
|
610
744
|
});
|
|
611
745
|
// Start the interval to check if all plugins are loaded and started and so start the matter server
|
|
746
|
+
// TODO set a counter or a timeout
|
|
747
|
+
this.log.info('**Starting start matter interval...');
|
|
612
748
|
const startMatterInterval = setInterval(async () => {
|
|
613
749
|
let allStarted = true;
|
|
614
750
|
this.registeredPlugins.forEach((plugin) => {
|
|
615
751
|
if (!plugin.enabled)
|
|
616
752
|
return;
|
|
617
|
-
this.log.
|
|
753
|
+
this.log.info(`**Waiting in start matter server interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
|
|
618
754
|
if (plugin.enabled && (!plugin.loaded || !plugin.started))
|
|
619
755
|
allStarted = false;
|
|
620
756
|
});
|
|
621
757
|
if (!allStarted)
|
|
622
758
|
return;
|
|
759
|
+
this.log.info('**Starting matter server in start matter server interval...');
|
|
623
760
|
// Setting reachability to true
|
|
624
761
|
this.registeredPlugins.forEach((plugin) => {
|
|
625
762
|
if (!plugin.enabled)
|
|
@@ -635,20 +772,18 @@ export class Matterbridge {
|
|
|
635
772
|
}
|
|
636
773
|
});
|
|
637
774
|
});
|
|
638
|
-
this.log.debug('Starting matter server');
|
|
639
775
|
await this.startMatterServer();
|
|
640
|
-
this.registeredPlugins
|
|
641
|
-
if (plugin.platform)
|
|
642
|
-
await plugin.platform.onMatterStarted();
|
|
776
|
+
for (const plugin of this.registeredPlugins) {
|
|
643
777
|
this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.name);
|
|
644
|
-
}
|
|
645
|
-
Logger.defaultLogLevel = Level.DEBUG;
|
|
778
|
+
}
|
|
779
|
+
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
646
780
|
clearInterval(startMatterInterval);
|
|
647
781
|
}, 1000);
|
|
648
782
|
return;
|
|
649
783
|
}
|
|
650
784
|
}
|
|
651
785
|
async startMatterServer() {
|
|
786
|
+
this.log.debug('Starting matter server');
|
|
652
787
|
await this.matterServer.start();
|
|
653
788
|
this.log.debug('Started matter server');
|
|
654
789
|
}
|
|
@@ -715,25 +850,34 @@ export class Matterbridge {
|
|
|
715
850
|
*
|
|
716
851
|
* @param commissioningServer - The commissioning server to show the QR code for.
|
|
717
852
|
* @param storageContext - The storage context to store the pairing codes.
|
|
718
|
-
* @param
|
|
853
|
+
* @param pluginName - The name of the commissioning server.
|
|
719
854
|
*/
|
|
720
|
-
showCommissioningQRCode(commissioningServer, storageContext,
|
|
721
|
-
if (!commissioningServer || !storageContext || !
|
|
855
|
+
async showCommissioningQRCode(commissioningServer, storageContext, pluginName) {
|
|
856
|
+
if (!commissioningServer || !storageContext || !pluginName)
|
|
722
857
|
return;
|
|
723
858
|
if (!commissioningServer.isCommissioned()) {
|
|
724
|
-
this.log.info(`***The commissioning server for ${plg}${
|
|
859
|
+
this.log.info(`***The commissioning server for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code ...`);
|
|
725
860
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
726
861
|
storageContext.set('qrPairingCode', qrPairingCode);
|
|
727
862
|
storageContext.set('manualPairingCode', manualPairingCode);
|
|
728
863
|
const QrCode = new QrCodeSchema();
|
|
729
|
-
this.log.
|
|
864
|
+
this.log.info(`Pairing code:\n\n${QrCode.encode(qrPairingCode)}\nManual pairing code: ${manualPairingCode}\n`);
|
|
865
|
+
if (this.bridgeMode === 'childbridge') {
|
|
866
|
+
const plugin = this.findPlugin(pluginName);
|
|
867
|
+
if (plugin) {
|
|
868
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
869
|
+
plugin.paired = false;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
730
872
|
}
|
|
731
873
|
else {
|
|
732
|
-
this.log.info(`***The commissioning server for ${plg}${
|
|
874
|
+
this.log.info(`***The commissioning server for ${plg}${pluginName}${nf} is already commissioned. Waiting for controllers to connect ...`);
|
|
733
875
|
if (this.bridgeMode === 'childbridge') {
|
|
734
|
-
const plugin = this.findPlugin(
|
|
735
|
-
if (plugin)
|
|
876
|
+
const plugin = this.findPlugin(pluginName);
|
|
877
|
+
if (plugin) {
|
|
878
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
736
879
|
plugin.paired = true;
|
|
880
|
+
}
|
|
737
881
|
}
|
|
738
882
|
}
|
|
739
883
|
}
|
|
@@ -794,14 +938,18 @@ export class Matterbridge {
|
|
|
794
938
|
},
|
|
795
939
|
activeSessionsChangedCallback: (fabricIndex) => {
|
|
796
940
|
const info = commissioningServer.getActiveSessionInformation(fabricIndex);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
this.log.
|
|
941
|
+
let connected = false;
|
|
942
|
+
info.forEach((session) => {
|
|
943
|
+
this.log.debug(`***Active session changed on fabric ${fabricIndex} ${session.fabric?.rootVendorId}/${session.fabric?.label} for ${plg}${name}${nf}`, debugStringify(session));
|
|
944
|
+
if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
|
|
945
|
+
this.log.info(`***Controller ${session.fabric?.rootVendorId}/${session.fabric?.label} connected to ${plg}${name}${nf}`);
|
|
946
|
+
connected = true;
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
if (connected) {
|
|
800
950
|
if (this.bridgeMode === 'childbridge') {
|
|
801
951
|
const plugin = this.findPlugin(name);
|
|
802
952
|
if (plugin) {
|
|
803
|
-
if (plugin.connected === true)
|
|
804
|
-
return; // Only once cause the devices are already added to the plugins aggregator
|
|
805
953
|
plugin.paired = true;
|
|
806
954
|
plugin.connected = true;
|
|
807
955
|
}
|
|
@@ -809,45 +957,40 @@ export class Matterbridge {
|
|
|
809
957
|
setTimeout(() => {
|
|
810
958
|
if (this.bridgeMode === 'bridge') {
|
|
811
959
|
//Logger.defaultLogLevel = Level.INFO;
|
|
812
|
-
this.registeredPlugins
|
|
960
|
+
for (const plugin of this.registeredPlugins) {
|
|
813
961
|
if (!plugin.enabled)
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
this.log.info(`***Started plugin ${plg}${plugin.name}${nf}`);
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
this.log.error(`***Platform not found for plugin ${plg}${plugin.name}${er}`);
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
Logger.defaultLogLevel = Level.DEBUG;
|
|
962
|
+
continue;
|
|
963
|
+
this.startPlugin(plugin, 'Matterbridge is commissioned and controllers are connected', true); // No await do it asyncronously
|
|
964
|
+
//this.configurePlugin(plugin); // No await do it asyncronously
|
|
965
|
+
}
|
|
966
|
+
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
826
967
|
}
|
|
827
968
|
if (this.bridgeMode === 'childbridge') {
|
|
828
969
|
//Logger.defaultLogLevel = Level.INFO;
|
|
829
970
|
const plugin = this.findPlugin(name);
|
|
830
|
-
if (
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
971
|
+
if (plugin && plugin.type === 'DynamicPlatform') {
|
|
972
|
+
for (const registeredDevice of this.registeredDevices) {
|
|
973
|
+
if (registeredDevice.plugin === name) {
|
|
974
|
+
this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
975
|
+
if (!plugin.aggregator) {
|
|
976
|
+
this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
plugin.aggregator.addBridgedDevice(registeredDevice.device);
|
|
980
|
+
if (plugin.addedDevices !== undefined)
|
|
981
|
+
plugin.addedDevices++;
|
|
982
|
+
this.log.info(`Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}${nf} for plugin ${plg}${plugin.name}${nf}`);
|
|
983
|
+
registeredDevice.added = true;
|
|
984
|
+
}
|
|
839
985
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
986
|
+
}
|
|
987
|
+
for (const plugin of this.registeredPlugins) {
|
|
988
|
+
if (plugin.name === name && plugin.platform) {
|
|
989
|
+
this.configurePlugin(plugin); // No await do it asyncronously
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
846
993
|
}
|
|
847
|
-
this.registeredPlugins.forEach(async (plugin) => {
|
|
848
|
-
if (plugin.name === name && plugin.platform)
|
|
849
|
-
plugin.platform.onConfigure();
|
|
850
|
-
});
|
|
851
994
|
//logEndpoint(commissioningServer.getRootEndpoint());
|
|
852
995
|
}, 2000);
|
|
853
996
|
}
|
|
@@ -856,7 +999,8 @@ export class Matterbridge {
|
|
|
856
999
|
const info = commissioningServer.getCommissionedFabricInformation(fabricIndex);
|
|
857
1000
|
this.log.debug(`***Commissioning changed on fabric ${fabricIndex} for ${plg}${name}${nf}`, debugStringify(info));
|
|
858
1001
|
if (info.length === 0) {
|
|
859
|
-
this.log.warn(`***Commissioning removed from fabric ${fabricIndex} for ${plg}${name}${nf}
|
|
1002
|
+
this.log.warn(`***Commissioning removed from fabric ${fabricIndex} for ${plg}${name}${nf}. Resetting the commissioning server ...`);
|
|
1003
|
+
commissioningServer.factoryReset(); // TODO delete from storage also "matterbridge-eve-weather.EndpointStructure"
|
|
860
1004
|
}
|
|
861
1005
|
},
|
|
862
1006
|
});
|
|
@@ -987,6 +1131,10 @@ export class Matterbridge {
|
|
|
987
1131
|
await fs.mkdir(this.matterbridgeDirectory);
|
|
988
1132
|
}
|
|
989
1133
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1134
|
+
// Matterbridge version
|
|
1135
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1136
|
+
this.matterbridgeVersion = packageJson.version;
|
|
1137
|
+
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
990
1138
|
// Current working directory
|
|
991
1139
|
const currentDir = process.cwd();
|
|
992
1140
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
@@ -994,7 +1142,25 @@ export class Matterbridge {
|
|
|
994
1142
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
995
1143
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
996
1144
|
}
|
|
997
|
-
|
|
1145
|
+
getBaseRegisteredPlugins() {
|
|
1146
|
+
const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
|
|
1147
|
+
path: plugin.path,
|
|
1148
|
+
type: plugin.type,
|
|
1149
|
+
name: plugin.name,
|
|
1150
|
+
version: plugin.version,
|
|
1151
|
+
description: plugin.description,
|
|
1152
|
+
author: plugin.author,
|
|
1153
|
+
enabled: plugin.enabled,
|
|
1154
|
+
loaded: plugin.loaded,
|
|
1155
|
+
started: plugin.started,
|
|
1156
|
+
configured: plugin.configured,
|
|
1157
|
+
paired: plugin.paired,
|
|
1158
|
+
connected: plugin.connected,
|
|
1159
|
+
registeredDevices: plugin.registeredDevices,
|
|
1160
|
+
}));
|
|
1161
|
+
return baseRegisteredPlugins;
|
|
1162
|
+
}
|
|
1163
|
+
getBaseRegisteredDevices() {
|
|
998
1164
|
const baseRegisteredPlugins = this.registeredPlugins.map((plugin) => ({
|
|
999
1165
|
path: plugin.path,
|
|
1000
1166
|
type: plugin.type,
|
|
@@ -1044,8 +1210,7 @@ export class Matterbridge {
|
|
|
1044
1210
|
// Endpoint to provide plugins
|
|
1045
1211
|
this.app.get('/api/plugins', (req, res) => {
|
|
1046
1212
|
this.log.debug('The frontend sent /api/plugins');
|
|
1047
|
-
|
|
1048
|
-
res.json(baseRegisteredPlugins);
|
|
1213
|
+
res.json(this.getBaseRegisteredPlugins());
|
|
1049
1214
|
});
|
|
1050
1215
|
// Endpoint to provide devices
|
|
1051
1216
|
this.app.get('/api/devices', (req, res) => {
|
|
@@ -1077,15 +1242,15 @@ export class Matterbridge {
|
|
|
1077
1242
|
// Endpoint to provide the cluster servers of the devices
|
|
1078
1243
|
this.app.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
1079
1244
|
const selectedPluginName = req.params.selectedPluginName;
|
|
1080
|
-
const selectedDeviceEndpoint = req.params.selectedDeviceEndpoint;
|
|
1245
|
+
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
1081
1246
|
this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
|
|
1082
|
-
if (selectedPluginName === 'none'
|
|
1247
|
+
if (selectedPluginName === 'none') {
|
|
1083
1248
|
res.json([]);
|
|
1084
1249
|
return;
|
|
1085
1250
|
}
|
|
1086
1251
|
const data = [];
|
|
1087
1252
|
this.registeredDevices.forEach((registeredDevice) => {
|
|
1088
|
-
if (registeredDevice.plugin === selectedPluginName) {
|
|
1253
|
+
if (registeredDevice.plugin === selectedPluginName && registeredDevice.device.id === selectedDeviceEndpoint) {
|
|
1089
1254
|
const clusterServers = registeredDevice.device.getAllClusterServers();
|
|
1090
1255
|
clusterServers.forEach((clusterServer) => {
|
|
1091
1256
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
@@ -1189,6 +1354,13 @@ ws.onmessage = (event) => {
|
|
|
1189
1354
|
console.log(`Received message => ${event.data}`);
|
|
1190
1355
|
};
|
|
1191
1356
|
|
|
1357
|
+
*/
|
|
1358
|
+
/*
|
|
1359
|
+
// In Matterbridge
|
|
1360
|
+
global.matterbridgeInstance = Matterbridge.loadInstance();
|
|
1361
|
+
|
|
1362
|
+
// In plugins
|
|
1363
|
+
const matterbridge = global.matterbridgeInstance;
|
|
1192
1364
|
*/
|
|
1193
1365
|
/*
|
|
1194
1366
|
npx create-react-app matterbridge-frontend
|