Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ The `uart_id` and `victron_id` is optional if you use a single UART / victron de

The victron device pushs one status message per second. To reduce the update interval of the ESPHome entities please use the `throttle` parameter to discard some messages.

Transmission integrity is validated through a checksum. Checksum validation can be switched off with the `validate_checksum` parameter set to `false`.

## Entities

### Binary sensors
Expand Down
4 changes: 4 additions & 0 deletions components/victron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

CONF_VICTRON_ID = "victron_id"

CONF_VALIDATE_CHECKSUM = "validate_checksum"

VICTRON_COMPONENT_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_VICTRON_ID): cv.use_id(VictronComponent),
Expand All @@ -26,6 +28,7 @@
{
cv.GenerateID(): cv.declare_id(VictronComponent),
cv.Optional(CONF_THROTTLE, default="1s"): cv.positive_time_period_milliseconds,
cv.Optional(CONF_VALIDATE_CHECKSUM, default="true"): cv.boolean,
}
)

Expand All @@ -36,3 +39,4 @@ def to_code(config):
yield uart.register_uart_device(var, config)

cg.add(var.set_throttle(config[CONF_THROTTLE]))
cg.add(var.set_checksum_validation(config[CONF_VALIDATE_CHECKSUM]))
76 changes: 65 additions & 11 deletions components/victron/victron.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
#include <algorithm> // std::min
#include "esphome/core/helpers.h"

// size contraints used for overflow checks
static const uint8_t MAX_LABEL_LENGTH = 9;
static const uint8_t MAX_VALUE_LENGTH = 33;
// allow for some headroom over the 22 lines per frame according to protocol specs
static const uint8_t MAX_FIELDS_PER_BLOCK = 30;

#define MAX_BUF_SIZE ((2 + MAX_LABEL_LENGTH + 1 + MAX_VALUE_LENGTH) * MAX_FIELDS_PER_BLOCK)

namespace esphome {
namespace victron {

Expand Down Expand Up @@ -101,6 +109,7 @@ void VictronComponent::loop() {
while (available()) {
uint8_t c;
read_byte(&c);
checksum_ = (checksum_ + c) & 0xff;
if (state_ == 0) {
if (c == '\r' || c == '\n') {
continue;
Expand All @@ -118,35 +127,45 @@ void VictronComponent::loop() {
if (c == '\t') {
state_ = 2;
} else {
label_.push_back(c);
// transmission errors may impact delimiters, leading to excess label length
if (label_.length() <= MAX_LABEL_LENGTH) {
label_.push_back(c);
}
}
continue;
}
if (state_ == 2) {
if (label_ == "Checksum") {
state_ = 0;
// The checksum is used as end of frame indicator
if (now - this->last_publish_ >= this->throttle_) {
this->last_publish_ = now;
this->publishing_ = true;
} else {
this->publishing_ = false;
}
// The checksum is used as end of frame indicator, checksum_ should now be 0
publish_frame_();
frame_buffer_.clear();
checksum_ = 0;
continue;
}
if (c == '\r' || c == '\n') {
if (this->publishing_) {
handle_value_();
// a block/frame has up to 22 entries
// transmission errors could garble the end of frame indicator, leading to excess buffer length
if (frame_buffer_.size() + label_.size() + value_.size() + 3 < MAX_BUF_SIZE) {
frame_buffer_.append(label_);
frame_buffer_.append("\t");
frame_buffer_.append(value_);
frame_buffer_.append("\r\n");
}
state_ = 0;
} else {
value_.push_back(c);
// transmission errors may impact delimiters, leading to excess value length
if (value_.length() <= MAX_VALUE_LENGTH) {
value_.push_back(c);
}
}
}
// Discard ve.direct hex frame
if (state_ == 3) {
if (c == '\r' || c == '\n') {
// a hex frame ends with '\n' and has its own checksum; prepare to receive another text frame
state_ = 0;
checksum_ = 0;
}
}
}
Expand Down Expand Up @@ -713,6 +732,41 @@ static std::string off_reason_text(uint32_t mask) {
return value_list;
}

void VictronComponent::publish_frame_() {
if (checksum_ != 0) {
if (validate_checksum_) {
ESP_LOGW(TAG, "Dropping frame due to checksum error");
return;
} else {
ESP_LOGW(TAG, "Processing frame with checksum error. Consider enabling option 'validate_checksum'");
}
}

const uint32_t now = millis();
if (now - this->last_publish_ < this->throttle_) {
return;
}
this->last_publish_ = now;

size_t last = 0;
size_t next = 0;
while ((next = frame_buffer_.find("\r\n", last)) != std::string::npos) {
std::string item = frame_buffer_.substr(last, next - last);
last = next + 2;
if (item.empty()) {
continue;
}
size_t dpos = item.find('\t');
if (dpos == std::string::npos) {
continue;
}
label_ = item.substr(0, dpos);
value_ = item.substr(dpos + 1);
ESP_LOGD(TAG, "Handle %s value %s", label_.c_str(), value_.c_str());
handle_value_();
}
}

void VictronComponent::handle_value_() {
int value;

Expand Down
5 changes: 5 additions & 0 deletions components/victron/victron.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace victron {
class VictronComponent : public uart::UARTDevice, public Component {
public:
void set_throttle(uint32_t throttle) { this->throttle_ = throttle; }
void set_checksum_validation(bool state) { this->validate_checksum_ = state; }
void set_load_state_binary_sensor(binary_sensor::BinarySensor *load_state_binary_sensor) {
load_state_binary_sensor_ = load_state_binary_sensor;
}
Expand Down Expand Up @@ -209,6 +210,7 @@ class VictronComponent : public uart::UARTDevice, public Component {
float get_setup_priority() const override { return setup_priority::DATA; }

protected:
void publish_frame_();
void handle_value_();
void publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state);
void publish_state_(sensor::Sensor *sensor, float value);
Expand Down Expand Up @@ -289,11 +291,14 @@ class VictronComponent : public uart::UARTDevice, public Component {

bool publishing_{true};
int state_{0};
u_int16_t checksum_{0};
std::string label_;
std::string value_;
std::string frame_buffer_;
uint32_t last_transmission_{0};
uint32_t last_publish_{0};
uint32_t throttle_{0};
bool validate_checksum_{true};
};

} // namespace victron
Expand Down