ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mistral_ir.cpp
Go to the documentation of this file.
1#include "mistral_ir.h"
3#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
7#include <cmath>
8
10
11ESPHOME_LOG_TAG(TAG, "mistral_ir.climate");
12
13// Fixed bytes of every frame; bytes 1, 3, 4, 5, 6 and 11 are filled in per state
14static const uint8_t FRAME_TEMPLATE[MISTRAL_FRAME_SIZE] PROGMEM = {0x56, 0x00, 0x08, 0x00, 0x00, 0x00,
15 0x00, 0x00, 0xE8, 0x00, 0x60, 0x00};
16static const uint8_t FIXED_BYTES[] PROGMEM = {0, 2, 7, 8, 9, 10};
17static const uint8_t CHECKSUM_BYTES[] PROGMEM = {1, 3, 4, 5, 6, 8};
18
19constexpr uint8_t MISTRAL_POWER_OFF = 0x00;
20constexpr uint8_t MISTRAL_POWER_ON = 0x20;
21constexpr uint8_t MISTRAL_TEMP_OFFSET = 0x9F;
22constexpr uint8_t MISTRAL_MODE_AUTO = 0x10;
23
24// The tables live in flash, so entries are copied out before use
25struct ModeCode {
27 uint8_t code;
28};
29static const ModeCode MODES[] PROGMEM = {
35};
36
37struct FanCode {
39 uint8_t byte1;
40 uint8_t byte6;
41};
42static const FanCode FANS[] PROGMEM = {
43 {climate::CLIMATE_FAN_AUTO, 0xA4, 0x00},
44 {climate::CLIMATE_FAN_LOW, 0x44, 0x40},
45 {climate::CLIMATE_FAN_MEDIUM, 0x24, 0xC0},
46 {climate::CLIMATE_FAN_HIGH, 0xA4, 0xA0},
47};
48
49template<typename T> static T load_entry(const T &slot) {
50 T entry;
51 progmem_memcpy(&entry, &slot, sizeof(T));
52 return entry;
53}
54
55template<typename T, size_t N, typename Pred> static optional<T> find_entry(const T (&table)[N], Pred pred) {
56 for (const T &slot : table) {
57 T entry = load_entry(slot);
58 if (pred(entry))
59 return entry;
60 }
61 return {};
62}
63
64static uint8_t compute_checksum(const uint8_t *frame) {
65 uint8_t sum = 0;
66 for (const uint8_t &idx : CHECKSUM_BYTES)
67 sum += reverse_bits(frame[progmem_read_byte(&idx)]);
68 return reverse_bits(sum);
69}
70
71static bool is_valid_frame(const uint8_t *frame) {
72 for (const uint8_t &slot : FIXED_BYTES) {
73 const uint8_t idx = progmem_read_byte(&slot);
74 if (frame[idx] != progmem_read_byte(&FRAME_TEMPLATE[idx]))
75 return false;
76 }
77 return frame[11] == compute_checksum(frame);
78}
79
81 const auto mode = find_entry(MODES, [this](const ModeCode &m) { return m.mode == this->mode; });
82 const climate::ClimateFanMode fan_mode = this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO);
83 const FanCode fan =
84 find_entry(FANS, [fan_mode](const FanCode &f) { return f.fan == fan_mode; }).value_or(load_entry(FANS[0]));
85 const uint8_t temp = lroundf(clamp<float>(this->target_temperature, MISTRAL_TEMP_MIN, MISTRAL_TEMP_MAX));
86
87 uint8_t frame[MISTRAL_FRAME_SIZE];
88 progmem_memcpy(frame, FRAME_TEMPLATE, sizeof(frame));
89 frame[1] = fan.byte1;
91 frame[4] = mode.has_value() ? mode->code : MISTRAL_MODE_AUTO;
92 frame[5] = reverse_bits(static_cast<uint8_t>(MISTRAL_TEMP_OFFSET - temp));
93 frame[6] = fan.byte6;
94 frame[11] = compute_checksum(frame);
95
96 auto transmit = this->transmitter_->transmit();
97 remote_base::AEHAProtocol().encode(transmit.get_data(), MISTRAL_ADDRESS, frame, sizeof(frame));
98 transmit.perform();
99}
100
102 auto aeha = remote_base::AEHAProtocol().decode(data);
103 if (!aeha.has_value() || aeha->address != MISTRAL_ADDRESS || aeha->data.size() != MISTRAL_FRAME_SIZE)
104 return false;
105
106 const uint8_t *frame = aeha->data.data();
107 if (!is_valid_frame(frame))
108 return false;
109
110 if (frame[3] == MISTRAL_POWER_OFF) {
112 this->publish_state();
113 return true;
114 }
115 if (frame[3] != MISTRAL_POWER_ON)
116 return false;
117
118 // Decode everything before touching the state so an unknown code leaves it untouched
119 const auto mode = find_entry(MODES, [frame](const ModeCode &m) { return m.code == frame[4]; });
120 const auto fan = find_entry(FANS, [frame](const FanCode &f) { return f.byte1 == frame[1] && f.byte6 == frame[6]; });
121 if (!mode.has_value() || !fan.has_value())
122 return false;
123
124 this->mode = mode->mode;
125 this->target_temperature =
127 this->fan_mode = fan->fan;
128
129 ESP_LOGV(TAG, "Received Mistral frame: mode=%d temp=%.0f fan=%d", static_cast<int>(this->mode),
130 this->target_temperature, static_cast<int>(fan->fan));
131 this->publish_state();
132 return true;
133}
134
135} // namespace esphome::mistral_ir
uint8_t m
Definition bl0906.h:1
ClimateMode mode
The active mode of the climate device.
Definition climate.h:304
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:298
float target_temperature
The target temperature of the climate device.
Definition climate.h:285
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:429
bool on_receive(remote_base::RemoteReceiveData data) override
optional< AEHAData > decode(RemoteReceiveData src)
void encode(RemoteTransmitData *dst, const AEHAData &data)
constexpr float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM
Definition bme680.cpp:26
ClimateMode
Enum for all modes a climate device can be in.
@ 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_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 MISTRAL_POWER_OFF
ESPHOME_LOG_TAG(TAG, "mistral_ir.climate")
constexpr uint8_t MISTRAL_TEMP_OFFSET
constexpr size_t MISTRAL_FRAME_SIZE
Definition mistral_ir.h:12
constexpr uint8_t MISTRAL_TEMP_MAX
Definition mistral_ir.h:9
constexpr uint8_t MISTRAL_POWER_ON
constexpr uint8_t MISTRAL_TEMP_MIN
Definition mistral_ir.h:8
constexpr uint8_t MISTRAL_MODE_AUTO
constexpr uint16_t MISTRAL_ADDRESS
Definition mistral_ir.h:11
void progmem_memcpy(void *dst, const void *src, size_t len)
Definition hal.h:56
uint8_t reverse_bits(uint8_t x)
Reverse the order of 8 bits.
Definition helpers.h:1019
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51