ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
dsmr.cpp
Go to the documentation of this file.
1// Ignore Zephyr. It doesn't have any encryption library.
2#if defined(USE_ESP32) || defined(USE_ARDUINO) || defined(USE_HOST)
3
4#include "dsmr.h"
5#include "esphome/core/log.h"
6#include <dsmr_parser/util.h>
7
8namespace esphome::dsmr {
9
10static constexpr auto &TAG = "dsmr";
11
12static void log_callback(dsmr_parser::LogLevel level, const char *fmt, va_list args) {
13 std::array<char, 256> buf;
14 vsnprintf(buf.data(), buf.size(), fmt, args);
15 switch (level) {
16 case dsmr_parser::LogLevel::ERROR:
17 ESP_LOGE(TAG, "%s", buf.data());
18 break;
19 case dsmr_parser::LogLevel::WARNING:
20 ESP_LOGW(TAG, "%s", buf.data());
21 break;
22 case dsmr_parser::LogLevel::INFO:
23 ESP_LOGI(TAG, "%s", buf.data());
24 break;
25 case dsmr_parser::LogLevel::VERBOSE:
26 ESP_LOGV(TAG, "%s", buf.data());
27 break;
28 case dsmr_parser::LogLevel::VERY_VERBOSE:
29 ESP_LOGVV(TAG, "%s", buf.data());
30 break;
31 case dsmr_parser::LogLevel::DEBUG:
32 ESP_LOGD(TAG, "%s", buf.data());
33 break;
34 }
35}
36
38 dsmr_parser::Logger::set_log_function(log_callback);
39 if (this->request_pin_ != nullptr) {
40 this->request_pin_->setup();
41 }
42}
43
44void Dsmr::loop() {
45 if (!this->ready_to_request_data_()) {
46 return;
47 }
48
49 if (this->encryption_enabled_) {
51 } else {
52 this->receive_telegram_();
53 }
54}
55
57 if (!this->requesting_data_ && this->request_interval_reached_()) {
59 }
60 return this->requesting_data_;
61}
62
64 if (this->last_request_time_ == 0) {
65 return true;
66 }
67 return millis() - this->last_request_time_ > this->request_interval_;
68}
69
71 if (this->requesting_data_) {
72 return;
73 }
74
75 ESP_LOGV(TAG, "Start reading data from P1 port");
76 this->flush_rx_buffer_();
77
78 if (this->request_pin_ != nullptr) {
79 ESP_LOGV(TAG, "Set request pin to 1");
80 this->request_pin_->digital_write(true);
81 }
82
83 this->requesting_data_ = true;
84 this->last_request_time_ = millis();
85}
86
88 if (!this->requesting_data_) {
89 return;
90 }
91
92 ESP_LOGV(TAG, "Stop reading data from P1 port");
93 if (this->request_pin_ != nullptr) {
94 ESP_LOGV(TAG, "Set request pin to 0");
95 this->request_pin_->digital_write(false);
96 }
97 this->requesting_data_ = false;
98}
99
101 ESP_LOGV(TAG, "Flush UART RX buffer");
102 while (!this->uart_read_chunk_().empty()) {
103 }
104}
105
107 for (auto data = this->uart_read_chunk_(); !data.empty(); data = this->uart_read_chunk_()) {
108 for (uint8_t byte : data) {
109 const auto telegram = this->packet_accumulator_.process_byte(byte);
110 if (!telegram) { // No full packet received yet
111 continue;
112 }
113 if (this->parse_telegram_(telegram.value())) {
114 return;
115 }
116 }
117 }
118}
119
121 for (auto data = this->uart_read_chunk_(); !data.empty(); data = this->uart_read_chunk_()) {
122 for (uint8_t byte : data) {
123 if (this->buffer_pos_ >= this->buffer_.size()) { // Reset buffer if overflow
124 ESP_LOGW(TAG, "Encrypted buffer overflow, resetting");
125 this->buffer_pos_ = 0;
126 }
127
128 this->buffer_[this->buffer_pos_] = byte;
129 this->buffer_pos_++;
130 }
131 this->last_read_time_ = millis();
132 }
133
134 // Detect inter-frame delay. If no byte is received for more than receive_timeout, then the packet is complete.
135 if (millis() - this->last_read_time_ > this->receive_timeout_ && this->buffer_pos_ > 0) {
136 ESP_LOGV(TAG, "Encrypted telegram received (%zu bytes)", this->buffer_pos_);
137
138 const auto telegram = this->dlms_decryptor_.decrypt_inplace({this->buffer_.data(), this->buffer_pos_});
139
140 // Reset buffer position for the next packet
141 this->buffer_pos_ = 0;
142 this->last_read_time_ = 0;
143
144 if (!telegram) { // decryption failed
145 return;
146 }
147
148 // Parse and publish the telegram
149 this->parse_telegram_(telegram.value());
150 }
151}
152
153bool Dsmr::parse_telegram_(const dsmr_parser::DsmrUnencryptedTelegram &telegram) {
154 this->stop_requesting_data_();
155
156 ESP_LOGV(TAG, "Trying to parse telegram (%zu bytes)", telegram.content().size());
157 ESP_LOGVV(TAG, "Telegram content:\n %.*s", static_cast<int>(telegram.content().size()), telegram.content().data());
158
159 MyData data;
160 if (const bool res = dsmr_parser::DsmrParser::parse(data, telegram); !res) {
161 ESP_LOGE(TAG, "Failed to parse telegram");
162 return false;
163 }
164
165 this->status_clear_warning();
166 this->publish_sensors(data);
167
168 // Publish the telegram, after publishing the sensors so it can also trigger action based on latest values
169 if (this->s_telegram_ != nullptr) {
170 this->s_telegram_->publish_state(telegram.content().data(), telegram.content().size());
171 }
172 return true;
173}
174
176 ESP_LOGCONFIG(TAG,
177 "DSMR:\n"
178 " Max telegram length: %zu\n"
179 " Receive timeout: %.1fs",
180 this->buffer_.size(), this->receive_timeout_ / 1e3f);
181 if (this->request_pin_ != nullptr) {
182 LOG_PIN(" Request Pin: ", this->request_pin_);
183 }
184 if (this->request_interval_ > 0) {
185 ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
186 }
187
188#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
189 DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
190
191#define DSMR_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s_##s##_);
192 DSMR_TEXT_SENSOR_LIST(DSMR_LOG_TEXT_SENSOR, )
193}
194
195void Dsmr::set_decryption_key_(const char *decryption_key) {
196 if (decryption_key == nullptr || decryption_key[0] == '\0') {
197 this->encryption_enabled_ = false;
198 return;
199 }
200
201 auto key = dsmr_parser::Aes128GcmDecryptionKey::from_hex(decryption_key);
202 if (!key) {
203 ESP_LOGE(TAG, "Error, decryption key has incorrect format");
204 this->encryption_enabled_ = false;
205 return;
206 }
207
208 ESP_LOGI(TAG, "Decryption key is set");
209
210 this->gcm_decryptor_.set_encryption_key(key.value());
211 this->encryption_enabled_ = true;
212}
213
214std::span<uint8_t> Dsmr::uart_read_chunk_() {
215 const auto avail = this->available();
216 if (avail == 0) {
217 return {};
218 }
219 size_t to_read = std::min(avail, uart_chunk_reading_buf_.size());
220 if (!this->read_array(uart_chunk_reading_buf_.data(), to_read)) {
221 return {};
222 }
223 return {uart_chunk_reading_buf_.data(), to_read};
224}
225
226} // namespace esphome::dsmr
227
228#endif
void status_clear_warning()
Definition component.h:306
virtual void setup()=0
virtual void digital_write(bool value)=0
void stop_requesting_data_()
Definition dsmr.cpp:87
uint32_t last_request_time_
Definition dsmr.h:133
GPIOPin * request_pin_
Definition dsmr.h:125
uint32_t receive_timeout_
Definition dsmr.h:124
size_t buffer_pos_
Definition dsmr.h:137
bool request_interval_reached_() const
Definition dsmr.cpp:63
bool ready_to_request_data_()
Definition dsmr.cpp:56
dsmr_parser::DlmsPacketDecryptor dlms_decryptor_
Definition dsmr.h:141
void set_decryption_key_(const char *decryption_key)
Definition dsmr.cpp:195
text_sensor::TextSensor * s_telegram_
Definition dsmr.h:126
void flush_rx_buffer_()
Definition dsmr.cpp:100
void publish_sensors(MyData &data)
Definition dsmr.h:79
void start_requesting_data_()
Definition dsmr.cpp:70
dsmr_parser::PacketAccumulator packet_accumulator_
Definition dsmr.h:139
void receive_telegram_()
Definition dsmr.cpp:106
bool encryption_enabled_
Definition dsmr.h:136
uint32_t last_read_time_
Definition dsmr.h:134
DSMR_SENSOR_LIST(DSMR_SET_SENSOR,) DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR
bool requesting_data_
Definition dsmr.h:135
void loop() override
Definition dsmr.cpp:44
uint32_t request_interval_
Definition dsmr.h:123
std::array< uint8_t, 256 > uart_chunk_reading_buf_
Definition dsmr.h:142
void setup() override
Definition dsmr.cpp:37
void dump_config() override
Definition dsmr.cpp:175
Aes128GcmDecryptorImpl gcm_decryptor_
Definition dsmr.h:140
void receive_encrypted_telegram_()
Definition dsmr.cpp:120
bool parse_telegram_(const dsmr_parser::DsmrUnencryptedTelegram &telegram)
Definition dsmr.cpp:153
std::vector< uint8_t > buffer_
Definition dsmr.h:138
std::span< uint8_t > uart_read_chunk_()
Definition dsmr.cpp:214
void publish_state(const std::string &state)
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
dsmr_parser::ParsedData< DSMR_TEXT_SENSOR_LIST(DSMR_IDENTITY, DSMR_COMMA) DSMR_PREPEND_COMMA(DSMR_SENSOR_LIST(DSMR_IDENTITY, DSMR_COMMA))> MyData
Definition dsmr.h:57
const char int const __FlashStringHelper va_list args
Definition log.h:74
size_t size_t const char * fmt
Definition helpers.h:1039
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28