import { Service, PlatformAccessory, CharacteristicValue, CharacteristicSetCallback, CharacteristicGetCallback, Logger } from 'homebridge'; import TapoPlatform from './platform'; import P110 from './utils/p110'; /** * P110 Accessory * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ export class P110Accessory { private service: Service; private readonly fakeGatoHistoryService?; private p110: P110; constructor( public readonly log: Logger, private readonly platform: TapoPlatform, private readonly accessory: PlatformAccessory, private readonly timeout: number, private readonly updateInterval?: number, ) { this.log.debug('Start adding accessory: ' + accessory.context.device.host); this.p110 = new P110(this.log, accessory.context.device.host, platform.config.username, platform.config.password, this.timeout); this.fakeGatoHistoryService = new this.platform.FakeGatoHistoryService('energy', accessory, { log: this.log, size:4096, storage:'fs', }); this.p110.handshake().then(() => { this.p110.login().then(() => { this.p110.getDeviceInfo().then((sysInfo) => { // set accessory information this.accessory.getService(this.platform.Service.AccessoryInformation)! .setCharacteristic(this.platform.Characteristic.Manufacturer, 'TP-Link') .setCharacteristic(this.platform.Characteristic.Model, 'Tapo P110') .setCharacteristic(this.platform.Characteristic.SerialNumber, sysInfo.hw_id); // each service must implement at-minimum the "required characteristics" for the given service type // see https://developers.homebridge.io/#/service/Outlet // register handlers for the On/Off Characteristic this.service.getCharacteristic(this.platform.Characteristic.On) .on('set', this.setOn.bind(this)) // SET - bind to the `setOn` method below .on('get', this.getOn.bind(this)); // GET - bind to the `getOn` method below this.service.getCharacteristic(this.platform.customCharacteristics.CurrentConsumptionCharacteristic) .on('get', this.getCurrentConsumption.bind(this)); this.service.getCharacteristic(this.platform.customCharacteristics.TotalConsumptionCharacteristic) .on('get', this.getTotalConsumption.bind(this)); this.updateConsumption(); const interval = updateInterval ? updateInterval*1000 : 30000; setTimeout(()=>{ this.updateState(interval); }, interval); }).catch(() => { this.setNoResponse(); this.log.error('Get Device Info failed'); }); }).catch(() => { this.setNoResponse(); this.log.error('Login failed'); }); }).catch(() => { this.setNoResponse(); this.log.error('Handshake failed'); }); // get the Outlet service if it exists, otherwise create a new Outlet service this.service = this.accessory.getService(this.platform.Service.Outlet) || this.accessory.addService(this.platform.Service.Outlet); // set the service name, this is what is displayed as the default name on the Home app // we are using the name we stored in the `accessory.context` in the `discoverDevices` method. this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.name); } /** * Handle "SET" requests from HomeKit * These are sent when the user changes the state of an accessory. */ setOn(value: CharacteristicValue, callback: CharacteristicSetCallback) { this.p110.setPowerState(value as boolean).then((result) => { if(result){ this.platform.log.debug('Set Characteristic On ->', value); this.p110.getSysInfo().device_on = value as boolean; // you must call the callback function callback(null); } else{ callback(new Error('unreachable'), false); } }); } /** * Handle the "GET" requests from HomeKit * These are sent when HomeKit wants to know the current state of the accessory. * */ getOn(callback: CharacteristicGetCallback) { // implement your own code to check if the device is on this.p110.getDeviceInfo().then((response) => { if(response){ const isOn = response.device_on; this.platform.log.debug('Get Characteristic On ->', isOn); // you must call the callback function // the first argument should be null if there were no errors // the second argument should be the value to return // you must call the callback function if(isOn !== undefined){ callback(null, isOn); } else{ callback(new Error('unreachable'), isOn); } } else{ callback(new Error('unreachable'), false); } }).catch(() => { callback(new Error('unreachable'), false); }); } private updateConsumption(){ this.p110.getEnergyUsage().then((response) => { if (this.fakeGatoHistoryService && response && response.current_power) { this.platform.log.debug('Get Characteristic Power consumption ->', response.current_power); this.fakeGatoHistoryService.addEntry({ time: new Date().getTime() / 1000, power: response.current_power, }); } }); setTimeout(()=>{ this.updateConsumption(); }, 300000); } private updateState(interval:number){ this.p110.getDeviceInfo().then((response) => { if(response){ const isOn = response.device_on; this.platform.log.debug('Get Characteristic On ->', isOn); if(isOn !== undefined){ this.service.updateCharacteristic(this.platform.Characteristic.On, isOn); } else{ interval += 300000; this.setNoResponse(); } } setTimeout(()=>{ this.updateState(interval); }, interval); }).catch(()=>{ this.setNoResponse(); setTimeout(()=>{ this.updateState(interval + 300000); }, interval); }); } /** * Handle the "GET" requests from HomeKit * These are sent when HomeKit wants to know the current state of the accessory. * */ getCurrentConsumption(callback: CharacteristicGetCallback) { const consumption = this.p110.getPowerConsumption(); // you must call the callback function // the first argument should be null if there were no errors // the second argument should be the value to return // you must call the callback function callback(null, consumption.current); } /** * Handle the "GET" requests from HomeKit * These are sent when HomeKit wants to know the current state of the accessory. * */ getTotalConsumption(callback: CharacteristicGetCallback) { const consumption = this.p110.getPowerConsumption(); // you must call the callback function // the first argument should be null if there were no errors // the second argument should be the value to return // you must call the callback function callback(null, consumption.total); } private setNoResponse():void{ //@ts-ignore this.service.updateCharacteristic(this.platform.Characteristic.On, new Error('unreachable')); } }