zwave-js 13.2.0 → 13.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mock-server.js +2 -0
- package/build/lib/controller/Controller.d.ts +119 -9
- package/build/lib/controller/Controller.d.ts.map +1 -1
- package/build/lib/controller/Controller.js +1187 -81
- package/build/lib/controller/Controller.js.map +1 -1
- package/build/lib/controller/Inclusion.d.ts +44 -0
- package/build/lib/controller/Inclusion.d.ts.map +1 -1
- package/build/lib/controller/Inclusion.js +42 -1
- package/build/lib/controller/Inclusion.js.map +1 -1
- package/build/lib/driver/Driver.d.ts +16 -1
- package/build/lib/driver/Driver.d.ts.map +1 -1
- package/build/lib/driver/Driver.js +290 -97
- package/build/lib/driver/Driver.js.map +1 -1
- package/build/lib/driver/NetworkCache.d.ts +3 -0
- package/build/lib/driver/NetworkCache.d.ts.map +1 -1
- package/build/lib/driver/NetworkCache.js +37 -9
- package/build/lib/driver/NetworkCache.js.map +1 -1
- package/build/lib/driver/ZWaveOptions.d.ts +10 -3
- package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
- package/build/lib/driver/ZWaveOptions.js.map +1 -1
- package/build/lib/node/Node.d.ts +4 -3
- package/build/lib/node/Node.d.ts.map +1 -1
- package/build/lib/node/Node.js +130 -34
- package/build/lib/node/Node.js.map +1 -1
- package/build/lib/serialapi/application/ApplicationUpdateRequest.d.ts +5 -0
- package/build/lib/serialapi/application/ApplicationUpdateRequest.d.ts.map +1 -1
- package/build/lib/serialapi/application/ApplicationUpdateRequest.js +24 -1
- package/build/lib/serialapi/application/ApplicationUpdateRequest.js.map +1 -1
- package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.d.ts +2 -0
- package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.d.ts.map +1 -1
- package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.js +6 -0
- package/build/lib/serialapi/capability/GetControllerCapabilitiesMessages.js.map +1 -1
- package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.d.ts +2 -9
- package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.d.ts.map +1 -1
- package/build/lib/serialapi/capability/GetSerialApiInitDataMessages.js.map +1 -1
- package/build/lib/serialapi/capability/SerialAPISetupMessages.d.ts +27 -1
- package/build/lib/serialapi/capability/SerialAPISetupMessages.d.ts.map +1 -1
- package/build/lib/serialapi/capability/SerialAPISetupMessages.js +96 -1
- package/build/lib/serialapi/capability/SerialAPISetupMessages.js.map +1 -1
- package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.d.ts +47 -0
- package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.d.ts.map +1 -0
- package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.js +137 -0
- package/build/lib/serialapi/network-mgmt/SetLearnModeMessages.js.map +1 -0
- package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.d.ts +60 -0
- package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.d.ts.map +1 -0
- package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.js +201 -0
- package/build/lib/serialapi/nvm/ExtendedNVMOperationsMessages.js.map +1 -0
- package/package.json +9 -9
|
@@ -209,6 +209,17 @@ function checkOptions(options) {
|
|
|
209
209
|
throw new core_1.ZWaveError(`The inclusionUserCallbacks must contain the following functions: grantSecurityClasses, validateDSKAndEnterPIN, abort!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
+
if (options.joinNetworkUserCallbacks) {
|
|
213
|
+
if (!(0, typeguards_1.isObject)(options.joinNetworkUserCallbacks)) {
|
|
214
|
+
throw new core_1.ZWaveError(`The joinNetworkUserCallbacks must be an object!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
|
|
215
|
+
}
|
|
216
|
+
else if (typeof options.joinNetworkUserCallbacks.showDSK
|
|
217
|
+
!== "function"
|
|
218
|
+
|| typeof options.joinNetworkUserCallbacks.done
|
|
219
|
+
!== "function") {
|
|
220
|
+
throw new core_1.ZWaveError(`The joinNetworkUserCallbacks must contain the following functions: showDSK, done!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
212
223
|
if (options.rf != undefined) {
|
|
213
224
|
if (options.rf.region != undefined) {
|
|
214
225
|
if (typeof options.rf.region !== "number"
|
|
@@ -450,6 +461,24 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
450
461
|
const isLongRange = (0, core_1.isLongRangeNodeId)(nodeId);
|
|
451
462
|
return isLongRange ? this.securityManagerLR : this.securityManager2;
|
|
452
463
|
}
|
|
464
|
+
_learnModeAuthenticatedKeyPair;
|
|
465
|
+
/** @internal */
|
|
466
|
+
getLearnModeAuthenticatedKeyPair() {
|
|
467
|
+
if (this._learnModeAuthenticatedKeyPair == undefined) {
|
|
468
|
+
// Try restoring from cache
|
|
469
|
+
const privateKey = this.cacheGet(NetworkCache_1.cacheKeys.controller.privateKey);
|
|
470
|
+
if (privateKey) {
|
|
471
|
+
this._learnModeAuthenticatedKeyPair =
|
|
472
|
+
(0, core_1.keyPairFromRawECDHPrivateKey)(privateKey);
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
// Not found in cache, create a new one and cache it
|
|
476
|
+
this._learnModeAuthenticatedKeyPair = (0, core_1.generateECDHKeyPair)();
|
|
477
|
+
this.cacheSet(NetworkCache_1.cacheKeys.controller.privateKey, (0, core_1.extractRawECDHPrivateKey)(this._learnModeAuthenticatedKeyPair.privateKey));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return this._learnModeAuthenticatedKeyPair;
|
|
481
|
+
}
|
|
453
482
|
/**
|
|
454
483
|
* **!!! INTERNAL !!!**
|
|
455
484
|
*
|
|
@@ -604,13 +633,16 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
604
633
|
"disableOptimisticValueUpdate",
|
|
605
634
|
"emitValueUpdateAfterSetValue",
|
|
606
635
|
"inclusionUserCallbacks",
|
|
636
|
+
"joinNetworkUserCallbacks",
|
|
607
637
|
"interview",
|
|
608
638
|
"preferences",
|
|
609
639
|
"vendor",
|
|
610
640
|
]);
|
|
611
641
|
// Create a new deep-merged copy of the options so we can check them for validity
|
|
612
|
-
// without affecting our own options
|
|
613
|
-
const
|
|
642
|
+
// without affecting our own options. logConfig is potentially unsafe to clone, so just preserve it.
|
|
643
|
+
const { logConfig, ...rest } = this._options;
|
|
644
|
+
const newOptions = (0, shared_1.mergeDeep)((0, shared_1.cloneDeep)(rest), safeOptions, true);
|
|
645
|
+
newOptions.logConfig = logConfig;
|
|
614
646
|
checkOptions(newOptions);
|
|
615
647
|
if (options.userAgent && !(0, typeguards_1.isObject)(options.userAgent)) {
|
|
616
648
|
throw new core_1.ZWaveError(`The userAgent property must be an object!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
|
|
@@ -903,7 +935,10 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
903
935
|
this._controller
|
|
904
936
|
.on("node added", this.onNodeAdded.bind(this))
|
|
905
937
|
.on("node removed", this.onNodeRemoved.bind(this))
|
|
906
|
-
.on("status changed", this.onControllerStatusChanged.bind(this))
|
|
938
|
+
.on("status changed", this.onControllerStatusChanged.bind(this))
|
|
939
|
+
.on("network found", this.onNetworkFound.bind(this))
|
|
940
|
+
.on("network joined", this.onNetworkJoined.bind(this))
|
|
941
|
+
.on("network left", this.onNetworkLeft.bind(this));
|
|
907
942
|
}
|
|
908
943
|
if (!this._options.testingHooks?.skipControllerIdentification) {
|
|
909
944
|
// Determine what the controller can do
|
|
@@ -942,66 +977,128 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
942
977
|
await this.initValueDBs(this.controller.homeId);
|
|
943
978
|
await this.performCacheMigration();
|
|
944
979
|
// Initialize all nodes and restore the data from cache
|
|
945
|
-
await this.
|
|
980
|
+
await this.controller.initNodes(nodeIds, lrNodeIds ?? [], async () => {
|
|
946
981
|
// Try to restore the network information from the cache
|
|
947
982
|
if (process.env.NO_CACHE !== "true") {
|
|
948
983
|
await this.restoreNetworkStructureFromCache();
|
|
949
984
|
}
|
|
950
985
|
});
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
// interview because we need to know the controller node id.
|
|
956
|
-
const S0Key = this._options.securityKeys?.S0_Legacy;
|
|
957
|
-
if (S0Key) {
|
|
958
|
-
this.driverLog.print("Network key for S0 configured, enabling S0 security manager...");
|
|
959
|
-
this._securityManager = new core_1.SecurityManager({
|
|
960
|
-
networkKey: S0Key,
|
|
961
|
-
ownNodeId: this._controller.ownNodeId,
|
|
962
|
-
nonceTimeout: this._options.timeouts.nonce,
|
|
963
|
-
});
|
|
986
|
+
if (this.controller.role === core_1.ControllerRole.Primary) {
|
|
987
|
+
// Auto-enable smart start inclusion
|
|
988
|
+
this.controller.autoProvisionSmartStart();
|
|
989
|
+
}
|
|
964
990
|
}
|
|
965
991
|
else {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
992
|
+
// When skipping the controller identification, set the flags to consider the controller a primary
|
|
993
|
+
this.controller["_wasRealPrimary"] = true;
|
|
994
|
+
this.controller["_isSUC"] = true;
|
|
995
|
+
this.controller["_isSISPresent"] = true;
|
|
996
|
+
this.controller["_sucNodeId"] = 1;
|
|
997
|
+
}
|
|
998
|
+
if (this.controller.role === core_1.ControllerRole.Primary) {
|
|
999
|
+
// Set up the S0 security manager. We can only do that after the controller
|
|
1000
|
+
// interview because we need to know the controller node id.
|
|
1001
|
+
const S0Key = this._options.securityKeys?.S0_Legacy;
|
|
1002
|
+
if (S0Key) {
|
|
1003
|
+
this.driverLog.print("Network key for S0 configured, enabling S0 security manager...");
|
|
1004
|
+
this._securityManager = new core_1.SecurityManager({
|
|
1005
|
+
networkKey: S0Key,
|
|
1006
|
+
ownNodeId: this._controller.ownNodeId,
|
|
1007
|
+
nonceTimeout: this._options.timeouts.nonce,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
this.driverLog.print("No network key for S0 configured, communication with secure (S0) devices won't work!", "warn");
|
|
1012
|
+
}
|
|
1013
|
+
// The S2 security manager could be initialized earlier, but we do it here for consistency
|
|
1014
|
+
if (this._options.securityKeys
|
|
1015
|
+
// Only set it up if we have security keys for at least one S2 security class
|
|
1016
|
+
&& Object.keys(this._options.securityKeys).some((key) => key.startsWith("S2_")
|
|
1017
|
+
&& key in core_1.SecurityClass
|
|
1018
|
+
&& (0, core_1.securityClassIsS2)(core_1.SecurityClass[key]))) {
|
|
1019
|
+
this.driverLog.print("At least one network key for S2 configured, enabling S2 security manager...");
|
|
1020
|
+
this._securityManager2 = new core_1.SecurityManager2();
|
|
1021
|
+
// Set up all keys
|
|
1022
|
+
for (const secClass of [
|
|
1023
|
+
"S2_Unauthenticated",
|
|
1024
|
+
"S2_Authenticated",
|
|
1025
|
+
"S2_AccessControl",
|
|
1026
|
+
"S0_Legacy",
|
|
1027
|
+
]) {
|
|
1028
|
+
const key = this._options.securityKeys[secClass];
|
|
1029
|
+
if (key) {
|
|
1030
|
+
this._securityManager2.setKey(core_1.SecurityClass[secClass], key);
|
|
1031
|
+
}
|
|
986
1032
|
}
|
|
987
1033
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1034
|
+
else {
|
|
1035
|
+
this.driverLog.print("No network key for S2 configured, communication with secure (S2) devices won't work!", "warn");
|
|
1036
|
+
}
|
|
1037
|
+
if (this._options.securityKeysLongRange?.S2_AccessControl
|
|
1038
|
+
|| this._options.securityKeysLongRange?.S2_Authenticated) {
|
|
1039
|
+
this.driverLog.print("At least one network key for Z-Wave Long Range configured, enabling security manager...");
|
|
1040
|
+
this._securityManagerLR = new core_1.SecurityManager2();
|
|
1041
|
+
if (this._options.securityKeysLongRange?.S2_AccessControl) {
|
|
1042
|
+
this._securityManagerLR.setKey(core_1.SecurityClass.S2_AccessControl, this._options.securityKeysLongRange.S2_AccessControl);
|
|
1043
|
+
}
|
|
1044
|
+
if (this._options.securityKeysLongRange?.S2_Authenticated) {
|
|
1045
|
+
this._securityManagerLR.setKey(core_1.SecurityClass.S2_Authenticated, this._options.securityKeysLongRange.S2_Authenticated);
|
|
1046
|
+
}
|
|
998
1047
|
}
|
|
999
|
-
|
|
1000
|
-
this.
|
|
1048
|
+
else {
|
|
1049
|
+
this.driverLog.print("No network key for Z-Wave Long Range configured, communication won't work!", "warn");
|
|
1001
1050
|
}
|
|
1002
1051
|
}
|
|
1003
1052
|
else {
|
|
1004
|
-
|
|
1053
|
+
// Secondary controller - load security keys from cache.
|
|
1054
|
+
// Either LR or S2+S0, not both
|
|
1055
|
+
if ((0, core_1.isLongRangeNodeId)(this.controller.ownNodeId)) {
|
|
1056
|
+
const securityKeysLongRange = [
|
|
1057
|
+
core_1.SecurityClass.S2_AccessControl,
|
|
1058
|
+
core_1.SecurityClass.S2_Authenticated,
|
|
1059
|
+
].map((sc) => [
|
|
1060
|
+
sc,
|
|
1061
|
+
this.cacheGet(NetworkCache_1.cacheKeys.controller.securityKeysLongRange(sc)),
|
|
1062
|
+
]).filter((v) => v[1] != undefined);
|
|
1063
|
+
if (securityKeysLongRange.length) {
|
|
1064
|
+
this.driverLog.print("At least one network key for Z-Wave Long Range found in cache, enabling security manager...");
|
|
1065
|
+
this._securityManagerLR = new core_1.SecurityManager2();
|
|
1066
|
+
for (const [sc, key] of securityKeysLongRange) {
|
|
1067
|
+
this._securityManagerLR.setKey(sc, key);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
this.driverLog.print("No network key for Z-Wave Long Range configured, communication won't work!", "warn");
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
const s0Key = this.cacheGet(NetworkCache_1.cacheKeys.controller.securityKeys(core_1.SecurityClass.S0_Legacy));
|
|
1076
|
+
if (s0Key) {
|
|
1077
|
+
this.driverLog.print("Network key for S0 found in cache, enabling S0 security manager...");
|
|
1078
|
+
this._securityManager = new core_1.SecurityManager({
|
|
1079
|
+
networkKey: s0Key,
|
|
1080
|
+
ownNodeId: this._controller.ownNodeId,
|
|
1081
|
+
nonceTimeout: this._options.timeouts.nonce,
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
this.driverLog.print("No network key for S0 found in cache, communication with secure (S0) devices won't work!", "warn");
|
|
1086
|
+
}
|
|
1087
|
+
const securityKeys = core_1.securityClassOrder.map((sc) => [
|
|
1088
|
+
sc,
|
|
1089
|
+
this.cacheGet(NetworkCache_1.cacheKeys.controller.securityKeys(sc)),
|
|
1090
|
+
]).filter((v) => v[1] != undefined);
|
|
1091
|
+
if (securityKeys.length) {
|
|
1092
|
+
this.driverLog.print("At least one network key for S2 found in cache, enabling S2 security manager...");
|
|
1093
|
+
this._securityManager2 = new core_1.SecurityManager2();
|
|
1094
|
+
for (const [sc, key] of securityKeys) {
|
|
1095
|
+
this._securityManager2.setKey(sc, key);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
this.driverLog.print("No network key for S2 found in cache, communication with secure (S2) devices won't work!", "warn");
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1005
1102
|
}
|
|
1006
1103
|
// in any case we need to emit the driver ready event here
|
|
1007
1104
|
this._controllerInterviewed = true;
|
|
@@ -1011,56 +1108,96 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1011
1108
|
for (const node of this._controller.nodes.values()) {
|
|
1012
1109
|
this.addNodeEventHandlers(node);
|
|
1013
1110
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1111
|
+
if (this.controller.role === core_1.ControllerRole.Primary) {
|
|
1112
|
+
// Before interviewing nodes reset our knowledge about their ready state
|
|
1113
|
+
this._nodesReady.clear();
|
|
1114
|
+
this._nodesReadyEventEmitted = false;
|
|
1115
|
+
if (!this._options.testingHooks?.skipNodeInterview) {
|
|
1116
|
+
// Now interview all nodes
|
|
1117
|
+
// First complete the controller interview
|
|
1118
|
+
const controllerNode = this._controller.nodes.get(this._controller.ownNodeId);
|
|
1119
|
+
await this.interviewNodeInternal(controllerNode);
|
|
1120
|
+
// The controller node is always alive
|
|
1121
|
+
controllerNode.markAsAlive();
|
|
1122
|
+
// Then do all the nodes in parallel, but prioritize nodes that are more likely to be ready
|
|
1123
|
+
const nodeInterviewOrder = [...this._controller.nodes.values()]
|
|
1124
|
+
.filter((n) => n.id !== this._controller.ownNodeId)
|
|
1125
|
+
.sort((a, b) =>
|
|
1126
|
+
// Fully-interviewed devices first (need the least amount of communication now)
|
|
1127
|
+
(b.interviewStage - a.interviewStage)
|
|
1128
|
+
// Always listening -> FLiRS -> sleeping
|
|
1129
|
+
|| ((b.isListening ? 2 : b.isFrequentListening ? 1 : 0)
|
|
1130
|
+
- (a.isListening
|
|
1131
|
+
? 2
|
|
1132
|
+
: a.isFrequentListening
|
|
1133
|
+
? 1
|
|
1134
|
+
: 0))
|
|
1135
|
+
// Then by last seen, more recently first
|
|
1136
|
+
|| ((b.lastSeen?.getTime() ?? 0)
|
|
1137
|
+
- (a.lastSeen?.getTime() ?? 0))
|
|
1138
|
+
// Lastly ascending by node ID
|
|
1139
|
+
|| (a.id - b.id));
|
|
1140
|
+
if (nodeInterviewOrder.length) {
|
|
1141
|
+
this.controllerLog.print(`Interviewing nodes and/or determining their status: ${nodeInterviewOrder.map((n) => n.id).join(", ")}`);
|
|
1142
|
+
for (const node of nodeInterviewOrder) {
|
|
1143
|
+
if (node.canSleep) {
|
|
1144
|
+
// A node that can sleep should be assumed to be sleeping after resuming from cache
|
|
1145
|
+
node.markAsAsleep();
|
|
1146
|
+
}
|
|
1147
|
+
void (async () => {
|
|
1148
|
+
// Continue the interview if necessary. If that is not necessary, at least
|
|
1149
|
+
// determine the node's status
|
|
1150
|
+
if (node.interviewStage < _Types_1.InterviewStage.Complete) {
|
|
1151
|
+
await this.interviewNodeInternal(node);
|
|
1152
|
+
}
|
|
1153
|
+
else if (node.isListening || node.isFrequentListening) {
|
|
1154
|
+
// Ping non-sleeping nodes to determine their status
|
|
1155
|
+
await node.ping();
|
|
1156
|
+
}
|
|
1157
|
+
})();
|
|
1158
|
+
}
|
|
1042
1159
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
if (!this._options.testingHooks?.skipNodeInterview) {
|
|
1164
|
+
// We're a secondary controller. Just determine if nodes are ready and do the interview at another time.
|
|
1165
|
+
// First complete the controller "interview"
|
|
1166
|
+
const controllerNode = this._controller.nodes.get(this._controller.ownNodeId);
|
|
1167
|
+
await this.interviewNodeInternal(controllerNode);
|
|
1168
|
+
// The controller node is always alive
|
|
1169
|
+
controllerNode.markAsAlive();
|
|
1170
|
+
// Query the protocol information from the controller
|
|
1171
|
+
for (const node of this._controller.nodes.values()) {
|
|
1172
|
+
if (node.isControllerNode)
|
|
1173
|
+
continue;
|
|
1174
|
+
await node["queryProtocolInfo"]();
|
|
1046
1175
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1176
|
+
// Then ping (frequently) listening nodes to determine their status
|
|
1177
|
+
const nodeInterviewOrder = [...this._controller.nodes.values()]
|
|
1178
|
+
.filter((n) => n.id !== this._controller.ownNodeId)
|
|
1179
|
+
.filter((n) => n.isListening || n.isFrequentListening)
|
|
1180
|
+
.sort((a, b) =>
|
|
1181
|
+
// Always listening -> FLiRS
|
|
1182
|
+
((b.isListening ? 1 : 0)
|
|
1183
|
+
- (a.isListening ? 1 : 0))
|
|
1184
|
+
// Then by last seen, more recently first
|
|
1185
|
+
|| ((b.lastSeen?.getTime() ?? 0)
|
|
1186
|
+
- (a.lastSeen?.getTime() ?? 0))
|
|
1187
|
+
// Lastly ascending by node ID
|
|
1188
|
+
|| (a.id - b.id));
|
|
1189
|
+
if (nodeInterviewOrder.length) {
|
|
1190
|
+
this.controllerLog.print(`Determining node status: ${nodeInterviewOrder.map((n) => n.id).join(", ")}`);
|
|
1191
|
+
for (const node of nodeInterviewOrder) {
|
|
1192
|
+
void node.ping();
|
|
1056
1193
|
}
|
|
1057
|
-
}
|
|
1194
|
+
}
|
|
1058
1195
|
}
|
|
1059
|
-
// If we only have sleeping nodes or a controller-only network, the send
|
|
1060
|
-
// thread is idle before the driver gets marked ready, the idle tasks won't be triggered.
|
|
1061
|
-
// So do it manually.
|
|
1062
|
-
this.handleQueueIdleChange(this.queueIdle);
|
|
1063
1196
|
}
|
|
1197
|
+
// If we only have sleeping nodes or a controller-only network, the send
|
|
1198
|
+
// thread is idle before the driver gets marked ready, the idle tasks won't be triggered.
|
|
1199
|
+
// So do it manually.
|
|
1200
|
+
this.handleQueueIdleChange(this.queueIdle);
|
|
1064
1201
|
}
|
|
1065
1202
|
autoRefreshNodeValueTimers = new Map();
|
|
1066
1203
|
retryNodeInterviewTimeouts = new Map();
|
|
@@ -1451,6 +1588,36 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1451
1588
|
onControllerStatusChanged(_status) {
|
|
1452
1589
|
this.triggerQueues();
|
|
1453
1590
|
}
|
|
1591
|
+
async onNetworkFound(homeId, _ownNodeId) {
|
|
1592
|
+
try {
|
|
1593
|
+
this.driverLog.print(`Joined network with home ID ${(0, shared_1.num2hex)(homeId)}, switching to new network cache...`);
|
|
1594
|
+
await this.recreateNetworkCacheAndValueDBs();
|
|
1595
|
+
}
|
|
1596
|
+
catch (e) {
|
|
1597
|
+
this.driverLog.print(`Recreating the network cache and value DBs failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
onNetworkJoined() {
|
|
1601
|
+
this.driverLog.print(`Finished joining network`);
|
|
1602
|
+
}
|
|
1603
|
+
async onNetworkLeft() {
|
|
1604
|
+
try {
|
|
1605
|
+
this.driverLog.print(`Left the previous network, switching network cache to new home ID ${(0, shared_1.num2hex)(this.controller.homeId)}...`);
|
|
1606
|
+
await this.recreateNetworkCacheAndValueDBs();
|
|
1607
|
+
}
|
|
1608
|
+
catch (e) {
|
|
1609
|
+
this.driverLog.print(`Recreating the network cache and value DBs failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
async recreateNetworkCacheAndValueDBs() {
|
|
1613
|
+
await this._networkCache?.close();
|
|
1614
|
+
await this._valueDB?.close();
|
|
1615
|
+
await this._metadataDB?.close();
|
|
1616
|
+
// Reopen with the new home ID
|
|
1617
|
+
await this.initNetworkCache(this.controller.homeId);
|
|
1618
|
+
await this.initValueDBs(this.controller.homeId);
|
|
1619
|
+
await this.performCacheMigration();
|
|
1620
|
+
}
|
|
1454
1621
|
/**
|
|
1455
1622
|
* Returns the time in seconds to actually wait after a firmware upgrade, depending on what the device said.
|
|
1456
1623
|
* This number will always be a bit greater than the advertised duration, because devices have been found to take longer to actually reboot.
|
|
@@ -1897,6 +2064,8 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1897
2064
|
this.controllerLog.print(message, "error");
|
|
1898
2065
|
throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
|
|
1899
2066
|
}
|
|
2067
|
+
// Preserve the private key for the authenticated learn mode ECDH key pair
|
|
2068
|
+
const oldPrivateKey = this.cacheGet(NetworkCache_1.cacheKeys.controller.privateKey);
|
|
1900
2069
|
// Update the controller NIF prior to hard resetting
|
|
1901
2070
|
await this.controller.setControllerNIF();
|
|
1902
2071
|
await this.controller.hardReset();
|
|
@@ -1914,6 +2083,12 @@ class Driver extends shared_1.TypedEventEmitter {
|
|
|
1914
2083
|
}
|
|
1915
2084
|
this._controllerInterviewed = false;
|
|
1916
2085
|
void this.initializeControllerAndNodes();
|
|
2086
|
+
// Save the key pair in the new cache again
|
|
2087
|
+
if (oldPrivateKey) {
|
|
2088
|
+
this.once("driver ready", () => {
|
|
2089
|
+
this.cacheSet(NetworkCache_1.cacheKeys.controller.privateKey, oldPrivateKey);
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
1917
2092
|
}
|
|
1918
2093
|
/**
|
|
1919
2094
|
* Instructs the Z-Wave API to shut down in order to safely remove the power.
|
|
@@ -4681,7 +4856,7 @@ ${handlers.length} left`);
|
|
|
4681
4856
|
}
|
|
4682
4857
|
}
|
|
4683
4858
|
/** Computes the maximum net CC payload size for the given CC or SendDataRequest */
|
|
4684
|
-
computeNetCCPayloadSize(commandOrMsg) {
|
|
4859
|
+
computeNetCCPayloadSize(commandOrMsg, ignoreEncapsulation = false) {
|
|
4685
4860
|
// Recreate the correct encapsulation structure
|
|
4686
4861
|
let msg;
|
|
4687
4862
|
if ((0, SendDataShared_1.isSendDataSinglecast)(commandOrMsg)) {
|
|
@@ -4691,7 +4866,9 @@ ${handlers.length} left`);
|
|
|
4691
4866
|
const SendDataConstructor = this.getSendDataSinglecastConstructor();
|
|
4692
4867
|
msg = new SendDataConstructor(this, { command: commandOrMsg });
|
|
4693
4868
|
}
|
|
4694
|
-
|
|
4869
|
+
if (!ignoreEncapsulation) {
|
|
4870
|
+
msg.command = this.encapsulateCommands(msg.command);
|
|
4871
|
+
}
|
|
4695
4872
|
return msg.command.getMaxPayloadLength(this.getMaxPayloadLength(msg));
|
|
4696
4873
|
}
|
|
4697
4874
|
/** Computes the maximum payload size that can be transmitted with the given message */
|
|
@@ -5062,6 +5239,22 @@ ${handlers.length} left`);
|
|
|
5062
5239
|
};
|
|
5063
5240
|
}
|
|
5064
5241
|
}
|
|
5242
|
+
/**
|
|
5243
|
+
* Resets the S2 singlecast encryption state (SPAN) for the given node, which forces
|
|
5244
|
+
* a re-synchronization on the next communication attempt.
|
|
5245
|
+
*/
|
|
5246
|
+
resetSPAN(nodeId) {
|
|
5247
|
+
this.getSecurityManager2(nodeId)?.deleteNonce(nodeId);
|
|
5248
|
+
}
|
|
5249
|
+
/**
|
|
5250
|
+
* Resets the S2 singlecast encryption state (SPAN) for all nodes, which forces
|
|
5251
|
+
* a re-synchronization on the next communication attempt.
|
|
5252
|
+
*/
|
|
5253
|
+
resetAllSPANs() {
|
|
5254
|
+
for (const nodeId of this.controller.nodes.keys()) {
|
|
5255
|
+
this.resetSPAN(nodeId);
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5065
5258
|
}
|
|
5066
5259
|
exports.Driver = Driver;
|
|
5067
5260
|
//# sourceMappingURL=Driver.js.map
|