ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
teleinfo.cpp
Go to the documentation of this file.
1#include "teleinfo.h"
2#include "esphome/core/log.h"
3
4namespace esphome::teleinfo {
5
6ESPHOME_LOG_TAG(TAG, "teleinfo");
7
8/* Helpers */
9static int get_field(char *dest, uint8_t *buf_start, uint8_t *buf_end, int sep, int max_len) {
10 uint8_t *field_end;
11 int len;
12
13 field_end = static_cast<uint8_t *>(memchr(buf_start, sep, buf_end - buf_start));
14 if (!field_end)
15 return 0;
16 len = field_end - buf_start;
17 if (len >= max_len)
18 return len;
19 memcpy(dest, buf_start, len);
20 dest[len] = '\0';
21
22 return len;
23}
24/* TeleInfo methods */
25bool TeleInfo::check_crc_(const uint8_t *grp, const uint8_t *grp_end) {
26 int grp_len = grp_end - grp;
27 if (grp_len < this->checksum_area_end_) {
28 ESP_LOGE(TAG, "Empty or too short group");
29 return false;
30 }
31 uint8_t raw_crc = grp[grp_len - 1];
32 uint8_t crc_tmp = sum_bytes(grp, grp_len - this->checksum_area_end_);
33
34 crc_tmp &= 0x3F;
35 crc_tmp += 0x20;
36 if (raw_crc != crc_tmp) {
37 ESP_LOGE(TAG, "bad crc: got %d except %d", raw_crc, crc_tmp);
38 return false;
39 }
40
41 return true;
42}
43bool TeleInfo::read_chars_until_(bool drop, uint8_t c) {
44 uint8_t received;
45 int j = 0;
46
47 while (available() > 0 && j < 128) {
48 j++;
49 if (!read_byte(&received))
50 break;
51 if (received == c)
52 return true;
53 if (drop)
54 continue;
55 /*
56 * Internal buffer is full, switch to OFF mode.
57 * Data will be retrieved on next update.
58 */
59 if (buf_index_ >= (MAX_BUF_SIZE - 1)) {
60 ESP_LOGW(TAG, "Internal buffer full");
61 state_ = STATE_OFF;
62 return false;
63 }
64 buf_[buf_index_++] = received;
65 }
66
67 return false;
68}
69void TeleInfo::setup() { state_ = STATE_OFF; }
71 if (state_ == STATE_OFF) {
72 buf_index_ = 0;
73 state_ = STATE_ON;
74 }
75}
77 switch (state_) {
78 case STATE_OFF:
79 break;
80 case STATE_ON:
81 /* Dequeue chars until start frame (0x2) */
82 if (read_chars_until_(true, 0x2))
83 state_ = START_FRAME_RECEIVED;
84 break;
86 /* Dequeue chars until end frame (0x3) */
87 if (read_chars_until_(false, 0x3))
88 state_ = END_FRAME_RECEIVED;
89 break;
91 uint8_t *buf_finger;
92 uint8_t *grp_end;
93 uint8_t *buf_end;
94 int field_len;
95
96 buf_finger = buf_;
97 buf_end = buf_ + buf_index_;
98
99 /* Each frame is composed of multiple groups starting by 0xa(Line Feed) and ending by
100 * 0xd ('\r').
101 *
102 * Historical mode: each group contains tag, data and a CRC separated by 0x20 (Space)
103 * 0xa | Tag | 0x20 | Data | 0x20 | CRC | 0xd
104 * ^^^^^^^^^^^^^^^^^^^^
105 * Checksum is computed on the above in historical mode.
106 *
107 * Standard mode: each group contains tag, data and a CRC separated by 0x9 (\t)
108 * 0xa | Tag | 0x9 | Data | 0x9 | CRC | 0xd
109 * ^^^^^^^^^^^^^^^^^^^^^^^^^
110 * Checksum is computed on the above in standard mode.
111 *
112 * Note that some Tags may have a timestamp in Standard mode. In this case
113 * the group would looks like this:
114 * 0xa | Tag | 0x9 | Timestamp | 0x9 | Data | 0x9 | CRC | 0xd
115 *
116 * The DATE tag is a special case. The group looks like this
117 * 0xa | Tag | 0x9 | Timestamp | 0x9 | 0x9 | CRC | 0xd
118 *
119 */
120 while ((buf_finger = static_cast<uint8_t *>(memchr(buf_finger, (int) 0xa, buf_end - buf_finger)))) {
121 /*
122 * Make sure timesamp is nullified between each tag as some tags don't
123 * have a timestamp
124 */
125 timestamp_[0] = '\0';
126 /* Point to the first char of the group after 0xa */
127 buf_finger += 1;
128
129 /* Group len */
130 grp_end = static_cast<uint8_t *>(memchr(buf_finger, 0xd, buf_end - buf_finger));
131 if (!grp_end) {
132 ESP_LOGE(TAG, "No group found");
133 break;
134 }
135
136 if (!check_crc_(buf_finger, grp_end))
137 continue;
138
139 /* Get tag */
140 field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE);
141 if (!field_len || field_len >= MAX_TAG_SIZE) {
142 ESP_LOGE(TAG, "Invalid tag.");
143 continue;
144 }
145
146 /* Advance buf_finger to after the tag and the separator. */
147 buf_finger += field_len + 1;
148
149 /*
150 * If there is two separators and the tag is not equal to "DATE" or
151 * historical mode is not in use (separator_ != 0x20), it means there is a
152 * timestamp to read first.
153 */
154 if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0 && separator_ != 0x20) {
155 field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE);
156 if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) {
157 ESP_LOGE(TAG, "Invalid timestamp for tag %s", tag_);
158 continue;
159 }
160
161 /* Advance buf_finger to after the first data and the separator. */
162 buf_finger += field_len + 1;
163 }
164
165 field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE);
166 if (!field_len || field_len >= MAX_VAL_SIZE) {
167 ESP_LOGE(TAG, "Invalid value for tag %s", tag_);
168 continue;
169 }
170
171 /* Resume after the group's 0xd; grp_end < buf_end, so this stays in bounds. */
172 buf_finger = grp_end + 1;
173
174 publish_value_(std::string(tag_), std::string(val_));
175 }
176 state_ = STATE_OFF;
177 break;
178 }
179}
180void TeleInfo::publish_value_(const std::string &tag, const std::string &val) {
181 for (auto *element : teleinfo_listeners_) {
182 if (tag != element->tag)
183 continue;
184 element->publish_val(val);
185 }
186}
187void TeleInfo::dump_config() { ESP_LOGCONFIG(TAG, "TeleInfo:"); }
188TeleInfo::TeleInfo(bool historical_mode) {
189 if (historical_mode) {
190 /*
191 * Historical mode doesn't contain last separator between checksum and data.
192 */
194 separator_ = 0x20;
195 } else {
197 separator_ = 0x9;
198 }
199}
201
202} // namespace esphome::teleinfo
void publish_value_(const std::string &tag, const std::string &val)
Definition teleinfo.cpp:180
bool check_crc_(const uint8_t *grp, const uint8_t *grp_end)
Definition teleinfo.cpp:25
char timestamp_[MAX_TIMESTAMP_SIZE]
Definition teleinfo.h:40
TeleInfo(bool historical_mode)
Definition teleinfo.cpp:188
char val_[MAX_VAL_SIZE]
Definition teleinfo.h:39
void register_teleinfo_listener(TeleInfoListener *listener)
Definition teleinfo.cpp:200
std::vector< TeleInfoListener * > teleinfo_listeners_
Definition teleinfo.h:31
char tag_[MAX_TAG_SIZE]
Definition teleinfo.h:38
enum esphome::teleinfo::TeleInfo::State STATE_OFF
uint8_t buf_[MAX_BUF_SIZE]
Definition teleinfo.h:36
void dump_config() override
Definition teleinfo.cpp:187
bool read_chars_until_(bool drop, uint8_t c)
Definition teleinfo.cpp:43
bool read_byte(uint8_t *data)
Definition uart.h:39
mopeka_std_values val[3]
ESPHOME_LOG_TAG(TAG, "teleinfo_sensor")
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:894
const void size_t len
Definition hal.h:68