Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: don't recover from bootloader to flash firmware #7444

Merged
merged 4 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/flash/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
69 changes: 47 additions & 22 deletions packages/zwave-js/src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -1449,30 +1451,53 @@ export class Driver extends TypedEventTarget<DriverEventCallbacks>
// 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;
}
}
}
}
Expand Down
26 changes: 19 additions & 7 deletions packages/zwave-js/src/lib/driver/ZWaveOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ integrationTest(
// debug: true,

additionalDriverOptions: {
allowBootloaderOnly: true,
bootloaderMode: "allow",
},

async customSetup(driver, mockController, mockNode) {
Expand Down
5 changes: 4 additions & 1 deletion packages/zwave-js/src/lib/test/integrationTestSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
5 changes: 3 additions & 2 deletions packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ export function prepareDriver(
): Promise<CreateAndStartDriverWithMockPortResult> {
// 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) {
Expand Down
Loading