ESPHome 2026.6.2
Loading...
Searching...
No Matches
dlms_meter.cpp
Go to the documentation of this file.
1#include "dlms_meter.h"
2#include "esphome/core/log.h"
3
4#include <cstdio>
5
7
8static const char *const TAG = "dlms_meter";
9static void log_callback(dlms_parser::LogLevel level, const char *fmt, va_list args) {
10 std::array<char, 256> buf;
11 vsnprintf(buf.data(), buf.size(), fmt, args);
12 switch (level) {
13 case dlms_parser::LogLevel::ERROR:
14 ESP_LOGE(TAG, "%s", buf.data());
15 break;
16 case dlms_parser::LogLevel::WARNING:
17 ESP_LOGW(TAG, "%s", buf.data());
18 break;
19 case dlms_parser::LogLevel::INFO:
20 ESP_LOGI(TAG, "%s", buf.data());
21 break;
22 case dlms_parser::LogLevel::VERBOSE:
23 ESP_LOGV(TAG, "%s", buf.data());
24 break;
25 case dlms_parser::LogLevel::VERY_VERBOSE:
26 ESP_LOGVV(TAG, "%s", buf.data());
27 break;
28 case dlms_parser::LogLevel::DEBUG:
29 ESP_LOGD(TAG, "%s", buf.data());
30 break;
31 }
32}
33
34DlmsMeterComponent::DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check,
35 std::optional<std::array<uint8_t, 16>> decryption_key,
36 std::optional<std::array<uint8_t, 16>> authentication_key,
37 std::vector<CustomPattern> custom_patterns)
38 : receive_timeout_ms_(receive_timeout_ms),
39 skip_crc_check_(skip_crc_check),
40 custom_patterns_(std::move(custom_patterns)),
41 parser_(&decryptor_) {
42 dlms_parser::Logger::set_log_function(log_callback);
43
44 if (decryption_key.has_value()) {
45#ifdef DLMS_METER_NO_CRYPTO
46 ESP_LOGE(TAG, "Decryption is not supported on this platform (no compatible crypto library found)");
47#else
48 auto opt_key = dlms_parser::Aes128GcmDecryptionKey::from_bytes(decryption_key.value());
49 if (opt_key) {
50 this->parser_.set_decryption_key(*opt_key);
51 } else {
52 ESP_LOGE(TAG, "Failed to set decryption key: invalid key format");
53 }
54#endif
55 }
56
57 if (authentication_key.has_value()) {
58#ifdef DLMS_METER_NO_CRYPTO
59 ESP_LOGE(TAG, "Authentication is not supported on this platform (no compatible crypto library found)");
60#else
61 auto opt_key = dlms_parser::Aes128GcmAuthenticationKey::from_bytes(authentication_key.value());
62 if (opt_key) {
63 this->parser_.set_authentication_key(*opt_key);
64 } else {
65 ESP_LOGE(TAG, "Failed to set authentication key: invalid key format");
66 }
67#endif
68 }
69
70 this->parser_.set_skip_crc_check(this->skip_crc_check_);
71
72 this->parser_.load_default_patterns();
73 for (const auto &pattern : this->custom_patterns_) {
74 if (pattern.default_obis.has_value() && pattern.name.has_value()) {
75 this->parser_.register_pattern(pattern.name->c_str(), pattern.pattern.c_str(), pattern.priority,
76 pattern.default_obis.value());
77 } else if (pattern.name.has_value()) {
78 this->parser_.register_pattern(pattern.name->c_str(), pattern.pattern.c_str(), pattern.priority);
79 } else {
80 this->parser_.register_pattern(pattern.pattern.c_str());
81 }
82 }
83}
84
86
88 ESP_LOGCONFIG(TAG, "DLMS Meter:");
89 ESP_LOGCONFIG(TAG, " Receive Timeout: %u ms", this->receive_timeout_ms_);
90 ESP_LOGCONFIG(TAG, " Skip CRC Check: %s", YESNO(this->skip_crc_check_));
91
92 for (const auto &pattern : this->custom_patterns_) {
93 if (pattern.default_obis.has_value() && pattern.name.has_value()) {
94 const auto &obis = pattern.default_obis.value();
95 ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d, default_obis: %d.%d.%d.%d.%d.%d)",
96 pattern.pattern.c_str(), pattern.name->c_str(), pattern.priority, obis[0], obis[1], obis[2],
97 obis[3], obis[4], obis[5]);
98 } else if (pattern.name.has_value()) {
99 ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d)", pattern.pattern.c_str(),
100 pattern.name->c_str(), pattern.priority);
101 } else {
102 ESP_LOGCONFIG(TAG, " Custom Pattern: '%s'", pattern.pattern.c_str());
103 }
104 }
105
106#ifdef USE_SENSOR
107 for (const auto &entry : this->sensors_) {
108 LOG_SENSOR(" ", "Numeric Sensor (OBIS)", entry.sensor);
109 ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str());
110 }
111#endif
112#ifdef USE_TEXT_SENSOR
113 for (const auto &entry : this->text_sensors_) {
114 LOG_TEXT_SENSOR(" ", "Text Sensor (OBIS)", entry.sensor);
115 ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str());
116 }
117#endif
118#ifdef USE_BINARY_SENSOR
119 for (const auto &entry : this->binary_sensors_) {
120 LOG_BINARY_SENSOR(" ", "Binary Sensor (OBIS)", entry.sensor);
121 ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str());
122 }
123#endif
124}
125
127 this->read_rx_buffer_();
128 if (this->bytes_accumulated_ > 0 &&
129 App.get_loop_component_start_time() - this->last_rx_char_time_ > this->receive_timeout_ms_) {
130 this->process_frame_();
131 }
132}
133
135 while (this->available()) {
136 this->read();
137 }
138}
139
141 int available = this->available();
142 if (available == 0)
143 return;
144
145 if (this->bytes_accumulated_ + available > this->rx_buffer_.size()) {
146 ESP_LOGW(TAG, "RX Buffer overflow. Frame too large! Dropping frame.");
147 this->bytes_accumulated_ = 0;
148
149 this->flush_rx_buffer_();
150 return;
151 }
152
153 bool success = this->read_array(this->rx_buffer_.data() + this->bytes_accumulated_, available);
154 if (!success) {
155 ESP_LOGW(TAG, "UART read failed. Dropping frame.");
156 this->bytes_accumulated_ = 0;
157 this->flush_rx_buffer_();
158 return;
159 }
160
162
164}
165
167 ESP_LOGV(TAG, "Processing frame of size: %zu bytes", this->bytes_accumulated_);
168
169 auto callback = [this](const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
170 this->on_data_(obis_code, float_val, str_val, is_numeric);
171 };
172
173 this->parser_.parse({this->rx_buffer_.data(), this->bytes_accumulated_}, callback);
174
175 this->bytes_accumulated_ = 0;
176}
177
178void DlmsMeterComponent::on_data_(const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
179 int updated_count = 0;
180
181#ifdef USE_SENSOR
182 if (is_numeric) {
183 for (auto &item : this->sensors_) {
184 if (item.obis_code == obis_code) {
185 item.sensor->publish_state(float_val);
186 updated_count++;
187 }
188 }
189 }
190#endif
191
192#ifdef USE_TEXT_SENSOR
193 if (!is_numeric && str_val != nullptr) {
194 for (auto &item : this->text_sensors_) {
195 if (item.obis_code == obis_code) {
196 item.sensor->publish_state(str_val);
197 updated_count++;
198 }
199 }
200 }
201#endif
202
203#ifdef USE_BINARY_SENSOR
204 if (is_numeric) {
205 bool state = float_val != 0.0f;
206 for (auto &item : this->binary_sensors_) {
207 if (item.obis_code == obis_code) {
208 item.sensor->publish_state(state);
209 updated_count++;
210 }
211 }
212 }
213#endif
214
215 if (updated_count == 0) {
216 ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code);
217 }
218}
219
220#ifdef USE_SENSOR
221void DlmsMeterComponent::register_sensor(const std::string &obis_code, sensor::Sensor *sensor) {
222 this->sensors_.push_back({obis_code, sensor});
223}
224#endif
225#ifdef USE_TEXT_SENSOR
226void DlmsMeterComponent::register_text_sensor(const std::string &obis_code, text_sensor::TextSensor *sensor) {
227 this->text_sensors_.push_back({obis_code, sensor});
228}
229#endif
230#ifdef USE_BINARY_SENSOR
232 this->binary_sensors_.push_back({obis_code, sensor});
233}
234#endif
235
236} // namespace esphome::dlms_meter
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
Base class for all binary_sensor-type classes.
StaticVector< SensorItem, DLMS_MAX_SENSORS > sensors_
Definition dlms_meter.h:141
std::vector< CustomPattern > custom_patterns_
Definition dlms_meter.h:135
void register_sensor(const std::string &obis_code, sensor::Sensor *sensor)
std::array< uint8_t, 2048 > rx_buffer_
Definition dlms_meter.h:128
void register_binary_sensor(const std::string &obis_code, binary_sensor::BinarySensor *sensor)
StaticVector< TextSensorItem, DLMS_MAX_TEXT_SENSORS > text_sensors_
Definition dlms_meter.h:144
StaticVector< BinarySensorItem, DLMS_MAX_BINARY_SENSORS > binary_sensors_
Definition dlms_meter.h:147
void on_data_(const char *obis_code, float float_val, const char *str_val, bool is_numeric)
void register_text_sensor(const std::string &obis_code, text_sensor::TextSensor *sensor)
DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check, std::optional< std::array< uint8_t, 16 > > decryption_key, std::optional< std::array< uint8_t, 16 > > authentication_key, std::vector< CustomPattern > custom_patterns)
Base-class for all sensors.
Definition sensor.h:47
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
bool state
Definition fan.h:2
const char int const __FlashStringHelper va_list args
Definition log.h:74
size_t size_t const char * fmt
Definition helpers.h:1039
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t