ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
airton.cpp
Go to the documentation of this file.
1#include "airton.h"
3#include "esphome/core/log.h"
4
5namespace esphome::airton {
6
7ESPHOME_LOG_TAG(TAG, "airton.climate");
8
9// Distinct from the climate's own RESTORE_STATE_VERSION, which shares the entity's preference key
10static constexpr uint32_t RESTORE_SETTINGS_VERSION = 0x2E86A1F3UL;
11
12// From IRutils.h of IRremoteESP8266 library
13static uint8_t checksum(const uint8_t *frame) {
14 return static_cast<uint8_t>(0x7F - sum_bytes(frame, AIRTON_STATE_FRAME_SIZE - 1)) ^ 0x2C;
15}
16
19 // The flap position is the one source of truth for the swing mode the climate state restored
20 this->settings_pref_ = this->make_entity_preference<AirtonSettings>(RESTORE_SETTINGS_VERSION);
21 this->settings_pref_.load(&this->settings_);
22 this->sync_swing_mode_();
23 this->publish_settings_();
24 this->publish_state();
25}
26
28#ifdef USE_SWITCH
29 if (this->sleep_mode_switch_ != nullptr)
31 if (this->display_switch_ != nullptr)
33#endif
34#ifdef USE_SELECT
35 if (this->vertical_direction_select_ != nullptr)
37#endif
38}
39
41
47
48// The unit has no sleep mode while it only fans or picks the mode itself
52
53// Each setter stores and saves before it publishes: an automation on the entity runs inside
54// publish_state() and may set the value again, which must not be undone afterwards
56 state = state && this->sleep_mode_allowed_();
57 const bool changed = state != this->settings_.sleep_state;
58 if (changed) {
60 this->save_settings_();
61 }
62#ifdef USE_SWITCH
63 if (this->sleep_mode_switch_ != nullptr)
65#endif
66 return changed;
67}
68
70 const bool changed = state != this->settings_.display_state;
71 if (changed) {
73 this->save_settings_();
74 }
75#ifdef USE_SWITCH
76 if (this->display_switch_ != nullptr)
77 this->display_switch_->publish_state(state);
78#endif
79 return changed;
80}
81
83 const bool changed = direction != this->settings_.vertical_direction;
84 if (changed) {
86 this->sync_swing_mode_();
87 this->save_settings_();
88 }
89#ifdef USE_SELECT
90 if (this->vertical_direction_select_ != nullptr)
91 this->vertical_direction_select_->publish_state(static_cast<size_t>(direction));
92#endif
93 return changed;
94}
95
97 this->transmit_state();
98 this->publish_state();
99}
100
102 // ClimateIR::control() sends and publishes the whole state afterwards
103 // Only a change of swing mode moves the flap, so a repeated swing-off keeps a fixed position
104 if (auto swing_mode = call.get_swing_mode(); swing_mode.has_value() && *swing_mode != this->swing_mode) {
108 }
109 ClimateIR::control(call);
110}
111
113 // Entering fan-only or automatic mode drops sleep mode, as the remote does
114 if (!this->sleep_mode_allowed_())
115 this->set_sleep_mode_state(false);
116 // Sampled valid state
117 // Power: On, Mode: 2 (Dry), Fan: 1 (Quiet), Temp: 20C, Swing(V): On, Econo: Off, Turbo: Off, Light: On, Health: On,
118 // Sleep: Off. 0x74C461041A11D3
119 // The turbo bit above the fan speed stays clear, as the remote sends it
120 uint8_t remote_state[AIRTON_STATE_FRAME_SIZE] = {
121 AIRTON_HEADER_BYTE0,
122 AIRTON_HEADER_BYTE1,
123 static_cast<uint8_t>(this->operation_mode_() | (this->fan_speed_() << 4)),
124 this->temperature_(),
125 static_cast<uint8_t>(this->settings_.vertical_direction),
126 this->operation_settings_(),
127 0,
128 };
129 remote_state[6] = checksum(remote_state);
130
131 ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X %02X %02X %02X", remote_state[6], remote_state[5], remote_state[4],
132 remote_state[3], remote_state[2], remote_state[1], remote_state[0]);
133
134 auto transmit = this->transmitter_->transmit();
135 auto *data = transmit.get_data();
136 data->set_carrier_frequency(AIRTON_IR_FREQUENCY);
137 data->mark(AIRTON_HEADER_MARK);
138 data->space(AIRTON_HEADER_SPACE);
139 data->item_bytes<AIRTON_BIT_TIMINGS>(remote_state, AIRTON_STATE_FRAME_SIZE);
140
141 data->mark(AIRTON_BIT_MARK);
142 data->space(AIRTON_MESSAGE_SPACE);
143
144 transmit.perform();
145}
146
148 uint8_t operating_mode = 0b1000; // First bit is for power state
149 switch (this->mode) {
151 operating_mode |= AIRTON_MODE_COOL;
152 break;
154 operating_mode |= AIRTON_MODE_DRY;
155 break;
157 operating_mode |= AIRTON_MODE_HEAT;
158 break;
160 operating_mode |= AIRTON_MODE_AUTO;
161 break;
163 operating_mode |= AIRTON_MODE_FAN;
164 break;
166 default:
167 operating_mode = 0b0111 & this->previous_mode_; // Set previous mode with power state bit off
168 }
169 this->previous_mode_ = operating_mode;
170 return operating_mode;
171}
172
174 uint8_t fan_speed;
175 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) {
177 fan_speed = AIRTON_FAN_1;
178 break;
180 fan_speed = AIRTON_FAN_3;
181 break;
183 fan_speed = AIRTON_FAN_5;
184 break;
186 default:
187 fan_speed = AIRTON_FAN_AUTO;
188 }
189 return fan_speed;
190}
191
193 switch (this->mode) {
195 return AIRTON_AUTO_MODE_TEMP - AIRTON_TEMP_MIN;
196 default:
197 return static_cast<uint8_t>(roundf(clamp<float>(this->target_temperature, AIRTON_TEMP_MIN, AIRTON_TEMP_MAX))) -
198 AIRTON_TEMP_MIN;
199 }
200}
201
202// The bits of this packet's byte have the following meanings (from MSB to LSB)
203// Light, Health, Unknown, HeatOn, Unknown, NotAutoOn, Sleep, Econo
205 uint8_t settings = 0;
206 if (this->mode == climate::CLIMATE_MODE_HEAT) { // Set heating bit if on the corresponding mode
207 settings |= (1 << 4);
208 }
209 if (this->settings_.display_state) {
210 settings |= (1 << 7);
211 }
212 if (this->settings_.sleep_state) {
213 settings |= (1 << 1);
214 }
215 settings |= 0b01000000; // Health, as the remote always sends it
217 settings |= 0b00000100; // NotAutoOn: clear only while the unit picks the mode itself
218 }
219 return settings;
220}
221
222void AirtonClimate::parse_state_frame_(const uint8_t *frame) {
223 uint8_t mode = frame[2];
224 // A later "off" repeats the mode the remote last sent, as the remote itself does
225 this->previous_mode_ = mode & 0b00001111;
226 if (mode & 0b00001000) { // The power bit
227 switch (mode & 0b00000111) {
228 case AIRTON_MODE_COOL:
229 this->mode = climate::CLIMATE_MODE_COOL;
230 break;
231 case AIRTON_MODE_DRY:
232 this->mode = climate::CLIMATE_MODE_DRY;
233 break;
234 case AIRTON_MODE_HEAT:
235 this->mode = climate::CLIMATE_MODE_HEAT;
236 break;
237 case AIRTON_MODE_AUTO:
239 break;
240 case AIRTON_MODE_FAN:
242 break;
243 }
244 } else {
245 this->mode = climate::CLIMATE_MODE_OFF;
246 }
247 uint8_t fan_mode = (frame[2] & 0b01110000) >> 4;
248
249 switch (fan_mode) {
250 case AIRTON_FAN_1:
251 case AIRTON_FAN_2:
252 this->fan_mode = climate::CLIMATE_FAN_LOW;
253 break;
254 case AIRTON_FAN_3:
255 this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
256 break;
257 case AIRTON_FAN_4:
258 case AIRTON_FAN_5:
259 this->fan_mode = climate::CLIMATE_FAN_HIGH;
260 break;
261 case AIRTON_FAN_AUTO:
262 this->fan_mode = climate::CLIMATE_FAN_AUTO;
263 break;
264 default:
265 break;
266 }
267
268 this->target_temperature = (frame[3] & 0b00001111) + AIRTON_TEMP_MIN;
269
270 const uint8_t direction = frame[4] & 0b00001111;
271 if (direction <= static_cast<uint8_t>(VerticalDirection::VERTICAL_DIRECTION_DOWN)) {
273 } else {
274 ESP_LOGV(TAG, "Unknown vertical direction %u", direction);
275 }
276 this->set_display_state((frame[5] & 0b10000000) != 0);
277 this->set_sleep_mode_state((frame[5] & 0b00000010) != 0);
278
279 this->publish_state();
280}
281
283 uint8_t remote_state[AIRTON_STATE_FRAME_SIZE];
284 if (!data.expect_item(AIRTON_HEADER_MARK, AIRTON_HEADER_SPACE)) {
285 ESP_LOGV(TAG, "Wrong header encoding detected!");
286 return false;
287 }
288 if (!data.expect_bytes<AIRTON_BIT_TIMINGS>(remote_state, AIRTON_STATE_FRAME_SIZE)) {
289 ESP_LOGV(TAG, "Wrong modulation encoding");
290 return false;
291 }
292
293 if (remote_state[0] != AIRTON_HEADER_BYTE0 || remote_state[1] != AIRTON_HEADER_BYTE1) {
294 ESP_LOGV(TAG, "Wrong header contents: %02X %02X", remote_state[1], remote_state[0]);
295 return false;
296 }
297 const uint8_t expected = checksum(remote_state);
298 if (remote_state[AIRTON_STATE_FRAME_SIZE - 1] != expected) {
299 ESP_LOGV(TAG, "Checksum error: calculated %02X, received %02X", expected,
300 remote_state[AIRTON_STATE_FRAME_SIZE - 1]);
301 return false;
302 }
303 ESP_LOGV(TAG, "Received: %02X %02X %02X %02X %02X %02X %02X", remote_state[6], remote_state[5], remote_state[4],
304 remote_state[3], remote_state[2], remote_state[1], remote_state[0]);
305 this->parse_state_frame_(remote_state);
306 return true;
307}
308
309} // namespace esphome::airton
uint8_t checksum
Definition bl0906.h:3
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
bool set_display_state(bool state)
Definition airton.cpp:69
switch_::Switch * display_switch_
Definition airton.h:125
bool sleep_mode_allowed_() const
Definition airton.cpp:49
void control(const climate::ClimateCall &call) override
Definition airton.cpp:101
bool set_sleep_mode_state(bool state)
Definition airton.cpp:55
void transmit_state() override
Definition airton.cpp:112
void parse_state_frame_(const uint8_t *frame)
Definition airton.cpp:222
select::Select * vertical_direction_select_
Definition airton.h:128
bool set_vertical_direction_state(VerticalDirection direction)
Definition airton.cpp:82
switch_::Switch * sleep_mode_switch_
Definition airton.h:124
ESPPreferenceObject settings_pref_
Definition airton.h:130
bool on_receive(remote_base::RemoteReceiveData data) override
Definition airton.cpp:282
This class is used to encode all control actions on a climate device.
Definition climate.h:31
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
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
void publish_state(const std::string &state)
Definition select.h:35
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:70
FanDirection direction
Definition fan.h:5
bool state
Definition fan.h:2
ESPHOME_LOG_TAG(TAG, "airton.climate")
@ CLIMATE_SWING_OFF
The swing mode is set to Off.
@ CLIMATE_SWING_VERTICAL
The fan mode is set to Vertical.
@ 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_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
constexpr uint8_t sum_bytes(const uint8_t *data, size_t len, uint8_t sum=0)
Add up the bytes of data with size len, truncated to 8 bits: the checksum many serial and IR protocol...
Definition helpers.h:894
static void uint32_t
VerticalDirection vertical_direction
Definition airton.h:70