diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp index 3b29f66bf1..1a35495618 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "mitsubishi_cn105.h" @@ -8,6 +9,8 @@ static const char *const TAG = "mitsubishi_cn105.driver"; static constexpr uint32_t WRITE_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 HEADER_LEN = 5; static constexpr uint8_t PREAMBLE = 0xFC; @@ -23,6 +26,9 @@ static constexpr uint8_t PACKET_TYPE_STATUS_RESPONSE = 0x62; static constexpr uint8_t STATUS_MSG_SETTINGS = 0x02; static constexpr uint8_t STATUS_MSG_ROOM_TEMP = 0x03; +static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41; +static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61; + static constexpr std::array, 9> PROTOCOL_MODE_MAP = { std::nullopt, // 0x00 MitsubishiCN105::Mode::HEAT, // 0x01 @@ -50,6 +56,18 @@ static constexpr std::optional lookup(const std::array, N> & return (value < N) ? table[value] : std::nullopt; } +template +static constexpr bool reverse_lookup(const std::array, N> &table, T value, uint8_t &placeholder) { + for (size_t i = 0; i < N; ++i) { + const auto &table_value = table[i]; + if (table_value.has_value() && table_value == value) { + placeholder = i; + return true; + } + } + return false; +} + static constexpr uint8_t checksum(const uint8_t *bytes, size_t length) { return static_cast(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0})); } @@ -72,10 +90,16 @@ static constexpr auto CONNECT_PACKET = make_packet(PACKET_TYPE_CONNECT_REQUEST, void MitsubishiCN105::initialize() { this->set_state_(State::CONNECTING); } bool MitsubishiCN105::update() { - if (const auto start = this->status_update_start_ms_; - start && (get_loop_time_ms() - *start) >= this->update_interval_ms_) { - this->cancel_waiting_and_transition_to_(State::UPDATING_STATUS); - return false; + if (const auto start = this->status_update_start_ms_) { + if (this->pending_updates_.any()) { + this->cancel_waiting_and_transition_to_(State::APPLYING_SETTINGS); + return false; + } + + if ((get_loop_time_ms() - *start) >= this->update_interval_ms_) { + this->cancel_waiting_and_transition_to_(State::UPDATING_STATUS); + return false; + } } if (const auto start = this->write_timeout_start_ms_; start && (get_loop_time_ms() - *start) >= WRITE_TIMEOUT_MS) { @@ -118,13 +142,19 @@ bool MitsubishiCN105::should_transition(State from, State to) { return from == State::UPDATING_STATUS; case State::SCHEDULE_NEXT_STATUS_UPDATE: - return from == State::STATUS_UPDATED; + return from == State::STATUS_UPDATED || from == State::SETTINGS_APPLIED; case State::WAITING_FOR_SCHEDULED_STATUS_UPDATE: return from == State::SCHEDULE_NEXT_STATUS_UPDATE; + case State::APPLYING_SETTINGS: + return from == State::WAITING_FOR_SCHEDULED_STATUS_UPDATE || from == State::STATUS_UPDATED; + + case State::SETTINGS_APPLIED: + return from == State::APPLYING_SETTINGS; + case State::READ_TIMEOUT: - return from == State::UPDATING_STATUS || from == State::CONNECTING; + return from == State::UPDATING_STATUS || from == State::APPLYING_SETTINGS || from == State::CONNECTING; default: return false; @@ -149,7 +179,9 @@ void MitsubishiCN105::did_transition_(State to) { case State::STATUS_UPDATED: { this->write_timeout_start_ms_.reset(); - if (this->current_status_msg_type_ == STATUS_MSG_SETTINGS && this->should_request_room_temperature_()) { + if (this->pending_updates_.any() && this->is_status_initialized()) { + this->set_state_(State::APPLYING_SETTINGS); + } else if (this->current_status_msg_type_ == STATUS_MSG_SETTINGS && this->should_request_room_temperature_()) { this->current_status_msg_type_ = STATUS_MSG_ROOM_TEMP; this->set_state_(State::UPDATING_STATUS); } else { @@ -164,6 +196,16 @@ void MitsubishiCN105::did_transition_(State to) { this->set_state_(State::WAITING_FOR_SCHEDULED_STATUS_UPDATE); break; + case State::APPLYING_SETTINGS: + this->apply_settings_(); + this->pending_updates_.clear(); + break; + + case State::SETTINGS_APPLIED: + this->write_timeout_start_ms_.reset(); + this->set_state_(State::SCHEDULE_NEXT_STATUS_UPDATE); + break; + case State::READ_TIMEOUT: this->set_state_(State::CONNECTING); break; @@ -210,6 +252,10 @@ bool MitsubishiCN105::process_rx_packet_(uint8_t type, const uint8_t *payload, s case PACKET_TYPE_STATUS_RESPONSE: return this->process_status_packet_(payload, len); + case PACKET_TYPE_WRITE_SETTINGS_RESPONSE: + this->set_state_(State::SETTINGS_APPLIED); + return false; + default: ESP_LOGVV(TAG, "RX unknown packet type 0x%02X", type); return false; @@ -263,11 +309,23 @@ bool MitsubishiCN105::parse_status_settings_(const uint8_t *payload, size_t len) return false; } - const bool i_see = payload[3] > 0x08; - this->status_.mode = lookup(PROTOCOL_MODE_MAP, payload[3] - (i_see ? 0x08 : 0)).value_or(Mode::UNKNOWN); - this->status_.fan_mode = lookup(PROTOCOL_FAN_MODE_MAP, payload[5]).value_or(FanMode::UNKNOWN); - this->status_.power_on = payload[2] != 0; - this->status_.target_temperature = decode_temperature(-payload[4], payload[10], 31); + if (!this->pending_updates_.has(UpdateFlag::POWER)) { + this->status_.power_on = payload[2] != 0; + } + + this->use_temperature_encoding_b_ = payload[10] != 0; + if (!this->pending_updates_.has(UpdateFlag::TEMPERATURE)) { + this->status_.target_temperature = decode_temperature(-payload[4], payload[10], TARGET_TEMPERATURE_ENC_A_OFFSET); + } + + if (!this->pending_updates_.has(UpdateFlag::MODE)) { + const bool i_see = payload[3] > 0x08; + this->status_.mode = lookup(PROTOCOL_MODE_MAP, payload[3] - (i_see ? 0x08 : 0)).value_or(Mode::UNKNOWN); + } + + if (!this->pending_updates_.has(UpdateFlag::FAN)) { + this->status_.fan_mode = lookup(PROTOCOL_FAN_MODE_MAP, payload[5]).value_or(FanMode::UNKNOWN); + } return true; } @@ -284,6 +342,70 @@ bool MitsubishiCN105::parse_status_room_temperature_(const uint8_t *payload, siz return true; } +void MitsubishiCN105::set_power(bool power_on) { + this->status_.power_on = power_on; + this->pending_updates_.set(UpdateFlag::POWER); +} + +void MitsubishiCN105::set_target_temperature(float target_temperature) { + if (target_temperature < 16 || target_temperature > 31) { + ESP_LOGD(TAG, "Setting temperature out-of-range: %.1f", target_temperature); + return; + } + this->status_.target_temperature = std::round(target_temperature); + this->pending_updates_.set(UpdateFlag::TEMPERATURE); +} + +void MitsubishiCN105::set_mode(Mode mode) { + uint8_t placeholder; + if (!reverse_lookup(PROTOCOL_MODE_MAP, mode, placeholder)) { + ESP_LOGD(TAG, "Setting invalid mode: %u", static_cast(mode)); + return; + } + this->status_.mode = mode; + this->pending_updates_.set(UpdateFlag::MODE); +} + +void MitsubishiCN105::set_fan_mode(FanMode fan_mode) { + uint8_t placeholder; + if (!reverse_lookup(PROTOCOL_FAN_MODE_MAP, fan_mode, placeholder)) { + ESP_LOGD(TAG, "Setting invalid fan mode: %u", static_cast(fan_mode)); + return; + } + this->status_.fan_mode = fan_mode; + this->pending_updates_.set(UpdateFlag::FAN); +} + +void MitsubishiCN105::apply_settings_() { + std::array payload = {0x01}; + + if (this->pending_updates_.has(UpdateFlag::POWER)) { + payload[1] |= 0x01; + payload[3] = this->status_.power_on ? 0x01 : 0x00; + } + + if (this->pending_updates_.has(UpdateFlag::TEMPERATURE)) { + payload[1] |= 0x04; + if (this->use_temperature_encoding_b_) { + payload[14] = static_cast(this->status_.target_temperature * 2.0f + 128.0f); + } else { + payload[5] = static_cast(TARGET_TEMPERATURE_ENC_A_OFFSET - this->status_.target_temperature); + } + } + + if (this->pending_updates_.has(UpdateFlag::MODE) && + reverse_lookup(PROTOCOL_MODE_MAP, this->status_.mode, payload[4])) { + payload[1] |= 0x02; + } + + if (this->pending_updates_.has(UpdateFlag::FAN) && + reverse_lookup(PROTOCOL_FAN_MODE_MAP, this->status_.fan_mode, payload[6])) { + payload[1] |= 0x08; + } + + this->send_packet_(make_packet(PACKET_TYPE_WRITE_SETTINGS_REQUEST, payload)); +} + const LogString *MitsubishiCN105::state_to_string(State state) { switch (state) { case State::NOT_CONNECTED: @@ -300,6 +422,10 @@ const LogString *MitsubishiCN105::state_to_string(State state) { return LOG_STR("ScheduleNextStatusUpdate"); case State::WAITING_FOR_SCHEDULED_STATUS_UPDATE: return LOG_STR("WaitingForScheduledStatusUpdate"); + case State::APPLYING_SETTINGS: + return LOG_STR("ApplyingSettings"); + case State::SETTINGS_APPLIED: + return LOG_STR("SettingsApplied"); case State::READ_TIMEOUT: return LOG_STR("ReadTimeout"); } diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h index 6a29763696..68d98bf6d9 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h @@ -56,6 +56,11 @@ class MitsubishiCN105 { : !std::isnan(this->status_.target_temperature); } + void set_power(bool power_on); + void set_target_temperature(float target_temperature); + void set_mode(Mode mode); + void set_fan_mode(FanMode fan_mode); + protected: enum class State : uint8_t { NOT_CONNECTED, @@ -65,6 +70,8 @@ class MitsubishiCN105 { STATUS_UPDATED, SCHEDULE_NEXT_STATUS_UPDATE, WAITING_FOR_SCHEDULED_STATUS_UPDATE, + APPLYING_SETTINGS, + SETTINGS_APPLIED, READ_TIMEOUT }; @@ -83,6 +90,23 @@ class MitsubishiCN105 { uint8_t read_pos_{0}; }; + enum class UpdateFlag : uint8_t { + TEMPERATURE = 1 << 0, + POWER = 1 << 1, + MODE = 1 << 2, + FAN = 1 << 3, + }; + + struct UpdateFlags { + void set(UpdateFlag f) { flags_ |= static_cast(f); } + void clear() { flags_ = 0; } + bool any() const { return flags_ != 0; } + bool has(UpdateFlag f) const { return (flags_ & static_cast(f)) != 0; } + + protected: + uint8_t flags_{0}; + }; + void set_state_(State new_state); void did_transition_(State to); bool process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len); @@ -94,6 +118,7 @@ class MitsubishiCN105 { void update_status_(); void cancel_waiting_and_transition_to_(State state); bool should_request_room_temperature_() const; + void apply_settings_(); template void send_packet_(const T &packet) { this->send_packet_(packet.data(), packet.size()); } static bool should_transition(State from, State to); static const LogString *state_to_string(State state); @@ -106,6 +131,8 @@ class MitsubishiCN105 { std::optional last_room_temperature_update_ms_; Status status_{}; State state_{State::NOT_CONNECTED}; + UpdateFlags pending_updates_; + bool use_temperature_encoding_b_{false}; uint8_t current_status_msg_type_{0}; FrameParser frame_parser_; }; diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp index 21ce272993..40ddb88a79 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp @@ -34,6 +34,22 @@ static bool map_lookup(const std::array, N> &map, A key, B &out) return false; } +template +static constexpr std::optional reverse_map_lookup(const std::array, N> &map, Right key) { + for (const auto &entry : map) { + if (entry.second == key) { + return entry.first; + } + } + return std::nullopt; +} + +template +static constexpr std::optional reverse_map_lookup(const std::array, N> &map, + const std::optional &key) { + return key.has_value() ? reverse_map_lookup(map, *key) : std::nullopt; +} + void MitsubishiCN105Climate::dump_config() { LOG_CLIMATE("", "Mitsubishi CN105 Climate", this); if (this->hp_.is_room_temperature_enabled()) { @@ -90,7 +106,28 @@ climate::ClimateTraits MitsubishiCN105Climate::traits() { return traits; } -void MitsubishiCN105Climate::control(const climate::ClimateCall &call) {} +void MitsubishiCN105Climate::control(const climate::ClimateCall &call) { + if (const auto target_temperature = call.get_target_temperature()) { + this->hp_.set_target_temperature(*target_temperature); + } + + if (const auto mode = call.get_mode()) { + if (*mode == climate::CLIMATE_MODE_OFF) { + this->hp_.set_power(false); + } else if (const auto mapped = reverse_map_lookup(MODE_MAP, *mode)) { + this->hp_.set_power(true); + this->hp_.set_mode(*mapped); + } + } + + if (const auto fan_mode = reverse_map_lookup(FAN_MODE_MAP, call.get_fan_mode())) { + this->hp_.set_fan_mode(*fan_mode); + } + + if (this->hp_.is_status_initialized()) { + this->apply_values_(); + } +} void MitsubishiCN105Climate::apply_values_() { const auto &status = this->hp_.status(); diff --git a/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp b/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp index f26b0d82b6..7846a31193 100644 --- a/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp +++ b/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp @@ -60,8 +60,8 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) { // Settings should still have initial values EXPECT_FALSE(ctx.sut.status().power_on); EXPECT_THAT(ctx.sut.status().target_temperature, ::testing::IsNan()); - EXPECT_EQ(ctx.sut.status().mode, TestableMitsubishiCN105::Mode::UNKNOWN); - EXPECT_EQ(ctx.sut.status().fan_mode, TestableMitsubishiCN105::FanMode::UNKNOWN); + EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::UNKNOWN); + EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::UNKNOWN); ctx.sut.set_current_time(300); ASSERT_FALSE(ctx.sut.update()); @@ -70,8 +70,8 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) { // Check settings that we just read from received package EXPECT_FALSE(ctx.sut.status().power_on); EXPECT_EQ(ctx.sut.status().target_temperature, 24.0f); - EXPECT_EQ(ctx.sut.status().mode, TestableMitsubishiCN105::Mode::AUTO); - EXPECT_EQ(ctx.sut.status().fan_mode, TestableMitsubishiCN105::FanMode::AUTO); + EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::AUTO); + EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::AUTO); // Now fetch room temperature (0x03) EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS); @@ -269,9 +269,10 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) { ctx.sut.update(); EXPECT_TRUE(ctx.sut.status().power_on); + EXPECT_FALSE(ctx.sut.use_temperature_encoding_b_); EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f); - EXPECT_EQ(ctx.sut.status().mode, TestableMitsubishiCN105::Mode::COOL); - EXPECT_EQ(ctx.sut.status().fan_mode, TestableMitsubishiCN105::FanMode::QUIET); + EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::COOL); + EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::QUIET); } TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) { @@ -283,9 +284,10 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) { ctx.sut.update(); EXPECT_FALSE(ctx.sut.status().power_on); + EXPECT_TRUE(ctx.sut.use_temperature_encoding_b_); EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f); - EXPECT_EQ(ctx.sut.status().mode, TestableMitsubishiCN105::Mode::FAN_ONLY); - EXPECT_EQ(ctx.sut.status().fan_mode, TestableMitsubishiCN105::FanMode::SPEED_4); + EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::FAN_ONLY); + EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::SPEED_4); } TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedA) { @@ -308,4 +310,87 @@ TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedB) { EXPECT_EQ(ctx.sut.status().room_temperature, 30.0f); } +TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) { + auto ctx = TestContext{}; + + ctx.sut.set_power(true); + ctx.sut.apply_settings(); + + EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B)); +} + +TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedA) { + auto ctx = TestContext{}; + + ctx.sut.set_target_temperature(23.0f); + ctx.sut.apply_settings(); + + EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71)); +} + +TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) { + auto ctx = TestContext{}; + + ctx.sut.use_temperature_encoding_b_ = true; + ctx.sut.set_target_temperature(26.0f); + ctx.sut.apply_settings(); + + EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0x00, 0xC5)); +} + +TEST(MitsubishiCN105Tests, ApplyModeCool) { + auto ctx = TestContext{}; + + ctx.sut.set_mode(MitsubishiCN105::Mode::COOL); + ctx.sut.apply_settings(); + + EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x02, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78)); +} + +TEST(MitsubishiCN105Tests, ApplyFanModeSpeed1) { + auto ctx = TestContext{}; + + ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::SPEED_1); + ctx.sut.apply_settings(); + + EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73)); +} + +TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) { + auto ctx = TestContext{}; + + // Waiting for next scheduled status update + ctx.sut.state_ = TestableMitsubishiCN105::State::STATUS_UPDATED; + ctx.sut.set_state(TestableMitsubishiCN105::State::SCHEDULE_NEXT_STATUS_UPDATE); + EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE); + + // Nothing to do in update (rx empty, no timeout) + ASSERT_FALSE(ctx.sut.update()); + EXPECT_TRUE(ctx.uart.tx.empty()); + + // Write new values + ctx.sut.use_temperature_encoding_b_ = true; + ctx.sut.set_power(false); + ctx.sut.set_target_temperature(25.0f); + ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT); + ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::AUTO); + + // Waiting for next status update must be interrupted and new values send to AC + ASSERT_FALSE(ctx.sut.update()); + EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::APPLYING_SETTINGS); + 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)); + + // Write ACK response + ctx.uart.push_rx({0xFC, 0x61, 0x01, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E}); + ASSERT_FALSE(ctx.sut.update()); + EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE); +} + } // namespace esphome::mitsubishi_cn105::testing diff --git a/tests/components/mitsubishi_cn105/common.h b/tests/components/mitsubishi_cn105/common.h index ed55c3dc0c..0862d64fa7 100644 --- a/tests/components/mitsubishi_cn105/common.h +++ b/tests/components/mitsubishi_cn105/common.h @@ -45,8 +45,10 @@ class TestableMitsubishiCN105 : public MitsubishiCN105 { using MitsubishiCN105::state_; using MitsubishiCN105::write_timeout_start_ms_; using MitsubishiCN105::status_update_start_ms_; + using MitsubishiCN105::use_temperature_encoding_b_; void set_state(State s) { this->set_state_(s); } + void apply_settings() { this->apply_settings_(); } static inline uint32_t test_loop_time_ms = 0;