6.1 KiB
☁️ Perform an OTA update {docsify-ignore-all}
NOTE: The following example expects that you have worked through the Quick Start guide and the Blink a light with Basic CC example. Make sure to do that before continuing.
This example assumes that you have a file with the OTA update on disk. To read it, we need to import the fs/promises
module. Add this to the top of your script:
import fs from "node:fs/promises";
Then read the file before the firmware update:
const filename = "path/to/firmware.bin";
const rawData = await fs.readFile(filename);
There are lots of different file formats for Z-Wave firmware updates. Some are encoded as Intel HEX, some are binary. Some are compressed, some are encrypted in a format that the end node can decrypt, some are encrypted in a format that has to be decrypted first, some are just plaintext.
Z-Wave JS can help figure out the format of the firmware file and extract the data that needs to be transmitted to the end device. To have Z-Wave JS "guess" the format of the firmware file, use the guessFirmwareFileFormat
method. This takes conventions about the filename into account, e.g. .otz
files are typically encoded as Intel HEX, but can also be binary, .gbl
files are to be transmitted as-is.
To do so, add this import to the top of your script:
import { extractFirmwareAsync, guessFirmwareFileFormat } from "@zwave-js/core";
and determine the firmware format:
const format = guessFirmwareFileFormat(filename);
Alternatively, you can provide the format yourself, see the updateFirmware
documentation for details. For example, if you know that the firmware file is in the gbl
format, you can specify it like this:
const format: FirmwareFileFormat = "gbl";
Now extract the firmware data that should be transmitted to the end device:
const firmware = await extractFirmwareAsync(rawData, format);
Most file formats do not contain information about the firmware target that should be updated. If you plan on updating a different target than the Z-Wave chip (0), set it manually:
firmware.firmwareTarget = 1;
Likewise, there is no way to determine the firmware ID from firmware update files. If your end device validates the firmware ID, you need to set it manuall, e.g.:
firmware.firmwareId = 0xcafe;
Afterwards, start the firmware update:
try {
const result = await node.updateFirmware([firmware]);
if (result.success) {
console.log("Firmware update finished successfully");
} else {
console.error("Firmware update failed with status:", result.status);
}
} catch (error) {
console.error("Failed to start firmware update:", error);
}
If you know that your firmware data can be transmitted as-is, you can also skip the extraction part and provide the raw data directly:
const result = await node.updateFirmware([{
data: firmwareData,
// optionally specify target and firmare ID:
firmwareTarget: 1,
firmwareId: 0xcafe,
}]);
To be notified of the progress, two driver events exist. Listening to progress can be done like this:
driver.on("firmware update progress", (node, progress) => {
console.log(
`Firmware update progress: ${progress.sentFragments}/${progress.totalFragments}`,
);
});
To be notified about the outcome, either await
the updateFirmware
call like we did above, or use the following event:
driver.on("firmware update finished", (node, result) => {
if (result.success) {
console.log("Firmware update finished successfully");
} else {
console.error("Firmware update failed with status:", result.status);
}
});
Putting it all together
Here's a complete example where you can provide the node ID, firmware file, target and firmware ID as command line arguments
import { extractFirmwareAsync, guessFirmwareFileFormat } from "@zwave-js/core";
import fs from "node:fs/promises";
import { Driver } from "zwave-js";
// ...driver initialization, see Quick Start guide
async function main() {
const node = driver.controller.nodes.getOrThrow(2);
// Expects the script to be called like
// node ota-update.js <nodeId> <path/to/firmware> [<target> <firmwareIdHex>]
// where the target and firmware id are optional
const nodeId = process.argv[2] ? parseInt(process.argv[2], 10) : undefined;
const filename = process.argv[3];
if (!nodeId || !filename) {
console.error(
"Usage: node ota-update.js <nodeId> <path/to/firmware> [<target> <firmwareIdHex>]",
);
process.exit(1);
}
const node = driver.controller.nodes.get(nodeId);
if (!node) {
console.error(`Node ${nodeId} not found`);
process.exit(1);
}
const firmwareTarget = process.argv[4] ? parseInt(process.argv[4], 10) : 0;
const firmwareId = process.argv[5]
? parseInt(process.argv[5], 16)
: undefined;
const rawData = await fs.readFile(filename);
let firmware;
try {
const format = guessFirmwareFileFormat(filename);
firmware = await extractFirmwareAsync(rawData, format);
} catch (e) {
console.error("Failed to extract firmware", e);
process.exit(1);
}
if (firmwareTarget !== 0) {
firmware.firmwareTarget = firmwareTarget;
}
if (firmwareId != undefined) {
firmware.firmwareId = firmwareId;
}
try {
const result = await node.updateFirmware([firmware]);
if (result.success) {
console.log("Firmware update finished successfully");
process.exit(0);
} else {
console.error("Firmware update failed with status:", result.status);
process.exit(1);
}
} catch (error) {
console.error("Failed to start firmware update:", error);
process.exit(1);
}
}
driver.on("firmware update progress", (node, progress) => {
console.log(
`Firmware update progress: ${progress.sentFragments}/${progress.totalFragments}`,
);
});
To update node 2 with path/to/firmware.bin
, simply run the script with
node ota-update.js 2 path/to/firmware.bin
If you want to update a different target (1) or firmware ID (0xcafe
), you can provide them as additional arguments:
node ota-update.js 2 path/to/firmware.bin 1 0xcafe