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