zwave-js 8.6.1 → 8.7.0
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 +62 -20
- package/build/lib/controller/Controller.d.ts.map +1 -1
- package/build/lib/controller/Controller.js +629 -264
- 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 +1 -1
- package/build/lib/driver/Driver.d.ts.map +1 -1
- package/build/lib/driver/Driver.js +154 -33
- 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 +20 -0
- 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,28 @@ 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
|
+
}
|
|
188
208
|
/** Returns the controller node's value DB */
|
|
189
209
|
get valueDB() {
|
|
190
210
|
return this._nodes.get(this._ownNodeId).valueDB;
|
|
@@ -202,9 +222,95 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
202
222
|
const nodes = nodeIDs.map((id) => this._nodes.getOrThrow(id));
|
|
203
223
|
return new VirtualNode_1.VirtualNode(undefined, this.driver, nodes);
|
|
204
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
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
this._provisioningList[index] = entry;
|
|
237
|
+
}
|
|
238
|
+
this.autoProvisionSmartStart();
|
|
239
|
+
void this.driver.saveNetworkToCache();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Removes the given DSK or node ID from the controller's SmartStart provisioning list.
|
|
243
|
+
*
|
|
244
|
+
* **Note:** If this entry corresponds to an included node, it will **NOT** be excluded
|
|
245
|
+
*/
|
|
246
|
+
unprovisionSmartStartNode(dskOrNodeId) {
|
|
247
|
+
const index = this._provisioningList.findIndex((e) => e.dsk === dskOrNodeId ||
|
|
248
|
+
(typeof dskOrNodeId === "number" &&
|
|
249
|
+
"nodeId" in e &&
|
|
250
|
+
e.nodeId === dskOrNodeId));
|
|
251
|
+
if (index >= 0) {
|
|
252
|
+
this._provisioningList.splice(index, 1);
|
|
253
|
+
this.autoProvisionSmartStart();
|
|
254
|
+
void this.driver.saveNetworkToCache();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Returns the entry for the given DSK from the controller's SmartStart provisioning list.
|
|
259
|
+
*/
|
|
260
|
+
getProvisioningEntry(dsk) {
|
|
261
|
+
return this._provisioningList.find((e) => e.dsk === dsk);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Returns all entries from the controller's SmartStart provisioning list.
|
|
265
|
+
*/
|
|
266
|
+
getProvisioningEntries() {
|
|
267
|
+
// Make copies so no one can modify the internal list (except for user info)
|
|
268
|
+
return this._provisioningList.map((e) => {
|
|
269
|
+
const { dsk, securityClasses, nodeId, ...rest } = e;
|
|
270
|
+
return {
|
|
271
|
+
dsk,
|
|
272
|
+
securityClasses: [...securityClasses],
|
|
273
|
+
...(nodeId != undefined ? { nodeId } : {}),
|
|
274
|
+
...rest,
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/** Returns whether the SmartStart provisioning list contains entries that have not been included yet */
|
|
279
|
+
hasPlannedProvisioningEntries() {
|
|
280
|
+
return this._provisioningList.some((e) => !("nodeId" in e));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* @internal
|
|
284
|
+
* Automatically starts smart start inclusion if there are nodes pending inclusion.
|
|
285
|
+
*/
|
|
286
|
+
autoProvisionSmartStart() {
|
|
287
|
+
if (this.hasPlannedProvisioningEntries()) {
|
|
288
|
+
// SmartStart should be enabled
|
|
289
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
290
|
+
void this.enableSmartStart().catch(() => { });
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// SmartStart should be disabled
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
295
|
+
void this.disableSmartStart().catch(() => { });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
markNodeOnProvisioningList(node) {
|
|
299
|
+
// If this node's DSK is on the provisioning list, remember the node ID
|
|
300
|
+
if (node.dsk) {
|
|
301
|
+
const entry = this._provisioningList.find((e) => e.dsk === (0, core_1.dskToString)(node.dsk));
|
|
302
|
+
if (entry)
|
|
303
|
+
entry.nodeId = node.id;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
unmarkNodeOnProvisioningList(nodeId) {
|
|
307
|
+
const entry = this._provisioningList.find((e) => "nodeId" in e && e.nodeId === nodeId);
|
|
308
|
+
if (entry)
|
|
309
|
+
delete entry.nodeId;
|
|
310
|
+
}
|
|
205
311
|
/**
|
|
206
312
|
* @internal
|
|
207
|
-
* Queries the controller IDs
|
|
313
|
+
* Queries the controller IDs and its Serial API capabilities
|
|
208
314
|
*/
|
|
209
315
|
async identify() {
|
|
210
316
|
// get the home and node id of the controller
|
|
@@ -215,14 +321,24 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
215
321
|
this.driver.controllerLog.print(`received controller IDs:
|
|
216
322
|
home ID: ${(0, shared_1.num2hex)(this._homeId)}
|
|
217
323
|
own node ID: ${this._ownNodeId}`);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.
|
|
324
|
+
// Figure out what the serial API can do
|
|
325
|
+
this.driver.controllerLog.print(`querying API capabilities...`);
|
|
326
|
+
const apiCaps = await this.driver.sendMessage(new GetSerialApiCapabilitiesMessages_1.GetSerialApiCapabilitiesRequest(this.driver), {
|
|
327
|
+
supportCheck: false,
|
|
328
|
+
});
|
|
329
|
+
this._serialApiVersion = apiCaps.serialApiVersion;
|
|
330
|
+
this._manufacturerId = apiCaps.manufacturerId;
|
|
331
|
+
this._productType = apiCaps.productType;
|
|
332
|
+
this._productId = apiCaps.productId;
|
|
333
|
+
this._supportedFunctionTypes = apiCaps.supportedFunctionTypes;
|
|
334
|
+
this.driver.controllerLog.print(`received API capabilities:
|
|
335
|
+
serial API version: ${this._serialApiVersion}
|
|
336
|
+
manufacturer ID: ${(0, shared_1.num2hex)(this._manufacturerId)}
|
|
337
|
+
product type: ${(0, shared_1.num2hex)(this._productType)}
|
|
338
|
+
product ID: ${(0, shared_1.num2hex)(this._productId)}
|
|
339
|
+
supported functions: ${this._supportedFunctionTypes
|
|
340
|
+
.map((fn) => `\n · ${Constants_1.FunctionType[fn]} (${(0, shared_1.num2hex)(fn)})`)
|
|
341
|
+
.join("")}`);
|
|
226
342
|
}
|
|
227
343
|
/**
|
|
228
344
|
* @internal
|
|
@@ -230,7 +346,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
230
346
|
* @param restoreFromCache Asynchronous callback for the driver to restore the network from cache after nodes are created
|
|
231
347
|
*/
|
|
232
348
|
async interview(restoreFromCache) {
|
|
233
|
-
this.driver.controllerLog.print("beginning interview...");
|
|
234
349
|
// get basic controller version info
|
|
235
350
|
this.driver.controllerLog.print(`querying version info...`);
|
|
236
351
|
const version = await this.driver.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this.driver), {
|
|
@@ -258,25 +373,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
258
373
|
is SIS present: ${this._isSISPresent}
|
|
259
374
|
was real primary: ${this._wasRealPrimary}
|
|
260
375
|
is a SUC: ${this._isStaticUpdateController}`);
|
|
261
|
-
// find out which part of the API is supported
|
|
262
|
-
this.driver.controllerLog.print(`querying API capabilities...`);
|
|
263
|
-
const apiCaps = await this.driver.sendMessage(new GetSerialApiCapabilitiesMessages_1.GetSerialApiCapabilitiesRequest(this.driver), {
|
|
264
|
-
supportCheck: false,
|
|
265
|
-
});
|
|
266
|
-
this._serialApiVersion = apiCaps.serialApiVersion;
|
|
267
|
-
this._manufacturerId = apiCaps.manufacturerId;
|
|
268
|
-
this._productType = apiCaps.productType;
|
|
269
|
-
this._productId = apiCaps.productId;
|
|
270
|
-
this._supportedFunctionTypes = apiCaps.supportedFunctionTypes;
|
|
271
|
-
this.driver.controllerLog.print(`received API capabilities:
|
|
272
|
-
serial API version: ${this._serialApiVersion}
|
|
273
|
-
manufacturer ID: ${(0, shared_1.num2hex)(this._manufacturerId)}
|
|
274
|
-
product type: ${(0, shared_1.num2hex)(this._productType)}
|
|
275
|
-
product ID: ${(0, shared_1.num2hex)(this._productId)}
|
|
276
|
-
supported functions: ${this._supportedFunctionTypes
|
|
277
|
-
.map((fn) => `\n · ${Constants_1.FunctionType[fn]} (${(0, shared_1.num2hex)(fn)})`)
|
|
278
|
-
.join("")}`);
|
|
279
|
-
// now we can check if a function is supported
|
|
280
376
|
// Figure out which sub commands of SerialAPISetup are supported
|
|
281
377
|
if (this.isFunctionSupported(Constants_1.FunctionType.SerialAPISetup)) {
|
|
282
378
|
this.driver.controllerLog.print(`querying serial API setup capabilities...`);
|
|
@@ -353,9 +449,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
353
449
|
]);
|
|
354
450
|
// create an empty entry in the nodes map so we can initialize them afterwards
|
|
355
451
|
for (const nodeId of initData.nodeIds) {
|
|
356
|
-
// The controller node is created separately
|
|
357
|
-
if (nodeId === this._ownNodeId)
|
|
358
|
-
continue;
|
|
359
452
|
this._nodes.set(nodeId, new Node_1.ZWaveNode(nodeId, this.driver, undefined, undefined, undefined,
|
|
360
453
|
// Use the previously created index to avoid doing extra work when creating the value DB
|
|
361
454
|
this.createValueDBForNode(nodeId, valueDBIndexes.get(nodeId))));
|
|
@@ -465,9 +558,24 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
465
558
|
}
|
|
466
559
|
});
|
|
467
560
|
}
|
|
561
|
+
get inclusionState() {
|
|
562
|
+
return this._inclusionState;
|
|
563
|
+
}
|
|
564
|
+
setInclusionState(state) {
|
|
565
|
+
if (this._inclusionState === state)
|
|
566
|
+
return;
|
|
567
|
+
this._inclusionState = state;
|
|
568
|
+
if (state === Inclusion_1.InclusionState.Idle && this._smartStartEnabled) {
|
|
569
|
+
// If Smart Start was enabled before the inclusion/exclusion,
|
|
570
|
+
// enable it again and ignore errors
|
|
571
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
572
|
+
this.enableSmartStart().catch(() => { });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
468
575
|
async beginInclusion(options) {
|
|
469
|
-
|
|
470
|
-
|
|
576
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
577
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
578
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
471
579
|
return false;
|
|
472
580
|
}
|
|
473
581
|
if (options == undefined) {
|
|
@@ -483,97 +591,280 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
483
591
|
};
|
|
484
592
|
}
|
|
485
593
|
// Protect against invalid inclusion options
|
|
486
|
-
if (!(options.strategy in Inclusion_1.InclusionStrategy)
|
|
594
|
+
if (!(options.strategy in Inclusion_1.InclusionStrategy) ||
|
|
595
|
+
// @ts-expect-error We're checking for user errors
|
|
596
|
+
options.strategy === Inclusion_1.InclusionStrategy.SmartStart) {
|
|
487
597
|
throw new core_1.ZWaveError(`Invalid inclusion strategy: ${options.strategy}`, core_1.ZWaveErrorCodes.Argument_Invalid);
|
|
488
598
|
}
|
|
489
|
-
if (
|
|
490
|
-
|
|
599
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
600
|
+
// Disable listening mode so we can switch to inclusion mode
|
|
601
|
+
await this.stopInclusion();
|
|
491
602
|
}
|
|
492
|
-
this.
|
|
603
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
493
604
|
this._inclusionOptions = options;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
605
|
+
try {
|
|
606
|
+
this.driver.controllerLog.print(`Starting inclusion process with strategy ${(0, shared_1.getEnumMemberName)(Inclusion_1.InclusionStrategy, options.strategy)}...`);
|
|
607
|
+
// kick off the inclusion process
|
|
608
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
609
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Any,
|
|
610
|
+
highPower: true,
|
|
611
|
+
networkWide: true,
|
|
612
|
+
}));
|
|
613
|
+
this.driver.controllerLog.print(`The controller is now ready to add nodes`);
|
|
614
|
+
this.emit("inclusion started",
|
|
615
|
+
// TODO: Remove first parameter in next major version
|
|
616
|
+
options.strategy !== Inclusion_1.InclusionStrategy.Insecure, options.strategy);
|
|
617
|
+
}
|
|
618
|
+
catch (e) {
|
|
619
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
620
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
621
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
622
|
+
this.driver.controllerLog.print(`Starting the inclusion failed`, "error");
|
|
623
|
+
throw new core_1.ZWaveError("The inclusion could not be started.", core_1.ZWaveErrorCodes.Controller_InclusionFailed);
|
|
624
|
+
}
|
|
625
|
+
throw e;
|
|
626
|
+
}
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
/** @internal */
|
|
630
|
+
async beginInclusionSmartStart(provisioningEntry) {
|
|
631
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
632
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
633
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
// Disable listening mode so we can switch to inclusion mode
|
|
637
|
+
await this.stopInclusion();
|
|
638
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
639
|
+
this._inclusionOptions = {
|
|
640
|
+
strategy: Inclusion_1.InclusionStrategy.SmartStart,
|
|
641
|
+
provisioning: provisioningEntry,
|
|
642
|
+
};
|
|
643
|
+
try {
|
|
644
|
+
this.driver.controllerLog.print(`Including SmartStart node with DSK ${provisioningEntry.dsk}`);
|
|
645
|
+
// kick off the inclusion process
|
|
646
|
+
const dskBuffer = (0, core_1.dskFromString)(provisioningEntry.dsk);
|
|
647
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeDSKToNetworkRequest(this.driver, {
|
|
648
|
+
nwiHomeId: (0, core_1.nwiHomeIdFromDSK)(dskBuffer),
|
|
649
|
+
authHomeId: (0, core_1.authHomeIdFromDSK)(dskBuffer),
|
|
650
|
+
highPower: true,
|
|
651
|
+
networkWide: true,
|
|
652
|
+
}));
|
|
653
|
+
this.emit("inclusion started",
|
|
654
|
+
// TODO: Remove first parameter in next major version
|
|
655
|
+
true, Inclusion_1.InclusionStrategy.SmartStart);
|
|
656
|
+
}
|
|
657
|
+
catch (e) {
|
|
658
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
659
|
+
// Error handling for this happens at the call site
|
|
660
|
+
throw e;
|
|
661
|
+
}
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Is used internally to stop an active inclusion process without waiting for a confirmation
|
|
666
|
+
* @internal
|
|
667
|
+
*/
|
|
668
|
+
async stopInclusionNoCallback() {
|
|
498
669
|
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
499
|
-
|
|
670
|
+
callbackId: 0,
|
|
671
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
500
672
|
highPower: true,
|
|
501
673
|
networkWide: true,
|
|
502
674
|
}));
|
|
503
|
-
|
|
675
|
+
this.driver.controllerLog.print(`The inclusion process was stopped`);
|
|
676
|
+
this.emit("inclusion stopped");
|
|
504
677
|
}
|
|
505
|
-
/**
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
this.
|
|
511
|
-
this.driver.
|
|
512
|
-
// create the promise we're going to return
|
|
513
|
-
this._stopInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
|
|
514
|
-
// kick off the inclusion process
|
|
515
|
-
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
678
|
+
/**
|
|
679
|
+
* Finishes an inclusion process. This must only be called after the ProtocolDone status is received.
|
|
680
|
+
* Returns the ID of the newly added node.
|
|
681
|
+
*/
|
|
682
|
+
async finishInclusion() {
|
|
683
|
+
this.driver.controllerLog.print(`finishing inclusion process...`);
|
|
684
|
+
const response = await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
516
685
|
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
517
686
|
highPower: true,
|
|
518
687
|
networkWide: true,
|
|
519
688
|
}));
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
689
|
+
if (response.status === AddNodeToNetworkRequest_1.AddNodeStatus.Done) {
|
|
690
|
+
return response.statusContext.nodeId;
|
|
691
|
+
}
|
|
692
|
+
this.driver.controllerLog.print(`Finishing the inclusion failed`, "error");
|
|
693
|
+
throw new core_1.ZWaveError("Finishing the inclusion failed", core_1.ZWaveErrorCodes.Controller_InclusionFailed);
|
|
525
694
|
}
|
|
526
695
|
/**
|
|
527
696
|
* Stops an active inclusion process. Resolves to true when the controller leaves inclusion mode,
|
|
528
697
|
* and false if the inclusion was not active.
|
|
529
698
|
*/
|
|
530
699
|
async stopInclusion() {
|
|
531
|
-
|
|
532
|
-
if (!this._inclusionActive)
|
|
700
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Including) {
|
|
533
701
|
return false;
|
|
534
|
-
|
|
535
|
-
|
|
702
|
+
}
|
|
703
|
+
this.driver.controllerLog.print(`stopping inclusion process...`);
|
|
704
|
+
try {
|
|
705
|
+
// stop the inclusion process
|
|
706
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
|
|
707
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
708
|
+
highPower: true,
|
|
709
|
+
networkWide: true,
|
|
710
|
+
}));
|
|
711
|
+
this.driver.controllerLog.print(`The inclusion process was stopped`);
|
|
712
|
+
this.emit("inclusion stopped");
|
|
713
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
catch (e) {
|
|
717
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
718
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
719
|
+
this.driver.controllerLog.print(`Stopping the inclusion failed`, "error");
|
|
720
|
+
throw new core_1.ZWaveError("The inclusion could not be stopped.", core_1.ZWaveErrorCodes.Controller_InclusionFailed);
|
|
721
|
+
}
|
|
722
|
+
throw e;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Puts the controller into listening mode for Smart Start inclusion.
|
|
727
|
+
* Whenever a node on the provisioning list announces itself, it will automatically be added.
|
|
728
|
+
*
|
|
729
|
+
* Resolves to `true` when the listening mode is started or was active, and `false` if it is scheduled for later activation.
|
|
730
|
+
*/
|
|
731
|
+
async enableSmartStart() {
|
|
732
|
+
if (this._smartStartEnabled)
|
|
733
|
+
return true;
|
|
734
|
+
this._smartStartEnabled = true;
|
|
735
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Idle) {
|
|
736
|
+
this.setInclusionState(Inclusion_1.InclusionState.SmartStart);
|
|
737
|
+
this.driver.controllerLog.print(`Enabling Smart Start listening mode...`);
|
|
738
|
+
try {
|
|
739
|
+
await this.driver.sendMessage(new AddNodeToNetworkRequest_1.EnableSmartStartListenRequest(this.driver, {}));
|
|
740
|
+
this.driver.controllerLog.print(`Smart Start listening mode enabled`);
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
catch (e) {
|
|
744
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
745
|
+
this.driver.controllerLog.print(`Smart Start listening mode could not be enabled: ${(0, shared_1.getErrorMessage)(e)}`, "error");
|
|
746
|
+
throw e;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
else if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
this.driver.controllerLog.print(`Smart Start listening mode scheduled for later activation...`);
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Disables the listening mode for Smart Start inclusion.
|
|
759
|
+
*
|
|
760
|
+
* Resolves to `true` when the listening mode is stopped, and `false` if was not active.
|
|
761
|
+
*/
|
|
762
|
+
async disableSmartStart() {
|
|
763
|
+
if (!this._smartStartEnabled)
|
|
764
|
+
return false;
|
|
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 {
|
|
781
|
+
this.driver.controllerLog.print(`Smart Start listening mode disabled`);
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
536
784
|
}
|
|
537
785
|
/**
|
|
538
786
|
* Starts the exclusion process of new nodes.
|
|
539
|
-
* Resolves to true when the process was started,
|
|
540
|
-
*
|
|
787
|
+
* Resolves to true when the process was started, and false if an inclusion or exclusion process was already active.
|
|
788
|
+
*
|
|
789
|
+
* @param unprovision Whether the removed node should also be removed from the Smart Start provisioning list.
|
|
541
790
|
*/
|
|
542
|
-
async beginExclusion() {
|
|
543
|
-
|
|
544
|
-
|
|
791
|
+
async beginExclusion(unprovision = false) {
|
|
792
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
793
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
794
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
545
795
|
return false;
|
|
546
|
-
|
|
796
|
+
}
|
|
797
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
798
|
+
// Disable listening mode so we can switch to exclusion mode
|
|
799
|
+
await this.stopInclusion();
|
|
800
|
+
}
|
|
801
|
+
this.setInclusionState(Inclusion_1.InclusionState.Excluding);
|
|
547
802
|
this.driver.controllerLog.print(`starting exclusion process...`);
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
803
|
+
try {
|
|
804
|
+
// kick off the inclusion process
|
|
805
|
+
await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
|
|
806
|
+
removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Any,
|
|
807
|
+
highPower: true,
|
|
808
|
+
networkWide: true,
|
|
809
|
+
}));
|
|
810
|
+
this.driver.controllerLog.print(`The controller is now ready to remove nodes`);
|
|
811
|
+
this._unprovisionRemovedNode = unprovision;
|
|
812
|
+
this.emit("exclusion started");
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
catch (e) {
|
|
816
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
817
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
818
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
819
|
+
this.driver.controllerLog.print(`Starting the exclusion failed`, "error");
|
|
820
|
+
throw new core_1.ZWaveError("The exclusion could not be started.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed);
|
|
821
|
+
}
|
|
822
|
+
throw e;
|
|
823
|
+
}
|
|
557
824
|
}
|
|
558
|
-
/**
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
this._exclusionActive = false;
|
|
564
|
-
this.driver.controllerLog.print(`stopping exclusion process...`);
|
|
565
|
-
// create the promise we're going to return
|
|
566
|
-
this._stopInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
|
|
567
|
-
// kick off the inclusion process
|
|
825
|
+
/**
|
|
826
|
+
* Is used internally to stop an active exclusion process without waiting for confirmation
|
|
827
|
+
* @internal
|
|
828
|
+
*/
|
|
829
|
+
async stopExclusionNoCallback() {
|
|
568
830
|
await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
|
|
831
|
+
callbackId: 0,
|
|
569
832
|
removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Stop,
|
|
570
833
|
highPower: true,
|
|
571
834
|
networkWide: true,
|
|
572
835
|
}));
|
|
573
|
-
|
|
836
|
+
this.driver.controllerLog.print(`the exclusion process was stopped`);
|
|
837
|
+
this.emit("exclusion stopped");
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Stops an active exclusion process. Resolves to true when the controller leaves exclusion mode,
|
|
841
|
+
* and false if the inclusion was not active.
|
|
842
|
+
*/
|
|
843
|
+
async stopExclusion() {
|
|
844
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Excluding) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
this.driver.controllerLog.print(`stopping exclusion process...`);
|
|
848
|
+
try {
|
|
849
|
+
// kick off the inclusion process
|
|
850
|
+
await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
|
|
851
|
+
removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Stop,
|
|
852
|
+
highPower: true,
|
|
853
|
+
networkWide: true,
|
|
854
|
+
}));
|
|
574
855
|
this.driver.controllerLog.print(`the exclusion process was stopped`);
|
|
575
856
|
this.emit("exclusion stopped");
|
|
576
|
-
|
|
857
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
catch (e) {
|
|
861
|
+
if ((0, core_1.isZWaveError)(e) &&
|
|
862
|
+
e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
|
|
863
|
+
this.driver.controllerLog.print(`Stopping the exclusion failed`, "error");
|
|
864
|
+
throw new core_1.ZWaveError("The exclusion could not be stopped.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed);
|
|
865
|
+
}
|
|
866
|
+
throw e;
|
|
867
|
+
}
|
|
577
868
|
}
|
|
578
869
|
async secureBootstrapS0(node, assumeSupported = false) {
|
|
579
870
|
if (!this.driver.securityManager) {
|
|
@@ -665,7 +956,34 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
665
956
|
version: 1,
|
|
666
957
|
});
|
|
667
958
|
}
|
|
668
|
-
|
|
959
|
+
let userCallbacks;
|
|
960
|
+
const inclusionOptions = this
|
|
961
|
+
._inclusionOptions;
|
|
962
|
+
if ("provisioning" in inclusionOptions) {
|
|
963
|
+
// SmartStart and S2 with QR code are pre-provisioned, so we don't need to ask the user for anything
|
|
964
|
+
userCallbacks = {
|
|
965
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
966
|
+
abort() { },
|
|
967
|
+
grantSecurityClasses: (requested) => {
|
|
968
|
+
return Promise.resolve({
|
|
969
|
+
clientSideAuth: false,
|
|
970
|
+
securityClasses: requested.securityClasses.filter((r) => inclusionOptions.provisioning.securityClasses.includes(r)),
|
|
971
|
+
});
|
|
972
|
+
},
|
|
973
|
+
validateDSKAndEnterPIN: (dsk) => {
|
|
974
|
+
const fullDSK = inclusionOptions.provisioning.dsk;
|
|
975
|
+
const pin = fullDSK.slice(0, 5);
|
|
976
|
+
// Make sure the DSK matches
|
|
977
|
+
if (pin + dsk !== fullDSK)
|
|
978
|
+
return Promise.resolve(false);
|
|
979
|
+
return Promise.resolve(pin);
|
|
980
|
+
},
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
// Use the provided callbacks
|
|
985
|
+
userCallbacks = inclusionOptions.userCallbacks;
|
|
986
|
+
}
|
|
669
987
|
const deleteTempKey = () => {
|
|
670
988
|
var _a, _b;
|
|
671
989
|
// Whatever happens, no further communication needs the temporary key
|
|
@@ -787,16 +1105,27 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
787
1105
|
return abort();
|
|
788
1106
|
}
|
|
789
1107
|
const nodePublicKey = pubKeyResponse.publicKey;
|
|
790
|
-
// This is the starting point of the timer TAI2.
|
|
791
|
-
// the node has less time to send its KEXSet
|
|
1108
|
+
// This is the starting point of the timer TAI2.
|
|
792
1109
|
const timerStartTAI2 = Date.now();
|
|
1110
|
+
// Generate ECDH key pair. We need to immediately send the other node our public key,
|
|
1111
|
+
// so it won't abort bootstrapping
|
|
1112
|
+
const keyPair = await util_1.default.promisify(crypto_1.default.generateKeyPair)("x25519");
|
|
1113
|
+
const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
|
|
1114
|
+
type: "spki",
|
|
1115
|
+
format: "der",
|
|
1116
|
+
}));
|
|
1117
|
+
await api.sendPublicKey(publicKey);
|
|
1118
|
+
// After this, the node will start sending us a KEX SET every 10 seconds.
|
|
1119
|
+
// We won't be able to decode it until the DSK was verified
|
|
793
1120
|
if (grantedKeys.includes(core_1.SecurityClass.S2_AccessControl) ||
|
|
794
1121
|
grantedKeys.includes(core_1.SecurityClass.S2_Authenticated)) {
|
|
795
1122
|
// For authenticated encryption, the DSK (first 16 bytes of the public key) is obfuscated (missing the first 2 bytes)
|
|
796
1123
|
// Request the user to enter the missing part as a 5-digit PIN
|
|
797
1124
|
const dsk = (0, core_1.dskToString)(nodePublicKey.slice(0, 16)).slice(5);
|
|
1125
|
+
// The time the user has to enter the PIN is limited by the timeout TAI2
|
|
1126
|
+
const tai2RemainingMs = shared_2.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
|
|
798
1127
|
const pinResult = await Promise.race([
|
|
799
|
-
(0, async_1.wait)(
|
|
1128
|
+
(0, async_1.wait)(tai2RemainingMs, true).then(() => false),
|
|
800
1129
|
userCallbacks
|
|
801
1130
|
.validateDSKAndEnterPIN(dsk)
|
|
802
1131
|
// ignore errors in application callbacks
|
|
@@ -814,12 +1143,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
814
1143
|
// Fill in the missing two bytes of the public key
|
|
815
1144
|
nodePublicKey.writeUInt16BE(parseInt(pinResult, 10), 0);
|
|
816
1145
|
}
|
|
817
|
-
//
|
|
818
|
-
|
|
819
|
-
const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
|
|
820
|
-
type: "spki",
|
|
821
|
-
format: "der",
|
|
822
|
-
}));
|
|
1146
|
+
// After the user has verified the DSK, we can derive the shared secret
|
|
1147
|
+
// Z-Wave works with the "raw" keys, so this is a tad complicated
|
|
823
1148
|
const sharedSecret = crypto_1.default.diffieHellman({
|
|
824
1149
|
publicKey: crypto_1.default.createPublicKey({
|
|
825
1150
|
key: (0, core_1.encodeX25519KeyDERSPKI)(nodePublicKey),
|
|
@@ -828,15 +1153,14 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
828
1153
|
}),
|
|
829
1154
|
privateKey: keyPair.privateKey,
|
|
830
1155
|
});
|
|
831
|
-
// Derive temporary key from ECDH key pair
|
|
1156
|
+
// Derive temporary key from ECDH key pair - this will allow us to receive the node's KEX SET commands
|
|
832
1157
|
const tempKeys = (0, core_1.deriveTempKeys)((0, core_1.computePRK)(sharedSecret, publicKey, nodePublicKey));
|
|
1158
|
+
this.driver.securityManager2.deleteNonce(node.id);
|
|
833
1159
|
this.driver.securityManager2.tempKeys.set(node.id, {
|
|
834
1160
|
keyCCM: tempKeys.tempKeyCCM,
|
|
835
1161
|
personalizationString: tempKeys.tempPersonalizationString,
|
|
836
1162
|
});
|
|
837
|
-
|
|
838
|
-
// Wait until the encrypted KEXSet from the node was received
|
|
839
|
-
// (if there is even time left)
|
|
1163
|
+
// Now wait for the next KEXSet from the node (if there is even time left)
|
|
840
1164
|
const tai2RemainingMs = shared_2.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
|
|
841
1165
|
if (tai2RemainingMs < 1) {
|
|
842
1166
|
this.driver.controllerLog.logNode(node.id, {
|
|
@@ -953,6 +1277,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
953
1277
|
for (const securityClass of core_1.securityClassOrder) {
|
|
954
1278
|
node.securityClasses.set(securityClass, grantedKeys.includes(securityClass));
|
|
955
1279
|
}
|
|
1280
|
+
// Remember the DSK (first 16 bytes of the public key)
|
|
1281
|
+
node.dsk = nodePublicKey.slice(0, 16);
|
|
956
1282
|
this.driver.controllerLog.logNode(node.id, {
|
|
957
1283
|
message: `Security S2 bootstrapping successful with these security classes:${[
|
|
958
1284
|
...node.securityClasses.entries(),
|
|
@@ -1079,60 +1405,32 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1079
1405
|
}
|
|
1080
1406
|
}
|
|
1081
1407
|
}
|
|
1082
|
-
/**
|
|
1083
|
-
* Stops an active exclusion process. Resolves to true when the controller leaves exclusion mode,
|
|
1084
|
-
* and false if the inclusion was not active.
|
|
1085
|
-
*/
|
|
1086
|
-
async stopExclusion() {
|
|
1087
|
-
// don't stop it twice
|
|
1088
|
-
if (!this._exclusionActive)
|
|
1089
|
-
return false;
|
|
1090
|
-
await this.stopExclusionInternal();
|
|
1091
|
-
return this._stopInclusionPromise;
|
|
1092
|
-
}
|
|
1093
1408
|
/**
|
|
1094
1409
|
* Is called when an AddNode request is received from the controller.
|
|
1095
1410
|
* Handles and controls the inclusion process.
|
|
1096
1411
|
*/
|
|
1097
|
-
async
|
|
1412
|
+
async handleAddNodeStatusReport(msg) {
|
|
1098
1413
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1099
1414
|
this.driver.controllerLog.print(`handling add node request (status = ${AddNodeToNetworkRequest_1.AddNodeStatus[msg.status]})`);
|
|
1100
|
-
if (
|
|
1415
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Including ||
|
|
1101
1416
|
this._inclusionOptions == undefined) {
|
|
1102
1417
|
this.driver.controllerLog.print(` inclusion is NOT active, ignoring it...`);
|
|
1103
1418
|
return true; // Don't invoke any more handlers
|
|
1104
1419
|
}
|
|
1105
1420
|
switch (msg.status) {
|
|
1106
|
-
case AddNodeToNetworkRequest_1.AddNodeStatus.Ready:
|
|
1107
|
-
// this is called when inclusion was started successfully
|
|
1108
|
-
this.driver.controllerLog.print(` the controller is now ready to add nodes`);
|
|
1109
|
-
if (this._beginInclusionPromise != null) {
|
|
1110
|
-
this._beginInclusionPromise.resolve(true);
|
|
1111
|
-
this.emit("inclusion started",
|
|
1112
|
-
// TODO: Remove first parameter in next major version
|
|
1113
|
-
this._inclusionOptions.strategy !==
|
|
1114
|
-
Inclusion_1.InclusionStrategy.Insecure, this._inclusionOptions.strategy);
|
|
1115
|
-
}
|
|
1116
|
-
break;
|
|
1117
1421
|
case AddNodeToNetworkRequest_1.AddNodeStatus.Failed:
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1123
|
-
else {
|
|
1124
|
-
// ...or adding a node failed
|
|
1125
|
-
this.driver.controllerLog.print(` adding the node failed`, "error");
|
|
1126
|
-
this.emit("inclusion failed");
|
|
1127
|
-
}
|
|
1422
|
+
// This code is handled elsewhere for starting the inclusion, so this means
|
|
1423
|
+
// that adding a node failed
|
|
1424
|
+
this.driver.controllerLog.print(`Adding the node failed`, "error");
|
|
1425
|
+
this.emit("inclusion failed");
|
|
1128
1426
|
// in any case, stop the inclusion process so we don't accidentally add another node
|
|
1129
1427
|
try {
|
|
1130
|
-
await this.
|
|
1428
|
+
await this.stopInclusion();
|
|
1131
1429
|
}
|
|
1132
1430
|
catch {
|
|
1133
1431
|
/* ok */
|
|
1134
1432
|
}
|
|
1135
|
-
|
|
1433
|
+
return true; // Don't invoke any more handlers
|
|
1136
1434
|
case AddNodeToNetworkRequest_1.AddNodeStatus.AddingController:
|
|
1137
1435
|
this._includeController = true;
|
|
1138
1436
|
// fall through!
|
|
@@ -1149,103 +1447,113 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1149
1447
|
case AddNodeToNetworkRequest_1.AddNodeStatus.ProtocolDone: {
|
|
1150
1448
|
// this is called after a new node is added
|
|
1151
1449
|
// stop the inclusion process so we don't accidentally add another node
|
|
1450
|
+
let nodeId;
|
|
1152
1451
|
try {
|
|
1153
|
-
await this.
|
|
1452
|
+
nodeId = await this.finishInclusion();
|
|
1154
1453
|
}
|
|
1155
1454
|
catch {
|
|
1156
|
-
|
|
1455
|
+
// ignore the error
|
|
1157
1456
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1457
|
+
// It is recommended to send another STOP command to the controller
|
|
1458
|
+
try {
|
|
1459
|
+
await this.stopInclusionNoCallback();
|
|
1460
|
+
}
|
|
1461
|
+
catch {
|
|
1462
|
+
// ignore the error
|
|
1463
|
+
}
|
|
1464
|
+
if (!nodeId || !this._nodePendingInclusion) {
|
|
1465
|
+
// The inclusion did not succeed
|
|
1466
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1467
|
+
this._nodePendingInclusion = undefined;
|
|
1468
|
+
return true;
|
|
1469
|
+
}
|
|
1470
|
+
else if (nodeId === core_1.NODE_ID_BROADCAST) {
|
|
1167
1471
|
// No idea how this can happen but it dit at least once
|
|
1168
1472
|
this.driver.controllerLog.print(`Cannot add a node with the Node ID ${core_1.NODE_ID_BROADCAST}, aborting...`, "warn");
|
|
1473
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1169
1474
|
this._nodePendingInclusion = undefined;
|
|
1475
|
+
return true;
|
|
1170
1476
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
]
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1477
|
+
// We're technically done with the inclusion but should not include
|
|
1478
|
+
// anything else until the node has been bootstrapped
|
|
1479
|
+
this.setInclusionState(Inclusion_1.InclusionState.Busy);
|
|
1480
|
+
// Inclusion is now completed, bootstrap the node
|
|
1481
|
+
const newNode = this._nodePendingInclusion;
|
|
1482
|
+
const supportedCommandClasses = [
|
|
1483
|
+
...newNode.implementedCommandClasses.entries(),
|
|
1484
|
+
]
|
|
1485
|
+
.filter(([, info]) => info.isSupported)
|
|
1486
|
+
.map(([cc]) => cc);
|
|
1487
|
+
const controlledCommandClasses = [
|
|
1488
|
+
...newNode.implementedCommandClasses.entries(),
|
|
1489
|
+
]
|
|
1490
|
+
.filter(([, info]) => info.isControlled)
|
|
1491
|
+
.map(([cc]) => cc);
|
|
1492
|
+
this.driver.controllerLog.print(`finished adding node ${newNode.id}:
|
|
1184
1493
|
basic device class: ${(_a = newNode.deviceClass) === null || _a === void 0 ? void 0 : _a.basic.label}
|
|
1185
1494
|
generic device class: ${(_b = newNode.deviceClass) === null || _b === void 0 ? void 0 : _b.generic.label}
|
|
1186
1495
|
specific device class: ${(_c = newNode.deviceClass) === null || _c === void 0 ? void 0 : _c.specific.label}
|
|
1187
1496
|
supported CCs: ${supportedCommandClasses
|
|
1188
|
-
|
|
1189
|
-
|
|
1497
|
+
.map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
|
|
1498
|
+
.join("")}
|
|
1190
1499
|
controlled CCs: ${controlledCommandClasses
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1500
|
+
.map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
|
|
1501
|
+
.join("")}`);
|
|
1502
|
+
// remember the node
|
|
1503
|
+
this._nodes.set(newNode.id, newNode);
|
|
1504
|
+
this._nodePendingInclusion = undefined;
|
|
1505
|
+
// We're communicating with the device, so assume it is alive
|
|
1506
|
+
// If it is actually a sleeping device, it will be marked as such later
|
|
1507
|
+
newNode.markAsAlive();
|
|
1508
|
+
// Assign SUC return route to make sure the node knows where to get its routes from
|
|
1509
|
+
newNode.hasSUCReturnRoute = await this.assignSUCReturnRoute(newNode.id);
|
|
1510
|
+
const opts = this._inclusionOptions;
|
|
1511
|
+
// The default inclusion strategy is: Use S2 if possible, only use S0 if necessary, use no encryption otherwise
|
|
1512
|
+
let lowSecurity = false;
|
|
1513
|
+
if (newNode.supportsCC(core_1.CommandClasses["Security 2"]) &&
|
|
1514
|
+
(opts.strategy === Inclusion_1.InclusionStrategy.Default ||
|
|
1515
|
+
opts.strategy === Inclusion_1.InclusionStrategy.Security_S2 ||
|
|
1516
|
+
opts.strategy === Inclusion_1.InclusionStrategy.SmartStart)) {
|
|
1517
|
+
await this.secureBootstrapS2(newNode);
|
|
1518
|
+
const actualSecurityClass = newNode.getHighestSecurityClass();
|
|
1519
|
+
if (actualSecurityClass == undefined ||
|
|
1520
|
+
actualSecurityClass < core_1.SecurityClass.S2_Unauthenticated) {
|
|
1521
|
+
lowSecurity = true;
|
|
1214
1522
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1523
|
+
}
|
|
1524
|
+
else if (newNode.supportsCC(core_1.CommandClasses.Security) &&
|
|
1525
|
+
(opts.strategy === Inclusion_1.InclusionStrategy.Security_S0 ||
|
|
1526
|
+
(opts.strategy === Inclusion_1.InclusionStrategy.Default &&
|
|
1527
|
+
(opts.forceSecurity ||
|
|
1528
|
+
((_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))))) {
|
|
1529
|
+
await this.secureBootstrapS0(newNode);
|
|
1530
|
+
const actualSecurityClass = newNode.getHighestSecurityClass();
|
|
1531
|
+
if (actualSecurityClass == undefined ||
|
|
1532
|
+
actualSecurityClass < core_1.SecurityClass.S0_Legacy) {
|
|
1533
|
+
lowSecurity = true;
|
|
1226
1534
|
}
|
|
1227
|
-
this._includeController = false;
|
|
1228
|
-
// Bootstrap the node's lifelines, so it knows where the controller is
|
|
1229
|
-
await this.bootstrapLifelineAndWakeup(newNode);
|
|
1230
|
-
// We're done adding this node, notify listeners
|
|
1231
|
-
const result = {};
|
|
1232
|
-
if (lowSecurity)
|
|
1233
|
-
result.lowSecurity = true;
|
|
1234
|
-
this.emit("node added", newNode, result);
|
|
1235
1535
|
}
|
|
1236
|
-
|
|
1536
|
+
this._includeController = false;
|
|
1537
|
+
// Bootstrap the node's lifelines, so it knows where the controller is
|
|
1538
|
+
await this.bootstrapLifelineAndWakeup(newNode);
|
|
1539
|
+
// We're done adding this node, notify listeners
|
|
1540
|
+
const result = {};
|
|
1541
|
+
if (lowSecurity)
|
|
1542
|
+
result.lowSecurity = true;
|
|
1543
|
+
this.markNodeOnProvisioningList(newNode);
|
|
1544
|
+
this.emit("node added", newNode, result);
|
|
1545
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1546
|
+
return true; // Don't invoke any more handlers
|
|
1237
1547
|
}
|
|
1238
|
-
default:
|
|
1239
|
-
// not sure what to do with this message
|
|
1240
|
-
return false;
|
|
1241
1548
|
}
|
|
1242
|
-
|
|
1549
|
+
// not sure what to do with this message
|
|
1550
|
+
return false;
|
|
1243
1551
|
}
|
|
1244
1552
|
/**
|
|
1245
1553
|
* Is called when an ReplaceFailed request is received from the controller.
|
|
1246
1554
|
* Handles and controls the replace process.
|
|
1247
1555
|
*/
|
|
1248
|
-
async
|
|
1556
|
+
async handleReplaceNodeStatusReport(msg) {
|
|
1249
1557
|
var _a, _b, _c;
|
|
1250
1558
|
this.driver.controllerLog.print(`handling replace node request (status = ${ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus[msg.replaceStatus]})`);
|
|
1251
1559
|
if (this._inclusionOptions == undefined) {
|
|
@@ -1254,9 +1562,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1254
1562
|
}
|
|
1255
1563
|
switch (msg.replaceStatus) {
|
|
1256
1564
|
case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.NodeOK:
|
|
1565
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1257
1566
|
(_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));
|
|
1258
1567
|
break;
|
|
1259
1568
|
case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.FailedNodeReplaceFailed:
|
|
1569
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1260
1570
|
(_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));
|
|
1261
1571
|
break;
|
|
1262
1572
|
case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.FailedNodeReplace:
|
|
@@ -1267,7 +1577,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1267
1577
|
// TODO: Remove first parameter in next major version
|
|
1268
1578
|
this._inclusionOptions.strategy !==
|
|
1269
1579
|
Inclusion_1.InclusionStrategy.Insecure, this._inclusionOptions.strategy);
|
|
1270
|
-
this.
|
|
1580
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
1271
1581
|
(_c = this._replaceFailedPromise) === null || _c === void 0 ? void 0 : _c.resolve(true);
|
|
1272
1582
|
// stop here, don't emit inclusion failed
|
|
1273
1583
|
return true;
|
|
@@ -1276,7 +1586,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1276
1586
|
this.emit("inclusion stopped");
|
|
1277
1587
|
if (this._nodePendingReplace) {
|
|
1278
1588
|
this.emit("node removed", this._nodePendingReplace, true);
|
|
1589
|
+
this.unmarkNodeOnProvisioningList(this._nodePendingReplace.id);
|
|
1279
1590
|
this._nodes.delete(this._nodePendingReplace.id);
|
|
1591
|
+
// We're technically done with the replacing but should not include
|
|
1592
|
+
// anything else until the node has been bootstrapped
|
|
1593
|
+
this.setInclusionState(Inclusion_1.InclusionState.Busy);
|
|
1280
1594
|
// Create a fresh node instance and forget the old one
|
|
1281
1595
|
const newNode = new Node_1.ZWaveNode(this._nodePendingReplace.id, this.driver, undefined, undefined, undefined,
|
|
1282
1596
|
// Create an empty value DB and specify that it contains no values
|
|
@@ -1316,6 +1630,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1316
1630
|
const result = {};
|
|
1317
1631
|
if (lowSecurity)
|
|
1318
1632
|
result.lowSecurity = true;
|
|
1633
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1634
|
+
this.markNodeOnProvisioningList(newNode);
|
|
1319
1635
|
this.emit("node added", newNode, result);
|
|
1320
1636
|
}
|
|
1321
1637
|
// stop here, don't emit inclusion failed
|
|
@@ -1328,40 +1644,26 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1328
1644
|
* Is called when a RemoveNode request is received from the controller.
|
|
1329
1645
|
* Handles and controls the exclusion process.
|
|
1330
1646
|
*/
|
|
1331
|
-
async
|
|
1647
|
+
async handleRemoveNodeStatusReport(msg) {
|
|
1332
1648
|
this.driver.controllerLog.print(`handling remove node request (status = ${RemoveNodeFromNetworkRequest_1.RemoveNodeStatus[msg.status]})`);
|
|
1333
|
-
if (
|
|
1649
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Excluding) {
|
|
1334
1650
|
this.driver.controllerLog.print(` exclusion is NOT active, ignoring it...`);
|
|
1335
1651
|
return true; // Don't invoke any more handlers
|
|
1336
1652
|
}
|
|
1337
1653
|
switch (msg.status) {
|
|
1338
|
-
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Ready:
|
|
1339
|
-
// this is called when inclusion was started successfully
|
|
1340
|
-
this.driver.controllerLog.print(` the controller is now ready to remove nodes`);
|
|
1341
|
-
if (this._beginInclusionPromise != null) {
|
|
1342
|
-
this._beginInclusionPromise.resolve(true);
|
|
1343
|
-
this.emit("exclusion started");
|
|
1344
|
-
}
|
|
1345
|
-
break;
|
|
1346
1654
|
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Failed:
|
|
1347
|
-
//
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1352
|
-
else {
|
|
1353
|
-
// ...or removing a node failed
|
|
1354
|
-
this.driver.controllerLog.print(` removing the node failed`, "error");
|
|
1355
|
-
this.emit("exclusion failed");
|
|
1356
|
-
}
|
|
1655
|
+
// This code is handled elsewhere for starting the exclusion, so this means
|
|
1656
|
+
// that removing a node failed
|
|
1657
|
+
this.driver.controllerLog.print(`Removing the node failed`, "error");
|
|
1658
|
+
this.emit("exclusion failed");
|
|
1357
1659
|
// in any case, stop the exclusion process so we don't accidentally remove another node
|
|
1358
1660
|
try {
|
|
1359
|
-
await this.
|
|
1661
|
+
await this.stopExclusion();
|
|
1360
1662
|
}
|
|
1361
1663
|
catch {
|
|
1362
1664
|
/* ok */
|
|
1363
1665
|
}
|
|
1364
|
-
|
|
1666
|
+
return true; // Don't invoke any more handlers
|
|
1365
1667
|
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.RemovingSlave:
|
|
1366
1668
|
case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.RemovingController: {
|
|
1367
1669
|
// this is called when a node is removed
|
|
@@ -1372,29 +1674,33 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
|
|
|
1372
1674
|
// this is called when the exclusion was completed
|
|
1373
1675
|
// stop the exclusion process so we don't accidentally remove another node
|
|
1374
1676
|
try {
|
|
1375
|
-
await this.
|
|
1677
|
+
await this.stopExclusionNoCallback();
|
|
1376
1678
|
}
|
|
1377
1679
|
catch {
|
|
1378
1680
|
/* ok */
|
|
1379
1681
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
this.
|
|
1383
|
-
|
|
1384
|
-
this.driver.controllerLog.print(`Node ${this._nodePendingExclusion.id} was removed`);
|
|
1385
|
-
// notify listeners
|
|
1386
|
-
this.emit("node removed", this._nodePendingExclusion, false);
|
|
1387
|
-
// and forget the node
|
|
1388
|
-
this._nodes.delete(this._nodePendingExclusion.id);
|
|
1389
|
-
this._nodePendingExclusion = undefined;
|
|
1682
|
+
if (!this._nodePendingExclusion) {
|
|
1683
|
+
// The exclusion did not succeed
|
|
1684
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1685
|
+
return true;
|
|
1390
1686
|
}
|
|
1391
|
-
|
|
1687
|
+
const nodeId = this._nodePendingExclusion.id;
|
|
1688
|
+
this.driver.controllerLog.print(`Node ${nodeId} was removed`);
|
|
1689
|
+
// Avoid automatic re-inclusion using SmartStart if desired
|
|
1690
|
+
if (this._unprovisionRemovedNode)
|
|
1691
|
+
this.unprovisionSmartStartNode(nodeId);
|
|
1692
|
+
// notify listeners
|
|
1693
|
+
this.emit("node removed", this._nodePendingExclusion, false);
|
|
1694
|
+
// and forget the node
|
|
1695
|
+
this.unmarkNodeOnProvisioningList(nodeId);
|
|
1696
|
+
this._nodes.delete(nodeId);
|
|
1697
|
+
this._nodePendingExclusion = undefined;
|
|
1698
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
1699
|
+
return true; // Don't invoke any more handlers
|
|
1392
1700
|
}
|
|
1393
|
-
default:
|
|
1394
|
-
// not sure what to do with this message
|
|
1395
|
-
return false;
|
|
1396
1701
|
}
|
|
1397
|
-
|
|
1702
|
+
// not sure what to do with this message
|
|
1703
|
+
return false;
|
|
1398
1704
|
}
|
|
1399
1705
|
/**
|
|
1400
1706
|
* Performs a healing process for all alive nodes in the network,
|
|
@@ -2171,15 +2477,23 @@ ${associatedNodes.join(", ")}`,
|
|
|
2171
2477
|
// Emit the removed event so the driver and applications can react
|
|
2172
2478
|
this.emit("node removed", this.nodes.get(nodeId), false);
|
|
2173
2479
|
// and forget the node
|
|
2480
|
+
this.unmarkNodeOnProvisioningList(nodeId);
|
|
2174
2481
|
this._nodes.delete(nodeId);
|
|
2175
2482
|
return;
|
|
2176
2483
|
}
|
|
2177
2484
|
}
|
|
2178
2485
|
}
|
|
2179
2486
|
async replaceFailedNode(nodeId, options) {
|
|
2180
|
-
|
|
2181
|
-
|
|
2487
|
+
if (this._inclusionState === Inclusion_1.InclusionState.Including ||
|
|
2488
|
+
this._inclusionState === Inclusion_1.InclusionState.Excluding ||
|
|
2489
|
+
this._inclusionState === Inclusion_1.InclusionState.Busy) {
|
|
2182
2490
|
return false;
|
|
2491
|
+
}
|
|
2492
|
+
if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
|
|
2493
|
+
// Disable listening mode so we can switch to exclusion mode
|
|
2494
|
+
await this.stopInclusion();
|
|
2495
|
+
}
|
|
2496
|
+
this.setInclusionState(Inclusion_1.InclusionState.Busy);
|
|
2183
2497
|
if (options == undefined) {
|
|
2184
2498
|
options = {
|
|
2185
2499
|
strategy: Inclusion_1.InclusionStrategy.Security_S0,
|
|
@@ -2195,6 +2509,7 @@ ${associatedNodes.join(", ")}`,
|
|
|
2195
2509
|
this.driver.controllerLog.print(`starting replace failed node process...`);
|
|
2196
2510
|
const node = this.nodes.getOrThrow(nodeId);
|
|
2197
2511
|
if (await node.ping()) {
|
|
2512
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
2198
2513
|
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);
|
|
2199
2514
|
}
|
|
2200
2515
|
this._inclusionOptions = options;
|
|
@@ -2220,6 +2535,7 @@ ${associatedNodes.join(", ")}`,
|
|
|
2220
2535
|
ReplaceFailedNodeRequest_1.ReplaceFailedNodeStartFlags.ReplaceFailed)) {
|
|
2221
2536
|
message += `\n· The controller is busy or the node has responded`;
|
|
2222
2537
|
}
|
|
2538
|
+
this.setInclusionState(Inclusion_1.InclusionState.Idle);
|
|
2223
2539
|
throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.ReplaceFailedNode_Failed);
|
|
2224
2540
|
}
|
|
2225
2541
|
else {
|
|
@@ -2334,6 +2650,18 @@ ${associatedNodes.join(", ")}`,
|
|
|
2334
2650
|
*/
|
|
2335
2651
|
serialize() {
|
|
2336
2652
|
return {
|
|
2653
|
+
controller: {
|
|
2654
|
+
supportsSoftReset: this.supportsSoftReset,
|
|
2655
|
+
provisioningList: this.provisioningList.map((e) => {
|
|
2656
|
+
const { dsk, securityClasses, ...rest } = e;
|
|
2657
|
+
return {
|
|
2658
|
+
dsk,
|
|
2659
|
+
securityClasses: securityClasses.map((s) => core_1.SecurityClass[s]),
|
|
2660
|
+
// The user-defined properties are saved as-is
|
|
2661
|
+
...rest,
|
|
2662
|
+
};
|
|
2663
|
+
}),
|
|
2664
|
+
},
|
|
2337
2665
|
nodes: (0, objects_1.composeObject)([...this.nodes.entries()].map(([id, node]) => [id.toString(), node.serialize()])),
|
|
2338
2666
|
};
|
|
2339
2667
|
}
|
|
@@ -2342,6 +2670,43 @@ ${associatedNodes.join(", ")}`,
|
|
|
2342
2670
|
* Deserializes the controller information and all nodes from the cache.
|
|
2343
2671
|
*/
|
|
2344
2672
|
async deserialize(serialized) {
|
|
2673
|
+
if ((0, typeguards_1.isObject)(serialized.controller)) {
|
|
2674
|
+
// Parse whether the controller supports soft reset
|
|
2675
|
+
if (typeof serialized.controller.supportsSoftReset === "boolean") {
|
|
2676
|
+
this.supportsSoftReset =
|
|
2677
|
+
serialized.controller.supportsSoftReset;
|
|
2678
|
+
}
|
|
2679
|
+
// Parse the controller's Smart Start provisioning list
|
|
2680
|
+
if ((0, typeguards_1.isArray)(serialized.controller.provisioningList)) {
|
|
2681
|
+
entries: for (const entry of serialized.controller
|
|
2682
|
+
.provisioningList) {
|
|
2683
|
+
if (!(0, typeguards_1.isObject)(entry))
|
|
2684
|
+
continue;
|
|
2685
|
+
const { dsk, securityClasses: secClasses, ...rest } = entry;
|
|
2686
|
+
if (typeof entry.dsk !== "string")
|
|
2687
|
+
continue;
|
|
2688
|
+
if (!(0, typeguards_1.isArray)(entry.securityClasses))
|
|
2689
|
+
continue;
|
|
2690
|
+
if (!(0, core_1.isValidDSK)(entry.dsk))
|
|
2691
|
+
continue;
|
|
2692
|
+
const securityClasses = [];
|
|
2693
|
+
for (const s of secClasses) {
|
|
2694
|
+
if (typeof s !== "string")
|
|
2695
|
+
continue entries;
|
|
2696
|
+
const secClass = core_1.SecurityClass[s];
|
|
2697
|
+
if (typeof secClass !== "number")
|
|
2698
|
+
continue entries;
|
|
2699
|
+
securityClasses.push(secClass);
|
|
2700
|
+
}
|
|
2701
|
+
this._provisioningList.push({
|
|
2702
|
+
dsk: entry.dsk,
|
|
2703
|
+
securityClasses,
|
|
2704
|
+
// The user-defined properties are not validated further
|
|
2705
|
+
...rest,
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2345
2710
|
if ((0, typeguards_1.isObject)(serialized.nodes)) {
|
|
2346
2711
|
for (const nodeId of Object.keys(serialized.nodes)) {
|
|
2347
2712
|
const serializedNode = serialized.nodes[nodeId];
|
|
@@ -2351,9 +2716,9 @@ ${associatedNodes.join(", ")}`,
|
|
|
2351
2716
|
throw new core_1.ZWaveError("The cache file is invalid", core_1.ZWaveErrorCodes.Driver_InvalidCache);
|
|
2352
2717
|
}
|
|
2353
2718
|
if (this.nodes.has(serializedNode.id)) {
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2719
|
+
const node = this.nodes.getOrThrow(serializedNode.id);
|
|
2720
|
+
await node.deserialize(serializedNode);
|
|
2721
|
+
this.markNodeOnProvisioningList(node);
|
|
2357
2722
|
}
|
|
2358
2723
|
}
|
|
2359
2724
|
}
|