zwave-js 8.5.1 → 8.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/build/Utils.d.ts +2 -2
  2. package/build/Utils.d.ts.map +1 -1
  3. package/build/Utils.js +3 -1
  4. package/build/Utils.js.map +1 -1
  5. package/build/lib/commandclass/API.d.ts +7 -3
  6. package/build/lib/commandclass/API.d.ts.map +1 -1
  7. package/build/lib/commandclass/API.js +9 -1
  8. package/build/lib/commandclass/API.js.map +1 -1
  9. package/build/lib/commandclass/AssociationCC.d.ts.map +1 -1
  10. package/build/lib/commandclass/AssociationCC.js +2 -4
  11. package/build/lib/commandclass/AssociationCC.js.map +1 -1
  12. package/build/lib/commandclass/BinarySwitchCC.d.ts.map +1 -1
  13. package/build/lib/commandclass/BinarySwitchCC.js +7 -3
  14. package/build/lib/commandclass/BinarySwitchCC.js.map +1 -1
  15. package/build/lib/commandclass/ColorSwitchCC.js +2 -2
  16. package/build/lib/commandclass/ColorSwitchCC.js.map +1 -1
  17. package/build/lib/commandclass/ConfigurationCC.d.ts.map +1 -1
  18. package/build/lib/commandclass/ConfigurationCC.js +3 -1
  19. package/build/lib/commandclass/ConfigurationCC.js.map +1 -1
  20. package/build/lib/commandclass/ManufacturerProprietaryCC.d.ts.map +1 -1
  21. package/build/lib/commandclass/ManufacturerProprietaryCC.js +15 -8
  22. package/build/lib/commandclass/ManufacturerProprietaryCC.js.map +1 -1
  23. package/build/lib/commandclass/MultiChannelAssociationCC.d.ts.map +1 -1
  24. package/build/lib/commandclass/MultiChannelAssociationCC.js +2 -4
  25. package/build/lib/commandclass/MultiChannelAssociationCC.js.map +1 -1
  26. package/build/lib/commandclass/MultilevelSwitchCC.d.ts.map +1 -1
  27. package/build/lib/commandclass/MultilevelSwitchCC.js +2 -4
  28. package/build/lib/commandclass/MultilevelSwitchCC.js.map +1 -1
  29. package/build/lib/commandclass/SoundSwitchCC.js +2 -2
  30. package/build/lib/commandclass/SoundSwitchCC.js.map +1 -1
  31. package/build/lib/commandclass/ThermostatFanModeCC.js +2 -2
  32. package/build/lib/commandclass/ThermostatFanModeCC.js.map +1 -1
  33. package/build/lib/commandclass/ThermostatModeCC.js +2 -2
  34. package/build/lib/commandclass/ThermostatModeCC.js.map +1 -1
  35. package/build/lib/commandclass/ThermostatSetpointCC.d.ts.map +1 -1
  36. package/build/lib/commandclass/ThermostatSetpointCC.js +2 -2
  37. package/build/lib/commandclass/ThermostatSetpointCC.js.map +1 -1
  38. package/build/lib/commandclass/UserCodeCC.js +2 -2
  39. package/build/lib/commandclass/UserCodeCC.js.map +1 -1
  40. package/build/lib/commandclass/WakeUpCC.js +2 -2
  41. package/build/lib/commandclass/WakeUpCC.js.map +1 -1
  42. package/build/lib/controller/AddNodeToNetworkRequest.d.ts +36 -8
  43. package/build/lib/controller/AddNodeToNetworkRequest.d.ts.map +1 -1
  44. package/build/lib/controller/AddNodeToNetworkRequest.js +111 -41
  45. package/build/lib/controller/AddNodeToNetworkRequest.js.map +1 -1
  46. package/build/lib/controller/ApplicationUpdateRequest.d.ts +16 -4
  47. package/build/lib/controller/ApplicationUpdateRequest.d.ts.map +1 -1
  48. package/build/lib/controller/ApplicationUpdateRequest.js +56 -15
  49. package/build/lib/controller/ApplicationUpdateRequest.js.map +1 -1
  50. package/build/lib/controller/Controller.d.ts +128 -23
  51. package/build/lib/controller/Controller.d.ts.map +1 -1
  52. package/build/lib/controller/Controller.js +882 -281
  53. package/build/lib/controller/Controller.js.map +1 -1
  54. package/build/lib/controller/Inclusion.d.ts +31 -4
  55. package/build/lib/controller/Inclusion.d.ts.map +1 -1
  56. package/build/lib/controller/Inclusion.js +15 -3
  57. package/build/lib/controller/Inclusion.js.map +1 -1
  58. package/build/lib/controller/RemoveNodeFromNetworkRequest.d.ts +15 -9
  59. package/build/lib/controller/RemoveNodeFromNetworkRequest.d.ts.map +1 -1
  60. package/build/lib/controller/RemoveNodeFromNetworkRequest.js +70 -41
  61. package/build/lib/controller/RemoveNodeFromNetworkRequest.js.map +1 -1
  62. package/build/lib/controller/SoftResetRequest.d.ts +4 -0
  63. package/build/lib/controller/SoftResetRequest.d.ts.map +1 -0
  64. package/build/lib/controller/SoftResetRequest.js +19 -0
  65. package/build/lib/controller/SoftResetRequest.js.map +1 -0
  66. package/build/lib/driver/Driver.d.ts +31 -2
  67. package/build/lib/driver/Driver.d.ts.map +1 -1
  68. package/build/lib/driver/Driver.js +478 -82
  69. package/build/lib/driver/Driver.js.map +1 -1
  70. package/build/lib/driver/SendThreadMachine.d.ts +4 -1
  71. package/build/lib/driver/SendThreadMachine.d.ts.map +1 -1
  72. package/build/lib/driver/SendThreadMachine.js +23 -0
  73. package/build/lib/driver/SendThreadMachine.js.map +1 -1
  74. package/build/lib/driver/Transaction.d.ts +2 -0
  75. package/build/lib/driver/Transaction.d.ts.map +1 -1
  76. package/build/lib/driver/Transaction.js +2 -0
  77. package/build/lib/driver/Transaction.js.map +1 -1
  78. package/build/lib/driver/UpdateConfig.js.map +1 -1
  79. package/build/lib/driver/ZWaveOptions.d.ts +18 -1
  80. package/build/lib/driver/ZWaveOptions.d.ts.map +1 -1
  81. package/build/lib/log/Controller.d.ts +12 -2
  82. package/build/lib/log/Controller.d.ts.map +1 -1
  83. package/build/lib/log/Controller.js +45 -0
  84. package/build/lib/log/Controller.js.map +1 -1
  85. package/build/lib/log/Driver.d.ts +5 -2
  86. package/build/lib/log/Driver.d.ts.map +1 -1
  87. package/build/lib/log/Driver.js +3 -0
  88. package/build/lib/log/Driver.js.map +1 -1
  89. package/build/lib/message/Constants.d.ts +3 -2
  90. package/build/lib/message/Constants.d.ts.map +1 -1
  91. package/build/lib/message/Constants.js +3 -2
  92. package/build/lib/message/Constants.js.map +1 -1
  93. package/build/lib/node/Node.d.ts +7 -1
  94. package/build/lib/node/Node.d.ts.map +1 -1
  95. package/build/lib/node/Node.js +35 -9
  96. package/build/lib/node/Node.js.map +1 -1
  97. package/build/lib/serialapi/misc/SerialAPIStartedRequest.d.ts +41 -0
  98. package/build/lib/serialapi/misc/SerialAPIStartedRequest.d.ts.map +1 -0
  99. package/build/lib/serialapi/misc/SerialAPIStartedRequest.js +91 -0
  100. package/build/lib/serialapi/misc/SerialAPIStartedRequest.js.map +1 -0
  101. package/build/lib/serialapi/nvm/NVMOperationsMessages.d.ts +60 -0
  102. package/build/lib/serialapi/nvm/NVMOperationsMessages.d.ts.map +1 -0
  103. package/build/lib/serialapi/nvm/NVMOperationsMessages.js +187 -0
  104. package/build/lib/serialapi/nvm/NVMOperationsMessages.js.map +1 -0
  105. package/package.json +14 -14
@@ -55,9 +55,12 @@ const ApplicationCommandRequest_1 = require("../controller/ApplicationCommandReq
55
55
  const ApplicationUpdateRequest_1 = require("../controller/ApplicationUpdateRequest");
56
56
  const BridgeApplicationCommandRequest_1 = require("../controller/BridgeApplicationCommandRequest");
57
57
  const Controller_1 = require("../controller/Controller");
58
+ const GetControllerVersionMessages_1 = require("../controller/GetControllerVersionMessages");
59
+ const Inclusion_1 = require("../controller/Inclusion");
58
60
  const SendDataBridgeMessages_1 = require("../controller/SendDataBridgeMessages");
59
61
  const SendDataMessages_1 = require("../controller/SendDataMessages");
60
62
  const SendDataShared_1 = require("../controller/SendDataShared");
63
+ const SoftResetRequest_1 = require("../controller/SoftResetRequest");
61
64
  const Controller_2 = require("../log/Controller");
62
65
  const Driver_1 = require("../log/Driver");
63
66
  const Constants_1 = require("../message/Constants");
@@ -93,9 +96,12 @@ const defaultOptions = {
93
96
  report: 10000,
94
97
  nonce: 5000,
95
98
  sendDataCallback: 65000,
96
- refreshValue: 5000, // Default should handle most slow devices until we have a better solution
99
+ refreshValue: 5000,
100
+ refreshValueAfterTransition: 1000,
101
+ serialAPIStarted: 5000,
97
102
  },
98
103
  attempts: {
104
+ openSerialPort: 10,
99
105
  controller: 3,
100
106
  sendData: 3,
101
107
  retryAfterTransmitReport: false,
@@ -103,6 +109,8 @@ const defaultOptions = {
103
109
  },
104
110
  preserveUnknownValues: false,
105
111
  disableOptimisticValueUpdate: false,
112
+ // By default enable soft reset unless the env variable is set
113
+ enableSoftReset: !process.env.ZWAVEJS_DISABLE_SOFT_RESET,
106
114
  interview: {
107
115
  skipInterview: false,
108
116
  queryAllUserCodes: false,
@@ -110,6 +118,7 @@ const defaultOptions = {
110
118
  storage: {
111
119
  driver: fs_extra_1.default,
112
120
  cacheDir: path_1.default.resolve(libraryRootDir, "cache"),
121
+ lockDir: process.env.ZWAVEJS_LOCK_DIRECTORY,
113
122
  throttle: "normal",
114
123
  },
115
124
  preferences: {
@@ -138,6 +147,10 @@ function checkOptions(options) {
138
147
  if (options.timeouts.sendDataCallback < 10000) {
139
148
  throw new core_1.ZWaveError(`The Send Data Callback timeout must be at least 10000 milliseconds!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
140
149
  }
150
+ if (options.timeouts.serialAPIStarted < 1000 ||
151
+ options.timeouts.serialAPIStarted > 30000) {
152
+ throw new core_1.ZWaveError(`The Serial API started timeout must be between 1000 and 30000 milliseconds!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
153
+ }
141
154
  if (options.securityKeys != undefined) {
142
155
  if (options.networkKey != undefined) {
143
156
  throw new core_1.ZWaveError(`The deprecated networkKey option may not be used together with the new securityKeys option!`, core_1.ZWaveErrorCodes.Driver_InvalidOptions);
@@ -182,6 +195,8 @@ class Driver extends shared_1.TypedEventEmitter {
182
195
  this.port = port;
183
196
  /** A map of handlers for all sorts of requests */
184
197
  this.requestHandlers = new Map();
198
+ /** A map of awaited messages */
199
+ this.awaitedMessages = [];
185
200
  /** A map of awaited commands */
186
201
  this.awaitedCommands = [];
187
202
  /** A map of Node ID -> ongoing sessions */
@@ -193,6 +208,7 @@ class Driver extends shared_1.TypedEventEmitter {
193
208
  this._nodesReadyEventEmitted = false;
194
209
  this.retryNodeInterviewTimeouts = new Map();
195
210
  this._statisticsEnabled = false;
211
+ this.isSoftResetting = false;
196
212
  this._cleanupHandler = () => {
197
213
  void this.destroy();
198
214
  };
@@ -217,6 +233,7 @@ class Driver extends shared_1.TypedEventEmitter {
217
233
  this._controllerLog = new Controller_2.ControllerLogger(this._logContainer);
218
234
  // Initialize the cache
219
235
  this.cacheDir = this.options.storage.cacheDir;
236
+ // TODO: Load provisioning list
220
237
  // Initialize config manager
221
238
  this.configManager = new config_1.ConfigManager({
222
239
  logContainer: this._logContainer,
@@ -408,15 +425,19 @@ class Driver extends shared_1.TypedEventEmitter {
408
425
  this.serial
409
426
  .on("data", this.serialport_onData.bind(this))
410
427
  .on("error", (err) => {
428
+ var _a;
429
+ if (this.isSoftResetting && !((_a = this.serial) === null || _a === void 0 ? void 0 : _a.isOpen)) {
430
+ // A disconnection while soft resetting is to be expected
431
+ return;
432
+ }
433
+ else if (!this._isOpen) {
434
+ // tryOpenSerialport takes care of error handling
435
+ return;
436
+ }
411
437
  const message = `Serial port errored: ${err.message}`;
412
438
  this.driverLog.print(message, "error");
413
439
  const error = new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_Failed);
414
- if (this._isOpen) {
415
- this.emit("error", error);
416
- }
417
- else {
418
- spOpenPromise.reject(error);
419
- }
440
+ this.emit("error", error);
420
441
  void this.destroy();
421
442
  });
422
443
  // If the port is already open, close it first
@@ -426,22 +447,16 @@ class Driver extends shared_1.TypedEventEmitter {
426
447
  // Everything async (inluding opening the serial port) must happen in the setImmediate callback
427
448
  // asynchronously open the serial port
428
449
  setImmediate(async () => {
429
- try {
430
- await this.serial.open();
431
- }
432
- catch (e) {
433
- const message = `Failed to open the serial port: ${(0, shared_1.getErrorMessage)(e)}`;
434
- this.driverLog.print(message, "error");
435
- spOpenPromise.reject(new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_Failed));
436
- void this.destroy();
450
+ if (!(await this.tryOpenSerialport(spOpenPromise)))
437
451
  return;
438
- }
439
452
  this.driverLog.print("serial port opened");
440
453
  this._isOpen = true;
441
454
  spOpenPromise.resolve();
455
+ // Perform initialization sequence
442
456
  await this.writeHeader(serial_1.MessageHeaders.NAK);
443
- // set unref, so stopping the process doesn't need to wait for the 1500ms
444
- await (0, async_1.wait)(1500, true);
457
+ // Per the specs, this should be followed by a soft-reset but we need to be able
458
+ // to handle sticks that don't support the soft reset command. Therefore we do it
459
+ // after opening the value DBs
445
460
  // Try to create the cache directory. This can fail, in which case we should expose a good error message
446
461
  try {
447
462
  await this.options.storage.driver.ensureDir(this.cacheDir);
@@ -482,7 +497,7 @@ class Driver extends shared_1.TypedEventEmitter {
482
497
  message = `Failed to initialize the driver, no response from the controller. Are you sure this is a Z-Wave controller?`;
483
498
  }
484
499
  else {
485
- message = `Failed to initialize the driver: ${(0, shared_1.getErrorMessage)(e)}`;
500
+ message = `Failed to initialize the driver: ${(0, shared_1.getErrorMessage)(e, true)}`;
486
501
  }
487
502
  this.driverLog.print(message, "error");
488
503
  this.emit("error", new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_Failed));
@@ -492,10 +507,63 @@ class Driver extends shared_1.TypedEventEmitter {
492
507
  });
493
508
  return spOpenPromise;
494
509
  }
510
+ async tryOpenSerialport(openPromise) {
511
+ let lastError;
512
+ // After a reset, the serial port may need a few seconds until we can open it - try a few times
513
+ for (let attempt = 1; attempt <= this.options.attempts.openSerialPort; attempt++) {
514
+ try {
515
+ await this.serial.open();
516
+ return true;
517
+ }
518
+ catch (e) {
519
+ lastError = e;
520
+ }
521
+ if (attempt < this.options.attempts.openSerialPort) {
522
+ await (0, async_1.wait)(1000);
523
+ }
524
+ }
525
+ const message = `Failed to open the serial port: ${(0, shared_1.getErrorMessage)(lastError)}`;
526
+ 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;
536
+ }
495
537
  /** Indicates whether all nodes are ready, i.e. the "all nodes ready" event has been emitted */
496
538
  get allNodesReady() {
497
539
  return this._nodesReadyEventEmitted;
498
540
  }
541
+ async initValueDBs(homeId) {
542
+ // Always start the value and metadata databases
543
+ const options = {
544
+ ignoreReadErrors: true,
545
+ ...ThrottlePresets_1.throttlePresets[this.options.storage.throttle],
546
+ };
547
+ if (this.options.storage.lockDir) {
548
+ options.lockfileDirectory = this.options.storage.lockDir;
549
+ }
550
+ const valueDBFile = path_1.default.join(this.cacheDir, `${homeId.toString(16)}.values.jsonl`);
551
+ this._valueDB = new jsonl_db_1.JsonlDB(valueDBFile, {
552
+ ...options,
553
+ reviver: (key, value) => (0, core_1.deserializeCacheValue)(value),
554
+ serializer: (key, value) => (0, core_1.serializeCacheValue)(value),
555
+ });
556
+ await this._valueDB.open();
557
+ const metadataDBFile = path_1.default.join(this.cacheDir, `${homeId.toString(16)}.metadata.jsonl`);
558
+ this._metadataDB = new jsonl_db_1.JsonlDB(metadataDBFile, options);
559
+ await this._metadataDB.open();
560
+ if (process.env.NO_CACHE === "true") {
561
+ // Since value/metadata DBs are append-only, we need to clear them
562
+ // if the cache should be ignored
563
+ this._valueDB.clear();
564
+ this._metadataDB.clear();
565
+ }
566
+ }
499
567
  /**
500
568
  * Initializes the variables for controller and nodes,
501
569
  * adds event handlers and starts the interview process.
@@ -508,39 +576,56 @@ class Driver extends shared_1.TypedEventEmitter {
508
576
  .on("node added", this.onNodeAdded.bind(this))
509
577
  .on("node removed", this.onNodeRemoved.bind(this));
510
578
  }
511
- const initValueDBs = async () => {
512
- // Always start the value and metadata databases
513
- const options = {
514
- ignoreReadErrors: true,
515
- ...ThrottlePresets_1.throttlePresets[this.options.storage.throttle],
516
- };
517
- const valueDBFile = path_1.default.join(this.cacheDir, `${this._controller.homeId.toString(16)}.values.jsonl`);
518
- this._valueDB = new jsonl_db_1.JsonlDB(valueDBFile, {
519
- ...options,
520
- reviver: (key, value) => (0, core_1.deserializeCacheValue)(value),
521
- serializer: (key, value) => (0, core_1.serializeCacheValue)(value),
522
- });
523
- await this._valueDB.open();
524
- const metadataDBFile = path_1.default.join(this.cacheDir, `${this._controller.homeId.toString(16)}.metadata.jsonl`);
525
- this._metadataDB = new jsonl_db_1.JsonlDB(metadataDBFile, options);
526
- await this._metadataDB.open();
527
- if (process.env.NO_CACHE === "true") {
528
- // Since value/metadata DBs are append-only, we need to clear them
529
- // if the cache should be ignored
530
- this._valueDB.clear();
531
- this._metadataDB.clear();
532
- }
533
- };
534
579
  if (!this.options.interview.skipInterview) {
580
+ // Determine controller IDs to open the Value DBs
581
+ // We need to do this first because some older controllers, especially the UZB1 and
582
+ // some 500-series sticks in virtualized environments don't respond after a soft reset
583
+ // No need to initialize databases if skipInterview is true, because it is only used in some
584
+ // Driver unit tests that don't need access to them
585
+ // Identify the controller and determine if it supports soft reset
586
+ await this.controller.identify();
587
+ if (this.options.enableSoftReset && !(await this.maySoftReset())) {
588
+ this.driverLog.print(`Soft reset is enabled through config, but this stick does not support it.`, "warn");
589
+ this.options.enableSoftReset = false;
590
+ }
591
+ if (this.options.enableSoftReset) {
592
+ try {
593
+ await this.softResetInternal(false);
594
+ }
595
+ catch (e) {
596
+ if ((0, core_1.isZWaveError)(e) &&
597
+ e.code === core_1.ZWaveErrorCodes.Driver_Failed) {
598
+ // Remember that soft reset is not supported by this stick
599
+ await this.rememberNoSoftReset();
600
+ // Then fail the driver
601
+ await this.destroy();
602
+ return;
603
+ }
604
+ }
605
+ }
606
+ // There are situations where a controller claims it has the ID 0,
607
+ // which isn't valid. In this case try again after having soft-reset the stick
608
+ if (this.controller.ownNodeId === 0 &&
609
+ this.options.enableSoftReset) {
610
+ this.driverLog.print(`Controller identification returned invalid node ID 0 - trying again...`, "warn");
611
+ await this.controller.identify();
612
+ }
613
+ if (this.controller.ownNodeId === 0) {
614
+ this.driverLog.print(`Controller identification returned invalid node ID 0`, "error");
615
+ await this.destroy();
616
+ return;
617
+ }
618
+ // now that we know the home ID, we can open the databases
619
+ await this.initValueDBs(this.controller.homeId);
535
620
  // Interview the controller.
536
- await this._controller.interview(initValueDBs, async () => {
621
+ await this._controller.interview(async () => {
537
622
  // Try to restore the network information from the cache
538
623
  if (process.env.NO_CACHE !== "true") {
539
624
  await this.restoreNetworkStructureFromCache();
540
625
  }
541
626
  });
542
- // No need to initialize databases if skipInterview is true, because it is only used in some
543
- // Driver unit tests that don't need access to them
627
+ // Auto-enable smart start inclusion
628
+ this._controller.autoProvisionSmartStart();
544
629
  }
545
630
  // Set up the S0 security manager. We can only do that after the controller
546
631
  // interview because we need to know the controller node id.
@@ -1017,6 +1102,182 @@ class Driver extends shared_1.TypedEventEmitter {
1017
1102
  throw new core_1.ZWaveError("Cannot retrieve the version of a CC that is not implemented", core_1.ZWaveErrorCodes.CC_NotSupported);
1018
1103
  }
1019
1104
  }
1105
+ async maySoftReset() {
1106
+ var _a;
1107
+ // If we've previously determined a stick not to support soft reset, don't bother trying again
1108
+ let supportsSoftReset;
1109
+ if (this._controllerInterviewed) {
1110
+ supportsSoftReset = this.controller.supportsSoftReset;
1111
+ }
1112
+ else {
1113
+ // The controller wasn't interviewed yet, read the json file manually
1114
+ const fs = this.options.storage.driver;
1115
+ const cacheFile = path_1.default.join(this.cacheDir, this.controller.homeId.toString(16) + ".json");
1116
+ // Read it if it exists
1117
+ let json;
1118
+ if (await fs.pathExists(cacheFile)) {
1119
+ try {
1120
+ json = JSON.parse(await fs.readFile(cacheFile, "utf8"));
1121
+ }
1122
+ catch { }
1123
+ }
1124
+ supportsSoftReset = (_a = json === null || json === void 0 ? void 0 : json.controller) === null || _a === void 0 ? void 0 : _a.supportsSoftReset;
1125
+ }
1126
+ if (supportsSoftReset === false)
1127
+ return false;
1128
+ // Blacklist some sticks that are known to not support soft reset
1129
+ const { manufacturerId, productType, productId } = this.controller;
1130
+ // Z-Wave.me UZB1
1131
+ if (manufacturerId === 0x0115 &&
1132
+ productType === 0x0000 &&
1133
+ productId === 0x0000) {
1134
+ return false;
1135
+ }
1136
+ // Z-Wave.me UZB
1137
+ if (manufacturerId === 0x0115 &&
1138
+ productType === 0x0400 &&
1139
+ productId === 0x0001) {
1140
+ return false;
1141
+ }
1142
+ return true;
1143
+ }
1144
+ async rememberNoSoftReset() {
1145
+ var _a;
1146
+ this.driverLog.print("Soft reset seems not to be supported by this stick, disabling it.", "warn");
1147
+ this.controller.supportsSoftReset = false;
1148
+ if (this._controllerInterviewed) {
1149
+ // We can use the normal method for this
1150
+ await this.saveNetworkToCacheInternal();
1151
+ }
1152
+ else {
1153
+ // saveNetworkToCache won't write anything, just edit the file "manually"
1154
+ // TODO: This is ugly, rework this when changing how the network data is saved
1155
+ const fs = this.options.storage.driver;
1156
+ await fs.ensureDir(this.cacheDir);
1157
+ const cacheFile = path_1.default.join(this.cacheDir, this.controller.homeId.toString(16) + ".json");
1158
+ // Read it if it exists
1159
+ let json;
1160
+ if (await fs.pathExists(cacheFile)) {
1161
+ try {
1162
+ json = JSON.parse(await fs.readFile(cacheFile, "utf8"));
1163
+ }
1164
+ catch { }
1165
+ }
1166
+ // Change the supportsSoftReset flag
1167
+ json !== null && json !== void 0 ? json : (json = {});
1168
+ (_a = json.controller) !== null && _a !== void 0 ? _a : (json.controller = {});
1169
+ json.controller.supportsSoftReset = false;
1170
+ // And save it again
1171
+ const jsonString = (0, shared_1.stringify)(json);
1172
+ await this.options.storage.driver.writeFile(cacheFile, jsonString, "utf8");
1173
+ }
1174
+ }
1175
+ /**
1176
+ * Soft-resets the controller if the feature is enabled
1177
+ */
1178
+ async trySoftReset() {
1179
+ if (this.options.enableSoftReset) {
1180
+ await this.softReset();
1181
+ }
1182
+ else {
1183
+ const message = `The soft reset feature is not enabled, skipping API call.`;
1184
+ this.controllerLog.print(message, "warn");
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Instruct the controller to soft-reset.
1189
+ *
1190
+ * **Warning:** USB modules will reconnect, meaning that they might get a new address.
1191
+ *
1192
+ * **Warning:** This call will throw if soft-reset is not enabled.
1193
+ */
1194
+ async softReset() {
1195
+ if (!this.options.enableSoftReset) {
1196
+ const message = `The soft reset feature has been disabled with a config option or the ZWAVEJS_DISABLE_SOFT_RESET environment variable.`;
1197
+ this.controllerLog.print(message, "error");
1198
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_FeatureDisabled);
1199
+ }
1200
+ return this.softResetInternal(true);
1201
+ }
1202
+ async softResetInternal(destroyOnError) {
1203
+ this.controllerLog.print("Performing soft reset...");
1204
+ try {
1205
+ this.isSoftResetting = true;
1206
+ await this.sendMessage(new SoftResetRequest_1.SoftResetRequest(this), {
1207
+ supportCheck: false,
1208
+ pauseSendThread: true,
1209
+ });
1210
+ }
1211
+ catch (e) {
1212
+ this.controllerLog.print(`Soft reset failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
1213
+ }
1214
+ // Make sure we're able to communicate with the controller again
1215
+ if (!(await this.ensureSerialAPI())) {
1216
+ if (destroyOnError) {
1217
+ await this.destroy();
1218
+ }
1219
+ else {
1220
+ throw new core_1.ZWaveError("The Serial API did not respond after soft-reset", core_1.ZWaveErrorCodes.Driver_Failed);
1221
+ }
1222
+ }
1223
+ this.isSoftResetting = false;
1224
+ // And resume sending
1225
+ this.unpauseSendThread();
1226
+ }
1227
+ async ensureSerialAPI() {
1228
+ // Wait 1.5 seconds after reset to ensure that the module is ready for communication again
1229
+ // Z-Wave 700 sticks are relatively fast, so we also wait for the Serial API started command
1230
+ // to bail early
1231
+ this.controllerLog.print("Waiting for the controller to reconnect...");
1232
+ let waitResult = await this.waitForMessage((msg) => msg.functionType === Constants_1.FunctionType.SerialAPIStarted, 1500).catch(() => false);
1233
+ if (waitResult) {
1234
+ // Serial API did start, maybe do something with the information?
1235
+ this.controllerLog.print("reconnected and restarted");
1236
+ return true;
1237
+ }
1238
+ // If the controller disconnected the serial port during the soft reset, we need to re-open it
1239
+ if (!this.serial.isOpen) {
1240
+ this.controllerLog.print("Re-opening serial port...");
1241
+ await this.tryOpenSerialport();
1242
+ }
1243
+ // Wait the configured amount of time for the Serial API started command to be received
1244
+ this.controllerLog.print("Waiting for the Serial API to start...");
1245
+ waitResult = await this.waitForMessage((msg) => msg.functionType === Constants_1.FunctionType.SerialAPIStarted, this.options.timeouts.serialAPIStarted).catch(() => false);
1246
+ if (waitResult) {
1247
+ // Serial API did start, maybe do something with the information?
1248
+ this.controllerLog.print("Serial API started");
1249
+ return true;
1250
+ }
1251
+ this.controllerLog.print("Did not receive notification that Serial API has started, checking if it responds...");
1252
+ // We don't need to use any specific command here. However we're going to use this one in the interview
1253
+ // anyways, so we might aswell use it here too
1254
+ const pollController = async () => {
1255
+ try {
1256
+ // And resume sending - this requires us to unpause the send thread
1257
+ this.unpauseSendThread();
1258
+ await this.sendMessage(new GetControllerVersionMessages_1.GetControllerVersionRequest(this), {
1259
+ supportCheck: false,
1260
+ });
1261
+ this.pauseSendThread();
1262
+ this.controllerLog.print("Serial API responded");
1263
+ return true;
1264
+ }
1265
+ catch {
1266
+ return false;
1267
+ }
1268
+ };
1269
+ // Poll the controller with increasing backoff delay
1270
+ if (await pollController())
1271
+ return true;
1272
+ for (const backoff of [2, 5, 10, 15]) {
1273
+ this.controllerLog.print(`Serial API did not respond, trying again in ${backoff} seconds...`);
1274
+ await (0, async_1.wait)(backoff * 1000, true);
1275
+ if (await pollController())
1276
+ return true;
1277
+ }
1278
+ this.controllerLog.print("Serial API did not respond, giving up", "error");
1279
+ return false;
1280
+ }
1020
1281
  /**
1021
1282
  * Performs a hard reset on the controller. This wipes out all configuration!
1022
1283
  *
@@ -1063,12 +1324,27 @@ class Driver extends shared_1.TypedEventEmitter {
1063
1324
  * Must be called under any circumstances.
1064
1325
  */
1065
1326
  async destroy() {
1066
- var _a, _b, _c;
1327
+ var _a, _b, _c, _d;
1067
1328
  // Ensure this is only called once and all subsequent calls block
1068
1329
  if (this._destroyPromise)
1069
1330
  return this._destroyPromise;
1070
1331
  this._destroyPromise = (0, deferred_promise_1.createDeferredPromise)();
1071
1332
  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
+ }
1072
1348
  // First stop the send thread machine and close the serial port, so nothing happens anymore
1073
1349
  if (this.sendThread.initialized)
1074
1350
  this.sendThread.stop();
@@ -1088,8 +1364,8 @@ class Driver extends shared_1.TypedEventEmitter {
1088
1364
  }
1089
1365
  try {
1090
1366
  // Attempt to close the value DBs
1091
- await ((_a = this._valueDB) === null || _a === void 0 ? void 0 : _a.close());
1092
- await ((_b = this._metadataDB) === null || _b === void 0 ? void 0 : _b.close());
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());
1093
1369
  }
1094
1370
  catch (e) {
1095
1371
  this.driverLog.print(`Closing the value DBs failed: ${(0, shared_1.getErrorMessage)(e)}`, "error");
@@ -1105,7 +1381,7 @@ class Driver extends shared_1.TypedEventEmitter {
1105
1381
  clearTimeout(timeout);
1106
1382
  }
1107
1383
  // Destroy all nodes
1108
- (_c = this._controller) === null || _c === void 0 ? void 0 : _c.nodes.forEach((n) => n.destroy());
1384
+ (_d = this._controller) === null || _d === void 0 ? void 0 : _d.nodes.forEach((n) => n.destroy());
1109
1385
  process.removeListener("exit", this._cleanupHandler);
1110
1386
  process.removeListener("SIGINT", this._cleanupHandler);
1111
1387
  process.removeListener("uncaughtException", this._cleanupHandler);
@@ -1117,7 +1393,7 @@ class Driver extends shared_1.TypedEventEmitter {
1117
1393
  * Is called when the serial port has received a single-byte message or a complete message buffer
1118
1394
  */
1119
1395
  async serialport_onData(data) {
1120
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1396
+ var _a, _b, _c, _d, _e, _f, _g;
1121
1397
  if (typeof data === "number") {
1122
1398
  switch (data) {
1123
1399
  // single-byte messages - just forward them to the send thread
@@ -1145,10 +1421,14 @@ class Driver extends shared_1.TypedEventEmitter {
1145
1421
  // Then ensure there are no errors
1146
1422
  if ((0, ICommandClassContainer_1.isCommandClassContainer)(msg)) {
1147
1423
  (0, CommandClass_1.assertValidCCs)(msg);
1148
- (_c = msg.getNodeUnsafe()) === null || _c === void 0 ? void 0 : _c.incrementStatistics("commandsRX");
1149
1424
  }
1150
- else {
1151
- (_d = this._controller) === null || _d === void 0 ? void 0 : _d.incrementStatistics("messagesRX");
1425
+ if (!!this._controller) {
1426
+ if ((0, ICommandClassContainer_1.isCommandClassContainer)(msg)) {
1427
+ (_c = msg.getNodeUnsafe()) === null || _c === void 0 ? void 0 : _c.incrementStatistics("commandsRX");
1428
+ }
1429
+ else {
1430
+ this._controller.incrementStatistics("messagesRX");
1431
+ }
1152
1432
  }
1153
1433
  // all good, send ACK
1154
1434
  await this.writeHeader(serial_1.MessageHeaders.ACK);
@@ -1162,25 +1442,27 @@ class Driver extends shared_1.TypedEventEmitter {
1162
1442
  const response = this.handleDecodeError(e, data, msg);
1163
1443
  if (response)
1164
1444
  await this.writeHeader(response);
1165
- if ((0, ICommandClassContainer_1.isCommandClassContainer)(msg)) {
1166
- (_e = msg.getNodeUnsafe()) === null || _e === void 0 ? void 0 : _e.incrementStatistics("commandsDroppedRX");
1167
- // Figure out if the command was received with supervision encapsulation
1168
- const supervisionSessionId = SupervisionCC_1.SupervisionCC.getSessionId(msg.command);
1169
- if (supervisionSessionId !== undefined &&
1170
- msg.command instanceof CommandClass_1.InvalidCC) {
1171
- // If it was, we need to notify the sender that we couldn't decode the command
1172
- await ((_f = msg
1173
- .getNodeUnsafe()) === null || _f === void 0 ? void 0 : _f.createAPI(core_1.CommandClasses.Supervision, false).sendReport({
1174
- sessionId: supervisionSessionId,
1175
- moreUpdatesFollow: false,
1176
- status: SupervisionCC_1.SupervisionStatus.NoSupport,
1177
- secure: msg.command.secure,
1178
- }));
1179
- return;
1445
+ if (!!this._controller) {
1446
+ if ((0, ICommandClassContainer_1.isCommandClassContainer)(msg)) {
1447
+ (_d = msg.getNodeUnsafe()) === null || _d === void 0 ? void 0 : _d.incrementStatistics("commandsDroppedRX");
1448
+ // Figure out if the command was received with supervision encapsulation
1449
+ const supervisionSessionId = SupervisionCC_1.SupervisionCC.getSessionId(msg.command);
1450
+ if (supervisionSessionId !== undefined &&
1451
+ msg.command instanceof CommandClass_1.InvalidCC) {
1452
+ // If it was, we need to notify the sender that we couldn't decode the command
1453
+ await ((_e = msg
1454
+ .getNodeUnsafe()) === null || _e === void 0 ? void 0 : _e.createAPI(core_1.CommandClasses.Supervision, false).sendReport({
1455
+ sessionId: supervisionSessionId,
1456
+ moreUpdatesFollow: false,
1457
+ status: SupervisionCC_1.SupervisionStatus.NoSupport,
1458
+ secure: msg.command.secure,
1459
+ }));
1460
+ return;
1461
+ }
1462
+ }
1463
+ else {
1464
+ this._controller.incrementStatistics("messagesDroppedRX");
1180
1465
  }
1181
- }
1182
- else {
1183
- (_g = this._controller) === null || _g === void 0 ? void 0 : _g.incrementStatistics("messagesDroppedRX");
1184
1466
  }
1185
1467
  }
1186
1468
  }
@@ -1192,7 +1474,7 @@ class Driver extends shared_1.TypedEventEmitter {
1192
1474
  return;
1193
1475
  }
1194
1476
  // Print something, so we know what is wrong
1195
- this._driverLog.print((_h = ee.stack) !== null && _h !== void 0 ? _h : ee.message, "error");
1477
+ this._driverLog.print((_f = ee.stack) !== null && _f !== void 0 ? _f : ee.message, "error");
1196
1478
  }
1197
1479
  }
1198
1480
  // Don't keep handling the message
@@ -1207,7 +1489,7 @@ class Driver extends shared_1.TypedEventEmitter {
1207
1489
  // and handle the encapsulation part normally
1208
1490
  if (msg.command instanceof
1209
1491
  SecurityCC_1.SecurityCCCommandEncapsulationNonceGet) {
1210
- void ((_j = msg.getNodeUnsafe()) === null || _j === void 0 ? void 0 : _j.handleSecurityNonceGet());
1492
+ void ((_g = msg.getNodeUnsafe()) === null || _g === void 0 ? void 0 : _g.handleSecurityNonceGet());
1211
1493
  }
1212
1494
  // Transport Service commands must be handled before assembling partial CCs
1213
1495
  if ((0, TransportServiceCC_1.isTransportServiceEncapsulation)(msg.command)) {
@@ -1312,7 +1594,7 @@ class Driver extends shared_1.TypedEventEmitter {
1312
1594
  throw e;
1313
1595
  }
1314
1596
  async handleSecurityS2DecodeError(e, msg) {
1315
- var _a;
1597
+ var _a, _b;
1316
1598
  if (!(0, core_1.isZWaveError)(e))
1317
1599
  return false;
1318
1600
  if ((e.code === core_1.ZWaveErrorCodes.Security2CC_NoSPAN ||
@@ -1344,13 +1626,42 @@ class Driver extends shared_1.TypedEventEmitter {
1344
1626
  ? "Message authentication failed"
1345
1627
  : "No SPAN is established yet";
1346
1628
  if (this.controller.bootstrappingS2NodeId === nodeId) {
1347
- // The node is currently being bootstrapped. Us not being able to decode the command means we need to abort the bootstrapping process
1348
- this.controllerLog.logNode(nodeId, {
1349
- message: `${message}, cannot decode command. Aborting the S2 bootstrapping process...`,
1350
- level: "error",
1351
- direction: "inbound",
1352
- });
1353
- this.controller.cancelSecureBootstrapS2(shared_2.KEXFailType.BootstrappingCanceled);
1629
+ // The node is currently being bootstrapped.
1630
+ if ((_b = this.securityManager2) === null || _b === void 0 ? void 0 : _b.tempKeys.has(nodeId)) {
1631
+ // The DSK has been verified, so we should be able to decode this command.
1632
+ // If this is the first attempt, we need to request a nonce first
1633
+ if (this.securityManager2.getSPANState(nodeId).type ===
1634
+ core_1.SPANState.None) {
1635
+ this.controllerLog.logNode(nodeId, {
1636
+ message: `${message}, cannot decode command. Requesting a nonce...`,
1637
+ level: "verbose",
1638
+ direction: "outbound",
1639
+ });
1640
+ // Send the node our nonce
1641
+ node.commandClasses["Security 2"]
1642
+ .sendNonce()
1643
+ .catch(() => {
1644
+ // Ignore errors
1645
+ });
1646
+ }
1647
+ else {
1648
+ // Us repeatedly not being able to decode the command means we need to abort the bootstrapping process
1649
+ // because the PIN is wrong
1650
+ this.controllerLog.logNode(nodeId, {
1651
+ message: `${message}, cannot decode command. Aborting the S2 bootstrapping process...`,
1652
+ level: "error",
1653
+ direction: "inbound",
1654
+ });
1655
+ this.controller.cancelSecureBootstrapS2(shared_2.KEXFailType.BootstrappingCanceled);
1656
+ }
1657
+ }
1658
+ else {
1659
+ this.controllerLog.logNode(nodeId, {
1660
+ message: `Ignoring KEXSet because the DSK has not been verified yet`,
1661
+ level: "verbose",
1662
+ direction: "inbound",
1663
+ });
1664
+ }
1354
1665
  }
1355
1666
  else if (!this.hasPendingTransactions(isS2NonceReport)) {
1356
1667
  this.controllerLog.logNode(nodeId, {
@@ -1850,8 +2161,42 @@ ${handlers.length} left`);
1850
2161
  return;
1851
2162
  }
1852
2163
  }
2164
+ else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestSmartStartHomeIDReceived) {
2165
+ // the controller is in Smart Start learn mode and a node requests inclusion via Smart Start
2166
+ this.controllerLog.print("Received Smart Start inclusion request");
2167
+ if (this.controller.inclusionState !== Inclusion_1.InclusionState.Idle &&
2168
+ this.controller.inclusionState !== Inclusion_1.InclusionState.SmartStart) {
2169
+ this.controllerLog.print("Controller is busy and cannot handle this inclusion request right now...");
2170
+ return;
2171
+ }
2172
+ // Check if the node is on the provisioning list
2173
+ const provisioningEntry = this.controller.provisioningList.find((entry) => (0, core_1.nwiHomeIdFromDSK)((0, core_1.dskFromString)(entry.dsk)).equals(msg.nwiHomeId));
2174
+ if (!provisioningEntry) {
2175
+ this.controllerLog.print("NWI Home ID not found in provisioning list, ignoring request...");
2176
+ return;
2177
+ }
2178
+ this.controllerLog.print("NWI Home ID found in provisioning list, including node...");
2179
+ try {
2180
+ const result = await this.controller.beginInclusionSmartStart(provisioningEntry);
2181
+ if (!result) {
2182
+ this.controllerLog.print("Smart Start inclusion could not be started", "error");
2183
+ }
2184
+ }
2185
+ catch (e) {
2186
+ this.controllerLog.print(`Smart Start inclusion could not be started: ${(0, shared_1.getErrorMessage)(e)}`, "error");
2187
+ }
2188
+ }
1853
2189
  }
1854
2190
  else {
2191
+ // Check if we have a dynamic handler waiting for this message
2192
+ for (const entry of this.awaitedMessages) {
2193
+ if (entry.predicate(msg)) {
2194
+ // resolve the promise - this will remove the entry from the list
2195
+ entry.promise.resolve(msg);
2196
+ return;
2197
+ }
2198
+ }
2199
+ // Otherwise loop through the static handlers
1855
2200
  // TODO: This deserves a nicer formatting
1856
2201
  this.driverLog.print(`handling request ${Constants_1.FunctionType[msg.functionType]} (${msg.functionType})`);
1857
2202
  handlers = this.requestHandlers.get(msg.functionType);
@@ -2010,6 +2355,9 @@ ${handlers.length} left`);
2010
2355
  transaction.changeNodeStatusOnTimeout =
2011
2356
  options.changeNodeStatusOnMissingACK;
2012
2357
  }
2358
+ if (options.pauseSendThread === true) {
2359
+ transaction.pauseSendThread = true;
2360
+ }
2013
2361
  transaction.requestWakeUpOnDemand = !!options.requestWakeUpOnDemand;
2014
2362
  transaction.tag = options.tag;
2015
2363
  // start sending now (maybe)
@@ -2216,7 +2564,41 @@ ${handlers.length} left`);
2216
2564
  return (_a = this.serial) === null || _a === void 0 ? void 0 : _a.writeAsync(data);
2217
2565
  }
2218
2566
  /**
2219
- * Waits until a command is received or a timeout has elapsed. Returns the received command.
2567
+ * Waits until an unsolicited serial message is received or a timeout has elapsed. Returns the received message.
2568
+ *
2569
+ * **Note:** This does not trigger for [Bridge]ApplicationUpdateRequests, which are handled differently. To wait for a certain CommandClass, use {@link waitForCommand}.
2570
+ * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
2571
+ * @param predicate A predicate function to test all incoming messages
2572
+ */
2573
+ waitForMessage(predicate, timeout) {
2574
+ return new Promise((resolve, reject) => {
2575
+ const entry = {
2576
+ predicate,
2577
+ promise: (0, deferred_promise_1.createDeferredPromise)(),
2578
+ timeout: undefined,
2579
+ };
2580
+ this.awaitedMessages.push(entry);
2581
+ const removeEntry = () => {
2582
+ if (entry.timeout)
2583
+ clearTimeout(entry.timeout);
2584
+ const index = this.awaitedMessages.indexOf(entry);
2585
+ if (index !== -1)
2586
+ this.awaitedMessages.splice(index, 1);
2587
+ };
2588
+ // When the timeout elapses, remove the wait entry and reject the returned Promise
2589
+ entry.timeout = setTimeout(() => {
2590
+ removeEntry();
2591
+ reject(new core_1.ZWaveError(`Received no matching message within the provided timeout!`, core_1.ZWaveErrorCodes.Controller_Timeout));
2592
+ }, timeout).unref();
2593
+ // When the promise is resolved, remove the wait entry and resolve the returned Promise
2594
+ void entry.promise.then((cc) => {
2595
+ removeEntry();
2596
+ resolve(cc);
2597
+ });
2598
+ });
2599
+ }
2600
+ /**
2601
+ * Waits until a CommandClass is received or a timeout has elapsed. Returns the received command.
2220
2602
  * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
2221
2603
  * @param predicate A predicate function to test all incoming command classes
2222
2604
  */
@@ -2323,6 +2705,20 @@ ${handlers.length} left`);
2323
2705
  rejectAllTransactionsForNode(nodeId, errorMsg = `The node is dead`, errorCode = core_1.ZWaveErrorCodes.Controller_MessageDropped) {
2324
2706
  this.rejectTransactions((t) => t.message.getNodeId() === nodeId, errorMsg, errorCode);
2325
2707
  }
2708
+ /**
2709
+ * @internal
2710
+ * Pauses the send thread, avoiding commands to be sent to the controller
2711
+ */
2712
+ pauseSendThread() {
2713
+ this.sendThread.send({ type: "pause" });
2714
+ }
2715
+ /**
2716
+ * @internal
2717
+ * Unpauses the send thread, allowing commands to be sent to the controller again
2718
+ */
2719
+ unpauseSendThread() {
2720
+ this.sendThread.send({ type: "unpause" });
2721
+ }
2326
2722
  /** Re-sorts the send queue */
2327
2723
  sortSendQueue() {
2328
2724
  this.sendThread.send("sortQueue");