mirror of
https://github.com/esphome/esphome.git
synced 2026-10-09 21:13:12 +00:00
[dlms_meter] Add support for "Landis+Gyr E450 HDLC Cyprus". Adapt the code for new API of dlms_parser (#16942)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
9ab0fa9669
commit
8416a29862
@@ -3,9 +3,6 @@ from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, uart
|
||||
from esphome.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from esphome.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from esphome.components.text_sensor import DOMAIN as TEXT_SENSOR_DOMAIN
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
@@ -15,6 +12,7 @@ from esphome.const import (
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@SimonFischer04", "@Tomer27cz", "@latonita", "@PolarGoose"]
|
||||
@@ -30,19 +28,17 @@ CONF_SKIP_CRC = "skip_crc"
|
||||
CONF_DEFAULT_OBIS = "default_obis"
|
||||
CONF_PROVIDER = "provider"
|
||||
|
||||
DLMS_PARSER_VERSION = "2.2.0"
|
||||
|
||||
dlms_meter_component_ns = cg.esphome_ns.namespace("dlms_meter")
|
||||
DlmsMeterComponent = dlms_meter_component_ns.class_(
|
||||
"DlmsMeterComponent", cg.Component, uart.UARTDevice
|
||||
)
|
||||
CustomPattern = dlms_meter_component_ns.struct("CustomPattern")
|
||||
ObisId = cg.global_ns.namespace("dlms_parser").class_("ObisId")
|
||||
|
||||
|
||||
def obis_code(value: Any) -> str:
|
||||
# Normalize the OBIS code to the strict A.B.C.D.E.F format
|
||||
bytes_list = parse_obis_code_bytes(value)
|
||||
return ".".join(str(b) for b in bytes_list)
|
||||
|
||||
|
||||
def parse_obis_code_bytes(value: Any) -> list[int]:
|
||||
def obis_string_to_byte_list(value: Any) -> list[int]:
|
||||
value = cv.string(value)
|
||||
normalized = re.sub(r"[\-\:\*]", ".", value)
|
||||
parts = normalized.split(".")
|
||||
@@ -60,29 +56,48 @@ def parse_obis_code_bytes(value: Any) -> list[int]:
|
||||
return bytes_list
|
||||
|
||||
|
||||
def to_obis_id_struct(value: list[int]) -> cg.Expression:
|
||||
return ObisId(*value)
|
||||
|
||||
|
||||
_request_sensor_slot = cg.slot_counter("DLMS_MAX_SENSORS")
|
||||
_request_text_sensor_slot = cg.slot_counter("DLMS_MAX_TEXT_SENSORS")
|
||||
_request_binary_sensor_slot = cg.slot_counter("DLMS_MAX_BINARY_SENSORS")
|
||||
|
||||
|
||||
def register_sensor(hub: MockObj, obis: list[int], var: MockObj) -> None:
|
||||
_request_sensor_slot(str(hub))
|
||||
cg.add(hub.register_sensor(to_obis_id_struct(obis), var))
|
||||
|
||||
|
||||
def register_text_sensor(hub: MockObj, obis: list[int], var: MockObj) -> None:
|
||||
_request_text_sensor_slot(str(hub))
|
||||
cg.add(hub.register_text_sensor(to_obis_id_struct(obis), var))
|
||||
|
||||
|
||||
def register_binary_sensor(hub: MockObj, obis: list[int], var: MockObj) -> None:
|
||||
_request_binary_sensor_slot(str(hub))
|
||||
cg.add(hub.register_binary_sensor(to_obis_id_struct(obis), var))
|
||||
|
||||
|
||||
def custom_pattern_dict(value: Any) -> ConfigType:
|
||||
if isinstance(value, str):
|
||||
return {CONF_PATTERN: value}
|
||||
return value
|
||||
|
||||
|
||||
def validate_custom_pattern(value: ConfigType) -> ConfigType:
|
||||
if CONF_DEFAULT_OBIS in value and CONF_NAME not in value:
|
||||
raise cv.Invalid(f"'{CONF_DEFAULT_OBIS}' requires '{CONF_NAME}' to be set")
|
||||
return value
|
||||
|
||||
|
||||
CUSTOM_PATTERN_SCHEMA = cv.All(
|
||||
custom_pattern_dict,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATTERN): cv.string,
|
||||
cv.Optional(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_NAME, default="CUSTOM"): cv.string,
|
||||
cv.Optional(CONF_PRIORITY, default=0): cv.int_,
|
||||
cv.Optional(CONF_DEFAULT_OBIS): parse_obis_code_bytes,
|
||||
cv.Optional(
|
||||
CONF_DEFAULT_OBIS, default="0.0.0.0.0.0"
|
||||
): obis_string_to_byte_list,
|
||||
}
|
||||
),
|
||||
validate_custom_pattern,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
@@ -120,74 +135,30 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("dlms_meter", require_
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
dec_key_expr = cg.RawExpression("std::nullopt")
|
||||
if dec_key := config.get(CONF_DECRYPTION_KEY):
|
||||
key_bytes = [str(int(dec_key[i : i + 2], 16)) for i in range(0, 32, 2)]
|
||||
dec_key_expr = cg.RawExpression(
|
||||
f"std::array<uint8_t, 16>{{{', '.join(key_bytes)}}}"
|
||||
custom_patterns = [
|
||||
cg.StructInitializer(
|
||||
CustomPattern,
|
||||
(CONF_PATTERN, pattern[CONF_PATTERN]),
|
||||
(CONF_NAME, pattern[CONF_NAME]),
|
||||
(CONF_PRIORITY, pattern[CONF_PRIORITY]),
|
||||
(CONF_DEFAULT_OBIS, to_obis_id_struct(pattern[CONF_DEFAULT_OBIS])),
|
||||
)
|
||||
|
||||
auth_key_expr = cg.RawExpression("std::nullopt")
|
||||
if auth_key := config.get(CONF_AUTH_KEY):
|
||||
key_bytes = [str(int(auth_key[i : i + 2], 16)) for i in range(0, 32, 2)]
|
||||
auth_key_expr = cg.RawExpression(
|
||||
f"std::array<uint8_t, 16>{{{', '.join(key_bytes)}}}"
|
||||
)
|
||||
|
||||
patterns = []
|
||||
if custom_patterns := config.get(CONF_CUSTOM_PATTERNS):
|
||||
for p in custom_patterns:
|
||||
name_expr = cg.RawExpression("std::nullopt")
|
||||
if name_val := p.get(CONF_NAME):
|
||||
name_expr = name_val
|
||||
|
||||
if obis_vals := p.get(CONF_DEFAULT_OBIS):
|
||||
obis_expr = cg.RawExpression(
|
||||
f"std::array<uint8_t, 6>{{{obis_vals[0]}, {obis_vals[1]}, {obis_vals[2]}, {obis_vals[3]}, {obis_vals[4]}, {obis_vals[5]}}}"
|
||||
)
|
||||
else:
|
||||
obis_expr = cg.RawExpression("std::nullopt")
|
||||
|
||||
patterns.append(
|
||||
cg.ArrayInitializer(
|
||||
p[CONF_PATTERN],
|
||||
name_expr,
|
||||
p[CONF_PRIORITY],
|
||||
obis_expr,
|
||||
)
|
||||
)
|
||||
|
||||
patterns_expr = (
|
||||
cg.ArrayInitializer(*patterns) if patterns else cg.RawExpression("{}")
|
||||
)
|
||||
for pattern in config.get(CONF_CUSTOM_PATTERNS, [])
|
||||
]
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_RECEIVE_TIMEOUT],
|
||||
config[CONF_SKIP_CRC],
|
||||
dec_key_expr,
|
||||
auth_key_expr,
|
||||
patterns_expr,
|
||||
config.get(CONF_DECRYPTION_KEY, cg.nullptr),
|
||||
config.get(CONF_AUTH_KEY, cg.nullptr),
|
||||
cg.ArrayInitializer(*custom_patterns, multiline=True),
|
||||
)
|
||||
|
||||
hub_id = config[CONF_ID].id
|
||||
|
||||
for domain, define in (
|
||||
(SENSOR_DOMAIN, "DLMS_MAX_SENSORS"),
|
||||
(TEXT_SENSOR_DOMAIN, "DLMS_MAX_TEXT_SENSORS"),
|
||||
(BINARY_SENSOR_DOMAIN, "DLMS_MAX_BINARY_SENSORS"),
|
||||
):
|
||||
count = sum(
|
||||
1
|
||||
for conf in CORE.config.get(domain, [])
|
||||
if conf.get("platform") == DOMAIN and conf[CONF_DLMS_METER_ID].id == hub_id
|
||||
)
|
||||
cg.add_define(define, count)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if CORE.is_esp32:
|
||||
esp32.add_idf_component(name="esphome/dlms_parser", ref="1.1.0")
|
||||
esp32.add_idf_component(name="esphome/dlms_parser", ref=DLMS_PARSER_VERSION)
|
||||
else:
|
||||
cg.add_library("esphome/dlms_parser", "1.1.0")
|
||||
cg.add_library("esphome/dlms_parser", DLMS_PARSER_VERSION)
|
||||
|
||||
@@ -3,14 +3,20 @@ from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_DLMS_METER_ID, CONF_OBIS_CODE, DlmsMeterComponent, obis_code
|
||||
from .. import (
|
||||
CONF_DLMS_METER_ID,
|
||||
CONF_OBIS_CODE,
|
||||
DlmsMeterComponent,
|
||||
obis_string_to_byte_list,
|
||||
register_binary_sensor,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["dlms_meter"]
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
|
||||
cv.Required(CONF_OBIS_CODE): obis_code,
|
||||
cv.Required(CONF_OBIS_CODE): obis_string_to_byte_list,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -18,4 +24,4 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
cg.add(hub.register_binary_sensor(config[CONF_OBIS_CODE], var))
|
||||
register_binary_sensor(hub, config[CONF_OBIS_CODE], var)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Ignore Zephyr. It doesn't have any encryption library.
|
||||
#if defined(USE_ESP32) || defined(USE_ARDUINO) || defined(USE_HOST)
|
||||
|
||||
#include "dlms_meter.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome::dlms_meter {
|
||||
@@ -31,54 +36,40 @@ static void log_callback(dlms_parser::LogLevel level, const char *fmt, va_list a
|
||||
}
|
||||
}
|
||||
|
||||
DlmsMeterComponent::DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check,
|
||||
std::optional<std::array<uint8_t, 16>> decryption_key,
|
||||
std::optional<std::array<uint8_t, 16>> authentication_key,
|
||||
DlmsMeterComponent::DlmsMeterComponent(const uint32_t receive_timeout_ms, const bool skip_crc_check,
|
||||
const char *decryption_key, const char *authentication_key,
|
||||
std::vector<CustomPattern> custom_patterns)
|
||||
: receive_timeout_ms_(receive_timeout_ms),
|
||||
skip_crc_check_(skip_crc_check),
|
||||
custom_patterns_(std::move(custom_patterns)),
|
||||
parser_(&decryptor_) {
|
||||
parser_([this](const dlms_parser::AxdrCapture &capture) { this->on_data_(capture); }, &this->decryptor_) {
|
||||
dlms_parser::Logger::set_log_function(log_callback);
|
||||
|
||||
if (decryption_key.has_value()) {
|
||||
#ifdef DLMS_METER_NO_CRYPTO
|
||||
ESP_LOGE(TAG, "Decryption is not supported on this platform (no compatible crypto library found)");
|
||||
#else
|
||||
auto opt_key = dlms_parser::Aes128GcmDecryptionKey::from_bytes(decryption_key.value());
|
||||
if (opt_key) {
|
||||
this->parser_.set_decryption_key(*opt_key);
|
||||
if (decryption_key != nullptr) {
|
||||
const auto key = dlms_parser::Aes128GcmDecryptionKey::from_hex(decryption_key);
|
||||
if (key.has_value()) {
|
||||
this->parser_.set_decryption_key(*key);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set decryption key: invalid key format");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (authentication_key.has_value()) {
|
||||
#ifdef DLMS_METER_NO_CRYPTO
|
||||
ESP_LOGE(TAG, "Authentication is not supported on this platform (no compatible crypto library found)");
|
||||
#else
|
||||
auto opt_key = dlms_parser::Aes128GcmAuthenticationKey::from_bytes(authentication_key.value());
|
||||
if (opt_key) {
|
||||
this->parser_.set_authentication_key(*opt_key);
|
||||
if (authentication_key != nullptr) {
|
||||
const auto key = dlms_parser::Aes128GcmAuthenticationKey::from_hex(authentication_key);
|
||||
if (key.has_value()) {
|
||||
this->parser_.set_authentication_key(*key);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set authentication key: invalid key format");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
this->parser_.set_skip_crc_check(this->skip_crc_check_);
|
||||
|
||||
this->parser_.load_default_patterns();
|
||||
for (const auto &pattern : this->custom_patterns_) {
|
||||
if (pattern.default_obis.has_value() && pattern.name.has_value()) {
|
||||
this->parser_.register_pattern(pattern.name->c_str(), pattern.pattern.c_str(), pattern.priority,
|
||||
pattern.default_obis.value());
|
||||
} else if (pattern.name.has_value()) {
|
||||
this->parser_.register_pattern(pattern.name->c_str(), pattern.pattern.c_str(), pattern.priority);
|
||||
} else {
|
||||
this->parser_.register_pattern(pattern.pattern.c_str());
|
||||
}
|
||||
|
||||
for (const auto &custom_pattern : this->custom_patterns_) {
|
||||
this->parser_.register_pattern(custom_pattern.name, custom_pattern.pattern, custom_pattern.priority,
|
||||
custom_pattern.default_obis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,36 +80,36 @@ void DlmsMeterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Receive Timeout: %" PRIu32 " ms", this->receive_timeout_ms_);
|
||||
ESP_LOGCONFIG(TAG, " Skip CRC Check: %s", YESNO(this->skip_crc_check_));
|
||||
|
||||
for (const auto &pattern : this->custom_patterns_) {
|
||||
if (pattern.default_obis.has_value() && pattern.name.has_value()) {
|
||||
const auto &obis = pattern.default_obis.value();
|
||||
ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d, default_obis: %d.%d.%d.%d.%d.%d)",
|
||||
pattern.pattern.c_str(), pattern.name->c_str(), pattern.priority, obis[0], obis[1], obis[2],
|
||||
obis[3], obis[4], obis[5]);
|
||||
} else if (pattern.name.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d)", pattern.pattern.c_str(),
|
||||
pattern.name->c_str(), pattern.priority);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Custom Pattern: '%s'", pattern.pattern.c_str());
|
||||
}
|
||||
std::array<char, 24> obis_buf;
|
||||
|
||||
for (const auto &custom_pattern : this->custom_patterns_) {
|
||||
const auto default_obis = custom_pattern.default_obis.to_string(obis_buf);
|
||||
ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d, default_obis: %.*s)", custom_pattern.pattern,
|
||||
custom_pattern.name, custom_pattern.priority, static_cast<int>(default_obis.size()),
|
||||
default_obis.data());
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
for (const auto &entry : this->sensors_) {
|
||||
LOG_SENSOR(" ", "Numeric Sensor (OBIS)", entry.sensor);
|
||||
ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str());
|
||||
for (const auto &item : this->sensors_) {
|
||||
LOG_SENSOR(" ", "Numeric Sensor (OBIS)", item.sensor);
|
||||
const auto obis = item.obis.to_string(obis_buf);
|
||||
ESP_LOGCONFIG(TAG, " OBIS: %.*s", static_cast<int>(obis.size()), obis.data());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
for (const auto &entry : this->text_sensors_) {
|
||||
LOG_TEXT_SENSOR(" ", "Text Sensor (OBIS)", entry.sensor);
|
||||
ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str());
|
||||
for (const auto &item : this->text_sensors_) {
|
||||
LOG_TEXT_SENSOR(" ", "Text Sensor (OBIS)", item.sensor);
|
||||
const auto obis = item.obis.to_string(obis_buf);
|
||||
ESP_LOGCONFIG(TAG, " OBIS: %.*s", static_cast<int>(obis.size()), obis.data());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (const auto &entry : this->binary_sensors_) {
|
||||
LOG_BINARY_SENSOR(" ", "Binary Sensor (OBIS)", entry.sensor);
|
||||
ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str());
|
||||
for (const auto &item : this->binary_sensors_) {
|
||||
LOG_BINARY_SENSOR(" ", "Binary Sensor (OBIS)", item.sensor);
|
||||
const auto obis = item.obis.to_string(obis_buf);
|
||||
ESP_LOGCONFIG(TAG, " OBIS: %.*s", static_cast<int>(obis.size()), obis.data());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -166,71 +157,73 @@ void DlmsMeterComponent::read_rx_buffer_() {
|
||||
void DlmsMeterComponent::process_frame_() {
|
||||
ESP_LOGV(TAG, "Processing frame of size: %zu bytes", this->bytes_accumulated_);
|
||||
|
||||
auto callback = [this](const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
|
||||
this->on_data_(obis_code, float_val, str_val, is_numeric);
|
||||
};
|
||||
|
||||
this->parser_.parse({this->rx_buffer_.data(), this->bytes_accumulated_}, callback);
|
||||
this->parser_.parse({this->rx_buffer_.data(), this->bytes_accumulated_});
|
||||
|
||||
this->bytes_accumulated_ = 0;
|
||||
}
|
||||
|
||||
void DlmsMeterComponent::on_data_(const char *obis_code, float float_val, const char *str_val, bool is_numeric) {
|
||||
void DlmsMeterComponent::on_data_(const dlms_parser::AxdrCapture &capture) {
|
||||
int updated_count = 0;
|
||||
|
||||
if (capture.is_numeric()) {
|
||||
#if defined(USE_SENSOR) || defined(USE_BINARY_SENSOR)
|
||||
const float value = capture.value_as_float_with_scaler_applied();
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (is_numeric) {
|
||||
for (auto &item : this->sensors_) {
|
||||
if (item.obis_code == obis_code) {
|
||||
item.sensor->publish_state(float_val);
|
||||
for (const auto &item : this->sensors_) {
|
||||
if (item.obis == capture.obis) {
|
||||
item.sensor->publish_state(value);
|
||||
updated_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (!is_numeric && str_val != nullptr) {
|
||||
for (auto &item : this->text_sensors_) {
|
||||
if (item.obis_code == obis_code) {
|
||||
item.sensor->publish_state(str_val);
|
||||
updated_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (is_numeric) {
|
||||
bool state = float_val != 0.0f;
|
||||
for (auto &item : this->binary_sensors_) {
|
||||
if (item.obis_code == obis_code) {
|
||||
item.sensor->publish_state(state);
|
||||
for (const auto &item : this->binary_sensors_) {
|
||||
if (item.obis == capture.obis) {
|
||||
item.sensor->publish_state(value != 0.0f);
|
||||
updated_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
for (const auto &item : this->text_sensors_) {
|
||||
if (item.obis == capture.obis) {
|
||||
std::array<char, 128> value_buf;
|
||||
const auto value = capture.value_as_string(value_buf);
|
||||
item.sensor->publish_state(value.data(), value.size());
|
||||
updated_count++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (updated_count == 0) {
|
||||
ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code);
|
||||
std::array<char, 24> obis_buf;
|
||||
const auto obis = capture.obis.to_string(obis_buf);
|
||||
ESP_LOGV(TAG, "Received OBIS %.*s, but no sensors are registered for it.", static_cast<int>(obis.size()),
|
||||
obis.data());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void DlmsMeterComponent::register_sensor(const std::string &obis_code, sensor::Sensor *sensor) {
|
||||
this->sensors_.push_back({obis_code, sensor});
|
||||
void DlmsMeterComponent::register_sensor(const dlms_parser::ObisId &obis, sensor::Sensor *sensor) {
|
||||
this->sensors_.push_back({obis, sensor});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void DlmsMeterComponent::register_text_sensor(const std::string &obis_code, text_sensor::TextSensor *sensor) {
|
||||
this->text_sensors_.push_back({obis_code, sensor});
|
||||
void DlmsMeterComponent::register_text_sensor(const dlms_parser::ObisId &obis, text_sensor::TextSensor *sensor) {
|
||||
this->text_sensors_.push_back({obis, sensor});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void DlmsMeterComponent::register_binary_sensor(const std::string &obis_code, binary_sensor::BinarySensor *sensor) {
|
||||
this->binary_sensors_.push_back({obis_code, sensor});
|
||||
void DlmsMeterComponent::register_binary_sensor(const dlms_parser::ObisId &obis, binary_sensor::BinarySensor *sensor) {
|
||||
this->binary_sensors_.push_back({obis, sensor});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::dlms_meter
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
// Ignore Zephyr. It doesn't have any encryption library.
|
||||
#if defined(USE_ESP32) || defined(USE_ARDUINO) || defined(USE_HOST)
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
@@ -19,12 +23,6 @@
|
||||
|
||||
#include <dlms_parser/dlms_parser.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#if __has_include(<psa/crypto.h>)
|
||||
#include <dlms_parser/decryption/aes_128_gcm_decryptor_tfpsa.h>
|
||||
#elif !defined(USE_ESP8266) && __has_include(<mbedtls/gcm.h>)
|
||||
@@ -35,9 +33,12 @@
|
||||
#elif __has_include(<bearssl/bearssl.h>)
|
||||
#include <dlms_parser/decryption/aes_128_gcm_decryptor_bearssl.h>
|
||||
#else
|
||||
#define DLMS_METER_NO_CRYPTO
|
||||
#error "The platform doesn't provide a compatible encryption library for dlms_parser"
|
||||
#endif
|
||||
|
||||
namespace esphome::dlms_meter {
|
||||
|
||||
// Entity storage is sized by codegen; a platform with no entities emits no define
|
||||
#ifndef DLMS_MAX_SENSORS
|
||||
static constexpr uint8_t DLMS_MAX_SENSORS = 0;
|
||||
#endif
|
||||
@@ -48,82 +49,64 @@ static constexpr uint8_t DLMS_MAX_TEXT_SENSORS = 0;
|
||||
static constexpr uint8_t DLMS_MAX_BINARY_SENSORS = 0;
|
||||
#endif
|
||||
|
||||
namespace esphome::dlms_meter {
|
||||
|
||||
#ifdef DLMS_METER_NO_CRYPTO
|
||||
// Fallback dummy decryptor for platforms without supported crypto (e.g., Zephyr during clang-tidy)
|
||||
class Aes128GcmDecryptorDummy : public dlms_parser::Aes128GcmDecryptor {
|
||||
public:
|
||||
void set_decryption_key(const dlms_parser::Aes128GcmDecryptionKey &key) override {}
|
||||
bool decrypt_in_place(std::span<const uint8_t> iv, std::span<uint8_t> ciphertext_and_plaintext,
|
||||
std::span<const uint8_t> aad, std::span<const uint8_t> tag) override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if __has_include(<psa/crypto.h>)
|
||||
using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorTfPsa;
|
||||
#elif !defined(USE_ESP8266) && __has_include(<mbedtls/gcm.h>)
|
||||
using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorMbedTls;
|
||||
#elif __has_include(<bearssl/bearssl.h>)
|
||||
using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorBearSsl;
|
||||
#else
|
||||
using Aes128GcmDecryptorImpl = Aes128GcmDecryptorDummy;
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
struct SensorItem {
|
||||
std::string obis_code;
|
||||
dlms_parser::ObisId obis;
|
||||
sensor::Sensor *sensor;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
struct TextSensorItem {
|
||||
std::string obis_code;
|
||||
dlms_parser::ObisId obis;
|
||||
text_sensor::TextSensor *sensor;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct BinarySensorItem {
|
||||
std::string obis_code;
|
||||
dlms_parser::ObisId obis;
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct CustomPattern {
|
||||
std::string pattern;
|
||||
std::optional<std::string> name;
|
||||
int priority{0};
|
||||
std::optional<std::array<uint8_t, 6>> default_obis;
|
||||
const char *pattern;
|
||||
const char *name;
|
||||
int priority;
|
||||
dlms_parser::ObisId default_obis;
|
||||
};
|
||||
|
||||
#if __has_include(<psa/crypto.h>)
|
||||
using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorTfPsa;
|
||||
#elif !defined(USE_ESP8266) && __has_include(<mbedtls/gcm.h>)
|
||||
using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorMbedTls;
|
||||
#else
|
||||
using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorBearSsl;
|
||||
#endif
|
||||
|
||||
class DlmsMeterComponent final : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check,
|
||||
std::optional<std::array<uint8_t, 16>> decryption_key,
|
||||
std::optional<std::array<uint8_t, 16>> authentication_key,
|
||||
std::vector<CustomPattern> custom_patterns);
|
||||
DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check, const char *decryption_key,
|
||||
const char *authentication_key, std::vector<CustomPattern> custom_patterns);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void register_sensor(const std::string &obis_code, sensor::Sensor *sensor);
|
||||
void register_sensor(const dlms_parser::ObisId &obis, sensor::Sensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void register_text_sensor(const std::string &obis_code, text_sensor::TextSensor *sensor);
|
||||
void register_text_sensor(const dlms_parser::ObisId &obis, text_sensor::TextSensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void register_binary_sensor(const std::string &obis_code, binary_sensor::BinarySensor *sensor);
|
||||
void register_binary_sensor(const dlms_parser::ObisId &obis, binary_sensor::BinarySensor *sensor);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void read_rx_buffer_();
|
||||
void flush_rx_buffer_();
|
||||
void process_frame_();
|
||||
void on_data_(const char *obis_code, float float_val, const char *str_val, bool is_numeric);
|
||||
void on_data_(const dlms_parser::AxdrCapture &capture);
|
||||
|
||||
std::array<uint8_t, 2048> rx_buffer_;
|
||||
size_t bytes_accumulated_{0};
|
||||
@@ -149,3 +132,5 @@ class DlmsMeterComponent final : public Component, public uart::UARTDevice {
|
||||
};
|
||||
|
||||
} // namespace esphome::dlms_meter
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,7 +3,13 @@ from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_DLMS_METER_ID, CONF_OBIS_CODE, DlmsMeterComponent, obis_code
|
||||
from .. import (
|
||||
CONF_DLMS_METER_ID,
|
||||
CONF_OBIS_CODE,
|
||||
DlmsMeterComponent,
|
||||
obis_string_to_byte_list,
|
||||
register_sensor,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["dlms_meter"]
|
||||
|
||||
@@ -40,7 +46,7 @@ REMOVED_KEYS = {
|
||||
CONFIG_SCHEMA = sensor.sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
|
||||
cv.Required(CONF_OBIS_CODE): obis_code,
|
||||
cv.Required(CONF_OBIS_CODE): obis_string_to_byte_list,
|
||||
**{
|
||||
cv.Optional(key): cv.invalid(
|
||||
f"The predefined '{key}' key was removed in ESPHome 2026.11.0. "
|
||||
@@ -56,4 +62,4 @@ CONFIG_SCHEMA = sensor.sensor_schema().extend(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
|
||||
var = await sensor.new_sensor(config)
|
||||
cg.add(hub.register_sensor(config[CONF_OBIS_CODE], var))
|
||||
register_sensor(hub, config[CONF_OBIS_CODE], var)
|
||||
|
||||
@@ -3,7 +3,13 @@ from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_DLMS_METER_ID, CONF_OBIS_CODE, DlmsMeterComponent, obis_code
|
||||
from .. import (
|
||||
CONF_DLMS_METER_ID,
|
||||
CONF_OBIS_CODE,
|
||||
DlmsMeterComponent,
|
||||
obis_string_to_byte_list,
|
||||
register_text_sensor,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["dlms_meter"]
|
||||
|
||||
@@ -18,7 +24,7 @@ REMOVED_KEYS = {
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
|
||||
cv.Required(CONF_OBIS_CODE): obis_code,
|
||||
cv.Required(CONF_OBIS_CODE): obis_string_to_byte_list,
|
||||
**{
|
||||
cv.Optional(key): cv.invalid(
|
||||
f"The predefined '{key}' key was removed in ESPHome 2026.11.0. "
|
||||
@@ -34,4 +40,4 @@ CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
cg.add(hub.register_text_sensor(config[CONF_OBIS_CODE], var))
|
||||
register_text_sensor(hub, config[CONF_OBIS_CODE], var)
|
||||
|
||||
@@ -2,7 +2,7 @@ dependencies:
|
||||
bblanchon/arduinojson:
|
||||
version: "7.4.3"
|
||||
esphome/dlms_parser:
|
||||
version: 1.1.0
|
||||
version: 2.2.0
|
||||
esphome/esp-audio-libs:
|
||||
version: 4.0.1
|
||||
esphome/esp-micro-speech-features:
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ build_unflags =
|
||||
; esp32 envs (which would double-include them).
|
||||
[common:idf-component-libs]
|
||||
lib_deps =
|
||||
esphome/dlms_parser@1.1.0 ; dlms_meter
|
||||
esphome/dlms_parser@2.2.0 ; dlms_meter
|
||||
bblanchon/ArduinoJson@7.4.3 ; json
|
||||
lvgl/lvgl@9.5.0 ; lvgl
|
||||
|
||||
|
||||
Reference in New Issue
Block a user