#pragma once #include #include #include "esphome/components/climate/climate.h" #include "esphome/components/uart/uart.h" #include "esphome/core/automation.h" // HaierProtocol #include #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif namespace esphome { namespace haier { enum class ActionRequest : uint8_t { SEND_CUSTOM_COMMAND = 0, TURN_POWER_ON = 1, TURN_POWER_OFF = 2, TOGGLE_POWER = 3, START_SELF_CLEAN = 4, // only hOn START_STERI_CLEAN = 5, // only hOn }; struct HaierBaseSettings { bool health_mode; bool display_state; }; class HaierClimateBase : public esphome::Component, public esphome::climate::Climate, public esphome::uart::UARTDevice, public haier_protocol::ProtocolStream { #ifdef USE_SWITCH public: void set_display_switch(switch_::Switch *sw); void set_health_mode_switch(switch_::Switch *sw); protected: switch_::Switch *display_switch_{nullptr}; switch_::Switch *health_mode_switch_{nullptr}; #endif public: HaierClimateBase(); HaierClimateBase(const HaierClimateBase &) = delete; HaierClimateBase &operator=(const HaierClimateBase &) = delete; ~HaierClimateBase(); void setup() override; void loop() override; void control(const esphome::climate::ClimateCall &call) override; void dump_config() override; float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } void set_display_state(bool state); bool get_display_state() const; void set_health_mode(bool state); bool get_health_mode() const; void send_power_on_command(); void send_power_off_command(); void toggle_power(); void reset_protocol() { this->reset_protocol_request_ = true; }; void set_supported_modes(const std::set &modes); void set_supported_swing_modes(const std::set &modes); void set_supported_presets(const std::set &presets); bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; size_t read_array(uint8_t *data, size_t len) noexcept override { return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; }; void write_array(const uint8_t *data, size_t len) noexcept override { esphome::uart::UARTDevice::write_array(data, len); }; bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; void set_answer_timeout(uint32_t timeout); void set_send_wifi(bool send_wifi); void send_custom_command(const haier_protocol::HaierMessage &message); void add_status_message_callback(std::function &&callback); protected: enum class ProtocolPhases { UNKNOWN = -1, // INITIALIZATION SENDING_INIT_1 = 0, SENDING_INIT_2, SENDING_FIRST_STATUS_REQUEST, SENDING_FIRST_ALARM_STATUS_REQUEST, // FUNCTIONAL STATE IDLE, SENDING_STATUS_REQUEST, SENDING_UPDATE_SIGNAL_REQUEST, SENDING_SIGNAL_LEVEL, SENDING_CONTROL, SENDING_ACTION_COMMAND, SENDING_ALARM_STATUS_REQUEST, NUM_PROTOCOL_PHASES }; const char *phase_to_string_(ProtocolPhases phase); virtual void set_handlers() = 0; virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming) virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming) virtual void save_settings(); virtual void initialization(); virtual bool prepare_pending_action(); virtual void process_protocol_reset(); esphome::climate::ClimateTraits traits() override; // Answer handlers haier_protocol::HandlerError 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 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 haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type); // Helper functions void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats = 0, std::chrono::milliseconds interval = std::chrono::milliseconds::zero()); virtual void set_phase(ProtocolPhases phase); void reset_phase_(); void reset_to_idle_(); 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_control_message_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 haier_protocol::HaierMessage get_wifi_signal_message_(); #endif struct HvacSettings { esphome::optional mode; esphome::optional fan_mode; esphome::optional swing_mode; esphome::optional target_temperature; esphome::optional preset; bool valid; HvacSettings() : valid(false){}; HvacSettings(const HvacSettings &) = default; HvacSettings &operator=(const HvacSettings &) = default; void reset(); }; struct PendingAction { ActionRequest action; esphome::optional message; }; enum class SwitchState { OFF = 0b00, ON = 0b01, PENDING_OFF = 0b10, PENDING_ON = 0b11, }; haier_protocol::ProtocolHandler haier_protocol_; ProtocolPhases protocol_phase_; esphome::optional action_request_; uint8_t fan_mode_speed_; uint8_t other_modes_fan_speed_; SwitchState display_status_{SwitchState::ON}; SwitchState health_mode_{SwitchState::OFF}; bool force_send_control_; bool forced_request_status_; bool reset_protocol_request_; bool send_wifi_signal_; bool use_crc_; esphome::climate::ClimateTraits traits_; HvacSettings current_hvac_settings_; HvacSettings next_hvac_settings_; std::unique_ptr last_status_message_{nullptr}; std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout std::chrono::steady_clock::time_point last_status_request_; // To request AC status std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level CallbackManager status_message_callback_{}; ESPPreferenceObject base_rtc_; }; class StatusMessageTrigger : public Trigger { public: explicit StatusMessageTrigger(HaierClimateBase *parent) { parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); }); } }; } // namespace haier } // namespace esphome