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