tlcac
This commit is contained in:
@@ -1,265 +0,0 @@
|
|||||||
|
|
||||||
#from esphome import automation
|
|
||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components import climate, sensor, uart
|
|
||||||
from esphome.components.climate import ClimateMode, ClimatePreset, ClimateSwingMode
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import (
|
|
||||||
CONF_AUTOCONF,
|
|
||||||
CONF_BEEPER,
|
|
||||||
CONF_CUSTOM_FAN_MODES,
|
|
||||||
CONF_CUSTOM_PRESETS,
|
|
||||||
CONF_ID,
|
|
||||||
CONF_NUM_ATTEMPTS,
|
|
||||||
CONF_OUTDOOR_TEMPERATURE,
|
|
||||||
CONF_PERIOD,
|
|
||||||
CONF_SUPPORTED_MODES,
|
|
||||||
CONF_SUPPORTED_PRESETS,
|
|
||||||
CONF_SUPPORTED_SWING_MODES,
|
|
||||||
CONF_TEMPERATURE,
|
|
||||||
CONF_TIMEOUT,
|
|
||||||
CONF_USE_FAHRENHEIT,
|
|
||||||
DEVICE_CLASS_HUMIDITY,
|
|
||||||
DEVICE_CLASS_POWER,
|
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
|
||||||
ICON_POWER,
|
|
||||||
ICON_THERMOMETER,
|
|
||||||
ICON_WATER_PERCENT,
|
|
||||||
STATE_CLASS_MEASUREMENT,
|
|
||||||
UNIT_CELSIUS,
|
|
||||||
UNIT_PERCENT,
|
|
||||||
UNIT_WATT,
|
|
||||||
)
|
|
||||||
|
|
||||||
CODEOWNERS = ["@floriansimmer"]
|
|
||||||
DEPENDENCIES = ["climate", "uart"]
|
|
||||||
AUTO_LOAD = ["sensor"]
|
|
||||||
CONF_POWER_USAGE = "power_usage"
|
|
||||||
CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
|
|
||||||
|
|
||||||
tcl_ac_ns = cg.esphome_ns.namespace("TclClimate")
|
|
||||||
AirConditioner = tcl_ac_ns.class_("TclClimateComponent", climate.Climate, cg.Component)
|
|
||||||
|
|
||||||
|
|
||||||
ALLOWED_CLIMATE_MODES = {
|
|
||||||
"HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL,
|
|
||||||
"COOL": ClimateMode.CLIMATE_MODE_COOL,
|
|
||||||
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
|
|
||||||
"DRY": ClimateMode.CLIMATE_MODE_DRY,
|
|
||||||
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
|
||||||
}
|
|
||||||
|
|
||||||
ALLOWED_CLIMATE_PRESETS = {
|
|
||||||
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
|
|
||||||
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
|
||||||
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
|
|
||||||
}
|
|
||||||
|
|
||||||
ALLOWED_CLIMATE_SWING_MODES = {
|
|
||||||
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
|
|
||||||
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
|
|
||||||
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
|
|
||||||
}
|
|
||||||
|
|
||||||
CUSTOM_FAN_MODES = {
|
|
||||||
"SILENT": Capabilities.SILENT,
|
|
||||||
"TURBO": Capabilities.TURBO,
|
|
||||||
}
|
|
||||||
|
|
||||||
CUSTOM_PRESETS = {
|
|
||||||
"FREEZE_PROTECTION": Capabilities.FREEZE_PROTECTION,
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_modes = cv.enum(ALLOWED_CLIMATE_MODES, upper=True)
|
|
||||||
validate_presets = cv.enum(ALLOWED_CLIMATE_PRESETS, upper=True)
|
|
||||||
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
|
|
||||||
validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
|
|
||||||
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
|
|
||||||
|
|
||||||
# CONFIG_SCHEMA = cv.All(
|
|
||||||
# climate.climate_schema(AirConditioner)
|
|
||||||
# .extend(
|
|
||||||
# {
|
|
||||||
# cv.Optional(CONF_PERIOD, default="1s"): cv.time_period,
|
|
||||||
# cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period,
|
|
||||||
# cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5),
|
|
||||||
# cv.OnlyWith(CONF_TRANSMITTER_ID, "remote_transmitter"): cv.use_id(
|
|
||||||
# remote_transmitter.RemoteTransmitterComponent
|
|
||||||
# ),
|
|
||||||
# cv.Optional(CONF_BEEPER, default=False): cv.boolean,
|
|
||||||
# cv.Optional(CONF_AUTOCONF, default=True): cv.boolean,
|
|
||||||
# cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(validate_modes),
|
|
||||||
# cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list(
|
|
||||||
# validate_swing_modes
|
|
||||||
# ),
|
|
||||||
# cv.Optional(CONF_SUPPORTED_PRESETS): cv.ensure_list(validate_presets),
|
|
||||||
# cv.Optional(CONF_CUSTOM_PRESETS): cv.ensure_list(validate_custom_presets),
|
|
||||||
# cv.Optional(CONF_CUSTOM_FAN_MODES): cv.ensure_list(
|
|
||||||
# validate_custom_fan_modes
|
|
||||||
# ),
|
|
||||||
# cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
|
|
||||||
# unit_of_measurement=UNIT_CELSIUS,
|
|
||||||
# icon=ICON_THERMOMETER,
|
|
||||||
# accuracy_decimals=0,
|
|
||||||
# device_class=DEVICE_CLASS_TEMPERATURE,
|
|
||||||
# state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
# ),
|
|
||||||
# cv.Optional(CONF_POWER_USAGE): sensor.sensor_schema(
|
|
||||||
# unit_of_measurement=UNIT_WATT,
|
|
||||||
# icon=ICON_POWER,
|
|
||||||
# accuracy_decimals=0,
|
|
||||||
# device_class=DEVICE_CLASS_POWER,
|
|
||||||
# state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
# ),
|
|
||||||
# cv.Optional(CONF_HUMIDITY_SETPOINT): sensor.sensor_schema(
|
|
||||||
# unit_of_measurement=UNIT_PERCENT,
|
|
||||||
# icon=ICON_WATER_PERCENT,
|
|
||||||
# accuracy_decimals=0,
|
|
||||||
# device_class=DEVICE_CLASS_HUMIDITY,
|
|
||||||
# state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
# ),
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
# .extend(uart.UART_DEVICE_SCHEMA)
|
|
||||||
# .extend(cv.COMPONENT_SCHEMA),
|
|
||||||
# cv.only_with_arduino,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Actions
|
|
||||||
# FollowMeAction = tcl_ac_ns.class_("FollowMeAction", automation.Action)
|
|
||||||
# DisplayToggleAction = tcl_ac_ns.class_("DisplayToggleAction", automation.Action)
|
|
||||||
# SwingStepAction = tcl_ac_ns.class_("SwingStepAction", automation.Action)
|
|
||||||
# BeeperOnAction = tcl_ac_ns.class_("BeeperOnAction", automation.Action)
|
|
||||||
# BeeperOffAction = tcl_ac_ns.class_("BeeperOffAction", automation.Action)
|
|
||||||
# PowerOnAction = tcl_ac_ns.class_("PowerOnAction", automation.Action)
|
|
||||||
# PowerOffAction = tcl_ac_ns.class_("PowerOffAction", automation.Action)
|
|
||||||
# PowerToggleAction = tcl_ac_ns.class_("PowerToggleAction", automation.Action)
|
|
||||||
|
|
||||||
# tcl_ACTION_BASE_SCHEMA = cv.Schema(
|
|
||||||
# {
|
|
||||||
# cv.GenerateID(CONF_ID): cv.use_id(AirConditioner),
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # FollowMe action
|
|
||||||
# tcl_FOLLOW_ME_SCHEMA = cv.Schema(
|
|
||||||
# {
|
|
||||||
# cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
|
|
||||||
# cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.templatable(cv.boolean),
|
|
||||||
# cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
# @register_action("follow_me", FollowMeAction, tcl_FOLLOW_ME_SCHEMA)
|
|
||||||
# async def follow_me_to_code(var, config, args):
|
|
||||||
# template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
|
|
||||||
# cg.add(var.set_beeper(template_))
|
|
||||||
# template_ = await cg.templatable(config[CONF_USE_FAHRENHEIT], args, cg.bool_)
|
|
||||||
# cg.add(var.set_use_fahrenheit(template_))
|
|
||||||
# template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
|
|
||||||
# cg.add(var.set_temperature(template_))
|
|
||||||
|
|
||||||
|
|
||||||
# # Toggle Display action
|
|
||||||
# @register_action(
|
|
||||||
# "display_toggle",
|
|
||||||
# DisplayToggleAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def display_toggle_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# # Swing Step action
|
|
||||||
# @register_action(
|
|
||||||
# "swing_step",
|
|
||||||
# SwingStepAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def swing_step_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# # Beeper On action
|
|
||||||
# @register_action(
|
|
||||||
# "beeper_on",
|
|
||||||
# BeeperOnAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def beeper_on_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# # Beeper Off action
|
|
||||||
# @register_action(
|
|
||||||
# "beeper_off",
|
|
||||||
# BeeperOffAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def beeper_off_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# # Power On action
|
|
||||||
# @register_action(
|
|
||||||
# "power_on",
|
|
||||||
# PowerOnAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def power_on_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# # Power Off action
|
|
||||||
# @register_action(
|
|
||||||
# "power_off",
|
|
||||||
# PowerOffAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def power_off_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# # Power Toggle action
|
|
||||||
# @register_action(
|
|
||||||
# "power_toggle",
|
|
||||||
# PowerToggleAction,
|
|
||||||
# cv.Schema({}),
|
|
||||||
# )
|
|
||||||
# async def power_inv_to_code(var, config, args):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = await climate.new_climate(config)
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
await uart.register_uart_device(var, config)
|
|
||||||
# cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
|
|
||||||
# cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds))
|
|
||||||
# cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS]))
|
|
||||||
# if CONF_TRANSMITTER_ID in config:
|
|
||||||
# cg.add_define("USE_REMOTE_TRANSMITTER")
|
|
||||||
# transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID])
|
|
||||||
# cg.add(var.set_transmitter(transmitter_))
|
|
||||||
# cg.add(var.set_beeper_feedback(config[CONF_BEEPER]))
|
|
||||||
# cg.add(var.set_autoconf(config[CONF_AUTOCONF]))
|
|
||||||
# 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]))
|
|
||||||
# if CONF_SUPPORTED_PRESETS in config:
|
|
||||||
# cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
|
|
||||||
# if CONF_CUSTOM_PRESETS in config:
|
|
||||||
# cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS]))
|
|
||||||
# if CONF_CUSTOM_FAN_MODES in config:
|
|
||||||
# cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
|
|
||||||
# if CONF_OUTDOOR_TEMPERATURE in config:
|
|
||||||
# sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE])
|
|
||||||
# cg.add(var.set_outdoor_temperature_sensor(sens))
|
|
||||||
# if CONF_POWER_USAGE in config:
|
|
||||||
# sens = await sensor.new_sensor(config[CONF_POWER_USAGE])
|
|
||||||
# cg.add(var.set_power_sensor(sens))
|
|
||||||
# if CONF_HUMIDITY_SETPOINT in config:
|
|
||||||
# sens = await sensor.new_sensor(config[CONF_HUMIDITY_SETPOINT])
|
|
||||||
# cg.add(var.set_humidity_setpoint_sensor(sens))
|
|
||||||
@@ -1,650 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/uart/uart.h"
|
|
||||||
#include "esphome/components/climate/climate.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace TclClimate {
|
|
||||||
|
|
||||||
class TclClimateComponent : public component::PollingComponent, public climate::Climate, public uart::UARTDevice {
|
|
||||||
public:
|
|
||||||
|
|
||||||
TclClimate(UARTComponent *parent) : UARTDevice(parent) {}
|
|
||||||
TclClimate() : PollingComponent() {}
|
|
||||||
bool is_changed : 1;
|
|
||||||
|
|
||||||
union get_cmd_resp_t{
|
|
||||||
struct {
|
|
||||||
uint8_t header;
|
|
||||||
uint8_t byte_1;
|
|
||||||
uint8_t byte_2;
|
|
||||||
uint8_t type;
|
|
||||||
uint8_t len;
|
|
||||||
uint8_t byte_5;
|
|
||||||
uint8_t byte_6;
|
|
||||||
|
|
||||||
uint8_t mode : 4;
|
|
||||||
uint8_t power : 1;
|
|
||||||
uint8_t disp : 1;
|
|
||||||
uint8_t eco : 1;
|
|
||||||
uint8_t turbo : 1;
|
|
||||||
|
|
||||||
uint8_t temp : 4;
|
|
||||||
uint8_t fan : 3;
|
|
||||||
uint8_t byte_8_bit_7 : 1;
|
|
||||||
|
|
||||||
uint8_t byte_9;
|
|
||||||
|
|
||||||
uint8_t byte_10_bit_0_4 : 5;
|
|
||||||
uint8_t hswing : 1;
|
|
||||||
uint8_t vswing : 1;
|
|
||||||
uint8_t byte_10_bit_7 : 1;
|
|
||||||
|
|
||||||
uint8_t byte_11;
|
|
||||||
uint8_t byte_12;
|
|
||||||
uint8_t byte_13;
|
|
||||||
uint8_t byte_14;
|
|
||||||
uint8_t byte_15;
|
|
||||||
uint8_t byte_16;
|
|
||||||
uint8_t byte_17;
|
|
||||||
uint8_t byte_18;
|
|
||||||
uint8_t byte_19;
|
|
||||||
|
|
||||||
uint8_t byte_20;
|
|
||||||
uint8_t byte_21;
|
|
||||||
uint8_t byte_22;
|
|
||||||
uint8_t byte_23;
|
|
||||||
uint8_t byte_24;
|
|
||||||
uint8_t byte_25;
|
|
||||||
uint8_t byte_26;
|
|
||||||
uint8_t byte_27;
|
|
||||||
uint8_t byte_28;
|
|
||||||
uint8_t byte_29;
|
|
||||||
|
|
||||||
uint8_t byte_30;
|
|
||||||
uint8_t byte_31;
|
|
||||||
uint8_t byte_32;
|
|
||||||
|
|
||||||
uint8_t byte_33_bit_0_6 : 7;
|
|
||||||
uint8_t mute : 1;
|
|
||||||
|
|
||||||
uint8_t byte_34;
|
|
||||||
uint8_t byte_35;
|
|
||||||
uint8_t byte_36;
|
|
||||||
uint8_t byte_37;
|
|
||||||
uint8_t byte_38;
|
|
||||||
uint8_t byte_39;
|
|
||||||
|
|
||||||
uint8_t byte_40;
|
|
||||||
uint8_t byte_41;
|
|
||||||
uint8_t byte_42;
|
|
||||||
uint8_t byte_43;
|
|
||||||
uint8_t byte_44;
|
|
||||||
uint8_t byte_45;
|
|
||||||
uint8_t byte_46;
|
|
||||||
uint8_t byte_47;
|
|
||||||
uint8_t byte_48;
|
|
||||||
uint8_t byte_49;
|
|
||||||
|
|
||||||
uint8_t byte_50;
|
|
||||||
|
|
||||||
uint8_t vswing_fix : 3;
|
|
||||||
uint8_t vswing_mv : 2;
|
|
||||||
uint8_t byte_51_bit_5_7 : 3;
|
|
||||||
|
|
||||||
uint8_t hswing_fix : 3;
|
|
||||||
uint8_t hswing_mv : 3;
|
|
||||||
uint8_t byte_52_bit_6_7 : 2;
|
|
||||||
|
|
||||||
uint8_t byte_53;
|
|
||||||
uint8_t byte_54;
|
|
||||||
uint8_t byte_55;
|
|
||||||
uint8_t byte_56;
|
|
||||||
uint8_t byte_57;
|
|
||||||
uint8_t byte_58;
|
|
||||||
uint8_t byte_59;
|
|
||||||
|
|
||||||
uint8_t xor_sum;
|
|
||||||
} data;
|
|
||||||
uint8_t raw[61];
|
|
||||||
};
|
|
||||||
|
|
||||||
union set_cmd_t {
|
|
||||||
struct {
|
|
||||||
uint8_t header;
|
|
||||||
uint8_t byte_1;
|
|
||||||
uint8_t byte_2;
|
|
||||||
uint8_t type;
|
|
||||||
uint8_t len;
|
|
||||||
uint8_t byte_5;
|
|
||||||
uint8_t byte_6;
|
|
||||||
|
|
||||||
uint8_t byte_7_bit_0_1 : 2;
|
|
||||||
uint8_t power : 1;
|
|
||||||
uint8_t off_timer_en : 1;
|
|
||||||
uint8_t on_timer_en : 1;
|
|
||||||
uint8_t beep : 1;
|
|
||||||
uint8_t disp : 1;
|
|
||||||
uint8_t eco : 1;
|
|
||||||
|
|
||||||
uint8_t mode : 4;
|
|
||||||
uint8_t byte_8_bit_4_5 : 2;
|
|
||||||
uint8_t turbo : 1;
|
|
||||||
uint8_t mute : 1;
|
|
||||||
|
|
||||||
uint8_t temp : 4;
|
|
||||||
uint8_t byte_9_bit_4_7 : 4;
|
|
||||||
|
|
||||||
uint8_t fan : 3;
|
|
||||||
uint8_t vswing : 3;
|
|
||||||
uint8_t byte_10_bit_6 : 1;
|
|
||||||
uint8_t byte_10_bit_7 : 1;
|
|
||||||
|
|
||||||
uint8_t byte_11_bit_0_2 : 3;
|
|
||||||
uint8_t hswing : 1;
|
|
||||||
uint8_t byte_11_bit_4_7 : 4;
|
|
||||||
|
|
||||||
uint8_t byte_12;
|
|
||||||
uint8_t byte_13;
|
|
||||||
|
|
||||||
uint8_t byte_14_bit_0_2 : 3;
|
|
||||||
uint8_t byte_14_bit_3 : 1;
|
|
||||||
uint8_t byte_14_bit_4 : 1;//uint8_t hswing : 1;
|
|
||||||
uint8_t half_degree : 1;
|
|
||||||
uint8_t byte_14_bit_6_7 : 2;
|
|
||||||
|
|
||||||
uint8_t byte_15;
|
|
||||||
uint8_t byte_16;
|
|
||||||
uint8_t byte_17;
|
|
||||||
uint8_t byte_18;
|
|
||||||
uint8_t byte_19;
|
|
||||||
|
|
||||||
uint8_t byte_20;
|
|
||||||
uint8_t byte_21;
|
|
||||||
uint8_t byte_22;
|
|
||||||
uint8_t byte_23;
|
|
||||||
uint8_t byte_24;
|
|
||||||
uint8_t byte_25;
|
|
||||||
uint8_t byte_26;
|
|
||||||
uint8_t byte_27;
|
|
||||||
uint8_t byte_28;
|
|
||||||
uint8_t byte_29;
|
|
||||||
|
|
||||||
uint8_t byte_30;
|
|
||||||
uint8_t byte_31;
|
|
||||||
uint8_t vswing_fix : 3;
|
|
||||||
uint8_t vswing_mv : 2;
|
|
||||||
uint8_t byte_32_bit_5_7 : 3;
|
|
||||||
|
|
||||||
uint8_t hswing_fix : 3;
|
|
||||||
uint8_t hswing_mv : 3;
|
|
||||||
uint8_t byte_33_bit_6_7 : 2;
|
|
||||||
|
|
||||||
uint8_t xor_sum;
|
|
||||||
} data;
|
|
||||||
uint8_t raw[35];
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ready_to_send_set_cmd_flag = false;
|
|
||||||
|
|
||||||
uint8_t set_cmd_base[35] = {0xBB, 0x00, 0x01, 0x03, 0x1D, 0x00, 0x00, 0x64, 0x03, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
get_cmd_resp_t m_get_cmd_resp = {0};
|
|
||||||
set_cmd_t m_set_cmd = {0};
|
|
||||||
|
|
||||||
void set_current_temperature (float current_temperature) {
|
|
||||||
if (this->current_temperature == current_temperature) return;
|
|
||||||
this->is_changed = true;
|
|
||||||
this->current_temperature = current_temperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_custom_fan_mode (const std::string &fan_mode) {
|
|
||||||
if (this->custom_fan_mode == fan_mode) return;
|
|
||||||
this->is_changed = true;
|
|
||||||
this->custom_fan_mode = fan_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_mode (esphome::climate::ClimateMode mode) {
|
|
||||||
if (this->mode == mode) return;
|
|
||||||
this->is_changed = true;
|
|
||||||
this->mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_swing_mode (esphome::climate::ClimateSwingMode swing_mode) {
|
|
||||||
if (this->swing_mode == swing_mode) return;
|
|
||||||
this->is_changed = true;
|
|
||||||
this->swing_mode = swing_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string hswing_pos = "";
|
|
||||||
std::string vswing_pos = "";
|
|
||||||
|
|
||||||
void set_hswing_pos (const std::string &hswing_pos) {
|
|
||||||
if (this->hswing_pos == hswing_pos) return;
|
|
||||||
id(hswing).publish_state(hswing_pos);
|
|
||||||
this->hswing_pos = hswing_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_vswing_pos (const std::string &vswing_pos) {
|
|
||||||
if (this->vswing_pos == vswing_pos) return;
|
|
||||||
id(vswing).publish_state(vswing_pos);
|
|
||||||
this->vswing_pos = vswing_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_target_temperature (float target_temperature) {
|
|
||||||
if (this->target_temperature == target_temperature) return;
|
|
||||||
this->is_changed = true;
|
|
||||||
this->target_temperature = target_temperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
void build_set_cmd(get_cmd_resp_t * get_cmd_resp) {
|
|
||||||
memcpy(m_set_cmd.raw, set_cmd_base, sizeof(m_set_cmd.raw));
|
|
||||||
|
|
||||||
m_set_cmd.data.power = get_cmd_resp->data.power;
|
|
||||||
m_set_cmd.data.off_timer_en = 0;
|
|
||||||
m_set_cmd.data.on_timer_en = 0;
|
|
||||||
m_set_cmd.data.beep = 1;
|
|
||||||
m_set_cmd.data.disp = 1;
|
|
||||||
m_set_cmd.data.eco = 0;
|
|
||||||
|
|
||||||
switch (get_cmd_resp->data.mode) {
|
|
||||||
case 0x01:
|
|
||||||
m_set_cmd.data.mode = 0x03;
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
m_set_cmd.data.mode = 0x02;
|
|
||||||
break;
|
|
||||||
case 0x02:
|
|
||||||
m_set_cmd.data.mode = 0x07;
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
m_set_cmd.data.mode = 0x01;
|
|
||||||
break;
|
|
||||||
case 0x05:
|
|
||||||
m_set_cmd.data.mode = 0x08;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_set_cmd.data.turbo = get_cmd_resp->data.turbo;
|
|
||||||
m_set_cmd.data.mute = get_cmd_resp->data.mute;
|
|
||||||
m_set_cmd.data.temp = 15 - get_cmd_resp->data.temp;
|
|
||||||
|
|
||||||
switch (get_cmd_resp->data.fan) {
|
|
||||||
case 0x00:
|
|
||||||
m_set_cmd.data.fan = 0x00;
|
|
||||||
break;
|
|
||||||
case 0x01:
|
|
||||||
m_set_cmd.data.fan = 0x02;
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
m_set_cmd.data.fan = 0x06;
|
|
||||||
break;
|
|
||||||
case 0x02:
|
|
||||||
m_set_cmd.data.fan = 0x03;
|
|
||||||
break;
|
|
||||||
case 0x05:
|
|
||||||
m_set_cmd.data.fan = 0x07;
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
m_set_cmd.data.fan = 0x05;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//m_set_cmd.data.vswing = get_cmd_resp->data.vswing ? 0x07 : 0x00;
|
|
||||||
//m_set_cmd.data.hswing = get_cmd_resp->data.hswing;
|
|
||||||
|
|
||||||
if (get_cmd_resp->data.vswing_mv) {
|
|
||||||
m_set_cmd.data.vswing = 0x07;
|
|
||||||
m_set_cmd.data.vswing_fix = 0;
|
|
||||||
m_set_cmd.data.vswing_mv = get_cmd_resp->data.vswing_mv;
|
|
||||||
} else if (get_cmd_resp->data.vswing_fix) {
|
|
||||||
m_set_cmd.data.vswing = 0;
|
|
||||||
m_set_cmd.data.vswing_fix = get_cmd_resp->data.vswing_fix;
|
|
||||||
m_set_cmd.data.vswing_mv = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_cmd_resp->data.hswing_mv) {
|
|
||||||
m_set_cmd.data.hswing = 0x01;
|
|
||||||
m_set_cmd.data.hswing_fix = 0;
|
|
||||||
m_set_cmd.data.hswing_mv = get_cmd_resp->data.hswing_mv;
|
|
||||||
} else if (get_cmd_resp->data.hswing_fix) {
|
|
||||||
m_set_cmd.data.hswing = 0;
|
|
||||||
m_set_cmd.data.hswing_fix = get_cmd_resp->data.hswing_fix;
|
|
||||||
m_set_cmd.data.hswing_mv = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_set_cmd.data.half_degree = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(m_set_cmd.raw) - 1; i++) m_set_cmd.raw[sizeof(m_set_cmd.raw) - 1] ^= m_set_cmd.raw[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() override {
|
|
||||||
// This will be called by App.setup()
|
|
||||||
set_update_interval(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void control_vertical_swing(const std::string &swing_mode) {
|
|
||||||
// Implement the vertical swing control logic
|
|
||||||
ESP_LOGD("TclClimate", "Vertical swing set to: %s", swing_mode.c_str());
|
|
||||||
|
|
||||||
get_cmd_resp_t get_cmd_resp = {0};
|
|
||||||
memcpy(get_cmd_resp.raw, m_get_cmd_resp.raw, sizeof(get_cmd_resp.raw));
|
|
||||||
|
|
||||||
get_cmd_resp.data.vswing_mv = 0;
|
|
||||||
get_cmd_resp.data.vswing_fix = 0;
|
|
||||||
|
|
||||||
if (swing_mode == "Move full") get_cmd_resp.data.vswing_mv = 0x01;
|
|
||||||
else if (swing_mode == "Move upper") get_cmd_resp.data.vswing_mv = 0x02;
|
|
||||||
else if (swing_mode == "Move lower") get_cmd_resp.data.vswing_mv = 0x03;
|
|
||||||
else if (swing_mode == "Fix top") get_cmd_resp.data.vswing_fix = 0x01;
|
|
||||||
else if (swing_mode == "Fix upper") get_cmd_resp.data.vswing_fix = 0x02;
|
|
||||||
else if (swing_mode == "Fix mid") get_cmd_resp.data.vswing_fix = 0x03;
|
|
||||||
else if (swing_mode == "Fix lower") get_cmd_resp.data.vswing_fix = 0x04;
|
|
||||||
else if (swing_mode == "Fix bottom") get_cmd_resp.data.vswing_fix = 0x05;
|
|
||||||
|
|
||||||
if (get_cmd_resp.data.vswing_mv) get_cmd_resp.data.vswing = 0x01;
|
|
||||||
else get_cmd_resp.data.vswing = 0;
|
|
||||||
|
|
||||||
build_set_cmd(&get_cmd_resp);
|
|
||||||
ready_to_send_set_cmd_flag = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void control_horizontal_swing(const std::string &swing_mode) {
|
|
||||||
// Implement the vertical swing control logic
|
|
||||||
ESP_LOGD("TclClimate", "Horizontal swing set to: %s", swing_mode.c_str());
|
|
||||||
|
|
||||||
get_cmd_resp_t get_cmd_resp = {0};
|
|
||||||
memcpy(get_cmd_resp.raw, m_get_cmd_resp.raw, sizeof(get_cmd_resp.raw));
|
|
||||||
|
|
||||||
get_cmd_resp.data.hswing_mv = 0;
|
|
||||||
get_cmd_resp.data.hswing_fix = 0;
|
|
||||||
|
|
||||||
if (swing_mode == "Move full") get_cmd_resp.data.hswing_mv = 0x01;
|
|
||||||
else if (swing_mode == "Move left") get_cmd_resp.data.hswing_mv = 0x02;
|
|
||||||
else if (swing_mode == "Move mid") get_cmd_resp.data.hswing_mv = 0x03;
|
|
||||||
else if (swing_mode == "Move right") get_cmd_resp.data.hswing_mv = 0x04;
|
|
||||||
else if (swing_mode == "Fix left") get_cmd_resp.data.hswing_fix = 0x01;
|
|
||||||
else if (swing_mode == "Fix mid left") get_cmd_resp.data.hswing_fix = 0x02;
|
|
||||||
else if (swing_mode == "Fix mid") get_cmd_resp.data.hswing_fix = 0x03;
|
|
||||||
else if (swing_mode == "Fix mid right") get_cmd_resp.data.hswing_fix = 0x04;
|
|
||||||
else if (swing_mode == "Fix right") get_cmd_resp.data.hswing_fix = 0x05;
|
|
||||||
|
|
||||||
if (get_cmd_resp.data.vswing_mv) get_cmd_resp.data.hswing = 0x01;
|
|
||||||
else get_cmd_resp.data.hswing = 0;
|
|
||||||
|
|
||||||
build_set_cmd(&get_cmd_resp);
|
|
||||||
ready_to_send_set_cmd_flag = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void control(const ClimateCall &call) override {
|
|
||||||
if (call.get_mode().has_value()) {
|
|
||||||
// User requested mode change
|
|
||||||
ClimateMode climate_mode = *call.get_mode();
|
|
||||||
// Send mode to hardware
|
|
||||||
|
|
||||||
get_cmd_resp_t get_cmd_resp = {0};
|
|
||||||
memcpy(get_cmd_resp.raw, m_get_cmd_resp.raw, sizeof(get_cmd_resp.raw));
|
|
||||||
|
|
||||||
if (climate_mode == climate::CLIMATE_MODE_OFF) {
|
|
||||||
get_cmd_resp.data.power = 0x00;
|
|
||||||
} else {
|
|
||||||
get_cmd_resp.data.power = 0x01;
|
|
||||||
switch (climate_mode) {
|
|
||||||
case climate::CLIMATE_MODE_COOL:
|
|
||||||
get_cmd_resp.data.mode = 0x01;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_MODE_DRY:
|
|
||||||
get_cmd_resp.data.mode = 0x03;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
|
||||||
get_cmd_resp.data.mode = 0x02;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_MODE_HEAT:
|
|
||||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
|
||||||
get_cmd_resp.data.mode = 0x04;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_MODE_AUTO:
|
|
||||||
get_cmd_resp.data.mode = 0x05;
|
|
||||||
break;
|
|
||||||
case CLIMATE_MODE_OFF:
|
|
||||||
get_cmd_resp.data.power = 0x00;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build_set_cmd(&get_cmd_resp);
|
|
||||||
ready_to_send_set_cmd_flag = true;
|
|
||||||
|
|
||||||
// Publish updated state
|
|
||||||
// this->mode = mode;
|
|
||||||
// this->publish_state();
|
|
||||||
}
|
|
||||||
if (call.get_target_temperature().has_value()) {
|
|
||||||
// User requested target temperature change
|
|
||||||
float temp = *call.get_target_temperature();
|
|
||||||
|
|
||||||
get_cmd_resp_t get_cmd_resp = {0};
|
|
||||||
memcpy(get_cmd_resp.raw, m_get_cmd_resp.raw, sizeof(get_cmd_resp.raw));
|
|
||||||
|
|
||||||
get_cmd_resp.data.temp = uint8_t(temp) - 16;
|
|
||||||
|
|
||||||
build_set_cmd(&get_cmd_resp);
|
|
||||||
ready_to_send_set_cmd_flag = true;
|
|
||||||
}
|
|
||||||
if (false) {//call.get_swing_mode().has_value()) {
|
|
||||||
// User requested target temperature change
|
|
||||||
ClimateSwingMode swing_mode = *call.get_swing_mode();
|
|
||||||
|
|
||||||
get_cmd_resp_t get_cmd_resp = {0};
|
|
||||||
memcpy(get_cmd_resp.raw, m_get_cmd_resp.raw, sizeof(get_cmd_resp.raw));
|
|
||||||
|
|
||||||
switch(swing_mode) {
|
|
||||||
case climate::CLIMATE_SWING_OFF:
|
|
||||||
get_cmd_resp.data.hswing = 0;
|
|
||||||
get_cmd_resp.data.vswing = 0;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_SWING_BOTH:
|
|
||||||
get_cmd_resp.data.hswing = 1;
|
|
||||||
get_cmd_resp.data.vswing = 1;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_SWING_VERTICAL:
|
|
||||||
get_cmd_resp.data.hswing = 0;
|
|
||||||
get_cmd_resp.data.vswing = 1;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
|
||||||
get_cmd_resp.data.hswing = 1;
|
|
||||||
get_cmd_resp.data.vswing = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
build_set_cmd(&get_cmd_resp);
|
|
||||||
ready_to_send_set_cmd_flag = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (call.get_custom_fan_mode().has_value()) {
|
|
||||||
ESP_LOGI("ads", "ajskndjanjanwnjwa");
|
|
||||||
// User requested target temperature change
|
|
||||||
std::string fan_mode = *call.get_custom_fan_mode();
|
|
||||||
|
|
||||||
get_cmd_resp_t get_cmd_resp = {0};
|
|
||||||
memcpy(get_cmd_resp.raw, m_get_cmd_resp.raw, sizeof(get_cmd_resp.raw));
|
|
||||||
|
|
||||||
get_cmd_resp.data.turbo = 0x00;
|
|
||||||
get_cmd_resp.data.mute = 0x00;
|
|
||||||
if (fan_mode == esphome::to_string("Turbo")) {
|
|
||||||
get_cmd_resp.data.fan = 0x03;
|
|
||||||
get_cmd_resp.data.turbo = 0x01;
|
|
||||||
} else if (fan_mode == esphome::to_string("Mute")) {
|
|
||||||
get_cmd_resp.data.fan = 0x01;
|
|
||||||
get_cmd_resp.data.mute = 0x01;
|
|
||||||
} else if (fan_mode == esphome::to_string("Automatic")) get_cmd_resp.data.fan = 0x00;
|
|
||||||
else if (fan_mode == esphome::to_string("1")) get_cmd_resp.data.fan = 0x01;
|
|
||||||
else if (fan_mode == esphome::to_string("2")) get_cmd_resp.data.fan = 0x04;
|
|
||||||
else if (fan_mode == esphome::to_string("3")) get_cmd_resp.data.fan = 0x02;
|
|
||||||
else if (fan_mode == esphome::to_string("4")) get_cmd_resp.data.fan = 0x05;
|
|
||||||
else if (fan_mode == esphome::to_string("5")) get_cmd_resp.data.fan = 0x03;
|
|
||||||
|
|
||||||
build_set_cmd(&get_cmd_resp);
|
|
||||||
ready_to_send_set_cmd_flag = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClimateTraits traits() override {
|
|
||||||
// The capabilities of the climate device
|
|
||||||
auto traits = climate::ClimateTraits();
|
|
||||||
traits.set_supports_current_temperature(true);
|
|
||||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_AUTO});
|
|
||||||
traits.set_supported_custom_fan_modes({"Turbo", "Mute", "Automatic", "1", "2", "3", "4", "5"});
|
|
||||||
traits.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL});
|
|
||||||
traits.set_visual_min_temperature(16.0);
|
|
||||||
traits.set_visual_max_temperature(31.0);
|
|
||||||
traits.set_visual_target_temperature_step(1.0);
|
|
||||||
return traits;
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() override {
|
|
||||||
// This will be called every "update_interval" milliseconds.
|
|
||||||
uint8_t req_cmd[] = {0xBB, 0x00, 0x01, 0x04, 0x02, 0x01, 0x00, 0xBD};
|
|
||||||
|
|
||||||
if (ready_to_send_set_cmd_flag) {
|
|
||||||
ESP_LOGW("TCL", "Sending data");
|
|
||||||
ready_to_send_set_cmd_flag = false;
|
|
||||||
write_array(m_set_cmd.raw, sizeof(m_set_cmd.raw));
|
|
||||||
}
|
|
||||||
else write_array(req_cmd, sizeof(req_cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
int read_data_line(int readch, uint8_t *buffer, int len)
|
|
||||||
{
|
|
||||||
static int pos = 0;
|
|
||||||
static bool wait_len = false;
|
|
||||||
static int skipch = 0;
|
|
||||||
|
|
||||||
//ESP_LOGI("TclClimate", "%02X", readch);
|
|
||||||
|
|
||||||
if (readch >= 0) {
|
|
||||||
if (readch == 0xBB && skipch == 0 && !wait_len) {
|
|
||||||
pos = 0;
|
|
||||||
skipch = 3; // wait char with len
|
|
||||||
wait_len = true;
|
|
||||||
if (pos < len-1) buffer[pos++] = readch;
|
|
||||||
} else if (skipch == 0 && wait_len) {
|
|
||||||
if (pos < len-1) buffer[pos++] = readch;
|
|
||||||
skipch = readch + 1; // +1 control sum
|
|
||||||
ESP_LOGI("TCL", "len: %d", readch);
|
|
||||||
wait_len = false;
|
|
||||||
} else if (skipch > 0) {
|
|
||||||
if (pos < len-1) buffer[pos++] = readch;
|
|
||||||
if (--skipch == 0 && !wait_len) return pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No end of line has been found, so return -1.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_valid_xor(uint8_t *buffer, int len)
|
|
||||||
{
|
|
||||||
uint8_t xor_byte = 0;
|
|
||||||
for (int i = 0; i < len - 1; i++) xor_byte ^= buffer[i];
|
|
||||||
if (xor_byte == buffer[len - 1]) return true;
|
|
||||||
else {
|
|
||||||
ESP_LOGW("TCL", "No valid xor crc %02X (calculated %02X)", buffer[len], xor_byte);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_hex_str(uint8_t *buffer, int len)
|
|
||||||
{
|
|
||||||
char str[250] = {0};
|
|
||||||
char *pstr = str;
|
|
||||||
if (len * 2 > sizeof(str)) ESP_LOGE("TCL", "too long byte data");
|
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
pstr += sprintf(pstr, "%02X ", buffer[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI("TCL", "%s", str);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() override {
|
|
||||||
const int max_line_length = 100;
|
|
||||||
static uint8_t buffer[max_line_length];
|
|
||||||
|
|
||||||
while (available()) {
|
|
||||||
int len = read_data_line(read(), buffer, max_line_length);
|
|
||||||
if(len == sizeof(m_get_cmd_resp) && buffer[3] == 0x04) {
|
|
||||||
memcpy(m_get_cmd_resp.raw, buffer, len);
|
|
||||||
print_hex_str(buffer, len);
|
|
||||||
if (is_valid_xor(buffer, len)) {
|
|
||||||
float curr_temp = (((buffer[17] << 8) | buffer[18]) / 374 - 32) / 1.8;
|
|
||||||
this->is_changed = false;
|
|
||||||
|
|
||||||
if (m_get_cmd_resp.data.power == 0x00) this->set_mode(climate::CLIMATE_MODE_OFF);
|
|
||||||
else if (m_get_cmd_resp.data.mode == 0x01) this->set_mode(climate::CLIMATE_MODE_COOL);
|
|
||||||
else if (m_get_cmd_resp.data.mode == 0x03) this->set_mode(climate::CLIMATE_MODE_DRY);
|
|
||||||
else if (m_get_cmd_resp.data.mode == 0x02) this->set_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
|
||||||
else if (m_get_cmd_resp.data.mode == 0x04) this->set_mode(climate::CLIMATE_MODE_HEAT);
|
|
||||||
else if (m_get_cmd_resp.data.mode == 0x05) this->set_mode(climate::CLIMATE_MODE_AUTO);
|
|
||||||
|
|
||||||
|
|
||||||
if (m_get_cmd_resp.data.turbo) this->set_custom_fan_mode(esphome::to_string("Turbo"));
|
|
||||||
else if (m_get_cmd_resp.data.mute) this->set_custom_fan_mode(esphome::to_string("Mute"));
|
|
||||||
else if (m_get_cmd_resp.data.fan == 0x00) this->set_custom_fan_mode(esphome::to_string("Automatic"));
|
|
||||||
else if (m_get_cmd_resp.data.fan == 0x01) this->set_custom_fan_mode(esphome::to_string("1"));
|
|
||||||
else if (m_get_cmd_resp.data.fan == 0x04) this->set_custom_fan_mode(esphome::to_string("2"));
|
|
||||||
else if (m_get_cmd_resp.data.fan == 0x02) this->set_custom_fan_mode(esphome::to_string("3"));
|
|
||||||
else if (m_get_cmd_resp.data.fan == 0x05) this->set_custom_fan_mode(esphome::to_string("4"));
|
|
||||||
else if (m_get_cmd_resp.data.fan == 0x03) this->set_custom_fan_mode(esphome::to_string("5"));
|
|
||||||
|
|
||||||
|
|
||||||
if (m_get_cmd_resp.data.hswing && m_get_cmd_resp.data.vswing) this->set_swing_mode(climate::CLIMATE_SWING_BOTH);
|
|
||||||
else if (!m_get_cmd_resp.data.hswing && !m_get_cmd_resp.data.vswing) this->set_swing_mode(climate::CLIMATE_SWING_OFF);
|
|
||||||
else if (m_get_cmd_resp.data.vswing) this->set_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
|
||||||
else if (m_get_cmd_resp.data.hswing) this->set_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
|
|
||||||
|
|
||||||
if (m_get_cmd_resp.data.vswing_mv == 0x01) set_vswing_pos("Move full");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_mv == 0x02) set_vswing_pos("Move upper");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_mv == 0x03) set_vswing_pos("Move lower");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_fix == 0x01) set_vswing_pos("Fix top");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_fix == 0x02) set_vswing_pos("Fix upper");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_fix == 0x03) set_vswing_pos("Fix mid");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_fix == 0x04) set_vswing_pos("Fix lower");
|
|
||||||
else if (m_get_cmd_resp.data.vswing_fix == 0x05) set_vswing_pos("Fix bottom");
|
|
||||||
else set_vswing_pos("Last position");
|
|
||||||
|
|
||||||
if (m_get_cmd_resp.data.hswing_mv == 0x01) set_hswing_pos("Move full");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_mv == 0x02) set_hswing_pos("Move left");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_mv == 0x03) set_hswing_pos("Move mid");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_mv == 0x04) set_hswing_pos("Move right");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_fix == 0x01) set_hswing_pos("Fix left");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_fix == 0x02) set_hswing_pos("Fix mid left");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_fix == 0x03) set_hswing_pos("Fix mid");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_fix == 0x04) set_hswing_pos("Fix mid right");
|
|
||||||
else if (m_get_cmd_resp.data.hswing_fix == 0x05) set_hswing_pos("Fix right");
|
|
||||||
else set_hswing_pos("Last position");
|
|
||||||
|
|
||||||
ESP_LOGI("TCL", "fan %02X", m_get_cmd_resp.data.fan);
|
|
||||||
ESP_LOGI("TCL", "mode %02X", m_get_cmd_resp.data.mode);
|
|
||||||
this->set_target_temperature(float(m_get_cmd_resp.data.temp + 16));
|
|
||||||
this->set_current_temperature(curr_temp);
|
|
||||||
if (this->is_changed)
|
|
||||||
this->publish_state();
|
|
||||||
}
|
|
||||||
//publish_state(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//this->target_temperature = 20.0;
|
|
||||||
//this->publish_state();
|
|
||||||
// if(readline(read(), buffer, max_line_length) > 0) {
|
|
||||||
// publish_state(buffer);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
134
components/tclac/automation.h
Normal file
134
components/tclac/automation.h
Normal 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
343
components/tclac/climate.py
Normal 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
714
components/tclac/tclac.cpp
Normal 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
157
components/tclac/tclac.h
Normal 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
|
||||||
Reference in New Issue
Block a user