zwave-js 8.5.1 → 8.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/build/Utils.d.ts +2 -2
  2. package/build/Utils.d.ts.map +1 -1
  3. package/build/Utils.js +3 -1
  4. package/build/Utils.js.map +1 -1
  5. package/build/lib/commandclass/API.d.ts +7 -3
  6. package/build/lib/commandclass/API.d.ts.map +1 -1
  7. package/build/lib/commandclass/API.js +9 -1
  8. package/build/lib/commandclass/API.js.map +1 -1
  9. package/build/lib/commandclass/AssociationCC.d.ts.map +1 -1
  10. package/build/lib/commandclass/AssociationCC.js +2 -4
  11. package/build/lib/commandclass/AssociationCC.js.map +1 -1
  12. package/build/lib/commandclass/BinarySwitchCC.d.ts.map +1 -1
  13. package/build/lib/commandclass/BinarySwitchCC.js +7 -3
  14. package/build/lib/commandclass/BinarySwitchCC.js.map +1 -1
  15. package/build/lib/commandclass/ColorSwitchCC.js +2 -2
  16. package/build/lib/commandclass/ColorSwitchCC.js.map +1 -1
  17. package/build/lib/commandclass/ConfigurationCC.d.ts.map +1 -1
  18. package/build/lib/commandclass/ConfigurationCC.js +3 -1
  19. package/build/lib/commandclass/ConfigurationCC.js.map +1 -1
  20. package/build/lib/commandclass/ManufacturerProprietaryCC.d.ts.map +1 -1
  21. package/build/lib/commandclass/ManufacturerProprietaryCC.js +15 -8
  22. package/build/lib/commandclass/ManufacturerProprietaryCC.js.map +1 -1
  23. package/build/lib/commandclass/MultiChannelAssociationCC.d.ts.map +1 -1
  24. package/build/lib/commandclass/MultiChannelAssociationCC.js +2 -4
  25. package/build/lib/commandclass/MultiChannelAssociationCC.js.map +1 -1
  26. package/build/lib/commandclass/MultilevelSwitchCC.d.ts.map +1 -1
  27. package/build/lib/commandclass/MultilevelSwitchCC.js +2 -4
  28. package/build/lib/commandclass/MultilevelSwitchCC.js.map +1 -1
  29. package/build/lib/commandclass/SoundSwitchCC.js +2 -2
  30. package/build/lib/commandclass/SoundSwitchCC.js.map +1 -1
  31. package/build/lib/commandclass/ThermostatFanModeCC.js +2 -2
  32. package/build/lib/commandclass/ThermostatFanModeCC.js.map +1 -1
  33. package/build/lib/commandclass/ThermostatModeCC.js +2 -2
  34. package/build/lib/commandclass/ThermostatModeCC.js.map +1 -1
  35. package/build/lib/commandclass/ThermostatSetpointCC.d.ts.map +1 -1
  36. package/build/lib/commandclass/ThermostatSetpointCC.js +2 -2
  37. package/build/lib/commandclass/ThermostatSetpointCC.js.map +1 -1
  38. package/build/lib/commandclass/UserCodeCC.js +2 -2
  39. package/build/lib/commandclass/UserCodeCC.js.map +1 -1
  40. package/build/lib/commandclass/WakeUpCC.js +2 -2
  41. package/build/lib/commandclass/WakeUpCC.js.map +1 -1
  42. package/build/lib/controller/AddNodeToNetworkRequest.d.ts +36 -8
  43. package/build/lib/controller/AddNodeToNetworkRequest.d.ts.map +1 -1
  44. package/build/lib/controller/AddNodeToNetworkRequest.js +111 -41
  45. package/build/lib/controller/AddNodeToNetworkRequest.js.map +1 -1
  46. package/build/lib/controller/ApplicationUpdateRequest.d.ts +16 -4
  47. package/build/lib/controller/ApplicationUpdateRequest.d.ts.map +1 -1
  48. package/build/lib/controller/ApplicationUpdateRequest.js +56 -15
  49. package/build/lib/controller/ApplicationUpdateRequest.js.map +1 -1
  50. package/build/lib/controller/Controller.d.ts +128 -23
  51. package/build/lib/controller/Controller.d.ts.map +1 -1
  52. package/build/lib/controller/Controller.js +882 -281
  53. package/build/lib/controller/Controller.js.map +1 -1
  54. package/build/lib/controller/Inclusion.d.ts +31 -4
  55. package/build/lib/controller/Inclusion.d.ts.map +1 -1
  56. package/build/lib/controller/Inclusion.js +15 -3
  57. package/build/lib/controller/Inclusion.js.map +1 -1
  58. package/build/lib/controller/RemoveNodeFromNetworkRequest.d.ts +15 -9
  59. package/build/lib/controller/RemoveNodeFromNetworkRequest.d.ts.map +1 -1
  60. package/build/lib/controller/RemoveNodeFromNetworkRequest.js +70 -41
  61. package/build/lib/controller/RemoveNodeFromNetworkRequest.js.map +1 -1
  62. package/build/lib/controller/SoftResetRequest.d.ts +4 -0
  63. package/build/lib/controller/SoftResetRequest.d.ts.map +1 -0
  64. package/build/lib/controller/SoftResetRequest.js +19 -0
  65. package/build/lib/controller/SoftResetRequest.js.map +1 -0
  66. package/build/lib/driver/Driver.d.ts +31 -2
  67. package/build/lib/driver/Driver.d.ts.map +1 -1
  68. package/build/lib/driver/Driver.js +478 -82
  69. package/build/lib/driver/Driver.js.map +1 -1
  70. package/build/lib/driver/SendThreadMachine.d.ts +4 -1
  71. package/build/lib/driver/SendThreadMachine.d.ts.map +1 -1
  72. package/build/lib/driver/SendThreadMachine.js +23 -0
  73. package/build/lib/driver/SendThreadMachine.js.map +1 -1
  74. package/build/lib/driver/Transaction.d.ts +2 -0
  75. package/build/lib/driver/Transaction.d.ts.map +1 -1
  76. package/build/lib/driver/Transaction.js +2 -0
  77. package/build/lib/driver/Transaction.js.map +1 -1
  78. package/build/lib/driver/UpdateConfig.js.map +1 -1
  79. package/build/lib/driver/ZWaveOptions.d.ts +18 -1
  80. package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
  81. package/build/lib/log/Controller.d.ts +12 -2
  82. package/build/lib/log/Controller.d.ts.map +1 -1
  83. package/build/lib/log/Controller.js +45 -0
  84. package/build/lib/log/Controller.js.map +1 -1
  85. package/build/lib/log/Driver.d.ts +5 -2
  86. package/build/lib/log/Driver.d.ts.map +1 -1
  87. package/build/lib/log/Driver.js +3 -0
  88. package/build/lib/log/Driver.js.map +1 -1
  89. package/build/lib/message/Constants.d.ts +3 -2
  90. package/build/lib/message/Constants.d.ts.map +1 -1
  91. package/build/lib/message/Constants.js +3 -2
  92. package/build/lib/message/Constants.js.map +1 -1
  93. package/build/lib/node/Node.d.ts +7 -1
  94. package/build/lib/node/Node.d.ts.map +1 -1
  95. package/build/lib/node/Node.js +35 -9
  96. package/build/lib/node/Node.js.map +1 -1
  97. package/build/lib/serialapi/misc/SerialAPIStartedRequest.d.ts +41 -0
  98. package/build/lib/serialapi/misc/SerialAPIStartedRequest.d.ts.map +1 -0
  99. package/build/lib/serialapi/misc/SerialAPIStartedRequest.js +91 -0
  100. package/build/lib/serialapi/misc/SerialAPIStartedRequest.js.map +1 -0
  101. package/build/lib/serialapi/nvm/NVMOperationsMessages.d.ts +60 -0
  102. package/build/lib/serialapi/nvm/NVMOperationsMessages.d.ts.map +1 -0
  103. package/build/lib/serialapi/nvm/NVMOperationsMessages.js +187 -0
  104. package/build/lib/serialapi/nvm/NVMOperationsMessages.js.map +1 -0
  105. package/package.json +14 -14
@@ -18,6 +18,7 @@ const deferred_promise_1 = require("alcalzone-shared/deferred-promise");
18
18
  const objects_1 = require("alcalzone-shared/objects");
19
19
  const typeguards_1 = require("alcalzone-shared/typeguards");
20
20
  const crypto_1 = __importDefault(require("crypto"));
21
+ const semver_1 = __importDefault(require("semver"));
21
22
  const util_1 = __importDefault(require("util"));
22
23
  const commandclass_1 = require("../commandclass");
23
24
  const ManufacturerSpecificCC_1 = require("../commandclass/ManufacturerSpecificCC");
@@ -35,6 +36,7 @@ const ExtNVMReadLongByteMessages_1 = require("../serialapi/nvm/ExtNVMReadLongByt
35
36
  const ExtNVMWriteLongBufferMessages_1 = require("../serialapi/nvm/ExtNVMWriteLongBufferMessages");
36
37
  const ExtNVMWriteLongByteMessages_1 = require("../serialapi/nvm/ExtNVMWriteLongByteMessages");
37
38
  const GetNVMIdMessages_1 = require("../serialapi/nvm/GetNVMIdMessages");
39
+ const NVMOperationsMessages_1 = require("../serialapi/nvm/NVMOperationsMessages");
38
40
  const AddNodeToNetworkRequest_1 = require("./AddNodeToNetworkRequest");
39
41
  const AssignReturnRouteMessages_1 = require("./AssignReturnRouteMessages");
40
42
  const AssignSUCReturnRouteMessages_1 = require("./AssignSUCReturnRouteMessages");
@@ -64,9 +66,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
64
66
  super();
65
67
  this.driver = driver;
66
68
  this._healNetworkActive = false;
67
- this._exclusionActive = false;
68
- this._inclusionActive = false;
69
+ this._provisioningList = [];
70
+ this._inclusionState = Inclusion_1.InclusionState.Idle;
71
+ this._smartStartEnabled = false;
69
72
  this._includeController = false;
73
+ this._unprovisionRemovedNode = false;
70
74
  this._healNetworkProgress = new Map();
71
75
  this._nodes = new Map();
72
76
  this._nodes.getOrThrow = function (nodeId) {
@@ -77,9 +81,9 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
77
81
  return node;
78
82
  }.bind(this._nodes);
79
83
  // register message handlers
80
- driver.registerRequestHandler(Constants_1.FunctionType.AddNodeToNetwork, this.handleAddNodeRequest.bind(this));
81
- driver.registerRequestHandler(Constants_1.FunctionType.RemoveNodeFromNetwork, this.handleRemoveNodeRequest.bind(this));
82
- driver.registerRequestHandler(Constants_1.FunctionType.ReplaceFailedNode, this.handleReplaceNodeRequest.bind(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));
83
87
  }
84
88
  get libraryVersion() {
85
89
  return this._libraryVersion;
@@ -116,6 +120,34 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
116
120
  get serialApiVersion() {
117
121
  return this._serialApiVersion;
118
122
  }
123
+ /** Checks if the Serial API version is greater than the given one */
124
+ serialApiGt(version) {
125
+ if (this._serialApiVersion === undefined) {
126
+ return undefined;
127
+ }
128
+ return semver_1.default.gt((0, shared_1.padVersion)(this._serialApiVersion), (0, shared_1.padVersion)(version));
129
+ }
130
+ /** Checks if the Serial API version is greater than or equal to the given one */
131
+ serialApiGte(version) {
132
+ if (this._serialApiVersion === undefined) {
133
+ return undefined;
134
+ }
135
+ return semver_1.default.gte((0, shared_1.padVersion)(this._serialApiVersion), (0, shared_1.padVersion)(version));
136
+ }
137
+ /** Checks if the Serial API version is lower than the given one */
138
+ serialApiLt(version) {
139
+ if (this._serialApiVersion === undefined) {
140
+ return undefined;
141
+ }
142
+ return semver_1.default.lt((0, shared_1.padVersion)(this._serialApiVersion), (0, shared_1.padVersion)(version));
143
+ }
144
+ /** Checks if the Serial API version is lower than or equal to the given one */
145
+ serialApiLte(version) {
146
+ if (this._serialApiVersion === undefined) {
147
+ return undefined;
148
+ }
149
+ return semver_1.default.lte((0, shared_1.padVersion)(this._serialApiVersion), (0, shared_1.padVersion)(version));
150
+ }
119
151
  get manufacturerId() {
120
152
  return this._manufacturerId;
121
153
  }
@@ -151,10 +183,32 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
151
183
  get supportsTimers() {
152
184
  return this._supportsTimers;
153
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
+ }
154
194
  /** A dictionary of the nodes connected to this controller */
155
195
  get nodes() {
156
196
  return this._nodes;
157
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
+ }
158
212
  /** Returns whether the network or a node is currently being healed. */
159
213
  get isHealNetworkActive() {
160
214
  return this._healNetworkActive;
@@ -168,14 +222,130 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
168
222
  const nodes = nodeIDs.map((id) => this._nodes.getOrThrow(id));
169
223
  return new VirtualNode_1.VirtualNode(undefined, this.driver, nodes);
170
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
+ }
311
+ /**
312
+ * @internal
313
+ * Queries the controller IDs and its Serial API capabilities
314
+ */
315
+ async identify() {
316
+ // get the home and node id of the controller
317
+ this.driver.controllerLog.print(`querying controller IDs...`);
318
+ const ids = await this.driver.sendMessage(new GetControllerIdMessages_1.GetControllerIdRequest(this.driver), { supportCheck: false });
319
+ this._homeId = ids.homeId;
320
+ this._ownNodeId = ids.ownNodeId;
321
+ this.driver.controllerLog.print(`received controller IDs:
322
+ home ID: ${(0, shared_1.num2hex)(this._homeId)}
323
+ own node ID: ${this._ownNodeId}`);
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("")}`);
342
+ }
171
343
  /**
172
344
  * @internal
173
345
  * Interviews the controller for the necessary information.
174
- * @param initValueDBs Asynchronous callback for the driver to initialize the Value DBs before nodes are created
175
346
  * @param restoreFromCache Asynchronous callback for the driver to restore the network from cache after nodes are created
176
347
  */
177
- async interview(initValueDBs, restoreFromCache) {
178
- this.driver.controllerLog.print("beginning interview...");
348
+ async interview(restoreFromCache) {
179
349
  // get basic controller version info
180
350
  this.driver.controllerLog.print(`querying version info...`);
181
351
  const version = await this.driver.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this.driver), {
@@ -186,14 +356,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
186
356
  this.driver.controllerLog.print(`received version info:
187
357
  controller type: ${ZWaveLibraryTypes_1.ZWaveLibraryTypes[this._type]}
188
358
  library version: ${this._libraryVersion}`);
189
- // get the home and node id of the controller
190
- this.driver.controllerLog.print(`querying controller IDs...`);
191
- const ids = await this.driver.sendMessage(new GetControllerIdMessages_1.GetControllerIdRequest(this.driver), { supportCheck: false });
192
- this._homeId = ids.homeId;
193
- this._ownNodeId = ids.ownNodeId;
194
- this.driver.controllerLog.print(`received controller IDs:
195
- home ID: ${(0, shared_1.num2hex)(this._homeId)}
196
- own node ID: ${this._ownNodeId}`);
197
359
  // find out what the controller can do
198
360
  this.driver.controllerLog.print(`querying controller capabilities...`);
199
361
  const ctrlCaps = await this.driver.sendMessage(new GetControllerCapabilitiesMessages_1.GetControllerCapabilitiesRequest(this.driver), {
@@ -211,25 +373,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
211
373
  is SIS present: ${this._isSISPresent}
212
374
  was real primary: ${this._wasRealPrimary}
213
375
  is a SUC: ${this._isStaticUpdateController}`);
214
- // find out which part of the API is supported
215
- this.driver.controllerLog.print(`querying API capabilities...`);
216
- const apiCaps = await this.driver.sendMessage(new GetSerialApiCapabilitiesMessages_1.GetSerialApiCapabilitiesRequest(this.driver), {
217
- supportCheck: false,
218
- });
219
- this._serialApiVersion = apiCaps.serialApiVersion;
220
- this._manufacturerId = apiCaps.manufacturerId;
221
- this._productType = apiCaps.productType;
222
- this._productId = apiCaps.productId;
223
- this._supportedFunctionTypes = apiCaps.supportedFunctionTypes;
224
- this.driver.controllerLog.print(`received API capabilities:
225
- serial API version: ${this._serialApiVersion}
226
- manufacturer ID: ${(0, shared_1.num2hex)(this._manufacturerId)}
227
- product type: ${(0, shared_1.num2hex)(this._productType)}
228
- product ID: ${(0, shared_1.num2hex)(this._productId)}
229
- supported functions: ${this._supportedFunctionTypes
230
- .map((fn) => `\n · ${Constants_1.FunctionType[fn]} (${(0, shared_1.num2hex)(fn)})`)
231
- .join("")}`);
232
- // now we can check if a function is supported
233
376
  // Figure out which sub commands of SerialAPISetup are supported
234
377
  if (this.isFunctionSupported(Constants_1.FunctionType.SerialAPISetup)) {
235
378
  this.driver.controllerLog.print(`querying serial API setup capabilities...`);
@@ -283,8 +426,6 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
283
426
  this.isFunctionSupported(Constants_1.FunctionType.FUNC_ID_ZW_GET_VIRTUAL_NODES)) {
284
427
  // TODO: send FUNC_ID_ZW_GET_VIRTUAL_NODES message
285
428
  }
286
- // Give the Driver time to set up the value DBs
287
- await initValueDBs();
288
429
  // Request information about all nodes with the GetInitData message
289
430
  this.driver.controllerLog.print(`querying node information...`);
290
431
  const initData = await this.driver.sendMessage(new GetSerialApiInitDataMessages_1.GetSerialApiInitDataRequest(this.driver));
@@ -315,7 +456,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
315
456
  // Now try to deserialize all nodes from the cache
316
457
  await restoreFromCache();
317
458
  // Set manufacturer information for the controller node
318
- const controllerValueDB = this._nodes.get(this._ownNodeId).valueDB;
459
+ const controllerValueDB = this.valueDB;
319
460
  controllerValueDB.setMetadata((0, ManufacturerSpecificCC_1.getManufacturerIdValueId)(), (0, ManufacturerSpecificCC_1.getManufacturerIdValueMetadata)());
320
461
  controllerValueDB.setMetadata((0, ManufacturerSpecificCC_1.getProductTypeValueId)(), (0, ManufacturerSpecificCC_1.getProductTypeValueMetadata)());
321
462
  controllerValueDB.setMetadata((0, ManufacturerSpecificCC_1.getProductIdValueId)(), (0, ManufacturerSpecificCC_1.getProductIdValueMetadata)());
@@ -417,9 +558,24 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
417
558
  }
418
559
  });
419
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
+ }
420
575
  async beginInclusion(options) {
421
- // don't start it twice
422
- if (this._inclusionActive || this._exclusionActive) {
576
+ if (this._inclusionState === Inclusion_1.InclusionState.Including ||
577
+ this._inclusionState === Inclusion_1.InclusionState.Excluding ||
578
+ this._inclusionState === Inclusion_1.InclusionState.Busy) {
423
579
  return false;
424
580
  }
425
581
  if (options == undefined) {
@@ -435,97 +591,279 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
435
591
  };
436
592
  }
437
593
  // Protect against invalid inclusion options
438
- 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) {
439
597
  throw new core_1.ZWaveError(`Invalid inclusion strategy: ${options.strategy}`, core_1.ZWaveErrorCodes.Argument_Invalid);
440
598
  }
441
- if (options.strategy === Inclusion_1.InclusionStrategy.SmartStart) {
442
- throw new core_1.ZWaveError(`SmartStart is not supported yet!`, core_1.ZWaveErrorCodes.Driver_NotSupported);
599
+ if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
600
+ // Disable listening mode so we can switch to inclusion mode
601
+ await this.stopInclusion();
443
602
  }
444
- this._inclusionActive = true;
603
+ this.setInclusionState(Inclusion_1.InclusionState.Including);
445
604
  this._inclusionOptions = options;
446
- this.driver.controllerLog.print(`Starting inclusion process with strategy ${(0, shared_1.getEnumMemberName)(Inclusion_1.InclusionStrategy, options.strategy)}...`);
447
- // create the promise we're going to return
448
- this._beginInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
449
- // kick off the inclusion process
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() {
450
669
  await this.driver.sendMessage(new AddNodeToNetworkRequest_1.AddNodeToNetworkRequest(this.driver, {
451
- addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Any,
670
+ callbackId: 0,
671
+ addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
452
672
  highPower: true,
453
673
  networkWide: true,
454
674
  }));
455
- return this._beginInclusionPromise;
675
+ this.driver.controllerLog.print(`The inclusion process was stopped`);
676
+ this.emit("inclusion stopped");
456
677
  }
457
- /** Is used internally to stop an active inclusion process without creating deadlocks */
458
- async stopInclusionInternal() {
459
- // don't stop it twice
460
- if (!this._inclusionActive)
461
- return;
462
- this._inclusionActive = false;
463
- this.driver.controllerLog.print(`stopping inclusion process...`);
464
- // create the promise we're going to return
465
- this._stopInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
466
- // kick off the inclusion process
467
- 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, {
468
685
  addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
469
686
  highPower: true,
470
687
  networkWide: true,
471
688
  }));
472
- // Don't await the promise or we create a deadlock
473
- void this._stopInclusionPromise.then(() => {
474
- this.driver.controllerLog.print(`the inclusion process was stopped`);
475
- this.emit("inclusion stopped");
476
- });
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);
477
694
  }
478
695
  /**
479
696
  * Stops an active inclusion process. Resolves to true when the controller leaves inclusion mode,
480
697
  * and false if the inclusion was not active.
481
698
  */
482
699
  async stopInclusion() {
483
- // don't stop it twice
484
- if (!this._inclusionActive)
700
+ if (this._inclusionState !== Inclusion_1.InclusionState.Including) {
485
701
  return false;
486
- await this.stopInclusionInternal();
487
- return this._stopInclusionPromise;
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
+ this._smartStartEnabled = true;
733
+ if (this._inclusionState === Inclusion_1.InclusionState.Idle) {
734
+ this.setInclusionState(Inclusion_1.InclusionState.SmartStart);
735
+ this.driver.controllerLog.print(`Enabling Smart Start listening mode...`);
736
+ try {
737
+ await this.driver.sendMessage(new AddNodeToNetworkRequest_1.EnableSmartStartListenRequest(this.driver, {}));
738
+ this.driver.controllerLog.print(`Smart Start listening mode enabled`);
739
+ return true;
740
+ }
741
+ catch (e) {
742
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
743
+ this.driver.controllerLog.print(`Smart Start listening mode could not be enabled: ${(0, shared_1.getErrorMessage)(e)}`, "error");
744
+ throw e;
745
+ }
746
+ }
747
+ else if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
748
+ return true;
749
+ }
750
+ else {
751
+ this.driver.controllerLog.print(`Smart Start listening mode scheduled for later activation...`);
752
+ return false;
753
+ }
754
+ }
755
+ /**
756
+ * Disables the listening mode for Smart Start inclusion.
757
+ *
758
+ * Resolves to `true` when the listening mode is stopped, and `false` if was not active.
759
+ */
760
+ async disableSmartStart() {
761
+ this._smartStartEnabled = false;
762
+ if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
763
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
764
+ this.driver.controllerLog.print(`disabling Smart Start listening mode...`);
765
+ try {
766
+ await this.stopInclusion();
767
+ this.driver.controllerLog.print(`Smart Start listening mode disabled`);
768
+ return true;
769
+ }
770
+ catch (e) {
771
+ this.setInclusionState(Inclusion_1.InclusionState.SmartStart);
772
+ this.driver.controllerLog.print(`Smart Start listening mode could not be disabled: ${(0, shared_1.getErrorMessage)(e)}`, "error");
773
+ throw e;
774
+ }
775
+ }
776
+ else if (this._inclusionState === Inclusion_1.InclusionState.Idle) {
777
+ return true;
778
+ }
779
+ else {
780
+ this.driver.controllerLog.print(`Smart Start listening mode disabled`);
781
+ return true;
782
+ }
488
783
  }
489
784
  /**
490
785
  * Starts the exclusion process of new nodes.
491
- * Resolves to true when the process was started,
492
- * and false if an inclusion or exclusion process was already active
786
+ * Resolves to true when the process was started, and false if an inclusion or exclusion process was already active.
787
+ *
788
+ * @param unprovision Whether the removed node should also be removed from the Smart Start provisioning list.
493
789
  */
494
- async beginExclusion() {
495
- // don't start it twice
496
- if (this._inclusionActive || this._exclusionActive)
790
+ async beginExclusion(unprovision = false) {
791
+ if (this._inclusionState === Inclusion_1.InclusionState.Including ||
792
+ this._inclusionState === Inclusion_1.InclusionState.Excluding ||
793
+ this._inclusionState === Inclusion_1.InclusionState.Busy) {
497
794
  return false;
498
- this._exclusionActive = true;
795
+ }
796
+ if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
797
+ // Disable listening mode so we can switch to exclusion mode
798
+ await this.stopInclusion();
799
+ }
800
+ this.setInclusionState(Inclusion_1.InclusionState.Excluding);
499
801
  this.driver.controllerLog.print(`starting exclusion process...`);
500
- // create the promise we're going to return
501
- this._beginInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
502
- // kick off the inclusion process
503
- await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
504
- removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Any,
505
- highPower: true,
506
- networkWide: true,
507
- }));
508
- return this._beginInclusionPromise;
802
+ try {
803
+ // kick off the inclusion process
804
+ await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
805
+ removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Any,
806
+ highPower: true,
807
+ networkWide: true,
808
+ }));
809
+ this.driver.controllerLog.print(`The controller is now ready to remove nodes`);
810
+ this._unprovisionRemovedNode = unprovision;
811
+ this.emit("exclusion started");
812
+ return true;
813
+ }
814
+ catch (e) {
815
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
816
+ if ((0, core_1.isZWaveError)(e) &&
817
+ e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
818
+ this.driver.controllerLog.print(`Starting the exclusion failed`, "error");
819
+ throw new core_1.ZWaveError("The exclusion could not be started.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed);
820
+ }
821
+ throw e;
822
+ }
509
823
  }
510
- /** Is used internally to stop an active inclusion process without creating deadlocks */
511
- async stopExclusionInternal() {
512
- // don't stop it twice
513
- if (!this._exclusionActive)
514
- return;
515
- this._exclusionActive = false;
516
- this.driver.controllerLog.print(`stopping exclusion process...`);
517
- // create the promise we're going to return
518
- this._stopInclusionPromise = (0, deferred_promise_1.createDeferredPromise)();
519
- // kick off the inclusion process
824
+ /**
825
+ * Is used internally to stop an active exclusion process without waiting for confirmation
826
+ * @internal
827
+ */
828
+ async stopExclusionNoCallback() {
520
829
  await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
830
+ callbackId: 0,
521
831
  removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Stop,
522
832
  highPower: true,
523
833
  networkWide: true,
524
834
  }));
525
- void this._stopInclusionPromise.then(() => {
835
+ this.driver.controllerLog.print(`the exclusion process was stopped`);
836
+ this.emit("exclusion stopped");
837
+ }
838
+ /**
839
+ * Stops an active exclusion process. Resolves to true when the controller leaves exclusion mode,
840
+ * and false if the inclusion was not active.
841
+ */
842
+ async stopExclusion() {
843
+ if (this._inclusionState !== Inclusion_1.InclusionState.Excluding) {
844
+ return false;
845
+ }
846
+ this.driver.controllerLog.print(`stopping exclusion process...`);
847
+ try {
848
+ // kick off the inclusion process
849
+ await this.driver.sendMessage(new RemoveNodeFromNetworkRequest_1.RemoveNodeFromNetworkRequest(this.driver, {
850
+ removeNodeType: RemoveNodeFromNetworkRequest_1.RemoveNodeType.Stop,
851
+ highPower: true,
852
+ networkWide: true,
853
+ }));
526
854
  this.driver.controllerLog.print(`the exclusion process was stopped`);
527
855
  this.emit("exclusion stopped");
528
- });
856
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
857
+ return true;
858
+ }
859
+ catch (e) {
860
+ if ((0, core_1.isZWaveError)(e) &&
861
+ e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK) {
862
+ this.driver.controllerLog.print(`Stopping the exclusion failed`, "error");
863
+ throw new core_1.ZWaveError("The exclusion could not be stopped.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed);
864
+ }
865
+ throw e;
866
+ }
529
867
  }
530
868
  async secureBootstrapS0(node, assumeSupported = false) {
531
869
  if (!this.driver.securityManager) {
@@ -617,7 +955,34 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
617
955
  version: 1,
618
956
  });
619
957
  }
620
- const { userCallbacks } = this._inclusionOptions;
958
+ let userCallbacks;
959
+ const inclusionOptions = this
960
+ ._inclusionOptions;
961
+ if ("provisioning" in inclusionOptions) {
962
+ // SmartStart and S2 with QR code are pre-provisioned, so we don't need to ask the user for anything
963
+ userCallbacks = {
964
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
965
+ abort() { },
966
+ grantSecurityClasses: (requested) => {
967
+ return Promise.resolve({
968
+ clientSideAuth: false,
969
+ securityClasses: requested.securityClasses.filter((r) => inclusionOptions.provisioning.securityClasses.includes(r)),
970
+ });
971
+ },
972
+ validateDSKAndEnterPIN: (dsk) => {
973
+ const fullDSK = inclusionOptions.provisioning.dsk;
974
+ const pin = fullDSK.slice(0, 5);
975
+ // Make sure the DSK matches
976
+ if (pin + dsk !== fullDSK)
977
+ return Promise.resolve(false);
978
+ return Promise.resolve(pin);
979
+ },
980
+ };
981
+ }
982
+ else {
983
+ // Use the provided callbacks
984
+ userCallbacks = inclusionOptions.userCallbacks;
985
+ }
621
986
  const deleteTempKey = () => {
622
987
  var _a, _b;
623
988
  // Whatever happens, no further communication needs the temporary key
@@ -739,16 +1104,27 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
739
1104
  return abort();
740
1105
  }
741
1106
  const nodePublicKey = pubKeyResponse.publicKey;
742
- // This is the starting point of the timer TAI2. Depending on how long the user takes to confirm,
743
- // the node has less time to send its KEXSet
1107
+ // This is the starting point of the timer TAI2.
744
1108
  const timerStartTAI2 = Date.now();
1109
+ // Generate ECDH key pair. We need to immediately send the other node our public key,
1110
+ // so it won't abort bootstrapping
1111
+ const keyPair = await util_1.default.promisify(crypto_1.default.generateKeyPair)("x25519");
1112
+ const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
1113
+ type: "spki",
1114
+ format: "der",
1115
+ }));
1116
+ await api.sendPublicKey(publicKey);
1117
+ // After this, the node will start sending us a KEX SET every 10 seconds.
1118
+ // We won't be able to decode it until the DSK was verified
745
1119
  if (grantedKeys.includes(core_1.SecurityClass.S2_AccessControl) ||
746
1120
  grantedKeys.includes(core_1.SecurityClass.S2_Authenticated)) {
747
1121
  // For authenticated encryption, the DSK (first 16 bytes of the public key) is obfuscated (missing the first 2 bytes)
748
1122
  // Request the user to enter the missing part as a 5-digit PIN
749
1123
  const dsk = (0, core_1.dskToString)(nodePublicKey.slice(0, 16)).slice(5);
1124
+ // The time the user has to enter the PIN is limited by the timeout TAI2
1125
+ const tai2RemainingMs = shared_2.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
750
1126
  const pinResult = await Promise.race([
751
- (0, async_1.wait)(shared_2.inclusionTimeouts.TAI2, true).then(() => false),
1127
+ (0, async_1.wait)(tai2RemainingMs, true).then(() => false),
752
1128
  userCallbacks
753
1129
  .validateDSKAndEnterPIN(dsk)
754
1130
  // ignore errors in application callbacks
@@ -766,12 +1142,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
766
1142
  // Fill in the missing two bytes of the public key
767
1143
  nodePublicKey.writeUInt16BE(parseInt(pinResult, 10), 0);
768
1144
  }
769
- // Generate ECDH key pair. Z-Wave works with the "raw" keys, so this is a tad complicated
770
- const keyPair = await util_1.default.promisify(crypto_1.default.generateKeyPair)("x25519");
771
- const publicKey = (0, core_1.decodeX25519KeyDER)(keyPair.publicKey.export({
772
- type: "spki",
773
- format: "der",
774
- }));
1145
+ // After the user has verified the DSK, we can derive the shared secret
1146
+ // Z-Wave works with the "raw" keys, so this is a tad complicated
775
1147
  const sharedSecret = crypto_1.default.diffieHellman({
776
1148
  publicKey: crypto_1.default.createPublicKey({
777
1149
  key: (0, core_1.encodeX25519KeyDERSPKI)(nodePublicKey),
@@ -780,15 +1152,14 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
780
1152
  }),
781
1153
  privateKey: keyPair.privateKey,
782
1154
  });
783
- // Derive temporary key from ECDH key pair
1155
+ // Derive temporary key from ECDH key pair - this will allow us to receive the node's KEX SET commands
784
1156
  const tempKeys = (0, core_1.deriveTempKeys)((0, core_1.computePRK)(sharedSecret, publicKey, nodePublicKey));
1157
+ this.driver.securityManager2.deleteNonce(node.id);
785
1158
  this.driver.securityManager2.tempKeys.set(node.id, {
786
1159
  keyCCM: tempKeys.tempKeyCCM,
787
1160
  personalizationString: tempKeys.tempPersonalizationString,
788
1161
  });
789
- await api.sendPublicKey(publicKey);
790
- // Wait until the encrypted KEXSet from the node was received
791
- // (if there is even time left)
1162
+ // Now wait for the next KEXSet from the node (if there is even time left)
792
1163
  const tai2RemainingMs = shared_2.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
793
1164
  if (tai2RemainingMs < 1) {
794
1165
  this.driver.controllerLog.logNode(node.id, {
@@ -905,6 +1276,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
905
1276
  for (const securityClass of core_1.securityClassOrder) {
906
1277
  node.securityClasses.set(securityClass, grantedKeys.includes(securityClass));
907
1278
  }
1279
+ // Remember the DSK (first 16 bytes of the public key)
1280
+ node.dsk = nodePublicKey.slice(0, 16);
908
1281
  this.driver.controllerLog.logNode(node.id, {
909
1282
  message: `Security S2 bootstrapping successful with these security classes:${[
910
1283
  ...node.securityClasses.entries(),
@@ -1031,60 +1404,32 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1031
1404
  }
1032
1405
  }
1033
1406
  }
1034
- /**
1035
- * Stops an active exclusion process. Resolves to true when the controller leaves exclusion mode,
1036
- * and false if the inclusion was not active.
1037
- */
1038
- async stopExclusion() {
1039
- // don't stop it twice
1040
- if (!this._exclusionActive)
1041
- return false;
1042
- await this.stopExclusionInternal();
1043
- return this._stopInclusionPromise;
1044
- }
1045
1407
  /**
1046
1408
  * Is called when an AddNode request is received from the controller.
1047
1409
  * Handles and controls the inclusion process.
1048
1410
  */
1049
- async handleAddNodeRequest(msg) {
1411
+ async handleAddNodeStatusReport(msg) {
1050
1412
  var _a, _b, _c, _d, _e, _f, _g;
1051
1413
  this.driver.controllerLog.print(`handling add node request (status = ${AddNodeToNetworkRequest_1.AddNodeStatus[msg.status]})`);
1052
- if ((!this._inclusionActive && msg.status !== AddNodeToNetworkRequest_1.AddNodeStatus.Done) ||
1414
+ if (this._inclusionState !== Inclusion_1.InclusionState.Including ||
1053
1415
  this._inclusionOptions == undefined) {
1054
1416
  this.driver.controllerLog.print(` inclusion is NOT active, ignoring it...`);
1055
1417
  return true; // Don't invoke any more handlers
1056
1418
  }
1057
1419
  switch (msg.status) {
1058
- case AddNodeToNetworkRequest_1.AddNodeStatus.Ready:
1059
- // this is called when inclusion was started successfully
1060
- this.driver.controllerLog.print(` the controller is now ready to add nodes`);
1061
- if (this._beginInclusionPromise != null) {
1062
- this._beginInclusionPromise.resolve(true);
1063
- this.emit("inclusion started",
1064
- // TODO: Remove first parameter in next major version
1065
- this._inclusionOptions.strategy !==
1066
- Inclusion_1.InclusionStrategy.Insecure, this._inclusionOptions.strategy);
1067
- }
1068
- break;
1069
1420
  case AddNodeToNetworkRequest_1.AddNodeStatus.Failed:
1070
- // this is called when inclusion could not be started...
1071
- if (this._beginInclusionPromise != null) {
1072
- this.driver.controllerLog.print(` starting the inclusion failed`, "error");
1073
- this._beginInclusionPromise.reject(new core_1.ZWaveError("The inclusion could not be started.", core_1.ZWaveErrorCodes.Controller_InclusionFailed));
1074
- }
1075
- else {
1076
- // ...or adding a node failed
1077
- this.driver.controllerLog.print(` adding the node failed`, "error");
1078
- this.emit("inclusion failed");
1079
- }
1421
+ // This code is handled elsewhere for starting the inclusion, so this means
1422
+ // that adding a node failed
1423
+ this.driver.controllerLog.print(`Adding the node failed`, "error");
1424
+ this.emit("inclusion failed");
1080
1425
  // in any case, stop the inclusion process so we don't accidentally add another node
1081
1426
  try {
1082
- await this.stopInclusionInternal();
1427
+ await this.stopInclusion();
1083
1428
  }
1084
1429
  catch {
1085
1430
  /* ok */
1086
1431
  }
1087
- break;
1432
+ return true; // Don't invoke any more handlers
1088
1433
  case AddNodeToNetworkRequest_1.AddNodeStatus.AddingController:
1089
1434
  this._includeController = true;
1090
1435
  // fall through!
@@ -1101,103 +1446,113 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1101
1446
  case AddNodeToNetworkRequest_1.AddNodeStatus.ProtocolDone: {
1102
1447
  // this is called after a new node is added
1103
1448
  // stop the inclusion process so we don't accidentally add another node
1449
+ let nodeId;
1104
1450
  try {
1105
- await this.stopInclusionInternal();
1451
+ nodeId = await this.finishInclusion();
1106
1452
  }
1107
1453
  catch {
1108
- /* ok */
1454
+ // ignore the error
1109
1455
  }
1110
- break;
1111
- }
1112
- case AddNodeToNetworkRequest_1.AddNodeStatus.Done: {
1113
- // this is called when the inclusion was completed
1114
- this.driver.controllerLog.print(`done called for ${msg.statusContext.nodeId}`);
1115
- // stopping the inclusion was acknowledged by the controller
1116
- if (this._stopInclusionPromise != null)
1117
- this._stopInclusionPromise.resolve(true);
1118
- if (msg.statusContext.nodeId === core_1.NODE_ID_BROADCAST) {
1456
+ // It is recommended to send another STOP command to the controller
1457
+ try {
1458
+ await this.stopInclusionNoCallback();
1459
+ }
1460
+ catch {
1461
+ // ignore the error
1462
+ }
1463
+ if (!nodeId || !this._nodePendingInclusion) {
1464
+ // The inclusion did not succeed
1465
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1466
+ this._nodePendingInclusion = undefined;
1467
+ return true;
1468
+ }
1469
+ else if (nodeId === core_1.NODE_ID_BROADCAST) {
1119
1470
  // No idea how this can happen but it dit at least once
1120
1471
  this.driver.controllerLog.print(`Cannot add a node with the Node ID ${core_1.NODE_ID_BROADCAST}, aborting...`, "warn");
1472
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1121
1473
  this._nodePendingInclusion = undefined;
1474
+ return true;
1122
1475
  }
1123
- else if (this._nodePendingInclusion != null) {
1124
- const newNode = this._nodePendingInclusion;
1125
- const supportedCommandClasses = [
1126
- ...newNode.implementedCommandClasses.entries(),
1127
- ]
1128
- .filter(([, info]) => info.isSupported)
1129
- .map(([cc]) => cc);
1130
- const controlledCommandClasses = [
1131
- ...newNode.implementedCommandClasses.entries(),
1132
- ]
1133
- .filter(([, info]) => info.isControlled)
1134
- .map(([cc]) => cc);
1135
- this.driver.controllerLog.print(`finished adding node ${newNode.id}:
1476
+ // We're technically done with the inclusion but should not include
1477
+ // anything else until the node has been bootstrapped
1478
+ this.setInclusionState(Inclusion_1.InclusionState.Busy);
1479
+ // Inclusion is now completed, bootstrap the node
1480
+ const newNode = this._nodePendingInclusion;
1481
+ const supportedCommandClasses = [
1482
+ ...newNode.implementedCommandClasses.entries(),
1483
+ ]
1484
+ .filter(([, info]) => info.isSupported)
1485
+ .map(([cc]) => cc);
1486
+ const controlledCommandClasses = [
1487
+ ...newNode.implementedCommandClasses.entries(),
1488
+ ]
1489
+ .filter(([, info]) => info.isControlled)
1490
+ .map(([cc]) => cc);
1491
+ this.driver.controllerLog.print(`finished adding node ${newNode.id}:
1136
1492
  basic device class: ${(_a = newNode.deviceClass) === null || _a === void 0 ? void 0 : _a.basic.label}
1137
1493
  generic device class: ${(_b = newNode.deviceClass) === null || _b === void 0 ? void 0 : _b.generic.label}
1138
1494
  specific device class: ${(_c = newNode.deviceClass) === null || _c === void 0 ? void 0 : _c.specific.label}
1139
1495
  supported CCs: ${supportedCommandClasses
1140
- .map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
1141
- .join("")}
1496
+ .map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
1497
+ .join("")}
1142
1498
  controlled CCs: ${controlledCommandClasses
1143
- .map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
1144
- .join("")}`);
1145
- // remember the node
1146
- this._nodes.set(newNode.id, newNode);
1147
- this._nodePendingInclusion = undefined;
1148
- // We're communicating with the device, so assume it is alive
1149
- // If it is actually a sleeping device, it will be marked as such later
1150
- newNode.markAsAlive();
1151
- // Assign SUC return route to make sure the node knows where to get its routes from
1152
- newNode.hasSUCReturnRoute = await this.assignSUCReturnRoute(newNode.id);
1153
- const opts = this._inclusionOptions;
1154
- // The default inclusion strategy is: Use S2 if possible, only use S0 if necessary, use no encryption otherwise
1155
- let lowSecurity = false;
1156
- if (newNode.supportsCC(core_1.CommandClasses["Security 2"]) &&
1157
- (opts.strategy === Inclusion_1.InclusionStrategy.Default ||
1158
- opts.strategy === Inclusion_1.InclusionStrategy.Security_S2)) {
1159
- await this.secureBootstrapS2(newNode);
1160
- const actualSecurityClass = newNode.getHighestSecurityClass();
1161
- if (actualSecurityClass == undefined ||
1162
- actualSecurityClass <
1163
- core_1.SecurityClass.S2_Unauthenticated) {
1164
- lowSecurity = true;
1165
- }
1499
+ .map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
1500
+ .join("")}`);
1501
+ // remember the node
1502
+ this._nodes.set(newNode.id, newNode);
1503
+ this._nodePendingInclusion = undefined;
1504
+ // We're communicating with the device, so assume it is alive
1505
+ // If it is actually a sleeping device, it will be marked as such later
1506
+ newNode.markAsAlive();
1507
+ // Assign SUC return route to make sure the node knows where to get its routes from
1508
+ newNode.hasSUCReturnRoute = await this.assignSUCReturnRoute(newNode.id);
1509
+ const opts = this._inclusionOptions;
1510
+ // The default inclusion strategy is: Use S2 if possible, only use S0 if necessary, use no encryption otherwise
1511
+ let lowSecurity = false;
1512
+ if (newNode.supportsCC(core_1.CommandClasses["Security 2"]) &&
1513
+ (opts.strategy === Inclusion_1.InclusionStrategy.Default ||
1514
+ opts.strategy === Inclusion_1.InclusionStrategy.Security_S2 ||
1515
+ opts.strategy === Inclusion_1.InclusionStrategy.SmartStart)) {
1516
+ await this.secureBootstrapS2(newNode);
1517
+ const actualSecurityClass = newNode.getHighestSecurityClass();
1518
+ if (actualSecurityClass == undefined ||
1519
+ actualSecurityClass < core_1.SecurityClass.S2_Unauthenticated) {
1520
+ lowSecurity = true;
1166
1521
  }
1167
- else if (newNode.supportsCC(core_1.CommandClasses.Security) &&
1168
- (opts.strategy === Inclusion_1.InclusionStrategy.Security_S0 ||
1169
- (opts.strategy === Inclusion_1.InclusionStrategy.Default &&
1170
- (opts.forceSecurity ||
1171
- ((_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))))) {
1172
- await this.secureBootstrapS0(newNode);
1173
- const actualSecurityClass = newNode.getHighestSecurityClass();
1174
- if (actualSecurityClass == undefined ||
1175
- actualSecurityClass < core_1.SecurityClass.S0_Legacy) {
1176
- lowSecurity = true;
1177
- }
1522
+ }
1523
+ else if (newNode.supportsCC(core_1.CommandClasses.Security) &&
1524
+ (opts.strategy === Inclusion_1.InclusionStrategy.Security_S0 ||
1525
+ (opts.strategy === Inclusion_1.InclusionStrategy.Default &&
1526
+ (opts.forceSecurity ||
1527
+ ((_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))))) {
1528
+ await this.secureBootstrapS0(newNode);
1529
+ const actualSecurityClass = newNode.getHighestSecurityClass();
1530
+ if (actualSecurityClass == undefined ||
1531
+ actualSecurityClass < core_1.SecurityClass.S0_Legacy) {
1532
+ lowSecurity = true;
1178
1533
  }
1179
- this._includeController = false;
1180
- // Bootstrap the node's lifelines, so it knows where the controller is
1181
- await this.bootstrapLifelineAndWakeup(newNode);
1182
- // We're done adding this node, notify listeners
1183
- const result = {};
1184
- if (lowSecurity)
1185
- result.lowSecurity = true;
1186
- this.emit("node added", newNode, result);
1187
1534
  }
1188
- break;
1535
+ this._includeController = false;
1536
+ // Bootstrap the node's lifelines, so it knows where the controller is
1537
+ await this.bootstrapLifelineAndWakeup(newNode);
1538
+ // We're done adding this node, notify listeners
1539
+ const result = {};
1540
+ if (lowSecurity)
1541
+ result.lowSecurity = true;
1542
+ this.markNodeOnProvisioningList(newNode);
1543
+ this.emit("node added", newNode, result);
1544
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1545
+ return true; // Don't invoke any more handlers
1189
1546
  }
1190
- default:
1191
- // not sure what to do with this message
1192
- return false;
1193
1547
  }
1194
- return true; // Don't invoke any more handlers
1548
+ // not sure what to do with this message
1549
+ return false;
1195
1550
  }
1196
1551
  /**
1197
1552
  * Is called when an ReplaceFailed request is received from the controller.
1198
1553
  * Handles and controls the replace process.
1199
1554
  */
1200
- async handleReplaceNodeRequest(msg) {
1555
+ async handleReplaceNodeStatusReport(msg) {
1201
1556
  var _a, _b, _c;
1202
1557
  this.driver.controllerLog.print(`handling replace node request (status = ${ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus[msg.replaceStatus]})`);
1203
1558
  if (this._inclusionOptions == undefined) {
@@ -1206,9 +1561,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1206
1561
  }
1207
1562
  switch (msg.replaceStatus) {
1208
1563
  case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.NodeOK:
1564
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1209
1565
  (_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));
1210
1566
  break;
1211
1567
  case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.FailedNodeReplaceFailed:
1568
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1212
1569
  (_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));
1213
1570
  break;
1214
1571
  case ReplaceFailedNodeRequest_1.ReplaceFailedNodeStatus.FailedNodeReplace:
@@ -1219,7 +1576,7 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1219
1576
  // TODO: Remove first parameter in next major version
1220
1577
  this._inclusionOptions.strategy !==
1221
1578
  Inclusion_1.InclusionStrategy.Insecure, this._inclusionOptions.strategy);
1222
- this._inclusionActive = true;
1579
+ this.setInclusionState(Inclusion_1.InclusionState.Including);
1223
1580
  (_c = this._replaceFailedPromise) === null || _c === void 0 ? void 0 : _c.resolve(true);
1224
1581
  // stop here, don't emit inclusion failed
1225
1582
  return true;
@@ -1228,7 +1585,11 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1228
1585
  this.emit("inclusion stopped");
1229
1586
  if (this._nodePendingReplace) {
1230
1587
  this.emit("node removed", this._nodePendingReplace, true);
1588
+ this.unmarkNodeOnProvisioningList(this._nodePendingReplace.id);
1231
1589
  this._nodes.delete(this._nodePendingReplace.id);
1590
+ // We're technically done with the replacing but should not include
1591
+ // anything else until the node has been bootstrapped
1592
+ this.setInclusionState(Inclusion_1.InclusionState.Busy);
1232
1593
  // Create a fresh node instance and forget the old one
1233
1594
  const newNode = new Node_1.ZWaveNode(this._nodePendingReplace.id, this.driver, undefined, undefined, undefined,
1234
1595
  // Create an empty value DB and specify that it contains no values
@@ -1268,6 +1629,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1268
1629
  const result = {};
1269
1630
  if (lowSecurity)
1270
1631
  result.lowSecurity = true;
1632
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1633
+ this.markNodeOnProvisioningList(newNode);
1271
1634
  this.emit("node added", newNode, result);
1272
1635
  }
1273
1636
  // stop here, don't emit inclusion failed
@@ -1280,40 +1643,26 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1280
1643
  * Is called when a RemoveNode request is received from the controller.
1281
1644
  * Handles and controls the exclusion process.
1282
1645
  */
1283
- async handleRemoveNodeRequest(msg) {
1646
+ async handleRemoveNodeStatusReport(msg) {
1284
1647
  this.driver.controllerLog.print(`handling remove node request (status = ${RemoveNodeFromNetworkRequest_1.RemoveNodeStatus[msg.status]})`);
1285
- if (!this._exclusionActive && msg.status !== RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Done) {
1648
+ if (this._inclusionState !== Inclusion_1.InclusionState.Excluding) {
1286
1649
  this.driver.controllerLog.print(` exclusion is NOT active, ignoring it...`);
1287
1650
  return true; // Don't invoke any more handlers
1288
1651
  }
1289
1652
  switch (msg.status) {
1290
- case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Ready:
1291
- // this is called when inclusion was started successfully
1292
- this.driver.controllerLog.print(` the controller is now ready to remove nodes`);
1293
- if (this._beginInclusionPromise != null) {
1294
- this._beginInclusionPromise.resolve(true);
1295
- this.emit("exclusion started");
1296
- }
1297
- break;
1298
1653
  case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.Failed:
1299
- // this is called when inclusion could not be started...
1300
- if (this._beginInclusionPromise != null) {
1301
- this.driver.controllerLog.print(` starting the exclusion failed`, "error");
1302
- this._beginInclusionPromise.reject(new core_1.ZWaveError("The exclusion could not be started.", core_1.ZWaveErrorCodes.Controller_ExclusionFailed));
1303
- }
1304
- else {
1305
- // ...or removing a node failed
1306
- this.driver.controllerLog.print(` removing the node failed`, "error");
1307
- this.emit("exclusion failed");
1308
- }
1654
+ // This code is handled elsewhere for starting the exclusion, so this means
1655
+ // that removing a node failed
1656
+ this.driver.controllerLog.print(`Removing the node failed`, "error");
1657
+ this.emit("exclusion failed");
1309
1658
  // in any case, stop the exclusion process so we don't accidentally remove another node
1310
1659
  try {
1311
- await this.stopExclusionInternal();
1660
+ await this.stopExclusion();
1312
1661
  }
1313
1662
  catch {
1314
1663
  /* ok */
1315
1664
  }
1316
- break;
1665
+ return true; // Don't invoke any more handlers
1317
1666
  case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.RemovingSlave:
1318
1667
  case RemoveNodeFromNetworkRequest_1.RemoveNodeStatus.RemovingController: {
1319
1668
  // this is called when a node is removed
@@ -1324,29 +1673,33 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1324
1673
  // this is called when the exclusion was completed
1325
1674
  // stop the exclusion process so we don't accidentally remove another node
1326
1675
  try {
1327
- await this.stopExclusionInternal();
1676
+ await this.stopExclusionNoCallback();
1328
1677
  }
1329
1678
  catch {
1330
1679
  /* ok */
1331
1680
  }
1332
- // stopping the inclusion was acknowledged by the controller
1333
- if (this._stopInclusionPromise != null)
1334
- this._stopInclusionPromise.resolve(true);
1335
- if (this._nodePendingExclusion != null) {
1336
- this.driver.controllerLog.print(`Node ${this._nodePendingExclusion.id} was removed`);
1337
- // notify listeners
1338
- this.emit("node removed", this._nodePendingExclusion, false);
1339
- // and forget the node
1340
- this._nodes.delete(this._nodePendingExclusion.id);
1341
- this._nodePendingExclusion = undefined;
1681
+ if (!this._nodePendingExclusion) {
1682
+ // The exclusion did not succeed
1683
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1684
+ return true;
1342
1685
  }
1343
- break;
1686
+ const nodeId = this._nodePendingExclusion.id;
1687
+ this.driver.controllerLog.print(`Node ${nodeId} was removed`);
1688
+ // Avoid automatic re-inclusion using SmartStart if desired
1689
+ if (this._unprovisionRemovedNode)
1690
+ this.unprovisionSmartStartNode(nodeId);
1691
+ // notify listeners
1692
+ this.emit("node removed", this._nodePendingExclusion, false);
1693
+ // and forget the node
1694
+ this.unmarkNodeOnProvisioningList(nodeId);
1695
+ this._nodes.delete(nodeId);
1696
+ this._nodePendingExclusion = undefined;
1697
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1698
+ return true; // Don't invoke any more handlers
1344
1699
  }
1345
- default:
1346
- // not sure what to do with this message
1347
- return false;
1348
1700
  }
1349
- return true; // Don't invoke any more handlers
1701
+ // not sure what to do with this message
1702
+ return false;
1350
1703
  }
1351
1704
  /**
1352
1705
  * Performs a healing process for all alive nodes in the network,
@@ -2123,15 +2476,23 @@ ${associatedNodes.join(", ")}`,
2123
2476
  // Emit the removed event so the driver and applications can react
2124
2477
  this.emit("node removed", this.nodes.get(nodeId), false);
2125
2478
  // and forget the node
2479
+ this.unmarkNodeOnProvisioningList(nodeId);
2126
2480
  this._nodes.delete(nodeId);
2127
2481
  return;
2128
2482
  }
2129
2483
  }
2130
2484
  }
2131
2485
  async replaceFailedNode(nodeId, options) {
2132
- // don't start it twice
2133
- if (this._inclusionActive || this._exclusionActive)
2486
+ if (this._inclusionState === Inclusion_1.InclusionState.Including ||
2487
+ this._inclusionState === Inclusion_1.InclusionState.Excluding ||
2488
+ this._inclusionState === Inclusion_1.InclusionState.Busy) {
2134
2489
  return false;
2490
+ }
2491
+ if (this._inclusionState === Inclusion_1.InclusionState.SmartStart) {
2492
+ // Disable listening mode so we can switch to exclusion mode
2493
+ await this.stopInclusion();
2494
+ }
2495
+ this.setInclusionState(Inclusion_1.InclusionState.Busy);
2135
2496
  if (options == undefined) {
2136
2497
  options = {
2137
2498
  strategy: Inclusion_1.InclusionStrategy.Security_S0,
@@ -2147,6 +2508,7 @@ ${associatedNodes.join(", ")}`,
2147
2508
  this.driver.controllerLog.print(`starting replace failed node process...`);
2148
2509
  const node = this.nodes.getOrThrow(nodeId);
2149
2510
  if (await node.ping()) {
2511
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
2150
2512
  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);
2151
2513
  }
2152
2514
  this._inclusionOptions = options;
@@ -2172,6 +2534,7 @@ ${associatedNodes.join(", ")}`,
2172
2534
  ReplaceFailedNodeRequest_1.ReplaceFailedNodeStartFlags.ReplaceFailed)) {
2173
2535
  message += `\n· The controller is busy or the node has responded`;
2174
2536
  }
2537
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
2175
2538
  throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.ReplaceFailedNode_Failed);
2176
2539
  }
2177
2540
  else {
@@ -2187,7 +2550,8 @@ ${associatedNodes.join(", ")}`,
2187
2550
  if (result instanceof SerialAPISetupMessages_1.SerialAPISetup_CommandUnsupportedResponse) {
2188
2551
  throw new core_1.ZWaveError(`Your hardware does not support setting the RF region!`, core_1.ZWaveErrorCodes.Driver_NotSupported);
2189
2552
  }
2190
- // TODO: Issue soft reset
2553
+ if (result.success)
2554
+ await this.driver.trySoftReset();
2191
2555
  return result.success;
2192
2556
  }
2193
2557
  /** Request the current RF region configured at the Z-Wave API Module */
@@ -2285,6 +2649,18 @@ ${associatedNodes.join(", ")}`,
2285
2649
  */
2286
2650
  serialize() {
2287
2651
  return {
2652
+ controller: {
2653
+ supportsSoftReset: this.supportsSoftReset,
2654
+ provisioningList: this.provisioningList.map((e) => {
2655
+ const { dsk, securityClasses, ...rest } = e;
2656
+ return {
2657
+ dsk,
2658
+ securityClasses: securityClasses.map((s) => core_1.SecurityClass[s]),
2659
+ // The user-defined properties are saved as-is
2660
+ ...rest,
2661
+ };
2662
+ }),
2663
+ },
2288
2664
  nodes: (0, objects_1.composeObject)([...this.nodes.entries()].map(([id, node]) => [id.toString(), node.serialize()])),
2289
2665
  };
2290
2666
  }
@@ -2293,6 +2669,43 @@ ${associatedNodes.join(", ")}`,
2293
2669
  * Deserializes the controller information and all nodes from the cache.
2294
2670
  */
2295
2671
  async deserialize(serialized) {
2672
+ if ((0, typeguards_1.isObject)(serialized.controller)) {
2673
+ // Parse whether the controller supports soft reset
2674
+ if (typeof serialized.controller.supportsSoftReset === "boolean") {
2675
+ this.supportsSoftReset =
2676
+ serialized.controller.supportsSoftReset;
2677
+ }
2678
+ // Parse the controller's Smart Start provisioning list
2679
+ if ((0, typeguards_1.isArray)(serialized.controller.provisioningList)) {
2680
+ entries: for (const entry of serialized.controller
2681
+ .provisioningList) {
2682
+ if (!(0, typeguards_1.isObject)(entry))
2683
+ continue;
2684
+ const { dsk, securityClasses: secClasses, ...rest } = entry;
2685
+ if (typeof entry.dsk !== "string")
2686
+ continue;
2687
+ if (!(0, typeguards_1.isArray)(entry.securityClasses))
2688
+ continue;
2689
+ if (!(0, core_1.isValidDSK)(entry.dsk))
2690
+ continue;
2691
+ const securityClasses = [];
2692
+ for (const s of secClasses) {
2693
+ if (typeof s !== "string")
2694
+ continue entries;
2695
+ const secClass = core_1.SecurityClass[s];
2696
+ if (typeof secClass !== "number")
2697
+ continue entries;
2698
+ securityClasses.push(secClass);
2699
+ }
2700
+ this._provisioningList.push({
2701
+ dsk: entry.dsk,
2702
+ securityClasses,
2703
+ // The user-defined properties are not validated further
2704
+ ...rest,
2705
+ });
2706
+ }
2707
+ }
2708
+ }
2296
2709
  if ((0, typeguards_1.isObject)(serialized.nodes)) {
2297
2710
  for (const nodeId of Object.keys(serialized.nodes)) {
2298
2711
  const serializedNode = serialized.nodes[nodeId];
@@ -2302,9 +2715,9 @@ ${associatedNodes.join(", ")}`,
2302
2715
  throw new core_1.ZWaveError("The cache file is invalid", core_1.ZWaveErrorCodes.Driver_InvalidCache);
2303
2716
  }
2304
2717
  if (this.nodes.has(serializedNode.id)) {
2305
- await this.nodes
2306
- .get(serializedNode.id)
2307
- .deserialize(serializedNode);
2718
+ const node = this.nodes.getOrThrow(serializedNode.id);
2719
+ await node.deserialize(serializedNode);
2720
+ this.markNodeOnProvisioningList(node);
2308
2721
  }
2309
2722
  }
2310
2723
  }
@@ -2321,17 +2734,27 @@ ${associatedNodes.join(", ")}`,
2321
2734
  return false;
2322
2735
  }
2323
2736
  }
2324
- /** Returns information of the controller's external NVM */
2737
+ /**
2738
+ * **Z-Wave 500 series only**
2739
+ *
2740
+ * Returns information of the controller's external NVM
2741
+ */
2325
2742
  async getNVMId() {
2326
2743
  const ret = await this.driver.sendMessage(new GetNVMIdMessages_1.GetNVMIdRequest(this.driver));
2327
2744
  return (0, shared_1.pick)(ret, ["nvmManufacturerId", "memoryType", "memorySize"]);
2328
2745
  }
2329
- /** Reads a byte from the external NVM at the given offset */
2746
+ /**
2747
+ * **Z-Wave 500 series only**
2748
+ *
2749
+ * Reads a byte from the external NVM at the given offset
2750
+ */
2330
2751
  async externalNVMReadByte(offset) {
2331
2752
  const ret = await this.driver.sendMessage(new ExtNVMReadLongByteMessages_1.ExtNVMReadLongByteRequest(this.driver, { offset }));
2332
2753
  return ret.byte;
2333
2754
  }
2334
2755
  /**
2756
+ * **Z-Wave 500 series only**
2757
+ *
2335
2758
  * Writes a byte to the external NVM at the given offset
2336
2759
  * **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
2337
2760
  * Take care not to accidentally overwrite the protocol NVM area!
@@ -2342,12 +2765,46 @@ ${associatedNodes.join(", ")}`,
2342
2765
  const ret = await this.driver.sendMessage(new ExtNVMWriteLongByteMessages_1.ExtNVMWriteLongByteRequest(this.driver, { offset, byte: data }));
2343
2766
  return ret.success;
2344
2767
  }
2345
- /** Reads a buffer from the external NVM at the given offset */
2768
+ /**
2769
+ * **Z-Wave 500 series only**
2770
+ *
2771
+ * Reads a buffer from the external NVM at the given offset
2772
+ */
2346
2773
  async externalNVMReadBuffer(offset, length) {
2347
- const ret = await this.driver.sendMessage(new ExtNVMReadLongBufferMessages_1.ExtNVMReadLongBufferRequest(this.driver, { offset, length }));
2774
+ const ret = await this.driver.sendMessage(new ExtNVMReadLongBufferMessages_1.ExtNVMReadLongBufferRequest(this.driver, {
2775
+ offset,
2776
+ length,
2777
+ }));
2348
2778
  return ret.buffer;
2349
2779
  }
2350
2780
  /**
2781
+ * **Z-Wave 700 series only**
2782
+ *
2783
+ * Reads a buffer from the external NVM at the given offset
2784
+ */
2785
+ async externalNVMReadBuffer700(offset, length) {
2786
+ const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsReadRequest(this.driver, {
2787
+ offset,
2788
+ length,
2789
+ }));
2790
+ if (!ret.isOK()) {
2791
+ let message = "Could not read from the external NVM";
2792
+ if (ret.status === NVMOperationsMessages_1.NVMOperationStatus.Error_OperationInterference) {
2793
+ message += ": interference between read and write operation.";
2794
+ }
2795
+ else if (ret.status === NVMOperationsMessages_1.NVMOperationStatus.Error_OperationMismatch) {
2796
+ message += ": wrong operation requested.";
2797
+ }
2798
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Controller_CommandError);
2799
+ }
2800
+ return {
2801
+ buffer: ret.buffer,
2802
+ endOfFile: ret.status === NVMOperationsMessages_1.NVMOperationStatus.EndOfFile,
2803
+ };
2804
+ }
2805
+ /**
2806
+ * **Z-Wave 500 series only**
2807
+ *
2351
2808
  * Writes a buffer to the external NVM at the given offset
2352
2809
  * **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
2353
2810
  * Take care not to accidentally overwrite the protocol NVM area!
@@ -2361,16 +2818,86 @@ ${associatedNodes.join(", ")}`,
2361
2818
  }));
2362
2819
  return ret.success;
2363
2820
  }
2821
+ /**
2822
+ * **Z-Wave 700 series only**
2823
+ *
2824
+ * Writes a buffer to the external NVM at the given offset
2825
+ * **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
2826
+ * Take care not to accidentally overwrite the protocol NVM area!
2827
+ */
2828
+ async externalNVMWriteBuffer700(offset, buffer) {
2829
+ const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsWriteRequest(this.driver, {
2830
+ offset,
2831
+ buffer,
2832
+ }));
2833
+ if (!ret.isOK()) {
2834
+ let message = "Could not write to the external NVM";
2835
+ if (ret.status === NVMOperationsMessages_1.NVMOperationStatus.Error_OperationInterference) {
2836
+ message += ": interference between read and write operation.";
2837
+ }
2838
+ else if (ret.status === NVMOperationsMessages_1.NVMOperationStatus.Error_OperationMismatch) {
2839
+ message += ": wrong operation requested.";
2840
+ }
2841
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Controller_CommandError);
2842
+ }
2843
+ return {
2844
+ endOfFile: ret.status === NVMOperationsMessages_1.NVMOperationStatus.EndOfFile,
2845
+ };
2846
+ }
2847
+ /**
2848
+ * **Z-Wave 700 series only**
2849
+ *
2850
+ * Opens the controller's external NVM for reading/writing and returns the NVM size
2851
+ */
2852
+ async externalNVMOpen() {
2853
+ const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsOpenRequest(this.driver));
2854
+ if (!ret.isOK()) {
2855
+ throw new core_1.ZWaveError("Failed to open the external NVM", core_1.ZWaveErrorCodes.Controller_CommandError);
2856
+ }
2857
+ return ret.offsetOrSize;
2858
+ }
2859
+ /**
2860
+ * **Z-Wave 700 series only**
2861
+ *
2862
+ * Closes the controller's external NVM
2863
+ */
2864
+ async externalNVMClose() {
2865
+ const ret = await this.driver.sendMessage(new NVMOperationsMessages_1.NVMOperationsCloseRequest(this.driver));
2866
+ if (!ret.isOK()) {
2867
+ throw new core_1.ZWaveError("Failed to close the external NVM", core_1.ZWaveErrorCodes.Controller_CommandError);
2868
+ }
2869
+ }
2364
2870
  /**
2365
2871
  * Creates a backup of the NVM and returns the raw data as a Buffer. The Z-Wave radio is turned off/on automatically.
2366
2872
  * @param onProgress Can be used to monitor the progress of the operation, which may take several seconds up to a few minutes depending on the NVM size
2367
2873
  * @returns The raw NVM buffer
2368
2874
  */
2369
2875
  async backupNVMRaw(onProgress) {
2876
+ this.driver.controllerLog.print("Backing up NVM...");
2370
2877
  // Turn Z-Wave radio off to avoid having the protocol write to the NVM while dumping it
2371
2878
  if (!(await this.toggleRF(false))) {
2372
2879
  throw new core_1.ZWaveError("Could not turn off the Z-Wave radio before creating NVM backup!", core_1.ZWaveErrorCodes.Controller_ResponseNOK);
2373
2880
  }
2881
+ let ret;
2882
+ try {
2883
+ if (this.serialApiGte("7.0")) {
2884
+ ret = await this.backupNVMRaw700(onProgress);
2885
+ }
2886
+ else {
2887
+ ret = await this.backupNVMRaw500(onProgress);
2888
+ }
2889
+ this.driver.controllerLog.print("NVM backup completed");
2890
+ }
2891
+ finally {
2892
+ // Whatever happens, turn Z-Wave radio back on
2893
+ await this.toggleRF(true);
2894
+ }
2895
+ // TODO: You can also get away with eliding all the 0xff pages. The NVR also holds the page size of the NVM (NVMP),
2896
+ // so you can figure out which pages you don't have to save or restore. If you do this, you need to make sure to issue a
2897
+ // "factory reset" before restoring the NVM - that'll blank out the NVM to 0xffs before initializing it.
2898
+ return ret;
2899
+ }
2900
+ async backupNVMRaw500(onProgress) {
2374
2901
  const size = (0, GetNVMIdMessages_1.nvmSizeToBufferSize)((await this.getNVMId()).memorySize);
2375
2902
  if (!size) {
2376
2903
  throw new core_1.ZWaveError("Unknown NVM size - cannot backup!", core_1.ZWaveErrorCodes.Controller_NotSupported);
@@ -2396,11 +2923,40 @@ ${associatedNodes.join(", ")}`,
2396
2923
  if (onProgress)
2397
2924
  setImmediate(() => onProgress(offset, size));
2398
2925
  }
2399
- // TODO: You can also get away with eliding all the 0xff pages. The NVR also holds the page size of the NVM (NVMP),
2400
- // so you can figure out which pages you don't have to save or restore. If you do this, you need to make sure to issue a
2401
- // "factory reset" before restoring the NVM - that'll blank out the NVM to 0xffs before initializing it.
2402
- // Turn Z-Wave radio back on
2403
- await this.toggleRF(true);
2926
+ return ret;
2927
+ }
2928
+ async backupNVMRaw700(onProgress) {
2929
+ // Open NVM for reading
2930
+ const size = await this.externalNVMOpen();
2931
+ const ret = Buffer.allocUnsafe(size);
2932
+ let offset = 0;
2933
+ // Try reading the maximum size at first, the Serial API should return chunks in a size it supports
2934
+ // For some reason, there is no documentation and no official command for this
2935
+ let chunkSize = Math.min(0xff, ret.length);
2936
+ try {
2937
+ while (offset < ret.length) {
2938
+ const { buffer: chunk, endOfFile } = await this.externalNVMReadBuffer700(offset, Math.min(chunkSize, ret.length - offset));
2939
+ if (chunkSize === 0xff && chunk.length === 0) {
2940
+ // Some SDK versions return an empty buffer when trying to read a buffer that is too long
2941
+ // Fallback to a sane (but maybe slow) size
2942
+ chunkSize = 48;
2943
+ continue;
2944
+ }
2945
+ chunk.copy(ret, offset);
2946
+ offset += chunk.length;
2947
+ if (chunkSize > chunk.length)
2948
+ chunkSize = chunk.length;
2949
+ // Report progress for listeners
2950
+ if (onProgress)
2951
+ setImmediate(() => onProgress(offset, size));
2952
+ if (endOfFile)
2953
+ break;
2954
+ }
2955
+ }
2956
+ finally {
2957
+ // Whatever happens, close the NVM
2958
+ await this.externalNVMClose();
2959
+ }
2404
2960
  return ret;
2405
2961
  }
2406
2962
  /**
@@ -2411,10 +2967,36 @@ ${associatedNodes.join(", ")}`,
2411
2967
  * @param onProgress Can be used to monitor the progress of the operation, which may take several seconds up to a few minutes depending on the NVM size
2412
2968
  */
2413
2969
  async restoreNVMRaw(nvmData, onProgress) {
2970
+ this.driver.controllerLog.print("Restoring NVM...");
2414
2971
  // Turn Z-Wave radio off to avoid having the protocol write to the NVM while dumping it
2415
2972
  if (!(await this.toggleRF(false))) {
2416
2973
  throw new core_1.ZWaveError("Could not turn off the Z-Wave radio before restoring NVM backup!", core_1.ZWaveErrorCodes.Controller_ResponseNOK);
2417
2974
  }
2975
+ try {
2976
+ if (this.serialApiGte("7.0")) {
2977
+ await this.restoreNVMRaw700(nvmData, onProgress);
2978
+ }
2979
+ else {
2980
+ await this.restoreNVMRaw500(nvmData, onProgress);
2981
+ }
2982
+ this.driver.controllerLog.print("NVM backup restored");
2983
+ }
2984
+ finally {
2985
+ // Whatever happens, turn Z-Wave radio back on
2986
+ await this.toggleRF(true);
2987
+ }
2988
+ // TODO: You can also get away with eliding all the 0xff pages. The NVR also holds the page size of the NVM (NVMP),
2989
+ // so you can figure out which pages you don't have to save or restore. If you do this, you need to make sure to issue a
2990
+ // "factory reset" before restoring the NVM - that'll blank out the NVM to 0xffs before initializing it.
2991
+ if (this.driver.options.enableSoftReset) {
2992
+ this.driver.controllerLog.print("Activating restored NVM backup...");
2993
+ await this.driver.softReset();
2994
+ }
2995
+ else {
2996
+ this.driver.controllerLog.print("Soft reset not enabled, cannot automatically activate restored NVM backup!", "warn");
2997
+ }
2998
+ }
2999
+ async restoreNVMRaw500(nvmData, onProgress) {
2418
3000
  const size = (0, GetNVMIdMessages_1.nvmSizeToBufferSize)((await this.getNVMId()).memorySize);
2419
3001
  if (!size) {
2420
3002
  throw new core_1.ZWaveError("Unknown NVM size - cannot restore!", core_1.ZWaveErrorCodes.Controller_NotSupported);
@@ -2422,9 +3004,6 @@ ${associatedNodes.join(", ")}`,
2422
3004
  else if (size !== nvmData.length) {
2423
3005
  throw new core_1.ZWaveError("The given data does not match the NVM size - cannot restore!", core_1.ZWaveErrorCodes.Argument_Invalid);
2424
3006
  }
2425
- // TODO: You can also get away with eliding all the 0xff pages. The NVR also holds the page size of the NVM (NVMP),
2426
- // so you can figure out which pages you don't have to save or restore. If you do this, you need to make sure to issue a
2427
- // "factory reset" before restoring the NVM - that'll blank out the NVM to 0xffs before initializing it.
2428
3007
  // Figure out the maximum chunk size the Serial API supports
2429
3008
  // For some reason, there is no documentation and no official command for this
2430
3009
  // The write requests need 5 bytes more than the read response, so subtract 5 from the returned length
@@ -2435,9 +3014,31 @@ ${associatedNodes.join(", ")}`,
2435
3014
  if (onProgress)
2436
3015
  setImmediate(() => onProgress(offset, size));
2437
3016
  }
2438
- // Turn Z-Wave radio back on
2439
- await this.toggleRF(true);
2440
- // TODO: Soft Reset
3017
+ }
3018
+ async restoreNVMRaw700(nvmData, onProgress) {
3019
+ // Open NVM for reading
3020
+ const size = await this.externalNVMOpen();
3021
+ if (size !== nvmData.length) {
3022
+ throw new core_1.ZWaveError("The given data does not match the NVM size - cannot restore!", core_1.ZWaveErrorCodes.Argument_Invalid);
3023
+ }
3024
+ // Figure out the maximum chunk size the Serial API supports
3025
+ // For some reason, there is no documentation and no official command for this
3026
+ // The write requests have the same size as the read response - if this yields no
3027
+ // data, default to a sane (but maybe slow) size
3028
+ const chunkSize = (await this.externalNVMReadBuffer700(0, 0xff)).buffer.length || 48;
3029
+ // Close NVM and re-open again for writing
3030
+ await this.externalNVMClose();
3031
+ await this.externalNVMOpen();
3032
+ for (let offset = 0; offset < nvmData.length; offset += chunkSize) {
3033
+ const { endOfFile } = await this.externalNVMWriteBuffer700(offset, nvmData.slice(offset, offset + chunkSize));
3034
+ // Report progress for listeners
3035
+ if (onProgress)
3036
+ setImmediate(() => onProgress(offset, size));
3037
+ if (endOfFile)
3038
+ break;
3039
+ }
3040
+ // Close NVM
3041
+ await this.externalNVMClose();
2441
3042
  }
2442
3043
  };
2443
3044
  ZWaveController = __decorate([