import { API , APIEvent , CharacteristicSetCallback , CharacteristicValue , DynamicPlatformPlugin , HAP , Logging , PlatformAccessory , PlatformAccessoryEvent , PlatformConfig } from 'homebridge' ; import { AirClient , HttpClient , CoapClient , PlainCoapClient , HttpClientLegacy } from 'philips-air' ; import { promisify } from 'util' ; import { exec } from 'child_process' ; import * as fs from 'fs' ; import timestamp from 'time-stamp' ; import localStorage from 'node-sessionstorage' ; import { PhilipsAirPlatformConfig , DeviceConfig } from './configTypes' ; import { PurifierStatus , PurifierFilters , PurifierFirmware } from './deviceTypes' ; let hap ; let Accessory ; const PLUGIN_NAME = 'homebridge-philips-air' ; const PLATFORM_NAME = 'philipsAir' ; const pathToModule = require . resolve ( PLUGIN_NAME ) ; const pathTopyaircontrol = pathToModule . replace ( 'dist/index.js' , 'node_modules/philips-air/pyaircontrol.py' ) ; const pathToSensorFiles = pathToModule . replace ( 'dist/index.js' , 'sensor/' ) ; enum CommandType { Polling = 0 , GetFirmware , GetFilters , GetStatus , SetData } type Command = { purifier : Purifier ; type : CommandType ; callback ? : ( error ? : Error | null | undefined ) => void ; data ? : any ; } ; type Purifier = { accessory : PlatformAccessory ; client : AirClient ; config : DeviceConfig ; timeout ? : NodeJS . Timeout ; lastfirmware ? : number ; lastfilters ? : number ; laststatus ? : number ; aqil ? : number ; uil ? : string ; rh ? : number ; rhset ? : number ; func ? : string ; } ; class PhilipsAirPlatform implements DynamicPlatformPlugin { private readonly log : Logging ; private readonly api : API ; private readonly config : PhilipsAirPlatformConfig ; private readonly timeout : number ; private readonly cachedAccessories : Array < PlatformAccessory > = [ ] ; private readonly purifiers : Map < string , Purifier > = new Map ( ) ; private readonly commandQueue : Array < Command > = [ ] ; private queueRunning = false ; enqueuePromise = promisify ( this . enqueueCommand ) ; constructor ( log , config , api ) { this . log = log ; this . config = config as unknown as PhilipsAirPlatformConfig ; this . api = api ; this . timeout = ( this . config . timeout_seconds || 5 ) * 1000 ; api . on ( APIEvent . DID_FINISH_LAUNCHING , this . didFinishLaunching . bind ( this ) ) ; } configureAccessory ( accessory ) { this . cachedAccessories . push ( accessory ) ; } didFinishLaunching ( ) { const ips = [ ] ; this . config . devices . forEach ( ( device ) => { this . addAccessory ( device ) ; const uuid = hap . uuid . generate ( device . ip ) ; ips . push ( uuid ) ; } ) ; const badAccessories = [ ] ; this . cachedAccessories . forEach ( cachedAcc => { if ( ! ips . includes ( cachedAcc . UUID ) ) { badAccessories . push ( cachedAcc ) ; } } ) ; this . removeAccessories ( badAccessories ) ; this . purifiers . forEach ( ( purifier ) => { this . enqueueCommand ( CommandType . Polling , purifier ) ; this . enqueueCommand ( CommandType . GetFirmware , purifier ) ; this . enqueueCommand ( CommandType . GetStatus , purifier ) ; this . enqueueCommand ( CommandType . GetFilters , purifier ) ; } ) ; } async storeKey ( purifier ) { if ( purifier . client && purifier . client instanceof HttpClient ) { purifier . accessory . context . key = ( purifier . client as HttpClient ) . key ; } } async setData ( purifier , values , callback ? ) { try { await purifier . client ?. setValues ( values ) ; await this . storeKey ( purifier ) ; if ( callback ) { callback ( ) ; } } catch ( err ) { if ( callback ) { callback ( err ) ; } } } async updatePolling ( purifier ) { try { let polling = purifier . config . polling || 60 ; if ( polling < 60 ) { polling = 60 ; } setInterval ( function ( ) { exec ( 'python3 ' + pathTopyaircontrol + ' --ipaddr ' + purifier . config . ip + ' --protocol ' + purifier . config . protocol + ' --status' , ( error , stdout , stderr ) => { if ( error || stderr ) { console . log ( timestamp ( '[DD.MM.YYYY, HH:mm:ss] ' ) + '\x1b[36m[Philips Air] \x1b[31m[' + purifier . config . name + '] Unable to get data for polling: Error: spawnSync python3 ETIMEDOUT.\x1b[0m' ) ; console . log ( timestamp ( '[DD.MM.YYYY, HH:mm:ss] ' ) + '\x1b[33mIf your have "Error: spawnSync python3 ETIMEDOUT" your need unplug the accessory from outlet for 10 seconds and plug again.\x1b[0m' ) ; } if ( error || stderr || error && stderr ) { stdout = { om : localStorage . getItem ( 'om' ) , pwr : localStorage . getItem ( 'pwr' ) , cl : false , aqil : localStorage . getItem ( 'aqil' ) , uil : localStorage . getItem ( 'uil' ) , dt : 0 , dtrs : 0 , mode : localStorage . getItem ( 'mode' ) , func : localStorage . getItem ( 'func' ) , rhset : localStorage . getItem ( 'rhset' ) , 'rh' : localStorage . getItem ( 'rh' ) , 'temp' : localStorage . getItem ( 'temp' ) , pm25 : localStorage . getItem ( 'pm25' ) , iaql : localStorage . getItem ( 'iaql' ) , aqit : 4 , ddp : '1' , rddp : localStorage . getItem ( 'rddp' ) , err : 0 , wl : localStorage . getItem ( 'wl' ) , fltt1 : localStorage . getItem ( 'fltt1' ) , fltt2 : localStorage . getItem ( 'fltt2' ) , fltsts0 : localStorage . getItem ( 'fltsts0' ) , fltsts1 : localStorage . getItem ( 'fltsts1' ) , fltsts2 : localStorage . getItem ( 'fltsts2' ) , wicksts : localStorage . getItem ( 'wicksts' ) } ; stdout = JSON . stringify ( stdout ) ; } const obj = JSON . parse ( stdout ) ; if ( ! error || ! stderr || ! error && ! stderr ) { localStorage . setItem ( 'pwr' , obj . pwr ) ; localStorage . setItem ( 'om' , obj . om ) ; localStorage . setItem ( 'aqil' , obj . aqil ) ; localStorage . setItem ( 'uil' , obj . uil ) ; localStorage . setItem ( 'mode' , obj . mode ) ; localStorage . setItem ( 'func' , obj . func ) ; localStorage . setItem ( 'rhset' , obj . rhset ) ; localStorage . setItem ( 'iaql' , obj . iaql ) ; localStorage . setItem ( 'pm25' , obj . pm25 ) ; localStorage . setItem ( 'rh' , obj . rh ) ; localStorage . setItem ( 'temp' , obj . temp ) ; localStorage . setItem ( 'rddp' , obj . rddp ) ; localStorage . setItem ( 'wl' , obj . wl ) ; localStorage . setItem ( 'fltt1' , obj . fltt1 ) ; localStorage . setItem ( 'fltt2' , obj . fltt2 ) ; localStorage . setItem ( 'fltsts0' , obj . fltsts0 ) ; localStorage . setItem ( 'fltsts1' , obj . fltsts1 ) ; localStorage . setItem ( 'fltsts2' , obj . fltsts2 ) ; localStorage . setItem ( 'wicksts' , obj . wicksts ) ; } else { localStorage . setItem ( 'pwr' , 1 ) ; localStorage . setItem ( 'om' , '0' ) ; localStorage . setItem ( 'aqil' , '0' ) ; localStorage . setItem ( 'uil' , 0 ) ; localStorage . setItem ( 'mode' , 'A' ) ; localStorage . setItem ( 'func' , 'PH' ) ; localStorage . setItem ( 'rhset' , 50 ) ; localStorage . setItem ( 'iaql' , 1 ) ; localStorage . setItem ( 'pm25' , 1 ) ; localStorage . setItem ( 'rh' , 45 ) ; localStorage . setItem ( 'temp' , 25 ) ; localStorage . setItem ( 'rddp' , 1 ) ; localStorage . setItem ( 'wl' , 100 ) ; localStorage . setItem ( 'fltt1' , 'A3' ) ; localStorage . setItem ( 'fltt2' , 'C7' ) ; localStorage . setItem ( 'fltsts0' , 287 ) ; localStorage . setItem ( 'fltsts1' , 2553 ) ; localStorage . setItem ( 'fltsts2' , 2553 ) ; localStorage . setItem ( 'wicksts' , 4005 ) ; } const purifierService = purifier . accessory . getService ( hap . Service . AirPurifier ) ; if ( purifierService ) { const state = parseInt ( obj . pwr ) * 2 ; purifierService . updateCharacteristic ( hap . Characteristic . Active , obj . pwr ) . updateCharacteristic ( hap . Characteristic . CurrentAirPurifierState , state ) . updateCharacteristic ( hap . Characteristic . LockPhysicalControls , obj . cl ) ; } const qualityService = purifier . accessory . getService ( hap . Service . AirQualitySensor ) ; if ( qualityService ) { const iaql = Math . ceil ( obj . iaql / 3 ) ; qualityService . updateCharacteristic ( hap . Characteristic . AirQuality , iaql ) . updateCharacteristic ( hap . Characteristic . PM2_5Density , obj . pm25 ) ; } if ( purifier . config . temperature_sensor ) { const temperature_sensor = purifier . accessory . getService ( 'Temperature' ) ; if ( temperature_sensor ) { temperature_sensor . updateCharacteristic ( hap . Characteristic . CurrentTemperature , obj . temp ) ; } } if ( purifier . config . humidity_sensor ) { const humidity_sensor = purifier . accessory . getService ( 'Humidity' ) ; if ( humidity_sensor ) { humidity_sensor . updateCharacteristic ( hap . Characteristic . CurrentRelativeHumidity , obj . rh ) ; } } if ( purifier . config . light_control ) { const lightsService = purifier . accessory . getService ( 'Lights' ) ; if ( obj . pwr == '1' ) { if ( lightsService ) { lightsService . updateCharacteristic ( hap . Characteristic . On , obj . aqil > 0 ) . updateCharacteristic ( hap . Characteristic . Brightness , obj . aqil ) ; } } } if ( purifier . config . humidifier ) { let water_level = 100 ; let speed_humidity = 0 ; if ( obj . func == 'PH' && obj . wl == 0 ) { water_level = 0 ; } if ( obj . pwr == '1' ) { if ( obj . func == 'PH' && water_level == 100 ) { if ( obj . rhset == 40 ) { speed_humidity = 25 ; } else if ( obj . rhset == 50 ) { speed_humidity = 50 ; } else if ( obj . rhset == 60 ) { speed_humidity = 75 ; } else if ( obj . rhset == 70 ) { speed_humidity = 100 ; } } } const Humidifier = purifier . accessory . getService ( 'Humidifier' ) ; if ( Humidifier ) { Humidifier . updateCharacteristic ( hap . Characteristic . CurrentRelativeHumidity , obj . rh ) . updateCharacteristic ( hap . Characteristic . WaterLevel , water_level ) . updateCharacteristic ( hap . Characteristic . TargetHumidifierDehumidifierState , 1 ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , speed_humidity ) ; if ( water_level == 0 ) { if ( obj . func != 'P' ) { exec ( 'airctrl --ipaddr ' + purifier . config . ip + ' --protocol ' + purifier . config . protocol + ' --func P' , ( error , stdout , stderr ) => { if ( error || stderr ) { console . log ( timestamp ( '[DD.MM.YYYY, HH:mm:ss] ' ) + '\x1b[36m[Philips Air] \x1b[31m[' + purifier . config . name + '] Unable to get data for polling: Error: spawnSync python3 ETIMEDOUT.\x1b[0m' ) ; console . log ( timestamp ( '[DD.MM.YYYY, HH:mm:ss] ' ) + '\x1b[33mIf your have "Error: spawnSync python3 ETIMEDOUT" your need unplug the accessory from outlet for 10 seconds and plug again.\x1b[0m' ) ; } } ) ; } Humidifier . updateCharacteristic ( hap . Characteristic . Active , 0 ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , 0 ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , 0 ) ; } } } if ( purifier . config . logger ) { if ( purifier . config . temperature_sensor ) { if ( ! error || ! stderr || ! error && ! stderr ) { const logger_temp = fs . createWriteStream ( pathToSensorFiles + 'temp.txt' , { flags : 'w' } ) ; logger_temp . write ( obj . temp . toString ( ) ) ; logger_temp . end ( ) ; } } if ( purifier . config . humidity_sensor ) { if ( ! error || ! stderr || ! error && ! stderr ) { const logger_hum = fs . createWriteStream ( pathToSensorFiles + 'hum.txt' , { flags : 'w' } ) ; logger_hum . write ( obj . rh . toString ( ) ) ; logger_hum . end ( ) ; } } } } ) ; } , polling * 1000 ) ; } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Unable to load polling info' ) ; } } async updateFirmware ( purifier ) { try { purifier . lastfirmware = Date . now ( ) ; const firmware = await purifier . client ?. getFirmware ( ) ; await this . storeKey ( purifier ) ; const accInfo = purifier . accessory . getService ( hap . Service . AccessoryInformation ) ; if ( accInfo ) { const name = firmware . modelid ; accInfo . updateCharacteristic ( hap . Characteristic . Manufacturer , 'Philips' ) . updateCharacteristic ( hap . Characteristic . SerialNumber , purifier . config . ip ) . updateCharacteristic ( hap . Characteristic . Model , name ) . updateCharacteristic ( hap . Characteristic . FirmwareRevision , firmware . version ) ; } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Unable to load firmware info: ' + err ) ; } } async updateFilters ( purifier ) { try { const filters = await purifier . client ?. getFilters ( ) ; purifier . lastfilters = Date . now ( ) ; await this . storeKey ( purifier ) ; const preFilter = purifier . accessory . getService ( 'Pre-filter' ) ; if ( preFilter ) { const fltsts0change = filters . fltsts0 == 0 ; const fltsts0life = filters . fltsts0 / 360 * 100 ; preFilter . updateCharacteristic ( hap . Characteristic . FilterChangeIndication , fltsts0change ) . updateCharacteristic ( hap . Characteristic . FilterLifeLevel , fltsts0life ) ; } const carbonFilter = purifier . accessory . getService ( 'Active carbon filter' ) ; if ( carbonFilter ) { const fltsts2change = filters . fltsts2 == 0 ; const fltsts2life = filters . fltsts2 / 4800 * 100 ; carbonFilter . updateCharacteristic ( hap . Characteristic . FilterChangeIndication , fltsts2change ) . updateCharacteristic ( hap . Characteristic . FilterLifeLevel , fltsts2life ) ; } const hepaFilter = purifier . accessory . getService ( 'HEPA filter' ) ; if ( hepaFilter ) { const fltsts1change = filters . fltsts1 == 0 ; const fltsts1life = filters . fltsts1 / 4800 * 100 ; hepaFilter . updateCharacteristic ( hap . Characteristic . FilterChangeIndication , fltsts1change ) . updateCharacteristic ( hap . Characteristic . FilterLifeLevel , fltsts1life ) ; } if ( purifier . config . humidifier ) { const wickFilter = purifier . accessory . getService ( 'Wick filter' ) ; if ( wickFilter ) { const fltwickchange = filters . wicksts == 0 ; const fltwicklife = Math . round ( filters . wicksts / 4800 * 100 ) ; wickFilter . updateCharacteristic ( hap . Characteristic . FilterChangeIndication , fltwickchange ) . updateCharacteristic ( hap . Characteristic . FilterLifeLevel , fltwicklife ) ; } } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Unable to load filter info: ' + err ) ; } } async updateStatus ( purifier ) { try { const status = await purifier . client ?. getStatus ( ) ; purifier . laststatus = Date . now ( ) ; await this . storeKey ( purifier ) ; const purifierService = purifier . accessory . getService ( hap . Service . AirPurifier ) ; if ( purifierService ) { const state = parseInt ( status . pwr ) * 2 ; purifierService . updateCharacteristic ( hap . Characteristic . Active , status . pwr ) . updateCharacteristic ( hap . Characteristic . CurrentAirPurifierState , state ) . updateCharacteristic ( hap . Characteristic . LockPhysicalControls , status . cl ) ; } const qualityService = purifier . accessory . getService ( hap . Service . AirQualitySensor ) ; if ( qualityService ) { const iaql = Math . ceil ( status . iaql / 3 ) ; qualityService . updateCharacteristic ( hap . Characteristic . AirQuality , iaql ) . updateCharacteristic ( hap . Characteristic . PM2_5Density , status . pm25 ) ; } if ( purifier . config . temperature_sensor ) { const temperature_sensor = purifier . accessory . getService ( 'Temperature' ) ; if ( temperature_sensor ) { temperature_sensor . updateCharacteristic ( hap . Characteristic . CurrentTemperature , status . temp ) ; } } if ( purifier . config . humidity_sensor ) { const humidity_sensor = purifier . accessory . getService ( 'Humidity' ) ; if ( humidity_sensor ) { humidity_sensor . updateCharacteristic ( hap . Characteristic . CurrentRelativeHumidity , status . rh ) ; } } if ( purifier . config . light_control ) { const lightsService = purifier . accessory . getService ( 'Lights' ) ; if ( status . pwr == '1' ) { if ( lightsService ) { lightsService . updateCharacteristic ( hap . Characteristic . On , status . aqil > 0 ) . updateCharacteristic ( hap . Characteristic . Brightness , status . aqil ) ; } } } if ( purifier . config . humidifier ) { const Humidifier = purifier . accessory . getService ( 'Humidifier' ) ; if ( Humidifier ) { let speed_humidity = 0 ; let state_ph = 0 ; let water_level = 100 ; if ( status . func == 'PH' && status . wl == 0 ) { water_level = 0 ; } if ( status . pwr == '1' ) { if ( status . func == 'PH' && water_level == 100 ) { state_ph = 1 ; if ( status . rhset == 40 ) { speed_humidity = 25 ; } else if ( status . rhset == 50 ) { speed_humidity = 50 ; } else if ( status . rhset == 60 ) { speed_humidity = 75 ; } else if ( status . rhset == 70 ) { speed_humidity = 100 ; } } } Humidifier . updateCharacteristic ( hap . Characteristic . CurrentRelativeHumidity , status . rh ) . updateCharacteristic ( hap . Characteristic . WaterLevel , water_level ) . updateCharacteristic ( hap . Characteristic . TargetHumidifierDehumidifierState , 1 ) ; if ( state_ph && status . rhset >= 40 ) { Humidifier . updateCharacteristic ( hap . Characteristic . Active , state_ph ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , state_ph * 2 ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , speed_humidity ) ; } if ( water_level == 0 ) { if ( status . func != 'P' ) { exec ( 'airctrl --ipaddr ' + purifier . config . ip + ' --protocol ' + purifier . config . protocol + ' --func P' , ( err , stdout , stderr ) => { if ( err ) { return ; } if ( stderr ) { console . error ( 'Unable to switch off purifier ' + stderr + '. If your have "sync timeout" error your need unplug the accessory from the outlet for 10 seconds.' ) ; } } ) ; } } } } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Unable to load status info: ' + err ) ; } } async setPower ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; if ( purifier ) { const values = { pwr : ( state as boolean ) . toString ( ) } ; try { const status = await purifier . client ?. getStatus ( ) ; purifier . laststatus = Date . now ( ) ; await this . storeKey ( purifier ) ; let water_level = 100 ; if ( status . func == 'PH' && status . wl == 0 ) { water_level = 0 ; } const purifierService = accessory . getService ( hap . Service . AirPurifier ) ; if ( purifierService ) { purifierService . updateCharacteristic ( hap . Characteristic . CurrentAirPurifierState , state as number * 2 ) ; } if ( purifier . config . humidifier ) { if ( water_level == 0 ) { values [ 'func' ] = 'P' ; } } await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; if ( purifier . config . light_control ) { const lightsService = accessory . getService ( 'Lights' ) ; if ( lightsService ) { if ( state ) { lightsService . updateCharacteristic ( hap . Characteristic . On , status . aqil > 0 ) . updateCharacteristic ( hap . Characteristic . Brightness , status . aqil ) ; } else { lightsService . updateCharacteristic ( hap . Characteristic . On , 0 ) . updateCharacteristic ( hap . Characteristic . Brightness , 0 ) ; } } } if ( purifier . config . humidifier ) { const Humidifier = accessory . getService ( 'Humidifier' ) ; let state_ph = 0 ; if ( status . func == 'PH' && water_level == 100 ) { state_ph = 1 ; } if ( Humidifier ) { Humidifier . updateCharacteristic ( hap . Characteristic . TargetHumidifierDehumidifierState , 1 ) ; if ( state ) { Humidifier . updateCharacteristic ( hap . Characteristic . Active , state_ph ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , state_ph * 2 ) ; } else { Humidifier . updateCharacteristic ( hap . Characteristic . Active , 0 ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , 0 ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , 0 ) ; } } } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting power: ' + err ) ; } } } async setBrightness ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; if ( purifier ) { const values = { aqil : state , uil : state ? '1' : '0' } ; try { await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting brightness: ' + err ) ; } } } async setMode ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; if ( purifier ) { const values = { mode : state ? 'P' : 'M' } ; if ( purifier . config . allergic_func ) { values . mode = state ? 'P' : 'A' ; } else { values . mode = state ? 'P' : 'M' ; } try { await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; if ( state != 0 ) { const purifierService = accessory . getService ( hap . Service . AirPurifier ) ; if ( purifierService ) { purifierService . updateCharacteristic ( hap . Characteristic . RotationSpeed , 0 ) . updateCharacteristic ( hap . Characteristic . TargetAirPurifierState , state ) ; } } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting mode: ' + err ) ; } } } async setLock ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; if ( purifier ) { const values = { cl : state == 1 } ; try { await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting lock: ' + err ) ; } } } async setHumidity ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; const status = await purifier . client ?. getStatus ( ) ; purifier . laststatus = Date . now ( ) ; await this . storeKey ( purifier ) ; const Humidifier = accessory . getService ( 'Humidifier' ) ; if ( purifier ) { const values = { func : state ? 'PH' : 'P' } ; let water_level = 100 ; if ( status . func == 'PH' && status . wl == 0 ) { water_level = 0 ; } let speed_humidity = 0 ; let state_ph = 0 ; if ( status . func == 'PH' && water_level == 100 ) { state_ph = 1 ; if ( status . rhset == 40 ) { speed_humidity = 25 ; } else if ( status . rhset == 50 ) { speed_humidity = 50 ; } else if ( status . rhset == 60 ) { speed_humidity = 75 ; } else if ( status . rhset == 70 ) { speed_humidity = 100 ; } } try { await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; Humidifier . updateCharacteristic ( hap . Characteristic . TargetHumidifierDehumidifierState , 1 ) ; if ( state ) { Humidifier . updateCharacteristic ( hap . Characteristic . Active , 1 ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , state_ph * 2 ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , speed_humidity ) ; } else { Humidifier . updateCharacteristic ( hap . Characteristic . Active , 0 ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , 0 ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , 0 ) ; } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting func: ' + err ) ; } } } async setHumidityTarget ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; const status = await purifier . client ?. getStatus ( ) ; purifier . laststatus = Date . now ( ) ; await this . storeKey ( purifier ) ; const Humidifier = accessory . getService ( 'Humidifier' ) ; if ( purifier ) { const speed = state ; const values = { func : state ? 'PH' : 'P' , rhset : 40 } ; let speed_humidity = 0 ; if ( speed > 0 && speed <= 25 ) { values . rhset = 40 ; speed_humidity = 25 ; } else if ( speed > 25 && speed <= 50 ) { values . rhset = 50 ; speed_humidity = 50 ; } else if ( speed > 50 && speed <= 75 ) { values . rhset = 60 ; speed_humidity = 75 ; } else if ( speed > 75 && speed <= 100 ) { values . rhset = 70 ; speed_humidity = 100 ; } try { await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; if ( Humidifier ) { let water_level = 100 ; if ( status . func == 'PH' && status . wl == 0 ) { water_level = 0 ; } Humidifier . updateCharacteristic ( hap . Characteristic . TargetHumidifierDehumidifierState , 1 ) ; if ( speed_humidity > 0 ) { Humidifier . updateCharacteristic ( hap . Characteristic . Active , 1 ) . updateCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState , 2 ) . updateCharacteristic ( hap . Characteristic . WaterLevel , water_level ) . updateCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold , speed_humidity ) ; } else { Humidifier . updateCharacteristic ( hap . Characteristic . Active , 0 ) ; } } } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting humidifier: ' + err ) ; } } } async setFan ( accessory , state ) { const purifier = this . purifiers . get ( accessory . displayName ) ; if ( purifier ) { let divisor = 25 ; let offset = 0 ; if ( purifier . config . sleep_speed ) { divisor = 20 ; offset = 1 ; } const speed = Math . ceil ( state as number / divisor ) ; if ( speed > 0 ) { const values = { mode : 'M' , om : '' } ; if ( offset == 1 && speed == 1 ) { values . om = 's' ; } else if ( speed < 4 + offset ) { values . om = ( speed - offset ) . toString ( ) ; } else { values . om = 't' ; } try { await this . enqueuePromise ( CommandType . SetData , purifier , values ) ; const service = accessory . getService ( hap . Service . AirPurifier ) ; if ( service ) { service . updateCharacteristic ( hap . Characteristic . TargetAirPurifierState , 0 ) ; } if ( purifier . timeout ) { clearTimeout ( purifier . timeout ) ; } purifier . timeout = setTimeout ( ( ) => { if ( service ) { service . updateCharacteristic ( hap . Characteristic . RotationSpeed , speed * divisor ) ; } purifier . timeout = undefined ; } , 1000 ) ; } catch ( err ) { this . log . error ( '[' + purifier . config . name + '] Error setting fan: ' + err ) ; } } } } addAccessory ( config ) { this . log ( '[' + config . name + '] Initializing accessory...' ) ; const uuid = hap . uuid . generate ( config . ip ) ; let accessory = this . cachedAccessories . find ( cachedAcc => { return cachedAcc . UUID == uuid ; } ) ; if ( ! accessory ) { accessory = new Accessory ( config . name , uuid ) ; accessory . addService ( hap . Service . AirPurifier , config . name ) ; accessory . addService ( hap . Service . AirQualitySensor , 'Air quality' , 'Air quality' ) ; if ( config . light_control ) { accessory . addService ( hap . Service . Lightbulb , 'Lights' , 'Lights' ) . addCharacteristic ( hap . Characteristic . Brightness ) ; } accessory . addService ( hap . Service . FilterMaintenance , 'Pre-filter' , 'Pre-filter' ) ; accessory . addService ( hap . Service . FilterMaintenance , 'Active carbon filter' , 'Active carbon filter' ) ; accessory . addService ( hap . Service . FilterMaintenance , 'HEPA filter' , 'HEPA filter' ) ; if ( config . temperature_sensor ) { accessory . addService ( hap . Service . TemperatureSensor , 'Temperature' , 'Temperature' ) ; } if ( config . humidity_sensor ) { accessory . addService ( hap . Service . HumiditySensor , 'Humidity' , 'Humidity' ) ; } if ( config . humidifier ) { accessory . addService ( hap . Service . HumidifierDehumidifier , 'Humidifier' , 'Humidifier' ) ; accessory . addService ( hap . Service . FilterMaintenance , 'Wick filter' , 'Wick filter' ) ; } this . api . registerPlatformAccessories ( 'homebridge-philips-air' , 'philipsAir' , [ accessory ] ) ; } else { let lightsService = accessory . getService ( 'Lights' ) ; if ( config . light_control ) { if ( lightsService == undefined ) { lightsService = accessory . addService ( hap . Service . Lightbulb , 'Lights' , 'Lights' ) ; lightsService . addCharacteristic ( hap . Characteristic . Brightness ) ; } } else if ( lightsService != undefined ) { accessory . removeService ( lightsService ) ; } const temperature_sensor = accessory . getService ( 'Temperature' ) ; if ( config . temperature_sensor ) { if ( temperature_sensor == undefined ) { accessory . addService ( hap . Service . TemperatureSensor , 'Temperature' , 'Temperature' ) ; } } else if ( temperature_sensor != undefined ) { accessory . removeService ( temperature_sensor ) ; } const humidity_sensor = accessory . getService ( 'Humidity' ) ; if ( config . humidity_sensor ) { if ( humidity_sensor == undefined ) { accessory . addService ( hap . Service . HumiditySensor , 'Humidity' , 'Humidity' ) ; } } else if ( humidity_sensor != undefined ) { accessory . removeService ( humidity_sensor ) ; } const Humidifier = accessory . getService ( 'Humidifier' ) ; if ( config . humidifier ) { if ( Humidifier == undefined ) { accessory . addService ( hap . Service . HumidifierDehumidifier , 'Humidifier' , 'Humidifier' ) ; } } else if ( Humidifier != undefined ) { accessory . removeService ( Humidifier ) ; } } this . setService ( accessory , config ) ; let client ; switch ( config . protocol ) { case 'coap' : client = new CoapClient ( config . ip , this . timeout ) ; break ; case 'plain_coap' : client = new PlainCoapClient ( config . ip , this . timeout ) ; break ; case 'http_legacy' : client = new HttpClientLegacy ( config . ip , this . timeout ) ; break ; case 'http' : default : if ( accessory . context . key ) { client = new HttpClient ( config . ip , this . timeout , accessory . context . key ) ; } else { client = new HttpClient ( config . ip , this . timeout ) ; } } this . purifiers . set ( accessory . displayName , { accessory : accessory , client : client , config : config } ) ; } removeAccessories ( accessories ) { accessories . forEach ( accessory => { this . log ( '[' + accessory . displayName + '] Removed from Homebridge.' ) ; this . api . unregisterPlatformAccessories ( 'homebridge-philips-air' , 'philipsAir' , [ accessory ] ) ; } ) ; } setService ( accessory , config ) { accessory . on ( PlatformAccessoryEvent . IDENTIFY , ( ) => { this . log ( '[' + accessory . displayName + '] Identify requested.' ) ; } ) ; const purifierService = accessory . getService ( hap . Service . AirPurifier ) ; let min_step_purifier_speed = 25 ; if ( config . sleep_speed ) { min_step_purifier_speed = 20 ; } if ( purifierService ) { purifierService . getCharacteristic ( hap . Characteristic . Active ) . on ( 'set' , async ( state , callback ) => { try { await this . setPower ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) ; purifierService . getCharacteristic ( hap . Characteristic . TargetAirPurifierState ) . on ( 'set' , async ( state , callback ) => { try { await this . setMode ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) ; purifierService . getCharacteristic ( hap . Characteristic . LockPhysicalControls ) . on ( 'set' , async ( state , callback ) => { try { await this . setLock ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) ; purifierService . getCharacteristic ( hap . Characteristic . RotationSpeed ) . on ( 'set' , async ( state , callback ) => { try { await this . setFan ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) . setProps ( { minValue : 0 , maxValue : 100 , minStep : min_step_purifier_speed } ) ; } if ( config . light_control ) { const lightService = accessory . getService ( 'Lights' ) ; if ( lightService ) { lightService . getCharacteristic ( hap . Characteristic . Brightness ) . on ( 'set' , async ( state , callback ) => { try { await this . setBrightness ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) . setProps ( { minValue : 0 , maxValue : 100 , minStep : 25 } ) ; } } if ( config . humidifier ) { const Humidifier = accessory . getService ( 'Humidifier' ) ; if ( Humidifier ) { Humidifier . getCharacteristic ( hap . Characteristic . Active ) . on ( 'set' , async ( state , callback ) => { try { await this . setHumidity ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) ; Humidifier . getCharacteristic ( hap . Characteristic . CurrentHumidifierDehumidifierState ) . on ( 'set' , async ( state , callback ) => { try { await this . setHumidityTarget ( accessory , state ) ; await this . setHumidity ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) . setProps ( { validValues : [ hap . Characteristic . CurrentHumidifierDehumidifierState . INACTIVE , hap . Characteristic . CurrentHumidifierDehumidifierState . HUMIDIFYING ] } ) ; Humidifier . getCharacteristic ( hap . Characteristic . TargetHumidifierDehumidifierState ) . on ( 'set' , async ( state , callback ) => { try { await this . setHumidityTarget ( accessory , state ) ; await this . setHumidity ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) . setProps ( { validValues : [ hap . Characteristic . TargetHumidifierDehumidifierState . HUMIDIFIER ] } ) ; Humidifier . getCharacteristic ( hap . Characteristic . RelativeHumidityHumidifierThreshold ) . on ( 'set' , async ( state , callback ) => { try { await this . setHumidityTarget ( accessory , state ) ; callback ( ) ; } catch ( err ) { callback ( err ) ; } } ) . setProps ( { minValue : 0 , maxValue : 100 , minStep : 25 } ) ; } } } enqueueAccessory ( commandType , accessory ) { const purifier = this . purifiers . get ( accessory . displayName ) ; if ( purifier ) { this . enqueueCommand ( commandType , purifier ) ; } } enqueueCommand ( commandType , purifier , data ? , callback ? ) { if ( commandType != CommandType . SetData ) { const exists = this . commandQueue . find ( ( command ) => { return command . purifier . config . ip == purifier . config . ip && command . type == commandType ; } ) ; if ( exists ) { return ; } } this . commandQueue . push ( { purifier : purifier , type : commandType , callback : callback , data : data } ) ; if ( ! this . queueRunning ) { this . queueRunning = true ; this . nextCommand ( ) ; } } nextCommand ( ) { const todoItem = this . commandQueue . shift ( ) ; if ( ! todoItem ) { return ; } let command ; switch ( todoItem . type ) { case CommandType . Polling : command = this . updatePolling ( todoItem . purifier ) ; break ; case CommandType . GetFirmware : command = this . updateFirmware ( todoItem . purifier ) ; break ; case CommandType . GetFilters : command = this . updateFilters ( todoItem . purifier ) ; break ; case CommandType . GetStatus : command = this . updateStatus ( todoItem . purifier ) ; break ; case CommandType . SetData : command = this . setData ( todoItem . purifier , todoItem . data , todoItem . callback ) ; } command . then ( ( ) => { if ( this . commandQueue . length > 0 ) { this . nextCommand ( ) ; } else { this . queueRunning = false ; } } ) ; } } export = ( api ) : void => { hap = api . hap ; Accessory = api . platformAccessory ; api . registerPlatform ( PLUGIN_NAME , PLATFORM_NAME , PhilipsAirPlatform ) ; } ;