diff --git a/packages/flash/src/cli.ts b/packages/flash/src/cli.ts index f60ad8f56c3..186e45b3ef0 100644 --- a/packages/flash/src/cli.ts +++ b/packages/flash/src/cli.ts @@ -39,7 +39,7 @@ const driver = new Driver(port, { cacheDir: path.join(process.cwd(), "cache"), lockDir: path.join(process.cwd(), "cache/locks"), }, - 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 50cf1a50940..df5119fccee 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, }, @@ -1449,30 +1451,53 @@ 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.bootloaderMode === "stay") { + 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 ( + // 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", + ); + // Needed for the OTW feature to be available + this._controller = new ZWaveController( + this, + true, + ); + this.emit("bootloader ready"); + } else { + // bootloaderMode === "recover" + 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 d46e8a8faeb..74d21e61b39 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -351,16 +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. + * @deprecated + * To allow bootloader only mode, set `bootloaderMode` to `allow` instead. + */ + allowBootloaderOnly?: 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. * - * 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`. + * 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. * - * 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. + * Default: `recover` */ - allowBootloaderOnly?: boolean; + bootloaderMode?: "recover" | "allow" | "stay"; /** * An object with application/module/component names and their versions. 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 b5673c94e84..c0ea4034c65 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 55bb3c7d648..21dbf0c3cca 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 e9f679567a5..71879d27b68 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) {