ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
hitachi_ac344.cpp
Go to the documentation of this file.
1#include "hitachi_ac344.h"
3
4#include <cstring>
5
7
8ESPHOME_LOG_TAG(TAG, "climate.hitachi_ac344");
9
10void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) {
11 if (offset >= 8 || !nbits)
12 return; // Short circuit as it won't change.
13 // Calculate the mask for the supplied value.
14 uint8_t mask = UINT8_MAX >> (8 - ((nbits > 8) ? 8 : nbits));
15 // Calculate the mask & clear the space for the data.
16 // Clear the destination bits.
17 *dst &= ~(uint8_t) (mask << offset);
18 // Merge in the data.
19 *dst |= ((data & mask) << offset);
20}
21
22uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
23 for (uint16_t i = 1; i < length; i += 2) {
24 // Code done this way to avoid a compiler warning bug.
25 uint8_t inv = ~*(ptr + i - 1);
26 *(ptr + i) = inv;
27 }
28 return ptr;
29}
30
32
37
39
41 uint8_t new_mode = mode;
42 switch (mode) {
43 // Fan mode sets a special temp.
46 break;
50 break;
51 default:
52 new_mode = HITACHI_AC344_MODE_COOL;
53 }
55 if (new_mode != HITACHI_AC344_MODE_FAN)
57 set_fan_(get_fan_()); // Reset the fan speed after the mode change.
58 set_power_(true);
59}
60
61void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
62 uint8_t temp;
63 temp = std::min(celsius, HITACHI_AC344_TEMP_MAX);
64 temp = std::max(temp, HITACHI_AC344_TEMP_MIN);
66 if (previous_temp_ > temp) {
68 } else if (previous_temp_ < temp) {
70 }
71 if (set_previous)
72 previous_temp_ = temp;
73}
74
76
77void HitachiClimate::set_fan_(uint8_t speed) {
78 uint8_t new_speed = std::max(speed, HITACHI_AC344_FAN_MIN);
79 uint8_t fan_max = HITACHI_AC344_FAN_MAX;
80
81 // Only 2 x low speeds in Dry mode or Auto
83 fan_max = HITACHI_AC344_FAN_AUTO;
84 } else if (get_mode_() == HITACHI_AC344_MODE_DRY) {
87 // Fan Mode does not have auto. Set to safe low
88 new_speed = HITACHI_AC344_FAN_MIN;
89 }
90
91 new_speed = std::min(new_speed, fan_max);
92 // Handle the setting the button value if we are going to change the value.
93 if (new_speed != get_fan_())
95 // Set the values
96
98 remote_state_[9] = 0x92;
99
100 // When fan is at min/max, additional bytes seem to be set
101 if (new_speed == HITACHI_AC344_FAN_MIN)
102 remote_state_[9] = 0x98;
103 remote_state_[29] = 0x01;
104}
105
109
113
121
125
127
128static bool swing_vertical(climate::ClimateSwingMode mode) {
130}
131
135
137 // A repeated write resends the frame, so a missed burst can be retried
140 this->publish_mildew_proof_();
141}
142
144#ifdef USE_SWITCH
145 if (this->mildew_proof_switch_ != nullptr)
147#endif
148}
149
151 // Seed the frame before the base publishes the restored state; an on_state automation may transmit from that
152 // callback. Nothing is sent at boot, the restored settings ride along with the next frame.
153 auto restore = this->restore_state_();
154 if (restore.has_value()) {
155 set_swing_v_(swing_vertical(restore->swing_mode));
156 }
157#ifdef USE_SWITCH
158 // The restored value is the switch state, which an inverted switch flips relative to the frame bit
159 if (this->mildew_proof_switch_ != nullptr) {
160 if (auto initial = this->mildew_proof_switch_->get_initial_state_with_restore_mode()) {
162 *initial != this->mildew_proof_switch_->is_inverted());
163 }
164 }
165#endif
166 this->setup_(restore);
167 this->publish_mildew_proof_();
168}
169
171
173 switch (this->mode) {
176 break;
179 break;
182 break;
185 break;
188 break;
190 set_power_(false);
191 break;
192 default:
193 ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
194 }
195
196 set_temp_(static_cast<uint8_t>(this->target_temperature));
197
198 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
201 break;
204 break;
207 break;
210 break;
213 default:
215 }
216
217 const bool swing_v = swing_vertical(this->swing_mode);
218 const bool swing_h =
221 // The unit ignores the swing bit and toggles on the swing button, so only a vertical swing change presses it;
222 // everything else sends the given button
224 set_swing_v_(swing_v);
225
226 // The remote sends this when powering off with mildew proof on
227 if (this->mode == climate::CLIMATE_MODE_OFF && this->get_mildew_proof())
228 remote_state_[9] = 0xA5;
229
231
232 auto transmit = this->transmitter_->transmit();
233 auto *data = transmit.get_data();
234 data->set_carrier_frequency(HITACHI_AC344_FREQ);
235
236 uint8_t repeat = 0;
237 for (uint8_t r = 0; r <= repeat; r++) {
238 // Header
240 // Data
241 for (uint8_t i : remote_state_) {
242 for (uint8_t j = 0; j < 8; j++) {
243 data->mark(HITACHI_AC344_BIT_MARK);
244 bool bit = i & (1 << j);
246 }
247 }
248 // Footer
250 }
251 transmit.perform();
252
253 dump_state_("Sent", remote_state_);
254}
255
256bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
257 uint8_t power = remote_state[HITACHI_AC344_POWER_BYTE];
258 ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC344_POWER_BYTE], power);
259 uint8_t mode = remote_state[HITACHI_AC344_MODE_BYTE] & 0xF;
260 ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC344_MODE_BYTE], mode);
261 if (power == HITACHI_AC344_POWER_ON) {
262 switch (mode) {
264 this->mode = climate::CLIMATE_MODE_COOL;
265 break;
267 this->mode = climate::CLIMATE_MODE_DRY;
268 break;
270 this->mode = climate::CLIMATE_MODE_HEAT;
271 break;
274 break;
277 break;
278 default:
279 break;
280 }
281 } else {
282 this->mode = climate::CLIMATE_MODE_OFF;
283 }
284 return true;
285}
286
287bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) {
288 uint8_t temperature =
291 ESP_LOGV(TAG, "Temperature: %02X %02u %04f", remote_state[HITACHI_AC344_TEMP_BYTE], temperature,
292 this->target_temperature);
293 return true;
294}
295
296bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) {
297 uint8_t fan_mode = remote_state[HITACHI_AC344_FAN_BYTE] >> 4 & 0xF;
298 ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC344_FAN_BYTE], fan_mode);
299 switch (fan_mode) {
301 this->fan_mode = climate::CLIMATE_FAN_QUIET;
302 break;
304 this->fan_mode = climate::CLIMATE_FAN_LOW;
305 break;
307 this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
308 break;
311 this->fan_mode = climate::CLIMATE_FAN_HIGH;
312 break;
314 this->fan_mode = climate::CLIMATE_FAN_AUTO;
315 break;
316 default:
317 break;
318 }
319 return true;
320}
321
322bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
323 uint8_t swing_modeh =
325 ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh);
326 bool swing_v = GETBIT8(remote_state[HITACHI_AC344_SWINGV_BYTE], HITACHI_AC344_SWINGV_OFFSET);
327 ESP_LOGV(TAG, "SwingV: %02X %s", remote_state[HITACHI_AC344_SWINGV_BYTE], ONOFF(swing_v));
328
329 if (swing_modeh != HITACHI_AC344_SWINGH_AUTO) {
331 } else {
333 }
334
335 return true;
336}
337
339 // Validate header
340 if (!data.expect_item(HITACHI_AC344_HDR_MARK, HITACHI_AC344_HDR_SPACE)) {
341 ESP_LOGVV(TAG, "Header fail");
342 return false;
343 }
344
345 uint8_t recv_state[HITACHI_AC344_STATE_LENGTH] = {0};
346 // Read all bytes.
347 for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) {
348 // Read bit
349 for (int8_t bit = 0; bit < 8; bit++) {
350 if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) {
351 recv_state[pos] |= 1 << bit;
352 } else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
353 ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
354 return false;
355 }
356 }
357 }
358
359 // Validate footer
360 if (!data.expect_mark(HITACHI_AC344_BIT_MARK)) {
361 ESP_LOGVV(TAG, "Footer fail");
362 return false;
363 }
364
365 dump_state_("Recv", recv_state);
366
367 // parse mode
368 this->parse_mode_(recv_state);
369 // parse temperature
370 this->parse_temperature_(recv_state);
371 // parse fan
372 this->parse_fan_(recv_state);
373 // parse swingv
374 this->parse_swing_(recv_state);
375 // Copy first so an on_state automation transmits from the received frame
376 memcpy(remote_state_, recv_state, HITACHI_AC344_STATE_LENGTH);
377 this->publish_state();
378 this->publish_mildew_proof_();
379
380 return true;
381}
382
383void HitachiClimate::dump_state_(const char action[], uint8_t state[]) {
384 for (uint16_t i = 0; i < HITACHI_AC344_STATE_LENGTH - 10; i += 10) {
385 ESP_LOGV(TAG, "%s: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", action, state[i + 0], state[i + 1],
386 state[i + 2], state[i + 3], state[i + 4], state[i + 5], state[i + 6], state[i + 7], state[i + 8],
387 state[i + 9]);
388 }
389 ESP_LOGV(TAG, "%s: %02X %02X %02X", action, state[40], state[41], state[42]);
390}
391
392} // namespace esphome::hitachi_ac344
BedjetMode mode
BedJet operating mode.
ClimateMode mode
The active mode of the climate device.
Definition climate.h:317
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:311
float target_temperature
The target temperature of the climate device.
Definition climate.h:298
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:323
ClimateAction action
The active state of the climate device.
Definition climate.h:320
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:365
void setup_(optional< climate::ClimateDeviceRestoreState > restore)
setup() with the restored state already loaded, for subclasses that read it first.
bool on_receive(remote_base::RemoteReceiveData data) override
void dump_state_(const char action[], uint8_t remote_state[])
bool parse_temperature_(const uint8_t remote_state[])
bool parse_mode_(const uint8_t remote_state[])
void set_temp_(uint8_t celsius, bool set_previous=true)
bool parse_fan_(const uint8_t remote_state[])
uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]
bool parse_swing_(const uint8_t remote_state[])
void set_mildew_proof(bool on)
Mildew proof keeps the fan running after power off to dry the unit.
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:70
optional< bool > get_initial_state_with_restore_mode()
Returns the initial state of the switch, after applying restore mode rules.
Definition switch.cpp:49
bool is_inverted() const
Definition switch.h:130
float position
Definition cover.h:0
int speed
Definition fan.h:3
bool state
Definition fan.h:2
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.
@ 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 HITACHI_AC344_SWINGV_OFFSET
const uint8_t HITACHI_AC344_MILDEWPROOF_BYTE
const uint8_t HITACHI_AC344_TEMP_MIN
const uint8_t HITACHI_AC344_BUTTON_FAN
const uint16_t HITACHI_AC344_BIT_MARK
const uint8_t HITACHI_AC344_BUTTON_BYTE
const uint8_t HITACHI_AC344_TEMP_BYTE
const uint8_t HITACHI_AC344_MODE_BYTE
const uint8_t HITACHI_AC344_SWINGV_BYTE
const uint16_t HITACHI_AC344_ZERO_SPACE
const uint8_t HITACHI_AC344_SWINGH_SIZE
const uint8_t HITACHI_AC344_FAN_MIN
const uint8_t HITACHI_AC344_BUTTON_POWER
uint8_t * invert_byte_pairs(uint8_t *ptr, const uint16_t length)
const uint8_t HITACHI_AC344_MILDEWPROOF_OFFSET
const uint8_t HITACHI_AC344_FAN_MAX
const uint8_t HITACHI_AC344_MODE_DRY
const uint16_t HITACHI_AC344_HDR_SPACE
const uint8_t HITACHI_AC344_BUTTON_MILDEWPROOF
const uint8_t HITACHI_AC344_FAN_MAX_DRY
const uint8_t HITACHI_AC344_MODE_FAN
void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data)
const uint16_t HITACHI_AC344_FREQ
const uint8_t HITACHI_AC344_SWINGH_AUTO
const uint8_t HITACHI_AC344_BUTTON_TEMP_DOWN
const uint8_t HITACHI_AC344_MODE_AUTO
const uint8_t HITACHI_AC344_BUTTON_TEMP_UP
const uint8_t HITACHI_AC344_MODE_COOL
const uint16_t HITACHI_AC344_STATE_LENGTH
const uint8_t HITACHI_AC344_SWINGH_OFFSET
const uint8_t HITACHI_AC344_POWER_ON
const uint8_t HITACHI_AC344_TEMP_SIZE
const uint8_t HITACHI_AC344_FAN_HIGH
const uint8_t HITACHI_AC344_MODE_HEAT
const uint8_t HITACHI_AC344_FAN_BYTE
const uint16_t HITACHI_AC344_HDR_MARK
ESPHOME_LOG_TAG(TAG, "climate.hitachi_ac344")
const uint8_t HITACHI_AC344_SWINGH_LEFT_MAX
const uint8_t HITACHI_AC344_TEMP_MAX
const uint8_t HITACHI_AC344_TEMP_OFFSET
const uint8_t HITACHI_AC344_FAN_LOW
const uint32_t HITACHI_AC344_MIN_GAP
const uint8_t HITACHI_AC344_FAN_MEDIUM
const uint8_t HITACHI_AC344_POWER_OFF
const uint8_t HITACHI_AC344_SWINGH_MIDDLE
const uint8_t HITACHI_AC344_POWER_BYTE
const uint8_t HITACHI_AC344_FAN_AUTO
const uint16_t HITACHI_AC344_ONE_SPACE
const uint8_t HITACHI_AC344_SWINGH_BYTE
const uint8_t HITACHI_AC344_BUTTON_SWINGV
const uint8_t HITACHI_AC344_TEMP_FAN
ESPHOME_ALWAYS_INLINE constexpr void assign_bits(T &value, std::type_identity_t< T > mask, bool on)
Set the bits of mask in value when on is true, clear them otherwise.
Definition helpers.h:1072
size_t size_t pos
Definition helpers.h:1255
uint16_t temperature
Definition sun_gtil2.cpp:12
uint16_t length
Definition tt21100.cpp:0