ESPHome  2024.12.2
teleinfo.cpp
Go to the documentation of this file.
1 #include "teleinfo.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace teleinfo {
6 
7 static const char *const TAG = "teleinfo";
8 
9 /* Helpers */
10 static int get_field(char *dest, char *buf_start, char *buf_end, int sep, int max_len) {
11  char *field_end;
12  int len;
13 
14  field_end = static_cast<char *>(memchr(buf_start, sep, buf_end - buf_start));
15  if (!field_end)
16  return 0;
17  len = field_end - buf_start;
18  if (len >= max_len)
19  return len;
20  strncpy(dest, buf_start, len);
21  dest[len] = '\0';
22 
23  return len;
24 }
25 /* TeleInfo methods */
26 bool TeleInfo::check_crc_(const char *grp, const char *grp_end) {
27  int grp_len = grp_end - grp;
28  uint8_t raw_crc = grp[grp_len - 1];
29  uint8_t crc_tmp = 0;
30  int i;
31 
32  for (i = 0; i < grp_len - checksum_area_end_; i++)
33  crc_tmp += grp[i];
34 
35  crc_tmp &= 0x3F;
36  crc_tmp += 0x20;
37  if (raw_crc != crc_tmp) {
38  ESP_LOGE(TAG, "bad crc: got %d except %d", raw_crc, crc_tmp);
39  return false;
40  }
41 
42  return true;
43 }
44 bool TeleInfo::read_chars_until_(bool drop, uint8_t c) {
45  uint8_t received;
46  int j = 0;
47 
48  while (available() > 0 && j < 128) {
49  j++;
50  received = read();
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_ = OFF;
62  return false;
63  }
64  buf_[buf_index_++] = received;
65  }
66 
67  return false;
68 }
69 void TeleInfo::setup() { state_ = OFF; }
71  if (state_ == OFF) {
72  buf_index_ = 0;
73  state_ = ON;
74  }
75 }
77  switch (state_) {
78  case OFF:
79  break;
80  case 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;
90  case END_FRAME_RECEIVED:
91  char *buf_finger;
92  char *grp_end;
93  char *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<char *>(memchr(buf_finger, (int) 0xa, buf_index_ - 1))) &&
121  ((buf_finger - buf_) < buf_index_)) { // NOLINT(clang-diagnostic-sign-compare)
122  /*
123  * Make sure timesamp is nullified between each tag as some tags don't
124  * have a timestamp
125  */
126  timestamp_[0] = '\0';
127  /* Point to the first char of the group after 0xa */
128  buf_finger += 1;
129 
130  /* Group len */
131  grp_end = static_cast<char *>(memchr(buf_finger, 0xd, buf_end - buf_finger));
132  if (!grp_end) {
133  ESP_LOGE(TAG, "No group found");
134  break;
135  }
136 
137  if (!check_crc_(buf_finger, grp_end))
138  continue;
139 
140  /* Get tag */
141  field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE);
142  if (!field_len || field_len >= MAX_TAG_SIZE) {
143  ESP_LOGE(TAG, "Invalid tag.");
144  continue;
145  }
146 
147  /* Advance buf_finger to after the tag and the separator. */
148  buf_finger += field_len + 1;
149 
150  /*
151  * If there is two separators and the tag is not equal to "DATE" or
152  * historical mode is not in use (separator_ != 0x20), it means there is a
153  * timestamp to read first.
154  */
155  if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0 && separator_ != 0x20) {
156  field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE);
157  if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) {
158  ESP_LOGE(TAG, "Invalid timestamp for tag %s", timestamp_);
159  continue;
160  }
161 
162  /* Advance buf_finger to after the first data and the separator. */
163  buf_finger += field_len + 1;
164  }
165 
166  field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE);
167  if (!field_len || field_len >= MAX_VAL_SIZE) {
168  ESP_LOGE(TAG, "Invalid value for tag %s", tag_);
169  continue;
170  }
171 
172  /* Advance buf_finger to end of group */
173  buf_finger += field_len + 1 + 1 + 1;
174 
175  publish_value_(std::string(tag_), std::string(val_));
176  }
177  state_ = OFF;
178  break;
179  }
180 }
181 void TeleInfo::publish_value_(const std::string &tag, const std::string &val) {
182  for (auto *element : teleinfo_listeners_) {
183  if (tag != element->tag)
184  continue;
185  element->publish_val(val);
186  }
187 }
189  ESP_LOGCONFIG(TAG, "TeleInfo:");
191 }
192 TeleInfo::TeleInfo(bool historical_mode) {
193  if (historical_mode) {
194  /*
195  * Historical mode doesn't contain last separator between checksum and data.
196  */
197  checksum_area_end_ = 2;
198  separator_ = 0x20;
199  baud_rate_ = 1200;
200  } else {
201  checksum_area_end_ = 1;
202  separator_ = 0x9;
203  baud_rate_ = 9600;
204  }
205 }
207 
208 } // namespace teleinfo
209 } // namespace esphome
char buf_[MAX_BUF_SIZE]
Definition: teleinfo.h:38
void loop() override
Definition: teleinfo.cpp:76
TeleInfo(bool historical_mode)
Definition: teleinfo.cpp:192
enum esphome::teleinfo::TeleInfo::State OFF
mopeka_std_values val[4]
const char *const TAG
Definition: spi.cpp:8
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition: uart.cpp:13
void register_teleinfo_listener(TeleInfoListener *listener)
Definition: teleinfo.cpp:206
char timestamp_[MAX_TIMESTAMP_SIZE]
Definition: teleinfo.h:42
void setup() override
Definition: teleinfo.cpp:69
bool read_chars_until_(bool drop, uint8_t c)
Definition: teleinfo.cpp:44
bool check_crc_(const char *grp, const char *grp_end)
Definition: teleinfo.cpp:26
std::vector< TeleInfoListener * > teleinfo_listeners_
Definition: teleinfo.h:32
std::string size_t len
Definition: helpers.h:293
void update() override
Definition: teleinfo.cpp:70
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
char val_[MAX_VAL_SIZE]
Definition: teleinfo.h:41
char tag_[MAX_TAG_SIZE]
Definition: teleinfo.h:40
void publish_value_(const std::string &tag, const std::string &val)
Definition: teleinfo.cpp:181
void dump_config() override
Definition: teleinfo.cpp:188