mirror of
https://github.com/GrKoR/esphome_aux_ac_component.git
synced 2026-01-01 14:19:13 +03:00
v.1 beta 1 initial commit
This commit is contained in:
805
components/aux_ac/aircon.cpp
Normal file
805
components/aux_ac/aircon.cpp
Normal 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
276
components/aux_ac/aircon.h
Normal 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
|
||||
245
components/aux_ac/aircon_common.cpp
Normal file
245
components/aux_ac/aircon_common.cpp
Normal 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
|
||||
106
components/aux_ac/aircon_common.h
Normal file
106
components/aux_ac/aircon_common.h
Normal 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
|
||||
@@ -1,186 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include "aux_ac.h"
|
||||
#include "aircon.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aux_ac {
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
class AirCon;
|
||||
|
||||
// **************************************** DISPLAY ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOffAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {}
|
||||
// **************************************** DISPLAY ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOffAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOnAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOnAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
// **************************************** VERTICAL LOUVER ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSwingAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverSwingAction(AirCon *ac) : ac_(ac) {}
|
||||
// **************************************** VERTICAL LOUVER ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSwingAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverStopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverStopAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverStopAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverTopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverTopAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverTopAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAboveAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverMiddleAboveAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAboveAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverMiddleAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleBelowAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverMiddleBelowAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleBelowAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverBottomAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverBottomAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverBottomAction : public Action<Ts...>
|
||||
{
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSetAction : public Action<Ts...> {
|
||||
public:
|
||||
AirConVLouverSetAction(AirCon *ac) : ac_(ac) {}
|
||||
TEMPLATABLE_VALUE(uint8_t, value);
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSetAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
AirConVLouverSetAction(AirCon *ac) : ac_(ac) {}
|
||||
TEMPLATABLE_VALUE(uint8_t, value);
|
||||
|
||||
void play(Ts... x) {
|
||||
vlpos_ = this->value_.value(x...);
|
||||
this->ac_->setVLouverFrontendSequence((ac_vlouver_frontend)vlpos_);
|
||||
}
|
||||
void play(Ts... x)
|
||||
{
|
||||
this->ac_->action_set_vlouver_position((vlouver_esphome_position_t)this->value_.value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
uint8_t vlpos_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
// **************************************** SEND TEST PACKET ACTION ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConSendTestPacketAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {}
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->static_ = false;
|
||||
}
|
||||
void set_data_static(const std::vector<uint8_t> &data) {
|
||||
this->data_static_ = data;
|
||||
this->static_ = true;
|
||||
}
|
||||
// **************************************** POWER LIMITATION ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOffAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConPowerLimitationOffAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->static_) {
|
||||
this->ac_->sendTestPacket(this->data_static_);
|
||||
} else {
|
||||
auto val = this->data_func_(x...);
|
||||
this->ac_->sendTestPacket(val);
|
||||
}
|
||||
}
|
||||
void play(Ts... x) override { this->ac_->action_power_limitation_off(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
// **************************************** POWER LIMITATION ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOffAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConPowerLimitationOffAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOnAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
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:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOnAction : public Action<Ts...> {
|
||||
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
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,30 +38,43 @@ CODEOWNERS = ["@GrKoR"]
|
||||
DEPENDENCIES = ["climate", "uart"]
|
||||
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_OUTDOOR_TEMPERATURE = "outdoor_temperature"
|
||||
ICON_OUTDOOR_TEMPERATURE = "mdi:home-thermometer-outline"
|
||||
CONF_INDOOR_AMBIENT_TEMPERATURE = "indoor_ambient_temperature"
|
||||
|
||||
CONF_INBOUND_TEMPERATURE = "inbound_temperature"
|
||||
ICON_INBOUND_TEMPERATURE = "mdi:thermometer-plus"
|
||||
CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature"
|
||||
ICON_INDOOR_COIL_TEMPERATURE = "mdi:thermometer-plus"
|
||||
|
||||
CONF_OUTBOUND_TEMPERATURE = "outbound_temperature"
|
||||
ICON_OUTBOUND_TEMPERATURE = "mdi:thermometer-minus"
|
||||
CONF_OUTDOOR_AMBIENT_TEMPERATURE = "outdoor_ambient_temperature"
|
||||
ICON_OUTDOOR_AMBIENT_TEMPERATURE = "mdi:home-thermometer-outline"
|
||||
|
||||
CONF_COMPRESSOR_TEMPERATURE = "compressor_temperature"
|
||||
ICON_COMPRESSOR_TEMPERATURE = "mdi:thermometer-lines"
|
||||
CONF_OUTDOOR_CONDENSER_TEMPERATURE = "outdoor_condenser_temperature"
|
||||
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"
|
||||
ICON_DISPLAY_STATE = "mdi:clock-digital"
|
||||
|
||||
CONF_INVERTER_POWER = "inverter_power"
|
||||
CONF_INVERTER_POWER_DEPRICATED = "invertor_power"
|
||||
|
||||
CONF_DEFROST_STATE = "defrost_state"
|
||||
ICON_DEFROST = "mdi:snowflake-melt"
|
||||
ICON_DEFROST_STATE = "mdi:snowflake-melt"
|
||||
|
||||
CONF_DISPLAY_INVERTED = "display_inverted"
|
||||
ICON_DISPLAY = "mdi:clock-digital"
|
||||
|
||||
CONF_PRESET_REPORTER = "preset_reporter"
|
||||
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"
|
||||
|
||||
|
||||
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)
|
||||
Capabilities = aux_ac_ns.namespace("Constants")
|
||||
Capabilities = aux_ac_ns.namespace("Capabilities")
|
||||
|
||||
# Display actions
|
||||
AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action)
|
||||
AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action)
|
||||
AirConDisplayOffAction = aux_ac_ns.class_(
|
||||
"AirConDisplayOffAction", automation.Action)
|
||||
AirConDisplayOnAction = aux_ac_ns.class_(
|
||||
"AirConDisplayOnAction", automation.Action)
|
||||
|
||||
# test packet action
|
||||
AirConSendTestPacketAction = aux_ac_ns.class_(
|
||||
"AirConSendTestPacketAction", automation.Action
|
||||
)
|
||||
# AirConSendTestPacketAction = aux_ac_ns.class_(
|
||||
# "AirConSendTestPacketAction", automation.Action
|
||||
# )
|
||||
|
||||
# vertical louvers actions
|
||||
AirConVLouverSwingAction = aux_ac_ns.class_(
|
||||
"AirConVLouverSwingAction", automation.Action
|
||||
)
|
||||
AirConVLouverStopAction = aux_ac_ns.class_("AirConVLouverStopAction", automation.Action)
|
||||
AirConVLouverTopAction = aux_ac_ns.class_("AirConVLouverTopAction", automation.Action)
|
||||
"AirConVLouverSwingAction", automation.Action)
|
||||
AirConVLouverStopAction = aux_ac_ns.class_(
|
||||
"AirConVLouverStopAction", automation.Action)
|
||||
AirConVLouverTopAction = aux_ac_ns.class_(
|
||||
"AirConVLouverTopAction", automation.Action)
|
||||
AirConVLouverMiddleAboveAction = aux_ac_ns.class_(
|
||||
"AirConVLouverMiddleAboveAction", automation.Action
|
||||
)
|
||||
"AirConVLouverMiddleAboveAction", automation.Action)
|
||||
AirConVLouverMiddleAction = aux_ac_ns.class_(
|
||||
"AirConVLouverMiddleAction", automation.Action
|
||||
)
|
||||
"AirConVLouverMiddleAction", automation.Action)
|
||||
AirConVLouverMiddleBelowAction = aux_ac_ns.class_(
|
||||
"AirConVLouverMiddleBelowAction", automation.Action
|
||||
)
|
||||
"AirConVLouverMiddleBelowAction", automation.Action)
|
||||
AirConVLouverBottomAction = aux_ac_ns.class_(
|
||||
"AirConVLouverBottomAction", automation.Action
|
||||
)
|
||||
"AirConVLouverBottomAction", automation.Action)
|
||||
AirConVLouverSetAction = aux_ac_ns.class_(
|
||||
"AirConVLouverSetAction", automation.Action
|
||||
)
|
||||
"AirConVLouverSetAction", automation.Action)
|
||||
|
||||
# power limitation actions
|
||||
AirConPowerLimitationOffAction = aux_ac_ns.class_(
|
||||
"AirConPowerLimitationOffAction", automation.Action
|
||||
)
|
||||
"AirConPowerLimitationOffAction", automation.Action)
|
||||
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):
|
||||
minV = AC_PACKET_TIMEOUT_MIN
|
||||
maxV = AC_PACKET_TIMEOUT_MAX
|
||||
@@ -132,6 +143,8 @@ def validate_packet_timeout(value):
|
||||
|
||||
AC_POWER_LIMIT_MIN = 30
|
||||
AC_POWER_LIMIT_MAX = 100
|
||||
|
||||
|
||||
def validate_power_limit_range(value):
|
||||
minV = AC_POWER_LIMIT_MIN
|
||||
maxV = AC_POWER_LIMIT_MAX
|
||||
@@ -162,15 +175,15 @@ ALLOWED_CLIMATE_SWING_MODES = {
|
||||
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
CUSTOM_FAN_MODES = {
|
||||
"MUTE": Capabilities.MUTE,
|
||||
"TURBO": Capabilities.TURBO,
|
||||
"MUTE": Capabilities.CUSTOM_FAN_MODE_MUTE,
|
||||
"TURBO": Capabilities.CUSTOM_FAN_MODE_TURBO,
|
||||
}
|
||||
validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
|
||||
|
||||
CUSTOM_PRESETS = {
|
||||
"CLEAN": Capabilities.CLEAN,
|
||||
"HEALTH": Capabilities.HEALTH,
|
||||
"ANTIFUNGUS": Capabilities.ANTIFUNGUS,
|
||||
"CLEAN": Capabilities.CUSTOM_PRESET_CLEAN,
|
||||
"HEALTH": Capabilities.CUSTOM_PRESET_HEALTH,
|
||||
"ANTIFUNGUS": Capabilities.CUSTOM_PRESET_ANTIFUNGUS,
|
||||
}
|
||||
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
|
||||
|
||||
@@ -182,7 +195,7 @@ def validate_raw_data(value):
|
||||
|
||||
|
||||
def output_info(config):
|
||||
"""_LOGGER.info(config.items())"""
|
||||
# _LOGGER.info(config.items())
|
||||
return config
|
||||
|
||||
|
||||
@@ -191,12 +204,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirCon),
|
||||
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_TIMEOUT, default=AC_PACKET_TIMEOUT_MIN): validate_packet_timeout,
|
||||
cv.Optional(CONF_OPTIMISTIC, default="true"): cv.boolean,
|
||||
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(
|
||||
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,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
@@ -221,9 +240,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
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,
|
||||
icon=ICON_OUTDOOR_TEMPERATURE,
|
||||
icon=ICON_INDOOR_COIL_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -232,9 +255,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
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,
|
||||
icon=ICON_INBOUND_TEMPERATURE,
|
||||
icon=ICON_OUTDOOR_AMBIENT_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -243,9 +270,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
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,
|
||||
icon=ICON_OUTBOUND_TEMPERATURE,
|
||||
icon=ICON_OUTDOOR_CONDENSER_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -254,9 +282,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
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,
|
||||
icon=ICON_COMPRESSOR_TEMPERATURE,
|
||||
icon=ICON_COMPRESSOR_DISCHARGE_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -265,6 +297,34 @@ CONFIG_SCHEMA = cv.All(
|
||||
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(
|
||||
icon=ICON_VLOUVER_STATE,
|
||||
accuracy_decimals=0,
|
||||
@@ -274,14 +334,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_DISPLAY,
|
||||
icon=ICON_DISPLAY_STATE,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_DEFROST,
|
||||
icon=ICON_DEFROST_STATE,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
@@ -335,77 +395,88 @@ async def to_code(config):
|
||||
await climate.register_climate(var, config)
|
||||
|
||||
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:
|
||||
conf = config[CONF_INDOOR_TEMPERATURE]
|
||||
if CONF_INDOOR_AMBIENT_TEMPERATURE in config:
|
||||
conf = config[CONF_INDOOR_AMBIENT_TEMPERATURE]
|
||||
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:
|
||||
conf = config[CONF_OUTDOOR_TEMPERATURE]
|
||||
if CONF_INDOOR_COIL_TEMPERATURE in config:
|
||||
conf = config[CONF_INDOOR_COIL_TEMPERATURE]
|
||||
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:
|
||||
conf = config[CONF_OUTBOUND_TEMPERATURE]
|
||||
if CONF_OUTDOOR_AMBIENT_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTDOOR_AMBIENT_TEMPERATURE]
|
||||
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:
|
||||
conf = config[CONF_INBOUND_TEMPERATURE]
|
||||
if CONF_OUTDOOR_CONDENSER_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTDOOR_CONDENSER_TEMPERATURE]
|
||||
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:
|
||||
conf = config[CONF_COMPRESSOR_TEMPERATURE]
|
||||
if CONF_DEFROST_TEMPERATURE in config:
|
||||
conf = config[CONF_DEFROST_TEMPERATURE]
|
||||
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:
|
||||
conf = config[CONF_VLOUVER_STATE]
|
||||
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:
|
||||
conf = config[CONF_DISPLAY_STATE]
|
||||
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:
|
||||
conf = config[CONF_DEFROST_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_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))
|
||||
cg.add(var.set_sensor_defrost_state(sens))
|
||||
|
||||
if CONF_PRESET_REPORTER in config:
|
||||
conf = config[CONF_PRESET_REPORTER]
|
||||
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:
|
||||
conf = config[CONF_INVERTER_POWER_LIMIT_VALUE]
|
||||
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:
|
||||
conf = config[CONF_INVERTER_POWER_LIMIT_STATE]
|
||||
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_show_action(config[CONF_SHOW_ACTION]))
|
||||
cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED]))
|
||||
# cg.add(var.set_show_action(config[CONF_SHOW_ACTION]))
|
||||
cg.add(var.set_display_inversion(config[CONF_DISPLAY_INVERTED]))
|
||||
cg.add(var.set_packet_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if CONF_SUPPORTED_MODES in config:
|
||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||
if CONF_SUPPORTED_SWING_MODES in config:
|
||||
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
|
||||
cg.add(var.set_supported_swing_modes(
|
||||
config[CONF_SUPPORTED_SWING_MODES]))
|
||||
if CONF_SUPPORTED_PRESETS in config:
|
||||
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
|
||||
if CONF_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]))
|
||||
|
||||
|
||||
|
||||
DISPLAY_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA
|
||||
)
|
||||
async def display_off_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA
|
||||
)
|
||||
async def display_on_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
VLOUVER_ACTION_SCHEMA = maybe_simple_id(
|
||||
@@ -443,55 +514,61 @@ VLOUVER_ACTION_SCHEMA = maybe_simple_id(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_stop", AirConVLouverStopAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_stop_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_swing", AirConVLouverSwingAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_swing_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_top", AirConVLouverTopAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_top_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_middle_above", AirConVLouverMiddleAboveAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_middle_above_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_middle", AirConVLouverMiddleAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_middle_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_middle_below", AirConVLouverMiddleBelowAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_middle_below_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_bottom", AirConVLouverBottomAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_bottom_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
VLOUVER_SET_ACTION_SCHEMA = cv.Schema(
|
||||
@@ -501,81 +578,47 @@ VLOUVER_SET_ACTION_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_set", AirConVLouverSetAction, VLOUVER_SET_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_set_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)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
template_ = await cg.templatable(config[CONF_POSITION], args, int)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
|
||||
|
||||
POWER_LIMITATION_OFF_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.power_limit_off", AirConPowerLimitationOffAction, POWER_LIMITATION_OFF_ACTION_SCHEMA
|
||||
)
|
||||
async def power_limit_off_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
|
||||
POWER_LIMITATION_ON_ACTION_SCHEMA = cv.Schema(
|
||||
POWER_LIMITATION_ON_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
cv.Optional(CONF_LIMIT, default=AC_POWER_LIMIT_MIN): validate_power_limit_range,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.power_limit_on", AirConPowerLimitationOnAction, POWER_LIMITATION_ON_ACTION_SCHEMA
|
||||
)
|
||||
async def power_limit_on_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)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
template_ = await cg.templatable(config[CONF_LIMIT], args, int)
|
||||
cg.add(var.set_value(template_))
|
||||
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
|
||||
|
||||
454
components/aux_ac/command_builder.cpp
Normal file
454
components/aux_ac/command_builder.cpp
Normal 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
|
||||
71
components/aux_ac/command_builder.h
Normal file
71
components/aux_ac/command_builder.h
Normal 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
417
components/aux_ac/frame.cpp
Normal 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
110
components/aux_ac/frame.h
Normal 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
|
||||
34
components/aux_ac/frame_constants.h
Normal file
34
components/aux_ac/frame_constants.h
Normal 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
|
||||
371
components/aux_ac/frame_processor.cpp
Normal file
371
components/aux_ac/frame_processor.cpp
Normal 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
|
||||
87
components/aux_ac/frame_processor.h
Normal file
87
components/aux_ac/frame_processor.h
Normal 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
|
||||
74
components/aux_ac/frame_processor_manager.cpp
Normal file
74
components/aux_ac/frame_processor_manager.cpp
Normal 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
|
||||
34
components/aux_ac/frame_processor_manager.h
Normal file
34
components/aux_ac/frame_processor_manager.h
Normal 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
|
||||
12
components/aux_ac/helpers.cpp
Normal file
12
components/aux_ac/helpers.cpp
Normal 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
117
components/aux_ac/helpers.h
Normal 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
|
||||
@@ -63,39 +63,46 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
optimistic: true
|
||||
indoor_temperature:
|
||||
name: ${upper_devicename} Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
internal: false
|
||||
display_state:
|
||||
name: ${upper_devicename} Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: ${upper_devicename} Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
indoor_ambient_temperature:
|
||||
name: ${upper_devicename} Indoor Ambient Temperature
|
||||
id: ${devicename}_indoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: ${upper_devicename} Coolant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
indoor_coil_temperature:
|
||||
name: ${upper_devicename} Indoor Coil Temperature
|
||||
id: ${devicename}_indoor_coil_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: ${upper_devicename} Coolant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: ${upper_devicename} Outdoor Ambient Temperature
|
||||
id: ${devicename}_outdoor_ambient_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: ${upper_devicename} Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: ${upper_devicename} Outdoor Condenser Temperature
|
||||
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
|
||||
defrost_state:
|
||||
name: ${upper_devicename} Defrost State
|
||||
id: ${devicename}_defrost_state
|
||||
internal: false
|
||||
inverter_power:
|
||||
name: ${upper_devicename} Invertor Power
|
||||
name: ${upper_devicename} Inverter Power
|
||||
id: ${devicename}_inverter_power
|
||||
internal: false
|
||||
preset_reporter:
|
||||
@@ -109,7 +116,9 @@ climate:
|
||||
visual:
|
||||
min_temperature: 16
|
||||
max_temperature: 32
|
||||
temperature_step: 0.5
|
||||
temperature_step:
|
||||
target_temperature: 0.5
|
||||
current_temperature: 0.1
|
||||
supported_modes:
|
||||
- HEAT_COOL
|
||||
- COOL
|
||||
@@ -138,6 +147,8 @@ sensor:
|
||||
update_interval: 30s
|
||||
unit_of_measurement: "dBa"
|
||||
accuracy_decimals: 0
|
||||
- platform: uptime
|
||||
name: ${upper_devicename} Uptime Sensor
|
||||
|
||||
|
||||
switch:
|
||||
@@ -154,43 +165,72 @@ switch:
|
||||
turn_off_action:
|
||||
- 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:
|
||||
- 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
|
||||
name: ${upper_devicename} VLouver Stop
|
||||
icon: "mdi:circle-small"
|
||||
on_press:
|
||||
- aux_ac.vlouver_stop: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Swing
|
||||
icon: "mdi:pan-vertical"
|
||||
on_press:
|
||||
- aux_ac.vlouver_swing: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Top
|
||||
icon: "mdi:pan-up"
|
||||
on_press:
|
||||
- aux_ac.vlouver_top: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Middle Above
|
||||
icon: "mdi:pan-top-left"
|
||||
on_press:
|
||||
- aux_ac.vlouver_middle_above: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Middle
|
||||
icon: "mdi:pan-left"
|
||||
on_press:
|
||||
- aux_ac.vlouver_middle: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Middle Below
|
||||
icon: "mdi:pan-bottom-left"
|
||||
on_press:
|
||||
- aux_ac.vlouver_middle_below: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Bottom
|
||||
icon: "mdi:pan-down"
|
||||
@@ -226,4 +266,5 @@ number:
|
||||
step: 1
|
||||
set_action:
|
||||
then:
|
||||
- lambda: !lambda "id(aux_id).powerLimitationOnSequence( x );"
|
||||
- lambda: !lambda "id(aux_id).action_power_limitation_on( x );"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user