import { CommandClasses , ControllerCapabilityFlags , isZWaveError , NodeProtocolInfo , NodeType , stripUndefined , ZWaveError , ZWaveErrorCodes , } from "@zwave-js/core" ; import { cloneDeep , pick } from "@zwave-js/shared" ; import semver from "semver" ; import { ApplicationCCsFile , ApplicationCCsFileID , ApplicationDataFile , ApplicationDataFileID , ApplicationRFConfigFile , ApplicationRFConfigFileID , ApplicationTypeFile , ApplicationTypeFileID , ApplicationVersionFile , ApplicationVersionFileID , ControllerInfoFile , ControllerInfoFileID , ControllerInfoFileOptions , getEmptyRoute , nodeIdToNodeInfoFileIDV0 , nodeIdToNodeInfoFileIDV1 , nodeIdToRouteCacheFileIDV0 , nodeIdToRouteCacheFileIDV1 , NodeInfo , NodeInfoFileV0 , NodeInfoFileV1 , NVMFile , ProtocolAppRouteLockNodeMaskFile , ProtocolAppRouteLockNodeMaskFileID , ProtocolNodeListFile , ProtocolNodeListFileID , ProtocolPendingDiscoveryNodeMaskFile , ProtocolPendingDiscoveryNodeMaskFileID , ProtocolPreferredRepeatersFile , ProtocolPreferredRepeatersFileID , ProtocolRouteCacheExistsNodeMaskFile , ProtocolRouteCacheExistsNodeMaskFileID , ProtocolRouteSlaveSUCNodeMaskFile , ProtocolRouteSlaveSUCNodeMaskFileID , ProtocolSUCPendingUpdateNodeMaskFile , ProtocolSUCPendingUpdateNodeMaskFileID , ProtocolVersionFile , ProtocolVersionFileID , ProtocolVirtualNodeMaskFile , ProtocolVirtualNodeMaskFileID , Route , RouteCache , RouteCacheFileV0 , RouteCacheFileV1 , SUCUpdateEntriesFile , SUCUpdateEntriesFileID , SUCUpdateEntry , } from "./files" ; import { encodeNVM , getNVMMeta , NVM3Objects , NVMMeta , parseNVM , } from "./nvm3/nvm" ; import type { NVM3Object } from "./nvm3/object" ; import { mapToObject } from "./nvm3/utils" ; import { createParser as createNVM500Parser , nmvDetails500 , NVM500JSON , NVMSerializer , } from "./nvm500/NVMParser" ; export interface NVMJSON { format : number ; meta ? : NVMMeta ; controller : NVMJSONController ; nodes : Record < number , NVMJSONNode > ; } export interface NVMJSONController { protocolVersion : string ; applicationVersion : string ; homeId : string ; nodeId : number ; lastNodeId : number ; staticControllerNodeId : number ; sucLastIndex : number ; controllerConfiguration : number ; sucUpdateEntries : SUCUpdateEntry [ ] ; sucAwarenessPushNeeded ? : number | null ; maxNodeId : number ; reservedId : number ; systemState : number ; lastNodeIdLR ? : number | null ; maxNodeIdLR ? : number | null ; reservedIdLR ? : number | null ; primaryLongRangeChannelId ? : number | null ; dcdcConfig ? : number | null ; rfConfig ? : NVMJSONControllerRFConfig | null ; preferredRepeaters ? : number [ ] | null ; isListening : boolean ; optionalFunctionality : boolean ; genericDeviceClass : number ; specificDeviceClass : number ; commandClasses : { includedInsecurely : CommandClasses [ ] ; includedSecurelyInsecureCCs : CommandClasses [ ] ; includedSecurelySecureCCs : CommandClasses [ ] ; } ; applicationData ? : string | null ; } export interface NVMJSONControllerRFConfig { rfRegion : number ; txPower : number ; measured0dBm : number ; enablePTI : number | null ; maxTXPower : number | null ; } export interface NVMJSONNodeWithInfo extends Omit < NodeProtocolInfo , "hasSpecificDeviceClass" > { isVirtual : boolean ; genericDeviceClass : number ; specificDeviceClass : number | null ; neighbors : number [ ] ; sucUpdateIndex : number ; appRouteLock : boolean ; routeSlaveSUC : boolean ; sucPendingUpdate : boolean ; pendingDiscovery : boolean ; lwr ? : Route | null ; nlwr ? : Route | null ; } export interface NVMJSONVirtualNode { isVirtual : true ; } export type NVMJSONNode = NVMJSONNodeWithInfo | NVMJSONVirtualNode ; export function nodeHasInfo ( node ) { return ! node . isVirtual || Object . keys ( node ) . length > 1 ; } function createEmptyPhysicalNode ( ) { return { isVirtual : false , isListening : false , isFrequentListening : false , isRouting : false , supportedDataRates : [ ] , protocolVersion : 0 , optionalFunctionality : false , nodeType : NodeType [ "Routing End Node" ] , supportsSecurity : false , supportsBeaming : false , genericDeviceClass : 0 , specificDeviceClass : null , neighbors : [ ] , sucUpdateIndex : 0 , appRouteLock : false , routeSlaveSUC : false , sucPendingUpdate : false , pendingDiscovery : false , lwr : null , nlwr : null } ; } /** Converts a compressed set of NVM objects to a JSON representation */ export function nvmObjectsToJSON ( applicationObjects , protocolObjects ) { const nodes = new Map < number , NVMJSONNode > ( ) ; const getNode = ( id ) : NVMJSONNode => { if ( ! nodes . has ( id ) ) nodes . set ( id , createEmptyPhysicalNode ( ) ) ; return nodes . get ( id ) ! ; } ; const getObject = ( id ) : NVM3Object | undefined => { if ( typeof id === "number" ) { return protocolObjects . get ( id ) ?? applicationObjects . get ( id ) ; } else { for ( const [ key , obj ] of protocolObjects ) { if ( id ( key ) ) return obj ; } for ( const [ key , obj ] of applicationObjects ) { if ( id ( key ) ) return obj ; } } } ; const getObjectOrThrow = ( id ) : NVM3Object => { const ret = getObject ( id ) ; if ( ret ) return ret ; throw new ZWaveError ( `Object${ typeof id === "number" ? ` ${ id }` : "" } not found!` , ZWaveErrorCodes . NVM_ObjectNotFound ) ; } ; const getFileOrThrow = < T extends NVMFile > ( id ) : T => { const obj = getObjectOrThrow ( id ) ; return NVMFile . from ( obj ) as T ; } ; const getFile = < T extends NVMFile > ( id ) : T | undefined => { const obj = getObject ( id ) ; if ( ! obj ) return undefined ; return NVMFile . from ( obj ) as T ; } ; const protocolVersionFile = getFileOrThrow < ProtocolVersionFile > ( ProtocolVersionFileID ) ; const protocolFileFormat = protocolVersionFile . format ; const nodeIds = getFileOrThrow < ProtocolNodeListFile > ( ProtocolNodeListFileID ) . nodeIds ; const appRouteLock = new Set ( getFileOrThrow < ProtocolAppRouteLockNodeMaskFile > ( ProtocolAppRouteLockNodeMaskFileID ) . nodeIds ) ; const routeSlaveSUC = new Set ( getFileOrThrow < ProtocolRouteSlaveSUCNodeMaskFile > ( ProtocolRouteSlaveSUCNodeMaskFileID ) . nodeIds ) ; const sucPendingUpdate = new Set ( getFileOrThrow < ProtocolSUCPendingUpdateNodeMaskFile > ( ProtocolSUCPendingUpdateNodeMaskFileID ) . nodeIds ) ; const isVirtual = new Set ( getFileOrThrow < ProtocolVirtualNodeMaskFile > ( ProtocolVirtualNodeMaskFileID ) . nodeIds ) ; const pendingDiscovery = new Set ( getFileOrThrow < ProtocolPendingDiscoveryNodeMaskFile > ( ProtocolPendingDiscoveryNodeMaskFileID ) . nodeIds ) ; const routeCacheExists = new Set ( getFileOrThrow < ProtocolRouteCacheExistsNodeMaskFile > ( ProtocolRouteCacheExistsNodeMaskFileID ) . nodeIds ) ; for ( const id of nodeIds ) { const node = getNode ( id ) as NVMJSONNodeWithInfo ; const rememberOnlyVirtual = ( ) => { nodes . set ( id , { isVirtual : true } ) ; } ; let nodeInfo ; try { if ( protocolFileFormat === 0 ) { const fileId = nodeIdToNodeInfoFileIDV0 ( id ) ; const file = getFileOrThrow < NodeInfoFileV0 > ( fileId ) ; nodeInfo = file . nodeInfo ; } else if ( protocolFileFormat <= 4 ) { const fileId = nodeIdToNodeInfoFileIDV1 ( id ) ; const file = getFileOrThrow < NodeInfoFileV1 > ( fileId ) ; nodeInfo = file . nodeInfos . find ( ( i ) => i . nodeId === id ) ! ; } else { throw new ZWaveError ( `Unsupported protocol file format: ${ protocolFileFormat }` , ZWaveErrorCodes . NVM_NotSupported ) ; } } catch ( e ) { if ( e . message . includes ( "Object not found" ) ) { rememberOnlyVirtual ( ) ; continue ; } throw e ; } Object . assign ( node , nodeInfo ) ; node . isVirtual = isVirtual . has ( id ) ; node . appRouteLock = appRouteLock . has ( id ) ; node . routeSlaveSUC = routeSlaveSUC . has ( id ) ; node . sucPendingUpdate = sucPendingUpdate . has ( id ) ; node . pendingDiscovery = pendingDiscovery . has ( id ) ; if ( routeCacheExists . has ( id ) ) { let routeCache ; if ( protocolFileFormat === 0 ) { const fileId = nodeIdToRouteCacheFileIDV0 ( id ) ; const file = getFileOrThrow < RouteCacheFileV0 > ( fileId ) ; routeCache = file . routeCache ; } else if ( protocolFileFormat <= 4 ) { const fileId = nodeIdToRouteCacheFileIDV1 ( id ) ; const file = getFileOrThrow < RouteCacheFileV1 > ( fileId ) ; routeCache = file . routeCaches . find ( ( i ) => i . nodeId === id ) ! ; } else { throw new ZWaveError ( `Unsupported protocol file format: ${ protocolFileFormat }` , ZWaveErrorCodes . NVM_NotSupported ) ; } node . lwr = routeCache . lwr ; node . nlwr = routeCache . nlwr ; } delete node . nodeId ; } const controllerInfoFile = getFileOrThrow < ControllerInfoFile > ( ControllerInfoFileID ) ; const sucUpdateEntries = getFileOrThrow < SUCUpdateEntriesFile > ( SUCUpdateEntriesFileID ) . updateEntries ; const rfConfigFile = getFile < ApplicationRFConfigFile > ( ApplicationRFConfigFileID ) ; const applicationVersionFile = getFileOrThrow < ApplicationVersionFile > ( ApplicationVersionFileID ) ; const applicationCCsFile = getFileOrThrow < ApplicationCCsFile > ( ApplicationCCsFileID ) ; const applicationDataFile = getFile < ApplicationDataFile > ( ApplicationDataFileID ) ; const applicationTypeFile = getFileOrThrow < ApplicationTypeFile > ( ApplicationTypeFileID ) ; const preferredRepeaters = getFile < ProtocolPreferredRepeatersFile > ( ProtocolPreferredRepeatersFileID ) ?. nodeIds ; const controllerProps = [ "nodeId" , "lastNodeId" , "staticControllerNodeId" , "sucLastIndex" , "controllerConfiguration" , "sucAwarenessPushNeeded" , "maxNodeId" , "reservedId" , "systemState" , "lastNodeIdLR" , "maxNodeIdLR" , "reservedIdLR" , "primaryLongRangeChannelId" , "dcdcConfig" , ] as const ; const controller = { protocolVersion : `${ protocolVersionFile . major }.${ protocolVersionFile . minor }.${ protocolVersionFile . patch }` , applicationVersion : `${ applicationVersionFile . major }.${ applicationVersionFile . minor }.${ applicationVersionFile . patch }` , homeId : `0x${ controllerInfoFile . homeId . toString ( "hex" ) }` , ... pick ( controllerInfoFile , controllerProps ) , ... pick ( applicationTypeFile , [ "isListening" , "optionalFunctionality" , "genericDeviceClass" , "specificDeviceClass" , ] ) , commandClasses : pick ( applicationCCsFile , [ "includedInsecurely" , "includedSecurelyInsecureCCs" , "includedSecurelySecureCCs" , ] ) , preferredRepeaters , ... ( rfConfigFile ? { rfConfig : { rfRegion : rfConfigFile . rfRegion , txPower : rfConfigFile . txPower , measured0dBm : rfConfigFile . measured0dBm , enablePTI : rfConfigFile . enablePTI ?? null , maxTXPower : rfConfigFile . maxTXPower ?? null } } : { } ) , sucUpdateEntries , applicationData : applicationDataFile ?. data . toString ( "hex" ) ?? null } ; const optionalControllerProps = [ "sucAwarenessPushNeeded" , "lastNodeIdLR" , "maxNodeIdLR" , "reservedIdLR" , "primaryLongRangeChannelId" , "dcdcConfig" , "rfConfig" , "preferredRepeaters" , "applicationData" , ] as const ; for ( const prop of optionalControllerProps ) { if ( controller [ prop ] === undefined ) controller [ prop ] = null ; } const ret = { format : protocolFileFormat , controller , nodes : mapToObject ( nodes ) } ; return ret ; } function nvmJSONNodeToNodeInfo ( nodeId , node ) { return { nodeId , ... pick ( node , [ "isListening" , "isFrequentListening" , "isRouting" , "supportedDataRates" , "protocolVersion" , "optionalFunctionality" , "nodeType" , "supportsSecurity" , "supportsBeaming" , "genericDeviceClass" , "specificDeviceClass" , "neighbors" , "sucUpdateIndex" , ] ) } ; } function nvmJSONControllerToFileOptions ( ctrlr ) { const ret = { homeId : Buffer . from ( ctrlr . homeId . replace ( /^0x/ , "" ) , "hex" ) , nodeId : ctrlr . nodeId , lastNodeId : ctrlr . lastNodeId , staticControllerNodeId : ctrlr . staticControllerNodeId , sucLastIndex : ctrlr . sucLastIndex , controllerConfiguration : ctrlr . controllerConfiguration , maxNodeId : ctrlr . maxNodeId , reservedId : ctrlr . reservedId , systemState : ctrlr . systemState } as ControllerInfoFileOptions ; if ( ctrlr . sucAwarenessPushNeeded != undefined ) { ret . sucAwarenessPushNeeded = ctrlr . sucAwarenessPushNeeded ; } else { Object . assign ( ret , stripUndefined ( pick ( ctrlr , [ "sucAwarenessPushNeeded" , "lastNodeIdLR" , "maxNodeIdLR" , "reservedIdLR" , "primaryLongRangeChannelId" , "dcdcConfig" , ] ) ) ) ; } return ret ; } function serializeCommonApplicationObjects ( nvm ) { const ret = [ ] ; const applTypeFile = new ApplicationTypeFile ( pick ( nvm . controller , [ "isListening" , "optionalFunctionality" , "genericDeviceClass" , "specificDeviceClass" , ] ) ) ; ret . push ( applTypeFile . serialize ( ) ) ; const applCCsFile = new ApplicationCCsFile ( pick ( nvm . controller . commandClasses , [ "includedInsecurely" , "includedSecurelyInsecureCCs" , "includedSecurelySecureCCs" , ] ) ) ; ret . push ( applCCsFile . serialize ( ) ) ; if ( nvm . controller . rfConfig ) { const applRFConfigFile = new ApplicationRFConfigFile ( { ... pick ( nvm . controller . rfConfig , [ "rfRegion" , "txPower" , "measured0dBm" , ] ) , enablePTI : nvm . controller . rfConfig . enablePTI ?? undefined , maxTXPower : nvm . controller . rfConfig . maxTXPower ?? undefined } ) ; ret . push ( applRFConfigFile . serialize ( ) ) ; } if ( nvm . controller . applicationData ) { const applDataFile = new ApplicationDataFile ( { data : Buffer . from ( nvm . controller . applicationData , "hex" ) } ) ; ret . push ( applDataFile . serialize ( ) ) ; } return ret ; } function serializeCommonProtocolObjects ( nvm ) { const ret = [ ] ; const appRouteLock = new Set < number > ( ) ; const routeSlaveSUC = new Set < number > ( ) ; const sucPendingUpdate = new Set < number > ( ) ; const isVirtual = new Set < number > ( ) ; const pendingDiscovery = new Set < number > ( ) ; for ( const [ id , node ] of Object . entries ( nvm . nodes ) ) { const nodeId = parseInt ( id ) ; if ( ! nodeHasInfo ( node ) ) { isVirtual . add ( nodeId ) ; continue ; } else { if ( node . isVirtual ) isVirtual . add ( nodeId ) ; if ( node . appRouteLock ) appRouteLock . add ( nodeId ) ; if ( node . routeSlaveSUC ) routeSlaveSUC . add ( nodeId ) ; if ( node . sucPendingUpdate ) sucPendingUpdate . add ( nodeId ) ; if ( node . pendingDiscovery ) pendingDiscovery . add ( nodeId ) ; } } ret . push ( new ControllerInfoFile ( nvmJSONControllerToFileOptions ( nvm . controller ) ) . serialize ( ) ) ; ret . push ( new ProtocolAppRouteLockNodeMaskFile ( { nodeIds : [ ... appRouteLock ] } ) . serialize ( ) ) ; ret . push ( new ProtocolRouteSlaveSUCNodeMaskFile ( { nodeIds : [ ... routeSlaveSUC ] } ) . serialize ( ) ) ; ret . push ( new ProtocolSUCPendingUpdateNodeMaskFile ( { nodeIds : [ ... sucPendingUpdate ] } ) . serialize ( ) ) ; ret . push ( new ProtocolVirtualNodeMaskFile ( { nodeIds : [ ... isVirtual ] } ) . serialize ( ) ) ; ret . push ( new ProtocolPendingDiscoveryNodeMaskFile ( { nodeIds : [ ... pendingDiscovery ] } ) . serialize ( ) ) ; if ( nvm . controller . preferredRepeaters ?. length ) { ret . push ( new ProtocolPreferredRepeatersFile ( { nodeIds : nvm . controller . preferredRepeaters } ) . serialize ( ) ) ; } ret . push ( new SUCUpdateEntriesFile ( { updateEntries : nvm . controller . sucUpdateEntries } ) . serialize ( ) ) ; return ret ; } export function jsonToNVMObjects_v0 ( json , major , minor , patch ) { const target = cloneDeep ( json ) ; target . format = 0 ; const applicationObjects = new Map < number , NVM3Object > ( ) ; const protocolObjects = new Map < number , NVM3Object > ( ) ; const addApplicationObjects = ( ... objects ) => { for ( const o of objects ) { applicationObjects . set ( o . key , o ) ; } } ; const addProtocolObjects = ( ... objects ) => { for ( const o of objects ) { protocolObjects . set ( o . key , o ) ; } } ; const [ applMajor , applMinor , applPatch ] = target . controller . applicationVersion . split ( "." ) . map ( ( i ) => parseInt ( i ) ) ; const applVersionFile = new ApplicationVersionFile ( { format : target . format , major : applMajor , minor : applMinor , patch : applPatch } ) ; addApplicationObjects ( applVersionFile . serialize ( ) ) ; addApplicationObjects ( ... serializeCommonApplicationObjects ( target ) ) ; const protocolVersionFile = new ProtocolVersionFile ( { format : target . format , major , minor , patch } ) ; addProtocolObjects ( protocolVersionFile . serialize ( ) ) ; const nodeInfoFiles = new Map < number , NodeInfoFileV0 > ( ) ; const routeCacheFiles = new Map < number , RouteCacheFileV0 > ( ) ; const nodeInfoExists = new Set < number > ( ) ; const routeCacheExists = new Set < number > ( ) ; for ( const [ id , node ] of Object . entries ( target . nodes ) ) { const nodeId = parseInt ( id ) ; if ( ! nodeHasInfo ( node ) ) continue ; nodeInfoExists . add ( nodeId ) ; const nodeInfoFileIndex = nodeIdToNodeInfoFileIDV0 ( nodeId ) ; nodeInfoFiles . set ( nodeInfoFileIndex , new NodeInfoFileV0 ( { nodeInfo : nvmJSONNodeToNodeInfo ( nodeId , node ) } ) ) ; if ( node . lwr || node . nlwr ) { routeCacheExists . add ( nodeId ) ; const routeCacheFileIndex = nodeIdToRouteCacheFileIDV0 ( nodeId ) ; routeCacheFiles . set ( routeCacheFileIndex , new RouteCacheFileV0 ( { routeCache : { nodeId , lwr : node . lwr ?? getEmptyRoute ( ) , nlwr : node . nlwr ?? getEmptyRoute ( ) } } ) ) ; } } addProtocolObjects ( ... serializeCommonProtocolObjects ( target ) ) ; addProtocolObjects ( new ProtocolNodeListFile ( { nodeIds : [ ... nodeInfoExists ] } ) . serialize ( ) ) ; addProtocolObjects ( new ProtocolRouteCacheExistsNodeMaskFile ( { nodeIds : [ ... routeCacheExists ] } ) . serialize ( ) ) ; if ( nodeInfoFiles . size > 0 ) { addProtocolObjects ( ... [ ... nodeInfoFiles . values ( ) ] . map ( ( f ) => f . serialize ( ) ) ) ; } if ( routeCacheFiles . size > 0 ) { addProtocolObjects ( ... [ ... routeCacheFiles . values ( ) ] . map ( ( f ) => f . serialize ( ) ) ) ; } return { applicationObjects , protocolObjects } ; } export function jsonToNVMObjects_v1_to_v4 ( format , json , major , minor , patch ) { const target = cloneDeep ( json ) ; target . format = format ; const applicationObjects = new Map < number , NVM3Object > ( ) ; const protocolObjects = new Map < number , NVM3Object > ( ) ; const addApplicationObjects = ( ... objects ) => { for ( const o of objects ) { applicationObjects . set ( o . key , o ) ; } } ; const addProtocolObjects = ( ... objects ) => { for ( const o of objects ) { protocolObjects . set ( o . key , o ) ; } } ; const [ applMajor , applMinor , applPatch ] = target . controller . applicationVersion . split ( "." ) . map ( ( i ) => parseInt ( i ) ) ; const applVersionFile = new ApplicationVersionFile ( { format : target . format , major : applMajor , minor : applMinor , patch : applPatch } ) ; addApplicationObjects ( applVersionFile . serialize ( ) ) ; addApplicationObjects ( ... serializeCommonApplicationObjects ( target ) ) ; const protocolVersionFile = new ProtocolVersionFile ( { format : target . format , major , minor , patch } ) ; addProtocolObjects ( protocolVersionFile . serialize ( ) ) ; const nodeInfoFiles = new Map < number , NodeInfoFileV1 > ( ) ; const routeCacheFiles = new Map < number , RouteCacheFileV1 > ( ) ; const nodeInfoExists = new Set < number > ( ) ; const routeCacheExists = new Set < number > ( ) ; for ( const [ id , node ] of Object . entries ( target . nodes ) ) { const nodeId = parseInt ( id ) ; if ( ! nodeHasInfo ( node ) ) continue ; nodeInfoExists . add ( nodeId ) ; const nodeInfoFileIndex = nodeIdToNodeInfoFileIDV1 ( nodeId ) ; if ( ! nodeInfoFiles . has ( nodeInfoFileIndex ) ) { nodeInfoFiles . set ( nodeInfoFileIndex , new NodeInfoFileV1 ( { nodeInfos : [ ] } ) ) ; } const nodeInfoFile = nodeInfoFiles . get ( nodeInfoFileIndex ) ! ; nodeInfoFile . nodeInfos . push ( nvmJSONNodeToNodeInfo ( nodeId , node ) ) ; if ( node . lwr || node . nlwr ) { routeCacheExists . add ( nodeId ) ; const routeCacheFileIndex = nodeIdToRouteCacheFileIDV1 ( nodeId ) ; if ( ! routeCacheFiles . has ( routeCacheFileIndex ) ) { routeCacheFiles . set ( routeCacheFileIndex , new RouteCacheFileV1 ( { routeCaches : [ ] } ) ) ; } const routeCacheFile = routeCacheFiles . get ( routeCacheFileIndex ) ! ; routeCacheFile . routeCaches . push ( { nodeId , lwr : node . lwr ?? getEmptyRoute ( ) , nlwr : node . nlwr ?? getEmptyRoute ( ) } ) ; } } addProtocolObjects ( ... serializeCommonProtocolObjects ( target ) ) ; addProtocolObjects ( new ProtocolNodeListFile ( { nodeIds : [ ... nodeInfoExists ] } ) . serialize ( ) ) ; addProtocolObjects ( new ProtocolRouteCacheExistsNodeMaskFile ( { nodeIds : [ ... routeCacheExists ] } ) . serialize ( ) ) ; if ( nodeInfoFiles . size > 0 ) { addProtocolObjects ( ... [ ... nodeInfoFiles . values ( ) ] . map ( ( f ) => f . serialize ( ) ) ) ; } if ( routeCacheFiles . size > 0 ) { addProtocolObjects ( ... [ ... routeCacheFiles . values ( ) ] . map ( ( f ) => f . serialize ( ) ) ) ; } return { applicationObjects , protocolObjects } ; } /** Reads an NVM buffer and returns its JSON representation */ export function nvmToJSON ( buffer , debugLogs = false ) { const nvm = parseNVM ( buffer , debugLogs ) ; const ret = nvmObjectsToJSON ( nvm . applicationObjects , nvm . protocolObjects ) ; ret . meta = getNVMMeta ( nvm . protocolPages [ 0 ] ) ; return ret as Required < NVMJSON > ; } /** Reads an NVM buffer of a 500-series stick and returns its JSON representation */ export function nvm500ToJSON ( buffer ) { const parser = createNVM500Parser ( buffer ) ; if ( ! parser ) throw new ZWaveError ( "Did not find a matching NVM 500 parser implementation!" , ZWaveErrorCodes . NVM_NotSupported ) ; return parser . toJSON ( ) ; } /** Takes a JSON represented NVM and converts it to binary */ export function jsonToNVM ( json , protocolVersion ) { const parsedVersion = semver . parse ( protocolVersion ) ; if ( ! parsedVersion ) { throw new ZWaveError ( `Invalid protocol version: ${ protocolVersion }` , ZWaveErrorCodes . Argument_Invalid ) ; } let objects ; if ( semver . gte ( parsedVersion , "7.17.0" ) ) { objects = jsonToNVMObjects_v1_to_v4 ( 4 , json , parsedVersion . major , parsedVersion . minor , parsedVersion . patch ) ; } else if ( semver . gte ( parsedVersion , "7.15.3" ) ) { objects = jsonToNVMObjects_v1_to_v4 ( 3 , json , parsedVersion . major , parsedVersion . minor , parsedVersion . patch ) ; } else if ( semver . gte ( parsedVersion , "7.12.0" ) ) { objects = jsonToNVMObjects_v1_to_v4 ( 2 , json , parsedVersion . major , parsedVersion . minor , parsedVersion . patch ) ; } else if ( semver . gte ( parsedVersion , "7.11.0" ) ) { objects = jsonToNVMObjects_v1_to_v4 ( 1 , json , parsedVersion . major , parsedVersion . minor , parsedVersion . patch ) ; } else if ( semver . gte ( parsedVersion , "7.0.0" ) ) { objects = jsonToNVMObjects_v0 ( json , parsedVersion . major , parsedVersion . minor , parsedVersion . patch ) ; } else { throw new ZWaveError ( "jsonToNVM cannot convert to a pre-7.0 NVM version. Use jsonToNVM500 instead." , ZWaveErrorCodes . Argument_Invalid ) ; } return encodeNVM ( objects . applicationObjects , objects . protocolObjects , json . meta ) ; } /** Takes a JSON represented 500 series NVM and converts it to binary */ export function jsonToNVM500 ( json , protocolVersion ) { const impl = nmvDetails500 . find ( ( p ) => p . protocolVersions . includes ( protocolVersion ) && p . name . toLowerCase ( ) . startsWith ( json . meta . library ) ) ; if ( ! impl ) { throw new ZWaveError ( `Did not find a matching implementation for protocol version ${ protocolVersion } and library ${ json . meta . library }.` , ZWaveErrorCodes . NVM_NotSupported ) ; } const serializer = new NVMSerializer ( impl ) ; serializer . parseJSON ( json , protocolVersion ) ; return serializer . serialize ( ) ; } export function json500To700 ( json , truncateApplicationData ? ) { const source = cloneDeep ( json ) ; let controllerNode = source . nodes [ source . controller . nodeId || 1 ] as NVMJSONNodeWithInfo ; if ( ! nodeHasInfo ( controllerNode ) ) { controllerNode = { isListening : true , optionalFunctionality : false , genericDeviceClass : 0x02 , specificDeviceClass : 0x01 } as any ; } let applicationData = null ; if ( source . controller . applicationData ) { let raw = Buffer . from ( source . controller . applicationData , "hex" ) ; let start = 0 ; while ( start < raw . length && raw [ start ] === 0 ) { start ++ ; } let end = raw . length - 1 ; while ( end > start && raw [ end ] === 0 ) { end -- ; } raw = raw . slice ( start , end + 1 ) ; if ( raw . length > 512 ) { if ( ! truncateApplicationData ) { throw new ZWaveError ( "Invalid NVM JSON: Application data would be truncated! Set truncateApplicationData to true to allow this." , ZWaveErrorCodes . NVM_InvalidJSON ) ; } raw = raw . slice ( 0 , 512 ) ; } applicationData = raw . toString ( "hex" ) ; } let homeId ; if ( source . controller . controllerConfiguration & ControllerCapabilityFlags . OnOtherNetwork ) { if ( ! source . controller . learnedHomeId ) { throw new ZWaveError ( "Invalid NVM JSON: Controller is part of another network but has no learned Home ID!" , ZWaveErrorCodes . NVM_InvalidJSON ) ; } else if ( ! source . controller . nodeId ) { throw new ZWaveError ( "Invalid NVM JSON: Controller is part of another network but node ID is zero!" , ZWaveErrorCodes . NVM_InvalidJSON ) ; } homeId = source . controller . learnedHomeId ; } else { homeId = source . controller . ownHomeId ; source . controller . nodeId = 1 ; } const ret = { format : 0 , controller : { protocolVersion : source . controller . protocolVersion , applicationVersion : source . controller . applicationVersion , homeId , nodeId : source . controller . nodeId , lastNodeId : source . controller . lastNodeId , staticControllerNodeId : source . controller . staticControllerNodeId , sucLastIndex : source . controller . sucLastIndex , controllerConfiguration : source . controller . controllerConfiguration , sucUpdateEntries : source . controller . sucUpdateEntries , maxNodeId : source . controller . maxNodeId , reservedId : source . controller . reservedId , systemState : source . controller . systemState , preferredRepeaters : source . controller . preferredRepeaters , isListening : controllerNode . isListening , optionalFunctionality : controllerNode . optionalFunctionality , genericDeviceClass : controllerNode . genericDeviceClass , specificDeviceClass : controllerNode . specificDeviceClass ?? 0 , commandClasses : { includedInsecurely : source . controller . commandClasses , includedSecurelyInsecureCCs : [ ] , includedSecurelySecureCCs : [ ] } , applicationData } , nodes : source . nodes } ; return ret ; } export function json700To500 ( json ) { const source = cloneDeep ( json ) ; let ownHomeId ; let learnedHomeId = null ; let nodeId ; if ( source . controller . controllerConfiguration & ControllerCapabilityFlags . OnOtherNetwork ) { ownHomeId = learnedHomeId = source . controller . homeId ; nodeId = source . controller . nodeId ; } else { ownHomeId = source . controller . homeId ; nodeId = 0 ; } const ret = { format : 500 , controller : { protocolVersion : source . controller . protocolVersion , applicationVersion : source . controller . applicationVersion , ownHomeId , learnedHomeId , nodeId , lastNodeId : source . controller . lastNodeId , staticControllerNodeId : source . controller . staticControllerNodeId , sucLastIndex : source . controller . sucLastIndex , controllerConfiguration : source . controller . controllerConfiguration , sucUpdateEntries : source . controller . sucUpdateEntries , maxNodeId : source . controller . maxNodeId , reservedId : source . controller . reservedId , systemState : source . controller . systemState , watchdogStarted : 0 , preferredRepeaters : json . controller . preferredRepeaters ?? [ ] , rfConfig : { powerLevelNormal : [ 255 , 255 , 255 ] , powerLevelLow : [ 255 , 255 , 255 ] , powerMode : 255 , powerModeExtintEnable : 255 , powerModeWutTimeout : 0xffffffff } , commandClasses : source . controller . commandClasses . includedInsecurely , applicationData : source . controller . applicationData } , nodes : source . nodes } ; return ret ; } /** Converts the given source NVM into a format that is compatible with the given target NVM */ export function migrateNVM ( source , target ) { let sourceJSON ; let targetJSON ; let sourceIs500 ; let targetIs500 ; try { sourceJSON = nvmToJSON ( source ) ; sourceIs500 = false ; } catch ( e ) { if ( isZWaveError ( e ) && e . code === ZWaveErrorCodes . NVM_InvalidFormat ) { sourceJSON = nvm500ToJSON ( source ) ; sourceIs500 = true ; } else { throw e ; } } try { targetJSON = nvmToJSON ( target ) ; targetIs500 = false ; } catch ( e ) { if ( isZWaveError ( e ) && e . code === ZWaveErrorCodes . NVM_InvalidFormat ) { targetJSON = nvm500ToJSON ( target ) ; targetIs500 = true ; } else { throw e ; } } if ( ! sourceIs500 && ! targetIs500 ) { const sourceProtocolVersion = sourceJSON . controller . protocolVersion ; const targetProtocolVersion = targetJSON . controller . protocolVersion ; if ( semver . gte ( targetProtocolVersion , "7.16.0" ) && semver . gte ( targetProtocolVersion , sourceProtocolVersion ) ) { return source ; } } sourceJSON . controller . applicationVersion = targetJSON . controller . applicationVersion ; if ( sourceIs500 && targetIs500 ) { const json = { ... ( sourceJSON as Required < NVM500JSON > ) , meta : ( targetJSON as Required < NVM500JSON > ) . meta } ; json . controller . rfConfig = ( targetJSON as Required < NVM500JSON > ) . controller . rfConfig ; return jsonToNVM500 ( json , targetJSON . controller . protocolVersion ) ; } else if ( sourceIs500 && ! targetIs500 ) { const json = { ... json500To700 ( sourceJSON as Required < NVM500JSON > , true ) , meta : ( targetJSON as Required < NVMJSON > ) . meta } ; return jsonToNVM ( json , targetJSON . controller . protocolVersion ) ; } else if ( ! sourceIs500 && targetIs500 ) { const json = { ... json700To500 ( sourceJSON as Required < NVMJSON > ) , meta : ( targetJSON as Required < NVM500JSON > ) . meta } ; json . controller . rfConfig = ( targetJSON as Required < NVM500JSON > ) . controller . rfConfig ; return jsonToNVM500 ( json , targetJSON . controller . protocolVersion ) ; } else { const json = { ... ( sourceJSON as Required < NVMJSON > ) , meta : ( targetJSON as Required < NVMJSON > ) . meta } ; return jsonToNVM ( json , targetJSON . controller . protocolVersion ) ; } }