diff --git a/package.json b/package.json index 31f2d4717823..40b67790ccf3 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@zwave-js/cc": "workspace:*", "@zwave-js/config": "workspace:*", "@zwave-js/core": "workspace:*", + "@zwave-js/flash": "workspace:*", "@zwave-js/host": "workspace:*", "@zwave-js/maintenance": "workspace:*", "@zwave-js/nvmedit": "workspace:*", diff --git a/packages/flash/README.md b/packages/flash/README.md new file mode 100644 index 000000000000..b2d193697e87 --- /dev/null +++ b/packages/flash/README.md @@ -0,0 +1,21 @@ +# Z-Wave JS: Firmware Flasher + +CLI utility to flash the firmware on Z-Wave controllers + +**WARNING:** Flashing the wrong firmware may brick your controller. Use at your own risk! + +## Usage + +You can either execute the current version directly from `npm` using + +``` +npx @zwave-js/flash [--verbose] +``` + +or you can execute the version in the checked out repository by executing + +``` +yarn ts packages/nvmedit/src/cli.ts [--verbose] +``` + +The `--verbose` flag will cause the driver logs to be printed to console. diff --git a/packages/flash/bin/flasher.js b/packages/flash/bin/flasher.js new file mode 100644 index 000000000000..89394a709020 --- /dev/null +++ b/packages/flash/bin/flasher.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require("../build/cli.js"); diff --git a/packages/flash/package.json b/packages/flash/package.json new file mode 100644 index 000000000000..3ccccb74968d --- /dev/null +++ b/packages/flash/package.json @@ -0,0 +1,57 @@ +{ + "name": "@zwave-js/flash", + "version": "10.4.0", + "description": "zwave-js: firmware flash utility", + "keywords": [], + "publishConfig": { + "access": "public" + }, + "main": "build/cli.js", + "files": [ + "bin/", + "build/**/*.{js,d.ts,map}" + ], + "bin": "bin/flash.js", + "author": { + "name": "AlCalzone", + "email": "d.griesel@gmx.net" + }, + "license": "MIT", + "homepage": "https://github.com/AlCalzone/node-zwave-js#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/AlCalzone/node-zwave-js.git" + }, + "bugs": { + "url": "https://github.com/AlCalzone/node-zwave-js/issues" + }, + "funding": { + "url": "https://github.com/sponsors/AlCalzone/" + }, + "engines": { + "node": ">=14.13.0" + }, + "dependencies": { + "@zwave-js/core": "workspace:*", + "fs-extra": "^10.1.0", + "yargs": "^17.5.1", + "zwave-js": "workspace:*" + }, + "scripts": { + "build": "tsc -b tsconfig.build.json", + "clean": "del-cli build/ \"*.tsbuildinfo\"", + "//watch": "yarn run build --watch --pretty", + "lint:ts": "eslint --ext .ts --rule \"prettier/prettier: off\" \"src/**/*.ts\"", + "lint:ts:fix": "yarn run lint:ts --fix", + "lint:prettier": "prettier -c \"src/**/*.ts\"", + "lint:prettier:fix": "yarn run lint:prettier -w" + }, + "devDependencies": { + "@types/fs-extra": "^9.0.13", + "@types/node": "^14.18.36", + "@types/yargs": "^17.0.12", + "del-cli": "^5.0.0", + "prettier": "^2.8.1", + "typescript": "4.9.4" + } +} diff --git a/packages/flash/src/cli.ts b/packages/flash/src/cli.ts new file mode 100644 index 000000000000..0978a7eff7a8 --- /dev/null +++ b/packages/flash/src/cli.ts @@ -0,0 +1,121 @@ +import { isZWaveError, ZWaveErrorCodes } from "@zwave-js/core/safe"; +import { wait } from "alcalzone-shared/async"; +import fs from "fs-extra"; +import path from "path"; +import yargs from "yargs"; +import { + ControllerFirmwareUpdateStatus, + Driver, + extractFirmware, + getEnumMemberName, + guessFirmwareFileFormat, +} from "zwave-js"; + +const argv = yargs.parseSync(); +const [port, filename] = argv._.map(String); + +if (!port || !filename) { + console.error("Usage: flasher "); + process.exit(1); +} + +const verbose = !!argv.verbose; + +let firmware: Buffer; + +const driver = new Driver(port, { + logConfig: { + enabled: verbose, + }, + testingHooks: { + skipNodeInterview: true, + loadConfiguration: false, + }, + storage: { + cacheDir: path.join(__dirname, "cache"), + lockDir: path.join(__dirname, "cache/locks"), + }, + allowBootloaderOnly: true, +}) + .on("error", (e) => { + if (isZWaveError(e) && e.code === ZWaveErrorCodes.Driver_Failed) { + process.exit(0); + } + }) + .once("driver ready", async () => { + await flash(); + }) + .once("bootloader ready", async () => { + await flash(); + }); + +function clearLastLine() { + if (verbose) return; + process.stdout.moveCursor(0, -1); + process.stdout.clearLine(1); +} + +async function flash() { + console.log("Flashing firmware..."); + let lastProgress = 0; + driver.controller.on("firmware update progress", (p) => { + const rounded = Math.round(p.progress); + if (rounded > lastProgress) { + lastProgress = rounded; + clearLastLine(); + console.log( + `Flashing firmware... ${rounded.toString().padStart(3, " ")}%`, + ); + } + }); + driver.controller.on("firmware update finished", async (r) => { + if (r.success) { + console.log("Firmware update successful"); + await wait(1000); + process.exit(0); + } else { + console.log( + `Firmware update failed: ${getEnumMemberName( + ControllerFirmwareUpdateStatus, + r.status, + )}`, + ); + await wait(1000); + process.exit(2); + } + }); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + await driver.controller.firmwareUpdateOTW(firmware).catch(() => {}); +} + +async function main() { + let rawFile: Buffer; + try { + const fullPath = path.isAbsolute(filename) + ? filename + : path.join(process.cwd(), filename); + rawFile = await fs.readFile(fullPath); + } catch (e: any) { + console.error("Could not read firmware file:", e.message); + process.exit(1); + } + + try { + const format = guessFirmwareFileFormat(filename, rawFile); + firmware = extractFirmware(rawFile, format).data; + } catch (e: any) { + console.error("Could not parse firmware file:", e.message); + process.exit(1); + } + + try { + console.log("Starting driver..."); + await driver.start(); + } catch (e: any) { + console.error("The Z-Wave driver could not be started:", e.message); + process.exit(1); + } +} + +void main().catch(console.error); diff --git a/packages/flash/tsconfig.build.json b/packages/flash/tsconfig.build.json new file mode 100644 index 000000000000..9e091026d8ef --- /dev/null +++ b/packages/flash/tsconfig.build.json @@ -0,0 +1,10 @@ +// tsconfig for building - only applies to the src directory +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/flash/tsconfig.json b/packages/flash/tsconfig.json new file mode 100644 index 000000000000..afb910bfc4ba --- /dev/null +++ b/packages/flash/tsconfig.json @@ -0,0 +1,7 @@ +// tsconfig for IntelliSense - active in all files in the current package +{ + "extends": "../../tsconfig.json", + "compilerOptions": {}, + "include": ["src/**/*.ts"], + "exclude": ["build/**", "node_modules/**"] +} diff --git a/yarn.lock b/yarn.lock index e9ada12463f6..e26de3eaa8fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4623,6 +4623,25 @@ __metadata: languageName: unknown linkType: soft +"@zwave-js/flash@workspace:*, @zwave-js/flash@workspace:packages/flash": + version: 0.0.0-use.local + resolution: "@zwave-js/flash@workspace:packages/flash" + dependencies: + "@types/fs-extra": ^9.0.13 + "@types/node": ^14.18.36 + "@types/yargs": ^17.0.12 + "@zwave-js/core": "workspace:*" + del-cli: ^5.0.0 + fs-extra: ^10.1.0 + prettier: ^2.8.1 + typescript: 4.9.4 + yargs: ^17.5.1 + zwave-js: "workspace:*" + bin: + flash: bin/flash.js + languageName: unknown + linkType: soft + "@zwave-js/host@workspace:*, @zwave-js/host@workspace:packages/host": version: 0.0.0-use.local resolution: "@zwave-js/host@workspace:packages/host" @@ -4732,6 +4751,7 @@ __metadata: "@zwave-js/cc": "workspace:*" "@zwave-js/config": "workspace:*" "@zwave-js/core": "workspace:*" + "@zwave-js/flash": "workspace:*" "@zwave-js/host": "workspace:*" "@zwave-js/maintenance": "workspace:*" "@zwave-js/nvmedit": "workspace:*"