ESPHome  2024.4.0
dsmr.cpp
Go to the documentation of this file.
1 #ifdef USE_ARDUINO
2 
3 #include "dsmr.h"
4 #include "esphome/core/log.h"
5 
6 #include <AES.h>
7 #include <Crypto.h>
8 #include <GCM.h>
9 
10 namespace esphome {
11 namespace dsmr {
12 
13 static const char *const TAG = "dsmr";
14 
15 void Dsmr::setup() {
16  this->telegram_ = new char[this->max_telegram_len_]; // NOLINT
17  if (this->request_pin_ != nullptr) {
18  this->request_pin_->setup();
19  }
20 }
21 
22 void Dsmr::loop() {
23  if (this->ready_to_request_data_()) {
24  if (this->decryption_key_.empty()) {
25  this->receive_telegram_();
26  } else {
28  }
29  }
30 }
31 
33  // When using a request pin, then wait for the next request interval.
34  if (this->request_pin_ != nullptr) {
35  if (!this->requesting_data_ && this->request_interval_reached_()) {
36  this->start_requesting_data_();
37  }
38  }
39  // Otherwise, sink serial data until next request interval.
40  else {
41  if (this->request_interval_reached_()) {
42  this->start_requesting_data_();
43  }
44  if (!this->requesting_data_) {
45  while (this->available()) {
46  this->read();
47  }
48  }
49  }
50  return this->requesting_data_;
51 }
52 
54  if (this->last_request_time_ == 0) {
55  return true;
56  }
57  return millis() - this->last_request_time_ > this->request_interval_;
58 }
59 
61 
63  // Data are available for reading on the UART bus?
64  // Then we can start reading right away.
65  if (this->available()) {
66  this->last_read_time_ = millis();
67  return true;
68  }
69  // When we're not in the process of reading a telegram, then there is
70  // no need to actively wait for new data to come in.
71  if (!header_found_) {
72  return false;
73  }
74  // A telegram is being read. The smart meter might not deliver a telegram
75  // in one go, but instead send it in chunks with small pauses in between.
76  // When the UART RX buffer cannot hold a full telegram, then make sure
77  // that the UART read buffer does not overflow while other components
78  // perform their work in their loop. Do this by not returning control to
79  // the main loop, until the read timeout is reached.
80  if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) {
81  while (!this->receive_timeout_reached_()) {
82  delay(5);
83  if (this->available()) {
84  this->last_read_time_ = millis();
85  return true;
86  }
87  }
88  }
89  // No new data has come in during the read timeout? Then stop reading the
90  // telegram and start waiting for the next one to arrive.
91  if (this->receive_timeout_reached_()) {
92  ESP_LOGW(TAG, "Timeout while reading data for telegram");
93  this->reset_telegram_();
94  }
95 
96  return false;
97 }
98 
100  if (!this->requesting_data_) {
101  if (this->request_pin_ != nullptr) {
102  ESP_LOGV(TAG, "Start requesting data from P1 port");
103  this->request_pin_->digital_write(true);
104  } else {
105  ESP_LOGV(TAG, "Start reading data from P1 port");
106  }
107  this->requesting_data_ = true;
108  this->last_request_time_ = millis();
109  }
110 }
111 
113  if (this->requesting_data_) {
114  if (this->request_pin_ != nullptr) {
115  ESP_LOGV(TAG, "Stop requesting data from P1 port");
116  this->request_pin_->digital_write(false);
117  } else {
118  ESP_LOGV(TAG, "Stop reading data from P1 port");
119  }
120  while (this->available()) {
121  this->read();
122  }
123  this->requesting_data_ = false;
124  }
125 }
126 
128  this->header_found_ = false;
129  this->footer_found_ = false;
130  this->bytes_read_ = 0;
131  this->crypt_bytes_read_ = 0;
132  this->crypt_telegram_len_ = 0;
133  this->last_read_time_ = 0;
134 }
135 
137  while (this->available_within_timeout_()) {
138  const char c = this->read();
139 
140  // Find a new telegram header, i.e. forward slash.
141  if (c == '/') {
142  ESP_LOGV(TAG, "Header of telegram found");
143  this->reset_telegram_();
144  this->header_found_ = true;
145  }
146  if (!this->header_found_)
147  continue;
148 
149  // Check for buffer overflow.
150  if (this->bytes_read_ >= this->max_telegram_len_) {
151  this->reset_telegram_();
152  ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
153  return;
154  }
155 
156  // Some v2.2 or v3 meters will send a new value which starts with '('
157  // in a new line, while the value belongs to the previous ObisId. For
158  // proper parsing, remove these new line characters.
159  if (c == '(') {
160  while (true) {
161  auto previous_char = this->telegram_[this->bytes_read_ - 1];
162  if (previous_char == '\n' || previous_char == '\r') {
163  this->bytes_read_--;
164  } else {
165  break;
166  }
167  }
168  }
169 
170  // Store the byte in the buffer.
171  this->telegram_[this->bytes_read_] = c;
172  this->bytes_read_++;
173 
174  // Check for a footer, i.e. exclamation mark, followed by a hex checksum.
175  if (c == '!') {
176  ESP_LOGV(TAG, "Footer of telegram found");
177  this->footer_found_ = true;
178  continue;
179  }
180  // Check for the end of the hex checksum, i.e. a newline.
181  if (this->footer_found_ && c == '\n') {
182  // Parse the telegram and publish sensor values.
183  this->parse_telegram();
184  this->reset_telegram_();
185  return;
186  }
187  }
188 }
189 
191  while (this->available_within_timeout_()) {
192  const char c = this->read();
193 
194  // Find a new telegram start byte.
195  if (!this->header_found_) {
196  if ((uint8_t) c != 0xDB) {
197  continue;
198  }
199  ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
200  this->reset_telegram_();
201  this->header_found_ = true;
202  }
203 
204  // Check for buffer overflow.
205  if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
206  this->reset_telegram_();
207  ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
208  return;
209  }
210 
211  // Store the byte in the buffer.
212  this->crypt_telegram_[this->crypt_bytes_read_] = c;
213  this->crypt_bytes_read_++;
214 
215  // Read the length of the incoming encrypted telegram.
216  if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
217  // Complete header + data bytes
218  this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
219  ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
220  }
221 
222  // Check for the end of the encrypted telegram.
223  if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
224  continue;
225  }
226  ESP_LOGV(TAG, "End of encrypted telegram found");
227 
228  // Decrypt the encrypted telegram.
229  GCM<AES128> *gcmaes128{new GCM<AES128>()};
230  gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
231  // the iv is 8 bytes of the system title + 4 bytes frame counter
232  // system title is at byte 2 and frame counter at byte 15
233  for (int i = 10; i < 14; i++)
234  this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
235  constexpr uint16_t iv_size{12};
236  gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
237  gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
238  // the ciphertext start at byte 18
239  &this->crypt_telegram_[18],
240  // cipher size
241  this->crypt_bytes_read_ - 17);
242  delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
243 
244  this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
245  ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
246  ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
247 
248  // Parse the decrypted telegram and publish sensor values.
249  this->parse_telegram();
250  this->reset_telegram_();
251  return;
252  }
253 }
254 
256  MyData data;
257  ESP_LOGV(TAG, "Trying to parse telegram");
258  this->stop_requesting_data_();
259  ::dsmr::ParseResult<void> res =
260  ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
261  this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
262  if (res.err) {
263  // Parsing error, show it
264  auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
265  ESP_LOGE(TAG, "%s", err_str.c_str());
266  return false;
267  } else {
268  this->status_clear_warning();
269  this->publish_sensors(data);
270  return true;
271  }
272 }
273 
275  ESP_LOGCONFIG(TAG, "DSMR:");
276  ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_);
277  ESP_LOGCONFIG(TAG, " Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
278  if (this->request_pin_ != nullptr) {
279  LOG_PIN(" Request Pin: ", this->request_pin_);
280  }
281  if (this->request_interval_ > 0) {
282  ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
283  }
284 
285 #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
286  DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
287 
288 #define DSMR_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s_##s##_);
289  DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
290 }
291 
292 void Dsmr::set_decryption_key(const std::string &decryption_key) {
293  if (decryption_key.length() == 0) {
294  ESP_LOGI(TAG, "Disabling decryption");
295  this->decryption_key_.clear();
296  if (this->crypt_telegram_ != nullptr) {
297  delete[] this->crypt_telegram_;
298  this->crypt_telegram_ = nullptr;
299  }
300  return;
301  }
302 
303  if (decryption_key.length() != 32) {
304  ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
305  return;
306  }
307  this->decryption_key_.clear();
308 
309  ESP_LOGI(TAG, "Decryption key is set");
310  // Verbose level prints decryption key
311  ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
312 
313  char temp[3] = {0};
314  for (int i = 0; i < 16; i++) {
315  strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
316  this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
317  }
318 
319  if (this->crypt_telegram_ == nullptr) {
320  this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
321  }
322 }
323 
324 } // namespace dsmr
325 } // namespace esphome
326 
327 #endif // USE_ARDUINO
virtual void digital_write(bool value)=0
::dsmr::ParsedData< DSMR_TEXT_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_COMMA) DSMR_BOTH DSMR_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_COMMA)> MyData
Definition: dsmr.h:48
bool available_within_timeout_()
Wait for UART data to become available within the read timeout.
Definition: dsmr.cpp:62
void dump_config() override
Definition: dsmr.cpp:274
uint32_t receive_timeout_
Definition: dsmr.h:115
bool footer_found_
Definition: dsmr.h:125
bool requesting_data_
Definition: dsmr.h:109
bool receive_timeout_reached_()
Definition: dsmr.cpp:60
std::vector< uint8_t > decryption_key_
Definition: dsmr.h:134
size_t crypt_telegram_len_
Definition: dsmr.h:121
virtual void setup()=0
UARTComponent * parent_
Definition: uart.h:68
uint32_t last_read_time_
Definition: dsmr.h:123
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void loop() override
Definition: dsmr.cpp:22
size_t bytes_read_
Definition: dsmr.h:119
void publish_sensors(MyData &data)
Definition: dsmr.h:59
bool header_found_
Definition: dsmr.h:124
void receive_encrypted_telegram_()
Definition: dsmr.cpp:190
DSMR_SENSOR_LIST(DSMR_SET_SENSOR,) DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR
void status_clear_warning()
Definition: component.cpp:166
char * telegram_
Definition: dsmr.h:118
GPIOPin * request_pin_
Definition: dsmr.h:107
uint32_t request_interval_
Definition: dsmr.h:105
void receive_telegram_()
Definition: dsmr.cpp:136
void set_decryption_key(const std::string &decryption_key)
Definition: dsmr.cpp:292
size_t crypt_bytes_read_
Definition: dsmr.h:122
bool ready_to_request_data_()
Definition: dsmr.cpp:32
bool parse_telegram()
Definition: dsmr.cpp:255
void start_requesting_data_()
Definition: dsmr.cpp:99
bool request_interval_reached_()
Definition: dsmr.cpp:53
size_t max_telegram_len_
Definition: dsmr.h:117
uint32_t last_request_time_
Definition: dsmr.h:108
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
void setup() override
Definition: dsmr.cpp:15
void stop_requesting_data_()
Definition: dsmr.cpp:112
uint8_t * crypt_telegram_
Definition: dsmr.h:120
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26
void reset_telegram_()
Definition: dsmr.cpp:127