[haier] `text_sensor and button` platforms (#6780)

This commit is contained in:
Pavlo Dudnytskyi 2024-05-23 23:07:39 +02:00 committed by GitHub
parent 4ab7a5d964
commit aed0593793
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 396 additions and 80 deletions

View file

@ -152,6 +152,10 @@ esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/gt911/* @clydebarrow @jesserockz esphome/components/gt911/* @clydebarrow @jesserockz
esphome/components/haier/* @paveldn esphome/components/haier/* @paveldn
esphome/components/haier/binary_sensor/* @paveldn
esphome/components/haier/button/* @paveldn
esphome/components/haier/sensor/* @paveldn
esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann

View file

@ -46,7 +46,7 @@ template<typename... Ts> class BeeperOffAction : public Action<Ts...> {
template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> { template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
public: public:
VerticalAirflowAction(HonClimate *parent) : parent_(parent) {} VerticalAirflowAction(HonClimate *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(AirflowVerticalDirection, direction) TEMPLATABLE_VALUE(hon_protocol::VerticalSwingMode, direction)
void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); } void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); }
protected: protected:
@ -56,7 +56,7 @@ template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
template<typename... Ts> class HorizontalAirflowAction : public Action<Ts...> { template<typename... Ts> class HorizontalAirflowAction : public Action<Ts...> {
public: public:
HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {} HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(AirflowHorizontalDirection, direction) TEMPLATABLE_VALUE(hon_protocol::HorizontalSwingMode, direction)
void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); } void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); }
protected: protected:

View file

@ -11,6 +11,7 @@ from ..climate import (
HonClimate, HonClimate,
) )
CODEOWNERS = ["@paveldn"]
BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True) BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True)
# Haier sensors # Haier sensors

View file

@ -0,0 +1,41 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import button
from ..climate import (
CONF_HAIER_ID,
HonClimate,
haier_ns,
)
CODEOWNERS = ["@paveldn"]
SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button)
SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button)
# Haier buttons
CONF_SELF_CLEANING = "self_cleaning"
CONF_STERI_CLEANING = "steri_cleaning"
# Additional icons
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
CONFIG_SCHEMA = cv.Schema(
{
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
SelfCleaningButton,
icon=ICON_SPRAY_BOTTLE,
),
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
SteriCleaningButton,
icon=ICON_SPRAY_BOTTLE,
),
}
)
async def to_code(config):
for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]:
if conf := config.get(button_type):
btn = await button.new_button(conf)
await cg.register_parented(btn, config[CONF_HAIER_ID])

View file

@ -0,0 +1,9 @@
#include "self_cleaning.h"
namespace esphome {
namespace haier {
void SelfCleaningButton::press_action() { this->parent_->start_self_cleaning(); }
} // namespace haier
} // namespace esphome

View file

@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../hon_climate.h"
namespace esphome {
namespace haier {
class SelfCleaningButton : public button::Button, public Parented<HonClimate> {
public:
SelfCleaningButton() = default;
protected:
void press_action() override;
};
} // namespace haier
} // namespace esphome

View file

@ -0,0 +1,9 @@
#include "steri_cleaning.h"
namespace esphome {
namespace haier {
void SteriCleaningButton::press_action() { this->parent_->start_steri_cleaning(); }
} // namespace haier
} // namespace esphome

View file

@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../hon_climate.h"
namespace esphome {
namespace haier {
class SteriCleaningButton : public button::Button, public Parented<HonClimate> {
public:
SteriCleaningButton() = default;
protected:
void press_action() override;
};
} // namespace haier
} // namespace esphome

View file

@ -55,6 +55,7 @@ PROTOCOL_HON = "HON"
PROTOCOL_SMARTAIR2 = "SMARTAIR2" PROTOCOL_SMARTAIR2 = "SMARTAIR2"
haier_ns = cg.esphome_ns.namespace("haier") haier_ns = cg.esphome_ns.namespace("haier")
hon_protocol_ns = haier_ns.namespace("hon_protocol")
HaierClimateBase = haier_ns.class_( HaierClimateBase = haier_ns.class_(
"HaierClimateBase", uart.UARTDevice, climate.Climate, cg.Component "HaierClimateBase", uart.UARTDevice, climate.Climate, cg.Component
) )
@ -63,7 +64,7 @@ Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase)
CONF_HAIER_ID = "haier_id" CONF_HAIER_ID = "haier_id"
AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True) AirflowVerticalDirection = hon_protocol_ns.enum("VerticalSwingMode", True)
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = { AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
"HEALTH_UP": AirflowVerticalDirection.HEALTH_UP, "HEALTH_UP": AirflowVerticalDirection.HEALTH_UP,
"MAX_UP": AirflowVerticalDirection.MAX_UP, "MAX_UP": AirflowVerticalDirection.MAX_UP,
@ -73,7 +74,7 @@ AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
"HEALTH_DOWN": AirflowVerticalDirection.HEALTH_DOWN, "HEALTH_DOWN": AirflowVerticalDirection.HEALTH_DOWN,
} }
AirflowHorizontalDirection = haier_ns.enum("AirflowHorizontalDirection", True) AirflowHorizontalDirection = hon_protocol_ns.enum("HorizontalSwingMode", True)
AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = { AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = {
"MAX_LEFT": AirflowHorizontalDirection.MAX_LEFT, "MAX_LEFT": AirflowHorizontalDirection.MAX_LEFT,
"LEFT": AirflowHorizontalDirection.LEFT, "LEFT": AirflowHorizontalDirection.LEFT,
@ -483,4 +484,4 @@ async def to_code(config):
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf 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.25") cg.add_library("pavlodn/HaierProtocol", "0.9.28")

View file

@ -234,6 +234,7 @@ void HaierClimateBase::setup() {
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(); this->set_handlers();
this->initialization();
} }
void HaierClimateBase::dump_config() { void HaierClimateBase::dump_config() {
@ -326,7 +327,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; }
void HaierClimateBase::control(const ClimateCall &call) { void HaierClimateBase::control(const ClimateCall &call) {
ESP_LOGD("Control", "Control call"); ESP_LOGD("Control", "Control call");
if (this->protocol_phase_ < ProtocolPhases::IDLE) { if (!this->valid_connection()) {
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.
} }

View file

@ -44,7 +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; }; bool valid_connection() const { 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;
@ -80,6 +80,7 @@ class HaierClimateBase : public esphome::Component,
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 haier_protocol::HaierMessage get_power_message(bool state) = 0; virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
virtual void initialization(){};
virtual bool prepare_pending_action(); virtual bool prepare_pending_action();
virtual void process_protocol_reset(); virtual void process_protocol_reset();
esphome::climate::ClimateTraits traits() override; esphome::climate::ClimateTraits traits() override;

View file

@ -19,38 +19,6 @@ 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; constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) {
switch (direction) {
case AirflowVerticalDirection::HEALTH_UP:
return hon_protocol::VerticalSwingMode::HEALTH_UP;
case AirflowVerticalDirection::MAX_UP:
return hon_protocol::VerticalSwingMode::MAX_UP;
case AirflowVerticalDirection::UP:
return hon_protocol::VerticalSwingMode::UP;
case AirflowVerticalDirection::DOWN:
return hon_protocol::VerticalSwingMode::DOWN;
case AirflowVerticalDirection::HEALTH_DOWN:
return hon_protocol::VerticalSwingMode::HEALTH_DOWN;
default:
return hon_protocol::VerticalSwingMode::CENTER;
}
}
hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDirection direction) {
switch (direction) {
case AirflowHorizontalDirection::MAX_LEFT:
return hon_protocol::HorizontalSwingMode::MAX_LEFT;
case AirflowHorizontalDirection::LEFT:
return hon_protocol::HorizontalSwingMode::LEFT;
case AirflowHorizontalDirection::RIGHT:
return hon_protocol::HorizontalSwingMode::RIGHT;
case AirflowHorizontalDirection::MAX_RIGHT:
return hon_protocol::HorizontalSwingMode::MAX_RIGHT;
default:
return hon_protocol::HorizontalSwingMode::CENTER;
}
}
HonClimate::HonClimate() HonClimate::HonClimate()
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00, : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -66,17 +34,21 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; }
bool HonClimate::get_beeper_state() const { return this->beeper_status_; } bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; }; esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
return this->current_vertical_swing_;
};
void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { void HonClimate::set_vertical_airflow(hon_protocol::VerticalSwingMode direction) {
this->vertical_direction_ = direction; this->pending_vertical_direction_ = direction;
this->force_send_control_ = true; this->force_send_control_ = true;
} }
AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; } esphome::optional<hon_protocol::HorizontalSwingMode> HonClimate::get_horizontal_airflow() const {
return this->current_horizontal_swing_;
}
void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) { void HonClimate::set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction) {
this->horizontal_direction_ = direction; this->pending_horizontal_direction_ = direction;
this->force_send_control_ = true; this->force_send_control_ = true;
} }
@ -148,6 +120,11 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haie
this->hvac_hardware_info_.value().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_hardware_info_.value().device_name_ = std::string(tmp); this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
#ifdef USE_TEXT_SENSOR
this->update_sub_text_sensor_(SubTextSensorType::APPLIANCE_NAME, this->hvac_hardware_info_.value().device_name_);
this->update_sub_text_sensor_(SubTextSensorType::PROTOCOL_VERSION,
this->hvac_hardware_info_.value().protocol_version_);
#endif
this->hvac_hardware_info_.value().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_hardware_info_.value().functions_[1] = this->hvac_hardware_info_.value().functions_[1] =
(answr->functions[1] & 0x02) != 0; // controller-device mode support (answr->functions[1] & 0x02) != 0; // controller-device mode support
@ -488,6 +465,19 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
} }
} }
void HonClimate::initialization() {
constexpr uint32_t restore_settings_version = 0xE834D8DCUL;
this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
HonSettings recovered;
if (this->rtc_.load(&recovered)) {
this->settings_ = recovered;
} else {
this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER};
}
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
}
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));
@ -560,16 +550,16 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
if (climate_control.swing_mode.has_value()) { if (climate_control.swing_mode.has_value()) {
switch (climate_control.swing_mode.value()) { switch (climate_control.swing_mode.value()) {
case CLIMATE_SWING_OFF: case CLIMATE_SWING_OFF:
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_); out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_); out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
break; break;
case CLIMATE_SWING_VERTICAL: case CLIMATE_SWING_VERTICAL:
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_); out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO; out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
break; break;
case CLIMATE_SWING_HORIZONTAL: case CLIMATE_SWING_HORIZONTAL:
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO; out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_); out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
break; break;
case CLIMATE_SWING_BOTH: case CLIMATE_SWING_BOTH:
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO; out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
@ -631,11 +621,14 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
break; break;
} }
} }
} else { }
if (out_data->vertical_swing_mode != (uint8_t) hon_protocol::VerticalSwingMode::AUTO) if (this->pending_vertical_direction_.has_value()) {
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_); out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
if (out_data->horizontal_swing_mode != (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) this->pending_vertical_direction_.reset();
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_); }
if (this->pending_horizontal_direction_.has_value()) {
out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
this->pending_horizontal_direction_.reset();
} }
out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0; out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0;
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
@ -737,6 +730,33 @@ void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t val
} }
#endif // USE_BINARY_SENSOR #endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
void HonClimate::set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens) {
this->sub_text_sensors_[(size_t) type] = sens;
switch (type) {
case SubTextSensorType::APPLIANCE_NAME:
if (this->hvac_hardware_info_.has_value())
sens->publish_state(this->hvac_hardware_info_.value().device_name_);
break;
case SubTextSensorType::PROTOCOL_VERSION:
if (this->hvac_hardware_info_.has_value())
sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
break;
case SubTextSensorType::CLEANING_STATUS:
sens->publish_state(this->get_cleaning_status_text());
break;
default:
break;
}
}
void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::string &value) {
size_t index = (size_t) type;
if (this->sub_text_sensors_[index] != nullptr)
this->sub_text_sensors_[index]->publish_state(value);
}
#endif // USE_TEXT_SENSOR
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) {
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) + size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
this->extra_control_packet_bytes_; this->extra_control_packet_bytes_;
@ -896,6 +916,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()}); PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
} }
this->cleaning_status_ = new_cleaning; this->cleaning_status_ = new_cleaning;
#ifdef USE_TEXT_SENSOR
this->update_sub_text_sensor_(SubTextSensorType::CLEANING_STATUS, this->get_cleaning_status_text());
#endif // USE_TEXT_SENSOR
} }
} }
{ {
@ -941,6 +964,19 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
this->swing_mode = CLIMATE_SWING_OFF; this->swing_mode = CLIMATE_SWING_OFF;
} }
} }
// Saving last known non auto mode for vertical and horizontal swing
this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
bool save_settings = ((this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO) &&
(this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO_SPECIAL) &&
(this->current_vertical_swing_.value() != this->settings_.last_vertiacal_swing)) ||
((this->current_horizontal_swing_.value() != hon_protocol::HorizontalSwingMode::AUTO) &&
(this->current_horizontal_swing_.value() != this->settings_.last_horizontal_swing));
if (save_settings) {
this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value();
this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value();
this->rtc_.save(&this->settings_);
}
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();

View file

@ -7,29 +7,16 @@
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#endif #endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "haier_base.h" #include "haier_base.h"
#include "hon_packet.h"
namespace esphome { namespace esphome {
namespace haier { namespace haier {
enum class AirflowVerticalDirection : uint8_t {
HEALTH_UP = 0,
MAX_UP = 1,
UP = 2,
CENTER = 3,
DOWN = 4,
HEALTH_DOWN = 5,
};
enum class AirflowHorizontalDirection : uint8_t {
MAX_LEFT = 0,
LEFT = 1,
CENTER = 2,
RIGHT = 3,
MAX_RIGHT = 4,
};
enum class CleaningState : uint8_t { enum class CleaningState : uint8_t {
NO_CLEANING = 0, NO_CLEANING = 0,
SELF_CLEAN = 1, SELF_CLEAN = 1,
@ -38,6 +25,11 @@ enum class CleaningState : uint8_t {
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
struct HonSettings {
hon_protocol::VerticalSwingMode last_vertiacal_swing;
hon_protocol::HorizontalSwingMode last_horizontal_swing;
};
class HonClimate : public HaierClimateBase { class HonClimate : public HaierClimateBase {
#ifdef USE_SENSOR #ifdef USE_SENSOR
public: public:
@ -80,6 +72,20 @@ class HonClimate : public HaierClimateBase {
protected: protected:
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value); void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value);
binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr}; binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr};
#endif
#ifdef USE_TEXT_SENSOR
public:
enum class SubTextSensorType {
CLEANING_STATUS = 0,
PROTOCOL_VERSION,
APPLIANCE_NAME,
SUB_TEXT_SENSOR_TYPE_COUNT,
};
void set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens);
protected:
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value);
text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr};
#endif #endif
public: public:
HonClimate(); HonClimate();
@ -89,10 +95,10 @@ class HonClimate : public HaierClimateBase {
void dump_config() override; void dump_config() override;
void set_beeper_state(bool state); void set_beeper_state(bool state);
bool get_beeper_state() const; bool get_beeper_state() const;
AirflowVerticalDirection get_vertical_airflow() const; esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const;
void set_vertical_airflow(AirflowVerticalDirection direction); void set_vertical_airflow(hon_protocol::VerticalSwingMode direction);
AirflowHorizontalDirection get_horizontal_airflow() const; esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const;
void set_horizontal_airflow(AirflowHorizontalDirection direction); void set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction);
std::string get_cleaning_status_text() const; std::string get_cleaning_status_text() const;
CleaningState get_cleaning_status() const; CleaningState get_cleaning_status() const;
void start_self_cleaning(); void start_self_cleaning();
@ -108,6 +114,7 @@ class HonClimate : public HaierClimateBase {
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;
haier_protocol::HaierMessage get_power_message(bool state) override; haier_protocol::HaierMessage get_power_message(bool state) override;
void initialization() override;
bool prepare_pending_action() override; bool prepare_pending_action() override;
void process_protocol_reset() override; void process_protocol_reset() override;
bool should_get_big_data_(); bool should_get_big_data_();
@ -147,9 +154,9 @@ class HonClimate : public HaierClimateBase {
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_; esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{};
AirflowHorizontalDirection horizontal_direction_; esphome::optional<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{};
esphome::optional<HardwareInfo> hvac_hardware_info_; esphome::optional<HardwareInfo> hvac_hardware_info_{};
uint8_t active_alarms_[8]; uint8_t active_alarms_[8];
int extra_control_packet_bytes_; int extra_control_packet_bytes_;
HonControlMethod control_method_; HonControlMethod control_method_;
@ -159,6 +166,10 @@ class HonClimate : public HaierClimateBase {
float active_alarm_count_{NAN}; float active_alarm_count_{NAN};
std::chrono::steady_clock::time_point last_alarm_request_; std::chrono::steady_clock::time_point last_alarm_request_;
int big_data_sensors_{0}; int big_data_sensors_{0};
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
HonSettings settings_;
ESPPreferenceObject rtc_;
}; };
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {

View file

@ -13,7 +13,10 @@ enum class VerticalSwingMode : uint8_t {
UP = 0x04, UP = 0x04,
CENTER = 0x06, CENTER = 0x06,
DOWN = 0x08, DOWN = 0x08,
AUTO = 0x0C MAX_DOWN = 0x0A,
AUTO = 0x0C,
// Auto for special modes
AUTO_SPECIAL = 0x0E
}; };
enum class HorizontalSwingMode : uint8_t { enum class HorizontalSwingMode : uint8_t {

View file

@ -31,6 +31,7 @@ from ..climate import (
HonClimate, HonClimate,
) )
CODEOWNERS = ["@paveldn"]
SensorTypeEnum = HonClimate.enum("SubSensorType", True) SensorTypeEnum = HonClimate.enum("SubSensorType", True)
# Haier sensors # Haier sensors

View file

@ -0,0 +1,54 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_NONE,
)
from ..climate import (
CONF_HAIER_ID,
HonClimate,
)
CODEOWNERS = ["@paveldn"]
TextSensorTypeEnum = HonClimate.enum("SubTextSensorType", True)
# Haier text sensors
CONF_CLEANING_STATUS = "cleaning_status"
CONF_PROTOCOL_VERSION = "protocol_version"
CONF_APPLIANCE_NAME = "appliance_name"
# Additional icons
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
ICON_TEXT_BOX = "mdi:text-box-outline"
TEXT_SENSOR_TYPES = {
CONF_CLEANING_STATUS: text_sensor.text_sensor_schema(
icon=ICON_SPRAY_BOTTLE,
entity_category=ENTITY_CATEGORY_NONE,
),
CONF_PROTOCOL_VERSION: text_sensor.text_sensor_schema(
icon=ICON_TEXT_BOX,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
CONF_APPLIANCE_NAME: text_sensor.text_sensor_schema(
icon=ICON_TEXT_BOX,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
CONFIG_SCHEMA = cv.Schema(
{
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
}
).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()})
async def to_code(config):
paren = await cg.get_variable(config[CONF_HAIER_ID])
for type, _ in TEXT_SENSOR_TYPES.items():
if conf := config.get(type):
sens = await text_sensor.new_text_sensor(conf)
text_sensor_type = getattr(TextSensorTypeEnum, type.upper())
cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))

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.25 ; haier pavlodn/HaierProtocol@0.9.28 ; 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 =

View file

@ -93,3 +93,21 @@ binary_sensor:
name: Haier Indoor Fan Status name: Haier Indoor Fan Status
outdoor_fan_status: outdoor_fan_status:
name: Haier Outdoor Fan Status name: Haier Outdoor Fan Status
button:
- platform: haier
haier_id: haier_ac
self_cleaning:
name: Haier start self cleaning
steri_cleaning:
name: Haier start 56°C steri-cleaning
text_sensor:
- platform: haier
haier_id: haier_ac
appliance_name:
name: Haier appliance name
cleaning_status:
name: Haier cleaning status
protocol_version:
name: Haier protocol version

View file

@ -93,3 +93,21 @@ binary_sensor:
name: Haier Indoor Fan Status name: Haier Indoor Fan Status
outdoor_fan_status: outdoor_fan_status:
name: Haier Outdoor Fan Status name: Haier Outdoor Fan Status
button:
- platform: haier
haier_id: haier_ac
self_cleaning:
name: Haier start self cleaning
steri_cleaning:
name: Haier start 56°C steri-cleaning
text_sensor:
- platform: haier
haier_id: haier_ac
appliance_name:
name: Haier appliance name
cleaning_status:
name: Haier cleaning status
protocol_version:
name: Haier protocol version

View file

@ -93,3 +93,21 @@ binary_sensor:
name: Haier Indoor Fan Status name: Haier Indoor Fan Status
outdoor_fan_status: outdoor_fan_status:
name: Haier Outdoor Fan Status name: Haier Outdoor Fan Status
button:
- platform: haier
haier_id: haier_ac
self_cleaning:
name: Haier start self cleaning
steri_cleaning:
name: Haier start 56°C steri-cleaning
text_sensor:
- platform: haier
haier_id: haier_ac
appliance_name:
name: Haier appliance name
cleaning_status:
name: Haier cleaning status
protocol_version:
name: Haier protocol version

View file

@ -93,3 +93,21 @@ binary_sensor:
name: Haier Indoor Fan Status name: Haier Indoor Fan Status
outdoor_fan_status: outdoor_fan_status:
name: Haier Outdoor Fan Status name: Haier Outdoor Fan Status
button:
- platform: haier
haier_id: haier_ac
self_cleaning:
name: Haier start self cleaning
steri_cleaning:
name: Haier start 56°C steri-cleaning
text_sensor:
- platform: haier
haier_id: haier_ac
appliance_name:
name: Haier appliance name
cleaning_status:
name: Haier cleaning status
protocol_version:
name: Haier protocol version

View file

@ -93,3 +93,21 @@ binary_sensor:
name: Haier Indoor Fan Status name: Haier Indoor Fan Status
outdoor_fan_status: outdoor_fan_status:
name: Haier Outdoor Fan Status name: Haier Outdoor Fan Status
button:
- platform: haier
haier_id: haier_ac
self_cleaning:
name: Haier start self cleaning
steri_cleaning:
name: Haier start 56°C steri-cleaning
text_sensor:
- platform: haier
haier_id: haier_ac
appliance_name:
name: Haier appliance name
cleaning_status:
name: Haier cleaning status
protocol_version:
name: Haier protocol version

View file

@ -93,3 +93,21 @@ binary_sensor:
name: Haier Indoor Fan Status name: Haier Indoor Fan Status
outdoor_fan_status: outdoor_fan_status:
name: Haier Outdoor Fan Status name: Haier Outdoor Fan Status
button:
- platform: haier
haier_id: haier_ac
self_cleaning:
name: Haier start self cleaning
steri_cleaning:
name: Haier start 56°C steri-cleaning
text_sensor:
- platform: haier
haier_id: haier_ac
appliance_name:
name: Haier appliance name
cleaning_status:
name: Haier cleaning status
protocol_version:
name: Haier protocol version