diff --git a/esphome/components/mitsubishi_cn105/climate.py b/esphome/components/mitsubishi_cn105/climate.py index 5ea72d4cd2..7fa6825ea6 100644 --- a/esphome/components/mitsubishi_cn105/climate.py +++ b/esphome/components/mitsubishi_cn105/climate.py @@ -8,6 +8,8 @@ DEPENDENCIES = ["uart"] AUTO_LOAD = ["climate"] CODEOWNERS = ["@crnjan"] +CONF_CURRENT_TEMPERATURE_MIN_INTERVAL = "current_temperature_min_interval" + mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi_cn105") MitsubishiCN105Climate = mitsubishi_ns.class_( @@ -20,7 +22,14 @@ MitsubishiCN105Climate = mitsubishi_ns.class_( CONFIG_SCHEMA = ( climate.climate_schema(MitsubishiCN105Climate) .extend(uart.UART_DEVICE_SCHEMA) - .extend({cv.Optional(CONF_UPDATE_INTERVAL, default="1s"): cv.update_interval}) + .extend( + { + cv.Optional(CONF_UPDATE_INTERVAL, default="1s"): cv.update_interval, + cv.Optional( + CONF_CURRENT_TEMPERATURE_MIN_INTERVAL, default="60s" + ): cv.update_interval, + } + ) ) FINAL_VALIDATE_SCHEMA = cv.All( @@ -39,3 +48,8 @@ async def to_code(config: ConfigType) -> None: var = await climate.new_climate(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) + cg.add( + var.set_current_temperature_min_interval( + config[CONF_CURRENT_TEMPERATURE_MIN_INTERVAL] + ) + ) diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp index 0bce8da1ad..3b29f66bf1 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp @@ -22,7 +22,33 @@ static constexpr uint8_t PACKET_TYPE_STATUS_REQUEST = 0x42; 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 std::array STATUS_MSG_TYPES = {STATUS_MSG_SETTINGS, STATUS_MSG_ROOM_TEMP}; + +static constexpr std::array, 9> PROTOCOL_MODE_MAP = { + std::nullopt, // 0x00 + MitsubishiCN105::Mode::HEAT, // 0x01 + MitsubishiCN105::Mode::DRY, // 0x02 + MitsubishiCN105::Mode::COOL, // 0x03 + std::nullopt, // 0x04 + std::nullopt, // 0x05 + std::nullopt, // 0x06 + MitsubishiCN105::Mode::FAN_ONLY, // 0x07 + MitsubishiCN105::Mode::AUTO // 0x08 +}; + +static constexpr std::array, 7> PROTOCOL_FAN_MODE_MAP = { + MitsubishiCN105::FanMode::AUTO, // 0x00 + MitsubishiCN105::FanMode::QUIET, // 0x01 + MitsubishiCN105::FanMode::SPEED_1, // 0x02 + MitsubishiCN105::FanMode::SPEED_2, // 0x03 + std::nullopt, // 0x04 + MitsubishiCN105::FanMode::SPEED_3, // 0x05 + MitsubishiCN105::FanMode::SPEED_4 // 0x06 +}; + +template +static constexpr std::optional lookup(const std::array, N> &table, uint8_t value) { + return (value < N) ? table[value] : std::nullopt; +} static constexpr uint8_t checksum(const uint8_t *bytes, size_t length) { return static_cast(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0})); @@ -54,12 +80,14 @@ bool MitsubishiCN105::update() { if (const auto start = this->write_timeout_start_ms_; start && (get_loop_time_ms() - *start) >= WRITE_TIMEOUT_MS) { this->write_timeout_start_ms_.reset(); - this->read_pos_ = 0; + this->frame_parser_.reset(); this->set_state_(State::READ_TIMEOUT); return false; } - return this->read_incoming_bytes_(); + return this->frame_parser_.read_and_parse(this->device_, [this](uint8_t type, const uint8_t *payload, size_t len) { + return this->process_rx_packet_(type, payload, len); + }); } void MitsubishiCN105::set_state_(State new_state) { @@ -111,7 +139,7 @@ void MitsubishiCN105::did_transition_(State to) { case State::CONNECTED: this->write_timeout_start_ms_.reset(); - this->status_msg_index_ = 0; + this->current_status_msg_type_ = STATUS_MSG_SETTINGS; this->set_state_(State::UPDATING_STATUS); break; @@ -121,10 +149,8 @@ void MitsubishiCN105::did_transition_(State to) { case State::STATUS_UPDATED: { this->write_timeout_start_ms_.reset(); - if (++this->status_msg_index_ >= STATUS_MSG_TYPES.size()) { - this->status_msg_index_ = 0; - } - if (this->status_msg_index_ != 0) { + 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 { this->set_state_(State::SCHEDULE_NEXT_STATUS_UPDATE); @@ -134,6 +160,7 @@ void MitsubishiCN105::did_transition_(State to) { case State::SCHEDULE_NEXT_STATUS_UPDATE: this->status_update_start_ms_ = get_loop_time_ms(); + this->current_status_msg_type_ = STATUS_MSG_SETTINGS; this->set_state_(State::WAITING_FOR_SCHEDULED_STATUS_UPDATE); break; @@ -146,15 +173,26 @@ void MitsubishiCN105::did_transition_(State to) { } } +bool MitsubishiCN105::should_request_room_temperature_() const { + if (!this->is_room_temperature_enabled()) { + return false; + } + + if (!this->last_room_temperature_update_ms_.has_value()) { + return true; + } + + return (get_loop_time_ms() - *this->last_room_temperature_update_ms_) >= this->room_temperature_min_interval_ms_; +} + void MitsubishiCN105::send_packet_(const uint8_t *packet, size_t len) { - dump_buffer_vv("TX", packet, len); + FrameParser::dump_buffer_vv("TX", packet, len); this->device_.write_array(packet, len); this->write_timeout_start_ms_ = get_loop_time_ms(); } void MitsubishiCN105::update_status_() { - ESP_LOGV(TAG, "Requesting status update, index=%u", this->status_msg_index_); - std::array payload = {STATUS_MSG_TYPES[this->status_msg_index_]}; + std::array payload = {this->current_status_msg_type_}; this->send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload)); } @@ -163,67 +201,6 @@ void MitsubishiCN105::cancel_waiting_and_transition_to_(State state) { this->set_state_(state); } -bool MitsubishiCN105::read_incoming_bytes_() { - uint8_t watchdog = 64; - while (this->device_.available() > 0 && watchdog-- > 0) { - uint8_t &value = this->read_buffer_[this->read_pos_]; - if (!this->device_.read_byte(&value)) { - ESP_LOGW(TAG, "UART read failed while data available"); - return false; - } - - switch (++this->read_pos_) { - case 1: - if (value != PREAMBLE) { - this->reset_read_position_and_dump_buffer_("RX ignoring preamble"); - } - continue; - - case 2: - continue; - - case 3: - if (value != HEADER_BYTE_1) { - this->reset_read_position_and_dump_buffer_("RX invalid: header 1 mismatch"); - } - continue; - - case 4: - if (value != HEADER_BYTE_2) { - this->reset_read_position_and_dump_buffer_("RX invalid: header 2 mismatch"); - } - continue; - - case HEADER_LEN: - static_assert(READ_BUFFER_SIZE > HEADER_LEN); - if (this->read_buffer_[HEADER_LEN - 1] >= READ_BUFFER_SIZE - HEADER_LEN) { - this->reset_read_position_and_dump_buffer_("RX invalid: payload too large"); - } - continue; - - default: - break; - } - - const size_t len_without_checksum = HEADER_LEN + static_cast(this->read_buffer_[HEADER_LEN - 1]); - if (this->read_pos_ <= len_without_checksum) { - continue; - } - - if (checksum(this->read_buffer_, len_without_checksum) != value) { - this->reset_read_position_and_dump_buffer_("RX invalid: checksum mismatch"); - continue; - } - - bool processed = this->process_rx_packet_(this->read_buffer_[1], this->read_buffer_ + HEADER_LEN, - len_without_checksum - HEADER_LEN); - this->reset_read_position_and_dump_buffer_("RX"); - return processed; - } - - return false; -} - bool MitsubishiCN105::process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len) { switch (type) { case PACKET_TYPE_CONNECT_RESPONSE: @@ -251,11 +228,19 @@ bool MitsubishiCN105::process_status_packet_(const uint8_t *payload, size_t len) return false; } - if (msg_type == STATUS_MSG_TYPES[this->status_msg_index_]) { + if (msg_type == this->current_status_msg_type_) { this->set_state_(State::STATUS_UPDATED); } - return previous != this->status_ && this->is_status_initialized(); + bool changed = previous.power_on != this->status_.power_on || previous.mode != this->status_.mode || + previous.fan_mode != this->status_.fan_mode || + previous.target_temperature != this->status_.target_temperature; + + if (this->is_room_temperature_enabled()) { + changed |= previous.room_temperature != this->status_.room_temperature; + } + + return changed && this->is_status_initialized(); } bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len) { @@ -278,6 +263,9 @@ 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); @@ -291,21 +279,11 @@ bool MitsubishiCN105::parse_status_room_temperature_(const uint8_t *payload, siz } this->status_.room_temperature = decode_temperature(payload[2], payload[5], 10); + this->last_room_temperature_update_ms_ = get_loop_time_ms(); + return true; } -void MitsubishiCN105::reset_read_position_and_dump_buffer_(const char *prefix) { - dump_buffer_vv(prefix, this->read_buffer_, this->read_pos_); - this->read_pos_ = 0; -} - -void MitsubishiCN105::dump_buffer_vv(const char *prefix, const uint8_t *data, size_t len) { -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE - char buf[format_hex_pretty_size(READ_BUFFER_SIZE)]; - ESP_LOGVV(TAG, "%s (%zu): %s", prefix, len, format_hex_pretty_to(buf, data, len)); -#endif -} - const LogString *MitsubishiCN105::state_to_string(State state) { switch (state) { case State::NOT_CONNECTED: @@ -328,4 +306,79 @@ const LogString *MitsubishiCN105::state_to_string(State state) { return LOG_STR("Unknown"); } +template +bool MitsubishiCN105::FrameParser::read_and_parse(uart::UARTDevice &device, Callback &&callback) { + uint8_t watchdog = 64; + while (device.available() > 0 && watchdog-- > 0) { + uint8_t &value = this->read_buffer_[this->read_pos_]; + if (!device.read_byte(&value)) { + ESP_LOGW(TAG, "UART read failed while data available"); + return false; + } + + switch (++this->read_pos_) { + case 1: + if (value != PREAMBLE) { + this->reset_and_dump_buffer_("RX ignoring preamble"); + } + continue; + + case 2: + continue; + + case 3: + if (value != HEADER_BYTE_1) { + this->reset_and_dump_buffer_("RX invalid: header 1 mismatch"); + } + continue; + + case 4: + if (value != HEADER_BYTE_2) { + this->reset_and_dump_buffer_("RX invalid: header 2 mismatch"); + } + continue; + + case HEADER_LEN: + static_assert(READ_BUFFER_SIZE > HEADER_LEN); + if (this->read_buffer_[HEADER_LEN - 1] >= READ_BUFFER_SIZE - HEADER_LEN) { + this->reset_and_dump_buffer_("RX invalid: payload too large"); + } + continue; + + default: + break; + } + + const size_t len_without_checksum = HEADER_LEN + static_cast(this->read_buffer_[HEADER_LEN - 1]); + if (this->read_pos_ <= len_without_checksum) { + continue; + } + + if (checksum(this->read_buffer_, len_without_checksum) != value) { + this->reset_and_dump_buffer_("RX invalid: checksum mismatch"); + continue; + } + + dump_buffer_vv("RX", this->read_buffer_, this->read_pos_); + const bool processed = + callback(this->read_buffer_[1], this->read_buffer_ + HEADER_LEN, len_without_checksum - HEADER_LEN); + this->read_pos_ = 0; + return processed; + } + + return false; +} + +void MitsubishiCN105::FrameParser::reset_and_dump_buffer_(const char *prefix) { + dump_buffer_vv(prefix, this->read_buffer_, this->read_pos_); + this->read_pos_ = 0; +} + +void MitsubishiCN105::FrameParser::dump_buffer_vv(const char *prefix, const uint8_t *data, size_t len) { +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char buf[format_hex_pretty_size(READ_BUFFER_SIZE)]; + ESP_LOGVV(TAG, "%s (%zu): %s", prefix, len, format_hex_pretty_to(buf, data, len)); +#endif +} + } // namespace esphome::mitsubishi_cn105 diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h index d43904b313..6a29763696 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.h @@ -9,11 +9,30 @@ uint32_t get_loop_time_ms(); class MitsubishiCN105 { public: - struct Status { - bool operator==(const Status &) const = default; + enum class Mode : uint8_t { + HEAT, + DRY, + COOL, + FAN_ONLY, + AUTO, + UNKNOWN, + }; + enum class FanMode : uint8_t { + AUTO, + QUIET, + SPEED_1, + SPEED_2, + SPEED_3, + SPEED_4, + UNKNOWN, + }; + + struct Status { bool power_on{false}; float target_temperature{NAN}; + Mode mode{Mode::UNKNOWN}; + FanMode fan_mode{FanMode::UNKNOWN}; float room_temperature{NAN}; }; @@ -25,8 +44,17 @@ class MitsubishiCN105 { uint32_t get_update_interval() const { return this->update_interval_ms_; } void set_update_interval(uint32_t interval_ms) { this->update_interval_ms_ = interval_ms; } + uint32_t get_room_temperature_min_interval() const { return this->room_temperature_min_interval_ms_; } + bool is_room_temperature_enabled() const { return this->room_temperature_min_interval_ms_ != SCHEDULER_DONT_RUN; } + void set_room_temperature_min_interval(uint32_t interval_ms) { + this->room_temperature_min_interval_ms_ = interval_ms; + } + const Status &status() const { return this->status_; } - bool is_status_initialized() const { return !std::isnan(status_.room_temperature); } + bool is_status_initialized() const { + return this->is_room_temperature_enabled() ? !std::isnan(this->status_.room_temperature) + : !std::isnan(this->status_.target_temperature); + } protected: enum class State : uint8_t { @@ -40,35 +68,46 @@ class MitsubishiCN105 { READ_TIMEOUT }; + class FrameParser { + public: + template bool read_and_parse(uart::UARTDevice &device, Callback &&callback); + void reset() { read_pos_ = 0; } + static void dump_buffer_vv(const char *prefix, const uint8_t *data, size_t len); + + protected: + void reset_and_dump_buffer_(const char *prefix); + + private: + static constexpr size_t READ_BUFFER_SIZE = 32; + uint8_t read_buffer_[READ_BUFFER_SIZE]; + uint8_t read_pos_{0}; + }; + void set_state_(State new_state); void did_transition_(State to); - bool read_incoming_bytes_(); 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 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); bool parse_status_room_temperature_(const uint8_t *payload, size_t len); - void reset_read_position_and_dump_buffer_(const char *prefix); void send_packet_(const uint8_t *packet, size_t len); void update_status_(); void cancel_waiting_and_transition_to_(State state); + bool should_request_room_temperature_() const; 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); - static void dump_buffer_vv(const char *prefix, const uint8_t *data, size_t len); uart::UARTDevice &device_; uint32_t update_interval_ms_{1000}; + uint32_t room_temperature_min_interval_ms_{60000}; std::optional write_timeout_start_ms_; std::optional status_update_start_ms_; + std::optional last_room_temperature_update_ms_; Status status_{}; State state_{State::NOT_CONNECTED}; - uint8_t status_msg_index_{0}; - - private: - static constexpr size_t READ_BUFFER_SIZE = 32; - uint8_t read_buffer_[READ_BUFFER_SIZE]; - uint8_t read_pos_{0}; + uint8_t current_status_msg_type_{0}; + FrameParser frame_parser_; }; } // namespace esphome::mitsubishi_cn105 diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp index 55fc23c449..21ce272993 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp @@ -6,8 +6,42 @@ namespace esphome::mitsubishi_cn105 { static const char *const TAG = "mitsubishi_cn105.climate"; +static constexpr std::array MODE_MAP{ + std::pair{MitsubishiCN105::Mode::AUTO, climate::CLIMATE_MODE_AUTO}, + std::pair{MitsubishiCN105::Mode::HEAT, climate::CLIMATE_MODE_HEAT}, + std::pair{MitsubishiCN105::Mode::DRY, climate::CLIMATE_MODE_DRY}, + std::pair{MitsubishiCN105::Mode::COOL, climate::CLIMATE_MODE_COOL}, + std::pair{MitsubishiCN105::Mode::FAN_ONLY, climate::CLIMATE_MODE_FAN_ONLY}, +}; + +static constexpr std::array FAN_MODE_MAP{ + std::pair{MitsubishiCN105::FanMode::AUTO, climate::CLIMATE_FAN_AUTO}, + std::pair{MitsubishiCN105::FanMode::QUIET, climate::CLIMATE_FAN_QUIET}, + std::pair{MitsubishiCN105::FanMode::SPEED_1, climate::CLIMATE_FAN_LOW}, + std::pair{MitsubishiCN105::FanMode::SPEED_2, climate::CLIMATE_FAN_MEDIUM}, + std::pair{MitsubishiCN105::FanMode::SPEED_3, climate::CLIMATE_FAN_MIDDLE}, + std::pair{MitsubishiCN105::FanMode::SPEED_4, climate::CLIMATE_FAN_HIGH}, +}; + +template +static bool map_lookup(const std::array, N> &map, A key, B &out) { + for (const auto &[from, to] : map) { + if (from == key) { + out = to; + return true; + } + } + return false; +} + void MitsubishiCN105Climate::dump_config() { LOG_CLIMATE("", "Mitsubishi CN105 Climate", this); + if (this->hp_.is_room_temperature_enabled()) { + ESP_LOGCONFIG(TAG, " Current temperature min interval: %" PRIu32 " ms", + this->hp_.get_room_temperature_min_interval()); + } else { + ESP_LOGCONFIG(TAG, " Current temperature: disabled"); + } ESP_LOGCONFIG(TAG, " Update interval: %" PRIu32 " ms\n" " UART: baud_rate=%" PRIu32 " data_bits=%u parity=%s stop_bits=%u", @@ -26,12 +60,32 @@ void MitsubishiCN105Climate::loop() { climate::ClimateTraits MitsubishiCN105Climate::traits() { climate::ClimateTraits traits; - traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_FAN_ONLY, + climate::CLIMATE_MODE_AUTO, + }); + + traits.set_supported_fan_modes({ + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_QUIET, + climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_MIDDLE, + climate::CLIMATE_FAN_HIGH, + }); traits.set_visual_min_temperature(16.0f); traits.set_visual_max_temperature(31.0f); traits.set_visual_temperature_step(1.0f); - traits.set_visual_current_temperature_step(0.5f); + + if (this->hp_.is_room_temperature_enabled()) { + traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE); + traits.set_visual_current_temperature_step(0.5f); + } return traits; } @@ -42,7 +96,25 @@ void MitsubishiCN105Climate::apply_values_() { const auto &status = this->hp_.status(); this->target_temperature = status.target_temperature; - this->current_temperature = status.room_temperature; + + if (this->hp_.is_room_temperature_enabled()) { + this->current_temperature = status.room_temperature; + } + + if (status.power_on) { + if (!map_lookup(MODE_MAP, status.mode, this->mode)) { + ESP_LOGD(TAG, "Unable to map mode"); + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + + climate::ClimateFanMode fan_mode; + if (map_lookup(FAN_MODE_MAP, status.fan_mode, fan_mode)) { + this->fan_mode = fan_mode; + } else { + ESP_LOGD(TAG, "Unable to map fan mode"); + } this->publish_state(); } diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h index da8f8d8d0a..eee4c20966 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h @@ -19,6 +19,7 @@ class MitsubishiCN105Climate : public climate::Climate, public Component, public void control(const climate::ClimateCall &call) override; void set_update_interval(uint32_t ms) { hp_.set_update_interval(ms); } + void set_current_temperature_min_interval(uint32_t ms) { hp_.set_room_temperature_min_interval(ms); } protected: void apply_values_(); diff --git a/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp b/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp index 5b4f84623e..f26b0d82b6 100644 --- a/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp +++ b/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp @@ -60,6 +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); ctx.sut.set_current_time(300); ASSERT_FALSE(ctx.sut.update()); @@ -68,6 +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); // Now fetch room temperature (0x03) EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS); @@ -260,24 +264,28 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) { auto ctx = TestContext{}; ctx.uart.push_rx( - {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x01, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56}); + {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x01, 0x03, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55}); ctx.sut.update(); EXPECT_TRUE(ctx.sut.status().power_on); 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); } TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) { auto ctx = TestContext{}; ctx.uart.push_rx( - {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xB7}); + {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xAD}); ctx.sut.update(); EXPECT_FALSE(ctx.sut.status().power_on); 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); } TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedA) {