From 29f72037feefe9e0448c23985034452f5f5d826e Mon Sep 17 00:00:00 2001 From: Sourabh Jaiswal Date: Sun, 8 Aug 2021 19:29:52 +0530 Subject: [PATCH] Added support for Hitachi AC424 remote type (#2101) --- CODEOWNERS | 1 + esphome/components/hitachi_ac424/__init__.py | 1 + esphome/components/hitachi_ac424/climate.py | 20 + .../hitachi_ac424/hitachi_ac424.cpp | 368 ++++++++++++++++++ .../components/hitachi_ac424/hitachi_ac424.h | 123 ++++++ 5 files changed, 513 insertions(+) create mode 100644 esphome/components/hitachi_ac424/__init__.py create mode 100644 esphome/components/hitachi_ac424/climate.py create mode 100644 esphome/components/hitachi_ac424/hitachi_ac424.cpp create mode 100644 esphome/components/hitachi_ac424/hitachi_ac424.h diff --git a/CODEOWNERS b/CODEOWNERS index b235643539..c79dfc066e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -48,6 +48,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/havells_solar/* @sourabhjaiswal +esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core diff --git a/esphome/components/hitachi_ac424/__init__.py b/esphome/components/hitachi_ac424/__init__.py new file mode 100644 index 0000000000..10f2c27fe8 --- /dev/null +++ b/esphome/components/hitachi_ac424/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@sourabhjaiswal"] diff --git a/esphome/components/hitachi_ac424/climate.py b/esphome/components/hitachi_ac424/climate.py new file mode 100644 index 0000000000..33532230df --- /dev/null +++ b/esphome/components/hitachi_ac424/climate.py @@ -0,0 +1,20 @@ +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"] + +hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424") +HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HitachiClimate), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp new file mode 100644 index 0000000000..10b83cbd58 --- /dev/null +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -0,0 +1,368 @@ +#include "hitachi_ac424.h" + +namespace esphome { +namespace hitachi_ac424 { + +static const char *const TAG = "climate.hitachi_ac424"; + +void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) { + if (offset >= 8 || !nbits) + return; // Short circuit as it won't change. + // Calculate the mask for the supplied value. + uint8_t mask = UINT8_MAX >> (8 - ((nbits > 8) ? 8 : nbits)); + // Calculate the mask & clear the space for the data. + // Clear the destination bits. + *dst &= ~(uint8_t)(mask << offset); + // Merge in the data. + *dst |= ((data & mask) << offset); +} + +void set_bit(uint8_t *const data, const uint8_t position, const bool on) { + uint8_t mask = 1 << position; + if (on) + *data |= mask; + else + *data &= ~mask; +} + +uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) { + for (uint16_t i = 1; i < length; i += 2) { + // Code done this way to avoid a compiler warning bug. + uint8_t inv = ~*(ptr + i - 1); + *(ptr + i) = inv; + } + return ptr; +} + +bool HitachiClimate::get_power_() { return remote_state_[HITACHI_AC424_POWER_BYTE] == HITACHI_AC424_POWER_ON; } + +void HitachiClimate::set_power_(bool on) { + set_button_(HITACHI_AC424_BUTTON_POWER); + remote_state_[HITACHI_AC424_POWER_BYTE] = on ? HITACHI_AC424_POWER_ON : HITACHI_AC424_POWER_OFF; +} + +uint8_t HitachiClimate::get_mode_() { return remote_state_[HITACHI_AC424_MODE_BYTE] & 0xF; } + +void HitachiClimate::set_mode_(uint8_t mode) { + uint8_t new_mode = mode; + switch (mode) { + // Fan mode sets a special temp. + case HITACHI_AC424_MODE_FAN: + set_temp_(HITACHI_AC424_TEMP_FAN, false); + break; + case HITACHI_AC424_MODE_HEAT: + case HITACHI_AC424_MODE_COOL: + case HITACHI_AC424_MODE_DRY: + break; + default: + new_mode = HITACHI_AC424_MODE_COOL; + } + set_bits(&remote_state_[HITACHI_AC424_MODE_BYTE], 0, 4, new_mode); + if (new_mode != HITACHI_AC424_MODE_FAN) + set_temp_(previous_temp_); + set_fan_(get_fan_()); // Reset the fan speed after the mode change. + set_power_(true); +} + +void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) { + uint8_t temp; + temp = std::min(celsius, HITACHI_AC424_TEMP_MAX); + temp = std::max(temp, HITACHI_AC424_TEMP_MIN); + set_bits(&remote_state_[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE, temp); + if (previous_temp_ > temp) + set_button_(HITACHI_AC424_BUTTON_TEMP_DOWN); + else if (previous_temp_ < temp) + set_button_(HITACHI_AC424_BUTTON_TEMP_UP); + if (set_previous) + previous_temp_ = temp; +} + +uint8_t HitachiClimate::get_fan_() { return remote_state_[HITACHI_AC424_FAN_BYTE] >> 4 & 0xF; } + +void HitachiClimate::set_fan_(uint8_t speed) { + uint8_t new_speed = std::max(speed, HITACHI_AC424_FAN_MIN); + uint8_t fan_max = HITACHI_AC424_FAN_MAX; + + // Only 2 x low speeds in Dry mode or Auto + if (get_mode_() == HITACHI_AC424_MODE_DRY && speed == HITACHI_AC424_FAN_AUTO) { + fan_max = HITACHI_AC424_FAN_AUTO; + } else if (get_mode_() == HITACHI_AC424_MODE_DRY) { + fan_max = HITACHI_AC424_FAN_MAX_DRY; + } else if (get_mode_() == HITACHI_AC424_MODE_FAN && speed == HITACHI_AC424_FAN_AUTO) { + // Fan Mode does not have auto. Set to safe low + new_speed = HITACHI_AC424_FAN_MIN; + } + + new_speed = std::min(new_speed, fan_max); + // Handle the setting the button value if we are going to change the value. + if (new_speed != get_fan_()) + set_button_(HITACHI_AC424_BUTTON_FAN); + // Set the values + + set_bits(&remote_state_[HITACHI_AC424_FAN_BYTE], 4, 4, new_speed); + remote_state_[9] = 0x92; + + // When fan is at min/max, additional bytes seem to be set + if (new_speed == HITACHI_AC424_FAN_MIN) + remote_state_[9] = 0x98; + remote_state_[29] = 0x01; +} + +void HitachiClimate::set_swing_v_toggle_(bool on) { + uint8_t button = get_button_(); // Get the current button value. + if (on) + button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV. + else if (button == HITACHI_AC424_BUTTON_SWINGV) // Asked to unset it + // It was set previous, so use Power as a default + button = HITACHI_AC424_BUTTON_POWER; + set_button_(button); +} + +bool HitachiClimate::get_swing_v_toggle_() { return get_button_() == HITACHI_AC424_BUTTON_SWINGV; } + +void HitachiClimate::set_swing_v_(bool on) { + set_swing_v_toggle_(on); // Set the button value. + set_bit(&remote_state_[HITACHI_AC424_SWINGV_BYTE], HITACHI_AC424_SWINGV_OFFSET, on); +} + +bool HitachiClimate::get_swing_v_() { + return HITACHI_AC424_GETBIT8(remote_state_[HITACHI_AC424_SWINGV_BYTE], HITACHI_AC424_SWINGV_OFFSET); +} + +void HitachiClimate::set_swing_h_(uint8_t position) { + if (position > HITACHI_AC424_SWINGH_LEFT_MAX) + return set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + set_bits(&remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, HITACHI_AC424_SWINGH_SIZE, position); + set_button_(HITACHI_AC424_BUTTON_SWINGH); +} + +uint8_t HitachiClimate::get_swing_h_() { + return HITACHI_AC424_GETBITS8(remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, + HITACHI_AC424_SWINGH_SIZE); +} + +uint8_t HitachiClimate::get_button_() { return remote_state_[HITACHI_AC424_BUTTON_BYTE]; } + +void HitachiClimate::set_button_(uint8_t button) { remote_state_[HITACHI_AC424_BUTTON_BYTE] = button; } + +void HitachiClimate::transmit_state() { + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + set_mode_(HITACHI_AC424_MODE_COOL); + break; + case climate::CLIMATE_MODE_DRY: + set_mode_(HITACHI_AC424_MODE_DRY); + break; + case climate::CLIMATE_MODE_HEAT: + set_mode_(HITACHI_AC424_MODE_HEAT); + break; + case climate::CLIMATE_MODE_HEAT_COOL: + set_mode_(HITACHI_AC424_MODE_AUTO); + break; + case climate::CLIMATE_MODE_FAN_ONLY: + set_mode_(HITACHI_AC424_MODE_FAN); + break; + case climate::CLIMATE_MODE_OFF: + set_power_(false); + break; + default: + ESP_LOGW(TAG, "Unsupported mode: %s", climate_mode_to_string(this->mode)); + } + + set_temp_(static_cast(this->target_temperature)); + + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + set_fan_(HITACHI_AC424_FAN_LOW); + break; + case climate::CLIMATE_FAN_MEDIUM: + set_fan_(HITACHI_AC424_FAN_MEDIUM); + break; + case climate::CLIMATE_FAN_HIGH: + set_fan_(HITACHI_AC424_FAN_HIGH); + break; + case climate::CLIMATE_FAN_ON: + case climate::CLIMATE_FAN_AUTO: + default: + set_fan_(HITACHI_AC424_FAN_AUTO); + } + + switch (this->swing_mode) { + case climate::CLIMATE_SWING_BOTH: + set_swing_v_(true); + set_swing_h_(HITACHI_AC424_SWINGH_AUTO); + break; + case climate::CLIMATE_SWING_VERTICAL: + set_swing_v_(true); + set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + break; + case climate::CLIMATE_SWING_HORIZONTAL: + set_swing_v_(false); + set_swing_h_(HITACHI_AC424_SWINGH_AUTO); + break; + case climate::CLIMATE_SWING_OFF: + set_swing_v_(false); + set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + break; + } + + // TODO: find change value to set button, now always set to power button + set_button_(HITACHI_AC424_BUTTON_POWER); + + invert_byte_pairs(remote_state_ + 3, HITACHI_AC424_STATE_LENGTH - 3); + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + data->set_carrier_frequency(HITACHI_AC424_FREQ); + + uint8_t repeat = 0; + for (uint8_t r = 0; r <= repeat; r++) { + // Header + data->item(HITACHI_AC424_HDR_MARK, HITACHI_AC424_HDR_SPACE); + // Data + for (uint8_t i : remote_state_) { + for (uint8_t j = 0; j < 8; j++) { + data->mark(HITACHI_AC424_BIT_MARK); + bool bit = i & (1 << j); + data->space(bit ? HITACHI_AC424_ONE_SPACE : HITACHI_AC424_ZERO_SPACE); + } + } + // Footer + data->item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_MIN_GAP); + } + transmit.perform(); + + dump_state_("Sent", remote_state_); +} + +bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) { + uint8_t power = remote_state[HITACHI_AC424_POWER_BYTE]; + ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC424_POWER_BYTE], power); + uint8_t mode = remote_state[HITACHI_AC424_MODE_BYTE] & 0xF; + ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC424_MODE_BYTE], mode); + if (power == HITACHI_AC424_POWER_ON) { + switch (mode) { + case HITACHI_AC424_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case HITACHI_AC424_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case HITACHI_AC424_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case HITACHI_AC424_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + case HITACHI_AC424_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + return true; +} + +bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) { + uint8_t temperature = + HITACHI_AC424_GETBITS8(remote_state[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE); + this->target_temperature = temperature; + ESP_LOGV(TAG, "Temperature: %02X %02u %04f", remote_state[HITACHI_AC424_TEMP_BYTE], temperature, + this->target_temperature); + return true; +} + +bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) { + uint8_t fan_mode = remote_state[HITACHI_AC424_FAN_BYTE] >> 4 & 0xF; + ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC424_FAN_BYTE], fan_mode); + switch (fan_mode) { + case HITACHI_AC424_FAN_MIN: + case HITACHI_AC424_FAN_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case HITACHI_AC424_FAN_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case HITACHI_AC424_FAN_HIGH: + case HITACHI_AC424_FAN_MAX: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case HITACHI_AC424_FAN_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + return true; +} + +bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { + uint8_t swing_modeh = HITACHI_AC424_GETBITS8(remote_state[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, + HITACHI_AC424_SWINGH_SIZE); + ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); + + if ((swing_modeh & 0x7) == 0x0) { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } else if ((swing_modeh & 0x3) == 0x3) { + this->swing_mode = climate::CLIMATE_SWING_OFF; + } else { + this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; + } + + return true; +} + +bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { + // Validate header + if (!data.expect_item(HITACHI_AC424_HDR_MARK, HITACHI_AC424_HDR_SPACE)) { + ESP_LOGVV(TAG, "Header fail"); + return false; + } + + uint8_t recv_state[HITACHI_AC424_STATE_LENGTH] = {0}; + // Read all bytes. + for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) { + // Read bit + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) + recv_state[pos] |= 1 << bit; + else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { + ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); + return false; + } + } + } + + // Validate footer + if (!data.expect_mark(HITACHI_AC424_BIT_MARK)) { + ESP_LOGVV(TAG, "Footer fail"); + return false; + } + + dump_state_("Recv", recv_state); + + // parse mode + this->parse_mode_(recv_state); + // parse temperature + this->parse_temperature_(recv_state); + // parse fan + this->parse_fan_(recv_state); + // parse swingv + this->parse_swing_(recv_state); + this->publish_state(); + for (uint8_t i = 0; i < HITACHI_AC424_STATE_LENGTH; i++) + remote_state_[i] = recv_state[i]; + + return true; +} + +void HitachiClimate::dump_state_(const char action[], uint8_t state[]) { + for (uint16_t i = 0; i < HITACHI_AC424_STATE_LENGTH - 10; i += 10) { + ESP_LOGV(TAG, "%s: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", action, state[i + 0], state[i + 1], + state[i + 2], state[i + 3], state[i + 4], state[i + 5], state[i + 6], state[i + 7], state[i + 8], + state[i + 9]); + } + ESP_LOGV(TAG, "%s: %02X %02X %02X", action, state[40], state[41], state[42]); +} + +} // namespace hitachi_ac424 +} // namespace esphome diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.h b/esphome/components/hitachi_ac424/hitachi_ac424.h new file mode 100644 index 0000000000..1005aa6df7 --- /dev/null +++ b/esphome/components/hitachi_ac424/hitachi_ac424.h @@ -0,0 +1,123 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace hitachi_ac424 { + +const uint16_t HITACHI_AC424_HDR_MARK = 3416; // ac +const uint16_t HITACHI_AC424_HDR_SPACE = 1604; // ac +const uint16_t HITACHI_AC424_BIT_MARK = 463; +const uint16_t HITACHI_AC424_ONE_SPACE = 1208; +const uint16_t HITACHI_AC424_ZERO_SPACE = 372; +const uint32_t HITACHI_AC424_MIN_GAP = 100000; // just a guess. +const uint16_t HITACHI_AC424_FREQ = 38000; // Hz. + +const uint8_t HITACHI_AC424_BUTTON_BYTE = 11; +const uint8_t HITACHI_AC424_BUTTON_POWER = 0x13; +const uint8_t HITACHI_AC424_BUTTON_SLEEP = 0x31; +const uint8_t HITACHI_AC424_BUTTON_MODE = 0x41; +const uint8_t HITACHI_AC424_BUTTON_FAN = 0x42; +const uint8_t HITACHI_AC424_BUTTON_TEMP_DOWN = 0x43; +const uint8_t HITACHI_AC424_BUTTON_TEMP_UP = 0x44; +const uint8_t HITACHI_AC424_BUTTON_SWINGV = 0x81; +const uint8_t HITACHI_AC424_BUTTON_SWINGH = 0x8C; +const uint8_t HITACHI_AC424_BUTTON_MILDEWPROOF = 0xE2; + +const uint8_t HITACHI_AC424_TEMP_BYTE = 13; +const uint8_t HITACHI_AC424_TEMP_OFFSET = 2; +const uint8_t HITACHI_AC424_TEMP_SIZE = 6; +const uint8_t HITACHI_AC424_TEMP_MIN = 16; // 16C +const uint8_t HITACHI_AC424_TEMP_MAX = 32; // 32C +const uint8_t HITACHI_AC424_TEMP_FAN = 27; // 27C + +const uint8_t HITACHI_AC424_TIMER_BYTE = 15; + +const uint8_t HITACHI_AC424_MODE_BYTE = 25; +const uint8_t HITACHI_AC424_MODE_FAN = 1; +const uint8_t HITACHI_AC424_MODE_COOL = 3; +const uint8_t HITACHI_AC424_MODE_DRY = 5; +const uint8_t HITACHI_AC424_MODE_HEAT = 6; +const uint8_t HITACHI_AC424_MODE_AUTO = 14; +const uint8_t HITACHI_AC424_MODE_POWERFUL = 19; + +const uint8_t HITACHI_AC424_FAN_BYTE = HITACHI_AC424_MODE_BYTE; +const uint8_t HITACHI_AC424_FAN_MIN = 1; +const uint8_t HITACHI_AC424_FAN_LOW = 2; +const uint8_t HITACHI_AC424_FAN_MEDIUM = 3; +const uint8_t HITACHI_AC424_FAN_HIGH = 4; +const uint8_t HITACHI_AC424_FAN_AUTO = 5; +const uint8_t HITACHI_AC424_FAN_MAX = 6; +const uint8_t HITACHI_AC424_FAN_MAX_DRY = 2; + +const uint8_t HITACHI_AC424_POWER_BYTE = 27; +const uint8_t HITACHI_AC424_POWER_ON = 0xF1; +const uint8_t HITACHI_AC424_POWER_OFF = 0xE1; + +const uint8_t HITACHI_AC424_SWINGH_BYTE = 35; +const uint8_t HITACHI_AC424_SWINGH_OFFSET = 0; // Mask 0b00000xxx +const uint8_t HITACHI_AC424_SWINGH_SIZE = 3; // Mask 0b00000xxx +const uint8_t HITACHI_AC424_SWINGH_AUTO = 0; // 0b000 +const uint8_t HITACHI_AC424_SWINGH_RIGHT_MAX = 1; // 0b001 +const uint8_t HITACHI_AC424_SWINGH_RIGHT = 2; // 0b010 +const uint8_t HITACHI_AC424_SWINGH_MIDDLE = 3; // 0b011 +const uint8_t HITACHI_AC424_SWINGH_LEFT = 4; // 0b100 +const uint8_t HITACHI_AC424_SWINGH_LEFT_MAX = 5; // 0b101 + +const uint8_t HITACHI_AC424_SWINGV_BYTE = 37; +const uint8_t HITACHI_AC424_SWINGV_OFFSET = 5; // Mask 0b00x00000 + +const uint8_t HITACHI_AC424_MILDEWPROOF_BYTE = HITACHI_AC424_SWINGV_BYTE; +const uint8_t HITACHI_AC424_MILDEWPROOF_OFFSET = 2; // Mask 0b00000x00 + +const uint16_t HITACHI_AC424_STATE_LENGTH = 53; +const uint16_t HITACHI_AC424_BITS = HITACHI_AC424_STATE_LENGTH * 8; + +#define HITACHI_AC424_GETBIT8(a, b) ((a) & ((uint8_t) 1 << (b))) +#define HITACHI_AC424_GETBITS8(data, offset, size) \ + (((data) & (((uint8_t) UINT8_MAX >> (8 - (size))) << (offset))) >> (offset)) + +class HitachiClimate : public climate_ir::ClimateIR { + public: + HitachiClimate() + : climate_ir::ClimateIR(HITACHI_AC424_TEMP_MIN, HITACHI_AC424_TEMP_MAX, 1.0F, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} + + protected: + uint8_t remote_state_[HITACHI_AC424_STATE_LENGTH]{ + 0x01, 0x10, 0x00, 0x40, 0xBF, 0xFF, 0x00, 0xCC, 0x33, 0x92, 0x6D, 0x13, 0xEC, 0x5C, 0xA3, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x53, 0xAC, 0xF1, 0x0E, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7F, 0x03, + 0xFC, 0x01, 0xFE, 0x88, 0x77, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}; + uint8_t previous_temp_{27}; + // Transmit via IR the state of this climate controller. + void transmit_state() override; + bool get_power_(); + void set_power_(bool on); + uint8_t get_mode_(); + void set_mode_(uint8_t mode); + void set_temp_(uint8_t celsius, bool set_previous = false); + uint8_t get_fan_(); + void set_fan_(uint8_t speed); + void set_swing_v_toggle_(bool on); + bool get_swing_v_toggle_(); + void set_swing_v_(bool on); + bool get_swing_v_(); + void set_swing_h_(uint8_t position); + uint8_t get_swing_h_(); + uint8_t get_button_(); + void set_button_(uint8_t button); + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_mode_(const uint8_t remote_state[]); + bool parse_temperature_(const uint8_t remote_state[]); + bool parse_fan_(const uint8_t remote_state[]); + bool parse_swing_(const uint8_t remote_state[]); + bool parse_state_frame_(const uint8_t frame[]); + void dump_state_(const char action[], uint8_t remote_state[]); +}; + +} // namespace hitachi_ac424 +} // namespace esphome