import { SPANState , ZWaveError , ZWaveErrorCodes } from "@zwave-js/core" ; import { createDeferredPromise , DeferredPromise , } from "alcalzone-shared/deferred-promise" ; import { CommandClass , Security2CCMessageEncapsulation , Security2CCNonceGet , Security2CCNonceReport , } from "../commandclass" ; import { isCommandClassContainer } from "../commandclass/ICommandClassContainer" ; import { SecurityCCCommandEncapsulation , SecurityCCNonceGet , SecurityCCNonceReport , } from "../commandclass/SecurityCC" ; import { isSendData , isTransmitReport } from "../controller/SendDataShared" ; import type { Message } from "../message/Message" ; import type { Driver , SendCommandOptions } from "./Driver" ; import { sendDataErrorToZWaveError } from "./StateMachineShared" ; import type { MessageGenerator } from "./Transaction" ; export type MessageGeneratorImplementation = ( /** A reference to the driver */ driver : Driver , /** The "primary" message */ message : Message , /**
 * A hook to get notified about each sent message and the result of the Serial API call
 * without waiting for the message generator to finish completely.
 */ onMessageSent : ( msg : Message , result : Message | undefined ) => void ) => AsyncGenerator < Message , Message , Message > ; export async function waitForNodeUpdate < T extends Message > ( driver , msg ) { try { return await driver . waitForMessage < T > ( ( received ) => { return msg . isExpectedNodeUpdate ( received ) ; } , driver . options . timeouts . report ) ; } catch ( e ) { throw new ZWaveError ( `Timed out while waiting for a response from the node` , ZWaveErrorCodes . Controller_NodeTimeout ) ; } } /** A simple message generator that simply sends a message, waits for the ACK (and the response if one is expected) */ export const simpleMessageGenerator = async function * ( driver , msg , onMessageSent ) { let result ; try { result = yield msg ; onMessageSent ( msg , result ) ; } catch ( e ) { throw e ; } if ( isTransmitReport ( result ) && ! result . isOK ( ) ) { throw result ; } if ( msg . expectsNodeUpdate ( ) ) { return waitForNodeUpdate ( driver , msg ) ; } return result ; } ; /** A simple (internal) generator that simply sends a command, and optionally returns the response command */ async function * sendCommandGenerator < TResponse extends CommandClass = CommandClass > ( driver , command , onMessageSent , options ) { const msg = driver . createSendDataMessage ( command , options ) ; const resp = yield * simpleMessageGenerator ( driver , msg , onMessageSent ) ; if ( resp && isCommandClassContainer ( resp ) ) { driver . unwrapCommands ( resp ) ; return resp . command as TResponse ; } } /** A message generator for security encapsulated messages (S0) */ export const secureMessageGeneratorS0 = async function * ( driver , msg , onMessageSent ) { if ( ! isSendData ( msg ) ) { throw new ZWaveError ( "Cannot use the S0 message generator for a command that's not a SendData message!" , ZWaveErrorCodes . Argument_Invalid ) ; } else if ( typeof msg . command . nodeId !== "number" ) { throw new ZWaveError ( "Cannot use the S0 message generator for multicast commands!" , ZWaveErrorCodes . Argument_Invalid ) ; } else if ( ! ( msg . command instanceof SecurityCCCommandEncapsulation ) ) { throw new ZWaveError ( "The S0 message generator can only be used for Security S0 command encapsulation!" , ZWaveErrorCodes . Argument_Invalid ) ; } const secMan = driver . securityManager ! ; const nodeId = msg . command . nodeId ; let nonce = secMan . getFreeNonce ( nodeId ) ; if ( ! nonce ) { const cc = new SecurityCCNonceGet ( driver , { nodeId : nodeId , endpoint : msg . command . endpointIndex } ) ; const nonceResp = yield * sendCommandGenerator < SecurityCCNonceReport > ( driver , cc , onMessageSent , { maxSendAttempts : 1 } ) ; if ( ! nonceResp ) { throw new ZWaveError ( "No nonce received from the node, cannot send secure command!" , ZWaveErrorCodes . SecurityCC_NoNonce ) ; } nonce = nonceResp . nonce ; } msg . command . nonce = nonce ; return yield * simpleMessageGenerator ( driver , msg , onMessageSent ) ; } ; /** A message generator for security encapsulated messages (S2) */ export const secureMessageGeneratorS2 = async function * ( driver , msg , onMessageSent ) { if ( ! isSendData ( msg ) ) { throw new ZWaveError ( "Cannot use the S2 message generator for a command that's not a SendData message!" , ZWaveErrorCodes . Argument_Invalid ) ; } else if ( typeof msg . command . nodeId !== "number" ) { throw new ZWaveError ( "Cannot use the S2 message generator for multicast commands!" , ZWaveErrorCodes . Argument_Invalid ) ; } else if ( ! ( msg . command instanceof Security2CCMessageEncapsulation ) ) { throw new ZWaveError ( "The S2 message generator can only be used for Security S2 command encapsulation!" , ZWaveErrorCodes . Argument_Invalid ) ; } const secMan = driver . securityManager2 ! ; const nodeId = msg . command . nodeId ; const spanState = secMan . getSPANState ( nodeId ) ; if ( spanState . type === SPANState . None || spanState . type === SPANState . LocalEI ) { const cc = new Security2CCNonceGet ( driver , { nodeId : nodeId , endpoint : msg . command . endpointIndex } ) ; const nonceResp = yield * sendCommandGenerator < Security2CCNonceReport > ( driver , cc , onMessageSent , { maxSendAttempts : 1 } ) ; if ( ! nonceResp ) { throw new ZWaveError ( "No nonce received from the node, cannot send secure command!" , ZWaveErrorCodes . Security2CC_NoSPAN ) ; } } let response = yield * simpleMessageGenerator ( driver , msg , onMessageSent ) ; if ( isCommandClassContainer ( response ) && response . command instanceof Security2CCNonceReport ) { const command = response . command ; if ( command . SOS && command . receiverEI ) { secMan . storeRemoteEI ( nodeId , command . receiverEI ) ; } driver . controllerLog . logNode ( nodeId , { message : `failed to decode the message, retrying with SPAN extension...` , direction : "none" } ) ; msg . callbackId = undefined ; msg . command . unsetSequenceNumber ( ) ; response = yield * simpleMessageGenerator ( driver , msg , onMessageSent ) ; if ( isCommandClassContainer ( response ) && response . command instanceof Security2CCNonceReport ) { driver . controllerLog . logNode ( nodeId , { message : `failed to decode the message after re-transmission with SPAN extension, dropping the message.` , direction : "none" , level : "warn" } ) ; throw new ZWaveError ( "The node failed to decode the message." , ZWaveErrorCodes . Security2CC_CannotDecode ) ; } } return response ; } ; export function createMessageGenerator < TResponse extends Message = Message > ( driver , msg , onMessageSent ) { const resultPromise = createDeferredPromise < TResponse > ( ) ; const generator = { parent : undefined as any , current : undefined , self : undefined , start : ( ) => { const resetGenerator = ( ) => { generator . current = undefined ; generator . self = undefined ; } ; async function * gen ( ) { let implementation = simpleMessageGenerator ; if ( isSendData ( msg ) ) { if ( msg . command instanceof Security2CCMessageEncapsulation ) { implementation = secureMessageGeneratorS2 ; } else if ( msg . command instanceof SecurityCCCommandEncapsulation ) { implementation = secureMessageGeneratorS0 ; } } const gen = implementation ( driver , msg , onMessageSent ) ; let sendResult ; let result ; while ( true ) { try { const { value , done } = await gen . next ( sendResult ! ) ; if ( done ) { result = value ; break ; } generator . current = value ; sendResult = yield generator . current ; } catch ( e ) { if ( e instanceof Error ) { resultPromise . reject ( e ) ; } else if ( isTransmitReport ( e ) && ! e . isOK ( ) ) { if ( driver . handleMissingNodeACK ( generator . parent as any ) ) { resetGenerator ( ) ; return ; } else { resultPromise . reject ( sendDataErrorToZWaveError ( "callback NOK" , generator . parent , e ) ) ; } } else { resultPromise . resolve ( e as TResponse ) ; } break ; } } resultPromise . resolve ( result as TResponse ) ; resetGenerator ( ) ; return ; } generator . self = gen ( ) ; return generator . self ; } } ; return { resultPromise , generator } ; }