ESPHome 2026.10.2
Loading...
Searching...
No Matches
fujitsu_general.cpp
Go to the documentation of this file.
1#include "fujitsu_general.h"
2
4
5static const char *const TAG = "fujitsu_general.climate";
6
7// Common header
8constexpr uint8_t FUJITSU_GENERAL_COMMON_LENGTH = 6;
9constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE0 = 0x14;
10constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE1 = 0x63;
11constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE2 = 0x00;
12constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE3 = 0x10;
13constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE4 = 0x10;
15
16// State message - temp & fan etc.
18constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE = 0xFE;
19
20// Util messages - off & eco etc.
22constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF = 0x02;
23constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY = 0x09;
24constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE = 0x6C;
25
26// State header
27constexpr uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE0 = 0x09;
28constexpr uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE1 = 0x30;
29
30// State footer
31constexpr uint8_t FUJITSU_GENERAL_STATE_FOOTER_BYTE0 = 0x20;
32
33// Power on
34constexpr uint8_t FUJITSU_GENERAL_POWER_OFF = 0x00;
35constexpr uint8_t FUJITSU_GENERAL_POWER_ON = 0x01;
36
37// Mode
38// Bit 3 is the clean flag, which is also 10 degree heat on the ARRAH2E and ARREW4E remotes.
39constexpr uint8_t FUJITSU_GENERAL_MODE_MASK = 0b0111;
40constexpr uint8_t FUJITSU_GENERAL_CLEAN_BIT = 0b1000;
41constexpr uint8_t FUJITSU_GENERAL_MODE_AUTO = 0x00;
42constexpr uint8_t FUJITSU_GENERAL_MODE_COOL = 0x01;
43constexpr uint8_t FUJITSU_GENERAL_MODE_DRY = 0x02;
44constexpr uint8_t FUJITSU_GENERAL_MODE_FAN = 0x03;
45constexpr uint8_t FUJITSU_GENERAL_MODE_HEAT = 0x04;
46
47// Swing
48constexpr uint8_t FUJITSU_GENERAL_SWING_MASK = 0b0011;
49constexpr uint8_t FUJITSU_GENERAL_SWING_NONE = 0x00;
50constexpr uint8_t FUJITSU_GENERAL_SWING_VERTICAL = 0x01;
51constexpr uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL = 0x02;
52constexpr uint8_t FUJITSU_GENERAL_SWING_BOTH = 0x03;
53
54// Fan
55constexpr uint8_t FUJITSU_GENERAL_FAN_MASK = 0b0111;
56constexpr uint8_t FUJITSU_GENERAL_FAN_AUTO = 0x00;
57constexpr uint8_t FUJITSU_GENERAL_FAN_HIGH = 0x01;
58constexpr uint8_t FUJITSU_GENERAL_FAN_MEDIUM = 0x02;
59constexpr uint8_t FUJITSU_GENERAL_FAN_LOW = 0x03;
60constexpr uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04;
61
62// TODO Outdoor Unit Low Noise
63// const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0;
64// const uint8_t FUJITSU_GENERAL_STATE_BYTE14 = 0x20;
65
66constexpr uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300;
67constexpr uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600;
68
69constexpr uint16_t FUJITSU_GENERAL_BIT_MARK = 420;
70constexpr uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200;
71constexpr uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420;
72
73constexpr uint16_t FUJITSU_GENERAL_TRL_MARK = 420;
74constexpr uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
75
77
79 if (this->mode == climate::CLIMATE_MODE_OFF) {
80 this->transmit_off_();
81 return;
82 }
83
84 ESP_LOGV(TAG, "Transmit state");
85
86 uint8_t remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
87
88 // Common message header
89 remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
90 remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
91 remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
92 remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
93 remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
94 remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_STATE;
95 remote_state[6] = FUJITSU_GENERAL_STATE_HEADER_BYTE0;
96 remote_state[7] = FUJITSU_GENERAL_STATE_HEADER_BYTE1;
97
98 // unknown, does not appear to change with any remote settings
99 remote_state[14] = FUJITSU_GENERAL_STATE_FOOTER_BYTE0;
100
101 // Set temperature
102 uint8_t temperature_clamped =
103 (uint8_t) roundf(clamp<float>(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
104 uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN;
105 set_nibble(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset);
106
107 // Set power on
108 if (!this->power_) {
110 }
111
112 // Set mode
113 switch (this->mode) {
116 break;
119 break;
122 break;
125 break;
127 default:
129 break;
130 }
131
132 // Set fan
133 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
136 break;
139 break;
142 break;
145 break;
147 default:
149 break;
150 }
151
152 // Set swing
153 switch (this->swing_mode) {
156 break;
159 break;
162 break;
164 default:
166 break;
167 }
168
169 // TODO: missing support for outdoor unit low noise
170 // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
171
172 remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state);
173
175
176 this->power_ = true;
177}
178
180 ESP_LOGV(TAG, "Transmit off");
181
182 uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0};
183
184 remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
185 remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
186 remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
187 remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
188 remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
189 remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_OFF;
190 remote_state[6] = this->checksum_util_(remote_state);
191
193
194 this->power_ = false;
195}
196
197void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
198 ESP_LOGV(TAG, "Transmit message length %d", length);
199
200 auto transmit = this->transmitter_->transmit();
201 auto *data = transmit.get_data();
202
203 data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);
204
205 // Header
206 data->mark(FUJITSU_GENERAL_HEADER_MARK);
207 data->space(FUJITSU_GENERAL_HEADER_SPACE);
208
209 // Data
210 for (uint8_t i = 0; i < length; ++i) {
211 const uint8_t byte = message[i];
212 for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left
213 data->mark(FUJITSU_GENERAL_BIT_MARK);
214 bool bit = byte & mask;
216 }
217 }
218
219 // Footer
220 data->mark(FUJITSU_GENERAL_TRL_MARK);
221 data->space(FUJITSU_GENERAL_TRL_SPACE);
222
223 transmit.perform();
224}
225
227 uint8_t checksum = 0;
228 for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) {
229 checksum += message[i];
230 }
231 return 256 - checksum;
232}
233
234uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const *message) { return 255 - message[5]; }
235
236// These decoders use if chains rather than switches: on ESP8266 the compiler turns a dense switch
237// into a lookup table in .rodata, which lives in RAM there.
238climate::ClimateMode decode_mode(uint8_t mode_field, climate::ClimateMode current_mode) {
239 const uint8_t mode = mode_field & FUJITSU_GENERAL_MODE_MASK;
250 // A state frame means the unit is on, so never keep OFF.
251 ESP_LOGW(TAG, "Received unassigned mode %X, keeping the current mode", mode);
252 return current_mode == climate::CLIMATE_MODE_OFF ? climate::CLIMATE_MODE_HEAT_COOL : current_mode;
253}
254
255optional<climate::ClimateFanMode> decode_fan_mode(uint8_t fan_field, optional<climate::ClimateFanMode> current_mode) {
256 const uint8_t fan = fan_field & FUJITSU_GENERAL_FAN_MASK;
257 if (fan == FUJITSU_GENERAL_FAN_HIGH)
261 if (fan == FUJITSU_GENERAL_FAN_LOW)
265 if (fan == FUJITSU_GENERAL_FAN_AUTO)
267 ESP_LOGW(TAG, "Received unassigned fan speed %X, keeping the current fan mode", fan);
268 return current_mode;
269}
270
281
283 ESP_LOGV(TAG, "Received IR message");
284
285 // Validate header
287 ESP_LOGV(TAG, "Header fail");
288 return false;
289 }
290
291 uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
292
293 // Read header
294 for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) {
295 // Read bit
296 for (uint8_t bit = 0; bit < 8; ++bit) {
298 recv_message[byte] |= 1 << bit; // read from right to left
299 } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
300 ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
301 return false;
302 }
303 }
304 }
305
306 const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE];
307 uint8_t recv_message_length;
308
309 switch (recv_message_type) {
311 ESP_LOGV(TAG, "Received state message");
312 recv_message_length = FUJITSU_GENERAL_STATE_MESSAGE_LENGTH;
313 break;
317 ESP_LOGV(TAG, "Received util message");
318 recv_message_length = FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH;
319 break;
320 default:
321 ESP_LOGV(TAG, "Unknown message type %X", recv_message_type);
322 return false;
323 }
324
325 // Read message body
326 for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) {
327 for (uint8_t bit = 0; bit < 8; ++bit) {
329 recv_message[byte] |= 1 << bit; // read from right to left
330 } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
331 ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
332 return false;
333 }
334 }
335 }
336
337 for (uint8_t byte = 0; byte < recv_message_length; ++byte) {
338 ESP_LOGVV(TAG, "%02X", recv_message[byte]);
339 }
340
341 const uint8_t recv_checksum = recv_message[recv_message_length - 1];
342 uint8_t calculated_checksum;
343 if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
344 calculated_checksum = this->checksum_state_(recv_message);
345 } else {
346 calculated_checksum = this->checksum_util_(recv_message);
347 }
348
349 if (recv_checksum != calculated_checksum) {
350 ESP_LOGV(TAG, "Checksum fail - expected %X - got %X", calculated_checksum, recv_checksum);
351 return false;
352 }
353
354 if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
355 const uint8_t recv_tempertature = get_nibble(recv_message, FUJITSU_GENERAL_TEMPERATURE_NIBBLE);
356 const uint8_t offset_temperature = recv_tempertature + FUJITSU_GENERAL_TEMP_MIN;
357 this->target_temperature = offset_temperature;
358 ESP_LOGV(TAG, "Received temperature %d", offset_temperature);
359
360 const uint8_t recv_mode = get_nibble(recv_message, FUJITSU_GENERAL_MODE_NIBBLE);
361 ESP_LOGV(TAG, "Received mode %X", recv_mode);
362 if ((recv_mode & FUJITSU_GENERAL_CLEAN_BIT) != 0) {
363 ESP_LOGW(TAG, "Received a frame with the clean / 10 degree heat bit set, which is not supported");
364 }
365 this->mode = decode_mode(recv_mode, this->mode);
366
367 const uint8_t recv_fan_mode = get_nibble(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
368 ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
369 this->fan_mode = decode_fan_mode(recv_fan_mode, this->fan_mode);
370
371 const uint8_t recv_swing_mode = get_nibble(recv_message, FUJITSU_GENERAL_SWING_NIBBLE);
372 ESP_LOGV(TAG, "Received swing mode %X", recv_swing_mode);
373 this->swing_mode = decode_swing_mode(recv_swing_mode);
374
375 this->power_ = true;
376 }
377
378 else if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_OFF) {
379 ESP_LOGV(TAG, "Received off message");
381 this->power_ = false;
382 }
383
384 else {
385 ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type);
386 return false;
387 }
388
389 this->publish_state();
390 return true;
391}
392
393} // namespace esphome::fujitsu_general
BedjetMode mode
BedJet operating mode.
uint8_t checksum
Definition bl0906.h:3
ClimateMode mode
The active mode of the climate device.
Definition climate.h:312
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:306
float target_temperature
The target temperature of the climate device.
Definition climate.h:293
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:318
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:429
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
ClimateSwingMode
Enum for all modes a climate swing can be in NOTE: If adding values, update ClimateSwingModeMask in c...
@ 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.
ClimateMode
Enum for all modes a climate device can be in.
@ 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.
optional< climate::ClimateFanMode > decode_fan_mode(uint8_t fan_field, optional< climate::ClimateFanMode > current_mode)
Unassigned values keep the current fan mode.
constexpr uint16_t FUJITSU_GENERAL_TRL_MARK
constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY
constexpr uint8_t FUJITSU_GENERAL_FAN_LOW
constexpr uint8_t FUJITSU_GENERAL_POWER_ON_NIBBLE
constexpr uint8_t FUJITSU_GENERAL_FAN_MASK
constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE1
constexpr uint16_t FUJITSU_GENERAL_HEADER_SPACE
constexpr uint8_t FUJITSU_GENERAL_FAN_SILENT
constexpr uint8_t FUJITSU_GENERAL_FAN_AUTO
constexpr uint8_t get_nibble(const uint8_t *message, uint8_t nibble)
constexpr uint8_t FUJITSU_GENERAL_CLEAN_BIT
constexpr uint8_t FUJITSU_GENERAL_STATE_MESSAGE_LENGTH
constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE4
constexpr uint16_t FUJITSU_GENERAL_HEADER_MARK
constexpr uint8_t FUJITSU_GENERAL_STATE_FOOTER_BYTE0
constexpr uint8_t FUJITSU_GENERAL_TEMPERATURE_NIBBLE
constexpr uint8_t FUJITSU_GENERAL_MODE_DRY
constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE
constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE0
constexpr uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE1
climate::ClimateSwingMode decode_swing_mode(uint8_t swing_field)
constexpr uint8_t FUJITSU_GENERAL_TEMP_MIN
constexpr uint8_t FUJITSU_GENERAL_SWING_NIBBLE
constexpr uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH
constexpr uint8_t FUJITSU_GENERAL_FAN_HIGH
constexpr uint8_t FUJITSU_GENERAL_MODE_FAN
constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_BYTE
constexpr uint8_t FUJITSU_GENERAL_MODE_NIBBLE
constexpr uint8_t FUJITSU_GENERAL_FAN_MEDIUM
constexpr uint8_t FUJITSU_GENERAL_SWING_BOTH
constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE2
climate::ClimateMode decode_mode(uint8_t mode_field, climate::ClimateMode current_mode)
Unassigned values keep the current mode, except that OFF becomes HEAT_COOL.
constexpr uint8_t FUJITSU_GENERAL_MODE_MASK
constexpr uint8_t FUJITSU_GENERAL_POWER_OFF
constexpr uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL
constexpr uint8_t FUJITSU_GENERAL_POWER_ON
constexpr uint16_t FUJITSU_GENERAL_TRL_SPACE
constexpr uint8_t FUJITSU_GENERAL_SWING_MASK
constexpr uint16_t FUJITSU_GENERAL_ZERO_SPACE
constexpr uint8_t FUJITSU_GENERAL_MODE_HEAT
constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE
constexpr uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE0
constexpr uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF
constexpr uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY
constexpr uint8_t FUJITSU_GENERAL_FAN_NIBBLE
constexpr void set_nibble(uint8_t *message, uint8_t nibble, uint8_t value)
Write a nibble into a zero-initialised frame.
constexpr uint8_t FUJITSU_GENERAL_SWING_VERTICAL
constexpr uint16_t FUJITSU_GENERAL_ONE_SPACE
constexpr uint8_t FUJITSU_GENERAL_TEMP_MAX
constexpr uint8_t FUJITSU_GENERAL_MODE_AUTO
constexpr uint16_t FUJITSU_GENERAL_BIT_MARK
constexpr uint8_t FUJITSU_GENERAL_SWING_NONE
constexpr uint8_t FUJITSU_GENERAL_COMMON_BYTE3
constexpr uint8_t FUJITSU_GENERAL_MODE_COOL
constexpr uint8_t FUJITSU_GENERAL_COMMON_LENGTH
static void uint32_t
uint16_t length
Definition tt21100.cpp:0