ESPHome  2024.4.1
fujitsu_general.cpp
Go to the documentation of this file.
1 #include "fujitsu_general.h"
2 
3 namespace esphome {
4 namespace fujitsu_general {
5 
6 // bytes' bits are reversed for fujitsu, so nibbles are ordered 1, 0, 3, 2, 5, 4, etc...
7 
8 #define SET_NIBBLE(message, nibble, value) \
9  ((message)[(nibble) / 2] |= ((value) &0b00001111) << (((nibble) % 2) ? 0 : 4))
10 #define GET_NIBBLE(message, nibble) (((message)[(nibble) / 2] >> (((nibble) % 2) ? 0 : 4)) & 0b00001111)
11 
12 static const char *const TAG = "fujitsu_general.climate";
13 
14 // Common header
16 const uint8_t FUJITSU_GENERAL_COMMON_BYTE0 = 0x14;
17 const uint8_t FUJITSU_GENERAL_COMMON_BYTE1 = 0x63;
18 const uint8_t FUJITSU_GENERAL_COMMON_BYTE2 = 0x00;
19 const uint8_t FUJITSU_GENERAL_COMMON_BYTE3 = 0x10;
20 const uint8_t FUJITSU_GENERAL_COMMON_BYTE4 = 0x10;
22 
23 // State message - temp & fan etc.
26 
27 // Util messages - off & eco etc.
29 const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF = 0x02;
32 
33 // State header
36 
37 // State footer
39 
40 // Temperature
42 
43 // Power on
45 const uint8_t FUJITSU_GENERAL_POWER_OFF = 0x00;
46 const uint8_t FUJITSU_GENERAL_POWER_ON = 0x01;
47 
48 // Mode
49 const uint8_t FUJITSU_GENERAL_MODE_NIBBLE = 19;
50 const uint8_t FUJITSU_GENERAL_MODE_AUTO = 0x00;
51 const uint8_t FUJITSU_GENERAL_MODE_COOL = 0x01;
52 const uint8_t FUJITSU_GENERAL_MODE_DRY = 0x02;
53 const uint8_t FUJITSU_GENERAL_MODE_FAN = 0x03;
54 const uint8_t FUJITSU_GENERAL_MODE_HEAT = 0x04;
55 // const uint8_t FUJITSU_GENERAL_MODE_10C = 0x0B;
56 
57 // Swing
58 const uint8_t FUJITSU_GENERAL_SWING_NIBBLE = 20;
59 const uint8_t FUJITSU_GENERAL_SWING_NONE = 0x00;
60 const uint8_t FUJITSU_GENERAL_SWING_VERTICAL = 0x01;
61 const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL = 0x02;
62 const uint8_t FUJITSU_GENERAL_SWING_BOTH = 0x03;
63 
64 // Fan
65 const uint8_t FUJITSU_GENERAL_FAN_NIBBLE = 21;
66 const uint8_t FUJITSU_GENERAL_FAN_AUTO = 0x00;
67 const uint8_t FUJITSU_GENERAL_FAN_HIGH = 0x01;
68 const uint8_t FUJITSU_GENERAL_FAN_MEDIUM = 0x02;
69 const uint8_t FUJITSU_GENERAL_FAN_LOW = 0x03;
70 const uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04;
71 
72 // TODO Outdoor Unit Low Noise
73 // const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0;
74 // const uint8_t FUJITSU_GENERAL_STATE_BYTE14 = 0x20;
75 
76 const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300;
77 const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600;
78 
79 const uint16_t FUJITSU_GENERAL_BIT_MARK = 420;
80 const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200;
81 const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420;
82 
83 const uint16_t FUJITSU_GENERAL_TRL_MARK = 420;
84 const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
85 
86 const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
87 
89  if (this->mode == climate::CLIMATE_MODE_OFF) {
90  this->transmit_off_();
91  return;
92  }
93 
94  ESP_LOGV(TAG, "Transmit state");
95 
96  uint8_t remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
97 
98  // Common message header
99  remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
100  remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
101  remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
102  remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
103  remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
104  remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_STATE;
105  remote_state[6] = FUJITSU_GENERAL_STATE_HEADER_BYTE0;
106  remote_state[7] = FUJITSU_GENERAL_STATE_HEADER_BYTE1;
107 
108  // unknown, does not appear to change with any remote settings
109  remote_state[14] = FUJITSU_GENERAL_STATE_FOOTER_BYTE0;
110 
111  // Set temperature
112  uint8_t temperature_clamped =
113  (uint8_t) roundf(clamp<float>(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
114  uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN;
115  SET_NIBBLE(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset);
116 
117  // Set power on
118  if (!this->power_) {
119  SET_NIBBLE(remote_state, FUJITSU_GENERAL_POWER_ON_NIBBLE, FUJITSU_GENERAL_POWER_ON);
120  }
121 
122  // Set mode
123  switch (this->mode) {
125  SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_COOL);
126  break;
128  SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_HEAT);
129  break;
131  SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_DRY);
132  break;
134  SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN);
135  break;
137  default:
138  SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO);
139  break;
140  // TODO: CLIMATE_MODE_10C is missing from esphome
141  }
142 
143  // Set fan
144  switch (this->fan_mode.value()) {
146  SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_HIGH);
147  break;
149  SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_MEDIUM);
150  break;
152  SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW);
153  break;
155  SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_SILENT);
156  break;
158  default:
159  SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO);
160  break;
161  }
162 
163  // Set swing
164  switch (this->swing_mode) {
166  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_VERTICAL);
167  break;
169  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_HORIZONTAL);
170  break;
172  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_BOTH);
173  break;
175  default:
176  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_NONE);
177  break;
178  }
179 
180  // TODO: missing support for outdoor unit low noise
181  // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
182 
183  remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state);
184 
185  this->transmit_(remote_state, FUJITSU_GENERAL_STATE_MESSAGE_LENGTH);
186 
187  this->power_ = true;
188 }
189 
191  ESP_LOGV(TAG, "Transmit off");
192 
193  uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0};
194 
195  remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
196  remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
197  remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
198  remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
199  remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
200  remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_OFF;
201  remote_state[6] = this->checksum_util_(remote_state);
202 
203  this->transmit_(remote_state, FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH);
204 
205  this->power_ = false;
206 }
207 
208 void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
209  ESP_LOGV(TAG, "Transmit message length %d", length);
210 
211  auto transmit = this->transmitter_->transmit();
212  auto *data = transmit.get_data();
213 
214  data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);
215 
216  // Header
217  data->mark(FUJITSU_GENERAL_HEADER_MARK);
218  data->space(FUJITSU_GENERAL_HEADER_SPACE);
219 
220  // Data
221  for (uint8_t i = 0; i < length; ++i) {
222  const uint8_t byte = message[i];
223  for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left
224  data->mark(FUJITSU_GENERAL_BIT_MARK);
225  bool bit = byte & mask;
226  data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE);
227  }
228  }
229 
230  // Footer
231  data->mark(FUJITSU_GENERAL_TRL_MARK);
232  data->space(FUJITSU_GENERAL_TRL_SPACE);
233 
234  transmit.perform();
235 }
236 
237 uint8_t FujitsuGeneralClimate::checksum_state_(uint8_t const *message) {
238  uint8_t checksum = 0;
239  for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) {
240  checksum += message[i];
241  }
242  return 256 - checksum;
243 }
244 
245 uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const *message) { return 255 - message[5]; }
246 
248  ESP_LOGV(TAG, "Received IR message");
249 
250  // Validate header
251  if (!data.expect_item(FUJITSU_GENERAL_HEADER_MARK, FUJITSU_GENERAL_HEADER_SPACE)) {
252  ESP_LOGV(TAG, "Header fail");
253  return false;
254  }
255 
256  uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
257 
258  // Read header
259  for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) {
260  // Read bit
261  for (uint8_t bit = 0; bit < 8; ++bit) {
262  if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
263  recv_message[byte] |= 1 << bit; // read from right to left
264  } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
265  ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
266  return false;
267  }
268  }
269  }
270 
271  const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE];
272  uint8_t recv_message_length;
273 
274  switch (recv_message_type) {
276  ESP_LOGV(TAG, "Received state message");
277  recv_message_length = FUJITSU_GENERAL_STATE_MESSAGE_LENGTH;
278  break;
282  ESP_LOGV(TAG, "Received util message");
283  recv_message_length = FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH;
284  break;
285  default:
286  ESP_LOGV(TAG, "Unknown message type %X", recv_message_type);
287  return false;
288  }
289 
290  // Read message body
291  for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) {
292  for (uint8_t bit = 0; bit < 8; ++bit) {
293  if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
294  recv_message[byte] |= 1 << bit; // read from right to left
295  } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
296  ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
297  return false;
298  }
299  }
300  }
301 
302  for (uint8_t byte = 0; byte < recv_message_length; ++byte) {
303  ESP_LOGVV(TAG, "%02X", recv_message[byte]);
304  }
305 
306  const uint8_t recv_checksum = recv_message[recv_message_length - 1];
307  uint8_t calculated_checksum;
308  if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
309  calculated_checksum = this->checksum_state_(recv_message);
310  } else {
311  calculated_checksum = this->checksum_util_(recv_message);
312  }
313 
314  if (recv_checksum != calculated_checksum) {
315  ESP_LOGV(TAG, "Checksum fail - expected %X - got %X", calculated_checksum, recv_checksum);
316  return false;
317  }
318 
319  if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
320  const uint8_t recv_tempertature = GET_NIBBLE(recv_message, FUJITSU_GENERAL_TEMPERATURE_NIBBLE);
321  const uint8_t offset_temperature = recv_tempertature + FUJITSU_GENERAL_TEMP_MIN;
322  this->target_temperature = offset_temperature;
323  ESP_LOGV(TAG, "Received temperature %d", offset_temperature);
324 
325  const uint8_t recv_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_MODE_NIBBLE);
326  ESP_LOGV(TAG, "Received mode %X", recv_mode);
327  switch (recv_mode) {
330  break;
333  break;
336  break;
339  break;
341  default:
342  // TODO: CLIMATE_MODE_10C is missing from esphome
344  break;
345  }
346 
347  const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
348  ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
349  switch (recv_fan_mode) {
352  break;
355  break;
358  break;
361  break;
363  default:
365  break;
366  }
367 
368  const uint8_t recv_swing_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_SWING_NIBBLE);
369  ESP_LOGV(TAG, "Received swing mode %X", recv_swing_mode);
370  switch (recv_swing_mode) {
373  break;
376  break;
379  break;
381  default:
383  }
384 
385  this->power_ = true;
386  }
387 
388  else if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_OFF) {
389  ESP_LOGV(TAG, "Received off message");
391  this->power_ = false;
392  }
393 
394  else {
395  ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type);
396  return false;
397  }
398 
399  this->publish_state();
400  return true;
401 }
402 
403 } // namespace fujitsu_general
404 } // namespace esphome
The fan mode is set to Low.
Definition: climate_mode.h:54
value_type const & value() const
Definition: optional.h:89
The fan mode is set to Quiet.
Definition: climate_mode.h:66
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition: climate.h:202
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_BYTE
bool on_receive(remote_base::RemoteReceiveData data) override
Parse incoming message.
The fan mode is set to Both.
Definition: climate_mode.h:74
const uint8_t FUJITSU_GENERAL_FAN_LOW
void transmit_(uint8_t const *message, uint8_t length)
Transmit message as IR pulses.
float target_temperature
The target temperature of the climate device.
Definition: climate.h:186
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL
void set_carrier_frequency(uint32_t carrier_frequency)
Definition: remote_base.h:29
const uint8_t FUJITSU_GENERAL_MODE_DRY
const uint16_t FUJITSU_GENERAL_TRL_MARK
const uint8_t FUJITSU_GENERAL_FAN_NIBBLE
const uint8_t FUJITSU_GENERAL_COMMON_BYTE1
The climate device is set to heat to reach the target temperature.
Definition: climate_mode.h:18
const uint8_t FUJITSU_GENERAL_MODE_FAN
ClimateMode mode
The active mode of the climate device.
Definition: climate.h:173
const uint8_t FUJITSU_GENERAL_FAN_HIGH
const uint16_t FUJITSU_GENERAL_HEADER_MARK
uint8_t checksum_util_(uint8_t const *message)
Calculate cecksum for a util message.
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF
The climate device is set to dry/humidity mode.
Definition: climate_mode.h:22
const uint8_t FUJITSU_GENERAL_SWING_BOTH
const uint8_t FUJITSU_GENERAL_COMMON_BYTE0
const uint16_t FUJITSU_GENERAL_ZERO_SPACE
void transmit_state() override
Transmit via IR the state of this climate controller.
const uint8_t FUJITSU_GENERAL_TEMP_MAX
const uint8_t FUJITSU_GENERAL_SWING_NIBBLE
const uint8_t FUJITSU_GENERAL_STATE_FOOTER_BYTE0
void transmit_off_()
Transmit via IR power off command.
const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE1
const uint8_t FUJITSU_GENERAL_COMMON_BYTE3
The fan mode is set to Horizontal.
Definition: climate_mode.h:78
The climate device is set to cool to reach the target temperature.
Definition: climate_mode.h:16
const uint8_t FUJITSU_GENERAL_COMMON_LENGTH
The fan mode is set to Auto.
Definition: climate_mode.h:52
uint8_t checksum_state_(uint8_t const *message)
Calculate checksum for a state message.
const uint8_t FUJITSU_GENERAL_COMMON_BYTE4
const uint8_t FUJITSU_GENERAL_FAN_AUTO
const uint16_t FUJITSU_GENERAL_HEADER_SPACE
RemoteTransmitterBase * transmitter_
Definition: remote_base.h:245
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY
const uint8_t FUJITSU_GENERAL_MODE_AUTO
const uint8_t FUJITSU_GENERAL_MODE_NIBBLE
The climate device is set to heat/cool to reach the target temperature.
Definition: climate_mode.h:14
const uint16_t FUJITSU_GENERAL_TRL_SPACE
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY
const uint8_t FUJITSU_GENERAL_POWER_OFF
The fan mode is set to Vertical.
Definition: climate_mode.h:76
const uint8_t FUJITSU_GENERAL_SWING_NONE
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE
uint8_t checksum
Definition: bl0939.h:35
const uint8_t FUJITSU_GENERAL_MODE_HEAT
const uint8_t FUJITSU_GENERAL_TEMP_MIN
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL
const uint8_t FUJITSU_GENERAL_MODE_COOL
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 uint16_t FUJITSU_GENERAL_BIT_MARK
const uint8_t FUJITSU_GENERAL_POWER_ON
const uint8_t FUJITSU_GENERAL_POWER_ON_NIBBLE
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:199
const uint8_t FUJITSU_GENERAL_TEMPERATURE_NIBBLE
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE
const uint8_t FUJITSU_GENERAL_STATE_MESSAGE_LENGTH
const uint16_t FUJITSU_GENERAL_ONE_SPACE
const uint8_t FUJITSU_GENERAL_FAN_SILENT
uint16_t length
Definition: tt21100.cpp:12
const uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
const uint8_t FUJITSU_GENERAL_COMMON_BYTE2
The fan mode is set to Medium.
Definition: climate_mode.h:56
const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE0
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