import { ZWaveError , ZWaveErrorCodes } from "@zwave-js/core" ; import { pick } from "@zwave-js/shared" ; 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 , p2 ) { 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 , verbose = false ) { let offset = 0 ; const pages = [ ] ; 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 ) ; applicationPages . sort ( comparePages ) ; protocolPages . sort ( comparePages ) ; 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 , /** A compressed map of protocol-level NVM objects */ protocolObjects , options ? ) { const { pageSize = FLASH_MAX_PAGE_SIZE , deviceFamily = 2047 , writeSize = PageWriteSize . WRITE_SIZE_16 , memoryMapped = true , } = options ?? { } ; 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 = [ ] ; for ( let i = 0 ; i < ZWAVE_APPLICATION_NVM_SIZE / pageSize ; i ++ ) { applicationPages . push ( createEmptyPage ( ) ) ; } const protocolPages = [ ] ; for ( let i = 0 ; i < ZWAVE_PROTOCOL_NVM_SIZE / pageSize ; i ++ ) { protocolPages . push ( createEmptyPage ( ) ) ; } const writeObjects = ( pages , objects ) => { let pageIndex = - 1 ; let offsetInPage = - 1 ; let remainingSpace = - 1 ; let currentPage ! ; 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 ) => { 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 ; 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 ) ) ) { nextPage ( ) ; } else if ( obj . type === ObjectType . CounterLarge || obj . type === ObjectType . DataLarge ) { 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 ) ; if ( fragments . length > 1 ) nextPage ( ) ; } } } ; writeObjects ( applicationPages , applicationObjects ) ; writeObjects ( protocolPages , protocolObjects ) ; return Buffer . concat ( [ ... applicationPages , ... protocolPages ] ) ; } export function getNVMMeta ( page ) { return pick ( page . header , [ "pageSize" , "writeSize" , "memoryMapped" , "deviceFamily" , ] ) ; }