Files
tclac/components/tclac/tclac.cpp
2025-11-02 10:01:21 +01:00

878 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Create by Miguel Ángel López on 20/07/19
* and modify by xaxexa, edit for newer models by Kannix2005
* Refactoring & component making:
* Соловей с паяльником 15.03.2024
**/
#include "esphome.h"
#include "esphome/core/defines.h"
#include "tclac.h"
#include <sstream>
#include <iomanip>
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); // Off mode is always available
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); // Auto mode is always available
traits.add_supported_mode(climate::CLIMATE_MODE_COOL); // Cool mode is always available
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); // Heat mode is always available
traits.add_supported_mode(climate::CLIMATE_MODE_DRY); // Dry mode is always available
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); // Fan only mode is also available
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); // Auto fan mode is always available
traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); // Swing off mode is always available
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);// No preset just in case
return traits;
}
void tclacClimate::setup() {
//this->esphome::uart::UARTDevice::write_array(setup1, sizeof(setup1));
//this->esphome::uart::UARTDevice::flush();
//this->esphome::uart::UARTDevice::write_array(setup2, sizeof(setup2));
//this->esphome::uart::UARTDevice::flush();
//this->esphome::uart::UARTDevice::write_array(setup3, sizeof(setup3));
//this->esphome::uart::UARTDevice::flush();
target_temperature_set = 20;
target_temperature = 20;
restore_state_();
#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() {
if (esphome::uart::UARTDevice::available() > 0) {
dataShow(0, true);
memset(dataRX, 0, sizeof(dataRX));
dataRX[0] = esphome::uart::UARTDevice::read();
// If the received byte is not the header (0xBB), just exit the loop
if (dataRX[0] != 0xBB) {
ESP_LOGD("TCL", "Wrong byte");
dataShow(0,0);
return;
}
// If the header (0xBB) matches, start reading 4 more bytes in sequence
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());
// From the first 5 bytes we need the fifth - it contains the message length
esphome::uart::UARTDevice::read_array(dataRX+5, dataRX[4]+1);
//int c = 0;
//while(esphome::uart::UARTDevice::available() != 0){
// esphome::uart::UARTDevice::read_byte(&dataRX[5+c]);
// c++;
//}
uint8_t check = getChecksum(dataRX, sizeof(dataRX));
auto raw = getHex(dataRX, sizeof(dataRX));
//ESP_LOGD("TCL", "RX full : %s ", raw.c_str());
// Check the checksum
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);
// After reading everything from the buffer, we proceed to parse the data
tclacClimate::readData();
}
}
void tclacClimate::update() {
tclacClimate::dataShow(1,1);
this->esphome::uart::UARTDevice::write_array(poll, sizeof(poll));
//const char* raw = tclacClimate::getHex(poll, sizeof(poll)).c_str();
this->esphome::uart::UARTDevice::flush();
//delay(100);
//this->esphome::uart::UARTDevice::write_array(poll2, sizeof(poll2));
//this->esphome::uart::UARTDevice::flush();
//this->esphome::uart::UARTDevice::write_array(poll3, sizeof(poll3));
//this->esphome::uart::UARTDevice::flush();
tclacClimate::dataShow(1,0);
}
void tclacClimate::readData() {
// BBx01x00x03x37x04x00x00x00x00x00x00x00x00x00x80x36x00x23x08x00x00x00x00x00x00x00x00x00x20x66xFFx42x00x50x21x21x21x00x00x80x00x00x00x00xE9x00x00x00x54x40x03x23x00x00x7Ax00x00x00x00xBA
//current_temperature = float((( (dataRX[17] << 8) | dataRX[18] ) / 374 - 32)/1.8);
//target_temperature = (dataRX[FAN_SPEED_POS] & SET_TEMP_MASK) + 16;
//current_temperature = float((dataRX[29] | (dataRX[30] << 8))*0.001);
//auto current_temperature2 = float((( (dataRX[45] << 8) | dataRX[46] ) / 374 - 32)/1.8);
current_temperature = 256 - dataRX[45]; // 256-233 = 23°C (plausible!)
//this->current_temperature = current_temperature;
//target_temperature = 20;
//this->target_temperature = target_temperature_set;
//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);
//ESP_LOGD("TCL", "TEMP: %f ", current_temperature2);
if (dataRX[MODE_POS] & ( 1 << 4)) {
// If the air conditioner is on, parse the data for display
// 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_OFF;
// }
// //OVERRIDE
// switch (dataRX[MODE_POS])
// {
// case 0x08:
// //auto
// mode = climate::CLIMATE_MODE_AUTO;
// break;
// case 0x07:
// //Lüfter
// mode = climate::CLIMATE_MODE_FAN_ONLY;
// break;
// case 0x02:
// //Trocknen
// mode = climate::CLIMATE_MODE_DRY;
// break;
// case 0x01:
// //Heizen
// mode = climate::CLIMATE_MODE_HEAT;
// break;
// case 0x03:
// //Kühlen
// mode = climate::CLIMATE_MODE_COOL;
// break;
// }
// 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 {
// If the air conditioner is off, all modes are shown as off
//mode = climate::CLIMATE_MODE_OFF;
//fan_mode = climate::CLIMATE_FAN_OFF;
//swing_mode = climate::CLIMATE_SWING_OFF;
//preset = ClimatePreset::CLIMATE_PRESET_NONE;
//}
// Publish the data
this->publish_state();
allow_take_control = true;
}
// Climate control
void tclacClimate::control(const ClimateCall &call) {
// Request data from the AC mode switch
if (call.get_mode().has_value()){
switch_climate_mode = call.get_mode().value();
switch (switch_climate_mode) {
case 6:
mode = climate::CLIMATE_MODE_AUTO;
break;
case 2:
mode = climate::CLIMATE_MODE_COOL;
break;
case 5:
mode = climate::CLIMATE_MODE_DRY;
break;
case 4:
mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case 3:
mode = climate::CLIMATE_MODE_HEAT;
break;
default:
mode = climate::CLIMATE_MODE_OFF;
}
ESP_LOGD("TCL", "Get MODE %i from call", (int) mode);
} //else {
//switch_climate_mode = mode;
//ESP_LOGD("TCL", "Get MODE from AC");
//}
// Request data from the AC preset switch
if (call.get_preset().has_value()){
switch_preset = call.get_preset().value();
switch (switch_preset){
case 0:
preset = climate::CLIMATE_PRESET_NONE;
case 5:
preset = climate::CLIMATE_PRESET_ECO;
case 6:
preset = climate::CLIMATE_PRESET_SLEEP;
case 4:
preset = climate::CLIMATE_PRESET_COMFORT;
}
} //else {
//switch_preset = preset.value();
//}
// Request data from the fan mode switch
if (call.get_fan_mode().has_value()){
switch_fan_mode = call.get_fan_mode().value();
if (mode != climate::CLIMATE_MODE_OFF){
switch (switch_fan_mode) {
case 2:
fan_mode = climate::CLIMATE_FAN_AUTO;
case 3:
fan_mode = climate::CLIMATE_FAN_LOW;
case 6:
fan_mode = climate::CLIMATE_FAN_MIDDLE;
case 4:
fan_mode = climate::CLIMATE_FAN_MEDIUM;
case 5:
fan_mode = climate::CLIMATE_FAN_HIGH;
case 7:
fan_mode = climate::CLIMATE_FAN_FOCUS;
default:
fan_mode = climate::CLIMATE_FAN_AUTO;
}
} else {
fan_mode = climate::CLIMATE_FAN_OFF;
}
} //else {
// switch_fan_mode = fan_mode.value();
//}
// Request data from the swing mode switch
if (call.get_swing_mode().has_value()){
switch_swing_mode = call.get_swing_mode().value();
switch (switch_swing_mode) {
case 0:
swing_mode = climate::CLIMATE_SWING_OFF;
case 3:
swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
case 2:
swing_mode = climate::CLIMATE_SWING_VERTICAL;
case 1:
swing_mode = climate::CLIMATE_SWING_BOTH;
default:
swing_mode = climate::CLIMATE_SWING_OFF;
}
} //else {
// If the switch is empty, fill with the value from the last state poll. Like, nothing changed.
//switch_swing_mode = swing_mode;
//}
// Calculate temperature
if (call.get_target_temperature().has_value()) {
target_temperature_set = (int)call.get_target_temperature().value();
target_temperature = target_temperature_set;
} //else {
//target_temperature_set = 111 - (int)target_temperature;
//}
is_call_control = true;
takeControl();
this->publish_state();
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 = (int)target_temperature;
}
// Enable or disable the beeper depending on the setting switch
if (beeper_status_){
ESP_LOGD("TCL", "Beep mode ON");
dataTX[7] += 0b00100000;
} else {
ESP_LOGD("TCL", "Beep mode OFF");
dataTX[7] += 0b00000000;
}
// Enable or disable the AC display depending on the setting switch
// Only enable the display if the AC is in one of the operating modes
// WARNING! When the display is turned off, the AC automatically switches to automatic mode!
if ((display_status_) && (switch_climate_mode != climate::CLIMATE_MODE_OFF)){
ESP_LOGD("TCL", "Display turn ON");
dataTX[7] += 0b01000000;
} else {
ESP_LOGD("TCL", "Display turn OFF");
dataTX[7] += 0b00000000;
}
// Configure the AC operating mode
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;
}
// Configure the fan mode
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;
}
// Set the damper swing mode
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;
}
// Set the AC presets
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;
}
//Damper mode
// Vertical damper
// Vertical damper swing [byte 10, mask 00111000]:
// 000 - Swing off, damper in last position or fixed
// 111 - Swing on in selected mode
// Vertical damper swing mode (fixed mode doesn't matter if swing is on) [byte 32, mask 00011000]:
// 01 - swing up-down, DEFAULT
// 10 - swing in upper half
// 11 - swing in lower half
// Damper fixed mode (swing mode doesn't matter if swing is off) [byte 32, mask 00000111]:
// 000 - no fixation, DEFAULT
// 001 - fixed at top
// 010 - fixed between top and center
// 011 - fixed in center
// 100 - fixed between center and bottom
// 101 - fixed at bottom
// Horizontal dampers
// Horizontal damper swing [byte 11, mask 00001000]:
// 0 - Swing off, dampers in last position or fixed
// 1 - Swing on in selected mode
// Horizontal damper swing mode (fixed mode doesn't matter if swing is on) [byte 33, mask 00111000]:
// 001 - swing left-right, DEFAULT
// 010 - swing left
// 011 - swing center
// 100 - swing right
// Horizontal damper fixed mode (swing mode doesn't matter if swing is off) [byte 33, mask 00000111]:
// 000 - no fixation, DEFAULT
// 001 - fixed left
// 010 - fixed between left and center
// 011 - fixed in center
// 100 - fixed between center and right
// 101 - fixed right
// Set the vertical damper swing mode
switch(vertical_swing_direction_) {
case VerticalSwingDirection::OFF:
dataTX[32] += 0b00000000;
ESP_LOGD("TCL", "Vertical swing: off");
break;
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;
}
// Set the horizontal damper swing mode
switch(horizontal_swing_direction_) {
case HorizontalSwingDirection::OFF:
dataTX[33] += 0b00000000;
ESP_LOGD("TCL", "Horizontal swing: left-right");
break;
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;
}
// Set the vertical damper fixed position
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;
}
// Set the horizontal damper fixed position
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;
}
// Set temperature
dataTX[9] = 111 - target_temperature_set;
// Assemble the byte array to send to the AC
dataTX[0] = 0xBB; // Header start byte
dataTX[1] = 0x00; // Header start byte
dataTX[2] = 0x01; // Header start byte
dataTX[3] = 0x03; // 0x03 - control, 0x04 - poll
dataTX[4] = 0x20; // 0x20 - control, 0x19 - poll
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 --- 0,0,0,0,0,0,0,1 = sleep on; 0,0,0,1,0,0,0,0 = self clean on
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[29] = 0x20; //??
dataTX[30] = 0x00; //??
dataTX[31] = 0x00; //??
//dataTX[32] = 0x00; //0,0,0,vertical swing mode(2), vertical fixed position(3)
//dataTX[33] = 0x00; //0,0,vertical swing mode(2), vertical fixed position(3)
dataTX[34] = 0x00; //??
dataTX[35] = 0x00; //??
dataTX[36] = 0x00; //??
dataTX[37] = 0xFF; //Checksum
dataTX[37] = tclacClimate::getChecksum(dataTX, sizeof(dataTX));
tclacClimate::sendData(dataTX, sizeof(dataTX));
allow_take_control = false;
is_call_control = false;
}
// Send data to the AC
void tclacClimate::sendData(uint8_t * message, uint8_t 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 sent to TCL...");
tclacClimate::dataShow(1,0);
}
// Convert byte to readable format
String tclacClimate::getHex(uint8_t *message, uint8_t size) {
std::ostringstream oss;
// print each byte as two uppercase hex digits
for (uint8_t i = 0; i < size; ++i) {
oss << std::hex
<< std::uppercase
<< std::setw(2)
<< std::setfill('0')
<< static_cast<int>(message[i]);
if (i + 1 < size)
oss << ' '; // optional separator
}
// grab the std::string, ensure uppercase
std::string s = oss.str();
// (oss.str() is already uppercase thanks to std::uppercase, so
// this transform is optional)
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
// convert to esphome::String and return
return String(s.c_str());
}
// Calculate checksum
uint8_t tclacClimate::getChecksum(const uint8_t * message, size_t size) {
uint8_t position = size - 1;
uint8_t crc = 0;
for (int i = 0; i < position; i++)
crc ^= message[i];
return crc;
}
// Blink LEDs
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
}
}
}
}
// Actions with data from config
// Get beeper state
void tclacClimate::set_beeper_state(bool state) {
this->beeper_status_ = state;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Get AC display state
void tclacClimate::set_display_state(bool state) {
this->display_status_ = state;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Get forced settings application mode state
void tclacClimate::set_force_mode_state(bool state) {
this->force_mode_status_ = state;
}
// Get receive data LED pin
#ifdef CONF_RX_LED
void tclacClimate::set_rx_led_pin(GPIOPin *rx_led_pin) {
this->rx_led_pin_ = rx_led_pin;
}
#endif
// Get transmit data LED pin
#ifdef CONF_TX_LED
void tclacClimate::set_tx_led_pin(GPIOPin *tx_led_pin) {
this->tx_led_pin_ = tx_led_pin;
}
#endif
// Get module communication LED state
void tclacClimate::set_module_display_state(bool state) {
this->module_display_status_ = state;
}
// Get vertical damper fixed mode
void tclacClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
this->vertical_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Get horizontal damper fixed mode
void tclacClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) {
this->horizontal_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Get vertical damper swing mode
void tclacClimate::set_vertical_swing_direction(VerticalSwingDirection direction) {
this->vertical_swing_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Get supported AC operating modes
void tclacClimate::set_supported_modes(const std::set<climate::ClimateMode> &modes) {
this->supported_modes_ = modes;
}
// Get horizontal damper swing mode
void tclacClimate::set_horizontal_swing_direction(HorizontalSwingDirection direction) {
horizontal_swing_direction_ = direction;
if (force_mode_status_){
if (allow_take_control){
tclacClimate::takeControl();
}
}
}
// Get supported fan speeds
void tclacClimate::set_supported_fan_modes(const std::set<climate::ClimateFanMode> &modes){
this->supported_fan_modes_ = modes;
}
// Get supported swing modes
void tclacClimate::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
this->supported_swing_modes_ = modes;
}
// Get supported presets
void tclacClimate::set_supported_presets(const std::set<climate::ClimatePreset> &presets) {
this->supported_presets_ = presets;
}
}
}