Haier component updated to support new protocol variations (#5713)

Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
This commit is contained in:
Pavlo Dudnytskyi 2023-11-21 02:12:36 +01:00 committed by GitHub
parent 55f13dc347
commit cf6b56c1ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 870 additions and 717 deletions

View file

@ -38,16 +38,20 @@ PROTOCOL_MIN_TEMPERATURE = 16.0
PROTOCOL_MAX_TEMPERATURE = 30.0 PROTOCOL_MAX_TEMPERATURE = 30.0
PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0
PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
PROTOCOL_CONTROL_PACKET_SIZE = 10
CODEOWNERS = ["@paveldn"] CODEOWNERS = ["@paveldn"]
AUTO_LOAD = ["sensor"] AUTO_LOAD = ["sensor"]
DEPENDENCIES = ["climate", "uart"] DEPENDENCIES = ["climate", "uart"]
CONF_WIFI_SIGNAL = "wifi_signal" CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control"
CONF_ANSWER_TIMEOUT = "answer_timeout" CONF_ANSWER_TIMEOUT = "answer_timeout"
CONF_CONTROL_METHOD = "control_method"
CONF_CONTROL_PACKET_SIZE = "control_packet_size"
CONF_DISPLAY = "display" CONF_DISPLAY = "display"
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
CONF_VERTICAL_AIRFLOW = "vertical_airflow" CONF_VERTICAL_AIRFLOW = "vertical_airflow"
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" CONF_WIFI_SIGNAL = "wifi_signal"
PROTOCOL_HON = "HON" PROTOCOL_HON = "HON"
PROTOCOL_SMARTAIR2 = "SMARTAIR2" PROTOCOL_SMARTAIR2 = "SMARTAIR2"
@ -107,6 +111,13 @@ SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
} }
HonControlMethod = haier_ns.enum("HonControlMethod", True)
SUPPORTED_HON_CONTROL_METHODS = {
"MONITOR_ONLY": HonControlMethod.MONITOR_ONLY,
"SET_GROUP_PARAMETERS": HonControlMethod.SET_GROUP_PARAMETERS,
"SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER,
}
def validate_visual(config): def validate_visual(config):
if CONF_VISUAL in config: if CONF_VISUAL in config:
@ -184,6 +195,9 @@ CONFIG_SCHEMA = cv.All(
PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(Smartair2Climate), cv.GenerateID(): cv.declare_id(Smartair2Climate),
cv.Optional(
CONF_ALTERNATIVE_SWING_CONTROL, default=False
): cv.boolean,
cv.Optional( cv.Optional(
CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_PRESETS,
default=list( default=list(
@ -197,7 +211,15 @@ CONFIG_SCHEMA = cv.All(
PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(HonClimate), cv.GenerateID(): cv.declare_id(HonClimate),
cv.Optional(
CONF_CONTROL_METHOD, default="SET_GROUP_PARAMETERS"
): cv.ensure_list(
cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True)
),
cv.Optional(CONF_BEEPER, default=True): cv.boolean, cv.Optional(CONF_BEEPER, default=True): cv.boolean,
cv.Optional(
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
cv.Optional( cv.Optional(
CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_PRESETS,
default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()),
@ -408,6 +430,8 @@ async def to_code(config):
await climate.register_climate(var, config) await climate.register_climate(var, config)
cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL]))
if CONF_CONTROL_METHOD in config:
cg.add(var.set_control_method(config[CONF_CONTROL_METHOD]))
if CONF_BEEPER in config: if CONF_BEEPER in config:
cg.add(var.set_beeper_state(config[CONF_BEEPER])) cg.add(var.set_beeper_state(config[CONF_BEEPER]))
if CONF_DISPLAY in config: if CONF_DISPLAY in config:
@ -423,5 +447,15 @@ async def to_code(config):
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_ANSWER_TIMEOUT in config: if CONF_ANSWER_TIMEOUT in config:
cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT])) cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT]))
if CONF_ALTERNATIVE_SWING_CONTROL in config:
cg.add(
var.set_alternative_swing_control(config[CONF_ALTERNATIVE_SWING_CONTROL])
)
if CONF_CONTROL_PACKET_SIZE in config:
cg.add(
var.set_extra_control_packet_bytes_size(
config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE
)
)
# https://github.com/paveldn/HaierProtocol # https://github.com/paveldn/HaierProtocol
cg.add_library("pavlodn/HaierProtocol", "0.9.20") cg.add_library("pavlodn/HaierProtocol", "0.9.24")

View file

@ -19,56 +19,45 @@ constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000;
constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000; constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000; constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400; constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
constexpr size_t CONTROL_TIMEOUT_MS = 7000;
constexpr size_t NO_COMMAND = 0xFF; // Indicate that there is no command supplied
#if (HAIER_LOG_LEVEL > 4)
// To reduce size of binary this function only available when log level is Verbose
const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
static const char *phase_names[] = { static const char *phase_names[] = {
"SENDING_INIT_1", "SENDING_INIT_1",
"WAITING_INIT_1_ANSWER",
"SENDING_INIT_2", "SENDING_INIT_2",
"WAITING_INIT_2_ANSWER",
"SENDING_FIRST_STATUS_REQUEST", "SENDING_FIRST_STATUS_REQUEST",
"WAITING_FIRST_STATUS_ANSWER",
"SENDING_ALARM_STATUS_REQUEST", "SENDING_ALARM_STATUS_REQUEST",
"WAITING_ALARM_STATUS_ANSWER",
"IDLE", "IDLE",
"UNKNOWN",
"SENDING_STATUS_REQUEST", "SENDING_STATUS_REQUEST",
"WAITING_STATUS_ANSWER",
"SENDING_UPDATE_SIGNAL_REQUEST", "SENDING_UPDATE_SIGNAL_REQUEST",
"WAITING_UPDATE_SIGNAL_ANSWER",
"SENDING_SIGNAL_LEVEL", "SENDING_SIGNAL_LEVEL",
"WAITING_SIGNAL_LEVEL_ANSWER",
"SENDING_CONTROL", "SENDING_CONTROL",
"WAITING_CONTROL_ANSWER", "SENDING_ACTION_COMMAND",
"SENDING_POWER_ON_COMMAND",
"WAITING_POWER_ON_ANSWER",
"SENDING_POWER_OFF_COMMAND",
"WAITING_POWER_OFF_ANSWER",
"UNKNOWN" // Should be the last! "UNKNOWN" // Should be the last!
}; };
static_assert(
(sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1),
"Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases");
int phase_index = (int) phase; int phase_index = (int) phase;
if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0)) if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0))
phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES; phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES;
return phase_names[phase_index]; return phase_names[phase_index];
} }
#endif
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
size_t timeout) {
return std::chrono::duration_cast<std::chrono::milliseconds>(now - tpoint).count() > timeout;
}
HaierClimateBase::HaierClimateBase() HaierClimateBase::HaierClimateBase()
: haier_protocol_(*this), : haier_protocol_(*this),
protocol_phase_(ProtocolPhases::SENDING_INIT_1), protocol_phase_(ProtocolPhases::SENDING_INIT_1),
action_request_(ActionRequest::NO_ACTION),
display_status_(true), display_status_(true),
health_mode_(false), health_mode_(false),
force_send_control_(false), force_send_control_(false),
forced_publish_(false),
forced_request_status_(false), forced_request_status_(false),
first_control_attempt_(false),
reset_protocol_request_(false), reset_protocol_request_(false),
send_wifi_signal_(true) { send_wifi_signal_(true),
use_crc_(false) {
this->traits_ = climate::ClimateTraits(); this->traits_ = climate::ClimateTraits();
this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT,
climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY,
@ -84,42 +73,43 @@ HaierClimateBase::~HaierClimateBase() {}
void HaierClimateBase::set_phase(ProtocolPhases phase) { void HaierClimateBase::set_phase(ProtocolPhases phase) {
if (this->protocol_phase_ != phase) { if (this->protocol_phase_ != phase) {
#if (HAIER_LOG_LEVEL > 4)
ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase)); ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase));
#else
ESP_LOGV(TAG, "Phase transition: %d => %d", (int) this->protocol_phase_, (int) phase);
#endif
this->protocol_phase_ = phase; this->protocol_phase_ = phase;
} }
} }
bool HaierClimateBase::check_timeout_(std::chrono::steady_clock::time_point now, void HaierClimateBase::reset_phase_() {
std::chrono::steady_clock::time_point tpoint, size_t timeout) { this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
return std::chrono::duration_cast<std::chrono::milliseconds>(now - tpoint).count() > timeout; : ProtocolPhases::SENDING_INIT_1);
}
void HaierClimateBase::reset_to_idle_() {
this->force_send_control_ = false;
if (this->current_hvac_settings_.valid)
this->current_hvac_settings_.reset();
this->forced_request_status_ = true;
this->set_phase(ProtocolPhases::IDLE);
this->action_request_.reset();
} }
bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) {
return this->check_timeout_(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS); return check_timeout(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS);
} }
bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) { bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) {
return this->check_timeout_(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS); return check_timeout(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS);
}
bool HaierClimateBase::is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now) {
return this->check_timeout_(now, this->control_request_timestamp_, CONTROL_TIMEOUT_MS);
} }
bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) {
return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); return check_timeout(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS);
} }
bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now) { bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now) {
return this->check_timeout_(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); return check_timeout(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL);
} }
#ifdef USE_WIFI #ifdef USE_WIFI
haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t message_type) { haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() {
static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00};
if (wifi::global_wifi_component->is_connected()) { if (wifi::global_wifi_component->is_connected()) {
wifi_status_data[1] = 0; wifi_status_data[1] = 0;
@ -131,7 +121,8 @@ haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t
wifi_status_data[1] = 1; wifi_status_data[1] = 1;
wifi_status_data[3] = 0; wifi_status_data[3] = 0;
} }
return haier_protocol::HaierMessage(message_type, wifi_status_data, sizeof(wifi_status_data)); return haier_protocol::HaierMessage(haier_protocol::FrameType::REPORT_NETWORK_STATUS, wifi_status_data,
sizeof(wifi_status_data));
} }
#endif #endif
@ -140,7 +131,7 @@ bool HaierClimateBase::get_display_state() const { return this->display_status_;
void HaierClimateBase::set_display_state(bool state) { void HaierClimateBase::set_display_state(bool state) {
if (this->display_status_ != state) { if (this->display_status_ != state) {
this->display_status_ = state; this->display_status_ = state;
this->set_force_send_control_(true); this->force_send_control_ = true;
} }
} }
@ -149,15 +140,24 @@ bool HaierClimateBase::get_health_mode() const { return this->health_mode_; }
void HaierClimateBase::set_health_mode(bool state) { void HaierClimateBase::set_health_mode(bool state) {
if (this->health_mode_ != state) { if (this->health_mode_ != state) {
this->health_mode_ = state; this->health_mode_ = state;
this->set_force_send_control_(true); this->force_send_control_ = true;
} }
} }
void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionRequest::TURN_POWER_ON; } void HaierClimateBase::send_power_on_command() {
this->action_request_ =
PendingAction({ActionRequest::TURN_POWER_ON, esphome::optional<haier_protocol::HaierMessage>()});
}
void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; } void HaierClimateBase::send_power_off_command() {
this->action_request_ =
PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
}
void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; } void HaierClimateBase::toggle_power() {
this->action_request_ =
PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()});
}
void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
this->traits_.set_supported_swing_modes(modes); this->traits_.set_supported_swing_modes(modes);
@ -165,9 +165,7 @@ void HaierClimateBase::set_supported_swing_modes(const std::set<climate::Climate
this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
} }
void HaierClimateBase::set_answer_timeout(uint32_t timeout) { void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); }
this->answer_timeout_ = std::chrono::milliseconds(timeout);
}
void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) { void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) {
this->traits_.set_supported_modes(modes); this->traits_.set_supported_modes(modes);
@ -183,29 +181,42 @@ void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePres
void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; }
haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t request_message_type, void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &message) {
uint8_t expected_request_message_type, this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message});
uint8_t answer_message_type, }
uint8_t expected_answer_message_type,
ProtocolPhases expected_phase) { 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,
ProtocolPhases expected_phase) {
haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type)) if ((expected_request_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) &&
(request_message_type != expected_request_message_type))
result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type)) if ((expected_answer_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) &&
(answer_message_type != expected_answer_message_type))
result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)) if (!this->haier_protocol_.is_waiting_for_answer() ||
((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)))
result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
if (is_message_invalid(answer_message_type)) if (answer_message_type == haier_protocol::FrameType::INVALID)
result = haier_protocol::HandlerError::INVALID_ANSWER; result = haier_protocol::HandlerError::INVALID_ANSWER;
return result; return result;
} }
haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t request_type) { haier_protocol::HandlerError HaierClimateBase::report_network_status_answer_handler_(
#if (HAIER_LOG_LEVEL > 4) haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", request_type, phase_to_string_(this->protocol_phase_)); size_t data_size) {
#else haier_protocol::HandlerError result =
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_); this->answer_preprocess_(request_type, haier_protocol::FrameType::REPORT_NETWORK_STATUS, message_type,
#endif haier_protocol::FrameType::CONFIRM, ProtocolPhases::SENDING_SIGNAL_LEVEL);
this->set_phase(ProtocolPhases::IDLE);
return result;
}
haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) {
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type,
phase_to_string_(this->protocol_phase_));
if (this->protocol_phase_ > ProtocolPhases::IDLE) { if (this->protocol_phase_ > ProtocolPhases::IDLE) {
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
} else { } else {
@ -219,79 +230,95 @@ void HaierClimateBase::setup() {
// Set timestamp here to give AC time to boot // Set timestamp here to give AC time to boot
this->last_request_timestamp_ = std::chrono::steady_clock::now(); this->last_request_timestamp_ = std::chrono::steady_clock::now();
this->set_phase(ProtocolPhases::SENDING_INIT_1); this->set_phase(ProtocolPhases::SENDING_INIT_1);
this->set_handlers();
this->haier_protocol_.set_default_timeout_handler( this->haier_protocol_.set_default_timeout_handler(
std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
this->set_handlers();
} }
void HaierClimateBase::dump_config() { void HaierClimateBase::dump_config() {
LOG_CLIMATE("", "Haier Climate", this); LOG_CLIMATE("", "Haier Climate", this);
ESP_LOGCONFIG(TAG, " Device communication status: %s", ESP_LOGCONFIG(TAG, " Device communication status: %s", this->valid_connection() ? "established" : "none");
(this->protocol_phase_ >= ProtocolPhases::IDLE) ? "established" : "none");
} }
void HaierClimateBase::loop() { void HaierClimateBase::loop() {
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_valid_status_timestamp_).count() > if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_valid_status_timestamp_).count() >
COMMUNICATION_TIMEOUT_MS) || COMMUNICATION_TIMEOUT_MS) ||
(this->reset_protocol_request_)) { (this->reset_protocol_request_ && (!this->haier_protocol_.is_waiting_for_answer()))) {
this->last_valid_status_timestamp_ = now;
if (this->protocol_phase_ >= ProtocolPhases::IDLE) { if (this->protocol_phase_ >= ProtocolPhases::IDLE) {
// No status too long, reseting protocol // No status too long, reseting protocol
// No need to reset protocol if we didn't pass initialization phase
if (this->reset_protocol_request_) { if (this->reset_protocol_request_) {
this->reset_protocol_request_ = false; this->reset_protocol_request_ = false;
ESP_LOGW(TAG, "Protocol reset requested"); ESP_LOGW(TAG, "Protocol reset requested");
} else { } else {
ESP_LOGW(TAG, "Communication timeout, reseting protocol"); ESP_LOGW(TAG, "Communication timeout, reseting protocol");
} }
this->last_valid_status_timestamp_ = now; this->process_protocol_reset();
this->set_force_send_control_(false);
if (this->hvac_settings_.valid)
this->hvac_settings_.reset();
this->set_phase(ProtocolPhases::SENDING_INIT_1);
return; return;
} else {
// No need to reset protocol if we didn't pass initialization phase
this->last_valid_status_timestamp_ = now;
} }
}; };
if ((this->protocol_phase_ == ProtocolPhases::IDLE) || if ((!this->haier_protocol_.is_waiting_for_answer()) &&
(this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) || ((this->protocol_phase_ == ProtocolPhases::IDLE) ||
(this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) || (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) ||
(this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL)) { (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) ||
(this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL))) {
// If control message or action is pending we should send it ASAP unless we are in initialisation // If control message or action is pending we should send it ASAP unless we are in initialisation
// procedure or waiting for an answer // procedure or waiting for an answer
if (this->action_request_ != ActionRequest::NO_ACTION) { if (this->action_request_.has_value() && this->prepare_pending_action()) {
this->process_pending_action(); this->set_phase(ProtocolPhases::SENDING_ACTION_COMMAND);
} else if (this->hvac_settings_.valid || this->force_send_control_) { } else if (this->next_hvac_settings_.valid || this->force_send_control_) {
ESP_LOGV(TAG, "Control packet is pending..."); ESP_LOGV(TAG, "Control packet is pending...");
this->set_phase(ProtocolPhases::SENDING_CONTROL); this->set_phase(ProtocolPhases::SENDING_CONTROL);
if (this->next_hvac_settings_.valid) {
this->current_hvac_settings_ = this->next_hvac_settings_;
this->next_hvac_settings_.reset();
} else {
this->current_hvac_settings_.reset();
}
} }
} }
this->process_phase(now); this->process_phase(now);
this->haier_protocol_.loop(); this->haier_protocol_.loop();
} }
void HaierClimateBase::process_pending_action() { void HaierClimateBase::process_protocol_reset() {
ActionRequest request = this->action_request_; this->force_send_control_ = false;
if (this->action_request_ == ActionRequest::TOGGLE_POWER) { if (this->current_hvac_settings_.valid)
request = this->mode == CLIMATE_MODE_OFF ? ActionRequest::TURN_POWER_ON : ActionRequest::TURN_POWER_OFF; this->current_hvac_settings_.reset();
} if (this->next_hvac_settings_.valid)
switch (request) { this->next_hvac_settings_.reset();
case ActionRequest::TURN_POWER_ON: this->mode = CLIMATE_MODE_OFF;
this->set_phase(ProtocolPhases::SENDING_POWER_ON_COMMAND); this->current_temperature = NAN;
break; this->target_temperature = NAN;
case ActionRequest::TURN_POWER_OFF: this->fan_mode.reset();
this->set_phase(ProtocolPhases::SENDING_POWER_OFF_COMMAND); this->preset.reset();
break; this->publish_state();
case ActionRequest::TOGGLE_POWER: this->set_phase(ProtocolPhases::SENDING_INIT_1);
case ActionRequest::NO_ACTION: }
// shouldn't get here, do nothing
break; bool HaierClimateBase::prepare_pending_action() {
default: if (this->action_request_.has_value()) {
ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_); switch (this->action_request_.value().action) {
break; case ActionRequest::SEND_CUSTOM_COMMAND:
} return true;
this->action_request_ = ActionRequest::NO_ACTION; case ActionRequest::TURN_POWER_ON:
this->action_request_.value().message = this->get_power_message(true);
return true;
case ActionRequest::TURN_POWER_OFF:
this->action_request_.value().message = this->get_power_message(false);
return true;
case ActionRequest::TOGGLE_POWER:
this->action_request_.value().message = this->get_power_message(this->mode == ClimateMode::CLIMATE_MODE_OFF);
return true;
default:
ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_.value().action);
this->action_request_.reset();
return false;
}
} else
return false;
} }
ClimateTraits HaierClimateBase::traits() { return traits_; } ClimateTraits HaierClimateBase::traits() { return traits_; }
@ -302,23 +329,22 @@ void HaierClimateBase::control(const ClimateCall &call) {
ESP_LOGW(TAG, "Can't send control packet, first poll answer not received"); ESP_LOGW(TAG, "Can't send control packet, first poll answer not received");
return; // cancel the control, we cant do it without a poll answer. return; // cancel the control, we cant do it without a poll answer.
} }
if (this->hvac_settings_.valid) { if (this->current_hvac_settings_.valid) {
ESP_LOGW(TAG, "Overriding old valid settings before they were applied!"); ESP_LOGW(TAG, "New settings come faster then processed!");
} }
{ {
if (call.get_mode().has_value()) if (call.get_mode().has_value())
this->hvac_settings_.mode = call.get_mode(); this->next_hvac_settings_.mode = call.get_mode();
if (call.get_fan_mode().has_value()) if (call.get_fan_mode().has_value())
this->hvac_settings_.fan_mode = call.get_fan_mode(); this->next_hvac_settings_.fan_mode = call.get_fan_mode();
if (call.get_swing_mode().has_value()) if (call.get_swing_mode().has_value())
this->hvac_settings_.swing_mode = call.get_swing_mode(); this->next_hvac_settings_.swing_mode = call.get_swing_mode();
if (call.get_target_temperature().has_value()) if (call.get_target_temperature().has_value())
this->hvac_settings_.target_temperature = call.get_target_temperature(); this->next_hvac_settings_.target_temperature = call.get_target_temperature();
if (call.get_preset().has_value()) if (call.get_preset().has_value())
this->hvac_settings_.preset = call.get_preset(); this->next_hvac_settings_.preset = call.get_preset();
this->hvac_settings_.valid = true; this->next_hvac_settings_.valid = true;
} }
this->first_control_attempt_ = true;
} }
void HaierClimateBase::HvacSettings::reset() { void HaierClimateBase::HvacSettings::reset() {
@ -330,19 +356,9 @@ void HaierClimateBase::HvacSettings::reset() {
this->preset.reset(); this->preset.reset();
} }
void HaierClimateBase::set_force_send_control_(bool status) { void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats,
this->force_send_control_ = status; std::chrono::milliseconds interval) {
if (status) { this->haier_protocol_.send_message(command, use_crc, num_repeats, interval);
this->first_control_attempt_ = true;
}
}
void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc) {
if (this->answer_timeout_.has_value()) {
this->haier_protocol_.send_message(command, use_crc, this->answer_timeout_.value());
} else {
this->haier_protocol_.send_message(command, use_crc);
}
this->last_request_timestamp_ = std::chrono::steady_clock::now(); this->last_request_timestamp_ = std::chrono::steady_clock::now();
} }

View file

@ -11,7 +11,7 @@ namespace esphome {
namespace haier { namespace haier {
enum class ActionRequest : uint8_t { enum class ActionRequest : uint8_t {
NO_ACTION = 0, SEND_CUSTOM_COMMAND = 0,
TURN_POWER_ON = 1, TURN_POWER_ON = 1,
TURN_POWER_OFF = 2, TURN_POWER_OFF = 2,
TOGGLE_POWER = 3, TOGGLE_POWER = 3,
@ -33,7 +33,6 @@ class HaierClimateBase : public esphome::Component,
void control(const esphome::climate::ClimateCall &call) override; void control(const esphome::climate::ClimateCall &call) override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }
void set_fahrenheit(bool fahrenheit);
void set_display_state(bool state); void set_display_state(bool state);
bool get_display_state() const; bool get_display_state() const;
void set_health_mode(bool state); void set_health_mode(bool state);
@ -45,6 +44,7 @@ class HaierClimateBase : public esphome::Component,
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes); void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes);
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes); void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes);
void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets); void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets);
bool valid_connection() { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; size_t available() noexcept override { return esphome::uart::UARTDevice::available(); };
size_t read_array(uint8_t *data, size_t len) noexcept override { size_t read_array(uint8_t *data, size_t len) noexcept override {
return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; return esphome::uart::UARTDevice::read_array(data, len) ? len : 0;
@ -55,63 +55,56 @@ class HaierClimateBase : public esphome::Component,
bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; };
void set_answer_timeout(uint32_t timeout); void set_answer_timeout(uint32_t timeout);
void set_send_wifi(bool send_wifi); void set_send_wifi(bool send_wifi);
void send_custom_command(const haier_protocol::HaierMessage &message);
protected: protected:
enum class ProtocolPhases { enum class ProtocolPhases {
UNKNOWN = -1, UNKNOWN = -1,
// INITIALIZATION // INITIALIZATION
SENDING_INIT_1 = 0, SENDING_INIT_1 = 0,
WAITING_INIT_1_ANSWER = 1, SENDING_INIT_2,
SENDING_INIT_2 = 2, SENDING_FIRST_STATUS_REQUEST,
WAITING_INIT_2_ANSWER = 3, SENDING_ALARM_STATUS_REQUEST,
SENDING_FIRST_STATUS_REQUEST = 4,
WAITING_FIRST_STATUS_ANSWER = 5,
SENDING_ALARM_STATUS_REQUEST = 6,
WAITING_ALARM_STATUS_ANSWER = 7,
// FUNCTIONAL STATE // FUNCTIONAL STATE
IDLE = 8, IDLE,
SENDING_STATUS_REQUEST = 10, SENDING_STATUS_REQUEST,
WAITING_STATUS_ANSWER = 11, SENDING_UPDATE_SIGNAL_REQUEST,
SENDING_UPDATE_SIGNAL_REQUEST = 12, SENDING_SIGNAL_LEVEL,
WAITING_UPDATE_SIGNAL_ANSWER = 13, SENDING_CONTROL,
SENDING_SIGNAL_LEVEL = 14, SENDING_ACTION_COMMAND,
WAITING_SIGNAL_LEVEL_ANSWER = 15,
SENDING_CONTROL = 16,
WAITING_CONTROL_ANSWER = 17,
SENDING_POWER_ON_COMMAND = 18,
WAITING_POWER_ON_ANSWER = 19,
SENDING_POWER_OFF_COMMAND = 20,
WAITING_POWER_OFF_ANSWER = 21,
NUM_PROTOCOL_PHASES NUM_PROTOCOL_PHASES
}; };
#if (HAIER_LOG_LEVEL > 4)
const char *phase_to_string_(ProtocolPhases phase); const char *phase_to_string_(ProtocolPhases phase);
#endif
virtual void set_handlers() = 0; virtual void set_handlers() = 0;
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0; virtual haier_protocol::HaierMessage get_control_message() = 0;
virtual bool is_message_invalid(uint8_t message_type) = 0; virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
virtual void process_pending_action(); virtual bool prepare_pending_action();
virtual void process_protocol_reset();
esphome::climate::ClimateTraits traits() override; esphome::climate::ClimateTraits traits() override;
// Answers handlers // Answer handlers
haier_protocol::HandlerError answer_preprocess_(uint8_t request_message_type, uint8_t expected_request_message_type, haier_protocol::HandlerError answer_preprocess_(haier_protocol::FrameType request_message_type,
uint8_t answer_message_type, uint8_t expected_answer_message_type, haier_protocol::FrameType expected_request_message_type,
haier_protocol::FrameType answer_message_type,
haier_protocol::FrameType expected_answer_message_type,
ProtocolPhases expected_phase); ProtocolPhases expected_phase);
haier_protocol::HandlerError report_network_status_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size);
// Timeout handler // Timeout handler
haier_protocol::HandlerError timeout_default_handler_(uint8_t request_type); haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type);
// Helper functions // Helper functions
void set_force_send_control_(bool status); void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats = 0,
void send_message_(const haier_protocol::HaierMessage &command, bool use_crc); std::chrono::milliseconds interval = std::chrono::milliseconds::zero());
virtual void set_phase(ProtocolPhases phase); virtual void set_phase(ProtocolPhases phase);
bool check_timeout_(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, void reset_phase_();
size_t timeout); void reset_to_idle_();
bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now);
bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now);
bool is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now);
bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now);
bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now);
#ifdef USE_WIFI #ifdef USE_WIFI
haier_protocol::HaierMessage get_wifi_signal_message_(uint8_t message_type); haier_protocol::HaierMessage get_wifi_signal_message_();
#endif #endif
struct HvacSettings { struct HvacSettings {
@ -122,29 +115,34 @@ class HaierClimateBase : public esphome::Component,
esphome::optional<esphome::climate::ClimatePreset> preset; esphome::optional<esphome::climate::ClimatePreset> preset;
bool valid; bool valid;
HvacSettings() : valid(false){}; HvacSettings() : valid(false){};
HvacSettings(const HvacSettings &) = default;
HvacSettings &operator=(const HvacSettings &) = default;
void reset(); void reset();
}; };
struct PendingAction {
ActionRequest action;
esphome::optional<haier_protocol::HaierMessage> message;
};
haier_protocol::ProtocolHandler haier_protocol_; haier_protocol::ProtocolHandler haier_protocol_;
ProtocolPhases protocol_phase_; ProtocolPhases protocol_phase_;
ActionRequest action_request_; esphome::optional<PendingAction> action_request_;
uint8_t fan_mode_speed_; uint8_t fan_mode_speed_;
uint8_t other_modes_fan_speed_; uint8_t other_modes_fan_speed_;
bool display_status_; bool display_status_;
bool health_mode_; bool health_mode_;
bool force_send_control_; bool force_send_control_;
bool forced_publish_;
bool forced_request_status_; bool forced_request_status_;
bool first_control_attempt_;
bool reset_protocol_request_; bool reset_protocol_request_;
bool send_wifi_signal_;
bool use_crc_;
esphome::climate::ClimateTraits traits_; esphome::climate::ClimateTraits traits_;
HvacSettings hvac_settings_; HvacSettings current_hvac_settings_;
HvacSettings next_hvac_settings_;
std::unique_ptr<uint8_t[]> last_status_message_;
std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages 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_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_status_request_; // To request AC status
std::chrono::steady_clock::time_point control_request_timestamp_; // To send control message std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
optional<std::chrono::milliseconds> answer_timeout_; // Message answer timeout
bool send_wifi_signal_;
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
}; };
} // namespace haier } // namespace haier

View file

@ -14,6 +14,8 @@ namespace haier {
static const char *const TAG = "haier.climate"; static const char *const TAG = "haier.climate";
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; 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);
hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) {
switch (direction) { switch (direction) {
@ -48,14 +50,11 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir
} }
HonClimate::HonClimate() HonClimate::HonClimate()
: last_status_message_(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]), : cleaning_status_(CleaningState::NO_CLEANING),
cleaning_status_(CleaningState::NO_CLEANING),
got_valid_outdoor_temp_(false), got_valid_outdoor_temp_(false),
hvac_hardware_info_available_(false),
hvac_functions_{false, false, false, false, false},
use_crc_(hvac_functions_[2]),
active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
outdoor_sensor_(nullptr) { outdoor_sensor_(nullptr) {
last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]);
this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
} }
@ -72,14 +71,14 @@ AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this-
void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
this->vertical_direction_ = direction; this->vertical_direction_ = direction;
this->set_force_send_control_(true); this->force_send_control_ = true;
} }
AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; } AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; }
void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) { void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) {
this->horizontal_direction_ = direction; this->horizontal_direction_ = direction;
this->set_force_send_control_(true); this->force_send_control_ = true;
} }
std::string HonClimate::get_cleaning_status_text() const { std::string HonClimate::get_cleaning_status_text() const {
@ -98,35 +97,35 @@ CleaningState HonClimate::get_cleaning_status() const { return this->cleaning_st
void HonClimate::start_self_cleaning() { void HonClimate::start_self_cleaning() {
if (this->cleaning_status_ == CleaningState::NO_CLEANING) { if (this->cleaning_status_ == CleaningState::NO_CLEANING) {
ESP_LOGI(TAG, "Sending self cleaning start request"); ESP_LOGI(TAG, "Sending self cleaning start request");
this->action_request_ = ActionRequest::START_SELF_CLEAN; this->action_request_ =
this->set_force_send_control_(true); PendingAction({ActionRequest::START_SELF_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
} }
} }
void HonClimate::start_steri_cleaning() { void HonClimate::start_steri_cleaning() {
if (this->cleaning_status_ == CleaningState::NO_CLEANING) { if (this->cleaning_status_ == CleaningState::NO_CLEANING) {
ESP_LOGI(TAG, "Sending steri cleaning start request"); ESP_LOGI(TAG, "Sending steri cleaning start request");
this->action_request_ = ActionRequest::START_STERI_CLEAN; this->action_request_ =
this->set_force_send_control_(true); PendingAction({ActionRequest::START_STERI_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
} }
} }
haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size) { const uint8_t *data, size_t data_size) {
// Should check this before preprocess // Should check this before preprocess
if (message_type == (uint8_t) hon_protocol::FrameType::INVALID) { if (message_type == haier_protocol::FrameType::INVALID) {
ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 " ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
"protocol instead of hOn"); "protocol instead of hOn");
this->set_phase(ProtocolPhases::SENDING_INIT_1); this->set_phase(ProtocolPhases::SENDING_INIT_1);
return haier_protocol::HandlerError::INVALID_ANSWER; return haier_protocol::HandlerError::INVALID_ANSWER;
} }
haier_protocol::HandlerError result = this->answer_preprocess_( haier_protocol::HandlerError result =
request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type, this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
(uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_INIT_1_ANSWER); haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
if (result == haier_protocol::HandlerError::HANDLER_OK) { if (result == haier_protocol::HandlerError::HANDLER_OK) {
if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
// Wrong structure // Wrong structure
this->set_phase(ProtocolPhases::SENDING_INIT_1);
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
} }
// All OK // All OK
@ -134,54 +133,57 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint
char tmp[9]; char tmp[9];
tmp[8] = 0; tmp[8] = 0;
strncpy(tmp, answr->protocol_version, 8); strncpy(tmp, answr->protocol_version, 8);
this->hvac_protocol_version_ = std::string(tmp); this->hvac_hardware_info_ = HardwareInfo();
this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp);
strncpy(tmp, answr->software_version, 8); strncpy(tmp, answr->software_version, 8);
this->hvac_software_version_ = std::string(tmp); this->hvac_hardware_info_.value().software_version_ = std::string(tmp);
strncpy(tmp, answr->hardware_version, 8); strncpy(tmp, answr->hardware_version, 8);
this->hvac_hardware_version_ = std::string(tmp); this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
strncpy(tmp, answr->device_name, 8); strncpy(tmp, answr->device_name, 8);
this->hvac_device_name_ = std::string(tmp); this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
this->hvac_functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
this->hvac_functions_[1] = (answr->functions[1] & 0x02) != 0; // controller-device mode support this->hvac_hardware_info_.value().functions_[1] =
this->hvac_functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support (answr->functions[1] & 0x02) != 0; // controller-device mode support
this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
this->hvac_hardware_info_available_ = true; this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
this->use_crc_ = this->hvac_hardware_info_.value().functions_[2];
this->set_phase(ProtocolPhases::SENDING_INIT_2); this->set_phase(ProtocolPhases::SENDING_INIT_2);
return result; return result;
} else { } else {
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE this->reset_phase_();
: ProtocolPhases::SENDING_INIT_1);
return result; return result;
} }
} }
haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size) { const uint8_t *data, size_t data_size) {
haier_protocol::HandlerError result = this->answer_preprocess_( haier_protocol::HandlerError result =
request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type, this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
(uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_INIT_2_ANSWER); haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
if (result == haier_protocol::HandlerError::HANDLER_OK) { if (result == haier_protocol::HandlerError::HANDLER_OK) {
this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
return result; return result;
} else { } else {
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE this->reset_phase_();
: ProtocolPhases::SENDING_INIT_1);
return result; return result;
} }
} }
haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
const uint8_t *data, size_t data_size) { haier_protocol::FrameType message_type, const uint8_t *data,
size_t data_size) {
haier_protocol::HandlerError result = haier_protocol::HandlerError result =
this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::CONTROL, message_type, this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
(uint8_t) hon_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
if (result == haier_protocol::HandlerError::HANDLER_OK) { if (result == haier_protocol::HandlerError::HANDLER_OK) {
result = this->process_status_message_(data, data_size); result = this->process_status_message_(data, data_size);
if (result != haier_protocol::HandlerError::HANDLER_OK) { if (result != haier_protocol::HandlerError::HANDLER_OK) {
ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE this->reset_phase_();
: ProtocolPhases::SENDING_INIT_1); this->action_request_.reset();
this->force_send_control_ = false;
} else { } else {
if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) {
memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl));
@ -189,36 +191,48 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
sizeof(hon_protocol::HaierPacketControl)); sizeof(hon_protocol::HaierPacketControl));
} }
if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { switch (this->protocol_phase_) {
ESP_LOGI(TAG, "First HVAC status received"); case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); ESP_LOGI(TAG, "First HVAC status received");
} else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) || this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST);
(this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) || break;
(this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) { case ProtocolPhases::SENDING_ACTION_COMMAND:
this->set_phase(ProtocolPhases::IDLE); // Do nothing, phase will be changed in process_phase
} else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { break;
this->set_phase(ProtocolPhases::IDLE); case ProtocolPhases::SENDING_STATUS_REQUEST:
this->set_force_send_control_(false); this->set_phase(ProtocolPhases::IDLE);
if (this->hvac_settings_.valid) break;
this->hvac_settings_.reset(); case ProtocolPhases::SENDING_CONTROL:
if (!this->control_messages_queue_.empty())
this->control_messages_queue_.pop();
if (this->control_messages_queue_.empty()) {
this->set_phase(ProtocolPhases::IDLE);
this->force_send_control_ = false;
if (this->current_hvac_settings_.valid)
this->current_hvac_settings_.reset();
} else {
this->set_phase(ProtocolPhases::SENDING_CONTROL);
}
break;
default:
break;
} }
} }
return result; return result;
} else { } else {
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE this->action_request_.reset();
: ProtocolPhases::SENDING_INIT_1); this->force_send_control_ = false;
this->reset_phase_();
return result; return result;
} }
} }
haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_(uint8_t request_type, haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_(
uint8_t message_type, haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
const uint8_t *data, size_t data_size) {
size_t data_size) { haier_protocol::HandlerError result = this->answer_preprocess_(
haier_protocol::HandlerError result = request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE,
ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER);
if (result == haier_protocol::HandlerError::HANDLER_OK) { if (result == haier_protocol::HandlerError::HANDLER_OK) {
this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL);
return result; return result;
@ -228,25 +242,16 @@ haier_protocol::HandlerError HonClimate::get_management_information_answer_handl
} }
} }
haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(uint8_t request_type, haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
uint8_t message_type, haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size) {
haier_protocol::HandlerError result =
this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, message_type,
(uint8_t) hon_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
this->set_phase(ProtocolPhases::IDLE);
return result;
}
haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type,
const uint8_t *data, size_t data_size) { const uint8_t *data, size_t data_size) {
if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) { if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
// Unexpected answer to request // Unexpected answer to request
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
} }
if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) { if (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) {
// Don't expect this answer now // Don't expect this answer now
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
@ -263,27 +268,27 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_
void HonClimate::set_handlers() { void HonClimate::set_handlers() {
// Set handlers // Set handlers
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION), haier_protocol::FrameType::GET_DEVICE_VERSION,
std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (hon_protocol::FrameType::GET_DEVICE_ID), haier_protocol::FrameType::GET_DEVICE_ID,
std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (hon_protocol::FrameType::CONTROL), haier_protocol::FrameType::CONTROL,
std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4)); std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION), haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1, std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (hon_protocol::FrameType::GET_ALARM_STATUS), haier_protocol::FrameType::GET_ALARM_STATUS,
std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (hon_protocol::FrameType::REPORT_NETWORK_STATUS), haier_protocol::FrameType::REPORT_NETWORK_STATUS,
std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
} }
@ -291,14 +296,18 @@ void HonClimate::set_handlers() {
void HonClimate::dump_config() { void HonClimate::dump_config() {
HaierClimateBase::dump_config(); HaierClimateBase::dump_config();
ESP_LOGCONFIG(TAG, " Protocol version: hOn"); ESP_LOGCONFIG(TAG, " Protocol version: hOn");
if (this->hvac_hardware_info_available_) { ESP_LOGCONFIG(TAG, " Control method: %d", (uint8_t) this->control_method_);
ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_protocol_version_.c_str()); if (this->hvac_hardware_info_.has_value()) {
ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_software_version_.c_str()); ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_hardware_info_.value().protocol_version_.c_str());
ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_version_.c_str()); ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_hardware_info_.value().software_version_.c_str());
ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_device_name_.c_str()); ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_info_.value().hardware_version_.c_str());
ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s", (this->hvac_functions_[0] ? " interactive" : ""), ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_hardware_info_.value().device_name_.c_str());
(this->hvac_functions_[1] ? " controller-device" : ""), (this->hvac_functions_[2] ? " crc" : ""), ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
(this->hvac_functions_[3] ? " multinode" : ""), (this->hvac_functions_[4] ? " role" : "")); (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""),
(this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""),
(this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""),
(this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""),
(this->hvac_hardware_info_.value().functions_[4] ? " role" : ""));
ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str()); ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
} }
} }
@ -307,7 +316,6 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
switch (this->protocol_phase_) { switch (this->protocol_phase_) {
case ProtocolPhases::SENDING_INIT_1: case ProtocolPhases::SENDING_INIT_1:
if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) {
this->hvac_hardware_info_available_ = false;
// Indicate device capabilities: // Indicate device capabilities:
// bit 0 - if 1 module support interactive mode // bit 0 - if 1 module support interactive mode
// bit 1 - if 1 module support controller-device mode // bit 1 - if 1 module support controller-device mode
@ -316,109 +324,95 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
// bit 4..bit 15 - not used // bit 4..bit 15 - not used
uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
(uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_INIT_2: case ProtocolPhases::SENDING_INIT_2:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
static const haier_protocol::HaierMessage DEVICEID_REQUEST((uint8_t) hon_protocol::FrameType::GET_DEVICE_ID); static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
this->send_message_(DEVICEID_REQUEST, this->use_crc_); this->send_message_(DEVICEID_REQUEST, this->use_crc_);
this->set_phase(ProtocolPhases::WAITING_INIT_2_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
case ProtocolPhases::SENDING_STATUS_REQUEST: case ProtocolPhases::SENDING_STATUS_REQUEST:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
static const haier_protocol::HaierMessage STATUS_REQUEST( static const haier_protocol::HaierMessage STATUS_REQUEST(
(uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
this->send_message_(STATUS_REQUEST, this->use_crc_); this->send_message_(STATUS_REQUEST, this->use_crc_);
this->last_status_request_ = now; this->last_status_request_ = now;
this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1));
} }
break; break;
#ifdef USE_WIFI #ifdef USE_WIFI
case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST( static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
(uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION); haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
this->last_signal_request_ = now; this->last_signal_request_ = now;
this->set_phase(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_SIGNAL_LEVEL: case ProtocolPhases::SENDING_SIGNAL_LEVEL:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
this->send_message_(this->get_wifi_signal_message_((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS), this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
this->use_crc_);
this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
} }
break; break;
case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER:
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
break;
#else #else
case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
case ProtocolPhases::SENDING_SIGNAL_LEVEL: case ProtocolPhases::SENDING_SIGNAL_LEVEL:
case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER:
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
break; break;
#endif #endif
case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST( static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
(uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS);
this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
this->set_phase(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_CONTROL: case ProtocolPhases::SENDING_CONTROL:
if (this->first_control_attempt_) { if (this->control_messages_queue_.empty()) {
this->control_request_timestamp_ = now; switch (this->control_method_) {
this->first_control_attempt_ = false; case HonControlMethod::SET_GROUP_PARAMETERS: {
haier_protocol::HaierMessage control_message = this->get_control_message();
this->control_messages_queue_.push(control_message);
} break;
case HonControlMethod::SET_SINGLE_PARAMETER:
this->fill_control_messages_queue_();
break;
case HonControlMethod::MONITOR_ONLY:
ESP_LOGI(TAG, "AC control is disabled, monitor only");
this->reset_to_idle_();
return;
default:
ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
this->reset_to_idle_();
return;
}
} }
if (this->is_control_message_timeout_exceeded_(now)) { if (this->control_messages_queue_.empty()) {
ESP_LOGW(TAG, "Sending control packet timeout!"); ESP_LOGW(TAG, "Control message queue is empty!");
this->set_force_send_control_(false); this->reset_to_idle_();
if (this->hvac_settings_.valid)
this->hvac_settings_.reset();
this->forced_request_status_ = true;
this->forced_publish_ = true;
this->set_phase(ProtocolPhases::IDLE);
} else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
haier_protocol::HaierMessage control_message = get_control_message(); ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
this->send_message_(control_message, this->use_crc_); this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
ESP_LOGI(TAG, "Control packet sent"); CONTROL_MESSAGE_RETRIES_INTERVAL);
this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_POWER_ON_COMMAND: case ProtocolPhases::SENDING_ACTION_COMMAND:
case ProtocolPhases::SENDING_POWER_OFF_COMMAND: if (this->action_request_.has_value()) {
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->action_request_.value().message.has_value()) {
uint8_t pwr_cmd_buf[2] = {0x00, 0x00}; this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND) this->action_request_.value().message.reset();
pwr_cmd_buf[1] = 0x01; } else {
haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL, // Message already sent, reseting request and return to idle
((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, this->action_request_.reset();
pwr_cmd_buf, sizeof(pwr_cmd_buf)); this->set_phase(ProtocolPhases::IDLE);
this->send_message_(power_cmd, this->use_crc_); }
this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND } else {
? ProtocolPhases::WAITING_POWER_ON_ANSWER ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
: ProtocolPhases::WAITING_POWER_OFF_ANSWER); this->set_phase(ProtocolPhases::IDLE);
} }
break; break;
case ProtocolPhases::WAITING_INIT_1_ANSWER:
case ProtocolPhases::WAITING_INIT_2_ANSWER:
case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER:
case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER:
case ProtocolPhases::WAITING_STATUS_ANSWER:
case ProtocolPhases::WAITING_CONTROL_ANSWER:
case ProtocolPhases::WAITING_POWER_ON_ANSWER:
case ProtocolPhases::WAITING_POWER_OFF_ANSWER:
break;
case ProtocolPhases::IDLE: { case ProtocolPhases::IDLE: {
if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) {
this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST);
@ -433,26 +427,35 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
} break; } break;
default: default:
// Shouldn't get here // Shouldn't get here
#if (HAIER_LOG_LEVEL > 4)
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
#else
ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_);
#endif
this->set_phase(ProtocolPhases::SENDING_INIT_1); this->set_phase(ProtocolPhases::SENDING_INIT_1);
break; break;
} }
} }
haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
if (state) {
static haier_protocol::HaierMessage power_on_message(
haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
return power_on_message;
} else {
static haier_protocol::HaierMessage power_off_message(
haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
return power_off_message;
}
}
haier_protocol::HaierMessage HonClimate::get_control_message() { haier_protocol::HaierMessage HonClimate::get_control_message() {
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
memcpy(control_out_buffer, this->last_status_message_.get(), 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; hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
bool has_hvac_settings = false; bool has_hvac_settings = false;
if (this->hvac_settings_.valid) { if (this->current_hvac_settings_.valid) {
has_hvac_settings = true; has_hvac_settings = true;
HvacSettings climate_control; HvacSettings &climate_control = this->current_hvac_settings_;
climate_control = this->hvac_settings_;
if (climate_control.mode.has_value()) { if (climate_control.mode.has_value()) {
switch (climate_control.mode.value()) { switch (climate_control.mode.value()) {
case CLIMATE_MODE_OFF: case CLIMATE_MODE_OFF:
@ -535,7 +538,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
} }
if (climate_control.target_temperature.has_value()) { if (climate_control.target_temperature.has_value()) {
float target_temp = climate_control.target_temperature.value(); float target_temp = climate_control.target_temperature.value();
out_data->set_point = ((int) target_temp) - 16; // set the temperature at our offset, subtract 16. out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
} }
if (out_data->ac_power == 0) { if (out_data->ac_power == 0) {
@ -587,50 +590,28 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
control_out_buffer[4] = 0; // This byte should be cleared before setting values control_out_buffer[4] = 0; // This byte should be cleared before setting values
out_data->display_status = this->display_status_ ? 1 : 0; out_data->display_status = this->display_status_ ? 1 : 0;
out_data->health_mode = this->health_mode_ ? 1 : 0; out_data->health_mode = this->health_mode_ ? 1 : 0;
switch (this->action_request_) { return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
case ActionRequest::START_SELF_CLEAN:
this->action_request_ = ActionRequest::NO_ACTION;
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;
break;
case ActionRequest::START_STERI_CLEAN:
this->action_request_ = ActionRequest::NO_ACTION;
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;
break;
default:
// No change
break;
}
return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
} }
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
if (size < sizeof(hon_protocol::HaierStatus)) if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_)
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
hon_protocol::HaierStatus packet; struct {
if (size < sizeof(hon_protocol::HaierStatus)) hon_protocol::HaierPacketControl control;
size = sizeof(hon_protocol::HaierStatus); hon_protocol::HaierPacketSensors sensors;
memcpy(&packet, packet_buffer, size); } 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_,
sizeof(hon_protocol::HaierPacketSensors));
if (packet.sensors.error_status != 0) { if (packet.sensors.error_status != 0) {
ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
} }
if ((this->outdoor_sensor_ != nullptr) && (got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { if ((this->outdoor_sensor_ != nullptr) &&
got_valid_outdoor_temp_ = true; (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
this->got_valid_outdoor_temp_ = true;
float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET);
if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp))
this->outdoor_sensor_->publish_state(otemp); this->outdoor_sensor_->publish_state(otemp);
@ -703,7 +684,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
// Do something only if display status changed // Do something only if display status changed
if (this->mode == CLIMATE_MODE_OFF) { if (this->mode == CLIMATE_MODE_OFF) {
// AC just turned on from remote need to turn off display // AC just turned on from remote need to turn off display
this->set_force_send_control_(true); this->force_send_control_ = true;
} else { } else {
this->display_status_ = disp_status; this->display_status_ = disp_status;
} }
@ -732,7 +713,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
if (new_cleaning == CleaningState::NO_CLEANING) { if (new_cleaning == CleaningState::NO_CLEANING) {
// Turning AC off after cleaning // Turning AC off after cleaning
this->action_request_ = ActionRequest::TURN_POWER_OFF; this->action_request_ =
PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
} }
this->cleaning_status_ = new_cleaning; this->cleaning_status_ = new_cleaning;
} }
@ -783,51 +765,257 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
should_publish = should_publish || (old_swing_mode != this->swing_mode); should_publish = should_publish || (old_swing_mode != this->swing_mode);
} }
this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
if (this->forced_publish_ || should_publish) { if (should_publish) {
#if (HAIER_LOG_LEVEL > 4)
std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now();
#endif
this->publish_state(); this->publish_state();
#if (HAIER_LOG_LEVEL > 4)
ESP_LOGV(TAG, "Publish delay: %lld ms",
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() -
_publish_start)
.count());
#endif
this->forced_publish_ = false;
} }
if (should_publish) { if (should_publish) {
ESP_LOGI(TAG, "HVAC values changed"); ESP_LOGI(TAG, "HVAC values changed");
} }
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
"HVAC Mode = 0x%X", packet.control.ac_mode); esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
"Fan speed Status = 0x%X", packet.control.fan_mode); esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
"Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode); esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__,
"Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__,
"Set Point Status = 0x%X", packet.control.set_point);
return haier_protocol::HandlerError::HANDLER_OK; return haier_protocol::HandlerError::HANDLER_OK;
} }
bool HonClimate::is_message_invalid(uint8_t message_type) { void HonClimate::fill_control_messages_queue_() {
return message_type == (uint8_t) hon_protocol::FrameType::INVALID; 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_();
HvacSettings climate_control;
climate_control = this->current_hvac_settings_;
// Beeper command
{
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::BEEPER_STATUS,
this->beeper_status_ ? zero_buf : one_buf, 2));
}
// Health mode
{
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::HEALTH_MODE,
this->health_mode_ ? one_buf : zero_buf, 2));
}
// Climate mode
bool new_power = this->mode != CLIMATE_MODE_OFF;
uint8_t fan_mode_buf[] = {0x00, 0xFF};
uint8_t quiet_mode_buf[] = {0x00, 0xFF};
if (climate_control.mode.has_value()) {
uint8_t buffer[2] = {0x00, 0x00};
switch (climate_control.mode.value()) {
case CLIMATE_MODE_OFF:
new_power = false;
break;
case CLIMATE_MODE_HEAT_COOL:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
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::AC_MODE,
buffer, 2));
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
case CLIMATE_MODE_HEAT:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
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::AC_MODE,
buffer, 2));
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
case CLIMATE_MODE_DRY:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
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::AC_MODE,
buffer, 2));
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
case CLIMATE_MODE_FAN_ONLY:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
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::AC_MODE,
buffer, 2));
fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
// Disabling eco mode for Fan only
quiet_mode_buf[1] = 0;
break;
case CLIMATE_MODE_COOL:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
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::AC_MODE,
buffer, 2));
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
default:
ESP_LOGE("Control", "Unsupported climate mode");
break;
}
}
// Climate power
{
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::AC_POWER,
new_power ? one_buf : zero_buf, 2));
}
// CLimate preset
{
uint8_t fast_mode_buf[] = {0x00, 0xFF};
if (!new_power) {
// If AC is off - no presets allowed
quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00;
} else if (climate_control.preset.has_value()) {
switch (climate_control.preset.value()) {
case CLIMATE_PRESET_NONE:
quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00;
break;
case CLIMATE_PRESET_ECO:
// Eco is not supported in Fan only mode
quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
fast_mode_buf[1] = 0x00;
break;
case CLIMATE_PRESET_BOOST:
quiet_mode_buf[1] = 0x00;
// Boost is not supported in Fan only mode
fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
break;
default:
ESP_LOGE("Control", "Unsupported preset");
break;
}
}
if (quiet_mode_buf[1] != 0xFF) {
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::QUIET_MODE,
quiet_mode_buf, 2));
}
if (fast_mode_buf[1] != 0xFF) {
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::FAST_MODE,
fast_mode_buf, 2));
}
}
// Target temperature
if (climate_control.target_temperature.has_value()) {
uint8_t buffer[2] = {0x00, 0x00};
buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
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::SET_POINT,
buffer, 2));
}
// Fan mode
if (climate_control.fan_mode.has_value()) {
switch (climate_control.fan_mode.value()) {
case CLIMATE_FAN_LOW:
fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
break;
case CLIMATE_FAN_MEDIUM:
fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
break;
case CLIMATE_FAN_HIGH:
fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
break;
case CLIMATE_FAN_AUTO:
if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
break;
default:
ESP_LOGE("Control", "Unsupported fan mode");
break;
}
if (fan_mode_buf[1] != 0xFF) {
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::FAN_MODE,
fan_mode_buf, 2));
}
}
} }
void HonClimate::process_pending_action() { void HonClimate::clear_control_messages_queue_() {
switch (this->action_request_) { while (!this->control_messages_queue_.empty())
case ActionRequest::START_SELF_CLEAN: this->control_messages_queue_.pop();
case ActionRequest::START_STERI_CLEAN: }
// Will reset action with control message sending
this->set_phase(ProtocolPhases::SENDING_CONTROL); bool HonClimate::prepare_pending_action() {
break; 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;
default: default:
HaierClimateBase::process_pending_action(); return HaierClimateBase::prepare_pending_action();
break;
} }
} }
void HonClimate::process_protocol_reset() {
HaierClimateBase::process_protocol_reset();
if (this->outdoor_sensor_ != nullptr) {
this->outdoor_sensor_->publish_state(NAN);
}
this->got_valid_outdoor_temp_ = false;
this->hvac_hardware_info_.reset();
}
} // namespace haier } // namespace haier
} // namespace esphome } // namespace esphome

View file

@ -30,6 +30,8 @@ enum class CleaningState : uint8_t {
STERI_CLEAN = 2, STERI_CLEAN = 2,
}; };
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
class HonClimate : public HaierClimateBase { class HonClimate : public HaierClimateBase {
public: public:
HonClimate(); HonClimate();
@ -48,44 +50,57 @@ class HonClimate : public HaierClimateBase {
CleaningState get_cleaning_status() const; CleaningState get_cleaning_status() const;
void start_self_cleaning(); void start_self_cleaning();
void start_steri_cleaning(); void start_steri_cleaning();
void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; };
void set_control_method(HonControlMethod method) { this->control_method_ = method; };
protected: protected:
void set_handlers() override; void set_handlers() override;
void process_phase(std::chrono::steady_clock::time_point now) override; void process_phase(std::chrono::steady_clock::time_point now) override;
haier_protocol::HaierMessage get_control_message() override; haier_protocol::HaierMessage get_control_message() override;
bool is_message_invalid(uint8_t message_type) override; haier_protocol::HaierMessage get_power_message(bool state) override;
void process_pending_action() override; bool prepare_pending_action() override;
void process_protocol_reset() override;
// Answers handlers // Answers handlers
haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size); const uint8_t *data, size_t data_size);
haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size); const uint8_t *data, size_t data_size);
haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type, const uint8_t *data,
size_t data_size); size_t data_size);
haier_protocol::HandlerError get_management_information_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError get_management_information_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size); const uint8_t *data, size_t data_size);
haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
const uint8_t *data, size_t data_size); haier_protocol::FrameType message_type,
haier_protocol::HandlerError get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type,
const uint8_t *data, size_t data_size); const uint8_t *data, size_t data_size);
// Helper functions // Helper functions
haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size);
std::unique_ptr<uint8_t[]> last_status_message_; void fill_control_messages_queue_();
void clear_control_messages_queue_();
struct HardwareInfo {
std::string protocol_version_;
std::string software_version_;
std::string hardware_version_;
std::string device_name_;
bool functions_[5];
};
bool beeper_status_; bool beeper_status_;
CleaningState cleaning_status_; CleaningState cleaning_status_;
bool got_valid_outdoor_temp_; bool got_valid_outdoor_temp_;
AirflowVerticalDirection vertical_direction_; AirflowVerticalDirection vertical_direction_;
AirflowHorizontalDirection horizontal_direction_; AirflowHorizontalDirection horizontal_direction_;
bool hvac_hardware_info_available_; esphome::optional<HardwareInfo> hvac_hardware_info_;
std::string hvac_protocol_version_;
std::string hvac_software_version_;
std::string hvac_hardware_version_;
std::string hvac_device_name_;
bool hvac_functions_[5];
bool &use_crc_;
uint8_t active_alarms_[8]; uint8_t active_alarms_[8];
int extra_control_packet_bytes_;
HonControlMethod control_method_;
esphome::sensor::Sensor *outdoor_sensor_; esphome::sensor::Sensor *outdoor_sensor_;
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
}; };
} // namespace haier } // namespace haier

View file

@ -35,6 +35,20 @@ enum class ConditioningMode : uint8_t {
FAN = 0x06 FAN = 0x06
}; };
enum class DataParameters : uint8_t {
AC_POWER = 0x01,
SET_POINT = 0x02,
AC_MODE = 0x04,
FAN_MODE = 0x05,
USE_FAHRENHEIT = 0x07,
TEN_DEGREE = 0x0A,
HEALTH_MODE = 0x0B,
BEEPER_STATUS = 0x16,
LOCK_REMOTE = 0x17,
QUIET_MODE = 0x19,
FAST_MODE = 0x1A,
};
enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 };
enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 }; enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 };
@ -124,11 +138,7 @@ struct HaierPacketSensors {
uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step) uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step)
}; };
struct HaierStatus { constexpr size_t HAIER_STATUS_FRAME_SIZE = 2 + sizeof(HaierPacketControl) + sizeof(HaierPacketSensors);
uint16_t subcommand;
HaierPacketControl control;
HaierPacketSensors sensors;
};
struct DeviceVersionAnswer { struct DeviceVersionAnswer {
char protocol_version[8]; char protocol_version[8];
@ -140,76 +150,6 @@ struct DeviceVersionAnswer {
uint8_t functions[2]; uint8_t functions[2];
}; };
// In this section comments:
// - module is the ESP32 control module (communication module in Haier protocol document)
// - device is the conditioner control board (network appliances in Haier protocol document)
enum class FrameType : uint8_t {
CONTROL = 0x01, // Requests or sets one or multiple parameters (module <-> device, required)
STATUS = 0x02, // Contains one or multiple parameters values, usually answer to control frame (module <-> device,
// required)
INVALID = 0x03, // Communication error indication (module <-> device, required)
ALARM_STATUS = 0x04, // Alarm status report (module <-> device, interactive, required)
CONFIRM = 0x05, // Acknowledgment, usually used to confirm reception of frame if there is no special answer (module
// <-> device, required)
REPORT = 0x06, // Report frame (module <-> device, interactive, required)
STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required)
SYSTEM_DOWNLINK = 0x11, // System downlink frame (module -> device, optional)
DEVICE_UPLINK = 0x12, // Device uplink frame (module <- device , interactive, optional)
SYSTEM_QUERY = 0x13, // System query frame (module -> device, optional)
SYSTEM_QUERY_RESPONSE = 0x14, // System query response frame (module <- device , optional)
DEVICE_QUERY = 0x15, // Device query frame (module <- device, optional)
DEVICE_QUERY_RESPONSE = 0x16, // Device query response frame (module -> device, optional)
GROUP_COMMAND = 0x60, // Group command frame (module -> device, interactive, optional)
GET_DEVICE_VERSION = 0x61, // Requests device version (module -> device, required)
GET_DEVICE_VERSION_RESPONSE = 0x62, // Device version answer (module <- device, required_
GET_ALL_ADDRESSES = 0x67, // Requests all devices addresses (module -> device, interactive, optional)
GET_ALL_ADDRESSES_RESPONSE =
0x68, // Answer to request of all devices addresses (module <- device , interactive, optional)
HANDSET_CHANGE_NOTIFICATION = 0x69, // Handset change notification frame (module <- device , interactive, optional)
GET_DEVICE_ID = 0x70, // Requests Device ID (module -> device, required)
GET_DEVICE_ID_RESPONSE = 0x71, // Response to device ID request (module <- device , required)
GET_ALARM_STATUS = 0x73, // Alarm status request (module -> device, required)
GET_ALARM_STATUS_RESPONSE = 0x74, // Response to alarm status request (module <- device, required)
GET_DEVICE_CONFIGURATION = 0x7C, // Requests device configuration (module -> device, interactive, required)
GET_DEVICE_CONFIGURATION_RESPONSE =
0x7D, // Response to device configuration request (module <- device, interactive, required)
DOWNLINK_TRANSPARENT_TRANSMISSION = 0x8C, // Downlink transparent transmission (proxy data Haier cloud -> device)
// (module -> device, interactive, optional)
UPLINK_TRANSPARENT_TRANSMISSION = 0x8D, // Uplink transparent transmission (proxy data device -> Haier cloud) (module
// <- device, interactive, optional)
START_DEVICE_UPGRADE = 0xE1, // Initiate device OTA upgrade (module -> device, OTA required)
START_DEVICE_UPGRADE_RESPONSE = 0xE2, // Response to initiate device upgrade command (module <- device, OTA required)
GET_FIRMWARE_CONTENT = 0xE5, // Requests to send firmware (module <- device, OTA required)
GET_FIRMWARE_CONTENT_RESPONSE =
0xE6, // Response to send firmware request (module -> device, OTA required) (multipacket?)
CHANGE_BAUD_RATE = 0xE7, // Requests to change port baud rate (module <- device, OTA required)
CHANGE_BAUD_RATE_RESPONSE = 0xE8, // Response to change port baud rate request (module -> device, OTA required)
GET_SUBBOARD_INFO = 0xE9, // Requests subboard information (module -> device, required)
GET_SUBBOARD_INFO_RESPONSE = 0xEA, // Response to subboard information request (module <- device, required)
GET_HARDWARE_INFO = 0xEB, // Requests information about device and subboard (module -> device, required)
GET_HARDWARE_INFO_RESPONSE = 0xEC, // Response to hardware information request (module <- device, required)
GET_UPGRADE_RESULT = 0xED, // Requests result of the firmware update (module <- device, OTA required)
GET_UPGRADE_RESULT_RESPONSE = 0xEF, // Response to firmware update results request (module -> device, OTA required)
GET_NETWORK_STATUS = 0xF0, // Requests network status (module <- device, interactive, optional)
GET_NETWORK_STATUS_RESPONSE = 0xF1, // Response to network status request (module -> device, interactive, optional)
START_WIFI_CONFIGURATION = 0xF2, // Starts WiFi configuration procedure (module <- device, interactive, required)
START_WIFI_CONFIGURATION_RESPONSE =
0xF3, // Response to start WiFi configuration request (module -> device, interactive, required)
STOP_WIFI_CONFIGURATION = 0xF4, // Stop WiFi configuration procedure (module <- device, interactive, required)
STOP_WIFI_CONFIGURATION_RESPONSE =
0xF5, // Response to stop WiFi configuration request (module -> device, interactive, required)
REPORT_NETWORK_STATUS = 0xF7, // Reports network status (module -> device, required)
CLEAR_CONFIGURATION = 0xF8, // Request to clear module configuration (module <- device, interactive, optional)
BIG_DATA_REPORT_CONFIGURATION =
0xFA, // Configuration for autoreport device full status (module -> device, interactive, optional)
BIG_DATA_REPORT_CONFIGURATION_RESPONSE =
0xFB, // Response to set big data configuration (module <- device, interactive, optional)
GET_MANAGEMENT_INFORMATION = 0xFC, // Request management information from device (module -> device, required)
GET_MANAGEMENT_INFORMATION_RESPONSE =
0xFD, // Response to management information request (module <- device, required)
WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional)
};
enum class SubcommandsControl : uint16_t { enum class SubcommandsControl : uint16_t {
GET_PARAMETERS = 0x4C01, // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) GET_PARAMETERS = 0x4C01, // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...)
GET_USER_DATA = 0x4D01, // Request all user data from device (packet content: None) GET_USER_DATA = 0x4D01, // Request all user data from device (packet content: None)

View file

@ -12,21 +12,28 @@ namespace haier {
static const char *const TAG = "haier.climate"; static const char *const TAG = "haier.climate";
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
constexpr uint8_t INIT_REQUESTS_RETRY = 2;
constexpr std::chrono::milliseconds INIT_REQUESTS_RETRY_INTERVAL = std::chrono::milliseconds(2000);
Smartair2Climate::Smartair2Climate() Smartair2Climate::Smartair2Climate() {
: last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]), timeouts_counter_(0) {} last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]);
}
haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size) { const uint8_t *data, size_t data_size) {
haier_protocol::HandlerError result = haier_protocol::HandlerError result =
this->answer_preprocess_(request_type, (uint8_t) smartair2_protocol::FrameType::CONTROL, message_type, this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
(uint8_t) smartair2_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
if (result == haier_protocol::HandlerError::HANDLER_OK) { if (result == haier_protocol::HandlerError::HANDLER_OK) {
result = this->process_status_message_(data, data_size); result = this->process_status_message_(data, data_size);
if (result != haier_protocol::HandlerError::HANDLER_OK) { if (result != haier_protocol::HandlerError::HANDLER_OK) {
ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE this->reset_phase_();
: ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); this->action_request_.reset();
this->force_send_control_ = false;
} else { } else {
if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) {
memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl));
@ -34,36 +41,45 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
sizeof(smartair2_protocol::HaierPacketControl)); sizeof(smartair2_protocol::HaierPacketControl));
} }
if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { switch (this->protocol_phase_) {
ESP_LOGI(TAG, "First HVAC status received"); case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
this->set_phase(ProtocolPhases::IDLE); ESP_LOGI(TAG, "First HVAC status received");
} else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) { this->set_phase(ProtocolPhases::IDLE);
this->set_phase(ProtocolPhases::IDLE); break;
} else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { case ProtocolPhases::SENDING_ACTION_COMMAND:
this->set_phase(ProtocolPhases::IDLE); // Do nothing, phase will be changed in process_phase
this->set_force_send_control_(false); break;
if (this->hvac_settings_.valid) case ProtocolPhases::SENDING_STATUS_REQUEST:
this->hvac_settings_.reset(); this->set_phase(ProtocolPhases::IDLE);
break;
case ProtocolPhases::SENDING_CONTROL:
this->set_phase(ProtocolPhases::IDLE);
this->force_send_control_ = false;
if (this->current_hvac_settings_.valid)
this->current_hvac_settings_.reset();
break;
default:
break;
} }
} }
return result; return result;
} else { } else {
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE this->action_request_.reset();
: ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); this->force_send_control_ = false;
this->reset_phase_();
return result; return result;
} }
} }
haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(uint8_t request_type, haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(
uint8_t message_type, haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
const uint8_t *data, size_t data_size) {
size_t data_size) { if (request_type != haier_protocol::FrameType::GET_DEVICE_VERSION)
if (request_type != (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION)
return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
if (ProtocolPhases::WAITING_INIT_1_ANSWER != this->protocol_phase_) if (ProtocolPhases::SENDING_INIT_1 != this->protocol_phase_)
return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
// Invalid packet is expected answer // Invalid packet is expected answer
if ((message_type == (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && if ((message_type == haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) &&
((data[37] & 0x04) != 0)) { ((data[37] & 0x04) != 0)) {
ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol "
"instead of smartAir2"); "instead of smartAir2");
@ -72,58 +88,35 @@ haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler
return haier_protocol::HandlerError::HANDLER_OK; return haier_protocol::HandlerError::HANDLER_OK;
} }
haier_protocol::HandlerError Smartair2Climate::report_network_status_answer_handler_(uint8_t request_type, haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cycle_for_init_(
uint8_t message_type, haier_protocol::FrameType message_type) {
const uint8_t *data,
size_t data_size) {
haier_protocol::HandlerError result = this->answer_preprocess_(
request_type, (uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS, message_type,
(uint8_t) smartair2_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
this->set_phase(ProtocolPhases::IDLE);
return result;
}
haier_protocol::HandlerError Smartair2Climate::initial_messages_timeout_handler_(uint8_t message_type) {
if (this->protocol_phase_ >= ProtocolPhases::IDLE) if (this->protocol_phase_ >= ProtocolPhases::IDLE)
return HaierClimateBase::timeout_default_handler_(message_type); return HaierClimateBase::timeout_default_handler_(message_type);
this->timeouts_counter_++; ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type,
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %d, timeout counter %d", message_type, phase_to_string_(this->protocol_phase_));
(int) this->protocol_phase_, this->timeouts_counter_); ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
if (this->timeouts_counter_ >= 3) { if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST)
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); new_phase = ProtocolPhases::SENDING_INIT_1;
if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) this->set_phase(new_phase);
new_phase = ProtocolPhases::SENDING_INIT_1;
this->set_phase(new_phase);
} else {
// Returning to the previous state to try again
this->set_phase((ProtocolPhases) ((int) this->protocol_phase_ - 1));
}
return haier_protocol::HandlerError::HANDLER_OK; return haier_protocol::HandlerError::HANDLER_OK;
} }
void Smartair2Climate::set_handlers() { void Smartair2Climate::set_handlers() {
// Set handlers // Set handlers
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), haier_protocol::FrameType::GET_DEVICE_VERSION,
std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (smartair2_protocol::FrameType::CONTROL), haier_protocol::FrameType::CONTROL,
std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
(uint8_t) (smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), haier_protocol::FrameType::REPORT_NETWORK_STATUS,
std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
this->haier_protocol_.set_timeout_handler( this->haier_protocol_.set_default_timeout_handler(
(uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_ID), std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_, this, std::placeholders::_1));
std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1));
this->haier_protocol_.set_timeout_handler(
(uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION),
std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1));
this->haier_protocol_.set_timeout_handler(
(uint8_t) (smartair2_protocol::FrameType::CONTROL),
std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1));
} }
void Smartair2Climate::dump_config() { void Smartair2Climate::dump_config() {
@ -134,9 +127,7 @@ void Smartair2Climate::dump_config() {
void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) {
switch (this->protocol_phase_) { switch (this->protocol_phase_) {
case ProtocolPhases::SENDING_INIT_1: case ProtocolPhases::SENDING_INIT_1:
if (this->can_send_message() && if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) {
(((this->timeouts_counter_ == 0) && (this->is_protocol_initialisation_interval_exceeded_(now))) ||
((this->timeouts_counter_ > 0) && (this->is_message_interval_exceeded_(now))))) {
// Indicate device capabilities: // Indicate device capabilities:
// bit 0 - if 1 module support interactive mode // bit 0 - if 1 module support interactive mode
// bit 1 - if 1 module support controller-device mode // bit 1 - if 1 module support controller-device mode
@ -145,92 +136,65 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
// bit 4..bit 15 - not used // bit 4..bit 15 - not used
uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
(uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
sizeof(module_capabilities)); this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL);
this->send_message_(DEVICE_VERSION_REQUEST, false);
this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_INIT_2: case ProtocolPhases::SENDING_INIT_2:
case ProtocolPhases::WAITING_INIT_2_ANSWER:
this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
break; break;
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
case ProtocolPhases::SENDING_STATUS_REQUEST: case ProtocolPhases::SENDING_STATUS_REQUEST:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, static const haier_protocol::HaierMessage STATUS_REQUEST(haier_protocol::FrameType::CONTROL, 0x4D01);
0x4D01); if (this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) {
this->send_message_(STATUS_REQUEST, false); this->send_message_(STATUS_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL);
} else {
this->send_message_(STATUS_REQUEST, this->use_crc_);
}
this->last_status_request_ = now; this->last_status_request_ = now;
this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1));
} }
break; break;
#ifdef USE_WIFI #ifdef USE_WIFI
case ProtocolPhases::SENDING_SIGNAL_LEVEL: case ProtocolPhases::SENDING_SIGNAL_LEVEL:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
this->send_message_( this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
this->get_wifi_signal_message_((uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), false);
this->last_signal_request_ = now; this->last_signal_request_ = now;
this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
} }
break; break;
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
break;
#else #else
case ProtocolPhases::SENDING_SIGNAL_LEVEL: case ProtocolPhases::SENDING_SIGNAL_LEVEL:
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
break; break;
#endif #endif
case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER:
this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL);
break; break;
case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER:
this->set_phase(ProtocolPhases::SENDING_INIT_1); this->set_phase(ProtocolPhases::SENDING_INIT_1);
break; break;
case ProtocolPhases::SENDING_CONTROL: case ProtocolPhases::SENDING_CONTROL:
if (this->first_control_attempt_) { if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
this->control_request_timestamp_ = now; ESP_LOGI(TAG, "Sending control packet");
this->first_control_attempt_ = false; this->send_message_(get_control_message(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
CONTROL_MESSAGE_RETRIES_INTERVAL);
} }
if (this->is_control_message_timeout_exceeded_(now)) { break;
ESP_LOGW(TAG, "Sending control packet timeout!"); case ProtocolPhases::SENDING_ACTION_COMMAND:
this->set_force_send_control_(false); if (this->action_request_.has_value()) {
if (this->hvac_settings_.valid) if (this->action_request_.value().message.has_value()) {
this->hvac_settings_.reset(); this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
this->forced_request_status_ = true; this->action_request_.value().message.reset();
this->forced_publish_ = true; } else {
// Message already sent, reseting request and return to idle
this->action_request_.reset();
this->set_phase(ProtocolPhases::IDLE);
}
} else {
ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
} else if (this->can_send_message() && this->is_control_message_interval_exceeded_(
now)) // Using CONTROL_MESSAGES_INTERVAL_MS to speedup requests
{
haier_protocol::HaierMessage control_message = get_control_message();
this->send_message_(control_message, false);
ESP_LOGI(TAG, "Control packet sent");
this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER);
} }
break; break;
case ProtocolPhases::SENDING_POWER_ON_COMMAND:
case ProtocolPhases::SENDING_POWER_OFF_COMMAND:
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
haier_protocol::HaierMessage power_cmd(
(uint8_t) smartair2_protocol::FrameType::CONTROL,
this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND ? 0x4D02 : 0x4D03);
this->send_message_(power_cmd, false);
this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND
? ProtocolPhases::WAITING_POWER_ON_ANSWER
: ProtocolPhases::WAITING_POWER_OFF_ANSWER);
}
break;
case ProtocolPhases::WAITING_INIT_1_ANSWER:
case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER:
case ProtocolPhases::WAITING_STATUS_ANSWER:
case ProtocolPhases::WAITING_CONTROL_ANSWER:
case ProtocolPhases::WAITING_POWER_ON_ANSWER:
case ProtocolPhases::WAITING_POWER_OFF_ANSWER:
break;
case ProtocolPhases::IDLE: { case ProtocolPhases::IDLE: {
if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) {
this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST);
@ -245,55 +209,55 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
} break; } break;
default: default:
// Shouldn't get here // Shouldn't get here
#if (HAIER_LOG_LEVEL > 4)
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
#else
ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_);
#endif
this->set_phase(ProtocolPhases::SENDING_INIT_1); this->set_phase(ProtocolPhases::SENDING_INIT_1);
break; break;
} }
} }
haier_protocol::HaierMessage Smartair2Climate::get_power_message(bool state) {
if (state) {
static haier_protocol::HaierMessage power_on_message(haier_protocol::FrameType::CONTROL, 0x4D02);
return power_on_message;
} else {
static haier_protocol::HaierMessage power_off_message(haier_protocol::FrameType::CONTROL, 0x4D03);
return power_off_message;
}
}
haier_protocol::HaierMessage Smartair2Climate::get_control_message() { haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
uint8_t control_out_buffer[sizeof(smartair2_protocol::HaierPacketControl)]; uint8_t control_out_buffer[sizeof(smartair2_protocol::HaierPacketControl)];
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(smartair2_protocol::HaierPacketControl)); memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(smartair2_protocol::HaierPacketControl));
smartair2_protocol::HaierPacketControl *out_data = (smartair2_protocol::HaierPacketControl *) control_out_buffer; smartair2_protocol::HaierPacketControl *out_data = (smartair2_protocol::HaierPacketControl *) control_out_buffer;
out_data->cntrl = 0; out_data->cntrl = 0;
if (this->hvac_settings_.valid) { if (this->current_hvac_settings_.valid) {
HvacSettings climate_control; HvacSettings &climate_control = this->current_hvac_settings_;
climate_control = this->hvac_settings_;
if (climate_control.mode.has_value()) { if (climate_control.mode.has_value()) {
switch (climate_control.mode.value()) { switch (climate_control.mode.value()) {
case CLIMATE_MODE_OFF: case CLIMATE_MODE_OFF:
out_data->ac_power = 0; out_data->ac_power = 0;
break; break;
case CLIMATE_MODE_HEAT_COOL: case CLIMATE_MODE_HEAT_COOL:
out_data->ac_power = 1; out_data->ac_power = 1;
out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO;
out_data->fan_mode = this->other_modes_fan_speed_; out_data->fan_mode = this->other_modes_fan_speed_;
break; break;
case CLIMATE_MODE_HEAT: case CLIMATE_MODE_HEAT:
out_data->ac_power = 1; out_data->ac_power = 1;
out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT;
out_data->fan_mode = this->other_modes_fan_speed_; out_data->fan_mode = this->other_modes_fan_speed_;
break; break;
case CLIMATE_MODE_DRY: case CLIMATE_MODE_DRY:
out_data->ac_power = 1; out_data->ac_power = 1;
out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY;
out_data->fan_mode = this->other_modes_fan_speed_; out_data->fan_mode = this->other_modes_fan_speed_;
break; break;
case CLIMATE_MODE_FAN_ONLY: case CLIMATE_MODE_FAN_ONLY:
out_data->ac_power = 1; out_data->ac_power = 1;
out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN;
out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
break; break;
case CLIMATE_MODE_COOL: case CLIMATE_MODE_COOL:
out_data->ac_power = 1; out_data->ac_power = 1;
out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL; out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL;
@ -327,32 +291,49 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
} }
// Set swing mode // Set swing mode
if (climate_control.swing_mode.has_value()) { if (climate_control.swing_mode.has_value()) {
switch (climate_control.swing_mode.value()) { if (this->use_alternative_swing_control_) {
case CLIMATE_SWING_OFF: switch (climate_control.swing_mode.value()) {
out_data->use_swing_bits = 0; case CLIMATE_SWING_OFF:
out_data->swing_both = 0; out_data->swing_mode = 0;
break; break;
case CLIMATE_SWING_VERTICAL: case CLIMATE_SWING_VERTICAL:
out_data->swing_both = 0; out_data->swing_mode = 1;
out_data->vertical_swing = 1; break;
out_data->horizontal_swing = 0; case CLIMATE_SWING_HORIZONTAL:
break; out_data->swing_mode = 2;
case CLIMATE_SWING_HORIZONTAL: break;
out_data->swing_both = 0; case CLIMATE_SWING_BOTH:
out_data->vertical_swing = 0; out_data->swing_mode = 3;
out_data->horizontal_swing = 1; break;
break; }
case CLIMATE_SWING_BOTH: } else {
out_data->swing_both = 1; switch (climate_control.swing_mode.value()) {
out_data->use_swing_bits = 0; case CLIMATE_SWING_OFF:
out_data->vertical_swing = 0; out_data->use_swing_bits = 0;
out_data->horizontal_swing = 0; out_data->swing_mode = 0;
break; break;
case CLIMATE_SWING_VERTICAL:
out_data->swing_mode = 0;
out_data->vertical_swing = 1;
out_data->horizontal_swing = 0;
break;
case CLIMATE_SWING_HORIZONTAL:
out_data->swing_mode = 0;
out_data->vertical_swing = 0;
out_data->horizontal_swing = 1;
break;
case CLIMATE_SWING_BOTH:
out_data->swing_mode = 1;
out_data->use_swing_bits = 0;
out_data->vertical_swing = 0;
out_data->horizontal_swing = 0;
break;
}
} }
} }
if (climate_control.target_temperature.has_value()) { if (climate_control.target_temperature.has_value()) {
float target_temp = climate_control.target_temperature.value(); float target_temp = climate_control.target_temperature.value();
out_data->set_point = target_temp - 16; // set the temperature with offset 16 out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
} }
if (out_data->ac_power == 0) { if (out_data->ac_power == 0) {
@ -383,7 +364,7 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
} }
out_data->display_status = this->display_status_ ? 0 : 1; out_data->display_status = this->display_status_ ? 0 : 1;
out_data->health_mode = this->health_mode_ ? 1 : 0; out_data->health_mode = this->health_mode_ ? 1 : 0;
return haier_protocol::HaierMessage((uint8_t) smartair2_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer,
sizeof(smartair2_protocol::HaierPacketControl)); sizeof(smartair2_protocol::HaierPacketControl));
} }
@ -459,13 +440,19 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
// Do something only if display status changed // Do something only if display status changed
if (this->mode == CLIMATE_MODE_OFF) { if (this->mode == CLIMATE_MODE_OFF) {
// AC just turned on from remote need to turn off display // AC just turned on from remote need to turn off display
this->set_force_send_control_(true); this->force_send_control_ = true;
} else { } else {
this->display_status_ = disp_status; this->display_status_ = disp_status;
} }
} }
} }
} }
{
// Health mode
bool old_health_mode = this->health_mode_;
this->health_mode_ = packet.control.health_mode == 1;
should_publish = should_publish || (old_health_mode != this->health_mode_);
}
{ {
// Climate mode // Climate mode
ClimateMode old_mode = this->mode; ClimateMode old_mode = this->mode;
@ -493,70 +480,57 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
} }
should_publish = should_publish || (old_mode != this->mode); should_publish = should_publish || (old_mode != this->mode);
} }
{
// Health mode
bool old_health_mode = this->health_mode_;
this->health_mode_ = packet.control.health_mode == 1;
should_publish = should_publish || (old_health_mode != this->health_mode_);
}
{ {
// Swing mode // Swing mode
ClimateSwingMode old_swing_mode = this->swing_mode; ClimateSwingMode old_swing_mode = this->swing_mode;
if (packet.control.swing_both == 0) { if (this->use_alternative_swing_control_) {
if (packet.control.vertical_swing != 0) { switch (packet.control.swing_mode) {
this->swing_mode = CLIMATE_SWING_VERTICAL; case 1:
} else if (packet.control.horizontal_swing != 0) { this->swing_mode = CLIMATE_SWING_VERTICAL;
this->swing_mode = CLIMATE_SWING_HORIZONTAL; break;
} else { case 2:
this->swing_mode = CLIMATE_SWING_OFF; this->swing_mode = CLIMATE_SWING_HORIZONTAL;
break;
case 3:
this->swing_mode = CLIMATE_SWING_BOTH;
break;
default:
this->swing_mode = CLIMATE_SWING_OFF;
break;
} }
} else { } else {
swing_mode = CLIMATE_SWING_BOTH; if (packet.control.swing_mode == 0) {
if (packet.control.vertical_swing != 0) {
this->swing_mode = CLIMATE_SWING_VERTICAL;
} else if (packet.control.horizontal_swing != 0) {
this->swing_mode = CLIMATE_SWING_HORIZONTAL;
} else {
this->swing_mode = CLIMATE_SWING_OFF;
}
} else {
swing_mode = CLIMATE_SWING_BOTH;
}
} }
should_publish = should_publish || (old_swing_mode != this->swing_mode); should_publish = should_publish || (old_swing_mode != this->swing_mode);
} }
this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
if (this->forced_publish_ || should_publish) { if (should_publish) {
#if (HAIER_LOG_LEVEL > 4)
std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now();
#endif
this->publish_state(); this->publish_state();
#if (HAIER_LOG_LEVEL > 4)
ESP_LOGV(TAG, "Publish delay: %lld ms",
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() -
_publish_start)
.count());
#endif
this->forced_publish_ = false;
} }
if (should_publish) { if (should_publish) {
ESP_LOGI(TAG, "HVAC values changed"); ESP_LOGI(TAG, "HVAC values changed");
} }
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
"HVAC Mode = 0x%X", packet.control.ac_mode); esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
"Fan speed Status = 0x%X", packet.control.fan_mode); esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing);
"Horizontal Swing Status = 0x%X", packet.control.horizontal_swing); esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__,
"Vertical Swing Status = 0x%X", packet.control.vertical_swing);
esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__,
"Set Point Status = 0x%X", packet.control.set_point);
return haier_protocol::HandlerError::HANDLER_OK; return haier_protocol::HandlerError::HANDLER_OK;
} }
bool Smartair2Climate::is_message_invalid(uint8_t message_type) { void Smartair2Climate::set_alternative_swing_control(bool swing_control) {
return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID; this->use_alternative_swing_control_ = swing_control;
}
void Smartair2Climate::set_phase(HaierClimateBase::ProtocolPhases phase) {
int old_phase = (int) this->protocol_phase_;
int new_phase = (int) phase;
int min_p = std::min(old_phase, new_phase);
int max_p = std::max(old_phase, new_phase);
if ((min_p % 2 != 0) || (max_p - min_p > 1))
this->timeouts_counter_ = 0;
HaierClimateBase::set_phase(phase);
} }
} // namespace haier } // namespace haier

View file

@ -13,27 +13,27 @@ class Smartair2Climate : public HaierClimateBase {
Smartair2Climate &operator=(const Smartair2Climate &) = delete; Smartair2Climate &operator=(const Smartair2Climate &) = delete;
~Smartair2Climate(); ~Smartair2Climate();
void dump_config() override; void dump_config() override;
void set_alternative_swing_control(bool swing_control);
protected: protected:
void set_handlers() override; void set_handlers() override;
void process_phase(std::chrono::steady_clock::time_point now) override; void process_phase(std::chrono::steady_clock::time_point now) override;
haier_protocol::HaierMessage get_power_message(bool state) override;
haier_protocol::HaierMessage get_control_message() override; haier_protocol::HaierMessage get_control_message() override;
bool is_message_invalid(uint8_t message_type) override; // Answer handlers
void set_phase(HaierClimateBase::ProtocolPhases phase) override; haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type,
// Answer and timeout handlers haier_protocol::FrameType message_type, const uint8_t *data,
haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data,
size_t data_size); size_t data_size);
haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size); const uint8_t *data, size_t data_size);
haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size); const uint8_t *data, size_t data_size);
haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, haier_protocol::HandlerError messages_timeout_handler_with_cycle_for_init_(haier_protocol::FrameType message_type);
const uint8_t *data, size_t data_size);
haier_protocol::HandlerError initial_messages_timeout_handler_(uint8_t message_type);
// Helper functions // Helper functions
haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size);
std::unique_ptr<uint8_t[]> last_status_message_; bool use_alternative_swing_control_;
unsigned int timeouts_counter_;
}; };
} // namespace haier } // namespace haier

View file

@ -41,8 +41,9 @@ struct HaierPacketControl {
// 24 // 24
uint8_t : 8; uint8_t : 8;
// 25 // 25
uint8_t swing_both; // If 1 - swing both direction, if 0 - horizontal_swing and vertical_swing define uint8_t swing_mode; // In normal mode: If 1 - swing both direction, if 0 - horizontal_swing and
// vertical/horizontal/off // vertical_swing define vertical/horizontal/off
// In alternative mode: 0 - off, 01 - vertical, 02 - horizontal, 03 - both
// 26 // 26
uint8_t : 3; uint8_t : 3;
uint8_t use_fahrenheit : 1; uint8_t use_fahrenheit : 1;
@ -82,19 +83,6 @@ struct HaierStatus {
HaierPacketControl control; HaierPacketControl control;
}; };
enum class FrameType : uint8_t {
CONTROL = 0x01,
STATUS = 0x02,
INVALID = 0x03,
CONFIRM = 0x05,
GET_DEVICE_VERSION = 0x61,
GET_DEVICE_VERSION_RESPONSE = 0x62,
GET_DEVICE_ID = 0x70,
GET_DEVICE_ID_RESPONSE = 0x71,
REPORT_NETWORK_STATUS = 0xF7,
NO_COMMAND = 0xFF,
};
} // namespace smartair2_protocol } // namespace smartair2_protocol
} // namespace haier } // namespace haier
} // namespace esphome } // namespace esphome

View file

@ -39,7 +39,7 @@ lib_deps =
bblanchon/ArduinoJson@6.18.5 ; json bblanchon/ArduinoJson@6.18.5 ; json
wjtje/qr-code-generator-library@1.7.0 ; qr_code wjtje/qr-code-generator-library@1.7.0 ; qr_code
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
pavlodn/HaierProtocol@0.9.20 ; haier pavlodn/HaierProtocol@0.9.24 ; haier
; This is using the repository until a new release is published to PlatformIO ; 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 https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
build_flags = build_flags =