import type { Maybe , MessageOrCCLogEntry , ValueID } from "@zwave-js/core" ; import { CommandClasses , validatePayload , 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 type { ZWaveNode } from "../node/Node" ; import { NodeStatus } from "../node/Types" ; import { CCAPI , PollValueImplementation , POLL_VALUE , SetValueImplementation , SET_VALUE , throwUnsupportedProperty , throwWrongValueType , } from "./API" ; import { API , CCCommand , CCCommandOptions , ccValue , ccValueMetadata , CommandClass , commandClass , CommandClassDeserializationOptions , expectedCCResponse , gotDeserializationOptions , implementedVersion , } from "./CommandClass" ; export function getWakeUpIntervalValueId ( ) { return { commandClass : CommandClasses [ "Wake Up" ] , property : "wakeUpInterval" } ; } export function getWakeUpOnDemandSupportedValueId ( ) { return { commandClass : CommandClasses [ "Wake Up" ] , property : "wakeUpOnDemandSupported" } ; } export enum WakeUpCommand { IntervalSet = 0x04 , IntervalGet = 0x05 , IntervalReport = 0x06 , WakeUpNotification = 0x07 , NoMoreInformation = 0x08 , IntervalCapabilitiesGet = 0x09 , IntervalCapabilitiesReport = 0x0a } @ API ( CommandClasses [ "Wake Up" ] ) export class WakeUpCCAPI extends CCAPI { public supportsCommand ( cmd ) { switch ( cmd ) { case WakeUpCommand . IntervalGet : return this . isSinglecast ( ) ; case WakeUpCommand . IntervalSet : case WakeUpCommand . NoMoreInformation : return true ; case WakeUpCommand . IntervalCapabilitiesGet : return this . version >= 2 && this . isSinglecast ( ) ; } return super . supportsCommand ( cmd ) ; } protected [ SET_VALUE ] : SetValueImplementation = async ( { property } , value ) : Promise < void > => { if ( property !== "wakeUpInterval" ) { throwUnsupportedProperty ( this . ccId , property ) ; } if ( typeof value !== "number" ) { throwWrongValueType ( this . ccId , property , "number" , typeof value ) ; } await this . setInterval ( value , this . driver . controller . ownNodeId ?? 1 ) ; if ( this . isSinglecast ( ) ) { this . schedulePoll ( { property } , { transition : "fast" } ) ; } } ; protected [ POLL_VALUE ] : PollValueImplementation = async ( { property , } ) : Promise < unknown > => { switch ( property ) { case "wakeUpInterval" : return ( await this . getInterval ( ) ) ?. [ property ] ; default : throwUnsupportedProperty ( this . ccId , property ) ; } } ; public async getInterval ( ) { this . assertSupportsCommand ( WakeUpCommand , WakeUpCommand . IntervalGet ) ; const cc = new WakeUpCCIntervalGet ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index } ) ; const response = await this . driver . sendCommand < WakeUpCCIntervalReport > ( cc , this . commandOptions ) ; if ( response ) { return pick ( response , [ "wakeUpInterval" , "controllerNodeId" ] ) ; } } public async getIntervalCapabilities ( ) { this . assertSupportsCommand ( WakeUpCommand , WakeUpCommand . IntervalCapabilitiesGet ) ; const cc = new WakeUpCCIntervalCapabilitiesGet ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index } ) ; const response = await this . driver . sendCommand < WakeUpCCIntervalCapabilitiesReport > ( cc , this . commandOptions ) ; if ( response ) { return pick ( response , [ "defaultWakeUpInterval" , "minWakeUpInterval" , "maxWakeUpInterval" , "wakeUpIntervalSteps" , "wakeUpOnDemandSupported" , ] ) ; } } public async setInterval ( wakeUpInterval , controllerNodeId ) { this . assertSupportsCommand ( WakeUpCommand , WakeUpCommand . IntervalSet ) ; const cc = new WakeUpCCIntervalSet ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index , wakeUpInterval , controllerNodeId } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } public async sendNoMoreInformation ( ) { this . assertSupportsCommand ( WakeUpCommand , WakeUpCommand . NoMoreInformation ) ; const cc = new WakeUpCCNoMoreInformation ( this . driver , { nodeId : this . endpoint . nodeId , endpoint : this . endpoint . index } ) ; await this . driver . sendCommand ( cc , { ... this . commandOptions , priority : MessagePriority . WakeUp , maxSendAttempts : 1 } ) ; } } @ commandClass ( CommandClasses [ "Wake Up" ] ) @ implementedVersion ( 3 ) export class WakeUpCC extends CommandClass { declare ccCommand : WakeUpCommand ; public isAwake ( ) { return WakeUpCC . isAwake ( this . getNode ( ) ! ) ; } public static isAwake ( node ) { switch ( node . status ) { case NodeStatus . Asleep : case NodeStatus . Dead : return false ; case NodeStatus . Unknown : case NodeStatus . Alive : case NodeStatus . Awake : return true ; } } public async interview ( ) { const node = this . getNode ( ) ! ; const endpoint = this . getEndpoint ( ) ! ; const api = endpoint . commandClasses [ "Wake Up" ] . withOptions ( { priority : MessagePriority . NodeQuery } ) ; this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : `Interviewing ${ this . ccName }...` , direction : "none" } ) ; let hadCriticalTimeout = false ; if ( node . isControllerNode ( ) ) { this . driver . controllerLog . logNode ( node . id , `skipping wakeup configuration for the controller` ) ; } else if ( node . isFrequentListening ) { this . driver . controllerLog . logNode ( node . id , `skipping wakeup configuration for frequent listening device` ) ; } else { if ( this . version >= 2 ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : "retrieving wakeup capabilities from the device..." , direction : "outbound" } ) ; const wakeupCaps = await api . getIntervalCapabilities ( ) ; if ( wakeupCaps ) { const logMessage = `received wakeup capabilities:
default wakeup interval: ${ wakeupCaps . defaultWakeUpInterval } seconds
minimum wakeup interval: ${ wakeupCaps . minWakeUpInterval } seconds
maximum wakeup interval: ${ wakeupCaps . maxWakeUpInterval } seconds
wakeup interval steps:   ${ wakeupCaps . wakeUpIntervalSteps } seconds
wakeup on demand supported: ${ wakeupCaps . wakeUpOnDemandSupported }` ; this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : logMessage , direction : "inbound" } ) ; } else { hadCriticalTimeout = true ; } } this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : "retrieving wakeup interval from the device..." , direction : "outbound" } ) ; const wakeupResp = await api . getInterval ( ) ; if ( wakeupResp ) { const logMessage = `received wakeup configuration:
wakeup interval: ${ wakeupResp . wakeUpInterval } seconds
controller node: ${ wakeupResp . controllerNodeId }` ; this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : logMessage , direction : "inbound" } ) ; const ownNodeId = this . driver . controller . ownNodeId ! ; if ( wakeupResp . controllerNodeId !== ownNodeId ) { this . driver . controllerLog . logNode ( node . id , { endpoint : this . endpointIndex , message : "configuring wakeup destination node" , direction : "outbound" } ) ; await api . setInterval ( wakeupResp . wakeUpInterval , ownNodeId ) ; this . getValueDB ( ) . setValue ( getWakeUpIntervalValueId ( ) , ownNodeId ) ; this . driver . controllerLog . logNode ( node . id , "wakeup destination node changed!" ) ; } } else { hadCriticalTimeout = true ; } } if ( ! hadCriticalTimeout ) this . interviewComplete = true ; } } interface WakeUpCCIntervalSetOptions extends CCCommandOptions { wakeUpInterval : number ; controllerNodeId : number ; } @ CCCommand ( WakeUpCommand . IntervalSet ) export class WakeUpCCIntervalSet extends WakeUpCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { throw new ZWaveError ( `${ this . constructor . name }: deserialization not implemented` , ZWaveErrorCodes . Deserialization_NotImplemented ) ; } else { this . wakeUpInterval = options . wakeUpInterval ; this . controllerNodeId = options . controllerNodeId ; } } public wakeUpInterval : number ; public controllerNodeId : number ; public serialize ( ) { this . payload = Buffer . from ( [ 0 , 0 , 0 , this . controllerNodeId , ] ) ; this . payload . writeUIntBE ( this . wakeUpInterval , 0 , 3 ) ; return super . serialize ( ) ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "wake-up interval" : `${ this . wakeUpInterval } seconds` , "controller node id" : this . controllerNodeId } } ; } } @ CCCommand ( WakeUpCommand . IntervalReport ) export class WakeUpCCIntervalReport extends WakeUpCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 4 ) ; this . _wakeUpInterval = this . payload . readUIntBE ( 0 , 3 ) ; this . _controllerNodeId = this . payload [ 3 ] ; this . persistValues ( ) ; } private _wakeUpInterval : number ; @ ccValue ( ) @ ccValueMetadata ( { ... ValueMetadata . UInt24 , label : "Wake Up interval" } ) public get wakeUpInterval ( ) : number { return this . _wakeUpInterval ; } private _controllerNodeId : number ; @ ccValue ( ) @ ccValueMetadata ( { ... ValueMetadata . ReadOnly , label : "Node ID of the controller" } ) public get controllerNodeId ( ) : number { return this . _controllerNodeId ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "wake-up interval" : `${ this . wakeUpInterval } seconds` , "controller node id" : this . _controllerNodeId } } ; } } @ CCCommand ( WakeUpCommand . IntervalGet ) @ expectedCCResponse ( WakeUpCCIntervalReport ) export class WakeUpCCIntervalGet extends WakeUpCC { } @ CCCommand ( WakeUpCommand . WakeUpNotification ) export class WakeUpCCWakeUpNotification extends WakeUpCC { } @ CCCommand ( WakeUpCommand . NoMoreInformation ) export class WakeUpCCNoMoreInformation extends WakeUpCC { } @ CCCommand ( WakeUpCommand . IntervalCapabilitiesReport ) export class WakeUpCCIntervalCapabilitiesReport extends WakeUpCC { public constructor ( driver , options ) { super ( driver , options ) ; validatePayload ( this . payload . length >= 12 ) ; this . _minWakeUpInterval = this . payload . readUIntBE ( 0 , 3 ) ; this . _maxWakeUpInterval = this . payload . readUIntBE ( 3 , 3 ) ; this . _defaultWakeUpInterval = this . payload . readUIntBE ( 6 , 3 ) ; this . _wakeUpIntervalSteps = this . payload . readUIntBE ( 9 , 3 ) ; if ( this . version >= 3 && this . payload . length >= 13 ) { this . _wakeUpOnDemandSupported = ! ! ( this . payload [ 12 ] & 0b1 ) ; } else { this . _wakeUpOnDemandSupported = false ; } this . getValueDB ( ) . setMetadata ( { commandClass : this . ccId , endpoint : this . endpointIndex , property : "wakeUpInterval" } , { ... ValueMetadata . WriteOnlyUInt24 , min : this . _minWakeUpInterval , max : this . _maxWakeUpInterval , steps : this . _wakeUpIntervalSteps , default : this . _defaultWakeUpInterval } ) ; this . persistValues ( ) ; } private _minWakeUpInterval : number ; public get minWakeUpInterval ( ) : number { return this . _minWakeUpInterval ; } private _maxWakeUpInterval : number ; public get maxWakeUpInterval ( ) : number { return this . _maxWakeUpInterval ; } private _defaultWakeUpInterval : number ; public get defaultWakeUpInterval ( ) : number { return this . _defaultWakeUpInterval ; } private _wakeUpIntervalSteps : number ; public get wakeUpIntervalSteps ( ) : number { return this . _wakeUpIntervalSteps ; } private _wakeUpOnDemandSupported : boolean ; @ ccValue ( { minVersion : 3 } ) @ ccValueMetadata ( { ... ValueMetadata . ReadOnlyBoolean , label : "Wake Up On Demand supported" } ) public get wakeUpOnDemandSupported ( ) : boolean { return this . _wakeUpOnDemandSupported ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "default interval" : `${ this . _defaultWakeUpInterval } seconds` , "minimum interval" : `${ this . _minWakeUpInterval } seconds` , "maximum interval" : `${ this . _maxWakeUpInterval } seconds` , "interval steps" : `${ this . _wakeUpIntervalSteps } seconds` , "wake up on demand supported" : `${ this . _wakeUpOnDemandSupported }` } } ; } } @ CCCommand ( WakeUpCommand . IntervalCapabilitiesGet ) @ expectedCCResponse ( WakeUpCCIntervalCapabilitiesReport ) export class WakeUpCCIntervalCapabilitiesGet extends WakeUpCC { }