mirror of
https://github.com/esphome/esphome.git
synced 2026-05-23 11:16:52 +08:00
[mitsubishi_cn105] Add vane and wide-vane support (#16405)
This commit is contained in:
@@ -50,6 +50,11 @@ template<auto Unknown, size_t N> struct LookupMap {
|
||||
}
|
||||
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]) {
|
||||
@@ -78,6 +83,33 @@ static constexpr auto PROTOCOL_FAN_MODE_MAP = make_map<MitsubishiCN105::FanMode:
|
||||
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) {
|
||||
return static_cast<uint8_t>(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0}));
|
||||
}
|
||||
@@ -91,7 +123,7 @@ 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -290,9 +322,10 @@ bool MitsubishiCN105::process_status_packet_(const uint8_t *payload, size_t len)
|
||||
this->set_state_(State::STATUS_UPDATED);
|
||||
}
|
||||
|
||||
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;
|
||||
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 ||
|
||||
previous.vane_mode != this->status_.vane_mode || previous.wide_vane_mode != this->status_.wide_vane_mode;
|
||||
|
||||
if (this->is_room_temperature_enabled()) {
|
||||
changed |= previous.room_temperature != this->status_.room_temperature;
|
||||
@@ -339,6 +372,15 @@ bool MitsubishiCN105::parse_status_settings_(const uint8_t *payload, size_t len)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -390,8 +432,7 @@ void MitsubishiCN105::set_target_temperature(float target_temperature) {
|
||||
}
|
||||
|
||||
void MitsubishiCN105::set_mode(Mode mode) {
|
||||
uint8_t placeholder;
|
||||
if (!PROTOCOL_MODE_MAP.reverse_lookup(mode, placeholder)) {
|
||||
if (!PROTOCOL_MODE_MAP.is_valid(mode)) {
|
||||
ESP_LOGD(TAG, "Setting invalid mode: %u", static_cast<uint8_t>(mode));
|
||||
return;
|
||||
}
|
||||
@@ -400,8 +441,7 @@ void MitsubishiCN105::set_mode(Mode mode) {
|
||||
}
|
||||
|
||||
void MitsubishiCN105::set_fan_mode(FanMode fan_mode) {
|
||||
uint8_t placeholder;
|
||||
if (!PROTOCOL_FAN_MODE_MAP.reverse_lookup(fan_mode, placeholder)) {
|
||||
if (!PROTOCOL_FAN_MODE_MAP.is_valid(fan_mode)) {
|
||||
ESP_LOGD(TAG, "Setting invalid fan mode: %u", static_cast<uint8_t>(fan_mode));
|
||||
return;
|
||||
}
|
||||
@@ -409,6 +449,24 @@ void MitsubishiCN105::set_fan_mode(FanMode fan_mode) {
|
||||
this->pending_updates_.set(UpdateFlag::FAN);
|
||||
}
|
||||
|
||||
void MitsubishiCN105::set_vane_mode(VaneMode vane_mode) {
|
||||
if (!PROTOCOL_VANE_MODE_MAP.is_valid(vane_mode)) {
|
||||
ESP_LOGD(TAG, "Setting 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) {
|
||||
if (!PROTOCOL_WIDE_VANE_MODE_MAP.is_valid(wide_vane_mode)) {
|
||||
ESP_LOGD(TAG, "Setting 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_() {
|
||||
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{};
|
||||
|
||||
@@ -450,7 +508,21 @@ void MitsubishiCN105::apply_settings_() {
|
||||
payload[1] |= 0x08;
|
||||
}
|
||||
|
||||
this->pending_updates_.clear(UpdateFlag::POWER, UpdateFlag::TEMPERATURE, UpdateFlag::MODE, UpdateFlag::FAN);
|
||||
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));
|
||||
|
||||
@@ -29,12 +29,36 @@ class MitsubishiCN105 {
|
||||
UNKNOWN,
|
||||
};
|
||||
|
||||
enum class VaneMode : uint8_t {
|
||||
AUTO,
|
||||
POSITION_1,
|
||||
POSITION_2,
|
||||
POSITION_3,
|
||||
POSITION_4,
|
||||
POSITION_5,
|
||||
SWING,
|
||||
UNKNOWN,
|
||||
};
|
||||
|
||||
enum class WideVaneMode : uint8_t {
|
||||
FAR_LEFT,
|
||||
LEFT,
|
||||
CENTER,
|
||||
RIGHT,
|
||||
FAR_RIGHT,
|
||||
LEFT_RIGHT,
|
||||
SWING,
|
||||
UNKNOWN,
|
||||
};
|
||||
|
||||
struct Status {
|
||||
bool power_on{false};
|
||||
float target_temperature{NAN};
|
||||
float room_temperature{NAN};
|
||||
bool power_on{false};
|
||||
Mode mode{Mode::UNKNOWN};
|
||||
FanMode fan_mode{FanMode::UNKNOWN};
|
||||
float room_temperature{NAN};
|
||||
VaneMode vane_mode{VaneMode::UNKNOWN};
|
||||
WideVaneMode wide_vane_mode{WideVaneMode::UNKNOWN};
|
||||
};
|
||||
|
||||
explicit MitsubishiCN105(uart::UARTDevice &device) : device_(device) {}
|
||||
@@ -61,6 +85,8 @@ class MitsubishiCN105 {
|
||||
void set_target_temperature(float target_temperature);
|
||||
void set_mode(Mode mode);
|
||||
void set_fan_mode(FanMode fan_mode);
|
||||
void set_vane_mode(VaneMode vane_mode);
|
||||
void set_wide_vane_mode(WideVaneMode mode);
|
||||
void set_remote_temperature(float temperature);
|
||||
void clear_remote_temperature();
|
||||
|
||||
@@ -98,7 +124,9 @@ class MitsubishiCN105 {
|
||||
POWER = 1,
|
||||
MODE = 2,
|
||||
FAN = 3,
|
||||
REMOTE_TEMPERATURE = 4,
|
||||
VANE = 4,
|
||||
WIDE_VANE = 5,
|
||||
REMOTE_TEMPERATURE = 6,
|
||||
};
|
||||
|
||||
struct UpdateFlags {
|
||||
@@ -142,6 +170,7 @@ class MitsubishiCN105 {
|
||||
State state_{State::NOT_CONNECTED};
|
||||
UpdateFlags pending_updates_;
|
||||
bool use_temperature_encoding_b_{false};
|
||||
bool set_wide_vane_high_bit_{false};
|
||||
FrameParser frame_parser_;
|
||||
uint8_t current_status_msg_type_{0};
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ void MitsubishiCN105Climate::dump_config() {
|
||||
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, " Current temperature: DISABLED");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Update interval: %" PRIu32 " ms\n"
|
||||
|
||||
@@ -53,13 +53,15 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
|
||||
|
||||
// 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});
|
||||
0x00, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C});
|
||||
|
||||
// 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, MitsubishiCN105::Mode::UNKNOWN);
|
||||
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::UNKNOWN);
|
||||
EXPECT_EQ(ctx.sut.status().vane_mode, MitsubishiCN105::VaneMode::UNKNOWN);
|
||||
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::UNKNOWN);
|
||||
|
||||
ctx.sut.set_current_time(300);
|
||||
ASSERT_FALSE(ctx.sut.update());
|
||||
@@ -70,6 +72,8 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) {
|
||||
EXPECT_EQ(ctx.sut.status().target_temperature, 24.0f);
|
||||
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::AUTO);
|
||||
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::AUTO);
|
||||
EXPECT_EQ(ctx.sut.status().vane_mode, MitsubishiCN105::VaneMode::POSITION_4);
|
||||
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::SWING);
|
||||
|
||||
// Now fetch room temperature (0x03)
|
||||
EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::UPDATING_STATUS);
|
||||
@@ -303,6 +307,30 @@ TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedB) {
|
||||
EXPECT_EQ(ctx.sut.status().room_temperature, 30.0f);
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitNotSet) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58});
|
||||
|
||||
ctx.sut.update();
|
||||
|
||||
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
|
||||
EXPECT_FALSE(ctx.sut.set_wide_vane_high_bit_);
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8});
|
||||
|
||||
ctx.sut.update();
|
||||
|
||||
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
|
||||
EXPECT_TRUE(ctx.sut.set_wide_vane_high_bit_);
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
@@ -365,6 +393,37 @@ TEST(MitsubishiCN105Tests, ApplyFanModeSpeed1) {
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73));
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, ApplyVaneModeSwing) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
ctx.sut.set_vane_mode(MitsubishiCN105::VaneMode::SWING);
|
||||
ctx.sut.apply_settings();
|
||||
|
||||
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66));
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitNotSet) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
|
||||
ctx.sut.apply_settings();
|
||||
|
||||
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x7A));
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
ctx.sut.set_wide_vane_high_bit_ = true;
|
||||
ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
|
||||
ctx.sut.apply_settings();
|
||||
|
||||
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0xFA));
|
||||
}
|
||||
|
||||
TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) {
|
||||
auto ctx = TestContext{};
|
||||
|
||||
@@ -391,15 +450,15 @@ TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) {
|
||||
ctx.sut.set_target_temperature(25.0f);
|
||||
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
||||
ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::AUTO);
|
||||
ctx.sut.set_vane_mode(MitsubishiCN105::VaneMode::AUTO);
|
||||
|
||||
// Waiting for next status update must be interrupted and new values send to AC
|
||||
ctx.sut.set_current_time(6000);
|
||||
ASSERT_FALSE(ctx.sut.update());
|
||||
EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 1000);
|
||||
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));
|
||||
|
||||
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x1F, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0xAB));
|
||||
// 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});
|
||||
|
||||
@@ -46,6 +46,7 @@ class TestableMitsubishiCN105 : public MitsubishiCN105 {
|
||||
using MitsubishiCN105::state_;
|
||||
using MitsubishiCN105::operation_start_ms_;
|
||||
using MitsubishiCN105::use_temperature_encoding_b_;
|
||||
using MitsubishiCN105::set_wide_vane_high_bit_;
|
||||
using MitsubishiCN105::status_update_wait_credit_ms_;
|
||||
using MitsubishiCN105::pending_updates_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user