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.
- 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/API.d.ts +7 -3
- package/build/lib/commandclass/API.d.ts.map +1 -1
- package/build/lib/commandclass/API.js +9 -1
- package/build/lib/commandclass/API.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/BinarySwitchCC.d.ts.map +1 -1
- package/build/lib/commandclass/BinarySwitchCC.js +7 -3
- package/build/lib/commandclass/BinarySwitchCC.js.map +1 -1
- package/build/lib/commandclass/ColorSwitchCC.js +2 -2
- package/build/lib/commandclass/ColorSwitchCC.js.map +1 -1
- package/build/lib/commandclass/ConfigurationCC.d.ts.map +1 -1
- package/build/lib/commandclass/ConfigurationCC.js +3 -1
- package/build/lib/commandclass/ConfigurationCC.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/commandclass/MultilevelSwitchCC.d.ts.map +1 -1
- package/build/lib/commandclass/MultilevelSwitchCC.js +2 -4
- package/build/lib/commandclass/MultilevelSwitchCC.js.map +1 -1
- package/build/lib/commandclass/SoundSwitchCC.js +2 -2
- package/build/lib/commandclass/SoundSwitchCC.js.map +1 -1
- package/build/lib/commandclass/ThermostatFanModeCC.js +2 -2
- package/build/lib/commandclass/ThermostatFanModeCC.js.map +1 -1
- package/build/lib/commandclass/ThermostatModeCC.js +2 -2
- package/build/lib/commandclass/ThermostatModeCC.js.map +1 -1
- package/build/lib/commandclass/ThermostatSetpointCC.d.ts.map +1 -1
- package/build/lib/commandclass/ThermostatSetpointCC.js +2 -2
- package/build/lib/commandclass/ThermostatSetpointCC.js.map +1 -1
- package/build/lib/commandclass/UserCodeCC.js +2 -2
- package/build/lib/commandclass/UserCodeCC.js.map +1 -1
- package/build/lib/commandclass/WakeUpCC.js +2 -2
- package/build/lib/commandclass/WakeUpCC.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 +128 -23
- package/build/lib/controller/Controller.d.ts.map +1 -1
- package/build/lib/controller/Controller.js +882 -281
- 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/controller/SoftResetRequest.d.ts +4 -0
- package/build/lib/controller/SoftResetRequest.d.ts.map +1 -0
- package/build/lib/controller/SoftResetRequest.js +19 -0
- package/build/lib/controller/SoftResetRequest.js.map +1 -0
- package/build/lib/driver/Driver.d.ts +31 -2
- package/build/lib/driver/Driver.d.ts.map +1 -1
- package/build/lib/driver/Driver.js +478 -82
- package/build/lib/driver/Driver.js.map +1 -1
- package/build/lib/driver/SendThreadMachine.d.ts +4 -1
- package/build/lib/driver/SendThreadMachine.d.ts.map +1 -1
- package/build/lib/driver/SendThreadMachine.js +23 -0
- package/build/lib/driver/SendThreadMachine.js.map +1 -1
- package/build/lib/driver/Transaction.d.ts +2 -0
- package/build/lib/driver/Transaction.d.ts.map +1 -1
- package/build/lib/driver/Transaction.js +2 -0
- package/build/lib/driver/Transaction.js.map +1 -1
- package/build/lib/driver/UpdateConfig.js.map +1 -1
- package/build/lib/driver/ZWaveOptions.d.ts +18 -1
- package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
- package/build/lib/log/Controller.d.ts +12 -2
- package/build/lib/log/Controller.d.ts.map +1 -1
- package/build/lib/log/Controller.js +45 -0
- package/build/lib/log/Controller.js.map +1 -1
- package/build/lib/log/Driver.d.ts +5 -2
- package/build/lib/log/Driver.d.ts.map +1 -1
- package/build/lib/log/Driver.js +3 -0
- package/build/lib/log/Driver.js.map +1 -1
- package/build/lib/message/Constants.d.ts +3 -2
- package/build/lib/message/Constants.d.ts.map +1 -1
- package/build/lib/message/Constants.js +3 -2
- package/build/lib/message/Constants.js.map +1 -1
- package/build/lib/node/Node.d.ts +7 -1
- package/build/lib/node/Node.d.ts.map +1 -1
- package/build/lib/node/Node.js +35 -9
- package/build/lib/node/Node.js.map +1 -1
- package/build/lib/serialapi/misc/SerialAPIStartedRequest.d.ts +41 -0
- package/build/lib/serialapi/misc/SerialAPIStartedRequest.d.ts.map +1 -0
- package/build/lib/serialapi/misc/SerialAPIStartedRequest.js +91 -0
- package/build/lib/serialapi/misc/SerialAPIStartedRequest.js.map +1 -0
- package/build/lib/serialapi/nvm/NVMOperationsMessages.d.ts +60 -0
- package/build/lib/serialapi/nvm/NVMOperationsMessages.d.ts.map +1 -0
- package/build/lib/serialapi/nvm/NVMOperationsMessages.js +187 -0
- package/build/lib/serialapi/nvm/NVMOperationsMessages.js.map +1 -0
- 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.
|
|
68
|
-
this.
|
|
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.
|
|
81
|
-
driver.registerRequestHandler(Constants_1.FunctionType.RemoveNodeFromNetwork, this.
|
|
82
|
-
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));
|
|
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(
|
|
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.
|
|
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
|
-
|
|
422
|
-
|
|
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 (
|
|
442
|
-
|
|
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.
|
|
603
|
+
this.setInclusionState(Inclusion_1.InclusionState.Including);
|
|
445
604
|
this._inclusionOptions = options;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
670
|
+
callbackId: 0,
|
|
671
|
+
addNodeType: AddNodeToNetworkRequest_1.AddNodeType.Stop,
|
|
452
672
|
highPower: true,
|
|
453
673
|
networkWide: true,
|
|
454
674
|
}));
|
|
455
|
-
|
|
675
|
+
this.driver.controllerLog.print(`The inclusion process was stopped`);
|
|
676
|
+
this.emit("inclusion stopped");
|
|
456
677
|
}
|
|
457
|
-
/**
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
this.
|
|
463
|
-
this.driver.
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
484
|
-
if (!this._inclusionActive)
|
|
700
|
+
if (this._inclusionState !== Inclusion_1.InclusionState.Including) {
|
|
485
701
|
return false;
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
496
|
-
|
|
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
|
-
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
/**
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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)(
|
|
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
|
-
//
|
|
770
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
//
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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.
|
|
1427
|
+
await this.stopInclusion();
|
|
1083
1428
|
}
|
|
1084
1429
|
catch {
|
|
1085
1430
|
/* ok */
|
|
1086
1431
|
}
|
|
1087
|
-
|
|
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.
|
|
1451
|
+
nodeId = await this.finishInclusion();
|
|
1106
1452
|
}
|
|
1107
1453
|
catch {
|
|
1108
|
-
|
|
1454
|
+
// ignore the error
|
|
1109
1455
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
]
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1496
|
+
.map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
|
|
1497
|
+
.join("")}
|
|
1142
1498
|
controlled CCs: ${controlledCommandClasses
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
1646
|
+
async handleRemoveNodeStatusReport(msg) {
|
|
1284
1647
|
this.driver.controllerLog.print(`handling remove node request (status = ${RemoveNodeFromNetworkRequest_1.RemoveNodeStatus[msg.status]})`);
|
|
1285
|
-
if (
|
|
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
|
-
//
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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.
|
|
1660
|
+
await this.stopExclusion();
|
|
1312
1661
|
}
|
|
1313
1662
|
catch {
|
|
1314
1663
|
/* ok */
|
|
1315
1664
|
}
|
|
1316
|
-
|
|
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.
|
|
1676
|
+
await this.stopExclusionNoCallback();
|
|
1328
1677
|
}
|
|
1329
1678
|
catch {
|
|
1330
1679
|
/* ok */
|
|
1331
1680
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
this.
|
|
1335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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, {
|
|
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
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
//
|
|
2403
|
-
await this.
|
|
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
|
-
|
|
2439
|
-
|
|
2440
|
-
//
|
|
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([
|