This commit is contained in:
Florian Simmer
2025-07-04 17:10:48 +02:00
parent 909454f545
commit 00711c4f42
7 changed files with 1348 additions and 915 deletions

View File

View File

@@ -0,0 +1,134 @@
#pragma once
#include "esphome/core/automation.h"
#include "tclac.h"
namespace esphome {
namespace tclac {
// Шаблон действия: изменение вертикальной фиксации заслонки
template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
public:
VerticalAirflowAction(tclacClimate *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(AirflowVerticalDirection, direction)
void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: изменение горизонтальной фиксации заслонок
template<typename... Ts> class HorizontalAirflowAction : public Action<Ts...> {
public:
HorizontalAirflowAction(tclacClimate *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(AirflowHorizontalDirection, direction)
void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: изменение режима вертикального качания заслонки
template<typename... Ts> class VerticalSwingDirectionAction : public Action<Ts...> {
public:
VerticalSwingDirectionAction(tclacClimate *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(VerticalSwingDirection, direction)
void play(Ts... x) { this->parent_->set_vertical_swing_direction(this->direction_.value(x...)); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: изменение режима горизонтального качания заслонок
template<typename... Ts> class HorizontalSwingDirectionAction : public Action<Ts...> {
public:
HorizontalSwingDirectionAction(tclacClimate *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(HorizontalSwingDirection, direction)
void play(Ts... x) { this->parent_->set_horizontal_swing_direction(this->direction_.value(x...)); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: включение дисплея
template<typename... Ts> class DisplayOnAction : public Action<Ts...> {
public:
DisplayOnAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_display_state(true); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: выключение дисплея
template<typename... Ts> class DisplayOffAction : public Action<Ts...> {
public:
DisplayOffAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_display_state(false); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: включение пищалки
template<typename... Ts> class BeeperOnAction : public Action<Ts...> {
public:
BeeperOnAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_beeper_state(true); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: выклюение пищалки
template<typename... Ts> class BeeperOffAction : public Action<Ts...> {
public:
BeeperOffAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_beeper_state(false); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: включение индикатора модуля
template<typename... Ts> class ModuleDisplayOnAction : public Action<Ts...> {
public:
ModuleDisplayOnAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_module_display_state(true); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: выключение индикатора модуля
template<typename... Ts> class ModuleDisplayOffAction : public Action<Ts...> {
public:
ModuleDisplayOffAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_module_display_state(false); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: включение принудительного применения настроек
template<typename... Ts> class ForceOnAction : public Action<Ts...> {
public:
ForceOnAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_force_mode_state(true); }
protected:
tclacClimate *parent_;
};
// Шаблон действия: выключение принудительного применения настроек
template<typename... Ts> class ForceOffAction : public Action<Ts...> {
public:
ForceOffAction(tclacClimate *parent) : parent_(parent) {}
void play(Ts... x) { this->parent_->set_force_mode_state(false); }
protected:
tclacClimate *parent_;
};
} // namespace tclac
} // namespace esphome

343
components/tclac/climate.py Normal file
View File

@@ -0,0 +1,343 @@
from esphome import automation, pins
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate, uart
from esphome.const import (
CONF_ID,
CONF_LEVEL,
CONF_BEEPER,
CONF_VISUAL,
CONF_MAX_TEMPERATURE,
CONF_MIN_TEMPERATURE,
CONF_SUPPORTED_MODES,
CONF_TEMPERATURE_STEP,
CONF_SUPPORTED_PRESETS,
CONF_TARGET_TEMPERATURE,
CONF_SUPPORTED_FAN_MODES,
CONF_SUPPORTED_SWING_MODES,
)
from esphome.components.climate import (
ClimateMode,
ClimatePreset,
ClimateSwingMode,
CONF_CURRENT_TEMPERATURE,
)
AUTO_LOAD = ["climate"]
CODEOWNERS = ["@I-am-nightingale", "@xaxexa", "@junkfix"]
DEPENDENCIES = ["climate", "uart"]
TCLAC_MIN_TEMPERATURE = 16.0
TCLAC_MAX_TEMPERATURE = 31.0
TCLAC_TARGET_TEMPERATURE_STEP = 1.0
TCLAC_CURRENT_TEMPERATURE_STEP = 1.0
CONF_RX_LED = "rx_led"
CONF_TX_LED = "tx_led"
CONF_DISPLAY = "show_display"
CONF_FORCE_MODE = "force_mode"
CONF_VERTICAL_AIRFLOW = "vertical_airflow"
CONF_MODULE_DISPLAY = "show_module_display"
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
CONF_VERTICAL_SWING_MODE = "vertical_swing_mode"
CONF_HORIZONTAL_SWING_MODE = "horizontal_swing_mode"
tclac_ns = cg.esphome_ns.namespace("tclac")
tclacClimate = tclac_ns.class_("tclacClimate", uart.UARTDevice, climate.Climate, cg.PollingComponent)
SUPPORTED_FAN_MODES_OPTIONS = {
"AUTO": ClimateMode.CLIMATE_FAN_AUTO, # Доступен всегда
"QUIET": ClimateMode.CLIMATE_FAN_QUIET,
"LOW": ClimateMode.CLIMATE_FAN_LOW,
"MIDDLE": ClimateMode.CLIMATE_FAN_MIDDLE,
"MEDIUM": ClimateMode.CLIMATE_FAN_MEDIUM,
"HIGH": ClimateMode.CLIMATE_FAN_HIGH,
"FOCUS": ClimateMode.CLIMATE_FAN_FOCUS,
"DIFFUSE": ClimateMode.CLIMATE_FAN_DIFFUSE,
}
SUPPORTED_SWING_MODES_OPTIONS = {
"OFF": ClimateSwingMode.CLIMATE_SWING_OFF, # Доступен всегда
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
}
SUPPORTED_CLIMATE_MODES_OPTIONS = {
"OFF": ClimateMode.CLIMATE_MODE_OFF, # Доступен всегда
"AUTO": ClimateMode.CLIMATE_MODE_AUTO, # Доступен всегда
"COOL": ClimateMode.CLIMATE_MODE_COOL,
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
"DRY": ClimateMode.CLIMATE_MODE_DRY,
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
}
SUPPORTED_CLIMATE_PRESETS_OPTIONS = {
"NONE": ClimatePreset.CLIMATE_PRESET_NONE, # Доступен всегда
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
"COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT,
}
VerticalSwingDirection = tclac_ns.enum("VerticalSwingDirection", True)
VERTICAL_SWING_DIRECTION_OPTIONS = {
"UP_DOWN": VerticalSwingDirection.UPDOWN,
"UPSIDE": VerticalSwingDirection.UPSIDE,
"DOWNSIDE": VerticalSwingDirection.DOWNSIDE,
}
HorizontalSwingDirection = tclac_ns.enum("HorizontalSwingDirection", True)
HORIZONTAL_SWING_DIRECTION_OPTIONS = {
"LEFT_RIGHT": HorizontalSwingDirection.LEFT_RIGHT,
"LEFTSIDE": HorizontalSwingDirection.LEFTSIDE,
"CENTER": HorizontalSwingDirection.CENTER,
"RIGHTSIDE": HorizontalSwingDirection.RIGHTSIDE,
}
AirflowVerticalDirection = tclac_ns.enum("AirflowVerticalDirection", True)
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
"LAST": AirflowVerticalDirection.LAST,
"MAX_UP": AirflowVerticalDirection.MAX_UP,
"UP": AirflowVerticalDirection.UP,
"CENTER": AirflowVerticalDirection.CENTER,
"DOWN": AirflowVerticalDirection.DOWN,
"MAX_DOWN": AirflowVerticalDirection.MAX_DOWN,
}
AirflowHorizontalDirection = tclac_ns.enum("AirflowHorizontalDirection", True)
AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = {
"LAST": AirflowHorizontalDirection.LAST,
"MAX_LEFT": AirflowHorizontalDirection.MAX_LEFT,
"LEFT": AirflowHorizontalDirection.LEFT,
"CENTER": AirflowHorizontalDirection.CENTER,
"RIGHT": AirflowHorizontalDirection.RIGHT,
"MAX_RIGHT": AirflowHorizontalDirection.MAX_RIGHT,
}
# Проверка конфигурации интерфейса и принятие значений по умолчанию
def validate_visual(config):
if CONF_VISUAL in config:
visual_config = config[CONF_VISUAL]
if CONF_MIN_TEMPERATURE in visual_config:
min_temp = visual_config[CONF_MIN_TEMPERATURE]
if min_temp < TCLAC_MIN_TEMPERATURE:
raise cv.Invalid(f"Указанная интерфейсная минимальная температура в {min_temp} ниже допустимой {TCLAC_MIN_TEMPERATURE} для кондиционера")
else:
config[CONF_VISUAL][CONF_MIN_TEMPERATURE] = TCLAC_MIN_TEMPERATURE
if CONF_MAX_TEMPERATURE in visual_config:
max_temp = visual_config[CONF_MAX_TEMPERATURE]
if max_temp > TCLAC_MAX_TEMPERATURE:
raise cv.Invalid(f"Указанная интерфейсная максимальная температура в {max_temp} выше допустимой {TCLAC_MAX_TEMPERATURE} для кондиционера")
else:
config[CONF_VISUAL][CONF_MAX_TEMPERATURE] = TCLAC_MAX_TEMPERATURE
if CONF_TEMPERATURE_STEP in visual_config:
temp_step = config[CONF_VISUAL][CONF_TEMPERATURE_STEP][CONF_TARGET_TEMPERATURE]
if ((int)(temp_step * 2)) / 2 != temp_step:
raise cv.Invalid(f"Указанный шаг температуры {temp_step} не корректен, должен быть кратен 1")
else:
config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = {CONF_TARGET_TEMPERATURE: TCLAC_TARGET_TEMPERATURE_STEP,CONF_CURRENT_TEMPERATURE: TCLAC_CURRENT_TEMPERATURE_STEP,}
else:
config[CONF_VISUAL] = {CONF_MIN_TEMPERATURE: TCLAC_MIN_TEMPERATURE,CONF_MAX_TEMPERATURE: TCLAC_MAX_TEMPERATURE,CONF_TEMPERATURE_STEP: {CONF_TARGET_TEMPERATURE: TCLAC_TARGET_TEMPERATURE_STEP,CONF_CURRENT_TEMPERATURE: TCLAC_CURRENT_TEMPERATURE_STEP,},}
return config
# Проверка конфигурации компонента и принятие значений по умолчанию
CONFIG_SCHEMA = cv.All(
#climate.CLIMATE_SCHEMA.extend(
climate.climate_schema(tclacClimate).extend(
{
cv.GenerateID(): cv.declare_id(tclacClimate),
cv.Optional(CONF_BEEPER, default=True): cv.boolean,
cv.Optional(CONF_DISPLAY, default=True): cv.boolean,
cv.Optional(CONF_RX_LED): pins.gpio_output_pin_schema,
cv.Optional(CONF_TX_LED): pins.gpio_output_pin_schema,
cv.Optional(CONF_FORCE_MODE, default=True): cv.boolean,
cv.Optional(CONF_MODULE_DISPLAY, default=True): cv.boolean,
cv.Optional(CONF_VERTICAL_AIRFLOW, default="CENTER"): cv.ensure_list(cv.enum(AIRFLOW_VERTICAL_DIRECTION_OPTIONS, upper=True)),
cv.Optional(CONF_VERTICAL_SWING_MODE, default="UP_DOWN"): cv.ensure_list(cv.enum(VERTICAL_SWING_DIRECTION_OPTIONS, upper=True)),
cv.Optional(CONF_HORIZONTAL_AIRFLOW, default="CENTER"): cv.ensure_list(cv.enum(AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS, upper=True)),
cv.Optional(CONF_HORIZONTAL_SWING_MODE, default="LEFT_RIGHT"): cv.ensure_list(cv.enum(HORIZONTAL_SWING_DIRECTION_OPTIONS, upper=True)),
cv.Optional(CONF_SUPPORTED_PRESETS,default=["NONE","ECO","SLEEP","COMFORT",],): cv.ensure_list(cv.enum(SUPPORTED_CLIMATE_PRESETS_OPTIONS, upper=True)),
cv.Optional(CONF_SUPPORTED_SWING_MODES,default=["OFF","VERTICAL","HORIZONTAL","BOTH",],): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)),
cv.Optional(CONF_SUPPORTED_MODES,default=["OFF","AUTO","COOL","HEAT","DRY","FAN_ONLY",],): cv.ensure_list(cv.enum(SUPPORTED_CLIMATE_MODES_OPTIONS, upper=True)),
cv.Optional(CONF_SUPPORTED_FAN_MODES,default=["AUTO","QUIET","LOW","MIDDLE","MEDIUM","HIGH","FOCUS","DIFFUSE",],): cv.ensure_list(cv.enum(SUPPORTED_FAN_MODES_OPTIONS, upper=True)),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
validate_visual,
)
ForceOnAction = tclac_ns.class_("ForceOnAction", automation.Action)
ForceOffAction = tclac_ns.class_("ForceOffAction", automation.Action)
BeeperOnAction = tclac_ns.class_("BeeperOnAction", automation.Action)
BeeperOffAction = tclac_ns.class_("BeeperOffAction", automation.Action)
DisplayOnAction = tclac_ns.class_("DisplayOnAction", automation.Action)
DisplayOffAction = tclac_ns.class_("DisplayOffAction", automation.Action)
ModuleDisplayOnAction = tclac_ns.class_("ModuleDisplayOnAction", automation.Action)
VerticalAirflowAction = tclac_ns.class_("VerticalAirflowAction", automation.Action)
ModuleDisplayOffAction = tclac_ns.class_("ModuleDisplayOffAction", automation.Action)
HorizontalAirflowAction = tclac_ns.class_("HorizontalAirflowAction", automation.Action)
VerticalSwingDirectionAction = tclac_ns.class_("VerticalSwingDirectionAction", automation.Action)
HorizontalSwingDirectionAction = tclac_ns.class_("HorizontalSwingDirectionAction", automation.Action)
TCLAC_ACTION_BASE_SCHEMA = automation.maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(tclacClimate),})
# Регистрация событий включения и отключения дисплея кондиционера
@automation.register_action(
"climate.tclac.display_on", DisplayOnAction, cv.Schema
)
@automation.register_action(
"climate.tclac.display_off", DisplayOffAction, cv.Schema
)
async def display_action_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)
return var
# Регистрация событий включения и отключения пищалки кондиционера
@automation.register_action(
"climate.tclac.beeper_on", BeeperOnAction, cv.Schema
)
@automation.register_action(
"climate.tclac.beeper_off", BeeperOffAction, cv.Schema
)
async def beeper_action_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)
return var
# Регистрация событий включения и отключения светодиодов связи модуля
@automation.register_action(
"climate.tclac.module_display_on", ModuleDisplayOnAction, cv.Schema
)
@automation.register_action(
"climate.tclac.module_display_off", ModuleDisplayOffAction, cv.Schema
)
async def module_display_action_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)
return var
# Регистрация событий включения и отключения принудительного применения настроек
@automation.register_action(
"climate.tclac.force_mode_on", ForceOnAction, cv.Schema
)
@automation.register_action(
"climate.tclac.force_mode_off", ForceOffAction, cv.Schema
)
async def force_mode_action_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)
return var
# Регистрация события установки вертикальной фиксации заслонки
@automation.register_action(
"climate.tclac.set_vertical_airflow",
VerticalAirflowAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(tclacClimate),
cv.Required(CONF_VERTICAL_AIRFLOW): cv.templatable(cv.enum(AIRFLOW_VERTICAL_DIRECTION_OPTIONS, upper=True)),
}
),
)
async def tclac_set_vertical_airflow_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)
template_ = await cg.templatable(
config[CONF_VERTICAL_AIRFLOW], args, AirflowVerticalDirection
)
cg.add(var.set_direction(template_))
return var
# Регистрация события установки горизонтальной фиксации заслонок
@automation.register_action(
"climate.tclac.set_horizontal_airflow",
HorizontalAirflowAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(tclacClimate),
cv.Required(CONF_HORIZONTAL_AIRFLOW): cv.templatable(cv.enum(AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS, upper=True)),
}
),
)
async def tclac_set_horizontal_airflow_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)
template_ = await cg.templatable(config[CONF_HORIZONTAL_AIRFLOW], args, AirflowHorizontalDirection)
cg.add(var.set_direction(template_))
return var
# Регистрация события установки вертикального качания шторки
@automation.register_action(
"climate.tclac.set_vertical_swing_direction",
VerticalSwingDirectionAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(tclacClimate),
cv.Required(CONF_VERTICAL_SWING_MODE): cv.templatable(cv.enum(VERTICAL_SWING_DIRECTION_OPTIONS, upper=True)),
}
),
)
async def tclac_set_vertical_swing_direction_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)
template_ = await cg.templatable(config[CONF_VERTICAL_SWING_MODE], args, VerticalSwingDirection)
cg.add(var.set_swing_direction(template_))
return var
# Регистрация события установки горизонтального качания шторок
@automation.register_action(
"climate.tclac.set_horizontal_swing_direction",
HorizontalSwingDirectionAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(tclacClimate),
cv.Required(CONF_HORIZONTAL_SWING_MODE): cv.templatable(cv.enum(HORIZONTAL_SWING_DIRECTION_OPTIONS, upper=True)),
}
),
)
async def tclac_set_horizontal_swing_direction_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)
template_ = await cg.templatable(config[CONF_HORIZONTAL_SWING_MODE], args, HorizontalSwingDirection)
cg.add(var.set_swing_direction(template_))
return var
# Добавление конфигурации в код
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
yield climate.register_climate(var, config)
if CONF_BEEPER in config:
cg.add(var.set_beeper_state(config[CONF_BEEPER]))
if CONF_DISPLAY in config:
cg.add(var.set_display_state(config[CONF_DISPLAY]))
if CONF_FORCE_MODE in config:
cg.add(var.set_force_mode_state(config[CONF_FORCE_MODE]))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
if CONF_SUPPORTED_PRESETS in config:
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_MODULE_DISPLAY in config:
cg.add(var.set_module_display_state(config[CONF_MODULE_DISPLAY]))
if CONF_SUPPORTED_FAN_MODES in config:
cg.add(var.set_supported_fan_modes(config[CONF_SUPPORTED_FAN_MODES]))
if CONF_SUPPORTED_SWING_MODES in config:
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
if CONF_TX_LED in config:
cg.add_define("CONF_TX_LED")
tx_led_pin = yield cg.gpio_pin_expression(config[CONF_TX_LED])
cg.add(var.set_tx_led_pin(tx_led_pin))
if CONF_RX_LED in config:
cg.add_define("CONF_RX_LED")
rx_led_pin = yield cg.gpio_pin_expression(config[CONF_RX_LED])
cg.add(var.set_rx_led_pin(rx_led_pin))

714
components/tclac/tclac.cpp Normal file
View File

@@ -0,0 +1,714 @@
/**
* Create by Miguel Ángel López on 20/07/19
* and modify by xaxexa
* Refactoring & component making:
* Соловей с паяльником 15.03.2024
**/
#include "esphome.h"
#include "esphome/core/defines.h"
#include "tclac.h"
namespace esphome{
namespace tclac{
ClimateTraits tclacClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_action(false);
traits.set_supports_current_temperature(true);
traits.set_supports_two_point_target_temperature(false);
traits.set_supported_modes(this->supported_modes_);
traits.set_supported_presets(this->supported_presets_);
traits.set_supported_fan_modes(this->supported_fan_modes_);
traits.set_supported_swing_modes(this->supported_swing_modes_);
traits.add_supported_mode(climate::CLIMATE_MODE_OFF); // Выключенный режим кондиционера доступен всегда
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); // Автоматический режим кондиционера тоже
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); // Автоматический режим вентилятора доступен всегда
traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); // Выключенный режим качания заслонок доступен всегда
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);// На всякий случай без предустановок
return traits;
}
void tclacClimate::setup() {
#ifdef CONF_RX_LED
this->rx_led_pin_->setup();
this->rx_led_pin_->digital_write(false);
#endif
#ifdef CONF_TX_LED
this->tx_led_pin_->setup();
this->tx_led_pin_->digital_write(false);
#endif
}
void tclacClimate::loop() {
// Если в буфере UART что-то есть, то читаем это что-то
if (esphome::uart::UARTDevice::available() > 0) {
dataShow(0, true);
dataRX[0] = esphome::uart::UARTDevice::read();
// Если принятый байт- не заголовок (0xBB), то просто покидаем цикл
if (dataRX[0] != 0xBB) {
ESP_LOGD("TCL", "Wrong byte");
dataShow(0,0);
return;
}
// А вот если совпал заголовок (0xBB), то начинаем чтение по цепочке еще 4 байт
delay(5);
dataRX[1] = esphome::uart::UARTDevice::read();
delay(5);
dataRX[2] = esphome::uart::UARTDevice::read();
delay(5);
dataRX[3] = esphome::uart::UARTDevice::read();
delay(5);
dataRX[4] = esphome::uart::UARTDevice::read();
//auto raw = getHex(dataRX, 5);
//ESP_LOGD("TCL", "first 5 byte : %s ", raw.c_str());
// Из первых 5 байт нам нужен пятый- он содержит длину сообщения
esphome::uart::UARTDevice::read_array(dataRX+5, dataRX[4]+1);
byte check = getChecksum(dataRX, sizeof(dataRX));
//raw = getHex(dataRX, sizeof(dataRX));
//ESP_LOGD("TCL", "RX full : %s ", raw.c_str());
// Проверяем контрольную сумму
if (check != dataRX[60]) {
ESP_LOGD("TCL", "Invalid checksum %x", check);
tclacClimate::dataShow(0,0);
return;
} else {
//ESP_LOGD("TCL", "checksum OK %x", check);
}
tclacClimate::dataShow(0,0);
// Прочитав все из буфера приступаем к разбору данных
tclacClimate::readData();
}
}
void tclacClimate::update() {
tclacClimate::dataShow(1,1);
this->esphome::uart::UARTDevice::write_array(poll, sizeof(poll));
//auto raw = tclacClimate::getHex(poll, sizeof(poll));
//ESP_LOGD("TCL", "chek status sended");
tclacClimate::dataShow(1,0);
}
void tclacClimate::readData() {
current_temperature = float((( (dataRX[17] << 8) | dataRX[18] ) / 374 - 32)/1.8);
target_temperature = (dataRX[FAN_SPEED_POS] & SET_TEMP_MASK) + 16;
//ESP_LOGD("TCL", "TEMP: %f ", current_temperature);
if (dataRX[MODE_POS] & ( 1 << 4)) {
// Если кондиционер включен, то разбираем данные для отображения
// ESP_LOGD("TCL", "AC is on");
uint8_t modeswitch = MODE_MASK & dataRX[MODE_POS];
uint8_t fanspeedswitch = FAN_SPEED_MASK & dataRX[FAN_SPEED_POS];
uint8_t swingmodeswitch = SWING_MODE_MASK & dataRX[SWING_POS];
switch (modeswitch) {
case MODE_AUTO:
mode = climate::CLIMATE_MODE_AUTO;
break;
case MODE_COOL:
mode = climate::CLIMATE_MODE_COOL;
break;
case MODE_DRY:
mode = climate::CLIMATE_MODE_DRY;
break;
case MODE_FAN_ONLY:
mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case MODE_HEAT:
mode = climate::CLIMATE_MODE_HEAT;
break;
default:
mode = climate::CLIMATE_MODE_AUTO;
}
if ( dataRX[FAN_QUIET_POS] & FAN_QUIET) {
fan_mode = climate::CLIMATE_FAN_QUIET;
} else if (dataRX[MODE_POS] & FAN_DIFFUSE){
fan_mode = climate::CLIMATE_FAN_DIFFUSE;
} else {
switch (fanspeedswitch) {
case FAN_AUTO:
fan_mode = climate::CLIMATE_FAN_AUTO;
break;
case FAN_LOW:
fan_mode = climate::CLIMATE_FAN_LOW;
break;
case FAN_MIDDLE:
fan_mode = climate::CLIMATE_FAN_MIDDLE;
break;
case FAN_MEDIUM:
fan_mode = climate::CLIMATE_FAN_MEDIUM;
break;
case FAN_HIGH:
fan_mode = climate::CLIMATE_FAN_HIGH;
break;
case FAN_FOCUS:
fan_mode = climate::CLIMATE_FAN_FOCUS;
break;
default:
fan_mode = climate::CLIMATE_FAN_AUTO;
}
}
switch (swingmodeswitch) {
case SWING_OFF:
swing_mode = climate::CLIMATE_SWING_OFF;
break;
case SWING_HORIZONTAL:
swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
break;
case SWING_VERTICAL:
swing_mode = climate::CLIMATE_SWING_VERTICAL;
break;
case SWING_BOTH:
swing_mode = climate::CLIMATE_SWING_BOTH;
break;
}
// Обработка данных о пресете
preset = ClimatePreset::CLIMATE_PRESET_NONE;
if (dataRX[7] & (1 << 6)){
preset = ClimatePreset::CLIMATE_PRESET_ECO;
} else if (dataRX[9] & (1 << 2)){
preset = ClimatePreset::CLIMATE_PRESET_COMFORT;
} else if (dataRX[19] & (1 << 0)){
preset = ClimatePreset::CLIMATE_PRESET_SLEEP;
}
} else {
// Если кондиционер выключен, то все режимы показываются, как выключенные
mode = climate::CLIMATE_MODE_OFF;
//fan_mode = climate::CLIMATE_FAN_OFF;
swing_mode = climate::CLIMATE_SWING_OFF;
preset = ClimatePreset::CLIMATE_PRESET_NONE;
}
// Публикуем данные
this->publish_state();
allow_take_control = true;
}
// Climate control
void tclacClimate::control(const ClimateCall &call) {
// Запрашиваем данные из переключателя режимов работы кондиционера
if (call.get_mode().has_value()){
switch_climate_mode = call.get_mode().value();
ESP_LOGD("TCL", "Get MODE from call");
} else {
switch_climate_mode = mode;
ESP_LOGD("TCL", "Get MODE from AC");
}
// Запрашиваем данные из переключателя предустановок кондиционера
if (call.get_preset().has_value()){
switch_preset = call.get_preset().value();
} else {
switch_preset = preset.value();
}
// Запрашиваем данные из переключателя режимов вентилятора
if (call.get_fan_mode().has_value()){
switch_fan_mode = call.get_fan_mode().value();
} else {
switch_fan_mode = fan_mode.value();
}
// Запрашиваем данные из переключателя режимов качания заслонок
if (call.get_swing_mode().has_value()){
switch_swing_mode = call.get_swing_mode().value();
} else {
// А если в переключателе пусто- заполняем значением из последнего опроса состояния. Типа, ничего не поменялось.
switch_swing_mode = swing_mode;
}
// Расчет температуры
if (call.get_target_temperature().has_value()) {
target_temperature_set = 31-(int)call.get_target_temperature().value();
} else {
target_temperature_set = 31-(int)target_temperature;
}
is_call_control = true;
takeControl();
allow_take_control = true;
}
void tclacClimate::takeControl() {
dataTX[7] = 0b00000000;
dataTX[8] = 0b00000000;
dataTX[9] = 0b00000000;
dataTX[10] = 0b00000000;
dataTX[11] = 0b00000000;
dataTX[19] = 0b00000000;
dataTX[32] = 0b00000000;
dataTX[33] = 0b00000000;
if (is_call_control != true){
ESP_LOGD("TCL", "Get MODE from AC for force config");
switch_climate_mode = mode;
switch_preset = preset.value();
switch_fan_mode = fan_mode.value();
switch_swing_mode = swing_mode;
target_temperature_set = 31-(int)target_temperature;
}
// Включаем или отключаем пищалку в зависимости от переключателя в настройках
if (beeper_status_){
ESP_LOGD("TCL", "Beep mode ON");
dataTX[7] += 0b00100000;
} else {
ESP_LOGD("TCL", "Beep mode OFF");
dataTX[7] += 0b00000000;
}
// Включаем или отключаем дисплей на кондиционере в зависимости от переключателя в настройках
// Включаем дисплей только если кондиционер в одном из рабочих режимов
// ВНИМАНИЕ! При выключении дисплея кондиционер сам принудительно переходит в автоматический режим!
if ((display_status_) && (switch_climate_mode != climate::CLIMATE_MODE_OFF)){
ESP_LOGD("TCL", "Dispaly turn ON");
dataTX[7] += 0b01000000;
} else {
ESP_LOGD("TCL", "Dispaly turn OFF");
dataTX[7] += 0b00000000;
}
// Настраиваем режим работы кондиционера
switch (switch_climate_mode) {
case climate::CLIMATE_MODE_OFF:
dataTX[7] += 0b00000000;
dataTX[8] += 0b00000000;
break;
case climate::CLIMATE_MODE_AUTO:
dataTX[7] += 0b00000100;
dataTX[8] += 0b00001000;
break;
case climate::CLIMATE_MODE_COOL:
dataTX[7] += 0b00000100;
dataTX[8] += 0b00000011;
break;
case climate::CLIMATE_MODE_DRY:
dataTX[7] += 0b00000100;
dataTX[8] += 0b00000010;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
dataTX[7] += 0b00000100;
dataTX[8] += 0b00000111;
break;
case climate::CLIMATE_MODE_HEAT:
dataTX[7] += 0b00000100;
dataTX[8] += 0b00000001;
break;
}
// Настраиваем режим вентилятора
switch(switch_fan_mode) {
case climate::CLIMATE_FAN_AUTO:
dataTX[8] += 0b00000000;
dataTX[10] += 0b00000000;
break;
case climate::CLIMATE_FAN_QUIET:
dataTX[8] += 0b10000000;
dataTX[10] += 0b00000000;
break;
case climate::CLIMATE_FAN_LOW:
dataTX[8] += 0b00000000;
dataTX[10] += 0b00000001;
break;
case climate::CLIMATE_FAN_MIDDLE:
dataTX[8] += 0b00000000;
dataTX[10] += 0b00000110;
break;
case climate::CLIMATE_FAN_MEDIUM:
dataTX[8] += 0b00000000;
dataTX[10] += 0b00000011;
break;
case climate::CLIMATE_FAN_HIGH:
dataTX[8] += 0b00000000;
dataTX[10] += 0b00000111;
break;
case climate::CLIMATE_FAN_FOCUS:
dataTX[8] += 0b00000000;
dataTX[10] += 0b00000101;
break;
case climate::CLIMATE_FAN_DIFFUSE:
dataTX[8] += 0b01000000;
dataTX[10] += 0b00000000;
break;
}
// Устанавливаем режим качания заслонок
switch(switch_swing_mode) {
case climate::CLIMATE_SWING_OFF:
dataTX[10] += 0b00000000;
dataTX[11] += 0b00000000;
break;
case climate::CLIMATE_SWING_VERTICAL:
dataTX[10] += 0b00111000;
dataTX[11] += 0b00000000;
break;
case climate::CLIMATE_SWING_HORIZONTAL:
dataTX[10] += 0b00000000;
dataTX[11] += 0b00001000;
break;
case climate::CLIMATE_SWING_BOTH:
dataTX[10] += 0b00111000;
dataTX[11] += 0b00001000;
break;
}
// Устанавливаем предустановки кондиционера
switch(switch_preset) {
case ClimatePreset::CLIMATE_PRESET_NONE:
break;
case ClimatePreset::CLIMATE_PRESET_ECO:
dataTX[7] += 0b10000000;
break;
case ClimatePreset::CLIMATE_PRESET_SLEEP:
dataTX[19] += 0b00000001;
break;
case ClimatePreset::CLIMATE_PRESET_COMFORT:
dataTX[8] += 0b00010000;
break;
}
//Режим заслонок
// Вертикальная заслонка
// Качание вертикальной заслонки [10 байт, маска 00111000]:
// 000 - Качание отключено, заслонка в последней позиции или в фиксации
// 111 - Качание включено в выбранном режиме
// Режим качания вертикальной заслонки (режим фиксации заслонки роли не играет, если качание включено) [32 байт, маска 00011000]:
// 01 - качание сверху вниз, ПО УМОЛЧАНИЮ
// 10 - качание в верхней половине
// 11 - качание в нижней половине
// Режим фиксации заслонки (режим качания заслонки роли не играет, если качание выключено) [32 байт, маска 00000111]:
// 000 - нет фиксации, ПО УМОЛЧАНИЮ
// 001 - фиксация вверху
// 010 - фиксация между верхом и серединой
// 011 - фиксация в середине
// 100 - фиксация между серединой и низом
// 101 - фиксация внизу
// Горизонтальные заслонки
// Качание горизонтальных заслонок [11 байт, маска 00001000]:
// 0 - Качание отключено, заслонки в последней позиции или в фиксации
// 1 - Качание включено в выбранном режиме
// Режим качания горизонтальных заслонок (режим фиксации заслонок роли не играет, если качание включено) [33 байт, маска 00111000]:
// 001 - качание слева направо, ПО УМОЛЧАНИЮ
// 010 - качание слева
// 011 - качание по середине
// 100 - качание справа
// Режим фиксации горизонтальных заслонок (режим качания заслонок роли не играет, если качание выключено) [33 байт, маска 00000111]:
// 000 - нет фиксации, ПО УМОЛЧАНИЮ
// 001 - фиксация слева
// 010 - фиксация между левой стороной и серединой
// 011 - фиксация в середине
// 100 - фиксация между серединой и правой стороной
// 101 - фиксация справа
// Устанавливаем режим для качания вертикальной заслонки
switch(vertical_swing_direction_) {
case VerticalSwingDirection::UP_DOWN:
dataTX[32] += 0b00001000;
ESP_LOGD("TCL", "Vertical swing: up-down");
break;
case VerticalSwingDirection::UPSIDE:
dataTX[32] += 0b00010000;
ESP_LOGD("TCL", "Vertical swing: upper");
break;
case VerticalSwingDirection::DOWNSIDE:
dataTX[32] += 0b00011000;
ESP_LOGD("TCL", "Vertical swing: downer");
break;
}
// Устанавливаем режим для качания горизонтальных заслонок
switch(horizontal_swing_direction_) {
case HorizontalSwingDirection::LEFT_RIGHT:
dataTX[33] += 0b00001000;
ESP_LOGD("TCL", "Horizontal swing: left-right");
break;
case HorizontalSwingDirection::LEFTSIDE:
dataTX[33] += 0b00010000;
ESP_LOGD("TCL", "Horizontal swing: lefter");
break;
case HorizontalSwingDirection::CENTER:
dataTX[33] += 0b00011000;
ESP_LOGD("TCL", "Horizontal swing: center");
break;
case HorizontalSwingDirection::RIGHTSIDE:
dataTX[33] += 0b00100000;
ESP_LOGD("TCL", "Horizontal swing: righter");
break;
}
// Устанавливаем положение фиксации вертикальной заслонки
switch(vertical_direction_) {
case AirflowVerticalDirection::LAST:
dataTX[32] += 0b00000000;
ESP_LOGD("TCL", "Vertical fix: last position");
break;
case AirflowVerticalDirection::MAX_UP:
dataTX[32] += 0b00000001;
ESP_LOGD("TCL", "Vertical fix: up");
break;
case AirflowVerticalDirection::UP:
dataTX[32] += 0b00000010;
ESP_LOGD("TCL", "Vertical fix: upper");
break;
case AirflowVerticalDirection::CENTER:
dataTX[32] += 0b00000011;
ESP_LOGD("TCL", "Vertical fix: center");
break;
case AirflowVerticalDirection::DOWN:
dataTX[32] += 0b00000100;
ESP_LOGD("TCL", "Vertical fix: downer");
break;
case AirflowVerticalDirection::MAX_DOWN:
dataTX[32] += 0b00000101;
ESP_LOGD("TCL", "Vertical fix: down");
break;
}
// Устанавливаем положение фиксации горизонтальных заслонок
switch(horizontal_direction_) {
case AirflowHorizontalDirection::LAST:
dataTX[33] += 0b00000000;
ESP_LOGD("TCL", "Horizontal fix: last position");
break;
case AirflowHorizontalDirection::MAX_LEFT:
dataTX[33] += 0b00000001;
ESP_LOGD("TCL", "Horizontal fix: left");
break;
case AirflowHorizontalDirection::LEFT:
dataTX[33] += 0b00000010;
ESP_LOGD("TCL", "Horizontal fix: lefter");
break;
case AirflowHorizontalDirection::CENTER:
dataTX[33] += 0b00000011;
ESP_LOGD("TCL", "Horizontal fix: center");
break;
case AirflowHorizontalDirection::RIGHT:
dataTX[33] += 0b00000100;
ESP_LOGD("TCL", "Horizontal fix: righter");
break;
case AirflowHorizontalDirection::MAX_RIGHT:
dataTX[33] += 0b00000101;
ESP_LOGD("TCL", "Horizontal fix: right");
break;
}
// Установка температуры
dataTX[9] = target_temperature_set;
// Собираем массив байт для отправки в кондиционер
dataTX[0] = 0xBB; //стартовый байт заголовка
dataTX[1] = 0x00; //стартовый байт заголовка
dataTX[2] = 0x01; //стартовый байт заголовка
dataTX[3] = 0x03; //0x03 - управление, 0x04 - опрос
dataTX[4] = 0x20; //0x20 - управление, 0x19 - опрос
dataTX[5] = 0x03; //??
dataTX[6] = 0x01; //??
//dataTX[7] = 0x64; //eco,display,beep,ontimerenable, offtimerenable,power,0,0
//dataTX[8] = 0x08; //mute,0,turbo,health, mode(4) mode 01 heat, 02 dry, 03 cool, 07 fan, 08 auto, health(+16), 41=turbo-heat 43=turbo-cool (turbo = 0x40+ 0x01..0x08)
//dataTX[9] = 0x0f; //0 -31 ; 15 - 16 0,0,0,0, temp(4) settemp 31 - x
//dataTX[10] = 0x00; //0,timerindicator,swingv(3),fan(3) fan+swing modes //0=auto 1=low 2=med 3=high
//dataTX[11] = 0x00; //0,offtimer(6),0
dataTX[12] = 0x00; //fahrenheit,ontimer(6),0 cf 80=f 0=c
dataTX[13] = 0x01; //??
dataTX[14] = 0x00; //0,0,halfdegree,0,0,0,0,0
dataTX[15] = 0x00; //??
dataTX[16] = 0x00; //??
dataTX[17] = 0x00; //??
dataTX[18] = 0x00; //??
//dataTX[19] = 0x00; //sleep on = 1 off=0
dataTX[20] = 0x00; //??
dataTX[21] = 0x00; //??
dataTX[22] = 0x00; //??
dataTX[23] = 0x00; //??
dataTX[24] = 0x00; //??
dataTX[25] = 0x00; //??
dataTX[26] = 0x00; //??
dataTX[27] = 0x00; //??
dataTX[28] = 0x00; //??
dataTX[30] = 0x00; //??
dataTX[31] = 0x00; //??
//dataTX[32] = 0x00; //0,0,0,режим вертикального качания(2),режим вертикальной фиксации(3)
//dataTX[33] = 0x00; //0,0,режим горизонтального качания(3),режим горизонтальной фиксации(3)
dataTX[34] = 0x00; //??
dataTX[35] = 0x00; //??
dataTX[36] = 0x00; //??
dataTX[37] = 0xFF; //Контрольная сумма
dataTX[37] = tclacClimate::getChecksum(dataTX, sizeof(dataTX));
tclacClimate::sendData(dataTX, sizeof(dataTX));
allow_take_control = false;
is_call_control = false;
}
// Отправка данных в кондиционер
void tclacClimate::sendData(byte * message, byte size) {
tclacClimate::dataShow(1,1);
//Serial.write(message, size);
this->esphome::uart::UARTDevice::write_array(message, size);
//auto raw = getHex(message, size);
ESP_LOGD("TCL", "Message to TCL sended...");
tclacClimate::dataShow(1,0);
}
// Преобразование байта в читабельный формат
String tclacClimate::getHex(byte *message, byte size) {
String raw;
for (int i = 0; i < size; i++) {
raw += "\n" + String(message[i]);
}
raw.toUpperCase();
return raw;
}
// Вычисление контрольной суммы
byte tclacClimate::getChecksum(const byte * message, size_t size) {
byte position = size - 1;
byte crc = 0;
for (int i = 0; i < position; i++)
crc ^= message[i];
return crc;
}
// Мигаем светодиодами
void tclacClimate::dataShow(bool flow, bool shine) {
if (module_display_status_){
if (flow == 0){
if (shine == 1){
#ifdef CONF_RX_LED
this->rx_led_pin_->digital_write(true);
#endif
} else {
#ifdef CONF_RX_LED
this->rx_led_pin_->digital_write(false);
#endif
}
}
if (flow == 1) {
if (shine == 1){
#ifdef CONF_TX_LED
this->tx_led_pin_->digital_write(true);
#endif
} else {
#ifdef CONF_TX_LED
this->tx_led_pin_->digital_write(false);
#endif
}
}
}
}
// Действия с данными из конфига
// Получение состояния пищалки
void tclacClimate::set_beeper_state(bool state) {
this->beeper_status_ = state;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Получение состояния дисплея кондиционера
void tclacClimate::set_display_state(bool state) {
this->display_status_ = state;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Получение состояния режима принудительного применения настроек
void tclacClimate::set_force_mode_state(bool state) {
this->force_mode_status_ = state;
}
// Получение пина светодиода приема данных
#ifdef CONF_RX_LED
void tclacClimate::set_rx_led_pin(GPIOPin *rx_led_pin) {
this->rx_led_pin_ = rx_led_pin;
}
#endif
// Получение пина светодиода передачи данных
#ifdef CONF_TX_LED
void tclacClimate::set_tx_led_pin(GPIOPin *tx_led_pin) {
this->tx_led_pin_ = tx_led_pin;
}
#endif
// Получение состояния светодиодов связи модуля
void tclacClimate::set_module_display_state(bool state) {
this->module_display_status_ = state;
}
// Получение режима фиксации вертикальной заслонки
void tclacClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
this->vertical_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Получение режима фиксации горизонтальных заслонок
void tclacClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) {
this->horizontal_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Получение режима качания вертикальной заслонки
void tclacClimate::set_vertical_swing_direction(VerticalSwingDirection direction) {
this->vertical_swing_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Получение доступных режимов работы кондиционера
void tclacClimate::set_supported_modes(const std::set<climate::ClimateMode> &modes) {
this->supported_modes_ = modes;
}
// Получение режима качания горизонтальных заслонок
void tclacClimate::set_horizontal_swing_direction(HorizontalSwingDirection direction) {
horizontal_swing_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Получение доступных скоростей вентилятора
void tclacClimate::set_supported_fan_modes(const std::set<climate::ClimateFanMode> &modes){
this->supported_fan_modes_ = modes;
}
// Получение доступных режимов качания заслонок
void tclacClimate::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
this->supported_swing_modes_ = modes;
}
// Получение доступных предустановок
void tclacClimate::set_supported_presets(const std::set<climate::ClimatePreset> &presets) {
this->supported_presets_ = presets;
}
}
}

157
components/tclac/tclac.h Normal file
View File

@@ -0,0 +1,157 @@
/**
* Create by Miguel Ángel López on 20/07/19
* and modify by xaxexa
* Refactoring & component making:
* Соловей с паяльником 15.03.2024
**/
#ifndef TCL_ESP_TCL_H
#define TCL_ESP_TCL_H
#include "esphome.h"
#include "esphome/core/defines.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/climate/climate.h"
namespace esphome {
namespace tclac {
#define SET_TEMP_MASK 0b00001111
#define MODE_POS 7
#define MODE_MASK 0b00111111
#define MODE_AUTO 0b00110101
#define MODE_COOL 0b00110001
#define MODE_DRY 0b00110011
#define MODE_FAN_ONLY 0b00110010
#define MODE_HEAT 0b00110100
#define FAN_SPEED_POS 8
#define FAN_QUIET_POS 33
#define FAN_AUTO 0b10000000 //auto
#define FAN_QUIET 0x80 //silent
#define FAN_LOW 0b10010000 // |
#define FAN_MIDDLE 0b11000000 // ||
#define FAN_MEDIUM 0b10100000 // |||
#define FAN_HIGH 0b11010000 // ||||
#define FAN_FOCUS 0b10110000 // |||||
#define FAN_DIFFUSE 0b10000000 // POWER [7]
#define FAN_SPEED_MASK 0b11110000 //FAN SPEED MASK
#define SWING_POS 10
#define SWING_OFF 0b00000000
#define SWING_HORIZONTAL 0b00100000
#define SWING_VERTICAL 0b01000000
#define SWING_BOTH 0b01100000
#define SWING_MODE_MASK 0b01100000
using climate::ClimateCall;
using climate::ClimateMode;
using climate::ClimatePreset;
using climate::ClimateTraits;
using climate::ClimateFanMode;
using climate::ClimateSwingMode;
enum class VerticalSwingDirection : uint8_t {
UP_DOWN = 0,
UPSIDE = 1,
DOWNSIDE = 2,
};
enum class HorizontalSwingDirection : uint8_t {
LEFT_RIGHT = 0,
LEFTSIDE = 1,
CENTER = 2,
RIGHTSIDE = 3,
};
enum class AirflowVerticalDirection : uint8_t {
LAST = 0,
MAX_UP = 1,
UP = 2,
CENTER = 3,
DOWN = 4,
MAX_DOWN = 5,
};
enum class AirflowHorizontalDirection : uint8_t {
LAST = 0,
MAX_LEFT = 1,
LEFT = 2,
CENTER = 3,
RIGHT = 4,
MAX_RIGHT = 5,
};
class tclacClimate : public climate::Climate, public esphome::uart::UARTDevice, public PollingComponent {
private:
byte checksum;
// dataTX с управлением состоит из 38 байт
byte dataTX[38];
// А dataRX по прежнему из 61 байта
byte dataRX[61];
// Команда запроса состояния
byte poll[8] = {0xBB,0x00,0x01,0x04,0x02,0x01,0x00,0xBD};
// Инициализация и начальное наполнение переменных состоянй переключателей
bool beeper_status_;
bool display_status_;
bool force_mode_status_;
uint8_t switch_preset = 0;
bool module_display_status_;
uint8_t switch_fan_mode = 0;
bool is_call_control = false;
uint8_t switch_swing_mode = 0;
int target_temperature_set = 0;
uint8_t switch_climate_mode = 0;
bool allow_take_control = false;
esphome::climate::ClimateTraits traits_;
public:
tclacClimate() : PollingComponent(5 * 1000) {
checksum = 0;
}
void readData();
void takeControl();
void loop() override;
void setup() override;
void update() override;
void set_beeper_state(bool state);
void set_display_state(bool state);
void dataShow(bool flow, bool shine);
void set_force_mode_state(bool state);
void set_rx_led_pin(GPIOPin *rx_led_pin);
void set_tx_led_pin(GPIOPin *tx_led_pin);
void sendData(byte * message, byte size);
void set_module_display_state(bool state);
static String getHex(byte *message, byte size);
void control(const ClimateCall &call) override;
static byte getChecksum(const byte * message, size_t size);
void set_vertical_airflow(AirflowVerticalDirection direction);
void set_horizontal_airflow(AirflowHorizontalDirection direction);
void set_vertical_swing_direction(VerticalSwingDirection direction);
void set_horizontal_swing_direction(HorizontalSwingDirection direction);
void set_supported_presets(const std::set<climate::ClimatePreset> &presets);
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes);
void set_supported_fan_modes(const std::set<esphome::climate::ClimateFanMode> &modes);
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes);
protected:
GPIOPin *rx_led_pin_;
GPIOPin *tx_led_pin_;
ClimateTraits traits() override;
std::set<ClimateMode> supported_modes_{};
std::set<ClimatePreset> supported_presets_{};
AirflowVerticalDirection vertical_direction_;
std::set<ClimateFanMode> supported_fan_modes_{};
AirflowHorizontalDirection horizontal_direction_;
VerticalSwingDirection vertical_swing_direction_;
std::set<ClimateSwingMode> supported_swing_modes_{};
HorizontalSwingDirection horizontal_swing_direction_;
};
}
}
#endif //TCL_ESP_TCL_H