zwave-js 8.7.8 → 8.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/build/CommandClass.js +1 -0
  2. package/build/CommandClass.js.map +1 -1
  3. package/build/Controller.d.ts +1 -0
  4. package/build/Controller.d.ts.map +1 -1
  5. package/build/Controller.js +4 -1
  6. package/build/Controller.js.map +1 -1
  7. package/build/Node.d.ts +1 -1
  8. package/build/Node.d.ts.map +1 -1
  9. package/build/Node.js.map +1 -1
  10. package/build/Utils.d.ts +1 -0
  11. package/build/Utils.d.ts.map +1 -1
  12. package/build/Utils.js +7 -1
  13. package/build/Utils.js.map +1 -1
  14. package/build/lib/commandclass/API.d.ts +15 -3
  15. package/build/lib/commandclass/API.d.ts.map +1 -1
  16. package/build/lib/commandclass/API.js +50 -0
  17. package/build/lib/commandclass/API.js.map +1 -1
  18. package/build/lib/commandclass/CommandClass.d.ts +0 -6
  19. package/build/lib/commandclass/CommandClass.d.ts.map +1 -1
  20. package/build/lib/commandclass/CommandClass.js +0 -12
  21. package/build/lib/commandclass/CommandClass.js.map +1 -1
  22. package/build/lib/commandclass/Security2CC.d.ts +1 -10
  23. package/build/lib/commandclass/Security2CC.d.ts.map +1 -1
  24. package/build/lib/commandclass/Security2CC.js +16 -48
  25. package/build/lib/commandclass/Security2CC.js.map +1 -1
  26. package/build/lib/commandclass/SecurityCC.d.ts +2 -9
  27. package/build/lib/commandclass/SecurityCC.d.ts.map +1 -1
  28. package/build/lib/commandclass/SecurityCC.js +9 -53
  29. package/build/lib/commandclass/SecurityCC.js.map +1 -1
  30. package/build/lib/commandclass/WakeUpCC.d.ts.map +1 -1
  31. package/build/lib/commandclass/WakeUpCC.js +1 -0
  32. package/build/lib/commandclass/WakeUpCC.js.map +1 -1
  33. package/build/lib/controller/BridgeApplicationCommandRequest.d.ts +2 -6
  34. package/build/lib/controller/BridgeApplicationCommandRequest.d.ts.map +1 -1
  35. package/build/lib/controller/BridgeApplicationCommandRequest.js +9 -19
  36. package/build/lib/controller/BridgeApplicationCommandRequest.js.map +1 -1
  37. package/build/lib/controller/Controller.d.ts +12 -2
  38. package/build/lib/controller/Controller.d.ts.map +1 -1
  39. package/build/lib/controller/Controller.js +14 -6
  40. package/build/lib/controller/Controller.js.map +1 -1
  41. package/build/lib/controller/Inclusion.d.ts +1 -1
  42. package/build/lib/controller/SendDataBridgeMessages.d.ts +5 -3
  43. package/build/lib/controller/SendDataBridgeMessages.d.ts.map +1 -1
  44. package/build/lib/controller/SendDataBridgeMessages.js +21 -8
  45. package/build/lib/controller/SendDataBridgeMessages.js.map +1 -1
  46. package/build/lib/controller/SendDataMessages.d.ts +5 -3
  47. package/build/lib/controller/SendDataMessages.d.ts.map +1 -1
  48. package/build/lib/controller/SendDataMessages.js +21 -9
  49. package/build/lib/controller/SendDataMessages.js.map +1 -1
  50. package/build/lib/controller/SendDataShared.d.ts +72 -2
  51. package/build/lib/controller/SendDataShared.d.ts.map +1 -1
  52. package/build/lib/controller/SendDataShared.js +195 -1
  53. package/build/lib/controller/SendDataShared.js.map +1 -1
  54. package/build/lib/driver/CommandQueueMachine.d.ts +7 -3
  55. package/build/lib/driver/CommandQueueMachine.d.ts.map +1 -1
  56. package/build/lib/driver/CommandQueueMachine.js +66 -29
  57. package/build/lib/driver/CommandQueueMachine.js.map +1 -1
  58. package/build/lib/driver/Driver.d.ts +16 -12
  59. package/build/lib/driver/Driver.d.ts.map +1 -1
  60. package/build/lib/driver/Driver.js +179 -164
  61. package/build/lib/driver/Driver.js.map +1 -1
  62. package/build/lib/driver/MessageGenerators.d.ts +26 -0
  63. package/build/lib/driver/MessageGenerators.d.ts.map +1 -0
  64. package/build/lib/driver/MessageGenerators.js +246 -0
  65. package/build/lib/driver/MessageGenerators.js.map +1 -0
  66. package/build/lib/driver/SendThreadMachine.d.ts +24 -53
  67. package/build/lib/driver/SendThreadMachine.d.ts.map +1 -1
  68. package/build/lib/driver/SendThreadMachine.js +160 -618
  69. package/build/lib/driver/SendThreadMachine.js.map +1 -1
  70. package/build/lib/driver/StateMachineShared.d.ts +2 -0
  71. package/build/lib/driver/StateMachineShared.d.ts.map +1 -1
  72. package/build/lib/driver/StateMachineShared.js +21 -10
  73. package/build/lib/driver/StateMachineShared.js.map +1 -1
  74. package/build/lib/driver/Transaction.d.ts +35 -3
  75. package/build/lib/driver/Transaction.d.ts.map +1 -1
  76. package/build/lib/driver/Transaction.js +20 -15
  77. package/build/lib/driver/Transaction.js.map +1 -1
  78. package/build/lib/driver/TransactionMachine.d.ts +30 -0
  79. package/build/lib/driver/TransactionMachine.d.ts.map +1 -0
  80. package/build/lib/driver/TransactionMachine.js +247 -0
  81. package/build/lib/driver/TransactionMachine.js.map +1 -0
  82. package/build/lib/driver/ZWaveOptions.d.ts +0 -2
  83. package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
  84. package/build/lib/message/Constants.d.ts +8 -9
  85. package/build/lib/message/Constants.d.ts.map +1 -1
  86. package/build/lib/message/Constants.js +9 -12
  87. package/build/lib/message/Constants.js.map +1 -1
  88. package/build/lib/message/Message.d.ts +5 -1
  89. package/build/lib/message/Message.d.ts.map +1 -1
  90. package/build/lib/message/Message.js +11 -0
  91. package/build/lib/message/Message.js.map +1 -1
  92. package/build/lib/node/HealthCheck.d.ts +8 -0
  93. package/build/lib/node/HealthCheck.d.ts.map +1 -0
  94. package/build/lib/node/HealthCheck.js +83 -0
  95. package/build/lib/node/HealthCheck.js.map +1 -0
  96. package/build/lib/node/Node.d.ts +11 -3
  97. package/build/lib/node/Node.d.ts.map +1 -1
  98. package/build/lib/node/Node.js +283 -7
  99. package/build/lib/node/Node.js.map +1 -1
  100. package/build/lib/node/Types.d.ts +132 -1
  101. package/build/lib/node/Types.d.ts.map +1 -1
  102. package/build/lib/node/Types.js.map +1 -1
  103. package/build/lib/serialapi/misc/GetBackgroundRSSIMessages.d.ts +14 -0
  104. package/build/lib/serialapi/misc/GetBackgroundRSSIMessages.d.ts.map +1 -0
  105. package/build/lib/serialapi/misc/GetBackgroundRSSIMessages.js +46 -0
  106. package/build/lib/serialapi/misc/GetBackgroundRSSIMessages.js.map +1 -0
  107. package/package.json +14 -14
@@ -69,6 +69,7 @@ const INodeQuery_1 = require("../node/INodeQuery");
69
69
  const Types_1 = require("../node/Types");
70
70
  const deviceConfig_1 = require("../telemetry/deviceConfig");
71
71
  const statistics_1 = require("../telemetry/statistics");
72
+ const MessageGenerators_1 = require("./MessageGenerators");
72
73
  const SendThreadMachine_1 = require("./SendThreadMachine");
73
74
  const ThrottlePresets_1 = require("./ThrottlePresets");
74
75
  const Transaction_1 = require("./Transaction");
@@ -104,7 +105,6 @@ const defaultOptions = {
104
105
  openSerialPort: 10,
105
106
  controller: 3,
106
107
  sendData: 3,
107
- retryAfterTransmitReport: false,
108
108
  nodeInterview: 5,
109
109
  },
110
110
  preserveUnknownValues: false,
@@ -209,9 +209,6 @@ class Driver extends shared_1.TypedEventEmitter {
209
209
  this.retryNodeInterviewTimeouts = new Map();
210
210
  this._statisticsEnabled = false;
211
211
  this.isSoftResetting = false;
212
- this._cleanupHandler = () => {
213
- void this.destroy();
214
- };
215
212
  this.partialCCSessions = new Map();
216
213
  this.lastCallbackId = 0xff;
217
214
  this.lastSaveToCache = 0;
@@ -222,11 +219,6 @@ class Driver extends shared_1.TypedEventEmitter {
222
219
  this.options = (0, shared_1.mergeDeep)(options, defaultOptions);
223
220
  // And make sure they contain valid values
224
221
  checkOptions(this.options);
225
- // register some cleanup handlers in case the program doesn't get closed cleanly
226
- this._cleanupHandler = this._cleanupHandler.bind(this);
227
- process.on("exit", this._cleanupHandler);
228
- process.on("SIGINT", this._cleanupHandler);
229
- process.on("uncaughtException", this._cleanupHandler);
230
222
  // Initialize logging
231
223
  this._logContainer = new core_1.ZWaveLogContainer(this.options.logConfig);
232
224
  this._driverLog = new Driver_1.DriverLogger(this._logContainer);
@@ -291,10 +283,26 @@ class Driver extends shared_1.TypedEventEmitter {
291
283
  if (this.handleMissingNodeACK(transaction))
292
284
  return;
293
285
  }
294
- transaction.promise.reject(error);
286
+ // If the transaction was already started, we need to throw the error into the message generator
287
+ // so it correctly gets ended. Otherwise just reject the result promise
288
+ if (transaction.parts.self) {
289
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
290
+ transaction.parts.self.throw(error).catch(() => { });
291
+ }
292
+ else {
293
+ transaction.promise.reject(error);
294
+ }
295
295
  },
296
296
  resolveTransaction: (transaction, result) => {
297
- transaction.promise.resolve(result);
297
+ // If the transaction was already started, we need to end the message generator early by throwing
298
+ // the result. Otherwise just resolve the result promise
299
+ if (transaction.parts.self) {
300
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
301
+ transaction.parts.self.throw(result).catch(() => { });
302
+ }
303
+ else {
304
+ transaction.promise.resolve(result);
305
+ }
298
306
  },
299
307
  logOutgoingMessage: (msg) => {
300
308
  this.driverLog.logMessage(msg, {
@@ -318,15 +326,30 @@ class Driver extends shared_1.TypedEventEmitter {
318
326
  }
319
327
  },
320
328
  log: this.driverLog.print.bind(this.driverLog),
329
+ logQueue: this.driverLog.sendQueue.bind(this.driverLog),
321
330
  }, (0, shared_1.pick)(this.options, ["timeouts", "attempts"]));
322
331
  this.sendThread = (0, xstate_1.interpret)(sendThreadMachine);
323
332
  // this.sendThread.onTransition((state) => {
324
333
  // if (state.changed)
325
334
  // this.driverLog.print(
326
- // `send thread state: ${state.toStrings().slice(-1)[0]}`,
335
+ // `send thread state: ${state.toStrings().join("->")}`,
327
336
  // "verbose",
328
337
  // );
329
338
  // });
339
+ // this.sendThread.onEvent((evt) => {
340
+ // if (evt.type === "forward") {
341
+ // this.driverLog.print(
342
+ // // @ts-ignore
343
+ // `forwarding event: ${evt.payload.type} from ${evt.from} to ${evt.to}`,
344
+ // "verbose",
345
+ // );
346
+ // } else {
347
+ // this.driverLog.print(
348
+ // `send thread event: ${evt.type}`,
349
+ // "verbose",
350
+ // );
351
+ // }
352
+ // });
330
353
  }
331
354
  ensureNodeSessions(nodeId) {
332
355
  if (!this.nodeSessions.has(nodeId)) {
@@ -447,8 +470,14 @@ class Driver extends shared_1.TypedEventEmitter {
447
470
  // Everything async (inluding opening the serial port) must happen in the setImmediate callback
448
471
  // asynchronously open the serial port
449
472
  setImmediate(async () => {
450
- if (!(await this.tryOpenSerialport(spOpenPromise)))
473
+ try {
474
+ await this.openSerialport();
475
+ }
476
+ catch (e) {
477
+ spOpenPromise.reject(e);
478
+ void this.destroy();
451
479
  return;
480
+ }
452
481
  this.driverLog.print("serial port opened");
453
482
  this._isOpen = true;
454
483
  spOpenPromise.resolve();
@@ -507,13 +536,13 @@ class Driver extends shared_1.TypedEventEmitter {
507
536
  });
508
537
  return spOpenPromise;
509
538
  }
510
- async tryOpenSerialport(openPromise) {
539
+ async openSerialport() {
511
540
  let lastError;
512
541
  // After a reset, the serial port may need a few seconds until we can open it - try a few times
513
542
  for (let attempt = 1; attempt <= this.options.attempts.openSerialPort; attempt++) {
514
543
  try {
515
544
  await this.serial.open();
516
- return true;
545
+ return;
517
546
  }
518
547
  catch (e) {
519
548
  lastError = e;
@@ -524,15 +553,7 @@ class Driver extends shared_1.TypedEventEmitter {
524
553
  }
525
554
  const message = `Failed to open the serial port: ${(0, shared_1.getErrorMessage)(lastError)}`;
526
555
  this.driverLog.print(message, "error");
527
- const error = new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_Failed);
528
- if (this._isOpen || !openPromise) {
529
- this.emit("error", error);
530
- }
531
- else {
532
- openPromise === null || openPromise === void 0 ? void 0 : openPromise.reject(error);
533
- }
534
- void this.destroy();
535
- return false;
556
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_Failed);
536
557
  }
537
558
  /** Indicates whether all nodes are ready, i.e. the "all nodes ready" event has been emitted */
538
559
  get allNodesReady() {
@@ -1050,9 +1071,14 @@ class Driver extends shared_1.TypedEventEmitter {
1050
1071
  }
1051
1072
  /** Checks if there are any pending transactions that match the given predicate */
1052
1073
  hasPendingTransactions(predicate) {
1053
- const { queue, currentTransaction } = this.sendThread.state.context;
1054
- return ((currentTransaction && predicate(currentTransaction)) ||
1055
- !!queue.find((t) => predicate(t)));
1074
+ const { queue, activeTransactions } = this.sendThread.state.context;
1075
+ if (!!queue.find((t) => predicate(t)))
1076
+ return true;
1077
+ for (const { transaction } of activeTransactions.values()) {
1078
+ if (predicate(transaction))
1079
+ return true;
1080
+ }
1081
+ return false;
1056
1082
  }
1057
1083
  /**
1058
1084
  * Retrieves the maximum version of a command class the given endpoint supports.
@@ -1139,6 +1165,14 @@ class Driver extends shared_1.TypedEventEmitter {
1139
1165
  productId === 0x0001) {
1140
1166
  return false;
1141
1167
  }
1168
+ // Vision Gen5 USB Stick
1169
+ if (manufacturerId === 0x0109 &&
1170
+ productType === 0x1001 &&
1171
+ productId === 0x0201
1172
+ // firmware version 15.1 (GH#3730)
1173
+ ) {
1174
+ return false;
1175
+ }
1142
1176
  return true;
1143
1177
  }
1144
1178
  async rememberNoSoftReset() {
@@ -1200,6 +1234,7 @@ class Driver extends shared_1.TypedEventEmitter {
1200
1234
  return this.softResetInternal(true);
1201
1235
  }
1202
1236
  async softResetInternal(destroyOnError) {
1237
+ var _a;
1203
1238
  this.controllerLog.print("Performing soft reset...");
1204
1239
  try {
1205
1240
  this.isSoftResetting = true;
@@ -1221,8 +1256,11 @@ class Driver extends shared_1.TypedEventEmitter {
1221
1256
  }
1222
1257
  }
1223
1258
  this.isSoftResetting = false;
1224
- // And resume sending
1259
+ // Resume sending
1225
1260
  this.unpauseSendThread();
1261
+ // Soft-resetting disables any ongoing inclusion, so we need to reset
1262
+ // the state that is tracked in the controller
1263
+ (_a = this._controller) === null || _a === void 0 ? void 0 : _a.setInclusionState(Inclusion_1.InclusionState.Idle);
1226
1264
  }
1227
1265
  async ensureSerialAPI() {
1228
1266
  // Wait 1.5 seconds after reset to ensure that the module is ready for communication again
@@ -1238,7 +1276,12 @@ class Driver extends shared_1.TypedEventEmitter {
1238
1276
  // If the controller disconnected the serial port during the soft reset, we need to re-open it
1239
1277
  if (!this.serial.isOpen) {
1240
1278
  this.controllerLog.print("Re-opening serial port...");
1241
- await this.tryOpenSerialport();
1279
+ try {
1280
+ await this.openSerialport();
1281
+ }
1282
+ catch {
1283
+ return false;
1284
+ }
1242
1285
  }
1243
1286
  // Wait the configured amount of time for the Serial API started command to be received
1244
1287
  this.controllerLog.print("Waiting for the Serial API to start...");
@@ -1324,27 +1367,12 @@ class Driver extends shared_1.TypedEventEmitter {
1324
1367
  * Must be called under any circumstances.
1325
1368
  */
1326
1369
  async destroy() {
1327
- var _a, _b, _c, _d;
1370
+ var _a, _b, _c;
1328
1371
  // Ensure this is only called once and all subsequent calls block
1329
1372
  if (this._destroyPromise)
1330
1373
  return this._destroyPromise;
1331
1374
  this._destroyPromise = (0, deferred_promise_1.createDeferredPromise)();
1332
1375
  this.driverLog.print("destroying driver instance...");
1333
- // Disable inclusion before shutting down
1334
- try {
1335
- switch ((_a = this._controller) === null || _a === void 0 ? void 0 : _a.inclusionState) {
1336
- case Inclusion_1.InclusionState.SmartStart:
1337
- case Inclusion_1.InclusionState.Including:
1338
- await this._controller.stopInclusionNoCallback();
1339
- break;
1340
- case Inclusion_1.InclusionState.Excluding:
1341
- await this._controller.stopExclusionNoCallback();
1342
- break;
1343
- }
1344
- }
1345
- catch {
1346
- // ignore
1347
- }
1348
1376
  // First stop the send thread machine and close the serial port, so nothing happens anymore
1349
1377
  if (this.sendThread.initialized)
1350
1378
  this.sendThread.stop();
@@ -1364,8 +1392,8 @@ class Driver extends shared_1.TypedEventEmitter {
1364
1392
  }
1365
1393
  try {
1366
1394
  // Attempt to close the value DBs
1367
- await ((_b = this._valueDB) === null || _b === void 0 ? void 0 : _b.close());
1368
- await ((_c = this._metadataDB) === null || _c === void 0 ? void 0 : _c.close());
1395
+ await ((_a = this._valueDB) === null || _a === void 0 ? void 0 : _a.close());
1396
+ await ((_b = this._metadataDB) === null || _b === void 0 ? void 0 : _b.close());
1369
1397
  }
1370
1398
  catch (e) {
1371
1399
  this.driverLog.print(`Closing the value DBs failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
@@ -1383,10 +1411,8 @@ class Driver extends shared_1.TypedEventEmitter {
1383
1411
  clearTimeout(timeout);
1384
1412
  }
1385
1413
  // Destroy all nodes
1386
- (_d = this._controller) === null || _d === void 0 ? void 0 : _d.nodes.forEach((n) => n.destroy());
1387
- process.removeListener("exit", this._cleanupHandler);
1388
- process.removeListener("SIGINT", this._cleanupHandler);
1389
- process.removeListener("uncaughtException", this._cleanupHandler);
1414
+ (_c = this._controller) === null || _c === void 0 ? void 0 : _c.nodes.forEach((n) => n.destroy());
1415
+ this.driverLog.print(`driver instance destroyed`);
1390
1416
  // destroy loggers as the very last thing
1391
1417
  this._logContainer.destroy();
1392
1418
  this._destroyPromise.resolve();
@@ -1693,11 +1719,10 @@ class Driver extends shared_1.TypedEventEmitter {
1693
1719
  // If the node does not acknowledge our request, it is either asleep or dead
1694
1720
  e.code === core_1.ZWaveErrorCodes.Controller_CallbackNOK &&
1695
1721
  (transaction.message instanceof SendDataMessages_1.SendDataRequest ||
1696
- transaction.message instanceof SendDataBridgeMessages_1.SendDataBridgeRequest) &&
1697
- // Ignore pre-transmit handshakes because the actual transaction will be retried
1698
- transaction.priority !== Constants_1.MessagePriority.PreTransmitHandshake);
1722
+ transaction.message instanceof SendDataBridgeMessages_1.SendDataBridgeRequest));
1699
1723
  }
1700
1724
  /**
1725
+ * @internal
1701
1726
  * Handles the case that a node failed to respond in time.
1702
1727
  * Returns `true` if the transaction failure was handled, `false` if it needs to be rejected.
1703
1728
  */
@@ -1714,15 +1739,8 @@ class Driver extends shared_1.TypedEventEmitter {
1714
1739
  It is probably asleep, moving its messages to the wakeup queue.`, "warn");
1715
1740
  // Mark the node as asleep
1716
1741
  // The handler for the asleep status will move the messages to the wakeup queue
1717
- // We need to re-add the current transaction if that is allowed because otherwise it will be dropped silently
1718
- if (this.mayMoveToWakeupQueue(transaction)) {
1719
- this.sendThread.send({ type: "add", transaction });
1720
- }
1721
- else {
1722
- transaction.promise.reject(new core_1.ZWaveError(`The node is asleep`, core_1.ZWaveErrorCodes.Controller_MessageDropped));
1723
- }
1724
1742
  node.markAsAsleep();
1725
- return true;
1743
+ return this.mayMoveToWakeupQueue(transaction);
1726
1744
  }
1727
1745
  else {
1728
1746
  const errorMsg = `Node ${node.id} did not respond after ${transaction.message.maxSendAttempts} attempts, it is presumed dead`;
@@ -2013,9 +2031,6 @@ ${handlers.length} left`);
2013
2031
  */
2014
2032
  async handleRequest(msg) {
2015
2033
  let handlers;
2016
- // For further actions, we are only interested in the innermost CC
2017
- if ((0, ICommandClassContainer_1.isCommandClassContainer)(msg))
2018
- this.unwrapCommands(msg);
2019
2034
  if ((0, INodeQuery_1.isNodeQuery)(msg) || (0, ICommandClassContainer_1.isCommandClassContainer)(msg)) {
2020
2035
  const node = msg.getNodeUnsafe();
2021
2036
  if (node) {
@@ -2025,7 +2040,17 @@ ${handlers.length} left`);
2025
2040
  }
2026
2041
  }
2027
2042
  }
2043
+ // Check if we have a dynamic handler waiting for this message
2044
+ for (const entry of this.awaitedMessages) {
2045
+ if (entry.predicate(msg)) {
2046
+ // resolve the promise - this will remove the entry from the list
2047
+ entry.promise.resolve(msg);
2048
+ return;
2049
+ }
2050
+ }
2028
2051
  if ((0, ICommandClassContainer_1.isCommandClassContainer)(msg)) {
2052
+ // For further actions, we are only interested in the innermost CC
2053
+ this.unwrapCommands(msg);
2029
2054
  const node = msg.getNodeUnsafe();
2030
2055
  // If we receive an encrypted message but assume the node is insecure, change our assumption
2031
2056
  if ((node === null || node === void 0 ? void 0 : node.isSecure) === false &&
@@ -2039,6 +2064,7 @@ ${handlers.length} left`);
2039
2064
  if (!this.mayHandleUnsolicitedCommand(msg.command))
2040
2065
  return;
2041
2066
  }
2067
+ // Otherwise go through the static handlers
2042
2068
  if (msg instanceof ApplicationCommandRequest_1.ApplicationCommandRequest ||
2043
2069
  msg instanceof BridgeApplicationCommandRequest_1.BridgeApplicationCommandRequest) {
2044
2070
  // we handle ApplicationCommandRequests differently because they are handled by the nodes directly
@@ -2190,15 +2216,6 @@ ${handlers.length} left`);
2190
2216
  }
2191
2217
  }
2192
2218
  else {
2193
- // Check if we have a dynamic handler waiting for this message
2194
- for (const entry of this.awaitedMessages) {
2195
- if (entry.predicate(msg)) {
2196
- // resolve the promise - this will remove the entry from the list
2197
- entry.promise.resolve(msg);
2198
- return;
2199
- }
2200
- }
2201
- // Otherwise loop through the static handlers
2202
2219
  // TODO: This deserves a nicer formatting
2203
2220
  this.driverLog.print(`handling request ${Constants_1.FunctionType[msg.functionType]} (${msg.functionType})`);
2204
2221
  handlers = this.requestHandlers.get(msg.functionType);
@@ -2291,13 +2308,62 @@ ${handlers.length} left`);
2291
2308
  msg.command = unwrapped;
2292
2309
  }
2293
2310
  }
2311
+ /**
2312
+ * Gets called whenever any Serial API command succeeded or a SendData command had a negative callback.
2313
+ */
2314
+ handleSerialAPICommandResult(msg, options, result) {
2315
+ var _a, _b;
2316
+ // Update statistics
2317
+ const node = msg.getNodeUnsafe();
2318
+ let success = true;
2319
+ if ((0, SendDataShared_1.isSendData)(msg)) {
2320
+ // This shouldn't happen, but just in case
2321
+ if (!node)
2322
+ return;
2323
+ if ((0, SendDataShared_1.isSendDataTransmitReport)(result)) {
2324
+ if (!result.isOK()) {
2325
+ success = false;
2326
+ node.incrementStatistics("commandsDroppedTX");
2327
+ }
2328
+ else {
2329
+ node.incrementStatistics("commandsTX");
2330
+ }
2331
+ // Notify listeners about the status report
2332
+ if ((0, SendDataShared_1.hasTXReport)(result)) {
2333
+ (_a = options.onTXReport) === null || _a === void 0 ? void 0 : _a.call(options, result.txReport);
2334
+ // TODO: Update statistics based on the TX report
2335
+ }
2336
+ }
2337
+ }
2338
+ else {
2339
+ (_b = this._controller) === null || _b === void 0 ? void 0 : _b.incrementStatistics("messagesTX");
2340
+ }
2341
+ // Track and potentially update the status of the node when communication succeeds
2342
+ if (node && success) {
2343
+ if (node.canSleep) {
2344
+ // Do not update the node status when we just responded to a nonce request
2345
+ if (options.priority !== Constants_1.MessagePriority.Handshake) {
2346
+ // If the node is not meant to be kept awake, try to send it back to sleep
2347
+ if (!node.keepAwake) {
2348
+ this.debounceSendNodeToSleep(node);
2349
+ }
2350
+ // The node must be awake because it answered
2351
+ node.markAsAwake();
2352
+ }
2353
+ }
2354
+ else if (node.status !== Types_1.NodeStatus.Alive) {
2355
+ // The node status was unknown or dead - in either case it must be alive because it answered
2356
+ node.markAsAlive();
2357
+ }
2358
+ }
2359
+ }
2294
2360
  /**
2295
2361
  * Sends a message to the Z-Wave stick.
2296
2362
  * @param msg The message to send
2297
2363
  * @param options (optional) Options regarding the message transmission
2298
2364
  */
2299
2365
  async sendMessage(msg, options = {}) {
2300
- var _a, _b;
2366
+ var _a;
2301
2367
  this.ensureReady();
2302
2368
  let node;
2303
2369
  // Don't send messages to dead nodes
@@ -2342,17 +2408,24 @@ ${handlers.length} left`);
2342
2408
  !(msg instanceof SendDataMessages_1.SendDataMulticastRequest) &&
2343
2409
  !(msg instanceof SendDataBridgeMessages_1.SendDataMulticastBridgeRequest) &&
2344
2410
  // Handshake messages are meant to be sent immediately
2345
- options.priority !== Constants_1.MessagePriority.Handshake &&
2346
- options.priority !== Constants_1.MessagePriority.PreTransmitHandshake) {
2411
+ options.priority !== Constants_1.MessagePriority.Handshake) {
2347
2412
  if (options.priority === Constants_1.MessagePriority.NodeQuery) {
2348
2413
  // Remember that this transaction was part of an interview
2349
2414
  options.tag = "interview";
2350
2415
  }
2351
2416
  options.priority = Constants_1.MessagePriority.WakeUp;
2352
2417
  }
2353
- // create the transaction and enqueue it
2354
- const promise = (0, deferred_promise_1.createDeferredPromise)();
2355
- const transaction = new Transaction_1.Transaction(this, msg, promise, options.priority);
2418
+ // Create the transaction
2419
+ const { generator, resultPromise } = (0, MessageGenerators_1.createMessageGenerator)(this, msg, (msg, _result) => {
2420
+ this.handleSerialAPICommandResult(msg, options, _result);
2421
+ });
2422
+ const transaction = new Transaction_1.Transaction(this, {
2423
+ message: msg,
2424
+ priority: options.priority,
2425
+ parts: generator,
2426
+ promise: resultPromise,
2427
+ });
2428
+ // Configure its options
2356
2429
  if (options.changeNodeStatusOnMissingACK != undefined) {
2357
2430
  transaction.changeNodeStatusOnTimeout =
2358
2431
  options.changeNodeStatusOnMissingACK;
@@ -2362,7 +2435,7 @@ ${handlers.length} left`);
2362
2435
  }
2363
2436
  transaction.requestWakeUpOnDemand = !!options.requestWakeUpOnDemand;
2364
2437
  transaction.tag = options.tag;
2365
- // start sending now (maybe)
2438
+ // And queue it
2366
2439
  this.sendThread.send({ type: "add", transaction });
2367
2440
  // If the transaction should expire, start the timeout
2368
2441
  let expirationTimeout;
@@ -2383,36 +2456,7 @@ ${handlers.length} left`);
2383
2456
  }, options.expire).unref();
2384
2457
  }
2385
2458
  try {
2386
- const ret = await promise;
2387
- // The message was transmitted, so it can no longer expire
2388
- if (expirationTimeout)
2389
- clearTimeout(expirationTimeout);
2390
- // Update statistics
2391
- if ((0, SendDataShared_1.isSendData)(msg)) {
2392
- node === null || node === void 0 ? void 0 : node.incrementStatistics("commandsTX");
2393
- }
2394
- else {
2395
- (_a = this._controller) === null || _a === void 0 ? void 0 : _a.incrementStatistics("messagesTX");
2396
- }
2397
- // Track and potentially update the status of the node when communication succeeds
2398
- if (node) {
2399
- if (node.canSleep) {
2400
- // Do not update the node status when we just responded to a nonce request
2401
- if (options.priority !== Constants_1.MessagePriority.Handshake) {
2402
- // If the node is not meant to be kept awake, try to send it back to sleep
2403
- if (!node.keepAwake) {
2404
- this.debounceSendNodeToSleep(node);
2405
- }
2406
- // The node must be awake because it answered
2407
- node.markAsAwake();
2408
- }
2409
- }
2410
- else if (node.status !== Types_1.NodeStatus.Alive) {
2411
- // The node status was unknown or dead - in either case it must be alive because it answered
2412
- node.markAsAlive();
2413
- }
2414
- }
2415
- return ret;
2459
+ return (await resultPromise);
2416
2460
  }
2417
2461
  catch (e) {
2418
2462
  if ((0, core_1.isZWaveError)(e)) {
@@ -2427,24 +2471,28 @@ ${handlers.length} left`);
2427
2471
  e.context.functionType !== Constants_1.FunctionType.SendDataBridge &&
2428
2472
  e.context.functionType !==
2429
2473
  Constants_1.FunctionType.SendDataMulticastBridge) {
2430
- (_b = this._controller) === null || _b === void 0 ? void 0 : _b.incrementStatistics("messagesDroppedTX");
2474
+ (_a = this._controller) === null || _a === void 0 ? void 0 : _a.incrementStatistics("messagesDroppedTX");
2431
2475
  return e.context;
2432
2476
  }
2433
2477
  else if (e.code === core_1.ZWaveErrorCodes.Controller_NodeTimeout) {
2434
2478
  // If the node failed to respond in time, remember this for the statistics
2435
2479
  node === null || node === void 0 ? void 0 : node.incrementStatistics("timeoutResponse");
2436
2480
  }
2481
+ // Enrich errors with the transaction's stack instead of the internal stack
2482
+ if (!e.transactionSource) {
2483
+ throw new core_1.ZWaveError(e.message, e.code, e.context, transaction.stack);
2484
+ }
2437
2485
  }
2438
2486
  throw e;
2439
2487
  }
2488
+ finally {
2489
+ // The transaction was handled, so it can no longer expire
2490
+ if (expirationTimeout)
2491
+ clearTimeout(expirationTimeout);
2492
+ }
2440
2493
  }
2441
- /**
2442
- * Sends a command to a Z-Wave node. If the node returns a command in response, that command will be the return value.
2443
- * If the command expects no response **or** the response times out, nothing will be returned
2444
- * @param command The command to send. It will be encapsulated in a SendData[Multicast]Request.
2445
- * @param options (optional) Options regarding the message transmission
2446
- */
2447
- async sendCommand(command, options = {}) {
2494
+ /** Wraps a CC in the correct SendData message to use for sending */
2495
+ createSendDataMessage(command, options = {}) {
2448
2496
  let msg;
2449
2497
  if (command.isSinglecast()) {
2450
2498
  if (this.controller.isFunctionSupported(Constants_1.FunctionType.SendDataBridge)) {
@@ -2456,7 +2504,7 @@ ${handlers.length} left`);
2456
2504
  }
2457
2505
  }
2458
2506
  else if (command.isMulticast()) {
2459
- if (this.controller.isFunctionSupported(Constants_1.FunctionType.SendDataBridge)) {
2507
+ if (this.controller.isFunctionSupported(Constants_1.FunctionType.SendDataMulticastBridge)) {
2460
2508
  // Prioritize Bridge commands when they are supported
2461
2509
  msg = new SendDataBridgeMessages_1.SendDataMulticastBridgeRequest(this, { command });
2462
2510
  }
@@ -2482,6 +2530,16 @@ ${handlers.length} left`);
2482
2530
  // Automatically encapsulate commands before sending
2483
2531
  if (options.autoEncapsulate !== false)
2484
2532
  this.encapsulateCommands(msg);
2533
+ return msg;
2534
+ }
2535
+ /**
2536
+ * Sends a command to a Z-Wave node. If the node returns a command in response, that command will be the return value.
2537
+ * If the command expects no response **or** the response times out, nothing will be returned
2538
+ * @param command The command to send. It will be encapsulated in a SendData[Multicast]Request.
2539
+ * @param options (optional) Options regarding the message transmission
2540
+ */
2541
+ async sendCommand(command, options = {}) {
2542
+ const msg = this.createSendDataMessage(command, options);
2485
2543
  try {
2486
2544
  const resp = await this.sendMessage(msg, options);
2487
2545
  // And unwrap the response if there was any
@@ -2568,7 +2626,7 @@ ${handlers.length} left`);
2568
2626
  /**
2569
2627
  * Waits until an unsolicited serial message is received or a timeout has elapsed. Returns the received message.
2570
2628
  *
2571
- * **Note:** This does not trigger for [Bridge]ApplicationUpdateRequests, which are handled differently. To wait for a certain CommandClass, use {@link waitForCommand}.
2629
+ * **Note:** To wait for a certain CommandClass, better use {@link waitForCommand}.
2572
2630
  * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
2573
2631
  * @param predicate A predicate function to test all incoming messages
2574
2632
  */
@@ -2639,8 +2697,6 @@ ${handlers.length} left`);
2639
2697
  // so they must be dropped
2640
2698
  case (0, NoOperationCC_1.messageIsPing)(msg):
2641
2699
  case transaction.priority === Constants_1.MessagePriority.Handshake:
2642
- // Outgoing handshake requests are very likely not valid after wakeup, so drop them too
2643
- case transaction.priority === Constants_1.MessagePriority.PreTransmitHandshake:
2644
2700
  // We also don't want to immediately send the node to sleep when it wakes up
2645
2701
  case (0, ICommandClassContainer_1.isCommandClassContainer)(msg) &&
2646
2702
  msg.command instanceof WakeUpCC_1.WakeUpCCNoMoreInformation:
@@ -2725,47 +2781,6 @@ ${handlers.length} left`);
2725
2781
  sortSendQueue() {
2726
2782
  this.sendThread.send("sortQueue");
2727
2783
  }
2728
- /** Re-sends the current command if it is S2 encapsulated */
2729
- resendS2EncapsulatedCommand() {
2730
- // If this is called, a receiving node couldn't decode the last message we sent it
2731
- const { currentTransaction } = this.sendThread.state.context;
2732
- if (currentTransaction &&
2733
- (0, ICommandClassContainer_1.isCommandClassContainer)(currentTransaction.message) &&
2734
- currentTransaction.message.command instanceof
2735
- commandclass_1.Security2CCMessageEncapsulation) {
2736
- const cmd = currentTransaction.message.command;
2737
- if (cmd.wasRetriedAfterDecodeFailure) {
2738
- this._controllerLog.logNode(cmd.nodeId, {
2739
- message: `failed to decode the message after re-transmission with SPAN extension, dropping the message.`,
2740
- direction: "none",
2741
- level: "warn",
2742
- });
2743
- this.sendThread.send({
2744
- type: "reduce",
2745
- reducer: (_t, source) => {
2746
- if (source === "current") {
2747
- return {
2748
- type: "reject",
2749
- code: core_1.ZWaveErrorCodes.Security2CC_CannotDecode,
2750
- message: "The node failed to decode the message.",
2751
- };
2752
- }
2753
- else {
2754
- return { type: "keep" };
2755
- }
2756
- },
2757
- });
2758
- }
2759
- else {
2760
- this._controllerLog.logNode(cmd.nodeId, {
2761
- message: `failed to decode the message, retrying with SPAN extension...`,
2762
- direction: "none",
2763
- });
2764
- cmd.prepareRetryAfterDecodeFailure();
2765
- this.sendThread.send("resend");
2766
- }
2767
- }
2768
- }
2769
2784
  /**
2770
2785
  * Does the work for saveNetworkToCache. This is not throttled, so any call
2771
2786
  * to this method WILL save the network.