zwave-js 10.3.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/build/Controller.d.ts +1 -1
  2. package/build/Controller.d.ts.map +1 -1
  3. package/build/Controller.js +2 -1
  4. package/build/Controller.js.map +1 -1
  5. package/build/Controller_safe.d.ts +1 -1
  6. package/build/Controller_safe.d.ts.map +1 -1
  7. package/build/Controller_safe.js +2 -1
  8. package/build/Controller_safe.js.map +1 -1
  9. package/build/lib/controller/Controller.d.ts +65 -2
  10. package/build/lib/controller/Controller.d.ts.map +1 -1
  11. package/build/lib/controller/Controller.js +670 -21
  12. package/build/lib/controller/Controller.js.map +1 -1
  13. package/build/lib/controller/FirmwareUpdateService.d.ts +2 -1
  14. package/build/lib/controller/FirmwareUpdateService.d.ts.map +1 -1
  15. package/build/lib/controller/FirmwareUpdateService.js +47 -82
  16. package/build/lib/controller/FirmwareUpdateService.js.map +1 -1
  17. package/build/lib/controller/Inclusion.d.ts +9 -4
  18. package/build/lib/controller/Inclusion.d.ts.map +1 -1
  19. package/build/lib/controller/Inclusion.js.map +1 -1
  20. package/build/lib/controller/NodeInformationFrame.d.ts.map +1 -1
  21. package/build/lib/controller/NodeInformationFrame.js +3 -2
  22. package/build/lib/controller/NodeInformationFrame.js.map +1 -1
  23. package/build/lib/controller/_Types.d.ts +24 -2
  24. package/build/lib/controller/_Types.d.ts.map +1 -1
  25. package/build/lib/controller/_Types.js +13 -0
  26. package/build/lib/controller/_Types.js.map +1 -1
  27. package/build/lib/driver/Bootloader.d.ts +22 -0
  28. package/build/lib/driver/Bootloader.d.ts.map +1 -0
  29. package/build/lib/driver/Bootloader.js +52 -0
  30. package/build/lib/driver/Bootloader.js.map +1 -0
  31. package/build/lib/driver/CommandQueueMachine.d.ts +4 -4
  32. package/build/lib/driver/CommandQueueMachine.d.ts.map +1 -1
  33. package/build/lib/driver/Driver.d.ts +27 -3
  34. package/build/lib/driver/Driver.d.ts.map +1 -1
  35. package/build/lib/driver/Driver.js +178 -51
  36. package/build/lib/driver/Driver.js.map +1 -1
  37. package/build/lib/driver/DriverMock.d.ts +5 -1
  38. package/build/lib/driver/DriverMock.d.ts.map +1 -1
  39. package/build/lib/driver/DriverMock.js +5 -1
  40. package/build/lib/driver/DriverMock.js.map +1 -1
  41. package/build/lib/driver/MessageGenerators.d.ts +1 -1
  42. package/build/lib/driver/MessageGenerators.d.ts.map +1 -1
  43. package/build/lib/driver/SendThreadMachine.d.ts +7 -7
  44. package/build/lib/driver/SendThreadMachine.d.ts.map +1 -1
  45. package/build/lib/driver/SerialAPICommandMachine.d.ts +8 -8
  46. package/build/lib/driver/SerialAPICommandMachine.d.ts.map +1 -1
  47. package/build/lib/driver/SerialAPICommandMachine.js +7 -1
  48. package/build/lib/driver/SerialAPICommandMachine.js.map +1 -1
  49. package/build/lib/driver/StateMachineShared.d.ts +2 -2
  50. package/build/lib/driver/StateMachineShared.d.ts.map +1 -1
  51. package/build/lib/driver/TransactionMachine.d.ts +3 -3
  52. package/build/lib/driver/TransactionMachine.d.ts.map +1 -1
  53. package/build/lib/driver/TransportServiceMachine.d.ts +4 -4
  54. package/build/lib/driver/TransportServiceMachine.d.ts.map +1 -1
  55. package/build/lib/driver/ZWaveOptions.d.ts +12 -1
  56. package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
  57. package/build/lib/node/Node.d.ts +3 -3
  58. package/build/lib/node/Node.d.ts.map +1 -1
  59. package/build/lib/node/Node.js +65 -14
  60. package/build/lib/node/Node.js.map +1 -1
  61. package/build/lib/node/NodeReadyMachine.d.ts +3 -3
  62. package/build/lib/node/NodeReadyMachine.d.ts.map +1 -1
  63. package/build/lib/node/NodeStatusMachine.d.ts +3 -3
  64. package/build/lib/node/NodeStatusMachine.d.ts.map +1 -1
  65. package/build/lib/node/_Types.d.ts +21 -21
  66. package/build/lib/node/_Types.d.ts.map +1 -1
  67. package/build/lib/serialapi/application/ApplicationUpdateRequest.d.ts +14 -10
  68. package/build/lib/serialapi/application/ApplicationUpdateRequest.d.ts.map +1 -1
  69. package/build/lib/serialapi/application/ApplicationUpdateRequest.js +27 -16
  70. package/build/lib/serialapi/application/ApplicationUpdateRequest.js.map +1 -1
  71. package/build/lib/serialapi/network-mgmt/RequestNodeInfoMessages.d.ts +3 -0
  72. package/build/lib/serialapi/network-mgmt/RequestNodeInfoMessages.d.ts.map +1 -1
  73. package/build/lib/serialapi/network-mgmt/RequestNodeInfoMessages.js +12 -0
  74. package/build/lib/serialapi/network-mgmt/RequestNodeInfoMessages.js.map +1 -1
  75. package/build/lib/serialapi/nvm/FirmwareUpdateNVMMessages.d.ts +96 -0
  76. package/build/lib/serialapi/nvm/FirmwareUpdateNVMMessages.d.ts.map +1 -0
  77. package/build/lib/serialapi/nvm/FirmwareUpdateNVMMessages.js +326 -0
  78. package/build/lib/serialapi/nvm/FirmwareUpdateNVMMessages.js.map +1 -0
  79. package/build/lib/serialapi/nvm/GetNVMIdMessages.d.ts +1 -1
  80. package/build/lib/serialapi/nvm/GetNVMIdMessages.d.ts.map +1 -1
  81. package/build/lib/serialapi/transport/SendDataShared.d.ts +3 -3
  82. package/build/lib/serialapi/transport/SendDataShared.d.ts.map +1 -1
  83. package/package.json +15 -15
@@ -18,6 +18,7 @@ const shared_1 = require("@zwave-js/shared");
18
18
  const arrays_1 = require("alcalzone-shared/arrays");
19
19
  const async_1 = require("alcalzone-shared/async");
20
20
  const deferred_promise_1 = require("alcalzone-shared/deferred-promise");
21
+ const math_1 = require("alcalzone-shared/math");
21
22
  const typeguards_1 = require("alcalzone-shared/typeguards");
22
23
  const crypto_1 = __importDefault(require("crypto"));
23
24
  const semver_1 = __importDefault(require("semver"));
@@ -27,6 +28,7 @@ const DeviceClass_1 = require("../node/DeviceClass");
27
28
  const Node_1 = require("../node/Node");
28
29
  const VirtualNode_1 = require("../node/VirtualNode");
29
30
  const _Types_1 = require("../node/_Types");
31
+ const ApplicationUpdateRequest_1 = require("../serialapi/application/ApplicationUpdateRequest");
30
32
  const GetControllerCapabilitiesMessages_1 = require("../serialapi/capability/GetControllerCapabilitiesMessages");
31
33
  const GetControllerVersionMessages_1 = require("../serialapi/capability/GetControllerVersionMessages");
32
34
  const GetProtocolVersionMessages_1 = require("../serialapi/capability/GetProtocolVersionMessages");
@@ -56,6 +58,7 @@ const ExtNVMReadLongBufferMessages_1 = require("../serialapi/nvm/ExtNVMReadLongB
56
58
  const ExtNVMReadLongByteMessages_1 = require("../serialapi/nvm/ExtNVMReadLongByteMessages");
57
59
  const ExtNVMWriteLongBufferMessages_1 = require("../serialapi/nvm/ExtNVMWriteLongBufferMessages");
58
60
  const ExtNVMWriteLongByteMessages_1 = require("../serialapi/nvm/ExtNVMWriteLongByteMessages");
61
+ const FirmwareUpdateNVMMessages_1 = require("../serialapi/nvm/FirmwareUpdateNVMMessages");
59
62
  const GetNVMIdMessages_1 = require("../serialapi/nvm/GetNVMIdMessages");
60
63
  const NVMOperationsMessages_1 = require("../serialapi/nvm/NVMOperationsMessages");
61
64
  const _Types_2 = require("../serialapi/_Types");
@@ -66,9 +69,10 @@ const Inclusion_1 = require("./Inclusion");
66
69
  const NodeInformationFrame_1 = require("./NodeInformationFrame");
67
70
  const utils_1 = require("./utils");
68
71
  const ZWaveSDKVersions_1 = require("./ZWaveSDKVersions");
72
+ const _Types_3 = require("./_Types");
69
73
  let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
70
74
  /** @internal */
71
- constructor(driver) {
75
+ constructor(driver, bootloaderOnly = false) {
72
76
  super();
73
77
  this.driver = driver;
74
78
  this._healNetworkActive = false;
@@ -76,9 +80,13 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
76
80
  this._smartStartEnabled = false;
77
81
  this._includeController = false;
78
82
  this._healNetworkProgress = new Map();
83
+ this._firmwareUpdateInProgress = false;
79
84
  this._nodes = (0, shared_1.createThrowingMap)((nodeId) => {
80
85
  throw new core_1.ZWaveError(`Node ${nodeId} was not found!`, core_1.ZWaveErrorCodes.Controller_NodeNotFound);
81
86
  });
87
+ // Limit interaction with the controller in bootloader-only mode
88
+ if (bootloaderOnly)
89
+ return;
82
90
  // register message handlers
83
91
  driver.registerRequestHandler(serial_1.FunctionType.AddNodeToNetwork, this.handleAddNodeStatusReport.bind(this));
84
92
  driver.registerRequestHandler(serial_1.FunctionType.RemoveNodeFromNetwork, this.handleRemoveNodeStatusReport.bind(this));
@@ -87,6 +95,9 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
87
95
  get type() {
88
96
  return this._type;
89
97
  }
98
+ get protocolVersion() {
99
+ return this._protocolVersion;
100
+ }
90
101
  get sdkVersion() {
91
102
  return this._sdkVersion;
92
103
  }
@@ -130,32 +141,28 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
130
141
  if (this._sdkVersion === undefined) {
131
142
  return undefined;
132
143
  }
133
- const sdkVersion = (0, ZWaveSDKVersions_1.protocolVersionToSDKVersion)(this._sdkVersion);
134
- return semver_1.default.gt((0, shared_1.padVersion)(sdkVersion), (0, shared_1.padVersion)(version));
144
+ return semver_1.default.gt((0, shared_1.padVersion)(this._sdkVersion), (0, shared_1.padVersion)(version));
135
145
  }
136
146
  /** Checks if the SDK version is greater than or equal to the given one */
137
147
  sdkVersionGte(version) {
138
148
  if (this._sdkVersion === undefined) {
139
149
  return undefined;
140
150
  }
141
- const sdkVersion = (0, ZWaveSDKVersions_1.protocolVersionToSDKVersion)(this._sdkVersion);
142
- return semver_1.default.gte((0, shared_1.padVersion)(sdkVersion), (0, shared_1.padVersion)(version));
151
+ return semver_1.default.gte((0, shared_1.padVersion)(this._sdkVersion), (0, shared_1.padVersion)(version));
143
152
  }
144
153
  /** Checks if the SDK version is lower than the given one */
145
154
  sdkVersionLt(version) {
146
155
  if (this._sdkVersion === undefined) {
147
156
  return undefined;
148
157
  }
149
- const sdkVersion = (0, ZWaveSDKVersions_1.protocolVersionToSDKVersion)(this._sdkVersion);
150
- return semver_1.default.lt((0, shared_1.padVersion)(sdkVersion), (0, shared_1.padVersion)(version));
158
+ return semver_1.default.lt((0, shared_1.padVersion)(this._sdkVersion), (0, shared_1.padVersion)(version));
151
159
  }
152
160
  /** Checks if the SDK version is lower than or equal to the given one */
153
161
  sdkVersionLte(version) {
154
162
  if (this._sdkVersion === undefined) {
155
163
  return undefined;
156
164
  }
157
- const sdkVersion = (0, ZWaveSDKVersions_1.protocolVersionToSDKVersion)(this._sdkVersion);
158
- return semver_1.default.lte((0, shared_1.padVersion)(sdkVersion), (0, shared_1.padVersion)(version));
165
+ return semver_1.default.lte((0, shared_1.padVersion)(this._sdkVersion), (0, shared_1.padVersion)(version));
159
166
  }
160
167
  get manufacturerId() {
161
168
  return this._manufacturerId;
@@ -219,6 +226,10 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
219
226
  set supportsSoftReset(value) {
220
227
  this.driver.cacheSet(NetworkCache_1.cacheKeys.controller.supportsSoftReset, value);
221
228
  }
229
+ /** Which RF region the controller is currently set to, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setRFRegion}. */
230
+ get rfRegion() {
231
+ return this._rfRegion;
232
+ }
222
233
  /** A dictionary of the nodes connected to this controller */
223
234
  get nodes() {
224
235
  return this._nodes;
@@ -416,18 +427,16 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
416
427
  const version = await this.driver.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this.driver), {
417
428
  supportCheck: false,
418
429
  });
419
- this._sdkVersion = version.libraryVersion;
430
+ this._protocolVersion = version.libraryVersion;
420
431
  this._type = version.controllerType;
421
432
  this.driver.controllerLog.print(`received version info:
422
433
  controller type: ${(0, shared_1.getEnumMemberName)(_Types_2.ZWaveLibraryTypes, this._type)}
423
- library version: ${this._sdkVersion}`);
434
+ library version: ${this._protocolVersion}`);
424
435
  // If supported, get more fine-grained version info
425
436
  if (this.isFunctionSupported(serial_1.FunctionType.GetProtocolVersion)) {
426
437
  this.driver.controllerLog.print(`querying protocol version info...`);
427
438
  const protocol = await this.driver.sendMessage(new GetProtocolVersionMessages_1.GetProtocolVersionRequest(this.driver));
428
- // Overwrite the SDK version with the more fine grained protocol version. We can assume this to be
429
- // valid for 7.x firmwares, where SDK and protocol version are the same.
430
- this._sdkVersion = protocol.protocolVersion;
439
+ this._protocolVersion = protocol.protocolVersion;
431
440
  let message = `received protocol version info:
432
441
  protocol type: ${(0, shared_1.getEnumMemberName)(core_1.ProtocolType, protocol.protocolType)}
433
442
  protocol version: ${protocol.protocolVersion}`;
@@ -441,6 +450,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
441
450
  }
442
451
  this.driver.controllerLog.print(message);
443
452
  }
453
+ // The SDK version cannot be queried directly, but we can deduce it from the protocol version
454
+ this._sdkVersion = (0, ZWaveSDKVersions_1.protocolVersionToSDKVersion)(this._protocolVersion);
444
455
  this.driver.controllerLog.print(`supported Z-Wave features: ${Object.keys(Features_1.ZWaveFeature)
445
456
  .filter((k) => /^\d+$/.test(k))
446
457
  .map((k) => parseInt(k))
@@ -484,6 +495,17 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
484
495
  }));
485
496
  this.driver.controllerLog.print(`Enabling TX status report ${resp.success ? "successful" : "failed"}...`);
486
497
  }
498
+ // Also query the controller's current RF region if possible
499
+ if (this.isSerialAPISetupCommandSupported(SerialAPISetupMessages_1.SerialAPISetupCommand.GetRFRegion)) {
500
+ this.driver.controllerLog.print(`Querying configured RF region...`);
501
+ const resp = await this.getRFRegion().catch(() => undefined);
502
+ if (resp != undefined) {
503
+ this.driver.controllerLog.print(`The controller is using RF region ${(0, shared_1.getEnumMemberName)(core_1.RFRegion, resp)}`);
504
+ }
505
+ else {
506
+ this.driver.controllerLog.print(`Querying the RF region failed!`, "warn");
507
+ }
508
+ }
487
509
  // find the SUC
488
510
  this.driver.controllerLog.print(`finding SUC...`);
489
511
  const suc = await this.driver.sendMessage(new GetSUCNodeIdMessages_1.GetSUCNodeIdRequest(this.driver), { supportCheck: false });
@@ -569,6 +591,8 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
569
591
  controllerValueDB.setValue(cc_1.VersionCCValues.firmwareVersions.id, [
570
592
  this._firmwareVersion,
571
593
  ]);
594
+ controllerValueDB.setMetadata(cc_1.VersionCCValues.zWaveProtocolVersion.id, cc_1.VersionCCValues.zWaveProtocolVersion.meta);
595
+ controllerValueDB.setValue(cc_1.VersionCCValues.zWaveProtocolVersion.id, this._protocolVersion);
572
596
  controllerValueDB.setMetadata(cc_1.VersionCCValues.sdkVersion.id, cc_1.VersionCCValues.sdkVersion.meta);
573
597
  controllerValueDB.setValue(cc_1.VersionCCValues.sdkVersion.id, this._sdkVersion);
574
598
  if (this.type !== _Types_2.ZWaveLibraryTypes["Bridge Controller"] &&
@@ -964,6 +988,299 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
964
988
  throw e;
965
989
  }
966
990
  }
991
+ /** @internal */
992
+ async handleApplicationUpdateRequest(msg) {
993
+ const nodeId = msg.getNodeId();
994
+ let node;
995
+ if (nodeId != undefined) {
996
+ node = this.nodes.get(nodeId);
997
+ }
998
+ if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestNodeInfoReceived) {
999
+ if (node) {
1000
+ this.driver.controllerLog.logNode(node.id, {
1001
+ message: "Received updated node info",
1002
+ direction: "inbound",
1003
+ });
1004
+ node.updateNodeInfo(msg.nodeInformation);
1005
+ // Tell the send thread that we received a NIF from the node
1006
+ this.driver["sendThread"].send({
1007
+ type: "NIF",
1008
+ nodeId: node.id,
1009
+ });
1010
+ if (node.canSleep &&
1011
+ node.supportsCC(core_1.CommandClasses["Wake Up"])) {
1012
+ // In case this is a sleeping node and there are no messages in the queue, the node may go back to sleep very soon
1013
+ this.driver.debounceSendNodeToSleep(node);
1014
+ }
1015
+ return;
1016
+ }
1017
+ }
1018
+ else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestSmartStartHomeIDReceived) {
1019
+ // the controller is in Smart Start learn mode and a node requests inclusion via Smart Start
1020
+ this.driver.controllerLog.print("Received Smart Start inclusion request");
1021
+ if (this.inclusionState !== Inclusion_1.InclusionState.Idle &&
1022
+ this.inclusionState !== Inclusion_1.InclusionState.SmartStart) {
1023
+ this.driver.controllerLog.print("Controller is busy and cannot handle this inclusion request right now...");
1024
+ return;
1025
+ }
1026
+ // Check if the node is on the provisioning list
1027
+ const provisioningEntry = this.provisioningList.find((entry) => (0, core_1.nwiHomeIdFromDSK)((0, core_1.dskFromString)(entry.dsk)).equals(msg.nwiHomeId));
1028
+ if (!provisioningEntry) {
1029
+ this.driver.controllerLog.print("NWI Home ID not found in provisioning list, ignoring request...");
1030
+ return;
1031
+ }
1032
+ else if (provisioningEntry.status === Inclusion_1.ProvisioningEntryStatus.Inactive) {
1033
+ this.driver.controllerLog.print("The provisioning entry for this node is inactive, ignoring request...");
1034
+ return;
1035
+ }
1036
+ this.driver.controllerLog.print("NWI Home ID found in provisioning list, including node...");
1037
+ try {
1038
+ const result = await this.beginInclusionSmartStart(provisioningEntry);
1039
+ if (!result) {
1040
+ this.driver.controllerLog.print("Smart Start inclusion could not be started", "error");
1041
+ }
1042
+ }
1043
+ catch (e) {
1044
+ this.driver.controllerLog.print(`Smart Start inclusion could not be started: ${(0, shared_1.getErrorMessage)(e)}`, "error");
1045
+ }
1046
+ }
1047
+ else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestNodeRemoved) {
1048
+ // A node was removed by another controller
1049
+ const node = this.nodes.get(msg.nodeId);
1050
+ if (node) {
1051
+ this.driver.controllerLog.logNode(node.id, "was removed from the network by another controller");
1052
+ this.emit("node removed", node, false);
1053
+ }
1054
+ }
1055
+ else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestNodeAdded) {
1056
+ // A node was included by another controller
1057
+ const nodeId = msg.nodeId;
1058
+ const nodeInfo = msg.nodeInformation;
1059
+ this.setInclusionState(Inclusion_1.InclusionState.Busy);
1060
+ const deviceClass = new DeviceClass_1.DeviceClass(this.driver.configManager, nodeInfo.basicDeviceClass, nodeInfo.genericDeviceClass, nodeInfo.specificDeviceClass);
1061
+ const newNode = new Node_1.ZWaveNode(nodeId, this.driver, deviceClass, nodeInfo.supportedCCs, undefined,
1062
+ // Create an empty value DB and specify that it contains no values
1063
+ // to avoid indexing the existing values
1064
+ this.createValueDBForNode(nodeId, new Set()));
1065
+ this._nodes.set(nodeId, newNode);
1066
+ this.emit("node found", {
1067
+ id: nodeId,
1068
+ deviceClass,
1069
+ supportedCCs: nodeInfo.supportedCCs,
1070
+ });
1071
+ this.driver.controllerLog.print(`Node ${newNode.id} was included by another controller:
1072
+ basic device class: ${newNode.deviceClass?.basic.label}
1073
+ generic device class: ${newNode.deviceClass?.generic.label}
1074
+ specific device class: ${newNode.deviceClass?.specific.label}
1075
+ supported CCs: ${nodeInfo.supportedCCs
1076
+ .map((cc) => `\n · ${core_1.CommandClasses[cc]} (${(0, shared_1.num2hex)(cc)})`)
1077
+ .join("")}`);
1078
+ this.driver.controllerLog.logNode(nodeId, "Waiting for initiate command to bootstrap node...");
1079
+ // Handle inclusion in the background
1080
+ process.nextTick(async () => {
1081
+ // If an Inclusion Controller that does not support the Inclusion Controller Command Class includes a
1082
+ // new node in a network, the SIS will never receive an Inclusion Controller Initiate Command. If no
1083
+ // Initiate Command has been received approximately 10 seconds after a new node has been added to a
1084
+ // network, the SIS SHOULD start interviewing the newly included node
1085
+ const initiate = await this.driver
1086
+ .waitForCommand((cc) => cc instanceof cc_1.InclusionControllerCCInitiate &&
1087
+ cc.isSinglecast() &&
1088
+ cc.includedNodeId === nodeId &&
1089
+ cc.step === cc_1.InclusionControllerStep.ProxyInclusion, 10000)
1090
+ .catch(() => undefined);
1091
+ // Assume the device is alive
1092
+ // If it is actually a sleeping device, it will be marked as such later
1093
+ newNode.markAsAlive();
1094
+ let inclCtrlr;
1095
+ let lowSecurity = false;
1096
+ if (initiate) {
1097
+ inclCtrlr = this.nodes.getOrThrow(initiate.nodeId);
1098
+ this.driver.controllerLog.logNode(nodeId, `Initiate command received from node ${inclCtrlr.id}`);
1099
+ // Inclusion is handled by the inclusion controller, which (hopefully) sets the SUC return route
1100
+ newNode.hasSUCReturnRoute = true;
1101
+ // SIS, A, MUST request a Node Info Frame from Joining Node, B
1102
+ const requestedNodeInfo = await newNode
1103
+ .requestNodeInfo()
1104
+ .catch(() => undefined);
1105
+ if (requestedNodeInfo)
1106
+ newNode.updateNodeInfo(requestedNodeInfo);
1107
+ // Perform S0/S2 bootstrapping
1108
+ lowSecurity = (await this.proxyBootstrap(newNode, inclCtrlr)).lowSecurity;
1109
+ }
1110
+ else {
1111
+ // No command received, bootstrap node by ourselves
1112
+ this.driver.controllerLog.logNode(nodeId, "no initiate command received, bootstrapping node...");
1113
+ // Assign SUC return route to make sure the node knows where to get its routes from
1114
+ newNode.hasSUCReturnRoute = await this.assignSUCReturnRoute(newNode.id);
1115
+ // Include using the default inclusion strategy:
1116
+ // * Use S2 if possible,
1117
+ // * only use S0 if necessary,
1118
+ // * use no encryption otherwise
1119
+ if (newNode.supportsCC(core_1.CommandClasses["Security 2"])) {
1120
+ await this.secureBootstrapS2(newNode);
1121
+ const actualSecurityClass = newNode.getHighestSecurityClass();
1122
+ if (actualSecurityClass == undefined ||
1123
+ actualSecurityClass <
1124
+ core_1.SecurityClass.S2_Unauthenticated) {
1125
+ lowSecurity = true;
1126
+ }
1127
+ }
1128
+ else if (newNode.supportsCC(core_1.CommandClasses.Security) &&
1129
+ (deviceClass.specific ?? deviceClass.generic)
1130
+ .requiresSecurity) {
1131
+ await this.secureBootstrapS0(newNode);
1132
+ const actualSecurityClass = newNode.getHighestSecurityClass();
1133
+ if (actualSecurityClass == undefined ||
1134
+ actualSecurityClass < core_1.SecurityClass.S0_Legacy) {
1135
+ lowSecurity = true;
1136
+ }
1137
+ }
1138
+ else {
1139
+ // Remember that no security classes were granted
1140
+ for (const secClass of core_1.securityClassOrder) {
1141
+ newNode.securityClasses.set(secClass, false);
1142
+ }
1143
+ }
1144
+ }
1145
+ // Bootstrap the node's lifelines, so it knows where the controller is
1146
+ await this.bootstrapLifelineAndWakeup(newNode);
1147
+ // We're done adding this node, notify listeners
1148
+ const result = {};
1149
+ if (lowSecurity)
1150
+ result.lowSecurity = true;
1151
+ this.emit("node added", newNode, result);
1152
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1153
+ if (inclCtrlr && initiate) {
1154
+ const inclCtrlrId = inclCtrlr.id;
1155
+ const step = initiate.step;
1156
+ newNode.once("ready", () => {
1157
+ this.driver.controllerLog.logNode(nodeId, `Notifying node ${inclCtrlrId} of finished inclusion`);
1158
+ void inclCtrlr.commandClasses["Inclusion Controller"]
1159
+ .completeStep(step, cc_1.InclusionControllerStatus.OK)
1160
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1161
+ .catch(() => { });
1162
+ });
1163
+ }
1164
+ });
1165
+ }
1166
+ }
1167
+ /**
1168
+ * @internal
1169
+ * Handles replace requests from an inclusion controller
1170
+ */
1171
+ handleInclusionControllerCCInitiateReplace(initiate) {
1172
+ if (initiate.step !== cc_1.InclusionControllerStep.ProxyInclusionReplace) {
1173
+ throw new core_1.ZWaveError("Expected an inclusion controller replace request, but got a different step", core_1.ZWaveErrorCodes.Argument_Invalid);
1174
+ }
1175
+ this.setInclusionState(Inclusion_1.InclusionState.Busy);
1176
+ const inclCtrlr = this.nodes.getOrThrow(initiate.nodeId);
1177
+ const replacedNodeId = initiate.includedNodeId;
1178
+ const oldNode = this.nodes.get(replacedNodeId);
1179
+ if (oldNode) {
1180
+ this.emit("node removed", oldNode, true);
1181
+ this._nodes.delete(oldNode.id);
1182
+ }
1183
+ // Create a fresh node instance and forget the old one
1184
+ const newNode = new Node_1.ZWaveNode(replacedNodeId, this.driver, undefined, undefined, undefined,
1185
+ // Create an empty value DB and specify that it contains no values
1186
+ // to avoid indexing the existing values
1187
+ this.createValueDBForNode(replacedNodeId, new Set()));
1188
+ this._nodes.set(newNode.id, newNode);
1189
+ this.emit("node found", {
1190
+ id: newNode.id,
1191
+ });
1192
+ // Assume the device is alive
1193
+ // If it is actually a sleeping device, it will be marked as such later
1194
+ newNode.markAsAlive();
1195
+ // Inclusion is handled by the inclusion controller, which (hopefully) sets the SUC return route
1196
+ newNode.hasSUCReturnRoute = true;
1197
+ // Handle communication with the node in the background
1198
+ process.nextTick(async () => {
1199
+ // SIS, A, MUST request a Node Info Frame from Joining Node, B
1200
+ const requestedNodeInfo = await newNode
1201
+ .requestNodeInfo()
1202
+ .catch(() => undefined);
1203
+ if (requestedNodeInfo) {
1204
+ newNode.updateNodeInfo(requestedNodeInfo);
1205
+ // TODO: Check if this stuff works for a normal replace too
1206
+ const deviceClass = new DeviceClass_1.DeviceClass(this.driver.configManager, requestedNodeInfo.basicDeviceClass, requestedNodeInfo.genericDeviceClass, requestedNodeInfo.specificDeviceClass);
1207
+ newNode["applyDeviceClass"](deviceClass);
1208
+ }
1209
+ // Perform S0/S2 bootstrapping
1210
+ const lowSecurity = (await this.proxyBootstrap(newNode, inclCtrlr))
1211
+ .lowSecurity;
1212
+ // Bootstrap the node's lifelines, so it knows where the controller is
1213
+ await this.bootstrapLifelineAndWakeup(newNode);
1214
+ // We're done adding this node, notify listeners
1215
+ const result = {};
1216
+ if (lowSecurity)
1217
+ result.lowSecurity = true;
1218
+ this.emit("node added", newNode, result);
1219
+ this.setInclusionState(Inclusion_1.InclusionState.Idle);
1220
+ // And notify the inclusion controller after we're done interviewing
1221
+ newNode.once("ready", () => {
1222
+ this.driver.controllerLog.logNode(inclCtrlr.nodeId, `Notifying inclusion controller of finished inclusion`);
1223
+ void inclCtrlr.commandClasses["Inclusion Controller"]
1224
+ .completeStep(initiate.step, cc_1.InclusionControllerStatus.OK)
1225
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1226
+ .catch(() => { });
1227
+ });
1228
+ });
1229
+ }
1230
+ /**
1231
+ * Handles bootstrapping the security keys for a node that was included by an inclusion controller
1232
+ */
1233
+ async proxyBootstrap(newNode, inclCtrlr) {
1234
+ // This part is to be done before the interview
1235
+ const deviceClass = newNode.deviceClass;
1236
+ let lowSecurity = false;
1237
+ // Include using the default inclusion strategy:
1238
+ // * Use S2 if possible,
1239
+ // * only use S0 if necessary,
1240
+ // * use no encryption otherwise
1241
+ if (newNode.supportsCC(core_1.CommandClasses["Security 2"])) {
1242
+ await this.secureBootstrapS2(newNode);
1243
+ const actualSecurityClass = newNode.getHighestSecurityClass();
1244
+ if (actualSecurityClass == undefined ||
1245
+ actualSecurityClass < core_1.SecurityClass.S2_Unauthenticated) {
1246
+ lowSecurity = true;
1247
+ }
1248
+ }
1249
+ else if (newNode.supportsCC(core_1.CommandClasses.Security) &&
1250
+ (deviceClass.specific ?? deviceClass.generic).requiresSecurity) {
1251
+ // S0 bootstrapping is deferred to the inclusion controller
1252
+ this.driver.controllerLog.logNode(newNode.id, `Waiting for node ${inclCtrlr.id} to perform S0 bootstrapping...`);
1253
+ await inclCtrlr.commandClasses["Inclusion Controller"].initiateStep(newNode.id, cc_1.InclusionControllerStep.S0Inclusion);
1254
+ // Wait 60s for the S0 bootstrapping to complete
1255
+ const s0result = await this.driver
1256
+ .waitForCommand((cc) => cc.nodeId === inclCtrlr.id &&
1257
+ cc instanceof cc_1.InclusionControllerCCComplete &&
1258
+ cc.step === cc_1.InclusionControllerStep.S0Inclusion, 60000)
1259
+ .catch(() => undefined);
1260
+ this.driver.controllerLog.logNode(newNode.id, `S0 bootstrapping ${s0result == undefined
1261
+ ? "timed out"
1262
+ : s0result.status === cc_1.InclusionControllerStatus.OK
1263
+ ? "succeeded"
1264
+ : "failed"}`);
1265
+ // When bootstrapping with S0, no other keys are granted
1266
+ for (const secClass of core_1.securityClassOrder) {
1267
+ if (secClass !== core_1.SecurityClass.S0_Legacy) {
1268
+ newNode.securityClasses.set(secClass, false);
1269
+ }
1270
+ }
1271
+ // Whether the S0 key is granted depends on the result
1272
+ // received from the inclusion controller
1273
+ newNode.securityClasses.set(core_1.SecurityClass.S0_Legacy, s0result?.status === cc_1.InclusionControllerStatus.OK);
1274
+ lowSecurity = !newNode.hasSecurityClass(core_1.SecurityClass.S0_Legacy);
1275
+ }
1276
+ else {
1277
+ // Remember that no security classes were granted
1278
+ for (const secClass of core_1.securityClassOrder) {
1279
+ newNode.securityClasses.set(secClass, false);
1280
+ }
1281
+ }
1282
+ return { lowSecurity };
1283
+ }
967
1284
  async secureBootstrapS0(node, assumeSupported = false) {
968
1285
  // When bootstrapping with S0, no other keys are granted
969
1286
  for (const secClass of core_1.securityClassOrder) {
@@ -1237,13 +1554,21 @@ let ZWaveController = class ZWaveController extends shared_1.TypedEventEmitter {
1237
1554
  const dsk = (0, core_1.dskToString)(nodePublicKey.slice(0, 16)).slice(5);
1238
1555
  // The time the user has to enter the PIN is limited by the timeout TAI2
1239
1556
  const tai2RemainingMs = cc_1.inclusionTimeouts.TAI2 - (Date.now() - timerStartTAI2);
1240
- const pinResult = await Promise.race([
1241
- (0, async_1.wait)(tai2RemainingMs, true).then(() => false),
1242
- userCallbacks
1243
- .validateDSKAndEnterPIN(dsk)
1244
- // ignore errors in application callbacks
1245
- .catch(() => false),
1246
- ]);
1557
+ let pinResult;
1558
+ if ("dsk" in inclusionOptions &&
1559
+ typeof inclusionOptions.dsk === "string" &&
1560
+ (0, core_1.isValidDSK)(inclusionOptions.dsk)) {
1561
+ pinResult = inclusionOptions.dsk.slice(0, 5);
1562
+ }
1563
+ else {
1564
+ pinResult = await Promise.race([
1565
+ (0, async_1.wait)(tai2RemainingMs, true).then(() => false),
1566
+ userCallbacks
1567
+ .validateDSKAndEnterPIN(dsk)
1568
+ // ignore errors in application callbacks
1569
+ .catch(() => false),
1570
+ ]);
1571
+ }
1247
1572
  if (typeof pinResult !== "string" ||
1248
1573
  !/^\d{5}$/.test(pinResult)) {
1249
1574
  // There was a timeout, the user did not confirm the DSK or entered an invalid PIN
@@ -2442,6 +2767,7 @@ ${associatedNodes.join(", ")}`,
2442
2767
  }
2443
2768
  if (result.success)
2444
2769
  await this.driver.trySoftReset();
2770
+ this._rfRegion = region;
2445
2771
  return result.success;
2446
2772
  }
2447
2773
  /** Request the current RF region configured at the Z-Wave API Module */
@@ -2450,6 +2776,7 @@ ${associatedNodes.join(", ")}`,
2450
2776
  if (result instanceof SerialAPISetupMessages_1.SerialAPISetup_CommandUnsupportedResponse) {
2451
2777
  throw new core_1.ZWaveError(`Your hardware does not support getting the RF region!`, core_1.ZWaveErrorCodes.Driver_NotSupported);
2452
2778
  }
2779
+ this._rfRegion = result.region;
2453
2780
  return result.region;
2454
2781
  }
2455
2782
  /** Configure the Powerlevel setting of the Z-Wave API */
@@ -2586,6 +2913,67 @@ ${associatedNodes.join(", ")}`,
2586
2913
  return false;
2587
2914
  }
2588
2915
  }
2916
+ /**
2917
+ * **Z-Wave 500 series only**
2918
+ *
2919
+ * Initialize the Firmware Update functionality and determine if the firmware can be updated.
2920
+ */
2921
+ async firmwareUpdateNVMInit() {
2922
+ const ret = await this.driver.sendMessage(new FirmwareUpdateNVMMessages_1.FirmwareUpdateNVM_InitRequest(this.driver));
2923
+ return ret.supported;
2924
+ }
2925
+ /**
2926
+ * **Z-Wave 500 series only**
2927
+ *
2928
+ * Set the NEWIMAGE marker in the NVM (to the given value), which is used to signal that a new firmware image is present
2929
+ */
2930
+ async firmwareUpdateNVMSetNewImage(value = true) {
2931
+ await this.driver.sendMessage(new FirmwareUpdateNVMMessages_1.FirmwareUpdateNVM_SetNewImageRequest(this.driver, {
2932
+ newImage: value,
2933
+ }));
2934
+ }
2935
+ /**
2936
+ * **Z-Wave 500 series only**
2937
+ *
2938
+ * Return the value of the NEWIMAGE marker in the NVM, which is used to signal that a new firmware image is present
2939
+ */
2940
+ async firmwareUpdateNVMGetNewImage() {
2941
+ const ret = await this.driver.sendMessage(new FirmwareUpdateNVMMessages_1.FirmwareUpdateNVM_GetNewImageRequest(this.driver));
2942
+ return ret.newImage;
2943
+ }
2944
+ /**
2945
+ * **Z-Wave 500 series only**
2946
+ *
2947
+ * Calculates the CRC-16 for the specified block of data in the NVM
2948
+ */
2949
+ async firmwareUpdateNVMUpdateCRC16(offset, blockLength, crcSeed) {
2950
+ const ret = await this.driver.sendMessage(new FirmwareUpdateNVMMessages_1.FirmwareUpdateNVM_UpdateCRC16Request(this.driver, {
2951
+ offset,
2952
+ blockLength,
2953
+ crcSeed,
2954
+ }));
2955
+ return ret.crc16;
2956
+ }
2957
+ /**
2958
+ * **Z-Wave 500 series only**
2959
+ *
2960
+ * Writes the given data into the firmware update region of the NVM.
2961
+ */
2962
+ async firmwareUpdateNVMWrite(offset, buffer) {
2963
+ await this.driver.sendMessage(new FirmwareUpdateNVMMessages_1.FirmwareUpdateNVM_WriteRequest(this.driver, {
2964
+ offset,
2965
+ buffer,
2966
+ }));
2967
+ }
2968
+ /**
2969
+ * **Z-Wave 500 series only**
2970
+ *
2971
+ * Checks if the firmware present in the NVM is valid
2972
+ */
2973
+ async firmwareUpdateNVMIsValidCRC16() {
2974
+ const ret = await this.driver.sendMessage(new FirmwareUpdateNVMMessages_1.FirmwareUpdateNVM_IsValidCRC16Request(this.driver));
2975
+ return ret.isValid;
2976
+ }
2589
2977
  /**
2590
2978
  * **Z-Wave 500 series only**
2591
2979
  *
@@ -3035,6 +3423,7 @@ ${associatedNodes.join(", ")}`,
3035
3423
  productType,
3036
3424
  productId,
3037
3425
  firmwareVersion,
3426
+ rfRegion: this.rfRegion,
3038
3427
  }, {
3039
3428
  userAgent: this.driver.getUserAgentStringWithComponents(options?.additionalUserAgentComponents),
3040
3429
  apiKey: options?.apiKey ??
@@ -3070,6 +3459,18 @@ ${associatedNodes.join(", ")}`,
3070
3459
  * @deprecated Use {@link firmwareUpdateOTA} instead, which properly handles multi-target updates
3071
3460
  */
3072
3461
  async beginOTAFirmwareUpdate(nodeId, update) {
3462
+ // Don't let two firmware updates happen in parallel
3463
+ if (this.isAnyOTAFirmwareUpdateInProgress()) {
3464
+ const message = `Failed to start the update: A firmware update is already in progress on this network!`;
3465
+ this.driver.controllerLog.print(message, "error");
3466
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
3467
+ }
3468
+ // Don't allow updating firmware when the controller is currently updating its own firmware
3469
+ if (this.isFirmwareUpdateInProgress()) {
3470
+ const message = `Failed to start the update: The controller is currently being updated!`;
3471
+ this.driver.controllerLog.print(message, "error");
3472
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
3473
+ }
3073
3474
  const node = this.nodes.getOrThrow(nodeId);
3074
3475
  let firmware;
3075
3476
  try {
@@ -3111,6 +3512,18 @@ ${associatedNodes.join(", ")}`,
3111
3512
  if (updates.length === 0) {
3112
3513
  throw new core_1.ZWaveError(`At least one update must be provided`, core_1.ZWaveErrorCodes.Argument_Invalid);
3113
3514
  }
3515
+ // Don't let two firmware updates happen in parallel
3516
+ if (this.isAnyOTAFirmwareUpdateInProgress()) {
3517
+ const message = `Failed to start the update: A firmware update is already in progress on this network!`;
3518
+ this.driver.controllerLog.print(message, "error");
3519
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
3520
+ }
3521
+ // Don't allow updating firmware when the controller is currently updating its own firmware
3522
+ if (this.isFirmwareUpdateInProgress()) {
3523
+ const message = `Failed to start the update: The controller is currently being updated!`;
3524
+ this.driver.controllerLog.print(message, "error");
3525
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
3526
+ }
3114
3527
  const node = this.nodes.getOrThrow(nodeId);
3115
3528
  this.driver.controllerLog.logNode(nodeId, `OTA firmware update started, downloading ${updates.length} updates...`);
3116
3529
  const loglevel = this.driver.getLogConfig().level;
@@ -3154,6 +3567,242 @@ ${associatedNodes.join(", ")}`,
3154
3567
  this.driver.controllerLog.logNode(nodeId, `All updates downloaded, installing...`);
3155
3568
  return node.updateFirmware(firmwares);
3156
3569
  }
3570
+ /**
3571
+ * Returns whether a firmware update is in progress for the controller.
3572
+ */
3573
+ isFirmwareUpdateInProgress() {
3574
+ return this._firmwareUpdateInProgress;
3575
+ }
3576
+ /**
3577
+ * Updates the firmware of the controller using the given firmware file.
3578
+ *
3579
+ * The return value indicates whether the update was successful.
3580
+ * **WARNING:** After a successful update, the Z-Wave driver will destroy itself so it can be restarted.
3581
+ *
3582
+ * **WARNING:** A failure during this process may put your controller in recovery mode, rendering it unusable until a correct firmware image is uploaded. Use at your own risk!
3583
+ */
3584
+ async firmwareUpdateOTW(data) {
3585
+ // Don't let two firmware updates happen in parallel
3586
+ if (this.isAnyOTAFirmwareUpdateInProgress()) {
3587
+ const message = `Failed to start the update: A firmware update is already in progress on this network!`;
3588
+ this.driver.controllerLog.print(message, "error");
3589
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.OTW_Update_Busy);
3590
+ }
3591
+ // Don't allow updating firmware when the controller is currently updating its own firmware
3592
+ if (this.isFirmwareUpdateInProgress()) {
3593
+ const message = `Failed to start the update: The controller is currently being updated!`;
3594
+ this.driver.controllerLog.print(message, "error");
3595
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.OTW_Update_Busy);
3596
+ }
3597
+ if (this.sdkVersionGte("7.0")) {
3598
+ return this.firmwareUpdateOTW700(data);
3599
+ }
3600
+ else if (this.sdkVersionGte("6.50.0") &&
3601
+ this.supportedFunctionTypes?.includes(serial_1.FunctionType.FirmwareUpdateNVM)) {
3602
+ // This is 500 series
3603
+ const wasUpdated = await this.firmwareUpdateOTW500(data);
3604
+ if (wasUpdated) {
3605
+ // After updating the firmware on 500 series sticks, we MUST soft-reset them
3606
+ await this.driver.softResetAndRestart("Activating new firmware and restarting driver...", "Controller firmware updates require a driver restart!");
3607
+ }
3608
+ return wasUpdated;
3609
+ }
3610
+ else {
3611
+ throw new core_1.ZWaveError(`Firmware updates are not supported on this controller`, core_1.ZWaveErrorCodes.Controller_NotSupported);
3612
+ }
3613
+ }
3614
+ async firmwareUpdateOTW500(data) {
3615
+ this._firmwareUpdateInProgress = true;
3616
+ let turnedRadioOff = false;
3617
+ try {
3618
+ this.driver.controllerLog.print("Beginning firmware update");
3619
+ const canUpdate = await this.firmwareUpdateNVMInit();
3620
+ if (!canUpdate) {
3621
+ this.driver.controllerLog.print("OTW update failed: This controller does not support firmware updates", "error");
3622
+ this.emit("firmware update finished", {
3623
+ success: false,
3624
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_NotSupported,
3625
+ });
3626
+ return false;
3627
+ }
3628
+ // Avoid interruption by incoming messages
3629
+ await this.toggleRF(false);
3630
+ turnedRadioOff = true;
3631
+ // Upload the firmware data
3632
+ const BLOCK_SIZE = 64;
3633
+ const numFragments = Math.ceil(data.length / BLOCK_SIZE);
3634
+ for (let fragment = 0; fragment < numFragments; fragment++) {
3635
+ const fragmentData = data.slice(fragment * BLOCK_SIZE, (fragment + 1) * BLOCK_SIZE);
3636
+ await this.firmwareUpdateNVMWrite(fragment * BLOCK_SIZE, fragmentData);
3637
+ // This progress is technically too low, but we can keep 100% for after CRC checking this way
3638
+ const progress = {
3639
+ sentFragments: fragment,
3640
+ totalFragments: numFragments,
3641
+ progress: (0, math_1.roundTo)((fragment / numFragments) * 100, 2),
3642
+ };
3643
+ this.emit("firmware update progress", progress);
3644
+ }
3645
+ // Check if a valid image was written
3646
+ const isValidCRC = await this.firmwareUpdateNVMIsValidCRC16();
3647
+ if (!isValidCRC) {
3648
+ this.driver.controllerLog.print("OTW update failed: The firmware image is invalid", "error");
3649
+ this.emit("firmware update finished", {
3650
+ success: false,
3651
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_Aborted,
3652
+ });
3653
+ return false;
3654
+ }
3655
+ this.emit("firmware update progress", {
3656
+ sentFragments: numFragments,
3657
+ totalFragments: numFragments,
3658
+ progress: 100,
3659
+ });
3660
+ // Enable the image
3661
+ await this.firmwareUpdateNVMSetNewImage();
3662
+ this.driver.controllerLog.print("Firmware update succeeded");
3663
+ this.emit("firmware update finished", {
3664
+ success: true,
3665
+ status: _Types_3.ControllerFirmwareUpdateStatus.OK,
3666
+ });
3667
+ return true;
3668
+ }
3669
+ finally {
3670
+ this._firmwareUpdateInProgress = false;
3671
+ if (turnedRadioOff)
3672
+ await this.toggleRF(true);
3673
+ }
3674
+ }
3675
+ async firmwareUpdateOTW700(data) {
3676
+ this._firmwareUpdateInProgress = true;
3677
+ let destroy = false;
3678
+ try {
3679
+ if (!this.driver.isInBootloader()) {
3680
+ await this.driver.enterBootloader();
3681
+ }
3682
+ // Start the update process
3683
+ this.driver.controllerLog.print("Beginning firmware upload");
3684
+ await this.driver.bootloader.beginUpload();
3685
+ // Wait for the bootloader to accept fragments
3686
+ try {
3687
+ await this.driver.waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.Message &&
3688
+ c.message === "begin upload", 5000);
3689
+ await this.driver.waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.FlowControl &&
3690
+ c.command === serial_1.XModemMessageHeaders.C, 1000);
3691
+ }
3692
+ catch {
3693
+ this.driver.controllerLog.print("OTW update failed: Expected response not received from the bootloader", "error");
3694
+ this.emit("firmware update finished", {
3695
+ success: false,
3696
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_Timeout,
3697
+ });
3698
+ return false;
3699
+ }
3700
+ const BLOCK_SIZE = 128;
3701
+ if (data.length % BLOCK_SIZE !== 0) {
3702
+ // Pad the data to a multiple of BLOCK_SIZE
3703
+ data = Buffer.concat([
3704
+ data,
3705
+ Buffer.alloc(BLOCK_SIZE - (data.length % BLOCK_SIZE), 0xff),
3706
+ ]);
3707
+ }
3708
+ const numFragments = Math.ceil(data.length / BLOCK_SIZE);
3709
+ let aborted = false;
3710
+ transfer: for (let fragment = 1; fragment <= numFragments; fragment++) {
3711
+ const fragmentData = data.slice((fragment - 1) * BLOCK_SIZE, fragment * BLOCK_SIZE);
3712
+ retry: for (let retry = 0; retry < 3; retry++) {
3713
+ await this.driver.bootloader.uploadFragment(fragment, fragmentData);
3714
+ let result;
3715
+ try {
3716
+ result = await this.driver.waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.FlowControl, 1000);
3717
+ }
3718
+ catch (e) {
3719
+ this.driver.controllerLog.print("OTW update failed: The bootloader did not acknowledge the start of transfer.", "error");
3720
+ this.emit("firmware update finished", {
3721
+ success: false,
3722
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_Timeout,
3723
+ });
3724
+ return false;
3725
+ }
3726
+ switch (result.command) {
3727
+ case serial_1.XModemMessageHeaders.ACK: {
3728
+ // The fragment was accepted
3729
+ const progress = {
3730
+ sentFragments: fragment,
3731
+ totalFragments: numFragments,
3732
+ progress: (0, math_1.roundTo)((fragment / numFragments) * 100, 2),
3733
+ };
3734
+ this.emit("firmware update progress", progress);
3735
+ // we've transmitted at least one fragment, so we need to destroy the driver afterwards
3736
+ destroy = true;
3737
+ continue transfer;
3738
+ }
3739
+ case serial_1.XModemMessageHeaders.NAK:
3740
+ // The fragment was rejected, try again
3741
+ continue retry;
3742
+ case serial_1.XModemMessageHeaders.CAN:
3743
+ // The bootloader aborted the update. We'll receive the reason afterwards as a message
3744
+ aborted = true;
3745
+ break transfer;
3746
+ }
3747
+ }
3748
+ this.driver.controllerLog.print("OTW update failed: Maximum retry attempts reached", "error");
3749
+ this.emit("firmware update finished", {
3750
+ success: false,
3751
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_RetryLimitReached,
3752
+ });
3753
+ return false;
3754
+ }
3755
+ if (aborted) {
3756
+ // wait for the reason to craft a good error message
3757
+ const error = await this.driver
3758
+ .waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.Message &&
3759
+ c.message.includes("error 0x"), 1000)
3760
+ .catch(() => undefined);
3761
+ // wait for the menu screen so it doesn't show up in logs
3762
+ await this.driver
3763
+ .waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.Menu, 1000)
3764
+ .catch(() => undefined);
3765
+ let message = `OTW update was aborted by the bootloader.`;
3766
+ if (error) {
3767
+ message += ` ${error.message}`;
3768
+ // TODO: parse error code
3769
+ }
3770
+ this.driver.controllerLog.print(message, "error");
3771
+ this.emit("firmware update finished", {
3772
+ success: false,
3773
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_Aborted,
3774
+ });
3775
+ return false;
3776
+ }
3777
+ else {
3778
+ // We're done, send EOT and wait for the menu screen
3779
+ await this.driver.bootloader.finishUpload();
3780
+ try {
3781
+ await this.driver.waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.Message &&
3782
+ c.message.includes("upload complete"), 1000);
3783
+ await this.driver.waitForBootloaderChunk((c) => c.type === serial_1.BootloaderChunkType.Menu, 1000);
3784
+ }
3785
+ catch (e) {
3786
+ this.driver.controllerLog.print("OTW update failed: The bootloader did not acknowledge the end of transfer.", "error");
3787
+ this.emit("firmware update finished", {
3788
+ success: false,
3789
+ status: _Types_3.ControllerFirmwareUpdateStatus.Error_Timeout,
3790
+ });
3791
+ return false;
3792
+ }
3793
+ }
3794
+ this.driver.controllerLog.print("Firmware update succeeded");
3795
+ this.emit("firmware update finished", {
3796
+ success: true,
3797
+ status: _Types_3.ControllerFirmwareUpdateStatus.OK,
3798
+ });
3799
+ return true;
3800
+ }
3801
+ finally {
3802
+ await this.driver.leaveBootloader(destroy);
3803
+ this._firmwareUpdateInProgress = false;
3804
+ }
3805
+ }
3157
3806
  };
3158
3807
  ZWaveController = __decorate([
3159
3808
  (0, shared_1.Mixin)([ControllerStatistics_1.ControllerStatisticsHost])