mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[mitsubishi_cn105] Refactor property encoding/decoding (#16709)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
J. Nick Koston
parent
2a0f2d59f0
commit
007c677da1
@@ -4,6 +4,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include "mitsubishi_cn105_properties.h"
|
||||||
|
|
||||||
namespace esphome::mitsubishi_cn105 {
|
namespace esphome::mitsubishi_cn105 {
|
||||||
|
|
||||||
@@ -11,8 +12,6 @@ static const char *const TAG = "mitsubishi_cn105.driver";
|
|||||||
|
|
||||||
static constexpr uint32_t RESPONSE_TIMEOUT_MS = 2000;
|
static constexpr uint32_t RESPONSE_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 REQUEST_PAYLOAD_LEN = 0x10;
|
||||||
static constexpr size_t HEADER_LEN = 5;
|
static constexpr size_t HEADER_LEN = 5;
|
||||||
static constexpr uint8_t PREAMBLE = 0xFC;
|
static constexpr uint8_t PREAMBLE = 0xFC;
|
||||||
@@ -31,86 +30,6 @@ static constexpr uint8_t STATUS_MSG_TELEMETRY = 0x03;
|
|||||||
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41;
|
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41;
|
||||||
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61;
|
static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61;
|
||||||
|
|
||||||
template<auto Unknown, size_t N> struct LookupMap {
|
|
||||||
using value_type = decltype(Unknown);
|
|
||||||
static constexpr auto UNKNOWN_VALUE = Unknown;
|
|
||||||
const std::array<value_type, N> table;
|
|
||||||
|
|
||||||
constexpr value_type lookup(uint8_t raw) const { return (raw < N) ? this->table[raw] : UNKNOWN_VALUE; }
|
|
||||||
|
|
||||||
constexpr bool reverse_lookup(value_type value, uint8_t &out) const {
|
|
||||||
static_assert(N <= std::numeric_limits<uint8_t>::max());
|
|
||||||
if (value == UNKNOWN_VALUE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (uint8_t i = 0; i < static_cast<uint8_t>(N); ++i) {
|
|
||||||
if (this->table[i] == value) {
|
|
||||||
out = i;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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]) {
|
|
||||||
return LookupMap<Unknown, N>{std::to_array(values)};
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr auto PROTOCOL_MODE_MAP = make_map<MitsubishiCN105::Mode::UNKNOWN>({
|
|
||||||
MitsubishiCN105::Mode::UNKNOWN, // 0x00
|
|
||||||
MitsubishiCN105::Mode::HEAT, // 0x01
|
|
||||||
MitsubishiCN105::Mode::DRY, // 0x02
|
|
||||||
MitsubishiCN105::Mode::COOL, // 0x03
|
|
||||||
MitsubishiCN105::Mode::UNKNOWN, // 0x04
|
|
||||||
MitsubishiCN105::Mode::UNKNOWN, // 0x05
|
|
||||||
MitsubishiCN105::Mode::UNKNOWN, // 0x06
|
|
||||||
MitsubishiCN105::Mode::FAN_ONLY, // 0x07
|
|
||||||
MitsubishiCN105::Mode::AUTO // 0x08
|
|
||||||
});
|
|
||||||
|
|
||||||
static constexpr auto PROTOCOL_FAN_MODE_MAP = make_map<MitsubishiCN105::FanMode::UNKNOWN>({
|
|
||||||
MitsubishiCN105::FanMode::AUTO, // 0x00
|
|
||||||
MitsubishiCN105::FanMode::QUIET, // 0x01
|
|
||||||
MitsubishiCN105::FanMode::SPEED_1, // 0x02
|
|
||||||
MitsubishiCN105::FanMode::SPEED_2, // 0x03
|
|
||||||
MitsubishiCN105::FanMode::UNKNOWN, // 0x04
|
|
||||||
MitsubishiCN105::FanMode::SPEED_3, // 0x05
|
|
||||||
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) {
|
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}));
|
return static_cast<uint8_t>(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0}));
|
||||||
}
|
}
|
||||||
@@ -124,10 +43,6 @@ static constexpr auto make_packet(uint8_t type, const std::array<uint8_t, Payloa
|
|||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr auto CONNECT_PACKET = make_packet(PACKET_TYPE_CONNECT_REQUEST, CONNECT_REQUEST_PAYLOAD);
|
static constexpr auto CONNECT_PACKET = make_packet(PACKET_TYPE_CONNECT_REQUEST, CONNECT_REQUEST_PAYLOAD);
|
||||||
|
|
||||||
void MitsubishiCN105::initialize() { this->set_state_(State::CONNECTING); }
|
void MitsubishiCN105::initialize() { this->set_state_(State::CONNECTING); }
|
||||||
@@ -277,14 +192,14 @@ bool MitsubishiCN105::should_request_telemetry_() const {
|
|||||||
return (get_loop_time_ms() - *this->last_telemetry_update_ms_) >= this->telemetry_request_min_interval_ms_;
|
return (get_loop_time_ms() - *this->last_telemetry_update_ms_) >= this->telemetry_request_min_interval_ms_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MitsubishiCN105::send_packet_(const uint8_t *packet, size_t len) {
|
void MitsubishiCN105::send_packet_(std::span<const uint8_t> packet) {
|
||||||
FrameParser::dump_buffer_vv("TX", packet, len);
|
FrameParser::dump_buffer_vv("TX", packet.data(), packet.size());
|
||||||
this->device_.write_array(packet, len);
|
this->device_.write_array(packet.data(), packet.size());
|
||||||
this->operation_start_ms_ = get_loop_time_ms();
|
this->operation_start_ms_ = get_loop_time_ms();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MitsubishiCN105::update_status_() {
|
void MitsubishiCN105::update_status_() {
|
||||||
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload = {this->current_status_msg_type_};
|
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{this->current_status_msg_type_};
|
||||||
this->send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload));
|
this->send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,12 +251,22 @@ bool MitsubishiCN105::process_status_packet_(const uint8_t *payload, size_t len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len) {
|
bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len) {
|
||||||
|
Property::Decoder decoder{std::span{payload, len}, this->property_context_, this->pending_updates_};
|
||||||
switch (msg_type) {
|
switch (msg_type) {
|
||||||
case STATUS_MSG_SETTINGS:
|
case STATUS_MSG_SETTINGS:
|
||||||
return this->parse_status_settings_(payload, len);
|
if (!decoder.decode_settings(this->status_)) {
|
||||||
|
ESP_LOGVV(TAG, "RX settings payload too short");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
case STATUS_MSG_TELEMETRY:
|
case STATUS_MSG_TELEMETRY:
|
||||||
return this->parse_status_telemetry_(payload, len);
|
if (!decoder.decode_room_temperature(this->status_)) {
|
||||||
|
ESP_LOGVV(TAG, "RX telemetry payload too short");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->last_telemetry_update_ms_ = get_loop_time_ms();
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type);
|
ESP_LOGVV(TAG, "RX unsupported status msg type 0x%02X", msg_type);
|
||||||
@@ -349,54 +274,6 @@ bool MitsubishiCN105::parse_status_payload_(uint8_t msg_type, const uint8_t *pay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->pending_updates_.contains(UpdateFlag::POWER)) {
|
|
||||||
this->status_.power_on = payload[2] != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->use_temperature_encoding_b_ = payload[10] != 0;
|
|
||||||
if (!this->pending_updates_.contains(UpdateFlag::TEMPERATURE)) {
|
|
||||||
this->status_.target_temperature = decode_temperature(-payload[4], payload[10], TARGET_TEMPERATURE_ENC_A_OFFSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->pending_updates_.contains(UpdateFlag::MODE)) {
|
|
||||||
const bool i_see = payload[3] > 0x08;
|
|
||||||
this->status_.mode = PROTOCOL_MODE_MAP.lookup(payload[3] - (i_see ? 0x08 : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->pending_updates_.contains(UpdateFlag::FAN)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MitsubishiCN105::parse_status_telemetry_(const uint8_t *payload, size_t len) {
|
|
||||||
if (len <= 5) {
|
|
||||||
ESP_LOGVV(TAG, "RX telemetry payload too short");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->status_.room_temperature = decode_temperature(payload[2], payload[5], 10);
|
|
||||||
this->last_telemetry_update_ms_ = get_loop_time_ms();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MitsubishiCN105::set_remote_temperature(float temperature) {
|
void MitsubishiCN105::set_remote_temperature(float temperature) {
|
||||||
if (std::isnan(temperature)) {
|
if (std::isnan(temperature)) {
|
||||||
ESP_LOGD(TAG, "Ignoring NaN remote temperature");
|
ESP_LOGD(TAG, "Ignoring NaN remote temperature");
|
||||||
@@ -415,12 +292,12 @@ void MitsubishiCN105::clear_remote_temperature() {
|
|||||||
|
|
||||||
void MitsubishiCN105::set_remote_temperature_half_deg_(uint8_t temperature_half_deg) {
|
void MitsubishiCN105::set_remote_temperature_half_deg_(uint8_t temperature_half_deg) {
|
||||||
this->remote_temperature_half_deg_ = temperature_half_deg;
|
this->remote_temperature_half_deg_ = temperature_half_deg;
|
||||||
this->pending_updates_.set(UpdateFlag::REMOTE_TEMPERATURE);
|
this->pending_updates_.set(Property::Temperature::Remote::ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MitsubishiCN105::set_power(bool power_on) {
|
void MitsubishiCN105::set_power(bool power_on) {
|
||||||
this->status_.power_on = power_on;
|
this->status_.power_on = power_on;
|
||||||
this->pending_updates_.set(UpdateFlag::POWER);
|
this->pending_updates_.set(Property::Power::ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MitsubishiCN105::set_target_temperature(float target_temperature) {
|
void MitsubishiCN105::set_target_temperature(float target_temperature) {
|
||||||
@@ -429,101 +306,42 @@ void MitsubishiCN105::set_target_temperature(float target_temperature) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->status_.target_temperature = target_temperature;
|
this->status_.target_temperature = target_temperature;
|
||||||
this->pending_updates_.set(UpdateFlag::TEMPERATURE);
|
this->pending_updates_.set(Property::Temperature::Target::ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MitsubishiCN105::set_mode(Mode mode) {
|
void MitsubishiCN105::set_mode(Mode mode) {
|
||||||
if (!PROTOCOL_MODE_MAP.is_valid(mode)) {
|
if (!Property::Mode::validate_and_set(mode, this->status_, this->pending_updates_)) {
|
||||||
ESP_LOGD(TAG, "Setting invalid mode: %u", static_cast<uint8_t>(mode));
|
ESP_LOGD(TAG, "Ignoring 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) {
|
void MitsubishiCN105::set_fan_mode(FanMode fan_mode) {
|
||||||
if (!PROTOCOL_FAN_MODE_MAP.is_valid(fan_mode)) {
|
if (!Property::FanMode::validate_and_set(fan_mode, this->status_, this->pending_updates_)) {
|
||||||
ESP_LOGD(TAG, "Setting invalid fan mode: %u", static_cast<uint8_t>(fan_mode));
|
ESP_LOGD(TAG, "Ignoring 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::set_vane_mode(VaneMode vane_mode) {
|
void MitsubishiCN105::set_vane_mode(VaneMode vane_mode) {
|
||||||
if (!PROTOCOL_VANE_MODE_MAP.is_valid(vane_mode)) {
|
if (!Property::VaneMode::validate_and_set(vane_mode, this->status_, this->pending_updates_)) {
|
||||||
ESP_LOGD(TAG, "Setting invalid vane mode: %u", static_cast<uint8_t>(vane_mode));
|
ESP_LOGD(TAG, "Ignoring 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) {
|
void MitsubishiCN105::set_wide_vane_mode(WideVaneMode wide_vane_mode) {
|
||||||
if (!PROTOCOL_WIDE_VANE_MODE_MAP.is_valid(wide_vane_mode)) {
|
if (!Property::WideVaneMode::validate_and_set(wide_vane_mode, this->status_, this->pending_updates_)) {
|
||||||
ESP_LOGD(TAG, "Setting invalid wide vane mode: %u", static_cast<uint8_t>(wide_vane_mode));
|
ESP_LOGD(TAG, "Ignoring 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_() {
|
void MitsubishiCN105::apply_settings_() {
|
||||||
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{};
|
std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{};
|
||||||
|
Property::Encoder encoder{payload.data(), this->property_context_, this->pending_updates_};
|
||||||
|
|
||||||
// Apply all other pending settings first; handle REMOTE_TEMPERATURE last
|
// Apply all other pending settings first; handle REMOTE_TEMPERATURE last
|
||||||
if (this->pending_updates_.contains_only(UpdateFlag::REMOTE_TEMPERATURE)) {
|
if (this->pending_updates_.contains_only(Property::Temperature::Remote::ID)) {
|
||||||
payload[0] = 0x07;
|
encoder.encode_remote_temperature(this->remote_temperature_half_deg_);
|
||||||
if (this->remote_temperature_half_deg_ == REMOTE_TEMPERATURE_DISABLED) {
|
|
||||||
payload[3] = 0x80;
|
|
||||||
} else {
|
} else {
|
||||||
payload[1] = 0x01;
|
encoder.encode_settings(this->status_);
|
||||||
payload[2] = static_cast<uint8_t>(this->remote_temperature_half_deg_ - 16);
|
|
||||||
payload[3] = static_cast<uint8_t>(this->remote_temperature_half_deg_ + 128);
|
|
||||||
}
|
|
||||||
this->pending_updates_.clear(UpdateFlag::REMOTE_TEMPERATURE);
|
|
||||||
} else {
|
|
||||||
payload[0] = 0x01;
|
|
||||||
if (this->pending_updates_.contains(UpdateFlag::POWER)) {
|
|
||||||
payload[1] |= 0x01;
|
|
||||||
payload[3] = this->status_.power_on ? 0x01 : 0x00;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->pending_updates_.contains(UpdateFlag::TEMPERATURE)) {
|
|
||||||
payload[1] |= 0x04;
|
|
||||||
if (this->use_temperature_encoding_b_) {
|
|
||||||
payload[14] = static_cast<uint8_t>(std::round(this->status_.target_temperature * 2.0f) + 128);
|
|
||||||
} else {
|
|
||||||
payload[5] =
|
|
||||||
static_cast<uint8_t>(TARGET_TEMPERATURE_ENC_A_OFFSET - std::round(this->status_.target_temperature));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->pending_updates_.contains(UpdateFlag::MODE) &&
|
|
||||||
PROTOCOL_MODE_MAP.reverse_lookup(this->status_.mode, payload[4])) {
|
|
||||||
payload[1] |= 0x02;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->pending_updates_.contains(UpdateFlag::FAN) &&
|
|
||||||
PROTOCOL_FAN_MODE_MAP.reverse_lookup(this->status_.fan_mode, payload[6])) {
|
|
||||||
payload[1] |= 0x08;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
this->send_packet_(make_packet(PACKET_TYPE_WRITE_SETTINGS_REQUEST, payload));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
namespace esphome::mitsubishi_cn105 {
|
namespace esphome::mitsubishi_cn105 {
|
||||||
|
|
||||||
@@ -121,44 +122,47 @@ class MitsubishiCN105 {
|
|||||||
uint8_t read_pos_{0};
|
uint8_t read_pos_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class UpdateFlag : uint8_t {
|
enum class PropertyId : uint8_t {
|
||||||
TEMPERATURE = 0,
|
TEMPERATURE = 0,
|
||||||
POWER = 1,
|
POWER = 1,
|
||||||
MODE = 2,
|
MODE = 2,
|
||||||
FAN = 3,
|
FAN = 3,
|
||||||
VANE = 4,
|
VANE = 4,
|
||||||
WIDE_VANE = 5,
|
WIDE_VANE = 5,
|
||||||
REMOTE_TEMPERATURE = 6,
|
REMOTE_TEMPERATURE = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UpdateFlags {
|
struct UpdateFlags {
|
||||||
template<typename... Flags> void set(Flags... flags) { (this->mask_.insert(flags), ...); }
|
void set(PropertyId id) { this->mask_.insert(id); }
|
||||||
template<typename... Flags> void clear(Flags... flags) { (this->mask_.erase(flags), ...); }
|
void clear(PropertyId id) { this->mask_.erase(id); }
|
||||||
bool any() const { return !this->mask_.empty(); }
|
bool any() const { return !this->mask_.empty(); }
|
||||||
bool contains(UpdateFlag flag) const { return this->mask_.count(flag); }
|
bool contains(PropertyId id) const { return this->mask_.count(id); }
|
||||||
bool contains_only(UpdateFlag flag) const { return this->mask_.get_mask() == Mask{flag}.get_mask(); }
|
bool contains_only(PropertyId id) const { return this->mask_.get_mask() == Mask{id}.get_mask(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using Mask =
|
using Mask =
|
||||||
FiniteSetMask<UpdateFlag, DefaultBitPolicy<UpdateFlag, static_cast<int>(UpdateFlag::REMOTE_TEMPERATURE) + 1>>;
|
FiniteSetMask<PropertyId, DefaultBitPolicy<PropertyId, static_cast<int>(PropertyId::REMOTE_TEMPERATURE) + 1>>;
|
||||||
|
|
||||||
Mask mask_;
|
Mask mask_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PropertyContext {
|
||||||
|
bool use_temperature_encoding_b{false};
|
||||||
|
bool set_wide_vane_high_bit{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
friend struct Property;
|
||||||
|
|
||||||
void set_state_(State new_state);
|
void set_state_(State new_state);
|
||||||
void did_transition_(State to);
|
void did_transition_(State to);
|
||||||
bool process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len);
|
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 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_payload_(uint8_t msg_type, const uint8_t *payload, size_t len);
|
||||||
bool parse_status_settings_(const uint8_t *payload, size_t len);
|
void send_packet_(std::span<const uint8_t> packet);
|
||||||
bool parse_status_telemetry_(const uint8_t *payload, size_t len);
|
|
||||||
void send_packet_(const uint8_t *packet, size_t len);
|
|
||||||
void update_status_();
|
void update_status_();
|
||||||
bool should_request_telemetry_() const;
|
bool should_request_telemetry_() const;
|
||||||
void apply_settings_();
|
void apply_settings_();
|
||||||
bool has_timed_out_(uint32_t timeout) const { return ((get_loop_time_ms() - this->operation_start_ms_) >= timeout); }
|
bool has_timed_out_(uint32_t timeout) const { return ((get_loop_time_ms() - this->operation_start_ms_) >= timeout); }
|
||||||
void set_remote_temperature_half_deg_(uint8_t temperature_half_deg);
|
void set_remote_temperature_half_deg_(uint8_t temperature_half_deg);
|
||||||
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 bool should_transition(State from, State to);
|
||||||
static const LogString *state_to_string(State state);
|
static const LogString *state_to_string(State state);
|
||||||
|
|
||||||
@@ -175,8 +179,7 @@ class MitsubishiCN105 {
|
|||||||
Status status_{};
|
Status status_{};
|
||||||
State state_{State::NOT_CONNECTED};
|
State state_{State::NOT_CONNECTED};
|
||||||
UpdateFlags pending_updates_;
|
UpdateFlags pending_updates_;
|
||||||
bool use_temperature_encoding_b_{false};
|
PropertyContext property_context_;
|
||||||
bool set_wide_vane_high_bit_{false};
|
|
||||||
FrameParser frame_parser_;
|
FrameParser frame_parser_;
|
||||||
uint8_t current_status_msg_type_{0};
|
uint8_t current_status_msg_type_{0};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "mitsubishi_cn105.h"
|
||||||
|
|
||||||
|
namespace esphome::mitsubishi_cn105 {
|
||||||
|
|
||||||
|
template<auto Unknown, size_t N> struct LookupMap {
|
||||||
|
using value_type = decltype(Unknown);
|
||||||
|
const std::array<value_type, N> table;
|
||||||
|
|
||||||
|
constexpr value_type lookup(uint8_t raw) const { return (raw < N) ? this->table[raw] : Unknown; }
|
||||||
|
|
||||||
|
constexpr bool reverse_lookup(value_type value, uint8_t &out) const {
|
||||||
|
static_assert(N <= std::numeric_limits<uint8_t>::max());
|
||||||
|
if (value == Unknown) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < static_cast<uint8_t>(N); ++i) {
|
||||||
|
if (this->table[i] == value) {
|
||||||
|
out = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<auto Unknown, class T, std::size_t N> static constexpr auto make_map(const T (&values)[N]) {
|
||||||
|
return LookupMap<Unknown, N>{std::to_array(values)};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Property {
|
||||||
|
using PropertyId = MitsubishiCN105::PropertyId;
|
||||||
|
using Status = MitsubishiCN105::Status;
|
||||||
|
using PropertyContext = MitsubishiCN105::PropertyContext;
|
||||||
|
|
||||||
|
struct Power {
|
||||||
|
static constexpr auto ID = PropertyId::POWER;
|
||||||
|
|
||||||
|
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {}
|
||||||
|
|
||||||
|
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
|
||||||
|
status.power_on = payload[2] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode(uint8_t *payload, const Status &status, const PropertyContext &ctx) {
|
||||||
|
payload[1] |= 0x01;
|
||||||
|
payload[3] = status.power_on ? 0x01 : 0x00;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Temperature {
|
||||||
|
struct Target {
|
||||||
|
static constexpr auto ID = PropertyId::TEMPERATURE;
|
||||||
|
static constexpr uint8_t TARGET_TEMPERATURE_ENC_A_OFFSET = 31;
|
||||||
|
|
||||||
|
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {
|
||||||
|
ctx.use_temperature_encoding_b = payload[10] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
|
||||||
|
status.target_temperature = Temperature::decode(-payload[4], payload[10], TARGET_TEMPERATURE_ENC_A_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode(uint8_t *payload, const Status &status, const PropertyContext &ctx) {
|
||||||
|
payload[1] |= 0x04;
|
||||||
|
if (ctx.use_temperature_encoding_b) {
|
||||||
|
payload[14] = static_cast<uint8_t>(std::round(status.target_temperature * 2.0f) + 128);
|
||||||
|
} else {
|
||||||
|
payload[5] = static_cast<uint8_t>(TARGET_TEMPERATURE_ENC_A_OFFSET - std::round(status.target_temperature));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Room {
|
||||||
|
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {}
|
||||||
|
|
||||||
|
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
|
||||||
|
status.room_temperature = Temperature::decode(payload[2], payload[5], 10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Remote {
|
||||||
|
static constexpr auto ID = PropertyId::REMOTE_TEMPERATURE;
|
||||||
|
|
||||||
|
static void encode(uint8_t *payload, uint8_t remote_temperature_half_deg, const PropertyContext &) {
|
||||||
|
if (remote_temperature_half_deg == MitsubishiCN105::REMOTE_TEMPERATURE_DISABLED) {
|
||||||
|
payload[3] = 0x80;
|
||||||
|
} else {
|
||||||
|
payload[1] = 0x01;
|
||||||
|
payload[2] = static_cast<uint8_t>(remote_temperature_half_deg - 16);
|
||||||
|
payload[3] = static_cast<uint8_t>(remote_temperature_half_deg + 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static constexpr float decode(int temp_a, int temp_b, int delta) {
|
||||||
|
return temp_b != 0 ? (temp_b - 128) / 2.0f : delta + temp_a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Derived, auto Field> struct Lookup {
|
||||||
|
using Value = std::remove_cvref_t<decltype(std::declval<Status>().*Field)>;
|
||||||
|
|
||||||
|
static void decode_context(PropertyContext &ctx, const uint8_t *payload) {}
|
||||||
|
|
||||||
|
static void decode(Status &status, const uint8_t *payload, const PropertyContext &ctx) {
|
||||||
|
status.*Field = Derived::MAP.lookup(Derived::decode_raw(payload, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode(uint8_t *payload, const Status &status, const PropertyContext &ctx) {
|
||||||
|
uint8_t raw;
|
||||||
|
if (Derived::MAP.reverse_lookup(status.*Field, raw)) {
|
||||||
|
Derived::encode_raw(payload, raw, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Mask> static bool validate_and_set(Value value, Status &status, Mask &mask) {
|
||||||
|
uint8_t raw;
|
||||||
|
if (!Derived::MAP.reverse_lookup(value, raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
status.*Field = value;
|
||||||
|
mask.set(Derived::ID);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend Derived;
|
||||||
|
constexpr Lookup() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mode : Lookup<Mode, &Status::mode> {
|
||||||
|
static constexpr auto ID = PropertyId::MODE;
|
||||||
|
static constexpr auto MAP = make_map<MitsubishiCN105::Mode::UNKNOWN>({
|
||||||
|
MitsubishiCN105::Mode::UNKNOWN, // 0x00
|
||||||
|
MitsubishiCN105::Mode::HEAT, // 0x01
|
||||||
|
MitsubishiCN105::Mode::DRY, // 0x02
|
||||||
|
MitsubishiCN105::Mode::COOL, // 0x03
|
||||||
|
MitsubishiCN105::Mode::UNKNOWN, // 0x04
|
||||||
|
MitsubishiCN105::Mode::UNKNOWN, // 0x05
|
||||||
|
MitsubishiCN105::Mode::UNKNOWN, // 0x06
|
||||||
|
MitsubishiCN105::Mode::FAN_ONLY, // 0x07
|
||||||
|
MitsubishiCN105::Mode::AUTO // 0x08
|
||||||
|
});
|
||||||
|
|
||||||
|
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) {
|
||||||
|
const bool i_see = payload[3] > 0x08;
|
||||||
|
return payload[3] - (i_see ? 0x08 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &) {
|
||||||
|
payload[1] |= 0x02;
|
||||||
|
payload[4] = raw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FanMode : Lookup<FanMode, &Status::fan_mode> {
|
||||||
|
static constexpr auto ID = PropertyId::FAN;
|
||||||
|
static constexpr auto MAP = make_map<MitsubishiCN105::FanMode::UNKNOWN>({
|
||||||
|
MitsubishiCN105::FanMode::AUTO, // 0x00
|
||||||
|
MitsubishiCN105::FanMode::QUIET, // 0x01
|
||||||
|
MitsubishiCN105::FanMode::SPEED_1, // 0x02
|
||||||
|
MitsubishiCN105::FanMode::SPEED_2, // 0x03
|
||||||
|
MitsubishiCN105::FanMode::UNKNOWN, // 0x04
|
||||||
|
MitsubishiCN105::FanMode::SPEED_3, // 0x05
|
||||||
|
MitsubishiCN105::FanMode::SPEED_4 // 0x06
|
||||||
|
});
|
||||||
|
|
||||||
|
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) { return payload[5]; }
|
||||||
|
|
||||||
|
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &) {
|
||||||
|
payload[1] |= 0x08;
|
||||||
|
payload[6] = raw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VaneMode : Lookup<VaneMode, &Status::vane_mode> {
|
||||||
|
static constexpr auto ID = PropertyId::VANE;
|
||||||
|
static constexpr auto 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 uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) { return payload[6]; }
|
||||||
|
|
||||||
|
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &) {
|
||||||
|
payload[1] |= 0x10;
|
||||||
|
payload[7] = raw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WideVaneMode : Lookup<WideVaneMode, &Status::wide_vane_mode> {
|
||||||
|
static constexpr auto ID = PropertyId::WIDE_VANE;
|
||||||
|
static constexpr auto 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 void decode_context(PropertyContext &ctx, const uint8_t *payload) {
|
||||||
|
ctx.set_wide_vane_high_bit = (payload[9] & 0xF0) == 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t decode_raw(const uint8_t *payload, const PropertyContext &ctx) { return payload[9] & 0x0F; }
|
||||||
|
|
||||||
|
static void encode_raw(uint8_t *payload, uint8_t raw, const PropertyContext &ctx) {
|
||||||
|
payload[2] |= 0x01;
|
||||||
|
payload[13] = ctx.set_wide_vane_high_bit ? raw | 0x80 : raw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Mask> struct Decoder {
|
||||||
|
const std::span<const uint8_t> payload;
|
||||||
|
PropertyContext &context;
|
||||||
|
const Mask &pending_writes;
|
||||||
|
|
||||||
|
bool ESPHOME_ALWAYS_INLINE decode_settings(Status &status) {
|
||||||
|
if (this->payload.size() <= 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->decode_<Power, Temperature::Target, Mode, FanMode, VaneMode, WideVaneMode>(status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPHOME_ALWAYS_INLINE decode_room_temperature(Status &status) {
|
||||||
|
if (this->payload.size() <= 5) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->decode_<Temperature::Room>(status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename T, typename Out> ESPHOME_ALWAYS_INLINE void decode_one_(Out &out) {
|
||||||
|
T::decode_context(this->context, this->payload.data());
|
||||||
|
if constexpr (requires { T::ID; }) {
|
||||||
|
if (this->pending_writes.contains(T::ID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T::decode(out, this->payload.data(), this->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... T, typename Out> void ESPHOME_ALWAYS_INLINE decode_(Out &out) {
|
||||||
|
(this->decode_one_<T>(out), ...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Mask> struct Encoder {
|
||||||
|
uint8_t *payload;
|
||||||
|
const PropertyContext &context;
|
||||||
|
Mask &pending_writes;
|
||||||
|
|
||||||
|
void ESPHOME_ALWAYS_INLINE encode_settings(const Status &status) {
|
||||||
|
this->payload[0] = 0x01;
|
||||||
|
this->encode_and_clear_<Power, Temperature::Target, WideVaneMode, VaneMode, Mode, FanMode>(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPHOME_ALWAYS_INLINE encode_remote_temperature(uint8_t remote_temperature_half_deg) {
|
||||||
|
this->payload[0] = 0x07;
|
||||||
|
this->encode_and_clear_<Temperature::Remote>(remote_temperature_half_deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename... T, typename In> void ESPHOME_ALWAYS_INLINE encode_and_clear_(const In &in) {
|
||||||
|
(this->encode_one_<T>(in), ...);
|
||||||
|
(this->pending_writes.clear(T::ID), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename In> void encode_one_(const In &in) {
|
||||||
|
if (this->pending_writes.contains(T::ID)) {
|
||||||
|
T::encode(this->payload, in, this->context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::mitsubishi_cn105
|
||||||
@@ -266,7 +266,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) {
|
|||||||
ctx.sut.update();
|
ctx.sut.update();
|
||||||
|
|
||||||
EXPECT_TRUE(ctx.sut.status().power_on);
|
EXPECT_TRUE(ctx.sut.status().power_on);
|
||||||
EXPECT_FALSE(ctx.sut.use_temperature_encoding_b_);
|
EXPECT_FALSE(ctx.sut.property_context_.use_temperature_encoding_b);
|
||||||
EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f);
|
EXPECT_EQ(ctx.sut.status().target_temperature, 26.0f);
|
||||||
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::COOL);
|
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::COOL);
|
||||||
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::QUIET);
|
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::QUIET);
|
||||||
@@ -281,7 +281,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) {
|
|||||||
ctx.sut.update();
|
ctx.sut.update();
|
||||||
|
|
||||||
EXPECT_FALSE(ctx.sut.status().power_on);
|
EXPECT_FALSE(ctx.sut.status().power_on);
|
||||||
EXPECT_TRUE(ctx.sut.use_temperature_encoding_b_);
|
EXPECT_TRUE(ctx.sut.property_context_.use_temperature_encoding_b);
|
||||||
EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f);
|
EXPECT_EQ(ctx.sut.status().target_temperature, 18.5f);
|
||||||
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::FAN_ONLY);
|
EXPECT_EQ(ctx.sut.status().mode, MitsubishiCN105::Mode::FAN_ONLY);
|
||||||
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::SPEED_4);
|
EXPECT_EQ(ctx.sut.status().fan_mode, MitsubishiCN105::FanMode::SPEED_4);
|
||||||
@@ -316,7 +316,7 @@ TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitNotSet) {
|
|||||||
ctx.sut.update();
|
ctx.sut.update();
|
||||||
|
|
||||||
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
|
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
|
||||||
EXPECT_FALSE(ctx.sut.set_wide_vane_high_bit_);
|
EXPECT_FALSE(ctx.sut.property_context_.set_wide_vane_high_bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
|
TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
|
||||||
@@ -328,7 +328,7 @@ TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) {
|
|||||||
ctx.sut.update();
|
ctx.sut.update();
|
||||||
|
|
||||||
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
|
EXPECT_EQ(ctx.sut.status().wide_vane_mode, MitsubishiCN105::WideVaneMode::CENTER);
|
||||||
EXPECT_TRUE(ctx.sut.set_wide_vane_high_bit_);
|
EXPECT_TRUE(ctx.sut.property_context_.set_wide_vane_high_bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) {
|
TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) {
|
||||||
@@ -354,7 +354,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedA) {
|
|||||||
TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) {
|
TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) {
|
||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
|
|
||||||
ctx.sut.use_temperature_encoding_b_ = true;
|
ctx.sut.property_context_.use_temperature_encoding_b = true;
|
||||||
ctx.sut.set_target_temperature(26.0f);
|
ctx.sut.set_target_temperature(26.0f);
|
||||||
ctx.sut.apply_settings();
|
ctx.sut.apply_settings();
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) {
|
|||||||
TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) {
|
TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) {
|
||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
|
|
||||||
ctx.sut.use_temperature_encoding_b_ = true;
|
ctx.sut.property_context_.use_temperature_encoding_b = true;
|
||||||
ctx.sut.set_target_temperature(26.5f);
|
ctx.sut.set_target_temperature(26.5f);
|
||||||
ctx.sut.apply_settings();
|
ctx.sut.apply_settings();
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@ TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitNotSet) {
|
|||||||
TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) {
|
TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) {
|
||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
|
|
||||||
ctx.sut.set_wide_vane_high_bit_ = true;
|
ctx.sut.property_context_.set_wide_vane_high_bit = true;
|
||||||
ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
|
ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT);
|
||||||
ctx.sut.apply_settings();
|
ctx.sut.apply_settings();
|
||||||
|
|
||||||
@@ -445,7 +445,7 @@ TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) {
|
|||||||
EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
|
EXPECT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
|
||||||
|
|
||||||
// Write new values
|
// Write new values
|
||||||
ctx.sut.use_temperature_encoding_b_ = true;
|
ctx.sut.property_context_.use_temperature_encoding_b = true;
|
||||||
ctx.sut.set_power(false);
|
ctx.sut.set_power(false);
|
||||||
ctx.sut.set_target_temperature(25.0f);
|
ctx.sut.set_target_temperature(25.0f);
|
||||||
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
||||||
@@ -508,7 +508,7 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
|
|||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
|
|
||||||
// Queue normal settings plus remote temperature together.
|
// Queue normal settings plus remote temperature together.
|
||||||
ctx.sut.use_temperature_encoding_b_ = true;
|
ctx.sut.property_context_.use_temperature_encoding_b = true;
|
||||||
ctx.sut.set_power(false);
|
ctx.sut.set_power(false);
|
||||||
ctx.sut.set_target_temperature(25.0f);
|
ctx.sut.set_target_temperature(25.0f);
|
||||||
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
||||||
@@ -521,11 +521,11 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
|
|||||||
|
|
||||||
EXPECT_THAT(ctx.uart.tx, ::testing::ElementsAre(0xFC, 0x41, 0x01, 0x30, 0x10, 0x01, 0x0F, 0x00, 0x00, 0x01, 0x00,
|
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));
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0x00, 0xBB));
|
||||||
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::POWER));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::POWER));
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::TEMPERATURE));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::TEMPERATURE));
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::MODE));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::MODE));
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::FAN));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::FAN));
|
||||||
|
|
||||||
// ACK the first write. Remote temperature should still be pending afterward.
|
// ACK the first write. Remote temperature should still be pending afterward.
|
||||||
ctx.uart.tx.clear();
|
ctx.uart.tx.clear();
|
||||||
@@ -533,7 +533,7 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) {
|
|||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E});
|
||||||
ASSERT_FALSE(ctx.sut.update());
|
ASSERT_FALSE(ctx.sut.update());
|
||||||
|
|
||||||
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
|
|
||||||
// The next apply sends the remote-temperature packet and clears the last pending flag.
|
// The next apply sends the remote-temperature packet and clears the last pending flag.
|
||||||
ctx.uart.tx.clear();
|
ctx.uart.tx.clear();
|
||||||
@@ -557,7 +557,7 @@ TEST(MitsubishiCN105Tests, WriteTimeoutClearsStatusUpdateWaitCreditOnReconnect)
|
|||||||
ASSERT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
|
ASSERT_EQ(ctx.sut.status_update_wait_credit_ms_, 0);
|
||||||
|
|
||||||
// Interrupt that wait with a write so credit is accumulated.
|
// Interrupt that wait with a write so credit is accumulated.
|
||||||
ctx.sut.use_temperature_encoding_b_ = true;
|
ctx.sut.property_context_.use_temperature_encoding_b = true;
|
||||||
ctx.sut.set_power(false);
|
ctx.sut.set_power(false);
|
||||||
ctx.sut.set_target_temperature(25.0f);
|
ctx.sut.set_target_temperature(25.0f);
|
||||||
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
ctx.sut.set_mode(MitsubishiCN105::Mode::HEAT);
|
||||||
@@ -581,25 +581,25 @@ TEST(MitsubishiCN105Tests, SetOutOfRangeRemoteRoomTempIsIgnored) {
|
|||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
|
|
||||||
ctx.sut.set_remote_temperature(7.0f);
|
ctx.sut.set_remote_temperature(7.0f);
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
|
|
||||||
ctx.sut.set_remote_temperature(40.0f);
|
ctx.sut.set_remote_temperature(40.0f);
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
|
|
||||||
ctx.sut.set_remote_temperature(NAN);
|
ctx.sut.set_remote_temperature(NAN);
|
||||||
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MitsubishiCN105Tests, SetMinRemoteRoomTemp) {
|
TEST(MitsubishiCN105Tests, SetMinRemoteRoomTemp) {
|
||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
ctx.sut.set_remote_temperature(8.0f);
|
ctx.sut.set_remote_temperature(8.0f);
|
||||||
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MitsubishiCN105Tests, SetMaxRemoteRoomTemp) {
|
TEST(MitsubishiCN105Tests, SetMaxRemoteRoomTemp) {
|
||||||
MitsubishiCN105TestsContext ctx;
|
MitsubishiCN105TestsContext ctx;
|
||||||
ctx.sut.set_remote_temperature(39.5f);
|
ctx.sut.set_remote_temperature(39.5f);
|
||||||
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE));
|
EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::PropertyId::REMOTE_TEMPERATURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::mitsubishi_cn105::testing
|
} // namespace esphome::mitsubishi_cn105::testing
|
||||||
|
|||||||
@@ -47,12 +47,11 @@ class TestableMitsubishiCN105 : public MitsubishiCN105 {
|
|||||||
public:
|
public:
|
||||||
using MitsubishiCN105::MitsubishiCN105;
|
using MitsubishiCN105::MitsubishiCN105;
|
||||||
using MitsubishiCN105::State;
|
using MitsubishiCN105::State;
|
||||||
using MitsubishiCN105::UpdateFlag;
|
using MitsubishiCN105::PropertyId;
|
||||||
using MitsubishiCN105::state_;
|
using MitsubishiCN105::state_;
|
||||||
using MitsubishiCN105::status_;
|
using MitsubishiCN105::status_;
|
||||||
using MitsubishiCN105::operation_start_ms_;
|
using MitsubishiCN105::operation_start_ms_;
|
||||||
using MitsubishiCN105::use_temperature_encoding_b_;
|
using MitsubishiCN105::property_context_;
|
||||||
using MitsubishiCN105::set_wide_vane_high_bit_;
|
|
||||||
using MitsubishiCN105::status_update_wait_credit_ms_;
|
using MitsubishiCN105::status_update_wait_credit_ms_;
|
||||||
using MitsubishiCN105::pending_updates_;
|
using MitsubishiCN105::pending_updates_;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user