ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
heatpumpir.cpp
Go to the documentation of this file.
1#include "heatpumpir.h"
2
4
5#if defined(USE_ARDUINO) || defined(USE_ESP32)
6
7#include <cmath>
8#include <IRSender.h>
9#include <HeatpumpIRFactory.h>
11#include "esphome/core/log.h"
12
14
15// IRSenderESPHome - bridge between ESPHome's remote_transmitter and HeatpumpIR library
16// Defined here (not in a header) to isolate HeatpumpIR's headers from the rest of ESPHome,
17// as they define conflicting symbols like millis() in the global namespace.
18class IRSenderESPHome : public IRSender {
19 public:
20 IRSenderESPHome(remote_base::RemoteTransmitterBase *transmitter) : IRSender(0), transmit_(transmitter->transmit()) {}
21
22 void setFrequency(int frequency) override { // NOLINT(readability-identifier-naming)
23 auto *data = this->transmit_.get_data();
24 data->set_carrier_frequency(1000 * frequency);
25 }
26
27 void space(int space_length) override {
28 if (space_length) {
29 auto *data = this->transmit_.get_data();
30 data->space(space_length);
31 } else {
32 this->transmit_.perform();
33 }
34 }
35
36 void mark(int mark_length) override {
37 auto *data = this->transmit_.get_data();
38 data->mark(mark_length);
39 }
40
41 protected:
43};
44
45ESPHOME_LOG_TAG(TAG, "heatpumpir.climate");
46
47template<Protocol P> struct ProtocolClass;
48
49template<Protocol P> HeatpumpIR *create_heatpump_ir() {
50 return new typename ProtocolClass<P>::type(); // NOLINT
51}
52
53// Codegen calls only the configured protocols, so the linker drops the other classes
54#define HEATPUMPIR_PROTOCOL(protocol, cls) \
55 template<> struct ProtocolClass<protocol> { \
56 using type = cls; \
57 }; \
58 template HeatpumpIR *create_heatpump_ir<protocol>();
59
61HEATPUMPIR_PROTOCOL(PROTOCOL_BALLU, BalluHeatpumpIR)
63HEATPUMPIR_PROTOCOL(PROTOCOL_CARRIER_NQV, CarrierNQVHeatpumpIR)
65HEATPUMPIR_PROTOCOL(PROTOCOL_DAIKIN_ARC480, DaikinHeatpumpARC480A14IR)
67HEATPUMPIR_PROTOCOL(PROTOCOL_ELECTROLUXYAL, ElectroluxYALHeatpumpIR)
70HEATPUMPIR_PROTOCOL(PROTOCOL_GREE, GreeGenericHeatpumpIR)
71HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAA, GreeYAAHeatpumpIR)
73HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAC, GreeYACHeatpumpIR)
75HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAP, GreeYAPHeatpumpIR)
77HEATPUMPIR_PROTOCOL(PROTOCOL_HITACHI, HitachiHeatpumpIR)
81HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_FA, MitsubishiFAHeatpumpIR)
83HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_FE, MitsubishiFEHeatpumpIR)
85HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_HEAVY_ZJ, MitsubishiHeavyZJHeatpumpIR)
87HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_HEAVY_ZMP, MitsubishiHeavyZMPHeatpumpIR)
89HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_MSC, MitsubishiMSCHeatpumpIR)
91HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_SEZ, MitsubishiSEZKDXXHeatpumpIR)
93HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_DKE, PanasonicDKEHeatpumpIR)
95HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_JKE, PanasonicJKEHeatpumpIR)
97HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_NKE, PanasonicNKEHeatpumpIR)
99HEATPUMPIR_PROTOCOL(PROTOCOL_SAMSUNG_FJM, SamsungFJMHeatpumpIR)
101HEATPUMPIR_PROTOCOL(PROTOCOL_TOSHIBA_DAISEIKAI, ToshibaDaiseikaiHeatpumpIR)
103HEATPUMPIR_PROTOCOL(PROTOCOL_ZHLT01, ZHLT01HeatpumpIR)
105HEATPUMPIR_PROTOCOL(PROTOCOL_QLIMA_1, Qlima1HeatpumpIR)
107HEATPUMPIR_PROTOCOL(PROTOCOL_SAMSUNG_AQV12MSAN, SamsungAQV12MSANHeatpumpIR)
109HEATPUMPIR_PROTOCOL(PROTOCOL_AIRWAY, AIRWAYHeatpumpIR)
111HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_ALTDKE, PanasonicAltDKEHeatpumpIR)
115
116#undef HEATPUMPIR_PROTOCOL
117
120 if (this->sensor_) {
121 this->sensor_->add_on_state_callback([this](float state) {
123
124 IRSenderESPHome esp_sender(this->transmitter_);
125 this->heatpump_ir_->send(esp_sender, uint8_t(std::lround(this->current_temperature)));
126
127 // current temperature changed, publish state
128 this->publish_state();
129 });
130 this->current_temperature = this->sensor_->state;
131 } else {
132 this->current_temperature = NAN;
133 }
134}
135
137 // Picking a fan speed ends HiPower or Econo, as it does on the remote
138 if (this->has_fan_byte_presets_() && call.get_fan_mode().has_value() && !call.get_preset().has_value())
141}
142
144 uint8_t power_mode_cmd;
145 uint8_t operating_mode_cmd;
146 uint8_t temperature_cmd;
147 uint8_t fan_speed_cmd;
148
149 uint8_t swing_v_cmd;
152 swing_v_cmd = VDIR_AUTO;
153 break;
155 swing_v_cmd = VDIR_UP;
156 break;
158 swing_v_cmd = VDIR_MUP;
159 break;
161 swing_v_cmd = VDIR_MIDDLE;
162 break;
164 swing_v_cmd = VDIR_MDOWN;
165 break;
167 swing_v_cmd = VDIR_DOWN;
168 break;
169 default:
170 ESP_LOGE(TAG, "Invalid default vertical direction");
171 return;
172 }
174 swing_v_cmd = VDIR_SWING;
175 }
176
177 uint8_t swing_h_cmd;
180 swing_h_cmd = HDIR_AUTO;
181 break;
183 swing_h_cmd = HDIR_MIDDLE;
184 break;
186 swing_h_cmd = HDIR_LEFT;
187 break;
189 swing_h_cmd = HDIR_MLEFT;
190 break;
192 swing_h_cmd = HDIR_MRIGHT;
193 break;
195 swing_h_cmd = HDIR_RIGHT;
196 break;
197 default:
198 ESP_LOGE(TAG, "Invalid default horizontal direction");
199 return;
200 }
202 swing_h_cmd = HDIR_SWING;
203 }
204
205 // MitsubishiMSCHeatpumpIR only knows FAN_1..FAN_3 and sends anything else as auto,
206 // so low/medium/high have to start at FAN_1 there instead of FAN_2. Mitsubishi Heavy
207 // ZJ and ZMP also stop at FAN_3 and use FAN_4 and FAN_5 for HiPower and Econo.
208 const bool three_speed_fan = this->protocol_ == PROTOCOL_MITSUBISHI_MSC || this->has_fan_byte_presets_();
209 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) {
211 fan_speed_cmd = three_speed_fan ? FAN_1 : FAN_2;
212 break;
214 fan_speed_cmd = three_speed_fan ? FAN_2 : FAN_3;
215 break;
217 fan_speed_cmd = three_speed_fan ? FAN_3 : FAN_4;
218 break;
220 default:
221 fan_speed_cmd = FAN_AUTO;
222 break;
223 }
224
225 if (this->has_fan_byte_presets_()) {
226 switch (this->preset.value_or(climate::CLIMATE_PRESET_NONE)) {
228 fan_speed_cmd = FAN_4; // HiPower
229 break;
231 fan_speed_cmd = FAN_5; // Econo
232 break;
233 default:
234 break;
235 }
236 }
237
238 switch (this->mode) {
240 power_mode_cmd = POWER_ON;
241 operating_mode_cmd = MODE_COOL;
242 break;
244 power_mode_cmd = POWER_ON;
245 operating_mode_cmd = MODE_HEAT;
246 break;
247 // Map HEAT_COOL to hardware AUTO mode (automatic heat/cool changeover based on temperature).
248 // In hardware AUTO mode, the device automatically switches between heating and cooling
249 // based on the current temperature versus the target temperature.
250 // See https://github.com/esphome/esphome/issues/11161 for further discussion.
253 power_mode_cmd = POWER_ON;
254 operating_mode_cmd = MODE_AUTO;
255 break;
257 power_mode_cmd = POWER_ON;
258 operating_mode_cmd = MODE_FAN;
259 break;
261 power_mode_cmd = POWER_ON;
262 operating_mode_cmd = MODE_DRY;
263 break;
265 default:
266 power_mode_cmd = POWER_OFF;
267 operating_mode_cmd = MODE_AUTO;
268 break;
269 }
270
271 temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
272
273 IRSenderESPHome esp_sender(this->transmitter_);
274 heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
275 swing_h_cmd);
276}
277
279 switch (this->protocol_) {
281 uint8_t frame[MITSUBISHI_HEAVY_FRAME_LENGTH];
282 if (!decode_mitsubishi_heavy_frame(data, frame, *this))
283 return false;
284 decode_mitsubishi_heavy_zmp(frame, *this);
285 break;
286 }
287 default:
288 return false; // no decoder for this protocol
289 }
291 this->publish_state();
292 return true;
293}
294
295} // namespace esphome::heatpumpir
296
297#endif
uint16_le_t frequency
Definition bl0942.h:6
This class is used to encode all control actions on a climate device.
Definition climate.h:31
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:316
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
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:291
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:314
void control(const climate::ClimateCall &call) override
Override control to change settings of the climate device.
bool on_receive(remote_base::RemoteReceiveData data) override
Handle received IR data from the remote control.
VerticalDirection default_vertical_direction_
Definition heatpumpir.h:142
void transmit_state() override
Transmit via IR the state of this climate controller.
bool has_fan_byte_presets_() const
Mitsubishi Heavy ZJ and ZMP carry HiPower and Econo in the fan byte as FAN_4 and FAN_5.
Definition heatpumpir.h:135
HorizontalDirection default_horizontal_direction_
Definition heatpumpir.h:141
void control(const climate::ClimateCall &call) override
void add_on_state_callback(F &&callback)
Add a callback that will be called every time a filtered value arrives.
Definition sensor.h:121
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:140
bool state
Definition fan.h:2
@ CLIMATE_PRESET_NONE
No preset is active.
@ CLIMATE_PRESET_BOOST
Device is in boost preset.
@ CLIMATE_PRESET_ECO
Device is running an energy-saving preset.
@ 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_MODE_AUTO
The climate device is adjusting the temperature dynamically.
@ 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.
HEATPUMPIR_PROTOCOL(PROTOCOL_AUX, AUXHeatpumpIR) HEATPUMPIR_PROTOCOL(PROTOCOL_BALLU
bool decode_mitsubishi_heavy_frame(remote_base::RemoteReceiveData &data, uint8_t frame[MITSUBISHI_HEAVY_FRAME_LENGTH], HeatpumpIRClimate &climate)
Read a Mitsubishi Heavy frame into frame, check its prefix and inverted byte pairs,...
ESPHOME_LOG_TAG(TAG, "heatpumpir.climate")
HeatpumpIR * create_heatpump_ir()
Creates the HeatpumpIR library class for a protocol; instantiated in heatpumpir.cpp.
void decode_mitsubishi_heavy_zmp(const uint8_t frame[MITSUBISHI_HEAVY_FRAME_LENGTH], HeatpumpIRClimate &climate)
Apply the ZMP-specific fields (fan, preset, swing) of a validated frame.
constexpr uint8_t MITSUBISHI_HEAVY_FRAME_LENGTH
Bytes in a Mitsubishi Heavy IR frame, prefix included.