[mitsubishi_cn105] Add climate component for Mitsubishi A/C units with CN105 connector (Part 3) (#15437)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
Boris Krivonog
2026-04-05 00:40:05 +02:00
committed by GitHub
parent 1a1725f958
commit 830517a98f
8 changed files with 353 additions and 32 deletions
@@ -8,6 +8,7 @@ static const char *const TAG = "mitsubishi_cn105.driver";
static constexpr uint32_t WRITE_TIMEOUT_MS = 2000;
static constexpr size_t REQUEST_PAYLOAD_LEN = 0x10;
static constexpr size_t HEADER_LEN = 5;
static constexpr uint8_t PREAMBLE = 0xFC;
static constexpr uint8_t HEADER_BYTE_1 = 0x01;
@@ -15,7 +16,13 @@ static constexpr uint8_t HEADER_BYTE_2 = 0x30;
static constexpr uint8_t PACKET_TYPE_CONNECT_REQUEST = 0x5A;
static constexpr uint8_t PACKET_TYPE_CONNECT_RESPONSE = 0x7A;
static constexpr std::array<uint8_t, 2> CONNECT_REQUEST_PAYLOAD = {{0xCA, 0x01}};
static constexpr std::array<uint8_t, 2> CONNECT_REQUEST_PAYLOAD = {0xCA, 0x01};
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<uint8_t, 2> STATUS_MSG_TYPES = {STATUS_MSG_SETTINGS, STATUS_MSG_ROOM_TEMP};
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}));
@@ -30,19 +37,29 @@ static constexpr auto make_packet(uint8_t type, const std::array<uint8_t, Payloa
return packet;
}
static 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);
void MitsubishiCN105::initialize() { this->set_state_(State::CONNECTING); }
void MitsubishiCN105::update() {
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->write_timeout_start_ms_; start && (get_loop_time_ms() - *start) >= WRITE_TIMEOUT_MS) {
this->write_timeout_start_ms_.reset();
this->read_pos_ = 0;
this->set_state_(State::READ_TIMEOUT);
return;
return false;
}
this->read_incoming_bytes_();
return this->read_incoming_bytes_();
}
void MitsubishiCN105::set_state_(State new_state) {
@@ -63,9 +80,24 @@ bool MitsubishiCN105::should_transition(State from, State to) {
return from == State::NOT_CONNECTED || from == State::READ_TIMEOUT;
case State::CONNECTED:
case State::READ_TIMEOUT:
return from == State::CONNECTING;
case State::UPDATING_STATUS:
return from == State::CONNECTED || from == State::STATUS_UPDATED ||
from == State::WAITING_FOR_SCHEDULED_STATUS_UPDATE;
case State::STATUS_UPDATED:
return from == State::UPDATING_STATUS;
case State::SCHEDULE_NEXT_STATUS_UPDATE:
return from == State::STATUS_UPDATED;
case State::WAITING_FOR_SCHEDULED_STATUS_UPDATE:
return from == State::SCHEDULE_NEXT_STATUS_UPDATE;
case State::READ_TIMEOUT:
return from == State::UPDATING_STATUS || from == State::CONNECTING;
default:
return false;
}
@@ -79,7 +111,30 @@ void MitsubishiCN105::did_transition_(State to) {
case State::CONNECTED:
this->write_timeout_start_ms_.reset();
// TODO: read AC status after connected, next PR
this->status_msg_index_ = 0;
this->set_state_(State::UPDATING_STATUS);
break;
case State::UPDATING_STATUS:
this->update_status_();
break;
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) {
this->set_state_(State::UPDATING_STATUS);
} else {
this->set_state_(State::SCHEDULE_NEXT_STATUS_UPDATE);
}
break;
}
case State::SCHEDULE_NEXT_STATUS_UPDATE:
this->status_update_start_ms_ = get_loop_time_ms();
this->set_state_(State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
break;
case State::READ_TIMEOUT:
@@ -97,13 +152,24 @@ void MitsubishiCN105::send_packet_(const uint8_t *packet, size_t len) {
this->write_timeout_start_ms_ = get_loop_time_ms();
}
void MitsubishiCN105::read_incoming_bytes_() {
void MitsubishiCN105::update_status_() {
ESP_LOGV(TAG, "Requesting status update, index=%u", this->status_msg_index_);
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload = {STATUS_MSG_TYPES[this->status_msg_index_]};
this->send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload));
}
void MitsubishiCN105::cancel_waiting_and_transition_to_(State state) {
this->status_update_start_ms_.reset();
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;
return false;
}
switch (++this->read_pos_) {
@@ -149,23 +215,85 @@ void MitsubishiCN105::read_incoming_bytes_() {
continue;
}
this->process_rx_packet_(this->read_buffer_[1], this->read_buffer_ + HEADER_LEN, len_without_checksum - HEADER_LEN);
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;
}
void MitsubishiCN105::process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len) {
bool MitsubishiCN105::process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len) {
switch (type) {
case PACKET_TYPE_CONNECT_RESPONSE:
this->set_state_(State::CONNECTED);
break;
return false;
case PACKET_TYPE_STATUS_RESPONSE:
return this->process_status_packet_(payload, len);
default:
ESP_LOGVV(TAG, "RX unknown packet type 0x%02X", type);
break;
return false;
}
}
bool MitsubishiCN105::process_status_packet_(const uint8_t *payload, size_t len) {
if (len == 0) {
ESP_LOGVV(TAG, "RX status packet too short");
return false;
}
const auto previous = this->status_;
const auto msg_type = payload[0];
if (!this->parse_status_payload_(msg_type, payload + 1, len - 1)) {
return false;
}
if (msg_type == STATUS_MSG_TYPES[this->status_msg_index_]) {
this->set_state_(State::STATUS_UPDATED);
}
return previous != this->status_ && this->is_status_initialized();
}
bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len) {
switch (msg_type) {
case STATUS_MSG_SETTINGS:
return this->parse_status_settings_(payload, len);
case STATUS_MSG_ROOM_TEMP:
return this->parse_status_room_temperature_(payload, len);
default:
ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type);
return false;
}
}
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;
}
this->status_.power_on = payload[2] != 0;
this->status_.target_temperature = decode_temperature(-payload[4], payload[10], 31);
return true;
}
bool MitsubishiCN105::parse_status_room_temperature_(const uint8_t *payload, size_t len) {
if (len <= 5) {
ESP_LOGVV(TAG, "RX room temperature payload too short");
return false;
}
this->status_.room_temperature = decode_temperature(payload[2], payload[5], 10);
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;
@@ -186,6 +314,14 @@ const LogString *MitsubishiCN105::state_to_string(State state) {
return LOG_STR("Connecting");
case State::CONNECTED:
return LOG_STR("Connected");
case State::UPDATING_STATUS:
return LOG_STR("UpdatingStatus");
case State::STATUS_UPDATED:
return LOG_STR("StatusUpdated");
case State::SCHEDULE_NEXT_STATUS_UPDATE:
return LOG_STR("ScheduleNextStatusUpdate");
case State::WAITING_FOR_SCHEDULED_STATUS_UPDATE:
return LOG_STR("WaitingForScheduledStatusUpdate");
case State::READ_TIMEOUT:
return LOG_STR("ReadTimeout");
}
@@ -9,23 +9,49 @@ uint32_t get_loop_time_ms();
class MitsubishiCN105 {
public:
struct Status {
bool operator==(const Status &) const = default;
bool power_on{false};
float target_temperature{NAN};
float room_temperature{NAN};
};
explicit MitsubishiCN105(uart::UARTDevice &device) : device_(device) {}
void initialize();
void update();
bool update();
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; }
const Status &status() const { return this->status_; }
bool is_status_initialized() const { return !std::isnan(status_.room_temperature); }
protected:
enum class State : uint8_t { NOT_CONNECTED, CONNECTING, CONNECTED, READ_TIMEOUT };
enum class State : uint8_t {
NOT_CONNECTED,
CONNECTING,
CONNECTED,
UPDATING_STATUS,
STATUS_UPDATED,
SCHEDULE_NEXT_STATUS_UPDATE,
WAITING_FOR_SCHEDULED_STATUS_UPDATE,
READ_TIMEOUT
};
void set_state_(State new_state);
void did_transition_(State to);
void read_incoming_bytes_();
void process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len);
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);
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 const LogString *state_to_string(State state);
@@ -34,7 +60,10 @@ class MitsubishiCN105 {
uart::UARTDevice &device_;
uint32_t update_interval_ms_{1000};
std::optional<uint32_t> write_timeout_start_ms_;
std::optional<uint32_t> status_update_start_ms_;
Status status_{};
State state_{State::NOT_CONNECTED};
uint8_t status_msg_index_{0};
private:
static constexpr size_t READ_BUFFER_SIZE = 32;
@@ -17,13 +17,34 @@ void MitsubishiCN105Climate::dump_config() {
void MitsubishiCN105Climate::setup() { this->hp_.initialize(); }
void MitsubishiCN105Climate::loop() { this->hp_.update(); }
void MitsubishiCN105Climate::loop() {
if (this->hp_.update()) {
this->apply_values_();
}
}
climate::ClimateTraits MitsubishiCN105Climate::traits() {
climate::ClimateTraits traits;
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
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);
return traits;
}
void MitsubishiCN105Climate::control(const climate::ClimateCall &call) {}
void MitsubishiCN105Climate::apply_values_() {
const auto &status = this->hp_.status();
this->target_temperature = status.target_temperature;
this->current_temperature = status.room_temperature;
this->publish_state();
}
} // namespace esphome::mitsubishi_cn105
@@ -21,6 +21,8 @@ class MitsubishiCN105Climate : public climate::Climate, public Component, public
void set_update_interval(uint32_t ms) { hp_.set_update_interval(ms); }
protected:
void apply_values_();
MitsubishiCN105 hp_;
};
@@ -2,6 +2,6 @@
namespace esphome::mitsubishi_cn105 {
uint32_t __attribute__((weak)) get_loop_time_ms() { return App.get_loop_component_start_time(); };
uint32_t __attribute__((weak)) get_loop_time_ms() { return App.get_loop_component_start_time(); }
} // namespace esphome::mitsubishi_cn105
@@ -25,27 +25,80 @@ TEST(MitsubishiCN105Tests, InitSendsConnectPacket) {
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{123});
}
TEST(MitsubishiCN105Tests, SuccessfullyConnects) {
TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
auto ctx = TestContext{};
ctx.sut.initialize();
ctx.uart.tx.clear(); // Remove first connect packet bytes
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_TRUE(ctx.sut.write_timeout_start_ms_.has_value());
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{0});
EXPECT_FALSE(ctx.sut.status_update_start_ms_.has_value());
// Connect response
ctx.uart.push_rx({0xFC, 0x7A, 0x01, 0x30, 0x00, 0x55});
ctx.sut.update();
ctx.sut.set_current_time(200);
ASSERT_FALSE(ctx.sut.update());
// All bytes from UART should be consumed and state = CONNECTED
// All bytes from UART should be consumed
EXPECT_TRUE(ctx.uart.rx.empty());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTED);
EXPECT_FALSE(ctx.sut.write_timeout_start_ms_.has_value());
// After successful connect we request status, first settings (0x02)
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x42, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B));
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{200});
EXPECT_FALSE(ctx.sut.status_update_start_ms_.has_value());
// Clear TX bytes.
ctx.uart.tx.clear();
// Settings response
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x08, 0x07,
0x00, 0x00, 0x00, 0x00, 0x03, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x99});
// Settings should still have initial values
EXPECT_FALSE(ctx.sut.status().power_on);
EXPECT_THAT(ctx.sut.status().target_temperature, ::testing::IsNan());
ctx.sut.set_current_time(300);
ASSERT_FALSE(ctx.sut.update());
EXPECT_TRUE(ctx.uart.rx.empty());
// 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);
// Now fetch room temperature (0x03)
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x42, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A));
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{300});
EXPECT_FALSE(ctx.sut.status_update_start_ms_.has_value());
// Clear TX bytes.
ctx.uart.tx.clear();
// Room temperature response
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x03, 0x00, 0x00, 0x0B, 0x00, 0x00,
0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5});
// Room temperature should still have initial value
EXPECT_THAT(ctx.sut.status().room_temperature, ::testing::IsNan());
ctx.sut.set_current_time(400);
EXPECT_FALSE(ctx.sut.is_status_initialized());
ASSERT_TRUE(ctx.sut.update());
EXPECT_TRUE(ctx.uart.rx.empty());
EXPECT_TRUE(ctx.sut.is_status_initialized());
// Check room temperature we just read from received package
EXPECT_EQ(ctx.sut.status().room_temperature, 21.0f);
// Nothing should be send to UART
EXPECT_TRUE(ctx.uart.tx.empty());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
EXPECT_FALSE(ctx.sut.write_timeout_start_ms_.has_value());
EXPECT_EQ(ctx.sut.status_update_start_ms_, std::optional<uint32_t>{400});
}
TEST(MitsubishiCN105Tests, NoResponseTriggersReconnect) {
@@ -55,21 +108,21 @@ TEST(MitsubishiCN105Tests, NoResponseTriggersReconnect) {
ctx.uart.tx.clear(); // Remove first connect packet bytes
// No response (no RX data), no retry yet
ctx.sut.update();
ASSERT_FALSE(ctx.sut.update());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_TRUE(ctx.uart.tx.empty());
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{0});
// Still no response after 1999ms, no retry yet
ctx.sut.set_current_time(1999);
ctx.sut.update();
ASSERT_FALSE(ctx.sut.update());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_TRUE(ctx.uart.tx.empty());
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{0});
// Stop waiting after 2s and retry connect
ctx.sut.set_current_time(2000);
ctx.sut.update();
ASSERT_FALSE(ctx.sut.update());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x5A, 0x01, 0x30, 0x02, 0xCA, 0x01, 0xA8));
EXPECT_EQ(ctx.sut.write_timeout_start_ms_, std::optional<uint32_t>{2000});
@@ -92,7 +145,7 @@ TEST(MitsubishiCN105Tests, RxWatchdogLimitsProcessingPerUpdate) {
ASSERT_GT(ctx.uart.rx.size(), 64);
// No valid response, no state change expected
ctx.sut.update();
ASSERT_FALSE(ctx.sut.update());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_TRUE(ctx.uart.tx.empty());
@@ -100,7 +153,7 @@ TEST(MitsubishiCN105Tests, RxWatchdogLimitsProcessingPerUpdate) {
EXPECT_FALSE(ctx.uart.rx.empty());
// Next update will read remaining bytes, no state change expected
ctx.sut.update();
ASSERT_FALSE(ctx.sut.update());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_TRUE(ctx.uart.tx.empty());
EXPECT_TRUE(ctx.uart.rx.empty());
@@ -162,7 +215,7 @@ TEST(MitsubishiCN105Tests, ParserHandlesMixedRxStream) {
// Drain RX - no valid response, no state change expected
int iterations = 0;
while (!ctx.uart.rx.empty() && iterations++ < 10) {
ctx.sut.update();
ASSERT_FALSE(ctx.sut.update());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::CONNECTING);
EXPECT_TRUE(ctx.uart.tx.empty());
}
@@ -170,4 +223,81 @@ TEST(MitsubishiCN105Tests, ParserHandlesMixedRxStream) {
EXPECT_TRUE(ctx.uart.rx.empty());
}
TEST(MitsubishiCN105Tests, NextStatusUpdateAfterUpdateIntervalMilliseconds) {
auto ctx = TestContext{};
ctx.sut.set_update_interval(2000);
ctx.sut.set_current_time(80000);
// No scheduled status update
EXPECT_FALSE(ctx.sut.status_update_start_ms_.has_value());
// Status update completed, schedule next 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);
EXPECT_EQ(ctx.sut.status_update_start_ms_, std::optional<uint32_t>{80000});
// Wait for update_interval (ms) before doing another status update
ASSERT_FALSE(ctx.sut.update());
EXPECT_TRUE(ctx.uart.tx.empty());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
ctx.sut.set_current_time(81999);
ASSERT_FALSE(ctx.sut.update());
EXPECT_TRUE(ctx.uart.tx.empty());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::WAITING_FOR_SCHEDULED_STATUS_UPDATE);
ctx.sut.set_current_time(82000);
ASSERT_FALSE(ctx.sut.update());
EXPECT_FALSE(ctx.uart.tx.empty());
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
EXPECT_FALSE(ctx.sut.status_update_start_ms_.has_value());
}
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});
ctx.sut.update();
EXPECT_TRUE(ctx.sut.status().power_on);
EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f);
}
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});
ctx.sut.update();
EXPECT_FALSE(ctx.sut.status().power_on);
EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f);
}
TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedA) {
auto ctx = TestContext{};
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x07, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x5D});
ctx.sut.update();
EXPECT_EQ(ctx.sut.status().room_temperature, 16.0f);
}
TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedB) {
auto ctx = TestContext{};
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xA7});
ctx.sut.update();
EXPECT_EQ(ctx.sut.status().room_temperature, 30.0f);
}
} // namespace esphome::mitsubishi_cn105::testing
+1 -1
View File
@@ -2,6 +2,6 @@
namespace esphome::mitsubishi_cn105 {
uint32_t get_loop_time_ms() { return testing::TestableMitsubishiCN105::test_loop_time_ms; };
uint32_t get_loop_time_ms() { return testing::TestableMitsubishiCN105::test_loop_time_ms; }
} // namespace esphome::mitsubishi_cn105
@@ -44,6 +44,9 @@ class TestableMitsubishiCN105 : public MitsubishiCN105 {
using MitsubishiCN105::State;
using MitsubishiCN105::state_;
using MitsubishiCN105::write_timeout_start_ms_;
using MitsubishiCN105::status_update_start_ms_;
void set_state(State s) { this->set_state_(s); }
static inline uint32_t test_loop_time_ms = 0;