import type { ParamInfoMap } from "@zwave-js/config" ; import { CacheMetadata , CacheValue , CommandClasses , ConfigurationMetadata , ConfigValueFormat , encodeBitMask , encodePartial , getBitMaskWidth , getIntegerLimits , getMinIntegerSize , isConsecutiveArray , Maybe , MessageOrCCLogEntry , MessageRecord , parseBitMask , parsePartial , stripUndefined , validatePayload , ValueID , ValueMetadata , ZWaveError , ZWaveErrorCodes , } from "@zwave-js/core" ; import { getEnumMemberName , pick } from "@zwave-js/shared" ; import { distinct } from "alcalzone-shared/arrays" ; import { composeObject } from "alcalzone-shared/objects" ; import { padStart } from "alcalzone-shared/strings" ; import type { Driver } from "../driver/Driver" ; import { MessagePriority } from "../message/Constants" ; import { Endpoint } from "../node/Endpoint" ; import type { VirtualEndpoint } from "../node/VirtualEndpoint" ; import { CCAPI , PollValueImplementation , POLL_VALUE , SetValueImplementation , SET_VALUE , throwUnsupportedProperty , throwUnsupportedPropertyKey , throwWrongValueType , } from "./API" ; import { API , CCCommand , CCCommandOptions , CommandClass , commandClass , CommandClassDeserializationOptions , CommandClassOptions , expectedCCResponse , gotDeserializationOptions , implementedVersion , } from "./CommandClass" ; export enum ConfigurationCommand { Set = 0x04 , Get = 0x05 , Report = 0x06 , BulkSet = 0x07 , BulkGet = 0x08 , BulkReport = 0x09 , NameGet = 0x0a , NameReport = 0x0b , InfoGet = 0x0c , InfoReport = 0x0d , PropertiesGet = 0x0e , PropertiesReport = 0x0f , DefaultReset = 0x01 } publicAPI export type ConfigValue = import ( "@zwave-js/core" ) . ConfigValue ; function configValueToString ( value ) { if ( typeof value === "number" ) return value . toString ( ) ; else return [ ... value ] . join ( ", " ) ; } export class ConfigurationCCError extends ZWaveError { public constructor ( public readonly message , public readonly code , public readonly argument ) { super ( message , code ) ; Object . setPrototypeOf ( this , ConfigurationCCError . prototype ) ; } } export function getParamInformationValueID ( parameter , bitMask ? ) { return { commandClass : CommandClasses . Configuration , property : parameter , propertyKey : bitMask } ; } const isParamInfoFromConfigValueId = { commandClass : CommandClasses . Configuration , property : "isParamInformationFromConfig" } ; export type ConfigurationCCAPISetOptions = { parameter : number ; } & ( { bitMask ? : undefined ; value : ConfigValue ; } | { bitMask ? : undefined ; value : ConfigValue ; valueSize : 1 | 2 | 4 ; valueFormat : ConfigValueFormat ; } | { bitMask : number ; value : number ; } ) ; type NormalizedConfigurationCCAPISetOptions = { parameter : number ; valueSize : 1 | 2 | 4 ; valueFormat : ConfigValueFormat ; } & ( { bitMask ? : undefined ; value : ConfigValue ; } | { bitMask : number ; value : number ; } ) ; function normalizeConfigurationCCAPISetOptions ( endpoint , options ) { if ( "bitMask" in options && options . bitMask ) { const ccc = endpoint instanceof Endpoint ? endpoint . createCCInstance ( ConfigurationCC ) ! : endpoint . node . physicalNodes [ 0 ] . createCCInstance ( ConfigurationCC ) ! ; const paramInfo = ccc . getParamInformation ( options . parameter , options . bitMask ) ; if ( ! paramInfo . isFromConfig ) { throw new ZWaveError ( "Setting a partial configuration parameter requires it to be defined in a device config file!" , ZWaveErrorCodes . Argument_Invalid ) ; } return { parameter : options . parameter , bitMask : options . bitMask , value : options . value , valueSize : paramInfo . valueSize as any , valueFormat : paramInfo . format ! } ; } else if ( "valueSize" in options ) { return pick ( options , [ "parameter" , "value" , "valueSize" , "valueFormat" , ] ) ; } else { const ccc = endpoint instanceof Endpoint ? endpoint . createCCInstance ( ConfigurationCC ) ! : endpoint . node . physicalNodes [ 0 ] . createCCInstance ( ConfigurationCC ) ! ; const paramInfo = ccc . getParamInformation ( options . parameter , options . bitMask ) ; if ( ! paramInfo . isFromConfig ) { throw new ZWaveError ( "Setting a configuration parameter without specifying a value size and format requires it to be defined in a device config file!" , ZWaveErrorCodes . Argument_Invalid ) ; } return { parameter : options . parameter , value : options . value , valueSize : paramInfo . valueSize as any , valueFormat : paramInfo . format ! } ; } } function bulkMergePartialParamValues ( endpoint , options ) { const allParams = options . filter ( ( o ) => o . bitMask == undefined ) ; const unmergedPartials = new Map < number , NormalizedConfigurationCCAPISetOptions [ ] > ( ) ; for ( const partial of options . filter ( ( o ) => o . bitMask != undefined ) ) { if ( ! unmergedPartials . has ( partial . parameter ) ) { unmergedPartials . set ( partial . parameter , [ ] ) ; } unmergedPartials . get ( partial . parameter ) ! . push ( partial ) ; } if ( unmergedPartials . size ) { const ccc = endpoint instanceof Endpoint ? endpoint . createCCInstance ( ConfigurationCC ) ! : endpoint . node . physicalNodes [ 0 ] . createCCInstance ( ConfigurationCC ) ! ; for ( const [ parameter , partials ] of unmergedPartials ) { allParams . push ( { parameter , value : ccc . composePartialParamValues ( parameter , partials . map ( ( p ) => ( { bitMask : p . bitMask ! , partialValue : p . value as number } ) ) ) , valueSize : partials [ 0 ] . valueSize , valueFormat : partials [ 0 ] . valueFormat } ) ; } } return allParams as any ; } /** Determines whether a partial parameter needs to be interpreted as signed */ function isSignedPartial ( bitMask , format ) { return ( getBitMaskWidth ( bitMask ) > 1 && ( format ?? ConfigValueFormat . SignedInteger ) === ConfigValueFormat . SignedInteger ) ; } @ API ( CommandClasses . Configuration ) export class ConfigurationCCAPI extends CCAPI { public supportsCommand ( cmd ) { switch ( cmd ) { case ConfigurationCommand . Get : case ConfigurationCommand . Set : return true ; case ConfigurationCommand . BulkGet : case ConfigurationCommand . BulkSet : return this . version >= 2 ; case ConfigurationCommand . NameGet : case ConfigurationCommand . InfoGet : case ConfigurationCommand . PropertiesGet : return this . version >= 3 ; case ConfigurationCommand . DefaultReset : return this . version >= 4 ; } return super . supportsCommand ( cmd ) ; } protected [ SET_VALUE ] : SetValueImplementation = async ( { property , propertyKey } , value ) : Promise < void > => { if ( typeof property !== "number" ) { throwUnsupportedProperty ( this . ccId , property ) ; } if ( propertyKey != undefined && typeof propertyKey !== "number" ) { throwUnsupportedPropertyKey ( this . ccId , property , propertyKey ) ; } if ( typeof value !== "number" ) { throwWrongValueType ( this . ccId , property , "number" , typeof value ) ; } let ccInstance ; if ( this . isSinglecast ( ) ) { ccInstance = this . endpoint . createCCInstance ( ConfigurationCC ) ! ; } else if ( this . isMulticast ( ) ) { const nodes = this . endpoint . node . physicalNodes ; if ( ! nodes . every ( ( node ) => node . getEndpoint ( this . endpoint . index ) ?. supportsCC ( CommandClasses . Configuration ) ) ) { throw new ZWaveError ( `The multicast setValue API for Configuration CC requires all virtual target endpoints to support Configuration CC!` , ZWaveErrorCodes . CC_Invalid ) ; } const paramInfos = this . endpoint . node . physicalNodes . map ( ( node ) => node . getEndpoint ( this . endpoint . index ) ! . createCCInstance ( ConfigurationCC ) ! . getParamInformation ( property , propertyKey ) ) ; if ( ! paramInfos . length || ! paramInfos . every ( ( info , index ) => { if ( index === 0 ) return true ; return ( info . valueSize === paramInfos [ 0 ] . valueSize && info . format === paramInfos [ 0 ] . format ) ; } ) ) { throw new ZWaveError ( `The multicast setValue API for Configuration CC requires all virtual target nodes to have the same parameter definition!` , ZWaveErrorCodes . CC_Invalid ) ; } ccInstance = this . endpoint . node . physicalNodes [ 0 ] . getEndpoint ( this . endpoint . index ) ! . createCCInstance ( ConfigurationCC ) ! ; } else { throw new ZWaveError ( `The setValue API for Configuration CC is not supported via broadcast!` , ZWaveErrorCodes . CC_NotSupported ) ; } let valueSize = ccInstance . getParamInformation ( property ) . valueSize ; let valueFormat = ccInstance . getParamInformation ( property ) . format ?? ConfigValueFormat . SignedInteger ; let targetValue ; if ( propertyKey ) { if ( ! valueSize ) { valueSize = ccInstance . getParamInformation ( property , propertyKey ) . valueSize ; } targetValue = ccInstance . composePartialParamValue ( property , propertyKey , value ) ; valueFormat = ConfigValueFormat . UnsignedInteger ; } else { targetValue = value ; } if ( ! valueSize ) { valueSize = getMinIntegerSize ( targetValue , valueFormat === ConfigValueFormat . SignedInteger ) ; if ( ! valueSize ) { throw new ZWaveError ( `The value ${ targetValue } is not valid for configuration parameters!` , ZWaveErrorCodes . Argument_Invalid ) ; } } if ( ! isSafeValue ( targetValue , valueSize , valueFormat ) ) { throwInvalidValueError ( targetValue , property , valueSize , valueFormat ) ; } await this . set ( { parameter : property , value : targetValue , valueSize : valueSize as any , valueFormat } ) ; if ( ( this as ConfigurationCCAPI ) . isSinglecast ( ) ) { ( this as ConfigurationCCAPI ) . schedulePoll ( { property , propertyKey } , { transition : "fast" } ) ; } } ; protected [ POLL_VALUE ] : PollValueImplementation = async ( { property , propertyKey , } ) : Promise < unknown > => { if ( typeof property !== "number" ) { throwUnsupportedProperty ( this . ccId , property ) ; } if ( propertyKey != undefined && typeof propertyKey !== "number" ) { throwUnsupportedPropertyKey ( this . ccId , property , propertyKey ) ; } return this . get ( property , { valueBitMask : propertyKey } ) ; } ; /**
     * Requests the current value of a given config parameter from the device.
     * This may timeout and return `undefined` if the node does not respond.
     * If the node replied with a different parameter number, a `ConfigurationCCError`
     * is thrown with the `argument` property set to the reported parameter number.
     */ public async get ( parameter , options ? ) { this . assertPhysicalEndpoint ( this . endpoint ) ; this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . Get ) ; const { valueBitMask , allowUnexpectedResponse } = options ?? { } ; const cc = new ConfigurationCCGet ( this . driver , { nodeId : this . endpoint . nodeId , parameter , allowUnexpectedResponse } ) ; const response = await this . driver . sendCommand < ConfigurationCCReport > ( cc , this . commandOptions ) ; if ( ! response ) return ; if ( response . parameter === parameter ) { if ( ! valueBitMask ) return response . value ; const paramInfo = cc . getParamInformation ( response . parameter , valueBitMask ) ; return parsePartial ( response . value as any , valueBitMask , isSignedPartial ( valueBitMask , paramInfo . format ) ) ; } this . driver . controllerLog . logNode ( this . endpoint . nodeId , { message : `Received unexpected ConfigurationReport (param = ${ response . parameter }, value = ${ response . value . toString ( ) })` , direction : "inbound" , level : "error" } ) ; throw new ConfigurationCCError ( `The first existing parameter on this node is ${ response . parameter }` , ZWaveErrorCodes . ConfigurationCC_FirstParameterNumber , response . parameter ) ; } /**
     * Requests the current value of the config parameters from the device.
     * When the node does not respond due to a timeout, the `value` in the returned array will be `undefined`.
     */ public async getBulk ( options ) { this . assertPhysicalEndpoint ( this . endpoint ) ; let values ; const distinctParameters = distinct ( options . map ( ( o ) => o . parameter ) ) ; if ( this . supportsCommand ( ConfigurationCommand . BulkGet ) && isConsecutiveArray ( distinctParameters ) ) { const cc = new ConfigurationCCBulkGet ( this . driver , { nodeId : this . endpoint . nodeId , parameters : distinctParameters } ) ; const response = await this . driver . sendCommand < ConfigurationCCBulkReport > ( cc , this . commandOptions ) ; if ( response ) values = response . values ; } else { this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . Get ) ; const _values = new Map < number , ConfigValue > ( ) ; for ( const parameter of distinctParameters ) { const cc = new ConfigurationCCGet ( this . driver , { nodeId : this . endpoint . nodeId , parameter } ) ; const response = await this . driver . sendCommand < ConfigurationCCReport > ( cc , this . commandOptions ) ; if ( response ) { _values . set ( response . parameter , response . value ) ; } } values = _values ; } const cc = this . endpoint . createCCInstance ( ConfigurationCC ) ! ; return options . map ( ( o ) => { let value = values . get ( o . parameter ) ; if ( typeof value === "number" && o . bitMask ) { const paramInfo = cc . getParamInformation ( o . parameter , o . bitMask ) ; value = parsePartial ( value , o . bitMask , isSignedPartial ( o . bitMask , paramInfo . format ) ) ; } return { ... o , value } ; } ) ; } deprecated public async set ( parameter , value , valueSize , valueFormat ? ) ; /**
     * Sets a new value for a given config parameter of the device.
     */ public async set ( options ) ; /**
     * Sets a new value for a given config parameter of the device.
     */ public async set ( ... args ) { this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . Set ) ; let cc ; if ( args . length === 1 ) { const options = normalizeConfigurationCCAPISetOptions ( this . endpoint , args [ 0 ] ) ; let value = options . value ; if ( options . bitMask ) { const ccc = this . endpoint instanceof Endpoint ? this . endpoint . createCCInstance ( ConfigurationCC ) ! : this . endpoint . node . physicalNodes [ 0 ] . createCCInstance ( ConfigurationCC ) ! ; value = ccc . composePartialParamValue ( options . parameter , options . bitMask , options . value ) ; } cc = new ConfigurationCCSet ( this . driver , { nodeId : this . endpoint . nodeId , resetToDefault : false , parameter : options . parameter , value , valueSize : options . valueSize , valueFormat : options . valueFormat } ) ; } else { cc = new ConfigurationCCSet ( this . driver , { nodeId : this . endpoint . nodeId , resetToDefault : false , parameter : args [ 0 ] , value : args [ 1 ] , valueSize : args [ 2 ] , valueFormat : args [ 3 ] ?? ConfigValueFormat . SignedInteger } ) ; } await this . driver . sendCommand ( cc , this . commandOptions ) ; } /**
     * Sets new values for multiple config parameters of the device. Uses the `BulkSet` command if supported, otherwise falls back to individual `Set` commands.
     */ public async setBulk ( values ) { const normalized = values . map ( ( v ) => normalizeConfigurationCCAPISetOptions ( this . endpoint , v ) ) ; const allParams = bulkMergePartialParamValues ( this . endpoint , normalized ) ; const canUseBulkSet = this . supportsCommand ( ConfigurationCommand . BulkSet ) && isConsecutiveArray ( allParams . map ( ( v ) => v . parameter ) ) && new Set ( allParams . map ( ( v ) => v . valueFormat ) ) . size === 1 && new Set ( allParams . map ( ( v ) => v . valueSize ) ) . size === 1 ; if ( canUseBulkSet ) { const cc = new ConfigurationCCBulkSet ( this . driver , { nodeId : this . endpoint . nodeId , parameters : allParams . map ( ( v ) => v . parameter ) , valueSize : allParams [ 0 ] . valueSize , valueFormat : allParams [ 0 ] . valueFormat , values : allParams . map ( ( v ) => v . value as number ) , handshake : true } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } else { this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . Set ) ; for ( const { parameter , value , valueSize , valueFormat , } of allParams ) { const cc = new ConfigurationCCSet ( this . driver , { nodeId : this . endpoint . nodeId , parameter , value , valueSize , valueFormat } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } } } /**
     * Resets a configuration parameter to its default value.
     *
     * WARNING: This will throw on legacy devices (ConfigurationCC v3 and below)
     */ public async reset ( parameter ) { this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . Set ) ; const cc = new ConfigurationCCSet ( this . driver , { nodeId : this . endpoint . nodeId , parameter , resetToDefault : true } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } /**
     * Resets multiple configuration parameters to their default value. Uses BulkSet if supported, otherwise falls back to individual Set commands.
     *
     * WARNING: This will throw on legacy devices (ConfigurationCC v3 and below)
     */ public async resetBulk ( parameters ) { if ( isConsecutiveArray ( parameters ) && this . supportsCommand ( ConfigurationCommand . BulkSet ) ) { const cc = new ConfigurationCCBulkSet ( this . driver , { nodeId : this . endpoint . nodeId , parameters , resetToDefault : true } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } else { this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . Set ) ; const CCs = distinct ( parameters ) . map ( ( parameter ) => new ConfigurationCCSet ( this . driver , { nodeId : this . endpoint . nodeId , parameter , resetToDefault : true } ) ) ; for ( const cc of CCs ) { await this . driver . sendCommand ( cc , this . commandOptions ) ; } } } /** Resets all configuration parameters to their default value */ public async resetAll ( ) { this . assertPhysicalEndpoint ( this . endpoint ) ; this . assertSupportsCommand ( ConfigurationCommand , ConfigurationCommand . DefaultReset ) ; const cc = new ConfigurationCCDefaultReset ( this . driver , { nodeId : this . endpoint . nodeId } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } public async getProperties ( parameter ) { this . assertPhysicalEndpoint ( this . endpoint ) ; const cc = new ConfigurationCCPropertiesGet ( this . driver , { nodeId : this . endpoint . nodeId , parameter } ) ; const response = await this . driver . sendCommand < ConfigurationCCPropertiesReport > ( cc , this . commandOptions ) ; if ( response ) { return pick ( response , [ "valueSize" , "valueFormat" , "minValue" , "maxValue" , "defaultValue" , "nextParameter" , "altersCapabilities" , "isReadonly" , "isAdvanced" , "noBulkSupport" , ] ) ; } } /** Requests the name of a configuration parameter from the node */ public async getName ( parameter ) { this . assertPhysicalEndpoint ( this . endpoint ) ; const cc = new ConfigurationCCNameGet ( this . driver , { nodeId : this . endpoint . nodeId , parameter } ) ; const response = await this . driver . sendCommand < ConfigurationCCNameReport > ( cc , this . commandOptions ) ; return response ?. name ; } /** Requests usage info for a configuration parameter from the node */ public async getInfo ( parameter ) { this . assertPhysicalEndpoint ( this . endpoint ) ; const cc = new ConfigurationCCInfoGet ( this . driver , { nodeId : this . endpoint . nodeId , parameter } ) ; const response = await this . driver . sendCommand < ConfigurationCCInfoReport > ( cc , this . commandOptions ) ; return response ?. info ; } /**
     * This scans the node for the existing parameters. Found parameters will be reported
     * through the `value added` and `value updated` events.
     *
     * WARNING: This method throws for newer devices.
     *
     * WARNING: On nodes implementing V1 and V2, this process may take
     * **up to an hour**, depending on the configured timeout.
     *
     * WARNING: On nodes implementing V2, all parameters after 255 will be ignored.
     */ public async scanParametersLegacy ( ) { if ( this . version >= 3 ) { throw new ZWaveError ( "Use ConfigurationCC.interview instead of scanning parameters for versions 3 and above." , ZWaveErrorCodes . ConfigurationCC_NoLegacyScanOnNewDevices ) ; } this . assertPhysicalEndpoint ( this . endpoint ) ; this . driver . controllerLog . logNode ( this . endpoint . nodeId , `Scanning available parameters...` ) ; const ccInstance = this . endpoint . createCCInstance ( ConfigurationCC ) ! ; for ( let param = 1 ; param <= 255 ; param ++ ) { let originalValue ; this . driver . controllerLog . logNode ( this . endpoint . nodeId , { message : `  trying param ${ param }...` , direction : "outbound" } ) ; try { originalValue = await this . get ( param , { allowUnexpectedResponse : param === 1 } ) ; if ( originalValue != undefined ) { const logMessage = `  Param ${ param }:
    readable  = true
    valueSize = ${ ccInstance . getParamInformation ( param ) . valueSize }
    value     = ${ originalValue . toString ( ) }` ; this . driver . controllerLog . logNode ( this . endpoint . nodeId , { message : logMessage , direction : "inbound" } ) ; } } catch ( e ) { if ( e instanceof ConfigurationCCError && e . code === ZWaveErrorCodes . ConfigurationCC_FirstParameterNumber ) { if ( e . argument - 1 > param ) param = e . argument - 1 ; continue ; } throw e ; } } } } @ commandClass ( CommandClasses . Configuration ) @ implementedVersion ( 4 ) export class ConfigurationCC extends CommandClass { declare ccCommand : ConfigurationCommand ; public constructor ( driver , options ) { super ( driver , options ) ; this . registerValue ( "isParamInformationFromConfig" as any , true ) ; } public async interview ( ) { const node = this . getNode ( ) ! ; const endpoint = this . getEndpoint ( ) ! ; const api = endpoint . commandClasses . Configuration . withOptions ( { priority : MessagePriority . NodeQuery } ) ; this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `Interviewing ${ this . ccName }...` , direction : "none" } ) ; const config = node . deviceConfig ?. paramInformation ; if ( config ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `${ this . constructor . name }: Loading configuration parameters from device config` , direction : "none" } ) ; this . deserializeParamInformationFromConfig ( config ) ; } if ( this . version >= 3 ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : "finding first configuration parameter..." , direction : "outbound" } ) ; const param0props = await api . getProperties ( 0 ) ; let param ; if ( param0props ) { param = param0props . nextParameter ; if ( param === 0 ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `didn't report any config params, trying #1 just to be sure...` , direction : "inbound" } ) ; param = 1 ; } } else { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : "Finding first configuration parameter timed out, skipping interview..." , level : "warn" } ) ; return ; } while ( param > 0 ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `querying parameter #${ param } information...` , direction : "outbound" } ) ; const props = await api . getProperties ( param ) ; if ( ! props ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `Querying parameter #${ param } information timed out, skipping interview...` , level : "warn" } ) ; return ; } const { nextParameter , ... properties } = props ; let logMessage ; if ( properties . valueSize === 0 ) { logMessage = `Parameter #${ param } is unsupported. Next parameter: ${ nextParameter }` ; } else { const name = ( await api . getName ( param ) ) ?? "(unknown)" ; if ( ! node . deviceConfig ?. compat ?. skipConfigurationInfoQuery ) { await api . getInfo ( param ) ; } logMessage = `received information for parameter #${ param }:
parameter name:      ${ name }
value format:        ${ getEnumMemberName ( ConfigValueFormat , properties . valueFormat ) }
value size:          ${ properties . valueSize } bytes
min value:           ${ properties . minValue ?. toString ( ) ?? "undefined" }
max value:           ${ properties . maxValue ?. toString ( ) ?? "undefined" }
default value:       ${ properties . defaultValue ?. toString ( ) ?? "undefined" }
is read-only:        ${ ! ! properties . isReadonly }
is advanced (UI):    ${ ! ! properties . isAdvanced }
has bulk support:    ${ ! properties . noBulkSupport }
alters capabilities: ${ ! ! properties . altersCapabilities }` ; } this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : logMessage , direction : "inbound" } ) ; if ( nextParameter > param ) { param = nextParameter ; } else { break ; } } } await this . refreshValues ( ) ; this . interviewComplete = true ; } public async refreshValues ( ) { const node = this . getNode ( ) ! ; const endpoint = this . getEndpoint ( ) ! ; const api = endpoint . commandClasses . Configuration . withOptions ( { priority : MessagePriority . NodeQuery } ) ; if ( this . version < 3 ) { const paramInfo = node . deviceConfig ?. paramInformation ; if ( paramInfo ?. size ) { const alreadyQueried = new Set < number > ( ) ; for ( const param of paramInfo . keys ( ) ) { if ( paramInfo . get ( param ) ?. writeOnly ) continue ; if ( alreadyQueried . has ( param . parameter ) ) continue ; alreadyQueried . add ( param . parameter ) ; this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `querying parameter #${ param . parameter } value...` , direction : "outbound" } ) ; const paramValue = await api . get ( param . parameter ) ; if ( typeof paramValue === "number" ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `parameter #${ param . parameter } has value: ${ paramValue }` , direction : "inbound" } ) ; } else if ( ! paramValue ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `received no value for parameter #${ param . parameter }` , direction : "inbound" , level : "warn" } ) ; } } } else { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `${ this . constructor . name }: skipping interview because CC version is < 3 and there is no config file` , direction : "none" } ) ; } } else { const parameters = distinct ( this . getDefinedValueIDs ( ) . map ( ( v ) => v . property ) . filter ( ( p ) : p is number => typeof p === "number" ) ) ; for ( const param of parameters ) { if ( this . getParamInformation ( param ) . readable !== false ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `querying parameter #${ param } value...` , direction : "outbound" } ) ; await api . get ( param ) ; } else { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `not querying parameter #${ param } value, because it is writeonly` , direction : "none" } ) ; } } } } /**
     * Whether this node's param information was loaded from a config file.
     * If this is true, we don't trust what the node reports
     */ protected get isParamInformationFromConfig ( ) : boolean { return ( this . getValueDB ( ) . getValue ( isParamInfoFromConfigValueId ) === true ) ; } internal public extendParamInformation ( parameter , valueBitMask , info ) { if ( this . isParamInformationFromConfig ) return ; const valueDB = this . getValueDB ( ) ; const valueId = getParamInformationValueID ( parameter , valueBitMask ) ; const metadata = this . getParamInformation ( parameter , valueBitMask ) ; Object . assign ( metadata , info ) ; valueDB . setMetadata ( valueId , metadata ) ; } internal public getParamInformation ( parameter , valueBitMask ? ) { const valueDB = this . getValueDB ( ) ; const valueId = getParamInformationValueID ( parameter , valueBitMask ) ; return ( valueDB . getMetadata ( valueId ) ?? { ... ValueMetadata . Any } ) as ConfigurationMetadata ; } internal public getQueriedParamInfos ( ) { const parameters = distinct ( this . getDefinedValueIDs ( ) . map ( ( v ) => v . property ) . filter ( ( p ) : p is number => typeof p === "number" ) ) ; return composeObject ( parameters . map ( ( p ) => [ p as any , this . getParamInformation ( p ) ] ) ) ; } /**
     * Returns stored config parameter metadata for all partial config params addressed with the given parameter number
     */ public getPartialParamInfos ( parameter ) { const valueDB = this . getValueDB ( ) ; return valueDB . findMetadata ( ( id ) => id . commandClass === this . ccId && id . property === parameter && id . propertyKey != undefined ) as ( ValueID & { metadata : ConfigurationMetadata ; } ) [ ] ; } /**
     * Computes the full value of a parameter after applying a partial param value
     */ public composePartialParamValue ( parameter , bitMask , partialValue ) { return this . composePartialParamValues ( parameter , [ { bitMask , partialValue } , ] ) ; } /**
     * Computes the full value of a parameter after applying multiple partial param values
     */ public composePartialParamValues ( parameter , partials ) { const valueDB = this . getValueDB ( ) ; const otherValues = valueDB . findValues ( ( id ) => id . commandClass === this . ccId && id . property === parameter && id . propertyKey != undefined && ! partials . some ( ( p ) => id . propertyKey === p . bitMask ) ) ; let ret = 0 ; for ( const { propertyKey : bitMask , value : partialValue , } of otherValues ) { ret = encodePartial ( ret , partialValue as number , bitMask as number ) ; } for ( const { bitMask , partialValue } of partials ) { ret = encodePartial ( ret , partialValue , bitMask ) ; } return ret ; } public serializeValuesForCache ( ) { let values = super . serializeValuesForCache ( ) ; values = values . filter ( ( v ) => v . property !== "isParamInformationFromConfig" ) ; return values ; } public serializeMetadataForCache ( ) { let metadata = super . serializeMetadataForCache ( ) ; if ( this . isParamInformationFromConfig ) { metadata = metadata . filter ( ( m ) => typeof m . property === "number" ) ; } return metadata ; } /** Deserializes the config parameter info from a config file */ public deserializeParamInformationFromConfig ( config ) { const valueDB = this . getValueDB ( ) ; for ( const meta of valueDB . getAllMetadata ( this . ccId ) ) { if ( typeof meta . property === "number" ) { valueDB . setMetadata ( meta , undefined , { noEvent : true } ) ; } } valueDB . setValue ( isParamInfoFromConfigValueId , false ) ; for ( const [ param , info ] of config . entries ( ) ) { const paramInfo = stripUndefined ( { type : "number" , valueSize : info . valueSize , min : info . minValue , max : info . maxValue , default : info . defaultValue , unit : info . unit , format : info . unsigned ? ConfigValueFormat . UnsignedInteger : ConfigValueFormat . SignedInteger , readable : ! info . writeOnly , writeable : ! info . readOnly , allowManualEntry : info . allowManualEntry , states : info . options . length > 0 ? composeObject ( info . options . map ( ( { label , value } ) => [ value . toString ( ) , label , ] ) ) : undefined , label : info . label , description : info . description , isFromConfig : true } ) ; this . extendParamInformation ( param . parameter , param . valueBitMask , paramInfo ) ; } valueDB . setValue ( isParamInfoFromConfigValueId , true ) ; } public translatePropertyKey ( property , propertyKey ? ) { if ( typeof property === "number" && ( propertyKey == undefined || typeof propertyKey === "number" ) ) { return undefined ; } return super . translateProperty ( property , propertyKey ) ; } public translateProperty ( property , propertyKey ? ) { if ( typeof property === "number" && ( propertyKey == undefined || typeof propertyKey === "number" ) ) { const paramInfo = this . getParamInformation ( property , propertyKey ) ; if ( paramInfo . label ) return paramInfo . label ; let ret = `param${ padStart ( property . toString ( ) , 3 , "0" ) }` ; if ( propertyKey != undefined ) { ret += "_" + propertyKey . toString ( ) ; } return ret ; } return super . translateProperty ( property , propertyKey ) ; } } @ CCCommand ( ConfigurationCommand . Report ) export class ConfigurationCCReport extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length > 2 ) ; this . _parameter = this . payload [ 0 ] ; this . _valueSize = this . payload [ 1 ] & 0b111 ; validatePayload ( this . _valueSize >= 1 , this . _valueSize <= 4 , this . payload . length >= 2 + this . _valueSize ) ; const oldParamInformation = this . getParamInformation ( this . _parameter ) ; this . _value = parseValue ( this . payload . slice ( 2 ) , this . _valueSize , oldParamInformation . format ?? ConfigValueFormat . SignedInteger ) ; this . extendParamInformation ( this . _parameter , undefined , { valueSize : this . _valueSize , type : oldParamInformation . format === ConfigValueFormat . BitField ? "number[]" : "number" } ) ; if ( this . version < 3 && ! this . isParamInformationFromConfig && oldParamInformation . min == undefined && oldParamInformation . max == undefined ) { const isSigned = oldParamInformation . format == undefined || oldParamInformation . format === ConfigValueFormat . SignedInteger ; this . extendParamInformation ( this . _parameter , undefined , getIntegerLimits ( this . _valueSize as any , isSigned ) ) ; } const partialParams = this . getPartialParamInfos ( this . _parameter ) ; if ( partialParams . length > 0 ) { for ( const param of partialParams ) { if ( typeof param . propertyKey === "number" ) { this . getValueDB ( ) . setValue ( { commandClass : this . ccId , property : this . _parameter , propertyKey : param . propertyKey } , parsePartial ( this . _value as any , param . propertyKey , isSignedPartial ( param . propertyKey , param . metadata . format ) ) ) ; } } } else { this . getValueDB ( ) . setValue ( { commandClass : this . ccId , property : this . _parameter } , this . _value ) ; } } private _parameter : number ; public get parameter ( ) : number { return this . _parameter ; } private _value : ConfigValue ; public get value ( ) : ConfigValue { return this . _value ; } private _valueSize : number ; public get valueSize ( ) : number { return this . _valueSize ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter , "value size" : this . valueSize , value : configValueToString ( this . value ) } } ; } } function testResponseForConfigurationGet ( sent , received ) { return ( sent . parameter === received . parameter || sent . allowUnexpectedResponse ) ; } interface ConfigurationCCGetOptions extends CCCommandOptions { parameter : number ; /**
     * If this is `true`, responses with different parameters than expected are accepted
     * and treated as hints for the first parameter number.
     */ allowUnexpectedResponse ? : boolean ; } @ CCCommand ( ConfigurationCommand . Get ) @ expectedCCResponse ( ConfigurationCCReport , testResponseForConfigurationGet ) export class ConfigurationCCGet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . parameter = options . parameter ; this . allowUnexpectedResponse = options . allowUnexpectedResponse ?? false ; } } public parameter : number ; public allowUnexpectedResponse : boolean ; public serialize ( ) { this . payload = Buffer . from ( [ this . parameter ] ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter } } ; } } type ConfigurationCCSetOptions = CCCommandOptions & ( { parameter : number ; resetToDefault : true ; } | { parameter : number ; resetToDefault ? : false ; valueSize : number ; /** How the value is encoded. Defaults to SignedInteger */ valueFormat ? : ConfigValueFormat ; value : ConfigValue ; } ) ; @ CCCommand ( ConfigurationCommand . Set ) export class ConfigurationCCSet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . parameter = options . parameter ; if ( options . resetToDefault && this . version <= 3 ) { throw new ZWaveError ( `The resetToDefault flag MUST not be used on nodes implementing ConfigurationCC V3 or less!` , ZWaveErrorCodes . ConfigurationCC_NoResetToDefaultOnLegacyDevices ) ; } this . resetToDefault = ! ! options . resetToDefault ; if ( ! options . resetToDefault ) { this . valueSize = options . valueSize ; this . valueFormat = options . valueFormat ?? ConfigValueFormat . SignedInteger ; this . value = options . value ; } } } public resetToDefault : boolean ; public parameter : number ; public valueSize : number | undefined ; public valueFormat : ConfigValueFormat | undefined ; public value : ConfigValue | undefined ; public serialize ( ) { const valueSize = this . resetToDefault ? 1 : this . valueSize ! ; const payloadLength = 2 + valueSize ; this . payload = Buffer . alloc ( payloadLength , 0 ) ; this . payload [ 0 ] = this . parameter ; this . payload [ 1 ] = ( this . resetToDefault ? 128 : 0 ) | ( valueSize & 0b111 ) ; if ( ! this . resetToDefault ) { if ( typeof this . value === "number" && ! isSafeValue ( this . value , valueSize , this . valueFormat ! ) ) { throwInvalidValueError ( this . value , this . parameter , valueSize , this . valueFormat ! ) ; } try { serializeValue ( this . payload , 2 , valueSize , this . valueFormat ! , this . value ! ) ; } catch ( e ) { tryCatchOutOfBoundsError ( e as Error , this . value , this . parameter , valueSize , this . valueFormat ! ) ; } } return super . serialize ( ) ; } public toLogEntry ( ) { const message = { "parameter #" : this . parameter , "reset to default" : this . resetToDefault } ; if ( this . valueSize != undefined ) { message [ "value size" ] = this . valueSize ; } if ( this . valueFormat != undefined ) { message [ "value format" ] = getEnumMemberName ( ConfigValueFormat , this . valueFormat ) ; } if ( this . value != undefined ) { message . value = configValueToString ( this . value ) ; } return { ... super . toLogEntry ( ) , message } ; } } type ConfigurationCCBulkSetOptions = CCCommandOptions & { parameters : number [ ] ; handshake ? : boolean ; } & ( { resetToDefault : true ; } | { resetToDefault ? : false ; valueSize : number ; valueFormat ? : ConfigValueFormat ; values : number [ ] ; } ) ; function getResponseForBulkSet ( cc ) { return cc . handshake ? ConfigurationCCBulkReport : undefined ; } @ CCCommand ( ConfigurationCommand . BulkSet ) @ expectedCCResponse ( getResponseForBulkSet ) export class ConfigurationCCBulkSet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . _parameters = options . parameters ; if ( this . _parameters . length < 1 ) { throw new ZWaveError ( `In a ConfigurationCC.BulkSet, parameters must be a non-empty array` , ZWaveErrorCodes . CC_Invalid ) ; } else if ( ! isConsecutiveArray ( this . _parameters ) ) { throw new ZWaveError ( `A ConfigurationCC.BulkSet can only be used for consecutive parameters` , ZWaveErrorCodes . CC_Invalid ) ; } this . _handshake = ! ! options . handshake ; this . _resetToDefault = ! ! options . resetToDefault ; if ( ! ! options . resetToDefault ) { this . _valueSize = 1 ; this . _valueFormat = ConfigValueFormat . SignedInteger ; this . _values = this . _parameters . map ( ( ) => 0 ) ; } else { this . _valueSize = options . valueSize ; this . _valueFormat = options . valueFormat ?? this . getParamInformation ( this . _parameters [ 0 ] ) . format ?? ConfigValueFormat . SignedInteger ; this . _values = options . values ; } } } private _parameters : number [ ] ; public get parameters ( ) : number [ ] { return this . _parameters ; } private _resetToDefault : boolean ; public get resetToDefault ( ) : boolean { return this . _resetToDefault ; } private _valueSize : number ; public get valueSize ( ) : number { return this . _valueSize ; } private _valueFormat : ConfigValueFormat ; public get valueFormat ( ) : ConfigValueFormat { return this . _valueFormat ; } private _values : number [ ] ; public get values ( ) : number [ ] { return this . _values ; } private _handshake : boolean ; public get handshake ( ) : boolean { return this . _handshake ; } public serialize ( ) { const valueSize = this . _resetToDefault ? 1 : this . valueSize ; const payloadLength = 4 + valueSize * this . parameters . length ; this . payload = Buffer . alloc ( payloadLength , 0 ) ; this . payload . writeUInt16BE ( this . parameters [ 0 ] , 0 ) ; this . payload [ 2 ] = this . parameters . length ; this . payload [ 3 ] = ( this . _resetToDefault ? 128 : 0 ) | ( this . handshake ? 64 : 0 ) | ( valueSize & 0b111 ) ; if ( ! this . _resetToDefault ) { for ( let i = 0 ; i < this . parameters . length ; i ++ ) { const value = this . _values [ i ] ; const param = this . _parameters [ i ] ; if ( ! isSafeValue ( value , valueSize , this . _valueFormat ) ) { throwInvalidValueError ( value , param , valueSize , this . _valueFormat ) ; } try { serializeValue ( this . payload , 4 + i * valueSize , valueSize , this . _valueFormat , value ) ; } catch ( e ) { tryCatchOutOfBoundsError ( e as Error , value , param , valueSize , this . _valueFormat ) ; } } } return super . serialize ( ) ; } public toLogEntry ( ) { const message = { handshake : this . handshake , "reset to default" : this . resetToDefault , "value size" : this . valueSize } ; if ( this . _values . length > 0 ) { message . values = this . _values . map ( ( value , i ) => `\n· #${ this . _parameters [ i ] }: ${ configValueToString ( value ) }` ) . join ( "" ) ; } return { ... super . toLogEntry ( ) , message } ; } } @ CCCommand ( ConfigurationCommand . BulkReport ) export class ConfigurationCCBulkReport extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 5 ) ; const firstParameter = this . payload . readUInt16BE ( 0 ) ; const numParams = this . payload [ 2 ] ; this . _reportsToFollow = this . payload [ 3 ] ; this . _defaultValues = ! ! ( this . payload [ 4 ] & 128 ) ; this . _isHandshakeResponse = ! ! ( this . payload [ 4 ] & 64 ) ; this . _valueSize = this . payload [ 4 ] & 0b111 ; validatePayload ( this . payload . length >= 5 + numParams * this . _valueSize ) ; for ( let i = 0 ; i < numParams ; i ++ ) { const param = firstParameter + i ; this . _values . set ( param , parseValue ( this . payload . slice ( 5 + i * this . valueSize ) , this . valueSize , this . getParamInformation ( param ) . format ?? ConfigValueFormat . SignedInteger ) ) ; } for ( const [ parameter , value ] of this . _values . entries ( ) ) { this . getValueDB ( ) . setValue ( { commandClass : this . ccId , property : parameter } , value ) ; } } private _reportsToFollow : number ; public get reportsToFollow ( ) : number { return this . _reportsToFollow ; } public getPartialCCSessionId ( ) { return { } ; } public expectMoreMessages ( ) { return this . _reportsToFollow > 0 ; } private _defaultValues : boolean ; public get defaultValues ( ) : boolean { return this . _defaultValues ; } private _isHandshakeResponse : boolean ; public get isHandshakeResponse ( ) : boolean { return this . _isHandshakeResponse ; } private _valueSize : number ; public get valueSize ( ) : number { return this . _valueSize ; } private _values = new Map < number , ConfigValue > ( ) ; public get values ( ) : ReadonlyMap < number , ConfigValue > { return this . _values ; } public toLogEntry ( ) { const message = { "handshake response" : this . _isHandshakeResponse , "default values" : this . _defaultValues , "value size" : this . _valueSize , "reports to follow" : this . reportsToFollow } ; if ( this . _values . size > 0 ) { message . values = [ ... this . _values ] . map ( ( [ param , value ] ) => `
· #${ param }: ${ configValueToString ( value ) }` ) . join ( "" ) ; } return { ... super . toLogEntry ( ) , message } ; } } interface ConfigurationCCBulkGetOptions extends CCCommandOptions { parameters : number [ ] ; } @ CCCommand ( ConfigurationCommand . BulkGet ) @ expectedCCResponse ( ConfigurationCCBulkReport ) export class ConfigurationCCBulkGet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . _parameters = options . parameters . sort ( ) ; if ( ! isConsecutiveArray ( this . parameters ) ) { throw new ZWaveError ( `A ConfigurationCC.BulkGet can only be used for consecutive parameters` , ZWaveErrorCodes . CC_Invalid ) ; } } } private _parameters : number [ ] ; public get parameters ( ) : number [ ] { return this . _parameters ; } public serialize ( ) { this . payload = Buffer . allocUnsafe ( 3 ) ; this . payload . writeUInt16BE ( this . parameters [ 0 ] , 0 ) ; this . payload [ 2 ] = this . parameters . length ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { parameters : this . parameters . join ( ", " ) } } ; } } @ CCCommand ( ConfigurationCommand . NameReport ) export class ConfigurationCCNameReport extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 3 ) ; this . _parameter = this . payload . readUInt16BE ( 0 ) ; this . _reportsToFollow = this . payload [ 2 ] ; if ( this . _reportsToFollow > 0 ) { validatePayload ( this . payload . length >= 4 ) ; } this . _name = this . payload . slice ( 3 ) . toString ( "utf8" ) ; } private _parameter : number ; public get parameter ( ) : number { return this . _parameter ; } private _name : string ; public get name ( ) : string { return this . _name ; } private _reportsToFollow : number ; public get reportsToFollow ( ) : number { return this . _reportsToFollow ; } public getPartialCCSessionId ( ) { return { parameter : this . _parameter } ; } public expectMoreMessages ( ) { return this . _reportsToFollow > 0 ; } public mergePartialCCs ( partials ) { this . _name = [ ... partials , this ] . map ( ( report ) => report . _name ) . reduce ( ( prev , cur ) => prev + cur , "" ) ; this . extendParamInformation ( this . parameter , undefined , { name : this . name } ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter , name : this . name , "reports to follow" : this . reportsToFollow } } ; } } @ CCCommand ( ConfigurationCommand . NameGet ) @ expectedCCResponse ( ConfigurationCCNameReport ) export class ConfigurationCCNameGet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . parameter = options . parameter ; } } public parameter : number ; public serialize ( ) { this . payload = Buffer . allocUnsafe ( 2 ) ; this . payload . writeUInt16BE ( this . parameter , 0 ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter } } ; } } @ CCCommand ( ConfigurationCommand . InfoReport ) export class ConfigurationCCInfoReport extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 3 ) ; this . _parameter = this . payload . readUInt16BE ( 0 ) ; this . _reportsToFollow = this . payload [ 2 ] ; if ( this . _reportsToFollow > 0 ) { validatePayload ( this . payload . length >= 4 ) ; } this . _info = this . payload . slice ( 3 ) . toString ( "utf8" ) ; } private _parameter : number ; public get parameter ( ) : number { return this . _parameter ; } private _info : string ; public get info ( ) : string { return this . _info ; } private _reportsToFollow : number ; public get reportsToFollow ( ) : number { return this . _reportsToFollow ; } public getPartialCCSessionId ( ) { return { parameter : this . _parameter } ; } public expectMoreMessages ( ) { return this . _reportsToFollow > 0 ; } public mergePartialCCs ( partials ) { this . _info = [ ... partials , this ] . map ( ( report ) => report . _info ) . reduce ( ( prev , cur ) => prev + cur , "" ) ; this . extendParamInformation ( this . _parameter , undefined , { info : this . _info } ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter , info : this . info , "reports to follow" : this . reportsToFollow } } ; } } @ CCCommand ( ConfigurationCommand . InfoGet ) @ expectedCCResponse ( ConfigurationCCInfoReport ) export class ConfigurationCCInfoGet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . parameter = options . parameter ; } } public parameter : number ; public serialize ( ) { this . payload = Buffer . allocUnsafe ( 2 ) ; this . payload . writeUInt16BE ( this . parameter , 0 ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter } } ; } } @ CCCommand ( ConfigurationCommand . PropertiesReport ) export class ConfigurationCCPropertiesReport extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 3 ) ; this . _parameter = this . payload . readUInt16BE ( 0 ) ; this . _valueFormat = ( this . payload [ 2 ] & 0b111000 ) >>> 3 ; this . _valueSize = this . payload [ 2 ] & 0b111 ; if ( this . _valueSize === 0 && this . payload . length < 5 ) { this . _nextParameter = 0 ; return ; } const nextParameterOffset = 3 + 3 * this . _valueSize ; validatePayload ( this . payload . length >= nextParameterOffset + 2 ) ; if ( this . valueSize > 0 ) { if ( this . _valueFormat !== ConfigValueFormat . BitField ) { this . _minValue = parseValue ( this . payload . slice ( 3 ) , this . _valueSize , this . _valueFormat ) ; } this . _maxValue = parseValue ( this . payload . slice ( 3 + this . _valueSize ) , this . _valueSize , this . _valueFormat ) ; this . _defaultValue = parseValue ( this . payload . slice ( 3 + 2 * this . _valueSize ) , this . _valueSize , this . _valueFormat ) ; } if ( this . version < 4 ) { this . _nextParameter = this . payload . readUInt16BE ( this . payload . length - 2 ) ; } else { this . _nextParameter = this . payload . readUInt16BE ( nextParameterOffset ) ; validatePayload ( this . payload . length >= nextParameterOffset + 3 ) ; const options1 = this . payload [ 2 ] ; const options2 = this . payload [ 3 + 3 * this . valueSize + 2 ] ; this . _altersCapabilities = ! ! ( options1 & 128 ) ; this . _isReadonly = ! ! ( options1 & 64 ) ; this . _isAdvanced = ! ! ( options2 & 0b1 ) ; this . _noBulkSupport = ! ! ( options2 & 0b10 ) ; } if ( this . _valueSize > 0 ) { const valueType = this . _valueFormat === ConfigValueFormat . SignedInteger || this . _valueFormat === ConfigValueFormat . UnsignedInteger ? "number" : "number[]" ; const paramInfo = stripUndefined ( { type : valueType , valueFormat : this . _valueFormat , valueSize : this . _valueSize , minValue : this . _minValue , maxValue : this . _maxValue , defaultValue : this . _defaultValue , requiresReInclusion : this . _altersCapabilities , writeable : ! this . _isReadonly , isAdvanced : this . _isAdvanced , noBulkSupport : this . _noBulkSupport } ) ; this . extendParamInformation ( this . _parameter , undefined , paramInfo ) ; } } private _parameter : number ; public get parameter ( ) : number { return this . _parameter ; } private _valueSize : number ; public get valueSize ( ) : number { return this . _valueSize ; } private _valueFormat : ConfigValueFormat ; public get valueFormat ( ) : ConfigValueFormat { return this . _valueFormat ; } private _minValue : ConfigValue | undefined ; public get minValue ( ) : ConfigValue | undefined { return this . _minValue ; } private _maxValue : ConfigValue | undefined ; public get maxValue ( ) : ConfigValue | undefined { return this . _maxValue ; } private _defaultValue : ConfigValue | undefined ; public get defaultValue ( ) : ConfigValue | undefined { return this . _defaultValue ; } private _nextParameter : number ; public get nextParameter ( ) : number { return this . _nextParameter ; } private _altersCapabilities : boolean | undefined ; public get altersCapabilities ( ) : boolean | undefined { return this . _altersCapabilities ; } private _isReadonly : boolean | undefined ; public get isReadonly ( ) : boolean | undefined { return this . _isReadonly ; } private _isAdvanced : boolean | undefined ; public get isAdvanced ( ) : boolean | undefined { return this . _isAdvanced ; } private _noBulkSupport : boolean | undefined ; public get noBulkSupport ( ) : boolean | undefined { return this . _noBulkSupport ; } public toLogEntry ( ) { const message = { "parameter #" : this . _parameter , "next param #" : this . _nextParameter , "value size" : this . _valueSize , "value format" : getEnumMemberName ( ConfigValueFormat , this . _valueFormat ) } ; if ( this . _minValue != undefined ) { message [ "min value" ] = configValueToString ( this . _minValue ) ; } if ( this . _maxValue != undefined ) { message [ "max value" ] = configValueToString ( this . _maxValue ) ; } if ( this . _defaultValue != undefined ) { message [ "default value" ] = configValueToString ( this . _defaultValue ) ; } if ( this . _altersCapabilities != undefined ) { message [ "alters capabilities" ] = this . _altersCapabilities ; } if ( this . _isReadonly != undefined ) { message . readonly = this . _isReadonly ; } if ( this . _isAdvanced != undefined ) { message . advanced = this . _isAdvanced ; } if ( this . _noBulkSupport != undefined ) { message [ "bulk support" ] = ! this . _noBulkSupport ; } return { ... super . toLogEntry ( ) , message } ; } } @ CCCommand ( ConfigurationCommand . PropertiesGet ) @ expectedCCResponse ( ConfigurationCCPropertiesReport ) export class ConfigurationCCPropertiesGet extends ConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . parameter = options . parameter ; } } public parameter : number ; public serialize ( ) { this . payload = Buffer . allocUnsafe ( 2 ) ; this . payload . writeUInt16BE ( this . parameter , 0 ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "parameter #" : this . parameter } } ; } } @ CCCommand ( ConfigurationCommand . DefaultReset ) export class ConfigurationCCDefaultReset extends ConfigurationCC { } function isSafeValue ( value , size , format ) { let minValue ; let maxValue ; switch ( format ) { case ConfigValueFormat . SignedInteger : minValue = - Math . pow ( 2 , 8 * size - 1 ) ; maxValue = Math . pow ( 2 , 8 * size - 1 ) - 1 ; break ; case ConfigValueFormat . UnsignedInteger : case ConfigValueFormat . Enumerated : minValue = 0 ; maxValue = Math . pow ( 2 , 8 * size ) ; break ; case ConfigValueFormat . BitField : default : throw new Error ( "not implemented" ) ; } return minValue <= value && value <= maxValue ; } /** Interprets values from the payload depending on the value format */ function parseValue ( raw , size , format ) { switch ( format ) { case ConfigValueFormat . SignedInteger : return raw . readIntBE ( 0 , size ) ; case ConfigValueFormat . UnsignedInteger : case ConfigValueFormat . Enumerated : return raw . readUIntBE ( 0 , size ) ; case ConfigValueFormat . BitField : return new Set ( parseBitMask ( raw . slice ( 0 , size ) ) ) ; } } function throwInvalidValueError ( value , parameter , valueSize , valueFormat ) { throw new ZWaveError ( `The value ${ value } is invalid for configuration parameter ${ parameter } (size = ${ valueSize }, format = ${ getEnumMemberName ( ConfigValueFormat , valueFormat ) })!` , ZWaveErrorCodes . Argument_Invalid ) ; } function tryCatchOutOfBoundsError ( e , value , parameter , valueSize , valueFormat ) { if ( e . message . includes ( "out of bounds" ) ) { throwInvalidValueError ( value , parameter , valueSize , valueFormat ) ; } else { throw e ; } } /** Serializes values into the payload according to the value format */ function serializeValue ( payload , offset , size , format , value ) { switch ( format ) { case ConfigValueFormat . SignedInteger : payload . writeIntBE ( value as number , offset , size ) ; return ; case ConfigValueFormat . UnsignedInteger : case ConfigValueFormat . Enumerated : payload . writeUIntBE ( value as number , offset , size ) ; return ; case ConfigValueFormat . BitField : { const mask = encodeBitMask ( [ ... ( value as Set < number > ) . values ( ) ] , size * 8 ) ; mask . copy ( payload , offset ) ; return ; } } }