import { GLOBAL_CONTEXT } from '../config' ; import { Device , Property , Method } from '../interfaces/device' ; import { SimulationStore } from '../store/simulationStore' ; import { Mqtt as M1 , clientFromConnectionString } from 'azure-iot-device-mqtt' ; import { Client , ModuleClient } from 'azure-iot-device' ; import { Message , SharedAccessSignature } from 'azure-iot-device' ; import { ConnectionString , SharedAccessSignature as CommonSaS , } from 'azure-iot-common' ; import { Mqtt as Protocol } from 'azure-iot-device-mqtt' ; import { Mqtt as MqttDps } from 'azure-iot-provisioning-device-mqtt' ; import { SymmetricKeySecurityClient } from 'azure-iot-security-symmetric-key' ; import { ProvisioningDeviceClient } from 'azure-iot-provisioning-device' ; import { ValueByIdPayload , DesiredPayload } from '../interfaces/payload' ; import * as Utils from './utils' ; import * as request from 'request' ; import * as rw from 'random-words' ; import * as Crypto from 'crypto' ; import * as _ from 'lodash' ; import { PlugIn } from '../interfaces/plugin' ; import { Registry } from 'azure-iothub' ; import { over } from 'lodash' ; export const LOGGING_TAGS = { CTRL : { HUB : 'HUB' , DPS : 'DPS' , DEV : 'DEV' , DGT : 'DGT' , EDG : 'EDG' , MOD : 'MOD' , LEA : 'LEA' } , DATA : { FNC : 'FNC' , RECV : 'RECV' , SEND : 'SEND' , METH : 'METH' , C2D : 'C2D' , } , LOG : { OPS : 'PROC' , EV : { OFF : 'OFF' , WAITING : 'WAITING' , DELAY : 'DELAY' , ON : 'ON' , TRYING : 'TRYING' , SUCCESS : 'SUCCESS' , INIT : 'INIT' , CONNECTED : 'CONNECTED' , ERROR : 'ERROR' , } , } , MSG : { TWIN : 'TWIN' , MSG : 'MSG' } , SUB : { COMP : 'C' } , STAT : { MSG : { FIRST : 'MSG_FIRST' , LAST : 'MSG_LAST' , COUNT : 'MSG_COUNT' , RATE : 'MSG_RATE' } , TWIN : { FIRST : 'TWIN_FIRST' , LAST : 'TWIN_LAST' , COUNT : 'TWIN_COUNT' , RATE : 'TWIN_RATE' } , ON : 'ON' , OFF : 'OFF' , CONNECTS : 'CONNECTS' , COMMANDS : 'COMMANDS' , C2D : 'C2D' , DESIRED : 'DESIRED' , RESTART : 'RESTART' , ERRORS : 'ERRORS' , DPS : 'DPS' , RECONFIGURES : 'RECONFIGURES' , } } ; interface IoTHubDevice { hubName : string ; client : Client | ModuleClient ; } interface Timers { timeRemain : number ; originalTime : number ; } interface Stats { ON : number ; OFF : number ; MSG_FIRST : string ; MSG_LAST : string ; MSG_COUNT : number ; MSG_RATE : number ; TWIN_FIRST : string ; TWIN_LAST : string ; TWIN_COUNT : number ; TWIN_RATE : number ; CONNECTS : number ; COMMANDS : number ; C2D : number ; DESIRED : number ; RESTART : number ; ERRORS : number ; RECONFIGURES : number ; DPS : number ; } export class MockDevice { private controlled : boolean = false ; private previousControlStatus = null ; private currentControlStatus = LOGGING_TAGS . LOG . EV . OFF ; private CMD_REBOOT : string ; private CMD_FIRMWARE : string ; private CMD_SHUTDOWN : string ; private FIRMWARE_LOOP : number ; private CONNECT_POLL : number ; private RESTART_LOOP : number ; private LOOP_MINS : any ; private LOOP_SECS : any ; private CONNECT_RESTART : boolean = false ; private useSasMode = true ; private sasTokenExpiry = 0 ; private simulationStore = new SimulationStore ( ) ; private ranges : any = { } ; private geo : any = { } ; private connectionDPSTimer = null ; private connectionTimer = null ; private device : Device = null ; private iotHubDevice : IoTHubDevice = { client : undefined , hubName : undefined , } ; private edgeHubName : string = null ; private edgeRLTimer = null ; private edgeRLPayloadAdditions : { } ; private methodRLTimer = null ; private methodReturnPayload = null ; private receivedMethodParams = { } ; private twinDesiredPayloadRead = { } ; private twinRLTimer = null ; private twinRLProps : Array < Property > = [ ] ; private twinRLPropsPlanValues : Array < Property > = [ ] ; private twinRLReportedTimers : Array < Timers > = [ ] ; private twinRLPayloadAdditions : ValueByIdPayload = < ValueByIdPayload > { } ; private twinRLStartUp : Array < string > = [ ] ; private twinRLStartUpCache : Array < string > = [ ] ; private msgRLTimer = null ; private msgRLProps : Array < Property > = [ ] ; private msgRLPropsPlanValues : Array < Property > = [ ] ; private msgRLReportedTimers : Array < Timers > = [ ] ; private msgRLPayloadAdditions : ValueByIdPayload = < ValueByIdPayload > { } ; private msgRLStartUp : Array < string > = [ ] ; private msgRLStartUpCache : Array < string > = [ ] ; private desiredMergedCache = { } ; private desiredOverrides : DesiredPayload = < DesiredPayload > { } ; private twinRLMockSensorTimers = { } ; private msgRLMockSensorTimers = { } ; private running : boolean = false ; private messageService = null ; private registrationConnectionString : string = null ; private planModeLastEventTime = 0 ; private dpsRetires : number = 10 ; private delayStartTimer = null ; private resolversCollection = { nameDesiredToId : { } , idPropToCommIndex : { } , nameMethodToId : { } , nameDesiredToIndex : { } , nameC2dToCommIndex : { } , nameDmToCommIndex : { } } ; private stats : Stats ; private firstSendMins : string ; private plugIn : PlugIn = undefined ; private datastoreConfigCb = null ; constructor ( device , messageService , plugIn , configUpdateCb ) { if ( device . configuration . _kind === 'template' ) { return ; } this . messageService = messageService ; this . plugIn = plugIn ; this . datastoreConfigCb = configUpdateCb ; this . initialize ( device ) ; } getControlStatus ( ) { return this . currentControlStatus ; } getIoTHubHostname ( ) { return this . iotHubDevice . hubName ; } getSecondsFromHours ( hours ) { var raw = ( Date . now ( ) / 1000 ) + ( 3600 * hours ) ; return Math . ceil ( raw ) ; } initialize ( device ) { this . ranges = this . simulationStore . get ( ) [ 'ranges' ] ; const i = 0 ; let geoIndex = 0 ; if ( device . configuration . geo ) { try { geoIndex = parseInt ( device . configuration . geo ) ; } catch { } } this . geo = this . simulationStore . get ( ) [ 'geo' ] [ geoIndex ] ; const commands = this . simulationStore . get ( ) [ 'commands' ] ; this . CMD_REBOOT = commands [ 'reboot' ] ; this . CMD_FIRMWARE = commands [ 'firmware' ] ; this . CMD_SHUTDOWN = commands [ 'shutdown' ] ; const simulation = this . simulationStore . get ( ) [ 'simulation' ] ; this . FIRMWARE_LOOP = simulation [ 'firmware' ] ; this . CONNECT_POLL = simulation [ 'connect' ] ; const { min , max } = simulation [ 'restart' ] ; const runloop = this . simulationStore . get ( ) [ 'runloop' ] ; this . LOOP_MINS = runloop [ 'mins' ] ; this . LOOP_SECS = runloop [ 'secs' ] ; this . RESTART_LOOP = Utils . getRandomNumberBetweenRange ( min , max , true ) * 3600000 ; this . sasTokenExpiry = this . getSecondsFromHours ( simulation [ 'sasExpire' ] ) ; this . stats = { MSG_COUNT : 0 , MSG_FIRST : null , MSG_LAST : null , MSG_RATE : 0 , TWIN_COUNT : 0 , TWIN_FIRST : null , TWIN_LAST : null , TWIN_RATE : 0 , CONNECTS : 0 , DESIRED : 0 , ON : 0 , OFF : 0 , RESTART : 0 , RECONFIGURES : - 1 , COMMANDS : 0 , C2D : 0 , DPS : 0 , ERRORS : 0 , } ; this . updateDevice ( device , false ) ; } updateDevice ( device , valueOnlyUpdate ) { if ( device . configuration . _kind === 'template' ) { return ; } if ( this . device != null && this . device . configuration . connectionString != device . configuration . connectionString ) { this . log ( 'DEVICE/MODULE UPDATE ERROR. CONNECTION STRING HAS CHANGED. DELETE DEVICE' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; } else { this . device = Object . assign ( { } , device ) ; if ( this . plugIn ) { this . plugIn . configureDevice ( this . device . configuration . deviceId , this . running , this . device . configuration , this . updateDeviceConfiguration . bind ( this ) ) ; } this . reconfigDeviceDynamically ( valueOnlyUpdate ) ; } } updateDeviceConfiguration ( configuration ) { this . log ( `DEVICE CONFIGURATION UPDATE REQUEST. DEVICE WILL SHUTDOWN AND RESTART AFTER 5 SECS` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; setTimeout ( ( ) => { this . stop ( ) ; this . device . configuration = configuration ; this . datastoreConfigCb ( configuration ) ; setTimeout ( ( ) => { this . start ( undefined ) ; } , 5000 ) ; } , 5000 ) ; } getRunningStatus ( ) { return this . running ; } reconfigDeviceDynamically ( valueOnlyUpdate ) { if ( valueOnlyUpdate ) { return ; } this . log ( 'DEVICE/MODULE HAS BEEN CONFIGURED' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logStat ( LOGGING_TAGS . STAT . RECONFIGURES ) ; this . resolversCollection = { idPropToCommIndex : { } , nameDesiredToId : { } , nameMethodToId : { } , nameDesiredToIndex : { } , nameC2dToCommIndex : { } , nameDmToCommIndex : { } } ; this . twinRLProps = [ ] ; this . twinRLPropsPlanValues = [ ] ; this . twinRLReportedTimers = [ ] ; this . twinRLMockSensorTimers = { } ; this . twinRLStartUp = [ ] ; this . twinRLStartUpCache = [ ] ; this . msgRLProps = [ ] ; this . msgRLPropsPlanValues = [ ] ; this . msgRLReportedTimers = [ ] ; this . msgRLMockSensorTimers = { } ; this . msgRLStartUp = [ ] ; this . msgRLStartUpCache = [ ] ; this . buildIndexes ( ) ; if ( this . device . configuration . planMode ) { const config = this . simulationStore . get ( ) [ 'plan' ] ; this . device . plan . startup . forEach ( ( item ) => { const comm = this . device . comms [ this . resolversCollection . idPropToCommIndex [ item . property ] ] ; if ( comm . sdk === 'twin' ) { this . twinRLProps . push ( comm ) ; this . twinRLPropsPlanValues . push ( item . value ) ; this . twinRLReportedTimers . push ( { timeRemain : config [ 'startDelay' ] , originalTime : config [ 'startDelay' ] } ) ; } else if ( comm . sdk === 'msg' ) { this . msgRLProps . push ( comm ) ; this . msgRLPropsPlanValues . push ( item . value ) ; this . msgRLReportedTimers . push ( { timeRemain : config [ 'startDelay' ] , originalTime : config [ 'startDelay' ] } ) ; } } ) ; this . device . plan . timeline . forEach ( ( item ) => { this . planModeLastEventTime = ( item . time * 1000 ) + config [ 'timelineDelay' ] ; const comm = this . device . comms [ this . resolversCollection . idPropToCommIndex [ item . property ] ] ; if ( comm . sdk === 'twin' ) { this . twinRLProps . push ( comm ) ; this . twinRLPropsPlanValues . push ( item . value ) ; this . twinRLReportedTimers . push ( { timeRemain : this . planModeLastEventTime , originalTime : this . planModeLastEventTime } ) ; } else if ( comm . sdk === 'msg' ) { this . msgRLProps . push ( comm ) ; this . msgRLPropsPlanValues . push ( item . value ) ; this . msgRLReportedTimers . push ( { timeRemain : this . planModeLastEventTime , originalTime : this . planModeLastEventTime } ) ; } } ) ; return ; } for ( const i in this . device . comms ) { const comm = this . device . comms [ i ] ; if ( this . device . comms [ i ] . _type != 'property' ) { continue ; } if ( comm . runloop && comm . runloop . onStartUp && ! comm . runloop . include ) { this . addLoopStartUpComm ( comm , ! this . running ) ; } if ( comm . runloop && comm . runloop . include === true ) { if ( ! comm . runloop . _ms ) { if ( ! comm . runloop . valueMax ) { comm . runloop . valueMax = comm . runloop . value ; } let newRunloopValue = 0 ; if ( comm . runloop . override ) { const simLoop = comm . runloop . unit === 'secs' ? this . LOOP_SECS : this . LOOP_MINS ; newRunloopValue = Utils . getRandomNumberBetweenRange ( simLoop . min , simLoop . max , true ) ; } else { newRunloopValue = Utils . getRandomNumberBetweenRange ( comm . runloop . value , comm . runloop . valueMax , true ) ; } comm . runloop . _ms = newRunloopValue * ( comm . runloop . unit === 'secs' ? 1000 : 60000 ) ; } let mockSensorTimerObject = null ; if ( comm . mock ) { let slice = 0 ; let startValue = 0 ; if ( comm . mock . _type != 'function' ) { startValue = comm . mock . running && comm . mock . running > 0 ? comm . mock . running : comm . mock . init ; } else { startValue = comm . mock . init ; comm . mock . timeToRunning = 1 ; } slice = startValue / ( comm . mock . timeToRunning / 1000 ) ; mockSensorTimerObject = { sliceMs : slice , remainingMs : comm . mock . timeToRunning } ; comm . mock . _value = Utils . formatValue ( false , comm . mock . init ) ; } if ( comm . sdk === 'twin' ) { this . twinRLProps . push ( comm ) ; this . twinRLReportedTimers . push ( { timeRemain : comm . runloop . _ms , originalTime : comm . runloop . _ms } ) ; if ( mockSensorTimerObject != null ) { this . twinRLMockSensorTimers [ comm . _id ] = mockSensorTimerObject ; } if ( comm . runloop . onStartUp ) { this . twinRLStartUpCache . push ( comm . _id ) ; } if ( comm . runloop . onStartUp && ! this . running ) { this . twinRLStartUp . push ( comm . _id ) ; } } if ( comm . sdk === 'msg' ) { this . msgRLProps . push ( comm ) ; this . msgRLReportedTimers . push ( { timeRemain : comm . runloop . _ms , originalTime : comm . runloop . _ms } ) ; if ( mockSensorTimerObject != null ) { this . msgRLMockSensorTimers [ comm . _id ] = mockSensorTimerObject ; } if ( comm . runloop . onStartUp ) { this . msgRLStartUpCache . push ( comm . _id ) ; } if ( comm . runloop . onStartUp && ! this . running ) { this . msgRLStartUp . push ( comm . _id ) ; } } } } this . firstSendMins = ( Math . min ( Math . min . apply ( null , this . twinRLReportedTimers . map ( ( v ) => v . timeRemain ) ) , Math . min . apply ( null , this . msgRLReportedTimers . map ( ( v ) => v . timeRemain ) ) ) / 60000 ) . toFixed ( 1 ) ; } buildIndexes ( ) { this . device . comms . forEach ( ( comm , index ) => { this . resolversCollection . idPropToCommIndex [ comm . _id ] = index ; if ( comm . _type === 'property' && comm . sdk === 'twin' && comm . type . direction === 'c2d' ) { this . resolversCollection . nameDesiredToId [ comm . name ] = comm . _id ; this . resolversCollection . nameDesiredToIndex [ comm . name ] = index ; } if ( comm . _type === 'method' ) { this . resolversCollection . nameMethodToId [ comm . name ] = comm . _id ; const key = comm . component && comm . component . enabled ? `${ comm . component . name }*${ comm . name }` : comm . name ; if ( comm . execution === 'cloud' ) { this . resolversCollection . nameC2dToCommIndex [ key ] = index ; } if ( comm . execution === 'direct' ) { this . resolversCollection . nameDmToCommIndex [ key ] = index ; } } } ) ; } addLoopStartUpComm ( comm , liveUpdate ) { let json = < ValueByIdPayload > { } ; let converted = Utils . formatValue ( comm . string , comm . value ) ; json [ comm . _id ] = converted ; if ( comm . sdk === 'twin' ) { if ( comm . runloop . onStartUp ) { this . twinRLStartUpCache . push ( comm . _id ) ; } if ( comm . runloop . onStartUp && ! this . running ) { this . twinRLStartUp . push ( comm . _id ) ; } if ( liveUpdate ) { this . updateTwin ( json ) ; } } if ( comm . sdk === 'msg' ) { if ( comm . runloop . onStartUp ) { this . msgRLStartUpCache . push ( comm . _id ) ; } if ( comm . runloop . onStartUp && ! this . running ) { this . msgRLStartUp . push ( comm . _id ) ; } if ( liveUpdate ) { this . updateMsg ( json ) ; } } } updateTwin ( payload ) { Object . assign ( this . twinRLPayloadAdditions , payload ) ; } readTwin ( ) { return this . twinDesiredPayloadRead ; } readMethodParams ( ) { return this . receivedMethodParams ; } updateMsg ( payload ) { Object . assign ( this . msgRLPayloadAdditions , payload ) ; } updateEdgeModules ( payload ) { this . edgeRLPayloadAdditions = Object . assign ( { } , payload ) ; } processMockDevicesCMD ( name ) { const methodName = name . toLocaleLowerCase ( ) ; if ( methodName === this . CMD_SHUTDOWN ) { this . log ( 'DEVICE/MODULE METHOD SHUTDOWN ... STOPPING IMMEDIATELY' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . stop ( ) ; return ; } if ( methodName === this . CMD_REBOOT ) { this . log ( 'DEVICE/MODULE METHOD REBOOT ... RESTARTING IMMEDIATELY' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . stop ( ) ; this . start ( undefined ) ; return ; } if ( methodName === this . CMD_FIRMWARE ) { this . log ( `DEVICE/MODULE METHOD FIRMWARE ... RESTARTING IN ${ this . FIRMWARE_LOOP / 1000 } SECONDS` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . stop ( ) ; setTimeout ( ( ) => { this . start ( undefined ) ; } , this . FIRMWARE_LOOP ) ; } } public releaseControl ( ) { this . controlled = false ; } waitForInstruction ( ) { this . log ( `DEVICE/MODULE IS BEING ASKED TO WAIT` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . WAITING ) ; this . controlled = true ; let waitTimer = setInterval ( ( ) => { if ( ! this . controlled ) { this . log ( `MODULE WAIT RELEASED` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; clearInterval ( waitTimer ) ; } } , 100 ) ; } start ( delay , edgeHubName ? ) { if ( edgeHubName ) { this . edgeHubName = edgeHubName ; } if ( this . device . configuration . _kind === 'template' ) { return ; } if ( this . delayStartTimer || this . running ) { return ; } if ( delay ) { this . log ( `DEVICE/MODULE DELAYED START SECONDS: ${ delay / 1000 }` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . DELAY ) ; this . delayStartTimer = setTimeout ( ( ) => { this . startDevice ( ) ; setTimeout ( ( ) => { clearTimeout ( this . delayStartTimer ) ; this . delayStartTimer = null ; } , 100 ) ; } , delay ) ; } else { this . startDevice ( ) ; } } async startDevice ( ) { this . running = true ; const { deviceId , moduleId } = Utils . decodeModuleKey ( this . device . _id ) ; if ( moduleId ) { this . log ( 'MODULE IS SWITCHED ON' , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . ON ) ; this . logStat ( LOGGING_TAGS . STAT . ON ) ; this . iotHubDevice = { client : undefined , hubName : undefined } ; this . log ( 'MODULE INIT' , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . TRYING ) ; if ( this . device . configuration . _kind === 'moduleHosted' as string ) { if ( ! GLOBAL_CONTEXT . IOTEDGE_WORKLOADURI && ! GLOBAL_CONTEXT . IOTEDGE_DEVICEID && ! GLOBAL_CONTEXT . IOTEDGE_MODULEID && ! GLOBAL_CONTEXT . IOTEDGE_MODULEGENERATIONID && ! GLOBAL_CONTEXT . IOTEDGE_IOTHUBHOSTNAME && ! GLOBAL_CONTEXT . IOTEDGE_AUTHSCHEME ) { this . log ( `MODULE '${ moduleId }' ENVIRONMENT CHECK FAILED - MISSING IOTEDGE_*` , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . log ( 'MODULE WILL SHUTDOWN' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . OFF ) ; return ; } if ( GLOBAL_CONTEXT . IOTEDGE_DEVICEID != deviceId || GLOBAL_CONTEXT . IOTEDGE_MODULEID != moduleId ) { this . log ( `MODULE '${ moduleId }' DOES NOT MATCH THE MANIFEST CONFIGURATION FOR HOST DEVICE/MODULE (NOT A FAILURE)` , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . log ( 'MODULE WILL SHUTDOWN' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . OFF ) ; return ; } try { this . iotHubDevice . client = await ModuleClient . fromEnvironment ( Protocol ) ; this . log ( `MODULE '${ moduleId }' CHECK PASSED. ENTERING MAIN LOOP` , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . CONNECTED ) ; this . mainLoop ( ) ; } catch ( err ) { this . log ( `MODULE '${ moduleId }' FAILED TO CONNECT THROUGH ENVIRONMENT TO IOT HUB: ${ err }` , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . log ( `MODULE '${ moduleId }' WILL SHUTDOWN` , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . OFF ) ; } finally { return ; } } if ( ! this . edgeHubName ) { this . log ( `MODULE '${ moduleId }' CANNOT BE STARTED AS NO KNOWN HUBNAME FOR HOST. EDGE DEVICE PROBABLY FAILED TO START` , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . stop ( ) ; } else { let transformedSasKey = this . device . configuration . isMasterKey ? this . computeDrivedSymmetricKey ( this . device . configuration . gatewaySasKey , this . device . configuration . gatewayDeviceId ) : this . device . configuration . gatewaySasKey ; const registry = Registry . fromSharedAccessSignature ( CommonSaS . create ( this . edgeHubName , undefined , transformedSasKey , Date . now ( ) ) . toString ( ) ) ; try { await registry . getModule ( deviceId , moduleId ) ; } catch ( e ) { await registry . addModule ( { deviceId , moduleId , } ) ; } this . iotHubDevice . client = Client . fromConnectionString ( `HostName=${ this . edgeHubName };DeviceId=${ deviceId };SharedAccessKey=${ transformedSasKey };ModuleId=${ moduleId }` , Protocol ) ; this . log ( `MODULE '${ moduleId }' CHECK PASSED. ENTERING MAIN LOOP` , LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . MOD , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . CONNECTED ) ; this . mainLoop ( ) ; } return ; } this . log ( 'DEVICE IS SWITCHED ON' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . ON ) ; this . logStat ( LOGGING_TAGS . STAT . ON ) ; if ( this . device . configuration . _kind === 'dps' || this . device . configuration . _kind === 'edge' || this . device . configuration . _kind === 'leafDevice' ) { const simulation = this . simulationStore . get ( ) [ 'simulation' ] ; this . dpsRetires = simulation [ 'dpsRetries' ] || 10 ; this . registrationConnectionString = null ; this . connectionDPSTimer = setInterval ( ( ) => { if ( this . registrationConnectionString != null && this . registrationConnectionString != 'init' ) { clearInterval ( this . connectionDPSTimer ) ; this . connectionDPSTimer = null ; this . connectLoop ( this . registrationConnectionString ) ; return ; } if ( this . dpsRetires <= 0 ) { clearInterval ( this . connectionDPSTimer ) ; this . connectionDPSTimer = null ; this . stop ( ) ; this . logStat ( LOGGING_TAGS . STAT . ERRORS ) ; return ; } this . log ( 'ATTEMPTING DPS REGISTRATION' , LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . TRYING ) ; this . dpsRegistration ( ) ; } , this . CONNECT_POLL ) ; } else { this . connectLoop ( this . device . configuration . connectionString ) ; } } connectLoop ( connectionString ? ) { this . log ( 'IOT HUB INITIAL CONNECT START' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . INIT ) ; this . connectClient ( connectionString ) ; if ( this . plugIn ) { this . plugIn . postConnect ( this . device . configuration . deviceId ) ; this . log ( `DEVICE/MODULE IS USING A PLUGIN` , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; } this . reportModuleStatus ( { } , "start" ) ; this . mainLoop ( ) ; this . connectionTimer = setInterval ( ( ) => { this . log ( 'IOT HUB RECONNECT LOOP START' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . INIT ) ; this . logStat ( LOGGING_TAGS . STAT . RESTART ) ; this . CONNECT_RESTART = true ; this . cleanUp ( ) ; this . running = true ; this . connectClient ( connectionString ) ; this . mainLoop ( ) ; } , this . RESTART_LOOP ) ; } async stop ( ) { if ( this . delayStartTimer ) { clearTimeout ( this . delayStartTimer ) ; this . delayStartTimer = null ; this . log ( `DEVICE/MODULE DELAYED START CANCELED` , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . OFF ) ; } else if ( this . running ) { if ( this . device . configuration . _kind === 'module' ) { this . log ( 'MODULE WILL SHUTDOWN' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; } else { this . log ( 'DEVICE WILL SHUTDOWN' , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; } } await this . reportModuleStatus ( this . edgeRLPayloadAdditions , "stop" ) ; this . logCP ( LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . OFF ) ; if ( ! this . running ) { return ; } if ( this . plugIn ) { this . plugIn . stopDevice ( this . device . configuration . deviceId ) ; } this . final ( ) ; } final ( ) { this . dpsRetires = 0 ; clearInterval ( this . connectionTimer ) ; this . cleanUp ( ) ; } cleanUp ( ) { clearInterval ( this . edgeRLTimer ) ; clearInterval ( this . twinRLTimer ) ; clearInterval ( this . msgRLTimer ) ; clearInterval ( this . methodRLTimer ) ; try { if ( this . iotHubDevice && this . iotHubDevice . client ) { this . iotHubDevice . client . removeAllListeners ( ) ; this . iotHubDevice . client . close ( ) ; this . iotHubDevice . client = null ; this . iotHubDevice . hubName = undefined ; } } catch ( err ) { this . log ( `DEVICE/MODULE CLIENT TEARDOWN ERROR: ${ err . message }` , LOGGING_TAGS . CTRL . DEV , LOGGING_TAGS . LOG . OPS ) ; this . logStat ( LOGGING_TAGS . STAT . ERRORS ) ; } finally { this . running = false ; this . logStat ( LOGGING_TAGS . STAT . OFF ) ; } } mainLoop ( ) { if ( ! this . running ) { return ; } try { this . iotHubDevice . client . open ( async ( ) => { this . log ( `IOT HUB ${ this . device . configuration . _kind === 'module' ? 'MODULE' : '' } CLIENT CONNECTED` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . registerDirectMethods ( ) ; if ( this . device . configuration . _kind !== 'module' ) { this . registerC2D ( ) ; } this . log ( this . device . configuration . planMode ? 'PLAN MODE' : 'INTERACTIVE MODE' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . CONNECTED ) ; this . twinRLStartUp = this . twinRLStartUpCache . slice ( ) ; this . msgRLStartUp = this . msgRLStartUpCache . slice ( ) ; for ( const i in this . device . comms ) { if ( this . device . comms [ i ] . runloop && this . device . comms [ i ] . runloop . onStartUp && ! this . device . comms [ i ] . runloop . include ) { this . addLoopStartUpComm ( this . device . comms [ i ] , true ) ; } } this . iotHubDevice . client . getTwin ( ( err , twin ) => { if ( err ) { this . log ( 'IOT HUB TWIN REQUEST FAILED. CLIENT IN BAD STATE' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . stop ( ) ; } twin . on ( 'properties.desired' , ( ( delta ) => { this . logStat ( LOGGING_TAGS . STAT . DESIRED ) ; _ . merge ( this . desiredMergedCache , delta ) ; Object . assign ( this . twinDesiredPayloadRead , delta ) ; this . log ( JSON . stringify ( delta ) , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . MSG . TWIN , LOGGING_TAGS . DATA . RECV ) ; this . CONNECT_RESTART = false ; this . registerDesiredProperties ( delta , delta [ '$version' ] , false ) ; } ) ) ; if ( this . device . comms . length === 0 ) { this . log ( `DEVICE/MODULE HAS NO CAPABILITIES DEFINED. DEVICE/MODULE CAN RECEIVE EVENTS BUT WILL NOT SEND ANY DATA` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; } else { if ( Utils . isNumeric ( this . firstSendMins ) ) { this . log ( `LOOPS HAVE BEEN DEFINED. DATA WILL START SENDING IN ${ this . firstSendMins } MINUTES` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; } else { this . log ( `NO LOOPS HAVE BEEN DEFINED. SEND DATA MANUALLY` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; } } this . methodRLTimer = setInterval ( ( ) => { if ( this . methodReturnPayload != null ) { twin . properties . reported . update ( this . methodReturnPayload , ( ( err ) => { this . log ( err ? err . toString ( ) : JSON . stringify ( this . methodReturnPayload ) , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . MSG . TWIN , LOGGING_TAGS . DATA . SEND ) ; this . methodReturnPayload = null ; } ) ) ; } } , 500 ) ; this . twinRLTimer = setInterval ( ( ) => { let payload = < ValueByIdPayload > this . calcPropertyValues ( this . twinRLProps , this . twinRLReportedTimers , this . twinRLMockSensorTimers , this . twinRLPropsPlanValues , this . twinRLStartUp ) ; this . runloopTwin ( this . twinRLPayloadAdditions , payload , twin ) ; this . twinRLPayloadAdditions = < ValueByIdPayload > { } ; this . twinRLStartUp = [ ] ; } , 1000 ) ; } ) ; this . msgRLTimer = setInterval ( ( ) => { let payload = null ; payload = < ValueByIdPayload > this . calcPropertyValues ( this . msgRLProps , this . msgRLReportedTimers , this . msgRLMockSensorTimers , this . msgRLPropsPlanValues , this . msgRLStartUp ) ; this . runloopMsg ( this . msgRLPayloadAdditions , payload ) ; this . msgRLPayloadAdditions = < ValueByIdPayload > { } ; this . msgRLStartUp = [ ] ; } , 1000 ) ; if ( this . device . configuration . _kind === 'edge' ) { this . edgeRLTimer = setInterval ( ( ) => { this . reportModuleStatus ( this . edgeRLPayloadAdditions ) ; } , 30000 ) ; } } ) ; } catch ( err ) { this . log ( `SDK OPEN ERROR (CHECK CONN STRING): ${ err . message }` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . ERROR ) ; setTimeout ( ( ) => { this . stop ( ) ; } , 5000 ) ; } } registerDesiredProperties ( delta , version , component ) { for ( let name in delta ) { if ( name === '$version' ) { continue ; } if ( name === '__t' ) { continue ; } if ( delta [ name ] [ '__t' ] ) { this . registerDesiredProperties ( delta [ name ] , version , true ) ; } else { if ( this . device . configuration . planMode ) { this . sendPlanResponse ( this . resolversCollection . nameDesiredToId [ name ] ) ; } else { this . sendPropertyResponse ( delta [ name ] , name , version , component ) ; } } } } sendPropertyResponse ( sentValue , name , version , component ) { const property = this . device . comms [ this . resolversCollection . nameDesiredToIndex [ name ] ] ; if ( ! property ) { return ; } property . value = sentValue ; property . version = version ; if ( property . asProperty && property . asPropertyId ) { if ( property . asPropertyVersion ) { this . desiredOverrides [ property . asPropertyId ] = < DesiredPayload > { payload : property . asPropertyVersionPayload , convention : property . asPropertyConvention , value : sentValue , version : property . version , component : component } ; } const p = this . device . comms . filter ( ( x ) => { return x . _id === property . asPropertyId ; } ) ; for ( const send in p ) { let json = < ValueByIdPayload > { } ; let converted = Utils . formatValue ( p [ send ] . string , p [ send ] . value ) ; json [ p [ send ] . _id ] = converted ; if ( p [ send ] . sdk === 'twin' ) { this . updateTwin ( json ) ; } if ( p [ send ] . sdk === 'msg' ) { this . updateMsg ( json ) ; } } } } sendPlanResponse ( propertyId ) { const property = this . device . plan . receive . find ( ( prop ) => { return prop . property === propertyId ; } ) ; if ( property ) { const outboundProperty = this . device . comms [ this . resolversCollection . idPropToCommIndex [ property . propertyOut ] ] ; if ( ! outboundProperty ) { return ; } const payload = < ValueByIdPayload > { [ outboundProperty . _id ] : property . value } ; if ( outboundProperty . sdk === 'twin' ) { this . updateTwin ( payload ) ; } else { this . updateMsg ( payload ) ; } } } sendMethodResponse ( method ) { if ( this . device . configuration . planMode ) { this . sendPlanResponse ( this . resolversCollection . nameMethodToId [ method . name ] ) ; } else if ( ! this . device . configuration . planMode && method . asProperty && method . asPropertyId ) { if ( method . asPropertyVersion ) { this . desiredOverrides [ method . asPropertyId ] = < DesiredPayload > { payload : method . asPropertyVersionPayload , convention : null , value : null , version : null } ; } const p = this . device . comms . filter ( ( x ) => { return x . _id === method . asPropertyId ; } ) ; for ( const send in p ) { let json = < ValueByIdPayload > { } ; let converted = Utils . formatValue ( p [ send ] . string , p [ send ] . value ) ; json [ p [ send ] . _id ] = converted ; if ( p [ send ] . sdk === 'twin' ) { this . updateTwin ( json ) ; } if ( p [ send ] . sdk === 'msg' ) { this . updateMsg ( json ) ; } } } else if ( ! this . device . configuration . planMode && method . asProperty ) { this . methodReturnPayload = Object . assign ( { } , { [ method . name ] : method . payload } ) ; } setTimeout ( ( ) => { this . processMockDevicesCMD ( method . name ) ; } , 2000 ) ; } registerC2D ( ) { this . iotHubDevice . client . on ( 'message' , ( msg ) => { if ( msg === undefined || msg === null ) { return ; } let error = false ; try { const cloudName = msg . properties . getValue ( 'method-name' ) ; const cloudNameParts = cloudName . split ( ':' ) ; const cloudMethod = cloudNameParts . length === 2 ? cloudNameParts [ 1 ] : cloudNameParts [ 0 ] ; let cloudMethodPayload = msg . data . toString ( ) ; try { cloudMethodPayload = JSON . parse ( cloudMethodPayload ) ; } catch ( err ) { } if ( this . resolversCollection . nameC2dToCommIndex [ cloudMethod ] ) { const method = this . device . comms [ this . resolversCollection . nameC2dToCommIndex [ cloudMethod ] ] ; this . log ( cloudMethod + ' ' + JSON . stringify ( cloudMethodPayload ) , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . DATA . METH , LOGGING_TAGS . DATA . RECV , 'C2D REQUEST AND PAYLOAD' ) ; this . logStat ( LOGGING_TAGS . STAT . C2D ) ; Object . assign ( this . receivedMethodParams , { [ method . _id ] : { date : new Date ( ) . toUTCString ( ) , payload : JSON . stringify ( cloudMethodPayload , null , 2 ) } } ) ; this . sendMethodResponse ( method ) ; } } catch ( err ) { error = true ; this . log ( `${ err . toString ( ) }` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . DATA . METH , LOGGING_TAGS . DATA . SEND , 'C2D ERROR PARSING MESSAGE BODY' ) ; } this . iotHubDevice . client . complete ( msg , ( err ) => { this . log ( `${ err ? err . toString ( ) : error ? 'FAILED' : 'SUCCESS' }` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS , null , 'C2D COMPLETE' ) ; } ) ; } ) ; } registerDirectMethods ( ) { for ( const key in this . resolversCollection . nameDmToCommIndex ) { const clientMethodKey = this . iotHubDevice . client [ 'onDeviceMethod' ] ? 'onDeviceMethod' : 'onMethod' ; this . iotHubDevice . client [ clientMethodKey ] ( key , ( request , response ) => { const method = this . device . comms [ this . resolversCollection . nameDmToCommIndex [ key ] ] ; const methodPayload = JSON . parse ( method . payload || { } ) ; const requestPayload = request . payload ; this . log ( `${ request . methodName } : ${ requestPayload ? JSON . stringify ( requestPayload ) : '<NO PAYLOAD>' }` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . DATA . METH , LOGGING_TAGS . DATA . RECV , 'DIRECT METHOD REQUEST AND PAYLOAD' ) ; Object . assign ( this . receivedMethodParams , { [ method . _id ] : { date : new Date ( ) . toUTCString ( ) , payload : requestPayload } } ) ; response . send ( ( parseInt ( method . status ) ) , methodPayload , ( err ) => { this . log ( err ? err . toString ( ) : `${ method . name } -> [${ method . status }] ${ JSON . stringify ( methodPayload ) }` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . DATA . METH , LOGGING_TAGS . DATA . SEND , 'DIRECT METHOD RESPONSE PAYLOAD' ) ; this . logStat ( LOGGING_TAGS . STAT . COMMANDS ) ; this . messageService . sendAsLiveUpdate ( this . device . _id , { [ method . _id ] : new Date ( ) . toUTCString ( ) } ) ; let res = null ; if ( this . plugIn ) { try { res = this . plugIn . commandResponse ( this . device . configuration . deviceId , method , requestPayload ) ; } catch ( err ) { this . log ( err ? err . toString ( ) : 'PLUGIN COMMAND RESPONSE ERROR' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . DATA . METH , LOGGING_TAGS . DATA . SEND ) ; } } if ( res === undefined ) { this . sendMethodResponse ( method ) ; } } ) ; } ) ; } } connectClient ( connectionString ) { this . iotHubDevice = { client : undefined , hubName : undefined } ; if ( this . useSasMode ) { const cn = ConnectionString . parse ( connectionString ) ; let sas = SharedAccessSignature . create ( cn . HostName , cn . DeviceId , cn . SharedAccessKey , this . sasTokenExpiry ) ; this . iotHubDevice . client = Client . fromSharedAccessSignature ( sas , M1 ) ; this . iotHubDevice . hubName = cn . HostName ; const trueHours = Math . ceil ( ( this . sasTokenExpiry - Math . round ( Date . now ( ) / 1000 ) ) / 3600 ) ; this . log ( `CONNECTING VIA SAS.TOKEN EXPIRES AFTER ${ trueHours } HOURS` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; } else { this . iotHubDevice . client = clientFromConnectionString ( connectionString ) ; this . iotHubDevice . hubName = ConnectionString . parse ( connectionString ) . HostName ; this . log ( `CONNECTING VIA CONN STRING` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; } this . logStat ( LOGGING_TAGS . STAT . CONNECTS ) ; this . log ( `DEVICE/MODULE AUTO RESTARTS EVERY ${ this . RESTART_LOOP / 60000 } MINUTES` , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . LOG . OPS ) ; } dpsRegistration ( ) { if ( this . registrationConnectionString === 'init' ) { return ; } if ( this . dpsRetires === 0 ) { return ; } let config = this . device . configuration ; this . iotHubDevice = { client : undefined , hubName : undefined } ; this . registrationConnectionString = 'init' ; let transformedSasKey = config . isMasterKey ? this . computeDrivedSymmetricKey ( config . sasKey , config . deviceId ) : config . sasKey ; let dpsPayload = { } ; if ( config . dpsPayload ) { try { dpsPayload = JSON . parse ( config . dpsPayload ) ; } catch { dpsPayload = config . dpsPayload ; } } let provisioningSecurityClient = new SymmetricKeySecurityClient ( config . deviceId , transformedSasKey ) ; let provisioningClient = ProvisioningDeviceClient . create ( 'global.azure-devices-provisioning.net' , config . scopeId , new MqttDps ( ) , provisioningSecurityClient ) ; provisioningClient . setProvisioningPayload ( dpsPayload ) ; this . log ( 'CONNECTING TO ' + config . scopeId , LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS ) ; this . log ( 'WAITING FOR REGISTRATION' , LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . TRYING ) ; provisioningClient . register ( ( err , result ) => { if ( err ) { let msg = ( err . result && err . result . registrationState && err . result . registrationState . errorMessage ) || err ; this . log ( `REGISTRATION ERROR ${ this . dpsRetires }: ${ msg }` , LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . ERROR ) ; this . registrationConnectionString = null ; this . dpsRetires -- ; return ; } this . registrationConnectionString = 'HostName=' + result . assignedHub + ';DeviceId=' + result . deviceId + ';SharedAccessKey=' + transformedSasKey ; if ( this . device . configuration . _kind === 'leafDevice' ) { this . registrationConnectionString += `;GatewayId=${ this . device . configuration . gatewayDeviceId }` ; } this . log ( 'DEVICE REGISTRATION SUCCESS' , LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS ) ; this . logCP ( LOGGING_TAGS . CTRL . DPS , LOGGING_TAGS . LOG . OPS , LOGGING_TAGS . LOG . EV . SUCCESS ) ; this . logStat ( LOGGING_TAGS . STAT . DPS ) ; } ) ; } computeDrivedSymmetricKey ( masterKey , regId ) { return Crypto . createHmac ( 'SHA256' , Buffer . from ( masterKey , 'base64' ) ) . update ( regId , 'utf8' ) . digest ( 'base64' ) ; } async runloopTwin ( additions , payload , twin ? ) { if ( payload != null ) { Object . assign ( payload , additions ) ; if ( Object . keys ( payload ) . length > 0 ) { const transformed = this . transformPayload ( payload ) ; for ( const c in transformed . package ) { let sub = '' ; let data = transformed . package [ c ] ; if ( c != "_root" ) { data = { [ c ] : data } ; data [ c ] [ "__t" ] = "c" ; sub = c ; } setTimeout ( ( ) => { if ( ! this . iotHubDevice . client ) { this . log ( 'CLIENT HAS ALREADY CLOSED. ABANDONING MSG' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . MSG . TWIN , LOGGING_TAGS . DATA . SEND ) ; return ; } twin . properties . reported . update ( data , ( ( err ) => { this . log ( JSON . stringify ( data ) , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . MSG . TWIN , LOGGING_TAGS . DATA . SEND , sub ) ; this . logStat ( LOGGING_TAGS . STAT . TWIN . COUNT ) ; this . messageService . sendAsLiveUpdate ( this . device . _id , transformed . live ) ; } ) ) ; } , 250 ) ; } } } } async runloopMsg ( additions , payload ) { if ( payload != null ) { Object . assign ( payload , additions ) ; if ( Object . keys ( payload ) . length > 0 ) { const transformed = this . transformPayload ( payload ) ; for ( const c in transformed . package ) { const data = JSON . stringify ( transformed . package [ c ] ) ; let sub = '' ; let msg = new Message ( data ) ; msg . contentType = 'application/json' ; msg . contentEncoding = 'utf-8' ; if ( c != "_root" ) { msg . properties . add ( '$.sub' , c ) ; sub = c ; } setTimeout ( ( ) => { if ( ! this . iotHubDevice . client ) { this . log ( 'CLIENT HAS ALREADY CLOSED. ABANDONING MSG' , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . MSG . MSG , LOGGING_TAGS . DATA . SEND ) ; return ; } this . iotHubDevice . client . sendEvent ( msg , ( ( err ) => { this . log ( data , LOGGING_TAGS . CTRL . HUB , LOGGING_TAGS . MSG . MSG , LOGGING_TAGS . DATA . SEND , sub ) ; this . logStat ( LOGGING_TAGS . STAT . MSG . COUNT ) ; this . messageService . sendAsLiveUpdate ( this . device . _id , transformed . live ) ; } ) ) ; } , 250 ) ; } } } } async reportModuleStatus ( additions , override ? ) { if ( this . device . configuration . _kind !== 'edge' ) { return ; } const Agent_Running = 'running' ; const Agent_Stopped = 'stopped' ; let transformedSasKey = this . device . configuration . isMasterKey ? this . computeDrivedSymmetricKey ( this . device . configuration . sasKey , this . device . configuration . deviceId ) : this . device . configuration . sasKey ; const edgeAgentClient = Client . fromConnectionString ( `HostName=${ this . iotHubDevice . hubName };DeviceId=${ this . device . configuration . deviceId };SharedAccessKey=${ transformedSasKey };ModuleId=$edgeAgent` , Protocol ) ; try { await edgeAgentClient . open ( ) ; const edgeAgentTwin = await edgeAgentClient . getTwin ( ) ; const modules = { } ; for ( const module in additions ) { const modState = override && override === 'start' ? Agent_Running : override && override === 'stop' ? Agent_Stopped : additions [ module ] ? Agent_Running : Agent_Stopped ; modules [ module ] = { runtimeStatus : modState } ; } const sysState = override && override === 'start' ? Agent_Running : override && override === 'stop' ? Agent_Stopped : this . running ? Agent_Running : Agent_Stopped ; await edgeAgentTwin . properties . reported . update ( { systemModules : { edgeAgent : { runtimeStatus : sysState } , edgeHub : { runtimeStatus : sysState } , } , modules } ) ; } catch ( err ) { if ( this . running ) { this . log ( 'CANNOT UPDATE MODULES STATUS. EDGE DEVICE SHOULD BE RESTARTED' , LOGGING_TAGS . CTRL . EDG , LOGGING_TAGS . LOG . EV . ERROR ) ; } } finally { await edgeAgentClient . close ( ) ; } } calcPropertyValues ( runloopProperties , runloopTimers , propertySensorTimers , runloopPropertiesValues , startUpList ) { if ( this . iotHubDevice === null || this . iotHubDevice . client === null ) { clearInterval ( this . msgRLTimer ) ; return null ; } let payload = { } ; for ( const i in runloopProperties ) { let p = runloopProperties [ i ] ; let { timeRemain , originalTime } = runloopTimers [ i ] ; let possibleResetTime = runloopTimers [ runloopTimers . length - 1 ] . timeRemain + originalTime ; let res = this . processCountdown ( p , timeRemain , possibleResetTime , startUpList ) ; runloopTimers [ i ] = { 'timeRemain' : res . timeRemain , originalTime } ; if ( res . process && ( this . device . configuration . planMode || p . enabled ) ) { let o = < ValueByIdPayload > { } ; o [ p . _id ] = this . device . configuration . planMode ? Utils . formatValue ( p . string , runloopPropertiesValues [ i ] ) : ( p . mock ? p . mock . _value || Utils . formatValue ( false , p . mock . init ) : Utils . formatValue ( p . string , p . value ) ) ; Object . assign ( payload , o ) ; } this . updateSensorValue ( p , propertySensorTimers , res . process ) ; } return payload ; } async updateSensorValue ( p , propertySensorTimers , process ) { if ( p . mock === undefined ) { return ; } let slice = 0 ; let randomFromRange = Utils . getRandomNumberBetweenRange ( 1 , 10 , false ) ; if ( propertySensorTimers [ p . _id ] ) { slice = propertySensorTimers [ p . _id ] . sliceMs ; let sliceRemaining = propertySensorTimers [ p . _id ] . remainingMs - 1000 ; if ( sliceRemaining > 0 ) { propertySensorTimers [ p . _id ] . remaining = sliceRemaining ; } else { delete propertySensorTimers [ p . _id ] ; } } else { slice = p . mock . _value ; } if ( p . mock . _type === 'fan' ) { var variance = p . mock . variance / p . mock . running * 100 ; p . mock . _value = randomFromRange >= 5 ? p . mock . running - variance : p . mock . running + variance ; } if ( p . mock . _type === 'hotplate' ) { if ( p . mock . reset && Utils . isNumeric ( p . mock . reset ) && Utils . formatValue ( false , p . mock . reset ) === p . mock . _value ) { p . mock . _value = Utils . formatValue ( false , p . mock . init ) ; } else { var newCurrent = p . mock . _value + ( slice - ( slice * p . mock . variance ) ) ; p . mock . _value = newCurrent <= p . mock . running ? newCurrent : p . mock . running ; } } if ( p . mock . _type === 'battery' ) { if ( p . mock . reset && Utils . isNumeric ( p . mock . reset ) && Utils . formatValue ( false , p . mock . reset ) === p . mock . _value ) { p . mock . _value = Utils . formatValue ( false , p . mock . init ) ; } else { var newCurrent = p . mock . _value - ( slice + ( slice * p . mock . variance ) ) ; p . mock . _value = newCurrent > p . mock . running ? newCurrent : p . mock . running ; } } if ( p . mock . _type === 'random' ) { p . mock . _value = Math . floor ( Math . random ( ) * Math . floor ( Math . pow ( 10 , p . mock . variance ) + 1 ) ) ; } if ( p . mock . _type === 'function' && process ) { const res = await this . getFunctionPost ( p . mock . function , p . mock . _value ) ; p . mock . _value = res ; } if ( p . mock . _type === 'inc' && process ) { const inc = p . mock . variance && Utils . isNumeric ( p . mock . variance ) ? Utils . formatValue ( false , p . mock . variance ) : 1 ; if ( p . mock . reset && Utils . isNumeric ( p . mock . reset ) && Utils . formatValue ( false , p . mock . reset ) === p . mock . _value ) { p . mock . _value = Utils . formatValue ( false , p . mock . init ) ; } else { p . mock . _value = p . mock . _value + inc ; } } if ( p . mock . _type === 'dec' && process ) { const dec = p . mock . variance && Utils . isNumeric ( p . mock . variance ) ? Utils . formatValue ( false , p . mock . variance ) : 1 ; if ( p . mock . reset && Utils . isNumeric ( p . mock . reset ) && Utils . formatValue ( false , p . mock . reset ) === p . mock . _value ) { p . mock . _value = Utils . formatValue ( false , p . mock . init ) ; } else { p . mock . _value = p . mock . _value - dec ; } } } getFunctionPost ( url , value ) { return new Promise ( ( resolve , reject ) => { try { request . post ( { headers : { 'content-type' : 'application/json' } , url : url , body : JSON . stringify ( { 'value' : value } ) } , ( err , response , body ) => { if ( err ) { this . log ( `FUNCTION ERROR: ${ err . toString ( ) }` , LOGGING_TAGS . DATA . FNC , '' , LOGGING_TAGS . DATA . SEND ) ; reject ( err ) ; } else { let payload = JSON . parse ( body ) ; resolve ( payload . value ? parseFloat ( payload . value ) : payload . body ) ; } } ) ; } catch ( err ) { this . log ( `FUNCTION FAILED: ${ err . toString ( ) }` , LOGGING_TAGS . DATA . FNC , '' , LOGGING_TAGS . DATA . SEND ) ; reject ( err ) ; } } ) ; } processCountdown ( p , timeRemain , originalPlanTime , startUpList ) { let res = { } ; if ( timeRemain != 0 ) { timeRemain = timeRemain - 1000 ; res . process = false ; if ( startUpList . indexOf ( p . _id ) > - 1 ) { res . timeRemain = timeRemain ; res . process = true ; return res ; } } if ( timeRemain === 0 ) { if ( this . device . configuration . planMode ) { timeRemain = this . device . plan . loop ? originalPlanTime : - 1 ; } else { timeRemain = p . runloop . _ms ; } res . process = true ; } res . timeRemain = timeRemain ; return res ; } transformPayload ( payload ) { let remap = { package : { } , legacy : { } , live : { } } ; for ( const item in payload ) { const index = this . resolversCollection . idPropToCommIndex [ item ] ; const p = this . device . comms [ index ] ; if ( p && p . propertyObject ) { var val = p . string ? "\"" + payload [ p . _id ] + "\"" : payload [ p . _id ] ; var component = p . component && p . component . enabled ? p . component . name : null ; if ( component && ! remap . package [ component ] ) { remap . package [ component ] = { } ; } if ( ! component && ! remap . package [ "_root" ] ) { remap . package [ "_root" ] = { } ; } if ( this . plugIn ) { const val = p . propertyObject . type === 'templated' ? JSON . parse ( p . propertyObject . template ) : payload [ p . _id ] ; const res = this . plugIn . propertyResponse ( this . device . configuration . deviceId , p , val ) ; if ( res !== undefined ) { remap . legacy [ p . name ] = res ; remap . live [ p . _id ] = res ; remap . package [ component ? component : '_root' ] [ p . name ] = res ; continue ; } } if ( this . desiredOverrides [ p . _id ] ) { const sendOverrides = this . desiredOverrides [ p . _id ] ; let override = null ; try { override = JSON . parse ( sendOverrides . payload ) ; if ( sendOverrides . convention && ( typeof sendOverrides . value === 'object' && sendOverrides . value !== null ) ) { Object . assign ( override , sendOverrides . value ) ; } this . resolveRandom ( override , sendOverrides ) ; } catch ( err ) { override = this . resolveAuto ( sendOverrides . payload , sendOverrides ) ; } remap . legacy [ p . name ] = override ; remap . live [ p . _id ] = override ; remap . package [ component ? component : '_root' ] [ p . name ] = override ; delete this . desiredOverrides [ p . _id ] ; } else if ( p . propertyObject . type === 'templated' ) { try { var replacement = p . propertyObject . template . replace ( new RegExp ( /\"AUTO_VALUE\"/ , 'g' ) , val ) ; var object = JSON . parse ( replacement ) ; this . resolveRandom ( object ) ; remap . legacy [ p . name ] = object ; remap . live [ p . _id ] = object ; remap . package [ component ? component : '_root' ] [ p . name ] = object ; } catch ( ex ) { const err = 'ERR - transformPayload: ' + ex ; remap . legacy [ p . name ] = err ; remap . live [ p . _id ] = err ; remap . package [ component ? component : '_root' ] [ p . name ] = err ; } } else { const resolve = this . resolveAuto ( payload [ p . _id ] ) ; ; remap . legacy [ p . name ] = resolve ; remap . live [ p . _id ] = resolve ; remap . package [ component ? component : '_root' ] [ p . name ] = resolve ; } } else { const resolve = this . resolveAuto ( payload [ p . _id ] ) ; ; remap . legacy [ p . name ] = resolve ; remap . live [ p . _id ] = resolve ; remap . package [ component ? component : '_root' ] [ p . name ] = resolve ; } } return remap ; } resolveAuto ( macro , parameters ? ) { if ( macro === 'AUTO_STRING' ) { return rw ( ) ; } else if ( macro === 'AUTO_BOOLEAN' ) { return Utils . getRandomValue ( 'boolean' ) ; } else if ( macro === 'AUTO_INTEGER' || macro === 'AUTO_LONG' ) { return Utils . getRandomValue ( 'integer' , this . ranges [ 'AUTO_INTEGER' ] [ 'min' ] , this . ranges [ 'AUTO_INTEGER' ] [ 'max' ] ) ; } else if ( macro === 'AUTO_DOUBLE' || ( macro === 'AUTO_FLOAT' ) ) { return Utils . getRandomValue ( 'double' , this . ranges [ 'AUTO_DOUBLE' ] [ 'min' ] , this . ranges [ 'AUTO_DOUBLE' ] [ 'max' ] ) ; } else if ( macro === 'AUTO_DATE' ) { return Utils . getRandomValue ( 'date' ) ; } else if ( macro === 'AUTO_DATETIME' ) { return Utils . getRandomValue ( 'dateTime' ) ; } else if ( macro === 'AUTO_TIME' || macro === 'AUTO_DURATION' ) { return Utils . getRandomValue ( 'time' ) ; } else if ( macro === 'AUTO_GEOPOINT' ) { return Utils . getRandomGeo ( this . geo [ 'latitude' ] , this . geo [ 'longitude' ] , this . geo [ 'altitude' ] , this . geo [ 'radius' ] ) ; } else if ( macro === 'AUTO_VECTOR' ) { return Utils . getRandomVector ( this . ranges [ 'AUTO_VECTOR' ] [ 'min' ] , this . ranges [ 'AUTO_VECTOR' ] [ 'max' ] ) ; } else if ( macro === 'AUTO_MAP' ) { return Utils . getRandomMap ( ) ; } else if ( macro && macro . toString ( ) . startsWith ( 'AUTO_ENUM' ) ) { let parts = macro . split ( '/' ) ; if ( parts . length === 2 && parts [ 0 ] === 'AUTO_ENUM' ) { let arr = JSON . parse ( parts [ 1 ] ) ; const index = Utils . getRandomNumberBetweenRange ( 0 , arr . length , true ) ; return arr [ index ] ; } } else if ( macro === 'DESIRED_VALUE' ) { return parameters . value ; } else if ( macro === 'DESIRED_VERSION' ) { return parameters . version ; } return macro ; } resolveRandom ( node , parameters ? ) { for ( let key in node ) { if ( typeof node [ key ] == 'object' ) { this . resolveRandom ( node [ key ] , parameters ) ; } else { node [ key ] = this . resolveAuto ( node [ key ] , parameters ) ; } } } log ( message , type , operation , direction ? , submsg ? ) { let msg = `[${ type }][${ operation }][${ this . device . _id }]` ; if ( direction ) { msg += `[${ direction }]` ; } if ( submsg ) { msg += `[${ submsg }]` ; } this . messageService . sendConsoleUpdate ( `${ msg } ${ message }` ) ; } logCP ( type , operation , event ) { this . previousControlStatus = this . currentControlStatus ; this . messageService . sendAsControlPlane ( { [ this . device . _id ] : [ type , operation , event ] } ) ; this . currentControlStatus = event ; } logStat ( type ) { const parts = type . split ( '_' ) ; const update = parts . length === 2 ? parts [ 0 ] : 'METER' ; const date = new Date ( ) . toISOString ( ) ; if ( update === 'MSG' && ! this . stats . MSG_FIRST ) { this . stats . MSG_FIRST = date ; } if ( update === 'TWIN' && ! this . stats . TWIN_FIRST ) { this . stats . TWIN_FIRST = date ; } if ( update === 'MSG' ) { this . stats . MSG_LAST = date ; } if ( update === 'TWIN' ) { this . stats . TWIN_LAST = date ; } this . stats [ type ] = this . stats [ type ] + 1 ; if ( this . stats . MSG_FIRST && this . stats . MSG_LAST && update === 'MSG' ) { const s = Date . parse ( this . stats . MSG_FIRST ) ; const f = Date . parse ( this . stats . MSG_LAST ) ; this . stats [ LOGGING_TAGS . STAT . MSG . RATE ] = ( this . stats [ type ] / ( Math . round ( ( f - s ) / 60000 ) ) ) . toFixed ( 2 ) ; } if ( this . stats . TWIN_FIRST && this . stats . TWIN_LAST && update === 'TWIN' ) { const s = Date . parse ( this . stats . TWIN_FIRST ) ; const f = Date . parse ( this . stats . TWIN_LAST ) ; this . stats [ LOGGING_TAGS . STAT . TWIN . RATE ] = ( this . stats [ type ] / ( Math . round ( ( f - s ) / 60000 ) ) ) . toFixed ( 2 ) ; } this . messageService . sendAsStats ( { [ this . device . _id ] : this . stats } ) ; } }