ESPHome  2022.6.3
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  default:
156  SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO);
157  break;
158  // TODO Quiet / Silent
159  }
160 
161  // Set swing
162  switch (this->swing_mode) {
164  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_VERTICAL);
165  break;
167  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_HORIZONTAL);
168  break;
170  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_BOTH);
171  break;
173  default:
174  SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_NONE);
175  break;
176  }
177 
178  // TODO: missing support for outdoor unit low noise
179  // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
180 
181  remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state);
182 
183  this->transmit_(remote_state, FUJITSU_GENERAL_STATE_MESSAGE_LENGTH);
184 
185  this->power_ = true;
186 }
187 
189  ESP_LOGV(TAG, "Transmit off");
190 
191  uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0};
192 
193  remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
194  remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
195  remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
196  remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
197  remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
198  remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_OFF;
199  remote_state[6] = this->checksum_util_(remote_state);
200 
201  this->transmit_(remote_state, FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH);
202 
203  this->power_ = false;
204 }
205 
206 void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
207  ESP_LOGV(TAG, "Transmit message length %d", length);
208 
209  auto transmit = this->transmitter_->transmit();
210  auto *data = transmit.get_data();
211 
212  data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);
213 
214  // Header
215  data->mark(FUJITSU_GENERAL_HEADER_MARK);
216  data->space(FUJITSU_GENERAL_HEADER_SPACE);
217 
218  // Data
219  for (uint8_t i = 0; i < length; ++i) {
220  const uint8_t byte = message[i];
221  for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left
222  data->mark(FUJITSU_GENERAL_BIT_MARK);
223  bool bit = byte & mask;
224  data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE);
225  }
226  }
227 
228  // Footer
229  data->mark(FUJITSU_GENERAL_TRL_MARK);
230  data->space(FUJITSU_GENERAL_TRL_SPACE);
231 
232  transmit.perform();
233 }
234 
235 uint8_t FujitsuGeneralClimate::checksum_state_(uint8_t const *message) {
236  uint8_t checksum = 0;
237  for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) {
238  checksum += message[i];
239  }
240  return 256 - checksum;
241 }
242 
243 uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const *message) { return 255 - message[5]; }
244 
246  ESP_LOGV(TAG, "Received IR message");
247 
248  // Validate header
249  if (!data.expect_item(FUJITSU_GENERAL_HEADER_MARK, FUJITSU_GENERAL_HEADER_SPACE)) {
250  ESP_LOGV(TAG, "Header fail");
251  return false;
252  }
253 
254  uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
255 
256  // Read header
257  for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) {
258  // Read bit
259  for (uint8_t bit = 0; bit < 8; ++bit) {
260  if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
261  recv_message[byte] |= 1 << bit; // read from right to left
262  } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
263  ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
264  return false;
265  }
266  }
267  }
268 
269  const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE];
270  uint8_t recv_message_length;
271 
272  switch (recv_message_type) {
274  ESP_LOGV(TAG, "Received state message");
275  recv_message_length = FUJITSU_GENERAL_STATE_MESSAGE_LENGTH;
276  break;
280  ESP_LOGV(TAG, "Received util message");
281  recv_message_length = FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH;
282  break;
283  default:
284  ESP_LOGV(TAG, "Unknown message type %X", recv_message_type);
285  return false;
286  }
287 
288  // Read message body
289  for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) {
290  for (uint8_t bit = 0; bit < 8; ++bit) {
291  if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
292  recv_message[byte] |= 1 << bit; // read from right to left
293  } else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
294  ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
295  return false;
296  }
297  }
298  }
299 
300  for (uint8_t byte = 0; byte < recv_message_length; ++byte) {
301  ESP_LOGVV(TAG, "%02X", recv_message[byte]);
302  }
303 
304  const uint8_t recv_checksum = recv_message[recv_message_length - 1];
305  uint8_t calculated_checksum;
306  if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
307  calculated_checksum = this->checksum_state_(recv_message);
308  } else {
309  calculated_checksum = this->checksum_util_(recv_message);
310  }
311 
312  if (recv_checksum != calculated_checksum) {
313  ESP_LOGV(TAG, "Checksum fail - expected %X - got %X", calculated_checksum, recv_checksum);
314  return false;
315  }
316 
317  if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
318  const uint8_t recv_tempertature = GET_NIBBLE(recv_message, FUJITSU_GENERAL_TEMPERATURE_NIBBLE);
319  const uint8_t offset_temperature = recv_tempertature + FUJITSU_GENERAL_TEMP_MIN;
320  this->target_temperature = offset_temperature;
321  ESP_LOGV(TAG, "Received temperature %d", offset_temperature);
322 
323  const uint8_t recv_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_MODE_NIBBLE);
324  ESP_LOGV(TAG, "Received mode %X", recv_mode);
325  switch (recv_mode) {
328  break;
331  break;
334  break;
337  break;
339  default:
340  // TODO: CLIMATE_MODE_10C is missing from esphome
342  break;
343  }
344 
345  const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
346  ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
347  switch (recv_fan_mode) {
348  // TODO No Quiet / Silent in ESPH
352  break;
355  break;
358  break;
360  default:
362  break;
363  }
364 
365  const uint8_t recv_swing_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_SWING_NIBBLE);
366  ESP_LOGV(TAG, "Received swing mode %X", recv_swing_mode);
367  switch (recv_swing_mode) {
370  break;
373  break;
376  break;
378  default:
380  }
381 
382  this->power_ = true;
383  }
384 
385  else if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_OFF) {
386  ESP_LOGV(TAG, "Received off message");
388  this->power_ = false;
389  }
390 
391  else {
392  ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type);
393  return false;
394  }
395 
396  this->publish_state();
397  return true;
398 }
399 
400 } // namespace fujitsu_general
401 } // namespace esphome
The fan mode is set to Low.
Definition: climate_mode.h:54
value_type const & value() const
Definition: optional.h:89
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition: climate.h:204
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:72
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:183
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL
void set_carrier_frequency(uint32_t carrier_frequency)
Definition: remote_base.h:30
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:175
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:76
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
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:74
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:384
The fan mode is set to High.
Definition: climate_mode.h:58
The swing mode is set to Off.
Definition: climate_mode.h:70
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:198
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
const uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH
Definition: a4988.cpp:4
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.h:111
The climate device only has the fan enabled, no heating or cooling is taking place.
Definition: climate_mode.h:20
remote_transmitter::RemoteTransmitterComponent * transmitter_
Definition: climate_ir.h:67