[mitsubishi_cn105] Refactor property encoding/decoding (#16709)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Boris Krivonog
2026-08-10 18:58:34 -05:00
committed by GitHub
co-authored by J. Nick Koston
parent 2a0f2d59f0
commit 007c677da1
5 changed files with 374 additions and 252 deletions
@@ -4,6 +4,7 @@
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include "mitsubishi_cn105_properties.h"
namespace esphome::mitsubishi_cn105 { namespace esphome::mitsubishi_cn105 {
@@ -11,8 +12,6 @@ static const char *const TAG = "mitsubishi_cn105.driver";
static constexpr uint32_t RESPONSE_TIMEOUT_MS = 2000; static constexpr uint32_t RESPONSE_TIMEOUT_MS = 2000;
static constexpr uint8_t TARGET_TEMPERATURE_ENC_A_OFFSET = 31;
static constexpr size_t REQUEST_PAYLOAD_LEN = 0x10; static constexpr size_t REQUEST_PAYLOAD_LEN = 0x10;
static constexpr size_t HEADER_LEN = 5; static constexpr size_t HEADER_LEN = 5;
static constexpr uint8_t PREAMBLE = 0xFC; static constexpr uint8_t PREAMBLE = 0xFC;
@@ -31,86 +30,6 @@ static constexpr uint8_t STATUS_MSG_TELEMETRY = 0x03;
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41; static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41;
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61; static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61;
template<auto Unknown, size_t N> struct LookupMap {
using value_type = decltype(Unknown);
static constexpr auto UNKNOWN_VALUE = Unknown;
const std::array<value_type, N> table;
constexpr value_type lookup(uint8_t raw) const { return (raw < N) ? this->table[raw] : UNKNOWN_VALUE; }
constexpr bool reverse_lookup(value_type value, uint8_t &out) const {
static_assert(N <= std::numeric_limits<uint8_t>::max());
if (value == UNKNOWN_VALUE) {
return false;
}
for (uint8_t i = 0; i < static_cast<uint8_t>(N); ++i) {
if (this->table[i] == value) {
out = i;
return true;
}
}
return false;
}
constexpr bool is_valid(value_type value) const {
uint8_t raw;
return reverse_lookup(value, raw);
}
};
template<auto Unknown, class T, std::size_t N> static constexpr auto make_map(const T (&values)[N]) {
return LookupMap<Unknown, N>{std::to_array(values)};
}
static constexpr auto PROTOCOL_MODE_MAP = make_map<MitsubishiCN105::Mode::UNKNOWN>({
MitsubishiCN105::Mode::UNKNOWN, // 0x00
MitsubishiCN105::Mode::HEAT, // 0x01
MitsubishiCN105::Mode::DRY, // 0x02
MitsubishiCN105::Mode::COOL, // 0x03
MitsubishiCN105::Mode::UNKNOWN, // 0x04
MitsubishiCN105::Mode::UNKNOWN, // 0x05
MitsubishiCN105::Mode::UNKNOWN, // 0x06
MitsubishiCN105::Mode::FAN_ONLY, // 0x07
MitsubishiCN105::Mode::AUTO // 0x08
});
static constexpr auto PROTOCOL_FAN_MODE_MAP = make_map<MitsubishiCN105::FanMode::UNKNOWN>({
MitsubishiCN105::FanMode::AUTO, // 0x00
MitsubishiCN105::FanMode::QUIET, // 0x01
MitsubishiCN105::FanMode::SPEED_1, // 0x02
MitsubishiCN105::FanMode::SPEED_2, // 0x03
MitsubishiCN105::FanMode::UNKNOWN, // 0x04
MitsubishiCN105::FanMode::SPEED_3, // 0x05
MitsubishiCN105::FanMode::SPEED_4 // 0x06
});
static constexpr auto PROTOCOL_VANE_MODE_MAP = make_map<MitsubishiCN105::VaneMode::UNKNOWN>({
MitsubishiCN105::VaneMode::AUTO, // 0x00
MitsubishiCN105::VaneMode::POSITION_1, // 0x01
MitsubishiCN105::VaneMode::POSITION_2, // 0x02
MitsubishiCN105::VaneMode::POSITION_3, // 0x03
MitsubishiCN105::VaneMode::POSITION_4, // 0x04
MitsubishiCN105::VaneMode::POSITION_5, // 0x05
MitsubishiCN105::VaneMode::UNKNOWN, // 0x06
MitsubishiCN105::VaneMode::SWING // 0x07
});
static constexpr auto PROTOCOL_WIDE_VANE_MODE_MAP = make_map<MitsubishiCN105::WideVaneMode::UNKNOWN>({
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x00
MitsubishiCN105::WideVaneMode::FAR_LEFT, // 0x01
MitsubishiCN105::WideVaneMode::LEFT, // 0x02
MitsubishiCN105::WideVaneMode::CENTER, // 0x03
MitsubishiCN105::WideVaneMode::RIGHT, // 0x04
MitsubishiCN105::WideVaneMode::FAR_RIGHT, // 0x05
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x06
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x07
MitsubishiCN105::WideVaneMode::LEFT_RIGHT, // 0x08
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x09
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x0A
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x0B
MitsubishiCN105::WideVaneMode::SWING // 0x0C
});
static constexpr uint8_t checksum(const uint8_t *bytes, size_t length) { static constexpr uint8_t checksum(const uint8_t *bytes, size_t length) {
return static_cast<uint8_t>(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0})); return static_cast<uint8_t>(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0}));
} }
@@ -124,10 +43,6 @@ static constexpr auto make_packet(uint8_t type, const std::array<uint8_t, Payloa
return packet; return packet;
} }
static constexpr float decode_temperature(int temp_a, int temp_b, int delta) {
return temp_b != 0 ? (temp_b - 128) / 2.0f : delta + temp_a;
}
static constexpr auto CONNECT_PACKET = make_packet(PACKET_TYPE_CONNECT_REQUEST, CONNECT_REQUEST_PAYLOAD); static constexpr auto CONNECT_PACKET = make_packet(PACKET_TYPE_CONNECT_REQUEST, CONNECT_REQUEST_PAYLOAD);
void MitsubishiCN105::initialize() { this->set_state_(State::CONNECTING); } void MitsubishiCN105::initialize() { this->set_state_(State::CONNECTING); }
@@ -277,14 +192,14 @@ bool MitsubishiCN105::should_request_telemetry_() const {
return (get_loop_time_ms() - *this->last_telemetry_update_ms_) >= this->telemetry_request_min_interval_ms_; return (get_loop_time_ms() - *this->last_telemetry_update_ms_) >= this->telemetry_request_min_interval_ms_;
} }
void MitsubishiCN105::send_packet_(const uint8_t *packet, size_t len) { void MitsubishiCN105::send_packet_(std::span<const uint8_t> packet) {
FrameParser::dump_buffer_vv("TX", packet, len); FrameParser::dump_buffer_vv("TX", packet.data(), packet.size());
this->device_.write_array(packet, len); this->device_.write_array(packet.data(), packet.size());
this->operation_start_ms_ = get_loop_time_ms(); this->operation_start_ms_ = get_loop_time_ms();
} }
void MitsubishiCN105::update_status_() { void MitsubishiCN105::update_status_() {
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload = {this->current_status_msg_type_}; std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{this->current_status_msg_type_};
this->send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload)); this->send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload));
} }
@@ -336,12 +251,22 @@ bool MitsubishiCN105::process_status_packet_(const uint8_t *payload, size_t len)
} }
bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len) { bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len) {
Property::Decoder decoder{std::span{payload, len}, this->property_context_, this->pending_updates_};
switch (msg_type) { switch (msg_type) {
case STATUS_MSG_SETTINGS: case STATUS_MSG_SETTINGS:
return this->parse_status_settings_(payload, len); if (!decoder.decode_settings(this->status_)) {
ESP_LOGVV(TAG, "RX settings payload too short");
return false;
}
return true;
case STATUS_MSG_TELEMETRY: case STATUS_MSG_TELEMETRY:
return this->parse_status_telemetry_(payload, len); if (!decoder.decode_room_temperature(this->status_)) {
ESP_LOGVV(TAG, "RX telemetry payload too short");
return false;
}
this->last_telemetry_update_ms_ = get_loop_time_ms();
return true;
default: default:
ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type); ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type);
@@ -349,54 +274,6 @@ bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *pay
} }
} }
bool MitsubishiCN105::parse_status_settings_(const uint8_t *payload, size_t len) {
if (len <= 10) {
ESP_LOGVV(TAG, "RX settings payload too short");
return false;
}
if (!this->pending_updates_.contains(UpdateFlag::POWER)) {
this->status_.power_on = payload[2] != 0;
}
this->use_temperature_encoding_b_ = payload[10] != 0;
if (!this->pending_updates_.contains(UpdateFlag::TEMPERATURE)) {
this->status_.target_temperature = decode_temperature(-payload[4], payload[10], TARGET_TEMPERATURE_ENC_A_OFFSET);
}
if (!this->pending_updates_.contains(UpdateFlag::MODE)) {
const bool i_see = payload[3] > 0x08;
this->status_.mode = PROTOCOL_MODE_MAP.lookup(payload[3] - (i_see ? 0x08 : 0));
}
if (!this->pending_updates_.contains(UpdateFlag::FAN)) {
this->status_.fan_mode = PROTOCOL_FAN_MODE_MAP.lookup(payload[5]);
}
if (!this->pending_updates_.contains(UpdateFlag::VANE)) {
this->status_.vane_mode = PROTOCOL_VANE_MODE_MAP.lookup(payload[6]);
}
this->set_wide_vane_high_bit_ = (payload[9] & 0xF0) == 0x80;
if (!this->pending_updates_.contains(UpdateFlag::WIDE_VANE)) {
this->status_.wide_vane_mode = PROTOCOL_WIDE_VANE_MODE_MAP.lookup(payload[9] & 0x0F);
}
return true;
}
bool MitsubishiCN105::parse_status_telemetry_(const uint8_t *payload, size_t len) {
if (len <= 5) {
ESP_LOGVV(TAG, "RX telemetry payload too short");
return false;
}
this->status_.room_temperature = decode_temperature(payload[2], payload[5], 10);
this->last_telemetry_update_ms_ = get_loop_time_ms();
return true;
}
void MitsubishiCN105::set_remote_temperature(float temperature) { void MitsubishiCN105::set_remote_temperature(float temperature) {
if (std::isnan(temperature)) { if (std::isnan(temperature)) {
ESP_LOGD(TAG, "Ignoring NaN remote temperature"); ESP_LOGD(TAG, "Ignoring NaN remote temperature");
@@ -415,12 +292,12 @@ void MitsubishiCN105::clear_remote_temperature() {
void MitsubishiCN105::set_remote_temperature_half_deg_(uint8_t temperature_half_deg) { void MitsubishiCN105::set_remote_temperature_half_deg_(uint8_t temperature_half_deg) {
this->remote_temperature_half_deg_ = temperature_half_deg; this->remote_temperature_half_deg_ = temperature_half_deg;
this->pending_updates_.set(UpdateFlag::REMOTE_TEMPERATURE); this->pending_updates_.set(Property::Temperature::Remote::ID);
} }
void MitsubishiCN105::set_power(bool power_on) { void MitsubishiCN105::set_power(bool power_on) {
this->status_.power_on = power_on; this->status_.power_on = power_on;
this->pending_updates_.set(UpdateFlag::POWER); this->pending_updates_.set(Property::Power::ID);
} }
void MitsubishiCN105::set_target_temperature(float target_temperature) { void MitsubishiCN105::set_target_temperature(float target_temperature) {
@@ -429,101 +306,42 @@ void MitsubishiCN105::set_target_temperature(float target_temperature) {
return; return;
} }
this->status_.target_temperature = target_temperature; this->status_.target_temperature = target_temperature;
this->pending_updates_.set(UpdateFlag::TEMPERATURE); this->pending_updates_.set(Property::Temperature::Target::ID);
} }
void MitsubishiCN105::set_mode(Mode mode) { void MitsubishiCN105::set_mode(Mode mode) {
if (!PROTOCOL_MODE_MAP.is_valid(mode)) { if (!Property::Mode::validate_and_set(mode, this->status_, this->pending_updates_)) {
ESP_LOGD(TAG, "Setting invalid mode: %u", static_cast<uint8_t>(mode)); ESP_LOGD(TAG, "Ignoring invalid mode: %u", static_cast<uint8_t>(mode));
return;
} }
this->status_.mode = mode;
this->pending_updates_.set(UpdateFlag::MODE);
} }
void MitsubishiCN105::set_fan_mode(FanMode fan_mode) { void MitsubishiCN105::set_fan_mode(FanMode fan_mode) {
if (!PROTOCOL_FAN_MODE_MAP.is_valid(fan_mode)) { if (!Property::FanMode::validate_and_set(fan_mode, this->status_, this->pending_updates_)) {
ESP_LOGD(TAG, "Setting invalid fan mode: %u", static_cast<uint8_t>(fan_mode)); ESP_LOGD(TAG, "Ignoring invalid fan mode: %u", static_cast<uint8_t>(fan_mode));
return;
} }
this->status_.fan_mode = fan_mode;
this->pending_updates_.set(UpdateFlag::FAN);
} }
void MitsubishiCN105::set_vane_mode(VaneMode vane_mode) { void MitsubishiCN105::set_vane_mode(VaneMode vane_mode) {
if (!PROTOCOL_VANE_MODE_MAP.is_valid(vane_mode)) { if (!Property::VaneMode::validate_and_set(vane_mode, this->status_, this->pending_updates_)) {
ESP_LOGD(TAG, "Setting invalid vane mode: %u", static_cast<uint8_t>(vane_mode)); ESP_LOGD(TAG, "Ignoring invalid vane mode: %u", static_cast<uint8_t>(vane_mode));
return;
} }
this->status_.vane_mode = vane_mode;
this->pending_updates_.set(UpdateFlag::VANE);
} }
void MitsubishiCN105::set_wide_vane_mode(WideVaneMode wide_vane_mode) { void MitsubishiCN105::set_wide_vane_mode(WideVaneMode wide_vane_mode) {
if (!PROTOCOL_WIDE_VANE_MODE_MAP.is_valid(wide_vane_mode)) { if (!Property::WideVaneMode::validate_and_set(wide_vane_mode, this->status_, this->pending_updates_)) {
ESP_LOGD(TAG, "Setting invalid wide vane mode: %u", static_cast<uint8_t>(wide_vane_mode)); ESP_LOGD(TAG, "Ignoring invalid wide vane mode: %u", static_cast<uint8_t>(wide_vane_mode));
return;
} }
this->status_.wide_vane_mode = wide_vane_mode;
this->pending_updates_.set(UpdateFlag::WIDE_VANE);
} }
void MitsubishiCN105::apply_settings_() { void MitsubishiCN105::apply_settings_() {
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{}; std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{};
Property::Encoder encoder{payload.data(), this->property_context_, this->pending_updates_};
// Apply all other pending settings first; handle REMOTE_TEMPERATURE last // Apply all other pending settings first; handle REMOTE_TEMPERATURE last
if (this->pending_updates_.contains_only(UpdateFlag::REMOTE_TEMPERATURE)) { if (this->pending_updates_.contains_only(Property::Temperature::Remote::ID)) {
payload[0] = 0x07; encoder.encode_remote_temperature(this->remote_temperature_half_deg_);
if (this->remote_temperature_half_deg_ == REMOTE_TEMPERATURE_DISABLED) {
payload[3] = 0x80;
} else { } else {
payload[1] = 0x01; encoder.encode_settings(this->status_);
payload[2] = static_cast<uint8_t>(this->remote_temperature_half_deg_ - 16);
payload[3] = static_cast<uint8_t>(this->remote_temperature_half_deg_ + 128);
}
this->pending_updates_.clear(UpdateFlag::REMOTE_TEMPERATURE);
} else {
payload[0] = 0x01;
if (this->pending_updates_.contains(UpdateFlag::POWER)) {
payload[1] |= 0x01;
payload[3] = this->status_.power_on ? 0x01 : 0x00;
}
if (this->pending_updates_.contains(UpdateFlag::TEMPERATURE)) {
payload[1] |= 0x04;
if (this->use_temperature_encoding_b_) {
payload[14] = static_cast<uint8_t>(std::round(this->status_.target_temperature * 2.0f) + 128);
} else {
payload[5] =
static_cast<uint8_t>(TARGET_TEMPERATURE_ENC_A_OFFSET - std::round(this->status_.target_temperature));
}
}
if (this->pending_updates_.contains(UpdateFlag::MODE) &&
PROTOCOL_MODE_MAP.reverse_lookup(this->status_.mode, payload[4])) {
payload[1] |= 0x02;
}
if (this->pending_updates_.contains(UpdateFlag::FAN) &&
PROTOCOL_FAN_MODE_MAP.reverse_lookup(this->status_.fan_mode, payload[6])) {
payload[1] |= 0x08;
}
if (this->pending_updates_.contains(UpdateFlag::VANE) &&
PROTOCOL_VANE_MODE_MAP.reverse_lookup(this->status_.vane_mode, payload[7])) {
payload[1] |= 0x10;
}
if (this->pending_updates_.contains(UpdateFlag::WIDE_VANE) &&
PROTOCOL_WIDE_VANE_MODE_MAP.reverse_lookup(this->status_.wide_vane_mode, payload[13])) {
payload[2] |= 0x01;
if (this->set_wide_vane_high_bit_) {
payload[13] |= 0x80;
}
}
this->pending_updates_.clear(UpdateFlag::POWER, UpdateFlag::TEMPERATURE, UpdateFlag::MODE, UpdateFlag::FAN,
UpdateFlag::VANE, UpdateFlag::WIDE_VANE);
} }
this->send_packet_(make_packet(PACKET_TYPE_WRITE_SETTINGS_REQUEST, payload)); this->send_packet_(make_packet(PACKET_TYPE_WRITE_SETTINGS_REQUEST, payload));
@@ -5,6 +5,7 @@
#include <cmath> #include <cmath>
#include <optional> #include <optional>
#include <span>
namespace esphome::mitsubishi_cn105 { namespace esphome::mitsubishi_cn105 {
@@ -121,44 +122,47 @@ class MitsubishiCN105 {
uint8_t read_pos_{0}; uint8_t read_pos_{0};
}; };
enum class UpdateFlag : uint8_t { enum class PropertyId : uint8_t {
TEMPERATURE = 0, TEMPERATURE = 0,
POWER = 1, POWER = 1,
MODE = 2, MODE = 2,
FAN = 3, FAN = 3,
VANE = 4, VANE = 4,
WIDE_VANE = 5, WIDE_VANE = 5,
REMOTE_TEMPERATURE = 6, REMOTE_TEMPERATURE = 6
}; };
struct UpdateFlags { struct UpdateFlags {
template<typename... Flags> void set(Flags... flags) { (this->mask_.insert(flags), ...); } void set(PropertyId id) { this->mask_.insert(id); }
template<typename... Flags> void clear(Flags... flags) { (this->mask_.erase(flags), ...); } void clear(PropertyId id) { this->mask_.erase(id); }
bool any() const { return !this->mask_.empty(); } bool any() const { return !this->mask_.empty(); }
bool contains(UpdateFlag flag) const { return this->mask_.count(flag); } bool contains(PropertyId id) const { return this->mask_.count(id); }
bool contains_only(UpdateFlag flag) const { return this->mask_.get_mask() == Mask{flag}.get_mask(); } bool contains_only(PropertyId id) const { return this->mask_.get_mask() == Mask{id}.get_mask(); }
protected: protected:
using Mask = using Mask =
FiniteSetMask<UpdateFlag, DefaultBitPolicy<UpdateFlag, static_cast<int>(UpdateFlag::REMOTE_TEMPERATURE) + 1>>; FiniteSetMask<PropertyId, DefaultBitPolicy<PropertyId, static_cast<int>(PropertyId::REMOTE_TEMPERATURE) + 1>>;
Mask mask_; Mask mask_;
}; };
struct PropertyContext {
bool use_temperature_encoding_b{false};
bool set_wide_vane_high_bit{false};
};
friend struct Property;
void set_state_(State new_state); void set_state_(State new_state);
void did_transition_(State to); void did_transition_(State to);
bool process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len); bool process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len);
bool process_status_packet_(const uint8_t *payload, size_t len); bool process_status_packet_(const uint8_t *payload, size_t len);
bool parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len); bool parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len);
bool parse_status_settings_(const uint8_t *payload, size_t len); void send_packet_(std::span<const uint8_t> packet);
bool parse_status_telemetry_(const uint8_t *payload, size_t len);
void send_packet_(const uint8_t *packet, size_t len);
void update_status_(); void update_status_();
bool should_request_telemetry_() const; bool should_request_telemetry_() const;
void apply_settings_(); void apply_settings_();
bool has_timed_out_(uint32_t timeout) const { return ((get_loop_time_ms() - this->operation_start_ms_) >= timeout); } bool has_timed_out_(uint32_t timeout) const { return ((get_loop_time_ms() - this->operation_start_ms_) >= timeout); }
void set_remote_temperature_half_deg_(uint8_t temperature_half_deg); void set_remote_temperature_half_deg_(uint8_t temperature_half_deg);
template<typename T> void send_packet_(const T &packet) { this->send_packet_(packet.data(), packet.size()); }
static bool should_transition(State from, State to); static bool should_transition(State from, State to);
static const LogString *state_to_string(State state); static const LogString *state_to_string(State state);
@@ -175,8 +179,7 @@ class MitsubishiCN105 {
Status status_{}; Status status_{};
State state_{State::NOT_CONNECTED}; State state_{State::NOT_CONNECTED};
UpdateFlags pending_updates_; UpdateFlags pending_updates_;
bool use_temperature_encoding_b_{false}; PropertyContext property_context_;
bool set_wide_vane_high_bit_{false};
FrameParser frame_parser_; FrameParser frame_parser_;
uint8_t current_status_msg_type_{0}; uint8_t current_status_msg_type_{0};
@@ -0,0 +1,302 @@
#pragma once
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include "mitsubishi_cn105.h"
namespace esphome::mitsubishi_cn105 {
template<auto Unknown, size_t N> struct LookupMap {
using value_type = decltype(Unknown);
const std::array<value_type, N> table;
constexpr value_type lookup(uint8_t raw) const { return (raw < N) ? this->table[raw] : Unknown; }
constexpr bool reverse_lookup(value_type value, uint8_t &out) const {
static_assert(N <= std::numeric_limits<uint8_t>::max());
if (value == Unknown) {
return false;
}
for (uint8_t i = 0; i < static_cast<uint8_t>(N); ++i) {
if (this->table[i] == value) {
out = i;
return true;
}
}
return false;
}
};
template<auto Unknown, class T, std::size_t N> static constexpr auto make_map(const T (&values)[N]) {
return LookupMap<Unknown, N>{std::to_array(values)};
}
struct Property {
using PropertyId = MitsubishiCN105::PropertyId;
using Status = MitsubishiCN105::Status;
using PropertyContext = MitsubishiCN105::PropertyContext;
struct Power {
static constexpr auto ID = PropertyId::POWER;
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {}
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
status.power_on = payload[2] != 0;
}
static void encode(uint8_t *payload, const Status &status, const PropertyContext &ctx) {
payload[1] |= 0x01;
payload[3] = status.power_on ? 0x01 : 0x00;
}
};
struct Temperature {
struct Target {
static constexpr auto ID = PropertyId::TEMPERATURE;
static constexpr uint8_t TARGET_TEMPERATURE_ENC_A_OFFSET = 31;
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {
ctx.use_temperature_encoding_b = payload[10] != 0;
}
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
status.target_temperature = Temperature::decode(-payload[4], payload[10], TARGET_TEMPERATURE_ENC_A_OFFSET);
}
static void encode(uint8_t *payload, const Status &status, const PropertyContext &ctx) {
payload[1] |= 0x04;
if (ctx.use_temperature_encoding_b) {
payload[14] = static_cast<uint8_t>(std::round(status.target_temperature * 2.0f) + 128);
} else {
payload[5] = static_cast<uint8_t>(TARGET_TEMPERATURE_ENC_A_OFFSET - std::round(status.target_temperature));
}
}
};
struct Room {
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {}
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
status.room_temperature = Temperature::decode(payload[2], payload[5], 10);
}
};
struct Remote {
static constexpr auto ID = PropertyId::REMOTE_TEMPERATURE;
static void encode(uint8_t *payload, uint8_t remote_temperature_half_deg, const PropertyContext &) {
if (remote_temperature_half_deg == MitsubishiCN105::REMOTE_TEMPERATURE_DISABLED) {
payload[3] = 0x80;
} else {
payload[1] = 0x01;
payload[2] = static_cast<uint8_t>(remote_temperature_half_deg - 16);
payload[3] = static_cast<uint8_t>(remote_temperature_half_deg + 128);
}
}
};
protected:
static constexpr float decode(int temp_a, int temp_b, int delta) {
return temp_b != 0 ? (temp_b - 128) / 2.0f : delta + temp_a;
}
};
template<typename Derived, auto Field> struct Lookup {
using Value = std::remove_cvref_t<decltype(std::declval<Status>().*Field)>;
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {}
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
status.*Field = Derived::MAP.lookup(Derived::decode_raw(payload, ctx));
}
static void encode(uint8_t *payload, const Status &status, const PropertyContext &ctx) {
uint8_t raw;
if (Derived::MAP.reverse_lookup(status.*Field, raw)) {
Derived::encode_raw(payload, raw, ctx);
}
}
template<typename Mask> static bool validate_and_set(Value value, Status &status, Mask &mask) {
uint8_t raw;
if (!Derived::MAP.reverse_lookup(value, raw)) {
return false;
}
status.*Field = value;
mask.set(Derived::ID);
return true;
}
private:
friend Derived;
constexpr Lookup() = default;
};
struct Mode : Lookup<Mode, &Status::mode> {
static constexpr auto ID = PropertyId::MODE;
static constexpr auto MAP = make_map<MitsubishiCN105::Mode::UNKNOWN>({
MitsubishiCN105::Mode::UNKNOWN, // 0x00
MitsubishiCN105::Mode::HEAT, // 0x01
MitsubishiCN105::Mode::DRY, // 0x02
MitsubishiCN105::Mode::COOL, // 0x03
MitsubishiCN105::Mode::UNKNOWN, // 0x04
MitsubishiCN105::Mode::UNKNOWN, // 0x05
MitsubishiCN105::Mode::UNKNOWN, // 0x06
MitsubishiCN105::Mode::FAN_ONLY, // 0x07
MitsubishiCN105::Mode::AUTO // 0x08
});
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) {
const bool i_see = payload[3] > 0x08;
return payload[3] - (i_see ? 0x08 : 0);
}
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &) {
payload[1] |= 0x02;
payload[4] = raw;
}
};
struct FanMode : Lookup<FanMode, &Status::fan_mode> {
static constexpr auto ID = PropertyId::FAN;
static constexpr auto MAP = make_map<MitsubishiCN105::FanMode::UNKNOWN>({
MitsubishiCN105::FanMode::AUTO, // 0x00
MitsubishiCN105::FanMode::QUIET, // 0x01
MitsubishiCN105::FanMode::SPEED_1, // 0x02
MitsubishiCN105::FanMode::SPEED_2, // 0x03
MitsubishiCN105::FanMode::UNKNOWN, // 0x04
MitsubishiCN105::FanMode::SPEED_3, // 0x05
MitsubishiCN105::FanMode::SPEED_4 // 0x06
});
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) { return payload[5]; }
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &) {
payload[1] |= 0x08;
payload[6] = raw;
}
};
struct VaneMode : Lookup<VaneMode, &Status::vane_mode> {
static constexpr auto ID = PropertyId::VANE;
static constexpr auto MAP = make_map<MitsubishiCN105::VaneMode::UNKNOWN>({
MitsubishiCN105::VaneMode::AUTO, // 0x00
MitsubishiCN105::VaneMode::POSITION_1, // 0x01
MitsubishiCN105::VaneMode::POSITION_2, // 0x02
MitsubishiCN105::VaneMode::POSITION_3, // 0x03
MitsubishiCN105::VaneMode::POSITION_4, // 0x04
MitsubishiCN105::VaneMode::POSITION_5, // 0x05
MitsubishiCN105::VaneMode::UNKNOWN, // 0x06
MitsubishiCN105::VaneMode::SWING // 0x07
});
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) { return payload[6]; }
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &) {
payload[1] |= 0x10;
payload[7] = raw;
}
};
struct WideVaneMode : Lookup<WideVaneMode, &Status::wide_vane_mode> {
static constexpr auto ID = PropertyId::WIDE_VANE;
static constexpr auto MAP = make_map<MitsubishiCN105::WideVaneMode::UNKNOWN>({
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x00
MitsubishiCN105::WideVaneMode::FAR_LEFT, // 0x01
MitsubishiCN105::WideVaneMode::LEFT, // 0x02
MitsubishiCN105::WideVaneMode::CENTER, // 0x03
MitsubishiCN105::WideVaneMode::RIGHT, // 0x04
MitsubishiCN105::WideVaneMode::FAR_RIGHT, // 0x05
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x06
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x07
MitsubishiCN105::WideVaneMode::LEFT_RIGHT, // 0x08
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x09
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x0A
MitsubishiCN105::WideVaneMode::UNKNOWN, // 0x0B
MitsubishiCN105::WideVaneMode::SWING // 0x0C
});
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {
ctx.set_wide_vane_high_bit = (payload[9] & 0xF0) == 0x80;
}
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) { return payload[9] & 0x0F; }
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &ctx) {
payload[2] |= 0x01;
payload[13] = ctx.set_wide_vane_high_bit ? raw | 0x80 : raw;
}
};
template<typename Mask> struct Decoder {
const std::span<const uint8_t> payload;
PropertyContext &context;
const Mask &pending_writes;
bool ESPHOME_ALWAYS_INLINE decode_settings(Status &status) {
if (this->payload.size() <= 10) {
return false;
}
this->decode_<Power, Temperature::Target, Mode, FanMode, VaneMode, WideVaneMode>(status);
return true;
}
bool ESPHOME_ALWAYS_INLINE decode_room_temperature(Status &status) {
if (this->payload.size() <= 5) {
return false;
}
this->decode_<Temperature::Room>(status);
return true;
}
protected:
template<typename T, typename Out> ESPHOME_ALWAYS_INLINE void decode_one_(Out &out) {
T::decode_context(this->context, this->payload.data());
if constexpr (requires { T::ID; }) {
if (this->pending_writes.contains(T::ID)) {
return;
}
}
T::decode(out, this->payload.data(), this->context);
}
template<typename... T, typename Out> void ESPHOME_ALWAYS_INLINE decode_(Out &out) {
(this->decode_one_<T>(out), ...);
}
};
template<typename Mask> struct Encoder {
uint8_t *payload;
const PropertyContext &context;
Mask &pending_writes;
void ESPHOME_ALWAYS_INLINE encode_settings(const Status &status) {
this->payload[0] = 0x01;
this->encode_and_clear_<Power, Temperature::Target, WideVaneMode, VaneMode, Mode, FanMode>(status);
}
void ESPHOME_ALWAYS_INLINE encode_remote_temperature(uint8_t remote_temperature_half_deg) {
this->payload[0] = 0x07;
this->encode_and_clear_<Temperature::Remote>(remote_temperature_half_deg);
}
protected:
template<typename... T, typename In> void ESPHOME_ALWAYS_INLINE encode_and_clear_(const In &in) {
(this->encode_one_<T>(in), ...);
(this->pending_writes.clear(T::ID), ...);
}
template<typename T, typename In> void encode_one_(const In &in) {
if (this->pending_writes.contains(T::ID)) {
T::encode(this->payload, in, this->context);
}
}
};
};
} // namespace esphome::mitsubishi_cn105
@@ -266,7 +266,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) {
ctx.sut.update(); ctx.sut.update();
EXPECT_TRUE(ctx.sut.status().power_on); EXPECT_TRUE(ctx.sut.status().power_on);
EXPECT_FALSE(ctx.sut.use_temperature_encoding_b_); EXPECT_FALSE(ctx.sut.property_context_.use_temperature_encoding_b);
EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f); EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f);
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::COOL); EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::COOL);
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::QUIET); EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::QUIET);
@@ -281,7 +281,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) {
ctx.sut.update(); ctx.sut.update();
EXPECT_FALSE(ctx.sut.status().power_on); EXPECT_FALSE(ctx.sut.status().power_on);
EXPECT_TRUE(ctx.sut.use_temperature_encoding_b_); EXPECT_TRUE(ctx.sut.property_context_.use_temperature_encoding_b);
EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f); EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f);
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::FAN_ONLY); EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::FAN_ONLY);
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::SPEED_4); EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::SPEED_4);
@@ -316,7 +316,7 @@ TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitNotSet) {
ctx.sut.update(); ctx.sut.update();
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER); EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
EXPECT_FALSE(ctx.sut.set_wide_vane_high_bit_); EXPECT_FALSE(ctx.sut.property_context_.set_wide_vane_high_bit);
} }
TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) { TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
@@ -328,7 +328,7 @@ TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
ctx.sut.update(); ctx.sut.update();
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER); EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
EXPECT_TRUE(ctx.sut.set_wide_vane_high_bit_); EXPECT_TRUE(ctx.sut.property_context_.set_wide_vane_high_bit);
} }
TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) { TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) {
@@ -354,7 +354,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedA) {
TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) { TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.property_context_.use_temperature_encoding_b = true;
ctx.sut.set_target_temperature(26.0f); ctx.sut.set_target_temperature(26.0f);
ctx.sut.apply_settings(); ctx.sut.apply_settings();
@@ -365,7 +365,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) {
TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) { TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.property_context_.use_temperature_encoding_b = true;
ctx.sut.set_target_temperature(26.5f); ctx.sut.set_target_temperature(26.5f);
ctx.sut.apply_settings(); ctx.sut.apply_settings();
@@ -416,7 +416,7 @@ TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitNotSet) {
TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) { TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
ctx.sut.set_wide_vane_high_bit_ = true; ctx.sut.property_context_.set_wide_vane_high_bit = true;
ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT); ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
ctx.sut.apply_settings(); ctx.sut.apply_settings();
@@ -445,7 +445,7 @@ TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) {
EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0); EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
// Write new values // Write new values
ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.property_context_.use_temperature_encoding_b = true;
ctx.sut.set_power(false); ctx.sut.set_power(false);
ctx.sut.set_target_temperature(25.0f); ctx.sut.set_target_temperature(25.0f);
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT); ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
@@ -508,7 +508,7 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
// Queue normal settings plus remote temperature together. // Queue normal settings plus remote temperature together.
ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.property_context_.use_temperature_encoding_b = true;
ctx.sut.set_power(false); ctx.sut.set_power(false);
ctx.sut.set_target_temperature(25.0f); ctx.sut.set_target_temperature(25.0f);
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT); ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
@@ -521,11 +521,11 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x0F, 0x00, 0x00, 0x01, 0x00, EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x0F, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0xBB)); 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0xBB));
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::POWER)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::POWER));
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::TEMPERATURE)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::TEMPERATURE));
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::MODE)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::MODE));
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::FAN)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::FAN));
// ACK the first write. Remote temperature should still be pending afterward. // ACK the first write. Remote temperature should still be pending afterward.
ctx.uart.tx.clear(); ctx.uart.tx.clear();
@@ -533,7 +533,7 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E}); 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
ASSERT_FALSE(ctx.sut.update()); ASSERT_FALSE(ctx.sut.update());
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
// The next apply sends the remote-temperature packet and clears the last pending flag. // The next apply sends the remote-temperature packet and clears the last pending flag.
ctx.uart.tx.clear(); ctx.uart.tx.clear();
@@ -557,7 +557,7 @@ TEST(MitsubishiCN105Tests, WriteTimeoutClearsStatusUpdateWaitCreditOnReconnect)
ASSERT_EQ(ctx.sut.status_update_wait_credit_ms_, 0); ASSERT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
// Interrupt that wait with a write so credit is accumulated. // Interrupt that wait with a write so credit is accumulated.
ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.property_context_.use_temperature_encoding_b = true;
ctx.sut.set_power(false); ctx.sut.set_power(false);
ctx.sut.set_target_temperature(25.0f); ctx.sut.set_target_temperature(25.0f);
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT); ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
@@ -581,25 +581,25 @@ TEST(MitsubishiCN105Tests, SetOutOfRangeRemoteRoomTempIsIgnored) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
ctx.sut.set_remote_temperature(7.0f); ctx.sut.set_remote_temperature(7.0f);
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
ctx.sut.set_remote_temperature(40.0f); ctx.sut.set_remote_temperature(40.0f);
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
ctx.sut.set_remote_temperature(NAN); ctx.sut.set_remote_temperature(NAN);
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
} }
TEST(MitsubishiCN105Tests, SetMinRemoteRoomTemp) { TEST(MitsubishiCN105Tests, SetMinRemoteRoomTemp) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
ctx.sut.set_remote_temperature(8.0f); ctx.sut.set_remote_temperature(8.0f);
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
} }
TEST(MitsubishiCN105Tests, SetMaxRemoteRoomTemp) { TEST(MitsubishiCN105Tests, SetMaxRemoteRoomTemp) {
MitsubishiCN105TestsContext ctx; MitsubishiCN105TestsContext ctx;
ctx.sut.set_remote_temperature(39.5f); ctx.sut.set_remote_temperature(39.5f);
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
} }
} // namespace esphome::mitsubishi_cn105::testing } // namespace esphome::mitsubishi_cn105::testing
+2 -3
View File
@@ -47,12 +47,11 @@ class TestableMitsubishiCN105 : public MitsubishiCN105 {
public: public:
using MitsubishiCN105::MitsubishiCN105; using MitsubishiCN105::MitsubishiCN105;
using MitsubishiCN105::State; using MitsubishiCN105::State;
using MitsubishiCN105::UpdateFlag; using MitsubishiCN105::PropertyId;
using MitsubishiCN105::state_; using MitsubishiCN105::state_;
using MitsubishiCN105::status_; using MitsubishiCN105::status_;
using MitsubishiCN105::operation_start_ms_; using MitsubishiCN105::operation_start_ms_;
using MitsubishiCN105::use_temperature_encoding_b_; using MitsubishiCN105::property_context_;
using MitsubishiCN105::set_wide_vane_high_bit_;
using MitsubishiCN105::status_update_wait_credit_ms_; using MitsubishiCN105::status_update_wait_credit_ms_;
using MitsubishiCN105::pending_updates_; using MitsubishiCN105::pending_updates_;