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