9static const char *
const TAG =
"mitsubishi_cn105.driver";
11static constexpr uint32_t RESPONSE_TIMEOUT_MS = 2000;
13static constexpr uint8_t TARGET_TEMPERATURE_ENC_A_OFFSET = 31;
15static constexpr size_t REQUEST_PAYLOAD_LEN = 0x10;
16static constexpr size_t HEADER_LEN = 5;
17static constexpr uint8_t PREAMBLE = 0xFC;
18static constexpr uint8_t HEADER_BYTE_1 = 0x01;
19static constexpr uint8_t HEADER_BYTE_2 = 0x30;
21static constexpr uint8_t PACKET_TYPE_CONNECT_REQUEST = 0x5A;
22static constexpr uint8_t PACKET_TYPE_CONNECT_RESPONSE = 0x7A;
23static constexpr std::array<uint8_t, 2> CONNECT_REQUEST_PAYLOAD = {0xCA, 0x01};
25static constexpr uint8_t PACKET_TYPE_STATUS_REQUEST = 0x42;
26static constexpr uint8_t PACKET_TYPE_STATUS_RESPONSE = 0x62;
27static constexpr uint8_t STATUS_MSG_SETTINGS = 0x02;
28static constexpr uint8_t STATUS_MSG_ROOM_TEMP = 0x03;
30static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41;
31static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61;
33template<auto Unknown,
size_t N>
struct LookupMap {
34 using value_type =
decltype(Unknown);
35 static constexpr auto UNKNOWN_VALUE = Unknown;
36 const std::array<value_type, N> table;
38 constexpr value_type lookup(uint8_t
raw)
const {
return (
raw < N) ? this->table[
raw] : UNKNOWN_VALUE; }
40 constexpr bool reverse_lookup(value_type value, uint8_t &out)
const {
41 static_assert(N <= std::numeric_limits<uint8_t>::max());
42 if (value == UNKNOWN_VALUE) {
45 for (uint8_t i = 0; i < static_cast<uint8_t>(N); ++i) {
46 if (this->table[i] == value) {
54 constexpr bool is_valid(value_type value)
const {
56 return reverse_lookup(value,
raw);
60template<auto Unknown,
class T, std::
size_t N>
static constexpr auto make_map(
const T (&values)[N]) {
61 return LookupMap<Unknown, N>{std::to_array(values)};
64static constexpr auto PROTOCOL_MODE_MAP = make_map<MitsubishiCN105::Mode::UNKNOWN>({
76static constexpr auto PROTOCOL_FAN_MODE_MAP = make_map<MitsubishiCN105::FanMode::UNKNOWN>({
86static constexpr auto PROTOCOL_VANE_MODE_MAP = make_map<MitsubishiCN105::VaneMode::UNKNOWN>({
97static constexpr auto PROTOCOL_WIDE_VANE_MODE_MAP = make_map<MitsubishiCN105::WideVaneMode::UNKNOWN>({
113static constexpr uint8_t
checksum(
const uint8_t *bytes,
size_t length) {
114 return static_cast<uint8_t
>(0xFC - std::accumulate(bytes, bytes +
length, uint8_t{0}));
117template<std::
size_t PayloadSize>
118static constexpr auto make_packet(uint8_t
type,
const std::array<uint8_t, PayloadSize> &payload) {
119 const size_t full_len = PayloadSize + HEADER_LEN + 1;
120 std::array<uint8_t, full_len> packet{PREAMBLE,
type, HEADER_BYTE_1, HEADER_BYTE_2,
static_cast<uint8_t
>(PayloadSize)};
121 std::copy_n(payload.begin(), PayloadSize, packet.begin() + HEADER_LEN);
122 packet.back() =
checksum(packet.data(), packet.size() - 1);
126static constexpr float decode_temperature(
int temp_a,
int temp_b,
int delta) {
127 return temp_b != 0 ? (temp_b - 128) / 2.0f : delta + temp_a;
130static constexpr auto CONNECT_PACKET = make_packet(PACKET_TYPE_CONNECT_REQUEST, CONNECT_REQUEST_PAYLOAD);
287 this->
send_packet_(make_packet(PACKET_TYPE_STATUS_REQUEST, payload));
292 case PACKET_TYPE_CONNECT_RESPONSE:
296 case PACKET_TYPE_STATUS_RESPONSE:
299 case PACKET_TYPE_WRITE_SETTINGS_RESPONSE:
304 ESP_LOGVV(TAG,
"RX unknown packet type 0x%02X",
type);
311 ESP_LOGVV(TAG,
"RX status packet too short");
315 const auto previous = this->
status_;
316 const auto msg_type = payload[0];
339 case STATUS_MSG_SETTINGS:
342 case STATUS_MSG_ROOM_TEMP:
346 ESP_LOGVV(TAG,
"RX unsupported status msg type 0x%02X", msg_type);
353 ESP_LOGVV(TAG,
"RX settings payload too short");
367 const bool i_see = payload[3] > 0x08;
368 this->
status_.
mode = PROTOCOL_MODE_MAP.lookup(payload[3] - (i_see ? 0x08 : 0));
389 ESP_LOGVV(TAG,
"RX room temperature payload too short");
401 ESP_LOGD(TAG,
"Ignoring NaN remote temperature");
405 ESP_LOGD(TAG,
"Ignoring out-of-range remote temperature: %.1f",
temperature);
435 if (!PROTOCOL_MODE_MAP.is_valid(
mode)) {
436 ESP_LOGD(TAG,
"Setting invalid mode: %u",
static_cast<uint8_t
>(
mode));
444 if (!PROTOCOL_FAN_MODE_MAP.is_valid(
fan_mode)) {
445 ESP_LOGD(TAG,
"Setting invalid fan mode: %u",
static_cast<uint8_t
>(
fan_mode));
453 if (!PROTOCOL_VANE_MODE_MAP.is_valid(vane_mode)) {
454 ESP_LOGD(TAG,
"Setting invalid vane mode: %u",
static_cast<uint8_t
>(vane_mode));
462 if (!PROTOCOL_WIDE_VANE_MODE_MAP.is_valid(wide_vane_mode)) {
463 ESP_LOGD(TAG,
"Setting invalid wide vane mode: %u",
static_cast<uint8_t
>(wide_vane_mode));
471 std::array<uint8_t, REQUEST_PAYLOAD_LEN> payload{};
502 PROTOCOL_MODE_MAP.reverse_lookup(this->status_.mode, payload[4])) {
507 PROTOCOL_FAN_MODE_MAP.reverse_lookup(this->status_.fan_mode, payload[6])) {
512 PROTOCOL_VANE_MODE_MAP.reverse_lookup(this->status_.vane_mode, payload[7])) {
517 PROTOCOL_WIDE_VANE_MODE_MAP.reverse_lookup(this->status_.wide_vane_mode, payload[13])) {
528 this->
send_packet_(make_packet(PACKET_TYPE_WRITE_SETTINGS_REQUEST, payload));
534 return LOG_STR(
"Not connected");
536 return LOG_STR(
"Connecting");
538 return LOG_STR(
"Connected");
540 return LOG_STR(
"UpdatingStatus");
542 return LOG_STR(
"StatusUpdated");
544 return LOG_STR(
"ScheduleNextStatusUpdate");
546 return LOG_STR(
"WaitingForScheduledStatusUpdate");
548 return LOG_STR(
"ApplyingSettings");
550 return LOG_STR(
"SettingsApplied");
552 return LOG_STR(
"ReadTimeout");
554 return LOG_STR(
"Unknown");
557template<
typename Callback>
559 uint8_t watchdog = 64;
560 while (device.
available() > 0 && watchdog-- > 0) {
561 uint8_t &value = this->read_buffer_[this->read_pos_];
563 ESP_LOGW(TAG,
"UART read failed while data available");
567 switch (++this->read_pos_) {
569 if (value != PREAMBLE) {
578 if (value != HEADER_BYTE_1) {
584 if (value != HEADER_BYTE_2) {
590 static_assert(READ_BUFFER_SIZE > HEADER_LEN);
591 if (this->read_buffer_[HEADER_LEN - 1] >= READ_BUFFER_SIZE - HEADER_LEN) {
600 const size_t len_without_checksum = HEADER_LEN +
static_cast<size_t>(this->read_buffer_[HEADER_LEN - 1]);
601 if (this->read_pos_ <= len_without_checksum) {
605 if (
checksum(this->read_buffer_, len_without_checksum) != value) {
611 const bool processed =
612 callback(this->read_buffer_[1], this->read_buffer_ + HEADER_LEN, len_without_checksum - HEADER_LEN);
621 dump_buffer_vv(prefix, this->read_buffer_, this->read_pos_);
626#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
BedjetMode mode
BedJet operating mode.
void reset_and_dump_buffer_(const char *prefix)
bool read_and_parse(uart::UARTDevice &device, Callback &&callback)
static void dump_buffer_vv(const char *prefix, const uint8_t *data, size_t len)
std::optional< uint32_t > last_room_temperature_update_ms_
bool process_status_packet_(const uint8_t *payload, size_t len)
uint32_t operation_start_ms_
static bool should_transition(State from, State to)
uint8_t current_status_msg_type_
bool should_request_room_temperature_() const
void set_state_(State new_state)
void set_remote_temperature_half_deg_(uint8_t temperature_half_deg)
bool set_wide_vane_high_bit_
bool process_rx_packet_(uint8_t type, const uint8_t *payload, size_t len)
uint32_t status_update_wait_credit_ms_
bool is_status_initialized() const
bool use_temperature_encoding_b_
void clear_remote_temperature()
uart::UARTDevice & device_
void did_transition_(State to)
void set_remote_temperature(float temperature)
bool has_timed_out_(uint32_t timeout) const
static constexpr uint8_t REMOTE_TEMPERATURE_DISABLED
UpdateFlags pending_updates_
@ WAITING_FOR_SCHEDULED_STATUS_UPDATE
@ SCHEDULE_NEXT_STATUS_UPDATE
void set_power(bool power_on)
static const LogString * state_to_string(State state)
void set_wide_vane_mode(WideVaneMode mode)
void set_target_temperature(float target_temperature)
bool parse_status_settings_(const uint8_t *payload, size_t len)
uint32_t room_temperature_min_interval_ms_
uint32_t update_interval_ms_
void set_fan_mode(FanMode fan_mode)
bool is_room_temperature_enabled() const
void send_packet_(const uint8_t *packet, size_t len)
bool parse_status_payload_(uint8_t msg_type, const uint8_t *payload, size_t len)
uint8_t remote_temperature_half_deg_
FrameParser frame_parser_
void set_vane_mode(VaneMode vane_mode)
bool parse_status_room_temperature_(const uint8_t *payload, size_t len)
bool read_byte(uint8_t *data)
void write_array(const uint8_t *data, size_t len)
uint32_t get_loop_time_ms()
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Lightweight type-erased callback (8 bytes on 32-bit) that avoids std::function overhead.
WideVaneMode wide_vane_mode
void clear(Flags... flags)
bool contains_only(UpdateFlag flag) const
bool contains(UpdateFlag flag) const