From 82d5d50d616f6eed3c0a666caa9344e7694ff7e1 Mon Sep 17 00:00:00 2001 From: square99 Date: Mon, 22 Jun 2020 10:29:43 +0900 Subject: [PATCH] Add LG Climate IR (#1097) * Add LG Climate ir * Add LG Climate IR * Add LG Climate IR * Add LG Climate IR * Add LG Climate IR * Add LG Climate IR * Add LG Climate IR Co-authored-by: Sangheon Heo --- esphome/components/climate_ir_lg/__init__.py | 0 esphome/components/climate_ir_lg/climate.py | 18 ++ .../climate_ir_lg/climate_ir_lg.cpp | 204 ++++++++++++++++++ .../components/climate_ir_lg/climate_ir_lg.h | 44 ++++ tests/test1.yaml | 2 + 5 files changed, 268 insertions(+) create mode 100644 esphome/components/climate_ir_lg/__init__.py create mode 100644 esphome/components/climate_ir_lg/climate.py create mode 100644 esphome/components/climate_ir_lg/climate_ir_lg.cpp create mode 100644 esphome/components/climate_ir_lg/climate_ir_lg.h diff --git a/esphome/components/climate_ir_lg/__init__.py b/esphome/components/climate_ir_lg/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/climate_ir_lg/climate.py b/esphome/components/climate_ir_lg/climate.py new file mode 100644 index 0000000000..37bf9e2628 --- /dev/null +++ b/esphome/components/climate_ir_lg/climate.py @@ -0,0 +1,18 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ['climate_ir'] + +climate_ir_lg_ns = cg.esphome_ns.namespace('climate_ir_lg') +LgIrClimate = climate_ir_lg_ns.class_('LgIrClimate', climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(LgIrClimate), +}) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp new file mode 100644 index 0000000000..80677e6de3 --- /dev/null +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -0,0 +1,204 @@ +#include "climate_ir_lg.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace climate_ir_lg { + +static const char *TAG = "climate.climate_ir_lg"; + +const uint32_t COMMAND_ON = 0x00000; +const uint32_t COMMAND_ON_AI = 0x03000; +const uint32_t COMMAND_COOL = 0x08000; +const uint32_t COMMAND_OFF = 0xC0000; +const uint32_t COMMAND_SWING = 0x10000; +// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. +const uint32_t COMMAND_AUTO = 0x0B000; +const uint32_t COMMAND_DRY_FAN = 0x09000; + +const uint32_t COMMAND_MASK = 0xFF000; + +const uint32_t FAN_MASK = 0xF0; +const uint32_t FAN_AUTO = 0x50; +const uint32_t FAN_MIN = 0x00; +const uint32_t FAN_MED = 0x20; +const uint32_t FAN_MAX = 0x40; + +// Temperature +const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1; +const uint32_t TEMP_MASK = 0XF00; +const uint32_t TEMP_SHIFT = 8; + +// Constants +static const uint32_t HEADER_HIGH_US = 8000; +static const uint32_t HEADER_LOW_US = 4000; +static const uint32_t BIT_HIGH_US = 600; +static const uint32_t BIT_ONE_LOW_US = 1600; +static const uint32_t BIT_ZERO_LOW_US = 550; + +const uint16_t BITS = 28; + +void LgIrClimate::transmit_state() { + uint32_t remote_state = 0x8800000; + + // ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_); + if (send_swing_cmd_) { + send_swing_cmd_ = false; + remote_state |= COMMAND_SWING; + } else { + if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) { + remote_state |= COMMAND_ON_AI; + } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) { + remote_state |= COMMAND_ON; + this->mode = climate::CLIMATE_MODE_COOL; + } else { + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + remote_state |= COMMAND_COOL; + break; + case climate::CLIMATE_MODE_AUTO: + remote_state |= COMMAND_AUTO; + break; + case climate::CLIMATE_MODE_DRY: + remote_state |= COMMAND_DRY_FAN; + break; + case climate::CLIMATE_MODE_OFF: + default: + remote_state |= COMMAND_OFF; + break; + } + } + mode_before_ = this->mode; + + ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode); + + if (this->mode == climate::CLIMATE_MODE_OFF) { + remote_state |= FAN_AUTO; + } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) { + switch (this->fan_mode) { + case climate::CLIMATE_FAN_HIGH: + remote_state |= FAN_MAX; + break; + case climate::CLIMATE_FAN_MEDIUM: + remote_state |= FAN_MED; + break; + case climate::CLIMATE_FAN_LOW: + remote_state |= FAN_MIN; + break; + case climate::CLIMATE_FAN_AUTO: + default: + remote_state |= FAN_AUTO; + break; + } + } + + if (this->mode == climate::CLIMATE_MODE_AUTO) { + this->fan_mode = climate::CLIMATE_FAN_AUTO; + // remote_state |= FAN_MODE_AUTO_DRY; + } + if (this->mode == climate::CLIMATE_MODE_COOL) { + auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX)); + remote_state |= ((temp - 15) << TEMP_SHIFT); + } + } + transmit_(remote_state); + this->publish_state(); +} + +bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t nbits = 0; + uint32_t remote_state = 0; + + if (!data.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return false; + + for (nbits = 0; nbits < 32; nbits++) { + if (data.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + remote_state = (remote_state << 1) | 1; + } else if (data.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + remote_state = (remote_state << 1) | 0; + } else if (nbits == BITS) { + break; + } else { + return false; + } + } + + ESP_LOGD(TAG, "Decoded 0x%02X", remote_state); + if ((remote_state & 0xFF00000) != 0x8800000) + return false; + + if ((remote_state & COMMAND_MASK) == COMMAND_ON) { + this->mode = climate::CLIMATE_MODE_COOL; + } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) { + this->mode = climate::CLIMATE_MODE_AUTO; + } + + if ((remote_state & COMMAND_MASK) == COMMAND_OFF) { + this->mode = climate::CLIMATE_MODE_OFF; + } else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) { + this->swing_mode = + this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; + } else { + if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) + this->mode = climate::CLIMATE_MODE_AUTO; + else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) { + this->mode = climate::CLIMATE_MODE_DRY; + } else { + this->mode = climate::CLIMATE_MODE_COOL; + } + } + + // Temperature + if (this->mode == climate::CLIMATE_MODE_COOL) + this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; + + // Fan Speed + if (this->mode == climate::CLIMATE_MODE_AUTO) { + this->fan_mode = climate::CLIMATE_FAN_AUTO; + } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) { + if ((remote_state & FAN_MASK) == FAN_AUTO) + this->fan_mode = climate::CLIMATE_FAN_AUTO; + else if ((remote_state & FAN_MASK) == FAN_MIN) + this->fan_mode = climate::CLIMATE_FAN_LOW; + else if ((remote_state & FAN_MASK) == FAN_MED) + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + else if ((remote_state & FAN_MASK) == FAN_MAX) + this->fan_mode = climate::CLIMATE_FAN_HIGH; + } + this->publish_state(); + + return true; +} +void LgIrClimate::transmit_(uint32_t value) { + calc_checksum_(value); + ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value); + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + data->set_carrier_frequency(38000); + data->reserve(2 + BITS * 2u); + + data->item(HEADER_HIGH_US, HEADER_LOW_US); + + for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) { + if (value & mask) + data->item(BIT_HIGH_US, BIT_ONE_LOW_US); + else + data->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } + data->mark(BIT_HIGH_US); + transmit.perform(); +} +void LgIrClimate::calc_checksum_(uint32_t &value) { + uint32_t mask = 0xF; + uint32_t sum = 0; + for (uint8_t i = 1; i < 8; i++) { + sum += (value & (mask << (i * 4))) >> (i * 4); + } + + value |= (sum & mask); +} + +} // namespace climate_ir_lg +} // namespace esphome diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.h b/esphome/components/climate_ir_lg/climate_ir_lg.h new file mode 100644 index 0000000000..204482e7a8 --- /dev/null +++ b/esphome/components/climate_ir_lg/climate_ir_lg.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace climate_ir_lg { + +// Temperature +const uint8_t TEMP_MIN = 18; // Celsius +const uint8_t TEMP_MAX = 30; // Celsius + +class LgIrClimate : public climate_ir::ClimateIR { + public: + LgIrClimate() + : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {} + + /// Override control to change settings of the climate device. + void control(const climate::ClimateCall &call) override { + send_swing_cmd_ = call.get_swing_mode().has_value(); + // swing resets after unit powered off + if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF) + this->swing_mode = climate::CLIMATE_SWING_OFF; + climate_ir::ClimateIR::control(call); + } + + protected: + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + /// Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + + bool send_swing_cmd_{false}; + + void calc_checksum_(uint32_t &value); + void transmit_(uint32_t value); + + climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF}; +}; + +} // namespace climate_ir_lg +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index bd44ff6e63..5343d48970 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1277,6 +1277,8 @@ climate: name: Mitsubishi - platform: whirlpool name: Whirlpool Climate + - platform: climate_ir_lg + name: LG Climate switch: - platform: gpio