ESPHome 2026.10.1
Loading...
Searching...
No Matches
mitsubishi.cpp
Go to the documentation of this file.
1#include "mitsubishi.h"
3#include "esphome/core/log.h"
4
6
7static const char *const TAG = "mitsubishi.climate";
8
9// IR frame size for Mitsubishi climate
10static constexpr size_t MITSUBISHI_FRAME_SIZE = 18;
11
12const uint8_t MITSUBISHI_OFF = 0x00;
13
14const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
15const uint8_t MITSUBISHI_MODE_COOL = 0x18;
16const uint8_t MITSUBISHI_MODE_DRY = 0x10;
17const uint8_t MITSUBISHI_MODE_FAN_ONLY = 0x38;
18const uint8_t MITSUBISHI_MODE_HEAT = 0x08;
19
20const uint8_t MITSUBISHI_MODE_A_HEAT = 0x00;
21const uint8_t MITSUBISHI_MODE_A_DRY = 0x02;
22const uint8_t MITSUBISHI_MODE_A_COOL = 0x06;
23const uint8_t MITSUBISHI_MODE_A_AUTO = 0x06;
24
25const uint8_t MITSUBISHI_WIDE_VANE_SWING = 0xC0;
26
27const uint8_t MITSUBISHI_FAN_AUTO = 0x00;
28
29const uint8_t MITSUBISHI_VERTICAL_VANE_SWING = 0x38;
30
31// const uint8_t MITSUBISHI_AUTO = 0x80;
32const uint8_t MITSUBISHI_OTHERWISE = 0x40;
33const uint8_t MITSUBISHI_POWERFUL = 0x08;
34
35// Optional presets used to enable some model features
36const uint8_t MITSUBISHI_ECONOCOOL = 0x20;
37const uint8_t MITSUBISHI_NIGHTMODE = 0xC1;
38
39// Pulse parameters in usec
40const uint16_t MITSUBISHI_BIT_MARK = 430;
41const uint16_t MITSUBISHI_ONE_SPACE = 1250;
42const uint16_t MITSUBISHI_ZERO_SPACE = 390;
43const uint16_t MITSUBISHI_HEADER_MARK = 3500;
44const uint16_t MITSUBISHI_HEADER_SPACE = 1700;
45const uint16_t MITSUBISHI_MIN_GAP = 17500;
46
47// Marker bytes
48const uint8_t MITSUBISHI_BYTE00 = 0x23;
49const uint8_t MITSUBISHI_BYTE01 = 0xCB;
50const uint8_t MITSUBISHI_BYTE02 = 0x26;
51const uint8_t MITSUBISHI_BYTE03 = 0x01;
52const uint8_t MITSUBISHI_BYTE04 = 0x00;
53const uint8_t MITSUBISHI_BYTE13 = 0x00;
54const uint8_t MITSUBISHI_BYTE16 = 0x00;
55
57 // Byte 0-4: Constant: 0x23, 0xCB, 0x26, 0x01, 0x00
58 // Byte 5: On=0x20, Off: 0x00
59 // Byte 6: MODE (See MODEs above (Heat/Dry/Cool/Auto/FanOnly)
60 // Byte 7: TEMP bits 0,1,2,3, added to MITSUBISHI_TEMP_MIN
61 // Example: 0x00 = 0°C+MITSUBISHI_TEMP_MIN = 16°C; 0x07 = 7°C+MITSUBISHI_TEMP_MIN = 23°C
62 // Byte 8: MODE_A & Wide Vane (if present)
63 // MODE_A bits 0,1,2 different than Byte 6 (See MODE_As above)
64 // Wide Vane bits 4,5,6,7 (Middle = 0x30)
65 // Byte 9: FAN/Vertical Vane/Switch To Auto
66 // FAN (Speed) bits 0,1,2
67 // Vertical Vane bits 3,4,5 (Auto = 0x00)
68 // Switch To Auto bits 6,7
69 // Byte 10: CLOCK Current time as configured on remote (0x00=Not used)
70 // Byte 11: END CLOCK Stop time of HVAC (0x00 for no setting)
71 // Byte 12: START CLOCK Start time of HVAC (0x00 for no setting)
72 // Byte 13: Constant 0x00
73 // Byte 14: HVAC specfic, i.e. ECONO COOL, CLEAN MODE, always 0x00
74 // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
75 // Byte 16: Constant 0x00
76 // Byte 17: Checksum: SUM[Byte0...Byte16]
77 uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
78 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
79
80 switch (this->mode) {
82 remote_state[6] = MITSUBISHI_MODE_HEAT;
83 remote_state[8] = MITSUBISHI_MODE_A_HEAT;
84 break;
86 remote_state[6] = MITSUBISHI_MODE_DRY;
87 remote_state[8] = MITSUBISHI_MODE_A_DRY;
88 break;
90 remote_state[6] = MITSUBISHI_MODE_COOL;
91 remote_state[8] = MITSUBISHI_MODE_A_COOL;
92 break;
94 remote_state[6] = MITSUBISHI_MODE_AUTO;
95 remote_state[8] = MITSUBISHI_MODE_A_AUTO;
96 break;
98 remote_state[6] = MITSUBISHI_MODE_FAN_ONLY;
99 remote_state[8] = MITSUBISHI_MODE_A_AUTO;
100 break;
102 default:
103 remote_state[6] = MITSUBISHI_MODE_COOL;
104 remote_state[8] = MITSUBISHI_MODE_A_COOL;
106 remote_state[6] = MITSUBISHI_MODE_HEAT;
107 remote_state[8] = MITSUBISHI_MODE_A_HEAT;
108 }
109 remote_state[5] = MITSUBISHI_OFF;
110 break;
111 }
112
113 // Temperature
114 if (this->mode == climate::CLIMATE_MODE_DRY) {
115 remote_state[7] = 24 - MITSUBISHI_TEMP_MIN; // Remote sends always 24°C if "Dry" mode is selected
116 } else {
117 remote_state[7] = (uint8_t) roundf(
119 }
120
121 // Wide Vane
122 switch (this->swing_mode) {
125 remote_state[8] = remote_state[8] | MITSUBISHI_WIDE_VANE_SWING; // Wide Vane Swing
126 break;
128 default:
129 remote_state[8] = remote_state[8] | this->default_horizontal_direction_; // Off--> horizontal default position
130 break;
131 }
132
133 ESP_LOGD(TAG, "default_horizontal_direction_: %02X", this->default_horizontal_direction_);
134
135 // Fan Speed & Vertical Vane
136 // Map of Climate fan mode to this device expected value
137 // For 3Level: Low = 1, Medium = 2, High = 3
138 // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
139 // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
140 // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
141
142 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
144 remote_state[9] = 1;
145 break;
147 if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
148 remote_state[9] = 2;
149 } else {
150 remote_state[9] = 3;
151 }
152 break;
154 if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
155 remote_state[9] = 3;
156 } else {
157 remote_state[9] = 4;
158 }
159 break;
161 remote_state[9] = 2;
162 break;
164 remote_state[9] = 5;
165 break;
166 default:
167 remote_state[9] = MITSUBISHI_FAN_AUTO;
168 break;
169 }
170
171 ESP_LOGD(TAG, "fan: %02x state: %02x", static_cast<uint8_t>(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)),
172 remote_state[9]);
173
174 // Vertical Vane
175 switch (this->swing_mode) {
178 remote_state[9] = remote_state[9] | MITSUBISHI_VERTICAL_VANE_SWING | MITSUBISHI_OTHERWISE; // Vane Swing
179 break;
181 default:
182 remote_state[9] = remote_state[9] | this->default_vertical_direction_ |
183 MITSUBISHI_OTHERWISE; // Off--> vertical default position
184 break;
185 }
186
187 ESP_LOGD(TAG, "default_vertical_direction_: %02X", this->default_vertical_direction_);
188
189 // Special modes
190 switch (this->preset.value_or(climate::CLIMATE_PRESET_NONE)) {
192 remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE;
193 remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL;
194 remote_state[14] = MITSUBISHI_ECONOCOOL;
195 break;
197 remote_state[9] = MITSUBISHI_FAN_AUTO;
198 remote_state[14] = MITSUBISHI_NIGHTMODE;
199 break;
201 remote_state[6] |= MITSUBISHI_OTHERWISE;
202 remote_state[15] = MITSUBISHI_POWERFUL;
203 break;
205 default:
206 break;
207 }
208
209 // Checksum
210 for (int i = 0; i < 17; i++) {
211 remote_state[17] += remote_state[i];
212 }
213
214 ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X",
215 remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5],
216 remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11],
217 remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17]);
218
219 auto transmit = this->transmitter_->transmit();
220 auto *data = transmit.get_data();
221
222 data->set_carrier_frequency(38000);
223 // repeat twice
224 for (uint8_t r = 0; r < 2; r++) {
225 // Header
226 data->mark(MITSUBISHI_HEADER_MARK);
227 data->space(MITSUBISHI_HEADER_SPACE);
228 // Data
229 for (uint8_t i : remote_state) {
230 for (uint8_t j = 0; j < 8; j++) {
231 data->mark(MITSUBISHI_BIT_MARK);
232 bool bit = i & (1 << j);
233 data->space(bit ? MITSUBISHI_ONE_SPACE : MITSUBISHI_ZERO_SPACE);
234 }
235 }
236 // Footer
237 if (r == 0) {
238 data->mark(MITSUBISHI_BIT_MARK);
239 data->space(MITSUBISHI_MIN_GAP); // Pause before repeating
240 }
241 }
242 data->mark(MITSUBISHI_BIT_MARK);
243
244 transmit.perform();
245}
246
247bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; }
248
250 uint8_t state_frame[18] = {};
251
252 if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) {
253 ESP_LOGV(TAG, "Header fail");
254 return false;
255 }
256
257 for (uint8_t pos = 0; pos < 18; pos++) {
258 uint8_t byte = 0;
259 for (int8_t bit = 0; bit < 8; bit++) {
260 if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) {
261 byte |= 1 << bit;
262 } else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) {
263 ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit);
264 return false;
265 }
266 }
267 state_frame[pos] = byte;
268
269 // Check Header && Footer
270 if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) ||
271 (pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) ||
272 (pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) ||
273 (pos == 16 && byte != MITSUBISHI_BYTE16)) {
274 ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value");
275 return false;
276 }
277 }
278
279 // On/Off and Mode
280 if (state_frame[5] == MITSUBISHI_OFF) {
282 } else {
283 switch (state_frame[6]) {
286 break;
289 break;
292 break;
295 break;
298 break;
299 }
300 }
301
302 // Temp
303 this->target_temperature = state_frame[7] + MITSUBISHI_TEMP_MIN;
304
305 // Fan
306 uint8_t fan = state_frame[9] & 0x07; //(Bit 0,1,2 = Speed)
307 // Map of Climate fan mode to this device expected value
308 // For 3Level: Low = 1, Medium = 2, High = 3
309 // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
310 // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
311 // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
312 climate::ClimateFanMode modes_mapping[8] = {
321 this->fan_mode = modes_mapping[fan];
322
323 // Wide Vane
324 uint8_t wide_vane = state_frame[8] & 0xF0; // Bits 4,5,6,7
325 switch (wide_vane) {
328 break;
329 default:
331 break;
332 }
333
334 // Vertical Vane
335 uint8_t vertical_vane = state_frame[9] & 0x38; // Bits 3,4,5
336 switch (vertical_vane) {
340 } else {
342 }
343 break;
344 }
345
346 switch (state_frame[14]) {
349 break;
352 break;
353 }
354
355#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
356 char hex_buf[format_hex_pretty_size(MITSUBISHI_FRAME_SIZE)];
357#endif
358 ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty_to(hex_buf, state_frame, MITSUBISHI_FRAME_SIZE));
359
360 this->publish_state();
361 return true;
362}
363
364} // namespace esphome::mitsubishi
constexpr size_t count(ValueType value) const
Check if the set contains a specific value (std::set compatibility) Returns 1 if present,...
ClimateMode mode
The active mode of the climate device.
Definition climate.h:312
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:306
float target_temperature
The target temperature of the climate device.
Definition climate.h:293
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:318
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:429
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:309
climate::ClimateModeMask modes_
Definition climate_ir.h:77
bool on_receive(remote_base::RemoteReceiveData data) override
VerticalDirection default_vertical_direction_
Definition mitsubishi.h:85
HorizontalDirection default_horizontal_direction_
Definition mitsubishi.h:84
bool parse_state_frame_(const uint8_t frame[])
@ CLIMATE_PRESET_NONE
No preset is active.
@ CLIMATE_PRESET_BOOST
Device is in boost preset.
@ CLIMATE_PRESET_SLEEP
Device is prepared for sleep.
@ CLIMATE_PRESET_ECO
Device is running an energy-saving preset.
@ 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.
ClimateFanMode
NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value.
@ 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_MIDDLE
The fan mode is set to Middle.
@ CLIMATE_FAN_QUIET
The fan mode is set to Quiet.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const uint8_t MITSUBISHI_POWERFUL
const uint16_t MITSUBISHI_ONE_SPACE
const uint16_t MITSUBISHI_MIN_GAP
const uint8_t MITSUBISHI_MODE_AUTO
const uint8_t MITSUBISHI_MODE_COOL
const uint16_t MITSUBISHI_ZERO_SPACE
const uint8_t MITSUBISHI_FAN_AUTO
const uint8_t MITSUBISHI_BYTE00
const uint16_t MITSUBISHI_HEADER_MARK
const uint8_t MITSUBISHI_BYTE13
const uint8_t MITSUBISHI_BYTE01
const uint8_t MITSUBISHI_MODE_A_HEAT
const uint8_t MITSUBISHI_BYTE04
const uint8_t MITSUBISHI_BYTE03
const uint8_t MITSUBISHI_BYTE02
const uint8_t MITSUBISHI_TEMP_MAX
Definition mitsubishi.h:11
const uint8_t MITSUBISHI_NIGHTMODE
const uint8_t MITSUBISHI_TEMP_MIN
Definition mitsubishi.h:10
const uint8_t MITSUBISHI_WIDE_VANE_SWING
const uint16_t MITSUBISHI_HEADER_SPACE
const uint8_t MITSUBISHI_VERTICAL_VANE_SWING
const uint8_t MITSUBISHI_MODE_A_DRY
const uint8_t MITSUBISHI_OTHERWISE
const uint8_t MITSUBISHI_ECONOCOOL
const uint8_t MITSUBISHI_OFF
const uint8_t MITSUBISHI_MODE_A_COOL
const uint8_t MITSUBISHI_MODE_DRY
const uint8_t MITSUBISHI_BYTE16
const uint8_t MITSUBISHI_MODE_FAN_ONLY
const uint8_t MITSUBISHI_MODE_A_AUTO
const uint16_t MITSUBISHI_BIT_MARK
const uint8_t MITSUBISHI_MODE_HEAT
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:433
size_t size_t pos
Definition helpers.h:1186
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1532