zwave-js 8.6.0 → 8.7.2
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/build/Utils.d.ts +2 -2
- package/build/Utils.d.ts.map +1 -1
- package/build/Utils.js +3 -1
- package/build/Utils.js.map +1 -1
- package/build/lib/commandclass/AssociationCC.d.ts.map +1 -1
- package/build/lib/commandclass/AssociationCC.js +2 -4
- package/build/lib/commandclass/AssociationCC.js.map +1 -1
- package/build/lib/commandclass/ManufacturerProprietaryCC.d.ts.map +1 -1
- package/build/lib/commandclass/ManufacturerProprietaryCC.js +15 -8
- package/build/lib/commandclass/ManufacturerProprietaryCC.js.map +1 -1
- package/build/lib/commandclass/MultiChannelAssociationCC.d.ts.map +1 -1
- package/build/lib/commandclass/MultiChannelAssociationCC.js +2 -4
- package/build/lib/commandclass/MultiChannelAssociationCC.js.map +1 -1
- package/build/lib/controller/AddNodeToNetworkRequest.d.ts +36 -8
- package/build/lib/controller/AddNodeToNetworkRequest.d.ts.map +1 -1
- package/build/lib/controller/AddNodeToNetworkRequest.js +111 -41
- package/build/lib/controller/AddNodeToNetworkRequest.js.map +1 -1
- package/build/lib/controller/ApplicationUpdateRequest.d.ts +16 -4
- package/build/lib/controller/ApplicationUpdateRequest.d.ts.map +1 -1
- package/build/lib/controller/ApplicationUpdateRequest.js +56 -15
- package/build/lib/controller/ApplicationUpdateRequest.js.map +1 -1
- package/build/lib/controller/Controller.d.ts +65 -20
- package/build/lib/controller/Controller.d.ts.map +1 -1
- package/build/lib/controller/Controller.js +651 -265
- package/build/lib/controller/Controller.js.map +1 -1
- package/build/lib/controller/Inclusion.d.ts +31 -4
- package/build/lib/controller/Inclusion.d.ts.map +1 -1
- package/build/lib/controller/Inclusion.js +15 -3
- package/build/lib/controller/Inclusion.js.map +1 -1
- package/build/lib/controller/RemoveNodeFromNetworkRequest.d.ts +15 -9
- package/build/lib/controller/RemoveNodeFromNetworkRequest.d.ts.map +1 -1
- package/build/lib/controller/RemoveNodeFromNetworkRequest.js +70 -41
- package/build/lib/controller/RemoveNodeFromNetworkRequest.js.map +1 -1
- package/build/lib/driver/Driver.d.ts +5 -1
- package/build/lib/driver/Driver.d.ts.map +1 -1
- package/build/lib/driver/Driver.js +246 -59
- package/build/lib/driver/Driver.js.map +1 -1
- package/build/lib/node/Node.d.ts +6 -0
- package/build/lib/node/Node.d.ts.map +1 -1
- package/build/lib/node/Node.js +22 -9
- package/build/lib/node/Node.js.map +1 -1
- package/package.json +10 -10
|
@@ -66,9 +66,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
66
66
|
super();
|
|
67
67
|
this.driver = driver;
|
|
68
68
|
this._healNetworkActive = false;
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
69
|
+
this._provisioningList = [];
|
|
70
|
+
this._inclusionState = Inclusion_1.InclusionState.Idle;
|
|
71
|
+
this._smartStartEnabled = false;
|
|
71
72
|
this._includeController = false;
|
|
73
|
+
this._unprovisionRemovedNode = false;
|
|
72
74
|
this._healNetworkProgress = new Map();
|
|
73
75
|
this._nodes = new Map();
|
|
74
76
|
this._nodes.getOrThrow = function (nodeId) {
|
|
@@ -79,9 +81,9 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
79
81
|
return node;
|
|
80
82
|
}.bind(this._nodes);
|
|
81
83
|
// register message handlers
|
|
82
|
-
driver.registerRequestHandler(Constants_1.FunctionType.AddNodeToNetwork, this.
|
|
83
|
-
driver.registerRequestHandler(Constants_1.FunctionType.RemoveNodeFromNetwork, this.
|
|
84
|
-
driver.registerRequestHandler(Constants_1.FunctionType.ReplaceFailedNode, this.
|
|
84
|
+
driver.registerRequestHandler(Constants_1.FunctionType.AddNodeToNetwork, this.handleAddNodeStatusReport.bind(this));
|
|
85
|
+
driver.registerRequestHandler(Constants_1.FunctionType.RemoveNodeFromNetwork, this.handleRemoveNodeStatusReport.bind(this));
|
|
86
|
+
driver.registerRequestHandler(Constants_1.FunctionType.ReplaceFailedNode, this.handleReplaceNodeStatusReport.bind(this));
|
|
85
87
|
}
|
|
86
88
|
get libraryVersion() {
|
|
87
89
|
return this._libraryVersion;
|
|
@@ -181,10 +183,32 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
181
183
|
get supportsTimers() {
|
|
182
184
|
return this._supportsTimers;
|
|
183
185
|
}
|
|
186
|
+
/** Whether the controller is known to support soft reset */
|
|
187
|
+
get supportsSoftReset() {
|
|
188
|
+
return this._supportsSoftReset;
|
|
189
|
+
}
|
|
190
|
+
/** @internal */
|
|
191
|
+
set supportsSoftReset(value) {
|
|
192
|
+
this._supportsSoftReset = value;
|
|
193
|
+
}
|
|
184
194
|
/** A dictionary of the nodes connected to this controller */
|
|
185
195
|
get nodes() {
|
|
186
196
|
return this._nodes;
|
|
187
197
|
}
|
|
198
|
+
/** Returns the node with the given DSK */
|
|
199
|
+
getNodeByDSK(dsk) {
|
|
200
|
+
var _a;
|
|
201
|
+
if (typeof dsk === "string")
|
|
202
|
+
dsk = (0, core_1.dskFromString)(dsk);
|
|
203
|
+
for (const node of this._nodes.values()) {
|
|
204
|
+
if ((_a = node.dsk) === null || _a === void 0 ? void 0 : _a.equals(dsk))
|
|
205
|
+
return node;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Returns the controller node's value DB */
|
|
209
|
+
get valueDB() {
|
|
210
|
+
return this._nodes.get(this._ownNodeId).valueDB;
|
|
211
|
+
}
|
|
188
212
|
/** Returns whether the network or a node is currently being healed. */
|
|
189
213
|
get isHealNetworkActive() {
|
|
190
214
|
return this._healNetworkActive;
|
|
@@ -198,14 +222,134 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
198
222
|
const nodes = nodeIDs.map((id) => this._nodes.getOrThrow(id));
|
|
199
223
|
return new VirtualNode_1.VirtualNode(undefined, this.driver, nodes);
|
|
200
224
|
}
|
|
225
|
+
/** @internal */
|
|
226
|
+
get provisioningList() {
|
|
227
|
+
return this._provisioningList;
|
|
228
|
+
}
|
|
229
|
+
/** Adds the given entry (DSK and security classes) to the controller's SmartStart provisioning list or replaces an existing entry */
|
|
230
|
+
provisionSmartStartNode(entry) {
|
|
231
|
+
const index = this._provisioningList.findIndex((e) => e.dsk === entry.dsk);
|
|
232
|
+
if (index === -1) {
|
|
233
|
+
this._provisioningList.push(entry);
|
|
234
|
+
// If this is an entry for an existing node, mark it as included
|
|
235
|
+
const node = this.getNodeByDSK(entry.dsk);
|
|
236
|
+
if (node)
|
|
237
|
+
this.markNodeOnProvisioningList(node);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
this._provisioningList[index] = entry;
|
|
241
|
+
}
|
|
242
|
+
this.autoProvisionSmartStart();
|
|
243
|
+
void this.driver.saveNetworkToCache();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Removes the given DSK or node ID from the controller's SmartStart provisioning list.
|
|
247
|
+
*
|
|
248
|
+
* **Note:** If this entry corresponds to an included node, it will **NOT** be excluded
|
|
249
|
+
*/
|
|
250
|
+
unprovisionSmartStartNode(dskOrNodeId) {
|
|
251
|
+
const index = this._provisioningList.findIndex((e) => e.dsk === dskOrNodeId ||
|
|
252
|
+
(typeof dskOrNodeId === "number" &&
|
|
253
|
+
"nodeId" in e &&
|
|
254
|
+
e.nodeId === dskOrNodeId));
|
|
255
|
+
if (index >= 0) {
|
|
256
|
+
this._provisioningList.splice(index, 1);
|
|
257
|
+
this.autoProvisionSmartStart();
|
|
258
|
+
void this.driver.saveNetworkToCache();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Returns the entry for the given DSK from the controller's SmartStart provisioning list.
|
|
263
|
+
*/
|
|
264
|
+
getProvisioningEntry(dsk) {
|
|
265
|
+
return this._provisioningList.find((e) => e.dsk === dsk);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Returns all entries from the controller's SmartStart provisioning list.
|
|
269
|
+
*/
|
|
270
|
+
getProvisioningEntries() {
|
|
271
|
+
// Make copies so no one can modify the internal list (except for user info)
|
|
272
|
+
return this._provisioningList.map((e) => {
|
|
273
|
+
const { dsk, securityClasses, nodeId, ...rest } = e;
|
|
274
|
+
return {
|
|
275
|
+
dsk,
|
|
276
|
+
securityClasses: [...securityClasses],
|
|
277
|
+
...(nodeId != undefined ? { nodeId } : {}),
|
|
278
|
+
...rest,
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/** Returns whether the SmartStart provisioning list contains entries that have not been included yet */
|
|
283
|
+
hasPlannedProvisioningEntries() {
|
|
284
|
+
return this._provisioningList.some((e) => !("nodeId" in e));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* @internal
|
|
288
|
+
* Automatically starts smart start inclusion if there are nodes pending inclusion.
|
|
289
|
+
*/
|
|
290
|
+
autoProvisionSmartStart() {
|
|
291
|
+
if (this.hasPlannedProvisioningEntries()) {
|
|
292
|
+
// SmartStart should be enabled
|
|
293
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
294
|
+
void this.enableSmartStart().catch(() => { });
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// SmartStart should be disabled
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
299
|
+
void this.disableSmartStart().catch(() => { });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
markNodeOnProvisioningList(node) {
|
|
303
|
+
// If this node's DSK is on the provisioning list, remember the node ID
|
|
304
|
+
if (node.dsk) {
|
|
305
|
+
const entry = this._provisioningList.find((e) => e.dsk === (0, core_1.dskToString)(node.dsk));
|
|
306
|
+
if (entry)
|
|
307
|
+
entry.nodeId = node.id;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
unmarkNodeOnProvisioningList(nodeId) {
|
|
311
|
+
const entry = this._provisioningList.find((e) => "nodeId" in e && e.nodeId === nodeId);
|
|
312
|
+
if (entry)
|
|
313
|
+
delete entry.nodeId;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* @internal
|
|
317
|
+
* Queries the controller IDs and its Serial API capabilities
|
|
318
|
+
*/
|
|
319
|
+
async identify() {
|
|
320
|
+
// get the home and node id of the controller
|
|
321
|
+
this.driver.controllerLog.print(`querying controller IDs...`);
|
|
322
|
+
const ids = await this.driver.sendMessage(new GetControllerIdMessages_1.GetControllerIdRequest(this.driver), { supportCheck: false });
|
|
323
|
+
this._homeId = ids.homeId;
|
|
324
|
+
this._ownNodeId = ids.ownNodeId;
|
|
325
|
+
this.driver.controllerLog.print(`received controller IDs:
|
|
326
|
+
home ID: ${(0, shared_1.num2hex)(this._homeId)}
|
|
327
|
+
own node ID: ${this._ownNodeId}`);
|
|
328
|
+
// Figure out what the serial API can do
|
|
329
|
+
this.driver.controllerLog.print(`querying API capabilities...`);
|
|
330
|
+
const apiCaps = await this.driver.sendMessage(new GetSerialApiCapabilitiesMessages_1.GetSerialApiCapabilitiesRequest(this.driver), {
|
|
331
|
+
supportCheck: false,
|
|
332
|
+
});
|
|
333
|
+
this._serialApiVersion = apiCaps.serialApiVersion;
|
|
334
|
+
this._manufacturerId = apiCaps.manufacturerId;
|
|
335
|
+
this._productType = apiCaps.productType;
|
|
336
|
+
this._productId = apiCaps.productId;
|
|
337
|
+
this._supportedFunctionTypes = apiCaps.supportedFunctionTypes;
|
|
338
|
+
this.driver.controllerLog.print(`received API capabilities:
|
|
339
|
+
serial API version: ${this._serialApiVersion}
|
|
340
|
+
manufacturer ID: ${(0, shared_1.num2hex)(this._manufacturerId)}
|
|
341
|
+
product type: ${(0, shared_1.num2hex)(this._productType)}
|
|
342
|
+
product ID: ${(0, shared_1.num2hex)(this._productId)}
|
|
343
|
+
supported functions: ${this._supportedFunctionTypes
|
|
344
|
+
.map((fn) => `\n · ${Constants_1.FunctionType[fn]} (${(0, shared_1.num2hex)(fn)})`)
|
|
345
|
+
.join("")}`);
|
|
346
|
+
}
|
|
201
347
|
/**
|
|
202
348
|
* @internal
|
|
203
349
|
* Interviews the controller for the necessary information.
|
|
204
|
-
* @param initValueDBs Asynchronous callback for the driver to initialize the Value DBs before nodes are created
|
|
205
350
|
* @param restoreFromCache Asynchronous callback for the driver to restore the network from cache after nodes are created
|
|
206
351
|
*/
|
|
207
|
-
async interview(
|
|
208
|
-
this.driver.controllerLog.print("beginning interview...");
|
|
352
|
+
async interview(restoreFromCache) {
|
|
209
353
|
// get basic controller version info
|
|
210
354
|
this.driver.controllerLog.print(`querying version info...`);
|
|
211
355
|
const version = await this.driver.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this.driver), {
|
|
@@ -216,14 +360,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
216
360
|
this.driver.controllerLog.print(`received version info:
|
|
217
361
|
controller type: ${ZWaveLibraryTypes_1.ZWaveLibraryTypes[this._type]}
|
|
218
362
|
library version: ${this._libraryVersion}`);
|
|
219
|
-
// get the home and node id of the controller
|
|
220
|
-
this.driver.controllerLog.print(`querying controller IDs...`);
|
|
221
|
-
const ids = await this.driver.sendMessage(new GetControllerIdMessages_1.GetControllerIdRequest(this.driver), { supportCheck: false });
|
|
222
|
-
this._homeId = ids.homeId;
|
|
223
|
-
this._ownNodeId = ids.ownNodeId;
|
|
224
|
-
this.driver.controllerLog.print(`received controller IDs:
|
|
225
|
-
home ID: ${(0, shared_1.num2hex)(this._homeId)}
|
|
226
|
-
own node ID: ${this._ownNodeId}`);
|
|
227
363
|
// find out what the controller can do
|
|
228
364
|
this.driver.controllerLog.print(`querying controller capabilities...`);
|
|
229
365
|
const ctrlCaps = await this.driver.sendMessage(new GetControllerCapabilitiesMessages_1.GetControllerCapabilitiesRequest(this.driver), {
|
|
@@ -241,25 +377,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
241
377
|
is SIS present: ${this._isSISPresent}
|
|
242
378
|
was real primary: ${this._wasRealPrimary}
|
|
243
379
|
is a SUC: ${this._isStaticUpdateController}`);
|
|
244
|
-
// find out which part of the API is supported
|
|
245
|
-
this.driver.controllerLog.print(`querying API capabilities...`);
|
|
246
|
-
const apiCaps = await this.driver.sendMessage(new GetSerialApiCapabilitiesMessages_1.GetSerialApiCapabilitiesRequest(this.driver), {
|
|
247
|
-
supportCheck: false,
|
|
248
|
-
});
|
|
249
|
-
this._serialApiVersion = apiCaps.serialApiVersion;
|
|
250
|
-
this._manufacturerId = apiCaps.manufacturerId;
|
|
251
|
-
this._productType = apiCaps.productType;
|
|
252
|
-
this._productId = apiCaps.productId;
|
|
253
|
-
this._supportedFunctionTypes = apiCaps.supportedFunctionTypes;
|
|
254
|
-
this.driver.controllerLog.print(`received API capabilities:
|
|
255
|
-
serial API version: ${this._serialApiVersion}
|
|
256
|
-
manufacturer ID: ${(0, shared_1.num2hex)(this._manufacturerId)}
|
|
257
|
-
product type: ${(0, shared_1.num2hex)(this._productType)}
|
|
258
|
-
product ID: ${(0, shared_1.num2hex)(this._productId)}
|
|
259
|
-
supported functions: ${this._supportedFunctionTypes
|
|
260
|
-
.map((fn) => `\n · ${Constants_1.FunctionType[fn]} (${(0, shared_1.num2hex)(fn)})`)
|
|
261
|
-
.join("")}`);
|
|
262
|
-
// now we can check if a function is supported
|
|
263
380
|
// Figure out which sub commands of SerialAPISetup are supported
|
|
264
381
|
if (this.isFunctionSupported(Constants_1.FunctionType.SerialAPISetup)) {
|
|
265
382
|
this.driver.controllerLog.print(`querying serial API setup capabilities...`);
|
|
@@ -313,8 +430,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
313
430
|
this.isFunctionSupported(Constants_1.FunctionType.FUNC_ID_ZW_GET_VIRTUAL_NODES)) {
|
|
314
431
|
// TODO: send FUNC_ID_ZW_GET_VIRTUAL_NODES message
|
|
315
432
|
}
|
|
316
|
-
// Give the Driver time to set up the value DBs
|
|
317
|
-
await initValueDBs();
|
|
318
433
|
// Request information about all nodes with the GetInitData message
|
|
319
434
|
this.driver.controllerLog.print(`querying node information...`);
|
|
320
435
|
const initData = await this.driver.sendMessage(new GetSerialApiInitDataMessages_1.GetSerialApiInitDataRequest(this.driver));
|
|
@@ -345,7 +460,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
345
460
|
// Now try to deserialize all nodes from the cache
|
|
346
461
|
await restoreFromCache();
|
|
347
462
|
// Set manufacturer information for the controller node
|
|
348
|
-
const controllerValueDB = this.
|
|
463
|
+
const controllerValueDB = this.valueDB;
|
|
349
464
|
controllerValueDB.setMetadata((0, ManufacturerSpecificCC_1.getManufacturerIdValueId)(), (0, ManufacturerSpecificCC_1.getManufacturerIdValueMetadata)());
|
|
350
465
|
controllerValueDB.setMetadata((0, ManufacturerSpecificCC_1.getProductTypeValueId)(), (0, ManufacturerSpecificCC_1.getProductTypeValueMetadata)());
|
|
351
466
|
controllerValueDB.setMetadata((0, ManufacturerSpecificCC_1.getProductIdValueId)(), (0, ManufacturerSpecificCC_1.getProductIdValueMetadata)());
|
|
@@ -447,9 +562,24 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
447
562
|
}
|
|
448
563
|
});
|
|
449
564
|
}
|
|
565
|
+
get inclusionState() {
|
|
566
|
+
return this._inclusionState;
|
|
567
|
+
}
|
|
568
|
+
setInclusionState(state) {
|
|
569
|
+
if (this._inclusionState === state)
|
|
570
|
+
return;
|
|
571
|
+
this._inclusionState = state;
|
|
572
|
+
if (state === Inclusion_1.InclusionState.Idle && this._smartStartEnabled) {
|
|
573
|
+
// If Smart Start was enabled before the inclusion/exclusion,
|
|
574
|
+
// enable it again and ignore errors
|
|
575
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
576
|
+
this.enableSmartStart().catch(() => { });
|
|
577
|
+
}
|
|
578
|
+
}
|
|
450
579
|
async beginInclusion(options) {
|
|
451
|
-
|
|
452
|
-
|
|
580
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
581
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
582
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
453
583
|
return false;
|
|
454
584
|
}
|
|
455
585
|
if (options == undefined) {
|
|
@@ -465,97 +595,279 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
465
595
|
};
|
|
466
596
|
}
|
|
467
597
|
// Protect against invalid inclusion options
|
|
468
|
-
if (!(options.strategy in Inclusion_1.InclusionStrategy)
|
|
598
|
+
if (!(options.strategy in Inclusion_1.InclusionStrategy) ||
|
|
599
|
+
// @ts-expect-error We're checking for user errors
|
|
600
|
+
options.strategy === Inclusion_1.InclusionStrategy.SmartStart) {
|
|
469
601
|
throw new core_1.ZWaveError(`Invalid inclusion strategy: ${options.strategy}`, core_1.ZWaveErrorCodes.Argument_Invalid);
|
|
470
602
|
}
|
|
471
|
-
if (
|
|
472
|
-
|
|
603
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
604
|
+
// Disable listening mode so we can switch to inclusion mode
|
|
605
|
+
await this.stopInclusion();
|
|
473
606
|
}
|
|
474
|
-
this.
|
|
607
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
475
608
|
this._inclusionOptions = options;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
609
|
+
try {
|
|
610
|
+
this.driver.controllerLog.print(`Starting inclusion process with strategy ${(0, shared_1.getEnumMemberName)(Inclusion_1.InclusionStrategy, options.strategy)}...`);
|
|
611
|
+
// kick off the inclusion process
|
|
612
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
613
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Any,
|
|
614
|
+
highPower: true,
|
|
615
|
+
networkWide: true,
|
|
616
|
+
}));
|
|
617
|
+
this.driver.controllerLog.print(`The controller is now ready to add nodes`);
|
|
618
|
+
this.emit("inclusion started",
|
|
619
|
+
// TODO: Remove first parameter in next major version
|
|
620
|
+
options.strategy !== Inclusion_1.InclusionStrategy.Insecure, options.strategy);
|
|
621
|
+
}
|
|
622
|
+
catch (e) {
|
|
623
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
624
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
625
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
626
|
+
this.driver.controllerLog.print(`Starting the inclusion failed`, "error");
|
|
627
|
+
throw new core_1.ZWaveError("The inclusion could not be started.", core_1.ZWaveErrorCodes.Controller_InclusionFailed);
|
|
628
|
+
}
|
|
629
|
+
throw e;
|
|
630
|
+
}
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
/** @internal */
|
|
634
|
+
async beginInclusionSmartStart(provisioningEntry) {
|
|
635
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
636
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
637
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
// Disable listening mode so we can switch to inclusion mode
|
|
641
|
+
await this.stopInclusion();
|
|
642
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
643
|
+
this._inclusionOptions = {
|
|
644
|
+
strategy: Inclusion_1.InclusionStrategy.SmartStart,
|
|
645
|
+
provisioning: provisioningEntry,
|
|
646
|
+
};
|
|
647
|
+
try {
|
|
648
|
+
this.driver.controllerLog.print(`Including SmartStart node with DSK ${provisioningEntry.dsk}`);
|
|
649
|
+
// kick off the inclusion process
|
|
650
|
+
const dskBuffer = (0, core_1.dskFromString)(provisioningEntry.dsk);
|
|
651
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeDSKToNetworkRequest(this.driver, {
|
|
652
|
+
nwiHomeId: (0, core_1.nwiHomeIdFromDSK)(dskBuffer),
|
|
653
|
+
authHomeId: (0, core_1.authHomeIdFromDSK)(dskBuffer),
|
|
654
|
+
highPower: true,
|
|
655
|
+
networkWide: true,
|
|
656
|
+
}));
|
|
657
|
+
this.emit("inclusion started",
|
|
658
|
+
// TODO: Remove first parameter in next major version
|
|
659
|
+
true, Inclusion_1.InclusionStrategy.SmartStart);
|
|
660
|
+
}
|
|
661
|
+
catch (e) {
|
|
662
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
663
|
+
// Error handling for this happens at the call site
|
|
664
|
+
throw e;
|
|
665
|
+
}
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Is used internally to stop an active inclusion process without waiting for a confirmation
|
|
670
|
+
* @internal
|
|
671
|
+
*/
|
|
672
|
+
async stopInclusionNoCallback() {
|
|
480
673
|
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
481
|
-
|
|
674
|
+
callbackId: 0,
|
|
675
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
482
676
|
highPower: true,
|
|
483
677
|
networkWide: true,
|
|
484
678
|
}));
|
|
485
|
-
|
|
679
|
+
this.driver.controllerLog.print(`The inclusion process was stopped`);
|
|
680
|
+
this.emit("inclusion stopped");
|
|
486
681
|
}
|
|
487
|
-
/**
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
this.
|
|
493
|
-
this.driver.
|
|
494
|
-
// create the promise we're going to return
|
|
495
|
-
this._stopInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
|
|
496
|
-
// kick off the inclusion process
|
|
497
|
-
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
682
|
+
/**
|
|
683
|
+
* Finishes an inclusion process. This must only be called after the ProtocolDone status is received.
|
|
684
|
+
* Returns the ID of the newly added node.
|
|
685
|
+
*/
|
|
686
|
+
async finishInclusion() {
|
|
687
|
+
this.driver.controllerLog.print(`finishing inclusion process...`);
|
|
688
|
+
const response = await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
498
689
|
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
499
690
|
highPower: true,
|
|
500
691
|
networkWide: true,
|
|
501
692
|
}));
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
693
|
+
if (response.status === AddNodeToNetworkRequest_1.AddNodeStatus.Done) {
|
|
694
|
+
return response.statusContext.nodeId;
|
|
695
|
+
}
|
|
696
|
+
this.driver.controllerLog.print(`Finishing the inclusion failed`, "error");
|
|
697
|
+
throw new core_1.ZWaveError("Finishing the inclusion failed", core_1.ZWaveErrorCodes.Controller_InclusionFailed);
|
|
507
698
|
}
|
|
508
699
|
/**
|
|
509
700
|
* Stops an active inclusion process. Resolves to true when the controller leaves inclusion mode,
|
|
510
701
|
* and false if the inclusion was not active.
|
|
511
702
|
*/
|
|
512
703
|
async stopInclusion() {
|
|
513
|
-
|
|
514
|
-
|
|
704
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Including) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
this.driver.controllerLog.print(`stopping inclusion process...`);
|
|
708
|
+
try {
|
|
709
|
+
// stop the inclusion process
|
|
710
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
711
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
712
|
+
highPower: true,
|
|
713
|
+
networkWide: true,
|
|
714
|
+
}));
|
|
715
|
+
this.driver.controllerLog.print(`The inclusion process was stopped`);
|
|
716
|
+
this.emit("inclusion stopped");
|
|
717
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
catch (e) {
|
|
721
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
722
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
723
|
+
this.driver.controllerLog.print(`Stopping the inclusion failed`, "error");
|
|
724
|
+
throw new core_1.ZWaveError("The inclusion could not be stopped.", core_1.ZWaveErrorCodes.Controller_InclusionFailed);
|
|
725
|
+
}
|
|
726
|
+
throw e;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Puts the controller into listening mode for Smart Start inclusion.
|
|
731
|
+
* Whenever a node on the provisioning list announces itself, it will automatically be added.
|
|
732
|
+
*
|
|
733
|
+
* Resolves to `true` when the listening mode is started or was active, and `false` if it is scheduled for later activation.
|
|
734
|
+
*/
|
|
735
|
+
async enableSmartStart() {
|
|
736
|
+
this._smartStartEnabled = true;
|
|
737
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Idle) {
|
|
738
|
+
this.setInclusionState(Inclusion_1.InclusionState.SmartStart);
|
|
739
|
+
this.driver.controllerLog.print(`Enabling Smart Start listening mode...`);
|
|
740
|
+
try {
|
|
741
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.EnableSmartStartListenRequest(this.driver, {}));
|
|
742
|
+
this.driver.controllerLog.print(`Smart Start listening mode enabled`);
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
catch (e) {
|
|
746
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
747
|
+
this.driver.controllerLog.print(`Smart Start listening mode could not be enabled: ${(0, shared_1.getErrorMessage)(e)}`, "error");
|
|
748
|
+
throw e;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
this.driver.controllerLog.print(`Smart Start listening mode scheduled for later activation...`);
|
|
515
756
|
return false;
|
|
516
|
-
|
|
517
|
-
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Disables the listening mode for Smart Start inclusion.
|
|
761
|
+
*
|
|
762
|
+
* Resolves to `true` when the listening mode is stopped, and `false` if was not active.
|
|
763
|
+
*/
|
|
764
|
+
async disableSmartStart() {
|
|
765
|
+
this._smartStartEnabled = false;
|
|
766
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
767
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
768
|
+
this.driver.controllerLog.print(`disabling Smart Start listening mode...`);
|
|
769
|
+
try {
|
|
770
|
+
await this.stopInclusion();
|
|
771
|
+
this.driver.controllerLog.print(`Smart Start listening mode disabled`);
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
catch (e) {
|
|
775
|
+
this.setInclusionState(Inclusion_1.InclusionState.SmartStart);
|
|
776
|
+
this.driver.controllerLog.print(`Smart Start listening mode could not be disabled: ${(0, shared_1.getErrorMessage)(e)}`, "error");
|
|
777
|
+
throw e;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
else if (this._inclusionState === Inclusion_1.InclusionState.Idle) {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
this.driver.controllerLog.print(`Smart Start listening mode disabled`);
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
518
787
|
}
|
|
519
788
|
/**
|
|
520
789
|
* Starts the exclusion process of new nodes.
|
|
521
|
-
* Resolves to true when the process was started,
|
|
522
|
-
*
|
|
790
|
+
* Resolves to true when the process was started, and false if an inclusion or exclusion process was already active.
|
|
791
|
+
*
|
|
792
|
+
* @param unprovision Whether the removed node should also be removed from the Smart Start provisioning list.
|
|
523
793
|
*/
|
|
524
|
-
async beginExclusion() {
|
|
525
|
-
|
|
526
|
-
|
|
794
|
+
async beginExclusion(unprovision = false) {
|
|
795
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
796
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
797
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
527
798
|
return false;
|
|
528
|
-
|
|
799
|
+
}
|
|
800
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
801
|
+
// Disable listening mode so we can switch to exclusion mode
|
|
802
|
+
await this.stopInclusion();
|
|
803
|
+
}
|
|
804
|
+
this.setInclusionState(Inclusion_1.InclusionState.Excluding);
|
|
529
805
|
this.driver.controllerLog.print(`starting exclusion process...`);
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
806
|
+
try {
|
|
807
|
+
// kick off the inclusion process
|
|
808
|
+
await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
|
|
809
|
+
removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Any,
|
|
810
|
+
highPower: true,
|
|
811
|
+
networkWide: true,
|
|
812
|
+
}));
|
|
813
|
+
this.driver.controllerLog.print(`The controller is now ready to remove nodes`);
|
|
814
|
+
this._unprovisionRemovedNode = unprovision;
|
|
815
|
+
this.emit("exclusion started");
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
catch (e) {
|
|
819
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
820
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
821
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
822
|
+
this.driver.controllerLog.print(`Starting the exclusion failed`, "error");
|
|
823
|
+
throw new core_1.ZWaveError("The exclusion could not be started.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed);
|
|
824
|
+
}
|
|
825
|
+
throw e;
|
|
826
|
+
}
|
|
539
827
|
}
|
|
540
|
-
/**
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
this._exclusionActive = false;
|
|
546
|
-
this.driver.controllerLog.print(`stopping exclusion process...`);
|
|
547
|
-
// create the promise we're going to return
|
|
548
|
-
this._stopInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
|
|
549
|
-
// kick off the inclusion process
|
|
828
|
+
/**
|
|
829
|
+
* Is used internally to stop an active exclusion process without waiting for confirmation
|
|
830
|
+
* @internal
|
|
831
|
+
*/
|
|
832
|
+
async stopExclusionNoCallback() {
|
|
550
833
|
await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
|
|
834
|
+
callbackId: 0,
|
|
551
835
|
removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Stop,
|
|
552
836
|
highPower: true,
|
|
553
837
|
networkWide: true,
|
|
554
838
|
}));
|
|
555
|
-
|
|
839
|
+
this.driver.controllerLog.print(`the exclusion process was stopped`);
|
|
840
|
+
this.emit("exclusion stopped");
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Stops an active exclusion process. Resolves to true when the controller leaves exclusion mode,
|
|
844
|
+
* and false if the inclusion was not active.
|
|
845
|
+
*/
|
|
846
|
+
async stopExclusion() {
|
|
847
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Excluding) {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
this.driver.controllerLog.print(`stopping exclusion process...`);
|
|
851
|
+
try {
|
|
852
|
+
// kick off the inclusion process
|
|
853
|
+
await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
|
|
854
|
+
removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Stop,
|
|
855
|
+
highPower: true,
|
|
856
|
+
networkWide: true,
|
|
857
|
+
}));
|
|
556
858
|
this.driver.controllerLog.print(`the exclusion process was stopped`);
|
|
557
859
|
this.emit("exclusion stopped");
|
|
558
|
-
|
|
860
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
catch (e) {
|
|
864
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
865
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
866
|
+
this.driver.controllerLog.print(`Stopping the exclusion failed`, "error");
|
|
867
|
+
throw new core_1.ZWaveError("The exclusion could not be stopped.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed);
|
|
868
|
+
}
|
|
869
|
+
throw e;
|
|
870
|
+
}
|
|
559
871
|
}
|
|
560
872
|
async secureBootstrapS0(node, assumeSupported = false) {
|
|
561
873
|
if (!this.driver.securityManager) {
|
|
@@ -647,7 +959,34 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
647
959
|
version: 1,
|
|
648
960
|
});
|
|
649
961
|
}
|
|
650
|
-
|
|
962
|
+
let userCallbacks;
|
|
963
|
+
const inclusionOptions = this
|
|
964
|
+
._inclusionOptions;
|
|
965
|
+
if ("provisioning" in inclusionOptions) {
|
|
966
|
+
// SmartStart and S2 with QR code are pre-provisioned, so we don't need to ask the user for anything
|
|
967
|
+
userCallbacks = {
|
|
968
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
969
|
+
abort() { },
|
|
970
|
+
grantSecurityClasses: (requested) => {
|
|
971
|
+
return Promise.resolve({
|
|
972
|
+
clientSideAuth: false,
|
|
973
|
+
securityClasses: requested.securityClasses.filter((r) => inclusionOptions.provisioning.securityClasses.includes(r)),
|
|
974
|
+
});
|
|
975
|
+
},
|
|
976
|
+
validateDSKAndEnterPIN: (dsk) => {
|
|
977
|
+
const fullDSK = inclusionOptions.provisioning.dsk;
|
|
978
|
+
const pin = fullDSK.slice(0, 5);
|
|
979
|
+
// Make sure the DSK matches
|
|
980
|
+
if (pin + dsk !== fullDSK)
|
|
981
|
+
return Promise.resolve(false);
|
|
982
|
+
return Promise.resolve(pin);
|
|
983
|
+
},
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
// Use the provided callbacks
|
|
988
|
+
userCallbacks = inclusionOptions.userCallbacks;
|
|
989
|
+
}
|
|
651
990
|
const deleteTempKey = () => {
|
|
652
991
|
var _a, _b;
|
|
653
992
|
// Whatever happens, no further communication needs the temporary key
|
|
@@ -769,16 +1108,27 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
769
1108
|
return abort();
|
|
770
1109
|
}
|
|
771
1110
|
const nodePublicKey = pubKeyResponse.publicKey;
|
|
772
|
-
// This is the starting point of the timer TAI2.
|
|
773
|
-
// the node has less time to send its KEXSet
|
|
1111
|
+
// This is the starting point of the timer TAI2.
|
|
774
1112
|
const timerStartTAI2 = Date.now();
|
|
1113
|
+
// Generate ECDH key pair. We need to immediately send the other node our public key,
|
|
1114
|
+
// so it won't abort bootstrapping
|
|
1115
|
+
const keyPair = await util_1.default.promisify(crypto_1.default.generateKeyPair)("x25519");
|
|
1116
|
+
const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
|
|
1117
|
+
type: "spki",
|
|
1118
|
+
format: "der",
|
|
1119
|
+
}));
|
|
1120
|
+
await api.sendPublicKey(publicKey);
|
|
1121
|
+
// After this, the node will start sending us a KEX SET every 10 seconds.
|
|
1122
|
+
// We won't be able to decode it until the DSK was verified
|
|
775
1123
|
if (grantedKeys.includes(core_1.SecurityClass.S2_AccessControl) ||
|
|
776
1124
|
grantedKeys.includes(core_1.SecurityClass.S2_Authenticated)) {
|
|
777
1125
|
// For authenticated encryption, the DSK (first 16 bytes of the public key) is obfuscated (missing the first 2 bytes)
|
|
778
1126
|
// Request the user to enter the missing part as a 5-digit PIN
|
|
779
1127
|
const dsk = (0, core_1.dskToString)(nodePublicKey.slice(0, 16)).slice(5);
|
|
1128
|
+
// The time the user has to enter the PIN is limited by the timeout TAI2
|
|
1129
|
+
const tai2RemainingMs = shared_2.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
|
|
780
1130
|
const pinResult = await Promise.race([
|
|
781
|
-
(0, async_1.wait)(
|
|
1131
|
+
(0, async_1.wait)(tai2RemainingMs, true).then(() => false),
|
|
782
1132
|
userCallbacks
|
|
783
1133
|
.validateDSKAndEnterPIN(dsk)
|
|
784
1134
|
// ignore errors in application callbacks
|
|
@@ -796,12 +1146,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
796
1146
|
// Fill in the missing two bytes of the public key
|
|
797
1147
|
nodePublicKey.writeUInt16BE(parseInt(pinResult, 10), 0);
|
|
798
1148
|
}
|
|
799
|
-
//
|
|
800
|
-
|
|
801
|
-
const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
|
|
802
|
-
type: "spki",
|
|
803
|
-
format: "der",
|
|
804
|
-
}));
|
|
1149
|
+
// After the user has verified the DSK, we can derive the shared secret
|
|
1150
|
+
// Z-Wave works with the "raw" keys, so this is a tad complicated
|
|
805
1151
|
const sharedSecret = crypto_1.default.diffieHellman({
|
|
806
1152
|
publicKey: crypto_1.default.createPublicKey({
|
|
807
1153
|
key: (0, core_1.encodeX25519KeyDERSPKI)(nodePublicKey),
|
|
@@ -810,15 +1156,14 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
810
1156
|
}),
|
|
811
1157
|
privateKey: keyPair.privateKey,
|
|
812
1158
|
});
|
|
813
|
-
// Derive temporary key from ECDH key pair
|
|
1159
|
+
// Derive temporary key from ECDH key pair - this will allow us to receive the node's KEX SET commands
|
|
814
1160
|
const tempKeys = (0, core_1.deriveTempKeys)((0, core_1.computePRK)(sharedSecret, publicKey, nodePublicKey));
|
|
1161
|
+
this.driver.securityManager2.deleteNonce(node.id);
|
|
815
1162
|
this.driver.securityManager2.tempKeys.set(node.id, {
|
|
816
1163
|
keyCCM: tempKeys.tempKeyCCM,
|
|
817
1164
|
personalizationString: tempKeys.tempPersonalizationString,
|
|
818
1165
|
});
|
|
819
|
-
|
|
820
|
-
// Wait until the encrypted KEXSet from the node was received
|
|
821
|
-
// (if there is even time left)
|
|
1166
|
+
// Now wait for the next KEXSet from the node (if there is even time left)
|
|
822
1167
|
const tai2RemainingMs = shared_2.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
|
|
823
1168
|
if (tai2RemainingMs < 1) {
|
|
824
1169
|
this.driver.controllerLog.logNode(node.id, {
|
|
@@ -935,6 +1280,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
935
1280
|
for (const securityClass of core_1.securityClassOrder) {
|
|
936
1281
|
node.securityClasses.set(securityClass, grantedKeys.includes(securityClass));
|
|
937
1282
|
}
|
|
1283
|
+
// Remember the DSK (first 16 bytes of the public key)
|
|
1284
|
+
node.dsk = nodePublicKey.slice(0, 16);
|
|
938
1285
|
this.driver.controllerLog.logNode(node.id, {
|
|
939
1286
|
message: `Security S2 bootstrapping successful with these security classes:${[
|
|
940
1287
|
...node.securityClasses.entries(),
|
|
@@ -1061,60 +1408,32 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1061
1408
|
}
|
|
1062
1409
|
}
|
|
1063
1410
|
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Stops an active exclusion process. Resolves to true when the controller leaves exclusion mode,
|
|
1066
|
-
* and false if the inclusion was not active.
|
|
1067
|
-
*/
|
|
1068
|
-
async stopExclusion() {
|
|
1069
|
-
// don't stop it twice
|
|
1070
|
-
if (!this._exclusionActive)
|
|
1071
|
-
return false;
|
|
1072
|
-
await this.stopExclusionInternal();
|
|
1073
|
-
return this._stopInclusionPromise;
|
|
1074
|
-
}
|
|
1075
1411
|
/**
|
|
1076
1412
|
* Is called when an AddNode request is received from the controller.
|
|
1077
1413
|
* Handles and controls the inclusion process.
|
|
1078
1414
|
*/
|
|
1079
|
-
async
|
|
1415
|
+
async handleAddNodeStatusReport(msg) {
|
|
1080
1416
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1081
1417
|
this.driver.controllerLog.print(`handling add node request (status = ${AddNodeToNetworkRequest_1.AddNodeStatus[msg.status]})`);
|
|
1082
|
-
if (
|
|
1418
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Including ||
|
|
1083
1419
|
this._inclusionOptions == undefined) {
|
|
1084
1420
|
this.driver.controllerLog.print(` inclusion is NOT active, ignoring it...`);
|
|
1085
1421
|
return true; // Don't invoke any more handlers
|
|
1086
1422
|
}
|
|
1087
1423
|
switch (msg.status) {
|
|
1088
|
-
case AddNodeToNetworkRequest_1.AddNodeStatus.Ready:
|
|
1089
|
-
// this is called when inclusion was started successfully
|
|
1090
|
-
this.driver.controllerLog.print(` the controller is now ready to add nodes`);
|
|
1091
|
-
if (this._beginInclusionPromise != null) {
|
|
1092
|
-
this._beginInclusionPromise.resolve(true);
|
|
1093
|
-
this.emit("inclusion started",
|
|
1094
|
-
// TODO: Remove first parameter in next major version
|
|
1095
|
-
this._inclusionOptions.strategy !==
|
|
1096
|
-
Inclusion_1.InclusionStrategy.Insecure, this._inclusionOptions.strategy);
|
|
1097
|
-
}
|
|
1098
|
-
break;
|
|
1099
1424
|
case AddNodeToNetworkRequest_1.AddNodeStatus.Failed:
|
|
1100
|
-
//
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
else {
|
|
1106
|
-
// ...or adding a node failed
|
|
1107
|
-
this.driver.controllerLog.print(` adding the node failed`, "error");
|
|
1108
|
-
this.emit("inclusion failed");
|
|
1109
|
-
}
|
|
1425
|
+
// This code is handled elsewhere for starting the inclusion, so this means
|
|
1426
|
+
// that adding a node failed
|
|
1427
|
+
this.driver.controllerLog.print(`Adding the node failed`, "error");
|
|
1428
|
+
this.emit("inclusion failed");
|
|
1110
1429
|
// in any case, stop the inclusion process so we don't accidentally add another node
|
|
1111
1430
|
try {
|
|
1112
|
-
await this.
|
|
1431
|
+
await this.stopInclusion();
|
|
1113
1432
|
}
|
|
1114
1433
|
catch {
|
|
1115
1434
|
/* ok */
|
|
1116
1435
|
}
|
|
1117
|
-
|
|
1436
|
+
return true; // Don't invoke any more handlers
|
|
1118
1437
|
case AddNodeToNetworkRequest_1.AddNodeStatus.AddingController:
|
|
1119
1438
|
this._includeController = true;
|
|
1120
1439
|
// fall through!
|
|
@@ -1131,103 +1450,113 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1131
1450
|
case AddNodeToNetworkRequest_1.AddNodeStatus.ProtocolDone: {
|
|
1132
1451
|
// this is called after a new node is added
|
|
1133
1452
|
// stop the inclusion process so we don't accidentally add another node
|
|
1453
|
+
let nodeId;
|
|
1134
1454
|
try {
|
|
1135
|
-
await this.
|
|
1455
|
+
nodeId = await this.finishInclusion();
|
|
1136
1456
|
}
|
|
1137
1457
|
catch {
|
|
1138
|
-
|
|
1458
|
+
// ignore the error
|
|
1139
1459
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1460
|
+
// It is recommended to send another STOP command to the controller
|
|
1461
|
+
try {
|
|
1462
|
+
await this.stopInclusionNoCallback();
|
|
1463
|
+
}
|
|
1464
|
+
catch {
|
|
1465
|
+
// ignore the error
|
|
1466
|
+
}
|
|
1467
|
+
if (!nodeId || !this._nodePendingInclusion) {
|
|
1468
|
+
// The inclusion did not succeed
|
|
1469
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1470
|
+
this._nodePendingInclusion = undefined;
|
|
1471
|
+
return true;
|
|
1472
|
+
}
|
|
1473
|
+
else if (nodeId === core_1.NODE_ID_BROADCAST) {
|
|
1149
1474
|
// No idea how this can happen but it dit at least once
|
|
1150
1475
|
this.driver.controllerLog.print(`Cannot add a node with the Node ID ${core_1.NODE_ID_BROADCAST}, aborting...`, "warn");
|
|
1476
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1151
1477
|
this._nodePendingInclusion = undefined;
|
|
1478
|
+
return true;
|
|
1152
1479
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
]
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1480
|
+
// We're technically done with the inclusion but should not include
|
|
1481
|
+
// anything else until the node has been bootstrapped
|
|
1482
|
+
this.setInclusionState(Inclusion_1.InclusionState.Busy);
|
|
1483
|
+
// Inclusion is now completed, bootstrap the node
|
|
1484
|
+
const newNode = this._nodePendingInclusion;
|
|
1485
|
+
const supportedCommandClasses = [
|
|
1486
|
+
...newNode.implementedCommandClasses.entries(),
|
|
1487
|
+
]
|
|
1488
|
+
.filter(([, info]) => info.isSupported)
|
|
1489
|
+
.map(([cc]) => cc);
|
|
1490
|
+
const controlledCommandClasses = [
|
|
1491
|
+
...newNode.implementedCommandClasses.entries(),
|
|
1492
|
+
]
|
|
1493
|
+
.filter(([, info]) => info.isControlled)
|
|
1494
|
+
.map(([cc]) => cc);
|
|
1495
|
+
this.driver.controllerLog.print(`finished adding node ${newNode.id}:
|
|
1166
1496
|
basic device class: ${(_a = newNode.deviceClass) === null || _a === void 0 ? void 0 : _a.basic.label}
|
|
1167
1497
|
generic device class: ${(_b = newNode.deviceClass) === null || _b === void 0 ? void 0 : _b.generic.label}
|
|
1168
1498
|
specific device class: ${(_c = newNode.deviceClass) === null || _c === void 0 ? void 0 : _c.specific.label}
|
|
1169
1499
|
supported CCs: ${supportedCommandClasses
|
|
1170
|
-
|
|
1171
|
-
|
|
1500
|
+
.map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
|
|
1501
|
+
.join("")}
|
|
1172
1502
|
controlled CCs: ${controlledCommandClasses
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
}
|
|
1503
|
+
.map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
|
|
1504
|
+
.join("")}`);
|
|
1505
|
+
// remember the node
|
|
1506
|
+
this._nodes.set(newNode.id, newNode);
|
|
1507
|
+
this._nodePendingInclusion = undefined;
|
|
1508
|
+
// We're communicating with the device, so assume it is alive
|
|
1509
|
+
// If it is actually a sleeping device, it will be marked as such later
|
|
1510
|
+
newNode.markAsAlive();
|
|
1511
|
+
// Assign SUC return route to make sure the node knows where to get its routes from
|
|
1512
|
+
newNode.hasSUCReturnRoute = await this.assignSUCReturnRoute(newNode.id);
|
|
1513
|
+
const opts = this._inclusionOptions;
|
|
1514
|
+
// The default inclusion strategy is: Use S2 if possible, only use S0 if necessary, use no encryption otherwise
|
|
1515
|
+
let lowSecurity = false;
|
|
1516
|
+
if (newNode.supportsCC(core_1.CommandClasses["Security 2"]) &&
|
|
1517
|
+
(opts.strategy === Inclusion_1.InclusionStrategy.Default ||
|
|
1518
|
+
opts.strategy === Inclusion_1.InclusionStrategy.Security_S2 ||
|
|
1519
|
+
opts.strategy === Inclusion_1.InclusionStrategy.SmartStart)) {
|
|
1520
|
+
await this.secureBootstrapS2(newNode);
|
|
1521
|
+
const actualSecurityClass = newNode.getHighestSecurityClass();
|
|
1522
|
+
if (actualSecurityClass == undefined ||
|
|
1523
|
+
actualSecurityClass < core_1.SecurityClass.S2_Unauthenticated) {
|
|
1524
|
+
lowSecurity = true;
|
|
1196
1525
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1526
|
+
}
|
|
1527
|
+
else if (newNode.supportsCC(core_1.CommandClasses.Security) &&
|
|
1528
|
+
(opts.strategy === Inclusion_1.InclusionStrategy.Security_S0 ||
|
|
1529
|
+
(opts.strategy === Inclusion_1.InclusionStrategy.Default &&
|
|
1530
|
+
(opts.forceSecurity ||
|
|
1531
|
+
((_g = ((_e = (_d = newNode.deviceClass) === null || _d === void 0 ? void 0 : _d.specific) !== null && _e !== void 0 ? _e : (_f = newNode.deviceClass) === null || _f === void 0 ? void 0 : _f.generic)) === null || _g === void 0 ? void 0 : _g.requiresSecurity))))) {
|
|
1532
|
+
await this.secureBootstrapS0(newNode);
|
|
1533
|
+
const actualSecurityClass = newNode.getHighestSecurityClass();
|
|
1534
|
+
if (actualSecurityClass == undefined ||
|
|
1535
|
+
actualSecurityClass < core_1.SecurityClass.S0_Legacy) {
|
|
1536
|
+
lowSecurity = true;
|
|
1208
1537
|
}
|
|
1209
|
-
this._includeController = false;
|
|
1210
|
-
// Bootstrap the node's lifelines, so it knows where the controller is
|
|
1211
|
-
await this.bootstrapLifelineAndWakeup(newNode);
|
|
1212
|
-
// We're done adding this node, notify listeners
|
|
1213
|
-
const result = {};
|
|
1214
|
-
if (lowSecurity)
|
|
1215
|
-
result.lowSecurity = true;
|
|
1216
|
-
this.emit("node added", newNode, result);
|
|
1217
1538
|
}
|
|
1218
|
-
|
|
1539
|
+
this._includeController = false;
|
|
1540
|
+
// Bootstrap the node's lifelines, so it knows where the controller is
|
|
1541
|
+
await this.bootstrapLifelineAndWakeup(newNode);
|
|
1542
|
+
// We're done adding this node, notify listeners
|
|
1543
|
+
const result = {};
|
|
1544
|
+
if (lowSecurity)
|
|
1545
|
+
result.lowSecurity = true;
|
|
1546
|
+
this.markNodeOnProvisioningList(newNode);
|
|
1547
|
+
this.emit("node added", newNode, result);
|
|
1548
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1549
|
+
return true; // Don't invoke any more handlers
|
|
1219
1550
|
}
|
|
1220
|
-
default:
|
|
1221
|
-
// not sure what to do with this message
|
|
1222
|
-
return false;
|
|
1223
1551
|
}
|
|
1224
|
-
|
|
1552
|
+
// not sure what to do with this message
|
|
1553
|
+
return false;
|
|
1225
1554
|
}
|
|
1226
1555
|
/**
|
|
1227
1556
|
* Is called when an ReplaceFailed request is received from the controller.
|
|
1228
1557
|
* Handles and controls the replace process.
|
|
1229
1558
|
*/
|
|
1230
|
-
async
|
|
1559
|
+
async handleReplaceNodeStatusReport(msg) {
|
|
1231
1560
|
var _a, _b, _c;
|
|
1232
1561
|
this.driver.controllerLog.print(`handling replace node request (status = ${ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus[msg.replaceStatus]})`);
|
|
1233
1562
|
if (this._inclusionOptions == undefined) {
|
|
@@ -1236,9 +1565,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1236
1565
|
}
|
|
1237
1566
|
switch (msg.replaceStatus) {
|
|
1238
1567
|
case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.NodeOK:
|
|
1568
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1239
1569
|
(_a = this._replaceFailedPromise) === null || _a === void 0 ? void 0 : _a.reject(new core_1.ZWaveError(`The node could not be replaced because it has responded`, core_1.ZWaveErrorCodes.ReplaceFailedNode_NodeOK));
|
|
1240
1570
|
break;
|
|
1241
1571
|
case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.FailedNodeReplaceFailed:
|
|
1572
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1242
1573
|
(_b = this._replaceFailedPromise) === null || _b === void 0 ? void 0 : _b.reject(new core_1.ZWaveError(`The failed node has not been replaced`, core_1.ZWaveErrorCodes.ReplaceFailedNode_Failed));
|
|
1243
1574
|
break;
|
|
1244
1575
|
case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.FailedNodeReplace:
|
|
@@ -1249,7 +1580,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1249
1580
|
// TODO: Remove first parameter in next major version
|
|
1250
1581
|
this._inclusionOptions.strategy !==
|
|
1251
1582
|
Inclusion_1.InclusionStrategy.Insecure, this._inclusionOptions.strategy);
|
|
1252
|
-
this.
|
|
1583
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
1253
1584
|
(_c = this._replaceFailedPromise) === null || _c === void 0 ? void 0 : _c.resolve(true);
|
|
1254
1585
|
// stop here, don't emit inclusion failed
|
|
1255
1586
|
return true;
|
|
@@ -1258,7 +1589,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1258
1589
|
this.emit("inclusion stopped");
|
|
1259
1590
|
if (this._nodePendingReplace) {
|
|
1260
1591
|
this.emit("node removed", this._nodePendingReplace, true);
|
|
1592
|
+
this.unmarkNodeOnProvisioningList(this._nodePendingReplace.id);
|
|
1261
1593
|
this._nodes.delete(this._nodePendingReplace.id);
|
|
1594
|
+
// We're technically done with the replacing but should not include
|
|
1595
|
+
// anything else until the node has been bootstrapped
|
|
1596
|
+
this.setInclusionState(Inclusion_1.InclusionState.Busy);
|
|
1262
1597
|
// Create a fresh node instance and forget the old one
|
|
1263
1598
|
const newNode = new Node_1.ZWaveNode(this._nodePendingReplace.id, this.driver, undefined, undefined, undefined,
|
|
1264
1599
|
// Create an empty value DB and specify that it contains no values
|
|
@@ -1298,6 +1633,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1298
1633
|
const result = {};
|
|
1299
1634
|
if (lowSecurity)
|
|
1300
1635
|
result.lowSecurity = true;
|
|
1636
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1637
|
+
this.markNodeOnProvisioningList(newNode);
|
|
1301
1638
|
this.emit("node added", newNode, result);
|
|
1302
1639
|
}
|
|
1303
1640
|
// stop here, don't emit inclusion failed
|
|
@@ -1310,40 +1647,26 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1310
1647
|
* Is called when a RemoveNode request is received from the controller.
|
|
1311
1648
|
* Handles and controls the exclusion process.
|
|
1312
1649
|
*/
|
|
1313
|
-
async
|
|
1650
|
+
async handleRemoveNodeStatusReport(msg) {
|
|
1314
1651
|
this.driver.controllerLog.print(`handling remove node request (status = ${RemoveNodeFromNetworkRequest_1.RemoveNodeStatus[msg.status]})`);
|
|
1315
|
-
if (
|
|
1652
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Excluding) {
|
|
1316
1653
|
this.driver.controllerLog.print(` exclusion is NOT active, ignoring it...`);
|
|
1317
1654
|
return true; // Don't invoke any more handlers
|
|
1318
1655
|
}
|
|
1319
1656
|
switch (msg.status) {
|
|
1320
|
-
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Ready:
|
|
1321
|
-
// this is called when inclusion was started successfully
|
|
1322
|
-
this.driver.controllerLog.print(` the controller is now ready to remove nodes`);
|
|
1323
|
-
if (this._beginInclusionPromise != null) {
|
|
1324
|
-
this._beginInclusionPromise.resolve(true);
|
|
1325
|
-
this.emit("exclusion started");
|
|
1326
|
-
}
|
|
1327
|
-
break;
|
|
1328
1657
|
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Failed:
|
|
1329
|
-
//
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1334
|
-
else {
|
|
1335
|
-
// ...or removing a node failed
|
|
1336
|
-
this.driver.controllerLog.print(` removing the node failed`, "error");
|
|
1337
|
-
this.emit("exclusion failed");
|
|
1338
|
-
}
|
|
1658
|
+
// This code is handled elsewhere for starting the exclusion, so this means
|
|
1659
|
+
// that removing a node failed
|
|
1660
|
+
this.driver.controllerLog.print(`Removing the node failed`, "error");
|
|
1661
|
+
this.emit("exclusion failed");
|
|
1339
1662
|
// in any case, stop the exclusion process so we don't accidentally remove another node
|
|
1340
1663
|
try {
|
|
1341
|
-
await this.
|
|
1664
|
+
await this.stopExclusion();
|
|
1342
1665
|
}
|
|
1343
1666
|
catch {
|
|
1344
1667
|
/* ok */
|
|
1345
1668
|
}
|
|
1346
|
-
|
|
1669
|
+
return true; // Don't invoke any more handlers
|
|
1347
1670
|
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.RemovingSlave:
|
|
1348
1671
|
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.RemovingController: {
|
|
1349
1672
|
// this is called when a node is removed
|
|
@@ -1354,29 +1677,33 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1354
1677
|
// this is called when the exclusion was completed
|
|
1355
1678
|
// stop the exclusion process so we don't accidentally remove another node
|
|
1356
1679
|
try {
|
|
1357
|
-
await this.
|
|
1680
|
+
await this.stopExclusionNoCallback();
|
|
1358
1681
|
}
|
|
1359
1682
|
catch {
|
|
1360
1683
|
/* ok */
|
|
1361
1684
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
this.
|
|
1365
|
-
|
|
1366
|
-
this.driver.controllerLog.print(`Node ${this._nodePendingExclusion.id} was removed`);
|
|
1367
|
-
// notify listeners
|
|
1368
|
-
this.emit("node removed", this._nodePendingExclusion, false);
|
|
1369
|
-
// and forget the node
|
|
1370
|
-
this._nodes.delete(this._nodePendingExclusion.id);
|
|
1371
|
-
this._nodePendingExclusion = undefined;
|
|
1685
|
+
if (!this._nodePendingExclusion) {
|
|
1686
|
+
// The exclusion did not succeed
|
|
1687
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1688
|
+
return true;
|
|
1372
1689
|
}
|
|
1373
|
-
|
|
1690
|
+
const nodeId = this._nodePendingExclusion.id;
|
|
1691
|
+
this.driver.controllerLog.print(`Node ${nodeId} was removed`);
|
|
1692
|
+
// Avoid automatic re-inclusion using SmartStart if desired
|
|
1693
|
+
if (this._unprovisionRemovedNode)
|
|
1694
|
+
this.unprovisionSmartStartNode(nodeId);
|
|
1695
|
+
// notify listeners
|
|
1696
|
+
this.emit("node removed", this._nodePendingExclusion, false);
|
|
1697
|
+
// and forget the node
|
|
1698
|
+
this.unmarkNodeOnProvisioningList(nodeId);
|
|
1699
|
+
this._nodes.delete(nodeId);
|
|
1700
|
+
this._nodePendingExclusion = undefined;
|
|
1701
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1702
|
+
return true; // Don't invoke any more handlers
|
|
1374
1703
|
}
|
|
1375
|
-
default:
|
|
1376
|
-
// not sure what to do with this message
|
|
1377
|
-
return false;
|
|
1378
1704
|
}
|
|
1379
|
-
|
|
1705
|
+
// not sure what to do with this message
|
|
1706
|
+
return false;
|
|
1380
1707
|
}
|
|
1381
1708
|
/**
|
|
1382
1709
|
* Performs a healing process for all alive nodes in the network,
|
|
@@ -2153,15 +2480,23 @@ ${associatedNodes.join(", ")}`,
|
|
|
2153
2480
|
// Emit the removed event so the driver and applications can react
|
|
2154
2481
|
this.emit("node removed", this.nodes.get(nodeId), false);
|
|
2155
2482
|
// and forget the node
|
|
2483
|
+
this.unmarkNodeOnProvisioningList(nodeId);
|
|
2156
2484
|
this._nodes.delete(nodeId);
|
|
2157
2485
|
return;
|
|
2158
2486
|
}
|
|
2159
2487
|
}
|
|
2160
2488
|
}
|
|
2161
2489
|
async replaceFailedNode(nodeId, options) {
|
|
2162
|
-
|
|
2163
|
-
|
|
2490
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
2491
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
2492
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
2164
2493
|
return false;
|
|
2494
|
+
}
|
|
2495
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
2496
|
+
// Disable listening mode so we can switch to exclusion mode
|
|
2497
|
+
await this.stopInclusion();
|
|
2498
|
+
}
|
|
2499
|
+
this.setInclusionState(Inclusion_1.InclusionState.Busy);
|
|
2165
2500
|
if (options == undefined) {
|
|
2166
2501
|
options = {
|
|
2167
2502
|
strategy: Inclusion_1.InclusionStrategy.Security_S0,
|
|
@@ -2177,6 +2512,7 @@ ${associatedNodes.join(", ")}`,
|
|
|
2177
2512
|
this.driver.controllerLog.print(`starting replace failed node process...`);
|
|
2178
2513
|
const node = this.nodes.getOrThrow(nodeId);
|
|
2179
2514
|
if (await node.ping()) {
|
|
2515
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
2180
2516
|
throw new core_1.ZWaveError(`The node replace process could not be started because the node responded to a ping.`, core_1.ZWaveErrorCodes.ReplaceFailedNode_Failed);
|
|
2181
2517
|
}
|
|
2182
2518
|
this._inclusionOptions = options;
|
|
@@ -2202,6 +2538,7 @@ ${associatedNodes.join(", ")}`,
|
|
|
2202
2538
|
ReplaceFailedNodeRequest_1.ReplaceFailedNodeStartFlags.ReplaceFailed)) {
|
|
2203
2539
|
message += `\n· The controller is busy or the node has responded`;
|
|
2204
2540
|
}
|
|
2541
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
2205
2542
|
throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.ReplaceFailedNode_Failed);
|
|
2206
2543
|
}
|
|
2207
2544
|
else {
|
|
@@ -2316,6 +2653,18 @@ ${associatedNodes.join(", ")}`,
|
|
|
2316
2653
|
*/
|
|
2317
2654
|
serialize() {
|
|
2318
2655
|
return {
|
|
2656
|
+
controller: {
|
|
2657
|
+
supportsSoftReset: this.supportsSoftReset,
|
|
2658
|
+
provisioningList: this.provisioningList.map((e) => {
|
|
2659
|
+
const { dsk, securityClasses, ...rest } = e;
|
|
2660
|
+
return {
|
|
2661
|
+
dsk,
|
|
2662
|
+
securityClasses: securityClasses.map((s) => core_1.SecurityClass[s]),
|
|
2663
|
+
// The user-defined properties are saved as-is
|
|
2664
|
+
...rest,
|
|
2665
|
+
};
|
|
2666
|
+
}),
|
|
2667
|
+
},
|
|
2319
2668
|
nodes: (0, objects_1.composeObject)([...this.nodes.entries()].map(([id, node]) => [id.toString(), node.serialize()])),
|
|
2320
2669
|
};
|
|
2321
2670
|
}
|
|
@@ -2324,6 +2673,43 @@ ${associatedNodes.join(", ")}`,
|
|
|
2324
2673
|
* Deserializes the controller information and all nodes from the cache.
|
|
2325
2674
|
*/
|
|
2326
2675
|
async deserialize(serialized) {
|
|
2676
|
+
if ((0, typeguards_1.isObject)(serialized.controller)) {
|
|
2677
|
+
// Parse whether the controller supports soft reset
|
|
2678
|
+
if (typeof serialized.controller.supportsSoftReset === "boolean") {
|
|
2679
|
+
this.supportsSoftReset =
|
|
2680
|
+
serialized.controller.supportsSoftReset;
|
|
2681
|
+
}
|
|
2682
|
+
// Parse the controller's Smart Start provisioning list
|
|
2683
|
+
if ((0, typeguards_1.isArray)(serialized.controller.provisioningList)) {
|
|
2684
|
+
entries: for (const entry of serialized.controller
|
|
2685
|
+
.provisioningList) {
|
|
2686
|
+
if (!(0, typeguards_1.isObject)(entry))
|
|
2687
|
+
continue;
|
|
2688
|
+
const { dsk, securityClasses: secClasses, ...rest } = entry;
|
|
2689
|
+
if (typeof entry.dsk !== "string")
|
|
2690
|
+
continue;
|
|
2691
|
+
if (!(0, typeguards_1.isArray)(entry.securityClasses))
|
|
2692
|
+
continue;
|
|
2693
|
+
if (!(0, core_1.isValidDSK)(entry.dsk))
|
|
2694
|
+
continue;
|
|
2695
|
+
const securityClasses = [];
|
|
2696
|
+
for (const s of secClasses) {
|
|
2697
|
+
if (typeof s !== "string")
|
|
2698
|
+
continue entries;
|
|
2699
|
+
const secClass = core_1.SecurityClass[s];
|
|
2700
|
+
if (typeof secClass !== "number")
|
|
2701
|
+
continue entries;
|
|
2702
|
+
securityClasses.push(secClass);
|
|
2703
|
+
}
|
|
2704
|
+
this._provisioningList.push({
|
|
2705
|
+
dsk: entry.dsk,
|
|
2706
|
+
securityClasses,
|
|
2707
|
+
// The user-defined properties are not validated further
|
|
2708
|
+
...rest,
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2327
2713
|
if ((0, typeguards_1.isObject)(serialized.nodes)) {
|
|
2328
2714
|
for (const nodeId of Object.keys(serialized.nodes)) {
|
|
2329
2715
|
const serializedNode = serialized.nodes[nodeId];
|
|
@@ -2333,9 +2719,9 @@ ${associatedNodes.join(", ")}`,
|
|
|
2333
2719
|
throw new core_1.ZWaveError("The cache file is invalid", core_1.ZWaveErrorCodes.Driver_InvalidCache);
|
|
2334
2720
|
}
|
|
2335
2721
|
if (this.nodes.has(serializedNode.id)) {
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2722
|
+
const node = this.nodes.getOrThrow(serializedNode.id);
|
|
2723
|
+
await node.deserialize(serializedNode);
|
|
2724
|
+
this.markNodeOnProvisioningList(node);
|
|
2339
2725
|
}
|
|
2340
2726
|
}
|
|
2341
2727
|
}
|