From ddaa84683be70cba79a1dcc191eec23f59a4f973 Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Sat, 6 Jul 2024 09:00:44 +0200 Subject: [PATCH] Haier component update to support more protocol variations (#7040) Co-authored-by: Pavlo Dudnytskyi --- esphome/components/haier/climate.py | 41 ++++- esphome/components/haier/haier_base.cpp | 4 + esphome/components/haier/haier_base.h | 12 +- esphome/components/haier/hon_climate.cpp | 164 ++++++++++++------ esphome/components/haier/hon_climate.h | 8 +- esphome/components/haier/hon_packet.h | 5 + .../components/haier/smartair2_climate.cpp | 1 + platformio.ini | 2 +- tests/components/haier/common.yaml | 114 ++++++++++++ tests/components/haier/test.esp32-ard.yaml | 116 +------------ tests/components/haier/test.esp32-c3-ard.yaml | 112 +----------- tests/components/haier/test.esp32-c3-idf.yaml | 112 +----------- tests/components/haier/test.esp32-idf.yaml | 112 +----------- tests/components/haier/test.esp8266-ard.yaml | 112 +----------- tests/components/haier/test.rp2040-ard.yaml | 112 +----------- 15 files changed, 316 insertions(+), 711 deletions(-) create mode 100644 tests/components/haier/common.yaml diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index 3dcb35708c..f7423a1356 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -38,6 +38,9 @@ PROTOCOL_MAX_TEMPERATURE = 30.0 PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 PROTOCOL_CONTROL_PACKET_SIZE = 10 +PROTOCOL_MIN_SENSORS_PACKET_SIZE = 18 +PROTOCOL_DEFAULT_SENSORS_PACKET_SIZE = 22 +PROTOCOL_STATUS_MESSAGE_HEADER_SIZE = 0 CODEOWNERS = ["@paveldn"] DEPENDENCIES = ["climate", "uart"] @@ -48,6 +51,9 @@ CONF_CONTROL_PACKET_SIZE = "control_packet_size" CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" CONF_ON_ALARM_START = "on_alarm_start" CONF_ON_ALARM_END = "on_alarm_end" +CONF_ON_STATUS_MESSAGE = "on_status_message" +CONF_SENSORS_PACKET_SIZE = "sensors_packet_size" +CONF_STATUS_MESSAGE_HEADER_SIZE = "status_message_header_size" CONF_VERTICAL_AIRFLOW = "vertical_airflow" CONF_WIFI_SIGNAL = "wifi_signal" @@ -129,6 +135,11 @@ HaierAlarmEndTrigger = haier_ns.class_( automation.Trigger.template(cg.uint8, cg.const_char_ptr), ) +StatusMessageTrigger = haier_ns.class_( + "StatusMessageTrigger", + automation.Trigger.template(cg.const_char_ptr, cg.size_t), +) + def validate_visual(config): if CONF_VISUAL in config: @@ -193,6 +204,11 @@ BASE_CONFIG_SCHEMA = ( cv.Optional( CONF_ANSWER_TIMEOUT, ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StatusMessageTrigger), + } + ), } ) .extend(uart.UART_DEVICE_SCHEMA) @@ -228,6 +244,14 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), + cv.Optional( + CONF_SENSORS_PACKET_SIZE, + default=PROTOCOL_DEFAULT_SENSORS_PACKET_SIZE, + ): cv.int_range(min=PROTOCOL_MIN_SENSORS_PACKET_SIZE, max=50), + cv.Optional( + CONF_STATUS_MESSAGE_HEADER_SIZE, + default=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE, + ): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE), cv.Optional( CONF_SUPPORTED_PRESETS, default=["BOOST", "ECO", "SLEEP"], # No AWAY by default @@ -468,6 +492,16 @@ async def to_code(config): config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE ) ) + if CONF_SENSORS_PACKET_SIZE in config: + cg.add( + var.set_extra_sensors_packet_bytes_size( + config[CONF_SENSORS_PACKET_SIZE] - PROTOCOL_MIN_SENSORS_PACKET_SIZE + ) + ) + if CONF_STATUS_MESSAGE_HEADER_SIZE in config: + cg.add( + var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE]) + ) for conf in config.get(CONF_ON_ALARM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( @@ -478,5 +512,10 @@ async def to_code(config): await automation.build_automation( trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf ) + for conf in config.get(CONF_ON_STATUS_MESSAGE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf + ) # https://github.com/paveldn/HaierProtocol - cg.add_library("pavlodn/HaierProtocol", "0.9.28") + cg.add_library("pavlodn/HaierProtocol", "0.9.31") diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 1fca3dfb85..0bd3863160 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -186,6 +186,10 @@ void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &m this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message}); } +void HaierClimateBase::add_status_message_callback(std::function &&callback) { + this->status_message_callback_.add(std::move(callback)); +} + haier_protocol::HandlerError HaierClimateBase::answer_preprocess_( haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index f261a106a2..c0bf878519 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -4,6 +4,7 @@ #include #include "esphome/components/climate/climate.h" #include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" // HaierProtocol #include @@ -56,6 +57,7 @@ class HaierClimateBase : public esphome::Component, void set_answer_timeout(uint32_t timeout); void set_send_wifi(bool send_wifi); void send_custom_command(const haier_protocol::HaierMessage &message); + void add_status_message_callback(std::function &&callback); protected: enum class ProtocolPhases { @@ -140,11 +142,19 @@ class HaierClimateBase : public esphome::Component, esphome::climate::ClimateTraits traits_; HvacSettings current_hvac_settings_; HvacSettings next_hvac_settings_; - std::unique_ptr last_status_message_; + std::unique_ptr last_status_message_{nullptr}; std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout std::chrono::steady_clock::time_point last_status_request_; // To request AC status std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level + CallbackManager status_message_callback_{}; +}; + +class StatusMessageTrigger : public Trigger { + public: + explicit StatusMessageTrigger(HaierClimateBase *parent) { + parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); }); + } }; } // namespace haier diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index 903f7964da..a1c5098cec 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -18,12 +18,13 @@ constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000; +const uint8_t ONE_BUF[] = {0x00, 0x01}; +const uint8_t ZERO_BUF[] = {0x00, 0x00}; HonClimate::HonClimate() : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} { - last_status_message_ = std::unique_ptr(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]); this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; } @@ -169,11 +170,18 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy this->action_request_.reset(); this->force_send_control_ = false; } else { - if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { - memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); + if (!this->last_status_message_) { + this->real_control_packet_size_ = sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_; + this->real_sensors_packet_size_ = sizeof(hon_protocol::HaierPacketSensors) + this->extra_sensors_packet_bytes_; + this->last_status_message_.reset(); + this->last_status_message_ = std::unique_ptr(new uint8_t[this->real_control_packet_size_]); + }; + if (data_size >= this->real_control_packet_size_ + 2) { + memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_, + this->real_control_packet_size_); + this->status_message_callback_.call((const char *) data, data_size); } else { - ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, - sizeof(hon_protocol::HaierPacketControl)); + ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_); } switch (this->protocol_phase_) { case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: @@ -479,8 +487,8 @@ void HonClimate::initialization() { } haier_protocol::HaierMessage HonClimate::get_control_message() { - uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; - memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); + uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE]; + memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_); hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; control_out_buffer[4] = 0; // This byte should be cleared before setting values bool has_hvac_settings = false; @@ -636,7 +644,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { out_data->health_mode = this->health_mode_ ? 1 : 0; return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, - control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); + control_out_buffer, this->real_control_packet_size_); } void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) { @@ -758,15 +766,17 @@ void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::stri #endif // USE_TEXT_SENSOR haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { - size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) + - this->extra_control_packet_bytes_; - if (size < expected_size) + size_t expected_size = + 2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; + if (size < expected_size) { + ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size); return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + } uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; - if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) { + if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) { // Got BigData packet const hon_protocol::HaierPacketBigData *bd_packet = - (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]); + (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]); #ifdef USE_SENSOR this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20); this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); @@ -795,9 +805,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * hon_protocol::HaierPacketControl control; hon_protocol::HaierPacketSensors sensors; } packet; - memcpy(&packet.control, packet_buffer + 2, sizeof(hon_protocol::HaierPacketControl)); - memcpy(&packet.sensors, - packet_buffer + 2 + sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_, + memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_, + sizeof(hon_protocol::HaierPacketControl)); + memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_, sizeof(hon_protocol::HaierPacketSensors)); if (packet.sensors.error_status != 0) { ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); @@ -996,8 +1006,6 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * } void HonClimate::fill_control_messages_queue_() { - static uint8_t one_buf[] = {0x00, 0x01}; - static uint8_t zero_buf[] = {0x00, 0x00}; if (!this->current_hvac_settings_.valid && !this->force_send_control_) return; this->clear_control_messages_queue_(); @@ -1009,7 +1017,7 @@ void HonClimate::fill_control_messages_queue_() { haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, - this->beeper_status_ ? zero_buf : one_buf, 2)); + this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2)); } // Health mode { @@ -1017,7 +1025,7 @@ void HonClimate::fill_control_messages_queue_() { haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, - this->health_mode_ ? one_buf : zero_buf, 2)); + this->health_mode_ ? ONE_BUF : ZERO_BUF, 2)); } // Climate mode bool new_power = this->mode != CLIMATE_MODE_OFF; @@ -1092,7 +1100,7 @@ void HonClimate::fill_control_messages_queue_() { haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint8_t) hon_protocol::DataParameters::AC_POWER, - new_power ? one_buf : zero_buf, 2)); + new_power ? ONE_BUF : ZERO_BUF, 2)); } // CLimate preset { @@ -1165,6 +1173,35 @@ void HonClimate::fill_control_messages_queue_() { (uint8_t) hon_protocol::DataParameters::SET_POINT, buffer, 2)); } + // Vertical swing mode + if (climate_control.swing_mode.has_value()) { + uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO}; + uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO}; + switch (climate_control.swing_mode.value()) { + case CLIMATE_SWING_OFF: + horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing; + vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing; + break; + case CLIMATE_SWING_VERTICAL: + horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing; + break; + case CLIMATE_SWING_HORIZONTAL: + vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing; + break; + case CLIMATE_SWING_BOTH: + break; + } + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE, + horizontal_swing_buf, 2)); + this->control_messages_queue_.push( + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE, + vertical_swing_buf, 2)); + } // Fan mode if (climate_control.fan_mode.has_value()) { switch (climate_control.fan_mode.value()) { @@ -1202,40 +1239,56 @@ void HonClimate::clear_control_messages_queue_() { bool HonClimate::prepare_pending_action() { switch (this->action_request_.value().action) { - case ActionRequest::START_SELF_CLEAN: { - uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; - memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); - hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; - out_data->self_cleaning_status = 1; - out_data->steri_clean = 0; - out_data->set_point = 0x06; - out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; - out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; - out_data->ac_power = 1; - out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; - out_data->light_status = 0; - this->action_request_.value().message = haier_protocol::HaierMessage( - haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, - control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); - } - return true; - case ActionRequest::START_STERI_CLEAN: { - uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; - memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); - hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; - out_data->self_cleaning_status = 0; - out_data->steri_clean = 1; - out_data->set_point = 0x06; - out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; - out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; - out_data->ac_power = 1; - out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; - out_data->light_status = 0; - this->action_request_.value().message = haier_protocol::HaierMessage( - haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, - control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); - } - return true; + case ActionRequest::START_SELF_CLEAN: + if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) { + uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE]; + memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_); + hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + out_data->self_cleaning_status = 1; + out_data->steri_clean = 0; + out_data->set_point = 0x06; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->light_status = 0; + this->action_request_.value().message = haier_protocol::HaierMessage( + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, + control_out_buffer, this->real_control_packet_size_); + return true; + } else if (this->control_method_ == HonControlMethod::SET_SINGLE_PARAMETER) { + this->action_request_.value().message = + haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::SELF_CLEANING, + ONE_BUF, 2); + return true; + } else { + this->action_request_.reset(); + return false; + } + case ActionRequest::START_STERI_CLEAN: + if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) { + uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE]; + memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_); + hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + out_data->self_cleaning_status = 0; + out_data->steri_clean = 1; + out_data->set_point = 0x06; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->light_status = 0; + this->action_request_.value().message = haier_protocol::HaierMessage( + haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, + control_out_buffer, this->real_control_packet_size_); + return true; + } else { + // No Steri clean support (yet?) in SET_SINGLE_PARAMETER + this->action_request_.reset(); + return false; + } default: return HaierClimateBase::prepare_pending_action(); } @@ -1251,6 +1304,7 @@ void HonClimate::process_protocol_reset() { #endif // USE_SENSOR this->got_valid_outdoor_temp_ = false; this->hvac_hardware_info_.reset(); + this->last_status_message_.reset(nullptr); } bool HonClimate::should_get_big_data_() { diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index 7b4fcee6b9..64c54186ed 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -104,6 +104,8 @@ class HonClimate : public HaierClimateBase { void start_self_cleaning(); void start_steri_cleaning(); void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; }; + void set_extra_sensors_packet_bytes_size(size_t size) { this->extra_sensors_packet_bytes_ = size; }; + void set_status_message_header_size(size_t size) { this->status_message_header_size_ = size; }; void set_control_method(HonControlMethod method) { this->control_method_ = method; }; void add_alarm_start_callback(std::function &&callback); void add_alarm_end_callback(std::function &&callback); @@ -158,7 +160,11 @@ class HonClimate : public HaierClimateBase { esphome::optional pending_horizontal_direction_{}; esphome::optional hvac_hardware_info_{}; uint8_t active_alarms_[8]; - int extra_control_packet_bytes_; + int extra_control_packet_bytes_{0}; + int extra_sensors_packet_bytes_{4}; + int status_message_header_size_{0}; + int real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)}; + int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4}; HonControlMethod control_method_; std::queue control_messages_queue_; CallbackManager alarm_start_callback_{}; diff --git a/esphome/components/haier/hon_packet.h b/esphome/components/haier/hon_packet.h index a03ac2831f..615f93528e 100644 --- a/esphome/components/haier/hon_packet.h +++ b/esphome/components/haier/hon_packet.h @@ -41,15 +41,20 @@ enum class ConditioningMode : uint8_t { enum class DataParameters : uint8_t { AC_POWER = 0x01, SET_POINT = 0x02, + VERTICAL_SWING_MODE = 0x03, AC_MODE = 0x04, FAN_MODE = 0x05, USE_FAHRENHEIT = 0x07, + DISPLAY_STATUS = 0x09, TEN_DEGREE = 0x0A, HEALTH_MODE = 0x0B, + HORIZONTAL_SWING_MODE = 0x0C, + SELF_CLEANING = 0x0D, BEEPER_STATUS = 0x16, LOCK_REMOTE = 0x17, QUIET_MODE = 0x19, FAST_MODE = 0x1A, + SLEEP_MODE = 0x1B, }; enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index 00590694d5..028e8a4087 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -37,6 +37,7 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::F } else { if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); + this->status_message_callback_.call((const char *) data, data_size); } else { ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, sizeof(smartair2_protocol::HaierPacketControl)); diff --git a/platformio.ini b/platformio.ini index aa73437222..a72bf598c5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,7 +39,7 @@ lib_deps = bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 - pavlodn/HaierProtocol@0.9.28 ; haier + pavlodn/HaierProtocol@0.9.31 ; haier ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library build_flags = diff --git a/tests/components/haier/common.yaml b/tests/components/haier/common.yaml new file mode 100644 index 0000000000..b8a23bac5a --- /dev/null +++ b/tests/components/haier/common.yaml @@ -0,0 +1,114 @@ +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_haier + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 9600 + +climate: + - platform: haier + id: haier_ac + uart_id: uart_haier + protocol: hOn + name: Haier AC + wifi_signal: true + answer_timeout: 200ms + beeper: true + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: + target_temperature: 1 + current_temperature: 0.5 + supported_modes: + - 'OFF' + - HEAT_COOL + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH + supported_presets: + - AWAY + - BOOST + - ECO + - SLEEP + on_alarm_start: + then: + - logger.log: + level: DEBUG + format: "Alarm activated. Code: %d. Message: \"%s\"" + args: [code, message] + on_alarm_end: + then: + - logger.log: + level: DEBUG + format: "Alarm deactivated. Code: %d. Message: \"%s\"" + args: [code, message] + +sensor: + - platform: haier + haier_id: haier_ac + outdoor_temperature: + name: Haier outdoor temperature + humidity: + name: Haier Indoor Humidity + compressor_current: + name: Haier Compressor Current + compressor_frequency: + name: Haier Compressor Frequency + expansion_valve_open_degree: + name: Haier Expansion Valve Open Degree + indoor_coil_temperature: + name: Haier Indoor Coil Temperature + outdoor_coil_temperature: + name: Haier Outdoor Coil Temperature + outdoor_defrost_temperature: + name: Haier Outdoor Defrost Temperature + outdoor_in_air_temperature: + name: Haier Outdoor In Air Temperature + outdoor_out_air_temperature: + name: Haier Outdoor Out Air Temperature + power: + name: Haier Power + +binary_sensor: + - platform: haier + haier_id: haier_ac + compressor_status: + name: Haier Outdoor Compressor Status + defrost_status: + name: Haier Defrost Status + four_way_valve_status: + name: Haier Four Way Valve Status + indoor_electric_heating_status: + name: Haier Indoor Electric Heating Status + indoor_fan_status: + name: Haier Indoor Fan Status + outdoor_fan_status: + name: Haier Outdoor Fan Status + +button: + - platform: haier + haier_id: haier_ac + self_cleaning: + name: Haier start self cleaning + steri_cleaning: + name: Haier start 56°C steri-cleaning + +text_sensor: + - platform: haier + haier_id: haier_ac + appliance_name: + name: Haier appliance name + cleaning_status: + name: Haier cleaning status + protocol_version: + name: Haier protocol version diff --git a/tests/components/haier/test.esp32-ard.yaml b/tests/components/haier/test.esp32-ard.yaml index efff532d25..f486544afa 100644 --- a/tests/components/haier/test.esp32-ard.yaml +++ b/tests/components/haier/test.esp32-ard.yaml @@ -1,113 +1,5 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 -uart: - - id: uart_haier - tx_pin: 17 - rx_pin: 16 - baud_rate: 9600 - -climate: - - platform: haier - id: haier_ac - protocol: hOn - name: Haier AC - wifi_signal: true - answer_timeout: 200ms - beeper: true - visual: - min_temperature: 16 °C - max_temperature: 30 °C - temperature_step: - target_temperature: 1 - current_temperature: 0.5 - supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY - supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH - supported_presets: - - AWAY - - BOOST - - ECO - - SLEEP - on_alarm_start: - then: - - logger.log: - level: DEBUG - format: "Alarm activated. Code: %d. Message: \"%s\"" - args: [code, message] - on_alarm_end: - then: - - logger.log: - level: DEBUG - format: "Alarm deactivated. Code: %d. Message: \"%s\"" - args: [code, message] - -sensor: - - platform: haier - haier_id: haier_ac - outdoor_temperature: - name: Haier outdoor temperature - humidity: - name: Haier Indoor Humidity - compressor_current: - name: Haier Compressor Current - compressor_frequency: - name: Haier Compressor Frequency - expansion_valve_open_degree: - name: Haier Expansion Valve Open Degree - indoor_coil_temperature: - name: Haier Indoor Coil Temperature - outdoor_coil_temperature: - name: Haier Outdoor Coil Temperature - outdoor_defrost_temperature: - name: Haier Outdoor Defrost Temperature - outdoor_in_air_temperature: - name: Haier Outdoor In Air Temperature - outdoor_out_air_temperature: - name: Haier Outdoor Out Air Temperature - power: - name: Haier Power - -binary_sensor: - - platform: haier - haier_id: haier_ac - compressor_status: - name: Haier Outdoor Compressor Status - defrost_status: - name: Haier Defrost Status - four_way_valve_status: - name: Haier Four Way Valve Status - indoor_electric_heating_status: - name: Haier Indoor Electric Heating Status - indoor_fan_status: - name: Haier Indoor Fan Status - outdoor_fan_status: - name: Haier Outdoor Fan Status - -button: - - platform: haier - haier_id: haier_ac - self_cleaning: - name: Haier start self cleaning - steri_cleaning: - name: Haier start 56°C steri-cleaning - -text_sensor: - - platform: haier - haier_id: haier_ac - appliance_name: - name: Haier appliance name - cleaning_status: - name: Haier cleaning status - protocol_version: - name: Haier protocol version +<<: !include common.yaml diff --git a/tests/components/haier/test.esp32-c3-ard.yaml b/tests/components/haier/test.esp32-c3-ard.yaml index 0053220669..b516342f3b 100644 --- a/tests/components/haier/test.esp32-c3-ard.yaml +++ b/tests/components/haier/test.esp32-c3-ard.yaml @@ -1,109 +1,5 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_haier - tx_pin: 4 - rx_pin: 5 - baud_rate: 9600 - -climate: - - platform: haier - id: haier_ac - protocol: hOn - name: Haier AC - wifi_signal: true - answer_timeout: 200ms - beeper: true - visual: - min_temperature: 16 °C - max_temperature: 30 °C - temperature_step: - target_temperature: 1 - current_temperature: 0.5 - supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY - supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH - supported_presets: - - AWAY - - BOOST - - ECO - - SLEEP - on_alarm_start: - then: - - logger.log: - level: DEBUG - format: "Alarm activated. Code: %d. Message: \"%s\"" - args: [code, message] - on_alarm_end: - then: - - logger.log: - level: DEBUG - format: "Alarm deactivated. Code: %d. Message: \"%s\"" - args: [code, message] - -sensor: - - platform: haier - outdoor_temperature: - name: Haier outdoor temperature - humidity: - name: Haier Indoor Humidity - compressor_current: - name: Haier Compressor Current - compressor_frequency: - name: Haier Compressor Frequency - expansion_valve_open_degree: - name: Haier Expansion Valve Open Degree - indoor_coil_temperature: - name: Haier Indoor Coil Temperature - outdoor_coil_temperature: - name: Haier Outdoor Coil Temperature - outdoor_defrost_temperature: - name: Haier Outdoor Defrost Temperature - outdoor_in_air_temperature: - name: Haier Outdoor In Air Temperature - outdoor_out_air_temperature: - name: Haier Outdoor Out Air Temperature - power: - name: Haier Power - -binary_sensor: - - platform: haier - compressor_status: - name: Haier Outdoor Compressor Status - defrost_status: - name: Haier Defrost Status - four_way_valve_status: - name: Haier Four Way Valve Status - indoor_electric_heating_status: - name: Haier Indoor Electric Heating Status - indoor_fan_status: - name: Haier Indoor Fan Status - outdoor_fan_status: - name: Haier Outdoor Fan Status - -button: - - platform: haier - self_cleaning: - name: Haier start self cleaning - steri_cleaning: - name: Haier start 56°C steri-cleaning - -text_sensor: - - platform: haier - appliance_name: - name: Haier appliance name - cleaning_status: - name: Haier cleaning status - protocol_version: - name: Haier protocol version +<<: !include common.yaml diff --git a/tests/components/haier/test.esp32-c3-idf.yaml b/tests/components/haier/test.esp32-c3-idf.yaml index 0053220669..b516342f3b 100644 --- a/tests/components/haier/test.esp32-c3-idf.yaml +++ b/tests/components/haier/test.esp32-c3-idf.yaml @@ -1,109 +1,5 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_haier - tx_pin: 4 - rx_pin: 5 - baud_rate: 9600 - -climate: - - platform: haier - id: haier_ac - protocol: hOn - name: Haier AC - wifi_signal: true - answer_timeout: 200ms - beeper: true - visual: - min_temperature: 16 °C - max_temperature: 30 °C - temperature_step: - target_temperature: 1 - current_temperature: 0.5 - supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY - supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH - supported_presets: - - AWAY - - BOOST - - ECO - - SLEEP - on_alarm_start: - then: - - logger.log: - level: DEBUG - format: "Alarm activated. Code: %d. Message: \"%s\"" - args: [code, message] - on_alarm_end: - then: - - logger.log: - level: DEBUG - format: "Alarm deactivated. Code: %d. Message: \"%s\"" - args: [code, message] - -sensor: - - platform: haier - outdoor_temperature: - name: Haier outdoor temperature - humidity: - name: Haier Indoor Humidity - compressor_current: - name: Haier Compressor Current - compressor_frequency: - name: Haier Compressor Frequency - expansion_valve_open_degree: - name: Haier Expansion Valve Open Degree - indoor_coil_temperature: - name: Haier Indoor Coil Temperature - outdoor_coil_temperature: - name: Haier Outdoor Coil Temperature - outdoor_defrost_temperature: - name: Haier Outdoor Defrost Temperature - outdoor_in_air_temperature: - name: Haier Outdoor In Air Temperature - outdoor_out_air_temperature: - name: Haier Outdoor Out Air Temperature - power: - name: Haier Power - -binary_sensor: - - platform: haier - compressor_status: - name: Haier Outdoor Compressor Status - defrost_status: - name: Haier Defrost Status - four_way_valve_status: - name: Haier Four Way Valve Status - indoor_electric_heating_status: - name: Haier Indoor Electric Heating Status - indoor_fan_status: - name: Haier Indoor Fan Status - outdoor_fan_status: - name: Haier Outdoor Fan Status - -button: - - platform: haier - self_cleaning: - name: Haier start self cleaning - steri_cleaning: - name: Haier start 56°C steri-cleaning - -text_sensor: - - platform: haier - appliance_name: - name: Haier appliance name - cleaning_status: - name: Haier cleaning status - protocol_version: - name: Haier protocol version +<<: !include common.yaml diff --git a/tests/components/haier/test.esp32-idf.yaml b/tests/components/haier/test.esp32-idf.yaml index 54e384f3ce..f486544afa 100644 --- a/tests/components/haier/test.esp32-idf.yaml +++ b/tests/components/haier/test.esp32-idf.yaml @@ -1,109 +1,5 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 -uart: - - id: uart_haier - tx_pin: 17 - rx_pin: 16 - baud_rate: 9600 - -climate: - - platform: haier - id: haier_ac - protocol: hOn - name: Haier AC - wifi_signal: true - answer_timeout: 200ms - beeper: true - visual: - min_temperature: 16 °C - max_temperature: 30 °C - temperature_step: - target_temperature: 1 - current_temperature: 0.5 - supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY - supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH - supported_presets: - - AWAY - - BOOST - - ECO - - SLEEP - on_alarm_start: - then: - - logger.log: - level: DEBUG - format: "Alarm activated. Code: %d. Message: \"%s\"" - args: [code, message] - on_alarm_end: - then: - - logger.log: - level: DEBUG - format: "Alarm deactivated. Code: %d. Message: \"%s\"" - args: [code, message] - -sensor: - - platform: haier - outdoor_temperature: - name: Haier outdoor temperature - humidity: - name: Haier Indoor Humidity - compressor_current: - name: Haier Compressor Current - compressor_frequency: - name: Haier Compressor Frequency - expansion_valve_open_degree: - name: Haier Expansion Valve Open Degree - indoor_coil_temperature: - name: Haier Indoor Coil Temperature - outdoor_coil_temperature: - name: Haier Outdoor Coil Temperature - outdoor_defrost_temperature: - name: Haier Outdoor Defrost Temperature - outdoor_in_air_temperature: - name: Haier Outdoor In Air Temperature - outdoor_out_air_temperature: - name: Haier Outdoor Out Air Temperature - power: - name: Haier Power - -binary_sensor: - - platform: haier - compressor_status: - name: Haier Outdoor Compressor Status - defrost_status: - name: Haier Defrost Status - four_way_valve_status: - name: Haier Four Way Valve Status - indoor_electric_heating_status: - name: Haier Indoor Electric Heating Status - indoor_fan_status: - name: Haier Indoor Fan Status - outdoor_fan_status: - name: Haier Outdoor Fan Status - -button: - - platform: haier - self_cleaning: - name: Haier start self cleaning - steri_cleaning: - name: Haier start 56°C steri-cleaning - -text_sensor: - - platform: haier - appliance_name: - name: Haier appliance name - cleaning_status: - name: Haier cleaning status - protocol_version: - name: Haier protocol version +<<: !include common.yaml diff --git a/tests/components/haier/test.esp8266-ard.yaml b/tests/components/haier/test.esp8266-ard.yaml index 0053220669..b516342f3b 100644 --- a/tests/components/haier/test.esp8266-ard.yaml +++ b/tests/components/haier/test.esp8266-ard.yaml @@ -1,109 +1,5 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_haier - tx_pin: 4 - rx_pin: 5 - baud_rate: 9600 - -climate: - - platform: haier - id: haier_ac - protocol: hOn - name: Haier AC - wifi_signal: true - answer_timeout: 200ms - beeper: true - visual: - min_temperature: 16 °C - max_temperature: 30 °C - temperature_step: - target_temperature: 1 - current_temperature: 0.5 - supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY - supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH - supported_presets: - - AWAY - - BOOST - - ECO - - SLEEP - on_alarm_start: - then: - - logger.log: - level: DEBUG - format: "Alarm activated. Code: %d. Message: \"%s\"" - args: [code, message] - on_alarm_end: - then: - - logger.log: - level: DEBUG - format: "Alarm deactivated. Code: %d. Message: \"%s\"" - args: [code, message] - -sensor: - - platform: haier - outdoor_temperature: - name: Haier outdoor temperature - humidity: - name: Haier Indoor Humidity - compressor_current: - name: Haier Compressor Current - compressor_frequency: - name: Haier Compressor Frequency - expansion_valve_open_degree: - name: Haier Expansion Valve Open Degree - indoor_coil_temperature: - name: Haier Indoor Coil Temperature - outdoor_coil_temperature: - name: Haier Outdoor Coil Temperature - outdoor_defrost_temperature: - name: Haier Outdoor Defrost Temperature - outdoor_in_air_temperature: - name: Haier Outdoor In Air Temperature - outdoor_out_air_temperature: - name: Haier Outdoor Out Air Temperature - power: - name: Haier Power - -binary_sensor: - - platform: haier - compressor_status: - name: Haier Outdoor Compressor Status - defrost_status: - name: Haier Defrost Status - four_way_valve_status: - name: Haier Four Way Valve Status - indoor_electric_heating_status: - name: Haier Indoor Electric Heating Status - indoor_fan_status: - name: Haier Indoor Fan Status - outdoor_fan_status: - name: Haier Outdoor Fan Status - -button: - - platform: haier - self_cleaning: - name: Haier start self cleaning - steri_cleaning: - name: Haier start 56°C steri-cleaning - -text_sensor: - - platform: haier - appliance_name: - name: Haier appliance name - cleaning_status: - name: Haier cleaning status - protocol_version: - name: Haier protocol version +<<: !include common.yaml diff --git a/tests/components/haier/test.rp2040-ard.yaml b/tests/components/haier/test.rp2040-ard.yaml index 0053220669..b516342f3b 100644 --- a/tests/components/haier/test.rp2040-ard.yaml +++ b/tests/components/haier/test.rp2040-ard.yaml @@ -1,109 +1,5 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_haier - tx_pin: 4 - rx_pin: 5 - baud_rate: 9600 - -climate: - - platform: haier - id: haier_ac - protocol: hOn - name: Haier AC - wifi_signal: true - answer_timeout: 200ms - beeper: true - visual: - min_temperature: 16 °C - max_temperature: 30 °C - temperature_step: - target_temperature: 1 - current_temperature: 0.5 - supported_modes: - - 'OFF' - - HEAT_COOL - - COOL - - HEAT - - DRY - - FAN_ONLY - supported_swing_modes: - - 'OFF' - - VERTICAL - - HORIZONTAL - - BOTH - supported_presets: - - AWAY - - BOOST - - ECO - - SLEEP - on_alarm_start: - then: - - logger.log: - level: DEBUG - format: "Alarm activated. Code: %d. Message: \"%s\"" - args: [code, message] - on_alarm_end: - then: - - logger.log: - level: DEBUG - format: "Alarm deactivated. Code: %d. Message: \"%s\"" - args: [code, message] - -sensor: - - platform: haier - outdoor_temperature: - name: Haier outdoor temperature - humidity: - name: Haier Indoor Humidity - compressor_current: - name: Haier Compressor Current - compressor_frequency: - name: Haier Compressor Frequency - expansion_valve_open_degree: - name: Haier Expansion Valve Open Degree - indoor_coil_temperature: - name: Haier Indoor Coil Temperature - outdoor_coil_temperature: - name: Haier Outdoor Coil Temperature - outdoor_defrost_temperature: - name: Haier Outdoor Defrost Temperature - outdoor_in_air_temperature: - name: Haier Outdoor In Air Temperature - outdoor_out_air_temperature: - name: Haier Outdoor Out Air Temperature - power: - name: Haier Power - -binary_sensor: - - platform: haier - compressor_status: - name: Haier Outdoor Compressor Status - defrost_status: - name: Haier Defrost Status - four_way_valve_status: - name: Haier Four Way Valve Status - indoor_electric_heating_status: - name: Haier Indoor Electric Heating Status - indoor_fan_status: - name: Haier Indoor Fan Status - outdoor_fan_status: - name: Haier Outdoor Fan Status - -button: - - platform: haier - self_cleaning: - name: Haier start self cleaning - steri_cleaning: - name: Haier start 56°C steri-cleaning - -text_sensor: - - platform: haier - appliance_name: - name: Haier appliance name - cleaning_status: - name: Haier cleaning status - protocol_version: - name: Haier protocol version +<<: !include common.yaml