inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
236 lines (210 loc) • 6.04 kB
text/typescript
import { ZWaveError, ZWaveErrorCodes } from "@zwave-js/core/safe";
import { pick } from "@zwave-js/shared/safe";
import {
FLASH_MAX_PAGE_SIZE,
NVM3_COUNTER_SIZE,
NVM3_OBJ_HEADER_SIZE_SMALL,
NVM3_PAGE_HEADER_SIZE,
NVM3_WORD_SIZE,
ObjectType,
PageStatus,
PageWriteSize,
ZWAVE_APPLICATION_NVM_SIZE,
ZWAVE_PROTOCOL_NVM_SIZE,
} from "./consts";
import {
compressObjects,
fragmentLargeObject,
NVM3Object,
writeObject,
} from "./object";
import { NVM3Page, readPage, writePageHeader } from "./page";
import { dumpObject, dumpPage } from "./utils";
function comparePages(p1: NVM3Page, p2: NVM3Page) {
if (p1.header.eraseCount === p2.header.eraseCount) {
return p1.header.offset - p2.header.offset;
} else {
return p1.header.eraseCount - p2.header.eraseCount;
}
}
export interface NVMMeta {
pageSize: number;
deviceFamily: number;
writeSize: PageWriteSize;
memoryMapped: boolean;
}
export interface NVM3Pages {
/** All application pages in the NVM */
applicationPages: NVM3Page[];
/** All application pages in the NVM */
protocolPages: NVM3Page[];
}
export interface NVM3Objects {
/** A compressed map of application-level NVM objects */
applicationObjects: Map<number, NVM3Object>;
/** A compressed map of protocol-level NVM objects */
protocolObjects: Map<number, NVM3Object>;
}
export function parseNVM(
buffer: Buffer,
verbose: boolean = false,
): NVM3Pages & NVM3Objects {
let offset = 0;
const pages: NVM3Page[] = [];
while (offset < buffer.length) {
const { page, bytesRead } = readPage(buffer, offset);
if (verbose) dumpPage(page);
pages.push(page);
offset += bytesRead;
}
const applicationPages = pages.filter(
(p) => p.header.offset < ZWAVE_APPLICATION_NVM_SIZE,
);
const protocolPages = pages.filter(
(p) => p.header.offset >= ZWAVE_APPLICATION_NVM_SIZE,
);
// The pages are written in a ring buffer, find the one with the lowest erase count and start reading from there in order
applicationPages.sort(comparePages);
protocolPages.sort(comparePages);
// Build a compressed view of the NVM objects
const applicationObjects = compressObjects(
applicationPages.reduce(
(acc, page) => acc.concat(page.objects),
[] as NVM3Object[],
),
);
const protocolObjects = compressObjects(
protocolPages.reduce(
(acc, page) => acc.concat(page.objects),
[] as NVM3Object[],
),
);
if (verbose) {
console.log();
console.log();
console.log("Application objects:");
applicationObjects.forEach((obj) => dumpObject(obj, true));
console.log();
console.log("Protocol objects:");
protocolObjects.forEach((obj) => dumpObject(obj, true));
}
return {
applicationPages,
protocolPages,
applicationObjects,
protocolObjects,
};
}
export type EncodeNVMOptions = Partial<NVMMeta>;
export function encodeNVM(
/** A compressed map of application-level NVM objects */
applicationObjects: Map<number, NVM3Object>,
/** A compressed map of protocol-level NVM objects */
protocolObjects: Map<number, NVM3Object>,
options?: EncodeNVMOptions,
): Buffer {
const {
deviceFamily = 2047,
writeSize = PageWriteSize.WRITE_SIZE_16,
memoryMapped = true,
} = options ?? {};
const pageSize = Math.min(
options?.pageSize ?? FLASH_MAX_PAGE_SIZE,
FLASH_MAX_PAGE_SIZE,
);
const createEmptyPage = (): Buffer => {
const ret = Buffer.alloc(pageSize, 0xff);
writePageHeader({
version: 0x01,
eraseCount: 0,
encrypted: false,
deviceFamily,
memoryMapped,
pageSize,
status: PageStatus.OK,
writeSize,
}).copy(ret, 0);
return ret;
};
const applicationPages: Buffer[] = [];
for (let i = 0; i < ZWAVE_APPLICATION_NVM_SIZE / pageSize; i++) {
applicationPages.push(createEmptyPage());
}
const protocolPages: Buffer[] = [];
for (let i = 0; i < ZWAVE_PROTOCOL_NVM_SIZE / pageSize; i++) {
protocolPages.push(createEmptyPage());
}
const writeObjects = (
pages: Buffer[],
objects: Map<number, NVM3Object>,
) => {
// Keep track where we are at with writing in the pages
let pageIndex = -1;
let offsetInPage = -1;
let remainingSpace = -1;
let currentPage!: Buffer;
const nextPage = () => {
pageIndex++;
if (pageIndex >= pages.length) {
throw new ZWaveError(
"Not enough pages!",
ZWaveErrorCodes.NVM_NoSpace,
);
}
currentPage = pages[pageIndex];
offsetInPage = NVM3_PAGE_HEADER_SIZE;
remainingSpace = pageSize - offsetInPage;
};
const incrementOffset = (by: number) => {
const alignedDelta =
(by + NVM3_WORD_SIZE - 1) & ~(NVM3_WORD_SIZE - 1);
offsetInPage += alignedDelta;
remainingSpace = pageSize - offsetInPage;
};
nextPage();
for (const obj of objects.values()) {
let fragments: NVM3Object[] | undefined;
if (obj.type === ObjectType.Deleted) continue;
if (
(obj.type === ObjectType.CounterSmall &&
remainingSpace <
NVM3_OBJ_HEADER_SIZE_SMALL + NVM3_COUNTER_SIZE) ||
(obj.type === ObjectType.DataSmall &&
remainingSpace <
NVM3_OBJ_HEADER_SIZE_SMALL + (obj.data?.length ?? 0))
) {
// Small objects cannot be fragmented and need to go on the next page
nextPage();
} else if (
obj.type === ObjectType.CounterLarge ||
obj.type === ObjectType.DataLarge
) {
// Large objects may be fragmented
fragments = fragmentLargeObject(
obj as any,
remainingSpace,
pageSize - NVM3_PAGE_HEADER_SIZE,
);
}
if (!fragments) fragments = [obj];
for (const fragment of fragments) {
const objBuffer = writeObject(fragment);
objBuffer.copy(currentPage, offsetInPage);
incrementOffset(objBuffer.length);
// Each following fragment needs to be written to a different page^
if (fragments.length > 1) nextPage();
}
}
};
writeObjects(applicationPages, applicationObjects);
writeObjects(protocolPages, protocolObjects);
return Buffer.concat([...applicationPages, ...protocolPages]);
}
export function getNVMMeta(page: NVM3Page): NVMMeta {
return pick(page.header, [
"pageSize",
"writeSize",
"memoryMapped",
"deviceFamily",
]);
}