Optimize DlmsParser allocations and types

Use stack buffers and C-strings to avoid heap allocations, and modernize token handling and ownership.

- Replace heap-allocated std::string temporaries in dlms_parser.cpp with stack buffers and pass const char* to callbacks/logs to reduce allocations and copying.
- Change DlmsDataCallback signature to accept const char* for obis and string values; update DlmsPushComponent to match.
- Switch DlmsParser ownership to std::unique_ptr in DlmsPushComponent and add <memory> include.
- Replace std::list with std::vector for DSL token parsing, update iteration/insertion logic accordingly.
- Remove unused <list> include and adjust related function signatures/whitespace.

These changes improve performance and memory usage; note the callback/API signature change may require updating any external callers.
main
Tomer27cz 2026-03-07 20:58:19 +01:00
parent 789611a611
commit a9e13b6e8c
4 changed files with 21 additions and 23 deletions

View File

@ -399,10 +399,9 @@ bool DlmsParser::match_pattern_(uint8_t elem_idx, const AxdrDescriptorPattern &p
void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptures &c) { void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptures &c) {
if (!c.obis || !this->callback_) return; if (!c.obis || !this->callback_) return;
// Use stack-allocated buffer for OBIS to avoid string allocation // Use stack-allocated buffer for OBIS to avoid heap allocation
char obis_str_buf[32]; char obis_str_buf[32];
this->obis_to_string_(c.obis, obis_str_buf, sizeof(obis_str_buf)); this->obis_to_string_(c.obis, obis_str_buf, sizeof(obis_str_buf));
std::string obis_str(obis_str_buf);
float raw_val_f = this->data_as_float_(c.value_type, c.value_ptr, c.value_len); float raw_val_f = this->data_as_float_(c.value_type, c.value_ptr, c.value_len);
float val_f = raw_val_f; float val_f = raw_val_f;
@ -410,7 +409,6 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
// Use stack-allocated buffer for formatting data // Use stack-allocated buffer for formatting data
char val_s_buf[128]; char val_s_buf[128];
this->data_to_string_(c.value_type, c.value_ptr, c.value_len, val_s_buf, sizeof(val_s_buf)); this->data_to_string_(c.value_type, c.value_ptr, c.value_len, val_s_buf, sizeof(val_s_buf));
std::string val_s(val_s_buf);
bool is_numeric = (c.value_type != DLMS_DATA_TYPE_OCTET_STRING && bool is_numeric = (c.value_type != DLMS_DATA_TYPE_OCTET_STRING &&
c.value_type != DLMS_DATA_TYPE_STRING && c.value_type != DLMS_DATA_TYPE_STRING &&
@ -424,7 +422,7 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
ESP_LOGD(TAG, "Pattern match '%s' at idx %u ===============", pat.name.c_str(), c.elem_idx); ESP_LOGD(TAG, "Pattern match '%s' at idx %u ===============", pat.name.c_str(), c.elem_idx);
uint16_t cid = c.class_id ? c.class_id : pat.default_class_id; uint16_t cid = c.class_id ? c.class_id : pat.default_class_id;
ESP_LOGI(TAG, "Found attribute descriptor: class_id=%d, obis=%s", cid, obis_str.c_str()); ESP_LOGI(TAG, "Found attribute descriptor: class_id=%d, obis=%s", cid, obis_str_buf);
if (c.has_scaler_unit) { if (c.has_scaler_unit) {
ESP_LOGI(TAG, "Value type: %s, len %d, scaler %d, unit %d", ESP_LOGI(TAG, "Value type: %s, len %d, scaler %d, unit %d",
@ -438,7 +436,7 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
esphome::format_hex_pretty_to(hex_buf, sizeof(hex_buf), c.value_ptr, c.value_len); esphome::format_hex_pretty_to(hex_buf, sizeof(hex_buf), c.value_ptr, c.value_len);
ESP_LOGI(TAG, " as hex dump : %s", hex_buf); ESP_LOGI(TAG, " as hex dump : %s", hex_buf);
} }
ESP_LOGI(TAG, " as string :'%s'", val_s.c_str()); ESP_LOGI(TAG, " as string :'%s'", val_s_buf);
ESP_LOGI(TAG, " as number : %f", raw_val_f); ESP_LOGI(TAG, " as number : %f", raw_val_f);
if (c.has_scaler_unit && is_numeric) { if (c.has_scaler_unit && is_numeric) {
@ -446,7 +444,7 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
} }
} }
this->callback_(obis_str, val_f, val_s, is_numeric); this->callback_(obis_str_buf, val_f, val_s_buf, is_numeric);
this->objects_found_++; this->objects_found_++;
} }
@ -620,7 +618,7 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
return s.substr(b, e - b + 1); return s.substr(b, e - b + 1);
}; };
std::list<std::string> tokens; std::vector<std::string> tokens;
std::string current; std::string current;
int paren = 0; int paren = 0;
for (char c : dsl) { for (char c : dsl) {
@ -639,8 +637,8 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
} }
if (!current.empty()) tokens.push_back(trim(current)); if (!current.empty()) tokens.push_back(trim(current));
for (auto it = tokens.begin(); it != tokens.end(); ++it) { for (size_t i = 0; i < tokens.size(); i++) {
std::string tok = *it; std::string tok = tokens[i];
if (tok.empty()) continue; if (tok.empty()) continue;
if (tok == "F") pat.steps.push_back({AxdrTokenType::EXPECT_TO_BE_FIRST}); if (tok == "F") pat.steps.push_back({AxdrTokenType::EXPECT_TO_BE_FIRST});
@ -671,7 +669,7 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
size_t r = tok.rfind(')'); size_t r = tok.rfind(')');
if (l != std::string::npos && r != std::string::npos && r > l + 1) { if (l != std::string::npos && r != std::string::npos && r > l + 1) {
std::string inner = tok.substr(l + 1, r - l - 1); std::string inner = tok.substr(l + 1, r - l - 1);
std::list<std::string> inner_tokens; std::vector<std::string> inner_tokens;
std::string cur; std::string cur;
for (char c2 : inner) { for (char c2 : inner) {
if (c2 == ',') { if (c2 == ',') {
@ -685,9 +683,9 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
if (!inner_tokens.empty()) { if (!inner_tokens.empty()) {
pat.steps.push_back({AxdrTokenType::EXPECT_STRUCTURE_N, static_cast<uint8_t>(inner_tokens.size())}); pat.steps.push_back({AxdrTokenType::EXPECT_STRUCTURE_N, static_cast<uint8_t>(inner_tokens.size())});
inner_tokens.push_front("DN"); inner_tokens.insert(inner_tokens.begin(), "DN");
inner_tokens.push_back("UP"); inner_tokens.push_back("UP");
tokens.insert(std::next(it), inner_tokens.begin(), inner_tokens.end()); tokens.insert(tokens.begin() + i + 1, inner_tokens.begin(), inner_tokens.end());
} }
} }
} }

View File

@ -4,7 +4,6 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <list>
namespace esphome { namespace esphome {
namespace dlms_push { namespace dlms_push {
@ -37,7 +36,7 @@ enum DlmsDataType : uint8_t {
}; };
// Callback for the hub: OBIS code (e.g. "1.0.1.8.0.255"), numeric value, string value, is_numeric flag // Callback for the hub: OBIS code (e.g. "1.0.1.8.0.255"), numeric value, string value, is_numeric flag
using DlmsDataCallback = std::function<void(const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric)>; using DlmsDataCallback = std::function<void(const char *obis_code, float float_val, const char *str_val, bool is_numeric)>;
// --- Pattern Matching Enums & Structs --- // --- Pattern Matching Enums & Structs ---
enum class AxdrTokenType : uint8_t { enum class AxdrTokenType : uint8_t {

View File

@ -11,7 +11,7 @@ namespace dlms_push {
static const char *const TAG = "dlms_push"; static const char *const TAG = "dlms_push";
DlmsPushComponent::DlmsPushComponent() { DlmsPushComponent::DlmsPushComponent() {
this->parser_ = new DlmsParser(); this->parser_ = std::make_unique<DlmsParser>();
} }
void DlmsPushComponent::setup() { void DlmsPushComponent::setup() {
@ -96,7 +96,7 @@ void DlmsPushComponent::process_frame_() {
ESP_LOGD(TAG, "PUSH frame size: %zu bytes", this->rx_buffer_len_); ESP_LOGD(TAG, "PUSH frame size: %zu bytes", this->rx_buffer_len_);
} }
auto callback = [this](const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric) { auto callback = [this](const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
this->on_data_parsed_(obis_code, float_val, str_val, is_numeric); this->on_data_parsed_(obis_code, float_val, str_val, is_numeric);
}; };
@ -109,7 +109,7 @@ void DlmsPushComponent::process_frame_() {
this->rx_buffer_len_ = 0; this->rx_buffer_len_ = 0;
} }
void DlmsPushComponent::on_data_parsed_(const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric) { void DlmsPushComponent::on_data_parsed_(const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
int updated_count = 0; int updated_count = 0;
#ifdef USE_SENSOR #ifdef USE_SENSOR
@ -117,7 +117,7 @@ void DlmsPushComponent::on_data_parsed_(const std::string &obis_code, float floa
for (const auto &entry : this->sensors_) { for (const auto &entry : this->sensors_) {
if (entry.obis == obis_code) { if (entry.obis == obis_code) {
if (this->show_log_) { if (this->show_log_) {
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code.c_str(), entry.sensor->get_name().c_str()); ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code, entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Publishing data"); ESP_LOGD(TAG, "Publishing data");
} }
entry.sensor->publish_state(float_val); entry.sensor->publish_state(float_val);
@ -131,7 +131,7 @@ void DlmsPushComponent::on_data_parsed_(const std::string &obis_code, float floa
for (const auto &entry : this->text_sensors_) { for (const auto &entry : this->text_sensors_) {
if (entry.obis == obis_code) { if (entry.obis == obis_code) {
if (this->show_log_) { if (this->show_log_) {
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code.c_str(), entry.sensor->get_name().c_str()); ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code, entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Publishing data"); ESP_LOGD(TAG, "Publishing data");
} }
entry.sensor->publish_state(str_val); entry.sensor->publish_state(str_val);
@ -146,7 +146,7 @@ void DlmsPushComponent::on_data_parsed_(const std::string &obis_code, float floa
for (const auto &entry : this->binary_sensors_) { for (const auto &entry : this->binary_sensors_) {
if (entry.obis == obis_code) { if (entry.obis == obis_code) {
if (this->show_log_) { if (this->show_log_) {
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code.c_str(), entry.sensor->get_name().c_str()); ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code, entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Publishing data"); ESP_LOGD(TAG, "Publishing data");
} }
entry.sensor->publish_state(state); entry.sensor->publish_state(state);
@ -157,7 +157,7 @@ void DlmsPushComponent::on_data_parsed_(const std::string &obis_code, float floa
#endif #endif
if (this->show_log_ && updated_count == 0) { if (this->show_log_ && updated_count == 0) {
ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code.c_str()); ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code);
} }
} }

View File

@ -16,6 +16,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <array> #include <array>
#include <memory>
namespace esphome { namespace esphome {
namespace dlms_push { namespace dlms_push {
@ -49,7 +50,7 @@ class DlmsPushComponent : public Component, public uart::UARTDevice {
void read_rx_buffer_(); void read_rx_buffer_();
void process_frame_(); void process_frame_();
void on_data_parsed_(const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric); void on_data_parsed_(const char *obis_code, float float_val, const char *str_val, bool is_numeric);
uint32_t receive_timeout_ms_{50}; uint32_t receive_timeout_ms_{50};
bool show_log_{false}; bool show_log_{false};
@ -61,7 +62,7 @@ class DlmsPushComponent : public Component, public uart::UARTDevice {
uint32_t last_rx_char_time_{0}; uint32_t last_rx_char_time_{0};
bool receiving_{false}; bool receiving_{false};
DlmsParser *parser_{nullptr}; std::unique_ptr<DlmsParser> parser_;
#ifdef USE_SENSOR #ifdef USE_SENSOR
struct NumericSensorEntry { struct NumericSensorEntry {