import type { SendOptions } from '../client' ; import Device , { isPlugSysinfo } from '../device' ; import type { CommonSysinfo , DeviceConstructorOptions , Sysinfo , } from '../device' ; import Away from './away' ; import Cloud , { isCloudInfo } from '../shared/cloud' ; import type { CloudInfo } from '../shared/cloud' ; import Dimmer from './dimmer' ; import Emeter , { RealtimeNormalized } from '../shared/emeter' ; import Schedule from './schedule' ; import Timer from './timer' ; import Time from '../shared/time' ; import { extractResponse , hasErrCode , HasErrCode , isDefinedAndNotNull , isObjectLike , ResponseError , } from '../utils' ; type PlugChild = { id : string ; alias : string ; state : number ; } ; type SysinfoChildren = { children ? : [ { id : string ; alias : string ; state : number ; } ] ; } ; export type PlugSysinfo = CommonSysinfo & SysinfoChildren & ( { type : 'IOT.SMARTPLUGSWITCH' | 'IOT.RANGEEXTENDER.SMARTPLUG' ; } | { mic_type : 'IOT.SMARTPLUGSWITCH' ; } ) & ( { mac : string ; } | { ethernet_mac : string ; } ) & { feature : string ; led_off : 0 | 1 ; relay_state ? : 0 | 1 ; dev_name ? : string ; brightness ? : number ; } ; export function hasSysinfoChildren ( candidate ) { return ( isObjectLike ( candidate ) && 'children' in candidate && candidate . children !== undefined && isObjectLike ( candidate . children ) && candidate . children . length > 0 ) ; } export interface PlugConstructorOptions extends DeviceConstructorOptions { sysInfo : PlugSysinfo ; defaultValue inUseThreshold ? : number ; /**
     * If passed a string between 0 and 99 it will prepend the deviceId
     */ childId ? : string ; } export interface PlugEventEmitter { event on ( event , listener ) : this ; deprecated on ( event , listener ) : this ; /**
     * Plug's relay was turned on.
     */ on ( event , listener ) : this ; /**
     * Plug's relay was turned off.
     */ on ( event , listener ) : this ; /**
     * Plug's relay state was updated from device. Fired regardless if status was changed.
     */ on ( event , listener ) : this ; /**
     * Plug's relay was turned on _or_ power draw exceeded `inUseThreshold`
     */ on ( event , listener ) : this ; /**
     * Plug's relay was turned off _or_ power draw fell below `inUseThreshold`
     */ on ( event , listener ) : this ; /**
     * Plug's in-use state was updated from device. Fired regardless if status was changed.
     */ on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; emit ( event , value ) : boolean ; emit ( event , error ) : boolean ; emit ( event ) : boolean ; emit ( event ) : boolean ; emit ( event , value ) : boolean ; emit ( event ) : boolean ; emit ( event ) : boolean ; emit ( event , value ) : boolean ; emit ( event , value ) : boolean ; emit ( event , value ) : boolean ; } /**
 * Plug Device.
 *
 * TP-Link models: HS100, HS105, HS107, HS110, HS200, HS210, HS220, HS300.
 *
 * Models with multiple outlets (HS107, HS300) will have a children property.
 * If Plug is instantiated with a childId it will control the outlet associated with that childId.
 * Some functions only apply to the entire device, and are noted below.
 *
 * Emits events after device status is queried, such as  {@link #getSysInfo} and  {@link #getEmeterRealtime} .
 *  extends Device extends EventEmitter fires fires fires fires fires fires fires export default class Plug extends Device implements PlugEventEmitter { protected _sysInfo : PlugSysinfo ; #children : Map < string , PlugChild > = new Map ( ) ; #child ? : PlugChild ; #childId ? : string ; inUseThreshold = 0.1 ; emitEventsEnabled = true ; internal lastState = { inUse : false , relayState : false } ; static readonly apiModules = { system : 'system' , cloud : 'cnCloud' , schedule : 'schedule' , timesetting : 'time' , emeter : 'emeter' , netif : 'netif' , lightingservice : '' , } ; away : Away ; cloud : Cloud ; dimmer : Dimmer ; emeter : Emeter ; schedule : Schedule ; time : Time ; timer : Timer ; /**
     * Created by  Client - Do not instantiate directly.
     *
     * See [Device constructor] Device for common options.
      constructor ( options ) { super ( { client : options . client , _sysInfo : options . sysInfo , host : options . host , port : options . port , logger : options . logger , defaultSendOptions : options . defaultSendOptions , } ) ; const { sysInfo , inUseThreshold = 0.1 , childId } = options ; this . log . debug ( 'plug.constructor()' ) ; borrows borrows borrows borrows borrows borrows this . away = new Away ( this , 'anti_theft' , this . #childId ) ; borrows borrows borrows borrows borrows this . cloud = new Cloud ( this , 'cnCloud' ) ; borrows borrows borrows borrows borrows borrows borrows borrows borrows borrows borrows this . dimmer = new Dimmer ( this , 'smartlife.iot.dimmer' ) ; borrows borrows borrows borrows borrows this . emeter = new Emeter ( this , 'emeter' , this . #childId ) ; borrows borrows borrows borrows borrows borrows borrows borrows borrows borrows borrows this . schedule = new Schedule ( this , 'schedule' , this . #childId ) ; borrows borrows this . time = new Time ( this , 'time' ) ; borrows borrows borrows borrows this . timer = new Timer ( this , 'count_down' , this . #childId ) ; this . _sysInfo = sysInfo ; this . setSysInfo ( sysInfo ) ; this . inUseThreshold = inUseThreshold ; if ( isDefinedAndNotNull ( childId ) ) this . setChildId ( childId ) ; if ( this . sysInfo ) { this . lastState . inUse = this . inUse ; this . lastState . relayState = this . relayState ; } } get sysInfo ( ) : PlugSysinfo { return this . _sysInfo ; } internal setSysInfo ( sysInfo ) { super . setSysInfo ( sysInfo ) ; if ( sysInfo . children ) { this . setChildren ( sysInfo . children ) ; } if ( sysInfo . brightness !== undefined ) { this . dimmer . setBrightnessValue ( sysInfo . brightness ) ; } this . log . debug ( '[%s] plug sysInfo set' , this . alias ) ; this . emitEvents ( ) ; } /**
     * Returns children as a map keyed by childId. From cached results from last retrieval of `system.sysinfo.children`.
     */ get children ( ) : Map < string , PlugChild > { return this . #children ; } private setChildren ( children ) { if ( Array . isArray ( children ) ) { this . #children = new Map ( children . map ( ( child ) => { child . id = this . normalizeChildId ( child . id ) ; return [ child . id , child ] ; } ) ) ; } else if ( children instanceof Map ) { this . #children = children ; } if ( this . #childId && this . #children ) { if ( this . #childId !== undefined ) this . setChildId ( this . #childId ) ; } } /**
     * Returns childId.
     */ get childId ( ) : string | undefined { return this . #childId ; } private setChildId ( childId ) { this . #childId = this . normalizeChildId ( childId ) ; if ( this . #childId && this . #children ) { this . #child = this . #children . get ( this . #childId ) ; } if ( this . #childId && this . #child == null ) { throw new Error ( `Could not find child with childId ${ childId }` ) ; } } /**
     * Cached value of `sysinfo.alias` or `sysinfo.children[childId].alias` if childId set.
     */ get alias ( ) : string { if ( this . #childId && this . #child !== undefined ) { return this . #child . alias ; } if ( this . sysInfo === undefined ) return '' ; return this . sysInfo . alias ; } protected setAliasProperty ( alias ) { if ( this . #childId && this . #child !== undefined ) { this . #child . alias = alias ; } this . sysInfo . alias = alias ; } /**
     * Cached value of `sysinfo.dev_name`.
     */ get description ( ) : string | undefined { return this . sysInfo . dev_name ; } get deviceType ( ) : 'plug' { return 'plug' ; } /**
     * Cached value of `sysinfo.deviceId` or `childId` if set.
     */ get id ( ) : string { if ( this . #childId && this . #child !== undefined ) { return this . #childId ; } return this . sysInfo . deviceId ; } /**
     * Determines if device is in use based on cached `emeter.get_realtime` results.
     *
     * If device supports energy monitoring (e.g. HS110): `power > inUseThreshold`. `inUseThreshold` is specified in Watts
     *
     * Otherwise fallback on relay state: `relay_state === 1` or `sysinfo.children[childId].state === 1`.
     *
     * Supports childId.
     */ get inUse ( ) : boolean { if ( this . supportsEmeter && 'power' in this . emeter . realtime && this . emeter . realtime . power !== undefined ) { return this . emeter . realtime . power > this . inUseThreshold ; } return this . relayState ; } returns get relayState ( ) : boolean { if ( this . #childId && this . #child !== undefined ) { return this . #child . state === 1 ; } if ( this . #children && this . #children . size > 0 ) { return ( Array . from ( this . #children . values ( ) ) . findIndex ( ( child ) => { return child . state === 1 ; } ) !== - 1 ) ; } return this . sysInfo . relay_state === 1 ; } protected setRelayState ( relayState ) { if ( this . #childId && this . #child !== undefined ) { this . #child . state = relayState ? 1 : 0 ; return ; } if ( this . #children && this . #children . size > 0 ) { for ( const child of this . #children . values ( ) ) { child . state = relayState ? 1 : 0 ; } return ; } this . sysInfo . relay_state = relayState ? 1 : 0 ; } returns get supportsDimmer ( ) : boolean { return 'brightness' in this . sysInfo ; } returns get supportsEmeter ( ) : boolean { return this . sysInfo . feature && typeof this . sysInfo . feature === 'string' ? this . sysInfo . feature . indexOf ( 'ENE' ) >= 0 : false ; } /**
     * Gets plug's SysInfo.
     *
     * Requests `system.sysinfo` from device. Does not support childId.
  
     */ async getSysInfo ( sendOptions ? ) { const response = await super . getSysInfo ( sendOptions ) ; if ( ! isPlugSysinfo ( response ) ) { throw new Error ( `Unexpected Response: ${ response }` ) ; } return this . sysInfo ; } returns throws ResponseError async getInfo ( sendOptions ? ) { const sendOptionsForGetInfo = sendOptions == null ? { } : sendOptions ; if ( ! ( 'transport' in sendOptionsForGetInfo ) ) sendOptionsForGetInfo . transport = 'tcp' ; let data ; try { data = await this . sendCommand ( '{"emeter":{"get_realtime":{}},"schedule":{"get_next_action":{}},"system":{"get_sysinfo":{}},"cnCloud":{"get_info":{}}}' , this . #childId , sendOptionsForGetInfo ) ; } catch ( err ) { if ( err instanceof ResponseError && err . modules . length === 1 && err . modules [ 0 ] === 'emeter' ) { data = JSON . parse ( err . response ) ; } else { throw err ; } } const sysinfo = extractResponse ( data , 'system.get_sysinfo' , isPlugSysinfo ) as PlugSysinfo ; this . setSysInfo ( sysinfo ) ; const cloudInfo = extractResponse < CloudInfo & HasErrCode > ( data , 'cnCloud.get_info' , ( c ) => isCloudInfo ( c ) && hasErrCode ( c ) ) ; this . cloud . info = cloudInfo ; if ( isObjectLike ( data ) && 'emeter' in data && isObjectLike ( data . emeter ) && 'get_realtime' in data . emeter && isObjectLike ( data . emeter . get_realtime ) ) { this . emeter . setRealtime ( data . emeter . get_realtime ) ; } const scheduleNextAction = extractResponse ( data , 'schedule.get_next_action' , hasErrCode ) as HasErrCode ; this . schedule . nextAction = scheduleNextAction ; return { sysInfo : this . sysInfo , cloud : { info : this . cloud . info } , emeter : { realtime : this . emeter . realtime } , schedule : { nextAction : this . schedule . nextAction } , } ; } /**
     * Same as  {@link #inUse} , but requests current `emeter.get_realtime`. Supports childId.
     *  returns throws ResponseError async getInUse ( sendOptions ? ) { if ( this . supportsEmeter ) { await this . emeter . getRealtime ( sendOptions ) ; } else { await this . getSysInfo ( sendOptions ) ; } return this . inUse ; } param { SendOptions } sendOptions returns throws ResponseError async getLedState ( sendOptions ? ) { const sysInfo = await this . getSysInfo ( sendOptions ) ; return sysInfo . led_off === 0 ; } param value throws ResponseError async setLedState ( value , sendOptions ? ) { await this . sendCommand ( `{"system":{"set_led_off":{"off":${ value ? 0 : 1 }}}}` , undefined , sendOptions ) ; this . sysInfo . led_off = value ? 0 : 1 ; return true ; } /**
     * Get Plug relay state (on/off).
     *
     * Requests `system.get_sysinfo` and returns true if On. Calls  {@link #relayState} . Supports childId.
     *  throws ResponseError async getPowerState ( sendOptions ? ) { await this . getSysInfo ( sendOptions ) ; return this . relayState ; } throws ResponseError async setPowerState ( value , sendOptions ? ) { await this . sendCommand ( `{"system":{"set_relay_state":{"state":${ value ? 1 : 0 }}}}` , this . #childId , sendOptions ) ; this . setRelayState ( value ) ; this . emitEvents ( ) ; return true ; } throws ResponseError async togglePowerState ( sendOptions ? ) { const powerState = await this . getPowerState ( sendOptions ) ; await this . setPowerState ( ! powerState , sendOptions ) ; return ! powerState ; } throws ResponseError async blink ( times = 5 , rate = 1000 , sendOptions ? ) { const delay = ( t ) : Promise < void > => { return new Promise ( ( resolve ) => { setTimeout ( resolve , t ) ; } ) ; } ; const origLedState = await this . getLedState ( sendOptions ) ; let lastBlink ; let currLedState = false ; for ( let i = 0 ; i < times * 2 ; i += 1 ) { currLedState = ! currLedState ; lastBlink = Date . now ( ) ; await this . setLedState ( currLedState , sendOptions ) ; const timeToWait = rate / 2 - ( Date . now ( ) - lastBlink ) ; if ( timeToWait > 0 ) { await delay ( timeToWait ) ; } } if ( currLedState !== origLedState ) { await this . setLedState ( origLedState , sendOptions ) ; } return true ; } private emitEvents ( ) { if ( ! this . emitEventsEnabled ) { return ; } const { inUse , relayState } = this ; this . log . debug ( '[%s] plug.emitEvents() inUse: %s relayState: %s lastState: %j' , this . alias , inUse , relayState , this . lastState ) ; if ( this . lastState . inUse !== inUse ) { this . lastState . inUse = inUse ; if ( inUse ) { this . emit ( 'in-use' ) ; } else { this . emit ( 'not-in-use' ) ; } } this . emit ( 'in-use-update' , inUse ) ; if ( this . lastState . relayState !== relayState ) { this . lastState . relayState = relayState ; if ( relayState ) { this . emit ( 'power-on' ) ; } else { this . emit ( 'power-off' ) ; } } this . emit ( 'power-update' , relayState ) ; if ( this . supportsDimmer ) { this . dimmer . emitEvents ( ) ; } } }