@project-chip/matter.js 0.11.9-alpha.0-20241207-b604cfa44 → 0.11.9-alpha.0-20241208-09ad57f78
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/device/PairedNode.d.ts +10 -28
- package/dist/cjs/device/PairedNode.d.ts.map +1 -1
- package/dist/cjs/device/PairedNode.js +86 -56
- package/dist/cjs/device/PairedNode.js.map +1 -1
- package/dist/esm/device/PairedNode.d.ts +10 -28
- package/dist/esm/device/PairedNode.d.ts.map +1 -1
- package/dist/esm/device/PairedNode.js +86 -56
- package/dist/esm/device/PairedNode.js.map +1 -1
- package/package.json +8 -8
- package/src/device/PairedNode.ts +87 -56
package/src/device/PairedNode.ts
CHANGED
|
@@ -206,7 +206,7 @@ export class PairedNode {
|
|
|
206
206
|
readonly #updateEndpointStructureTimer = Time.getTimer(
|
|
207
207
|
"Endpoint structure update",
|
|
208
208
|
STRUCTURE_UPDATE_TIMEOUT_MS,
|
|
209
|
-
async () => await this
|
|
209
|
+
async () => await this.#updateEndpointStructure(),
|
|
210
210
|
);
|
|
211
211
|
#connectionState: NodeStates = NodeStates.Disconnected;
|
|
212
212
|
#reconnectionInProgress = false;
|
|
@@ -294,7 +294,7 @@ export class PairedNode {
|
|
|
294
294
|
}`,
|
|
295
295
|
);
|
|
296
296
|
if (this.#connectionState === NodeStates.Connected) {
|
|
297
|
-
this
|
|
297
|
+
this.#scheduleReconnect();
|
|
298
298
|
}
|
|
299
299
|
});
|
|
300
300
|
|
|
@@ -310,7 +310,7 @@ export class PairedNode {
|
|
|
310
310
|
logger.info(`Node ${this.nodeId}: Got a reconnect, so reconnection not needed anymore ...`);
|
|
311
311
|
this.#reconnectDelayTimer?.stop();
|
|
312
312
|
this.#reconnectDelayTimer = undefined;
|
|
313
|
-
this
|
|
313
|
+
this.#setConnectionState(NodeStates.Connected);
|
|
314
314
|
}
|
|
315
315
|
});
|
|
316
316
|
this.#nodeDetails = new DeviceInformation(nodeId, knownNodeDetails);
|
|
@@ -319,15 +319,15 @@ export class PairedNode {
|
|
|
319
319
|
this.#construction = Construction(this, async () => {
|
|
320
320
|
// We try to initialize from stored data already
|
|
321
321
|
if (storedAttributeData !== undefined) {
|
|
322
|
-
await this
|
|
322
|
+
await this.#initializeFromStoredData(storedAttributeData);
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
// This kicks of the remote initialization and automatic reconnection handling if it can not be connected
|
|
326
|
-
this
|
|
326
|
+
this.#initialize().catch(error => {
|
|
327
327
|
logger.info(`Node ${nodeId}: Error during remote initialization`, error);
|
|
328
328
|
if (this.state !== NodeStates.Disconnected) {
|
|
329
|
-
this
|
|
330
|
-
this
|
|
329
|
+
this.#setConnectionState(NodeStates.WaitingForDeviceDiscovery);
|
|
330
|
+
this.#scheduleReconnect();
|
|
331
331
|
}
|
|
332
332
|
});
|
|
333
333
|
});
|
|
@@ -365,7 +365,7 @@ export class PairedNode {
|
|
|
365
365
|
return this.#remoteInitializationDone || this.#localInitializationDone;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
|
|
368
|
+
#setConnectionState(state: NodeStates) {
|
|
369
369
|
if (
|
|
370
370
|
this.#connectionState === state ||
|
|
371
371
|
(this.#connectionState === NodeStates.WaitingForDeviceDiscovery && state === NodeStates.Reconnecting)
|
|
@@ -381,7 +381,7 @@ export class PairedNode {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
/** Make sure to not request a new Interaction client multiple times in parallel. */
|
|
384
|
-
async handleReconnect(discoveryType?: NodeDiscoveryType): Promise<void> {
|
|
384
|
+
async #handleReconnect(discoveryType?: NodeDiscoveryType): Promise<void> {
|
|
385
385
|
if (this.#clientReconnectInProgress) {
|
|
386
386
|
throw new NodeNotConnectedError("Reconnection already in progress. Node not reachable currently.");
|
|
387
387
|
}
|
|
@@ -395,47 +395,74 @@ export class PairedNode {
|
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
/**
|
|
398
|
-
*
|
|
398
|
+
* Trigger a reconnection to the device. This method is non-blocking and will return immediately.
|
|
399
|
+
* The reconnection happens in the background. Please monitor the state of the node to see if the
|
|
400
|
+
* reconnection was successful.
|
|
401
|
+
*/
|
|
402
|
+
triggerReconnect() {
|
|
403
|
+
if (this.#reconnectionInProgress || this.#remoteInitializationInProgress) {
|
|
404
|
+
logger.info(
|
|
405
|
+
`Ignoring reconnect request because ${this.#remoteInitializationInProgress ? "init" : "reconnect"} already underway.`,
|
|
406
|
+
);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
this.#scheduleReconnect(0);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Force a reconnection to the device.
|
|
414
|
+
* This method is mainly used internally to reconnect after the active session
|
|
399
415
|
* was closed or the device went offline and was detected as being online again.
|
|
416
|
+
* Please note that this method does not return until the device is reconnected.
|
|
417
|
+
* Please use triggerReconnect method for a non-blocking reconnection triggering.
|
|
400
418
|
*/
|
|
401
419
|
async reconnect(connectOptions?: CommissioningControllerNodeOptions) {
|
|
402
420
|
if (connectOptions !== undefined) {
|
|
403
421
|
this.options = connectOptions;
|
|
404
422
|
}
|
|
405
|
-
if (this.#reconnectionInProgress) {
|
|
406
|
-
logger.debug(
|
|
423
|
+
if (this.#reconnectionInProgress || this.#remoteInitializationInProgress) {
|
|
424
|
+
logger.debug(
|
|
425
|
+
`Ignoring reconnect request because ${this.#remoteInitializationInProgress ? "init" : "reconnect"} already underway.`,
|
|
426
|
+
);
|
|
407
427
|
return;
|
|
408
428
|
}
|
|
429
|
+
if (this.#reconnectDelayTimer?.isRunning) {
|
|
430
|
+
this.#reconnectDelayTimer.stop();
|
|
431
|
+
}
|
|
409
432
|
|
|
410
433
|
this.#reconnectionInProgress = true;
|
|
411
434
|
if (this.#connectionState !== NodeStates.WaitingForDeviceDiscovery) {
|
|
412
|
-
this
|
|
435
|
+
this.#setConnectionState(NodeStates.Reconnecting);
|
|
413
436
|
|
|
414
437
|
try {
|
|
415
438
|
// First try a reconnect to known addresses to see if the device is reachable
|
|
416
|
-
await this
|
|
439
|
+
await this.#handleReconnect(NodeDiscoveryType.None);
|
|
417
440
|
this.#reconnectionInProgress = false;
|
|
418
|
-
await this
|
|
441
|
+
await this.#initialize();
|
|
419
442
|
return;
|
|
420
443
|
} catch (error) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
444
|
+
if (error instanceof MatterError) {
|
|
445
|
+
logger.info(
|
|
446
|
+
`Node ${this.nodeId}: Simple re-establishing session did not worked. Reconnect ... `,
|
|
447
|
+
error,
|
|
448
|
+
);
|
|
449
|
+
} else {
|
|
450
|
+
this.#reconnectionInProgress = false;
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
426
453
|
}
|
|
427
454
|
}
|
|
428
455
|
|
|
429
|
-
this
|
|
456
|
+
this.#setConnectionState(NodeStates.WaitingForDeviceDiscovery);
|
|
430
457
|
|
|
431
458
|
try {
|
|
432
|
-
await this
|
|
459
|
+
await this.#initialize();
|
|
433
460
|
} catch (error) {
|
|
434
461
|
MatterError.accept(error);
|
|
435
462
|
|
|
436
463
|
if (error instanceof UnknownNodeError) {
|
|
437
464
|
logger.info(`Node ${this.nodeId}: Node is unknown by controller, we can not connect.`);
|
|
438
|
-
this
|
|
465
|
+
this.#setConnectionState(NodeStates.Disconnected);
|
|
439
466
|
} else if (this.#connectionState === NodeStates.Disconnected) {
|
|
440
467
|
logger.info("No reconnection desired because requested status is Disconnected.");
|
|
441
468
|
} else {
|
|
@@ -447,18 +474,19 @@ export class PairedNode {
|
|
|
447
474
|
logger.info(`Node ${this.nodeId}: Error waiting for device rediscovery, retrying`, error);
|
|
448
475
|
}
|
|
449
476
|
this.#reconnectErrorCount++;
|
|
450
|
-
this
|
|
477
|
+
this.#scheduleReconnect();
|
|
451
478
|
}
|
|
479
|
+
} finally {
|
|
480
|
+
this.#reconnectionInProgress = false;
|
|
452
481
|
}
|
|
453
|
-
this.#reconnectionInProgress = false;
|
|
454
482
|
}
|
|
455
483
|
|
|
456
484
|
/** Ensure that the node is connected by creating a new InteractionClient if needed. */
|
|
457
|
-
|
|
485
|
+
async #ensureConnection(forceConnect = false): Promise<InteractionClient> {
|
|
458
486
|
if (this.#connectionState === NodeStates.Disconnected) {
|
|
459
487
|
// Disconnected and having an InteractionClient means we initialized with an Offline one, so we do
|
|
460
488
|
// connection now on usage
|
|
461
|
-
this
|
|
489
|
+
this.#setConnectionState(NodeStates.Reconnecting);
|
|
462
490
|
return this.#interactionClient;
|
|
463
491
|
}
|
|
464
492
|
if (this.#connectionState === NodeStates.Connected && !forceConnect) {
|
|
@@ -466,17 +494,17 @@ export class PairedNode {
|
|
|
466
494
|
}
|
|
467
495
|
|
|
468
496
|
if (forceConnect) {
|
|
469
|
-
this
|
|
497
|
+
this.#setConnectionState(NodeStates.WaitingForDeviceDiscovery);
|
|
470
498
|
}
|
|
471
499
|
|
|
472
|
-
await this
|
|
500
|
+
await this.#handleReconnect(NodeDiscoveryType.FullDiscovery);
|
|
473
501
|
if (!forceConnect) {
|
|
474
|
-
this
|
|
502
|
+
this.#setConnectionState(NodeStates.Connected);
|
|
475
503
|
}
|
|
476
504
|
return this.#interactionClient;
|
|
477
505
|
}
|
|
478
506
|
|
|
479
|
-
|
|
507
|
+
async #initializeFromStoredData(storedAttributeData: DecodedAttributeReportValue<any>[]) {
|
|
480
508
|
const { autoSubscribe } = this.options;
|
|
481
509
|
if (this.#remoteInitializationDone || this.#localInitializationDone || autoSubscribe === false) return;
|
|
482
510
|
|
|
@@ -496,7 +524,7 @@ export class PairedNode {
|
|
|
496
524
|
return;
|
|
497
525
|
}
|
|
498
526
|
|
|
499
|
-
await this
|
|
527
|
+
await this.#initializeEndpointStructure(storedAttributeData);
|
|
500
528
|
|
|
501
529
|
// Inform interested parties that the node is initialized
|
|
502
530
|
await this.events.initialized.emit(this.#nodeDetails.toStorageData());
|
|
@@ -506,7 +534,7 @@ export class PairedNode {
|
|
|
506
534
|
/**
|
|
507
535
|
* Initialize the node after the InteractionClient was created and to subscribe attributes and events if requested.
|
|
508
536
|
*/
|
|
509
|
-
|
|
537
|
+
async #initialize() {
|
|
510
538
|
if (this.#remoteInitializationInProgress) {
|
|
511
539
|
logger.info(`Node ${this.nodeId}: Remote initialization already in progress ...`);
|
|
512
540
|
return;
|
|
@@ -514,7 +542,7 @@ export class PairedNode {
|
|
|
514
542
|
this.#remoteInitializationInProgress = true;
|
|
515
543
|
try {
|
|
516
544
|
// Enforce a new Connection
|
|
517
|
-
await this
|
|
545
|
+
await this.#ensureConnection(true);
|
|
518
546
|
const { autoSubscribe, attributeChangedCallback, eventTriggeredCallback } = this.options;
|
|
519
547
|
|
|
520
548
|
let deviceDetailsUpdated = false;
|
|
@@ -541,7 +569,10 @@ export class PairedNode {
|
|
|
541
569
|
if (initialSubscriptionData.attributeReports === undefined) {
|
|
542
570
|
throw new InternalError("No attribute reports received when subscribing to all values!");
|
|
543
571
|
}
|
|
544
|
-
await this
|
|
572
|
+
await this.#initializeEndpointStructure(
|
|
573
|
+
initialSubscriptionData.attributeReports,
|
|
574
|
+
anyInitializationDone,
|
|
575
|
+
);
|
|
545
576
|
|
|
546
577
|
if (!deviceDetailsUpdated) {
|
|
547
578
|
const rootEndpoint = this.getRootEndpoint();
|
|
@@ -551,10 +582,10 @@ export class PairedNode {
|
|
|
551
582
|
}
|
|
552
583
|
} else {
|
|
553
584
|
const allClusterAttributes = await this.readAllAttributes();
|
|
554
|
-
await this
|
|
585
|
+
await this.#initializeEndpointStructure(allClusterAttributes, anyInitializationDone);
|
|
555
586
|
}
|
|
556
587
|
this.#reconnectErrorCount = 0;
|
|
557
|
-
this
|
|
588
|
+
this.#setConnectionState(NodeStates.Connected);
|
|
558
589
|
await this.events.initializedFromRemote.emit(this.#nodeDetails.toStorageData());
|
|
559
590
|
if (!this.#localInitializationDone) {
|
|
560
591
|
await this.events.initialized.emit(this.#nodeDetails.toStorageData());
|
|
@@ -571,7 +602,7 @@ export class PairedNode {
|
|
|
571
602
|
* ClusterClients of the Devices of the node should be used instead.
|
|
572
603
|
*/
|
|
573
604
|
getInteractionClient() {
|
|
574
|
-
return this
|
|
605
|
+
return this.#ensureConnection();
|
|
575
606
|
}
|
|
576
607
|
|
|
577
608
|
/** Method to log the structure of this node with all endpoint and clusters. */
|
|
@@ -676,17 +707,17 @@ export class PairedNode {
|
|
|
676
707
|
},
|
|
677
708
|
updateTimeoutHandler: async () => {
|
|
678
709
|
logger.info(`Node ${this.nodeId}: Subscription timed out ... trying to re-establish ...`);
|
|
679
|
-
this
|
|
710
|
+
this.#setConnectionState(NodeStates.Reconnecting);
|
|
680
711
|
this.#reconnectionInProgress = true;
|
|
681
712
|
try {
|
|
682
713
|
await this.subscribeAllAttributesAndEvents({ ...options, ignoreInitialTriggers: false });
|
|
683
|
-
this
|
|
714
|
+
this.#setConnectionState(NodeStates.Connected);
|
|
684
715
|
} catch (error) {
|
|
685
716
|
logger.info(
|
|
686
717
|
`Node ${this.nodeId}: Error resubscribing to all attributes and events. Try to reconnect ...`,
|
|
687
718
|
error,
|
|
688
719
|
);
|
|
689
|
-
this
|
|
720
|
+
this.#scheduleReconnect();
|
|
690
721
|
} finally {
|
|
691
722
|
this.#reconnectionInProgress = false;
|
|
692
723
|
}
|
|
@@ -696,7 +727,7 @@ export class PairedNode {
|
|
|
696
727
|
logger.info(`Node ${this.nodeId}: Got subscription update, so reconnection not needed anymore ...`);
|
|
697
728
|
this.#reconnectDelayTimer.stop();
|
|
698
729
|
this.#reconnectDelayTimer = undefined;
|
|
699
|
-
this
|
|
730
|
+
this.#setConnectionState(NodeStates.Connected);
|
|
700
731
|
}
|
|
701
732
|
},
|
|
702
733
|
};
|
|
@@ -768,19 +799,19 @@ export class PairedNode {
|
|
|
768
799
|
#checkEventsForNeededStructureUpdate(_endpointId: EndpointNumber, clusterId: ClusterId, eventId: EventId) {
|
|
769
800
|
// When we subscribe all data here then we can also catch this case and handle it
|
|
770
801
|
if (clusterId === BasicInformation.Cluster.id && eventId === BasicInformation.Cluster.events.shutDown.id) {
|
|
771
|
-
this
|
|
802
|
+
this.#handleNodeShutdown();
|
|
772
803
|
}
|
|
773
804
|
}
|
|
774
805
|
|
|
775
806
|
/** Handles a node shutDown event (if supported by the node and received). */
|
|
776
|
-
|
|
807
|
+
#handleNodeShutdown() {
|
|
777
808
|
logger.info(`Node ${this.nodeId}: Node shutdown detected, trying to reconnect ...`);
|
|
778
|
-
this
|
|
809
|
+
this.#scheduleReconnect(RECONNECT_DELAY_AFTER_SHUTDOWN_MS);
|
|
779
810
|
}
|
|
780
811
|
|
|
781
|
-
|
|
812
|
+
#scheduleReconnect(delay?: number) {
|
|
782
813
|
if (this.state !== NodeStates.WaitingForDeviceDiscovery) {
|
|
783
|
-
this
|
|
814
|
+
this.#setConnectionState(NodeStates.Reconnecting);
|
|
784
815
|
}
|
|
785
816
|
|
|
786
817
|
if (!this.#reconnectDelayTimer?.isRunning) {
|
|
@@ -796,15 +827,15 @@ export class PairedNode {
|
|
|
796
827
|
this.#reconnectDelayTimer.start();
|
|
797
828
|
}
|
|
798
829
|
|
|
799
|
-
async updateEndpointStructure() {
|
|
830
|
+
async #updateEndpointStructure() {
|
|
800
831
|
const allClusterAttributes = await this.readAllAttributes();
|
|
801
|
-
await this
|
|
832
|
+
await this.#initializeEndpointStructure(allClusterAttributes, true);
|
|
802
833
|
this.options.stateInformationCallback?.(this.nodeId, NodeStateInformation.StructureChanged);
|
|
803
834
|
this.events.structureChanged.emit();
|
|
804
835
|
}
|
|
805
836
|
|
|
806
837
|
/** Reads all data from the device and create a device object structure out of it. */
|
|
807
|
-
|
|
838
|
+
async #initializeEndpointStructure(
|
|
808
839
|
allClusterAttributes: DecodedAttributeReportValue<any>[],
|
|
809
840
|
updateStructure = false,
|
|
810
841
|
) {
|
|
@@ -847,15 +878,15 @@ export class PairedNode {
|
|
|
847
878
|
logger.debug("Creating device", endpointId, Logger.toJSON(clusters));
|
|
848
879
|
this.#endpoints.set(
|
|
849
880
|
endpointIdNumber,
|
|
850
|
-
this
|
|
881
|
+
this.#createDevice(endpointIdNumber, clusters, this.#interactionClient),
|
|
851
882
|
);
|
|
852
883
|
}
|
|
853
884
|
|
|
854
|
-
this
|
|
885
|
+
this.#structureEndpoints(partLists);
|
|
855
886
|
}
|
|
856
887
|
|
|
857
888
|
/** Bring the endpoints in a structure based on their partsList attribute. */
|
|
858
|
-
|
|
889
|
+
#structureEndpoints(partLists: Map<EndpointNumber, EndpointNumber[]>) {
|
|
859
890
|
logger.debug(`Node ${this.nodeId}: Endpoints from PartsLists`, Logger.toJSON(Array.from(partLists.entries())));
|
|
860
891
|
|
|
861
892
|
const endpointUsages: { [key: EndpointNumber]: EndpointNumber[] } = {};
|
|
@@ -927,7 +958,7 @@ export class PairedNode {
|
|
|
927
958
|
* @param interactionClient InteractionClient to use for the device
|
|
928
959
|
* @private
|
|
929
960
|
*/
|
|
930
|
-
|
|
961
|
+
#createDevice(
|
|
931
962
|
endpointId: EndpointNumber,
|
|
932
963
|
data: { [key: ClusterId]: { [key: string]: any } },
|
|
933
964
|
interactionClient: InteractionClient,
|
|
@@ -1069,7 +1100,7 @@ export class PairedNode {
|
|
|
1069
1100
|
`Removing node ${this.nodeId} failed with status ${result.statusCode} "${result.debugText}".`,
|
|
1070
1101
|
);
|
|
1071
1102
|
}
|
|
1072
|
-
this
|
|
1103
|
+
this.#setConnectionState(NodeStates.Disconnected);
|
|
1073
1104
|
await this.commissioningController.removeNode(this.nodeId, false);
|
|
1074
1105
|
}
|
|
1075
1106
|
|
|
@@ -1180,7 +1211,7 @@ export class PairedNode {
|
|
|
1180
1211
|
this.options.stateInformationCallback?.(this.nodeId, NodeStateInformation.Decommissioned);
|
|
1181
1212
|
this.events.decommissioned.emit();
|
|
1182
1213
|
}
|
|
1183
|
-
this
|
|
1214
|
+
this.#setConnectionState(NodeStates.Disconnected);
|
|
1184
1215
|
}
|
|
1185
1216
|
|
|
1186
1217
|
/**
|