ESPHome 2026.3.3
Loading...
Searching...
No Matches
noblex.cpp
Go to the documentation of this file.
1#include "noblex.h"
3#include "esphome/core/log.h"
4
5namespace esphome {
6namespace noblex {
7
8static const char *const TAG = "noblex.climate";
9
10const uint16_t NOBLEX_HEADER_MARK = 9000;
11const uint16_t NOBLEX_HEADER_SPACE = 4500;
12const uint16_t NOBLEX_BIT_MARK = 660;
13const uint16_t NOBLEX_ONE_SPACE = 1640;
14const uint16_t NOBLEX_ZERO_SPACE = 520;
15const uint32_t NOBLEX_GAP = 20000;
16const uint8_t NOBLEX_POWER = 0x10;
17
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
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_or(climate::CLIMATE_FAN_ON)) {
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 (int byte_idx = 0; byte_idx < 8; byte_idx++) {
122 for (int8_t bit_idx = 7; bit_idx >= 0; bit_idx--) {
123 if ((byte_idx == 4) && (bit_idx == 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 = remote_state[byte_idx] & (1 << bit_idx);
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
149 // First part: header + first 36 bits, followed by 20ms gap
151 ESP_LOGV(TAG, "Header");
152 this->receiving_ = false;
153 memset(this->remote_state_, 0, sizeof(this->remote_state_));
154 for (int i = 0; i < 5; i++) {
155 for (int j = 7; j >= 0; j--) {
156 if ((i == 4) && (j == 4)) {
157 this->remote_state_[i] |= 1 << j;
158 ESP_LOGVV(TAG, "GAP");
159 this->receiving_ = true;
160 return false;
161 } else if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) {
162 this->remote_state_[i] |= 1 << j;
163 } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) {
164 ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j);
165 return false;
166 }
167 }
168 ESP_LOGV(TAG, "Byte %d %02X", i, this->remote_state_[i]);
169 }
170 return false;
171 }
172
173 // Second part: remaining 28 bits + 4-bit CRC + footer
174 if (!this->receiving_) {
175 return false;
176 }
177 this->receiving_ = false;
178 for (int i = 4; i < 8; i++) {
179 for (int j = 7; j >= 0; j--) {
180 if ((i == 4) && (j >= 4)) {
181 // already decoded in first part
182 } else if (data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ONE_SPACE)) {
183 this->remote_state_[i] |= 1 << j;
184 } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) {
185 ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j);
186 return false;
187 }
188 }
189 ESP_LOGV(TAG, "Byte %d %02X", i, this->remote_state_[i]);
190 }
191
192 // Read CRC
193 uint8_t crc = 0;
194 for (int i = 3; i >= 0; i--) {
196 crc |= 1 << i;
197 } else if (!data.expect_item(NOBLEX_BIT_MARK, NOBLEX_ZERO_SPACE)) {
198 ESP_LOGVV(TAG, "Bit %d CRC fail", i);
199 return false;
200 }
201 }
202 ESP_LOGV(TAG, "CRC %02X", crc);
203
204 // Validate footer
205 if (!data.expect_mark(NOBLEX_BIT_MARK)) {
206 ESP_LOGV(TAG, "Footer fail");
207 return false;
208 }
209
210 // Validate CRC
211 uint8_t crc_calculated = 0;
212 for (uint8_t i : this->remote_state_)
213 crc_calculated += reverse_bits(i);
214 crc_calculated = reverse_bits(uint8_t(crc_calculated & 0x0F)) >> 4;
215 ESP_LOGVV(TAG, "CRC calc %02X", crc_calculated);
216
217 if (crc != crc_calculated) {
218 ESP_LOGV(TAG, "CRC fail");
219 return false;
220 }
221
222 ESP_LOGD(TAG, "Received noblex code: %02X%02X %02X%02X %02X%02X %02X%02X", this->remote_state_[0],
223 this->remote_state_[1], this->remote_state_[2], this->remote_state_[3], this->remote_state_[4],
224 this->remote_state_[5], this->remote_state_[6], this->remote_state_[7]);
225
226 auto powered_on = false;
227 if ((this->remote_state_[0] & NOBLEX_POWER) == NOBLEX_POWER) {
228 powered_on = true;
229 this->powered_on_assumed = powered_on;
230 } else {
231 powered_on = false;
232 this->powered_on_assumed = powered_on;
234 }
235 // powr on/off button
236 ESP_LOGV(TAG, "Power: %01X", powered_on);
237
238 // Set received mode
239 if (powered_on_assumed) {
240 auto mode = (this->remote_state_[0] & 0xE0) >> 5;
241 ESP_LOGV(TAG, "Mode: %02X", mode);
242 switch (mode) {
243 case IRNoblexMode::IR_NOBLEX_MODE_AUTO:
245 break;
246 case IRNoblexMode::IR_NOBLEX_MODE_COOL:
248 break;
249 case IRNoblexMode::IR_NOBLEX_MODE_DRY:
251 break;
252 case IRNoblexMode::IR_NOBLEX_MODE_FAN:
254 break;
255 case IRNoblexMode::IR_NOBLEX_MODE_HEAT:
257 break;
258 }
259 }
260
261 // Set received temp
262 uint8_t temp = this->remote_state_[1];
263 ESP_LOGVV(TAG, "Temperature Raw: %02X", temp);
264
265 temp = 0x0F & reverse_bits(temp);
266 temp += NOBLEX_TEMP_MIN;
267 ESP_LOGV(TAG, "Temperature Climate: %u", temp);
268 this->target_temperature = temp;
269
270 // Set received fan speed
271 auto fan = (this->remote_state_[0] & 0x0C) >> 2;
272 ESP_LOGV(TAG, "Fan: %02X", fan);
273 switch (fan) {
274 case IRNoblexFan::IR_NOBLEX_FAN_HIGH:
276 break;
277 case IRNoblexFan::IR_NOBLEX_FAN_MEDIUM:
279 break;
280 case IRNoblexFan::IR_NOBLEX_FAN_LOW:
282 break;
283 case IRNoblexFan::IR_NOBLEX_FAN_AUTO:
284 default:
286 break;
287 }
288
289 // Set received swing status
290 if (this->remote_state_[0] & 0x02) {
291 ESP_LOGV(TAG, "Swing vertical");
293 } else {
294 ESP_LOGV(TAG, "Swing OFF");
296 }
297
298 this->publish_state();
299 return true;
300} // end on_receive()
301
302} // namespace noblex
303} // namespace esphome
ClimateMode mode
The active mode of the climate device.
Definition climate.h:266
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:260
float target_temperature
The target temperature of the climate device.
Definition climate.h:247
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:272
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:445
void transmit_state() override
Transmit via IR the state of this climate controller.
Definition noblex.cpp:34
bool on_receive(remote_base::RemoteReceiveData data) override
Handle received IR Buffer.
Definition noblex.cpp:147
bool expect_item(uint32_t mark, uint32_t space)
bool peek_item(uint32_t mark, uint32_t space, uint32_t offset=0) const
Definition remote_base.h:71
void set_carrier_frequency(uint32_t carrier_frequency)
Definition remote_base.h:30
@ 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 uint16_t NOBLEX_ONE_SPACE
Definition noblex.cpp:13
const uint16_t NOBLEX_HEADER_SPACE
Definition noblex.cpp:11
const uint8_t NOBLEX_POWER
Definition noblex.cpp:16
const uint16_t NOBLEX_ZERO_SPACE
Definition noblex.cpp:14
const uint32_t NOBLEX_GAP
Definition noblex.cpp:15
const uint16_t NOBLEX_HEADER_MARK
Definition noblex.cpp:10
const uint16_t NOBLEX_BIT_MARK
Definition noblex.cpp:12
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:18
const uint8_t NOBLEX_TEMP_MIN
Definition noblex.h:9
const uint8_t NOBLEX_TEMP_MAX
Definition noblex.h:10
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:26
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint8_t reverse_bits(uint8_t x)
Reverse the order of 8 bits.
Definition helpers.h:767
static void uint32_t