v.1 beta 1 initial commit

This commit is contained in:
GrKoR
2023-11-28 23:07:13 +03:00
parent 026a1d7cf2
commit 1b520d1515
19 changed files with 3604 additions and 4213 deletions

View File

@@ -0,0 +1,805 @@
#include "aircon.h"
#include "helpers.h"
#include "command_builder.h"
#include "frame_processor_manager.h"
#include "esphome/core/log.h"
namespace esphome
{
namespace aux_airconditioner
{
/*************************************************************************************************\
\*************************************************************************************************/
uint32_t Capabilities::normilize_packet_timeout(uint32_t timeout)
{
uint32_t result = timeout;
if (result > Capabilities::AC_PACKET_TIMEOUT_MAX)
result = Capabilities::AC_PACKET_TIMEOUT_MAX;
else if (result < Capabilities::AC_PACKET_TIMEOUT_MIN)
result = Capabilities::AC_PACKET_TIMEOUT_MIN;
return result;
}
float Capabilities::normilize_target_temperature(const float target_temperature)
{
float result = target_temperature;
if (result > Capabilities::AC_MAX_TEMPERATURE)
result = Capabilities::AC_MAX_TEMPERATURE;
else if (result < Capabilities::AC_MIN_TEMPERATURE)
result = Capabilities::AC_MIN_TEMPERATURE;
return result;
}
uint8_t Capabilities::normilize_inverter_power_limit(const uint8_t power_limit_value)
{
uint8_t result = power_limit_value;
if (result > Capabilities::AC_MAX_INVERTER_POWER_LIMIT)
result = Capabilities::AC_MAX_INVERTER_POWER_LIMIT;
else if (result < Capabilities::AC_MIN_INVERTER_POWER_LIMIT)
result = Capabilities::AC_MIN_INVERTER_POWER_LIMIT;
return result;
}
// **************************************************************************************************
const std::string Capabilities::AC_FIRMWARE_VERSION = "1.0.0 beta 1";
// **************************************************************************************************
// custom fan modes
const std::string Capabilities::CUSTOM_FAN_MODE_MUTE = "MUTE";
const std::string Capabilities::CUSTOM_FAN_MODE_TURBO = "TURBO";
// **************************************************************************************************
// custom presets
const std::string Capabilities::CUSTOM_PRESET_CLEAN = "CLEAN";
const std::string Capabilities::CUSTOM_PRESET_HEALTH = "HEALTH";
const std::string Capabilities::CUSTOM_PRESET_ANTIFUNGUS = "ANTIFUNGUS";
// **************************************************************************************************
// predefined default params
const float Capabilities::AC_MIN_TEMPERATURE = 16.0;
const float Capabilities::AC_MAX_TEMPERATURE = 32.0;
const float Capabilities::AC_TEMPERATURE_STEP_TARGET = 0.5;
const float Capabilities::AC_TEMPERATURE_STEP_CURRENT = 0.1;
const uint8_t Capabilities::AC_MIN_INVERTER_POWER_LIMIT = 30; // 30%
const uint8_t Capabilities::AC_MAX_INVERTER_POWER_LIMIT = 100; // 100%
const uint32_t Capabilities::AC_STATE_REQUEST_INTERVAL = 7000;
const uint32_t Capabilities::AC_CONNECTION_LOST_TIMEOUT = 4000;
const uint32_t Capabilities::AC_PACKET_TIMEOUT_MIN = 300;
const uint32_t Capabilities::AC_PACKET_TIMEOUT_MAX = 800;
// **************************************************************************************************
using esphome::helpers::update_property;
// **************************************************************************************************
void AirCon::_send_frame_from_tx_queue()
{
if (!this->is_hardware_connected() || !this->has_ping())
return;
if (this->_tx_frames.empty())
return;
Frame *frame = this->_tx_frames.front();
this->_tx_frames.pop();
frame->send(*this->_uart);
if (frame->get_frame_type() == FrameType::FRAME_TYPE_COMMAND)
{
_waiting_for_response_timer.reset();
_waiting_for_response_timer.set_callback([this](TimerInterface *timer)
{ ESP_LOGW(TAG, "Command response timeout!");
timer->stop();
this->set_receiver_callback(nullptr); });
switch (frame->get_value(8))
{
case 0x01: // command: set AC mode
{
uint8_t crc16_1 = 0, crc16_2 = 0;
frame->get_crc(crc16_1, crc16_2);
this->set_receiver_callback([this, crc16_1, crc16_2](Frame &frame)
{ if (this->_waiting_for_response_timer.is_enabled() &&
frame.get_frame_type() == FrameType::FRAME_TYPE_RESPONSE &&
frame.get_value(9) == 0x01)
{
// check the acknowledgement: should be equal to the command CRC
if (frame.get_value(10) != crc16_1 ||
frame.get_value(11) != crc16_2)
{
ESP_LOGW(TAG, "Command response acknowledgement error!");
}
this->_waiting_for_response_timer.stop();
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
this->set_receiver_callback(nullptr);
if (!this->_command_queue.empty() &&
this->_cmd_processor_state == command_processor_state_t::CMD_PROCESSOR_STATE_CMD_WAS_SENT)
{
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_POSTCHECK_DONE;
}
} });
_waiting_for_response_timer.start(this->get_packet_timeout());
break;
}
case 0x11: // command: request frame 11
{
this->set_receiver_callback([this](Frame &frame)
{ if (this->_waiting_for_response_timer.is_enabled() &&
frame.get_frame_type() == FrameType::FRAME_TYPE_RESPONSE &&
frame.get_value(9) == 0x11)
{
this->_waiting_for_response_timer.stop();
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
this->set_receiver_callback(nullptr);
if (!this->_command_queue.empty() &&
this->_cmd_processor_state == command_processor_state_t::CMD_PROCESSOR_STATE_WAITING_FOR_F11)
{
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_PRECHECK_DONE;
}
} });
_waiting_for_response_timer.start(this->get_packet_timeout());
break;
}
case 0x21: // command: request frame 21
{
this->set_receiver_callback([this](Frame &frame)
{ if (this->_waiting_for_response_timer.is_enabled() &&
frame.get_frame_type() == FrameType::FRAME_TYPE_RESPONSE &&
frame.get_value(9) == 0x21)
{
this->_waiting_for_response_timer.stop();
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
this->set_receiver_callback(nullptr);
} });
_waiting_for_response_timer.start(this->get_packet_timeout());
break;
}
default:
ESP_LOGW(TAG, "Unknown command: 0x%02X", frame->get_value(8));
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
break;
}
}
delete frame;
}
// **************************************************************************************************
void AirCon::_process_command_queue()
{
if (_command_queue.size() == 0)
return;
Frame frame;
ClimateCall &cmd = _command_queue.front();
switch (this->_cmd_processor_state)
{
case CMD_PROCESSOR_STATE_NOT_STARTED:
this->_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_REQUEST_11).fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_WAITING_FOR_F11;
break;
case CMD_PROCESSOR_STATE_WAITING_FOR_F11:
ESP_LOGW(TAG, "It should never have happened: processing the command with state 'CMD_PROCESSOR_STATE_WAITING_FOR_F11'");
break;
case CMD_PROCESSOR_STATE_PRECHECK_DONE:
this->_cmd_builder->init_new_command(cmd).fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_CMD_WAS_SENT;
break;
case CMD_PROCESSOR_STATE_CMD_WAS_SENT:
ESP_LOGW(TAG, "It should never have happened: processing the command with state 'CMD_PROCESSOR_STATE_CMD_WAS_SENT'");
break;
case CMD_PROCESSOR_STATE_POSTCHECK_DONE:
this->schedule_frame_to_send(*_frame_11_request);
this->schedule_frame_to_send(*_frame_2x_request);
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_NOT_STARTED;
_command_queue.pop();
break;
default:
ESP_LOGW(TAG, "unknown command state '0x%02X'!", this->_cmd_processor_state);
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_NOT_STARTED;
_command_queue.pop();
break;
}
}
// **************************************************************************************************
AirCon::AirCon()
{
_incoming_frame = new Frame;
_last_frame_11 = new Frame;
_last_frame_2x = new Frame;
_frame_processor_manager = new FrameProcessorManager;
_frame_processor_manager->set_aircon(*this);
_cmd_builder = new CommandBuilder(*this);
this->set_millis(&millis);
_timer_manager.set_millis_func(&millis);
_frame_11_request = new Frame;
this->_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_REQUEST_11).fill_frame_with_command(*_frame_11_request);
_frame_2x_request = new Frame;
this->_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_REQUEST_21).fill_frame_with_command(*_frame_2x_request);
_frame_ping_response = new Frame;
_frame_ping_response->append_data({Frame::get_start_byte(), 0x00, FrameType::FRAME_TYPE_PING, FrameDirection::FRAME_DIR_TO_AC, 0x01, 0x00, 0x08, 0x00});
_frame_ping_response->append_data({0x1C, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
_frame_ping_response->update_crc(true);
}
AirCon::~AirCon()
{
delete _cmd_builder;
delete _frame_processor_manager;
delete _incoming_frame;
delete _last_frame_11;
delete _last_frame_2x;
delete _frame_11_request;
delete _frame_2x_request;
delete _frame_ping_response;
}
// **************************************************************************************************
void AirCon::setup()
{
_traits.set_supports_current_temperature(true);
_traits.set_supports_two_point_target_temperature(false);
_traits.set_visual_min_temperature(Capabilities::AC_MIN_TEMPERATURE);
_traits.set_visual_max_temperature(Capabilities::AC_MAX_TEMPERATURE);
_traits.set_visual_current_temperature_step(Capabilities::AC_TEMPERATURE_STEP_CURRENT);
_traits.set_visual_target_temperature_step(Capabilities::AC_TEMPERATURE_STEP_TARGET);
/* + MINIMAL SET */
_traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF);
_traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY);
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO);
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW);
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM);
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH);
_traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF);
_traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);
// if the climate device supports reporting the active current action of the device with the action property.
//_traits.set_supports_action(this->_show_action);
_frame11_request_timer.set_callback([this](TimerInterface *timer)
{ this->schedule_frame_to_send(*this->_frame_11_request); });
_timer_manager.register_timer(_frame11_request_timer);
_frame11_request_timer.start(this->get_period());
_frame2x_request_timer.set_callback([this](TimerInterface *timer)
{ this->schedule_frame_to_send(*this->_frame_2x_request); });
_timer_manager.register_timer(_frame2x_request_timer);
_frame2x_request_timer.start(this->get_period());
_ping_timeout_timer.set_callback([this](TimerInterface *timer)
{ this->_has_ping = false;
ESP_LOGW(TAG, "Air conditioner connection lost!"); });
_timer_manager.register_timer(_ping_timeout_timer);
_ping_timeout_timer.start(Capabilities::AC_CONNECTION_LOST_TIMEOUT);
_timer_manager.register_timer(_waiting_for_response_timer);
_waiting_for_response_timer.stop();
// schedule initial requests
this->schedule_frame_to_send(*this->_frame_11_request);
this->schedule_frame_to_send(*this->_frame_2x_request);
}
// **************************************************************************************************
void AirCon::loop()
{
if (!this->is_hardware_connected())
return;
_timer_manager.task();
FrameState frame_state = _incoming_frame->get_frame_state();
switch (frame_state)
{
case FRAME_STATE_BLANK:
case FRAME_STATE_PARTIALLY_LOADED:
if (_uart->available() > 0)
{
_incoming_frame->load(*_uart);
}
else if (!this->_waiting_for_response_timer.is_enabled())
{
if (!this->_tx_frames.empty())
{
this->_send_frame_from_tx_queue();
}
else if (!this->_command_queue.empty())
{
this->_process_command_queue();
}
}
break;
case FRAME_STATE_ERROR:
_incoming_frame->set_frame_time(this->ms());
ESP_LOGW(TAG, "Incorrect frame! Frame state: %s, data: %s", _incoming_frame->state_to_string().c_str(), _incoming_frame->to_string(true).c_str());
_incoming_frame->clear();
break;
case FRAME_STATE_OK:
_incoming_frame->set_frame_time(this->ms());
ESP_LOGD(TAG, "%s", _incoming_frame->to_string(true).c_str());
this->_frame_processor_manager->process_frame(*_incoming_frame);
if (this->_receiver_callback != nullptr)
this->_receiver_callback(*_incoming_frame);
_incoming_frame->clear();
break;
default:
ESP_LOGW(TAG, "Unknown frame state: %d (0x%02X)", frame_state, frame_state);
break;
}
}
// **************************************************************************************************
void AirCon::dump_config()
{
ESP_LOGCONFIG(TAG, "AUX HVAC:");
ESP_LOGCONFIG(TAG, "firmware version: %s", Capabilities::AC_FIRMWARE_VERSION.c_str());
this->dump_traits_(TAG);
LOG_SENSOR(" ", "Vertical louver state", this->_sensor_vlouver_state);
LOG_BINARY_SENSOR(" ", "Display", this->_sensor_display_state);
LOG_BINARY_SENSOR(" ", "Defrost status", this->_sensor_defrost_state);
LOG_TEXT_SENSOR(" ", "Preset Reporter", this->_sensor_preset_reporter);
ESP_LOGCONFIG(TAG, " Temperatures:");
LOG_SENSOR(" ", "Indoor Ambient", this->_sensor_temperature_indoor_ambient);
LOG_SENSOR(" ", "Indoor Coil", this->_sensor_temperature_indoor_coil);
LOG_SENSOR(" ", "Outdoor Ambient", this->_sensor_temperature_outdoor_ambient);
LOG_SENSOR(" ", "Outdoor Condenser", this->_sensor_temperature_outdoor_condenser_middle);
LOG_SENSOR(" ", "Outdoor Defrost", this->_sensor_temperature_outdoor_defrost);
LOG_SENSOR(" ", "Outdoor Discharge", this->_sensor_temperature_outdoor_discharge);
LOG_SENSOR(" ", "Outdoor Suction", this->_sensor_temperature_outdoor_suction);
ESP_LOGCONFIG(TAG, " Inverter Power:");
LOG_SENSOR(" ", "Actual Value", this->_sensor_inverter_power_actual);
LOG_SENSOR(" ", "Limit Value", this->_sensor_inverter_power_limit_value);
LOG_BINARY_SENSOR(" ", "Limitation State", this->_sensor_inverter_power_limit_state);
};
// **************************************************************************************************
void AirCon::control(const esphome::climate::ClimateCall &call)
{
bool has_command = false;
// User requested mode change
if (call.get_mode().has_value())
{
ClimateMode mode = *call.get_mode();
update_property(this->mode, mode, has_command);
}
// User requested fan_mode change
if (call.get_fan_mode().has_value())
{
ClimateFanMode fanmode = *call.get_fan_mode();
update_property(this->fan_mode, fanmode, has_command);
}
else if (call.get_custom_fan_mode().has_value())
{
std::string customfanmode = *call.get_custom_fan_mode();
if ((customfanmode == Capabilities::CUSTOM_FAN_MODE_TURBO) ||
(customfanmode == Capabilities::CUSTOM_FAN_MODE_MUTE) ||
(customfanmode == ""))
{
update_property(this->custom_fan_mode, customfanmode, has_command);
}
}
// User selected preset
if (call.get_preset().has_value())
{
ClimatePreset preset = *call.get_preset();
update_property(this->preset, preset, has_command);
}
else if (call.get_custom_preset().has_value())
{
std::string custom_preset = *call.get_custom_preset();
if ((custom_preset == Capabilities::CUSTOM_PRESET_CLEAN) ||
(custom_preset == Capabilities::CUSTOM_PRESET_ANTIFUNGUS) ||
(custom_preset == Capabilities::CUSTOM_PRESET_HEALTH) ||
(custom_preset == ""))
{
update_property(this->custom_preset, custom_preset, has_command);
}
}
// User requested swing_mode change
if (call.get_swing_mode().has_value())
{
ClimateSwingMode swingmode = *call.get_swing_mode();
update_property(this->swing_mode, swingmode, has_command);
}
// User requested target temperature change
if (call.get_target_temperature().has_value())
{
// it isn't allowed in FAN mode
if (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)
update_property(this->target_temperature, *call.get_target_temperature(), has_command);
}
if (has_command)
{
this->schedule_command(call);
if (this->get_optimistic())
this->publish_all_states();
}
}
// **************************************************************************************************
void AirCon::action_display_off()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_display_state(false);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_display_on()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_display_state(true);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_swing()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_SWING_UPDOWN);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_stop()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_OFF);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_top_position()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_TOP);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_middle_above_position()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_MIDDLE_ABOVE);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_middle_position()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_MIDDLE);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_middle_below_position()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_MIDDLE_BELOW);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_bottom()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_BOTTOM);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_set_vlouver_position(vlouver_esphome_position_t position)
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(vlouver_frontend_to_ac_louver_V(position));
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_power_limitation_off()
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_inverter_power_limitation_state(false);
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::action_power_limitation_on(uint8_t limit)
{
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE)
.set_inverter_power_limitation_state(true)
.set_inverter_power_limitation_value(Capabilities::normilize_inverter_power_limit(limit));
Frame frame;
_cmd_builder->fill_frame_with_command(frame);
this->schedule_frame_to_send(frame);
}
// **************************************************************************************************
void AirCon::schedule_frame_to_send(const Frame &frame)
{
Frame *tx_frame = new Frame(frame);
_tx_frames.push(tx_frame);
}
// **************************************************************************************************
void AirCon::schedule_ping_response()
{
this->schedule_frame_to_send(*_frame_ping_response);
}
// **************************************************************************************************
void AirCon::schedule_command(const ClimateCall &cmd)
{
_command_queue.push(cmd);
}
// **************************************************************************************************
Frame &AirCon::get_last_frame_11()
{
return *(this->_last_frame_11);
}
// **************************************************************************************************
Frame &AirCon::get_last_frame_2x()
{
return *(this->_last_frame_2x);
}
// **************************************************************************************************
void AirCon::set_last_frame(const Frame &frame)
{
if (frame.get_frame_type() != FrameType::FRAME_TYPE_RESPONSE)
return;
if (frame.get_body_length() < 2) // filter out frames without CMD byte
return;
Frame *target_frame = nullptr;
if (frame.get_value(9) == 0x11)
{
target_frame = _last_frame_11;
}
else if (frame.get_value(9, 0b11110000) == 0x20)
{
target_frame = _last_frame_2x;
}
if (target_frame != nullptr)
{
target_frame->clear();
target_frame->append_data(frame.data(), frame.size(), true);
target_frame->set_frame_time(this->ms());
}
}
// **************************************************************************************************
void AirCon::_update_sensor_unit_of_measurement(Sensor *sensor)
{
if (sensor == nullptr)
return;
if (this->temperature_in_fahrenheit && sensor->get_unit_of_measurement() != "°F")
sensor->set_unit_of_measurement("°F");
else if (!this->temperature_in_fahrenheit && sensor->get_unit_of_measurement() != "°C")
sensor->set_unit_of_measurement("°C");
}
void AirCon::update_all_sensors_unit_of_measurement()
{
this->_update_sensor_unit_of_measurement(_sensor_temperature_indoor_ambient);
this->_update_sensor_unit_of_measurement(_sensor_temperature_indoor_coil);
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_condenser_middle);
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_ambient);
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_defrost);
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_discharge);
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_suction);
}
// **************************************************************************************************
template <typename T>
void publish_sensor_state(Sensor *sensor, optional<T> new_state)
{
if (sensor == nullptr)
return;
if (new_state.has_value() && !std::isnan((float)(new_state.value())))
{
if (sensor->get_raw_state() == (float)(new_state.value()))
return;
sensor->publish_state((float)(new_state.value()));
return;
}
if (std::isnan(sensor->get_raw_state()))
return;
sensor->publish_state(NAN);
}
// **************************************************************************************************
void publish_sensor_state(BinarySensor *sensor, optional<bool> new_state)
{
if (sensor == nullptr)
return;
if (!new_state.has_value())
return;
if (sensor->state == new_state.value())
return;
sensor->publish_state(new_state.value());
}
// **************************************************************************************************
void publish_sensor_state(TextSensor *sensor, optional<std::string> new_state)
{
if (sensor == nullptr)
return;
if (!new_state.has_value())
return;
if (sensor->get_raw_state() == new_state.value())
return;
sensor->publish_state(new_state.value());
}
// **************************************************************************************************
void AirCon::publish_all_states()
{
this->publish_state();
publish_sensor_state(_sensor_temperature_indoor_ambient, optional<float>(this->current_temperature));
publish_sensor_state(_sensor_temperature_indoor_coil, this->temperature_indoor_coil);
publish_sensor_state(_sensor_temperature_outdoor_condenser_middle, this->temperature_condenser_middle);
publish_sensor_state(_sensor_temperature_outdoor_ambient, this->temperature_outdoor_ambient);
publish_sensor_state(_sensor_temperature_outdoor_defrost, this->temperature_outdoor_defrost);
publish_sensor_state(_sensor_temperature_outdoor_discharge, this->temperature_outdoor_discharge);
publish_sensor_state(_sensor_temperature_outdoor_suction, this->temperature_outdoor_suction);
publish_sensor_state(_sensor_vlouver_state, optional<uint8_t>(this->get_current_vlouver_frontend_state()));
publish_sensor_state(_sensor_display_state, optional<bool>(this->display_enabled));
publish_sensor_state(_sensor_defrost_state, optional<bool>(this->defrost_enabled));
publish_sensor_state(_sensor_inverter_power_actual, this->inverter_power);
publish_sensor_state(_sensor_inverter_power_limit_value, this->inverter_power_limitation_value);
publish_sensor_state(_sensor_inverter_power_limit_state, this->inverter_power_limitation_on);
std::string state_str = "";
if (this->preset == ClimatePreset::CLIMATE_PRESET_SLEEP)
{
state_str += "SLEEP";
}
else if (this->custom_preset.has_value())
{
state_str += this->custom_preset.value().c_str();
}
else
{
state_str += "NONE";
}
publish_sensor_state(_sensor_preset_reporter, optional<std::string>(state_str));
}
// **************************************************************************************************
// converts vertical louver state from hardware codes to frontend code
vlouver_esphome_position_t AirCon::aux_vlouver_to_frontend(const ac_louver_V vLouver)
{
switch (vLouver)
{
case AC_LOUVERV_SWING_UPDOWN:
return AC_VLOUVER_FRONTEND_SWING;
case AC_LOUVERV_OFF:
return AC_VLOUVER_FRONTEND_STOP;
case AC_LOUVERV_TOP:
return AC_VLOUVER_FRONTEND_TOP;
case AC_LOUVERV_MIDDLE_ABOVE:
return AC_VLOUVER_FRONTEND_MIDDLE_ABOVE;
case AC_LOUVERV_MIDDLE:
return AC_VLOUVER_FRONTEND_MIDDLE;
case AC_LOUVERV_MIDDLE_BELOW:
return AC_VLOUVER_FRONTEND_MIDDLE_BELOW;
case AC_LOUVERV_BOTTOM:
return AC_VLOUVER_FRONTEND_BOTTOM;
default:
ESP_LOGW(TAG, "aux_vlouver_to_frontend: unknown vertical louver hardware state = %u", vLouver);
return AC_VLOUVER_FRONTEND_STOP;
}
}
// **************************************************************************************************
// current vertical louver position in esphome codes
vlouver_esphome_position_t AirCon::get_current_vlouver_frontend_state()
{
return aux_vlouver_to_frontend(this->louver_vertical);
}
// **************************************************************************************************
// converts vertical louver position from frontend codes to hardware code
ac_louver_V AirCon::frontend_vlouver_to_aux(const vlouver_esphome_position_t vLouver)
{
switch (vLouver)
{
case AC_VLOUVER_FRONTEND_SWING:
return AC_LOUVERV_SWING_UPDOWN;
case AC_VLOUVER_FRONTEND_STOP:
return AC_LOUVERV_OFF;
case AC_VLOUVER_FRONTEND_TOP:
return AC_LOUVERV_TOP;
case AC_VLOUVER_FRONTEND_MIDDLE_ABOVE:
return AC_LOUVERV_MIDDLE_ABOVE;
case AC_VLOUVER_FRONTEND_MIDDLE:
return AC_LOUVERV_MIDDLE;
case AC_VLOUVER_FRONTEND_MIDDLE_BELOW:
return AC_LOUVERV_MIDDLE_BELOW;
case AC_VLOUVER_FRONTEND_BOTTOM:
return AC_LOUVERV_BOTTOM;
default:
ESP_LOGW(TAG, "frontend_vlouver_to_aux: unknown frontend vertical louver state = %u", vLouver);
return AC_LOUVERV_OFF;
}
}
} // namespace aux_airconditioner
} // namespace esphome

276
components/aux_ac/aircon.h Normal file
View File

@@ -0,0 +1,276 @@
#pragma once
#include <math.h> // for NAN
#include <queue>
#include "esphome.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart_component.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/optional.h"
#include "frame.h"
#include "aircon_common.h"
#include "helpers.h"
namespace esphome
{
namespace aux_airconditioner
{
using esphome::Component;
using esphome::binary_sensor::BinarySensor;
using esphome::climate::Climate;
using esphome::climate::ClimateCall;
using esphome::climate::ClimatePreset;
using esphome::climate::ClimateSwingMode;
using esphome::climate::ClimateTraits;
using esphome::sensor::Sensor;
using esphome::text_sensor::TextSensor;
using esphome::uart::UARTComponent;
using esphome::helpers::Timer;
using esphome::helpers::TimerInterface;
using esphome::helpers::TimerManager;
using millis_function_t = uint32_t (*)();
static const char *const TAG = "AirCon";
/*************************************************************************************************\
\*************************************************************************************************/
class Capabilities
{
public:
// **************************************************************************************************
static const std::string AC_FIRMWARE_VERSION;
// **************************************************************************************************
// custom fan modes
static const std::string CUSTOM_FAN_MODE_MUTE;
static const std::string CUSTOM_FAN_MODE_TURBO;
// **************************************************************************************************
// custom presets
static const std::string CUSTOM_PRESET_CLEAN;
static const std::string CUSTOM_PRESET_HEALTH;
static const std::string CUSTOM_PRESET_ANTIFUNGUS;
// **************************************************************************************************
// predefined default params
static const uint32_t AC_STATE_REQUEST_INTERVAL;
static const uint32_t AC_CONNECTION_LOST_TIMEOUT;
static const uint32_t AC_PACKET_TIMEOUT_MIN;
static const uint32_t AC_PACKET_TIMEOUT_MAX;
static uint32_t normilize_packet_timeout(uint32_t timeout);
static const float AC_TEMPERATURE_STEP_TARGET;
static const float AC_TEMPERATURE_STEP_CURRENT;
static const float AC_MIN_TEMPERATURE;
static const float AC_MAX_TEMPERATURE;
static float normilize_target_temperature(const float target_temperature);
static const uint8_t AC_MIN_INVERTER_POWER_LIMIT;
static const uint8_t AC_MAX_INVERTER_POWER_LIMIT;
static uint8_t normilize_inverter_power_limit(const uint8_t power_limit_value);
};
/*************************************************************************************************\
\*************************************************************************************************/
class FrameProcessorManager;
class CommandBuilder;
class Frame;
class AirCon : public Component,
public Climate
{
private:
void _update_sensor_unit_of_measurement(Sensor *sensor);
protected:
// esphome sensors that display the parameters of the air conditioner
Sensor *_sensor_temperature_indoor_ambient{nullptr};
Sensor *_sensor_temperature_indoor_coil{nullptr};
Sensor *_sensor_temperature_outdoor_ambient{nullptr};
Sensor *_sensor_temperature_outdoor_condenser_middle{nullptr};
Sensor *_sensor_temperature_outdoor_defrost{nullptr};
Sensor *_sensor_temperature_outdoor_discharge{nullptr};
Sensor *_sensor_temperature_outdoor_suction{nullptr};
Sensor *_sensor_vlouver_state{nullptr};
BinarySensor *_sensor_display_state{nullptr};
BinarySensor *_sensor_defrost_state{nullptr};
TextSensor *_sensor_preset_reporter{nullptr};
Sensor *_sensor_inverter_power_actual{nullptr};
Sensor *_sensor_inverter_power_limit_value{nullptr};
BinarySensor *_sensor_inverter_power_limit_state{nullptr};
ClimateTraits _traits;
UARTComponent *_uart{nullptr};
bool _display_inverted{false};
bool _optimistic{true}; // in optimistic mode, the entity states are updated immediately after receiving a command from Home Assistant/ESPHome
uint32_t _update_period{Capabilities::AC_STATE_REQUEST_INTERVAL};
uint32_t _packet_timeout{Capabilities::AC_PACKET_TIMEOUT_MIN};
bool _has_ping{false};
millis_function_t _millis_func{nullptr};
std::queue<Frame *> _tx_frames;
Frame *_incoming_frame{nullptr};
Frame *_last_frame_11{nullptr};
Frame *_last_frame_2x{nullptr};
Frame *_frame_ping_response{nullptr};
Frame *_frame_11_request{nullptr};
Frame *_frame_2x_request{nullptr};
FrameProcessorManager *_frame_processor_manager{nullptr};
std::queue<ClimateCall> _command_queue;
command_processor_state_t _cmd_processor_state{CMD_PROCESSOR_STATE_NOT_STARTED};
CommandBuilder *_cmd_builder{nullptr};
void _send_frame_from_tx_queue();
void _process_command_queue();
TimerManager _timer_manager;
Timer _frame11_request_timer;
Timer _frame2x_request_timer;
Timer _waiting_for_response_timer;
Timer _ping_timeout_timer;
std::function<void(Frame &)> _receiver_callback = nullptr;
public:
AirCon();
~AirCon();
// **************************************************************************************************
// derived methods
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
virtual ClimateTraits traits() override { return _traits; }
virtual void setup() override;
virtual void loop() override;
virtual void dump_config() override;
virtual void control(const esphome::climate::ClimateCall &call) override;
// **************************************************************************************************
// current state
// ------- derived from Climate parameters -------
// ClimateMode mode{CLIMATE_MODE_OFF}; /// The active mode of the climate device.
// ClimateAction action{CLIMATE_ACTION_OFF}; /// The active state of the climate device.
// float current_temperature{NAN}; /// The current temperature of the climate device, as reported from the integration.
// float target_temperature; /// The target temperature of the climate device.
// ClimateFanMode fan_mode{CLIMATE_FAN_OFF}; /// The active fan mode of the climate device.
// std::string custom_fan_mode{}; /// The active custom fan mode of the climate device.
// ClimateSwingMode swing_mode{CLIMATE_SWING_OFF}; /// The active swing mode of the climate device.
// ClimatePreset preset{CLIMATE_PRESET_NONE}; /// The active preset of the climate device.
// std::string custom_preset{}; /// The active custom preset mode of the climate device.
// ------- own parameters -------
ac_louver_V louver_vertical{AC_LOUVERV_OFF};
ac_louver_H louver_horizontal{AC_LOUVERH_OFF};
bool temperature_in_fahrenheit{false};
bool display_enabled{true};
uint8_t last_IR_passed{0}; // time since last IR-remote command passed
optional<bool> inverter_power_limitation_on{false};
optional<uint8_t> inverter_power_limitation_value{100};
bool ac_type_inverter{false};
optional<uint8_t> temperature_indoor_coil{}; // byte 17, cmd=0x21
optional<uint8_t> temperature_condenser_middle{}; // byte 20, cmd=0x21
optional<uint8_t> temperature_outdoor_ambient{}; // byte 18, cmd=0x21
optional<uint8_t> temperature_outdoor_suction{}; // byte 21, cmd=0x21
optional<uint8_t> temperature_outdoor_discharge{}; // byte 22, cmd=0x21
optional<uint8_t> temperature_outdoor_defrost{}; // byte 23, cmd=0x21
ac_fanspeed_real real_fan_speed{AC_REAL_FAN_OFF};
optional<uint8_t> inverter_power{0};
bool defrost_enabled{false};
// **************************************************************************************************
// settings & config
void set_uart(UARTComponent &uart) { _uart = &uart; }
void set_uart(UARTComponent *uart) { _uart = uart; }
UARTComponent &get_uart() { return *_uart; }
bool is_hardware_connected() { return _uart != nullptr; }
bool has_ping() { return this->_has_ping; }
void reset_ping_timeout()
{
this->_has_ping = true;
this->_ping_timeout_timer.reset();
}
void set_millis(millis_function_t millis) { _millis_func = millis; }
uint32_t ms() { return (_millis_func != nullptr) ? _millis_func() : 0; }
void set_display_inversion(bool inversion) { _display_inverted = inversion; }
bool get_display_inversion() { return _display_inverted; }
void set_optimistic(bool optimistic) { this->_optimistic = optimistic; }
bool get_optimistic() { return this->_optimistic; }
void set_period(uint32_t ms) { this->_update_period = ms; }
uint32_t get_period() { return this->_update_period; }
void set_packet_timeout(uint32_t ms) { this->_packet_timeout = Capabilities::normilize_packet_timeout(ms); }
uint32_t get_packet_timeout() { return this->_packet_timeout; }
void set_supported_modes(const std::set<ClimateMode> &modes) { _traits.set_supported_modes(modes); }
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { _traits.set_supported_swing_modes(modes); }
void set_supported_presets(const std::set<ClimatePreset> &presets) { _traits.set_supported_presets(presets); }
void set_custom_presets(const std::set<std::string> &presets) { _traits.set_supported_custom_presets(presets); }
void set_custom_fan_modes(const std::set<std::string> &modes) { _traits.set_supported_custom_fan_modes(modes); }
// **************************************************************************************************
// setters for sensors
void set_sensor_temperature_indoor_ambient(Sensor *temperature_sensor) { _sensor_temperature_indoor_ambient = temperature_sensor; }
void set_sensor_temperature_indoor_coil(Sensor *temperature_sensor) { _sensor_temperature_indoor_coil = temperature_sensor; }
void set_sensor_temperature_outdoor_ambient(Sensor *temperature_sensor) { _sensor_temperature_outdoor_ambient = temperature_sensor; }
void set_sensor_temperature_outdoor_condenser_middle(Sensor *temperature_sensor) { _sensor_temperature_outdoor_condenser_middle = temperature_sensor; }
void set_sensor_temperature_outdoor_defrost(Sensor *temperature_sensor) { _sensor_temperature_outdoor_defrost = temperature_sensor; }
void set_sensor_temperature_outdoor_discharge(Sensor *temperature_sensor) { _sensor_temperature_outdoor_discharge = temperature_sensor; }
void set_sensor_temperature_outdoor_suction(Sensor *temperature_sensor) { _sensor_temperature_outdoor_suction = temperature_sensor; }
void set_sensor_vlouver_state(Sensor *sensor) { _sensor_vlouver_state = sensor; }
void set_sensor_display(BinarySensor *sensor) { _sensor_display_state = sensor; }
void set_sensor_defrost_state(BinarySensor *sensor) { _sensor_defrost_state = sensor; }
void set_sensor_preset_reporter(TextSensor *sensor) { _sensor_preset_reporter = sensor; }
void set_sensor_inverter_power(Sensor *sensor) { _sensor_inverter_power_actual = sensor; }
void set_sensor_inverter_power_limit_value(Sensor *sensor) { _sensor_inverter_power_limit_value = sensor; }
void set_sensor_inverter_power_limit_state(BinarySensor *sensor) { _sensor_inverter_power_limit_state = sensor; }
// **************************************************************************************************
// actions
void action_display_off();
void action_display_on();
void action_set_vlouver_swing();
void action_set_vlouver_stop();
void action_set_vlouver_top_position();
void action_set_vlouver_middle_above_position();
void action_set_vlouver_middle_position();
void action_set_vlouver_middle_below_position();
void action_set_vlouver_bottom();
void action_set_vlouver_position(vlouver_esphome_position_t position);
void action_power_limitation_off();
void action_power_limitation_on(uint8_t limit);
// **************************************************************************************************
// other methods
void schedule_frame_to_send(const Frame &frame);
void schedule_ping_response();
void schedule_command(const ClimateCall &cmd);
Frame &get_last_frame_11();
Frame &get_last_frame_2x();
void set_last_frame(const Frame &frame);
void update_all_sensors_unit_of_measurement();
void publish_all_states();
void set_receiver_callback(std::function<void(Frame &)> callback) { this->_receiver_callback = callback; }
// converts vertical louver state from hardware codes to frontend codes
vlouver_esphome_position_t aux_vlouver_to_frontend(const ac_louver_V vLouver);
// current vertical louver position in esphome codes
vlouver_esphome_position_t get_current_vlouver_frontend_state();
// converts vertical louver position from frontend codes to hardware code
ac_louver_V frontend_vlouver_to_aux(const vlouver_esphome_position_t vLouver);
};
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,245 @@
#include "aircon_common.h"
namespace esphome
{
namespace aux_airconditioner
{
std::string ac_mode_to_string(ac_mode mode)
{
switch (mode)
{
case AC_MODE_AUTO:
return "AC_MODE_AUTO";
case AC_MODE_COOL:
return "AC_MODE_COOL";
case AC_MODE_DRY:
return "AC_MODE_DRY";
case AC_MODE_HEAT:
return "AC_MODE_HEAT";
case AC_MODE_FAN:
return "AC_MODE_FAN";
default:
return "mode unknown";
}
}
ClimateMode ac_mode_to_climate_mode(ac_mode mode)
{
switch (mode)
{
case AC_MODE_AUTO:
return ClimateMode::CLIMATE_MODE_HEAT_COOL;
case AC_MODE_COOL:
return ClimateMode::CLIMATE_MODE_COOL;
case AC_MODE_DRY:
return ClimateMode::CLIMATE_MODE_DRY;
case AC_MODE_HEAT:
return ClimateMode::CLIMATE_MODE_HEAT;
case AC_MODE_FAN:
return ClimateMode::CLIMATE_MODE_FAN_ONLY;
default:
return ClimateMode::CLIMATE_MODE_OFF;
}
}
ac_mode climate_mode_to_ac_mode(ClimateMode mode)
{
switch (mode)
{
case ClimateMode::CLIMATE_MODE_HEAT_COOL:
return AC_MODE_AUTO;
case ClimateMode::CLIMATE_MODE_COOL:
return AC_MODE_COOL;
case ClimateMode::CLIMATE_MODE_DRY:
return AC_MODE_DRY;
case ClimateMode::CLIMATE_MODE_HEAT:
return AC_MODE_HEAT;
case ClimateMode::CLIMATE_MODE_FAN_ONLY:
return AC_MODE_FAN;
default:
return AC_MODE_FAN;
}
}
ac_louver_V vlouver_frontend_to_ac_louver_V(const vlouver_esphome_position_t vlouver_frontend)
{
switch (vlouver_frontend)
{
case AC_VLOUVER_FRONTEND_SWING:
return ac_louver_V::AC_LOUVERV_SWING_UPDOWN;
case AC_VLOUVER_FRONTEND_STOP:
return ac_louver_V::AC_LOUVERV_OFF;
case AC_VLOUVER_FRONTEND_TOP:
return ac_louver_V::AC_LOUVERV_SWING_UPDOWN;
case AC_VLOUVER_FRONTEND_MIDDLE_ABOVE:
return ac_louver_V::AC_LOUVERV_MIDDLE_ABOVE;
case AC_VLOUVER_FRONTEND_MIDDLE:
return ac_louver_V::AC_LOUVERV_MIDDLE;
case AC_VLOUVER_FRONTEND_MIDDLE_BELOW:
return ac_louver_V::AC_LOUVERV_MIDDLE_BELOW;
case AC_VLOUVER_FRONTEND_BOTTOM:
return ac_louver_V::AC_LOUVERV_BOTTOM;
default:
return ac_louver_V::AC_LOUVERV_OFF;
}
}
std::string ac_louver_V_to_string(ac_louver_V louver)
{
switch (louver)
{
case AC_LOUVERV_SWING_UPDOWN:
return "AC_LOUVERV_SWING_UPDOWN";
case AC_LOUVERV_TOP:
return "AC_LOUVERV_TOP";
case AC_LOUVERV_MIDDLE_ABOVE:
return "AC_LOUVERV_MIDDLE_ABOVE";
case AC_LOUVERV_MIDDLE:
return "AC_LOUVERV_MIDDLE";
case AC_LOUVERV_MIDDLE_BELOW:
return "AC_LOUVERV_MIDDLE_BELOW";
case AC_LOUVERV_BOTTOM:
return "AC_LOUVERV_BOTTOM";
case AC_LOUVERV_OFF:
return "AC_LOUVERV_OFF";
default:
return "unknown vertical louver position";
}
}
std::string ac_louver_H_to_string(ac_louver_H louver)
{
switch (louver)
{
case AC_LOUVERH_SWING_LEFTRIGHT:
return "AC_LOUVERH_SWING_LEFTRIGHT";
case AC_LOUVERH_OFF:
return "AC_LOUVERH_OFF";
default:
return "unknown horizontal louver position";
}
}
std::string ac_fanspeed_to_string(ac_fanspeed fanspeed)
{
switch (fanspeed)
{
case AC_FANSPEED_HIGH:
return "AC_FANSPEED_HIGH";
case AC_FANSPEED_MEDIUM:
return "AC_FANSPEED_MEDIUM";
case AC_FANSPEED_LOW:
return "AC_FANSPEED_LOW";
case AC_FANSPEED_AUTO:
return "AC_FANSPEED_AUTO";
default:
return "unknown";
}
}
ClimateFanMode ac_fanspeed_to_climate_fan_mode(ac_fanspeed fanspeed)
{
switch (fanspeed)
{
case AC_FANSPEED_HIGH:
return ClimateFanMode::CLIMATE_FAN_HIGH;
case AC_FANSPEED_MEDIUM:
return ClimateFanMode::CLIMATE_FAN_MEDIUM;
case AC_FANSPEED_LOW:
return ClimateFanMode::CLIMATE_FAN_LOW;
case AC_FANSPEED_AUTO:
return ClimateFanMode::CLIMATE_FAN_AUTO;
default:
return ClimateFanMode::CLIMATE_FAN_LOW;
}
}
ac_fanspeed climate_fan_mode_to_ac_fanspeed(ClimateFanMode fanmode)
{
switch (fanmode)
{
case ClimateFanMode::CLIMATE_FAN_AUTO:
return AC_FANSPEED_AUTO;
case ClimateFanMode::CLIMATE_FAN_LOW:
return AC_FANSPEED_LOW;
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
return AC_FANSPEED_MEDIUM;
case ClimateFanMode::CLIMATE_FAN_HIGH:
return AC_FANSPEED_HIGH;
default:
return ac_fanspeed::AC_FANSPEED_LOW;
}
}
std::string ac_fanspeed_real_to_string(ac_fanspeed_real real_fanspeed)
{
switch (real_fanspeed)
{
case AC_REAL_FAN_OFF:
return "AC_REAL_FAN_OFF";
case AC_REAL_FAN_MUTE:
return "AC_REAL_FAN_MUTE";
case AC_REAL_FAN_LOW:
return "AC_REAL_FAN_LOW";
case AC_REAL_FAN_MID:
return "AC_REAL_FAN_MID";
case AC_REAL_FAN_HIGH:
return "AC_REAL_FAN_HIGH";
case AC_REAL_FAN_TURBO:
return "AC_REAL_FAN_TURBO";
default:
return "unknown";
}
}
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,106 @@
#pragma once
#include "esphome/components/climate/climate.h"
#include "esphome/core/optional.h"
namespace esphome
{
namespace aux_airconditioner
{
enum ac_mode : uint8_t
{
AC_MODE_AUTO = 0x00,
AC_MODE_COOL = 0x20,
AC_MODE_DRY = 0x40,
AC_MODE_HEAT = 0x80,
AC_MODE_FAN = 0xC0
};
std::string ac_mode_to_string(ac_mode mode);
using esphome::climate::ClimateMode;
ClimateMode ac_mode_to_climate_mode(ac_mode mode);
ac_mode climate_mode_to_ac_mode(ClimateMode mode);
// vertical louvers position in esphome / HA frontend
enum vlouver_esphome_position_t : uint8_t
{
AC_VLOUVER_FRONTEND_SWING = 0x00,
AC_VLOUVER_FRONTEND_STOP = 0x01,
AC_VLOUVER_FRONTEND_TOP = 0x02,
AC_VLOUVER_FRONTEND_MIDDLE_ABOVE = 0x03,
AC_VLOUVER_FRONTEND_MIDDLE = 0x04,
AC_VLOUVER_FRONTEND_MIDDLE_BELOW = 0x05,
AC_VLOUVER_FRONTEND_BOTTOM = 0x06,
};
enum ac_louver_V : uint8_t
{
AC_LOUVERV_SWING_UPDOWN = 0x00,
AC_LOUVERV_TOP = 0x01,
AC_LOUVERV_MIDDLE_ABOVE = 0x02,
AC_LOUVERV_MIDDLE = 0x03,
AC_LOUVERV_MIDDLE_BELOW = 0x04,
AC_LOUVERV_BOTTOM = 0x05,
// 0x06 tested and doing nothing
AC_LOUVERV_OFF = 0x07
};
ac_louver_V vlouver_frontend_to_ac_louver_V(const vlouver_esphome_position_t vlouver_frontend);
std::string ac_louver_V_to_string(ac_louver_V louver);
enum ac_louver_H : uint8_t
{
AC_LOUVERH_SWING_LEFTRIGHT = 0x00,
// AC_LOUVERH_OFF_AUX = 0x20, // 0b00100000
AC_LOUVERH_OFF = 0xE0 // 0b11100000
};
std::string ac_louver_H_to_string(ac_louver_H louver);
enum ac_fanspeed : uint8_t
{
AC_FANSPEED_HIGH = 0x20,
AC_FANSPEED_MEDIUM = 0x40,
AC_FANSPEED_LOW = 0x60,
AC_FANSPEED_AUTO = 0xA0
};
std::string ac_fanspeed_to_string(ac_fanspeed fanspeed);
using esphome::climate::ClimateFanMode;
ClimateFanMode ac_fanspeed_to_climate_fan_mode(ac_fanspeed fanspeed);
ac_fanspeed climate_fan_mode_to_ac_fanspeed(ClimateFanMode fanmode);
enum ac_fanspeed_real : uint8_t
{
AC_REAL_FAN_OFF = 0x00,
AC_REAL_FAN_MUTE = 0x01,
AC_REAL_FAN_LOW = 0x02,
AC_REAL_FAN_MID = 0x04,
AC_REAL_FAN_HIGH = 0x06,
AC_REAL_FAN_TURBO = 0x07
};
std::string ac_fanspeed_real_to_string(ac_fanspeed_real real_fanspeed);
enum command_type_t : uint8_t
{
COMMAND_TYPE_NONE = 0x00,
COMMAND_TYPE_REQUEST_11 = 0x01,
COMMAND_TYPE_REQUEST_21 = 0x02,
COMMAND_TYPE_SET_STATE = 0x03,
};
enum command_processor_state_t : uint8_t
{
CMD_PROCESSOR_STATE_NOT_STARTED = 0x00,
CMD_PROCESSOR_STATE_WAITING_FOR_F11 = 0x01,
CMD_PROCESSOR_STATE_PRECHECK_DONE = 0x02,
CMD_PROCESSOR_STATE_CMD_WAS_SENT = 0x03,
CMD_PROCESSOR_STATE_POSTCHECK_DONE = 0x04,
};
} // namespace aux_airconditioner
} // namespace GrKoR

View File

@@ -1,186 +1,169 @@
#pragma once #pragma once
#include "aux_ac.h" #include "aircon.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
namespace esphome { namespace esphome
namespace aux_ac { {
namespace aux_airconditioner
{
class AirCon;
// **************************************** DISPLAY ACTIONS **************************************** // **************************************** DISPLAY ACTIONS ****************************************
template <typename... Ts> template <typename... Ts>
class AirConDisplayOffAction : public Action<Ts...> { class AirConDisplayOffAction : public Action<Ts...>
public: {
explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->displayOffSequence(); } void play(Ts... x) override { this->ac_->action_display_off(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConDisplayOnAction : public Action<Ts...> { class AirConDisplayOnAction : public Action<Ts...>
public: {
explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->displayOnSequence(); } void play(Ts... x) override { this->ac_->action_display_on(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
// **************************************** VERTICAL LOUVER ACTIONS **************************************** // **************************************** VERTICAL LOUVER ACTIONS ****************************************
template <typename... Ts> template <typename... Ts>
class AirConVLouverSwingAction : public Action<Ts...> { class AirConVLouverSwingAction : public Action<Ts...>
public: {
explicit AirConVLouverSwingAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverSwingAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverSwingSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_swing(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverStopAction : public Action<Ts...> { class AirConVLouverStopAction : public Action<Ts...>
public: {
explicit AirConVLouverStopAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverStopAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverStopSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_stop(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverTopAction : public Action<Ts...> { class AirConVLouverTopAction : public Action<Ts...>
public: {
explicit AirConVLouverTopAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverTopAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverTopSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_top_position(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverMiddleAboveAction : public Action<Ts...> { class AirConVLouverMiddleAboveAction : public Action<Ts...>
public: {
explicit AirConVLouverMiddleAboveAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverMiddleAboveAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverMiddleAboveSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_middle_above_position(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverMiddleAction : public Action<Ts...> { class AirConVLouverMiddleAction : public Action<Ts...>
public: {
explicit AirConVLouverMiddleAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverMiddleAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverMiddleSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_middle_position(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverMiddleBelowAction : public Action<Ts...> { class AirConVLouverMiddleBelowAction : public Action<Ts...>
public: {
explicit AirConVLouverMiddleBelowAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverMiddleBelowAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverMiddleBelowSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_middle_below_position(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverBottomAction : public Action<Ts...> { class AirConVLouverBottomAction : public Action<Ts...>
public: {
explicit AirConVLouverBottomAction(AirCon *ac) : ac_(ac) {} public:
explicit AirConVLouverBottomAction(AirCon *ac) : ac_(ac) {}
void play(Ts... x) override { this->ac_->setVLouverBottomSequence(); } void play(Ts... x) override { this->ac_->action_set_vlouver_bottom(); }
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> template <typename... Ts>
class AirConVLouverSetAction : public Action<Ts...> { class AirConVLouverSetAction : public Action<Ts...>
public: {
AirConVLouverSetAction(AirCon *ac) : ac_(ac) {} public:
TEMPLATABLE_VALUE(uint8_t, value); AirConVLouverSetAction(AirCon *ac) : ac_(ac) {}
TEMPLATABLE_VALUE(uint8_t, value);
void play(Ts... x) { void play(Ts... x)
vlpos_ = this->value_.value(x...); {
this->ac_->setVLouverFrontendSequence((ac_vlouver_frontend)vlpos_); this->ac_->action_set_vlouver_position((vlouver_esphome_position_t)this->value_.value(x...));
} }
protected: protected:
AirCon *ac_; AirCon *ac_;
uint8_t vlpos_; };
};
// **************************************** SEND TEST PACKET ACTION **************************************** // **************************************** POWER LIMITATION ACTIONS ****************************************
template <typename... Ts> template <typename... Ts>
class AirConSendTestPacketAction : public Action<Ts...> { class AirConPowerLimitationOffAction : public Action<Ts...>
public: {
explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {} public:
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { explicit AirConPowerLimitationOffAction(AirCon *ac) : ac_(ac) {}
this->data_func_ = func;
this->static_ = false;
}
void set_data_static(const std::vector<uint8_t> &data) {
this->data_static_ = data;
this->static_ = true;
}
void play(Ts... x) override { void play(Ts... x) override { this->ac_->action_power_limitation_off(); }
if (this->static_) {
this->ac_->sendTestPacket(this->data_static_);
} else {
auto val = this->data_func_(x...);
this->ac_->sendTestPacket(val);
}
}
protected: protected:
AirCon *ac_; AirCon *ac_;
bool static_{false}; };
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
std::vector<uint8_t> data_static_{};
};
// **************************************** POWER LIMITATION ACTIONS **************************************** template <typename... Ts>
template <typename... Ts> class AirConPowerLimitationOnAction : public Action<Ts...>
class AirConPowerLimitationOffAction : public Action<Ts...> { {
public: public:
explicit AirConPowerLimitationOffAction(AirCon *ac) : ac_(ac) {} AirConPowerLimitationOnAction(AirCon *ac) : ac_(ac) {}
TEMPLATABLE_VALUE(uint8_t, value);
void play(Ts... x) override { this->ac_->powerLimitationOffSequence(); } void play(Ts... x)
{
this->ac_->action_power_limitation_on(this->value_.value(x...));
}
protected: protected:
AirCon *ac_; AirCon *ac_;
}; };
template <typename... Ts> } // namespace aux_airconditioner
class AirConPowerLimitationOnAction : public Action<Ts...> { } // namespace esphome
public:
AirConPowerLimitationOnAction(AirCon *ac) : ac_(ac) {}
TEMPLATABLE_VALUE(uint8_t, value);
void play(Ts... x) {
this->pwr_lim_ = this->value_.value(x...);
this->ac_->powerLimitationOnSequence(this->pwr_lim_);
}
protected:
AirCon *ac_;
uint8_t pwr_lim_;
};
} // namespace aux_ac
} // namespace esphome

File diff suppressed because it is too large Load Diff

View File

@@ -38,30 +38,43 @@ CODEOWNERS = ["@GrKoR"]
DEPENDENCIES = ["climate", "uart"] DEPENDENCIES = ["climate", "uart"]
AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor"] AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor"]
CONF_SHOW_ACTION = "show_action" CONF_SHOW_ACTION_DEPRICATED = "show_action"
CONF_INDOOR_TEMPERATURE_DEPRICATED = "indoor_temperature"
CONF_INBOUND_TEMPERATURE_DEPRICATED = "inbound_temperature"
CONF_OUTDOOR_TEMPERATURE_DEPRICATED = "outdoor_temperature"
CONF_OUTBOUND_TEMPERATURE_DEPRICATED = "outbound_temperature"
CONF_COMPRESSOR_TEMPERATURE_DEPRICATED = "compressor_temperature"
CONF_INDOOR_TEMPERATURE = "indoor_temperature" CONF_INDOOR_AMBIENT_TEMPERATURE = "indoor_ambient_temperature"
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
ICON_OUTDOOR_TEMPERATURE = "mdi:home-thermometer-outline"
CONF_INBOUND_TEMPERATURE = "inbound_temperature" CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature"
ICON_INBOUND_TEMPERATURE = "mdi:thermometer-plus" ICON_INDOOR_COIL_TEMPERATURE = "mdi:thermometer-plus"
CONF_OUTBOUND_TEMPERATURE = "outbound_temperature" CONF_OUTDOOR_AMBIENT_TEMPERATURE = "outdoor_ambient_temperature"
ICON_OUTBOUND_TEMPERATURE = "mdi:thermometer-minus" ICON_OUTDOOR_AMBIENT_TEMPERATURE = "mdi:home-thermometer-outline"
CONF_COMPRESSOR_TEMPERATURE = "compressor_temperature" CONF_OUTDOOR_CONDENSER_TEMPERATURE = "outdoor_condenser_temperature"
ICON_COMPRESSOR_TEMPERATURE = "mdi:thermometer-lines" ICON_OUTDOOR_CONDENSER_TEMPERATURE = "mdi:thermometer-minus"
CONF_DEFROST_TEMPERATURE = "defrost_temperature"
ICON_DEFROST_TEMPERATURE = "mdi:thermometer-lines"
CONF_COMPRESSOR_DISCHARGE_TEMPERATURE = "compressor_discharge_temperature"
ICON_COMPRESSOR_DISCHARGE_TEMPERATURE = "mdi:thermometer-lines"
CONF_COMPRESSOR_SUCTION_TEMPERATURE = "compressor_suction_temperature"
ICON_COMPRESSOR_SUCTION_TEMPERATURE = "mdi:thermometer-lines"
CONF_DISPLAY_STATE = "display_state" CONF_DISPLAY_STATE = "display_state"
ICON_DISPLAY_STATE = "mdi:clock-digital"
CONF_INVERTER_POWER = "inverter_power" CONF_INVERTER_POWER = "inverter_power"
CONF_INVERTER_POWER_DEPRICATED = "invertor_power" CONF_INVERTER_POWER_DEPRICATED = "invertor_power"
CONF_DEFROST_STATE = "defrost_state" CONF_DEFROST_STATE = "defrost_state"
ICON_DEFROST = "mdi:snowflake-melt" ICON_DEFROST_STATE = "mdi:snowflake-melt"
CONF_DISPLAY_INVERTED = "display_inverted" CONF_DISPLAY_INVERTED = "display_inverted"
ICON_DISPLAY = "mdi:clock-digital"
CONF_PRESET_REPORTER = "preset_reporter" CONF_PRESET_REPORTER = "preset_reporter"
ICON_PRESET_REPORTER = "mdi:format-list-group" ICON_PRESET_REPORTER = "mdi:format-list-group"
@@ -76,52 +89,50 @@ CONF_INVERTER_POWER_LIMIT_STATE = "inverter_power_limit_state"
ICON_INVERTER_POWER_LIMIT_STATE = "mdi:meter-electric-outline" ICON_INVERTER_POWER_LIMIT_STATE = "mdi:meter-electric-outline"
aux_ac_ns = cg.esphome_ns.namespace("aux_ac") aux_ac_ns = cg.esphome_ns.namespace("aux_airconditioner")
AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component) AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component)
Capabilities = aux_ac_ns.namespace("Constants") Capabilities = aux_ac_ns.namespace("Capabilities")
# Display actions # Display actions
AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action) AirConDisplayOffAction = aux_ac_ns.class_(
AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action) "AirConDisplayOffAction", automation.Action)
AirConDisplayOnAction = aux_ac_ns.class_(
"AirConDisplayOnAction", automation.Action)
# test packet action # test packet action
AirConSendTestPacketAction = aux_ac_ns.class_( # AirConSendTestPacketAction = aux_ac_ns.class_(
"AirConSendTestPacketAction", automation.Action # "AirConSendTestPacketAction", automation.Action
) # )
# vertical louvers actions # vertical louvers actions
AirConVLouverSwingAction = aux_ac_ns.class_( AirConVLouverSwingAction = aux_ac_ns.class_(
"AirConVLouverSwingAction", automation.Action "AirConVLouverSwingAction", automation.Action)
) AirConVLouverStopAction = aux_ac_ns.class_(
AirConVLouverStopAction = aux_ac_ns.class_("AirConVLouverStopAction", automation.Action) "AirConVLouverStopAction", automation.Action)
AirConVLouverTopAction = aux_ac_ns.class_("AirConVLouverTopAction", automation.Action) AirConVLouverTopAction = aux_ac_ns.class_(
"AirConVLouverTopAction", automation.Action)
AirConVLouverMiddleAboveAction = aux_ac_ns.class_( AirConVLouverMiddleAboveAction = aux_ac_ns.class_(
"AirConVLouverMiddleAboveAction", automation.Action "AirConVLouverMiddleAboveAction", automation.Action)
)
AirConVLouverMiddleAction = aux_ac_ns.class_( AirConVLouverMiddleAction = aux_ac_ns.class_(
"AirConVLouverMiddleAction", automation.Action "AirConVLouverMiddleAction", automation.Action)
)
AirConVLouverMiddleBelowAction = aux_ac_ns.class_( AirConVLouverMiddleBelowAction = aux_ac_ns.class_(
"AirConVLouverMiddleBelowAction", automation.Action "AirConVLouverMiddleBelowAction", automation.Action)
)
AirConVLouverBottomAction = aux_ac_ns.class_( AirConVLouverBottomAction = aux_ac_ns.class_(
"AirConVLouverBottomAction", automation.Action "AirConVLouverBottomAction", automation.Action)
)
AirConVLouverSetAction = aux_ac_ns.class_( AirConVLouverSetAction = aux_ac_ns.class_(
"AirConVLouverSetAction", automation.Action "AirConVLouverSetAction", automation.Action)
)
# power limitation actions # power limitation actions
AirConPowerLimitationOffAction = aux_ac_ns.class_( AirConPowerLimitationOffAction = aux_ac_ns.class_(
"AirConPowerLimitationOffAction", automation.Action "AirConPowerLimitationOffAction", automation.Action)
)
AirConPowerLimitationOnAction = aux_ac_ns.class_( AirConPowerLimitationOnAction = aux_ac_ns.class_(
"AirConPowerLimitationOnAction", automation.Action "AirConPowerLimitationOnAction", automation.Action)
)
AC_PACKET_TIMEOUT_MIN = 300
AC_PACKET_TIMEOUT_MAX = 800
AC_PACKET_TIMEOUT_MIN = 150
AC_PACKET_TIMEOUT_MAX = 600
def validate_packet_timeout(value): def validate_packet_timeout(value):
minV = AC_PACKET_TIMEOUT_MIN minV = AC_PACKET_TIMEOUT_MIN
maxV = AC_PACKET_TIMEOUT_MAX maxV = AC_PACKET_TIMEOUT_MAX
@@ -132,6 +143,8 @@ def validate_packet_timeout(value):
AC_POWER_LIMIT_MIN = 30 AC_POWER_LIMIT_MIN = 30
AC_POWER_LIMIT_MAX = 100 AC_POWER_LIMIT_MAX = 100
def validate_power_limit_range(value): def validate_power_limit_range(value):
minV = AC_POWER_LIMIT_MIN minV = AC_POWER_LIMIT_MIN
maxV = AC_POWER_LIMIT_MAX maxV = AC_POWER_LIMIT_MAX
@@ -162,15 +175,15 @@ ALLOWED_CLIMATE_SWING_MODES = {
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
CUSTOM_FAN_MODES = { CUSTOM_FAN_MODES = {
"MUTE": Capabilities.MUTE, "MUTE": Capabilities.CUSTOM_FAN_MODE_MUTE,
"TURBO": Capabilities.TURBO, "TURBO": Capabilities.CUSTOM_FAN_MODE_TURBO,
} }
validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
CUSTOM_PRESETS = { CUSTOM_PRESETS = {
"CLEAN": Capabilities.CLEAN, "CLEAN": Capabilities.CUSTOM_PRESET_CLEAN,
"HEALTH": Capabilities.HEALTH, "HEALTH": Capabilities.CUSTOM_PRESET_HEALTH,
"ANTIFUNGUS": Capabilities.ANTIFUNGUS, "ANTIFUNGUS": Capabilities.CUSTOM_PRESET_ANTIFUNGUS,
} }
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
@@ -182,7 +195,7 @@ def validate_raw_data(value):
def output_info(config): def output_info(config):
"""_LOGGER.info(config.items())""" # _LOGGER.info(config.items())
return config return config
@@ -191,12 +204,15 @@ CONFIG_SCHEMA = cv.All(
{ {
cv.GenerateID(): cv.declare_id(AirCon), cv.GenerateID(): cv.declare_id(AirCon),
cv.Optional(CONF_PERIOD, default="7s"): cv.time_period, cv.Optional(CONF_PERIOD, default="7s"): cv.time_period,
cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean, # cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean,
cv.Optional(CONF_SHOW_ACTION_DEPRICATED): cv.invalid(
f"Parameter '{CONF_SHOW_ACTION_DEPRICATED}' was deleted in v.1.0.0. Update your config please."
),
cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean, cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean,
cv.Optional(CONF_TIMEOUT, default=AC_PACKET_TIMEOUT_MIN): validate_packet_timeout, cv.Optional(CONF_TIMEOUT, default=AC_PACKET_TIMEOUT_MIN): validate_packet_timeout,
cv.Optional(CONF_OPTIMISTIC, default="true"): cv.boolean, cv.Optional(CONF_OPTIMISTIC, default="true"): cv.boolean,
cv.Optional(CONF_INVERTER_POWER_DEPRICATED): cv.invalid( cv.Optional(CONF_INVERTER_POWER_DEPRICATED): cv.invalid(
"The name of sensor was changed in v.0.2.9 from 'invertor_power' to 'inverter_power'. Update your config please." f"The name of sensor was changed in v.0.2.9 from '{CONF_INVERTER_POWER_DEPRICATED}' to '{CONF_INVERTER_POWER}'. Update your config please."
), ),
cv.Optional(CONF_INVERTER_POWER): sensor.sensor_schema( cv.Optional(CONF_INVERTER_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
@@ -210,7 +226,10 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_INDOOR_TEMPERATURE_DEPRICATED): cv.invalid(
f"Parameter '{CONF_INDOOR_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_INDOOR_AMBIENT_TEMPERATURE}' instead."
),
cv.Optional(CONF_INDOOR_AMBIENT_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER, icon=ICON_THERMOMETER,
accuracy_decimals=1, accuracy_decimals=1,
@@ -221,9 +240,13 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
} }
), ),
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
cv.Optional(CONF_INBOUND_TEMPERATURE_DEPRICATED): cv.invalid(
f"Parameter '{CONF_INBOUND_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_INDOOR_COIL_TEMPERATURE}' instead."
),
cv.Optional(CONF_INDOOR_COIL_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_OUTDOOR_TEMPERATURE, icon=ICON_INDOOR_COIL_TEMPERATURE,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@@ -232,9 +255,13 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
} }
), ),
cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema(
cv.Optional(CONF_OUTDOOR_TEMPERATURE_DEPRICATED): cv.invalid(
f"Parameter '{CONF_OUTDOOR_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_OUTDOOR_AMBIENT_TEMPERATURE}' instead."
),
cv.Optional(CONF_OUTDOOR_AMBIENT_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_INBOUND_TEMPERATURE, icon=ICON_OUTDOOR_AMBIENT_TEMPERATURE,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@@ -243,9 +270,10 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
} }
), ),
cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema(
cv.Optional(CONF_OUTDOOR_CONDENSER_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_OUTBOUND_TEMPERATURE, icon=ICON_OUTDOOR_CONDENSER_TEMPERATURE,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@@ -254,9 +282,13 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
} }
), ),
cv.Optional(CONF_COMPRESSOR_TEMPERATURE): sensor.sensor_schema(
cv.Optional(CONF_COMPRESSOR_TEMPERATURE_DEPRICATED): cv.invalid(
f"Parameter '{CONF_COMPRESSOR_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_COMPRESSOR_DISCHARGE_TEMPERATURE}' instead."
),
cv.Optional(CONF_COMPRESSOR_DISCHARGE_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_COMPRESSOR_TEMPERATURE, icon=ICON_COMPRESSOR_DISCHARGE_TEMPERATURE,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@@ -265,6 +297,34 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
} }
), ),
cv.Optional(CONF_OUTBOUND_TEMPERATURE_DEPRICATED): cv.invalid(
f"Parameter '{CONF_OUTBOUND_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_COMPRESSOR_SUCTION_TEMPERATURE}' instead."
),
cv.Optional(CONF_COMPRESSOR_SUCTION_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_COMPRESSOR_SUCTION_TEMPERATURE,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_DEFROST_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_DEFROST_TEMPERATURE,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
}
),
cv.Optional(CONF_VLOUVER_STATE): sensor.sensor_schema( cv.Optional(CONF_VLOUVER_STATE): sensor.sensor_schema(
icon=ICON_VLOUVER_STATE, icon=ICON_VLOUVER_STATE,
accuracy_decimals=0, accuracy_decimals=0,
@@ -274,14 +334,14 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema( cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema(
icon=ICON_DISPLAY, icon=ICON_DISPLAY_STATE,
).extend( ).extend(
{ {
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
} }
), ),
cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema( cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema(
icon=ICON_DEFROST, icon=ICON_DEFROST_STATE,
).extend( ).extend(
{ {
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean, cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
@@ -335,77 +395,88 @@ async def to_code(config):
await climate.register_climate(var, config) await climate.register_climate(var, config)
parent = await cg.get_variable(config[CONF_UART_ID]) parent = await cg.get_variable(config[CONF_UART_ID])
cg.add(var.initAC(parent)) cg.add(var.set_uart(parent))
if CONF_INDOOR_TEMPERATURE in config: if CONF_INDOOR_AMBIENT_TEMPERATURE in config:
conf = config[CONF_INDOOR_TEMPERATURE] conf = config[CONF_INDOOR_AMBIENT_TEMPERATURE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_indoor_temperature_sensor(sens)) cg.add(var.set_sensor_temperature_indoor_ambient(sens))
if CONF_OUTDOOR_TEMPERATURE in config: if CONF_INDOOR_COIL_TEMPERATURE in config:
conf = config[CONF_OUTDOOR_TEMPERATURE] conf = config[CONF_INDOOR_COIL_TEMPERATURE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_outdoor_temperature_sensor(sens)) cg.add(var.set_sensor_temperature_indoor_coil(sens))
if CONF_OUTBOUND_TEMPERATURE in config: if CONF_OUTDOOR_AMBIENT_TEMPERATURE in config:
conf = config[CONF_OUTBOUND_TEMPERATURE] conf = config[CONF_OUTDOOR_AMBIENT_TEMPERATURE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_outbound_temperature_sensor(sens)) cg.add(var.set_sensor_temperature_outdoor_ambient(sens))
if CONF_INBOUND_TEMPERATURE in config: if CONF_OUTDOOR_CONDENSER_TEMPERATURE in config:
conf = config[CONF_INBOUND_TEMPERATURE] conf = config[CONF_OUTDOOR_CONDENSER_TEMPERATURE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_inbound_temperature_sensor(sens)) cg.add(var.set_sensor_temperature_outdoor_condenser_middle(sens))
if CONF_COMPRESSOR_TEMPERATURE in config: if CONF_DEFROST_TEMPERATURE in config:
conf = config[CONF_COMPRESSOR_TEMPERATURE] conf = config[CONF_DEFROST_TEMPERATURE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_compressor_temperature_sensor(sens)) cg.add(var.set_sensor_temperature_outdoor_defrost(sens))
if CONF_COMPRESSOR_DISCHARGE_TEMPERATURE in config:
conf = config[CONF_COMPRESSOR_DISCHARGE_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_sensor_temperature_outdoor_discharge(sens))
if CONF_COMPRESSOR_SUCTION_TEMPERATURE in config:
conf = config[CONF_COMPRESSOR_SUCTION_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_sensor_temperature_outdoor_suction(sens))
if CONF_VLOUVER_STATE in config: if CONF_VLOUVER_STATE in config:
conf = config[CONF_VLOUVER_STATE] conf = config[CONF_VLOUVER_STATE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_vlouver_state_sensor(sens)) cg.add(var.set_sensor_vlouver_state(sens))
if CONF_DISPLAY_STATE in config: if CONF_DISPLAY_STATE in config:
conf = config[CONF_DISPLAY_STATE] conf = config[CONF_DISPLAY_STATE]
sens = await binary_sensor.new_binary_sensor(conf) sens = await binary_sensor.new_binary_sensor(conf)
cg.add(var.set_display_sensor(sens)) cg.add(var.set_sensor_display(sens))
if CONF_DEFROST_STATE in config: if CONF_DEFROST_STATE in config:
conf = config[CONF_DEFROST_STATE] conf = config[CONF_DEFROST_STATE]
sens = await binary_sensor.new_binary_sensor(conf) sens = await binary_sensor.new_binary_sensor(conf)
cg.add(var.set_defrost_state(sens)) cg.add(var.set_sensor_defrost_state(sens))
if CONF_INVERTER_POWER in config:
conf = config[CONF_INVERTER_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_inverter_power_sensor(sens))
if CONF_PRESET_REPORTER in config: if CONF_PRESET_REPORTER in config:
conf = config[CONF_PRESET_REPORTER] conf = config[CONF_PRESET_REPORTER]
sens = await text_sensor.new_text_sensor(conf) sens = await text_sensor.new_text_sensor(conf)
cg.add(var.set_preset_reporter_sensor(sens)) cg.add(var.set_sensor_preset_reporter(sens))
if CONF_INVERTER_POWER in config:
conf = config[CONF_INVERTER_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_sensor_inverter_power(sens))
if CONF_INVERTER_POWER_LIMIT_VALUE in config: if CONF_INVERTER_POWER_LIMIT_VALUE in config:
conf = config[CONF_INVERTER_POWER_LIMIT_VALUE] conf = config[CONF_INVERTER_POWER_LIMIT_VALUE]
sens = await sensor.new_sensor(conf) sens = await sensor.new_sensor(conf)
cg.add(var.set_inverter_power_limit_value_sensor(sens)) cg.add(var.set_sensor_inverter_power_limit_value(sens))
if CONF_INVERTER_POWER_LIMIT_STATE in config: if CONF_INVERTER_POWER_LIMIT_STATE in config:
conf = config[CONF_INVERTER_POWER_LIMIT_STATE] conf = config[CONF_INVERTER_POWER_LIMIT_STATE]
sens = await binary_sensor.new_binary_sensor(conf) sens = await binary_sensor.new_binary_sensor(conf)
cg.add(var.set_inverter_power_limit_state_sensor(sens)) cg.add(var.set_sensor_inverter_power_limit_state(sens))
cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
cg.add(var.set_show_action(config[CONF_SHOW_ACTION])) # cg.add(var.set_show_action(config[CONF_SHOW_ACTION]))
cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED])) cg.add(var.set_display_inversion(config[CONF_DISPLAY_INVERTED]))
cg.add(var.set_packet_timeout(config[CONF_TIMEOUT])) cg.add(var.set_packet_timeout(config[CONF_TIMEOUT]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_SUPPORTED_MODES in config: if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
if CONF_SUPPORTED_SWING_MODES in config: if CONF_SUPPORTED_SWING_MODES in config:
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) cg.add(var.set_supported_swing_modes(
config[CONF_SUPPORTED_SWING_MODES]))
if CONF_SUPPORTED_PRESETS in config: if CONF_SUPPORTED_PRESETS in config:
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_CUSTOM_PRESETS in config: if CONF_CUSTOM_PRESETS in config:
@@ -414,27 +485,27 @@ async def to_code(config):
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
DISPLAY_ACTION_SCHEMA = maybe_simple_id( DISPLAY_ACTION_SCHEMA = maybe_simple_id(
{ {
cv.Required(CONF_ID): cv.use_id(AirCon), cv.Required(CONF_ID): cv.use_id(AirCon),
} }
) )
@automation.register_action( @automation.register_action(
"aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA "aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA
) )
async def display_off_to_code(config, action_id, template_arg, args): async def display_off_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA "aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA
) )
async def display_on_to_code(config, action_id, template_arg, args): async def display_on_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
VLOUVER_ACTION_SCHEMA = maybe_simple_id( VLOUVER_ACTION_SCHEMA = maybe_simple_id(
@@ -443,55 +514,61 @@ VLOUVER_ACTION_SCHEMA = maybe_simple_id(
} }
) )
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_stop", AirConVLouverStopAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_stop", AirConVLouverStopAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_stop_to_code(config, action_id, template_arg, args): async def vlouver_stop_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_swing", AirConVLouverSwingAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_swing", AirConVLouverSwingAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_swing_to_code(config, action_id, template_arg, args): async def vlouver_swing_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_top", AirConVLouverTopAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_top", AirConVLouverTopAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_top_to_code(config, action_id, template_arg, args): async def vlouver_top_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_middle_above", AirConVLouverMiddleAboveAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_middle_above", AirConVLouverMiddleAboveAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_middle_above_to_code(config, action_id, template_arg, args): async def vlouver_middle_above_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_middle", AirConVLouverMiddleAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_middle", AirConVLouverMiddleAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_middle_to_code(config, action_id, template_arg, args): async def vlouver_middle_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_middle_below", AirConVLouverMiddleBelowAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_middle_below", AirConVLouverMiddleBelowAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_middle_below_to_code(config, action_id, template_arg, args): async def vlouver_middle_below_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_bottom", AirConVLouverBottomAction, VLOUVER_ACTION_SCHEMA "aux_ac.vlouver_bottom", AirConVLouverBottomAction, VLOUVER_ACTION_SCHEMA
) )
async def vlouver_bottom_to_code(config, action_id, template_arg, args): async def vlouver_bottom_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
VLOUVER_SET_ACTION_SCHEMA = cv.Schema( VLOUVER_SET_ACTION_SCHEMA = cv.Schema(
@@ -501,81 +578,47 @@ VLOUVER_SET_ACTION_SCHEMA = cv.Schema(
} }
) )
@automation.register_action( @automation.register_action(
"aux_ac.vlouver_set", AirConVLouverSetAction, VLOUVER_SET_ACTION_SCHEMA "aux_ac.vlouver_set", AirConVLouverSetAction, VLOUVER_SET_ACTION_SCHEMA
) )
async def vlouver_set_to_code(config, action_id, template_arg, args): async def vlouver_set_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, parent)
template_ = await cg.templatable(config[CONF_POSITION], args, int) template_ = await cg.templatable(config[CONF_POSITION], args, int)
cg.add(var.set_value(template_)) cg.add(var.set_value(template_))
return var return var
POWER_LIMITATION_OFF_ACTION_SCHEMA = maybe_simple_id( POWER_LIMITATION_OFF_ACTION_SCHEMA = maybe_simple_id(
{ {
cv.Required(CONF_ID): cv.use_id(AirCon), cv.Required(CONF_ID): cv.use_id(AirCon),
} }
) )
@automation.register_action( @automation.register_action(
"aux_ac.power_limit_off", AirConPowerLimitationOffAction, POWER_LIMITATION_OFF_ACTION_SCHEMA "aux_ac.power_limit_off", AirConPowerLimitationOffAction, POWER_LIMITATION_OFF_ACTION_SCHEMA
) )
async def power_limit_off_to_code(config, action_id, template_arg, args): async def power_limit_off_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren) return cg.new_Pvariable(action_id, template_arg, parent)
POWER_LIMITATION_ON_ACTION_SCHEMA = maybe_simple_id(
POWER_LIMITATION_ON_ACTION_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_ID): cv.use_id(AirCon), cv.Required(CONF_ID): cv.use_id(AirCon),
cv.Optional(CONF_LIMIT, default=AC_POWER_LIMIT_MIN): validate_power_limit_range, cv.Optional(CONF_LIMIT, default=AC_POWER_LIMIT_MIN): validate_power_limit_range,
} }
) )
@automation.register_action( @automation.register_action(
"aux_ac.power_limit_on", AirConPowerLimitationOnAction, POWER_LIMITATION_ON_ACTION_SCHEMA "aux_ac.power_limit_on", AirConPowerLimitationOnAction, POWER_LIMITATION_ON_ACTION_SCHEMA
) )
async def power_limit_on_to_code(config, action_id, template_arg, args): async def power_limit_on_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, parent)
template_ = await cg.templatable(config[CONF_LIMIT], args, int) template_ = await cg.templatable(config[CONF_LIMIT], args, int)
cg.add(var.set_value(template_)) cg.add(var.set_value(template_))
return var return var
# *********************************************************************************************************
# ВАЖНО! Только для инженеров!
# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт
# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает.
# Вы действуете на свой страх и риск.
# *********************************************************************************************************
SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(AirCon),
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
}
)
@automation.register_action(
"aux_ac.send_packet", AirConSendTestPacketAction, SEND_TEST_PACKET_ACTION_SCHEMA
)
async def send_packet_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
data = config[CONF_DATA]
if isinstance(data, bytes):
data = list(data)
if cg.is_template(data):
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_data_template(templ))
else:
cg.add(var.set_data_static(data))
return var

View File

@@ -0,0 +1,454 @@
#include "command_builder.h"
#include "aircon.h"
#include "frame.h"
#include "esphome/core/optional.h"
namespace esphome
{
namespace aux_airconditioner
{
CommandBuilder::CommandBuilder(AirCon &aircon)
{
_aircon = &aircon;
_command_frame = new Frame;
}
CommandBuilder::~CommandBuilder()
{
delete _command_frame;
}
CommandBuilder &CommandBuilder::init_new_command(command_type_t command_type)
{
_command_frame->clear();
switch (command_type)
{
case COMMAND_TYPE_SET_STATE:
_command_frame->append_data(_aircon->get_last_frame_11().data(), _aircon->get_last_frame_11().size());
_command_frame->set_frame_dir(FrameDirection::FRAME_DIR_TO_AC).set_frame_type(FrameType::FRAME_TYPE_COMMAND);
_command_frame->set_value(8, COMMAND).set_value(9, FLAG);
break;
case COMMAND_TYPE_REQUEST_11:
_command_frame->append_data({_command_frame->get_start_byte(), 0x00, FrameType::FRAME_TYPE_COMMAND, FrameDirection::FRAME_DIR_TO_AC, 0x00, 0x00, COMMAND_REQUEST_BODY_LENGTH, 0x00});
_command_frame->append_data({0x11, FLAG});
break;
case COMMAND_TYPE_REQUEST_21:
_command_frame->append_data({_command_frame->get_start_byte(), 0x00, FrameType::FRAME_TYPE_COMMAND, FrameDirection::FRAME_DIR_TO_AC, 0x00, 0x00, COMMAND_REQUEST_BODY_LENGTH, 0x00});
_command_frame->append_data({0x21, FLAG});
break;
case COMMAND_TYPE_NONE:
default:
ESP_LOGW(TAG, "Command type 0x%02X is unsupported", command_type);
break;
}
_command_frame->update_crc(true);
if (_command_frame->get_frame_state() == FRAME_STATE_OK)
_command_frame->set_frame_time(_aircon->ms());
return *this;
}
CommandBuilder &CommandBuilder::init_new_command(ClimateCall &cmd)
{
this->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE);
if (cmd.get_mode().has_value())
this->set_climate_mode(*cmd.get_mode());
if (cmd.get_fan_mode().has_value())
this->set_climate_fan_mode(*cmd.get_fan_mode());
else if (cmd.get_custom_fan_mode().has_value())
this->set_climate_custom_fan_mode(*cmd.get_custom_fan_mode());
if (cmd.get_preset().has_value())
this->set_climate_preset(*cmd.get_preset());
else if (cmd.get_custom_preset().has_value())
this->set_climate_custom_preset(*cmd.get_custom_preset());
if (cmd.get_swing_mode().has_value())
this->set_climate_swing_mode(*cmd.get_swing_mode());
if (cmd.get_target_temperature().has_value())
if (this->_aircon->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)
this->set_target_temperature(*cmd.get_target_temperature());
return *this;
}
CommandBuilder &CommandBuilder::fill_frame_with_command(Frame &frame)
{
_command_frame->update_crc(true);
if (_command_frame->get_frame_state() != FRAME_STATE_OK)
return *this;
frame.clear();
frame.append_data(_command_frame->data(), 8 + _command_frame->get_body_length() + 2, true);
frame.set_frame_time(_command_frame->get_frame_time());
return *this;
}
Frame CommandBuilder::get_builder_result()
{
return *_command_frame;
}
CommandBuilder &CommandBuilder::set_climate_mode(ClimateMode value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
this->set_power(value != ClimateMode::CLIMATE_MODE_OFF);
if (value == ClimateMode::CLIMATE_MODE_OFF)
return *this;
this->set_mode(climate_mode_to_ac_mode(value));
if (value == ClimateMode::CLIMATE_MODE_FAN_ONLY)
{
this->set_sleep_mode(false);
}
else if (value == ClimateMode::CLIMATE_MODE_DRY)
{
this->set_sleep_mode(false);
this->set_fan_turbo(false);
}
return *this;
}
CommandBuilder &CommandBuilder::set_climate_fan_mode(ClimateFanMode value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
switch (value)
{
case ClimateFanMode::CLIMATE_FAN_AUTO:
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_AUTO);
this->set_fan_turbo(false);
this->set_fan_mute(false);
break;
case ClimateFanMode::CLIMATE_FAN_LOW:
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_LOW);
this->set_fan_turbo(false);
this->set_fan_mute(false);
break;
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_MEDIUM);
this->set_fan_turbo(false);
this->set_fan_mute(false);
break;
case ClimateFanMode::CLIMATE_FAN_HIGH:
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_HIGH);
this->set_fan_turbo(false);
this->set_fan_mute(false);
break;
// Other possible values should be ignored
case ClimateFanMode::CLIMATE_FAN_ON:
case ClimateFanMode::CLIMATE_FAN_OFF:
case ClimateFanMode::CLIMATE_FAN_MIDDLE:
case ClimateFanMode::CLIMATE_FAN_FOCUS:
case ClimateFanMode::CLIMATE_FAN_DIFFUSE:
default:
break;
}
return *this;
}
CommandBuilder &CommandBuilder::set_climate_custom_fan_mode(std::string value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
if (value == Capabilities::CUSTOM_FAN_MODE_TURBO)
{
this->set_fan_turbo(true);
this->set_fan_mute(false);
}
else if (value == Capabilities::CUSTOM_FAN_MODE_MUTE)
{
this->set_fan_turbo(false);
this->set_fan_mute(true);
}
else
{
this->set_fan_turbo(false);
this->set_fan_mute(false);
}
return *this;
}
CommandBuilder &CommandBuilder::set_climate_preset(ClimatePreset value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
switch (value)
{
case ClimatePreset::CLIMATE_PRESET_SLEEP:
// SLEEP function works in COOL and HEAT modes. Some air conditioners allow it in AUTO and DRY mode also.
// We ignore this. Trying to enable it in any mode.
this->set_sleep_mode(true);
this->set_health_mode(false);
break;
case ClimatePreset::CLIMATE_PRESET_NONE:
this->set_health_mode(false);
this->set_sleep_mode(false);
this->set_antifungus_mode(false);
this->set_iClean_mode(false);
break;
// all other presets are ignored
case ClimatePreset::CLIMATE_PRESET_HOME:
case ClimatePreset::CLIMATE_PRESET_AWAY:
case ClimatePreset::CLIMATE_PRESET_BOOST:
case ClimatePreset::CLIMATE_PRESET_COMFORT:
case ClimatePreset::CLIMATE_PRESET_ECO:
case ClimatePreset::CLIMATE_PRESET_ACTIVITY:
default:
break;
}
return *this;
}
CommandBuilder &CommandBuilder::set_climate_custom_preset(std::string value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
if (value == Capabilities::CUSTOM_PRESET_CLEAN)
{
this->set_iClean_mode(true);
this->set_antifungus_mode(false);
}
else if (value == Capabilities::CUSTOM_PRESET_HEALTH)
{
this->set_health_mode(true);
this->set_fan_turbo(false);
this->set_fan_mute(false);
this->set_sleep_mode(false);
}
else if (value == Capabilities::CUSTOM_PRESET_ANTIFUNGUS)
{
this->set_antifungus_mode(true);
this->set_iClean_mode(false);
}
return *this;
}
CommandBuilder &CommandBuilder::set_climate_swing_mode(ClimateSwingMode value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
switch (value)
{
case ClimateSwingMode::CLIMATE_SWING_OFF:
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_OFF);
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_OFF);
break;
case ClimateSwingMode::CLIMATE_SWING_BOTH:
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_SWING_UPDOWN);
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_SWING_LEFTRIGHT);
break;
case ClimateSwingMode::CLIMATE_SWING_VERTICAL:
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_SWING_UPDOWN);
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_OFF);
break;
case ClimateSwingMode::CLIMATE_SWING_HORIZONTAL:
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_OFF);
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_SWING_LEFTRIGHT);
break;
default:
break;
}
return *this;
}
CommandBuilder &CommandBuilder::set_target_temperature(float value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
value = Capabilities::normilize_target_temperature(value);
_command_frame->set_value(10, (uint8_t)(value - 8), 0b1111'1000, 3);
_command_frame->set_bit(12, 7, (value - (uint8_t)(value) >= 0.5));
return *this;
}
CommandBuilder &CommandBuilder::set_vertical_louver(ac_louver_V value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_value(10, (uint8_t)value, 0b0000'0111);
return *this;
}
CommandBuilder &CommandBuilder::set_horizontal_louver(ac_louver_H value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_value(11, (uint8_t)value, 0b1110'0000);
return *this;
}
CommandBuilder &CommandBuilder::set_fan_speed(ac_fanspeed value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_value(13, (uint8_t)value, 0b1110'0000);
return *this;
}
CommandBuilder &CommandBuilder::set_fan_turbo(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(14, 6, value);
if (value)
_command_frame->set_bit(14, 7, false); // MUTE off
return *this;
}
CommandBuilder &CommandBuilder::set_fan_mute(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(14, 7, value);
if (value)
_command_frame->set_bit(14, 6, false); // TURBO off
return *this;
}
CommandBuilder &CommandBuilder::set_mode(ac_mode value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_value(15, (uint8_t)value, 0b1110'0000);
return *this;
}
CommandBuilder &CommandBuilder::set_fahrenheit_temperature(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(15, 1, value);
return *this;
}
CommandBuilder &CommandBuilder::set_sleep_mode(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(15, 2, value);
return *this;
}
CommandBuilder &CommandBuilder::set_power(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(18, 5, value); // power
if (value) // iClean should be off in power on mode
_command_frame->set_bit(18, 2, false); //
else // Health function should be off in power down mode
_command_frame->set_bit(18, 1, false); //
return *this;
}
CommandBuilder &CommandBuilder::set_iClean_mode(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(18, 2, value);
if (value) // iClean works in power off mode only
_command_frame->set_bit(18, 5, false);
return *this;
}
CommandBuilder &CommandBuilder::set_health_mode(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(18, 1, value);
if (value) // Health function works in power on mode only
_command_frame->set_bit(18, 5, true);
return *this;
}
CommandBuilder &CommandBuilder::set_antifungus_mode(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(20, 3, value);
return *this;
}
CommandBuilder &CommandBuilder::set_display_state(bool value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
_command_frame->set_bit(20, 4, value ^ _aircon->get_display_inversion());
return *this;
}
CommandBuilder &CommandBuilder::set_inverter_power_limitation_state(bool enabled)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
if (!_aircon->ac_type_inverter)
return *this;
_command_frame->set_bit(21, 7, enabled);
return *this;
}
CommandBuilder &CommandBuilder::set_inverter_power_limitation_value(uint8_t value)
{
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
return *this;
if (!_aircon->ac_type_inverter)
return *this;
_command_frame->set_value(21, Capabilities::normilize_inverter_power_limit(value), 0b0111'1111);
return *this;
}
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,71 @@
#pragma once
#include "aircon_common.h"
#include "helpers.h"
#include "esphome.h"
#include "esphome/components/climate/climate.h"
namespace esphome
{
namespace aux_airconditioner
{
using esphome::climate::ClimateCall;
using esphome::climate::ClimateFanMode;
using esphome::climate::ClimateMode;
using esphome::climate::ClimatePreset;
using esphome::climate::ClimateSwingMode;
class AirCon;
class Frame;
class CommandBuilder
{
private:
static const uint8_t COMMAND = 0x01;
static const uint8_t FLAG = 0x01;
static const uint8_t COMMAND_SET_BODY_LENGTH = 0x0F;
static const uint8_t COMMAND_REQUEST_BODY_LENGTH = 0x02;
AirCon *_aircon{nullptr};
Frame *_command_frame{nullptr};
public:
CommandBuilder() = delete;
CommandBuilder(AirCon &aircon);
~CommandBuilder();
CommandBuilder &init_new_command(command_type_t command_type = COMMAND_TYPE_SET_STATE);
CommandBuilder &init_new_command(ClimateCall &cmd);
CommandBuilder &fill_frame_with_command(Frame &frame);
Frame get_builder_result();
// ESPHome climate setters (high level)
CommandBuilder &set_climate_mode(ClimateMode value);
CommandBuilder &set_climate_fan_mode(ClimateFanMode value);
CommandBuilder &set_climate_custom_fan_mode(std::string value);
CommandBuilder &set_climate_preset(ClimatePreset value);
CommandBuilder &set_climate_custom_preset(std::string value);
CommandBuilder &set_climate_swing_mode(ClimateSwingMode value);
// basic setters (low level)
CommandBuilder &set_target_temperature(float value);
CommandBuilder &set_vertical_louver(ac_louver_V value);
CommandBuilder &set_horizontal_louver(ac_louver_H value);
CommandBuilder &set_fan_speed(ac_fanspeed value);
CommandBuilder &set_fan_turbo(bool value);
CommandBuilder &set_fan_mute(bool value);
CommandBuilder &set_mode(ac_mode value);
CommandBuilder &set_fahrenheit_temperature(bool value);
CommandBuilder &set_sleep_mode(bool value);
CommandBuilder &set_power(bool value);
CommandBuilder &set_iClean_mode(bool value);
CommandBuilder &set_health_mode(bool value);
CommandBuilder &set_antifungus_mode(bool value);
CommandBuilder &set_display_state(bool value);
CommandBuilder &set_inverter_power_limitation_state(bool enabled);
CommandBuilder &set_inverter_power_limitation_value(uint8_t value);
};
} // namespace aux_airconditioner
} // namespace esphome

417
components/aux_ac/frame.cpp Normal file
View File

@@ -0,0 +1,417 @@
#include "frame.h"
#include <sstream>
#include <iomanip>
namespace esphome
{
namespace aux_airconditioner
{
bool Frame::_is_header_loaded() const
{
return this->size() >= Frame::FRAME_HEADER_SIZE;
}
Frame::crc16_t Frame::_calc_crc(uint8_t data_size) const
{
Frame::crc16_t crc16;
uint8_t data_length = data_size;
uint8_t corrected_data_length = data_length + (data_length % 2); // data length should be even for crc16
uint8_t crc_buffer[corrected_data_length];
memset(crc_buffer, 0, corrected_data_length);
memcpy(crc_buffer, this->data(), data_length);
data_length = corrected_data_length;
uint32_t crc_tmp = 0;
uint16_t *p_u16 = (uint16_t *)crc_buffer;
while (data_length > 0)
{
crc_tmp += *p_u16;
p_u16++;
data_length -= 2;
}
crc_tmp = (crc_tmp >> 16) + (crc_tmp & 0xFFFF);
crc_tmp = ~crc_tmp;
crc16.crc16 = crc_tmp & 0xFFFF;
return crc16;
}
FrameState Frame::_set_frame_state(FrameState state)
{
_state = state;
return this->_state;
}
std::string Frame::_dump_data(const uint8_t *data, uint8_t data_length)
{
if (data == nullptr || data_length == 0)
return "";
uint8_t counter = 0;
std::stringstream ss;
ss << std::hex << std::uppercase;
while (counter < data_length)
{
ss << std::setfill('0') << std::setw(2) << (int)*data;
counter++;
data++;
if (counter < data_length)
ss << " ";
}
return ss.str();
}
FrameType Frame::get_frame_type() const
{
return (this->_is_header_loaded()) ? (FrameType)this->get_value(Frame::OFFSET_FRAME_TYPE) : (FrameType)0;
}
Frame &Frame::set_frame_type(FrameType frame_type)
{
if (this->_is_header_loaded())
this->set_value(Frame::OFFSET_FRAME_TYPE, frame_type);
return *this;
}
uint8_t Frame::get_body_length() const
{
return (this->_is_header_loaded()) ? this->get_value(Frame::OFFSET_BODY_LENGTH) : 0;
}
Frame &Frame::set_body_length(uint8_t body_length)
{
if (this->_is_header_loaded())
this->set_value(Frame::OFFSET_BODY_LENGTH, body_length);
return *this;
}
FrameDirection Frame::get_frame_dir() const
{
return (this->_is_header_loaded()) ? (FrameDirection)this->get_value(Frame::OFFSET_FRAME_DIRECTION) : (FrameDirection)0;
}
Frame &Frame::set_frame_dir(FrameDirection frame_direction)
{
if (this->_is_header_loaded())
this->set_value(Frame::OFFSET_FRAME_DIRECTION, frame_direction);
return *this;
}
Frame &Frame::set_frame_time(uint32_t time)
{
this->_frame_time = time;
return *this;
}
Frame &Frame::clear()
{
this->_data.clear();
this->_frame_time = 0;
this->update_frame_state();
return *this;
}
bool Frame::send(UARTComponent &uart)
{
uart.write_array(this->data(), this->size());
ESP_LOGD(TAG, "%s", this->to_string(true).c_str());
return true;
}
FrameState Frame::load(UARTComponent &uart)
{
if (!this->has_frame_state(FRAME_STATE_PARTIALLY_LOADED))
this->clear();
if (uart.available() == 0)
return this->get_frame_state();
uint8_t data_byte = 0;
if (this->has_frame_state(FRAME_STATE_BLANK))
{
while (uart.available() &&
this->has_frame_state(FRAME_STATE_BLANK))
{
if (!uart.read_byte(&data_byte))
{
ESP_LOGW(TAG, "uart read error");
break;
}
if (data_byte == this->get_start_byte())
{
this->append_data(data_byte);
this->update_frame_state();
}
}
}
while (uart.available() &&
this->has_frame_state(FRAME_STATE_PARTIALLY_LOADED))
{
this->update_frame_state();
if (this->has_frame_state(FRAME_STATE_OK))
break;
if (this->has_frame_state(FRAME_STATE_ERROR))
{
ESP_LOGW(TAG, "Broken frame received: %s", this->to_string(true).c_str());
break;
}
if (!uart.read_byte(&data_byte))
{
ESP_LOGW(TAG, "UART read error");
break;
}
this->append_data(data_byte);
}
return this->update_frame_state();
}
Frame &Frame::append_data(uint8_t data, bool update_state)
{
this->_data.insert(this->_data.end(), data);
if (update_state)
this->update_frame_state();
return *this;
}
Frame &Frame::append_data(const uint8_t data, const uint8_t count, bool update_state)
{
this->_data.insert(this->_data.end(), count, data);
if (update_state)
this->update_frame_state();
return *this;
}
Frame &Frame::append_data(std::vector<uint8_t> data, bool update_state)
{
this->_data.insert(this->_data.end(), data.begin(), data.end());
if (update_state)
this->update_frame_state();
return *this;
}
Frame &Frame::append_data(const uint8_t *data, uint8_t data_length, bool update_state)
{
if (data != nullptr && data_length != 0)
std::copy(data, data + data_length, std::back_inserter(this->_data));
if (update_state)
this->update_frame_state();
return *this;
}
Frame &Frame::trim_data(uint8_t first_element_index)
{
if (first_element_index < this->size())
{
this->_data.erase(this->_data.begin() + first_element_index, this->_data.end());
}
return *this;
}
Frame &Frame::update_crc(bool update_state)
{
if (!this->_is_header_loaded())
return *this;
uint8_t expected_frame_size = Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t);
if (this->size() < expected_frame_size - 2 ||
this->size() > expected_frame_size)
return *this;
if (this->size() > expected_frame_size)
{
this->_data.erase(this->_data.begin() + expected_frame_size, this->_data.end());
}
else
{
this->_data.insert(this->_data.end(), expected_frame_size - this->size(), 0x00);
}
crc16_t crc = this->_calc_crc(this->size() - sizeof(crc16_t));
this->_data.erase(this->_data.end() - 2, this->_data.end());
this->_data.insert(this->_data.end(), {crc.crc[0], crc.crc[1]});
if (update_state)
this->update_frame_state();
return *this;
}
bool Frame::is_valid_crc() const
{
if (this->size() < 2)
return false;
crc16_t crc;
memcpy(&crc, &(this->_data.rbegin()[1]), 2);
return this->_calc_crc(this->size() - 2).crc16 == crc.crc16;
}
bool Frame::get_bit(uint8_t data_index, uint8_t bit_index) const
{
if (bit_index > 7)
return false;
return get_value(data_index, (1 << bit_index)) >> bit_index == 1;
}
Frame &Frame::set_bit(uint8_t data_index, uint8_t bit_index, bool value)
{
if (bit_index > 7)
return *this;
this->set_value(data_index, (value << bit_index), (1 << bit_index));
return *this;
}
uint8_t Frame::get_value(uint8_t index, uint8_t mask, uint8_t shift) const
{
if (index >= this->size())
return 0;
return (this->_data[index] & mask) >> shift;
}
Frame &Frame::set_value(uint8_t index, uint8_t value, uint8_t mask, uint8_t shift)
{
if (index >= this->size())
return *this;
this->_data[index] &= ~mask;
this->_data[index] |= (value << shift) & mask;
return *this;
}
bool Frame::get_crc(uint16_t &crc16) const
{
if (this->size() < 2)
return false;
memcpy(&crc16, &(this->_data.rbegin()[1]), 2);
return true;
}
bool Frame::get_crc(uint8_t &crc16_1, uint8_t &crc16_2) const
{
if (this->size() < 2)
return false;
crc16_1 = this->_data.rbegin()[1];
crc16_2 = this->_data.rbegin()[0];
return true;
}
FrameState Frame::update_frame_state()
{
this->_state = FRAME_STATE_ERROR;
if (this->size() == 0)
return this->_set_frame_state(FRAME_STATE_BLANK);
if (this->_data[0] != this->get_start_byte())
return this->_set_frame_state(FRAME_STATE_ERROR);
if (this->size() < Frame::FRAME_HEADER_SIZE)
return this->_set_frame_state(FRAME_STATE_PARTIALLY_LOADED);
if (this->size() >= Frame::FRAME_HEADER_SIZE)
{
if (this->size() < Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t))
return this->_set_frame_state(FRAME_STATE_PARTIALLY_LOADED);
if (this->size() > Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t))
return this->_set_frame_state(FRAME_STATE_ERROR);
if (this->size() == Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t))
{
return this->_set_frame_state(this->is_valid_crc() ? FRAME_STATE_OK : FRAME_STATE_ERROR);
}
}
return this->_state;
}
std::string Frame::to_string(bool show_time) const
{
std::stringstream ss;
if (show_time)
ss << std::setfill('0') << std::setw(10) << _frame_time << ": ";
if (this->has_frame_state(FRAME_STATE_OK))
{
ss << this->direction_to_string()
<< "[" << _dump_data(this->data(), Frame::FRAME_HEADER_SIZE) << "] "
<< _dump_data(this->data() + Frame::FRAME_HEADER_SIZE, this->get_body_length()) << ((this->get_body_length() != 0) ? " " : "")
<< "[" << _dump_data(this->data() + Frame::FRAME_HEADER_SIZE + this->get_body_length(), sizeof(crc16_t)) << "]";
}
else
{
ss << "[--] " << _dump_data(this->data(), this->size());
}
return ss.str();
}
std::string Frame::state_to_string() const
{
switch (this->get_frame_state())
{
case FRAME_STATE_BLANK:
return "blank";
case FRAME_STATE_ERROR:
return "error";
case FRAME_STATE_PARTIALLY_LOADED:
return "partially loaded";
case FRAME_STATE_OK:
return "ok";
default:
return "unknown";
}
}
std::string Frame::type_to_string() const
{
switch (this->get_frame_type())
{
case FRAME_TYPE_COMMAND:
return "command";
case FRAME_TYPE_INIT:
return "init";
case FRAME_TYPE_PING:
return "ping";
case FRAME_TYPE_RESPONSE:
return "response";
case FRAME_TYPE_STRANGE:
return "strange";
default:
return "unknown";
}
}
std::string Frame::direction_to_string() const
{
return (this->get_frame_dir() == FRAME_DIR_TO_AC) ? "[=>] " : "[<=] ";
}
} // namespace aux_ac
} // namespace esphome

110
components/aux_ac/frame.h Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
#include <string>
#include <stdint.h>
#include <stddef.h>
#include <vector>
#include "esphome.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/uart/uart_component.h"
#include "frame_constants.h"
namespace esphome
{
namespace aux_airconditioner
{
using esphome::uart::UARTComponent;
// **************************************************************************************************
class Frame
{
protected:
// Frame header params
static const uint8_t FRAME_HEADER_SIZE = 8;
static const uint8_t OFFSET_START_BYTE = 0;
static const uint8_t OFFSET_FRAME_TYPE = 2;
static const uint8_t OFFSET_FRAME_DIRECTION = 3;
static const uint8_t OFFSET_BODY_LENGTH = 6;
union crc16_t
{
uint16_t crc16;
uint8_t crc[2];
} __attribute__((packed));
static_assert(sizeof(crc16_t) == 2);
uint32_t _frame_time = 0;
std::vector<uint8_t> _data = {};
FrameState _state = FRAME_STATE_BLANK;
static const uint8_t START_BYTE = 0xBB;
bool _is_header_loaded() const;
crc16_t _calc_crc(uint8_t data_size) const;
FrameState _set_frame_state(FrameState state);
static std::string _dump_data(const uint8_t *data, uint8_t data_length);
public:
Frame() : _frame_time(0){};
Frame(uint32_t time) : _frame_time(time){};
Frame(uint32_t time, FrameType frame_type, FrameDirection frame_direction)
: _frame_time(time),
_data({START_BYTE, 0x00, frame_type, frame_direction, 0x00, 0x00, 0x00, 0x00}) { this->update_frame_state(); }
Frame(uint32_t time, std::vector<uint8_t> data)
: _frame_time(time),
_data(data) { this->update_frame_state(); }
~Frame() = default;
static uint8_t get_start_byte() { return Frame::START_BYTE; };
bool has_type(FrameType frame_type) const { return get_frame_type() == frame_type; };
FrameType get_frame_type() const;
Frame &set_frame_type(FrameType frame_type);
uint8_t get_body_length() const;
Frame &set_body_length(uint8_t body_length);
FrameDirection get_frame_dir() const;
Frame &set_frame_dir(FrameDirection frame_direction);
uint32_t get_frame_time() { return this->_frame_time; };
Frame &set_frame_time(uint32_t time);
Frame &clear();
bool send(UARTComponent &uart);
FrameState load(UARTComponent &uart);
Frame &append_data(uint8_t data, bool update_state = false);
Frame &append_data(const uint8_t data, const uint8_t count, bool update_state = false);
Frame &append_data(std::vector<uint8_t> data, bool update_state = false);
Frame &append_data(const uint8_t *data, uint8_t data_length, bool update_state = false);
Frame &trim_data(uint8_t first_element_index);
Frame &update_crc(bool update_state = false);
bool is_valid_crc() const;
bool is_valid_frame() const { return this->has_frame_state(FRAME_STATE_OK); };
bool get_bit(uint8_t data_index, uint8_t bit_index) const;
Frame &set_bit(uint8_t data_index, uint8_t bit_index, bool value);
uint8_t get_value(uint8_t index, uint8_t mask = 255, uint8_t shift = 0) const;
Frame &set_value(uint8_t index, uint8_t value, uint8_t mask = 255, uint8_t shift = 0);
bool get_crc(uint16_t &crc16) const;
bool get_crc(uint8_t &crc16_1, uint8_t &crc16_2) const;
FrameState get_frame_state() const { return this->_state; };
bool has_frame_state(FrameState frame_state) const { return this->get_frame_state() == frame_state; };
FrameState update_frame_state();
const uint8_t *data() const { return this->_data.data(); };
uint8_t size() const { return this->_data.size(); };
std::string to_string(bool show_time = false) const;
std::string state_to_string() const;
std::string type_to_string() const;
std::string direction_to_string() const;
};
} // namespace aux_ac
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
namespace esphome
{
namespace aux_airconditioner
{
enum FrameType : uint8_t
{
FRAME_TYPE_PING = 0x01,
FRAME_TYPE_COMMAND = 0x06,
FRAME_TYPE_RESPONSE = 0x07,
FRAME_TYPE_INIT = 0x09,
FRAME_TYPE_STRANGE = 0x0b,
};
enum FrameDirection : uint8_t
{
FRAME_DIR_TO_DONGLE = 0x00,
FRAME_DIR_TO_AC = 0x80,
};
enum FrameState : uint8_t
{
FRAME_STATE_BLANK = 0x00,
FRAME_STATE_PARTIALLY_LOADED = 0x01,
FRAME_STATE_OK = 0x0F,
FRAME_STATE_ERROR = 0xFF,
};
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,371 @@
#include "frame_processor.h"
#include "frame.h"
#include "helpers.h"
#include <algorithm>
namespace esphome
{
namespace aux_airconditioner
{
using esphome::helpers::update_property;
/*********************************************************************************************\
\*********************************************************************************************/
void FrameProcessorInterface::process(const Frame &frame, AirCon &aircon) const
{
if (!this->applicable(frame))
return;
if (!aircon.is_hardware_connected())
return;
aircon.reset_ping_timeout();
this->_specific_process(frame, aircon);
}
/*********************************************************************************************\
\*********************************************************************************************/
bool FrameProcessorPing::applicable(const Frame &frame) const
{
return frame.has_type(FrameType::FRAME_TYPE_PING);
}
FrameType FrameProcessorPing::get_applicable_frame_type() const
{
return FrameType::FRAME_TYPE_PING;
}
void FrameProcessorPing::_specific_process(const Frame &frame, AirCon &aircon) const
{
aircon.schedule_ping_response();
}
/*********************************************************************************************\
\*********************************************************************************************/
bool FrameProcessorResponse01::applicable(const Frame &frame) const
{
return frame.has_type(FrameType::FRAME_TYPE_RESPONSE) &&
frame.get_body_length() == 0x04 &&
frame.get_value(9) == 0x01;
}
FrameType FrameProcessorResponse01::get_applicable_frame_type() const
{
return FrameType::FRAME_TYPE_RESPONSE;
}
void FrameProcessorResponse01::_specific_process(const Frame &frame, AirCon &aircon) const
{
}
/*********************************************************************************************\
\*********************************************************************************************/
ClimateMode FrameProcessorResponse11::_power_and_mode_to_climate_mode(bool power_on, ac_mode mode) const
{
ClimateMode result = ClimateMode::CLIMATE_MODE_OFF;
if (power_on)
{
switch (mode)
{
case AC_MODE_AUTO:
result = ClimateMode::CLIMATE_MODE_HEAT_COOL;
break;
case AC_MODE_COOL:
result = ClimateMode::CLIMATE_MODE_COOL;
break;
case AC_MODE_DRY:
result = ClimateMode::CLIMATE_MODE_DRY;
break;
case AC_MODE_HEAT:
result = ClimateMode::CLIMATE_MODE_HEAT;
break;
case AC_MODE_FAN:
result = ClimateMode::CLIMATE_MODE_FAN_ONLY;
break;
default:
ESP_LOGW(TAG, "Warning: unknown air conditioner mode: 0x%02X", mode);
break;
}
}
return result;
}
bool FrameProcessorResponse11::applicable(const Frame &frame) const
{
return frame.has_type(FrameType::FRAME_TYPE_RESPONSE) &&
frame.get_body_length() == 0x0F &&
frame.get_value(9) == 0x11;
}
FrameType FrameProcessorResponse11::get_applicable_frame_type() const
{
return FrameType::FRAME_TYPE_RESPONSE;
}
void FrameProcessorResponse11::_specific_process(const Frame &frame, AirCon &aircon) const
{
aircon.set_last_frame(frame);
bool state_changed = false;
// target temperature:
// byte 10: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b10
// byte 12: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12
update_property(aircon.target_temperature, (float)(8.0 + (float)frame.get_value(10, 0b1111'1000, 3) + (frame.get_bit(12, 7) ? 0.5 : 0.0)), state_changed);
// vertical louver state:
// byte 10: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b10
// horizontal louver state:
// byte 11: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b11
update_property(aircon.louver_vertical, (ac_louver_V)frame.get_value(10, 0b0000'0111), state_changed);
update_property(aircon.louver_horizontal, (ac_louver_H)frame.get_value(11, 0b1110'0000), state_changed);
if (aircon.louver_vertical == AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal != AC_LOUVERH_SWING_LEFTRIGHT)
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_VERTICAL, state_changed);
else if (aircon.louver_vertical != AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal == AC_LOUVERH_SWING_LEFTRIGHT)
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_HORIZONTAL, state_changed);
else if (aircon.louver_vertical == AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal == AC_LOUVERH_SWING_LEFTRIGHT)
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_BOTH, state_changed);
else if (aircon.louver_vertical != AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal != AC_LOUVERH_SWING_LEFTRIGHT)
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_OFF, state_changed);
// last IR-command was this time ago (minutes)
// byte 12: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12
update_property(aircon.last_IR_passed, frame.get_value(12, 0b0011'1111), state_changed);
// fan speed:
// byte 13: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b13
update_property(aircon.fan_mode, ac_fanspeed_to_climate_fan_mode((ac_fanspeed)frame.get_value(13, 0b1110'0000)), state_changed);
// timer activation & delay:
// byte 13: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b13
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b14
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
// timer.delay_minutes_uint16 = frame.get_value(13, 0b0001'1111) * 60 + frame.get_value(14, 0b0001'1111);
// timer.enabled_bool = frame.get_bit(18, 6);
// update_property(aircon.???, ???, state_changed);
// fan TURBO mode:
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b14
if (frame.get_bit(14, 6))
{
update_property(aircon.custom_fan_mode, Capabilities::CUSTOM_FAN_MODE_TURBO, state_changed);
}
else if (aircon.custom_fan_mode == Capabilities::CUSTOM_FAN_MODE_TURBO)
{
update_property(aircon.custom_fan_mode, (std::string) "", state_changed);
}
// fan MUTE mode:
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b14
if (frame.get_bit(14, 7))
{
update_property(aircon.custom_fan_mode, Capabilities::CUSTOM_FAN_MODE_MUTE, state_changed);
}
else if (aircon.custom_fan_mode == Capabilities::CUSTOM_FAN_MODE_MUTE)
{
update_property(aircon.custom_fan_mode, (std::string) "", state_changed);
}
// power & mode:
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
update_property(aircon.mode, _power_and_mode_to_climate_mode(frame.get_bit(18, 5), (ac_mode)frame.get_value(15, 0b1110'0000)), state_changed);
// temperature: Celsius or Fahrenheit
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
if (update_property(aircon.temperature_in_fahrenheit, frame.get_bit(15, 1), state_changed))
aircon.update_all_sensors_unit_of_measurement();
// SLEEP preset:
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
if (frame.get_bit(15, 2))
{
update_property(aircon.preset, ClimatePreset::CLIMATE_PRESET_SLEEP, state_changed);
}
else if (aircon.preset == ClimatePreset::CLIMATE_PRESET_SLEEP)
{
update_property(aircon.preset, ClimatePreset::CLIMATE_PRESET_NONE, state_changed);
}
// iFeel function: disabled due to uselessness (and doesn't work with wi-fi)
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
// update_property(aircon.iFeel, frame.get_bit(15, 3), state_changed);
// iClean function:
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
if (frame.get_bit(18, 2) && !frame.get_bit(18, 5)) // iClean + Power_Off
{
update_property(aircon.custom_preset, Capabilities::CUSTOM_PRESET_CLEAN, state_changed);
}
else if (aircon.custom_preset == Capabilities::CUSTOM_PRESET_CLEAN)
{
update_property(aircon.custom_preset, (std::string) "", state_changed);
}
// Health function:
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
if (frame.get_bit(18, 1) && frame.get_bit(18, 5)) // Health + Power_On
{
update_property(aircon.custom_preset, Capabilities::CUSTOM_PRESET_HEALTH, state_changed);
}
else if (aircon.custom_preset == Capabilities::CUSTOM_PRESET_HEALTH)
{
update_property(aircon.custom_preset, (std::string) "", state_changed);
}
// Antifungus function:
// byte 20: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b20
if (frame.get_bit(20, 3))
{
update_property(aircon.custom_preset, Capabilities::CUSTOM_PRESET_ANTIFUNGUS, state_changed);
}
else if (aircon.custom_preset == Capabilities::CUSTOM_PRESET_ANTIFUNGUS)
{
update_property(aircon.custom_preset, (std::string) "", state_changed);
}
// Display:
// byte 20: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b20
update_property(aircon.display_enabled, (bool)(frame.get_bit(20, 4) ^ aircon.get_display_inversion()), state_changed);
// Power limitation for inverters:
// byte 21: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b21
if (aircon.ac_type_inverter)
{
update_property(aircon.inverter_power_limitation_value, frame.get_value(21, 0b0111'1111), state_changed);
update_property(aircon.inverter_power_limitation_on, frame.get_bit(21, 7), state_changed);
}
else
{
aircon.inverter_power_limitation_value.reset();
aircon.inverter_power_limitation_on.reset();
}
if (state_changed)
{
aircon.publish_all_states();
}
}
/*********************************************************************************************\
\*********************************************************************************************/
bool FrameProcessorResponse2x::applicable(const Frame &frame) const
{
return frame.has_type(FrameType::FRAME_TYPE_RESPONSE) &&
frame.get_body_length() == 0x18 &&
frame.get_value(9, 0b11110000) == 0x20;
}
FrameType FrameProcessorResponse2x::get_applicable_frame_type() const
{
return FrameType::FRAME_TYPE_RESPONSE;
}
void FrameProcessorResponse2x::_specific_process(const Frame &frame, AirCon &aircon) const
{
aircon.set_last_frame(frame);
bool state_changed = false;
// TODO: doublecheck the temperature bytes. Probably here is a mess...
// air conditioner type:
// byte 10: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b10
update_property(aircon.ac_type_inverter, frame.get_bit(10, 5), state_changed);
// byte 11: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b11
// iClean + defrost
// byte 12: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b12
update_property(aircon.defrost_enabled, frame.get_bit(12, 5), state_changed);
// real FAN speed
// byte 13: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b13
update_property(aircon.real_fan_speed, (ac_fanspeed_real)frame.get_value(13, 0b0000'0111), state_changed);
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b14
// ambient indoor temperature:
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b15
// byte 31: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31
update_property(aircon.current_temperature, (float)(frame.get_value(31, 0b0000'1111) / 10.0 + frame.get_value(15) - 0x20), state_changed);
// byte 16: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b16
// indoor coil temperature:
// byte 17: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b17
if (frame.get_value(17) >= 0x20)
update_property(aircon.temperature_indoor_coil, (uint8_t)(frame.get_value(17) - 0x20), state_changed);
else
aircon.temperature_indoor_coil.reset();
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b18
// TODO: maybe this is a mess
if (frame.get_value(18) >= 0x20)
update_property(aircon.temperature_outdoor_ambient, (uint8_t)(frame.get_value(18) - 0x20), state_changed);
else
aircon.temperature_outdoor_ambient.reset();
// byte 19: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b19
// condenser middle temperature sensor:
// byte 20: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b20
if (frame.get_value(20) >= 0x20)
update_property(aircon.temperature_condenser_middle, (uint8_t)(frame.get_value(20) - 0x20), state_changed);
else
aircon.temperature_condenser_middle.reset();
// temperature sensor #2 "PIPE"?:
// byte 21: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b21
// This byte is equal to 0x20 for inverters without this sensor.
// This byte is equal to 0x00 for on-off air conditioners.
if (frame.get_value(21) >= 0x20)
update_property(aircon.temperature_outdoor_suction, (uint8_t)(frame.get_value(21) - 0x20), state_changed);
else
aircon.temperature_outdoor_suction.reset();
// compressor temperature:
// byte 22: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22
if (frame.get_value(22, 0b0111'1111) >= 0x20)
update_property(aircon.temperature_outdoor_discharge, (uint8_t)(frame.get_value(22, 0b0111'1111) - 0x20), state_changed);
else
aircon.temperature_outdoor_discharge.reset();
// byte 23: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23
// TODO: maybe this is a mess
if (frame.get_value(23) >= 0x20)
update_property(aircon.temperature_outdoor_defrost, (uint8_t)(frame.get_value(23) - 0x20), state_changed);
else
aircon.temperature_outdoor_defrost.reset();
// inverter power (0..100 %)
// byte 24: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b24
if (aircon.ac_type_inverter)
update_property(aircon.inverter_power, frame.get_value(24, 0b0111'1111), state_changed);
else
aircon.inverter_power.reset();
// byte 25: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25
// byte 26: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b26
// byte 27: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27
// byte 28: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b28
// byte 29: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b29
// byte 30: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b30
// ambient temperature fractional part (see byte 15)
// byte 31: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31
if (state_changed)
aircon.publish_all_states();
}
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,87 @@
#pragma once
#include <map>
#include <list>
#include "esphome.h"
#include "esphome/components/climate/climate.h"
#include "aircon_common.h"
#include "frame_constants.h"
namespace esphome
{
namespace aux_airconditioner
{
using esphome::climate::ClimateAction;
using esphome::climate::ClimateFanMode;
using esphome::climate::ClimateMode;
using esphome::climate::ClimatePreset;
using esphome::climate::ClimateSwingMode;
class AirCon;
class Frame;
//*********************************************************************************************
class FrameProcessorInterface
{
protected:
virtual void _specific_process(const Frame &frame, AirCon &aircon) const = 0;
public:
virtual ~FrameProcessorInterface() = default;
virtual bool applicable(const Frame &frame) const = 0;
virtual FrameType get_applicable_frame_type() const = 0;
void process(const Frame &frame, AirCon &aircon) const;
};
//*********************************************************************************************
class FrameProcessorPing : public FrameProcessorInterface
{
protected:
void _specific_process(const Frame &frame, AirCon &aircon) const override;
public:
FrameProcessorPing() = default;
bool applicable(const Frame &frame) const override;
FrameType get_applicable_frame_type() const override;
};
//*********************************************************************************************
class FrameProcessorResponse01 : public FrameProcessorInterface
{
protected:
void _specific_process(const Frame &frame, AirCon &aircon) const override;
public:
FrameProcessorResponse01() = default;
bool applicable(const Frame &frame) const override;
FrameType get_applicable_frame_type() const override;
};
//*********************************************************************************************
class FrameProcessorResponse11 : public FrameProcessorInterface
{
protected:
ClimateMode _power_and_mode_to_climate_mode(bool power_on, ac_mode mode) const;
void _specific_process(const Frame &frame, AirCon &aircon) const override;
public:
FrameProcessorResponse11() = default;
bool applicable(const Frame &frame) const override;
FrameType get_applicable_frame_type() const override;
};
//*********************************************************************************************
class FrameProcessorResponse2x : public FrameProcessorInterface
{
protected:
void _specific_process(const Frame &frame, AirCon &aircon) const override;
public:
FrameProcessorResponse2x() = default;
bool applicable(const Frame &frame) const override;
FrameType get_applicable_frame_type() const override;
};
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,74 @@
#include "frame_processor_manager.h"
#include "frame.h"
#include <algorithm>
namespace esphome
{
namespace aux_airconditioner
{
void FrameProcessorManager::_update_map()
{
_processor_map.clear();
for (FrameProcessorInterface *processor : _processors)
{
auto it = std::find(_processor_map[processor->get_applicable_frame_type()].begin(), _processor_map[processor->get_applicable_frame_type()].end(), processor);
if (it == _processor_map[processor->get_applicable_frame_type()].end())
_processor_map[processor->get_applicable_frame_type()].push_back(processor);
}
}
FrameProcessorManager::FrameProcessorManager()
{
_processors.clear();
_processors.push_back(new FrameProcessorPing);
_processors.push_back(new FrameProcessorResponse01);
_processors.push_back(new FrameProcessorResponse11);
_processors.push_back(new FrameProcessorResponse2x);
this->_update_map();
}
void FrameProcessorManager::add_frame_processor(FrameProcessorInterface *frame_processor)
{
if (frame_processor == nullptr)
return;
_processors.push_back(frame_processor);
this->_update_map();
}
void FrameProcessorManager::delete_all_processors()
{
while (!_processors.empty())
{
delete _processors.front();
_processors.pop_front();
}
_processor_map.clear();
}
void FrameProcessorManager::process_frame(Frame &frame)
{
auto processor_it = _processor_map.find(frame.get_frame_type());
if (processor_it == _processor_map.end())
{
ESP_LOGW(TAG, "No processor for frame type 0x%02X (%s). Frame: %s", frame.get_frame_type(), frame.type_to_string().c_str(), frame.to_string().c_str());
return;
}
// check if list of processors is empty
if (processor_it->second.size() == 0)
return;
for (FrameProcessorInterface *processor : processor_it->second)
{
if (processor->applicable(frame))
{
processor->process(frame, *_aircon);
}
}
}
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,34 @@
#pragma once
#include "frame_processor.h"
#include <map>
#include <list>
namespace esphome
{
namespace aux_airconditioner
{
class AirCon;
class FrameProcessorInterface;
class Frame;
class FrameProcessorManager
{
protected:
AirCon *_aircon = nullptr;
std::map<uint8_t, std::list<FrameProcessorInterface *>> _processor_map;
std::list<FrameProcessorInterface *> _processors;
void _update_map();
public:
FrameProcessorManager();
~FrameProcessorManager() { this->delete_all_processors(); }
void set_aircon(AirCon &aircon) { _aircon = &aircon; }
void add_frame_processor(FrameProcessorInterface *frame_processor);
void delete_all_processors();
void process_frame(Frame &frame);
};
} // namespace aux_airconditioner
} // namespace esphome

View File

@@ -0,0 +1,12 @@
#include "helpers.h"
namespace esphome
{
namespace helpers
{
uint32_t TimerManager::_millis = 0;
} // namespace helpers
} // namespace GrKoR

117
components/aux_ac/helpers.h Normal file
View File

@@ -0,0 +1,117 @@
#pragma once
#include <stdint.h>
#include <functional>
#include <list>
#include "esphome/core/optional.h"
namespace esphome
{
namespace helpers
{
/*************************************************************************************************\
\*************************************************************************************************/
class TimerInterface
{
public:
virtual bool is_expired() const = 0;
virtual bool is_enabled() const = 0;
virtual void start(uint32_t period_ms) = 0;
virtual void stop() = 0;
virtual void reset() = 0;
virtual void set_callback(std::function<void(TimerInterface *)> callback) = 0;
virtual void trigger_callback() = 0;
};
/*************************************************************************************************\
\*************************************************************************************************/
using millis_function_t = uint32_t (*)();
class TimerManager
{
public:
static void set_millis(uint32_t current_time) { TimerManager::_millis = current_time; }
static uint32_t get_millis() { return TimerManager::_millis; }
void set_millis_func(millis_function_t millis) { _millis_func = millis; }
void register_timer(TimerInterface &timer) { _timers.push_back(&timer); }
void task()
{
if (_millis_func != nullptr)
_millis = _millis_func();
for (auto timer : _timers)
if (timer->is_enabled() && timer->is_expired())
timer->trigger_callback();
}
private:
millis_function_t _millis_func{nullptr};
static uint32_t _millis;
std::list<TimerInterface *> _timers;
};
/*************************************************************************************************\
\*************************************************************************************************/
static void dummy_stopper(TimerInterface *timer) { timer->stop(); }
class Timer : public TimerInterface
{
public:
Timer() : _callback(dummy_stopper), _period_ms(0) {}
virtual bool is_expired() const override { return TimerManager::get_millis() - this->_last_trigger_time >= this->_period_ms; }
virtual bool is_enabled() const override { return this->_period_ms > 0; }
virtual void start(uint32_t period_ms) override
{
this->_period_ms = period_ms;
this->reset();
}
virtual void stop() override { this->_period_ms = 0; }
virtual void reset() override { this->_last_trigger_time = TimerManager::get_millis(); }
virtual void set_callback(std::function<void(TimerInterface *)> callback) override { this->_callback = callback; }
virtual void trigger_callback() override
{
this->_callback((TimerInterface *)this);
this->reset();
}
private:
std::function<void(TimerInterface *)> _callback = nullptr;
uint32_t _period_ms;
uint32_t _last_trigger_time;
};
/*********************************************************************************************\
\*********************************************************************************************/
template <typename T>
bool update_property(T &property, const T &value, bool &flag)
{
if (property != value)
{
property = value;
flag = true;
return true;
}
return false;
}
template <typename T>
bool update_property(optional<T> &property, const T &value, bool &flag)
{
if (property != value)
{
property = value;
flag = true;
return true;
}
return false;
}
} // namespace helpers
} // namespace esphome

View File

@@ -63,39 +63,46 @@ climate:
id: aux_id id: aux_id
uart_id: ac_uart_bus uart_id: ac_uart_bus
period: 7s period: 7s
show_action: true
display_inverted: true display_inverted: true
optimistic: true optimistic: true
indoor_temperature:
name: ${upper_devicename} Indoor Temperature
id: ${devicename}_indoor_temp
internal: false
display_state: display_state:
name: ${upper_devicename} Display State name: ${upper_devicename} Display State
id: ${devicename}_display_state id: ${devicename}_display_state
internal: false internal: false
outdoor_temperature: indoor_ambient_temperature:
name: ${upper_devicename} Outdoor Temperature name: ${upper_devicename} Indoor Ambient Temperature
id: ${devicename}_outdoor_temp id: ${devicename}_indoor_ambient_temp
internal: false internal: false
outbound_temperature: indoor_coil_temperature:
name: ${upper_devicename} Coolant Outbound Temperature name: ${upper_devicename} Indoor Coil Temperature
id: ${devicename}_outbound_temp id: ${devicename}_indoor_coil_temp
internal: false internal: false
inbound_temperature: outdoor_ambient_temperature:
name: ${upper_devicename} Coolant Inbound Temperature name: ${upper_devicename} Outdoor Ambient Temperature
id: ${devicename}_inbound_temp id: ${devicename}_outdoor_ambient_temp
internal: false internal: false
compressor_temperature: outdoor_condenser_temperature:
name: ${upper_devicename} Compressor Temperature name: ${upper_devicename} Outdoor Condenser Temperature
id: ${devicename}_strange_temp id: ${devicename}_outdoor_condenser_temp
internal: false
compressor_suction_temperature:
name: ${upper_devicename} Compressor Suction Temperature
id: ${devicename}_compressor_suction_temp
internal: false
compressor_discharge_temperature:
name: ${upper_devicename} Compressor Discharge Temperature
id: ${devicename}_compressor_discharge_temp
internal: false
defrost_temperature:
name: ${upper_devicename} Defrost Temperature
id: ${devicename}_defrost_temp
internal: false internal: false
defrost_state: defrost_state:
name: ${upper_devicename} Defrost State name: ${upper_devicename} Defrost State
id: ${devicename}_defrost_state id: ${devicename}_defrost_state
internal: false internal: false
inverter_power: inverter_power:
name: ${upper_devicename} Invertor Power name: ${upper_devicename} Inverter Power
id: ${devicename}_inverter_power id: ${devicename}_inverter_power
internal: false internal: false
preset_reporter: preset_reporter:
@@ -109,7 +116,9 @@ climate:
visual: visual:
min_temperature: 16 min_temperature: 16
max_temperature: 32 max_temperature: 32
temperature_step: 0.5 temperature_step:
target_temperature: 0.5
current_temperature: 0.1
supported_modes: supported_modes:
- HEAT_COOL - HEAT_COOL
- COOL - COOL
@@ -138,6 +147,8 @@ sensor:
update_interval: 30s update_interval: 30s
unit_of_measurement: "dBa" unit_of_measurement: "dBa"
accuracy_decimals: 0 accuracy_decimals: 0
- platform: uptime
name: ${upper_devicename} Uptime Sensor
switch: switch:
@@ -154,43 +165,72 @@ switch:
turn_off_action: turn_off_action:
- aux_ac.display_off: aux_id - aux_ac.display_off: aux_id
- platform: template
name: ${upper_devicename} Power Limitation
lambda: |-
if (id(${devicename}_inverter_power).state) {
return true;
} else {
return false;
}
turn_on_action:
#- aux_ac.power_limit_on: aux_id
- aux_ac.power_limit_on:
id: aux_id
limit: 40
turn_off_action:
- aux_ac.power_limit_off: aux_id
button: button:
- platform: template
name: ${upper_devicename} 26 deg Cool Low Fan
on_press:
- climate.control:
id: aux_id
mode: COOL
target_temperature: 26°C
fan_mode: LOW
#custom_fan_mode: MUTE
swing_mode: "OFF"
- platform: template - platform: template
name: ${upper_devicename} VLouver Stop name: ${upper_devicename} VLouver Stop
icon: "mdi:circle-small" icon: "mdi:circle-small"
on_press: on_press:
- aux_ac.vlouver_stop: aux_id - aux_ac.vlouver_stop: aux_id
- platform: template - platform: template
name: ${upper_devicename} VLouver Swing name: ${upper_devicename} VLouver Swing
icon: "mdi:pan-vertical" icon: "mdi:pan-vertical"
on_press: on_press:
- aux_ac.vlouver_swing: aux_id - aux_ac.vlouver_swing: aux_id
- platform: template - platform: template
name: ${upper_devicename} VLouver Top name: ${upper_devicename} VLouver Top
icon: "mdi:pan-up" icon: "mdi:pan-up"
on_press: on_press:
- aux_ac.vlouver_top: aux_id - aux_ac.vlouver_top: aux_id
- platform: template - platform: template
name: ${upper_devicename} VLouver Middle Above name: ${upper_devicename} VLouver Middle Above
icon: "mdi:pan-top-left" icon: "mdi:pan-top-left"
on_press: on_press:
- aux_ac.vlouver_middle_above: aux_id - aux_ac.vlouver_middle_above: aux_id
- platform: template - platform: template
name: ${upper_devicename} VLouver Middle name: ${upper_devicename} VLouver Middle
icon: "mdi:pan-left" icon: "mdi:pan-left"
on_press: on_press:
- aux_ac.vlouver_middle: aux_id - aux_ac.vlouver_middle: aux_id
- platform: template - platform: template
name: ${upper_devicename} VLouver Middle Below name: ${upper_devicename} VLouver Middle Below
icon: "mdi:pan-bottom-left" icon: "mdi:pan-bottom-left"
on_press: on_press:
- aux_ac.vlouver_middle_below: aux_id - aux_ac.vlouver_middle_below: aux_id
- platform: template - platform: template
name: ${upper_devicename} VLouver Bottom name: ${upper_devicename} VLouver Bottom
icon: "mdi:pan-down" icon: "mdi:pan-down"
@@ -226,4 +266,5 @@ number:
step: 1 step: 1
set_action: set_action:
then: then:
- lambda: !lambda "id(aux_id).powerLimitationOnSequence( x );" - lambda: !lambda "id(aux_id).action_power_limitation_on( x );"