UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

236 lines (210 loc) 6.04 kB
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", ]); }