mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Merge branch 'modbus_func_ptr' into integration
This commit is contained in:
@@ -62,6 +62,7 @@ from esphome.cpp_types import ( # noqa: F401
|
|||||||
EntityBase,
|
EntityBase,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
ESPTime,
|
ESPTime,
|
||||||
|
FixedVector,
|
||||||
GPIOPin,
|
GPIOPin,
|
||||||
InternalGPIOPin,
|
InternalGPIOPin,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
|
|||||||
@@ -71,10 +71,12 @@ SERVICE_ARG_NATIVE_TYPES = {
|
|||||||
"int": cg.int32,
|
"int": cg.int32,
|
||||||
"float": float,
|
"float": float,
|
||||||
"string": cg.std_string,
|
"string": cg.std_string,
|
||||||
"bool[]": cg.std_vector.template(bool),
|
"bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
|
||||||
"int[]": cg.std_vector.template(cg.int32),
|
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
||||||
"float[]": cg.std_vector.template(float),
|
"float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
|
||||||
"string[]": cg.std_vector.template(cg.std_string),
|
"string[]": cg.FixedVector.template(cg.std_string)
|
||||||
|
.operator("const")
|
||||||
|
.operator("ref"),
|
||||||
}
|
}
|
||||||
CONF_ENCRYPTION = "encryption"
|
CONF_ENCRYPTION = "encryption"
|
||||||
CONF_BATCH_DELAY = "batch_delay"
|
CONF_BATCH_DELAY = "batch_delay"
|
||||||
|
|||||||
@@ -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<> 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_; }
|
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) {
|
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) {
|
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) {
|
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) {
|
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<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<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<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
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<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
|
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
|
||||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
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;
|
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
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor,
|
|||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
using transform_func_t = std::function<optional<bool>(ModbusBinarySensor *, bool, const std::vector<uint8_t> &)>;
|
using transform_func_t = optional<bool> (*)(ModbusBinarySensor *, bool, 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:
|
protected:
|
||||||
optional<transform_func_t> transform_func_{nullopt};
|
optional<transform_func_t> transform_func_{nullopt};
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
|
|||||||
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
||||||
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
|
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 transform_func_t = optional<float> (*)(ModbusNumber *, float, const std::vector<uint8_t> &);
|
||||||
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_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_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_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; }
|
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
void parse_and_publish(const std::vector<uint8_t> &data) override{};
|
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> &)>;
|
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_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; }
|
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -60,8 +60,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
void parse_and_publish(const std::vector<uint8_t> &data) override{};
|
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> &)>;
|
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_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; }
|
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -26,16 +26,15 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
|
|||||||
this->mapping_ = std::move(mapping);
|
this->mapping_ = std::move(mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
using transform_func_t =
|
using transform_func_t = optional<std::string> (*)(ModbusSelect *const, int64_t, const std::vector<uint8_t> &);
|
||||||
std::function<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,
|
||||||
using write_transform_func_t =
|
std::vector<uint16_t> &);
|
||||||
std::function<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_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_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
void set_template(transform_func_t &&f) { this->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 set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||||
|
|||||||
@@ -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 parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||||
void dump_config() 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:
|
protected:
|
||||||
optional<transform_func_t> transform_func_{nullopt};
|
optional<transform_func_t> transform_func_{nullopt};
|
||||||
|
|||||||
@@ -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 parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||||
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
||||||
|
|
||||||
using transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, const std::vector<uint8_t> &)>;
|
using transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, const std::vector<uint8_t> &);
|
||||||
using write_transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, 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_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_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; }
|
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||||
using transform_func_t =
|
using transform_func_t = optional<std::string> (*)(ModbusTextSensor *, std::string, const std::vector<uint8_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; }
|
||||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
optional<transform_func_t> transform_func_{nullopt};
|
optional<transform_func_t> transform_func_{nullopt};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from re import Match
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from esphome import core
|
from esphome import core
|
||||||
from esphome.config_helpers import Extend, Remove, merge_config, merge_dicts_ordered
|
from esphome.config_helpers import Extend, Remove, merge_config, merge_dicts_ordered
|
||||||
@@ -39,7 +41,34 @@ async def to_code(config):
|
|||||||
pass
|
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 has_jinja(value):
|
||||||
# If the original value passed in to this function is a JinjaStr, it means it contains an unresolved
|
# If the original value passed in to this function is a JinjaStr, it means it contains an unresolved
|
||||||
# Jinja expression from a previous pass.
|
# 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)}",
|
f"\nSee {'->'.join(str(x) for x in path)}",
|
||||||
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
|
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:
|
if "$" not in value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -76,14 +112,14 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
|||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
m = cv.VARIABLE_PROG.search(value, i)
|
m: Match[str] = cv.VARIABLE_PROG.search(value, i)
|
||||||
if not m:
|
if not m:
|
||||||
# No more variable substitutions found. See if the remainder looks like a jinja template
|
# No more variable substitutions found. See if the remainder looks like a jinja template
|
||||||
value = _expand_jinja(value, orig_value, path, jinja, ignore_missing)
|
value = _expand_jinja(value, orig_value, path, jinja, ignore_missing)
|
||||||
break
|
break
|
||||||
|
|
||||||
i, j = m.span(0)
|
i, j = m.span(0)
|
||||||
name = m.group(1)
|
name: str = m.group(1)
|
||||||
if name.startswith("{") and name.endswith("}"):
|
if name.startswith("{") and name.endswith("}"):
|
||||||
name = name[1:-1]
|
name = name[1:-1]
|
||||||
if name not in substitutions:
|
if name not in substitutions:
|
||||||
@@ -98,7 +134,7 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
|||||||
i = j
|
i = j
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sub = substitutions[name]
|
sub: Any = substitutions[name]
|
||||||
|
|
||||||
if i == 0 and j == len(value):
|
if i == 0 and j == len(value):
|
||||||
# The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly
|
# 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
|
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):
|
if isinstance(item, ESPLiteralValue):
|
||||||
return None # do not substitute inside literal blocks
|
return None # do not substitute inside literal blocks
|
||||||
if isinstance(item, list):
|
if isinstance(item, list):
|
||||||
@@ -160,7 +202,9 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
|||||||
return None
|
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:
|
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from itertools import chain, islice
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
from types import GeneratorType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import jinja2 as jinja
|
import jinja2 as jinja
|
||||||
from jinja2.sandbox import SandboxedEnvironment
|
from jinja2.nativetypes import NativeCodeGenerator, NativeTemplate
|
||||||
|
|
||||||
from esphome.yaml_util import ESPLiteralValue
|
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
|
return detect_jinja_re.search(st) is not None
|
||||||
|
|
||||||
|
|
||||||
@@ -109,12 +113,56 @@ class TrackerContext(jinja.runtime.Context):
|
|||||||
return val
|
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
|
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__(
|
super().__init__(
|
||||||
trim_blocks=True,
|
trim_blocks=True,
|
||||||
lstrip_blocks=True,
|
lstrip_blocks=True,
|
||||||
@@ -142,19 +190,10 @@ class Jinja(SandboxedEnvironment):
|
|||||||
**SAFE_GLOBALS,
|
**SAFE_GLOBALS,
|
||||||
}
|
}
|
||||||
|
|
||||||
def safe_eval(self, expr):
|
def expand(self, content_str: str | JinjaStr) -> Any:
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Renders a string that may contain Jinja expressions or statements
|
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
|
Otherwise, it returns a tagged (JinjaStr) string that captures variables
|
||||||
in scope (upvalues), like a closure for later evaluation.
|
in scope (upvalues), like a closure for later evaluation.
|
||||||
"""
|
"""
|
||||||
@@ -172,7 +211,7 @@ class Jinja(SandboxedEnvironment):
|
|||||||
self.context_trace = {}
|
self.context_trace = {}
|
||||||
try:
|
try:
|
||||||
template = self.from_string(content_str)
|
template = self.from_string(content_str)
|
||||||
result = self.safe_eval(template.render(override_vars))
|
result = template.render(override_vars)
|
||||||
if isinstance(result, Undefined):
|
if isinstance(result, Undefined):
|
||||||
print("" + result) # force a UndefinedError exception
|
print("" + result) # force a UndefinedError exception
|
||||||
except (TemplateSyntaxError, UndefinedError) as err:
|
except (TemplateSyntaxError, UndefinedError) as err:
|
||||||
@@ -201,3 +240,10 @@ class Jinja(SandboxedEnvironment):
|
|||||||
content_str.result = result
|
content_str.result = result
|
||||||
|
|
||||||
return result, None
|
return result, None
|
||||||
|
|
||||||
|
|
||||||
|
class JinjaTemplate(NativeTemplate):
|
||||||
|
environment_class = Jinja
|
||||||
|
|
||||||
|
|
||||||
|
Jinja.template_class = JinjaTemplate
|
||||||
|
|||||||
@@ -99,10 +99,26 @@ void IDFUARTComponent::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IDFUARTComponent::load_settings(bool dump_config) {
|
void IDFUARTComponent::load_settings(bool dump_config) {
|
||||||
uart_config_t uart_config = this->get_config_();
|
esp_err_t err;
|
||||||
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
|
|
||||||
|
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) {
|
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();
|
this->mark_failed();
|
||||||
return;
|
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;
|
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
||||||
|
|
||||||
uint32_t invert = 0;
|
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;
|
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;
|
invert |= UART_SIGNAL_RXD_INV;
|
||||||
|
}
|
||||||
|
|
||||||
err = uart_set_line_inverse(this->uart_num_, invert);
|
err = uart_set_line_inverse(this->uart_num_, invert);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -138,26 +156,6 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
|||||||
return;
|
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_);
|
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
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;
|
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) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
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) {
|
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();
|
this->dump_config();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDFUARTComponent::dump_config() {
|
void IDFUARTComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
||||||
LOG_PIN(" TX Pin: ", tx_pin_);
|
LOG_PIN(" TX Pin: ", this->tx_pin_);
|
||||||
LOG_PIN(" RX Pin: ", rx_pin_);
|
LOG_PIN(" RX Pin: ", this->rx_pin_);
|
||||||
LOG_PIN(" Flow Control Pin: ", flow_control_pin_);
|
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
|
||||||
if (this->rx_pin_ != nullptr) {
|
if (this->rx_pin_ != nullptr) {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
" RX Buffer Size: %u\n"
|
" RX Buffer Size: %u\n"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ size_t = global_ns.namespace("size_t")
|
|||||||
const_char_ptr = global_ns.namespace("const char *")
|
const_char_ptr = global_ns.namespace("const char *")
|
||||||
NAN = global_ns.namespace("NAN")
|
NAN = global_ns.namespace("NAN")
|
||||||
esphome_ns = global_ns # using namespace esphome;
|
esphome_ns = global_ns # using namespace esphome;
|
||||||
|
FixedVector = esphome_ns.class_("FixedVector")
|
||||||
App = esphome_ns.App
|
App = esphome_ns.App
|
||||||
EntityBase = esphome_ns.class_("EntityBase")
|
EntityBase = esphome_ns.class_("EntityBase")
|
||||||
Component = esphome_ns.class_("Component")
|
Component = esphome_ns.class_("Component")
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ def prepare(
|
|||||||
cert_file.flush()
|
cert_file.flush()
|
||||||
key_file.write(config[CONF_MQTT].get(CONF_CLIENT_CERTIFICATE_KEY))
|
key_file.write(config[CONF_MQTT].get(CONF_CLIENT_CERTIFICATE_KEY))
|
||||||
key_file.flush()
|
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)
|
client.tls_set_context(context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ binary_sensor:
|
|||||||
register_type: read
|
register_type: read
|
||||||
address: 0x3200
|
address: 0x3200
|
||||||
bitmask: 0x80
|
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:
|
number:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
@@ -65,6 +73,16 @@ number:
|
|||||||
address: 0x9001
|
address: 0x9001
|
||||||
value_type: U_WORD
|
value_type: U_WORD
|
||||||
multiply: 1.0
|
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:
|
output:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
@@ -74,6 +92,14 @@ output:
|
|||||||
register_type: holding
|
register_type: holding
|
||||||
value_type: U_WORD
|
value_type: U_WORD
|
||||||
multiply: 1000
|
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:
|
select:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
@@ -87,6 +113,34 @@ select:
|
|||||||
"One": 1
|
"One": 1
|
||||||
"Two": 2
|
"Two": 2
|
||||||
"Three": 3
|
"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:
|
sensor:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
@@ -97,6 +151,15 @@ sensor:
|
|||||||
address: 0x9001
|
address: 0x9001
|
||||||
unit_of_measurement: "AH"
|
unit_of_measurement: "AH"
|
||||||
value_type: U_WORD
|
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:
|
switch:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
@@ -106,6 +169,16 @@ switch:
|
|||||||
register_type: coil
|
register_type: coil
|
||||||
address: 0x15
|
address: 0x15
|
||||||
bitmask: 1
|
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:
|
text_sensor:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
@@ -117,3 +190,13 @@ text_sensor:
|
|||||||
register_count: 3
|
register_count: 3
|
||||||
raw_encode: HEXBYTES
|
raw_encode: HEXBYTES
|
||||||
response_size: 6
|
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;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import glob
|
import glob
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from esphome import config as config_module, yaml_util
|
from esphome import config as config_module, yaml_util
|
||||||
from esphome.components import substitutions
|
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")
|
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):
|
def test_substitutions_fixtures(fixture_path):
|
||||||
base_dir = fixture_path / "substitutions"
|
base_dir = fixture_path / "substitutions"
|
||||||
sources = sorted(glob.glob(str(base_dir / "*.input.yaml")))
|
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)
|
substitutions.do_substitution_pass(config, None)
|
||||||
|
|
||||||
resolve_extend_remove(config)
|
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
|
# Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE
|
||||||
if expected_path.is_file():
|
if expected_path.is_file():
|
||||||
|
|||||||
Reference in New Issue
Block a user