mirror of
https://github.com/esphome/esphome.git
synced 2025-03-15 15:18:16 +00:00
fix[sml]: reduce update latencies in sml component
SML component is issuing constant warnings about high latencies: ``` [17:13:31][W][component:237]: Component sml took a long time for an operation (87 ms). [17:13:31][W][component:238]: Components should block for at most 30 ms. ``` This change replaces std::vector with a new Span class inspired in C++20 std::span, removing associated memory allocations and reducing latencies in a ESP32-S2 from 120-130ms to 50-60ms when DEBUG logs are enabled, and from 80-90ms to less than 30ms when not. Once all frameworks use C++20, we can remove the Span class and use std::span instead.
This commit is contained in:
parent
bf739506c3
commit
8efcfd0ffd
@ -51,10 +51,10 @@ void Sml::loop() {
|
|||||||
if (!valid)
|
if (!valid)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// remove start/end sequence
|
// discard start/end sequence
|
||||||
this->sml_data_.erase(this->sml_data_.begin(), this->sml_data_.begin() + START_SEQ.size());
|
auto file_begin = this->sml_data_.begin() + START_SEQ.size();
|
||||||
this->sml_data_.resize(this->sml_data_.size() - 8);
|
auto file_length = this->sml_data_.size() - START_SEQ.size() - 8;
|
||||||
this->process_sml_file_(this->sml_data_);
|
this->process_sml_file_(byte_span(&*file_begin, file_length));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
@ -66,32 +66,13 @@ void Sml::add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &
|
|||||||
this->data_callbacks_.add(std::move(callback));
|
this->data_callbacks_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sml::process_sml_file_(const bytes &sml_data) {
|
void Sml::process_sml_file_(const byte_span &sml_data) {
|
||||||
SmlFile sml_file = SmlFile(sml_data);
|
SmlFile(sml_data).for_each_obis_info([this](const ObisInfo &obis_info) { this->publish_value_(obis_info); });
|
||||||
std::vector<ObisInfo> obis_info = sml_file.get_obis_info();
|
|
||||||
this->publish_obis_info_(obis_info);
|
|
||||||
|
|
||||||
this->log_obis_info_(obis_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sml::log_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
|
||||||
ESP_LOGD(TAG, "OBIS info:");
|
|
||||||
for (auto const &obis_info : obis_info_vec) {
|
|
||||||
std::string info;
|
|
||||||
info += " (" + bytes_repr(obis_info.server_id) + ") ";
|
|
||||||
info += obis_info.code_repr();
|
|
||||||
info += " [0x" + bytes_repr(obis_info.value) + "]";
|
|
||||||
ESP_LOGD(TAG, "%s", info.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Sml::publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec) {
|
|
||||||
for (auto const &obis_info : obis_info_vec) {
|
|
||||||
this->publish_value_(obis_info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sml::publish_value_(const ObisInfo &obis_info) {
|
void Sml::publish_value_(const ObisInfo &obis_info) {
|
||||||
|
ESP_LOGD(TAG, "OBIS (%s) %s [0x%s]", bytes_repr(obis_info.server_id).c_str(), obis_info.code_repr().c_str(),
|
||||||
|
bytes_repr(obis_info.value).c_str());
|
||||||
for (auto const &sml_listener : sml_listeners_) {
|
for (auto const &sml_listener : sml_listeners_) {
|
||||||
if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id))
|
if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id))
|
||||||
continue;
|
continue;
|
||||||
|
@ -27,9 +27,7 @@ class Sml : public Component, public uart::UARTDevice {
|
|||||||
void add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &&callback);
|
void add_on_data_callback(std::function<void(std::vector<uint8_t>, bool)> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process_sml_file_(const bytes &sml_data);
|
void process_sml_file_(const byte_span &sml_data);
|
||||||
void log_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
|
|
||||||
void publish_obis_info_(const std::vector<ObisInfo> &obis_info_vec);
|
|
||||||
char check_start_end_bytes_(uint8_t byte);
|
char check_start_end_bytes_(uint8_t byte);
|
||||||
void publish_value_(const ObisInfo &obis_info);
|
void publish_value_(const ObisInfo &obis_info);
|
||||||
|
|
||||||
|
@ -5,21 +5,16 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sml {
|
namespace sml {
|
||||||
|
|
||||||
SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) {
|
SmlFile::SmlFile(byte_span const &buffer) : buffer_(buffer) {
|
||||||
// extract messages
|
// extract messages
|
||||||
this->pos_ = 0;
|
this->pos_ = 0;
|
||||||
while (this->pos_ < this->buffer_.size()) {
|
while (this->pos_ < this->buffer_.size()) {
|
||||||
if (this->buffer_[this->pos_] == 0x00)
|
if (!this->setup_node(this->messages))
|
||||||
break; // EndOfSmlMsg
|
|
||||||
|
|
||||||
SmlNode message = SmlNode();
|
|
||||||
if (!this->setup_node(&message))
|
|
||||||
break;
|
break;
|
||||||
this->messages.emplace_back(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SmlFile::setup_node(SmlNode *node) {
|
bool SmlFile::setup_node(std::vector<SmlNode> &nodes) {
|
||||||
// If the TL field is 0x00, this is the end of the message
|
// If the TL field is 0x00, this is the end of the message
|
||||||
// (see 6.3.1 of SML protocol definition)
|
// (see 6.3.1 of SML protocol definition)
|
||||||
if (this->buffer_[this->pos_] == 0x00) {
|
if (this->buffer_[this->pos_] == 0x00) {
|
||||||
@ -61,49 +56,43 @@ bool SmlFile::setup_node(SmlNode *node) {
|
|||||||
if (this->pos_ + length > this->buffer_.size())
|
if (this->pos_ + length > this->buffer_.size())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
node->type = type;
|
|
||||||
node->nodes.clear();
|
|
||||||
node->value_bytes.clear();
|
|
||||||
|
|
||||||
if (type == SML_LIST) {
|
if (type == SML_LIST) {
|
||||||
node->nodes.reserve(length);
|
std::vector<SmlNode> child_nodes;
|
||||||
|
child_nodes.reserve(length);
|
||||||
for (size_t i = 0; i != length; i++) {
|
for (size_t i = 0; i != length; i++) {
|
||||||
SmlNode child_node = SmlNode();
|
if (!this->setup_node(child_nodes))
|
||||||
if (!this->setup_node(&child_node))
|
|
||||||
return false;
|
return false;
|
||||||
node->nodes.emplace_back(child_node);
|
|
||||||
}
|
}
|
||||||
|
nodes.emplace_back(type, std::move(child_nodes));
|
||||||
} else {
|
} else {
|
||||||
// Value starts at the current position
|
// Value starts at the current position
|
||||||
// Value ends "length" bytes later,
|
// Value ends "length" bytes later,
|
||||||
// (since the TL field is counted but already subtracted from length)
|
// (since the TL field is counted but already subtracted from length)
|
||||||
node->value_bytes = bytes(this->buffer_.begin() + this->pos_, this->buffer_.begin() + this->pos_ + length);
|
nodes.emplace_back(type, byte_span(this->buffer_.begin() + this->pos_, length));
|
||||||
// Increment the pointer past all consumed bytes
|
// Increment the pointer past all consumed bytes
|
||||||
this->pos_ += length;
|
this->pos_ += length;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ObisInfo> SmlFile::get_obis_info() {
|
void SmlFile::for_each_obis_info(const std::function<void(const ObisInfo &)> &callback) {
|
||||||
std::vector<ObisInfo> obis_info;
|
|
||||||
for (auto const &message : messages) {
|
for (auto const &message : messages) {
|
||||||
SmlNode message_body = message.nodes[3];
|
auto message_body = message.nodes[3];
|
||||||
uint16_t message_type = bytes_to_uint(message_body.nodes[0].value_bytes);
|
auto message_type = bytes_to_uint(message_body.nodes[0].value_bytes);
|
||||||
if (message_type != SML_GET_LIST_RES)
|
if (message_type != SML_GET_LIST_RES)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SmlNode get_list_response = message_body.nodes[1];
|
auto get_list_response = message_body.nodes[1];
|
||||||
bytes server_id = get_list_response.nodes[1].value_bytes;
|
auto server_id = get_list_response.nodes[1].value_bytes;
|
||||||
SmlNode val_list = get_list_response.nodes[4];
|
auto val_list = get_list_response.nodes[4];
|
||||||
|
|
||||||
for (auto const &val_list_entry : val_list.nodes) {
|
for (auto const &val_list_entry : val_list.nodes) {
|
||||||
obis_info.emplace_back(server_id, val_list_entry);
|
callback(ObisInfo(server_id, val_list_entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return obis_info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bytes_repr(const bytes &buffer) {
|
std::string bytes_repr(const byte_span &buffer) {
|
||||||
std::string repr;
|
std::string repr;
|
||||||
for (auto const value : buffer) {
|
for (auto const value : buffer) {
|
||||||
repr += str_sprintf("%02x", value & 0xff);
|
repr += str_sprintf("%02x", value & 0xff);
|
||||||
@ -111,7 +100,7 @@ std::string bytes_repr(const bytes &buffer) {
|
|||||||
return repr;
|
return repr;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t bytes_to_uint(const bytes &buffer) {
|
uint64_t bytes_to_uint(const byte_span &buffer) {
|
||||||
uint64_t val = 0;
|
uint64_t val = 0;
|
||||||
for (auto const value : buffer) {
|
for (auto const value : buffer) {
|
||||||
val = (val << 8) + value;
|
val = (val << 8) + value;
|
||||||
@ -119,7 +108,7 @@ uint64_t bytes_to_uint(const bytes &buffer) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t bytes_to_int(const bytes &buffer) {
|
int64_t bytes_to_int(const byte_span &buffer) {
|
||||||
uint64_t tmp = bytes_to_uint(buffer);
|
uint64_t tmp = bytes_to_uint(buffer);
|
||||||
int64_t val;
|
int64_t val;
|
||||||
|
|
||||||
@ -135,14 +124,14 @@ int64_t bytes_to_int(const bytes &buffer) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bytes_to_string(const bytes &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
std::string bytes_to_string(const byte_span &buffer) { return std::string(buffer.begin(), buffer.end()); }
|
||||||
|
|
||||||
ObisInfo::ObisInfo(bytes server_id, SmlNode val_list_entry) : server_id(std::move(server_id)) {
|
ObisInfo::ObisInfo(byte_span const &server_id, SmlNode const &val_list_entry) : server_id(server_id) {
|
||||||
this->code = val_list_entry.nodes[0].value_bytes;
|
this->code = val_list_entry.nodes[0].value_bytes;
|
||||||
this->status = val_list_entry.nodes[1].value_bytes;
|
this->status = val_list_entry.nodes[1].value_bytes;
|
||||||
this->unit = bytes_to_uint(val_list_entry.nodes[3].value_bytes);
|
this->unit = bytes_to_uint(val_list_entry.nodes[3].value_bytes);
|
||||||
this->scaler = bytes_to_int(val_list_entry.nodes[4].value_bytes);
|
this->scaler = bytes_to_int(val_list_entry.nodes[4].value_bytes);
|
||||||
SmlNode value_node = val_list_entry.nodes[5];
|
auto value_node = val_list_entry.nodes[5];
|
||||||
this->value = value_node.value_bytes;
|
this->value = value_node.value_bytes;
|
||||||
this->value_type = value_node.type;
|
this->value_type = value_node.type;
|
||||||
}
|
}
|
||||||
|
@ -5,50 +5,55 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
|
#include "span.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sml {
|
namespace sml {
|
||||||
|
|
||||||
using bytes = std::vector<uint8_t>;
|
using bytes = std::vector<uint8_t>;
|
||||||
|
using byte_span = Span<const uint8_t>;
|
||||||
|
|
||||||
class SmlNode {
|
class SmlNode {
|
||||||
public:
|
public:
|
||||||
uint8_t type;
|
SmlNode(uint8_t type, byte_span const &bytes) : type(type), value_bytes(bytes) {}
|
||||||
bytes value_bytes;
|
SmlNode(uint8_t type, std::vector<SmlNode> &&nodes) : type(type), nodes(nodes) {}
|
||||||
std::vector<SmlNode> nodes;
|
|
||||||
|
const uint8_t type;
|
||||||
|
const byte_span value_bytes;
|
||||||
|
const std::vector<SmlNode> nodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ObisInfo {
|
class ObisInfo {
|
||||||
public:
|
public:
|
||||||
ObisInfo(bytes server_id, SmlNode val_list_entry);
|
ObisInfo(byte_span const &server_id, SmlNode const &val_list_entry);
|
||||||
bytes server_id;
|
byte_span server_id;
|
||||||
bytes code;
|
byte_span code;
|
||||||
bytes status;
|
byte_span status;
|
||||||
char unit;
|
char unit;
|
||||||
char scaler;
|
char scaler;
|
||||||
bytes value;
|
byte_span value;
|
||||||
uint16_t value_type;
|
uint16_t value_type;
|
||||||
std::string code_repr() const;
|
std::string code_repr() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SmlFile {
|
class SmlFile {
|
||||||
public:
|
public:
|
||||||
SmlFile(bytes buffer);
|
SmlFile(byte_span const &buffer);
|
||||||
bool setup_node(SmlNode *node);
|
bool setup_node(std::vector<SmlNode> &nodes);
|
||||||
std::vector<SmlNode> messages;
|
std::vector<SmlNode> messages;
|
||||||
std::vector<ObisInfo> get_obis_info();
|
void for_each_obis_info(const std::function<void(const ObisInfo &)> &callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const bytes buffer_;
|
const byte_span buffer_;
|
||||||
size_t pos_;
|
size_t pos_;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string bytes_repr(const bytes &buffer);
|
std::string bytes_repr(const byte_span &buffer);
|
||||||
|
|
||||||
uint64_t bytes_to_uint(const bytes &buffer);
|
uint64_t bytes_to_uint(const byte_span &buffer);
|
||||||
|
|
||||||
int64_t bytes_to_int(const bytes &buffer);
|
int64_t bytes_to_int(const byte_span &buffer);
|
||||||
|
|
||||||
std::string bytes_to_string(const bytes &buffer);
|
std::string bytes_to_string(const byte_span &buffer);
|
||||||
} // namespace sml
|
} // namespace sml
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
31
esphome/components/sml/span.h
Normal file
31
esphome/components/sml/span.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace sml {
|
||||||
|
|
||||||
|
template<typename T> class Span {
|
||||||
|
public:
|
||||||
|
Span() : data_(nullptr), size_(0) {}
|
||||||
|
Span(T *data, size_t size) : data_(data), size_(size) {}
|
||||||
|
Span(const Span<T> &) = default;
|
||||||
|
|
||||||
|
Span &operator=(const Span<T> &) = default;
|
||||||
|
size_t size() const { return size_; }
|
||||||
|
|
||||||
|
const T &operator[](size_t index) const { return data_[index]; }
|
||||||
|
const T *begin() const { return data_; }
|
||||||
|
const T *end() const { return data_ + size_; }
|
||||||
|
|
||||||
|
T &operator[](size_t index) { return data_[index]; }
|
||||||
|
T *begin() { return data_; }
|
||||||
|
T *end() { return data_ + size_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T *data_;
|
||||||
|
size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sml
|
||||||
|
} // namespace esphome
|
Loading…
x
Reference in New Issue
Block a user