mirror of
https://github.com/esphome/esphome.git
synced 2026-06-02 11:08:06 +08:00
[mitsubishi_cn105] Add climate component for Mitsubishi A/C units with CN105 connector (Part 5) (#15483)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#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<std::optional<MitsubishiCN105::Mode>, 9> PROTOCOL_MODE_MAP = {
|
||||
std::nullopt, // 0x00
|
||||
MitsubishiCN105::Mode::HEAT, // 0x01
|
||||
@@ -50,6 +56,18 @@ static constexpr std::optional<T> lookup(const std::array<std::optional<T>, N> &
|
||||
return (value < N) ? table[value] : std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
static constexpr bool reverse_lookup(const std::array<std::optional<T>, 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<uint8_t>(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<uint8_t>(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<uint8_t>(fan_mode));
|
||||
return;
|
||||
}
|
||||
this->status_.fan_mode = fan_mode;
|
||||
this->pending_updates_.set(UpdateFlag::FAN);
|
||||
}
|
||||
|
||||
void MitsubishiCN105::apply_settings_() {
|
||||
std::array<uint8_t, REQUEST_PAYLOAD_LEN> 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<uint8_t>(this->status_.target_temperature * 2.0f + 128.0f);
|
||||
} else {
|
||||
payload[5] = static_cast<uint8_t>(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");
|
||||
}
|
||||
|
||||
@@ -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<uint8_t>(f); }
|
||||
void clear() { flags_ = 0; }
|
||||
bool any() const { return flags_ != 0; }
|
||||
bool has(UpdateFlag f) const { return (flags_ & static_cast<uint8_t>(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<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);
|
||||
@@ -106,6 +131,8 @@ class MitsubishiCN105 {
|
||||
std::optional<uint32_t> 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_;
|
||||
};
|
||||
|
||||
@@ -34,6 +34,22 @@ static bool map_lookup(const std::array<std::pair<A, B>, N> &map, A key, B &out)
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename Left, typename Right, std::size_t N>
|
||||
static constexpr std::optional<Left> reverse_map_lookup(const std::array<std::pair<Left, Right>, N> &map, Right key) {
|
||||
for (const auto &entry : map) {
|
||||
if (entry.second == key) {
|
||||
return entry.first;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename Left, typename Right, std::size_t N>
|
||||
static constexpr std::optional<Left> reverse_map_lookup(const std::array<std::pair<Left, Right>, N> &map,
|
||||
const std::optional<Right> &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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user