UNPKG

homebridge-lg-airco

Version:

Homebridge plugin to control a Smart Thinq enabled LG airco unit. Makes use of WideQ => https://github.com/sampsyo/wideq

286 lines (242 loc) 12.3 kB
import { AccessoryConfig, AccessoryPlugin, API, CharacteristicEventTypes, CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue, Formats, HAP, Logging, Perms, Service, Units } from "homebridge"; import {AirCooler, HSwingMode, Mode, VSwingMode, WideqAdapter} from "./lg/wideq-adapter"; import {LgAircoController} from "./lg/lg-airco-controller"; import {AsyncUtils} from "./utils/async-utils"; import {DummyController} from "./lg/dummy-controller"; import {Controller} from "./lg/controller"; import {PythonUtils} from "./utils/python-utils"; export class LgAirCoolerAccessory implements AccessoryPlugin { private readonly hap: HAP; private readonly log: Logging; private readonly config: AccessoryConfig; private readonly storagePath: string; private readonly logDebug: Function; private readonly informationService: Service; private readonly heaterCoolerService: Service; private airCooler: AirCooler; private controller: Controller; private powerStateWillChange: boolean = false; private handleRotationSpeedSetWithDebounce: Function; constructor(log: Logging, config: AccessoryConfig, api: API) { this.hap = api.hap; this.log = log; this.config = config; this.storagePath = api.user.storagePath() + '/wideq_state.json'; this.logDebug = this.config.debug ? this.log : () => {}; PythonUtils.logDebug = this.logDebug; this.informationService = new this.hap.Service.AccessoryInformation() .setCharacteristic(this.hap.Characteristic.Manufacturer, 'LG') .setCharacteristic(this.hap.Characteristic.Model, 'AIR CONDITIONER') .setCharacteristic(this.hap.Characteristic.SerialNumber, this.config.model); this.heaterCoolerService = new this.hap.Service.HeaterCooler(this.config.name); setTimeout(async () => { const airCoolers: AirCooler[] = await WideqAdapter.listAirCoolers(this.config.country, this.config.language, this.storagePath); if (airCoolers.length === 1) { this.airCooler = airCoolers[0]; } else if (airCoolers.length > 1) { const filteredAirCoolers: AirCooler[] = airCoolers.filter((airCooler: AirCooler) => { return airCooler.deviceId === this.config.deviceId; }); if (filteredAirCoolers.length === 1) { this.airCooler = filteredAirCoolers[0]; } else { this.log('Cannot find air cooler with id: ' + this.config.deviceId); return; } } else { this.log('No air coolers found!'); return; } if (this.config.dummy) { this.controller = new DummyController(this.airCooler, this.config.updateInterval, this.config.debug ? this.log : () => {}); } else { this.controller = new LgAircoController(this.airCooler, this.config.updateInterval, this.storagePath, this.config.debug, this.logDebug); } this.handleRotationSpeedSetWithDebounce = AsyncUtils.debounce((newFanSpeed: number) => { this.controller.setFanSpeed(WideqAdapter.percentageToFanSpeed(newFanSpeed)); }, 5000); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.Active) .on(CharacteristicEventTypes.GET, this.handleActiveGet.bind(this)) .on(CharacteristicEventTypes.SET, this.handleActiveSet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState) .on(CharacteristicEventTypes.GET, this.handleCurrentHeaterCoolerStateGet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.TargetHeaterCoolerState) .setProps({ format: Formats.UINT8, maxValue: 2, minValue: 0, validValues: [1, 2], perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }) .on(CharacteristicEventTypes.GET, this.handleTargetHeaterCoolerStateGet.bind(this)) .on(CharacteristicEventTypes.SET, this.handleTargetHeaterCoolerStateSet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.CurrentTemperature) .on(CharacteristicEventTypes.GET, this.handleCurrentTemperatureGet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature) .setProps({ format: Formats.FLOAT, unit: Units.CELSIUS, maxValue: this.config.maxCoolingTemp, minValue: this.config.minCoolingTemp, minStep: 1, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }) .on(CharacteristicEventTypes.GET, this.handleCoolingThresholdTemperatureGet.bind(this)) .on(CharacteristicEventTypes.SET, this.handleCoolingThresholdTemperatureSet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature) .setProps({ format: Formats.FLOAT, unit: Units.CELSIUS, maxValue: this.config.maxHeatingTemp, minValue: this.config.minHeatingTemp, minStep: 1, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }) .on(CharacteristicEventTypes.GET, this.handleHeatingThresholdTemperatureGet.bind(this)) .on(CharacteristicEventTypes.SET, this.handleHeatingThresholdTemperatureSet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.RotationSpeed) .on(CharacteristicEventTypes.GET, this.handleRotationSpeedGet.bind(this)) .on(CharacteristicEventTypes.SET, this.handleRotationSpeedSet.bind(this)); this.heaterCoolerService.getCharacteristic(this.hap.Characteristic.SwingMode) .on(CharacteristicEventTypes.GET, this.handleSwingModeGet.bind(this)) .on(CharacteristicEventTypes.SET, this.handleSwingModeSet.bind(this)); }); } public getServices(): Service[] { return [ this.informationService, this.heaterCoolerService ]; } public identify(): void { this.log("Identify!"); } private handleActiveGet(callback: CharacteristicGetCallback): void { callback(null, this.controller.isPoweredOn() ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE); } private handleActiveSet(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (!this.powerStateWillChange && !this.controller.isPoweredOn() === (value === this.hap.Characteristic.Active.ACTIVE)) { this.controller.setPowerState(value === this.hap.Characteristic.Active.ACTIVE); } else { this.powerStateWillChange = false; } callback(null); } private handleCurrentHeaterCoolerStateGet(callback: CharacteristicGetCallback): void { let currentHeaterCoolerState: any; if (!this.controller.isPoweredOn()) { currentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.INACTIVE; } else { switch (this.controller.getMode()) { case Mode.COOL: currentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.COOLING; break; case Mode.HEAT: currentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.HEATING; break; case Mode.FAN: case Mode.DRY: case Mode.AI: case Mode.AIRCLEAN: case Mode.ACO: case Mode.AROMA: case Mode.ENERGY_SAVING: case Mode.ENERGY_SAVER: currentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE; break; } } callback(null, currentHeaterCoolerState) } private handleTargetHeaterCoolerStateGet(callback: CharacteristicGetCallback): void { //This is the same in this implementation! this.handleCurrentHeaterCoolerStateGet(callback); } private handleTargetHeaterCoolerStateSet(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (!this.controller.isPoweredOn()) { this.powerStateWillChange = true; } switch (value) { case this.hap.Characteristic.TargetHeaterCoolerState.COOL: this.controller.setMode(Mode.COOL); break; case this.hap.Characteristic.TargetHeaterCoolerState.HEAT: this.controller.setMode(Mode.HEAT); break; case this.hap.Characteristic.TargetHeaterCoolerState.AUTO: break; } callback(null); } private handleCurrentTemperatureGet(callback: CharacteristicGetCallback): void { callback(null, this.controller.getCurrentTemperatureInCelsius()); } private handleCoolingThresholdTemperatureGet(callback: CharacteristicGetCallback): void { callback(null, this.controller.getTargetCoolingTemperatureInCelsius()); } private handleCoolingThresholdTemperatureSet(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (!this.controller.isPoweredOn()) { this.powerStateWillChange = true; } this.controller.setTargetCoolingTemperatureInCelsius(value as number); callback(null); } private handleHeatingThresholdTemperatureGet(callback: CharacteristicGetCallback): void { callback(null, this.controller.getTargetHeatingTemperatureInCelsius()); } private handleHeatingThresholdTemperatureSet(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (!this.controller.isPoweredOn()) { this.powerStateWillChange = true; } this.controller.setTargetHeatingTemperatureInCelsius(value as number); callback(null); } private handleRotationSpeedGet(callback: CharacteristicGetCallback): void { callback(null, Math.round(WideqAdapter.fanSpeedToPercentage(this.controller.getFanSpeed()))); } private handleRotationSpeedSet(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (!this.controller.isPoweredOn()) { this.powerStateWillChange = true; } this.handleRotationSpeedSetWithDebounce(value); callback(null); } private handleSwingModeGet(callback: CharacteristicGetCallback): void { callback(null, this.controller.getHorizontalSwingMode() === HSwingMode.ALL && this.controller.getVerticalSwingMode() === VSwingMode.ALL ? this.hap.Characteristic.SwingMode.SWING_ENABLED : this.hap.Characteristic.SwingMode.SWING_DISABLED ); } private handleSwingModeSet(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (!this.controller.isPoweredOn()) { this.powerStateWillChange = true; } setTimeout(async () => { if (value === this.hap.Characteristic.SwingMode.SWING_ENABLED) { await this.controller.setHorizontalSwingMode(HSwingMode.ALL); await AsyncUtils.sleep(2000); await this.controller.setVerticalSwingMode(VSwingMode.ALL); } else { await this.controller.setHorizontalSwingMode(HSwingMode.OFF); await AsyncUtils.sleep(2000); await this.controller.setVerticalSwingMode(VSwingMode.OFF); } }); callback(null); } }