ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mitsubishi.cpp
Go to the documentation of this file.
1#include "mitsubishi.h"
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5
7
8ESPHOME_LOG_TAG(TAG, "mitsubishi.climate");
9
10// IR frame size for Mitsubishi climate
11static constexpr size_t MITSUBISHI_FRAME_SIZE = 18;
12
13const uint8_t MITSUBISHI_OFF = 0x00;
14
15const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
16const uint8_t MITSUBISHI_MODE_COOL = 0x18;
17const uint8_t MITSUBISHI_MODE_DRY = 0x10;
18const uint8_t MITSUBISHI_MODE_FAN_ONLY = 0x38;
19const uint8_t MITSUBISHI_MODE_HEAT = 0x08;
20
21const uint8_t MITSUBISHI_MODE_A_HEAT = 0x00;
22const uint8_t MITSUBISHI_MODE_A_DRY = 0x02;
23const uint8_t MITSUBISHI_MODE_A_COOL = 0x06;
24const uint8_t MITSUBISHI_MODE_A_AUTO = 0x06;
25
26const uint8_t MITSUBISHI_WIDE_VANE_SWING = 0xC0;
27
28const uint8_t MITSUBISHI_FAN_AUTO = 0x00;
29
30const uint8_t MITSUBISHI_VERTICAL_VANE_MASK = 0x38; // Bits 3,4,5
31const uint8_t MITSUBISHI_VERTICAL_VANE_SWING = 0x38;
32
33// const uint8_t MITSUBISHI_AUTO = 0x80;
34const uint8_t MITSUBISHI_OTHERWISE = 0x40;
35const uint8_t MITSUBISHI_POWERFUL = 0x08;
36
37// Optional presets used to enable some model features
38const uint8_t MITSUBISHI_ECONOCOOL = 0x20;
39const uint8_t MITSUBISHI_NIGHTMODE = 0xC1;
40
41// Pulse parameters in usec
42const uint16_t MITSUBISHI_BIT_MARK = 430;
43static constexpr remote_base::BitTimings MITSUBISHI_BIT_TIMINGS{
44 .bit_mark = MITSUBISHI_BIT_MARK, .one_space = 1250, .zero_space = 390};
45const uint16_t MITSUBISHI_HEADER_MARK = 3500;
46const uint16_t MITSUBISHI_HEADER_SPACE = 1700;
47const uint16_t MITSUBISHI_MIN_GAP = 17500;
48
49// Marker bytes
50const uint8_t MITSUBISHI_BYTE00 = 0x23;
51const uint8_t MITSUBISHI_BYTE01 = 0xCB;
52const uint8_t MITSUBISHI_BYTE02 = 0x26;
53const uint8_t MITSUBISHI_BYTE03 = 0x01;
54const uint8_t MITSUBISHI_BYTE04 = 0x00;
55const uint8_t MITSUBISHI_BYTE13 = 0x00;
56
57// Byte 7 codes for 61°F through 88°F. Bit 4 is the half degree bit Mitsubishi uses so every Fahrenheit degree
58// maps to its own code.
59static const uint8_t FAHRENHEIT_CODES[] PROGMEM = {0x00, 0x10, 0x01, 0x11, 0x02, 0x12, 0x03, 0x04, 0x05, 0x15,
60 0x06, 0x16, 0x07, 0x17, 0x08, 0x18, 0x09, 0x19, 0x0a, 0x1a,
61 0x0b, 0x1b, 0x0c, 0x1c, 0x0d, 0x1d, 0x0e, 0x0f};
62static constexpr uint8_t FAHRENHEIT_MIN = 61;
63static constexpr uint8_t MITSUBISHI_HALF_DEGREE = 0x10;
64
65static uint8_t fahrenheit_code(float celsius) {
66 int fahrenheit = (int) roundf(celsius_to_fahrenheit(celsius));
67 size_t index = clamp<int>(fahrenheit - FAHRENHEIT_MIN, 0, sizeof(FAHRENHEIT_CODES) - 1);
68 return progmem_read_byte(&FAHRENHEIT_CODES[index]);
69}
70
71// In Fahrenheit mode a code from the table decodes to the exact Fahrenheit value. Any other code, and every code in
72// Celsius mode, is a whole degree plus the half degree bit, which a remote set to Fahrenheit also sets.
73static float decode_temperature(uint8_t code, bool fahrenheit) {
74 if (fahrenheit) {
75 for (size_t i = 0; i < sizeof(FAHRENHEIT_CODES); i++) {
76 if (code == progmem_read_byte(&FAHRENHEIT_CODES[i])) {
77 return fahrenheit_to_celsius(FAHRENHEIT_MIN + i);
78 }
79 }
80 }
81 return MITSUBISHI_TEMP_MIN + (code & 0x0F) + ((code & MITSUBISHI_HALF_DEGREE) ? 0.5f : 0.0f);
82}
83
85 // Byte 0-4: Constant: 0x23, 0xCB, 0x26, 0x01, 0x00
86 // Byte 5: On=0x20, Off: 0x00
87 // Byte 6: MODE (See MODEs above (Heat/Dry/Cool/Auto/FanOnly)
88 // Byte 7: TEMP bits 0,1,2,3, added to MITSUBISHI_TEMP_MIN
89 // Example: 0x00 = 0°C+MITSUBISHI_TEMP_MIN = 16°C; 0x07 = 7°C+MITSUBISHI_TEMP_MIN = 23°C
90 // Byte 8: MODE_A & Wide Vane (if present)
91 // MODE_A bits 0,1,2 different than Byte 6 (See MODE_As above)
92 // Wide Vane bits 4,5,6,7 (Middle = 0x30)
93 // Byte 9: FAN/Vertical Vane/Switch To Auto
94 // FAN (Speed) bits 0,1,2
95 // Vertical Vane bits 3,4,5 (Auto = 0x00)
96 // Switch To Auto bits 6,7
97 // Byte 10: CLOCK Current time as configured on remote (0x00=Not used)
98 // Byte 11: END CLOCK Stop time of HVAC (0x00 for no setting)
99 // Byte 12: START CLOCK Start time of HVAC (0x00 for no setting)
100 // Byte 13: Constant 0x00
101 // Byte 14: HVAC specfic, i.e. ECONO COOL, CLEAN MODE, always 0x00
102 // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
103 // Byte 16: Left vane control (for specific models). Constants match right vane controls.
104 // Byte 17: Checksum: SUM[Byte0...Byte16]
105 uint8_t remote_state[MITSUBISHI_FRAME_SIZE] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
106 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
107
108 switch (this->mode) {
110 remote_state[6] = MITSUBISHI_MODE_HEAT;
111 remote_state[8] = MITSUBISHI_MODE_A_HEAT;
112 break;
114 remote_state[6] = MITSUBISHI_MODE_DRY;
115 remote_state[8] = MITSUBISHI_MODE_A_DRY;
116 break;
118 remote_state[6] = MITSUBISHI_MODE_COOL;
119 remote_state[8] = MITSUBISHI_MODE_A_COOL;
120 break;
122 remote_state[6] = MITSUBISHI_MODE_AUTO;
123 remote_state[8] = MITSUBISHI_MODE_A_AUTO;
124 break;
126 remote_state[6] = MITSUBISHI_MODE_FAN_ONLY;
127 remote_state[8] = MITSUBISHI_MODE_A_AUTO;
128 break;
130 default:
131 remote_state[6] = MITSUBISHI_MODE_COOL;
132 remote_state[8] = MITSUBISHI_MODE_A_COOL;
134 remote_state[6] = MITSUBISHI_MODE_HEAT;
135 remote_state[8] = MITSUBISHI_MODE_A_HEAT;
136 }
137 remote_state[5] = MITSUBISHI_OFF;
138 break;
139 }
140
141 // Temperature
142 if (this->mode == climate::CLIMATE_MODE_DRY) {
143 remote_state[7] = 24 - MITSUBISHI_TEMP_MIN; // Remote sends always 24°C if "Dry" mode is selected
144 } else if (this->fahrenheit_compatibility_) {
145 remote_state[7] = fahrenheit_code(this->target_temperature);
146 } else {
147 remote_state[7] = (uint8_t) roundf(
149 }
150
151 // Wide Vane
152 switch (this->swing_mode) {
155 remote_state[8] = remote_state[8] | MITSUBISHI_WIDE_VANE_SWING; // Wide Vane Swing
156 break;
158 default:
159 remote_state[8] = remote_state[8] | this->default_horizontal_direction_; // Off--> horizontal default position
160 break;
161 }
162
163 ESP_LOGD(TAG, "default_horizontal_direction_: %02X", this->default_horizontal_direction_);
164
165 // Fan Speed & Vertical Vane
166 // Map of Climate fan mode to this device expected value
167 // For 3Level: Low = 1, Medium = 2, High = 3
168 // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
169 // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
170 // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
171
172 switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
174 remote_state[9] = 1;
175 break;
177 if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
178 remote_state[9] = 2;
179 } else {
180 remote_state[9] = 3;
181 }
182 break;
184 if (this->fan_mode_ == MITSUBISHI_FAN_3L) {
185 remote_state[9] = 3;
186 } else {
187 remote_state[9] = 4;
188 }
189 break;
191 remote_state[9] = 2;
192 break;
194 remote_state[9] = 5;
195 break;
196 default:
197 remote_state[9] = MITSUBISHI_FAN_AUTO;
198 break;
199 }
200
201 ESP_LOGD(TAG, "fan: %02x state: %02x", static_cast<uint8_t>(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)),
202 remote_state[9]);
203
204 // Vertical Vane
205 switch (this->swing_mode) {
208 remote_state[9] = remote_state[9] | MITSUBISHI_VERTICAL_VANE_SWING | MITSUBISHI_OTHERWISE; // Vane Swing
209 break;
211 default:
212 remote_state[9] = remote_state[9] | this->default_vertical_direction_ |
213 MITSUBISHI_OTHERWISE; // Off--> vertical default position
214 break;
215 }
216
217 ESP_LOGD(TAG, "Vertical default: 0x%02X, vanes: %u", this->default_vertical_direction_, this->vertical_vanes_);
218
219 // Special modes
220 switch (this->preset.value_or(climate::CLIMATE_PRESET_NONE)) {
222 remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE;
223 remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL;
224 remote_state[14] = MITSUBISHI_ECONOCOOL;
225 break;
227 remote_state[9] = MITSUBISHI_FAN_AUTO;
228 remote_state[14] = MITSUBISHI_NIGHTMODE;
229 break;
231 remote_state[6] |= MITSUBISHI_OTHERWISE;
232 remote_state[15] = MITSUBISHI_POWERFUL;
233 break;
235 default:
236 break;
237 }
238
239 if (this->vertical_vanes_ > 1) {
240 // Heads with two vertical vanes carry the left vane in byte 16; it follows the right one, presets included
241 remote_state[16] = remote_state[9] & MITSUBISHI_VERTICAL_VANE_MASK;
242 }
243
244 // Checksum
245 remote_state[17] = sum_bytes(remote_state, 17);
246
247 ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X",
248 remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5],
249 remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11],
250 remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17]);
251
252 auto transmit = this->transmitter_->transmit();
253 auto *data = transmit.get_data();
254
255 data->set_carrier_frequency(38000);
256 // repeat twice
257 for (uint8_t r = 0; r < 2; r++) {
258 // Header
259 data->mark(MITSUBISHI_HEADER_MARK);
260 data->space(MITSUBISHI_HEADER_SPACE);
261 // Data
262 data->item_bytes<MITSUBISHI_BIT_TIMINGS>(remote_state, sizeof(remote_state));
263 // Footer
264 if (r == 0) {
265 data->mark(MITSUBISHI_BIT_MARK);
266 data->space(MITSUBISHI_MIN_GAP); // Pause before repeating
267 }
268 }
269 data->mark(MITSUBISHI_BIT_MARK);
270
271 transmit.perform();
272}
273
274bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; }
275
277 uint8_t state_frame[MITSUBISHI_FRAME_SIZE];
278
279 if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) {
280 ESP_LOGV(TAG, "Header fail");
281 return false;
282 }
283
284 // Check Header && Footer. Byte 16 only ever carries the left vane position (bits 3,4,5).
285 if (!data.expect_bytes<MITSUBISHI_BIT_TIMINGS>(state_frame, sizeof(state_frame)) ||
286 state_frame[0] != MITSUBISHI_BYTE00 || state_frame[1] != MITSUBISHI_BYTE01 ||
287 state_frame[2] != MITSUBISHI_BYTE02 || state_frame[3] != MITSUBISHI_BYTE03 ||
288 state_frame[4] != MITSUBISHI_BYTE04 || state_frame[13] != MITSUBISHI_BYTE13 ||
289 (state_frame[16] & ~MITSUBISHI_VERTICAL_VANE_MASK) != 0) {
290 ESP_LOGV(TAG, "Bit decode or bytes 0,1,2,3,4,13 or 16 fail");
291 return false;
292 }
293
294 // On/Off and Mode
295 if (state_frame[5] == MITSUBISHI_OFF) {
297 } else {
298 switch (state_frame[6]) {
301 break;
304 break;
307 break;
310 break;
313 break;
314 }
315 }
316
317 // Temp
318 this->target_temperature = decode_temperature(state_frame[7], this->fahrenheit_compatibility_);
319
320 // Fan
321 uint8_t fan = state_frame[9] & 0x07; //(Bit 0,1,2 = Speed)
322 // Map of Climate fan mode to this device expected value
323 // For 3Level: Low = 1, Medium = 2, High = 3
324 // For 4Level: Low = 1, Middle = 2, Medium = 3, High = 4
325 // For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
326 // For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
327 climate::ClimateFanMode modes_mapping[8] = {
336 this->fan_mode = modes_mapping[fan];
337
338 // Wide Vane
339 uint8_t wide_vane = state_frame[8] & 0xF0; // Bits 4,5,6,7
340 switch (wide_vane) {
343 break;
344 default:
346 break;
347 }
348
349 // Vertical Vane
350 // On dual vane heads, the left vane is in [16] and right vane is in [9]. Left is ignored here because
351 // the swing_mode enum doesn't convey that level of detail.
352 uint8_t vertical_vane = state_frame[9] & MITSUBISHI_VERTICAL_VANE_MASK;
353 switch (vertical_vane) {
357 } else {
359 }
360 break;
361 }
362
363 switch (state_frame[14]) {
366 break;
369 break;
370 }
371
372 ESP_LOGV_HEX(TAG, state_frame, MITSUBISHI_FRAME_SIZE, MITSUBISHI_FRAME_SIZE, "Receiving: %s");
373
374 this->publish_state();
375 return true;
376}
377
378} // 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: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
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:314
climate::ClimateModeMask modes_
Definition climate_ir.h:81
bool on_receive(remote_base::RemoteReceiveData data) override
VerticalDirection default_vertical_direction_
Definition mitsubishi.h:96
HorizontalDirection default_horizontal_direction_
Definition mitsubishi.h:95
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_MIN_GAP
ESPHOME_LOG_TAG(TAG, "mitsubishi.climate")
const uint8_t MITSUBISHI_MODE_AUTO
const uint8_t MITSUBISHI_MODE_COOL
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:12
const uint8_t MITSUBISHI_NIGHTMODE
const uint8_t MITSUBISHI_TEMP_MIN
Definition mitsubishi.h:11
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_MODE_FAN_ONLY
const uint8_t MITSUBISHI_MODE_A_AUTO
const uint8_t MITSUBISHI_VERTICAL_VANE_MASK
const uint16_t MITSUBISHI_BIT_MARK
const uint8_t MITSUBISHI_MODE_HEAT
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
constexpr float celsius_to_fahrenheit(float value)
Convert degrees Celsius to degrees Fahrenheit.
Definition helpers.h:1785
constexpr float fahrenheit_to_celsius(float value)
Convert degrees Fahrenheit to degrees Celsius.
Definition helpers.h:1787
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51
A bit is a mark (one_mark or zero_mark, both bit_mark unless set) and a space (one_space or zero_spac...
Definition remote_base.h:29