diff --git a/README.md b/README.md index 6b69df1..ade3ace 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ Features currently available in Home Assistant: * Switch and binary sensor for Auto Dry (also known as Auto Clean) feature. Used to dry indoor unit when it's turned off after cooling/dehumidifying. * Sensors for reporting outdoor unit on/off, defrost, preheat, error code. * Sensors for reporting in/mid/out pipe temperatures (if supported by unit). +* Optional passive sensors for total energy and current power, if the unit emits `0xCC` + or `0xCF` frames on the wired-controller bus. * Input field for sleep timer from 0 to 420 minutes (0 turns off the sleep timer). * Input fields for fan speed installer setting (to fine-tune fan speeds, 0-255 with 0 being factory default). This is installer setting 3 (ESP Setting) on LG controllers. * Select option for over heating installer setting from 0-4 (to change over heating behavior in heating mode). This is installer setting 15 (Over Heating) on LG controllers. @@ -118,6 +120,10 @@ The LG ThinQ app and wireless remote can still be used to change these settings Unfortunately not all settings are exposed to the wired controller, but if you're interested in a feature and it's supported by the PREMTB100 or PREMTA200 controller, please open an issue and we can consider adding it. +The optional energy/power sensors only publish values for units that send the relevant wired-bus +frames. Some LG units expose energy data through ThinQ/WiFi but do not send `0xCC` or `0xCF` +frames to the wired controller; for those units these sensors will not update. + # Tips * [Issue #43](https://github.com/JanM321/esphome-lg-controller/issues/43) has some information on temperature sensors that work well for this. * It's possible to use a Home Assistant template sensor as room temperature sensor. I'm [using this](https://gist.github.com/JanM321/b550285713f20231386509b2c227f0b8) to work around some issues with my LG Multi F unit in heating mode. diff --git a/esphome/components/lg_controller/climate.py b/esphome/components/lg_controller/climate.py index 5677d1f..342ddcb 100644 --- a/esphome/components/lg_controller/climate.py +++ b/esphome/components/lg_controller/climate.py @@ -2,7 +2,17 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import binary_sensor, climate, number, select, sensor, switch, uart -from esphome.const import CONF_ID, CONF_RX_PIN, CONF_INTERNAL +from esphome.const import ( + CONF_ID, + CONF_RX_PIN, + CONF_INTERNAL, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_KILOWATT, + UNIT_KILOWATT_HOURS, +) CODEOWNERS = ["JanM321"] DEPENDENCIES = ["uart"] @@ -37,6 +47,8 @@ CONF_PIPE_TEMP_IN = "pipe_temp_in" CONF_PIPE_TEMP_MID = "pipe_temp_mid" CONF_PIPE_TEMP_OUT = "pipe_temp_out" +CONF_POWER_CONSUMPTION = "power_consumption" +CONF_CURRENT_POWER = "current_power" CONF_DEFROST = "defrost" CONF_PREHEAT = "preheat" @@ -75,6 +87,18 @@ cv.Required(CONF_PIPE_TEMP_IN): sensor.sensor_schema(), cv.Required(CONF_PIPE_TEMP_MID): sensor.sensor_schema(), cv.Required(CONF_PIPE_TEMP_OUT): sensor.sensor_schema(), + cv.Optional(CONF_POWER_CONSUMPTION): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_CURRENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Required(CONF_DEFROST): binary_sensor.binary_sensor_schema(), cv.Required(CONF_PREHEAT): binary_sensor.binary_sensor_schema(), @@ -120,6 +144,14 @@ async def to_code(config): pipe_temp_in = await sensor.new_sensor(config[CONF_PIPE_TEMP_IN]) pipe_temp_mid = await sensor.new_sensor(config[CONF_PIPE_TEMP_MID]) pipe_temp_out = await sensor.new_sensor(config[CONF_PIPE_TEMP_OUT]) + if CONF_POWER_CONSUMPTION in config: + power_consumption = await sensor.new_sensor(config[CONF_POWER_CONSUMPTION]) + else: + power_consumption = cg.nullptr + if CONF_CURRENT_POWER in config: + current_power = await sensor.new_sensor(config[CONF_CURRENT_POWER]) + else: + current_power = cg.nullptr defrost = await binary_sensor.new_binary_sensor(config[CONF_DEFROST]) preheat = await binary_sensor.new_binary_sensor(config[CONF_PREHEAT]) @@ -135,6 +167,7 @@ async def to_code(config): fan_speed_slow, fan_speed_low, fan_speed_medium, fan_speed_high, sleep_timer, error_code, pipe_temp_in, pipe_temp_mid, pipe_temp_out, + power_consumption, current_power, defrost, preheat, outdoor, auto_dry_active, purifier, internal_thermistor, auto_dry, config[CONF_FAHRENHEIT], config[CONF_IS_SLAVE_CONTROLLER]) diff --git a/esphome/components/lg_controller/lg-controller.h b/esphome/components/lg_controller/lg-controller.h index 6493f27..42f0679 100644 --- a/esphome/components/lg_controller/lg-controller.h +++ b/esphome/components/lg_controller/lg-controller.h @@ -153,6 +153,8 @@ class LgController final : public climate::Climate, public uart::UARTDevice, pub esphome::sensor::Sensor& pipe_temp_in_; esphome::sensor::Sensor& pipe_temp_mid_; esphome::sensor::Sensor& pipe_temp_out_; + esphome::sensor::Sensor* power_consumption_; + esphome::sensor::Sensor* current_power_; esphome::binary_sensor::BinarySensor& defrost_; esphome::binary_sensor::BinarySensor& preheat_; esphome::binary_sensor::BinarySensor& outdoor_; @@ -347,6 +349,8 @@ class LgController final : public climate::Climate, public uart::UARTDevice, pub sensor::Sensor* pipe_temp_in, sensor::Sensor* pipe_temp_mid, sensor::Sensor* pipe_temp_out, + sensor::Sensor* power_consumption, + sensor::Sensor* current_power, binary_sensor::BinarySensor* defrost, binary_sensor::BinarySensor* preheat, binary_sensor::BinarySensor* outdoor, @@ -371,6 +375,8 @@ class LgController final : public climate::Climate, public uart::UARTDevice, pub pipe_temp_in_(*pipe_temp_in), pipe_temp_mid_(*pipe_temp_mid), pipe_temp_out_(*pipe_temp_out), + power_consumption_(power_consumption), + current_power_(current_power), defrost_(*defrost), preheat_(*preheat), outdoor_(*outdoor), @@ -935,6 +941,12 @@ class LgController final : public climate::Climate, public uart::UARTDevice, pub case 3: // 0xCB/AB/2B process_type_b_settings_message(*sender, buffer); break; + case 4: // 0xCC/AC/2C + process_type_c_status_message(*sender, buffer); + break; + case 7: // 0xCF/AF/2F + process_type_f_status_message(*sender, buffer); + break; default: return; } @@ -1273,6 +1285,34 @@ class LgController final : public climate::Climate, public uart::UARTDevice, pub } } + static float decode_power_nibbles_(const uint8_t* buffer, size_t offset, size_t length, float divisor) { + // Power payloads use LG's nibble format rather than binary integers. Some documented + // current-power examples contain A-F nibbles, so this intentionally is not strict BCD. + uint32_t result = 0; + for (size_t i = 0; i < length; i++) { + result = result * 100 + ((buffer[offset + i] >> 4) & 0xF) * 10 + (buffer[offset + i] & 0xF); + } + return result / divisor; + } + + void process_type_c_status_message(MessageSender sender, const uint8_t* buffer) { + if (sender != MessageSender::Unit) { + return; + } + if (power_consumption_ != nullptr) { + power_consumption_->publish_state(decode_power_nibbles_(buffer, 3, 3, 10.0f)); + } + } + + void process_type_f_status_message(MessageSender sender, const uint8_t* buffer) { + if (sender != MessageSender::Unit) { + return; + } + if (current_power_ != nullptr) { + current_power_->publish_state(decode_power_nibbles_(buffer, 2, 3, 1000.0f)); + } + } + void update() { ESP_LOGD(TAG, "update");