From babe66ee20ee8e8438ff14786ba0589eea70c39e Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 29 Nov 2024 19:48:25 +0100 Subject: [PATCH 1/4] fix: don't recover from bootloader to flash firmware --- packages/flash/src/cli.ts | 3 +- packages/zwave-js/src/lib/driver/Driver.ts | 62 ++++++++++++------- .../zwave-js/src/lib/driver/ZWaveOptions.ts | 2 + 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/packages/flash/src/cli.ts b/packages/flash/src/cli.ts index f60ad8f56c3e..b1f0ee39b065 100644 --- a/packages/flash/src/cli.ts +++ b/packages/flash/src/cli.ts @@ -39,7 +39,8 @@ const driver = new Driver(port, { cacheDir: path.join(process.cwd(), "cache"), lockDir: path.join(process.cwd(), "cache/locks"), }, - allowBootloaderOnly: true, + forceBootloaderOnly: true, + // allowBootloaderOnly: true, }) .on("error", (e) => { if (isZWaveError(e) && e.code === ZWaveErrorCodes.Driver_Failed) { diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 50cf1a50940e..ff4162dbf86a 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -1449,30 +1449,48 @@ export class Driver extends TypedEventTarget // If we are, the bootloader will reply with its menu. await wait(1000); if (this._bootloader) { - this.driverLog.print( - "Controller is in bootloader, attempting to recover...", - "warn", - ); - await this.leaveBootloaderInternal(); - - // Wait a short time again. If we're in bootloader mode again, we're stuck - await wait(1000); - if (this._bootloader) { - if (this._options.allowBootloaderOnly) { - this.driverLog.print( - "Failed to recover from bootloader. Staying in bootloader mode as requested.", - "warn", - ); - // Needed for the OTW feature to be available - this._controller = new ZWaveController(this, true); - this.emit("bootloader ready"); - } else { - void this.destroyWithMessage( - "Failed to recover from bootloader. Please flash a new firmware to continue...", - ); - } + if (this._options.forceBootloaderOnly) { + this.driverLog.print( + "Controller is in bootloader mode. Staying in bootloader as requested.", + "warn", + ); + // Needed for the OTW feature to be available + this._controller = new ZWaveController( + this, + true, + ); + this.emit("bootloader ready"); return; + } else { + this.driverLog.print( + "Controller is in bootloader, attempting to recover...", + "warn", + ); + await this.leaveBootloaderInternal(); + + // Wait a short time again. If we're in bootloader mode again, we're stuck + await wait(1000); + if (this._bootloader) { + if (this._options.allowBootloaderOnly) { + this.driverLog.print( + "Failed to recover from bootloader. Staying in bootloader mode as requested.", + "warn", + ); + // Needed for the OTW feature to be available + this._controller = new ZWaveController( + this, + true, + ); + this.emit("bootloader ready"); + } else { + void this.destroyWithMessage( + "Failed to recover from bootloader. Please flash a new firmware to continue...", + ); + } + + return; + } } } } diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index d46e8a8faebe..094e3a7f8fc1 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -362,6 +362,8 @@ export interface ZWaveOptions { */ allowBootloaderOnly?: boolean; + forceBootloaderOnly?: boolean; + /** * An object with application/module/component names and their versions. * This will be used to build a user-agent string for requests to Z-Wave JS webservices. From 13bdcf5385028eb2db22e255406644e011bf83a4 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 31 Jan 2025 23:39:56 +0100 Subject: [PATCH 2/4] refactor: merge bootloader-related options --- packages/zwave-js/src/lib/driver/Driver.ts | 13 +++++++-- .../zwave-js/src/lib/driver/ZWaveOptions.ts | 28 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index ff4162dbf86a..7182b28dfb4e 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -316,6 +316,8 @@ const defaultOptions: ZWaveOptions = { // By default enable the watchdog, unless the env variable is set watchdog: !getenv("ZWAVEJS_DISABLE_WATCHDOG"), }, + // By default, try to recover from bootloader mode + bootloaderMode: "recover", interview: { queryAllUserCodes: false, }, @@ -1434,7 +1436,7 @@ export class Driver extends TypedEventTarget typeof this._options.testingHooks?.onSerialPortOpen === "function" ) { - await this._options.testingHooks.onSerialPortOpen(this.serial!); + await this._options.testingHooks.onSerialPortOpen(this.serial); } // Perform initialization sequence @@ -1449,7 +1451,7 @@ export class Driver extends TypedEventTarget // If we are, the bootloader will reply with its menu. await wait(1000); if (this._bootloader) { - if (this._options.forceBootloaderOnly) { + if (this._options.bootloaderMode === "stay") { this.driverLog.print( "Controller is in bootloader mode. Staying in bootloader as requested.", "warn", @@ -1472,7 +1474,11 @@ export class Driver extends TypedEventTarget // Wait a short time again. If we're in bootloader mode again, we're stuck await wait(1000); if (this._bootloader) { - if (this._options.allowBootloaderOnly) { + if ( + // eslint-disable-next-line @typescript-eslint/no-deprecated + this._options.allowBootloaderOnly + || this._options.bootloaderMode === "allow" + ) { this.driverLog.print( "Failed to recover from bootloader. Staying in bootloader mode as requested.", "warn", @@ -1484,6 +1490,7 @@ export class Driver extends TypedEventTarget ); this.emit("bootloader ready"); } else { + // bootloaderMode === "recover" void this.destroyWithMessage( "Failed to recover from bootloader. Please flash a new firmware to continue...", ); diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index 094e3a7f8fc1..74d21e61b399 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -351,18 +351,28 @@ export interface ZWaveOptions { }; /** - * Normally, the driver expects to start in Serial API mode and enter the bootloader on demand. If in bootloader, - * it will try to exit it and enter Serial API mode again. - * - * However there are situations where a controller may be stuck in bootloader mode and no Serial API is available. - * In this case, the driver startup will fail, unless this option is set to `true`. - * - * If it is, the driver instance will only be good for interacting with the bootloader, e.g. for flashing a new image. - * Commands attempting to talk to the serial API will fail. + * @deprecated + * To allow bootloader only mode, set `bootloaderMode` to `allow` instead. */ allowBootloaderOnly?: boolean; - forceBootloaderOnly?: boolean; + /** + * Determines how the driver should be have when it encounters a controller that is in bootloader mode + * and when the Serial API is not available (yet). + * This can be useful when a controller may be stuck in bootloader mode, or when the application + * wants to operate in bootloader mode anyways. + * + * The following options exist: + * - `recover`: Z-Wave JS will attempt to recover the controller from bootloader mode. + * If this does not succeed, the driver startup will fail. + * - `allow`: Z-Wave JS will attempt to recover the controller from bootloader mode. + * If this does not succeed, the driver will continue to operate in bootloader mode, + * e.g. for flashing a new image. Commands attempting to talk to the serial API will fail. + * - `stay`: Z-Wave JS will NOT attempt to recover the controller from bootlaoder mode. + * + * Default: `recover` + */ + bootloaderMode?: "recover" | "allow" | "stay"; /** * An object with application/module/component names and their versions. From b5637d51bdbdc2ac28ef33ef406b36c6ab5553bb Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 31 Jan 2025 23:44:27 +0100 Subject: [PATCH 3/4] fix: build --- packages/flash/src/cli.ts | 3 +-- packages/zwave-js/src/lib/driver/Driver.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/flash/src/cli.ts b/packages/flash/src/cli.ts index b1f0ee39b065..186e45b3ef0d 100644 --- a/packages/flash/src/cli.ts +++ b/packages/flash/src/cli.ts @@ -39,8 +39,7 @@ const driver = new Driver(port, { cacheDir: path.join(process.cwd(), "cache"), lockDir: path.join(process.cwd(), "cache/locks"), }, - forceBootloaderOnly: true, - // allowBootloaderOnly: true, + bootloaderMode: "stay", }) .on("error", (e) => { if (isZWaveError(e) && e.code === ZWaveErrorCodes.Driver_Failed) { diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 7182b28dfb4e..df5119fccee0 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -1436,7 +1436,7 @@ export class Driver extends TypedEventTarget typeof this._options.testingHooks?.onSerialPortOpen === "function" ) { - await this._options.testingHooks.onSerialPortOpen(this.serial); + await this._options.testingHooks.onSerialPortOpen(this.serial!); } // Perform initialization sequence From 951ae35430878f30dc2158cc50527218d73fd8c0 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Fri, 31 Jan 2025 23:49:35 +0100 Subject: [PATCH 4/4] fix: lint --- .../zwave-js/src/lib/test/driver/bootloaderDetection.test.ts | 2 +- packages/zwave-js/src/lib/test/integrationTestSuite.ts | 5 ++++- packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts b/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts index b5673c94e84c..c0ea4034c653 100644 --- a/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts +++ b/packages/zwave-js/src/lib/test/driver/bootloaderDetection.test.ts @@ -10,7 +10,7 @@ integrationTest( // debug: true, additionalDriverOptions: { - allowBootloaderOnly: true, + bootloaderMode: "allow", }, async customSetup(driver, mockController, mockNode) { diff --git a/packages/zwave-js/src/lib/test/integrationTestSuite.ts b/packages/zwave-js/src/lib/test/integrationTestSuite.ts index 55bb3c7d648f..21dbf0c3cca5 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuite.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuite.ts @@ -149,7 +149,10 @@ function suite( } }); - if (options.additionalDriverOptions?.allowBootloaderOnly) { + if ( + options.additionalDriverOptions?.bootloaderMode === "stay" + || options.additionalDriverOptions?.bootloaderMode === "allow" + ) { driver.once("bootloader ready", () => { process.nextTick(resolve); }); diff --git a/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts b/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts index e9f679567a59..71879d27b684 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts @@ -28,8 +28,9 @@ export function prepareDriver( ): Promise { // Skipping the bootloader check speeds up tests a lot additionalOptions.testingHooks ??= {}; - additionalOptions.testingHooks.skipBootloaderCheck = !additionalOptions - .allowBootloaderOnly; + additionalOptions.testingHooks.skipBootloaderCheck = + additionalOptions.bootloaderMode === "recover" + || additionalOptions.bootloaderMode == undefined; const logConfig = additionalOptions.logConfig ?? {}; if (logToFile) {