Compare commits

..

No commits in common. "c9bbbd574ca1f637ba5263e9c07ded11816e7ee2" and "81bbd19c091ff4b650d5dfbef83ccbf866d917c6" have entirely different histories.

4 changed files with 65 additions and 108 deletions

View File

@ -5,7 +5,8 @@
#include <cmath>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <iomanip>
#include <algorithm>
namespace esphome {
@ -65,11 +66,7 @@ size_t DlmsParser::parse(const uint8_t *buffer, size_t length, DlmsDataCallback
// First byte after flag should be the data type (usually Structure or Array)
uint8_t start_type = this->read_byte_();
if (start_type != DLMS_DATA_TYPE_STRUCTURE && start_type != DLMS_DATA_TYPE_ARRAY) {
if (this->show_log_) {
ESP_LOGW(TAG, "Expected STRUCTURE or ARRAY after header, found type %02X at position %zu",
start_type, this->pos_ - 1);
}
if (this->show_log_) ESP_LOGW(TAG, "Expected STRUCTURE or ARRAY after header, found type %02X at position %zu", start_type, this->pos_ - 1);
return 0;
}
@ -79,10 +76,7 @@ size_t DlmsParser::parse(const uint8_t *buffer, size_t length, DlmsDataCallback
ESP_LOGV(TAG, "Some errors occurred parsing DLMS data, or unexpected end of buffer.");
}
if (this->show_log_) {
ESP_LOGD(TAG, "Parsing completed. Processed %zu bytes, found %zu objects", this->pos_, this->objects_found_);
}
if (this->show_log_) ESP_LOGD(TAG, "Parsing completed. Processed %zu bytes, found %zu objects", this->pos_, this->objects_found_);
return this->objects_found_;
}
@ -256,11 +250,7 @@ bool DlmsParser::parse_sequence_(uint8_t type, uint8_t depth) {
}
if (this->pos_ >= this->buffer_len_) {
if (this->show_log_) {
ESP_LOGV(TAG, "Unexpected end while reading element %d of %s", elements_consumed + 1,
type == DLMS_DATA_TYPE_STRUCTURE ? "STRUCTURE" : "ARRAY");
}
if (this->show_log_) ESP_LOGV(TAG, "Unexpected end while reading element %d of %s", elements_consumed + 1, type == DLMS_DATA_TYPE_STRUCTURE ? "STRUCTURE" : "ARRAY");
return false;
}
@ -269,11 +259,7 @@ bool DlmsParser::parse_sequence_(uint8_t type, uint8_t depth) {
elements_consumed++;
if (this->pos_ == original_position) {
if (this->show_log_) {
ESP_LOGV(TAG, "No progress parsing element %d at position %zu, aborting to avoid infinite loop",
elements_consumed, original_position);
}
if (this->show_log_) ESP_LOGV(TAG, "No progress parsing element %d at position %zu, aborting to avoid infinite loop", elements_consumed, original_position);
return false;
}
}
@ -335,8 +321,7 @@ bool DlmsParser::try_match_patterns_(uint8_t elem_idx) {
return false;
}
bool DlmsParser::match_pattern_(uint8_t elem_idx, const AxdrDescriptorPattern &pat,
uint8_t &elements_consumed_at_level0) {
bool DlmsParser::match_pattern_(uint8_t elem_idx, const AxdrDescriptorPattern &pat, uint8_t &elements_consumed_at_level0) {
AxdrCaptures cap{};
elements_consumed_at_level0 = 0;
uint8_t level = 0;
@ -415,16 +400,10 @@ bool DlmsParser::match_pattern_(uint8_t elem_idx, const AxdrDescriptorPattern &p
void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptures &c) {
if (!c.obis || !this->callback_) return;
// Use stack-allocated buffer for OBIS to avoid heap allocation
char obis_str_buf[32];
this->obis_to_string_(c.obis, obis_str_buf, sizeof(obis_str_buf));
std::string obis_str = this->obis_to_string_(c.obis);
float raw_val_f = this->data_as_float_(c.value_type, c.value_ptr, c.value_len);
float val_f = raw_val_f;
// 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 = this->data_as_string_(c.value_type, c.value_ptr, c.value_len);
bool is_numeric = (c.value_type != DLMS_DATA_TYPE_OCTET_STRING &&
c.value_type != DLMS_DATA_TYPE_STRING &&
@ -438,7 +417,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);
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_buf);
ESP_LOGI(TAG, "Found attribute descriptor: class_id=%d, obis=%s", cid, obis_str.c_str());
if (c.has_scaler_unit) {
ESP_LOGI(TAG, "Value type: %s, len %d, scaler %d, unit %d",
@ -448,11 +427,9 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
}
if (c.value_ptr && c.value_len > 0) {
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 hex dump : %s", esphome::format_hex_pretty(c.value_ptr, c.value_len).c_str());
}
ESP_LOGI(TAG, " as string :'%s'", val_s_buf);
ESP_LOGI(TAG, " as string :'%s'", val_s.c_str());
ESP_LOGI(TAG, " as number : %f", raw_val_f);
if (c.has_scaler_unit && is_numeric) {
@ -460,7 +437,7 @@ void DlmsParser::emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptur
}
}
this->callback_(obis_str_buf, val_f, val_s_buf, is_numeric);
this->callback_(obis_str, val_f, val_s, is_numeric);
this->objects_found_++;
}
@ -468,9 +445,7 @@ float DlmsParser::data_as_float_(DlmsDataType value_type, const uint8_t *ptr, ui
if (!ptr || len == 0) return 0.0f;
auto be16 = [](const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); };
auto be32 = [](const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
};
auto be32 = [](const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; };
auto be64 = [](const uint8_t *p) {
return ((uint64_t)p[0] << 56) | ((uint64_t)p[1] << 48) | ((uint64_t)p[2] << 40) | ((uint64_t)p[3] << 32) |
((uint64_t)p[4] << 24) | ((uint64_t)p[5] << 16) | ((uint64_t)p[6] << 8) | (uint64_t)p[7];
@ -506,24 +481,20 @@ float DlmsParser::data_as_float_(DlmsDataType value_type, const uint8_t *ptr, ui
}
}
void DlmsParser::data_to_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len,
char *buffer, size_t max_len) {
if (max_len > 0) buffer[0] = '\0';
if (!ptr || len == 0 || max_len == 0) return;
std::string DlmsParser::data_as_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len) {
if (!ptr || len == 0) return "";
auto hex_of = [](const uint8_t *p, uint8_t l, char *out, size_t max_out) {
if (max_out == 0) return;
out[0] = '\0';
size_t pos = 0;
for (uint8_t i = 0; i < l && pos + 2 < max_out; i++) {
pos += snprintf(out + pos, max_out - pos, "%02x", p[i]);
auto hex_of = [](const uint8_t *p, uint8_t l) {
std::ostringstream ss;
ss << std::hex << std::setfill('0');
for (uint8_t i = 0; i < l; i++) {
ss << std::setw(2) << static_cast<int>(p[i]);
}
return ss.str();
};
auto be16 = [](const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); };
auto be32 = [](const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
};
auto be32 = [](const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; };
auto be64 = [](const uint8_t *p) {
uint64_t v = 0;
for (int i = 0; i < 8; i++) v = (v << 8) | p[i];
@ -533,70 +504,58 @@ void DlmsParser::data_to_string_(DlmsDataType value_type, const uint8_t *ptr, ui
switch (value_type) {
case DLMS_DATA_TYPE_OCTET_STRING:
case DLMS_DATA_TYPE_STRING:
case DLMS_DATA_TYPE_STRING_UTF8: {
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_STRING_UTF8:
return std::string(reinterpret_cast<const char *>(ptr), len);
case DLMS_DATA_TYPE_BIT_STRING:
case DLMS_DATA_TYPE_BINARY_CODED_DESIMAL:
case DLMS_DATA_TYPE_DATETIME:
case DLMS_DATA_TYPE_DATE:
case DLMS_DATA_TYPE_TIME:
hex_of(ptr, len, buffer, max_len);
break;
return hex_of(ptr, len);
case DLMS_DATA_TYPE_BOOLEAN:
case DLMS_DATA_TYPE_ENUM:
case DLMS_DATA_TYPE_UINT8:
snprintf(buffer, max_len, "%u", static_cast<unsigned>(ptr[0]));
break;
return std::to_string(static_cast<unsigned>(ptr[0]));
case DLMS_DATA_TYPE_INT8:
snprintf(buffer, max_len, "%d", static_cast<int>(static_cast<int8_t>(ptr[0])));
break;
return std::to_string(static_cast<int>(static_cast<int8_t>(ptr[0])));
case DLMS_DATA_TYPE_UINT16:
if (len >= 2) snprintf(buffer, max_len, "%u", be16(ptr));
break;
return len >= 2 ? std::to_string(be16(ptr)) : "";
case DLMS_DATA_TYPE_INT16:
if (len >= 2) snprintf(buffer, max_len, "%d", static_cast<int16_t>(be16(ptr)));
break;
return len >= 2 ? std::to_string(static_cast<int16_t>(be16(ptr))) : "";
case DLMS_DATA_TYPE_UINT32:
if (len >= 4) snprintf(buffer, max_len, "%lu", (unsigned long)be32(ptr));
break;
return len >= 4 ? std::to_string(be32(ptr)) : "";
case DLMS_DATA_TYPE_INT32:
if (len >= 4) snprintf(buffer, max_len, "%ld", (long)static_cast<int32_t>(be32(ptr)));
break;
return len >= 4 ? std::to_string(static_cast<int32_t>(be32(ptr))) : "";
case DLMS_DATA_TYPE_UINT64:
if (len >= 8) snprintf(buffer, max_len, "%llu", (unsigned long long)be64(ptr));
break;
return len >= 8 ? std::to_string(be64(ptr)) : "";
case DLMS_DATA_TYPE_INT64:
if (len >= 8) snprintf(buffer, max_len, "%lld", (long long)static_cast<int64_t>(be64(ptr)));
break;
return len >= 8 ? std::to_string(static_cast<int64_t>(be64(ptr))) : "";
case DLMS_DATA_TYPE_FLOAT32:
case DLMS_DATA_TYPE_FLOAT64: {
snprintf(buffer, max_len, "%f", this->data_as_float_(value_type, ptr, len));
break;
std::ostringstream ss;
ss << this->data_as_float_(value_type, ptr, len);
return ss.str();
}
default:
break;
return "";
}
}
void DlmsParser::obis_to_string_(const uint8_t *obis, char *buffer, size_t max_len) {
if (max_len > 0) buffer[0] = '\0';
if (!obis || max_len == 0) return;
snprintf(buffer, max_len, "%u.%u.%u.%u.%u.%u", obis[0], obis[1], obis[2], obis[3], obis[4], obis[5]);
std::string DlmsParser::obis_to_string_(const uint8_t *obis) {
char buf[32];
snprintf(buf, sizeof(buf), "%u.%u.%u.%u.%u.%u", obis[0], obis[1], obis[2], obis[3], obis[4], obis[5]);
return std::string(buf);
}
const char *DlmsParser::dlms_data_type_to_string_(DlmsDataType vt) {
@ -639,7 +598,7 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
return s.substr(b, e - b + 1);
};
std::vector<std::string> tokens;
std::list<std::string> tokens;
std::string current;
int paren = 0;
for (char c : dsl) {
@ -658,8 +617,8 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
}
if (!current.empty()) tokens.push_back(trim(current));
for (size_t i = 0; i < tokens.size(); i++) {
std::string tok = tokens[i];
for (auto it = tokens.begin(); it != tokens.end(); ++it) {
std::string tok = *it;
if (tok.empty()) continue;
if (tok == "F") pat.steps.push_back({AxdrTokenType::EXPECT_TO_BE_FIRST});
@ -690,7 +649,7 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
size_t r = tok.rfind(')');
if (l != std::string::npos && r != std::string::npos && r > l + 1) {
std::string inner = tok.substr(l + 1, r - l - 1);
std::vector<std::string> inner_tokens;
std::list<std::string> inner_tokens;
std::string cur;
for (char c2 : inner) {
if (c2 == ',') {
@ -704,9 +663,9 @@ void DlmsParser::register_pattern_dsl_(const std::string &name, const std::strin
if (!inner_tokens.empty()) {
pat.steps.push_back({AxdrTokenType::EXPECT_STRUCTURE_N, static_cast<uint8_t>(inner_tokens.size())});
inner_tokens.insert(inner_tokens.begin(), "DN");
inner_tokens.push_front("DN");
inner_tokens.push_back("UP");
tokens.insert(tokens.begin() + i + 1, inner_tokens.begin(), inner_tokens.end());
tokens.insert(std::next(it), inner_tokens.begin(), inner_tokens.end());
}
}
}

View File

@ -4,6 +4,7 @@
#include <functional>
#include <string>
#include <vector>
#include <list>
namespace esphome {
namespace dlms_push {
@ -36,8 +37,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
using DlmsDataCallback = std::function<void(const char *obis_code, float float_val, const char *str_val,
bool is_numeric)>;
using DlmsDataCallback = std::function<void(const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric)>;
// --- Pattern Matching Enums & Structs ---
enum class AxdrTokenType : uint8_t {
@ -113,8 +113,8 @@ class DlmsParser {
void emit_object_(const AxdrDescriptorPattern &pat, const AxdrCaptures &c);
float data_as_float_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len);
void data_to_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len, char *buffer, size_t max_len);
void obis_to_string_(const uint8_t *obis, char *buffer, size_t max_len);
std::string data_as_string_(DlmsDataType value_type, const uint8_t *ptr, uint8_t len);
std::string obis_to_string_(const uint8_t *obis);
const char *dlms_data_type_to_string_(DlmsDataType vt);
const uint8_t *buffer_{nullptr};

View File

@ -2,7 +2,6 @@
#include "dlms_parser.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
namespace esphome {
@ -11,7 +10,7 @@ namespace dlms_push {
static const char *const TAG = "dlms_push";
DlmsPushComponent::DlmsPushComponent() {
this->parser_ = std::make_unique<DlmsParser>();
this->parser_ = new DlmsParser();
}
void DlmsPushComponent::setup() {
@ -58,7 +57,7 @@ void DlmsPushComponent::dump_config() {
void DlmsPushComponent::loop() {
this->read_rx_buffer_();
if (this->receiving_ && (App.get_loop_component_start_time()-this->last_rx_char_time_ > this->receive_timeout_ms_)) {
if (this->receiving_ && (millis() - this->last_rx_char_time_ > this->receive_timeout_ms_)) {
this->receiving_ = false;
this->process_frame_();
}
@ -69,7 +68,7 @@ void DlmsPushComponent::read_rx_buffer_() {
if (available == 0) return;
this->receiving_ = true;
this->last_rx_char_time_ = App.get_loop_component_start_time();
this->last_rx_char_time_ = millis();
while (this->available()) {
if (this->rx_buffer_len_ >= MAX_RX_BUFFER_SIZE) {
@ -96,21 +95,20 @@ void DlmsPushComponent::process_frame_() {
ESP_LOGD(TAG, "PUSH frame size: %zu bytes", this->rx_buffer_len_);
}
auto callback = [this](const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
auto callback = [this](const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric) {
this->on_data_parsed_(obis_code, float_val, str_val, is_numeric);
};
size_t parsed_objects = this->parser_->parse(this->rx_buffer_.get(), this->rx_buffer_len_, callback, this->show_log_);
if (this->show_log_) {
ESP_LOGD(TAG, "PUSH data parsing complete: %zu objects, bytes consumed %zu/%zu", parsed_objects,
this->rx_buffer_len_, this->rx_buffer_len_);
ESP_LOGD(TAG, "PUSH data parsing complete: %zu objects, bytes consumed %zu/%zu", parsed_objects, this->rx_buffer_len_, this->rx_buffer_len_);
}
this->rx_buffer_len_ = 0;
}
void DlmsPushComponent::on_data_parsed_(const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
void DlmsPushComponent::on_data_parsed_(const std::string &obis_code, float float_val, const std::string &str_val, bool is_numeric) {
int updated_count = 0;
#ifdef USE_SENSOR
@ -118,7 +116,7 @@ void DlmsPushComponent::on_data_parsed_(const char *obis_code, float float_val,
for (const auto &entry : this->sensors_) {
if (entry.obis == obis_code) {
if (this->show_log_) {
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code, entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code.c_str(), entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Publishing data");
}
entry.sensor->publish_state(float_val);
@ -132,7 +130,7 @@ void DlmsPushComponent::on_data_parsed_(const char *obis_code, float float_val,
for (const auto &entry : this->text_sensors_) {
if (entry.obis == obis_code) {
if (this->show_log_) {
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code, entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code.c_str(), entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Publishing data");
}
entry.sensor->publish_state(str_val);
@ -147,7 +145,7 @@ void DlmsPushComponent::on_data_parsed_(const char *obis_code, float float_val,
for (const auto &entry : this->binary_sensors_) {
if (entry.obis == obis_code) {
if (this->show_log_) {
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code, entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Found sensor for OBIS code %s: '%s'", obis_code.c_str(), entry.sensor->get_name().c_str());
ESP_LOGD(TAG, "Publishing data");
}
entry.sensor->publish_state(state);
@ -158,7 +156,7 @@ void DlmsPushComponent::on_data_parsed_(const char *obis_code, float float_val,
#endif
if (this->show_log_ && updated_count == 0) {
ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code);
ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code.c_str());
}
}

View File

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