ESPHome 2025.5.0
Loading...
Searching...
No Matches
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
10namespace esphome {
11namespace dsmr {
12
13static const char *const TAG = "dsmr";
14
16 this->telegram_ = new char[this->max_telegram_len_]; // NOLINT
17 if (this->request_pin_ != nullptr) {
18 this->request_pin_->setup();
19 }
20}
21
22void 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_()) {
37 }
38 }
39 // Otherwise, sink serial data until next request interval.
40 else {
41 if (this->request_interval_reached_()) {
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
260 ::dsmr::ParseResult<void> res =
261 ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
262 this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
263 if (res.err) {
264 // Parsing error, show it
265 auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
266 ESP_LOGE(TAG, "%s", err_str.c_str());
267 return false;
268 } else {
269 this->status_clear_warning();
270 this->publish_sensors(data);
271
272 // publish the telegram, after publishing the sensors so it can also trigger action based on latest values
273 if (this->s_telegram_ != nullptr) {
274 this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_));
275 }
276 return true;
277 }
278}
279
281 ESP_LOGCONFIG(TAG, "DSMR:");
282 ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_);
283 ESP_LOGCONFIG(TAG, " Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
284 if (this->request_pin_ != nullptr) {
285 LOG_PIN(" Request Pin: ", this->request_pin_);
286 }
287 if (this->request_interval_ > 0) {
288 ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
289 }
290
291#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
292 DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
293
294#define DSMR_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s_##s##_);
295 DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
296}
297
298void Dsmr::set_decryption_key(const std::string &decryption_key) {
299 if (decryption_key.empty()) {
300 ESP_LOGI(TAG, "Disabling decryption");
301 this->decryption_key_.clear();
302 if (this->crypt_telegram_ != nullptr) {
303 delete[] this->crypt_telegram_;
304 this->crypt_telegram_ = nullptr;
305 }
306 return;
307 }
308
309 if (decryption_key.length() != 32) {
310 ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
311 return;
312 }
313 this->decryption_key_.clear();
314
315 ESP_LOGI(TAG, "Decryption key is set");
316 // Verbose level prints decryption key
317 ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
318
319 char temp[3] = {0};
320 for (int i = 0; i < 16; i++) {
321 strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
322 this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
323 }
324
325 if (this->crypt_telegram_ == nullptr) {
326 this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
327 }
328}
329
330} // namespace dsmr
331} // namespace esphome
332
333#endif // USE_ARDUINO
void status_clear_warning()
virtual void setup()=0
virtual void digital_write(bool value)=0
void stop_requesting_data_()
Definition dsmr.cpp:112
uint32_t last_request_time_
Definition dsmr.h:111
void reset_telegram_()
Definition dsmr.cpp:127
size_t crypt_telegram_len_
Definition dsmr.h:124
GPIOPin * request_pin_
Definition dsmr.h:110
uint32_t receive_timeout_
Definition dsmr.h:118
size_t crypt_bytes_read_
Definition dsmr.h:125
bool ready_to_request_data_()
Definition dsmr.cpp:32
bool available_within_timeout_()
Wait for UART data to become available within the read timeout.
Definition dsmr.cpp:62
text_sensor::TextSensor * s_telegram_
Definition dsmr.h:131
void publish_sensors(MyData &data)
Definition dsmr.h:59
bool request_interval_reached_()
Definition dsmr.cpp:53
void start_requesting_data_()
Definition dsmr.cpp:99
void receive_telegram_()
Definition dsmr.cpp:136
size_t max_telegram_len_
Definition dsmr.h:120
size_t bytes_read_
Definition dsmr.h:122
uint32_t last_read_time_
Definition dsmr.h:126
DSMR_SENSOR_LIST(DSMR_SET_SENSOR,) DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR
uint8_t * crypt_telegram_
Definition dsmr.h:123
char * telegram_
Definition dsmr.h:121
bool requesting_data_
Definition dsmr.h:112
void loop() override
Definition dsmr.cpp:22
bool parse_telegram()
Definition dsmr.cpp:255
uint32_t request_interval_
Definition dsmr.h:108
void setup() override
Definition dsmr.cpp:15
std::vector< uint8_t > decryption_key_
Definition dsmr.h:140
void dump_config() override
Definition dsmr.cpp:280
bool receive_timeout_reached_()
Definition dsmr.cpp:60
void receive_encrypted_telegram_()
Definition dsmr.cpp:190
void set_decryption_key(const std::string &decryption_key)
Definition dsmr.cpp:298
void publish_state(const std::string &state)
UARTComponent * parent_
Definition uart.h:68
::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:47
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27