import { CacheMetadata , CacheValue , CommandClasses , deserializeCacheValue , getCCName , isZWaveError , MessageOrCCLogEntry , MessageRecord , NODE_ID_BROADCAST , parseCCId , serializeCacheValue , stripUndefined , ValueDB , ValueID , valueIdToString , ValueMetadata , ZWaveError , ZWaveErrorCodes , } from "@zwave-js/core" ; import { buffer2hex , getEnumMemberName , JSONObject , num2hex , staticExtends , TypedClassDecorator , } from "@zwave-js/shared" ; import { isArray } from "alcalzone-shared/typeguards" ; import type { Driver } from "../driver/Driver" ; import type { Endpoint } from "../node/Endpoint" ; import type { ZWaveNode } from "../node/Node" ; import { InterviewStage } from "../node/Types" ; import type { VirtualEndpoint } from "../node/VirtualEndpoint" ; import { CCAPI } from "./API" ; import { EncapsulatingCommandClass , isEncapsulatingCommandClass , } from "./EncapsulatingCommandClass" ; import { ICommandClassContainer , isCommandClassContainer , } from "./ICommandClassContainer" ; export type MulticastDestination = [ number , number , ... number [ ] ] ; export type CommandClassDeserializationOptions = { data : Buffer ; } & ( { fromEncapsulation ? : false ; nodeId : number ; } | { fromEncapsulation : true ; encapCC : CommandClass ; } ) ; export function gotDeserializationOptions ( options ) { return "data" in options && Buffer . isBuffer ( options . data ) ; } export interface CCCommandOptions { nodeId : number | MulticastDestination ; endpoint ? : number ; supervised ? : boolean ; } interface CommandClassCreationOptions extends CCCommandOptions { ccId ? : number ; ccCommand ? : number ; payload ? : Buffer ; } function gotCCCommandOptions ( options ) { return typeof options . nodeId === "number" || isArray ( options . nodeId ) ; } export type CommandClassOptions = CommandClassCreationOptions | CommandClassDeserializationOptions ; export class CommandClass { public constructor ( driver , options ) { this . driver = driver ; this . ccId = ( "ccId" in options && options . ccId ) || getCommandClass ( this ) ; this . endpointIndex = ( "endpoint" in options ? options . endpoint : undefined ) ?? 0 ; this . supervised = ( "supervised" in options ? options . supervised : undefined ) ?? false ; this . registerValue ( "interviewComplete" , true ) ; if ( gotDeserializationOptions ( options ) ) { const CCConstructor = getCCConstructor ( CommandClass . getCommandClass ( options . data ) ) ?? CommandClass ; const ccCommand = CCConstructor . getCCCommand ( options . data ) ; if ( ccCommand != undefined ) { const CommandConstructor = getCCCommandConstructor ( this . ccId , ccCommand ) ; if ( CommandConstructor && ( new . target as any ) !== CommandConstructor ) { return new CommandConstructor ( driver , options ) ; } } if ( options . fromEncapsulation ) { this . nodeId = options . encapCC . nodeId ; if ( ! this . endpointIndex && options . encapCC . endpointIndex ) { this . endpointIndex = options . encapCC . endpointIndex ; } this . encapsulatingCC = options . encapCC as any ; } else { this . nodeId = options . nodeId ; } ( { ccId : this . ccId , ccCommand : this . ccCommand , payload : this . payload } = this . deserialize ( options . data ) ) ; } else if ( gotCCCommandOptions ( options ) ) { const { nodeId , ccCommand = getCCCommand ( this ) , payload = Buffer . allocUnsafe ( 0 ) , } = options ; this . nodeId = nodeId ; this . ccCommand = ccCommand ; this . payload = payload ; } if ( this instanceof InvalidCC ) return ; if ( this . isSinglecast ( ) && this . nodeId !== NODE_ID_BROADCAST ) { this . version = this . driver . getSafeCCVersionForNode ( this . ccId , this . nodeId , this . endpointIndex ) ; const node = this . getNodeUnsafe ( ) ; if ( node ?. interviewStage === InterviewStage . Complete && this . version === 0 && gotDeserializationOptions ( options ) ) { this . version = 1 ; } const endpoint = this . getNodeUnsafe ( ) ?. getEndpoint ( this . endpointIndex ) ; this . secure = node ?. isSecure !== false && ! ! ( endpoint ?? node ) ?. isCCSecure ( this . ccId ) && ! ! ( this . driver . securityManager || this . driver . securityManager2 ) ; } else { this . version = getImplementedVersion ( this . ccId ) ; } } protected driver : Driver ; /** This CC's identifier */ public ccId : CommandClasses ; public ccCommand ? : number ; public get ccName ( ) : string { return getCCName ( this . ccId ) ; } /** The ID of the target node(s) */ public nodeId ! : number | MulticastDestination ; public payload ! : Buffer ; /** The version of the command class used */ public version ! : number ; /** Which endpoint of the node this CC belongs to. 0 for the root device. */ public endpointIndex : number ; internal public supervised : boolean ; /** Contains a reference to the encapsulating CC if this CC is encapsulated */ public encapsulatingCC ? : EncapsulatingCommandClass ; internal public secure : boolean = false ; /** Returns true if this CC is an extended CC (0xF100..0xFFFF) */ public isExtended ( ) { return this . ccId >= 0xf100 ; } /** Whether the interview for this CC was previously completed */ public get interviewComplete ( ) : boolean { return ! ! this . getValueDB ( ) . getValue < boolean > ( { commandClass : this . ccId , endpoint : this . endpointIndex , property : "interviewComplete" } ) ; } public set interviewComplete ( value ) { this . getValueDB ( ) . setValue ( { commandClass : this . ccId , endpoint : this . endpointIndex , property : "interviewComplete" } , value ) ; } /** Can be used by endpoints to test if the root device was already interviewed */ public get rootDeviceInterviewComplete ( ) : boolean { return ! ! this . getValueDB ( ) . getValue < boolean > ( { commandClass : this . ccId , endpoint : 0 , property : "interviewComplete" } ) ; } /**
     * Deserializes a CC from a buffer that contains a serialized CC
     */ protected deserialize ( data ) { const ccId = CommandClass . getCommandClass ( data ) ; const ccIdLength = this . isExtended ( ) ? 2 : 1 ; if ( data . length > ccIdLength ) { const ccCommand = data [ ccIdLength ] ; const payload = data . slice ( ccIdLength + 1 ) ; return { ccId , ccCommand , payload } ; } else { const payload = Buffer . allocUnsafe ( 0 ) ; return { ccId , payload } ; } } /**
     * Serializes this CommandClass to be embedded in a message payload or another CC
     */ public serialize ( ) { if ( this . ccId === CommandClasses [ "No Operation" ] ) return Buffer . from ( [ this . ccId ] ) ; else if ( this . ccCommand == undefined ) { throw new ZWaveError ( "Cannot serialize a Command Class without a command" , ZWaveErrorCodes . CC_Invalid ) ; } const payloadLength = this . payload . length ; const ccIdLength = this . isExtended ( ) ? 2 : 1 ; const data = Buffer . allocUnsafe ( ccIdLength + 1 + payloadLength ) ; data . writeUIntBE ( this . ccId , 0 , ccIdLength ) ; data [ ccIdLength ] = this . ccCommand ; if ( payloadLength > 0 ) { this . payload . copy ( data , 1 + ccIdLength ) ; } return data ; } /** Extracts the CC id from a buffer that contains a serialized CC */ public static getCommandClass ( data ) { return parseCCId ( data ) . ccId ; } /** Extracts the CC command from a buffer that contains a serialized CC  */ public static getCCCommand ( data ) { if ( data [ 0 ] === 0 ) return undefined ; const isExtendedCC = data [ 0 ] >= 0xf1 ; return isExtendedCC ? data [ 2 ] : data [ 1 ] ; } /**
     * Retrieves the correct constructor for the CommandClass in the given Buffer.
     * It is assumed that the buffer only contains the serialized CC. This throws if the CC is not implemented.
     */ public static getConstructor ( ccData ) { const cc = CommandClass . getCommandClass ( ccData ) ; const ret = getCCConstructor ( cc ) ; if ( ! ret ) { const ccName = getCCName ( cc ) ; throw new ZWaveError ( `The command class ${ ccName } is not implemented` , ZWaveErrorCodes . CC_NotImplemented ) ; } return ret ; } /**
     * Creates an instance of the CC that is serialized in the given buffer
     */ public static from ( driver , options ) { const Constructor = CommandClass . getConstructor ( options . data ) ; try { const ret = new Constructor ( driver , options ) ; return ret ; } catch ( e ) { if ( isZWaveError ( e ) && e . code === ZWaveErrorCodes . PacketFormat_InvalidPayload ) { const nodeId = options . fromEncapsulation ? options . encapCC . nodeId : options . nodeId ; let ccName ; const ccId = CommandClass . getCommandClass ( options . data ) ; const ccCommand = CommandClass . getCCCommand ( options . data ) ; if ( ccCommand != undefined ) { ccName = getCCCommandConstructor ( ccId , ccCommand ) ?. name ; } if ( ! ccName ) { ccName = `${ getCCName ( ccId ) } CC` ; } let reason ; if ( typeof e . context === "string" || ( typeof e . context === "number" && ZWaveErrorCodes [ e . context ] != undefined ) ) { reason = e . context ; } const ret = new InvalidCC ( driver , { nodeId , ccId , ccName , reason } ) ; if ( options . fromEncapsulation ) { ret . encapsulatingCC = options . encapCC as any ; } return ret ; } throw e ; } } /** Generates a representation of this CC for the log */ public toLogEntry ( ) { let tag = this . constructor . name ; const message = { } ; if ( this . constructor === CommandClass ) { tag = `${ getEnumMemberName ( CommandClasses , this . ccId ) } CC (not implemented)` ; if ( this . ccCommand != undefined ) { message . command = num2hex ( this . ccCommand ) ; } } if ( this . payload . length > 0 ) { message . payload = buffer2hex ( this . payload ) ; } return { tags : [ tag ] , message } ; } /** Generates the JSON representation of this CC */ public toJSON ( ) { return this . toJSONInternal ( ) ; } private toJSONInternal ( ) { const ret = { nodeId : this . nodeId , ccId : CommandClasses [ this . ccId ] || num2hex ( this . ccId ) } ; if ( this . ccCommand != undefined ) { ret . ccCommand = num2hex ( this . ccCommand ) ; } if ( this . payload . length > 0 ) { ret . payload = "0x" + this . payload . toString ( "hex" ) ; } return ret ; } protected toJSONInherited ( props ) { const { payload , ... ret } = this . toJSONInternal ( ) ; return stripUndefined ( { ... ret , ... props } ) ; } protected throwMissingCriticalInterviewResponse ( ) { throw new ZWaveError ( `The node did not respond to a critical interview query in time.` , ZWaveErrorCodes . Controller_NodeTimeout ) ; } /**
     * Performs the interview procedure for this CC according to SDS14223
     */ public async interview ( ) { } /**
     * Refreshes all dynamic values of this CC
     */ public async refreshValues ( ) { } /** Determines which CC interviews must be performed before this CC can be interviewed */ public determineRequiredCCInterviews ( ) { return [ CommandClasses . Version ] ; } /**
     * Whether the endpoint interview may be skipped by a CC. Can be overwritten by a subclass.
     */ public skipEndpointInterview ( ) { return false ; } param value public setMappedBasicValue ( value ) { return false ; } public isSinglecast ( ) { return typeof this . nodeId === "number" ; } public isMulticast ( ) { return isArray ( this . nodeId ) ; } /**
     * Returns the node this CC is linked to. Throws if the controller is not yet ready.
     */ public getNode ( ) { if ( this . isSinglecast ( ) ) { return this . driver . controller . nodes . get ( this . nodeId ) ; } } internal public getNodeUnsafe ( ) { try { return this . getNode ( ) ; } catch ( e ) { if ( isZWaveError ( e ) && e . code === ZWaveErrorCodes . Driver_NotReady ) { return undefined ; } throw e ; } } public getEndpoint ( ) { return this . getNode ( ) ?. getEndpoint ( this . endpointIndex ) ; } /** Returns the value DB for this CC's node */ protected getValueDB ( ) { const node = this . getNode ( ) ; if ( node == undefined ) { throw new ZWaveError ( "The node for this CC does not exist or the driver is not ready yet" , ZWaveErrorCodes . Driver_NotReady ) ; } return node . valueDB ; } /** Which variables should be persisted when requested */ private _registeredCCValues = new Map < string | number , boolean > ( ) ; param property param internal public registerValue ( property , internal = false ) { this . _registeredCCValues . set ( property , internal ) ; } /** Returns a list of all value names that are defined for this CommandClass */ public getDefinedValueIDs ( ) { const ret = new Map < string , ValueID > ( ) ; const addValueId = ( property , propertyKey ? ) : void => { const valueId = { commandClass : this . ccId , endpoint : this . endpointIndex , property , propertyKey } ; const dbKey = valueIdToString ( valueId ) ; if ( ! ret . has ( dbKey ) ) ret . set ( dbKey , valueId ) ; } ; const registeredCCValueNames = [ ... this . _registeredCCValues ] . filter ( ( [ , isInternal ] ) => ! isInternal ) . map ( ( [ key ] ) => key ) ; registeredCCValueNames . forEach ( ( property ) => addValueId ( property ) ) ; const valueDefinitions = getCCValueDefinitions ( this ) ; const definedCCValueNames = [ ... valueDefinitions ] . filter ( ( [ , options ] ) => options . internal !== true && ( options . minVersion == undefined || options . minVersion <= this . version ) ) . map ( ( [ key ] ) => key ) ; definedCCValueNames . forEach ( ( property ) => addValueId ( property ) ) ; const kvpDefinitions = getCCKeyValuePairDefinitions ( this ) ; const existingValueIds = [ ... this . getValueDB ( ) . getValues ( this . ccId ) , ... this . getValueDB ( ) . getAllMetadata ( this . ccId ) , ] . filter ( ( valueId ) => valueId . endpoint === this . endpointIndex ) . filter ( ( valueId ) => ! this . _registeredCCValues . has ( valueId . property ) || this . _registeredCCValues . get ( valueId . property ) ! === false ) . filter ( ( valueId ) => ! valueDefinitions . has ( valueId . property ) || valueDefinitions . get ( valueId . property ) ! === false ) . filter ( ( valueId ) => ! kvpDefinitions . has ( valueId . property ) || kvpDefinitions . get ( valueId . property ) ! === false ) ; existingValueIds . forEach ( ( { property , propertyKey } ) => addValueId ( property , propertyKey ) ) ; return [ ... ret . values ( ) ] ; } /** Determines if the given value is an internal value */ public isInternalValue ( property ) { if ( this . _registeredCCValues . get ( property as string ) === true ) return true ; const ccValueDefinition = getCCValueDefinitions ( this ) . get ( property as string ) ; if ( ccValueDefinition ?. internal === true ) return true ; const ccKeyValuePairDefinition = getCCKeyValuePairDefinitions ( this ) . get ( property as string ) ; if ( ccKeyValuePairDefinition ?. internal === true ) return true ; return false ; } /** Determines if the given value should always be persisted */ public shouldValueAlwaysBeCreated ( property ) { const ccValueDefinition = getCCValueDefinitions ( this ) . get ( property as string ) ; if ( ccValueDefinition ?. forceCreation === true ) return true ; const ccKeyValuePairDefinition = getCCKeyValuePairDefinitions ( this ) . get ( property as string ) ; if ( ccKeyValuePairDefinition ?. forceCreation === true ) return true ; return false ; } /** Determines if the given value should be persisted or represents an event */ public isStatefulValue ( property ) { const ccValueDefinition = getCCValueDefinitions ( this ) . get ( property as string ) ; if ( ccValueDefinition ?. stateful === false ) return false ; const ccKeyValuePairDefinition = getCCKeyValuePairDefinitions ( this ) . get ( property as string ) ; if ( ccKeyValuePairDefinition ?. stateful === false ) return false ; return true ; } /** Persists all values on the given node into the value. Returns true if the process succeeded, false otherwise */ public persistValues ( valueNames ? ) { const keyValuePairDefinitions = getCCKeyValuePairDefinitions ( this ) ; const keyValuePairs = [ ... keyValuePairDefinitions ] . filter ( ( [ , options ] ) => options . minVersion == undefined || options . minVersion <= this . version ) ; const ccValueDefinitions = [ ... getCCValueDefinitions ( this ) ] . filter ( ( [ , options ] ) => options . minVersion == undefined || options . minVersion <= this . version ) ; if ( ! valueNames ) { valueNames = [ ... this . _registeredCCValues . keys ( ) , ... ccValueDefinitions . map ( ( [ key ] ) => key ) , ... keyValuePairs . map ( ( [ key ] ) => key ) , ] as unknown as ( keyof this ) [ ] ; } let db ; try { db = this . getValueDB ( ) ; } catch { return false ; } const cc = getCommandClass ( this ) ; for ( const variable of valueNames as string [ ] ) { if ( variable === "interviewComplete" ) continue ; const sourceValue = this [ variable as keyof this ] ; if ( sourceValue == undefined && ! this . shouldValueAlwaysBeCreated ( variable as keyof this ) ) { continue ; } if ( keyValuePairDefinitions . has ( variable ) ) { if ( sourceValue instanceof Map ) { for ( const [ propertyKey , value ] of ( sourceValue as Map < string | number , unknown > ) . entries ( ) ) { db . setValue ( { commandClass : cc , endpoint : this . endpointIndex , property : variable , propertyKey } , value ) ; } } else if ( isArray ( sourceValue ) ) { const [ propertyKey , value ] = sourceValue as any as [ string | number , unknown ] ; db . setValue ( { commandClass : cc , endpoint : this . endpointIndex , property : variable , propertyKey } , value ) ; } else { throw new ZWaveError ( `ccKeyValuePairs can only be Maps or [key, value]-tuples` , ZWaveErrorCodes . Argument_Invalid ) ; } } else { const valueId = { commandClass : cc , endpoint : this . endpointIndex , property : variable } ; if ( sourceValue != undefined || ! db . hasValue ( valueId ) ) { const stateful = this . isStatefulValue ( variable as keyof this ) ; db . setValue ( valueId , sourceValue , { stateful } ) ; } } } return true ; } /** Serializes all values to be stored in the cache */ public serializeValuesForCache ( ) { const ccValues = this . getValueDB ( ) . getValues ( getCommandClass ( this ) ) ; return ( ccValues . filter ( ( { value } ) => value != undefined ) . map ( ( { value , commandClass , ... props } ) => { return { ... props , value : serializeCacheValue ( value ) } ; } ) ) ; } /** Serializes metadata to be stored in the cache */ public serializeMetadataForCache ( ) { const allMetadata = this . getValueDB ( ) . getAllMetadata ( getCommandClass ( this ) ) ; return ( allMetadata . map ( ( { commandClass , ... props } ) => props ) ) ; } /** Deserializes values from the cache */ public deserializeValuesFromCache ( values ) { const cc = getCommandClass ( this ) ; for ( const val of values ) { this . getValueDB ( ) . setValue ( { commandClass : cc , endpoint : val . endpoint , property : val . property , propertyKey : val . propertyKey } , deserializeCacheValue ( val . value ) , { noEvent : true , noThrow : true } ) ; } } /** Deserializes value metadata from the cache */ public deserializeMetadataFromCache ( allMetadata ) { const cc = getCommandClass ( this ) ; for ( const meta of allMetadata ) { this . getValueDB ( ) . setMetadata ( { commandClass : cc , endpoint : meta . endpoint , property : meta . property , propertyKey : meta . propertyKey } , meta . metadata , { noEvent : true , noThrow : true } ) ; } } /**
     * When a CC supports to be split into multiple partial CCs, this can be used to identify the
     * session the partial CCs belong to.
     * If a CC expects `mergePartialCCs` to be always called, you should return an empty object here.
     */ public getPartialCCSessionId ( ) { return undefined ; } param session public expectMoreMessages ( session ) { return false ; } /** Include previously received partial responses into a final CC */ public mergePartialCCs ( partials ) { } /** Tests whether this CC expects at least one command in return */ public expectsCCResponse ( ) { let expected = getExpectedCCResponse ( this ) ; if ( typeof expected === "function" && ! staticExtends ( expected , CommandClass ) ) { expected = expected ( this ) ; } if ( expected === undefined ) return false ; if ( isArray ( expected ) ) { return expected . every ( ( cc ) => staticExtends ( cc , CommandClass ) ) ; } else { return staticExtends ( expected , CommandClass ) ; } } public isExpectedCCResponse ( received ) { if ( received . nodeId !== this . nodeId ) return false ; let expected = getExpectedCCResponse ( this ) ; if ( typeof expected === "function" && ! staticExtends ( expected , CommandClass ) ) { expected = expected ( this ) ; } if ( expected == undefined ) { return false ; } else if ( isArray ( expected ) && expected . every ( ( cc ) => staticExtends ( cc , CommandClass ) ) ) { if ( expected . every ( ( base ) => ! ( received instanceof base ) ) ) { return false ; } } else if ( staticExtends ( expected , CommandClass ) ) { if ( ! ( received instanceof expected ) ) return false ; } const predicate = getCCResponsePredicate ( this ) ; const ret = predicate ?. ( this , received ) ?? true ; if ( ret === "checkEncapsulated" ) { if ( isEncapsulatingCommandClass ( this ) && isEncapsulatingCommandClass ( received ) ) { return this . encapsulated . isExpectedCCResponse ( received . encapsulated ) ; } else { return false ; } } return ret ; } param property param _propertyKey public translateProperty ( property , _propertyKey ? ) { return property . toString ( ) ; } param property param propertyKey public translatePropertyKey ( property , propertyKey ) { return propertyKey . toString ( ) ; } /** Returns the number of bytes that are added to the payload by this CC */ protected computeEncapsulationOverhead ( ) { return ( this . isExtended ( ) ? 2 : 1 ) + 1 ; } /** Computes the maximum net payload size that can be transmitted inside this CC */ public getMaxPayloadLength ( baseLength ) { let ret = baseLength ; let cur = this ; while ( cur ) { ret -= cur . computeEncapsulationOverhead ( ) ; cur = isEncapsulatingCommandClass ( cur ) ? cur . encapsulated : undefined ; } return ret ; } /** Checks whether this CC is encapsulated with one that has the given CC id and (optionally) CC Command */ public isEncapsulatedWith ( ccId , ccCommand ? ) { let cc = this ; while ( cc . encapsulatingCC ) { cc = cc . encapsulatingCC ; if ( cc . ccId === ccId && ( ccCommand === undefined || cc . ccCommand === ccCommand ) ) { return true ; } } return false ; } /** Traverses the encapsulation stack of this CC and returns the one that has the given CC id and (optionally) CC Command if that exists. */ public getEncapsulatingCC ( ccId , ccCommand ? ) { let cc = this ; while ( cc . encapsulatingCC ) { cc = cc . encapsulatingCC ; if ( cc . ccId === ccId && ( ccCommand === undefined || cc . ccCommand === ccCommand ) ) { return cc ; } } } } export interface InvalidCCCreationOptions extends CommandClassCreationOptions { ccName : string ; reason ? : string | ZWaveErrorCodes ; } export class InvalidCC extends CommandClass { public constructor ( driver , options ) { super ( driver , options ) ; this . _ccName = options . ccName ; this . reason = options . reason ; } private _ccName : string ; public get ccName ( ) : string { return this . _ccName ; } public readonly reason ? : string | ZWaveErrorCodes ; public toLogEntry ( ) { return { tags : [ this . ccName , "INVALID" ] , message : this . reason != undefined ? { error : typeof this . reason === "string" ? this . reason : getEnumMemberName ( ZWaveErrorCodes , this . reason ) } : undefined } ; } } export function assertValidCCs ( container ) { if ( container . command instanceof InvalidCC ) { if ( typeof container . command . reason === "number" ) { throw new ZWaveError ( "The message payload failed validation!" , container . command . reason ) ; } else { throw new ZWaveError ( "The message payload is invalid!" , ZWaveErrorCodes . PacketFormat_InvalidPayload , container . command . reason ) ; } } else if ( isCommandClassContainer ( container . command ) ) { assertValidCCs ( container . command ) ; } } export type SinglecastCC < T extends CommandClass = CommandClass > = T & { nodeId : number ; } ; export type MulticastCC < T extends CommandClass = CommandClass > = T & { nodeId : MulticastDestination ; } ; const METADATA_commandClass = Symbol ( "commandClass" ) ; const METADATA_commandClassMap = Symbol ( "commandClassMap" ) ; const METADATA_ccResponse = Symbol ( "ccResponse" ) ; const METADATA_ccCommand = Symbol ( "ccCommand" ) ; const METADATA_ccCommandMap = Symbol ( "ccCommandMap" ) ; const METADATA_ccValues = Symbol ( "ccValues" ) ; const METADATA_ccKeyValuePairs = Symbol ( "ccKeyValuePairs" ) ; const METADATA_ccValueMeta = Symbol ( "ccValueMeta" ) ; const METADATA_version = Symbol ( "version" ) ; const METADATA_API = Symbol ( "API" ) ; const METADATA_APIMap = Symbol ( "APIMap" ) ; export type Constructable < T extends CommandClass > = typeof CommandClass & { new ( driver , options ) : T ; } ; type APIConstructor = new ( driver , endpoint ) => CCAPI ; type CommandClassMap = Map < CommandClasses , Constructable < CommandClass > > ; type CCCommandMap = Map < string , Constructable < CommandClass > > ; type APIMap = Map < CommandClasses , APIConstructor > ; function getCCCommandMapKey ( ccId , ccCommand ) { return JSON . stringify ( { ccId , ccCommand } ) ; } /**
 * May be used to define different expected CC responses depending on the sent CC
 */ export type DynamicCCResponse < TSent extends CommandClass , TReceived extends CommandClass = CommandClass > = ( sentCC : TSent ) => Constructable < TReceived > | Constructable < TReceived > [ ] | undefined ; export type CCResponseRole = boolean | "checkEncapsulated" ; /**
 * A predicate function to test if a received CC matches the sent CC
 */ export type CCResponsePredicate < TSent extends CommandClass , TReceived extends CommandClass = CommandClass > = ( sentCommand : TSent , receivedCommand : TReceived ) => CCResponseRole ; /**
 * Defines the command class associated with a Z-Wave message
 */ export function commandClass ( cc ) { return ( messageClass ) => { Reflect . defineMetadata ( METADATA_commandClass , cc , messageClass ) ; const map = Reflect . getMetadata ( METADATA_commandClassMap , CommandClass ) || new Map ( ) ; map . set ( cc , messageClass as unknown as Constructable < CommandClass > ) ; Reflect . defineMetadata ( METADATA_commandClassMap , map , CommandClass ) ; } ; } /**
 * Retrieves the command class defined for a Z-Wave message class
 */ export function getCommandClass < T extends CommandClass | CCAPI > ( cc ) { const constr = cc . constructor ; const ret = cc instanceof CommandClass ? Reflect . getMetadata ( METADATA_commandClass , constr ) : cc instanceof CCAPI ? Reflect . getMetadata ( METADATA_API , constr ) : undefined ; if ( ret == undefined ) { throw new ZWaveError ( `No command class defined for ${ constr . name }!` , ZWaveErrorCodes . CC_Invalid ) ; } return ret ; } /**
 * Retrieves the function type defined for a Z-Wave message class
 */ export function getCommandClassStatic < T extends Constructable < CommandClass > > ( classConstructor ) { const ret = Reflect . getMetadata ( METADATA_commandClass , classConstructor ) as CommandClasses | undefined ; if ( ret == undefined ) { throw new ZWaveError ( `No command class defined for ${ classConstructor . name }!` , ZWaveErrorCodes . CC_Invalid ) ; } return ret ; } /**
 * Looks up the command class constructor for a given command class type and function type
 */ export function getCCConstructor ( cc ) { const map = Reflect . getMetadata ( METADATA_commandClassMap , CommandClass ) as CommandClassMap | undefined ; if ( map != undefined ) return map . get ( cc ) ; } /**
 * Defines the implemented version of a Z-Wave command class
 */ export function implementedVersion ( version ) { return ( ccClass ) => { Reflect . defineMetadata ( METADATA_version , version , ccClass ) ; } ; } /**
 * Retrieves the implemented version defined for a Z-Wave command class
 */ export function getImplementedVersion < T extends CommandClass > ( cc ) { let constr ; if ( typeof cc === "number" ) { constr = getCCConstructor ( cc ) ; } else { constr = cc . constructor as Constructable < CommandClass > ; } let ret ; if ( constr != undefined ) ret = Reflect . getMetadata ( METADATA_version , constr ) ; if ( ret == undefined ) ret = 0 ; return ret ; } /**
 * Retrieves the implemented version defined for a Z-Wave command class
 */ export function getImplementedVersionStatic < T extends Constructable < CommandClass > > ( classConstructor ) { const ret = ( Reflect . getMetadata ( METADATA_version , classConstructor ) as number | undefined ) || 0 ; return ret ; } /**
 * Defines the CC command a subclass of a CC implements
 */ export function CCCommand ( command ) { return ( ccClass ) => { Reflect . defineMetadata ( METADATA_ccCommand , command , ccClass ) ; const ccId = getCommandClassStatic ( ccClass as unknown as typeof CommandClass ) ; const map = Reflect . getMetadata ( METADATA_ccCommandMap , CommandClass ) || new Map ( ) ; map . set ( getCCCommandMapKey ( ccId , command ) , ccClass as unknown as Constructable < CommandClass > ) ; Reflect . defineMetadata ( METADATA_ccCommandMap , map , CommandClass ) ; } ; } /**
 * Retrieves the CC command a subclass of a CC implements
 */ function getCCCommand < T extends CommandClass > ( cc ) { const constr = cc . constructor as Constructable < CommandClass > ; const ret = Reflect . getMetadata ( METADATA_ccCommand , constr ) as number | undefined ; return ret ; } /**
 * Looks up the command class constructor for a given command class type and function type
 */ function getCCCommandConstructor < TBase extends CommandClass > ( ccId , ccCommand ) { const map = Reflect . getMetadata ( METADATA_ccCommandMap , CommandClass ) as CCCommandMap | undefined ; if ( map != undefined ) return map . get ( getCCCommandMapKey ( ccId , ccCommand ) ) as unknown as Constructable < TBase > | undefined ; } /**
 * Defines the expected response associated with a Z-Wave message
 */ export function expectedCCResponse < TSent extends CommandClass , TReceived extends CommandClass > ( cc , predicate ? ) { return ( ccClass ) => { Reflect . defineMetadata ( METADATA_ccResponse , { cc , predicate } , ccClass ) ; } ; } /**
 * Retrieves the expected response (static or dynamic) defined for a Z-Wave message class
 */ export function getExpectedCCResponse < T extends CommandClass > ( ccClass ) { const constr = ccClass . constructor ; const ret = Reflect . getMetadata ( METADATA_ccResponse , constr ) as { cc : typeof CommandClass | DynamicCCResponse < T > ; } | undefined ; return ret ?. cc ; } /**
 * Retrieves the CC response predicate defined for a Z-Wave message class
 */ export function getCCResponsePredicate < T extends CommandClass > ( ccClass ) { const constr = ccClass . constructor ; const ret = Reflect . getMetadata ( METADATA_ccResponse , constr ) as { predicate : CCResponsePredicate < T > | undefined ; } | undefined ; return ret ?. predicate ; } export interface CCValueOptions { /**
     * Whether the decorated CC value is internal. Internal values are not
     * exposed to the user.
     */ internal ? : boolean ; /**
     * The minimum CC version required for this value to exist.
     */ minVersion ? : number ; /**
     * Whether this value should always be created/persisted, even if it is undefined. Default: false
     */ forceCreation ? : boolean ; /**
     * Whether this value represents a state (`true`) or a notification/event (`false`). Default: `true`
     */ stateful ? : boolean ; } param internal export function ccValue ( options ? ) { return ( target , property ) => { if ( ! target || ! ( target instanceof CommandClass ) ) return ; if ( ! options ) options = { } ; if ( options . internal == undefined ) options . internal = false ; if ( options . minVersion == undefined ) options . minVersion = 1 ; if ( options . forceCreation == undefined ) options . forceCreation = false ; const constr = target . constructor as typeof CommandClass ; const cc = getCommandClassStatic ( constr ) ; const metadata = Reflect . getMetadata ( METADATA_ccValues , CommandClass ) ?? { } ; if ( ! ( cc in metadata ) ) metadata [ cc ] = new Map ( ) ; const variables = metadata [ cc ] ; variables . set ( property as string | number , options ) ; Reflect . defineMetadata ( METADATA_ccValues , metadata , CommandClass ) ; } ; } /**
 * Returns all CC values and their definitions that have been defined with @ccValue()
 */ function getCCValueDefinitions ( commandClass ) { const constr = commandClass . constructor as typeof CommandClass ; const cc = getCommandClassStatic ( constr ) ; const metadata = Reflect . getMetadata ( METADATA_ccValues , CommandClass ) ?? { } ; if ( ! ( cc in metadata ) ) return new Map ( ) ; return metadata [ cc ] as Map < string | number , CCValueOptions > ; } param internal export function ccKeyValuePair ( options ? ) { return ( target , property ) => { if ( ! target || ! ( target instanceof CommandClass ) ) return ; if ( ! options ) options = { } ; if ( options . internal == undefined ) options . internal = false ; if ( options . minVersion == undefined ) options . minVersion = 1 ; if ( options . forceCreation == undefined ) options . forceCreation = false ; const constr = target . constructor as typeof CommandClass ; const cc = getCommandClassStatic ( constr ) ; const metadata = Reflect . getMetadata ( METADATA_ccKeyValuePairs , CommandClass ) || { } ; if ( ! ( cc in metadata ) ) metadata [ cc ] = new Map ( ) ; const variables = metadata [ cc ] ; variables . set ( property as string | number , options ) ; Reflect . defineMetadata ( METADATA_ccKeyValuePairs , metadata , CommandClass ) ; } ; } /**
 * Returns all CC key value pairs and their definitions that have been defined with @ccKeyValuePair()
 */ function getCCKeyValuePairDefinitions ( commandClass ) { const constr = commandClass . constructor as typeof CommandClass ; const cc = getCommandClassStatic ( constr ) ; const metadata = Reflect . getMetadata ( METADATA_ccKeyValuePairs , CommandClass ) || { } ; if ( ! ( cc in metadata ) ) return new Map ( ) ; return metadata [ cc ] as Map < string | number , CCValueOptions > ; } /**
 * Defines additional metadata for the given CC value
 */ export function ccValueMetadata ( meta ) { return ( target , property ) => { if ( ! target || ! ( target instanceof CommandClass ) ) return ; const constr = target . constructor as typeof CommandClass ; const cc = getCommandClassStatic ( constr ) ; const metadata = Reflect . getMetadata ( METADATA_ccValueMeta , CommandClass ) || { } ; if ( ! ( cc in metadata ) ) metadata [ cc ] = new Map ( ) ; const variables = metadata [ cc ] ; variables . set ( property as string | number , meta ) ; Reflect . defineMetadata ( METADATA_ccValueMeta , metadata , CommandClass ) ; } ; } /**
 * Retrieves defined metadata for the given CC value. If none is found, the default settings are returned.
 */ export function getCCValueMetadata ( cc , property ) { const metadata = Reflect . getMetadata ( METADATA_ccValueMeta , CommandClass ) || { } ; if ( ! ( cc in metadata ) ) return ValueMetadata . Any ; const map = metadata [ cc ] as Map < string | number , ValueMetadata > ; if ( map . has ( property ) ) return map . get ( property ) ! ; return ValueMetadata . Any ; } /**
 * Defines the simplified API associated with a Z-Wave command class
 */ export function API ( cc ) { return ( apiClass ) => { Reflect . defineMetadata ( METADATA_API , cc , apiClass ) ; const map = ( Reflect . getMetadata ( METADATA_APIMap , CCAPI ) || new Map ( ) ) as APIMap ; map . set ( cc , apiClass as unknown as APIConstructor ) ; Reflect . defineMetadata ( METADATA_APIMap , map , CCAPI ) ; } ; } publicAPI export function getAPI ( cc ) { const map = Reflect . getMetadata ( METADATA_APIMap , CCAPI ) as APIMap | undefined ; const ret = map ?. get ( cc ) ; return ret ; }