ESPHome  2024.12.2
noblex.cpp
Go to the documentation of this file.
1 #include "noblex.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/helpers.h"
4 
5 namespace esphome {
6 namespace noblex {
7 
8 static const char *const TAG = "noblex.climate";
9 
10 const uint16_t NOBLEX_HEADER_MARK = 9000;
11 const uint16_t NOBLEX_HEADER_SPACE = 4500;
12 const uint16_t NOBLEX_BIT_MARK = 660;
13 const uint16_t NOBLEX_ONE_SPACE = 1640;
14 const uint16_t NOBLEX_ZERO_SPACE = 520;
15 const uint32_t NOBLEX_GAP = 20000;
16 const uint8_t NOBLEX_POWER = 0x10;
17 
18 using IRNoblexMode = enum IRNoblexMode {
19  IR_NOBLEX_MODE_AUTO = 0b000,
20  IR_NOBLEX_MODE_COOL = 0b100,
21  IR_NOBLEX_MODE_DRY = 0b010,
22  IR_NOBLEX_MODE_FAN = 0b110,
23  IR_NOBLEX_MODE_HEAT = 0b001,
24 };
25 
26 using IRNoblexFan = enum IRNoblexFan {
27  IR_NOBLEX_FAN_AUTO = 0b00,
28  IR_NOBLEX_FAN_LOW = 0b10,
29  IR_NOBLEX_FAN_MEDIUM = 0b01,
30  IR_NOBLEX_FAN_HIGH = 0b11,
31 };
32 
33 // Transmit via IR the state of this climate controller.
35  uint8_t remote_state[8] = {0x80, 0x10, 0x00, 0x0A, 0x50, 0x00, 0x20, 0x00}; // OFF, COOL, 24C, FAN_AUTO
36 
37  auto powered_on = this->mode != climate::CLIMATE_MODE_OFF;
38  if (powered_on) {
39  remote_state[0] |= 0x10; // power bit
40  remote_state[2] = 0x02;
41  }
42  if (powered_on != this->powered_on_assumed)
43  this->powered_on_assumed = powered_on;
44 
45  auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, NOBLEX_TEMP_MIN, NOBLEX_TEMP_MAX));
46  remote_state[1] = reverse_bits(uint8_t((temp - NOBLEX_TEMP_MIN) & 0x0F));
47 
48  switch (this->mode) {
50  remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_AUTO << 5);
51  remote_state[1] = 0x90; // NOBLEX_TEMP_MAP 25C
52  break;
54  remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_COOL << 5);
55  break;
57  remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_DRY << 5);
58  break;
60  remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_FAN << 5);
61  break;
63  remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_HEAT << 5);
64  break;
66  default:
67  powered_on = false;
68  this->powered_on_assumed = powered_on;
69  remote_state[0] &= 0xEF;
70  remote_state[2] = 0x00;
71  break;
72  }
73 
74  switch (this->fan_mode.value()) {
76  remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_LOW << 2);
77  break;
79  remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_MEDIUM << 2);
80  break;
82  remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_HIGH << 2);
83  break;
85  default:
86  remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_AUTO << 2);
87  break;
88  }
89 
90  switch (this->swing_mode) {
92  remote_state[0] |= 0x02;
93  remote_state[4] = 0x58;
94  break;
96  default:
97  remote_state[0] &= 0xFD;
98  remote_state[4] = 0x50;
99  break;
100  }
101 
102  uint8_t crc = 0;
103  for (uint8_t i : remote_state) {
104  crc += reverse_bits(i);
105  }
106  crc = reverse_bits(uint8_t(crc & 0x0F)) >> 4;
107 
108  ESP_LOGD(TAG, "Sending noblex code: %02X%02X %02X%02X %02X%02X %02X%02X", remote_state[0], remote_state[1],
109  remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], remote_state[7]);
110 
111  ESP_LOGV(TAG, "CRC: %01X", crc);
112 
113  auto transmit = this->transmitter_->transmit();
114  auto *data = transmit.get_data();
115  data->set_carrier_frequency(38000);
116 
117  // Header
118  data->mark(NOBLEX_HEADER_MARK);
119  data->space(NOBLEX_HEADER_SPACE);
120  // Data (sent remote_state from the MSB to the LSB)
121  for (uint8_t i : remote_state) {
122  for (int8_t j = 7; j >= 0; j--) {
123  if ((i == 4) & (j == 4)) {
124  // Header intermediate
125  data->mark(NOBLEX_BIT_MARK);
126  data->space(NOBLEX_GAP); // gap en bit 36
127  } else {
128  data->mark(NOBLEX_BIT_MARK);
129  bool bit = i & (1 << j);
130  data->space(bit ? NOBLEX_ONE_SPACE : NOBLEX_ZERO_SPACE);
131  }
132  }
133  }
134  // send crc
135  for (int8_t i = 3; i >= 0; i--) {
136  data->mark(NOBLEX_BIT_MARK);
137  bool bit = crc & (1 << i);
138  data->space(bit ? NOBLEX_ONE_SPACE : NOBLEX_ZERO_SPACE);
139  }
140  // Footer
141  data->mark(NOBLEX_BIT_MARK);
142 
143  transmit.perform();
144 } // end transmit_state()
145 
146 // Handle received IR Buffer
148  uint8_t remote_state[8] = {0};
149  uint8_t crc = 0, crc_calculated = 0;
150 
151  if (!receiving_) {
152  // Validate header
153  if (data.expect_item(NOBLEX_HEADER_MARK, NOBLEX_HEADER_SPACE)) {
154  ESP_LOGV(TAG, "Header");
155  receiving_ = true;
156  // Read first 36 bits
157  for (int i = 0; i < 5; i++) {
158  // Read bit
159  for (int j = 7; j >= 0; j--) {
160  if ((i == 4) & (j == 4)) {
161  remote_state[i] |= 1 << j;
162  // Header intermediate
163  ESP_LOGVV(TAG, "GAP");
164  return false;
165  } else if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) {
166  remote_state[i] |= 1 << j;
167  } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) {
168  ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j);
169  return false;
170  }
171  }
172  ESP_LOGV(TAG, "Byte %d %02X", i, remote_state[i]);
173  }
174 
175  } else {
176  ESP_LOGV(TAG, "Header fail");
177  receiving_ = false;
178  return false;
179  }
180 
181  } else {
182  // Read the remaining 28 bits
183  for (int i = 4; i < 8; i++) {
184  // Read bit
185  for (int j = 7; j >= 0; j--) {
186  if ((i == 4) & (j >= 4)) {
187  // nothing
188  } else if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) {
189  remote_state[i] |= 1 << j;
190  } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) {
191  ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j);
192  return false;
193  }
194  }
195  ESP_LOGV(TAG, "Byte %d %02X", i, remote_state[i]);
196  }
197 
198  // Read crc
199  for (int i = 3; i >= 0; i--) {
200  if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) {
201  crc |= 1 << i;
202  } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) {
203  ESP_LOGVV(TAG, "Bit %d CRC fail", i);
204  return false;
205  }
206  }
207  ESP_LOGV(TAG, "CRC %02X", crc);
208 
209  // Validate footer
210  if (!data.expect_mark(NOBLEX_BIT_MARK)) {
211  ESP_LOGV(TAG, "Footer fail");
212  return false;
213  }
214  receiving_ = false;
215  }
216 
217  for (uint8_t i : remote_state)
218  crc_calculated += reverse_bits(i);
219  crc_calculated = reverse_bits(uint8_t(crc_calculated & 0x0F)) >> 4;
220  ESP_LOGVV(TAG, "CRC calc %02X", crc_calculated);
221 
222  if (crc != crc_calculated) {
223  ESP_LOGV(TAG, "CRC fail");
224  return false;
225  }
226 
227  ESP_LOGD(TAG, "Received noblex code: %02X%02X %02X%02X %02X%02X %02X%02X", remote_state[0], remote_state[1],
228  remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], remote_state[7]);
229 
230  auto powered_on = false;
231  if ((remote_state[0] & NOBLEX_POWER) == NOBLEX_POWER) {
232  powered_on = true;
233  this->powered_on_assumed = powered_on;
234  } else {
235  powered_on = false;
236  this->powered_on_assumed = powered_on;
238  }
239  // powr on/off button
240  ESP_LOGV(TAG, "Power: %01X", powered_on);
241 
242  // Set received mode
243  if (powered_on_assumed) {
244  auto mode = (remote_state[0] & 0xE0) >> 5;
245  ESP_LOGV(TAG, "Mode: %02X", mode);
246  switch (mode) {
247  case IRNoblexMode::IR_NOBLEX_MODE_AUTO:
249  break;
250  case IRNoblexMode::IR_NOBLEX_MODE_COOL:
252  break;
253  case IRNoblexMode::IR_NOBLEX_MODE_DRY:
255  break;
256  case IRNoblexMode::IR_NOBLEX_MODE_FAN:
258  break;
259  case IRNoblexMode::IR_NOBLEX_MODE_HEAT:
261  break;
262  }
263  }
264 
265  // Set received temp
266  uint8_t temp = remote_state[1];
267  ESP_LOGVV(TAG, "Temperature Raw: %02X", temp);
268 
269  temp = 0x0F & reverse_bits(temp);
270  temp += NOBLEX_TEMP_MIN;
271  ESP_LOGV(TAG, "Temperature Climate: %u", temp);
272  this->target_temperature = temp;
273 
274  // Set received fan speed
275  auto fan = (remote_state[0] & 0x0C) >> 2;
276  ESP_LOGV(TAG, "Fan: %02X", fan);
277  switch (fan) {
278  case IRNoblexFan::IR_NOBLEX_FAN_HIGH:
280  break;
281  case IRNoblexFan::IR_NOBLEX_FAN_MEDIUM:
283  break;
284  case IRNoblexFan::IR_NOBLEX_FAN_LOW:
286  break;
287  case IRNoblexFan::IR_NOBLEX_FAN_AUTO:
288  default:
290  break;
291  }
292 
293  // Set received swing status
294  if (remote_state[0] & 0x02) {
295  ESP_LOGV(TAG, "Swing vertical");
297  } else {
298  ESP_LOGV(TAG, "Swing OFF");
300  }
301 
302  for (uint8_t &i : remote_state)
303  i = 0;
304  this->publish_state();
305  return true;
306 } // end on_receive()
307 
308 } // namespace noblex
309 } // 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
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
The climate device is set to heat to reach the target temperature.
Definition: climate_mode.h:18
const uint8_t NOBLEX_TEMP_MAX
Definition: noblex.h:10
const uint32_t NOBLEX_GAP
Definition: noblex.cpp:15
ClimateMode mode
The active mode of the climate device.
Definition: climate.h:173
The climate device is set to dry/humidity mode.
Definition: climate_mode.h:22
const uint8_t NOBLEX_TEMP_MIN
Definition: noblex.h:9
enum IRNoblexMode { IR_NOBLEX_MODE_AUTO=0b000, IR_NOBLEX_MODE_COOL=0b100, IR_NOBLEX_MODE_DRY=0b010, IR_NOBLEX_MODE_FAN=0b110, IR_NOBLEX_MODE_HEAT=0b001, } IRNoblexMode
Definition: noblex.cpp:24
uint8_t reverse_bits(uint8_t x)
Reverse the order of 8 bits.
Definition: helpers.h:223
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
void transmit_state() override
Transmit via IR the state of this climate controller.
Definition: noblex.cpp:34
const uint16_t NOBLEX_HEADER_MARK
Definition: noblex.cpp:10
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
The fan mode is set to Vertical.
Definition: climate_mode.h:76
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 uint8_t NOBLEX_POWER
Definition: noblex.cpp:16
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:199
enum IRNoblexFan { IR_NOBLEX_FAN_AUTO=0b00, IR_NOBLEX_FAN_LOW=0b10, IR_NOBLEX_FAN_MEDIUM=0b01, IR_NOBLEX_FAN_HIGH=0b11, } IRNoblexFan
Definition: noblex.cpp:31
const uint16_t NOBLEX_BIT_MARK
Definition: noblex.cpp:12
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool on_receive(remote_base::RemoteReceiveData data) override
Handle received IR Buffer.
Definition: noblex.cpp:147
const uint16_t NOBLEX_HEADER_SPACE
Definition: noblex.cpp:11
The fan mode is set to Medium.
Definition: climate_mode.h:56
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 uint16_t NOBLEX_ZERO_SPACE
Definition: noblex.cpp:14
const uint16_t NOBLEX_ONE_SPACE
Definition: noblex.cpp:13