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