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

feat: support Firmware Update Meta Data CC v8 #7079

Merged
merged 4 commits into from
Aug 16, 2024
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
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 @@ -7621,6 +7622,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 @@ -7727,7 +7729,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
Loading