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
@@ -59,6 +59,7 @@ const SendDataShared_1 = require("../serialapi/transport/SendDataShared");
59
59
  const deviceConfig_1 = require("../telemetry/deviceConfig");
60
60
  const sentry_1 = require("../telemetry/sentry");
61
61
  const statistics_1 = require("../telemetry/statistics");
62
+ const Bootloader_1 = require("./Bootloader");
62
63
  const MessageGenerators_1 = require("./MessageGenerators");
63
64
  const NetworkCache_1 = require("./NetworkCache");
64
65
  const SendThreadMachine_1 = require("./SendThreadMachine");
@@ -195,6 +196,8 @@ class Driver extends shared_1.TypedEventEmitter {
195
196
  this.awaitedMessages = [];
196
197
  /** A map of awaited commands */
197
198
  this.awaitedCommands = [];
199
+ /** A map of awaited chunks from the bootloader */
200
+ this.awaitedBootloaderChunks = [];
198
201
  /** A map of Node ID -> ongoing sessions */
199
202
  this.nodeSessions = new Map();
200
203
  this._wasStarted = false;
@@ -457,6 +460,16 @@ class Driver extends shared_1.TypedEventEmitter {
457
460
  return this._controller;
458
461
  }
459
462
  /** @internal */
463
+ get bootloader() {
464
+ if (this._bootloader == undefined) {
465
+ throw new core_1.ZWaveError("The controller is not in bootloader mode!", core_1.ZWaveErrorCodes.Driver_NotReady);
466
+ }
467
+ return this._bootloader;
468
+ }
469
+ isInBootloader() {
470
+ return this._bootloader != undefined;
471
+ }
472
+ /** @internal */
460
473
  get securityManager() {
461
474
  return this._securityManager;
462
475
  }
@@ -608,6 +621,7 @@ class Driver extends shared_1.TypedEventEmitter {
608
621
  }
609
622
  this.serial
610
623
  .on("data", this.serialport_onData.bind(this))
624
+ .on("bootloaderData", this.serialport_onBootloaderData.bind(this))
611
625
  .on("error", (err) => {
612
626
  if (this.isSoftResetting && !this.serial?.isOpen) {
613
627
  // A disconnection while soft resetting is to be expected
@@ -650,6 +664,33 @@ class Driver extends shared_1.TypedEventEmitter {
650
664
  // Per the specs, this should be followed by a soft-reset but we need to be able
651
665
  // to handle sticks that don't support the soft reset command. Therefore we do it
652
666
  // after opening the value DBs
667
+ if (!this.options.testingHooks?.skipBootloaderCheck) {
668
+ // After an incomplete firmware upgrade, we might be stuck in the bootloader
669
+ // Therefore wait a short amount of time to see if the serialport detects bootloader mode.
670
+ // If we are, the bootloader will reply with its menu.
671
+ await (0, async_1.wait)(1000);
672
+ if (this._bootloader) {
673
+ this.driverLog.print("Controller is in bootloader, attempting to recover...", "warn");
674
+ await this.leaveBootloaderInternal();
675
+ // Wait a short time again. If we're in bootloader mode again, we're stuck
676
+ await (0, async_1.wait)(1000);
677
+ if (this._bootloader) {
678
+ if (this.options.allowBootloaderOnly) {
679
+ this.driverLog.print("Failed to recover from bootloader. Staying in bootloader mode as requested.", "warn");
680
+ // Needed for the OTW feature to be available
681
+ this._controller = new Controller_1.ZWaveController(this, true);
682
+ this.emit("bootloader ready");
683
+ }
684
+ else {
685
+ const message = "Failed to recover from bootloader. Please flash a new firmware to continue...";
686
+ this.driverLog.print(message, "error");
687
+ this.emit("error", new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_Failed));
688
+ void this.destroy();
689
+ }
690
+ return;
691
+ }
692
+ }
693
+ }
653
694
  // Try to create the cache directory. This can fail, in which case we should expose a good error message
654
695
  try {
655
696
  await this.options.storage.driver.ensureDir(this.cacheDir);
@@ -1538,6 +1579,11 @@ class Driver extends shared_1.TypedEventEmitter {
1538
1579
  this.controllerLog.print(message, "error");
1539
1580
  throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.Driver_FeatureDisabled);
1540
1581
  }
1582
+ if (this._controller?.isAnyOTAFirmwareUpdateInProgress()) {
1583
+ const message = `Failed to soft reset controller: A firmware update is in progress on this network.`;
1584
+ this.controllerLog.print(message, "error");
1585
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
1586
+ }
1541
1587
  return this.softResetInternal(true);
1542
1588
  }
1543
1589
  async softResetInternal(destroyOnError) {
@@ -1612,7 +1658,9 @@ class Driver extends shared_1.TypedEventEmitter {
1612
1658
  }
1613
1659
  // Wait the configured amount of time for the Serial API started command to be received
1614
1660
  this.controllerLog.print("Waiting for the Serial API to start...");
1615
- waitResult = await this.waitForMessage((msg) => msg.functionType === serial_1.FunctionType.SerialAPIStarted, this.options.timeouts.serialAPIStarted).catch(() => false);
1661
+ waitResult = await this.waitForMessage((msg) => {
1662
+ return msg.functionType === serial_1.FunctionType.SerialAPIStarted;
1663
+ }, this.options.timeouts.serialAPIStarted).catch(() => false);
1616
1664
  if (waitResult) {
1617
1665
  // Serial API did start, maybe do something with the information?
1618
1666
  this.controllerLog.print("Serial API started");
@@ -1656,9 +1704,14 @@ class Driver extends shared_1.TypedEventEmitter {
1656
1704
  */
1657
1705
  async hardReset() {
1658
1706
  this.ensureReady(true);
1707
+ if (this.controller.isAnyOTAFirmwareUpdateInProgress()) {
1708
+ const message = `Failed to hard reset controller: A firmware update is in progress on this network.`;
1709
+ this.controllerLog.print(message, "error");
1710
+ throw new core_1.ZWaveError(message, core_1.ZWaveErrorCodes.FirmwareUpdateCC_NetworkBusy);
1711
+ }
1659
1712
  // Update the controller NIF prior to hard resetting
1660
- await this._controller.setControllerNIF();
1661
- await this._controller.hardReset();
1713
+ await this.controller.setControllerNIF();
1714
+ await this.controller.hardReset();
1662
1715
  // Clean up
1663
1716
  this.rejectTransactions(() => true, `The controller was hard-reset`);
1664
1717
  this.sendNodeToSleepTimers.forEach((timeout) => clearTimeout(timeout));
@@ -1682,6 +1735,9 @@ class Driver extends shared_1.TypedEventEmitter {
1682
1735
  if (includingController && !this._controllerInterviewed) {
1683
1736
  throw new core_1.ZWaveError("The controller is not ready yet", core_1.ZWaveErrorCodes.Driver_NotReady);
1684
1737
  }
1738
+ if (this._bootloader) {
1739
+ throw new core_1.ZWaveError("Cannot do this while in bootloader mode", core_1.ZWaveErrorCodes.Driver_NotReady);
1740
+ }
1685
1741
  }
1686
1742
  /** Indicates whether the driver is ready, i.e. the "driver ready" event was emitted */
1687
1743
  get ready() {
@@ -1737,6 +1793,7 @@ class Driver extends shared_1.TypedEventEmitter {
1737
1793
  this.statisticsTimeout,
1738
1794
  ...this.awaitedCommands.map((c) => c.timeout),
1739
1795
  ...this.awaitedMessages.map((m) => m.timeout),
1796
+ ...this.awaitedBootloaderChunks.map((b) => b.timeout),
1740
1797
  ]) {
1741
1798
  if (timeout)
1742
1799
  clearTimeout(timeout);
@@ -2597,54 +2654,9 @@ ${handlers.length} left`);
2597
2654
  return;
2598
2655
  }
2599
2656
  else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequest) {
2600
- if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestNodeInfoReceived) {
2601
- const node = this.getNodeUnsafe(msg);
2602
- if (node) {
2603
- this.controllerLog.logNode(node.id, {
2604
- message: "Received updated node info",
2605
- direction: "inbound",
2606
- });
2607
- node.updateNodeInfo(msg.nodeInformation);
2608
- // Tell the send thread that we received a NIF from the node
2609
- this.sendThread.send({ type: "NIF", nodeId: node.id });
2610
- if (node.canSleep &&
2611
- node.supportsCC(core_1.CommandClasses["Wake Up"])) {
2612
- // In case this is a sleeping node and there are no messages in the queue, the node may go back to sleep very soon
2613
- this.debounceSendNodeToSleep(node);
2614
- }
2615
- return;
2616
- }
2617
- }
2618
- else if (msg instanceof ApplicationUpdateRequest_1.ApplicationUpdateRequestSmartStartHomeIDReceived) {
2619
- // the controller is in Smart Start learn mode and a node requests inclusion via Smart Start
2620
- this.controllerLog.print("Received Smart Start inclusion request");
2621
- if (this.controller.inclusionState !== Inclusion_1.InclusionState.Idle &&
2622
- this.controller.inclusionState !== Inclusion_1.InclusionState.SmartStart) {
2623
- this.controllerLog.print("Controller is busy and cannot handle this inclusion request right now...");
2624
- return;
2625
- }
2626
- // Check if the node is on the provisioning list
2627
- const provisioningEntry = this.controller.provisioningList.find((entry) => (0, core_1.nwiHomeIdFromDSK)((0, core_1.dskFromString)(entry.dsk)).equals(msg.nwiHomeId));
2628
- if (!provisioningEntry) {
2629
- this.controllerLog.print("NWI Home ID not found in provisioning list, ignoring request...");
2630
- return;
2631
- }
2632
- else if (provisioningEntry.status ===
2633
- Inclusion_1.ProvisioningEntryStatus.Inactive) {
2634
- this.controllerLog.print("The provisioning entry for this node is inactive, ignoring request...");
2635
- return;
2636
- }
2637
- this.controllerLog.print("NWI Home ID found in provisioning list, including node...");
2638
- try {
2639
- const result = await this.controller.beginInclusionSmartStart(provisioningEntry);
2640
- if (!result) {
2641
- this.controllerLog.print("Smart Start inclusion could not be started", "error");
2642
- }
2643
- }
2644
- catch (e) {
2645
- this.controllerLog.print(`Smart Start inclusion could not be started: ${(0, shared_1.getErrorMessage)(e)}`, "error");
2646
- }
2647
- }
2657
+ // Make sure we're ready to handle this command
2658
+ this.ensureReady(true);
2659
+ return this.controller.handleApplicationUpdateRequest(msg);
2648
2660
  }
2649
2661
  else {
2650
2662
  // TODO: This deserves a nicer formatting
@@ -3482,6 +3494,121 @@ ${handlers.length} left`);
3482
3494
  }
3483
3495
  return true;
3484
3496
  }
3497
+ /** @internal */
3498
+ async enterBootloader() {
3499
+ this.controllerLog.print("Entering bootloader...");
3500
+ // await this.controller.toggleRF(false);
3501
+ await this.trySoftReset();
3502
+ this.pauseSendThread();
3503
+ // It would be nicer to not hardcode the command here, but since we're switching stream parsers
3504
+ // mid-command - thus ignoring the ACK, we can't really use the existing communication machinery
3505
+ const promise = this.writeSerial(Buffer.from("01030027db", "hex"));
3506
+ this.serial.mode = serial_1.ZWaveSerialMode.Bootloader;
3507
+ await promise;
3508
+ // Wait if the menu shows up
3509
+ this._enterBootloaderPromise = (0, deferred_promise_1.createDeferredPromise)();
3510
+ const success = await Promise.race([
3511
+ this._enterBootloaderPromise.then(() => true),
3512
+ (0, async_1.wait)(5000, true).then(() => false),
3513
+ ]);
3514
+ if (success) {
3515
+ this.controllerLog.print("Entered bootloader");
3516
+ }
3517
+ else {
3518
+ throw new core_1.ZWaveError("Failed to enter bootloader", core_1.ZWaveErrorCodes.Controller_Timeout);
3519
+ }
3520
+ }
3521
+ leaveBootloaderInternal() {
3522
+ const promise = this.bootloader.runApplication();
3523
+ // Reset the known serial mode. We might end up in serial or bootloader mode afterwards.
3524
+ this.serial.mode = undefined;
3525
+ this._bootloader = undefined;
3526
+ return promise;
3527
+ }
3528
+ /**
3529
+ * @internal
3530
+ * Leaves the bootloader and destroys the driver instance if desired
3531
+ */
3532
+ async leaveBootloader(destroy = false) {
3533
+ this.controllerLog.print("Leaving bootloader...");
3534
+ await this.leaveBootloaderInternal();
3535
+ // TODO: do we need to wait here?
3536
+ if (destroy) {
3537
+ const restartReason = "Restarting driver after OTW update...";
3538
+ this.controllerLog.print(restartReason);
3539
+ await this.destroy();
3540
+ // Let the async calling context finish before emitting the error
3541
+ process.nextTick(() => {
3542
+ this.emit("error", new core_1.ZWaveError(restartReason, core_1.ZWaveErrorCodes.Driver_Failed));
3543
+ });
3544
+ }
3545
+ else {
3546
+ this.unpauseSendThread();
3547
+ await this.ensureSerialAPI();
3548
+ }
3549
+ }
3550
+ serialport_onBootloaderData(data) {
3551
+ switch (data.type) {
3552
+ case serial_1.BootloaderChunkType.Message: {
3553
+ this.controllerLog.print(`[BOOTLOADER] ${data.message}`, "verbose");
3554
+ break;
3555
+ }
3556
+ case serial_1.BootloaderChunkType.FlowControl: {
3557
+ if (data.command === serial_1.XModemMessageHeaders.C) {
3558
+ this.controllerLog.print(`[BOOTLOADER] awaiting input...`, "verbose");
3559
+ }
3560
+ break;
3561
+ }
3562
+ }
3563
+ for (const entry of this.awaitedBootloaderChunks) {
3564
+ if (entry.predicate(data)) {
3565
+ // resolve the promise - this will remove the entry from the list
3566
+ entry.promise.resolve(data);
3567
+ return;
3568
+ }
3569
+ }
3570
+ if (!this._bootloader && data.type === serial_1.BootloaderChunkType.Menu) {
3571
+ // We just entered the bootloader
3572
+ this.controllerLog.print(`[BOOTLOADER] version ${data.version}`, "verbose");
3573
+ this._bootloader = new Bootloader_1.Bootloader(this.writeSerial.bind(this), data.version, data.options);
3574
+ if (this._enterBootloaderPromise) {
3575
+ this._enterBootloaderPromise.resolve();
3576
+ this._enterBootloaderPromise = undefined;
3577
+ }
3578
+ }
3579
+ }
3580
+ /**
3581
+ * Waits until a specific chunk is received from the bootloader or a timeout has elapsed. Returns the received chunk.
3582
+ * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected
3583
+ * @param predicate A predicate function to test all incoming chunks
3584
+ */
3585
+ waitForBootloaderChunk(predicate, timeout) {
3586
+ return new Promise((resolve, reject) => {
3587
+ const entry = {
3588
+ predicate,
3589
+ promise: (0, deferred_promise_1.createDeferredPromise)(),
3590
+ timeout: undefined,
3591
+ };
3592
+ this.awaitedBootloaderChunks.push(entry);
3593
+ const removeEntry = () => {
3594
+ if (entry.timeout)
3595
+ clearTimeout(entry.timeout);
3596
+ const index = this.awaitedBootloaderChunks.indexOf(entry);
3597
+ if (index !== -1)
3598
+ this.awaitedBootloaderChunks.splice(index, 1);
3599
+ };
3600
+ // When the timeout elapses, remove the wait entry and reject the returned Promise
3601
+ entry.timeout = setTimeout(() => {
3602
+ removeEntry();
3603
+ reject(new core_1.ZWaveError(`Received no matching chunk within the provided timeout!`, core_1.ZWaveErrorCodes.Controller_Timeout));
3604
+ }, timeout);
3605
+ // When the promise is resolved, remove the wait entry and resolve the returned Promise
3606
+ void entry.promise.then((cc) => {
3607
+ removeEntry();
3608
+ resolve(cc);
3609
+ });
3610
+ });
3611
+ }
3485
3612
  }
3486
3613
  exports.Driver = Driver;
3487
3614
  //# sourceMappingURL=Driver.js.map