1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-31 07:03:55 +00:00

Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston
2025-10-27 19:15:28 -05:00
17 changed files with 344 additions and 87 deletions

View File

@@ -62,6 +62,7 @@ from esphome.cpp_types import ( # noqa: F401
EntityBase,
EntityCategory,
ESPTime,
FixedVector,
GPIOPin,
InternalGPIOPin,
JsonObject,

View File

@@ -71,10 +71,12 @@ SERVICE_ARG_NATIVE_TYPES = {
"int": cg.int32,
"float": float,
"string": cg.std_string,
"bool[]": cg.std_vector.template(bool),
"int[]": cg.std_vector.template(cg.int32),
"float[]": cg.std_vector.template(float),
"string[]": cg.std_vector.template(cg.std_string),
"bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
"float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
"string[]": cg.FixedVector.template(cg.std_string)
.operator("const")
.operator("ref"),
}
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"

View File

@@ -11,23 +11,58 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
}
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end());
std::vector<bool> result;
result.reserve(arg.bool_array.size());
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
return result;
}
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end());
std::vector<int32_t> result;
result.reserve(arg.int_array.size());
result.insert(result.end(), arg.int_array.begin(), arg.int_array.end());
return result;
}
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
return std::vector<float>(arg.float_array.begin(), arg.float_array.end());
std::vector<float> result;
result.reserve(arg.float_array.size());
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
return result;
}
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end());
std::vector<std::string> result;
result.reserve(arg.string_array.size());
result.insert(result.end(), arg.string_array.begin(), arg.string_array.end());
return result;
}
// New FixedVector const reference versions for YAML-generated services - zero-copy
template<>
const FixedVector<bool> &get_execute_arg_value<const FixedVector<bool> &>(const ExecuteServiceArgument &arg) {
return arg.bool_array;
}
template<>
const FixedVector<int32_t> &get_execute_arg_value<const FixedVector<int32_t> &>(const ExecuteServiceArgument &arg) {
return arg.int_array;
}
template<>
const FixedVector<float> &get_execute_arg_value<const FixedVector<float> &>(const ExecuteServiceArgument &arg) {
return arg.float_array;
}
template<>
const FixedVector<std::string> &get_execute_arg_value<const FixedVector<std::string> &>(
const ExecuteServiceArgument &arg) {
return arg.string_array;
}
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
// Legacy std::vector versions for external components using custom_api_device.h
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
@@ -39,4 +74,18 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
}
// New FixedVector const reference versions for YAML-generated services
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<bool> &>() {
return enums::SERVICE_ARG_TYPE_BOOL_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<int32_t> &>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<float> &>() {
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<std::string> &>() {
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
}
} // namespace esphome::api

View File

@@ -33,8 +33,8 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor,
void dump_config() override;
using transform_func_t = std::function<optional<bool>(ModbusBinarySensor *, bool, const std::vector<uint8_t> &)>;
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
using transform_func_t = optional<bool> (*)(ModbusBinarySensor *, bool, const std::vector<uint8_t> &);
void set_template(transform_func_t f) { this->transform_func_ = f; }
protected:
optional<transform_func_t> transform_func_{nullopt};

View File

@@ -31,10 +31,10 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
void set_parent(ModbusController *parent) { this->parent_ = parent; }
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>;
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
using transform_func_t = optional<float> (*)(ModbusNumber *, float, const std::vector<uint8_t> &);
using write_transform_func_t = optional<float> (*)(ModbusNumber *, float, std::vector<uint16_t> &);
void set_template(transform_func_t f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:

View File

@@ -29,8 +29,8 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
// Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{};
using write_transform_func_t = std::function<optional<float>(ModbusFloatOutput *, float, std::vector<uint16_t> &)>;
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
using write_transform_func_t = optional<float> (*)(ModbusFloatOutput *, float, std::vector<uint16_t> &);
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:
@@ -60,8 +60,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
// Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{};
using write_transform_func_t = std::function<optional<bool>(ModbusBinaryOutput *, bool, std::vector<uint8_t> &)>;
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
using write_transform_func_t = optional<bool> (*)(ModbusBinaryOutput *, bool, std::vector<uint8_t> &);
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:

View File

@@ -26,16 +26,15 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
this->mapping_ = std::move(mapping);
}
using transform_func_t =
std::function<optional<std::string>(ModbusSelect *const, int64_t, const std::vector<uint8_t> &)>;
using write_transform_func_t =
std::function<optional<int64_t>(ModbusSelect *const, const std::string &, int64_t, std::vector<uint16_t> &)>;
using transform_func_t = optional<std::string> (*)(ModbusSelect *const, int64_t, const std::vector<uint8_t> &);
using write_transform_func_t = optional<int64_t> (*)(ModbusSelect *const, const std::string &, int64_t,
std::vector<uint16_t> &);
void set_parent(ModbusController *const parent) { this->parent_ = parent; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
void set_template(transform_func_t f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override;

View File

@@ -25,9 +25,9 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override;
void dump_config() override;
using transform_func_t = std::function<optional<float>(ModbusSensor *, float, const std::vector<uint8_t> &)>;
using transform_func_t = optional<float> (*)(ModbusSensor *, float, const std::vector<uint8_t> &);
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
void set_template(transform_func_t f) { this->transform_func_ = f; }
protected:
optional<transform_func_t> transform_func_{nullopt};

View File

@@ -34,10 +34,10 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override;
void set_parent(ModbusController *parent) { this->parent_ = parent; }
using transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, const std::vector<uint8_t> &)>;
using write_transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, std::vector<uint8_t> &)>;
void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
using transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, const std::vector<uint8_t> &);
using write_transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, std::vector<uint8_t> &);
void set_template(transform_func_t f) { this->publish_transform_func_ = f; }
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:

View File

@@ -30,9 +30,8 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi
void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override;
using transform_func_t =
std::function<optional<std::string>(ModbusTextSensor *, std::string, const std::vector<uint8_t> &)>;
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
using transform_func_t = optional<std::string> (*)(ModbusTextSensor *, std::string, const std::vector<uint8_t> &);
void set_template(transform_func_t f) { this->transform_func_ = f; }
protected:
optional<transform_func_t> transform_func_{nullopt};

View File

@@ -1,4 +1,6 @@
import logging
from re import Match
from typing import Any
from esphome import core
from esphome.config_helpers import Extend, Remove, merge_config, merge_dicts_ordered
@@ -39,7 +41,34 @@ async def to_code(config):
pass
def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
def _restore_data_base(value: Any, orig_value: ESPHomeDataBase) -> ESPHomeDataBase:
"""This function restores ESPHomeDataBase metadata held by the original string.
This is needed because during jinja evaluation, strings can be replaced by other types,
but we want to keep the original metadata for error reporting and source mapping.
For example, if a substitution replaces a string with a dictionary, we want that items
in the dictionary to still point to the original document location
"""
if isinstance(value, ESPHomeDataBase):
return value
if isinstance(value, dict):
return {
_restore_data_base(k, orig_value): _restore_data_base(v, orig_value)
for k, v in value.items()
}
if isinstance(value, list):
return [_restore_data_base(v, orig_value) for v in value]
if isinstance(value, str):
return make_data_base(value, orig_value)
return value
def _expand_jinja(
value: str | JinjaStr,
orig_value: str | JinjaStr,
path,
jinja: Jinja,
ignore_missing: bool,
) -> Any:
if has_jinja(value):
# If the original value passed in to this function is a JinjaStr, it means it contains an unresolved
# Jinja expression from a previous pass.
@@ -65,10 +94,17 @@ def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
f"\nSee {'->'.join(str(x) for x in path)}",
path,
)
# If the original, unexpanded string, contained document metadata (ESPHomeDatabase),
# assign this same document metadata to the resulting value.
if isinstance(orig_value, ESPHomeDataBase):
value = _restore_data_base(value, orig_value)
return value
def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
def _expand_substitutions(
substitutions: dict, value: str, path, jinja: Jinja, ignore_missing: bool
) -> Any:
if "$" not in value:
return value
@@ -76,14 +112,14 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
i = 0
while True:
m = cv.VARIABLE_PROG.search(value, i)
m: Match[str] = cv.VARIABLE_PROG.search(value, i)
if not m:
# No more variable substitutions found. See if the remainder looks like a jinja template
value = _expand_jinja(value, orig_value, path, jinja, ignore_missing)
break
i, j = m.span(0)
name = m.group(1)
name: str = m.group(1)
if name.startswith("{") and name.endswith("}"):
name = name[1:-1]
if name not in substitutions:
@@ -98,7 +134,7 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
i = j
continue
sub = substitutions[name]
sub: Any = substitutions[name]
if i == 0 and j == len(value):
# The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly
@@ -121,7 +157,13 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
return value
def _substitute_item(substitutions, item, path, jinja, ignore_missing):
def _substitute_item(
substitutions: dict,
item: Any,
path: list[int | str],
jinja: Jinja,
ignore_missing: bool,
) -> Any | None:
if isinstance(item, ESPLiteralValue):
return None # do not substitute inside literal blocks
if isinstance(item, list):
@@ -160,7 +202,9 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing):
return None
def do_substitution_pass(config, command_line_substitutions, ignore_missing=False):
def do_substitution_pass(
config: dict, command_line_substitutions: dict, ignore_missing: bool = False
) -> None:
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
return

View File

@@ -1,10 +1,14 @@
from ast import literal_eval
from collections.abc import Iterator
from itertools import chain, islice
import logging
import math
import re
from types import GeneratorType
from typing import Any
import jinja2 as jinja
from jinja2.sandbox import SandboxedEnvironment
from jinja2.nativetypes import NativeCodeGenerator, NativeTemplate
from esphome.yaml_util import ESPLiteralValue
@@ -24,7 +28,7 @@ detect_jinja_re = re.compile(
)
def has_jinja(st):
def has_jinja(st: str) -> bool:
return detect_jinja_re.search(st) is not None
@@ -109,12 +113,56 @@ class TrackerContext(jinja.runtime.Context):
return val
class Jinja(SandboxedEnvironment):
def _concat_nodes_override(values: Iterator[Any]) -> Any:
"""
This function customizes how Jinja preserves native types when concatenating
multiple result nodes together. If the result is a single node, its value
is returned. Otherwise, the nodes are concatenated as strings. If
the result can be parsed with `ast.literal_eval`, the parsed
value is returned. Otherwise, the string is returned.
This helps preserve metadata such as ESPHomeDataBase from original values
and mimicks how HomeAssistant deals with template evaluation and preserving
the original datatype.
"""
head: list[Any] = list(islice(values, 2))
if not head:
return None
if len(head) == 1:
raw = head[0]
if not isinstance(raw, str):
return raw
else:
if isinstance(values, GeneratorType):
values = chain(head, values)
raw = "".join([str(v) for v in values])
try:
# Attempt to parse the concatenated string into a Python literal.
# This allows expressions like "1 + 2" to be evaluated to the integer 3.
# If the result is also a string or there is a parsing error,
# fall back to returning the raw string. This is consistent with
# Home Assistant's behavior when evaluating templates
result = literal_eval(raw)
if not isinstance(result, str):
return result
except (ValueError, SyntaxError, MemoryError, TypeError):
pass
return raw
class Jinja(jinja.Environment):
"""
Wraps a Jinja environment
"""
def __init__(self, context_vars):
# jinja environment customization overrides
code_generator_class = NativeCodeGenerator
concat = staticmethod(_concat_nodes_override)
def __init__(self, context_vars: dict):
super().__init__(
trim_blocks=True,
lstrip_blocks=True,
@@ -142,19 +190,10 @@ class Jinja(SandboxedEnvironment):
**SAFE_GLOBALS,
}
def safe_eval(self, expr):
try:
result = literal_eval(expr)
if not isinstance(result, str):
return result
except (ValueError, SyntaxError, MemoryError, TypeError):
pass
return expr
def expand(self, content_str):
def expand(self, content_str: str | JinjaStr) -> Any:
"""
Renders a string that may contain Jinja expressions or statements
Returns the resulting processed string if all values could be resolved.
Returns the resulting value if all variables and expressions could be resolved.
Otherwise, it returns a tagged (JinjaStr) string that captures variables
in scope (upvalues), like a closure for later evaluation.
"""
@@ -172,7 +211,7 @@ class Jinja(SandboxedEnvironment):
self.context_trace = {}
try:
template = self.from_string(content_str)
result = self.safe_eval(template.render(override_vars))
result = template.render(override_vars)
if isinstance(result, Undefined):
print("" + result) # force a UndefinedError exception
except (TemplateSyntaxError, UndefinedError) as err:
@@ -201,3 +240,10 @@ class Jinja(SandboxedEnvironment):
content_str.result = result
return result, None
class JinjaTemplate(NativeTemplate):
environment_class = Jinja
Jinja.template_class = JinjaTemplate

View File

@@ -99,10 +99,26 @@ void IDFUARTComponent::setup() {
}
void IDFUARTComponent::load_settings(bool dump_config) {
uart_config_t uart_config = this->get_config_();
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
esp_err_t err;
if (uart_is_driver_installed(this->uart_num_)) {
err = uart_driver_delete(this->uart_num_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
}
err = uart_driver_install(this->uart_num_, // UART number
this->rx_buffer_size_, // RX ring buffer size
0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
// block task until all data has been sent out
20, // event queue size/depth
&this->uart_event_queue_, // event queue
0 // Flags used to allocate the interrupt
);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
@@ -119,10 +135,12 @@ void IDFUARTComponent::load_settings(bool dump_config) {
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
uint32_t invert = 0;
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
invert |= UART_SIGNAL_TXD_INV;
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
}
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
invert |= UART_SIGNAL_RXD_INV;
}
err = uart_set_line_inverse(this->uart_num_, invert);
if (err != ESP_OK) {
@@ -138,26 +156,6 @@ void IDFUARTComponent::load_settings(bool dump_config) {
return;
}
if (uart_is_driver_installed(this->uart_num_)) {
uart_driver_delete(this->uart_num_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
}
err = uart_driver_install(this->uart_num_, /* UART RX ring buffer size. */ this->rx_buffer_size_,
/* UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will
block task until all data have been sent out.*/
0,
/* UART event queue size/depth. */ 20, &(this->uart_event_queue_),
/* Flags used to allocate the interrupt. */ 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
@@ -173,24 +171,32 @@ void IDFUARTComponent::load_settings(bool dump_config) {
}
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
err = uart_set_mode(this->uart_num_, mode);
err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install()
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
uart_config_t uart_config = this->get_config_();
err = uart_param_config(this->uart_num_, &uart_config);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
if (dump_config) {
ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_);
ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
this->dump_config();
}
}
void IDFUARTComponent::dump_config() {
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
LOG_PIN(" TX Pin: ", tx_pin_);
LOG_PIN(" RX Pin: ", rx_pin_);
LOG_PIN(" Flow Control Pin: ", flow_control_pin_);
LOG_PIN(" TX Pin: ", this->tx_pin_);
LOG_PIN(" RX Pin: ", this->rx_pin_);
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
if (this->rx_pin_ != nullptr) {
ESP_LOGCONFIG(TAG,
" RX Buffer Size: %u\n"

View File

@@ -23,6 +23,7 @@ size_t = global_ns.namespace("size_t")
const_char_ptr = global_ns.namespace("const char *")
NAN = global_ns.namespace("NAN")
esphome_ns = global_ns # using namespace esphome;
FixedVector = esphome_ns.class_("FixedVector")
App = esphome_ns.App
EntityBase = esphome_ns.class_("EntityBase")
Component = esphome_ns.class_("Component")

View File

@@ -120,7 +120,7 @@ def prepare(
cert_file.flush()
key_file.write(config[CONF_MQTT].get(CONF_CLIENT_CERTIFICATE_KEY))
key_file.flush()
context.load_cert_chain(cert_file, key_file)
context.load_cert_chain(cert_file.name, key_file.name)
client.tls_set_context(context)
try:

View File

@@ -56,6 +56,14 @@ binary_sensor:
register_type: read
address: 0x3200
bitmask: 0x80
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_binary_sensor2
name: Test Binary Sensor with Lambda
register_type: read
address: 0x3201
lambda: |-
return x;
number:
- platform: modbus_controller
@@ -65,6 +73,16 @@ number:
address: 0x9001
value_type: U_WORD
multiply: 1.0
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_number2
name: Test Number with Lambda
address: 0x9002
value_type: U_WORD
lambda: |-
return x * 2.0;
write_lambda: |-
return x / 2.0;
output:
- platform: modbus_controller
@@ -74,6 +92,14 @@ output:
register_type: holding
value_type: U_WORD
multiply: 1000
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_output2
address: 2049
register_type: holding
value_type: U_WORD
write_lambda: |-
return x * 100.0;
select:
- platform: modbus_controller
@@ -87,6 +113,34 @@ select:
"One": 1
"Two": 2
"Three": 3
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_select2
name: Test Select with Lambda
address: 1001
value_type: U_WORD
optionsmap:
"Off": 0
"On": 1
"Two": 2
lambda: |-
ESP_LOGD("Reg1001", "Received value %lld", x);
if (x > 1) {
return std::string("Two");
} else if (x == 1) {
return std::string("On");
}
return std::string("Off");
write_lambda: |-
ESP_LOGD("Reg1001", "Set option to %s (%lld)", x.c_str(), value);
if (x == "On") {
return 1;
}
if (x == "Two") {
payload.push_back(0x0002);
return 0;
}
return value;
sensor:
- platform: modbus_controller
@@ -97,6 +151,15 @@ sensor:
address: 0x9001
unit_of_measurement: "AH"
value_type: U_WORD
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_sensor2
name: Test Sensor with Lambda
register_type: holding
address: 0x9002
value_type: U_WORD
lambda: |-
return x / 10.0;
switch:
- platform: modbus_controller
@@ -106,6 +169,16 @@ switch:
register_type: coil
address: 0x15
bitmask: 1
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_switch2
name: Test Switch with Lambda
register_type: coil
address: 0x16
lambda: |-
return !x;
write_lambda: |-
return !x;
text_sensor:
- platform: modbus_controller
@@ -117,3 +190,13 @@ text_sensor:
register_count: 3
raw_encode: HEXBYTES
response_size: 6
- platform: modbus_controller
modbus_controller_id: modbus_controller1
id: modbus_text_sensor2
name: Test Text Sensor with Lambda
register_type: holding
address: 0x9014
register_count: 2
response_size: 4
lambda: |-
return "Modified: " + x;

View File

@@ -1,6 +1,7 @@
import glob
import logging
from pathlib import Path
from typing import Any
from esphome import config as config_module, yaml_util
from esphome.components import substitutions
@@ -60,6 +61,29 @@ def write_yaml(path: Path, data: dict) -> None:
path.write_text(yaml_util.dump(data), encoding="utf-8")
def verify_database(value: Any, path: str = "") -> str | None:
if isinstance(value, list):
for i, v in enumerate(value):
result = verify_database(v, f"{path}[{i}]")
if result is not None:
return result
return None
if isinstance(value, dict):
for k, v in value.items():
key_result = verify_database(k, f"{path}/{k}")
if key_result is not None:
return key_result
value_result = verify_database(v, f"{path}/{k}")
if value_result is not None:
return value_result
return None
if isinstance(value, str):
if not isinstance(value, yaml_util.ESPHomeDataBase):
return f"{path}: {value!r} is not ESPHomeDataBase"
return None
return None
def test_substitutions_fixtures(fixture_path):
base_dir = fixture_path / "substitutions"
sources = sorted(glob.glob(str(base_dir / "*.input.yaml")))
@@ -83,6 +107,9 @@ def test_substitutions_fixtures(fixture_path):
substitutions.do_substitution_pass(config, None)
resolve_extend_remove(config)
verify_database_result = verify_database(config)
if verify_database_result is not None:
raise AssertionError(verify_database_result)
# Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE
if expected_path.is_file():