@project-chip/matter.js 0.12.3 → 0.12.4-alpha.0-20250210-ad8edf096
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/dist/cjs/CommissioningController.d.ts +52 -30
- package/dist/cjs/CommissioningController.d.ts.map +1 -1
- package/dist/cjs/CommissioningController.js +154 -109
- package/dist/cjs/CommissioningController.js.map +1 -1
- package/dist/cjs/device/PairedNode.d.ts +22 -9
- package/dist/cjs/device/PairedNode.d.ts.map +1 -1
- package/dist/cjs/device/PairedNode.js +43 -31
- package/dist/cjs/device/PairedNode.js.map +1 -1
- package/dist/esm/CommissioningController.d.ts +52 -30
- package/dist/esm/CommissioningController.d.ts.map +1 -1
- package/dist/esm/CommissioningController.js +154 -109
- package/dist/esm/CommissioningController.js.map +1 -1
- package/dist/esm/device/PairedNode.d.ts +22 -9
- package/dist/esm/device/PairedNode.d.ts.map +1 -1
- package/dist/esm/device/PairedNode.js +43 -31
- package/dist/esm/device/PairedNode.js.map +1 -1
- package/package.json +8 -8
- package/src/CommissioningController.ts +155 -108
- package/src/device/PairedNode.ts +48 -35
|
@@ -133,37 +133,41 @@ export type NodeCommissioningOptions = CommissioningControllerNodeOptions & {
|
|
|
133
133
|
|
|
134
134
|
/** Controller class to commission and connect multiple nodes into one fabric. */
|
|
135
135
|
export class CommissioningController extends MatterNode {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
#started = false;
|
|
137
|
+
#ipv4Disabled?: boolean;
|
|
138
|
+
readonly #listeningAddressIpv4?: string;
|
|
139
|
+
readonly #listeningAddressIpv6?: string;
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
private storage?: StorageContext;
|
|
141
|
+
readonly #options: CommissioningControllerOptions;
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
#environment?: Environment; // Set when new API was initialized correctly
|
|
144
|
+
#storage?: StorageContext;
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
#mdnsScanner?: MdnsScanner;
|
|
147
|
+
#mdnsBroadcaster?: MdnsBroadcaster;
|
|
148
|
+
|
|
149
|
+
#controllerInstance?: MatterController;
|
|
150
|
+
readonly #initializedNodes = new Map<NodeId, PairedNode>();
|
|
151
|
+
readonly #nodeUpdateLabelHandlers = new Map<NodeId, (nodeState: NodeStates) => Promise<void>>();
|
|
152
|
+
readonly #sessionDisconnectedHandler = new Map<NodeId, () => Promise<void>>();
|
|
151
153
|
|
|
152
154
|
/**
|
|
153
155
|
* Creates a new CommissioningController instance
|
|
154
156
|
*
|
|
155
157
|
* @param options The options for the CommissioningController
|
|
156
158
|
*/
|
|
157
|
-
constructor(
|
|
159
|
+
constructor(options: CommissioningControllerOptions) {
|
|
158
160
|
super();
|
|
161
|
+
this.#options = options;
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
get nodeId() {
|
|
162
|
-
return this
|
|
165
|
+
return this.#controllerInstance?.nodeId;
|
|
163
166
|
}
|
|
164
167
|
|
|
168
|
+
/** Returns the configuration data needed to create a PASE commissioner, e.g. in a mobile app. */
|
|
165
169
|
get paseCommissionerConfig() {
|
|
166
|
-
const controller = this
|
|
170
|
+
const controller = this.#assertControllerIsStarted(
|
|
167
171
|
"The CommissioningController needs to be started to get the PASE commissioner data.",
|
|
168
172
|
);
|
|
169
173
|
const { caConfig, fabricConfig: fabricData } = controller;
|
|
@@ -173,33 +177,33 @@ export class CommissioningController extends MatterNode {
|
|
|
173
177
|
};
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
assertIsAddedToMatterServer() {
|
|
177
|
-
if (this
|
|
180
|
+
#assertIsAddedToMatterServer() {
|
|
181
|
+
if (this.#mdnsScanner === undefined || (this.#storage === undefined && this.#environment === undefined)) {
|
|
178
182
|
throw new ImplementationError("Add the node to the Matter instance before.");
|
|
179
183
|
}
|
|
180
|
-
if (!this
|
|
184
|
+
if (!this.#started) {
|
|
181
185
|
throw new ImplementationError("The node needs to be started before interacting with the controller.");
|
|
182
186
|
}
|
|
183
|
-
return { mdnsScanner: this
|
|
187
|
+
return { mdnsScanner: this.#mdnsScanner, storage: this.#storage, environment: this.#environment };
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
assertControllerIsStarted(errorText?: string) {
|
|
187
|
-
if (this
|
|
190
|
+
#assertControllerIsStarted(errorText?: string) {
|
|
191
|
+
if (this.#controllerInstance === undefined) {
|
|
188
192
|
throw new ImplementationError(
|
|
189
193
|
errorText ?? "Controller instance not yet started. Please call start() first.",
|
|
190
194
|
);
|
|
191
195
|
}
|
|
192
|
-
return this
|
|
196
|
+
return this.#controllerInstance;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
/** Internal method to initialize a MatterController instance. */
|
|
196
|
-
|
|
197
|
-
const { mdnsScanner, storage, environment } = this
|
|
198
|
-
if (this
|
|
199
|
-
return this
|
|
200
|
+
async #initializeController() {
|
|
201
|
+
const { mdnsScanner, storage, environment } = this.#assertIsAddedToMatterServer();
|
|
202
|
+
if (this.#controllerInstance !== undefined) {
|
|
203
|
+
return this.#controllerInstance;
|
|
200
204
|
}
|
|
201
205
|
const { localPort, adminFabricId, adminVendorId, adminFabricIndex, caseAuthenticatedTags, adminFabricLabel } =
|
|
202
|
-
this
|
|
206
|
+
this.#options;
|
|
203
207
|
|
|
204
208
|
if (environment === undefined && storage === undefined) {
|
|
205
209
|
throw new ImplementationError("Storage not initialized correctly.");
|
|
@@ -211,11 +215,11 @@ export class CommissioningController extends MatterNode {
|
|
|
211
215
|
: new LegacyControllerStore(storage!);
|
|
212
216
|
|
|
213
217
|
const { netInterfaces, scanners, port } = await configureNetwork({
|
|
214
|
-
ipv4Disabled: this
|
|
218
|
+
ipv4Disabled: this.#ipv4Disabled,
|
|
215
219
|
mdnsScanner,
|
|
216
220
|
localPort,
|
|
217
|
-
listeningAddressIpv4: this
|
|
218
|
-
listeningAddressIpv6: this
|
|
221
|
+
listeningAddressIpv4: this.#listeningAddressIpv4,
|
|
222
|
+
listeningAddressIpv6: this.#listeningAddressIpv6,
|
|
219
223
|
});
|
|
220
224
|
|
|
221
225
|
const controller = await MatterController.create({
|
|
@@ -224,7 +228,7 @@ export class CommissioningController extends MatterNode {
|
|
|
224
228
|
netInterfaces,
|
|
225
229
|
sessionClosedCallback: peerNodeId => {
|
|
226
230
|
logger.info(`Session for peer node ${peerNodeId} disconnected ...`);
|
|
227
|
-
const handler = this
|
|
231
|
+
const handler = this.#sessionDisconnectedHandler.get(peerNodeId);
|
|
228
232
|
if (handler !== undefined) {
|
|
229
233
|
handler().catch(error => logger.warn(`Error while handling session disconnect: ${error}`));
|
|
230
234
|
}
|
|
@@ -235,30 +239,30 @@ export class CommissioningController extends MatterNode {
|
|
|
235
239
|
caseAuthenticatedTags,
|
|
236
240
|
adminFabricLabel,
|
|
237
241
|
});
|
|
238
|
-
if (this
|
|
239
|
-
controller.addBroadcaster(this
|
|
242
|
+
if (this.#mdnsBroadcaster) {
|
|
243
|
+
controller.addBroadcaster(this.#mdnsBroadcaster.createInstanceBroadcaster(port));
|
|
240
244
|
}
|
|
241
245
|
return controller;
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
/**
|
|
245
249
|
* Commissions/Pairs a new device into the controller fabric. The method returns the NodeId of the commissioned
|
|
246
|
-
* node.
|
|
250
|
+
* node on success.
|
|
247
251
|
*/
|
|
248
252
|
async commissionNode(nodeOptions: NodeCommissioningOptions, connectNodeAfterCommissioning = true) {
|
|
249
|
-
this
|
|
250
|
-
const controller = this
|
|
253
|
+
this.#assertIsAddedToMatterServer();
|
|
254
|
+
const controller = this.#assertControllerIsStarted();
|
|
251
255
|
|
|
252
256
|
const nodeId = await controller.commission(nodeOptions);
|
|
253
257
|
|
|
254
258
|
if (connectNodeAfterCommissioning) {
|
|
255
259
|
const node = await this.connectNode(nodeId, {
|
|
256
260
|
...nodeOptions,
|
|
257
|
-
autoSubscribe: nodeOptions.autoSubscribe ?? this
|
|
261
|
+
autoSubscribe: nodeOptions.autoSubscribe ?? this.#options.autoSubscribe,
|
|
258
262
|
subscribeMinIntervalFloorSeconds:
|
|
259
|
-
nodeOptions.subscribeMinIntervalFloorSeconds ?? this
|
|
263
|
+
nodeOptions.subscribeMinIntervalFloorSeconds ?? this.#options.subscribeMinIntervalFloorSeconds,
|
|
260
264
|
subscribeMaxIntervalCeilingSeconds:
|
|
261
|
-
nodeOptions.subscribeMaxIntervalCeilingSeconds ?? this
|
|
265
|
+
nodeOptions.subscribeMaxIntervalCeilingSeconds ?? this.#options.subscribeMaxIntervalCeilingSeconds,
|
|
262
266
|
});
|
|
263
267
|
await node.events.initialized;
|
|
264
268
|
}
|
|
@@ -272,14 +276,14 @@ export class CommissioningController extends MatterNode {
|
|
|
272
276
|
* process.
|
|
273
277
|
*/
|
|
274
278
|
completeCommissioningForNode(peerNodeId: NodeId, discoveryData?: DiscoveryData) {
|
|
275
|
-
this
|
|
276
|
-
const controller = this
|
|
279
|
+
this.#assertIsAddedToMatterServer();
|
|
280
|
+
const controller = this.#assertControllerIsStarted();
|
|
277
281
|
return controller.completeCommissioning(peerNodeId, discoveryData);
|
|
278
282
|
}
|
|
279
283
|
|
|
280
284
|
/** Check if a given node id is commissioned on this controller. */
|
|
281
285
|
isNodeCommissioned(nodeId: NodeId) {
|
|
282
|
-
const controller = this
|
|
286
|
+
const controller = this.#assertControllerIsStarted();
|
|
283
287
|
return controller.getCommissionedNodes().includes(nodeId) ?? false;
|
|
284
288
|
}
|
|
285
289
|
|
|
@@ -287,11 +291,13 @@ export class CommissioningController extends MatterNode {
|
|
|
287
291
|
* Remove a Node id from the controller. This method should only be used if the decommission method on the
|
|
288
292
|
* PairedNode instance returns an error. By default, it tries to decommission the node from the controller but will
|
|
289
293
|
* remove it also in case of an error during decommissioning. Ideally try to decommission the node before and only
|
|
290
|
-
* use this in case of an error.
|
|
294
|
+
* use this in case of an error as last option.
|
|
295
|
+
* If this method is used the state of the PairedNode instance might be out of sync, so the PairedNode instance
|
|
296
|
+
* should be disconnected first.
|
|
291
297
|
*/
|
|
292
298
|
async removeNode(nodeId: NodeId, tryDecommissioning = true) {
|
|
293
|
-
const controller = this
|
|
294
|
-
const node = this
|
|
299
|
+
const controller = this.#assertControllerIsStarted();
|
|
300
|
+
const node = this.#initializedNodes.get(nodeId);
|
|
295
301
|
let decommissionSuccess = false;
|
|
296
302
|
if (tryDecommissioning) {
|
|
297
303
|
try {
|
|
@@ -308,19 +314,24 @@ export class CommissioningController extends MatterNode {
|
|
|
308
314
|
node.close(!decommissionSuccess);
|
|
309
315
|
}
|
|
310
316
|
await controller.removeNode(nodeId);
|
|
311
|
-
this
|
|
317
|
+
this.#initializedNodes.delete(nodeId);
|
|
312
318
|
}
|
|
313
319
|
|
|
320
|
+
/** @deprecated Use PairedNode.disconnect() instead */
|
|
314
321
|
async disconnectNode(nodeId: NodeId) {
|
|
315
|
-
const node = this
|
|
322
|
+
const node = this.#initializedNodes.get(nodeId);
|
|
316
323
|
if (node === undefined) {
|
|
317
324
|
throw new ImplementationError(`Node ${nodeId} is not connected!`);
|
|
318
325
|
}
|
|
319
|
-
await this
|
|
326
|
+
await this.#controllerInstance?.disconnect(nodeId);
|
|
320
327
|
}
|
|
321
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Returns the PairedNode instance for a given NodeId. The instance is initialized without auto connect if not yet
|
|
331
|
+
* created.
|
|
332
|
+
*/
|
|
322
333
|
async getNode(nodeId: NodeId) {
|
|
323
|
-
const existingNode = this
|
|
334
|
+
const existingNode = this.#initializedNodes.get(nodeId);
|
|
324
335
|
if (existingNode !== undefined) {
|
|
325
336
|
return existingNode;
|
|
326
337
|
}
|
|
@@ -332,15 +343,17 @@ export class CommissioningController extends MatterNode {
|
|
|
332
343
|
* After connection the endpoint data of the device is analyzed and an object structure is created.
|
|
333
344
|
* This call is not blocking and returns an initialized PairedNode instance. The connection or reconnection
|
|
334
345
|
* happens in the background. Please monitor the state of the node to see if the connection was successful.
|
|
346
|
+
*
|
|
347
|
+
* @deprecated Use getNode() instead and call PairedNode.connect() or PairedNode.disconnect() as needed.
|
|
335
348
|
*/
|
|
336
349
|
async connectNode(nodeId: NodeId, connectOptions?: CommissioningControllerNodeOptions) {
|
|
337
|
-
const controller = this
|
|
350
|
+
const controller = this.#assertControllerIsStarted();
|
|
338
351
|
|
|
339
352
|
if (!controller.getCommissionedNodes().includes(nodeId)) {
|
|
340
353
|
throw new ImplementationError(`Node ${nodeId} is not commissioned!`);
|
|
341
354
|
}
|
|
342
355
|
|
|
343
|
-
const existingNode = this
|
|
356
|
+
const existingNode = this.#initializedNodes.get(nodeId);
|
|
344
357
|
if (existingNode !== undefined) {
|
|
345
358
|
if (!existingNode.initialized) {
|
|
346
359
|
existingNode.connect(connectOptions);
|
|
@@ -352,14 +365,14 @@ export class CommissioningController extends MatterNode {
|
|
|
352
365
|
nodeId,
|
|
353
366
|
this,
|
|
354
367
|
connectOptions,
|
|
355
|
-
this
|
|
368
|
+
this.#controllerInstance?.getCommissionedNodeDetails(nodeId)?.deviceData ?? {},
|
|
356
369
|
await this.createInteractionClient(nodeId, NodeDiscoveryType.None, false), // First connect without discovery to last known address
|
|
357
370
|
async (discoveryType?: NodeDiscoveryType) => void (await controller.connect(nodeId, { discoveryType })),
|
|
358
|
-
handler => this
|
|
371
|
+
handler => this.#sessionDisconnectedHandler.set(nodeId, handler),
|
|
359
372
|
controller.sessions,
|
|
360
|
-
await this
|
|
373
|
+
await this.#collectStoredAttributeData(nodeId),
|
|
361
374
|
);
|
|
362
|
-
this
|
|
375
|
+
this.#initializedNodes.set(nodeId, pairedNode);
|
|
363
376
|
|
|
364
377
|
pairedNode.events.initializedFromRemote.on(
|
|
365
378
|
async deviceData => await controller.enhanceCommissionedNodeDetails(nodeId, deviceData),
|
|
@@ -368,8 +381,8 @@ export class CommissioningController extends MatterNode {
|
|
|
368
381
|
return pairedNode;
|
|
369
382
|
}
|
|
370
383
|
|
|
371
|
-
async collectStoredAttributeData(nodeId: NodeId): Promise<DecodedAttributeReportValue<any>[]> {
|
|
372
|
-
const controller = this
|
|
384
|
+
async #collectStoredAttributeData(nodeId: NodeId): Promise<DecodedAttributeReportValue<any>[]> {
|
|
385
|
+
const controller = this.#assertControllerIsStarted();
|
|
373
386
|
const storedDataVersions = await controller.getStoredClusterDataVersions(nodeId);
|
|
374
387
|
const result = new Array<DecodedAttributeReportValue<any>>();
|
|
375
388
|
for (const { endpointId, clusterId } of storedDataVersions) {
|
|
@@ -381,9 +394,11 @@ export class CommissioningController extends MatterNode {
|
|
|
381
394
|
/**
|
|
382
395
|
* Connects to all paired nodes.
|
|
383
396
|
* After connection the endpoint data of the device is analyzed and an object structure is created.
|
|
397
|
+
*
|
|
398
|
+
* @deprecated Use getCommissionedNodes() to get the list of nodes and getNode(nodeId) instead and call PairedNode.connect() or PairedNode.disconnect() as needed.
|
|
384
399
|
*/
|
|
385
400
|
async connect(connectOptions?: CommissioningControllerNodeOptions) {
|
|
386
|
-
const controller = this
|
|
401
|
+
const controller = this.#assertControllerIsStarted();
|
|
387
402
|
|
|
388
403
|
if (!controller.isCommissioned()) {
|
|
389
404
|
throw new ImplementationError(
|
|
@@ -394,40 +409,43 @@ export class CommissioningController extends MatterNode {
|
|
|
394
409
|
for (const nodeId of controller.getCommissionedNodes()) {
|
|
395
410
|
await this.connectNode(nodeId, connectOptions);
|
|
396
411
|
}
|
|
397
|
-
return Array.from(this
|
|
412
|
+
return Array.from(this.#initializedNodes.values());
|
|
398
413
|
}
|
|
399
414
|
|
|
400
415
|
/**
|
|
401
416
|
* Set the MDNS Scanner instance. Should be only used internally
|
|
402
417
|
*
|
|
403
418
|
* @param mdnsScanner MdnsScanner instance
|
|
419
|
+
* @private
|
|
404
420
|
*/
|
|
405
421
|
setMdnsScanner(mdnsScanner: MdnsScanner) {
|
|
406
|
-
this
|
|
422
|
+
this.#mdnsScanner = mdnsScanner;
|
|
407
423
|
}
|
|
408
424
|
|
|
409
425
|
/**
|
|
410
426
|
* Set the MDNS Broadcaster instance. Should be only used internally
|
|
411
427
|
*
|
|
412
428
|
* @param mdnsBroadcaster MdnsBroadcaster instance
|
|
429
|
+
* @private
|
|
413
430
|
*/
|
|
414
431
|
setMdnsBroadcaster(mdnsBroadcaster: MdnsBroadcaster) {
|
|
415
|
-
this
|
|
432
|
+
this.#mdnsBroadcaster = mdnsBroadcaster;
|
|
416
433
|
}
|
|
417
434
|
|
|
418
435
|
/**
|
|
419
436
|
* Set the Storage instance. Should be only used internally
|
|
420
437
|
*
|
|
421
438
|
* @param storage storage context to use
|
|
439
|
+
* @private
|
|
422
440
|
*/
|
|
423
441
|
setStorage(storage: StorageContext<SyncStorage>) {
|
|
424
|
-
this
|
|
425
|
-
this
|
|
442
|
+
this.#storage = storage;
|
|
443
|
+
this.#environment = undefined;
|
|
426
444
|
}
|
|
427
445
|
|
|
428
446
|
/** Returns true if t least one node is commissioned/paired with this controller instance. */
|
|
429
447
|
isCommissioned() {
|
|
430
|
-
const controller = this
|
|
448
|
+
const controller = this.#assertControllerIsStarted();
|
|
431
449
|
|
|
432
450
|
return controller.isCommissioned();
|
|
433
451
|
}
|
|
@@ -441,78 +459,90 @@ export class CommissioningController extends MatterNode {
|
|
|
441
459
|
discoveryType?: NodeDiscoveryType,
|
|
442
460
|
forcedConnection = true,
|
|
443
461
|
): Promise<InteractionClient> {
|
|
444
|
-
const controller = this
|
|
462
|
+
const controller = this.#assertControllerIsStarted();
|
|
445
463
|
if (!forcedConnection) {
|
|
446
464
|
return controller.createInteractionClient(nodeId, { discoveryType });
|
|
447
465
|
}
|
|
448
466
|
return controller.connect(nodeId, { discoveryType });
|
|
449
467
|
}
|
|
450
468
|
|
|
451
|
-
/**
|
|
469
|
+
/**
|
|
470
|
+
* Returns the PairedNode instance for a given node id, if this node is connected.
|
|
471
|
+
* @deprecated Use getNode() instead
|
|
472
|
+
*/
|
|
452
473
|
getPairedNode(nodeId: NodeId) {
|
|
453
|
-
return this
|
|
474
|
+
return this.#initializedNodes.get(nodeId);
|
|
454
475
|
}
|
|
455
476
|
|
|
456
|
-
/** Returns an array with the
|
|
477
|
+
/** Returns an array with the NodeIds of all commissioned nodes. */
|
|
457
478
|
getCommissionedNodes() {
|
|
458
|
-
const controller = this
|
|
479
|
+
const controller = this.#assertControllerIsStarted();
|
|
459
480
|
|
|
460
481
|
return controller.getCommissionedNodes() ?? [];
|
|
461
482
|
}
|
|
462
483
|
|
|
484
|
+
/** Returns an arra with all commissioned NodeIds and their metadata. */
|
|
463
485
|
getCommissionedNodesDetails() {
|
|
464
|
-
const controller = this
|
|
486
|
+
const controller = this.#assertControllerIsStarted();
|
|
465
487
|
|
|
466
488
|
return controller.getCommissionedNodesDetails() ?? [];
|
|
467
489
|
}
|
|
468
490
|
|
|
469
|
-
/**
|
|
491
|
+
/**
|
|
492
|
+
* Disconnects all connected nodes and closes the network connections and other resources of the controller.
|
|
493
|
+
* You can use "start()" to restart the controller after closing it.
|
|
494
|
+
*/
|
|
470
495
|
async close() {
|
|
471
|
-
for (const node of this
|
|
496
|
+
for (const node of this.#initializedNodes.values()) {
|
|
472
497
|
node.close();
|
|
473
498
|
}
|
|
474
|
-
await this
|
|
475
|
-
this
|
|
476
|
-
this
|
|
477
|
-
this
|
|
478
|
-
this
|
|
499
|
+
await this.#controllerInstance?.close();
|
|
500
|
+
this.#controllerInstance = undefined;
|
|
501
|
+
this.#initializedNodes.clear();
|
|
502
|
+
this.#ipv4Disabled = undefined;
|
|
503
|
+
this.#started = false;
|
|
479
504
|
}
|
|
480
505
|
|
|
506
|
+
/** Return the port used by the controller for the UDP interface. */
|
|
481
507
|
getPort(): number | undefined {
|
|
482
|
-
return this
|
|
508
|
+
return this.#options.localPort;
|
|
483
509
|
}
|
|
484
510
|
|
|
511
|
+
/** @private */
|
|
485
512
|
initialize(ipv4Disabled: boolean) {
|
|
486
|
-
if (this
|
|
513
|
+
if (this.#started) {
|
|
487
514
|
throw new ImplementationError("Controller instance already started.");
|
|
488
515
|
}
|
|
489
|
-
if (this
|
|
516
|
+
if (this.#ipv4Disabled !== undefined && this.#ipv4Disabled !== ipv4Disabled) {
|
|
490
517
|
throw new ImplementationError(
|
|
491
518
|
"Changing the IPv4 disabled flag after starting the controller is not supported.",
|
|
492
519
|
);
|
|
493
520
|
}
|
|
494
|
-
this
|
|
521
|
+
this.#ipv4Disabled = ipv4Disabled;
|
|
495
522
|
}
|
|
496
523
|
|
|
524
|
+
/** @private */
|
|
497
525
|
async initializeControllerStore() {
|
|
498
526
|
// This can only happen if "MatterServer" approach is not used
|
|
499
|
-
if (this
|
|
527
|
+
if (this.#options.environment === undefined) {
|
|
500
528
|
throw new ImplementationError("Initialization not done. Add the controller to the MatterServer first.");
|
|
501
529
|
}
|
|
502
530
|
|
|
503
|
-
const { environment, id } = this
|
|
531
|
+
const { environment, id } = this.#options.environment;
|
|
504
532
|
const controllerStore = await ControllerStore.create(id, environment);
|
|
505
533
|
environment.set(ControllerStore, controllerStore);
|
|
506
534
|
}
|
|
507
535
|
|
|
508
|
-
/**
|
|
536
|
+
/**
|
|
537
|
+
* Initialize the controller and initialize and connect to all commissioned nodes if autoConnect is not set to false.
|
|
538
|
+
*/
|
|
509
539
|
async start() {
|
|
510
|
-
if (this
|
|
511
|
-
if (this
|
|
540
|
+
if (this.#ipv4Disabled === undefined) {
|
|
541
|
+
if (this.#options.environment === undefined) {
|
|
512
542
|
throw new ImplementationError("Initialization not done. Add the controller to the MatterServer first.");
|
|
513
543
|
}
|
|
514
544
|
|
|
515
|
-
const { environment } = this
|
|
545
|
+
const { environment } = this.#options.environment;
|
|
516
546
|
|
|
517
547
|
if (!environment.has(ControllerStore)) {
|
|
518
548
|
await this.initializeControllerStore();
|
|
@@ -520,44 +550,52 @@ export class CommissioningController extends MatterNode {
|
|
|
520
550
|
|
|
521
551
|
// Load the MDNS service from the environment and set onto the controller
|
|
522
552
|
const mdnsService = await environment.load(MdnsService);
|
|
523
|
-
this
|
|
553
|
+
this.#ipv4Disabled = !mdnsService.enableIpv4;
|
|
524
554
|
this.setMdnsBroadcaster(mdnsService.broadcaster);
|
|
525
555
|
this.setMdnsScanner(mdnsService.scanner);
|
|
526
556
|
|
|
527
|
-
this
|
|
528
|
-
const runtime = this
|
|
557
|
+
this.#environment = environment;
|
|
558
|
+
const runtime = this.#environment.runtime;
|
|
529
559
|
runtime.add(this);
|
|
530
560
|
}
|
|
531
561
|
|
|
532
|
-
this
|
|
533
|
-
if (this
|
|
534
|
-
this
|
|
562
|
+
this.#started = true;
|
|
563
|
+
if (this.#controllerInstance === undefined) {
|
|
564
|
+
this.#controllerInstance = await this.#initializeController();
|
|
535
565
|
}
|
|
536
|
-
await this
|
|
537
|
-
if (this
|
|
566
|
+
await this.#controllerInstance.announce();
|
|
567
|
+
if (this.#options.autoConnect !== false && this.#controllerInstance.isCommissioned()) {
|
|
538
568
|
await this.connect();
|
|
539
569
|
}
|
|
540
570
|
}
|
|
541
571
|
|
|
572
|
+
/**
|
|
573
|
+
* Cancels the discovery process for commissionable devices started with discoverCommissionableDevices().
|
|
574
|
+
*/
|
|
542
575
|
cancelCommissionableDeviceDiscovery(
|
|
543
576
|
identifierData: CommissionableDeviceIdentifiers,
|
|
544
577
|
discoveryCapabilities?: TypeFromPartialBitSchema<typeof DiscoveryCapabilitiesBitmap>,
|
|
545
578
|
) {
|
|
546
|
-
this
|
|
547
|
-
const controller = this
|
|
579
|
+
this.#assertIsAddedToMatterServer();
|
|
580
|
+
const controller = this.#assertControllerIsStarted();
|
|
548
581
|
controller
|
|
549
582
|
.collectScanners(discoveryCapabilities)
|
|
550
583
|
.forEach(scanner => ControllerDiscovery.cancelCommissionableDeviceDiscovery(scanner, identifierData));
|
|
551
584
|
}
|
|
552
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Starts to discover commissionable devices.
|
|
588
|
+
* The promise will be fulfilled after the provided timeout or when the discovery is stopped via
|
|
589
|
+
* cancelCommissionableDeviceDiscovery(). The discoveredCallback will be called for each discovered device.
|
|
590
|
+
*/
|
|
553
591
|
async discoverCommissionableDevices(
|
|
554
592
|
identifierData: CommissionableDeviceIdentifiers,
|
|
555
593
|
discoveryCapabilities?: TypeFromPartialBitSchema<typeof DiscoveryCapabilitiesBitmap>,
|
|
556
594
|
discoveredCallback?: (device: CommissionableDevice) => void,
|
|
557
595
|
timeoutSeconds = 900,
|
|
558
596
|
) {
|
|
559
|
-
this
|
|
560
|
-
const controller = this
|
|
597
|
+
this.#assertIsAddedToMatterServer();
|
|
598
|
+
const controller = this.#assertControllerIsStarted();
|
|
561
599
|
return await ControllerDiscovery.discoverCommissionableDevices(
|
|
562
600
|
controller.collectScanners(discoveryCapabilities),
|
|
563
601
|
timeoutSeconds,
|
|
@@ -566,11 +604,15 @@ export class CommissioningController extends MatterNode {
|
|
|
566
604
|
);
|
|
567
605
|
}
|
|
568
606
|
|
|
607
|
+
/**
|
|
608
|
+
* Use this method to reset the Controller storage. The method can only be called if the controller is stopped and
|
|
609
|
+
* will remove all commissioning data and paired nodes from the controller.
|
|
610
|
+
*/
|
|
569
611
|
async resetStorage() {
|
|
570
|
-
this
|
|
612
|
+
this.#assertControllerIsStarted(
|
|
571
613
|
"Storage cannot be reset while the controller is operating! Please close the controller first.",
|
|
572
614
|
);
|
|
573
|
-
const { storage, environment } = this
|
|
615
|
+
const { storage, environment } = this.#assertIsAddedToMatterServer();
|
|
574
616
|
if (environment !== undefined) {
|
|
575
617
|
const controllerStore = environment.get(ControllerStore);
|
|
576
618
|
await controllerStore.erase();
|
|
@@ -583,12 +625,13 @@ export class CommissioningController extends MatterNode {
|
|
|
583
625
|
|
|
584
626
|
/** Returns active session information for all connected nodes. */
|
|
585
627
|
getActiveSessionInformation() {
|
|
586
|
-
return this
|
|
628
|
+
return this.#controllerInstance?.getActiveSessionInformation() ?? [];
|
|
587
629
|
}
|
|
588
630
|
|
|
631
|
+
/** @private */
|
|
589
632
|
async validateAndUpdateFabricLabel(nodeId: NodeId) {
|
|
590
|
-
const controller = this
|
|
591
|
-
const node = this
|
|
633
|
+
const controller = this.#assertControllerIsStarted();
|
|
634
|
+
const node = this.#initializedNodes.get(nodeId);
|
|
592
635
|
if (node === undefined) {
|
|
593
636
|
throw new ImplementationError(`Node ${nodeId} is not connected!`);
|
|
594
637
|
}
|
|
@@ -614,14 +657,18 @@ export class CommissioningController extends MatterNode {
|
|
|
614
657
|
}
|
|
615
658
|
}
|
|
616
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Updates the fabric label for the controller and all connected nodes.
|
|
662
|
+
* The label is used to identify the controller and all connected nodes in the fabric.
|
|
663
|
+
*/
|
|
617
664
|
async updateFabricLabel(label: string) {
|
|
618
|
-
const controller = this
|
|
665
|
+
const controller = this.#assertControllerIsStarted();
|
|
619
666
|
if (controller.fabricConfig.label === label) {
|
|
620
667
|
return;
|
|
621
668
|
}
|
|
622
669
|
await controller.updateFabricLabel(label);
|
|
623
670
|
|
|
624
|
-
for (const node of this
|
|
671
|
+
for (const node of this.#initializedNodes.values()) {
|
|
625
672
|
if (node.isConnected) {
|
|
626
673
|
// When Node is connected, update the fabric label on the node directly
|
|
627
674
|
try {
|
|
@@ -643,14 +690,14 @@ export class CommissioningController extends MatterNode {
|
|
|
643
690
|
|
|
644
691
|
// If no update handler is registered, register one
|
|
645
692
|
// TODO: Convert this next to a task system for node tasks and also better handle error cases
|
|
646
|
-
if (!this
|
|
693
|
+
if (!this.#nodeUpdateLabelHandlers.has(node.nodeId)) {
|
|
647
694
|
const updateOnReconnect = (nodeState: NodeStates) => {
|
|
648
695
|
if (nodeState === NodeStates.Connected) {
|
|
649
696
|
this.validateAndUpdateFabricLabel(node.nodeId)
|
|
650
697
|
.catch(error => logger.warn(`Error updating fabric label on node ${node.nodeId}:`, error))
|
|
651
698
|
.finally(() => {
|
|
652
699
|
node.events.stateChanged.off(updateOnReconnect);
|
|
653
|
-
this
|
|
700
|
+
this.#nodeUpdateLabelHandlers.delete(node.nodeId);
|
|
654
701
|
});
|
|
655
702
|
}
|
|
656
703
|
};
|