mirror of
https://github.com/esphome/esphome.git
synced 2024-12-25 17:07:50 +01:00
commit
265e019381
@ -14,7 +14,10 @@ from esphome.const import (
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_PROTOCOL,
|
||||
CONF_SUPPORTED_MODES,
|
||||
CONF_SUPPORTED_PRESETS,
|
||||
CONF_SUPPORTED_SWING_MODES,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_VISUAL,
|
||||
CONF_WIFI,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
@ -23,25 +26,29 @@ from esphome.const import (
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
from esphome.components.climate import (
|
||||
ClimateSwingMode,
|
||||
ClimateMode,
|
||||
ClimatePreset,
|
||||
ClimateSwingMode,
|
||||
CONF_CURRENT_TEMPERATURE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PROTOCOL_MIN_TEMPERATURE = 16.0
|
||||
PROTOCOL_MAX_TEMPERATURE = 30.0
|
||||
PROTOCOL_TEMPERATURE_STEP = 1.0
|
||||
PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0
|
||||
PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
AUTO_LOAD = ["sensor"]
|
||||
DEPENDENCIES = ["climate", "uart"]
|
||||
CONF_WIFI_SIGNAL = "wifi_signal"
|
||||
CONF_ANSWER_TIMEOUT = "answer_timeout"
|
||||
CONF_DISPLAY = "display"
|
||||
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
|
||||
CONF_VERTICAL_AIRFLOW = "vertical_airflow"
|
||||
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
|
||||
|
||||
|
||||
PROTOCOL_HON = "HON"
|
||||
PROTOCOL_SMARTAIR2 = "SMARTAIR2"
|
||||
PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2]
|
||||
@ -89,6 +96,17 @@ SUPPORTED_CLIMATE_MODES_OPTIONS = {
|
||||
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
||||
}
|
||||
|
||||
SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = {
|
||||
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
||||
"COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT,
|
||||
}
|
||||
|
||||
SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
|
||||
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
|
||||
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
||||
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
|
||||
}
|
||||
|
||||
|
||||
def validate_visual(config):
|
||||
if CONF_VISUAL in config:
|
||||
@ -109,10 +127,29 @@ def validate_visual(config):
|
||||
)
|
||||
else:
|
||||
config[CONF_VISUAL][CONF_MAX_TEMPERATURE] = PROTOCOL_MAX_TEMPERATURE
|
||||
if CONF_TEMPERATURE_STEP in visual_config:
|
||||
temp_step = config[CONF_VISUAL][CONF_TEMPERATURE_STEP][
|
||||
CONF_TARGET_TEMPERATURE
|
||||
]
|
||||
if ((int)(temp_step * 2)) / 2 != temp_step:
|
||||
raise cv.Invalid(
|
||||
f"Configured visual temperature step {temp_step} is wrong, it should be a multiple of 0.5"
|
||||
)
|
||||
else:
|
||||
config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = (
|
||||
{
|
||||
CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP,
|
||||
CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP,
|
||||
},
|
||||
)
|
||||
else:
|
||||
config[CONF_VISUAL] = {
|
||||
CONF_MIN_TEMPERATURE: PROTOCOL_MIN_TEMPERATURE,
|
||||
CONF_MAX_TEMPERATURE: PROTOCOL_MAX_TEMPERATURE,
|
||||
CONF_TEMPERATURE_STEP: {
|
||||
CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP,
|
||||
CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP,
|
||||
},
|
||||
}
|
||||
return config
|
||||
|
||||
@ -132,6 +169,11 @@ BASE_CONFIG_SCHEMA = (
|
||||
"BOTH",
|
||||
],
|
||||
): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)),
|
||||
cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DISPLAY): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ANSWER_TIMEOUT,
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
@ -144,13 +186,26 @@ CONFIG_SCHEMA = cv.All(
|
||||
PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Smartair2Climate),
|
||||
cv.Optional(
|
||||
CONF_SUPPORTED_PRESETS,
|
||||
default=list(
|
||||
SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys()
|
||||
),
|
||||
): cv.ensure_list(
|
||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
|
||||
),
|
||||
}
|
||||
),
|
||||
PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HonClimate),
|
||||
cv.Optional(CONF_WIFI_SIGNAL, default=True): cv.boolean,
|
||||
cv.Optional(CONF_BEEPER, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_SUPPORTED_PRESETS,
|
||||
default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()),
|
||||
): cv.ensure_list(
|
||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
||||
),
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
@ -354,10 +409,11 @@ async def to_code(config):
|
||||
await uart.register_uart_device(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
|
||||
if (CONF_WIFI_SIGNAL in config) and (config[CONF_WIFI_SIGNAL]):
|
||||
cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL]))
|
||||
cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL]))
|
||||
if CONF_BEEPER in config:
|
||||
cg.add(var.set_beeper_state(config[CONF_BEEPER]))
|
||||
if CONF_DISPLAY in config:
|
||||
cg.add(var.set_display_state(config[CONF_DISPLAY]))
|
||||
if CONF_OUTDOOR_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE])
|
||||
cg.add(var.set_outdoor_temperature_sensor(sens))
|
||||
@ -365,5 +421,9 @@ async def to_code(config):
|
||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||
if CONF_SUPPORTED_SWING_MODES in config:
|
||||
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
|
||||
if CONF_SUPPORTED_PRESETS in config:
|
||||
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
|
||||
if CONF_ANSWER_TIMEOUT in config:
|
||||
cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT]))
|
||||
# https://github.com/paveldn/HaierProtocol
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.18")
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.20")
|
||||
|
@ -2,6 +2,9 @@
|
||||
#include <string>
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#ifdef USE_WIFI
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
#include "haier_base.h"
|
||||
|
||||
using namespace esphome::climate;
|
||||
@ -24,14 +27,15 @@ constexpr size_t NO_COMMAND = 0xFF; // Indicate that there is no command suppli
|
||||
const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
|
||||
static const char *phase_names[] = {
|
||||
"SENDING_INIT_1",
|
||||
"WAITING_ANSWER_INIT_1",
|
||||
"WAITING_INIT_1_ANSWER",
|
||||
"SENDING_INIT_2",
|
||||
"WAITING_ANSWER_INIT_2",
|
||||
"WAITING_INIT_2_ANSWER",
|
||||
"SENDING_FIRST_STATUS_REQUEST",
|
||||
"WAITING_FIRST_STATUS_ANSWER",
|
||||
"SENDING_ALARM_STATUS_REQUEST",
|
||||
"WAITING_ALARM_STATUS_ANSWER",
|
||||
"IDLE",
|
||||
"UNKNOWN",
|
||||
"SENDING_STATUS_REQUEST",
|
||||
"WAITING_STATUS_ANSWER",
|
||||
"SENDING_UPDATE_SIGNAL_REQUEST",
|
||||
@ -63,7 +67,8 @@ HaierClimateBase::HaierClimateBase()
|
||||
forced_publish_(false),
|
||||
forced_request_status_(false),
|
||||
first_control_attempt_(false),
|
||||
reset_protocol_request_(false) {
|
||||
reset_protocol_request_(false),
|
||||
send_wifi_signal_(true) {
|
||||
this->traits_ = climate::ClimateTraits();
|
||||
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,
|
||||
@ -77,7 +82,7 @@ HaierClimateBase::HaierClimateBase()
|
||||
|
||||
HaierClimateBase::~HaierClimateBase() {}
|
||||
|
||||
void HaierClimateBase::set_phase_(ProtocolPhases phase) {
|
||||
void HaierClimateBase::set_phase(ProtocolPhases 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));
|
||||
@ -109,10 +114,27 @@ bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady
|
||||
return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS);
|
||||
}
|
||||
|
||||
bool HaierClimateBase::is_protocol_initialisation_interval_exceded_(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);
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI
|
||||
haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t message_type) {
|
||||
static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
if (wifi::global_wifi_component->is_connected()) {
|
||||
wifi_status_data[1] = 0;
|
||||
int8_t rssi = wifi::global_wifi_component->wifi_rssi();
|
||||
wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f);
|
||||
ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "WiFi is not connected");
|
||||
wifi_status_data[1] = 1;
|
||||
wifi_status_data[3] = 0;
|
||||
}
|
||||
return haier_protocol::HaierMessage(message_type, wifi_status_data, sizeof(wifi_status_data));
|
||||
}
|
||||
#endif
|
||||
|
||||
bool HaierClimateBase::get_display_state() const { return this->display_status_; }
|
||||
|
||||
void HaierClimateBase::set_display_state(bool state) {
|
||||
@ -136,10 +158,15 @@ void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionR
|
||||
void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; }
|
||||
|
||||
void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; }
|
||||
|
||||
void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
|
||||
this->traits_.set_supported_swing_modes(modes);
|
||||
this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); // Always available
|
||||
this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); // Always available
|
||||
if (!modes.empty())
|
||||
this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
|
||||
}
|
||||
|
||||
void HaierClimateBase::set_answer_timeout(uint32_t timeout) {
|
||||
this->answer_timeout_ = std::chrono::milliseconds(timeout);
|
||||
}
|
||||
|
||||
void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) {
|
||||
@ -148,6 +175,14 @@ void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode>
|
||||
this->traits_.add_supported_mode(climate::CLIMATE_MODE_AUTO); // Always available
|
||||
}
|
||||
|
||||
void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) {
|
||||
this->traits_.set_supported_presets(presets);
|
||||
if (!presets.empty())
|
||||
this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE);
|
||||
}
|
||||
|
||||
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,
|
||||
uint8_t expected_request_message_type,
|
||||
uint8_t answer_message_type,
|
||||
@ -155,9 +190,9 @@ haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t reques
|
||||
ProtocolPhases expected_phase) {
|
||||
haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
|
||||
if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type))
|
||||
result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE;
|
||||
result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
|
||||
if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type))
|
||||
result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE;
|
||||
result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
|
||||
if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_))
|
||||
result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
|
||||
if (is_message_invalid(answer_message_type))
|
||||
@ -172,9 +207,9 @@ haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t
|
||||
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_);
|
||||
#endif
|
||||
if (this->protocol_phase_ > ProtocolPhases::IDLE) {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
} else {
|
||||
this->set_phase_(ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
}
|
||||
return haier_protocol::HandlerError::HANDLER_OK;
|
||||
}
|
||||
@ -183,8 +218,8 @@ void HaierClimateBase::setup() {
|
||||
ESP_LOGI(TAG, "Haier initialization...");
|
||||
// Set timestamp here to give AC time to boot
|
||||
this->last_request_timestamp_ = std::chrono::steady_clock::now();
|
||||
this->set_phase_(ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_answers_handlers();
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_handlers();
|
||||
this->haier_protocol_.set_default_timeout_handler(
|
||||
std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
|
||||
}
|
||||
@ -212,7 +247,7 @@ void HaierClimateBase::loop() {
|
||||
this->set_force_send_control_(false);
|
||||
if (this->hvac_settings_.valid)
|
||||
this->hvac_settings_.reset();
|
||||
this->set_phase_(ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
return;
|
||||
} else {
|
||||
// No need to reset protocol if we didn't pass initialization phase
|
||||
@ -229,7 +264,7 @@ void HaierClimateBase::loop() {
|
||||
this->process_pending_action();
|
||||
} else if (this->hvac_settings_.valid || this->force_send_control_) {
|
||||
ESP_LOGV(TAG, "Control packet is pending...");
|
||||
this->set_phase_(ProtocolPhases::SENDING_CONTROL);
|
||||
this->set_phase(ProtocolPhases::SENDING_CONTROL);
|
||||
}
|
||||
}
|
||||
this->process_phase(now);
|
||||
@ -243,10 +278,10 @@ void HaierClimateBase::process_pending_action() {
|
||||
}
|
||||
switch (request) {
|
||||
case ActionRequest::TURN_POWER_ON:
|
||||
this->set_phase_(ProtocolPhases::SENDING_POWER_ON_COMMAND);
|
||||
this->set_phase(ProtocolPhases::SENDING_POWER_ON_COMMAND);
|
||||
break;
|
||||
case ActionRequest::TURN_POWER_OFF:
|
||||
this->set_phase_(ProtocolPhases::SENDING_POWER_OFF_COMMAND);
|
||||
this->set_phase(ProtocolPhases::SENDING_POWER_OFF_COMMAND);
|
||||
break;
|
||||
case ActionRequest::TOGGLE_POWER:
|
||||
case ActionRequest::NO_ACTION:
|
||||
@ -303,7 +338,11 @@ void HaierClimateBase::set_force_send_control_(bool status) {
|
||||
}
|
||||
|
||||
void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc) {
|
||||
this->haier_protocol_.send_message(command, 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();
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ class HaierClimateBase : public esphome::Component,
|
||||
void reset_protocol() { this->reset_protocol_request_ = true; };
|
||||
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_presets(const std::set<esphome::climate::ClimatePreset> &presets);
|
||||
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;
|
||||
@ -52,39 +53,41 @@ class HaierClimateBase : public esphome::Component,
|
||||
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);
|
||||
|
||||
protected:
|
||||
enum class ProtocolPhases {
|
||||
UNKNOWN = -1,
|
||||
// INITIALIZATION
|
||||
SENDING_INIT_1 = 0,
|
||||
WAITING_ANSWER_INIT_1 = 1,
|
||||
WAITING_INIT_1_ANSWER = 1,
|
||||
SENDING_INIT_2 = 2,
|
||||
WAITING_ANSWER_INIT_2 = 3,
|
||||
WAITING_INIT_2_ANSWER = 3,
|
||||
SENDING_FIRST_STATUS_REQUEST = 4,
|
||||
WAITING_FIRST_STATUS_ANSWER = 5,
|
||||
SENDING_ALARM_STATUS_REQUEST = 6,
|
||||
WAITING_ALARM_STATUS_ANSWER = 7,
|
||||
// FUNCTIONAL STATE
|
||||
IDLE = 8,
|
||||
SENDING_STATUS_REQUEST = 9,
|
||||
WAITING_STATUS_ANSWER = 10,
|
||||
SENDING_UPDATE_SIGNAL_REQUEST = 11,
|
||||
WAITING_UPDATE_SIGNAL_ANSWER = 12,
|
||||
SENDING_SIGNAL_LEVEL = 13,
|
||||
WAITING_SIGNAL_LEVEL_ANSWER = 14,
|
||||
SENDING_CONTROL = 15,
|
||||
WAITING_CONTROL_ANSWER = 16,
|
||||
SENDING_POWER_ON_COMMAND = 17,
|
||||
WAITING_POWER_ON_ANSWER = 18,
|
||||
SENDING_POWER_OFF_COMMAND = 19,
|
||||
WAITING_POWER_OFF_ANSWER = 20,
|
||||
SENDING_STATUS_REQUEST = 10,
|
||||
WAITING_STATUS_ANSWER = 11,
|
||||
SENDING_UPDATE_SIGNAL_REQUEST = 12,
|
||||
WAITING_UPDATE_SIGNAL_ANSWER = 13,
|
||||
SENDING_SIGNAL_LEVEL = 14,
|
||||
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
|
||||
};
|
||||
#if (HAIER_LOG_LEVEL > 4)
|
||||
const char *phase_to_string_(ProtocolPhases phase);
|
||||
#endif
|
||||
virtual void set_answers_handlers() = 0;
|
||||
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;
|
||||
virtual bool is_message_invalid(uint8_t message_type) = 0;
|
||||
@ -99,14 +102,17 @@ class HaierClimateBase : public esphome::Component,
|
||||
// Helper functions
|
||||
void set_force_send_control_(bool status);
|
||||
void send_message_(const haier_protocol::HaierMessage &command, bool use_crc);
|
||||
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,
|
||||
size_t timeout);
|
||||
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_timeout_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_exceded_(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_(uint8_t message_type);
|
||||
#endif
|
||||
|
||||
struct HvacSettings {
|
||||
esphome::optional<esphome::climate::ClimateMode> mode;
|
||||
@ -136,6 +142,9 @@ class HaierClimateBase : public esphome::Component,
|
||||
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 control_request_timestamp_; // To send control message
|
||||
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
|
||||
|
@ -2,9 +2,6 @@
|
||||
#include <string>
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#ifdef USE_WIFI
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#endif
|
||||
#include "hon_climate.h"
|
||||
#include "hon_packet.h"
|
||||
|
||||
@ -58,14 +55,7 @@ HonClimate::HonClimate()
|
||||
hvac_functions_{false, false, false, false, false},
|
||||
use_crc_(hvac_functions_[2]),
|
||||
active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
outdoor_sensor_(nullptr),
|
||||
send_wifi_signal_(true) {
|
||||
this->traits_.set_supported_presets({
|
||||
climate::CLIMATE_PRESET_NONE,
|
||||
climate::CLIMATE_PRESET_ECO,
|
||||
climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_SLEEP,
|
||||
});
|
||||
outdoor_sensor_(nullptr) {
|
||||
this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
|
||||
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
|
||||
}
|
||||
@ -121,17 +111,22 @@ void HonClimate::start_steri_cleaning() {
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; }
|
||||
|
||||
haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type,
|
||||
const uint8_t *data, size_t data_size) {
|
||||
// Should check this before preprocess
|
||||
if (message_type == (uint8_t) hon_protocol::FrameType::INVALID) {
|
||||
ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
|
||||
"protocol instead of hOn");
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
return haier_protocol::HandlerError::INVALID_ANSWER;
|
||||
}
|
||||
haier_protocol::HandlerError result = this->answer_preprocess_(
|
||||
request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type,
|
||||
(uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_1);
|
||||
(uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_INIT_1_ANSWER);
|
||||
if (result == haier_protocol::HandlerError::HANDLER_OK) {
|
||||
if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
|
||||
// Wrong structure
|
||||
this->set_phase_(ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
||||
}
|
||||
// All OK
|
||||
@ -152,11 +147,11 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint
|
||||
this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
|
||||
this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
|
||||
this->hvac_hardware_info_available_ = true;
|
||||
this->set_phase_(ProtocolPhases::SENDING_INIT_2);
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_2);
|
||||
return result;
|
||||
} else {
|
||||
this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -165,13 +160,13 @@ haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t r
|
||||
const uint8_t *data, size_t data_size) {
|
||||
haier_protocol::HandlerError result = this->answer_preprocess_(
|
||||
request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type,
|
||||
(uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_2);
|
||||
(uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_INIT_2_ANSWER);
|
||||
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;
|
||||
} else {
|
||||
this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -185,8 +180,8 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u
|
||||
result = this->process_status_message_(data, data_size);
|
||||
if (result != haier_protocol::HandlerError::HANDLER_OK) {
|
||||
ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
|
||||
this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
} else {
|
||||
if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) {
|
||||
memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl));
|
||||
@ -196,13 +191,13 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u
|
||||
}
|
||||
if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) {
|
||||
ESP_LOGI(TAG, "First HVAC status received");
|
||||
this->set_phase_(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST);
|
||||
this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST);
|
||||
} else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) ||
|
||||
(this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) ||
|
||||
(this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
} else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
this->set_force_send_control_(false);
|
||||
if (this->hvac_settings_.valid)
|
||||
this->hvac_settings_.reset();
|
||||
@ -210,8 +205,8 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_INIT_1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -225,10 +220,10 @@ haier_protocol::HandlerError HonClimate::get_management_information_answer_handl
|
||||
message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE,
|
||||
ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER);
|
||||
if (result == haier_protocol::HandlerError::HANDLER_OK) {
|
||||
this->set_phase_(ProtocolPhases::SENDING_SIGNAL_LEVEL);
|
||||
this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL);
|
||||
return result;
|
||||
} else {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -239,7 +234,7 @@ haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(u
|
||||
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);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -248,24 +243,24 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_
|
||||
if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) {
|
||||
if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
|
||||
// Unexpected answer to request
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
return haier_protocol::HandlerError::UNSUPORTED_MESSAGE;
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
|
||||
}
|
||||
if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) {
|
||||
// Don't expect this answer now
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
|
||||
}
|
||||
memcpy(this->active_alarms_, data + 2, 8);
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
return haier_protocol::HandlerError::HANDLER_OK;
|
||||
} else {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
return haier_protocol::HandlerError::UNSUPORTED_MESSAGE;
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::set_answers_handlers() {
|
||||
void HonClimate::set_handlers() {
|
||||
// Set handlers
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
(uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION),
|
||||
@ -311,7 +306,7 @@ void HonClimate::dump_config() {
|
||||
void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
switch (this->protocol_phase_) {
|
||||
case ProtocolPhases::SENDING_INIT_1:
|
||||
if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) {
|
||||
if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) {
|
||||
this->hvac_hardware_info_available_ = false;
|
||||
// Indicate device capabilities:
|
||||
// bit 0 - if 1 module support interactive mode
|
||||
@ -323,24 +318,24 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
|
||||
(uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
|
||||
this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
|
||||
this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_1);
|
||||
this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_INIT_2:
|
||||
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);
|
||||
this->send_message_(DEVICEID_REQUEST, this->use_crc_);
|
||||
this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_2);
|
||||
this->set_phase(ProtocolPhases::WAITING_INIT_2_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
||||
case ProtocolPhases::SENDING_STATUS_REQUEST:
|
||||
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
|
||||
static const haier_protocol::HaierMessage STATUS_REQUEST(
|
||||
(uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcomandsControl::GET_USER_DATA);
|
||||
(uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
|
||||
this->send_message_(STATUS_REQUEST, this->use_crc_);
|
||||
this->last_status_request_ = now;
|
||||
this->set_phase_((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1));
|
||||
this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1));
|
||||
}
|
||||
break;
|
||||
#ifdef USE_WIFI
|
||||
@ -350,26 +345,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
(uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
|
||||
this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
|
||||
this->last_signal_request_ = now;
|
||||
this->set_phase_(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER);
|
||||
this->set_phase(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_SIGNAL_LEVEL:
|
||||
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
|
||||
static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
if (wifi::global_wifi_component->is_connected()) {
|
||||
wifi_status_data[1] = 0;
|
||||
int8_t rssi = wifi::global_wifi_component->wifi_rssi();
|
||||
wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f);
|
||||
ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "WiFi is not connected");
|
||||
wifi_status_data[1] = 1;
|
||||
wifi_status_data[3] = 0;
|
||||
}
|
||||
haier_protocol::HaierMessage wifi_status_request((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS,
|
||||
wifi_status_data, sizeof(wifi_status_data));
|
||||
this->send_message_(wifi_status_request, this->use_crc_);
|
||||
this->set_phase_(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
|
||||
this->send_message_(this->get_wifi_signal_message_((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS),
|
||||
this->use_crc_);
|
||||
this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER:
|
||||
@ -380,7 +363,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
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;
|
||||
#endif
|
||||
case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
|
||||
@ -388,7 +371,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(
|
||||
(uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS);
|
||||
this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
|
||||
this->set_phase_(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER);
|
||||
this->set_phase(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_CONTROL:
|
||||
@ -403,12 +386,12 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
this->hvac_settings_.reset();
|
||||
this->forced_request_status_ = true;
|
||||
this->forced_publish_ = true;
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
} else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
|
||||
haier_protocol::HaierMessage control_message = get_control_message();
|
||||
this->send_message_(control_message, this->use_crc_);
|
||||
ESP_LOGI(TAG, "Control packet sent");
|
||||
this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER);
|
||||
this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_POWER_ON_COMMAND:
|
||||
@ -418,17 +401,17 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND)
|
||||
pwr_cmd_buf[1] = 0x01;
|
||||
haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL,
|
||||
((uint16_t) hon_protocol::SubcomandsControl::SET_SINGLE_PARAMETER) + 1,
|
||||
((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
|
||||
pwr_cmd_buf, sizeof(pwr_cmd_buf));
|
||||
this->send_message_(power_cmd, this->use_crc_);
|
||||
this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND
|
||||
? ProtocolPhases::WAITING_POWER_ON_ANSWER
|
||||
: ProtocolPhases::WAITING_POWER_OFF_ANSWER);
|
||||
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_ANSWER_INIT_1:
|
||||
case ProtocolPhases::WAITING_ANSWER_INIT_2:
|
||||
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:
|
||||
@ -438,14 +421,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
break;
|
||||
case ProtocolPhases::IDLE: {
|
||||
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;
|
||||
}
|
||||
#ifdef USE_WIFI
|
||||
else if (this->send_wifi_signal_ &&
|
||||
(std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
|
||||
SIGNAL_LEVEL_UPDATE_INTERVAL_MS))
|
||||
this->set_phase_(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
|
||||
this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
|
||||
#endif
|
||||
} break;
|
||||
default:
|
||||
@ -456,7 +439,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
#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;
|
||||
}
|
||||
}
|
||||
@ -551,11 +534,12 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
}
|
||||
}
|
||||
if (climate_control.target_temperature.has_value()) {
|
||||
out_data->set_point =
|
||||
climate_control.target_temperature.value() - 16; // set the temperature at our offset, subtract 16.
|
||||
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->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
|
||||
}
|
||||
if (out_data->ac_power == 0) {
|
||||
// If AC is off - no presets alowed
|
||||
// If AC is off - no presets allowed
|
||||
out_data->quiet_mode = 0;
|
||||
out_data->fast_mode = 0;
|
||||
out_data->sleep_mode = 0;
|
||||
@ -631,7 +615,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
break;
|
||||
}
|
||||
return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcomandsControl::SET_GROUP_PARAMETERS,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
||||
control_out_buffer, sizeof(hon_protocol::HaierPacketControl));
|
||||
}
|
||||
|
||||
@ -669,7 +653,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
{
|
||||
// Target temperature
|
||||
float old_target_temperature = this->target_temperature;
|
||||
this->target_temperature = packet.control.set_point + 16.0f;
|
||||
this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
|
||||
should_publish = should_publish || (old_target_temperature != this->target_temperature);
|
||||
}
|
||||
{
|
||||
@ -747,7 +731,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
if (new_cleaning != this->cleaning_status_) {
|
||||
ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
|
||||
if (new_cleaning == CleaningState::NO_CLEANING) {
|
||||
// Turnuin AC off after cleaning
|
||||
// Turning AC off after cleaning
|
||||
this->action_request_ = ActionRequest::TURN_POWER_OFF;
|
||||
}
|
||||
this->cleaning_status_ = new_cleaning;
|
||||
@ -837,7 +821,7 @@ void HonClimate::process_pending_action() {
|
||||
case ActionRequest::START_SELF_CLEAN:
|
||||
case ActionRequest::START_STERI_CLEAN:
|
||||
// Will reset action with control message sending
|
||||
this->set_phase_(ProtocolPhases::SENDING_CONTROL);
|
||||
this->set_phase(ProtocolPhases::SENDING_CONTROL);
|
||||
break;
|
||||
default:
|
||||
HaierClimateBase::process_pending_action();
|
||||
|
@ -48,10 +48,9 @@ class HonClimate : public HaierClimateBase {
|
||||
CleaningState get_cleaning_status() const;
|
||||
void start_self_cleaning();
|
||||
void start_steri_cleaning();
|
||||
void set_send_wifi(bool send_wifi);
|
||||
|
||||
protected:
|
||||
void set_answers_handlers() override;
|
||||
void set_handlers() override;
|
||||
void process_phase(std::chrono::steady_clock::time_point now) override;
|
||||
haier_protocol::HaierMessage get_control_message() override;
|
||||
bool is_message_invalid(uint8_t message_type) override;
|
||||
@ -87,8 +86,6 @@ class HonClimate : public HaierClimateBase {
|
||||
bool &use_crc_;
|
||||
uint8_t active_alarms_[8];
|
||||
esphome::sensor::Sensor *outdoor_sensor_;
|
||||
bool send_wifi_signal_;
|
||||
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
|
||||
};
|
||||
|
||||
} // namespace haier
|
||||
|
@ -53,12 +53,12 @@ struct HaierPacketControl {
|
||||
// 13
|
||||
uint8_t : 8;
|
||||
// 14
|
||||
uint8_t ten_degree : 1; // 10 degree status
|
||||
uint8_t display_status : 1; // If 0 disables AC's display
|
||||
uint8_t half_degree : 1; // Use half degree
|
||||
uint8_t intelegence_status : 1; // Intelligence status
|
||||
uint8_t pmv_status : 1; // Comfort/PMV status
|
||||
uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius
|
||||
uint8_t ten_degree : 1; // 10 degree status
|
||||
uint8_t display_status : 1; // If 0 disables AC's display
|
||||
uint8_t half_degree : 1; // Use half degree
|
||||
uint8_t intelligence_status : 1; // Intelligence status
|
||||
uint8_t pmv_status : 1; // Comfort/PMV status
|
||||
uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius
|
||||
uint8_t : 1;
|
||||
uint8_t steri_clean : 1;
|
||||
// 15
|
||||
@ -153,7 +153,7 @@ enum class FrameType : uint8_t {
|
||||
// <-> device, required)
|
||||
REPORT = 0x06, // Report frame (module <-> device, interactive, required)
|
||||
STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required)
|
||||
SYSTEM_DOWNLIK = 0x11, // System downlink frame (module -> device, optional)
|
||||
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)
|
||||
@ -210,7 +210,7 @@ enum class FrameType : uint8_t {
|
||||
WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional)
|
||||
};
|
||||
|
||||
enum class SubcomandsControl : uint16_t {
|
||||
enum class SubcommandsControl : uint16_t {
|
||||
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_BIG_DATA = 0x4DFE, // Request big data information from device (packet content: None)
|
||||
|
@ -11,15 +11,10 @@ namespace esphome {
|
||||
namespace haier {
|
||||
|
||||
static const char *const TAG = "haier.climate";
|
||||
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
|
||||
|
||||
Smartair2Climate::Smartair2Climate()
|
||||
: last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]) {
|
||||
this->traits_.set_supported_presets({
|
||||
climate::CLIMATE_PRESET_NONE,
|
||||
climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_COMFORT,
|
||||
});
|
||||
}
|
||||
: last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]), timeouts_counter_(0) {}
|
||||
|
||||
haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type,
|
||||
const uint8_t *data, size_t data_size) {
|
||||
@ -30,8 +25,8 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t
|
||||
result = this->process_status_message_(data, data_size);
|
||||
if (result != haier_protocol::HandlerError::HANDLER_OK) {
|
||||
ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
|
||||
this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
} else {
|
||||
if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) {
|
||||
memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl));
|
||||
@ -41,11 +36,11 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t
|
||||
}
|
||||
if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) {
|
||||
ESP_LOGI(TAG, "First HVAC status received");
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
} else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
} else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) {
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
this->set_force_send_control_(false);
|
||||
if (this->hvac_settings_.valid)
|
||||
this->hvac_settings_.reset();
|
||||
@ -53,17 +48,82 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE
|
||||
: ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
void Smartair2Climate::set_answers_handlers() {
|
||||
haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(uint8_t request_type,
|
||||
uint8_t message_type,
|
||||
const uint8_t *data,
|
||||
size_t data_size) {
|
||||
if (request_type != (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION)
|
||||
return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
|
||||
if (ProtocolPhases::WAITING_INIT_1_ANSWER != this->protocol_phase_)
|
||||
return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
|
||||
// Invalid packet is expected answer
|
||||
if ((message_type == (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) &&
|
||||
((data[37] & 0x04) != 0)) {
|
||||
ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol "
|
||||
"instead of smartAir2");
|
||||
}
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_2);
|
||||
return haier_protocol::HandlerError::HANDLER_OK;
|
||||
}
|
||||
|
||||
haier_protocol::HandlerError Smartair2Climate::report_network_status_answer_handler_(uint8_t request_type,
|
||||
uint8_t 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)
|
||||
return HaierClimateBase::timeout_default_handler_(message_type);
|
||||
this->timeouts_counter_++;
|
||||
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %d, timeout counter %d", message_type,
|
||||
(int) this->protocol_phase_, this->timeouts_counter_);
|
||||
if (this->timeouts_counter_ >= 3) {
|
||||
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
|
||||
if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST)
|
||||
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;
|
||||
}
|
||||
|
||||
void Smartair2Climate::set_handlers() {
|
||||
// Set handlers
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
(uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION),
|
||||
std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
(uint8_t) (smartair2_protocol::FrameType::CONTROL),
|
||||
std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
(uint8_t) (smartair2_protocol::FrameType::REPORT_NETWORK_STATUS),
|
||||
std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
this->haier_protocol_.set_timeout_handler(
|
||||
(uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_ID),
|
||||
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() {
|
||||
@ -74,39 +134,62 @@ void Smartair2Climate::dump_config() {
|
||||
void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
switch (this->protocol_phase_) {
|
||||
case ProtocolPhases::SENDING_INIT_1:
|
||||
this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
break;
|
||||
case ProtocolPhases::WAITING_ANSWER_INIT_1:
|
||||
case ProtocolPhases::SENDING_INIT_2:
|
||||
case ProtocolPhases::WAITING_ANSWER_INIT_2:
|
||||
case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
|
||||
case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER:
|
||||
this->set_phase_(ProtocolPhases::SENDING_INIT_1);
|
||||
break;
|
||||
case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
|
||||
case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER:
|
||||
case ProtocolPhases::SENDING_SIGNAL_LEVEL:
|
||||
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
|
||||
this->set_phase_(ProtocolPhases::IDLE);
|
||||
break;
|
||||
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
||||
if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) {
|
||||
static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL,
|
||||
0x4D01);
|
||||
this->send_message_(STATUS_REQUEST, false);
|
||||
this->last_status_request_ = now;
|
||||
this->set_phase_(ProtocolPhases::WAITING_FIRST_STATUS_ANSWER);
|
||||
if (this->can_send_message() &&
|
||||
(((this->timeouts_counter_ == 0) && (this->is_protocol_initialisation_interval_exceeded_(now))) ||
|
||||
((this->timeouts_counter_ > 0) && (this->is_message_interval_exceeded_(now))))) {
|
||||
// Indicate device capabilities:
|
||||
// bit 0 - if 1 module support interactive mode
|
||||
// bit 1 - if 1 module support controller-device mode
|
||||
// bit 2 - if 1 module support crc
|
||||
// bit 3 - if 1 module support multiple devices
|
||||
// bit 4..bit 15 - not used
|
||||
uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
|
||||
static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
|
||||
(uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities,
|
||||
sizeof(module_capabilities));
|
||||
this->send_message_(DEVICE_VERSION_REQUEST, false);
|
||||
this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_INIT_2:
|
||||
case ProtocolPhases::WAITING_INIT_2_ANSWER:
|
||||
this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
break;
|
||||
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
|
||||
case ProtocolPhases::SENDING_STATUS_REQUEST:
|
||||
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
|
||||
static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL,
|
||||
0x4D01);
|
||||
this->send_message_(STATUS_REQUEST, false);
|
||||
this->last_status_request_ = now;
|
||||
this->set_phase_(ProtocolPhases::WAITING_STATUS_ANSWER);
|
||||
this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1));
|
||||
}
|
||||
break;
|
||||
#ifdef USE_WIFI
|
||||
case ProtocolPhases::SENDING_SIGNAL_LEVEL:
|
||||
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
|
||||
this->send_message_(
|
||||
this->get_wifi_signal_message_((uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), false);
|
||||
this->last_signal_request_ = now;
|
||||
this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
|
||||
break;
|
||||
#else
|
||||
case ProtocolPhases::SENDING_SIGNAL_LEVEL:
|
||||
case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER:
|
||||
this->set_phase(ProtocolPhases::IDLE);
|
||||
break;
|
||||
#endif
|
||||
case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
|
||||
case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER:
|
||||
this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL);
|
||||
break;
|
||||
case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
|
||||
case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER:
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
break;
|
||||
case ProtocolPhases::SENDING_CONTROL:
|
||||
if (this->first_control_attempt_) {
|
||||
this->control_request_timestamp_ = now;
|
||||
@ -119,14 +202,14 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
|
||||
this->hvac_settings_.reset();
|
||||
this->forced_request_status_ = true;
|
||||
this->forced_publish_ = true;
|
||||
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);
|
||||
this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER);
|
||||
}
|
||||
break;
|
||||
case ProtocolPhases::SENDING_POWER_ON_COMMAND:
|
||||
@ -136,11 +219,12 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
|
||||
(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);
|
||||
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:
|
||||
@ -149,14 +233,25 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
|
||||
break;
|
||||
case ProtocolPhases::IDLE: {
|
||||
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;
|
||||
}
|
||||
#ifdef USE_WIFI
|
||||
else if (this->send_wifi_signal_ &&
|
||||
(std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
|
||||
SIGNAL_LEVEL_UPDATE_INTERVAL_MS))
|
||||
this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
|
||||
#endif
|
||||
} break;
|
||||
default:
|
||||
// Shouldn't get here
|
||||
#if (HAIER_LOG_LEVEL > 4)
|
||||
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
|
||||
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_);
|
||||
this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
|
||||
#endif
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -256,11 +351,12 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
|
||||
}
|
||||
}
|
||||
if (climate_control.target_temperature.has_value()) {
|
||||
out_data->set_point =
|
||||
climate_control.target_temperature.value() - 16; // set the temperature at our offset, subtract 16.
|
||||
float target_temp = climate_control.target_temperature.value();
|
||||
out_data->set_point = target_temp - 16; // set the temperature with offset 16
|
||||
out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
|
||||
}
|
||||
if (out_data->ac_power == 0) {
|
||||
// If AC is off - no presets alowed
|
||||
// If AC is off - no presets allowed
|
||||
out_data->turbo_mode = 0;
|
||||
out_data->quiet_mode = 0;
|
||||
} else if (climate_control.preset.has_value()) {
|
||||
@ -312,7 +408,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
|
||||
{
|
||||
// Target temperature
|
||||
float old_target_temperature = this->target_temperature;
|
||||
this->target_temperature = packet.control.set_point + 16.0f;
|
||||
this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
|
||||
should_publish = should_publish || (old_target_temperature != this->target_temperature);
|
||||
}
|
||||
{
|
||||
@ -333,7 +429,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
|
||||
}
|
||||
switch (packet.control.fan_mode) {
|
||||
case (uint8_t) smartair2_protocol::FanMode::FAN_AUTO:
|
||||
// Somtimes AC reports in fan only mode that fan speed is auto
|
||||
// Sometimes AC reports in fan only mode that fan speed is auto
|
||||
// but never accept this value back
|
||||
if (packet.control.ac_mode != (uint8_t) smartair2_protocol::ConditioningMode::FAN) {
|
||||
this->fan_mode = CLIMATE_FAN_AUTO;
|
||||
@ -453,5 +549,15 @@ bool Smartair2Climate::is_message_invalid(uint8_t message_type) {
|
||||
return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID;
|
||||
}
|
||||
|
||||
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 esphome
|
||||
|
@ -15,16 +15,25 @@ class Smartair2Climate : public HaierClimateBase {
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void set_answers_handlers() override;
|
||||
void set_handlers() override;
|
||||
void process_phase(std::chrono::steady_clock::time_point now) override;
|
||||
haier_protocol::HaierMessage get_control_message() override;
|
||||
bool is_message_invalid(uint8_t message_type) override;
|
||||
// Answers handlers
|
||||
void set_phase(HaierClimateBase::ProtocolPhases phase) override;
|
||||
// Answer and timeout handlers
|
||||
haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data,
|
||||
size_t data_size);
|
||||
haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type,
|
||||
const uint8_t *data, size_t data_size);
|
||||
haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type,
|
||||
const uint8_t *data, size_t data_size);
|
||||
haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type,
|
||||
const uint8_t *data, size_t data_size);
|
||||
haier_protocol::HandlerError initial_messages_timeout_handler_(uint8_t message_type);
|
||||
// Helper functions
|
||||
haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size);
|
||||
std::unique_ptr<uint8_t[]> last_status_message_;
|
||||
unsigned int timeouts_counter_;
|
||||
};
|
||||
|
||||
} // namespace haier
|
||||
|
@ -53,8 +53,8 @@ struct HaierPacketControl {
|
||||
uint8_t : 2;
|
||||
uint8_t health_mode : 1; // Health mode on or off
|
||||
uint8_t compressor : 1; // Compressor on or off ???
|
||||
uint8_t : 1;
|
||||
uint8_t ten_degree : 1; // 10 degree status (only work in heat mode)
|
||||
uint8_t half_degree : 1; // Use half degree
|
||||
uint8_t ten_degree : 1; // 10 degree status (only work in heat mode)
|
||||
uint8_t : 0;
|
||||
// 28
|
||||
uint8_t : 8;
|
||||
@ -88,6 +88,9 @@ enum class FrameType : uint8_t {
|
||||
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,
|
||||
};
|
||||
|
@ -51,7 +51,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
|
||||
#endif
|
||||
void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
|
||||
|
||||
void start();
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
size_t play(const uint8_t *data, size_t length) override;
|
||||
|
@ -26,7 +26,7 @@ void PCA9554Component::setup() {
|
||||
this->config_mask_ = 0;
|
||||
// Invert mask as the part sees a 1 as an input
|
||||
this->write_register_(CONFIG_REG, ~this->config_mask_);
|
||||
// All ouputs low
|
||||
// All outputs low
|
||||
this->output_mask_ = 0;
|
||||
this->write_register_(OUTPUT_REG, this->output_mask_);
|
||||
// Read the inputs
|
||||
@ -34,6 +34,14 @@ void PCA9554Component::setup() {
|
||||
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
||||
this->status_has_error());
|
||||
}
|
||||
|
||||
void PCA9554Component::loop() {
|
||||
// The read_inputs_() method will cache the input values from the chip.
|
||||
this->read_inputs_();
|
||||
// Clear all the previously read flags.
|
||||
this->was_previously_read_ = 0x00;
|
||||
}
|
||||
|
||||
void PCA9554Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PCA9554:");
|
||||
LOG_I2C_DEVICE(this)
|
||||
@ -43,7 +51,16 @@ void PCA9554Component::dump_config() {
|
||||
}
|
||||
|
||||
bool PCA9554Component::digital_read(uint8_t pin) {
|
||||
this->read_inputs_();
|
||||
// Note: We want to try and avoid doing any I2C bus read transactions here
|
||||
// to conserve I2C bus bandwidth. So what we do is check to see if we
|
||||
// have seen a read during the time esphome is running this loop. If we have,
|
||||
// we do an I2C bus transaction to get the latest value. If we haven't
|
||||
// we return a cached value which was read at the time loop() was called.
|
||||
if (this->was_previously_read_ & (1 << pin))
|
||||
this->read_inputs_(); // Force a read of a new value
|
||||
// Indicate we saw a read request for this pin in case a
|
||||
// read happens later in the same loop.
|
||||
this->was_previously_read_ |= (1 << pin);
|
||||
return this->input_mask_ & (1 << pin);
|
||||
}
|
||||
|
||||
@ -98,6 +115,10 @@ bool PCA9554Component::write_register_(uint8_t reg, uint8_t value) {
|
||||
|
||||
float PCA9554Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
// Run our loop() method very early in the loop, so that we cache read values before
|
||||
// before other components call our digital_read() method.
|
||||
float PCA9554Component::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
|
||||
void PCA9554GPIOPin::setup() { pin_mode(flags_); }
|
||||
void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
|
@ -13,6 +13,8 @@ class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
|
||||
/// Check i2c availability and setup masks
|
||||
void setup() override;
|
||||
/// Poll for input changes periodically
|
||||
void loop() override;
|
||||
/// Helper function to read the value of a pin.
|
||||
bool digital_read(uint8_t pin);
|
||||
/// Helper function to write the value of a pin.
|
||||
@ -22,6 +24,8 @@ class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
float get_loop_priority() const override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
@ -35,6 +39,8 @@ class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
uint8_t output_mask_{0x00};
|
||||
/// The state of the actual input pin states - 1 means HIGH, 0 means LOW
|
||||
uint8_t input_mask_{0x00};
|
||||
/// Flags to check if read previously during this loop
|
||||
uint8_t was_previously_read_ = {0x00};
|
||||
/// Storage for last I2C error seen
|
||||
esphome::i2c::ErrorCode last_error_;
|
||||
};
|
||||
|
@ -1488,11 +1488,9 @@ MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_p
|
||||
MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase)
|
||||
MIDEA_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CODE): cv.templatable(
|
||||
cv.All(
|
||||
[cv.Any(cv.hex_uint8_t, cv.uint8_t)],
|
||||
cv.Length(min=5, max=5),
|
||||
)
|
||||
cv.Required(CONF_CODE): cv.All(
|
||||
[cv.Any(cv.hex_uint8_t, cv.uint8_t)],
|
||||
cv.Length(min=5, max=5),
|
||||
),
|
||||
}
|
||||
)
|
||||
@ -1519,12 +1517,10 @@ def midea_dumper(var, config):
|
||||
MIDEA_SCHEMA,
|
||||
)
|
||||
async def midea_action(var, config, args):
|
||||
code_ = config[CONF_CODE]
|
||||
if cg.is_template(code_):
|
||||
template_ = await cg.templatable(code_, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_code_template(template_))
|
||||
else:
|
||||
cg.add(var.set_code_static(code_))
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_CODE], args, cg.std_vector.template(cg.uint8)
|
||||
)
|
||||
cg.add(var.set_code(template_))
|
||||
|
||||
|
||||
# AEHA
|
||||
@ -1569,4 +1565,7 @@ def aeha_dumper(var, config):
|
||||
async def aeha_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
|
||||
cg.add(var.set_address(template_))
|
||||
cg.add(var.set_data(config[CONF_DATA]))
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_DATA], args, cg.std_vector.template(cg.uint8)
|
||||
)
|
||||
cg.add(var.set_data(template_))
|
||||
|
@ -1,11 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "remote_base.h"
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
@ -84,23 +84,13 @@ using MideaDumper = RemoteReceiverDumper<MideaProtocol, MideaData>;
|
||||
|
||||
template<typename... Ts> class MideaAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, code)
|
||||
void set_code_static(std::vector<uint8_t> code) { code_static_ = std::move(code); }
|
||||
void set_code_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->code_func_ = func; }
|
||||
void set_code(std::initializer_list<uint8_t> code) { this->code_ = code; }
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
MideaData data;
|
||||
if (!this->code_static_.empty()) {
|
||||
data = MideaData(this->code_static_);
|
||||
} else {
|
||||
data = MideaData(this->code_func_(x...));
|
||||
}
|
||||
MideaData data(this->code_.value(x...));
|
||||
data.finalize();
|
||||
MideaProtocol().encode(dst, data);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<std::vector<uint8_t>(Ts...)> code_func_{};
|
||||
std::vector<uint8_t> code_static_{};
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
|
@ -13,8 +13,9 @@ enum State : uint8_t {
|
||||
class Speaker {
|
||||
public:
|
||||
virtual size_t play(const uint8_t *data, size_t length) = 0;
|
||||
virtual size_t play(const std::vector<uint8_t> &data) { return this->play(data.data(), data.size()); }
|
||||
size_t play(const std::vector<uint8_t> &data) { return this->play(data.data(), data.size()); }
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
bool is_running() const { return this->state_ == STATE_RUNNING; }
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include "tuya.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
|
||||
#ifdef USE_WIFI
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
@ -246,14 +246,18 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||
#ifdef USE_TIME
|
||||
if (this->time_id_.has_value()) {
|
||||
this->send_local_time_();
|
||||
auto *time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
||||
} else {
|
||||
|
||||
if (!this->time_sync_callback_registered_) {
|
||||
// tuya mcu supports time, so we let them know when our time changed
|
||||
auto *time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
|
||||
this->time_sync_callback_registered_ = true;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured");
|
||||
}
|
||||
#else
|
||||
ESP_LOGE(TAG, "LOCAL_TIME_QUERY is not handled");
|
||||
#endif
|
||||
break;
|
||||
case TuyaCommandType::VACUUM_MAP_UPLOAD:
|
||||
this->send_command_(
|
||||
|
@ -130,6 +130,7 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_TIME
|
||||
void send_local_time_();
|
||||
optional<time::RealTimeClock *> time_id_{};
|
||||
bool time_sync_callback_registered_{false};
|
||||
#endif
|
||||
TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT;
|
||||
bool init_failed_{false};
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2023.8.0b1"
|
||||
__version__ = "2023.8.0b2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@ -144,7 +144,14 @@ def resolve_ip_address(host):
|
||||
|
||||
|
||||
def get_bool_env(var, default=False):
|
||||
return bool(os.getenv(var, default))
|
||||
value = os.getenv(var, default)
|
||||
if isinstance(value, str):
|
||||
value = value.lower()
|
||||
if value in ["1", "true"]:
|
||||
return True
|
||||
if value in ["0", "false"]:
|
||||
return False
|
||||
return bool(value)
|
||||
|
||||
|
||||
def get_str_env(var, default=None):
|
||||
|
@ -39,7 +39,7 @@ lib_deps =
|
||||
bblanchon/ArduinoJson@6.18.5 ; json
|
||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||
pavlodn/HaierProtocol@0.9.18 ; haier
|
||||
pavlodn/HaierProtocol@0.9.20 ; haier
|
||||
; This is using the repository until a new release is published to PlatformIO
|
||||
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
||||
build_flags =
|
||||
|
@ -2333,6 +2333,8 @@ switch:
|
||||
second: !lambda "return 0xB21F98;"
|
||||
- remote_transmitter.transmit_midea:
|
||||
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
|
||||
- remote_transmitter.transmit_midea:
|
||||
code: !lambda "return {0xA2, 0x08, 0xFF, 0xFF, 0xFF};"
|
||||
- platform: gpio
|
||||
name: "MCP23S08 Pin #0"
|
||||
pin:
|
||||
|
@ -108,6 +108,10 @@ def test_is_ip_address__valid(value):
|
||||
("FOO", None, False, False),
|
||||
("FOO", None, True, True),
|
||||
("FOO", "", False, False),
|
||||
("FOO", "False", False, False),
|
||||
("FOO", "True", False, True),
|
||||
("FOO", "FALSE", True, False),
|
||||
("FOO", "fAlSe", True, False),
|
||||
("FOO", "Yes", False, True),
|
||||
("FOO", "123", False, True),
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user