import { CommandClasses , Duration , getCCName , Maybe , MessageOrCCLogEntry , validatePayload , ValueID , ValueMetadata , ZWaveError , ZWaveErrorCodes , } from "@zwave-js/core" ; import { pick } from "@zwave-js/shared" ; import type { Driver } from "../driver/Driver" ; import { MessagePriority } from "../message/Constants" ; import { CCAPI , PollValueImplementation , POLL_VALUE , SetValueImplementation , SET_VALUE , throwMissingPropertyKey , throwUnsupportedProperty , throwUnsupportedPropertyKey , throwWrongValueType , } from "./API" ; import type { AssociationCC } from "./AssociationCC" ; import { API , CCCommand , CCCommandOptions , CommandClass , commandClass , CommandClassDeserializationOptions , expectedCCResponse , gotDeserializationOptions , implementedVersion , } from "./CommandClass" ; export enum SceneControllerConfigurationCommand { Set = 0x01 , Get = 0x02 , Report = 0x03 } export function getSceneIdValueID ( endpoint , groupId ) { return { commandClass : CommandClasses [ "Scene Controller Configuration" ] , endpoint , property : "sceneId" , propertyKey : groupId } ; } export function getDimmingDurationValueID ( endpoint , groupId ) { return { commandClass : CommandClasses [ "Scene Controller Configuration" ] , endpoint , property : "dimmingDuration" , propertyKey : groupId } ; } function setSceneConfigurationMetadata ( this , groupId ) { const valueDB = this . getValueDB ( ) ; const sceneIdValueId = getSceneIdValueID ( this . endpointIndex , groupId ) ; const dimmingDurationValueId = getDimmingDurationValueID ( this . endpointIndex , groupId ) ; if ( ! valueDB . hasMetadata ( sceneIdValueId ) ) { valueDB . setMetadata ( sceneIdValueId , { ... ValueMetadata . UInt8 , label : `Associated Scene ID (${ groupId })` , valueChangeOptions : [ "transitionDuration" ] } ) ; } if ( ! valueDB . hasMetadata ( dimmingDurationValueId ) ) { valueDB . setMetadata ( dimmingDurationValueId , { ... ValueMetadata . Duration , label : `Dimming duration (${ groupId })` } ) ; } } function persistSceneConfig ( this , groupId , sceneId , dimmingDuration ) { const sceneIdValueId = getSceneIdValueID ( this . endpointIndex , groupId ) ; const dimmingDurationValueId = getDimmingDurationValueID ( this . endpointIndex , groupId ) ; const valueDB = this . getValueDB ( ) ; if ( ! valueDB . hasMetadata ( sceneIdValueId ) || ! valueDB . hasMetadata ( dimmingDurationValueId ) ) { setSceneConfigurationMetadata . call ( this , groupId ) ; } valueDB . setValue ( sceneIdValueId , sceneId ) ; valueDB . setValue ( dimmingDurationValueId , dimmingDuration ) ; return true ; } @ API ( CommandClasses [ "Scene Controller Configuration" ] ) export class SceneControllerConfigurationCCAPI extends CCAPI { public supportsCommand ( cmd ) { switch ( cmd ) { case SceneControllerConfigurationCommand . Get : return this . isSinglecast ( ) ; case SceneControllerConfigurationCommand . Set : return true ; } return super . supportsCommand ( cmd ) ; } protected [ SET_VALUE ] : SetValueImplementation = async ( { property , propertyKey } , value , options ) : Promise < void > => { if ( propertyKey == undefined ) { throwMissingPropertyKey ( this . ccId , property ) ; } else if ( typeof propertyKey !== "number" ) { throwUnsupportedPropertyKey ( this . ccId , property , propertyKey ) ; } if ( property === "sceneId" ) { if ( typeof value !== "number" ) { throwWrongValueType ( this . ccId , property , "number" , typeof value ) ; } if ( value === 0 ) { await this . disable ( propertyKey ) ; } else { const dimmingDuration = Duration . from ( options ?. transitionDuration ) ?? this . endpoint . getNodeUnsafe ( ) ! . getValue < Duration > ( getDimmingDurationValueID ( this . endpoint . index , propertyKey ) ) ?? new Duration ( 0 , "default" ) ; await this . set ( propertyKey , value , dimmingDuration ) ; } } else if ( property === "dimmingDuration" ) { if ( typeof value !== "string" && ! ( value instanceof Duration ) ) { throwWrongValueType ( this . ccId , property , "duration" , typeof value ) ; } const dimmingDuration = Duration . from ( value ) ; if ( dimmingDuration == undefined ) { throw new ZWaveError ( `${ getCCName ( this . ccId ) }: "${ property }" could not be set. ${ JSON . stringify ( value ) } is not a valid duration.` , ZWaveErrorCodes . Argument_Invalid ) ; } const node = this . endpoint . getNodeUnsafe ( ) ! ; const sceneId = node . getValue < number > ( getSceneIdValueID ( this . endpoint . index , propertyKey ) ) ; if ( sceneId == undefined || sceneId === 0 ) { const dimmingDurationValueId = getDimmingDurationValueID ( this . endpoint . index , propertyKey ) ; const valueDB = node . valueDB ; valueDB . setValue ( dimmingDurationValueId , dimmingDuration ) ; return ; } await this . set ( propertyKey , sceneId , dimmingDuration ) ; } else { throwUnsupportedProperty ( this . ccId , property ) ; } } ; protected [ POLL_VALUE ] : PollValueImplementation = async ( { property , propertyKey , } ) : Promise < unknown > => { switch ( property ) { case "sceneId" : case "dimmingDuration" : { if ( propertyKey == undefined ) { throwMissingPropertyKey ( this . ccId , property ) ; } else if ( typeof propertyKey !== "number" ) { throwUnsupportedPropertyKey ( this . ccId , property , propertyKey ) ; } return ( await this . get ( propertyKey ) ) ?. [ property ] ; } default : throwUnsupportedProperty ( this . ccId , property ) ; } } ; public async disable ( groupId ) { this . assertSupportsCommand ( SceneControllerConfigurationCommand , SceneControllerConfigurationCommand . Set ) ; return this . set ( groupId , 0 , new Duration ( 0 , "seconds" ) ) ; } public async set ( groupId , sceneId , dimmingDuration ? ) { this . assertSupportsCommand ( SceneControllerConfigurationCommand , SceneControllerConfigurationCommand . Set ) ; const cc = new SceneControllerConfigurationCCSet ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index , groupId , sceneId , dimmingDuration } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } public async getLastActivated ( ) { this . assertSupportsCommand ( SceneControllerConfigurationCommand , SceneControllerConfigurationCommand . Get ) ; const cc = new SceneControllerConfigurationCCGet ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index , groupId : 0 } ) ; const response = await this . driver . sendCommand < SceneControllerConfigurationCCReport > ( cc , this . commandOptions ) ; if ( response ) { return pick ( response , [ "groupId" , "sceneId" , "dimmingDuration" ] ) ; } } public async get ( groupId ) { this . assertSupportsCommand ( SceneControllerConfigurationCommand , SceneControllerConfigurationCommand . Get ) ; if ( groupId === 0 ) { throw new ZWaveError ( `Invalid group ID 0. To get the last activated group / scene, use getLastActivated() instead.` , ZWaveErrorCodes . Argument_Invalid ) ; } const cc = new SceneControllerConfigurationCCGet ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index , groupId } ) ; const response = await this . driver . sendCommand < SceneControllerConfigurationCCReport > ( cc , this . commandOptions ) ; if ( response ) { return pick ( response , [ "sceneId" , "dimmingDuration" ] ) ; } } } @ commandClass ( CommandClasses [ "Scene Controller Configuration" ] ) @ implementedVersion ( 1 ) export class SceneControllerConfigurationCC extends CommandClass { declare ccCommand : SceneControllerConfigurationCommand ; public determineRequiredCCInterviews ( ) { return [ ... super . determineRequiredCCInterviews ( ) , CommandClasses . Association , ] ; } public async interview ( complete = true ) { const node = this . getNode ( ) ! ; this . driver . controllerLog . logNode ( node . id , { message : `${ this . constructor . name }: doing a ${ complete ? "complete" : "partial" } interview...` , direction : "none" } ) ; const groupCount = this . getGroupCountCached ( ) ; if ( groupCount === 0 ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `skipping Scene Controller Configuration interview because Association group count is unknown` , direction : "none" , level : "warn" } ) ; return ; } for ( let groupId = 1 ; groupId <= groupCount ; groupId ++ ) { setSceneConfigurationMetadata . call ( this , groupId ) ; } this . interviewComplete = true ; } public async refreshValues ( ) { const node = this . getNode ( ) ! ; const endpoint = this . getEndpoint ( ) ! ; const api = endpoint . commandClasses [ "Scene Controller Configuration" ] . withOptions ( { priority : MessagePriority . NodeQuery } ) ; const groupCount = this . getGroupCountCached ( ) ; this . driver . controllerLog . logNode ( node . id , { message : "querying all scene controller configurations..." , direction : "outbound" } ) ; for ( let groupId = 1 ; groupId <= groupCount ; groupId ++ ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `querying scene configuration for group #${ groupId }...` , direction : "outbound" } ) ; const group = await api . get ( groupId ) ; if ( group != undefined ) { const logMessage = `received scene configuration for group #${ groupId }:
scene ID:         ${ group . sceneId }
dimming duration: ${ group . dimmingDuration . toString ( ) }` ; this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : logMessage , direction : "inbound" } ) ; } } } /**
     * Returns the number of association groups reported by the node.
     * This only works AFTER the node has been interviewed by this CC
     * or the AssociationCC.
     */ protected getGroupCountCached ( ) { return ( this . getNodeUnsafe ( ) ?. deviceConfig ?. compat ?. forceSceneControllerGroupCount ?? this . getEndpoint ( ) ?. createCCInstanceUnsafe < AssociationCC > ( CommandClasses . Association ) ?. getGroupCountCached ( ) ?? 0 ) ; } } interface SceneControllerConfigurationCCSetOptions extends CCCommandOptions { groupId : number ; sceneId : number ; dimmingDuration ? : Duration ; } @ CCCommand ( SceneControllerConfigurationCommand . Set ) export class SceneControllerConfigurationCCSet extends SceneControllerConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { const groupCount = this . getGroupCountCached ( ) ; this . groupId = options . groupId ; this . sceneId = options . sceneId ; this . dimmingDuration = options . dimmingDuration ?? new Duration ( 0 , "default" ) ; if ( this . groupId < 1 || this . groupId > groupCount ) { throw new ZWaveError ( `${ this . constructor . name }: The group ID must be between 1 and the number of supported groups ${ groupCount }.` , ZWaveErrorCodes . Argument_Invalid ) ; } } } public groupId : number ; public sceneId : number ; public dimmingDuration : Duration ; public serialize ( ) { this . payload = Buffer . from ( [ this . groupId , this . sceneId , this . dimmingDuration . serializeSet ( ) , ] ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "group id" : this . groupId , "scene id" : this . sceneId , "dimming duration" : this . dimmingDuration . toString ( ) } } ; } } @ CCCommand ( SceneControllerConfigurationCommand . Report ) export class SceneControllerConfigurationCCReport extends SceneControllerConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 3 ) ; this . groupId = this . payload [ 0 ] ; this . sceneId = this . payload [ 1 ] ; this . dimmingDuration = Duration . parseReport ( this . payload [ 2 ] ) ?? new Duration ( 0 , "unknown" ) ; this . persistValues ( ) ; } public readonly groupId : number ; public readonly sceneId : number ; public readonly dimmingDuration : Duration ; public persistValues ( ) { if ( this . groupId === 0 ) return false ; return persistSceneConfig . call ( this , this . groupId , this . sceneId , this . dimmingDuration ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "group id" : this . groupId , "scene id" : this . sceneId , "dimming duration" : this . dimmingDuration . toString ( ) } } ; } } function testResponseForSceneControllerConfigurationGet ( sent , received ) { return sent . groupId === 0 || received . groupId === sent . groupId ; } interface SceneControllerConfigurationCCGetOptions extends CCCommandOptions { groupId : number ; } @ CCCommand ( SceneControllerConfigurationCommand . Get ) @ expectedCCResponse ( SceneControllerConfigurationCCReport , testResponseForSceneControllerConfigurationGet ) export class SceneControllerConfigurationCCGet extends SceneControllerConfigurationCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { const groupCount = this . getGroupCountCached ( ) ; if ( options . groupId < 0 || options . groupId > groupCount ) { throw new ZWaveError ( `${ this . constructor . name }: The group ID must be between 0 and the number of supported groups ${ groupCount }.` , ZWaveErrorCodes . Argument_Invalid ) ; } this . groupId = options . groupId ; } } public groupId : number ; public serialize ( ) { this . payload = Buffer . from ( [ this . groupId ] ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "group id" : this . groupId } } ; } }