@shadman-a/homebridge-my-ac
Version:
A Homebridge plugin for controlling/monitoring LG ThinQ devices via LG ThinQ platform.
1,063 lines • 50.9 kB
JavaScript
import { BaseDevice } from '../baseDevice.js';
import { ValueType } from '../lib/DeviceModel.js';
import { cToF, fToC, normalizeBoolean, normalizeNumber } from '../helper.js';
export var ACModelType;
(function (ACModelType) {
ACModelType["AWHP"] = "AWHP";
ACModelType["RAC"] = "RAC";
})(ACModelType || (ACModelType = {}));
export var FanSpeed;
(function (FanSpeed) {
FanSpeed[FanSpeed["LOW"] = 2] = "LOW";
FanSpeed[FanSpeed["LOW_MEDIUM"] = 3] = "LOW_MEDIUM";
FanSpeed[FanSpeed["MEDIUM"] = 4] = "MEDIUM";
FanSpeed[FanSpeed["MEDIUM_HIGH"] = 5] = "MEDIUM_HIGH";
FanSpeed[FanSpeed["HIGH"] = 6] = "HIGH";
})(FanSpeed || (FanSpeed = {}));
const MAX_FAN_SPEED = 3;
const FAN_SPEED_MAP = [
FanSpeed.LOW,
FanSpeed.MEDIUM,
FanSpeed.HIGH,
];
const FAN_ROTATION_SPEEDS = [15, 60, 100];
export var ACModeOption;
(function (ACModeOption) {
ACModeOption[ACModeOption["COOL"] = 0] = "COOL";
ACModeOption[ACModeOption["FAN"] = 1] = "FAN";
ACModeOption[ACModeOption["DRY"] = 2] = "DRY";
ACModeOption[ACModeOption["ENERGY_SAVE"] = 3] = "ENERGY_SAVE";
})(ACModeOption || (ACModeOption = {}));
var OpMode;
(function (OpMode) {
OpMode[OpMode["AUTO"] = 6] = "AUTO";
OpMode[OpMode["COOL"] = 0] = "COOL";
OpMode[OpMode["HEAT"] = 4] = "HEAT";
OpMode[OpMode["FAN"] = 2] = "FAN";
OpMode[OpMode["DRY"] = 1] = "DRY";
OpMode[OpMode["AIR_CLEAN"] = 5] = "AIR_CLEAN";
})(OpMode || (OpMode = {}));
export default class AirConditioner extends BaseDevice {
platform;
accessory;
service;
serviceAirQuality;
serviceSensor;
serviceHumiditySensor;
serviceLight;
serviceFanV2;
serviceJetMode;
serviceQuietMode;
serviceEnergySaveMode;
serviceAirClean;
serviceLabelButtons;
jetModeModels = ['RAC_056905'];
quietModeModels = ['WINF_056905'];
airCleanModels = ['RAC_056905'];
currentTargetState = 2;
pendingPowerState;
pendingPowerStateUntil = 0;
constructor(platform, accessory, logger) {
super(platform, accessory, logger);
this.platform = platform;
this.accessory = accessory;
const device = this.accessory.context.device;
const { Service: { TemperatureSensor, HumiditySensor, Switch, Lightbulb, HeaterCooler, }, } = this.platform;
this.service = this.accessory.getService(HeaterCooler) || this.accessory.addService(HeaterCooler, device.name);
this.service.addOptionalCharacteristic(this.platform.customCharacteristics.TotalConsumption);
this.service.addOptionalCharacteristic(this.platform.customCharacteristics.ACMode);
this.createHeaterCoolerService();
if (this.config.ac_air_quality && this.Status.airQuality) {
this.createAirQualityService();
}
else if (this.serviceAirQuality) {
accessory.removeService(this.serviceAirQuality);
this.serviceAirQuality = undefined;
}
this.serviceSensor = accessory.getService(TemperatureSensor) || undefined;
if (this.config.ac_temperature_sensor) {
this.serviceSensor = this.serviceSensor || accessory.addService(TemperatureSensor);
this.serviceSensor.updateCharacteristic(platform.Characteristic.StatusActive, false);
this.serviceSensor.addLinkedService(this.service);
}
else if (this.serviceSensor) {
accessory.removeService(this.serviceSensor);
this.serviceSensor = undefined;
}
this.serviceHumiditySensor = accessory.getService(HumiditySensor) || undefined;
if (this.config.ac_humidity_sensor) {
this.serviceHumiditySensor = this.serviceHumiditySensor || accessory.addService(HumiditySensor);
this.serviceHumiditySensor.updateCharacteristic(platform.Characteristic.StatusActive, false);
this.serviceHumiditySensor.addLinkedService(this.service);
}
else if (this.serviceHumiditySensor) {
accessory.removeService(this.serviceHumiditySensor);
this.serviceHumiditySensor = undefined;
}
this.serviceLight = accessory.getService(Lightbulb) || undefined;
if (this.config.ac_led_control) {
this.serviceLight = this.serviceLight || accessory.addService(Lightbulb);
this.serviceLight.getCharacteristic(platform.Characteristic.On)
.onSet(this.setLight.bind(this))
.updateValue(false);
this.serviceLight.addLinkedService(this.service);
}
else if (this.serviceLight) {
accessory.removeService(this.serviceLight);
this.serviceLight = undefined;
}
if (this.config.ac_fan_control) {
this.createFanService();
}
else if (this.serviceFanV2) {
accessory.removeService(this.serviceFanV2);
this.serviceFanV2 = undefined;
}
if (this.config.ac_jet_control && this.supportsJetMode(device)) {
this.serviceJetMode = accessory.getService('Jet Mode') || accessory.addService(Switch, 'Jet Mode', 'Jet Mode');
this.serviceJetMode.addOptionalCharacteristic(platform.Characteristic.ConfiguredName);
this.serviceJetMode.setCharacteristic(platform.Characteristic.ConfiguredName, device.name + ' Jet Mode');
this.serviceJetMode.getCharacteristic(platform.Characteristic.On)
.onSet(this.setJetModeActive.bind(this));
}
else if (this.serviceJetMode) {
accessory.removeService(this.serviceJetMode);
this.serviceJetMode = undefined;
}
if (this.supportsQuietMode(device)) {
this.serviceQuietMode = accessory.getService('Quiet mode') || accessory.addService(Switch, 'Quiet mode', 'Quiet mode');
this.serviceQuietMode.addOptionalCharacteristic(platform.Characteristic.ConfiguredName);
this.serviceQuietMode.setCharacteristic(platform.Characteristic.ConfiguredName, device.name + ' Quiet mode');
this.serviceQuietMode.getCharacteristic(platform.Characteristic.On)
.onSet(this.setQuietModeActive.bind(this));
}
else if (this.serviceQuietMode) {
accessory.removeService(this.serviceQuietMode);
this.serviceQuietMode = undefined;
}
this.serviceEnergySaveMode = accessory.getService('Energy save') || undefined;
if (this.isEnergySaveSupported(device) && this.config.ac_energy_save) {
if (!this.serviceEnergySaveMode) {
this.serviceEnergySaveMode = accessory.addService(Switch, 'Energy save', 'Energy save');
}
this.serviceEnergySaveMode.addOptionalCharacteristic(platform.Characteristic.ConfiguredName);
this.serviceEnergySaveMode.setCharacteristic(platform.Characteristic.ConfiguredName, device.name + ' Energy save');
this.serviceEnergySaveMode.getCharacteristic(platform.Characteristic.On)
.onSet(this.setEnergySaveActive.bind(this));
}
else if (this.serviceEnergySaveMode) {
accessory.removeService(this.serviceEnergySaveMode);
this.serviceEnergySaveMode = undefined;
}
this.serviceAirClean = accessory.getService('Air Purify') || undefined;
if (this.supportsAirClean(device) && this.config.ac_air_clean) {
if (!this.serviceAirClean) {
this.serviceAirClean = accessory.addService(Switch, 'Air Purify', 'Air Purify');
}
this.serviceAirClean.addOptionalCharacteristic(platform.Characteristic.ConfiguredName);
this.serviceAirClean.setCharacteristic(platform.Characteristic.ConfiguredName, device.name + ' Air Purify');
this.serviceAirClean.getCharacteristic(platform.Characteristic.On)
.onSet(this.setAirCleanActive.bind(this));
}
else if (this.serviceAirClean) {
accessory.removeService(this.serviceAirClean);
this.serviceAirClean = undefined;
}
this.setupButton(device);
setInterval(() => {
if (device.online) {
this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.mon.timeout',
dataValue: '70',
}, 'Set', 'allEventEnable', 'control').catch(() => {
return;
});
}
}, 60000);
}
get config() {
return {
ac_swing_mode: 'BOTH',
ac_air_quality: false,
ac_mode: 'COOLING',
ac_temperature_sensor: false,
ac_humidity_sensor: false,
ac_led_control: false,
ac_fan_control: false,
ac_jet_control: false,
ac_temperature_unit: 'C',
ac_buttons: [],
ac_air_clean: true,
ac_energy_save: false,
...super.config,
};
}
get Status() {
return new ACStatus(this.accessory.context.device.snapshot, this.accessory.context.device, this.config, this.logger);
}
supportsJetMode(device) {
return this.jetModeModels.includes(device.model);
}
supportsQuietMode(device) {
return this.quietModeModels.includes(device.model);
}
supportsAirClean(device) {
return this.airCleanModels.includes(device.model);
}
isEnergySaveSupported(device) {
return Object.prototype.hasOwnProperty.call(device.snapshot, 'airState.powerSave.basic');
}
createFanService() {
const { Service: { Fanv2, }, Characteristic, } = this.platform;
const device = this.accessory.context.device;
this.serviceFanV2 = this.accessory.getService(Fanv2) || this.accessory.addService(Fanv2);
this.serviceFanV2.addLinkedService(this.service);
this.serviceFanV2.addOptionalCharacteristic(Characteristic.ConfiguredName);
this.serviceFanV2.setCharacteristic(Characteristic.ConfiguredName, device.name + ' Fan');
this.serviceFanV2.getCharacteristic(Characteristic.Active)
.onGet(() => {
return this.Status.isPowerOn ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE;
})
.onSet((value) => {
const isOn = normalizeBoolean(value);
if ((this.Status.isPowerOn && isOn) || (!this.Status.isPowerOn && !isOn)) {
return;
}
setTimeout(() => {
this.serviceFanV2?.updateCharacteristic(Characteristic.Active, this.Status.isPowerOn ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE);
this.serviceFanV2?.updateCharacteristic(Characteristic.RotationSpeed, this.Status.rotationSpeed);
}, 100);
})
.updateValue(Characteristic.Active.INACTIVE);
this.serviceFanV2.getCharacteristic(Characteristic.CurrentFanState)
.onGet(() => {
return this.Status.isPowerOn ? Characteristic.CurrentFanState.BLOWING_AIR : Characteristic.CurrentFanState.INACTIVE;
})
.setProps({
validValues: [Characteristic.CurrentFanState.INACTIVE, Characteristic.CurrentFanState.BLOWING_AIR],
})
.updateValue(Characteristic.CurrentFanState.INACTIVE);
this.serviceFanV2.getCharacteristic(Characteristic.TargetFanState)
.setProps({
validValues: [Characteristic.TargetFanState.MANUAL],
})
.onSet(this.setFanState.bind(this))
.updateValue(Characteristic.TargetFanState.MANUAL);
this.serviceFanV2.getCharacteristic(Characteristic.RotationSpeed)
.setProps({
minValue: 0,
maxValue: 100,
minStep: 1,
})
.updateValue(this.Status.rotationSpeed)
.onSet(this.setFanSpeed.bind(this));
this.serviceFanV2.getCharacteristic(Characteristic.SwingMode)
.onSet(this.setSwingMode.bind(this))
.updateValue(this.Status.isSwingOn ? Characteristic.SwingMode.SWING_ENABLED : Characteristic.SwingMode.SWING_DISABLED);
}
createAirQualityService() {
const { Service: { AirQualitySensor, }, } = this.platform;
this.serviceAirQuality = this.accessory.getService(AirQualitySensor) || this.accessory.addService(AirQualitySensor);
}
createHeaterCoolerService() {
const { Characteristic } = this.platform;
const device = this.accessory.context.device;
const status = this.Status;
this.service.setCharacteristic(Characteristic.Name, device.name);
this.service.getCharacteristic(Characteristic.Active)
.onSet(this.setActive.bind(this))
.updateValue(Characteristic.Active.INACTIVE);
this.service.getCharacteristic(Characteristic.CurrentHeaterCoolerState)
.updateValue(Characteristic.CurrentHeaterCoolerState.INACTIVE);
this.service.getCharacteristic(Characteristic.TargetHeaterCoolerState)
.setProps({
validValues: [
Characteristic.TargetHeaterCoolerState.HEAT,
Characteristic.TargetHeaterCoolerState.COOL,
],
})
.updateValue(Characteristic.TargetHeaterCoolerState.COOL);
this.service.getCharacteristic(Characteristic.TargetHeaterCoolerState)
.onSet(this.setTargetState.bind(this));
this.service.getCharacteristic(this.platform.customCharacteristics.ACMode)
.onSet(this.setACMode.bind(this))
.onGet(() => {
if (this.Status.opMode === OpMode.COOL && this.Status.isEnergySaveOn) {
return ACModeOption.ENERGY_SAVE;
}
switch (this.Status.opMode) {
case OpMode.FAN:
return ACModeOption.FAN;
case OpMode.DRY:
return ACModeOption.DRY;
default:
return ACModeOption.COOL;
}
})
.updateValue(ACModeOption.COOL);
if (status.currentTemperature) {
this.service.updateCharacteristic(Characteristic.CurrentTemperature, status.currentTemperature);
}
const currentTemperatureValue = device.deviceModel.value('airState.tempState.current');
if (currentTemperatureValue) {
this.service.getCharacteristic(Characteristic.CurrentTemperature)
.setProps({
minValue: status.convertTemperatureCelsiusFromLGToHomekit(currentTemperatureValue.min),
maxValue: status.convertTemperatureCelsiusFromLGToHomekit(currentTemperatureValue.max),
minStep: 0.01,
});
}
const targetHeatTemperature = status.getTemperatureRange(status.getTemperatureRangeForHeating());
if (targetHeatTemperature) {
this.service.getCharacteristic(Characteristic.HeatingThresholdTemperature)
.setProps({
minValue: status.convertTemperatureCelsiusFromLGToHomekit(targetHeatTemperature.min),
maxValue: status.convertTemperatureCelsiusFromLGToHomekit(targetHeatTemperature.max),
minStep: targetHeatTemperature.step || 0.01,
});
}
const targetCoolTemperature = status.getTemperatureRange(status.getTemperatureRangeForCooling());
if (targetCoolTemperature) {
this.service.getCharacteristic(Characteristic.CoolingThresholdTemperature)
.setProps({
minValue: status.convertTemperatureCelsiusFromLGToHomekit(targetCoolTemperature.min),
maxValue: status.convertTemperatureCelsiusFromLGToHomekit(targetCoolTemperature.max),
minStep: targetCoolTemperature.step || 0.01,
});
}
this.service.getCharacteristic(Characteristic.CoolingThresholdTemperature)
.onSet(this.setTargetTemperature.bind(this));
this.service.getCharacteristic(Characteristic.HeatingThresholdTemperature)
.onSet(this.setTargetTemperature.bind(this));
if (!this.config.ac_fan_control) {
this.service.getCharacteristic(Characteristic.RotationSpeed)
.setProps({
minValue: 0,
maxValue: 100,
minStep: 1,
})
.updateValue(this.Status.rotationSpeed)
.onSet(this.setFanSpeed.bind(this));
}
this.service.getCharacteristic(Characteristic.SwingMode)
.onSet(this.setSwingMode.bind(this));
}
async setEnergySaveActive(value) {
const device = this.accessory.context.device;
const enabled = normalizeBoolean(value);
const status = this.Status;
if (!(this.isEnergySaveSupported(device) && status.isPowerOn && status.opMode === OpMode.COOL)) {
this.logger.debug(`Energy save mode is not supported in the current state. Power: ${status.isPowerOn}, Mode: ${status.opMode}`);
return;
}
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.powerSave.basic',
dataValue: enabled ? 1 : 0,
});
this.accessory.context.device.data.snapshot['airState.powerSave.basic'] = enabled ? 1 : 0;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting energy save mode:', error);
}
}
async setAirCleanActive(value) {
const device = this.accessory.context.device;
const status = this.Status;
const enabled = normalizeBoolean(value);
if (!(this.supportsAirClean(device) && status.isPowerOn && status.opMode === OpMode.COOL)) {
this.logger.debug(`Air clean mode is not supported in the current state. Power: ${status.isPowerOn}, Mode: ${status.opMode}`);
return;
}
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.wMode.airClean',
dataValue: enabled ? 1 : 0,
});
this.accessory.context.device.data.snapshot['airState.wMode.airClean'] = enabled ? 1 : 0;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting air clean mode:', error);
}
}
async setQuietModeActive(value) {
const device = this.accessory.context.device;
const status = this.Status;
const enabled = normalizeBoolean(value);
if (!(this.supportsQuietMode(device) && status.isPowerOn && status.opMode === OpMode.COOL)) {
this.logger.debug(`Quiet mode is not supported in the current state. Power: ${status.isPowerOn}, Mode: ${status.opMode}`);
return;
}
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.miscFuncState.silentAWHP',
dataValue: enabled ? 1 : 0,
});
this.accessory.context.device.data.snapshot['airState.miscFuncState.silentAWHP'] = enabled ? 1 : 0;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting quiet mode:', error);
}
}
async setJetModeActive(value) {
const device = this.accessory.context.device;
const status = this.Status;
const enabled = normalizeBoolean(value);
if (!(this.supportsJetMode(device) && status.isPowerOn && status.opMode === OpMode.COOL)) {
this.logger.debug(`Jet mode is not supported in the current state. Power: ${status.isPowerOn}, Mode: ${status.opMode}`);
return;
}
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.wMode.jet',
dataValue: enabled ? 1 : 0,
});
this.accessory.context.device.data.snapshot['airState.wMode.jet'] = enabled ? 1 : 0;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting jet mode:', error);
}
}
async setFanState(value) {
void value;
const status = this.Status;
if (!status.isPowerOn) {
this.logger.debug('Power is off, cannot set fan state');
return;
}
const device = this.accessory.context.device;
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.windStrength',
dataValue: FanSpeed.HIGH,
});
this.accessory.context.device.data.snapshot['airState.windStrength'] = FanSpeed.HIGH;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting fan state:', error);
}
}
updateAccessoryCharacteristic(device) {
super.updateAccessoryCharacteristic(device);
this.updateAccessoryActiveCharacteristic();
this.updateAccessoryCurrentTemperatureCharacteristic();
this.updateAccessoryStateCharacteristics();
this.updateAccessoryTemperatureCharacteristics();
this.updateAccessoryFanStateCharacteristics();
this.updateAccessoryTotalConsumptionCharacteristic();
this.updateAccessoryAirQualityCharacteristic();
this.updateAccessoryTemperatureSensorCharacteristic();
this.updateAccessoryHumiditySensorCharacteristic();
this.updateAccessoryFanV2Characteristic();
this.updateAccessoryLedControlCharacteristic();
this.updateAccessoryJetModeCharacteristic();
this.updateAccessoryQuietModeCharacteristic();
this.updateAccessoryEnergySaveModeCharacteristic();
this.updateAccessoryAirCleanCharacteristic();
}
update(snapshot) {
if (this.pendingPowerState !== undefined && Object.prototype.hasOwnProperty.call(snapshot, 'airState.operation')) {
const now = Date.now();
const incomingPowerState = Number(snapshot['airState.operation']);
if (incomingPowerState === this.pendingPowerState || now > this.pendingPowerStateUntil) {
this.pendingPowerState = undefined;
}
else {
this.logger.debug('Ignoring stale AC power state while waiting for ThinQ to catch up:', incomingPowerState);
snapshot = {
...snapshot,
'airState.operation': this.pendingPowerState,
};
}
}
super.update(snapshot);
}
updateAccessoryActiveCharacteristic() {
this.service.updateCharacteristic(this.platform.Characteristic.Active, this.Status.isPowerOn ? this.platform.Characteristic.Active.ACTIVE : this.platform.Characteristic.Active.INACTIVE);
}
updateAccessoryCurrentTemperatureCharacteristic() {
const currentTemperature = this.Status.currentTemperature;
if (typeof currentTemperature === 'number' && !isNaN(currentTemperature)) {
this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, currentTemperature);
}
}
updateAccessoryStateCharacteristics() {
const { Characteristic } = this.platform;
const status = this.Status;
if (!status.isPowerOn) {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.INACTIVE);
}
else if (status.opMode === OpMode.HEAT) {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.HEATING);
this.service.updateCharacteristic(Characteristic.TargetHeaterCoolerState, Characteristic.TargetHeaterCoolerState.HEAT);
}
else if (status.opMode === OpMode.COOL || status.opMode === OpMode.DRY) {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.COOLING);
this.service.updateCharacteristic(Characteristic.TargetHeaterCoolerState, Characteristic.TargetHeaterCoolerState.COOL);
}
else if (status.opMode === OpMode.FAN) {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.IDLE);
this.service.updateCharacteristic(Characteristic.TargetHeaterCoolerState, Characteristic.TargetHeaterCoolerState.HEAT);
}
else if ([OpMode.AUTO, -1].includes(status.opMode)) {
if (status.currentTemperature < status.targetTemperature) {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.HEATING);
this.service.updateCharacteristic(Characteristic.TargetHeaterCoolerState, Characteristic.TargetHeaterCoolerState.HEAT);
}
else {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.COOLING);
this.service.updateCharacteristic(Characteristic.TargetHeaterCoolerState, Characteristic.TargetHeaterCoolerState.COOL);
}
}
else {
this.service.updateCharacteristic(Characteristic.CurrentHeaterCoolerState, Characteristic.CurrentHeaterCoolerState.IDLE);
}
this.service.updateCharacteristic(this.platform.customCharacteristics.ACMode, (() => {
if (status.opMode === OpMode.COOL && status.isEnergySaveOn) {
return ACModeOption.ENERGY_SAVE;
}
switch (status.opMode) {
case OpMode.FAN:
return ACModeOption.FAN;
case OpMode.DRY:
return ACModeOption.DRY;
default:
return ACModeOption.COOL;
}
})());
}
updateAccessoryTemperatureCharacteristics() {
const temperature = this.Status.targetTemperature;
if (typeof temperature !== 'number' || isNaN(temperature)) {
return;
}
const currentState = this.service.getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState).value;
if (currentState === this.platform.Characteristic.CurrentHeaterCoolerState.HEATING) {
this.service.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, temperature);
}
if (currentState === this.platform.Characteristic.CurrentHeaterCoolerState.COOLING) {
this.service.updateCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature, temperature);
}
}
updateAccessoryFanStateCharacteristics() {
const { Characteristic } = this.platform;
this.service.updateCharacteristic(Characteristic.RotationSpeed, this.Status.rotationSpeed);
this.service.updateCharacteristic(Characteristic.SwingMode, this.Status.isSwingOn ? Characteristic.SwingMode.SWING_ENABLED : Characteristic.SwingMode.SWING_DISABLED);
}
updateAccessoryTotalConsumptionCharacteristic() {
this.service.updateCharacteristic(this.platform.customCharacteristics.TotalConsumption, this.Status.currentConsumption);
}
updateAccessoryAirQualityCharacteristic() {
if (this.config.ac_air_quality && this.serviceAirQuality && this.Status.airQuality && this.Status.airQuality.isOn) {
this.serviceAirQuality.updateCharacteristic(this.platform.Characteristic.AirQuality, this.Status.airQuality.overall);
if (this.Status.airQuality.PM2) {
this.serviceAirQuality.updateCharacteristic(this.platform.Characteristic.PM2_5Density, this.Status.airQuality.PM2);
}
if (this.Status.airQuality.PM10) {
this.serviceAirQuality.updateCharacteristic(this.platform.Characteristic.PM10Density, this.Status.airQuality.PM10);
}
}
}
updateAccessoryTemperatureSensorCharacteristic() {
if (this.config.ac_temperature_sensor && this.serviceSensor) {
this.serviceSensor.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.Status.currentTemperature);
this.serviceSensor.updateCharacteristic(this.platform.Characteristic.StatusActive, this.Status.isPowerOn);
}
}
updateAccessoryHumiditySensorCharacteristic() {
if (this.config.ac_humidity_sensor && this.serviceHumiditySensor) {
this.serviceHumiditySensor.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.Status.currentRelativeHumidity);
this.serviceHumiditySensor.updateCharacteristic(this.platform.Characteristic.StatusActive, this.Status.isPowerOn);
}
}
updateAccessoryFanV2Characteristic() {
if (this.config.ac_fan_control && this.serviceFanV2) {
this.serviceFanV2.updateCharacteristic(this.platform.Characteristic.Active, this.Status.isPowerOn ? this.platform.Characteristic.Active.ACTIVE : this.platform.Characteristic.Active.INACTIVE);
this.serviceFanV2.updateCharacteristic(this.platform.Characteristic.CurrentFanState, this.Status.isPowerOn ? this.platform.Characteristic.CurrentFanState.BLOWING_AIR : this.platform.Characteristic.CurrentFanState.INACTIVE);
this.serviceFanV2.updateCharacteristic(this.platform.Characteristic.TargetFanState, this.platform.Characteristic.TargetFanState.MANUAL);
this.serviceFanV2.updateCharacteristic(this.platform.Characteristic.RotationSpeed, this.Status.rotationSpeed);
this.serviceFanV2.updateCharacteristic(this.platform.Characteristic.SwingMode, this.Status.isSwingOn
? this.platform.Characteristic.SwingMode.SWING_ENABLED
: this.platform.Characteristic.SwingMode.SWING_DISABLED);
}
}
updateAccessoryLedControlCharacteristic() {
if (this.config.ac_led_control && this.serviceLight) {
this.serviceLight.updateCharacteristic(this.platform.Characteristic.On, this.Status.isLightOn);
}
}
updateAccessoryJetModeCharacteristic() {
if (this.serviceJetMode && this.supportsJetMode(this.accessory.context.device)) {
this.serviceJetMode.updateCharacteristic(this.platform.Characteristic.On, !!this.accessory.context.device.snapshot['airState.wMode.jet']);
}
}
updateAccessoryQuietModeCharacteristic() {
const device = this.accessory.context.device;
if (this.serviceQuietMode && this.supportsQuietMode(device)) {
this.serviceQuietMode.updateCharacteristic(this.platform.Characteristic.On, !!device.snapshot['airState.miscFuncState.silentAWHP']);
}
}
updateAccessoryEnergySaveModeCharacteristic() {
const device = this.accessory.context.device;
if (this.serviceEnergySaveMode && this.isEnergySaveSupported(device) && this.config.ac_energy_save) {
this.serviceEnergySaveMode.updateCharacteristic(this.platform.Characteristic.On, !!device.snapshot['airState.powerSave.basic']);
}
}
updateAccessoryAirCleanCharacteristic() {
const device = this.accessory.context.device;
if (this.serviceAirClean && this.supportsAirClean(device) && this.config.ac_air_clean) {
this.serviceAirClean.updateCharacteristic(this.platform.Characteristic.On, !!device.snapshot['airState.wMode.airClean']);
}
}
async setLight(value) {
const status = this.Status;
if (!status.isPowerOn) {
this.logger.debug('Power is off, cannot set light state');
return;
}
try {
const device = this.accessory.context.device;
const enabled = normalizeBoolean(value);
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.lightingState.displayControl',
dataValue: enabled ? 1 : 0,
});
this.accessory.context.device.data.snapshot['airState.lightingState.displayControl'] = enabled ? 1 : 0;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting light state:', error);
}
}
async setTargetState(value) {
this.logger.debug('Set target AC mode = ', value);
const { Characteristic: { TargetHeaterCoolerState, }, } = this.platform;
const targetValue = normalizeNumber(value);
if (targetValue === null) {
return;
}
this.currentTargetState = targetValue;
try {
switch (targetValue) {
case TargetHeaterCoolerState.HEAT:
await this.setACMode(ACModeOption.FAN);
break;
case TargetHeaterCoolerState.COOL:
await this.setACMode(ACModeOption.COOL);
break;
default:
return;
}
}
catch (error) {
this.logger.error('Error setting target state:', error);
}
}
async setActive(value) {
const device = this.accessory.context.device;
const isOn = normalizeBoolean(value);
const isOnNumeric = isOn ? 1 : 0;
this.logger.debug('Set power on = ', isOnNumeric, ' current status = ', this.Status.isPowerOn);
if ((this.Status.isPowerOn && isOnNumeric === 1) || (!this.Status.isPowerOn && isOnNumeric === 0)) {
this.logger.debug('Power state already matches incoming value; skipping deviceControl.');
return;
}
this.pendingPowerState = isOnNumeric;
this.pendingPowerStateUntil = Date.now() + 10000;
this.accessory.context.device.data.snapshot['airState.operation'] = isOnNumeric;
this.updateAccessoryCharacteristic(this.accessory.context.device);
try {
const success = await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.operation',
dataValue: isOnNumeric,
}, 'Operation');
if (success === false) {
this.pendingPowerState = undefined;
this.accessory.context.device.data.snapshot['airState.operation'] = isOn ? 0 : 1;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
}
catch (error) {
this.pendingPowerState = undefined;
this.accessory.context.device.data.snapshot['airState.operation'] = isOn ? 0 : 1;
this.updateAccessoryCharacteristic(this.accessory.context.device);
this.logger.error('Error setting active state:', error);
}
}
async setTargetTemperature(value) {
const status = this.Status;
if (!status.isPowerOn) {
this.logger.error('Power is off, cannot set target temperature');
return;
}
const device = this.accessory.context.device;
const vNum = normalizeNumber(value);
if (vNum === null) {
this.logger.error('Invalid temperature value: ', value);
return;
}
const temperatureLG = status.convertTemperatureCelsiusFromHomekitToLG(vNum);
if (typeof temperatureLG !== 'number' || isNaN(temperatureLG)) {
this.logger.error('Converted temperature is not a valid number:', temperatureLG);
return;
}
if (temperatureLG === Number(device.snapshot['airState.tempState.target'])) {
this.logger.debug('Target temperature is identical to current setting; skipping.');
return;
}
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.tempState.target',
dataValue: temperatureLG,
});
this.accessory.context.device.data.snapshot['airState.tempState.target'] = temperatureLG;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting target temperature:', error);
}
}
async setFanSpeed(value) {
if (!this.Status.isPowerOn) {
return;
}
const vNum = normalizeNumber(value);
if (vNum === null) {
return;
}
const numeric = Math.round(vNum);
let index = 0;
let minDiff = Number.MAX_VALUE;
for (let i = 0; i < FAN_ROTATION_SPEEDS.length; i++) {
const diff = Math.abs(FAN_ROTATION_SPEEDS[i] - numeric);
if (diff < minDiff) {
minDiff = diff;
index = i;
}
}
const speedValue = Math.min(MAX_FAN_SPEED, index + 1);
const windStrength = FAN_SPEED_MAP[speedValue - 1] || FanSpeed.HIGH;
const device = this.accessory.context.device;
if (windStrength === Number(device.snapshot['airState.windStrength'])) {
return;
}
this.logger.debug('Set fan speed = ', speedValue);
try {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.windStrength',
dataValue: windStrength,
});
this.accessory.context.device.data.snapshot['airState.windStrength'] = windStrength;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting fan speed:', error);
}
}
async setSwingMode(value) {
if (!this.Status.isPowerOn) {
this.logger.debug('Power is off, cannot set swing mode');
return;
}
const swingValue = normalizeBoolean(value) ? '100' : '0';
const device = this.accessory.context.device;
try {
if (this.config.ac_swing_mode === 'BOTH') {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: null,
dataValue: null,
dataSetList: {
'airState.wDir.vStep': swingValue,
'airState.wDir.hStep': swingValue,
},
dataGetList: null,
}, 'Set', 'favoriteCtrl');
this.accessory.context.device.data.snapshot['airState.wDir.vStep'] = swingValue;
this.accessory.context.device.data.snapshot['airState.wDir.hStep'] = swingValue;
}
else if (this.config.ac_swing_mode === 'VERTICAL') {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.wDir.vStep',
dataValue: swingValue,
});
this.accessory.context.device.data.snapshot['airState.wDir.vStep'] = swingValue;
}
else if (this.config.ac_swing_mode === 'HORIZONTAL') {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.wDir.hStep',
dataValue: swingValue,
});
this.accessory.context.device.data.snapshot['airState.wDir.hStep'] = swingValue;
}
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting swing mode:', error);
}
}
async setOpMode(deviceId, opMode) {
return await this.platform.ThinQ?.deviceControl(deviceId, {
dataKey: 'airState.opMode',
dataValue: opMode,
});
}
async setACMode(value) {
if (!this.Status.isPowerOn) {
return;
}
const device = this.accessory.context.device;
const modeValue = normalizeNumber(value);
if (modeValue === null) {
return;
}
let opMode = OpMode.COOL;
let energySave = false;
switch (modeValue) {
case ACModeOption.FAN:
opMode = OpMode.FAN;
break;
case ACModeOption.DRY:
opMode = OpMode.DRY;
break;
case ACModeOption.ENERGY_SAVE:
opMode = OpMode.COOL;
energySave = true;
break;
default:
opMode = OpMode.COOL;
break;
}
try {
if (opMode !== this.Status.opMode) {
const success = await this.setOpMode(device.id, opMode);
if (success === false) {
return;
}
this.accessory.context.device.data.snapshot['airState.opMode'] = opMode;
}
if (this.isEnergySaveSupported(device)) {
const desired = energySave ? 1 : 0;
if (Number(device.snapshot['airState.powerSave.basic'] || 0) !== desired) {
await this.platform.ThinQ?.deviceControl(device.id, {
dataKey: 'airState.powerSave.basic',
dataValue: desired,
});
this.accessory.context.device.data.snapshot['airState.powerSave.basic'] = desired;
}
}
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
catch (error) {
this.logger.error('Error setting AC mode:', error);
}
}
setupButton(device) {
if (!this.config.ac_buttons.length) {
return;
}
this.serviceLabelButtons = this.accessory.getService('Buttons')
|| this.accessory.addService(this.platform.Service.ServiceLabel, 'Buttons', 'Buttons');
for (const linkedService of this.serviceLabelButtons.linkedServices ?? []) {
this.accessory.removeService(linkedService);
}
for (const button of this.config.ac_buttons) {
this.setupButtonOpmode(device, button.name, Number(button.op_mode));
}
}
setupButtonOpmode(device, name, opMode) {
const { Service: { Switch, }, Characteristic, } = this.platform;
if (!this.serviceLabelButtons) {
this.logger.error('ServiceLabelButtons not found cant setup button');
return;
}
const serviceButton = this.accessory.getService(name) || this.accessory.addService(Switch, name, name);
serviceButton.addOptionalCharacteristic(Characteristic.ConfiguredName);
serviceButton.setCharacteristic(Characteristic.ConfiguredName, name);
serviceButton.getCharacteristic(this.platform.Characteristic.On)
.onGet(() => {
return this.Status.opMode === opMode;
})
.onSet((value) => {
this.handleButtonOpmode(value, opMode);
});
this.serviceLabelButtons.addLinkedService(serviceButton);
}
async handleButtonOpmode(value, opMode) {
if (normalizeBoolean(value)) {
if (this.Status.opMode !== opMode) {
const success = await this.setOpMode(this.accessory.context.device.id, opMode);
if (success !== false) {
this.accessory.context.device.data.snapshot['airState.opMode'] = opMode;
this.updateAccessoryCharacteristic(this.accessory.context.device);
}
}
}
else {
const success = await this.setOpMode(this.accessory.context.device.id, OpMode.COOL);
if (success === false) {
return;
}
this.accessory.context.device.data.snapshot['airState.opMode'] = OpMode.COOL;
this.updateAccessoryCharacteristic(this.accessory.context.device);
await this.setTargetState(this.currentTargetState);
}
}
}
export class ACStatus {
data;
device;
config;
logger;
constructor(data, device, config, logger) {
this.data = data;
this.device = device;
this.config = config;
this.logger = logger;
}
get isFahrenheitUnit() {
return (this.config.ac_temperature_unit || '').toLowerCase() === 'f';
}
convertTemperatureCelsiusFromHomekitToLG(temperatureInCelsius) {
const tempNum = Number(temperatureInCelsius);
if (!this.isFahrenheitUnit) {
return tempNum;
}
const temperatureInFahrenheit = Math.round(cToF(tempNum));
try {
const mapped = this.device.deviceModel.lookupMonitorValue
&& this.device.deviceModel.lookupMonitorValue('TempFahToCel', String(temperatureInFahrenheit));
if (typeof mapped !== 'undefined' && mapped !== null) {
const n = Number(mapped);
if (!isNaN(n)) {
return n;
}
}
}
catch (error) {
this.logger.warn('Temperature mapping lookup failed, falling back to direct conversion.', error);
}
return temperatureInFahrenheit;
}
convertTemperatureCelsiusFromLGToHomekit(temperature) {
const tempNum = Number(temperature);
if (!this.isFahrenheitUnit) {
return tempNum;
}
try {
const mapped = this.device.deviceModel.lookupMonitorValue
&& this.device.deviceModel.lookupMonitorValue('TempCelToFah', String(tempNum));
if (typeof mapped !== 'undefined' && mapped !== null) {
const n = Number(mapped);
if (!isNaN(n)) {
return Math.round(fToC(n) * 100) / 100;
}
}
}
catch (error) {
this.logger.warn('Temperature mapping lookup failed, falling back to direct conversion.', error);
}
return Math.round(fToC(tempNum) * 100) / 100;
}
get opMode() {
return this.data['airState.opMode'];
}
get isPowerOn() {
return !!this.data['airState.operation'];
}
get currentRelativeHumidity() {
const humidity = parseInt(this.data['airState.humidity.current']);
if (humidity > 100) {
return humidity / 10;
}
return humidity;
}
get currentTemperature() {
return this.convertTemperatureCelsiusFromLGToHomekit(this.data['airState.tempState.current']);
}
get targetTemperature() {
return this.convertTemperatureCelsiusFromLGToHomekit(this.data['airState.tempState.target']);
}
get airQuality() {
if (!('airState.quality.overall' in this.data) && !('airState.quality.PM2' in this.data) && !('airState.quality.PM10' in this.data)) {
return null;
}
return {
isOn: this.isPowerOn || this.data['airState.quality.sensorMon'],
overall: parseInt(this.data['airState.quality.overall']),
PM2: parseInt(this.data['airState.quality.PM2'] || '0'),
PM10: parseInt(this.data['airState.quality.PM10'] || '0'),
};
}
get windStrength() {
const value = Number(this.data['airState.windStrength']);
if (value >= FanSpeed.HIGH) {
return 3;
}
if (value >= FanSpeed.MEDIUM) {
return 2;
}
return 1;
}
get rotationSpeed() {
return FAN_ROTATION_SPEEDS[this.windStrength - 1] ?? FAN_ROTATION_SPEEDS[1];
}
get isSwingOn() {
const vStep = Math.floor((this.data['airState.wDir.vStep'] || 0) / 100);
const hStep = Math.floor((this.data['airState.wDir.hStep'] || 0) / 100);
return !!(vStep + hStep);
}
get isLightOn() {
return !!this.data['airState.lightingState.displayControl'];
}
get isEnergySaveOn() {
return !!this.data['airState.powerSave.basic'];
}
get currentConsumption() {
const consumption = Number(this.data['airState.energy.onCurrent']);
if (isNaN(consumption)) {
return 0;
}
return consumption / 100;
}
get type() {
return this.device.deviceModel.data.Info.modelType || ACModelType.RAC;
}
getTemperatureRange([minRange, maxRange]) {
let temperature = {
type: ValueType.Range,
min: 0,
max: 0,
step: 0.01,
};
if (minRange && maxRange) {
const minRangeOptions = Object.values(minRange.options).filter((value) => typeof value === 'number');
const maxRangeOptions = Object.values(maxRange.options).filter((value) => typeof value === 'number');
if (minRangeOptions.length > 1) {
temperature.min = Math.min(...minRangeOptions.filter(value => value !== 0));
}
if (maxRangeOptions.length > 1) {
temperature.max = Math.max(...maxRangeOptions.filter(value => value !== 0));
}
}
if (!temperature.min || !temperature.max) {
temperature = this.device.deviceModel.value('airState.tempState.limitMin');
}
if ((!temperature?.min || !temperature?.max) && this.device.deviceModel.value('airState.tempState.target')) {
temperature = this.device.deviceModel.value('airState.tempState.target');
}
return temperature;
}
getTemperatureRangeForHeating() {
let heatLowLimitKey;
let heatHighLimitKey;
if (this.type === ACModelType.AWHP) {
heatLowLimitKey = 'support.airState.tempState.waterTempHeatMin';
heatHighLimitKey = 'support.airState.tempState.waterTempHeatMax';
}
else {
heatLowLimitKey = 'support.heat