ESPHome 2026.5.1
Loading...
Searching...
No Matches
tcl112.cpp
Go to the documentation of this file.
1#include "tcl112.h"
2#include "esphome/core/log.h"
3
4namespace esphome::tcl112 {
5
6static const char *const TAG = "tcl112.climate";
7
8const uint16_t TCL112_STATE_LENGTH = 14;
9const uint16_t TCL112_BITS = TCL112_STATE_LENGTH * 8;
10
11const uint8_t TCL112_HEAT = 1;
12const uint8_t TCL112_DRY = 2;
13const uint8_t TCL112_COOL = 3;
14const uint8_t TCL112_FAN = 7;
15const uint8_t TCL112_AUTO = 8;
16
17const uint8_t TCL112_FAN_AUTO = 0;
18const uint8_t TCL112_FAN_LOW = 2;
19const uint8_t TCL112_FAN_MED = 3;
20const uint8_t TCL112_FAN_HIGH = 5;
21
22const uint8_t TCL112_VSWING_MASK = 0x38;
23const uint8_t TCL112_POWER_MASK = 0x04;
24
25const uint8_t TCL112_HALF_DEGREE = 0b00100000;
26
27const uint16_t TCL112_HEADER_MARK = 3100;
28const uint16_t TCL112_HEADER_SPACE = 1650;
29const uint16_t TCL112_BIT_MARK = 500;
30const uint16_t TCL112_ONE_SPACE = 1100;
31const uint16_t TCL112_ZERO_SPACE = 350;
33
35 uint8_t remote_state[TCL112_STATE_LENGTH] = {0};
36
37 // A known good state. (On, Cool, 24C)
38 remote_state[0] = 0x23;
39 remote_state[1] = 0xCB;
40 remote_state[2] = 0x26;
41 remote_state[3] = 0x01;
42 remote_state[5] = 0x24;
43 remote_state[6] = 0x03;
44 remote_state[7] = 0x07;
45 remote_state[8] = 0x40;
46
47 // Set mode
48 switch (this->mode) {
50 remote_state[6] &= 0xF0;
51 remote_state[6] |= TCL112_AUTO;
52 break;
54 remote_state[6] &= 0xF0;
55 remote_state[6] |= TCL112_COOL;
56 break;
58 remote_state[6] &= 0xF0;
59 remote_state[6] |= TCL112_HEAT;
60 break;
62 remote_state[6] &= 0xF0;
63 remote_state[6] |= TCL112_DRY;
64 break;
66 remote_state[6] &= 0xF0;
67 remote_state[6] |= TCL112_FAN;
68 break;
70 default:
71 remote_state[5] &= ~TCL112_POWER_MASK;
72 break;
73 }
74
75 // Set temperature
76 // Make sure we have desired temp in the correct range.
77 float safecelsius = std::max(this->target_temperature, TCL112_TEMP_MIN);
78 safecelsius = std::min(safecelsius, TCL112_TEMP_MAX);
79 // Convert to integer nr. of half degrees.
80 auto half_degrees = static_cast<uint8_t>(safecelsius * 2);
81 if (half_degrees & 1) { // Do we have a half degree celsius?
82 remote_state[12] |= TCL112_HALF_DEGREE; // Add 0.5 degrees
83 } else {
84 remote_state[12] &= ~TCL112_HALF_DEGREE; // Clear the half degree.
85 }
86 remote_state[7] &= 0xF0; // Clear temp bits.
87 remote_state[7] |= ((uint8_t) TCL112_TEMP_MAX - half_degrees / 2);
88
89 // Set fan
90 uint8_t selected_fan;
91 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
93 selected_fan = TCL112_FAN_HIGH;
94 break;
96 selected_fan = TCL112_FAN_MED;
97 break;
99 selected_fan = TCL112_FAN_LOW;
100 break;
102 default:
103 selected_fan = TCL112_FAN_AUTO;
104 }
105 remote_state[8] |= selected_fan;
106
107 // Set swing
109 remote_state[8] |= TCL112_VSWING_MASK;
110 }
111
112 // Calculate & set the checksum for the current internal state of the remote.
113 // Stored the checksum value in the last byte.
114 for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++)
115 remote_state[TCL112_STATE_LENGTH - 1] += remote_state[checksum_byte];
116
117 ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", remote_state[0],
118 remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6],
119 remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12],
120 remote_state[13]);
121
122 auto transmit = this->transmitter_->transmit();
123 auto *data = transmit.get_data();
124
125 data->set_carrier_frequency(38000);
126
127 // Header
128 data->mark(TCL112_HEADER_MARK);
129 data->space(TCL112_HEADER_SPACE);
130 // Data
131 for (uint8_t i : remote_state) {
132 for (uint8_t j = 0; j < 8; j++) {
133 data->mark(TCL112_BIT_MARK);
134 bool bit = i & (1 << j);
135 data->space(bit ? TCL112_ONE_SPACE : TCL112_ZERO_SPACE);
136 }
137 }
138 // Footer
139 data->mark(TCL112_BIT_MARK);
140 data->space(TCL112_GAP);
141
142 transmit.perform();
143}
144
146 // Validate header
147 if (!data.expect_item(TCL112_HEADER_MARK, TCL112_HEADER_SPACE)) {
148 ESP_LOGVV(TAG, "Header fail");
149 return false;
150 }
151
152 uint8_t remote_state[TCL112_STATE_LENGTH] = {0};
153 // Read all bytes.
154 for (int i = 0; i < TCL112_STATE_LENGTH; i++) {
155 // Read bit
156 for (int j = 0; j < 8; j++) {
157 if (data.expect_item(TCL112_BIT_MARK, TCL112_ONE_SPACE)) {
158 remote_state[i] |= 1 << j;
159 } else if (!data.expect_item(TCL112_BIT_MARK, TCL112_ZERO_SPACE)) {
160 ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j);
161 return false;
162 }
163 }
164 }
165 // Validate footer
166 if (!data.expect_mark(TCL112_BIT_MARK)) {
167 ESP_LOGVV(TAG, "Footer fail");
168 return false;
169 }
170
171 uint8_t checksum = 0;
172 // Calculate & set the checksum for the current internal state of the remote.
173 // Stored the checksum value in the last byte.
174 for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++)
175 checksum += remote_state[checksum_byte];
176 if (checksum != remote_state[TCL112_STATE_LENGTH - 1]) {
177 ESP_LOGVV(TAG, "Checksum fail");
178 return false;
179 }
180
181 ESP_LOGV(TAG, "Received: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
182 remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5],
183 remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11],
184 remote_state[12], remote_state[13]);
185
186 // two first bytes are constant
187 if (remote_state[0] != 0x23 || remote_state[1] != 0xCB)
188 return false;
189
190 if ((remote_state[5] & TCL112_POWER_MASK) == 0) {
192 } else {
193 auto mode = remote_state[6] & 0x0F;
194 switch (mode) {
195 case TCL112_HEAT:
197 break;
198 case TCL112_COOL:
200 break;
201 case TCL112_DRY:
203 break;
204 case TCL112_FAN:
206 break;
207 case TCL112_AUTO:
209 break;
210 }
211 }
212 auto temp = TCL112_TEMP_MAX - remote_state[7];
213 if (remote_state[12] & TCL112_HALF_DEGREE)
214 temp += .5f;
215 this->target_temperature = temp;
216 auto fan = remote_state[8] & 0x7;
217 switch (fan) {
218 case TCL112_FAN_HIGH:
220 break;
221 case TCL112_FAN_MED:
223 break;
224 case TCL112_FAN_LOW:
226 break;
227 case TCL112_FAN_AUTO:
228 default:
230 break;
231 }
232 if ((remote_state[8] & TCL112_VSWING_MASK) == TCL112_VSWING_MASK) {
234 } else {
236 }
237
238 this->publish_state();
239 return true;
240}
241
242} // namespace esphome::tcl112
uint8_t checksum
Definition bl0906.h:3
ClimateMode mode
The active mode of the climate device.
Definition climate.h:293
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:287
float target_temperature
The target temperature of the climate device.
Definition climate.h:274
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:299
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:437
void transmit_state() override
Transmit via IR the state of this climate controller.
Definition tcl112.cpp:34
bool on_receive(remote_base::RemoteReceiveData data) override
Handle received IR Buffer.
Definition tcl112.cpp:145
@ CLIMATE_SWING_OFF
The swing mode is set to Off.
@ CLIMATE_SWING_VERTICAL
The fan mode is set to Vertical.
@ CLIMATE_MODE_DRY
The climate device is set to dry/humidity mode.
@ CLIMATE_MODE_FAN_ONLY
The climate device only has the fan enabled, no heating or cooling is taking place.
@ CLIMATE_MODE_HEAT
The climate device is set to heat to reach the target temperature.
@ CLIMATE_MODE_COOL
The climate device is set to cool to reach the target temperature.
@ CLIMATE_MODE_HEAT_COOL
The climate device is set to heat/cool to reach the target temperature.
@ CLIMATE_MODE_OFF
The climate device is off.
@ CLIMATE_FAN_MEDIUM
The fan mode is set to Medium.
@ CLIMATE_FAN_ON
The fan mode is set to On.
@ CLIMATE_FAN_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const uint8_t TCL112_DRY
Definition tcl112.cpp:12
const uint8_t TCL112_FAN
Definition tcl112.cpp:14
const uint8_t TCL112_AUTO
Definition tcl112.cpp:15
const float TCL112_TEMP_MAX
Definition tcl112.h:8
const uint16_t TCL112_BIT_MARK
Definition tcl112.cpp:29
const uint8_t TCL112_HALF_DEGREE
Definition tcl112.cpp:25
const uint8_t TCL112_VSWING_MASK
Definition tcl112.cpp:22
const uint16_t TCL112_HEADER_MARK
Definition tcl112.cpp:27
const uint16_t TCL112_ONE_SPACE
Definition tcl112.cpp:30
const uint16_t TCL112_HEADER_SPACE
Definition tcl112.cpp:28
const uint32_t TCL112_GAP
Definition tcl112.cpp:32
const uint8_t TCL112_FAN_MED
Definition tcl112.cpp:19
const uint8_t TCL112_FAN_HIGH
Definition tcl112.cpp:20
const uint16_t TCL112_ZERO_SPACE
Definition tcl112.cpp:31
const float TCL112_TEMP_MIN
Definition tcl112.h:9
const uint8_t TCL112_FAN_LOW
Definition tcl112.cpp:18
const uint8_t TCL112_FAN_AUTO
Definition tcl112.cpp:17
const uint8_t TCL112_COOL
Definition tcl112.cpp:13
const uint16_t TCL112_BITS
Definition tcl112.cpp:9
const uint8_t TCL112_POWER_MASK
Definition tcl112.cpp:23
const uint16_t TCL112_STATE_LENGTH
Definition tcl112.cpp:8
const uint8_t TCL112_HEAT
Definition tcl112.cpp:11
static void uint32_t