ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
fujitsu_general.cpp
Go to the documentation of this file.
1#include "fujitsu_general.h"
2
4
5// bytes' bits are reversed for fujitsu, so nibbles are ordered 1, 0, 3, 2, 5, 4, etc...
6
7#define SET_NIBBLE(message, nibble, value) \
8 ((message)[(nibble) / 2] |= ((value) &0b00001111) << (((nibble) % 2) ? 0 : 4))
9#define GET_NIBBLE(message, nibble) (((message)[(nibble) / 2] >> (((nibble) % 2) ? 0 : 4)) & 0b00001111)
10
11static const char *const TAG = "fujitsu_general.climate";
12
13// Common header
15const uint8_t FUJITSU_GENERAL_COMMON_BYTE0 = 0x14;
16const uint8_t FUJITSU_GENERAL_COMMON_BYTE1 = 0x63;
17const uint8_t FUJITSU_GENERAL_COMMON_BYTE2 = 0x00;
18const uint8_t FUJITSU_GENERAL_COMMON_BYTE3 = 0x10;
19const uint8_t FUJITSU_GENERAL_COMMON_BYTE4 = 0x10;
21
22// State message - temp & fan etc.
25
26// Util messages - off & eco etc.
31
32// State header
35
36// State footer
38
39// Temperature
41
42// Power on
44const uint8_t FUJITSU_GENERAL_POWER_OFF = 0x00;
45const uint8_t FUJITSU_GENERAL_POWER_ON = 0x01;
46
47// Mode
48const uint8_t FUJITSU_GENERAL_MODE_NIBBLE = 19;
49const uint8_t FUJITSU_GENERAL_MODE_AUTO = 0x00;
50const uint8_t FUJITSU_GENERAL_MODE_COOL = 0x01;
51const uint8_t FUJITSU_GENERAL_MODE_DRY = 0x02;
52const uint8_t FUJITSU_GENERAL_MODE_FAN = 0x03;
53const uint8_t FUJITSU_GENERAL_MODE_HEAT = 0x04;
54// const uint8_t FUJITSU_GENERAL_MODE_10C = 0x0B;
55
56// Swing
58const uint8_t FUJITSU_GENERAL_SWING_NONE = 0x00;
59const uint8_t FUJITSU_GENERAL_SWING_VERTICAL = 0x01;
61const uint8_t FUJITSU_GENERAL_SWING_BOTH = 0x03;
62
63// Fan
64const uint8_t FUJITSU_GENERAL_FAN_NIBBLE = 21;
65const uint8_t FUJITSU_GENERAL_FAN_AUTO = 0x00;
66const uint8_t FUJITSU_GENERAL_FAN_HIGH = 0x01;
67const uint8_t FUJITSU_GENERAL_FAN_MEDIUM = 0x02;
68const uint8_t FUJITSU_GENERAL_FAN_LOW = 0x03;
69const uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04;
70
71// TODO Outdoor Unit Low Noise
72// const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0;
73// const uint8_t FUJITSU_GENERAL_STATE_BYTE14 = 0x20;
74
75const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300;
76const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600;
77
78const uint16_t FUJITSU_GENERAL_BIT_MARK = 420;
79const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200;
80const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420;
81
82const uint16_t FUJITSU_GENERAL_TRL_MARK = 420;
83const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
84
86
88 if (this->mode == climate::CLIMATE_MODE_OFF) {
89 this->transmit_off_();
90 return;
91 }
92
93 ESP_LOGV(TAG, "Transmit state");
94
95 uint8_t remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
96
97 // Common message header
98 remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
99 remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
100 remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
101 remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
102 remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
103 remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_STATE;
104 remote_state[6] = FUJITSU_GENERAL_STATE_HEADER_BYTE0;
105 remote_state[7] = FUJITSU_GENERAL_STATE_HEADER_BYTE1;
106
107 // unknown, does not appear to change with any remote settings
108 remote_state[14] = FUJITSU_GENERAL_STATE_FOOTER_BYTE0;
109
110 // Set temperature
111 uint8_t temperature_clamped =
112 (uint8_t) roundf(clamp<float>(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
113 uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN;
114 SET_NIBBLE(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset);
115
116 // Set power on
117 if (!this->power_) {
119 }
120
121 // Set mode
122 switch (this->mode) {
125 break;
128 break;
130 SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_DRY);
131 break;
133 SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN);
134 break;
136 default:
138 break;
139 // TODO: CLIMATE_MODE_10C is missing from esphome
140 }
141
142 // Set fan
143 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
145 SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_HIGH);
146 break;
149 break;
151 SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW);
152 break;
155 break;
157 default:
158 SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO);
159 break;
160 }
161
162 // Set swing
163 switch (this->swing_mode) {
166 break;
169 break;
172 break;
174 default:
176 break;
177 }
178
179 // TODO: missing support for outdoor unit low noise
180 // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
181
182 remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state);
183
185
186 this->power_ = true;
187}
188
190 ESP_LOGV(TAG, "Transmit off");
191
192 uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0};
193
194 remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
195 remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
196 remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
197 remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
198 remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
199 remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_OFF;
200 remote_state[6] = this->checksum_util_(remote_state);
201
203
204 this->power_ = false;
205}
206
207void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
208 ESP_LOGV(TAG, "Transmit message length %d", length);
209
210 auto transmit = this->transmitter_->transmit();
211 auto *data = transmit.get_data();
212
213 data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);
214
215 // Header
216 data->mark(FUJITSU_GENERAL_HEADER_MARK);
217 data->space(FUJITSU_GENERAL_HEADER_SPACE);
218
219 // Data
220 for (uint8_t i = 0; i < length; ++i) {
221 const uint8_t byte = message[i];
222 for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left
223 data->mark(FUJITSU_GENERAL_BIT_MARK);
224 bool bit = byte & mask;
226 }
227 }
228
229 // Footer
230 data->mark(FUJITSU_GENERAL_TRL_MARK);
231 data->space(FUJITSU_GENERAL_TRL_SPACE);
232
233 transmit.perform();
234}
235
237 uint8_t checksum = 0;
238 for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) {
239 checksum += message[i];
240 }
241 return 256 - checksum;
242}
243
244uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const *message) { return 255 - message[5]; }
245
247 ESP_LOGV(TAG, "Received IR message");
248
249 // Validate header
251 ESP_LOGV(TAG, "Header fail");
252 return false;
253 }
254
255 uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
256
257 // Read header
258 for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) {
259 // Read bit
260 for (uint8_t bit = 0; bit < 8; ++bit) {
262 recv_message[byte] |= 1 << bit; // read from right to left
263 } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
264 ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
265 return false;
266 }
267 }
268 }
269
270 const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE];
271 uint8_t recv_message_length;
272
273 switch (recv_message_type) {
275 ESP_LOGV(TAG, "Received state message");
276 recv_message_length = FUJITSU_GENERAL_STATE_MESSAGE_LENGTH;
277 break;
281 ESP_LOGV(TAG, "Received util message");
282 recv_message_length = FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH;
283 break;
284 default:
285 ESP_LOGV(TAG, "Unknown message type %X", recv_message_type);
286 return false;
287 }
288
289 // Read message body
290 for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) {
291 for (uint8_t bit = 0; bit < 8; ++bit) {
293 recv_message[byte] |= 1 << bit; // read from right to left
294 } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
295 ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
296 return false;
297 }
298 }
299 }
300
301 for (uint8_t byte = 0; byte < recv_message_length; ++byte) {
302 ESP_LOGVV(TAG, "%02X", recv_message[byte]);
303 }
304
305 const uint8_t recv_checksum = recv_message[recv_message_length - 1];
306 uint8_t calculated_checksum;
307 if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
308 calculated_checksum = this->checksum_state_(recv_message);
309 } else {
310 calculated_checksum = this->checksum_util_(recv_message);
311 }
312
313 if (recv_checksum != calculated_checksum) {
314 ESP_LOGV(TAG, "Checksum fail - expected %X - got %X", calculated_checksum, recv_checksum);
315 return false;
316 }
317
318 if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
319 const uint8_t recv_tempertature = GET_NIBBLE(recv_message, FUJITSU_GENERAL_TEMPERATURE_NIBBLE);
320 const uint8_t offset_temperature = recv_tempertature + FUJITSU_GENERAL_TEMP_MIN;
321 this->target_temperature = offset_temperature;
322 ESP_LOGV(TAG, "Received temperature %d", offset_temperature);
323
324 const uint8_t recv_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_MODE_NIBBLE);
325 ESP_LOGV(TAG, "Received mode %X", recv_mode);
326 switch (recv_mode) {
329 break;
332 break;
335 break;
338 break;
340 default:
341 // TODO: CLIMATE_MODE_10C is missing from esphome
343 break;
344 }
345
346 const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
347 ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
348 switch (recv_fan_mode) {
351 break;
354 break;
357 break;
360 break;
362 default:
364 break;
365 }
366
367 const uint8_t recv_swing_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_SWING_NIBBLE);
368 ESP_LOGV(TAG, "Received swing mode %X", recv_swing_mode);
369 switch (recv_swing_mode) {
372 break;
375 break;
378 break;
380 default:
382 }
383
384 this->power_ = true;
385 }
386
387 else if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_OFF) {
388 ESP_LOGV(TAG, "Received off message");
390 this->power_ = false;
391 }
392
393 else {
394 ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type);
395 return false;
396 }
397
398 this->publish_state();
399 return true;
400}
401
402} // namespace esphome::fujitsu_general
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
uint8_t checksum_state_(uint8_t const *message)
Calculate checksum for a state message.
void transmit_state() override
Transmit via IR the state of this climate controller.
uint8_t checksum_util_(uint8_t const *message)
Calculate cecksum for a util message.
bool on_receive(remote_base::RemoteReceiveData data) override
Parse incoming message.
void transmit_off_()
Transmit via IR power off command.
void transmit_(uint8_t const *message, uint8_t length)
Transmit message as IR pulses.
const LogString * message
Definition component.cpp:35
@ CLIMATE_SWING_OFF
The swing mode is set to Off.
@ CLIMATE_SWING_HORIZONTAL
The fan mode is set to Horizontal.
@ CLIMATE_SWING_VERTICAL
The fan mode is set to Vertical.
@ CLIMATE_SWING_BOTH
The fan mode is set to Both.
@ 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_QUIET
The fan mode is set to Quiet.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const uint8_t FUJITSU_GENERAL_FAN_NIBBLE
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE
const uint16_t FUJITSU_GENERAL_ZERO_SPACE
const uint8_t FUJITSU_GENERAL_POWER_ON
const uint8_t FUJITSU_GENERAL_FAN_AUTO
const uint8_t FUJITSU_GENERAL_SWING_NONE
const uint8_t FUJITSU_GENERAL_FAN_SILENT
const uint16_t FUJITSU_GENERAL_HEADER_MARK
const uint16_t FUJITSU_GENERAL_TRL_SPACE
const uint16_t FUJITSU_GENERAL_BIT_MARK
const uint8_t FUJITSU_GENERAL_POWER_ON_NIBBLE
const uint8_t FUJITSU_GENERAL_FAN_HIGH
const uint8_t FUJITSU_GENERAL_COMMON_BYTE4
const uint8_t FUJITSU_GENERAL_SWING_BOTH
const uint16_t FUJITSU_GENERAL_HEADER_SPACE
const uint8_t FUJITSU_GENERAL_POWER_OFF
const uint16_t FUJITSU_GENERAL_ONE_SPACE
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY
const uint8_t FUJITSU_GENERAL_TEMPERATURE_NIBBLE
const uint8_t FUJITSU_GENERAL_COMMON_LENGTH
const uint8_t FUJITSU_GENERAL_MODE_DRY
const uint8_t FUJITSU_GENERAL_COMMON_BYTE1
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM
const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE1
const uint8_t FUJITSU_GENERAL_MODE_FAN
const uint8_t FUJITSU_GENERAL_MODE_AUTO
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_BYTE
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL
const uint8_t FUJITSU_GENERAL_STATE_MESSAGE_LENGTH
const uint16_t FUJITSU_GENERAL_TRL_MARK
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL
const uint8_t FUJITSU_GENERAL_MODE_HEAT
const uint8_t FUJITSU_GENERAL_MODE_NIBBLE
const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE0
const uint8_t FUJITSU_GENERAL_TEMP_MAX
const uint8_t FUJITSU_GENERAL_COMMON_BYTE2
const uint8_t FUJITSU_GENERAL_STATE_FOOTER_BYTE0
const uint8_t FUJITSU_GENERAL_TEMP_MIN
const uint8_t FUJITSU_GENERAL_MODE_COOL
const uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH
const uint8_t FUJITSU_GENERAL_FAN_LOW
const uint8_t FUJITSU_GENERAL_COMMON_BYTE0
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE
const uint8_t FUJITSU_GENERAL_SWING_NIBBLE
const uint8_t FUJITSU_GENERAL_COMMON_BYTE3
static void uint32_t
uint16_t length
Definition tt21100.cpp:0