Added alarm processing for Haier component (hOn protocol) (#5965)

This commit is contained in:
Pavlo Dudnytskyi 2023-12-21 01:10:46 +01:00 committed by GitHub
parent c6a37da9da
commit b5932940ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 282 additions and 17 deletions

View File

@ -18,6 +18,7 @@ from esphome.const import (
CONF_SUPPORTED_SWING_MODES, CONF_SUPPORTED_SWING_MODES,
CONF_TARGET_TEMPERATURE, CONF_TARGET_TEMPERATURE,
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_WIFI, CONF_WIFI,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
@ -49,6 +50,8 @@ CONF_CONTROL_METHOD = "control_method"
CONF_CONTROL_PACKET_SIZE = "control_packet_size" CONF_CONTROL_PACKET_SIZE = "control_packet_size"
CONF_DISPLAY = "display" CONF_DISPLAY = "display"
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
CONF_ON_ALARM_START = "on_alarm_start"
CONF_ON_ALARM_END = "on_alarm_end"
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
CONF_VERTICAL_AIRFLOW = "vertical_airflow" CONF_VERTICAL_AIRFLOW = "vertical_airflow"
CONF_WIFI_SIGNAL = "wifi_signal" CONF_WIFI_SIGNAL = "wifi_signal"
@ -85,8 +88,8 @@ AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = {
} }
SUPPORTED_SWING_MODES_OPTIONS = { SUPPORTED_SWING_MODES_OPTIONS = {
"OFF": ClimateSwingMode.CLIMATE_SWING_OFF, # always available "OFF": ClimateSwingMode.CLIMATE_SWING_OFF,
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, # always available "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
} }
@ -101,13 +104,15 @@ SUPPORTED_CLIMATE_MODES_OPTIONS = {
} }
SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = {
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT, "COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT,
} }
SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
"ECO": ClimatePreset.CLIMATE_PRESET_ECO, "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
} }
@ -118,6 +123,16 @@ SUPPORTED_HON_CONTROL_METHODS = {
"SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER,
} }
HaierAlarmStartTrigger = haier_ns.class_(
"HaierAlarmStartTrigger",
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
)
HaierAlarmEndTrigger = haier_ns.class_(
"HaierAlarmEndTrigger",
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
)
def validate_visual(config): def validate_visual(config):
if CONF_VISUAL in config: if CONF_VISUAL in config:
@ -200,9 +215,7 @@ CONFIG_SCHEMA = cv.All(
): cv.boolean, ): cv.boolean,
cv.Optional( cv.Optional(
CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_PRESETS,
default=list( default=list(["BOOST", "COMFORT"]), # No AWAY by default
SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys()
),
): cv.ensure_list( ): cv.ensure_list(
cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True) cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
), ),
@ -222,7 +235,7 @@ CONFIG_SCHEMA = cv.All(
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), ): 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(["BOOST", "ECO", "SLEEP"]), # No AWAY by default
): cv.ensure_list( ): cv.ensure_list(
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
), ),
@ -233,6 +246,20 @@ CONFIG_SCHEMA = cv.All(
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_ON_ALARM_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
HaierAlarmStartTrigger
),
}
),
cv.Optional(CONF_ON_ALARM_END): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
HaierAlarmEndTrigger
),
}
),
} }
), ),
}, },
@ -457,5 +484,15 @@ async def to_code(config):
config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE
) )
) )
for conf in config.get(CONF_ON_ALARM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
)
for conf in config.get(CONF_ON_ALARM_END, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
)
# https://github.com/paveldn/HaierProtocol # https://github.com/paveldn/HaierProtocol
cg.add_library("pavlodn/HaierProtocol", "0.9.24") cg.add_library("pavlodn/HaierProtocol", "0.9.24")

View File

@ -25,13 +25,14 @@ const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
"SENDING_INIT_1", "SENDING_INIT_1",
"SENDING_INIT_2", "SENDING_INIT_2",
"SENDING_FIRST_STATUS_REQUEST", "SENDING_FIRST_STATUS_REQUEST",
"SENDING_ALARM_STATUS_REQUEST", "SENDING_FIRST_ALARM_STATUS_REQUEST",
"IDLE", "IDLE",
"SENDING_STATUS_REQUEST", "SENDING_STATUS_REQUEST",
"SENDING_UPDATE_SIGNAL_REQUEST", "SENDING_UPDATE_SIGNAL_REQUEST",
"SENDING_SIGNAL_LEVEL", "SENDING_SIGNAL_LEVEL",
"SENDING_CONTROL", "SENDING_CONTROL",
"SENDING_ACTION_COMMAND", "SENDING_ACTION_COMMAND",
"SENDING_ALARM_STATUS_REQUEST",
"UNKNOWN" // Should be the last! "UNKNOWN" // Should be the last!
}; };
static_assert( static_assert(

View File

@ -64,7 +64,7 @@ class HaierClimateBase : public esphome::Component,
SENDING_INIT_1 = 0, SENDING_INIT_1 = 0,
SENDING_INIT_2, SENDING_INIT_2,
SENDING_FIRST_STATUS_REQUEST, SENDING_FIRST_STATUS_REQUEST,
SENDING_ALARM_STATUS_REQUEST, SENDING_FIRST_ALARM_STATUS_REQUEST,
// FUNCTIONAL STATE // FUNCTIONAL STATE
IDLE, IDLE,
SENDING_STATUS_REQUEST, SENDING_STATUS_REQUEST,
@ -72,6 +72,7 @@ class HaierClimateBase : public esphome::Component,
SENDING_SIGNAL_LEVEL, SENDING_SIGNAL_LEVEL,
SENDING_CONTROL, SENDING_CONTROL,
SENDING_ACTION_COMMAND, SENDING_ACTION_COMMAND,
SENDING_ALARM_STATUS_REQUEST,
NUM_PROTOCOL_PHASES NUM_PROTOCOL_PHASES
}; };
const char *phase_to_string_(ProtocolPhases phase); const char *phase_to_string_(ProtocolPhases phase);

View File

@ -16,6 +16,7 @@ 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 uint8_t CONTROL_MESSAGE_RETRIES = 5;
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) {
switch (direction) { switch (direction) {
@ -110,6 +111,14 @@ void HonClimate::start_steri_cleaning() {
} }
} }
void HonClimate::add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback) {
this->alarm_start_callback_.add(std::move(callback));
}
void HonClimate::add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback) {
this->alarm_end_callback_.add(std::move(callback));
}
haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_type, haier_protocol::FrameType message_type,
const uint8_t *data, size_t data_size) { const uint8_t *data, size_t data_size) {
@ -194,7 +203,7 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy
switch (this->protocol_phase_) { switch (this->protocol_phase_) {
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
ESP_LOGI(TAG, "First HVAC status received"); ESP_LOGI(TAG, "First HVAC status received");
this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); this->set_phase(ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST);
break; break;
case ProtocolPhases::SENDING_ACTION_COMMAND: case ProtocolPhases::SENDING_ACTION_COMMAND:
// Do nothing, phase will be changed in process_phase // Do nothing, phase will be changed in process_phase
@ -251,12 +260,15 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_
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::SENDING_ALARM_STATUS_REQUEST) { if ((this->protocol_phase_ != ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) &&
(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;
} }
memcpy(this->active_alarms_, data + 2, 8); if (data_size < sizeof(active_alarms_) + 2)
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
return haier_protocol::HandlerError::HANDLER_OK; return haier_protocol::HandlerError::HANDLER_OK;
} else { } else {
@ -265,6 +277,19 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_
} }
} }
haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
const uint8_t *buffer, size_t size) {
haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
if (size < sizeof(this->active_alarms_) + 2) {
// Log error but confirm anyway to avoid to many messages
result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
}
this->process_alarm_message_(buffer, size, true);
this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
this->last_alarm_request_ = std::chrono::steady_clock::now();
return result;
}
void HonClimate::set_handlers() { void HonClimate::set_handlers() {
// Set handlers // Set handlers
this->haier_protocol_.set_answer_handler( this->haier_protocol_.set_answer_handler(
@ -291,6 +316,10 @@ void HonClimate::set_handlers() {
haier_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));
this->haier_protocol_.set_message_handler(
haier_protocol::FrameType::ALARM_STATUS,
std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3));
} }
void HonClimate::dump_config() { void HonClimate::dump_config() {
@ -363,10 +392,12 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
this->set_phase(ProtocolPhases::IDLE); this->set_phase(ProtocolPhases::IDLE);
break; break;
#endif #endif
case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST:
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(haier_protocol::FrameType::GET_ALARM_STATUS); static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
this->last_alarm_request_ = now;
} }
break; break;
case ProtocolPhases::SENDING_CONTROL: case ProtocolPhases::SENDING_CONTROL:
@ -417,12 +448,16 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
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);
this->forced_request_status_ = false; this->forced_request_status_ = false;
} else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
ALARM_STATUS_REQUEST_INTERVAL_MS) {
this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST);
} }
#ifdef USE_WIFI #ifdef USE_WIFI
else if (this->send_wifi_signal_ && else if (this->send_wifi_signal_ &&
(std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() > (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) {
this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
}
#endif #endif
} break; } break;
default: default:
@ -452,6 +487,7 @@ 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;
control_out_buffer[4] = 0; // This byte should be cleared before setting values
bool has_hvac_settings = false; bool has_hvac_settings = false;
if (this->current_hvac_settings_.valid) { if (this->current_hvac_settings_.valid) {
has_hvac_settings = true; has_hvac_settings = true;
@ -552,31 +588,41 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
out_data->ten_degree = 0;
break; break;
case CLIMATE_PRESET_ECO: case CLIMATE_PRESET_ECO:
// Eco is not supported in Fan only mode // Eco is not supported in Fan only mode
out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
out_data->ten_degree = 0;
break; break;
case CLIMATE_PRESET_BOOST: case CLIMATE_PRESET_BOOST:
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
// Boost is not supported in Fan only mode // Boost is not supported in Fan only mode
out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
out_data->ten_degree = 0;
break; break;
case CLIMATE_PRESET_AWAY: case CLIMATE_PRESET_AWAY:
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 0; out_data->sleep_mode = 0;
// 10 degrees allowed only in heat mode
out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
break; break;
case CLIMATE_PRESET_SLEEP: case CLIMATE_PRESET_SLEEP:
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
out_data->fast_mode = 0; out_data->fast_mode = 0;
out_data->sleep_mode = 1; out_data->sleep_mode = 1;
out_data->ten_degree = 0;
break; break;
default: default:
ESP_LOGE("Control", "Unsupported preset"); ESP_LOGE("Control", "Unsupported preset");
out_data->quiet_mode = 0;
out_data->fast_mode = 0;
out_data->sleep_mode = 0;
out_data->ten_degree = 0;
break; break;
} }
} }
@ -595,6 +641,50 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
} }
void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
if (size >= active_alarms_size + 2) {
if (check_new) {
size_t alarm_code = 0;
for (int i = active_alarms_size - 1; i >= 0; i--) {
if (packet[2 + i] != active_alarms_[i]) {
uint8_t alarm_bit = 1;
for (int b = 0; b < 8; b++) {
if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
? esphome::haier::hon_protocol::HON_ALARM_MESSAGES[alarm_code].c_str()
: "Unknown";
esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
alarm_code, alarm_message);
if (alarm_status) {
this->alarm_start_callback_.call(alarm_code, alarm_message);
this->active_alarm_count_ += 1.0f;
} else {
this->alarm_end_callback_.call(alarm_code, alarm_message);
this->active_alarm_count_ -= 1.0f;
}
}
alarm_bit <<= 1;
alarm_code++;
}
active_alarms_[i] = packet[2 + i];
} else
alarm_code += 8;
}
} else {
float alarm_count = 0.0f;
static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
}
this->active_alarm_count_ = alarm_count;
memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
}
}
}
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 < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_) 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;
@ -626,6 +716,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
this->preset = CLIMATE_PRESET_BOOST; this->preset = CLIMATE_PRESET_BOOST;
} else if (packet.control.sleep_mode != 0) { } else if (packet.control.sleep_mode != 0) {
this->preset = CLIMATE_PRESET_SLEEP; this->preset = CLIMATE_PRESET_SLEEP;
} else if (packet.control.ten_degree != 0) {
this->preset = CLIMATE_PRESET_AWAY;
} else { } else {
this->preset = CLIMATE_PRESET_NONE; this->preset = CLIMATE_PRESET_NONE;
} }
@ -882,25 +974,35 @@ void HonClimate::fill_control_messages_queue_() {
// CLimate preset // CLimate preset
{ {
uint8_t fast_mode_buf[] = {0x00, 0xFF}; uint8_t fast_mode_buf[] = {0x00, 0xFF};
uint8_t away_mode_buf[] = {0x00, 0xFF};
if (!new_power) { if (!new_power) {
// If AC is off - no presets allowed // If AC is off - no presets allowed
quiet_mode_buf[1] = 0x00; quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00;
away_mode_buf[1] = 0x00;
} else if (climate_control.preset.has_value()) { } else if (climate_control.preset.has_value()) {
switch (climate_control.preset.value()) { switch (climate_control.preset.value()) {
case CLIMATE_PRESET_NONE: case CLIMATE_PRESET_NONE:
quiet_mode_buf[1] = 0x00; quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00;
away_mode_buf[1] = 0x00;
break; break;
case CLIMATE_PRESET_ECO: case CLIMATE_PRESET_ECO:
// Eco is not supported in Fan only mode // Eco is not supported in Fan only mode
quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
fast_mode_buf[1] = 0x00; fast_mode_buf[1] = 0x00;
away_mode_buf[1] = 0x00;
break; break;
case CLIMATE_PRESET_BOOST: case CLIMATE_PRESET_BOOST:
quiet_mode_buf[1] = 0x00; quiet_mode_buf[1] = 0x00;
// Boost is not supported in Fan only mode // Boost is not supported in Fan only mode
fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
away_mode_buf[1] = 0x00;
break;
case CLIMATE_PRESET_AWAY:
quiet_mode_buf[1] = 0x00;
fast_mode_buf[1] = 0x00;
away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
break; break;
default: default:
ESP_LOGE("Control", "Unsupported preset"); ESP_LOGE("Control", "Unsupported preset");
@ -921,6 +1023,13 @@ void HonClimate::fill_control_messages_queue_() {
(uint8_t) hon_protocol::DataParameters::FAST_MODE, (uint8_t) hon_protocol::DataParameters::FAST_MODE,
fast_mode_buf, 2)); fast_mode_buf, 2));
} }
if (away_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::TEN_DEGREE,
away_mode_buf, 2));
}
} }
// Target temperature // Target temperature
if (climate_control.target_temperature.has_value()) { if (climate_control.target_temperature.has_value()) {

View File

@ -2,6 +2,7 @@
#include <chrono> #include <chrono>
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/core/automation.h"
#include "haier_base.h" #include "haier_base.h"
namespace esphome { namespace esphome {
@ -52,6 +53,9 @@ class HonClimate : public HaierClimateBase {
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_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; };
void set_control_method(HonControlMethod method) { this->control_method_ = method; }; void set_control_method(HonControlMethod method) { this->control_method_ = method; };
void add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback);
void add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback);
float get_active_alarm_count() const { return this->active_alarm_count_; }
protected: protected:
void set_handlers() override; void set_handlers() override;
@ -77,8 +81,11 @@ class HonClimate : public HaierClimateBase {
haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
haier_protocol::FrameType message_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 alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer,
size_t 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);
void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new);
void fill_control_messages_queue_(); void fill_control_messages_queue_();
void clear_control_messages_queue_(); void clear_control_messages_queue_();
@ -101,6 +108,26 @@ class HonClimate : public HaierClimateBase {
HonControlMethod control_method_; HonControlMethod control_method_;
esphome::sensor::Sensor *outdoor_sensor_; esphome::sensor::Sensor *outdoor_sensor_;
std::queue<haier_protocol::HaierMessage> control_messages_queue_; std::queue<haier_protocol::HaierMessage> control_messages_queue_;
CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};
CallbackManager<void(uint8_t, const char *)> alarm_end_callback_{};
float active_alarm_count_{NAN};
std::chrono::steady_clock::time_point last_alarm_request_;
};
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
public:
explicit HaierAlarmStartTrigger(HonClimate *parent) {
parent->add_alarm_start_callback(
[this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); });
}
};
class HaierAlarmEndTrigger : public Trigger<uint8_t, const char *> {
public:
explicit HaierAlarmEndTrigger(HonClimate *parent) {
parent->add_alarm_end_callback(
[this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); });
}
}; };
} // namespace haier } // namespace haier

View File

@ -163,6 +163,62 @@ enum class SubcommandsControl : uint16_t {
// content: all values like in status packet) // content: all values like in status packet)
}; };
const std::string HON_ALARM_MESSAGES[] = {
"Outdoor module failure",
"Outdoor defrost sensor failure",
"Outdoor compressor exhaust sensor failure",
"Outdoor EEPROM abnormality",
"Indoor coil sensor failure",
"Indoor-outdoor communication failure",
"Power supply overvoltage protection",
"Communication failure between panel and indoor unit",
"Outdoor compressor overheat protection",
"Outdoor environmental sensor abnormality",
"Full water protection",
"Indoor EEPROM failure",
"Outdoor out air sensor failure",
"CBD and module communication failure",
"Indoor DC fan failure",
"Outdoor DC fan failure",
"Door switch failure",
"Dust filter needs cleaning reminder",
"Water shortage protection",
"Humidity sensor failure",
"Indoor temperature sensor failure",
"Manipulator limit failure",
"Indoor PM2.5 sensor failure",
"Outdoor PM2.5 sensor failure",
"Indoor heating overload/high load alarm",
"Outdoor AC current protection",
"Outdoor compressor operation abnormality",
"Outdoor DC current protection",
"Outdoor no-load failure",
"CT current abnormality",
"Indoor cooling freeze protection",
"High and low pressure protection",
"Compressor out air temperature is too high",
"Outdoor evaporator sensor failure",
"Outdoor cooling overload",
"Water pump drainage failure",
"Three-phase power supply failure",
"Four-way valve failure",
"External alarm/scraper flow switch failure",
"Temperature cutoff protection alarm",
"Different mode operation failure",
"Electronic expansion valve failure",
"Dual heat source sensor Tw failure",
"Communication failure with the wired controller",
"Indoor unit address duplication failure",
"50Hz zero crossing failure",
"Outdoor unit failure",
"Formaldehyde sensor failure",
"VOC sensor failure",
"CO2 sensor failure",
"Firewall failure",
};
constexpr size_t HON_ALARM_COUNT = sizeof(HON_ALARM_MESSAGES) / sizeof(HON_ALARM_MESSAGES[0]);
} // namespace hon_protocol } // namespace hon_protocol
} // namespace haier } // namespace haier
} // namespace esphome } // namespace esphome

View File

@ -95,7 +95,7 @@ haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cyc
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type, ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type,
phase_to_string_(this->protocol_phase_)); phase_to_string_(this->protocol_phase_));
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
new_phase = ProtocolPhases::SENDING_INIT_1; new_phase = ProtocolPhases::SENDING_INIT_1;
this->set_phase(new_phase); this->set_phase(new_phase);
return haier_protocol::HandlerError::HANDLER_OK; return haier_protocol::HandlerError::HANDLER_OK;
@ -170,9 +170,12 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
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_FIRST_ALARM_STATUS_REQUEST:
this->set_phase(ProtocolPhases::SENDING_INIT_1); this->set_phase(ProtocolPhases::SENDING_INIT_1);
break; break;
case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
this->set_phase(ProtocolPhases::IDLE);
break;
case ProtocolPhases::SENDING_CONTROL: case ProtocolPhases::SENDING_CONTROL:
if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
ESP_LOGI(TAG, "Sending control packet"); ESP_LOGI(TAG, "Sending control packet");
@ -343,19 +346,29 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
} else if (climate_control.preset.has_value()) { } else if (climate_control.preset.has_value()) {
switch (climate_control.preset.value()) { switch (climate_control.preset.value()) {
case CLIMATE_PRESET_NONE: case CLIMATE_PRESET_NONE:
out_data->ten_degree = 0;
out_data->turbo_mode = 0; out_data->turbo_mode = 0;
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
break; break;
case CLIMATE_PRESET_BOOST: case CLIMATE_PRESET_BOOST:
out_data->ten_degree = 0;
out_data->turbo_mode = 1; out_data->turbo_mode = 1;
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
break; break;
case CLIMATE_PRESET_COMFORT: case CLIMATE_PRESET_COMFORT:
out_data->ten_degree = 0;
out_data->turbo_mode = 0; out_data->turbo_mode = 0;
out_data->quiet_mode = 1; out_data->quiet_mode = 1;
break; break;
case CLIMATE_PRESET_AWAY:
// Only allowed in heat mode
out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
out_data->turbo_mode = 0;
out_data->quiet_mode = 0;
break;
default: default:
ESP_LOGE("Control", "Unsupported preset"); ESP_LOGE("Control", "Unsupported preset");
out_data->ten_degree = 0;
out_data->turbo_mode = 0; out_data->turbo_mode = 0;
out_data->quiet_mode = 0; out_data->quiet_mode = 0;
break; break;
@ -381,6 +394,8 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
this->preset = CLIMATE_PRESET_BOOST; this->preset = CLIMATE_PRESET_BOOST;
} else if (packet.control.quiet_mode != 0) { } else if (packet.control.quiet_mode != 0) {
this->preset = CLIMATE_PRESET_COMFORT; this->preset = CLIMATE_PRESET_COMFORT;
} else if (packet.control.ten_degree != 0) {
this->preset = CLIMATE_PRESET_AWAY;
} else { } else {
this->preset = CLIMATE_PRESET_NONE; this->preset = CLIMATE_PRESET_NONE;
} }

View File

@ -1026,11 +1026,13 @@ climate:
wifi_signal: true wifi_signal: true
beeper: true beeper: true
outdoor_temperature: outdoor_temperature:
name: Haier AC outdoor temperature name: Haier AC outdoor temperature
visual: visual:
min_temperature: 16 °C min_temperature: 16 °C
max_temperature: 30 °C max_temperature: 30 °C
temperature_step: 1 °C temperature_step:
target_temperature: 1
current_temperature: 0.5
supported_modes: supported_modes:
- 'OFF' - 'OFF'
- HEAT_COOL - HEAT_COOL
@ -1043,6 +1045,23 @@ climate:
- VERTICAL - VERTICAL
- HORIZONTAL - HORIZONTAL
- BOTH - BOTH
supported_presets:
- AWAY
- BOOST
- ECO
- SLEEP
on_alarm_start:
then:
- logger.log:
level: DEBUG
format: "Alarm activated. Code: %d. Message: \"%s\""
args: [ code, message]
on_alarm_end:
then:
- logger.log:
level: DEBUG
format: "Alarm deactivated. Code: %d. Message: \"%s\""
args: [ code, message]
sprinkler: sprinkler:
- id: yard_sprinkler_ctrlr - id: yard_sprinkler_ctrlr