import { CommandClasses , Duration , isTransmissionError , Maybe , MessageOrCCLogEntry , MessageRecord , validatePayload , ZWaveError , ZWaveErrorCodes , } from "@zwave-js/core" ; import { getEnumMemberName } from "@zwave-js/shared" ; import type { Driver } from "../driver/Driver" ; import { MessagePriority } from "../message/Constants" ; import { PhysicalCCAPI } from "./API" ; import { API , CCCommand , CCCommandOptions , CommandClass , commandClass , CommandClassDeserializationOptions , expectedCCResponse , gotDeserializationOptions , implementedVersion , } from "./CommandClass" ; export enum SupervisionCommand { Get = 0x01 , Report = 0x02 } publicAPI export enum SupervisionStatus { NoSupport = 0x00 , Working = 0x01 , Fail = 0x02 , Success = 0xff } publicAPI export interface SupervisionResult { status : SupervisionStatus ; remainingDuration ? : Duration ; } let sessionId = 0 ; /** Returns the next session ID to be used for supervision */ export function getNextSessionId ( ) { sessionId = ( sessionId + 1 ) & 0b111111 ; if ( sessionId === 0 ) sessionId ++ ; return sessionId ; } @ API ( CommandClasses . Supervision ) export class SupervisionCCAPI extends PhysicalCCAPI { public supportsCommand ( cmd ) { switch ( cmd ) { case SupervisionCommand . Get : case SupervisionCommand . Report : return true ; } return super . supportsCommand ( cmd ) ; } public async sendEncapsulated ( encapsulated , requestStatusUpdates = true ) { this . assertSupportsCommand ( SupervisionCommand , SupervisionCommand . Get ) ; const cc = new SupervisionCCGet ( this . driver , { nodeId : this . endpoint . nodeId , requestStatusUpdates , encapsulated } ) ; await this . driver . sendCommand ( cc , this . commandOptions ) ; } public async sendReport ( options ) { const requestWakeUpOnDemand = options . requestWakeUpOnDemand ?? this . endpoint . getNodeUnsafe ( ) ?. shouldRequestWakeUpOnDemand ; const { secure = false , ... cmdOptions } = options ; const cc = new SupervisionCCReport ( this . driver , { nodeId : this . endpoint . nodeId , ... cmdOptions , requestWakeUpOnDemand } ) ; cc . secure = secure ; try { await this . driver . sendCommand ( cc , { ... this . commandOptions , priority : MessagePriority . Supervision , changeNodeStatusOnMissingACK : false , maxSendAttempts : 1 } ) ; } catch ( e ) { if ( isTransmissionError ( e ) ) { return ; } else { throw e ; } } } } @ commandClass ( CommandClasses . Supervision ) @ implementedVersion ( 2 ) export class SupervisionCC extends CommandClass { declare ccCommand : SupervisionCommand ; declare nodeId : number ; /** Tests if a command should be supervised and thus requires encapsulation */ public static requiresEncapsulation ( cc ) { return cc . supervised && ! ( cc instanceof SupervisionCCGet ) ; } /** Encapsulates a command that targets a specific endpoint */ public static encapsulate ( driver , cc , requestStatusUpdates = true ) { if ( ! cc . isSinglecast ( ) ) { throw new ZWaveError ( `Supervision is only possible for singlecast commands!` , ZWaveErrorCodes . Argument_Invalid ) ; } return new SupervisionCCGet ( driver , { nodeId : cc . nodeId , endpoint : cc . endpointIndex , encapsulated : cc , requestStatusUpdates } ) ; } /**
     * Given a CC instance, this returns the Supervision session ID which is used for this command.
     * Returns `undefined` when there is no session ID or the command was sent as multicast.
     */ public static getSessionId ( command ) { if ( command . isEncapsulatedWith ( CommandClasses . Supervision , SupervisionCommand . Get ) ) { const supervisionEncapsulation = command . getEncapsulatingCC ( CommandClasses . Supervision , SupervisionCommand . Get ) as SupervisionCCGet ; if ( ! supervisionEncapsulation . isMulticast ( ) ) { return supervisionEncapsulation . sessionId ; } } } } export type SupervisionCCReportOptions = { moreUpdatesFollow : boolean ; requestWakeUpOnDemand ? : boolean ; sessionId : number ; } & ( { status : SupervisionStatus . Working ; duration : Duration ; } | { status : SupervisionStatus . NoSupport | SupervisionStatus . Fail | SupervisionStatus . Success ; } ) ; @ CCCommand ( SupervisionCommand . Report ) export class SupervisionCCReport extends SupervisionCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { validatePayload ( this . payload . length >= 3 ) ; this . moreUpdatesFollow = ! ! ( this . payload [ 0 ] & 128 ) ; this . requestWakeUpOnDemand = ! ! ( this . payload [ 0 ] & 64 ) ; this . sessionId = this . payload [ 0 ] & 0b111111 ; this . status = this . payload [ 1 ] ; this . duration = Duration . parseReport ( this . payload [ 2 ] ) ; } else { this . moreUpdatesFollow = options . moreUpdatesFollow ; this . requestWakeUpOnDemand = ! ! options . requestWakeUpOnDemand ; this . sessionId = options . sessionId ; this . status = options . status ; if ( options . status === SupervisionStatus . Working ) { this . duration = options . duration ; } else { this . duration = new Duration ( 0 , "seconds" ) ; } } } public readonly moreUpdatesFollow : boolean ; public readonly requestWakeUpOnDemand : boolean ; public readonly sessionId : number ; public readonly status : SupervisionStatus ; public readonly duration : Duration | undefined ; public serialize ( ) { this . payload = Buffer . concat ( [ Buffer . from ( [ ( this . moreUpdatesFollow ? 128 : 0 ) | ( this . requestWakeUpOnDemand ? 64 : 0 ) | ( this . sessionId & 0b111111 ) , this . status , ] ) , ] ) ; if ( this . duration ) { this . payload = Buffer . concat ( [ this . payload , Buffer . from ( [ this . duration . serializeReport ( ) ] ) , ] ) ; } return super . serialize ( ) ; } public toLogEntry ( ) { const message = { "session id" : this . sessionId , "more updates follow" : this . moreUpdatesFollow , status : getEnumMemberName ( SupervisionStatus , this . status ) } ; if ( this . duration ) { message . duration = this . duration . toString ( ) ; } return { ... super . toLogEntry ( ) , message } ; } } interface SupervisionCCGetOptions extends CCCommandOptions { requestStatusUpdates : boolean ; encapsulated : CommandClass ; } function testResponseForSupervisionCCGet ( sent , received ) { return received . sessionId === sent . sessionId ; } @ CCCommand ( SupervisionCommand . Get ) @ expectedCCResponse ( SupervisionCCReport , testResponseForSupervisionCCGet ) export class SupervisionCCGet extends SupervisionCC { public constructor ( driver , options ) { super ( driver , options ) ; if ( gotDeserializationOptions ( options ) ) { validatePayload ( this . payload . length >= 3 ) ; this . requestStatusUpdates = ! ! ( this . payload [ 0 ] & 128 ) ; this . sessionId = this . payload [ 0 ] & 0b111111 ; this . encapsulated = CommandClass . from ( this . driver , { data : this . payload . slice ( 2 ) , fromEncapsulation : true , encapCC : this } ) ; } else { this . sessionId = getNextSessionId ( ) ; this . requestStatusUpdates = options . requestStatusUpdates ; this . encapsulated = options . encapsulated ; options . encapsulated . encapsulatingCC = this as any ; if ( this . encapsulated . secure ) this . secure = true ; } } public requestStatusUpdates : boolean ; public sessionId : number ; public encapsulated : CommandClass ; public serialize ( ) { const encapCC = this . encapsulated . serialize ( ) ; this . payload = Buffer . concat ( [ Buffer . from ( [ ( this . requestStatusUpdates ? 128 : 0 ) | ( this . sessionId & 0b111111 ) , encapCC . length , ] ) , encapCC , ] ) ; return super . serialize ( ) ; } protected computeEncapsulationOverhead ( ) { return super . computeEncapsulationOverhead ( ) + 2 ; } public toLogEntry ( ) { return { ... super . toLogEntry ( ) , message : { "session id" : this . sessionId , "request updates" : this . requestStatusUpdates } } ; } }