Optimize DLMS parser buffers and RX timing

Replace heavy std::string/iostream usage in the DLMS parser with stack-allocated char buffers and snprintf to avoid heap allocations and reduce runtime overhead. Introduce buffer-based helpers (data_to_string_, obis_to_string_) and switch hex formatting to format_hex_pretty_to; remove <sstream>/<iomanip> and include <cstdio>. Update header signatures accordingly. Also change DlmsPushComponent to use App.get_loop_component_start_time() instead of millis() when tracking last_rx_char_time_ for RX timeout handling.

Files changed: components/dlms_push/dlms_parser.cpp/.h (buffer-based string helpers, logging, include changes), components/dlms_push/dlms_push.cpp (use App loop time for timeout).

Note: The string helper APIs were converted to buffer-oriented versions — update any callers to provide an output buffer.
main
Tomer27cz 2026-03-07 20:33:20 +01:00
parent 81bbd19c09
commit 8fc10fb496
4 changed files with 58 additions and 37 deletions

View File

@ -5,8 +5,7 @@
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <sstream> #include <cstdio>
#include <iomanip>
#include <algorithm> #include <algorithm>
namespace esphome { namespace esphome {
@ -400,10 +399,18 @@ 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;
std::string obis_str = this->obis_to_string_(c.obis); // Use stack-allocated buffer for OBIS to avoid string allocation
char obis_str_buf[32];
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;
std::string val_s = this->data_as_string_(c.value_type, c.value_ptr, c.value_len);
// Use stack-allocated buffer for formatting data
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));
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 &&
@ -427,7 +434,9 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
} }
if (c.value_ptr && c.value_len > 0) { if (c.value_ptr && c.value_len > 0) {
ESP_LOGI(TAG, " as hex dump : %s", esphome::format_hex_pretty(c.value_ptr, c.value_len).c_str()); char hex_buf[512];
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 string :'%s'", val_s.c_str()); ESP_LOGI(TAG, " as string :'%s'", val_s.c_str());
ESP_LOGI(TAG, " as number : %f", raw_val_f); ESP_LOGI(TAG, " as number : %f", raw_val_f);
@ -481,16 +490,17 @@ float DlmsParser::data_as_float_(DlmsDataType value_type, const uint8_t *ptr, ui
} }
} }
std::string DlmsParser::data_as_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len) { void DlmsParser::data_to_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len, char *buffer, size_t max_len) {
if (!ptr || len == 0) return ""; if (max_len > 0) buffer[0] = '\0';
if (!ptr || len == 0 || max_len == 0) return;
auto hex_of = [](const uint8_t *p, uint8_t l) { auto hex_of = [](const uint8_t *p, uint8_t l, char *out, size_t max_out) {
std::ostringstream ss; if (max_out == 0) return;
ss << std::hex << std::setfill('0'); out[0] = '\0';
for (uint8_t i = 0; i < l; i++) { size_t pos = 0;
ss << std::setw(2) << static_cast<int>(p[i]); for (uint8_t i = 0; i < l && pos + 2 < max_out; i++) {
pos += snprintf(out + pos, max_out - pos, "%02x", p[i]);
} }
return ss.str();
}; };
auto be16 = [](const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); }; auto be16 = [](const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); };
@ -504,58 +514,70 @@ std::string DlmsParser::data_as_string_(DlmsDataType value_type, const uint8_t *
switch (value_type) { switch (value_type) {
case DLMS_DATA_TYPE_OCTET_STRING: case DLMS_DATA_TYPE_OCTET_STRING:
case DLMS_DATA_TYPE_STRING: case DLMS_DATA_TYPE_STRING:
case DLMS_DATA_TYPE_STRING_UTF8: case DLMS_DATA_TYPE_STRING_UTF8: {
return std::string(reinterpret_cast<const char *>(ptr), len); size_t copy_len = std::min((size_t)len, max_len - 1);
std::memcpy(buffer, ptr, copy_len);
buffer[copy_len] = '\0';
break;
}
case DLMS_DATA_TYPE_BIT_STRING: case DLMS_DATA_TYPE_BIT_STRING:
case DLMS_DATA_TYPE_BINARY_CODED_DESIMAL: case DLMS_DATA_TYPE_BINARY_CODED_DESIMAL:
case DLMS_DATA_TYPE_DATETIME: case DLMS_DATA_TYPE_DATETIME:
case DLMS_DATA_TYPE_DATE: case DLMS_DATA_TYPE_DATE:
case DLMS_DATA_TYPE_TIME: case DLMS_DATA_TYPE_TIME:
return hex_of(ptr, len); hex_of(ptr, len, buffer, max_len);
break;
case DLMS_DATA_TYPE_BOOLEAN: case DLMS_DATA_TYPE_BOOLEAN:
case DLMS_DATA_TYPE_ENUM: case DLMS_DATA_TYPE_ENUM:
case DLMS_DATA_TYPE_UINT8: case DLMS_DATA_TYPE_UINT8:
return std::to_string(static_cast<unsigned>(ptr[0])); snprintf(buffer, max_len, "%u", static_cast<unsigned>(ptr[0]));
break;
case DLMS_DATA_TYPE_INT8: case DLMS_DATA_TYPE_INT8:
return std::to_string(static_cast<int>(static_cast<int8_t>(ptr[0]))); snprintf(buffer, max_len, "%d", static_cast<int>(static_cast<int8_t>(ptr[0])));
break;
case DLMS_DATA_TYPE_UINT16: case DLMS_DATA_TYPE_UINT16:
return len >= 2 ? std::to_string(be16(ptr)) : ""; if (len >= 2) snprintf(buffer, max_len, "%u", be16(ptr));
break;
case DLMS_DATA_TYPE_INT16: case DLMS_DATA_TYPE_INT16:
return len >= 2 ? std::to_string(static_cast<int16_t>(be16(ptr))) : ""; if (len >= 2) snprintf(buffer, max_len, "%d", static_cast<int16_t>(be16(ptr)));
break;
case DLMS_DATA_TYPE_UINT32: case DLMS_DATA_TYPE_UINT32:
return len >= 4 ? std::to_string(be32(ptr)) : ""; if (len >= 4) snprintf(buffer, max_len, "%lu", (unsigned long)be32(ptr));
break;
case DLMS_DATA_TYPE_INT32: case DLMS_DATA_TYPE_INT32:
return len >= 4 ? std::to_string(static_cast<int32_t>(be32(ptr))) : ""; if (len >= 4) snprintf(buffer, max_len, "%ld", (long)static_cast<int32_t>(be32(ptr)));
break;
case DLMS_DATA_TYPE_UINT64: case DLMS_DATA_TYPE_UINT64:
return len >= 8 ? std::to_string(be64(ptr)) : ""; if (len >= 8) snprintf(buffer, max_len, "%llu", (unsigned long long)be64(ptr));
break;
case DLMS_DATA_TYPE_INT64: case DLMS_DATA_TYPE_INT64:
return len >= 8 ? std::to_string(static_cast<int64_t>(be64(ptr))) : ""; if (len >= 8) snprintf(buffer, max_len, "%lld", (long long)static_cast<int64_t>(be64(ptr)));
break;
case DLMS_DATA_TYPE_FLOAT32: case DLMS_DATA_TYPE_FLOAT32:
case DLMS_DATA_TYPE_FLOAT64: { case DLMS_DATA_TYPE_FLOAT64: {
std::ostringstream ss; snprintf(buffer, max_len, "%f", this->data_as_float_(value_type, ptr, len));
ss << this->data_as_float_(value_type, ptr, len); break;
return ss.str();
} }
default: default:
return ""; break;
} }
} }
std::string DlmsParser::obis_to_string_(const uint8_t *obis) { void DlmsParser::obis_to_string_(const uint8_t *obis, char *buffer, size_t max_len) {
char buf[32]; if (max_len > 0) buffer[0] = '\0';
snprintf(buf, sizeof(buf), "%u.%u.%u.%u.%u.%u", obis[0], obis[1], obis[2], obis[3], obis[4], obis[5]); if (!obis || max_len == 0) return;
return std::string(buf); snprintf(buffer, max_len, "%u.%u.%u.%u.%u.%u", obis[0], obis[1], obis[2], obis[3], obis[4], obis[5]);
} }
const char *DlmsParser::dlms_data_type_to_string_(DlmsDataType vt) { const char *DlmsParser::dlms_data_type_to_string_(DlmsDataType vt) {

View File

@ -113,8 +113,8 @@ class DlmsParser {
void emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptures &c); void emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptures &c);
float data_as_float_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len); float data_as_float_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len);
std::string data_as_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len); std::string data_to_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len, char *buffer, size_t max_len);
std::string obis_to_string_(const uint8_t *obis); std::string obis_to_string_(const uint8_t *obis, char *buffer, size_t max_len);
const char *dlms_data_type_to_string_(DlmsDataType vt); const char *dlms_data_type_to_string_(DlmsDataType vt);
const uint8_t *buffer_{nullptr}; const uint8_t *buffer_{nullptr};

View File

@ -57,7 +57,7 @@ void DlmsPushComponent::dump_config() {
void DlmsPushComponent::loop() { void DlmsPushComponent::loop() {
this->read_rx_buffer_(); this->read_rx_buffer_();
if (this->receiving_ && (millis() - this->last_rx_char_time_ > this->receive_timeout_ms_)) { if (this->receiving_ && (App.get_loop_component_start_time() - this->last_rx_char_time_ > this->receive_timeout_ms_)) {
this->receiving_ = false; this->receiving_ = false;
this->process_frame_(); this->process_frame_();
} }
@ -68,7 +68,7 @@ void DlmsPushComponent::read_rx_buffer_() {
if (available == 0) return; if (available == 0) return;
this->receiving_ = true; this->receiving_ = true;
this->last_rx_char_time_ = millis(); this->last_rx_char_time_ = App.get_loop_component_start_time();
while (this->available()) { while (this->available()) {
if (this->rx_buffer_len_ >= MAX_RX_BUFFER_SIZE) { if (this->rx_buffer_len_ >= MAX_RX_BUFFER_SIZE) {

View File

@ -14,7 +14,6 @@
#endif #endif
#include <vector> #include <vector>
#include <map>
#include <string> #include <string>
#include <array> #include <array>