ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mk2pvrouter.cpp
Go to the documentation of this file.
1#include "mk2pvrouter.h"
2#include "esphome/core/log.h"
3#include <cstddef>
4#include <cstring>
5
6namespace esphome::mk2pvrouter {
7
8ESPHOME_LOG_TAG(TAG, "mk2pvrouter");
9
10constexpr uint8_t START_FRAME = 0x2;
11constexpr uint8_t END_FRAME = 0x3;
12constexpr uint8_t LINE_FEED = 0xa;
13constexpr uint8_t CARRIAGE_RETURN = 0xd;
14constexpr uint8_t TAB = 0x9;
15constexpr uint8_t MAX_ITERATIONS = 128;
16constexpr uint8_t CRC_MASK = 0x3F;
17constexpr uint8_t CRC_OFFSET = 0x20;
18
19// Extracts a TAB-delimited field from [buf_start, buf_end) into dest.
20// Returns the field length, or 0 if no TAB was found, or the (uncopied) field
21// length if it's >= max_len.
22static size_t get_field(char *dest, const uint8_t *buf_start, const uint8_t *buf_end, size_t max_len) {
23 const auto *const field_end = static_cast<const uint8_t *>(memchr(buf_start, TAB, buf_end - buf_start));
24 if (!field_end)
25 return 0;
26 const size_t len = field_end - buf_start;
27 if (len >= max_len) {
28 ESP_LOGE(TAG, "Field too long: %zu bytes (max %zu)", len, max_len);
29 return len;
30 }
31
32 memcpy(dest, buf_start, len);
33 dest[len] = '\0'; // Null-terminate
34 return len;
35}
36
37// Calculates the CRC (checksum) for a given group of bytes.
38uint8_t Mk2PVRouter::calculate_crc_(const uint8_t *grp, size_t grp_len) {
39 return (sum_bytes(grp, grp_len - CRC_SUFFIX_LEN) & CRC_MASK) + CRC_OFFSET;
40}
41
42// Verifies the CRC of a group against its trailing CRC byte.
43bool Mk2PVRouter::check_crc_(const uint8_t *grp, const uint8_t *grp_end) {
44 const auto grp_len = grp_end - grp;
45 if (grp_len < static_cast<std::ptrdiff_t>(CRC_SUFFIX_LEN)) {
46 ESP_LOGE(TAG, "Empty or too short group");
47 return false;
48 }
49 const auto raw_crc = grp[grp_len - 1];
50
51 const auto calculated_crc = this->calculate_crc_(grp, grp_len);
52
53 if (raw_crc != calculated_crc) {
54 ESP_LOGE(TAG, "CRC mismatch: expected %d, got %d", calculated_crc, raw_crc);
55 return false;
56 }
57 return true;
58}
59
60// Validates, parses, and publishes a single tag/value group.
61void Mk2PVRouter::process_group_(const uint8_t *grp, const uint8_t *grp_end) {
62 if (!this->check_crc_(grp, grp_end))
63 return;
64
65 size_t field_len = get_field(this->tag_, grp, grp_end, MAX_TAG_SIZE);
66 if (!field_len || field_len >= MAX_TAG_SIZE) {
67 ESP_LOGE(TAG, "Invalid tag");
68 return;
69 }
70 const auto *val_start = grp + field_len + 1; // Skip tag + TAB.
71
72 field_len = get_field(this->val_, val_start, grp_end, MAX_VAL_SIZE);
73 if (!field_len || field_len >= MAX_VAL_SIZE) {
74 ESP_LOGE(TAG, "Invalid value for tag %s", this->tag_);
75 return;
76 }
77
78 this->publish_value_(this->tag_, this->val_);
79}
80
81// Reads characters until `c` is found or the internal buffer is full.
82bool Mk2PVRouter::read_chars_until_(bool drop, uint8_t c) {
83 size_t j{0};
84
85 while (this->available() > 0 && j++ < MAX_ITERATIONS) {
86 uint8_t received;
87 if (!this->read_byte(&received))
88 break;
89 if (received == c)
90 return true;
91 if (drop)
92 continue;
93 if (this->buf_index_ >= (sizeof(this->buf_) - 1)) {
94 ESP_LOGW(TAG, "Internal buffer full");
95 this->buf_index_ = 0;
97 return false;
98 }
99 this->buf_[this->buf_index_++] = received;
100 }
101
102 return false;
103}
104
106 switch (this->state_) {
108 ESP_LOGVV(TAG, "State: WAITING_FOR_START");
109 if (this->read_chars_until_(true, START_FRAME))
111 break;
113 ESP_LOGVV(TAG, "State: START_FRAME_RECEIVED");
114 if (this->read_chars_until_(false, END_FRAME))
116 break;
118 ESP_LOGVV(TAG, "State: END_FRAME_RECEIVED -> processing");
119
120 if (this->buf_index_ == 0) {
122 break;
123 }
124
125 auto *buf_finger = this->buf_;
126 auto *buf_end = this->buf_ + this->buf_index_;
127
128 // Each group: 0xa(LF) | Tag | 0x9(TAB) | Data | 0x9(TAB) | CRC | 0xd(CR)
129 // CRC is computed over "Tag | TAB | Data | TAB".
130 while ((buf_finger = static_cast<uint8_t *>(memchr(buf_finger, LINE_FEED, buf_end - buf_finger))) != nullptr) {
131 ++buf_finger; // Skip LF to the start of the group.
132
133 auto *const grp_end = static_cast<uint8_t *>(memchr(buf_finger, CARRIAGE_RETURN, buf_end - buf_finger));
134 if (!grp_end) {
135 ESP_LOGE(TAG, "No group found");
136 break;
137 }
138
139 this->process_group_(buf_finger, grp_end);
140
141 buf_finger = grp_end; // grp_end is always < buf_end, so this stays in bounds.
142 }
143 this->buf_index_ = 0;
145 break;
146 }
147 }
148}
149
150void Mk2PVRouter::publish_value_(const char *tag, const char *val) {
151#ifdef MK2PVROUTER_LISTENER_COUNT
152 for (auto *element : this->mk2pvrouter_listeners_) {
153 if (strcmp(tag, element->get_tag()) != 0)
154 continue;
155 element->publish_val(val);
156 }
157#endif
158}
159
160void Mk2PVRouter::dump_config() { ESP_LOGCONFIG(TAG, "Mk2PVRouter:"); }
161
162#ifdef MK2PVROUTER_LISTENER_COUNT
166#endif
167
168} // namespace esphome::mk2pvrouter
bool check_crc_(const uint8_t *grp, const uint8_t *grp_end)
static constexpr size_t CRC_SUFFIX_LEN
Definition mk2pvrouter.h:45
void process_group_(const uint8_t *grp, const uint8_t *grp_end)
void publish_value_(const char *tag, const char *val)
bool read_chars_until_(bool drop, uint8_t c)
void register_mk2pvrouter_listener(Mk2PVRouterListener *listener)
StaticVector< Mk2PVRouterListener *, MK2PVROUTER_LISTENER_COUNT > mk2pvrouter_listeners_
Definition mk2pvrouter.h:54
uint8_t calculate_crc_(const uint8_t *grp, size_t grp_len)
uint8_t buf_[MAX_BUF_SIZE]
Definition mk2pvrouter.h:60
bool read_byte(uint8_t *data)
Definition uart.h:36
mopeka_std_values val[3]
constexpr uint8_t MAX_ITERATIONS
ESPHOME_LOG_TAG(TAG, "mk2pvrouter_binary_sensor")
constexpr uint8_t CARRIAGE_RETURN
constexpr uint8_t START_FRAME
constexpr uint8_t CRC_OFFSET
constexpr uint8_t LINE_FEED
constexpr uint8_t TAB
constexpr uint8_t END_FRAME
constexpr uint8_t CRC_MASK
constexpr uint8_t sum_bytes(const uint8_t *data, size_t len, uint8_t sum=0)
Add up the bytes of data with size len, truncated to 8 bits: the checksum many serial and IR protocol...
Definition helpers.h:886
const void size_t len
Definition hal.h:68