Skip to content

Commit

Permalink
feat: support Firmware Update Meta Data CC v8 (#7079)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Aug 16, 2024
1 parent b1dc682 commit 98d8e70
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 74 deletions.
122 changes: 96 additions & 26 deletions packages/cc/src/cc/FirmwareUpdateMetaDataCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { V } from "../lib/Values";
import {
FirmwareDownloadStatus,
FirmwareUpdateActivationStatus,
type FirmwareUpdateInitResult,
type FirmwareUpdateMetaData,
FirmwareUpdateMetaDataCommand,
FirmwareUpdateRequestStatus,
Expand All @@ -63,6 +64,12 @@ export const FirmwareUpdateMetaDataCCValues = Object.freeze({
...V.staticProperty("continuesToFunction", undefined, {
internal: true,
}),
...V.staticProperty("supportsResuming", undefined, {
internal: true,
}),
...V.staticProperty("supportsNonSecureTransfer", undefined, {
internal: true,
}),
}),
});

Expand Down Expand Up @@ -127,6 +134,8 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI {
"hardwareVersion",
"continuesToFunction",
"supportsActivation",
"supportsResuming",
"supportsNonSecureTransfer",
]);
}
}
Expand Down Expand Up @@ -155,7 +164,7 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI {
@validateArgs()
public async requestUpdate(
options: FirmwareUpdateMetaDataCCRequestGetOptions,
): Promise<FirmwareUpdateRequestStatus> {
): Promise<FirmwareUpdateInitResult> {
this.assertSupportsCommand(
FirmwareUpdateMetaDataCommand,
FirmwareUpdateMetaDataCommand.RequestGet,
Expand All @@ -175,15 +184,20 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI {
// Do not wait for Nonce Reports
s2VerifyDelivery: false,
});
const { status } = await this.applHost.waitForCommand<
FirmwareUpdateMetaDataCCRequestReport
>(
(cc) =>
cc instanceof FirmwareUpdateMetaDataCCRequestReport
&& cc.nodeId === this.endpoint.nodeId,
60000,
);
return status;
const result = await this.applHost
.waitForCommand<
FirmwareUpdateMetaDataCCRequestReport
>(
(cc) =>
cc instanceof FirmwareUpdateMetaDataCCRequestReport
&& cc.nodeId === this.endpoint.nodeId,
60000,
);
return pick(result, [
"status",
"resume",
"nonSecureTransfer",
]);
}

/**
Expand Down Expand Up @@ -240,7 +254,7 @@ export class FirmwareUpdateMetaDataCCAPI extends PhysicalCCAPI {
}

@commandClass(CommandClasses["Firmware Update Meta Data"])
@implementedVersion(7)
@implementedVersion(8)
@ccValues(FirmwareUpdateMetaDataCCValues)
export class FirmwareUpdateMetaDataCC extends CommandClass {
declare ccCommand: FirmwareUpdateMetaDataCommand;
Expand Down Expand Up @@ -277,9 +291,17 @@ export class FirmwareUpdateMetaDataCC extends CommandClass {
let logMessage = `Received firmware update capabilities:`;
if (caps.firmwareUpgradable) {
logMessage += `
firmware targets: ${[0, ...caps.additionalFirmwareIDs].join(", ")}
continues to function: ${caps.continuesToFunction}
supports activation: ${caps.supportsActivation}`;
firmware targets: ${[0, ...caps.additionalFirmwareIDs].join(", ")}
continues to function: ${caps.continuesToFunction}
supports activation: ${caps.supportsActivation}`;
if (caps.supportsResuming != undefined) {
logMessage += `
supports resuming: ${caps.supportsResuming}`;
}
if (caps.supportsNonSecureTransfer != undefined) {
logMessage += `
supports non-secure transfer: ${caps.supportsNonSecureTransfer}`;
}
} else {
logMessage += `\nfirmware upgradeable: false`;
}
Expand Down Expand Up @@ -312,6 +334,8 @@ export interface FirmwareUpdateMetaDataCCMetaDataReportOptions {
hardwareVersion?: number;
continuesToFunction?: MaybeNotKnown<boolean>;
supportsActivation?: MaybeNotKnown<boolean>;
supportsResuming?: MaybeNotKnown<boolean>;
supportsNonSecureTransfer?: MaybeNotKnown<boolean>;
}

@CCCommand(FirmwareUpdateMetaDataCommand.MetaDataReport)
Expand Down Expand Up @@ -368,6 +392,11 @@ export class FirmwareUpdateMetaDataCCMetaDataReport
if (this.version >= 7) {
this.supportsActivation = !!(capabilities & 0b10);
}
if (this.version >= 8) {
this.supportsResuming = !!(capabilities & 0b1000);
this.supportsNonSecureTransfer =
!!(capabilities & 0b100);
}
}
}
}
Expand All @@ -381,6 +410,8 @@ export class FirmwareUpdateMetaDataCCMetaDataReport
this.hardwareVersion = options.hardwareVersion;
this.continuesToFunction = options.continuesToFunction;
this.supportsActivation = options.supportsActivation;
this.supportsResuming = options.supportsResuming;
this.supportsNonSecureTransfer = options.supportsNonSecureTransfer;
}
}

Expand All @@ -395,9 +426,12 @@ export class FirmwareUpdateMetaDataCCMetaDataReport
public readonly hardwareVersion?: number;
@ccValue(FirmwareUpdateMetaDataCCValues.continuesToFunction)
public readonly continuesToFunction: MaybeNotKnown<boolean>;

@ccValue(FirmwareUpdateMetaDataCCValues.supportsActivation)
public readonly supportsActivation: MaybeNotKnown<boolean>;
@ccValue(FirmwareUpdateMetaDataCCValues.supportsResuming)
public readonly supportsResuming?: MaybeNotKnown<boolean>;
@ccValue(FirmwareUpdateMetaDataCCValues.supportsNonSecureTransfer)
public readonly supportsNonSecureTransfer?: MaybeNotKnown<boolean>;

public serialize(): Buffer {
this.payload = Buffer.alloc(
Expand All @@ -415,9 +449,10 @@ export class FirmwareUpdateMetaDataCCMetaDataReport
offset += 2;
}
this.payload[offset++] = this.hardwareVersion ?? 0xff;
this.payload[offset++] = (this.continuesToFunction ? 0b1 : 0) | (
this.supportsActivation ? 0b10 : 0
);
this.payload[offset++] = (this.continuesToFunction ? 0b1 : 0)
| (this.supportsActivation ? 0b10 : 0)
| (this.supportsNonSecureTransfer ? 0b100 : 0)
| (this.supportsResuming ? 0b1000 : 0);

return super.serialize();
}
Expand Down Expand Up @@ -446,6 +481,13 @@ export class FirmwareUpdateMetaDataCCMetaDataReport
if (this.supportsActivation != undefined) {
message["supports activation"] = this.supportsActivation;
}
if (this.supportsResuming != undefined) {
message["supports resuming"] = this.supportsResuming;
}
if (this.supportsNonSecureTransfer != undefined) {
message["supports non-secure transfer"] =
this.supportsNonSecureTransfer;
}

return {
...super.toLogEntry(host),
Expand All @@ -471,19 +513,32 @@ export class FirmwareUpdateMetaDataCCRequestReport
super(host, options);
validatePayload(this.payload.length >= 1);
this.status = this.payload[0];
if (this.payload.length >= 2) {
this.resume = !!(this.payload[1] & 0b100);
this.nonSecureTransfer = !!(this.payload[1] & 0b10);
}
}

public readonly status: FirmwareUpdateRequestStatus;
public resume?: boolean;
public nonSecureTransfer?: boolean;

public toLogEntry(host?: ZWaveValueHost): MessageOrCCLogEntry {
const message: MessageRecord = {
status: getEnumMemberName(
FirmwareUpdateRequestStatus,
this.status,
),
};
if (this.resume != undefined) {
message.resume = this.resume;
}
if (this.nonSecureTransfer != undefined) {
message["non-secure transfer"] = this.nonSecureTransfer;
}
return {
...super.toLogEntry(host),
message: {
status: getEnumMemberName(
FirmwareUpdateRequestStatus,
this.status,
),
},
message,
};
}
}
Expand All @@ -503,6 +558,9 @@ export type FirmwareUpdateMetaDataCCRequestGetOptions =
activation?: boolean;
// V5+
hardwareVersion?: number;
// V8+
resume?: boolean;
nonSecureTransfer?: boolean;
}>;

@CCCommand(FirmwareUpdateMetaDataCommand.RequestGet)
Expand Down Expand Up @@ -533,6 +591,8 @@ export class FirmwareUpdateMetaDataCCRequestGet
this.fragmentSize = options.fragmentSize;
this.activation = options.activation ?? false;
this.hardwareVersion = options.hardwareVersion;
this.resume = options.resume;
this.nonSecureTransfer = options.nonSecureTransfer;
}
}
}
Expand All @@ -544,12 +604,14 @@ export class FirmwareUpdateMetaDataCCRequestGet
public fragmentSize?: number;
public activation?: boolean;
public hardwareVersion?: number;
public resume?: boolean;
public nonSecureTransfer?: boolean;

public serialize(): Buffer {
const isV3 = this.version >= 3
&& this.firmwareTarget != undefined
&& this.fragmentSize != undefined;
const isV4 = isV3 && this.version >= 4 && this.activation != undefined;
const isV4 = isV3 && this.version >= 4;
const isV5 = isV4
&& this.version >= 5
&& this.hardwareVersion != undefined;
Expand All @@ -564,7 +626,9 @@ export class FirmwareUpdateMetaDataCCRequestGet
this.payload.writeUInt16BE(this.fragmentSize!, 7);
}
if (isV4) {
this.payload[9] = this.activation ? 1 : 0;
this.payload[9] = (this.activation ? 0b1 : 0)
| (this.nonSecureTransfer ? 0b10 : 0)
| (this.resume ? 0b100 : 0);
}
if (isV5) {
this.payload[10] = this.hardwareVersion!;
Expand All @@ -587,6 +651,12 @@ export class FirmwareUpdateMetaDataCCRequestGet
if (this.activation != undefined) {
message.activation = this.activation;
}
if (this.resume != undefined) {
message.resume = this.resume;
}
if (this.nonSecureTransfer != undefined) {
message["non-secure transfer"] = this.nonSecureTransfer;
}
if (this.hardwareVersion != undefined) {
message["hardware version"] = this.hardwareVersion;
}
Expand Down
26 changes: 26 additions & 0 deletions packages/cc/src/lib/_Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,8 @@ export interface FirmwareUpdateMetaData {
hardwareVersion?: number;
continuesToFunction: MaybeNotKnown<boolean>;
supportsActivation: MaybeNotKnown<boolean>;
supportsResuming?: MaybeNotKnown<boolean>;
supportsNonSecureTransfer?: MaybeNotKnown<boolean>;
}

export enum FirmwareUpdateRequestStatus {
Expand All @@ -739,6 +741,14 @@ export enum FirmwareUpdateRequestStatus {
OK = 0xff,
}

export interface FirmwareUpdateInitResult {
status: FirmwareUpdateRequestStatus;
/** Whether the node will resume a previous transfer */
resume?: boolean;
/** Whether the node will accept non-secure firmware fragments */
nonSecureTransfer?: boolean;
}

export enum FirmwareUpdateStatus {
// Error_Timeout is not part of the Z-Wave standard, but we use it to report
// that no status report was received
Expand Down Expand Up @@ -790,6 +800,10 @@ export type FirmwareUpdateCapabilities =
readonly continuesToFunction: MaybeNotKnown<boolean>;
/** Indicates whether the node supports delayed activation of the new firmware */
readonly supportsActivation: MaybeNotKnown<boolean>;
/** Indicates whether the node supports resuming aborted firmware transfers */
readonly supportsResuming: MaybeNotKnown<boolean>;
/** Indicates whether the node supports non-secure firmware transfers */
readonly supportsNonSecureTransfer: MaybeNotKnown<boolean>;
};

export interface FirmwareUpdateProgress {
Expand All @@ -816,6 +830,18 @@ export interface FirmwareUpdateResult {
reInterview: boolean;
}

export interface FirmwareUpdateOptions {
/**
* Whether a previous attempt to update this node's firmware should be resumed (if supported).
*/
resume?: boolean;
/**
* Whether the firmware data should be transferred without encryption (if supported).
* This can massively reduce the time needed.
*/
nonSecureTransfer?: boolean;
}

export enum HailCommand {
Hail = 0x01,
}
Expand Down
4 changes: 3 additions & 1 deletion packages/zwave-js/src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type AssociationGroup,
ECDHProfiles,
FLiRS2WakeUpTime,
type FirmwareUpdateOptions,
type FirmwareUpdateResult,
InclusionControllerCCComplete,
InclusionControllerCCInitiate,
Expand Down Expand Up @@ -7848,6 +7849,7 @@ ${associatedNodes.join(", ")}`,
public async firmwareUpdateOTA(
nodeId: number,
updateInfo: FirmwareUpdateInfo,
options?: FirmwareUpdateOptions,
): Promise<FirmwareUpdateResult> {
// Don't let two firmware updates happen in parallel
if (this.isAnyOTAFirmwareUpdateInProgress()) {
Expand Down Expand Up @@ -7954,7 +7956,7 @@ ${associatedNodes.join(", ")}`,
);
}

return node.updateFirmware(firmwares);
return node.updateFirmware(firmwares, options);
}

private _firmwareUpdateInProgress: boolean = false;
Expand Down
9 changes: 6 additions & 3 deletions packages/zwave-js/src/lib/driver/Driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6488,6 +6488,7 @@ ${handlers.length} left`,
/** Computes the maximum net CC payload size for the given CC or SendDataRequest */
public computeNetCCPayloadSize(
commandOrMsg: CommandClass | SendDataRequest | SendDataBridgeRequest,
ignoreEncapsulation: boolean = false,
): number {
// Recreate the correct encapsulation structure
let msg: SendDataRequest | SendDataBridgeRequest;
Expand All @@ -6497,9 +6498,11 @@ ${handlers.length} left`,
const SendDataConstructor = this.getSendDataSinglecastConstructor();
msg = new SendDataConstructor(this, { command: commandOrMsg });
}
msg.command = this.encapsulateCommands(
msg.command,
) as SinglecastCC<CommandClass>;
if (!ignoreEncapsulation) {
msg.command = this.encapsulateCommands(
msg.command,
) as SinglecastCC<CommandClass>;
}
return msg.command.getMaxPayloadLength(this.getMaxPayloadLength(msg));
}

Expand Down
Loading

0 comments on commit 98d8e70

Please sign in to comment.