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: implement 32-bit NVM operations #7114

Merged
merged 1 commit into from
Aug 15, 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
5 changes: 4 additions & 1 deletion packages/serial/src/message/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export enum FunctionType {
ExtNVMReadLongByte = 0x2c, // Reads a byte from the external NVM
ExtExtWriteLongByte = 0x2d, // Writes a byte to the external NVM

NVMOperations = 0x2e, // 700-series command to read and write from/to the external NVM
NVMOperations = 0x2e, // Read and write from/to the external NVM (700+ series)

UNKNOWN_FUNC_CLOCK_SET = 0x30, // ??
UNKNOWN_FUNC_CLOCK_GET = 0x31, // ??
Expand All @@ -80,6 +80,9 @@ export enum FunctionType {

GetBackgroundRSSI = 0x3b, // request the most recent background RSSI levels detected
SetListenBeforeTalkThreshold = 0x3c, // Set the RSSI threshold above which the stick will not transmit

ExtendedNVMOperations = 0x3d, // Read and write from/to the external NVM with 32-bit addresses (700+ series)

RemoveSpecificNodeIdFromNetwork = 0x3f, // Trigger removal of a specific node that desires exclusion from the network

FUNC_ID_ZW_SET_LEARN_NODE_STATE = 0x40, // Not implemented
Expand Down
263 changes: 245 additions & 18 deletions packages/zwave-js/src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
isValidDSK,
isZWaveError,
nwiHomeIdFromDSK,
parseBitMask,
securityClassIsS2,
securityClassOrder,
} from "@zwave-js/core";
Expand Down Expand Up @@ -310,6 +311,15 @@ import {
ExtNVMWriteLongByteRequest,
type ExtNVMWriteLongByteResponse,
} from "../serialapi/nvm/ExtNVMWriteLongByteMessages";
import {
ExtendedNVMOperationStatus,
ExtendedNVMOperationsCloseRequest,
ExtendedNVMOperationsCommand,
ExtendedNVMOperationsOpenRequest,
ExtendedNVMOperationsReadRequest,
type ExtendedNVMOperationsResponse,
ExtendedNVMOperationsWriteRequest,
} from "../serialapi/nvm/ExtendedNVMOperationsMessages";
import {
FirmwareUpdateNVM_GetNewImageRequest,
type FirmwareUpdateNVM_GetNewImageResponse,
Expand Down Expand Up @@ -6897,9 +6907,11 @@ ${associatedNodes.join(", ")}`,
}

/**
* **Z-Wave 700 series only**
* **Z-Wave 700+ series only**
*
* Reads a buffer from the external NVM at the given offset
*
* **Note:** Prefer {@link externalNVMReadBufferExt} if supported, as that command supports larger NVMs than 64 KiB.
*/
public async externalNVMReadBuffer700(
offset: number,
Expand Down Expand Up @@ -6932,6 +6944,50 @@ ${associatedNodes.join(", ")}`,
};
}

/**
* **Z-Wave 700+ series only**
*
* Reads a buffer from the external NVM at the given offset
*
* **Note:** If supported, this command should be preferred over {@link externalNVMReadBuffer700} as it supports larger NVMs than 64 KiB.
*/
public async externalNVMReadBufferExt(
offset: number,
length: number,
): Promise<{ buffer: Buffer; endOfFile: boolean }> {
const ret = await this.driver.sendMessage<
ExtendedNVMOperationsResponse
>(
new ExtendedNVMOperationsReadRequest(this.driver, {
offset,
length,
}),
);
if (!ret.isOK()) {
let message = "Could not read from the external NVM";
if (
ret.status
=== ExtendedNVMOperationStatus.Error_OperationInterference
) {
message += ": interference between read and write operation.";
} else if (
ret.status
=== ExtendedNVMOperationStatus.Error_OperationMismatch
) {
message += ": wrong operation requested.";
}
throw new ZWaveError(
message,
ZWaveErrorCodes.Controller_CommandError,
);
}

return {
buffer: ret.bufferOrBitmask,
endOfFile: ret.status === ExtendedNVMOperationStatus.EndOfFile,
};
}

/**
* **Z-Wave 500 series only**
*
Expand All @@ -6957,9 +7013,12 @@ ${associatedNodes.join(", ")}`,
}

/**
* **Z-Wave 700 series only**
* **Z-Wave 700+ series only**
*
* Writes a buffer to the external NVM at the given offset
*
* **Note:** Prefer {@link externalNVMWriteBufferExt} if supported, as that command supports larger NVMs than 64 KiB.
*
* **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
* Take care not to accidentally overwrite the protocol NVM area!
*/
Expand Down Expand Up @@ -6995,9 +7054,63 @@ ${associatedNodes.join(", ")}`,
}

/**
* **Z-Wave 700 series only**
* **Z-Wave 700+ series only**
*
* Writes a buffer to the external NVM at the given offset
*
* **Note:** If supported, this command should be preferred over {@link externalNVMWriteBuffer700} as it supports larger NVMs than 64 KiB.
*
* **WARNING:** This function can write in the full NVM address space and is not offset to start at the application area.
* Take care not to accidentally overwrite the protocol NVM area!
*/
public async externalNVMWriteBufferExt(
offset: number,
buffer: Buffer,
): Promise<{ endOfFile: boolean }> {
const ret = await this.driver.sendMessage<
ExtendedNVMOperationsResponse
>(
new ExtendedNVMOperationsWriteRequest(this.driver, {
offset,
buffer,
}),
);

if (!ret.isOK()) {
let message = "Could not write to the external NVM";
if (
ret.status
=== ExtendedNVMOperationStatus.Error_OperationInterference
) {
message += ": interference between read and write operation.";
} else if (
ret.status
=== ExtendedNVMOperationStatus.Error_OperationMismatch
) {
message += ": wrong operation requested.";
} else if (
ret.status
=== ExtendedNVMOperationStatus.Error_SubCommandNotSupported
) {
message += ": sub-command not supported.";
}
throw new ZWaveError(
message,
ZWaveErrorCodes.Controller_CommandError,
);
}

return {
endOfFile: ret.status === ExtendedNVMOperationStatus.EndOfFile,
};
}

/**
* **Z-Wave 700+ series only**
*
* Opens the controller's external NVM for reading/writing and returns the NVM size
*
* **Note:** Prefer {@link externalNVMOpenExt} if supported, as that command supports larger NVMs than 64 KiB.
*/
public async externalNVMOpen(): Promise<number> {
const ret = await this.driver.sendMessage<NVMOperationsResponse>(
Expand All @@ -7013,9 +7126,44 @@ ${associatedNodes.join(", ")}`,
}

/**
* **Z-Wave 700 series only**
* **Z-Wave 700+ series only**
*
* Opens the controller's external NVM for reading/writing and returns the NVM size and supported operations.
*
* **Note:** If supported, this command should be preferred over {@link externalNVMOpen} as it supports larger NVMs than 64 KiB.
*/
public async externalNVMOpenExt(): Promise<{
size: number;
supportedOperations: ExtendedNVMOperationsCommand[];
}> {
const ret = await this.driver.sendMessage<
ExtendedNVMOperationsResponse
>(
new ExtendedNVMOperationsOpenRequest(this.driver),
);
if (!ret.isOK()) {
throw new ZWaveError(
"Failed to open the external NVM",
ZWaveErrorCodes.Controller_CommandError,
);
}
const size = ret.offsetOrSize;
const supportedOperations = parseBitMask(
ret.bufferOrBitmask,
ExtendedNVMOperationsCommand.Open,
);
return {
size,
supportedOperations,
};
}

/**
* **Z-Wave 700+ series only**
*
* Closes the controller's external NVM
*
* **Note:** Prefer {@link externalNVMCloseExt} if supported, as that command supports larger NVMs than 64 KiB.
*/
public async externalNVMClose(): Promise<void> {
const ret = await this.driver.sendMessage<NVMOperationsResponse>(
Expand All @@ -7029,6 +7177,27 @@ ${associatedNodes.join(", ")}`,
}
}

/**
* **Z-Wave 700+ series only**
*
* Closes the controller's external NVM
*
* **Note:** If supported, this command should be preferred over {@link externalNVMClose} as it supports larger NVMs than 64 KiB.
*/
public async externalNVMCloseExt(): Promise<void> {
const ret = await this.driver.sendMessage<
ExtendedNVMOperationsResponse
>(
new ExtendedNVMOperationsCloseRequest(this.driver),
);
if (!ret.isOK()) {
throw new ZWaveError(
"Failed to close the external NVM",
ZWaveErrorCodes.Controller_CommandError,
);
}
}

/**
* Creates a backup of the NVM and returns the raw data as a Buffer. The Z-Wave radio is turned off/on automatically.
* @param onProgress Can be used to monitor the progress of the operation, which may take several seconds up to a few minutes depending on the NVM size
Expand Down Expand Up @@ -7114,8 +7283,34 @@ ${associatedNodes.join(", ")}`,
private async backupNVMRaw700(
onProgress?: (bytesRead: number, total: number) => void,
): Promise<Buffer> {
let open: () => Promise<number>;
let read: (
offset: number,
length: number,
) => Promise<{ buffer: Buffer; endOfFile: boolean }>;
let close: () => Promise<void>;

if (
this.supportedFunctionTypes?.includes(
FunctionType.ExtendedNVMOperations,
)
) {
open = async () => {
const { size } = await this.externalNVMOpenExt();
return size;
};
read = (offset, length) =>
this.externalNVMReadBufferExt(offset, length);
close = () => this.externalNVMCloseExt();
} else {
open = () => this.externalNVMOpen();
read = (offset, length) =>
this.externalNVMReadBuffer700(offset, length);
close = () => this.externalNVMClose();
}

// Open NVM for reading
const size = await this.externalNVMOpen();
const size = await open();

const ret = Buffer.allocUnsafe(size);
let offset = 0;
Expand All @@ -7124,11 +7319,10 @@ ${associatedNodes.join(", ")}`,
let chunkSize: number = Math.min(0xff, ret.length);
try {
while (offset < ret.length) {
const { buffer: chunk, endOfFile } = await this
.externalNVMReadBuffer700(
offset,
Math.min(chunkSize, ret.length - offset),
);
const { buffer: chunk, endOfFile } = await read(
offset,
Math.min(chunkSize, ret.length - offset),
);
if (chunkSize === 0xff && chunk.length === 0) {
// Some SDK versions return an empty buffer when trying to read a buffer that is too long
// Fallback to a sane (but maybe slow) size
Expand All @@ -7146,7 +7340,7 @@ ${associatedNodes.join(", ")}`,
}
} finally {
// Whatever happens, close the NVM
await this.externalNVMClose();
await close();
}

return ret;
Expand Down Expand Up @@ -7347,8 +7541,42 @@ ${associatedNodes.join(", ")}`,
nvmData: Buffer,
onProgress?: (bytesWritten: number, total: number) => void,
): Promise<void> {
let open: () => Promise<number>;
let read: (
offset: number,
length: number,
) => Promise<{ buffer: Buffer; endOfFile: boolean }>;
let write: (
offset: number,
buffer: Buffer,
) => Promise<{ endOfFile: boolean }>;
let close: () => Promise<void>;

if (
this.supportedFunctionTypes?.includes(
FunctionType.ExtendedNVMOperations,
)
) {
open = async () => {
const { size } = await this.externalNVMOpenExt();
return size;
};
read = (offset, length) =>
this.externalNVMReadBufferExt(offset, length);
write = (offset, buffer) =>
this.externalNVMWriteBufferExt(offset, buffer);
close = () => this.externalNVMCloseExt();
} else {
open = () => this.externalNVMOpen();
read = (offset, length) =>
this.externalNVMReadBuffer700(offset, length);
write = (offset, buffer) =>
this.externalNVMWriteBuffer700(offset, buffer);
close = () => this.externalNVMClose();
}

// Open NVM for reading
const size = await this.externalNVMOpen();
const size = await open();

if (size !== nvmData.length) {
throw new ZWaveError(
Expand All @@ -7361,15 +7589,14 @@ ${associatedNodes.join(", ")}`,
// For some reason, there is no documentation and no official command for this
// The write requests have the same size as the read response - if this yields no
// data, default to a sane (but maybe slow) size
const chunkSize =
(await this.externalNVMReadBuffer700(0, 0xff)).buffer.length || 48;
const chunkSize = (await read(0, 0xff)).buffer.length || 48;

// Close NVM and re-open again for writing
await this.externalNVMClose();
await this.externalNVMOpen();
await close();
await open();

for (let offset = 0; offset < nvmData.length; offset += chunkSize) {
const { endOfFile } = await this.externalNVMWriteBuffer700(
const { endOfFile } = await write(
offset,
nvmData.subarray(offset, offset + chunkSize),
);
Expand All @@ -7380,7 +7607,7 @@ ${associatedNodes.join(", ")}`,
if (endOfFile) break;
}
// Close NVM
await this.externalNVMClose();
await close();
}

/**
Expand Down
Loading
Loading