ESPHome 2025.5.0
Loading...
Searching...
No Matches
noblex.cpp
Go to the documentation of this file.
1#include "noblex.h"
2#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()) {
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
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--) {
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
ClimateMode mode
The active mode of the climate device.
Definition climate.h:173
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:199
float target_temperature
The target temperature of the climate device.
Definition climate.h:186
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:202
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:395
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
value_type const & value() const
Definition optional.h:89
bool expect_item(uint32_t mark, uint32_t space)
void set_carrier_frequency(uint32_t carrier_frequency)
Definition remote_base.h:34
@ 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_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:231