From 3ee85d7516747d946b31649de0234104f0594244 Mon Sep 17 00:00:00 2001 From: micw Date: Sun, 5 Nov 2023 22:27:11 +0100 Subject: [PATCH] Add callback for raw sml messages (#5668) --- esphome/components/sml/__init__.py | 32 ++++++++++++++++++++++++++++- esphome/components/sml/automation.h | 19 +++++++++++++++++ esphome/components/sml/constants.h | 4 +++- esphome/components/sml/sml.cpp | 20 ++++++++++++++---- esphome/components/sml/sml.h | 4 ++++ 5 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 esphome/components/sml/automation.h diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py index f3b6dd95ef..8bcfb69a45 100644 --- a/esphome/components/sml/__init__.py +++ b/esphome/components/sml/__init__.py @@ -1,9 +1,10 @@ import re +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_TRIGGER_ID CODEOWNERS = ["@alengwenus"] @@ -16,10 +17,26 @@ MULTI_CONF = True CONF_SML_ID = "sml_id" CONF_OBIS_CODE = "obis_code" CONF_SERVER_ID = "server_id" +CONF_ON_DATA = "on_data" + +sml_ns = cg.esphome_ns.namespace("sml") + +DataTrigger = sml_ns.class_( + "DataTrigger", + automation.Trigger.template( + cg.std_vector.template(cg.uint8).operator("ref"), cg.bool_ + ), +) + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Sml), + cv.Optional(CONF_ON_DATA): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DataTrigger), + } + ), } ).extend(uart.UART_DEVICE_SCHEMA) @@ -28,6 +45,19 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) + for conf in config.get(CONF_ON_DATA, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, + [ + ( + cg.std_vector.template(cg.uint8).operator("ref").operator("const"), + "bytes", + ), + (cg.bool_, "valid"), + ], + conf, + ) def obis_code(value): diff --git a/esphome/components/sml/automation.h b/esphome/components/sml/automation.h new file mode 100644 index 0000000000..d51063065d --- /dev/null +++ b/esphome/components/sml/automation.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "sml.h" + +#include + +namespace esphome { +namespace sml { + +class DataTrigger : public Trigger &, bool> { + public: + explicit DataTrigger(Sml *sml) { + sml->add_on_data_callback([this](const std::vector &data, bool valid) { this->trigger(data, valid); }); + } +}; + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/constants.h b/esphome/components/sml/constants.h index 08a124ccad..d6761d4bb7 100644 --- a/esphome/components/sml/constants.h +++ b/esphome/components/sml/constants.h @@ -18,8 +18,10 @@ enum SmlType : uint8_t { enum SmlMessageType : uint16_t { SML_PUBLIC_OPEN_RES = 0x0101, SML_GET_LIST_RES = 0x701 }; // masks with two-bit mapping 0x1b -> 0b01; 0x01 -> 0b10; 0x1a -> 0b11 -const uint16_t START_MASK = 0x55aa; // 0x1b 1b 1b 1b 1b 01 01 01 01 +const uint16_t START_MASK = 0x55aa; // 0x1b 1b 1b 1b 01 01 01 01 const uint16_t END_MASK = 0x0157; // 0x1b 1b 1b 1b 1a +const std::vector START_SEQ = {0x1b, 0x1b, 0x1b, 0x1b, 0x01, 0x01, 0x01, 0x01}; + } // namespace sml } // namespace esphome diff --git a/esphome/components/sml/sml.cpp b/esphome/components/sml/sml.cpp index 921623d4fd..bac13be923 100644 --- a/esphome/components/sml/sml.cpp +++ b/esphome/components/sml/sml.cpp @@ -35,16 +35,24 @@ void Sml::loop() { case START_BYTES_DETECTED: { this->record_ = true; this->sml_data_.clear(); + // add start sequence (for callbacks) + this->sml_data_.insert(this->sml_data_.begin(), START_SEQ.begin(), START_SEQ.end()); break; }; case END_BYTES_DETECTED: { if (this->record_) { this->record_ = false; - if (!check_sml_data(this->sml_data_)) + bool valid = check_sml_data(this->sml_data_); + + // call callbacks + this->data_callbacks_.call(this->sml_data_, valid); + + if (!valid) break; - // remove footer bytes + // remove start/end sequence + this->sml_data_.erase(this->sml_data_.begin(), this->sml_data_.begin() + START_SEQ.size()); this->sml_data_.resize(this->sml_data_.size() - 8); this->process_sml_file_(this->sml_data_); } @@ -54,6 +62,10 @@ void Sml::loop() { } } +void Sml::add_on_data_callback(std::function, bool)> &&callback) { + this->data_callbacks_.add(std::move(callback)); +} + void Sml::process_sml_file_(const bytes &sml_data) { SmlFile sml_file = SmlFile(sml_data); std::vector obis_info = sml_file.get_obis_info(); @@ -100,14 +112,14 @@ bool check_sml_data(const bytes &buffer) { } uint16_t crc_received = (buffer.at(buffer.size() - 2) << 8) | buffer.at(buffer.size() - 1); - uint16_t crc_calculated = crc16(buffer.data(), buffer.size() - 2, 0x6e23, 0x8408, true, true); + uint16_t crc_calculated = crc16(buffer.data() + 8, buffer.size() - 10, 0x6e23, 0x8408, true, true); crc_calculated = (crc_calculated >> 8) | (crc_calculated << 8); if (crc_received == crc_calculated) { ESP_LOGV(TAG, "Checksum verification successful with CRC16/X25."); return true; } - crc_calculated = crc16(buffer.data(), buffer.size() - 2, 0xed50, 0x8408); + crc_calculated = crc16(buffer.data() + 8, buffer.size() - 10, 0xed50, 0x8408); if (crc_received == crc_calculated) { ESP_LOGV(TAG, "Checksum verification successful with CRC16/KERMIT."); return true; diff --git a/esphome/components/sml/sml.h b/esphome/components/sml/sml.h index ebc8b17d7f..b0c932ca95 100644 --- a/esphome/components/sml/sml.h +++ b/esphome/components/sml/sml.h @@ -3,6 +3,7 @@ #include #include #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" #include "sml_parser.h" @@ -23,6 +24,7 @@ class Sml : public Component, public uart::UARTDevice { void loop() override; void dump_config() override; std::vector sml_listeners_{}; + void add_on_data_callback(std::function, bool)> &&callback); protected: void process_sml_file_(const bytes &sml_data); @@ -35,6 +37,8 @@ class Sml : public Component, public uart::UARTDevice { bool record_ = false; uint16_t incoming_mask_ = 0; bytes sml_data_; + + CallbackManager &, bool)> data_callbacks_{}; }; bool check_sml_data(const bytes &buffer);