1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 16:41:50 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
f9b08491cc [tests] Fix millis() ambiguity in component tests with gps component 2025-10-23 06:50:24 -06:00
113 changed files with 381 additions and 1240 deletions

View File

@@ -16,12 +16,7 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
)
from esphome.core import ID
from esphome.cpp_generator import (
LambdaExpression,
MockObj,
MockObjClass,
TemplateArgsType,
)
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.util import Registry
@@ -92,7 +87,6 @@ def validate_potentially_or_condition(value):
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
@@ -103,40 +97,9 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
def new_lambda_pvariable(
id_obj: ID,
lambda_expr: LambdaExpression,
stateless_class: MockObjClass,
template_arg: cg.TemplateArguments | None = None,
) -> MockObj:
"""Create Pvariable for lambda, using stateless class if applicable.
Combines ID selection and Pvariable creation in one call. For stateless
lambdas (empty capture), uses function pointer instead of std::function.
Args:
id_obj: The ID object (action_id, condition_id, or filter_id)
lambda_expr: The lambda expression object
stateless_class: The stateless class to use for stateless lambdas
template_arg: Optional template arguments (for actions/conditions)
Returns:
The created Pvariable
"""
# For stateless lambdas, use function pointer instead of std::function
if lambda_expr.capture == "":
id_obj = id_obj.copy()
id_obj.type = stateless_class
if template_arg is not None:
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
return cg.new_Pvariable(id_obj, lambda_expr)
def validate_automation(extra_schema=None, extra_validators=None, single=False):
if extra_schema is None:
extra_schema = {}
@@ -277,9 +240,7 @@ async def lambda_condition_to_code(
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool)
return new_lambda_pvariable(
condition_id, lambda_, StatelessLambdaCondition, template_arg
)
return cg.new_Pvariable(condition_id, template_arg, lambda_)
@register_condition(
@@ -445,7 +406,7 @@ async def lambda_action_to_code(
args: TemplateArgsType,
) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
return cg.new_Pvariable(action_id, template_arg, lambda_)
@register_action(

View File

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

View File

@@ -71,12 +71,10 @@ SERVICE_ARG_NATIVE_TYPES = {
"int": cg.int32,
"float": float,
"string": 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"),
"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),
}
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
@@ -260,10 +258,6 @@ async def to_code(config):
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
if config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_CUSTOM_SERVICES")
if config[CONF_HOMEASSISTANT_SERVICES]:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
@@ -271,8 +265,6 @@ async def to_code(config):
cg.add_define("USE_API_HOMEASSISTANT_STATES")
if actions := config.get(CONF_ACTIONS, []):
# Collect all triggers first, then register all at once with initializer_list
triggers: list[cg.Pvariable] = []
for conf in actions:
template_args = []
func_args = []
@@ -286,10 +278,8 @@ async def to_code(config):
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
)
triggers.append(trigger)
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
# Register all services at once - single allocation, no reallocations
cg.add(var.initialize_user_services(triggers))
if CONF_ON_CLIENT_CONNECTED in config:
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")

View File

@@ -125,14 +125,8 @@ class APIServer : public Component, public Controller {
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_SERVICES
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
this->user_services_.assign(services);
}
#ifdef USE_API_CUSTOM_SERVICES
// Only compile push_back method when custom_services: true (external components)
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif
#endif
#ifdef USE_HOMEASSISTANT_TIME
void request_time();
#endif

View File

@@ -53,14 +53,8 @@ class CustomAPIDevice {
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
#else
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
#endif
}
#else
template<typename T, typename... Ts>
@@ -92,14 +86,8 @@ class CustomAPIDevice {
*/
#ifdef USE_API_SERVICES
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
#else
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
#endif
}
#else
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {

View File

@@ -11,58 +11,23 @@ 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) {
std::vector<bool> result;
result.reserve(arg.bool_array.size());
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
return result;
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end());
}
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
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;
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end());
}
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
std::vector<float> result;
result.reserve(arg.float_array.size());
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
return result;
return std::vector<float>(arg.float_array.begin(), arg.float_array.end());
}
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
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;
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end());
}
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;
@@ -74,18 +39,4 @@ 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

@@ -155,7 +155,6 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
_LOGGER = getLogger(__name__)
@@ -300,7 +299,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool)
)
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
return cg.new_Pvariable(filter_id, lambda_)
@register_filter(

View File

@@ -111,21 +111,6 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_;
};
/** Optimized lambda filter for stateless lambdas (no capture).
*
* Uses function pointer instead of std::function to reduce memory overhead.
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
*/
class StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
optional<bool> new_value(bool value) override { return this->f_(value); }
protected:
optional<bool> (*f_)(bool);
};
class SettleFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value) override;

View File

@@ -304,13 +304,9 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
# format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
# a PIO platformio/framework-espidf value
if ver == cv.Version(5, 4, 3) or ver >= cv.Version(5, 5, 1):
ext = "tar.xz"
else:
ext = "zip"
if release:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.{ext}"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
def _is_framework_url(source: str) -> str:
@@ -359,7 +355,6 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),

View File

@@ -40,13 +40,13 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
// - 3 bytes for members below
// - 1 byte padding for alignment
// - 4 bytes for vtable pointer
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
gpio::Flags flags_{}; // GPIO flags (1 byte)
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
gpio::Flags flags_; // GPIO flags (1 byte)
struct PinFlags {
uint8_t inverted : 1; // Invert pin logic (1 bit)
uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits)
uint8_t reserved : 5; // Reserved for future use (5 bits)
} pin_flags_{}; // Total: 1 byte
} pin_flags_; // Total: 1 byte
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static bool isr_service_installed;
};

View File

@@ -223,10 +223,7 @@ async def esp32_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_inverted(config[CONF_INVERTED]))
if CONF_DRIVE_STRENGTH in config:
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))

View File

@@ -29,8 +29,8 @@ class ESP8266GPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_{};
gpio::Flags flags_{};
bool inverted_;
gpio::Flags flags_;
};
} // namespace esp8266

View File

@@ -165,10 +165,7 @@ async def esp8266_pin_to_code(config):
num = config[CONF_NUMBER]
mode = config[CONF_MODE]
cg.add(var.set_pin(num))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(mode)))
if num < 16:
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][

View File

@@ -14,7 +14,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.components.network import ip_address_literal
from esphome.components.network import IPAddress
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
import esphome.config_validation as cv
from esphome.const import (
@@ -320,11 +320,11 @@ def _final_validate_spi(config):
def manual_ip(config):
return cg.StructInitializer(
ManualIP,
("static_ip", ip_address_literal(config[CONF_STATIC_IP])),
("gateway", ip_address_literal(config[CONF_GATEWAY])),
("subnet", ip_address_literal(config[CONF_SUBNET])),
("dns1", ip_address_literal(config[CONF_DNS1])),
("dns2", ip_address_literal(config[CONF_DNS2])),
("static_ip", IPAddress(str(config[CONF_STATIC_IP]))),
("gateway", IPAddress(str(config[CONF_GATEWAY]))),
("subnet", IPAddress(str(config[CONF_SUBNET]))),
("dns1", IPAddress(str(config[CONF_DNS1]))),
("dns2", IPAddress(str(config[CONF_DNS2]))),
)

View File

@@ -28,8 +28,8 @@ class HostGPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_{};
gpio::Flags flags_{};
bool inverted_;
gpio::Flags flags_;
};
} // namespace host

View File

@@ -57,9 +57,6 @@ async def host_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -169,7 +169,7 @@ class HttpRequestComponent : public Component {
protected:
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
const std::string &body, const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) = 0;
std::set<std::string> collect_headers) = 0;
const char *useragent_{nullptr};
bool follow_redirects_{};
uint16_t redirect_limit_{};

View File

@@ -17,7 +17,7 @@ static const char *const TAG = "http_request.arduino";
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) {
std::set<std::string> collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -33,7 +33,7 @@ class HttpRequestArduino : public HttpRequestComponent {
protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) override;
std::set<std::string> collect_headers) override;
};
} // namespace http_request

View File

@@ -20,7 +20,7 @@ static const char *const TAG = "http_request.host";
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &response_headers) {
std::set<std::string> response_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
public:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &response_headers) override;
std::set<std::string> response_headers) override;
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
protected:

View File

@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) {
std::set<std::string> collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -39,7 +39,7 @@ class HttpRequestIDF : public HttpRequestComponent {
protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers,
const std::set<std::string> &collect_headers) override;
std::set<std::string> collect_headers) override;
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{};

View File

@@ -199,9 +199,6 @@ async def component_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -27,8 +27,8 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_{};
gpio::Flags flags_{};
bool inverted_;
gpio::Flags flags_;
};
} // namespace libretiny

View File

@@ -57,9 +57,9 @@ class AddressableLightEffect : public LightEffect {
class AddressableLambdaLightEffect : public AddressableLightEffect {
public:
AddressableLambdaLightEffect(const char *name, void (*f)(AddressableLight &, Color, bool initial_run),
AddressableLambdaLightEffect(const char *name, std::function<void(AddressableLight &, Color, bool initial_run)> f,
uint32_t update_interval)
: AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
: AddressableLightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
void start() override { this->initial_run_ = true; }
void apply(AddressableLight &it, const Color &current_color) override {
const uint32_t now = millis();
@@ -72,7 +72,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
}
protected:
void (*f_)(AddressableLight &, Color, bool initial_run);
std::function<void(AddressableLight &, Color, bool initial_run)> f_;
uint32_t update_interval_;
uint32_t last_run_{0};
bool initial_run_;

View File

@@ -112,8 +112,8 @@ class RandomLightEffect : public LightEffect {
class LambdaLightEffect : public LightEffect {
public:
LambdaLightEffect(const char *name, void (*f)(bool initial_run), uint32_t update_interval)
: LightEffect(name), f_(f), update_interval_(update_interval) {}
LambdaLightEffect(const char *name, std::function<void(bool initial_run)> f, uint32_t update_interval)
: LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
void start() override { this->initial_run_ = true; }
void apply() override {
@@ -130,7 +130,7 @@ class LambdaLightEffect : public LightEffect {
uint32_t get_current_index() const { return this->get_index(); }
protected:
void (*f_)(bool initial_run);
std::function<void(bool initial_run)> f_;
uint32_t update_interval_;
uint32_t last_run_{0};
bool initial_run_;

View File

@@ -1,7 +1,7 @@
import re
from esphome import automation
from esphome.automation import LambdaAction, StatelessLambdaAction
from esphome.automation import LambdaAction
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import (
@@ -430,9 +430,7 @@ async def logger_log_action_to_code(config, action_id, template_arg, args):
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
return cg.new_Pvariable(action_id, template_arg, lambda_)
@automation.register_action(
@@ -457,9 +455,7 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
text = str(cg.statement(logger.set_log_level(level)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
return cg.new_Pvariable(action_id, template_arg, lambda_)
FILTER_SOURCE_FILES = filter_source_files_from_platform(

View File

@@ -1,5 +1,3 @@
import ipaddress
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
@@ -12,41 +10,6 @@ AUTO_LOAD = ["mdns"]
network_ns = cg.esphome_ns.namespace("network")
IPAddress = network_ns.class_("IPAddress")
def ip_address_literal(ip: str | int | None) -> cg.MockObj:
"""Generate an IPAddress with compile-time initialization instead of runtime parsing.
This function parses the IP address in Python during code generation and generates
a call to the 4-octet constructor (IPAddress(192, 168, 1, 1)) instead of the
string constructor (IPAddress("192.168.1.1")). This eliminates runtime string
parsing overhead and reduces flash usage on embedded systems.
Args:
ip: IP address as string (e.g., "192.168.1.1"), ipaddress.IPv4Address, or None
Returns:
IPAddress expression that uses 4-octet constructor for efficiency
"""
if ip is None:
return IPAddress(0, 0, 0, 0)
try:
# Parse using Python's ipaddress module
ip_obj = ipaddress.ip_address(ip)
except (ValueError, TypeError):
pass
else:
# Only support IPv4 for now
if isinstance(ip_obj, ipaddress.IPv4Address):
# Extract octets from the packed bytes representation
octets = ip_obj.packed
# Generate call to 4-octet constructor: IPAddress(192, 168, 1, 1)
return IPAddress(octets[0], octets[1], octets[2], octets[3])
# Fallback to string constructor if parsing fails
return IPAddress(str(ip))
CONFIG_SCHEMA = cv.Schema(
{
cv.SplitDefault(

View File

@@ -540,23 +540,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/
void goto_page(uint8_t page);
/**
* Set the visibility of a component.
*
* @param component The component name.
* @param show True to show the component, false to hide it.
*
* @see show_component()
* @see hide_component()
*
* Example:
* ```cpp
* it.set_component_visibility("textview", true); // Equivalent to show_component("textview")
* it.set_component_visibility("textview", false); // Equivalent to hide_component("textview")
* ```
*/
void set_component_visibility(const char *component, bool show) override;
/**
* Hide a component.
* @param component The component name.

View File

@@ -45,7 +45,6 @@ class NextionBase {
virtual void set_component_pressed_font_color(const char *component, Color color) = 0;
virtual void set_component_font(const char *component, uint8_t font_id) = 0;
virtual void set_component_visibility(const char *component, bool show) = 0;
virtual void show_component(const char *component) = 0;
virtual void hide_component(const char *component) = 0;

View File

@@ -201,13 +201,13 @@ void Nextion::set_component_font(const char *component, uint8_t font_id) {
this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%" PRIu8, component, font_id);
}
void Nextion::set_component_visibility(const char *component, bool show) {
this->add_no_result_to_queue_with_printf_("set_component_visibility", "vis %s,%d", component, show ? 1 : 0);
void Nextion::hide_component(const char *component) {
this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component);
}
void Nextion::hide_component(const char *component) { this->set_component_visibility(component, false); }
void Nextion::show_component(const char *component) { this->set_component_visibility(component, true); }
void Nextion::show_component(const char *component) {
this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component);
}
void Nextion::enable_component_touch(const char *component) {
this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);

View File

@@ -81,11 +81,13 @@ void NextionComponent::update_component_settings(bool force_update) {
this->component_flags_.visible_needs_update = false;
this->nextion_->set_component_visibility(name_to_send.c_str(), this->component_flags_.visible);
if (!this->component_flags_.visible) {
if (this->component_flags_.visible) {
this->nextion_->show_component(name_to_send.c_str());
this->send_state_to_nextion();
} else {
this->nextion_->hide_component(name_to_send.c_str());
return;
}
this->send_state_to_nextion();
}
if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) {

View File

@@ -174,6 +174,11 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Check if baud rate is supported
this->original_baud_rate_ = this->parent_->get_baud_rate();
static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
115200, 230400, 250000, 256000, 512000, 921600};
if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
baud_rate = this->original_baud_rate_;
}
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client

View File

@@ -177,6 +177,11 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Check if baud rate is supported
this->original_baud_rate_ = this->parent_->get_baud_rate();
static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
115200, 230400, 250000, 256000, 512000, 921600};
if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
baud_rate = this->original_baud_rate_;
}
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client

View File

@@ -74,9 +74,6 @@ async def nrf52_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -252,10 +252,7 @@ async def setup_number_core_(
cg.add(var.traits.set_max_value(max_value))
cg.add(var.traits.set_step(step))
# Only set if non-default to avoid bloating setup() function
# (mode_ is initialized to NUMBER_MODE_AUTO in the header)
if config[CONF_MODE] != NumberMode.NUMBER_MODE_AUTO:
cg.add(var.traits.set_mode(config[CONF_MODE]))
cg.add(var.traits.set_mode(config[CONF_MODE]))
for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -38,6 +38,7 @@ void Pipsolar::loop() {
}
if (this->state_ == STATE_COMMAND_COMPLETE) {
if (this->check_incoming_length_(4)) {
ESP_LOGD(TAG, "response length for command OK");
if (this->check_incoming_crc_()) {
// crc ok
if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') {
@@ -48,15 +49,15 @@ void Pipsolar::loop() {
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
} else {
// crc failed
// no log message necessary, check_incoming_crc_() logs
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
}
} else {
ESP_LOGD(TAG, "command %s response length not OK: with length %zu",
ESP_LOGD(TAG, "response length for command %s not OK: with length %zu",
this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
@@ -65,10 +66,46 @@ void Pipsolar::loop() {
}
if (this->state_ == STATE_POLL_CHECKED) {
ESP_LOGD(TAG, "poll %s decode", this->enabled_polling_commands_[this->last_polling_command_].command);
this->handle_poll_response_(this->enabled_polling_commands_[this->last_polling_command_].identifier,
(const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
switch (this->enabled_polling_commands_[this->last_polling_command_].identifier) {
case POLLING_QPIRI:
ESP_LOGD(TAG, "Decode QPIRI");
handle_qpiri_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
case POLLING_QPIGS:
ESP_LOGD(TAG, "Decode QPIGS");
handle_qpigs_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
case POLLING_QMOD:
ESP_LOGD(TAG, "Decode QMOD");
handle_qmod_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
case POLLING_QFLAG:
ESP_LOGD(TAG, "Decode QFLAG");
handle_qflag_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
case POLLING_QPIWS:
ESP_LOGD(TAG, "Decode QPIWS");
handle_qpiws_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
case POLLING_QT:
ESP_LOGD(TAG, "Decode QT");
handle_qt_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
case POLLING_QMN:
ESP_LOGD(TAG, "Decode QMN");
handle_qmn_((const char *) this->read_buffer_);
this->state_ = STATE_IDLE;
break;
default:
this->state_ = STATE_IDLE;
break;
}
return;
}
@@ -76,8 +113,6 @@ void Pipsolar::loop() {
if (this->check_incoming_crc_()) {
if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' &&
this->read_buffer_[3] == 'K') {
ESP_LOGD(TAG, "poll %s NACK", this->enabled_polling_commands_[this->last_polling_command_].command);
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
this->state_ = STATE_IDLE;
return;
}
@@ -86,9 +121,6 @@ void Pipsolar::loop() {
this->state_ = STATE_POLL_CHECKED;
return;
} else {
// crc failed
// no log message necessary, check_incoming_crc_() logs
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
this->state_ = STATE_IDLE;
}
}
@@ -126,19 +158,21 @@ void Pipsolar::loop() {
// command timeout
const char *command = this->command_queue_[this->command_queue_position_].c_str();
this->command_start_millis_ = millis();
ESP_LOGD(TAG, "command %s timeout", command);
ESP_LOGD(TAG, "timeout command from queue: %s", command);
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
return;
} else {
}
}
if (this->state_ == STATE_POLL) {
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
// command timeout
ESP_LOGD(TAG, "poll %s timeout", this->enabled_polling_commands_[this->last_polling_command_].command);
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
ESP_LOGD(TAG, "timeout command to poll: %s",
this->enabled_polling_commands_[this->last_polling_command_].command);
this->state_ = STATE_IDLE;
} else {
}
}
}
@@ -153,6 +187,7 @@ uint8_t Pipsolar::check_incoming_length_(uint8_t length) {
uint8_t Pipsolar::check_incoming_crc_() {
uint16_t crc16;
crc16 = this->pipsolar_crc_(read_buffer_, read_pos_ - 3);
ESP_LOGD(TAG, "checking crc on incoming message");
if (((uint8_t) ((crc16) >> 8)) == read_buffer_[read_pos_ - 3] &&
((uint8_t) ((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) {
ESP_LOGD(TAG, "CRC OK");
@@ -218,7 +253,7 @@ bool Pipsolar::send_next_poll_() {
this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending polling command: %s with length %d",
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
this->enabled_polling_commands_[this->last_polling_command_].command,
this->enabled_polling_commands_[this->last_polling_command_].length);
return true;
@@ -239,38 +274,6 @@ void Pipsolar::queue_command(const std::string &command) {
ESP_LOGD(TAG, "Command queue full dropping command: %s", command.c_str());
}
void Pipsolar::handle_poll_response_(ENUMPollingCommand polling_command, const char *message) {
switch (polling_command) {
case POLLING_QPIRI:
handle_qpiri_(message);
break;
case POLLING_QPIGS:
handle_qpigs_(message);
break;
case POLLING_QMOD:
handle_qmod_(message);
break;
case POLLING_QFLAG:
handle_qflag_(message);
break;
case POLLING_QPIWS:
handle_qpiws_(message);
break;
case POLLING_QT:
handle_qt_(message);
break;
case POLLING_QMN:
handle_qmn_(message);
break;
default:
break;
}
}
void Pipsolar::handle_poll_error_(ENUMPollingCommand polling_command) {
// handlers are designed in a way that an empty message sets all sensors to unknown
this->handle_poll_response_(polling_command, "");
}
void Pipsolar::handle_qpiri_(const char *message) {
if (this->last_qpiri_) {
this->last_qpiri_->publish_state(message);

View File

@@ -204,9 +204,6 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent {
bool send_next_command_();
bool send_next_poll_();
void handle_poll_response_(ENUMPollingCommand polling_command, const char *message);
void handle_poll_error_(ENUMPollingCommand polling_command);
// these handlers are designed in a way that an empty message sets all sensors to unknown
void handle_qpiri_(const char *message);
void handle_qpigs_(const char *message);
void handle_qmod_(const char *message);

View File

@@ -4,18 +4,11 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_VOLTAGE,
CONF_BUS_VOLTAGE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_BATTERY,
ICON_CURRENT_AC,
ICON_FLASH,
ICON_GAUGE,
STATE_CLASS_MEASUREMENT,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
@@ -29,10 +22,6 @@ from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA
DEPENDENCIES = ["uart"]
ICON_SOLAR_POWER = "mdi:solar-power"
ICON_SOLAR_PANEL = "mdi:solar-panel"
ICON_CURRENT_DC = "mdi:current-dc"
# QPIRI sensors
CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage"
CONF_GRID_RATING_CURRENT = "grid_rating_current"
@@ -86,19 +75,16 @@ TYPES = {
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_GRID_RATING_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
@@ -112,12 +98,11 @@ TYPES = {
),
CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_APPARENT_POWER,
accuracy_decimals=1,
),
CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
),
CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema(
@@ -146,151 +131,124 @@ TYPES = {
device_class=DEVICE_CLASS_VOLTAGE,
),
CONF_BATTERY_TYPE: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
),
CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
),
CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_PARALLEL_MAX_NUM: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_MACHINE_TYPE: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_TOPOLOGY: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_OUTPUT_MODE: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema(
accuracy_decimals=1,
),
CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_PV_POWER_BALANCE: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_GRID_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_GRID_FREQUENCY: sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
),
CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
),
CONF_BUS_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
icon=ICON_FLASH,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_BATTERY_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
icon=ICON_BATTERY,
accuracy_decimals=2,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
icon=ICON_CURRENT_DC,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
),
CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
icon=ICON_SOLAR_PANEL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
icon=ICON_SOLAR_PANEL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
icon=ICON_CURRENT_DC,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
@@ -298,14 +256,12 @@ TYPES = {
device_class=DEVICE_CLASS_VOLTAGE,
),
CONF_EEPROM_VERSION: sensor.sensor_schema(
accuracy_decimals=0,
accuracy_decimals=1,
),
CONF_PV_CHARGING_POWER: sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
icon=ICON_SOLAR_POWER,
accuracy_decimals=0,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}

View File

@@ -12,25 +12,6 @@
namespace esphome {
namespace remote_transmitter {
#ifdef USE_ESP32
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
// IDF version 5.5.1 and above is required because of a bug in
// the RMT encoder: https://github.com/espressif/esp-idf/issues/17244
typedef union { // NOLINT(modernize-use-using)
struct {
uint16_t duration : 15;
uint16_t level : 1;
};
uint16_t val;
} rmt_symbol_half_t;
struct RemoteTransmitterComponentStore {
uint32_t times{0};
uint32_t index{0};
};
#endif
#endif
class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
public Component
#ifdef USE_ESP32
@@ -75,14 +56,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#ifdef USE_ESP32
void configure_rmt_();
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
RemoteTransmitterComponentStore store_{};
std::vector<rmt_symbol_half_t> rmt_temp_;
#else
std::vector<rmt_symbol_word_t> rmt_temp_;
#endif
uint32_t current_carrier_frequency_{38000};
bool initialized_{false};
std::vector<rmt_symbol_word_t> rmt_temp_;
bool with_dma_{false};
bool eot_level_{false};
rmt_channel_handle_t channel_{NULL};

View File

@@ -10,46 +10,6 @@ namespace remote_transmitter {
static const char *const TAG = "remote_transmitter";
// Maximum RMT symbol duration (15-bit field)
static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
rmt_symbol_word_t *symbols, bool *done, void *arg) {
auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
const auto *encoded = static_cast<const rmt_symbol_half_t *>(data);
size_t length = size / sizeof(rmt_symbol_half_t);
size_t count = 0;
// copy symbols
for (size_t i = 0; i < free; i++) {
uint16_t sym_0 = encoded[store->index++].val;
if (store->index >= length) {
store->index = 0;
store->times--;
if (store->times == 0) {
*done = true;
symbols[count++].val = sym_0;
return count;
}
}
uint16_t sym_1 = encoded[store->index++].val;
if (store->index >= length) {
store->index = 0;
store->times--;
if (store->times == 0) {
*done = true;
symbols[count++].val = sym_0 | (sym_1 << 16);
return count;
}
}
symbols[count++].val = sym_0 | (sym_1 << 16);
}
*done = false;
return count;
}
#endif
void RemoteTransmitterComponent::setup() {
this->inverted_ = this->pin_->is_inverted();
this->configure_rmt_();
@@ -74,17 +34,6 @@ void RemoteTransmitterComponent::dump_config() {
}
void RemoteTransmitterComponent::digital_write(bool value) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
rmt_symbol_half_t symbol = {
.duration = 1,
.level = value,
};
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.flags.eot_level = value;
this->store_.times = 1;
this->store_.index = 0;
#else
rmt_symbol_word_t symbol = {
.duration0 = 1,
.level0 = value,
@@ -93,8 +42,8 @@ void RemoteTransmitterComponent::digital_write(bool value) {
};
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = value;
#endif
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
@@ -141,20 +90,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
gpio_pullup_dis(gpio_num_t(this->pin_->get_pin()));
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
rmt_simple_encoder_config_t encoder;
memset(&encoder, 0, sizeof(encoder));
encoder.callback = encoder_callback;
encoder.arg = &this->store_;
encoder.min_chunk_size = 1;
error = rmt_new_simple_encoder(&encoder, &this->encoder_);
if (error != ESP_OK) {
this->error_code_ = error;
this->error_string_ = "in rmt_new_simple_encoder";
this->mark_failed();
return;
}
#else
rmt_copy_encoder_config_t encoder;
memset(&encoder, 0, sizeof(encoder));
error = rmt_new_copy_encoder(&encoder, &this->encoder_);
@@ -164,7 +99,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
this->mark_failed();
return;
}
#endif
error = rmt_enable(this->channel_);
if (error != ESP_OK) {
@@ -196,79 +130,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
}
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
if (this->is_failed()) {
return;
}
if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) {
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
this->configure_rmt_();
}
this->rmt_temp_.clear();
this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
// encode any delay at the start of the buffer to simplify the encoder callback
// this will be skipped the first time around
send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
while (send_wait > 0) {
int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
this->rmt_temp_.push_back({
.duration = static_cast<uint16_t>(duration),
.level = static_cast<uint16_t>(this->eot_level_),
});
send_wait -= duration;
}
// encode data
size_t offset = this->rmt_temp_.size();
for (int32_t value : this->temp_.get_data()) {
bool level = value >= 0;
if (!level) {
value = -value;
}
value = this->from_microseconds_(static_cast<uint32_t>(value));
while (value > 0) {
int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
this->rmt_temp_.push_back({
.duration = static_cast<uint16_t>(duration),
.level = static_cast<uint16_t>(level ^ this->inverted_),
});
value -= duration;
}
}
if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
ESP_LOGE(TAG, "Empty data");
return;
}
this->transmit_trigger_->trigger();
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.flags.eot_level = this->eot_level_;
this->store_.times = send_times;
this->store_.index = offset;
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
this->status_set_warning();
} else {
this->status_clear_warning();
}
error = rmt_tx_wait_all_done(this->channel_, -1);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning();
}
this->complete_trigger_->trigger();
}
#else
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
if (this->is_failed())
return;
@@ -290,7 +151,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
val = this->from_microseconds_(static_cast<uint32_t>(val));
do {
int32_t item = std::min(val, int32_t(RMT_SYMBOL_DURATION_MAX));
int32_t item = std::min(val, int32_t(32767));
val -= item;
if (rmt_i % 2 == 0) {
@@ -319,6 +180,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
for (uint32_t i = 0; i < send_times; i++) {
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = this->eot_level_;
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
@@ -338,7 +200,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
}
this->complete_trigger_->trigger();
}
#endif
} // namespace remote_transmitter
} // namespace esphome

View File

@@ -29,8 +29,8 @@ class RP2040GPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_{};
gpio::Flags flags_{};
bool inverted_;
gpio::Flags flags_;
};
} // namespace rp2040

View File

@@ -94,9 +94,6 @@ async def rp2040_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
# Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@@ -261,7 +261,6 @@ ExponentialMovingAverageFilter = sensor_ns.class_(
)
ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component)
LambdaFilter = sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter)
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
@@ -574,7 +573,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(float, "x")], return_type=cg.optional.template(float)
)
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
return cg.new_Pvariable(filter_id, lambda_)
DELTA_SCHEMA = cv.Schema(
@@ -879,9 +878,7 @@ async def setup_sensor_core_(var, config):
cg.add(var.set_unit_of_measurement(unit_of_measurement))
if (accuracy_decimals := config.get(CONF_ACCURACY_DECIMALS)) is not None:
cg.add(var.set_accuracy_decimals(accuracy_decimals))
# Only set force_update if True (default is False)
if config[CONF_FORCE_UPDATE]:
cg.add(var.set_force_update(True))
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
if config.get(CONF_FILTERS): # must exist and not be empty
filters = await build_filters(config[CONF_FILTERS])
cg.add(var.set_filters(filters))

View File

@@ -296,21 +296,6 @@ class LambdaFilter : public Filter {
lambda_filter_t lambda_filter_;
};
/** Optimized lambda filter for stateless lambdas (no capture).
*
* Uses function pointer instead of std::function to reduce memory overhead.
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
*/
class StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<float> (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {}
optional<float> new_value(float value) override { return this->lambda_filter_(value); }
protected:
optional<float> (*lambda_filter_)(float);
};
/// A simple filter that adds `offset` to each value it receives.
class OffsetFilter : public Filter {
public:

View File

@@ -27,7 +27,7 @@ void SNTPComponent::setup() {
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
size_t i = 0;
for (auto &server : this->servers_) {
esp_sntp_setservername(i++, server);
esp_sntp_setservername(i++, server.c_str());
}
esp_sntp_set_sync_interval(this->get_update_interval());
esp_sntp_set_time_sync_notification_cb([](struct timeval *tv) {
@@ -42,7 +42,7 @@ void SNTPComponent::setup() {
size_t i = 0;
for (auto &server : this->servers_) {
sntp_setservername(i++, server);
sntp_setservername(i++, server.c_str());
}
#if defined(USE_ESP8266)
@@ -59,7 +59,7 @@ void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SNTP Time:");
size_t i = 0;
for (auto &server : this->servers_) {
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server);
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str());
}
}
void SNTPComponent::update() {

View File

@@ -2,14 +2,10 @@
#include "esphome/core/component.h"
#include "esphome/components/time/real_time_clock.h"
#include <array>
namespace esphome {
namespace sntp {
// Server count is calculated at compile time by Python codegen
// SNTP_SERVER_COUNT will always be defined
/// The SNTP component allows you to configure local timekeeping via Simple Network Time Protocol.
///
/// \note
@@ -18,7 +14,10 @@ namespace sntp {
/// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
class SNTPComponent : public time::RealTimeClock {
public:
SNTPComponent(const std::array<const char *, SNTP_SERVER_COUNT> &servers) : servers_(servers) {}
SNTPComponent(const std::vector<std::string> &servers) : servers_(servers) {}
// Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would
// have had no effect anyway, and making the strings immutable avoids the need to strdup their contents.
void setup() override;
void dump_config() override;
@@ -30,10 +29,7 @@ class SNTPComponent : public time::RealTimeClock {
void time_synced();
protected:
// Store const char pointers to string literals
// ESP8266: strings in rodata (RAM), but avoids std::string overhead (~24 bytes each)
// Other platforms: strings in flash
std::array<const char *, SNTP_SERVER_COUNT> servers_;
std::vector<std::string> servers_;
bool has_time_{false};
#if defined(USE_ESP32)

View File

@@ -43,11 +43,6 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
servers = config[CONF_SERVERS]
# Define server count at compile time
cg.add_define("SNTP_SERVER_COUNT", len(servers))
# Pass string literals to constructor - stored in flash/rodata by compiler
var = cg.new_Pvariable(config[CONF_ID], servers)
await cg.register_component(var, config)

View File

@@ -1,6 +1,4 @@
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
@@ -41,34 +39,7 @@ async def to_code(config):
pass
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:
def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
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.
@@ -94,17 +65,10 @@ def _expand_jinja(
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: dict, value: str, path, jinja: Jinja, ignore_missing: bool
) -> Any:
def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
if "$" not in value:
return value
@@ -112,14 +76,14 @@ def _expand_substitutions(
i = 0
while True:
m: Match[str] = cv.VARIABLE_PROG.search(value, i)
m = 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: str = m.group(1)
name = m.group(1)
if name.startswith("{") and name.endswith("}"):
name = name[1:-1]
if name not in substitutions:
@@ -134,7 +98,7 @@ def _expand_substitutions(
i = j
continue
sub: Any = substitutions[name]
sub = substitutions[name]
if i == 0 and j == len(value):
# The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly
@@ -157,13 +121,7 @@ def _expand_substitutions(
return value
def _substitute_item(
substitutions: dict,
item: Any,
path: list[int | str],
jinja: Jinja,
ignore_missing: bool,
) -> Any | None:
def _substitute_item(substitutions, item, path, jinja, ignore_missing):
if isinstance(item, ESPLiteralValue):
return None # do not substitute inside literal blocks
if isinstance(item, list):
@@ -202,9 +160,7 @@ def _substitute_item(
return None
def do_substitution_pass(
config: dict, command_line_substitutions: dict, ignore_missing: bool = False
) -> None:
def do_substitution_pass(config, command_line_substitutions, ignore_missing=False):
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
return

View File

@@ -1,14 +1,10 @@
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.nativetypes import NativeCodeGenerator, NativeTemplate
from jinja2.sandbox import SandboxedEnvironment
from esphome.yaml_util import ESPLiteralValue
@@ -28,7 +24,7 @@ detect_jinja_re = re.compile(
)
def has_jinja(st: str) -> bool:
def has_jinja(st):
return detect_jinja_re.search(st) is not None
@@ -113,56 +109,12 @@ class TrackerContext(jinja.runtime.Context):
return val
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):
class Jinja(SandboxedEnvironment):
"""
Wraps a Jinja environment
"""
# jinja environment customization overrides
code_generator_class = NativeCodeGenerator
concat = staticmethod(_concat_nodes_override)
def __init__(self, context_vars: dict):
def __init__(self, context_vars):
super().__init__(
trim_blocks=True,
lstrip_blocks=True,
@@ -190,10 +142,19 @@ class Jinja(jinja.Environment):
**SAFE_GLOBALS,
}
def expand(self, content_str: str | JinjaStr) -> Any:
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):
"""
Renders a string that may contain Jinja expressions or statements
Returns the resulting value if all variables and expressions could be resolved.
Returns the resulting processed string if all values could be resolved.
Otherwise, it returns a tagged (JinjaStr) string that captures variables
in scope (upvalues), like a closure for later evaluation.
"""
@@ -211,7 +172,7 @@ class Jinja(jinja.Environment):
self.context_trace = {}
try:
template = self.from_string(content_str)
result = template.render(override_vars)
result = self.safe_eval(template.render(override_vars))
if isinstance(result, Undefined):
print("" + result) # force a UndefinedError exception
except (TemplateSyntaxError, UndefinedError) as err:
@@ -240,10 +201,3 @@ class Jinja(jinja.Environment):
content_str.result = result
return result, None
class JinjaTemplate(NativeTemplate):
environment_class = Jinja
Jinja.template_class = JinjaTemplate

View File

@@ -38,14 +38,8 @@ async def to_code(config):
condition = await automation.build_condition(
condition, cg.TemplateArguments(), []
)
# Generate a stateless lambda that calls condition.check()
# capture="" is safe because condition is a global variable in generated C++ code
# and doesn't need to be captured. This allows implicit conversion to function pointer.
template_ = LambdaExpression(
f"return {condition.check()};",
[],
return_type=cg.optional.template(bool),
capture="",
f"return {condition.check()};", [], return_type=cg.optional.template(bool)
)
cg.add(var.set_template(template_))

View File

@@ -9,10 +9,10 @@ static const char *const TAG = "template.binary_sensor";
void TemplateBinarySensor::setup() { this->loop(); }
void TemplateBinarySensor::loop() {
if (!this->f_.has_value())
if (this->f_ == nullptr)
return;
auto s = (*this->f_)();
auto s = this->f_();
if (s.has_value()) {
this->publish_state(*s);
}

View File

@@ -8,7 +8,7 @@ namespace template_ {
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
public:
void set_template(optional<bool> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
void setup() override;
void loop() override;
@@ -17,7 +17,7 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
optional<optional<bool> (*)()> f_;
std::function<optional<bool>()> f_{nullptr};
};
} // namespace template_

View File

@@ -63,7 +63,7 @@ void TemplateCover::loop() {
}
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
void TemplateCover::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
void TemplateCover::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; }
Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; }
@@ -124,7 +124,7 @@ CoverTraits TemplateCover::get_traits() {
}
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
void TemplateCover::set_tilt_lambda(optional<float> (*tilt_f)()) { this->tilt_f_ = tilt_f; }
void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; }
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }

View File

@@ -17,7 +17,7 @@ class TemplateCover : public cover::Cover, public Component {
public:
TemplateCover();
void set_state_lambda(optional<float> (*f)());
void set_state_lambda(std::function<optional<float>()> &&f);
Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger() const;
@@ -26,7 +26,7 @@ class TemplateCover : public cover::Cover, public Component {
Trigger<float> *get_tilt_trigger() const;
void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state);
void set_tilt_lambda(optional<float> (*tilt_f)());
void set_tilt_lambda(std::function<optional<float>()> &&tilt_f);
void set_has_stop(bool has_stop);
void set_has_position(bool has_position);
void set_has_tilt(bool has_tilt);
@@ -45,8 +45,8 @@ class TemplateCover : public cover::Cover, public Component {
void stop_prev_trigger_();
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
optional<optional<float> (*)()> state_f_;
optional<optional<float> (*)()> tilt_f_;
optional<std::function<optional<float>()>> state_f_;
optional<std::function<optional<float>()>> tilt_f_;
bool assumed_state_{false};
bool optimistic_{false};
Trigger<> *open_trigger_;

View File

@@ -15,7 +15,7 @@ namespace template_ {
class TemplateDate : public datetime::DateEntity, public PollingComponent {
public:
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
@@ -35,7 +35,7 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent {
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<optional<ESPTime> (*)()> f_;
optional<std::function<optional<ESPTime>()>> f_;
ESPPreferenceObject pref_;
};

View File

@@ -15,7 +15,7 @@ namespace template_ {
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
public:
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
@@ -35,7 +35,7 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<optional<ESPTime> (*)()> f_;
optional<std::function<optional<ESPTime>()>> f_;
ESPPreferenceObject pref_;
};

View File

@@ -15,7 +15,7 @@ namespace template_ {
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
public:
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
@@ -35,7 +35,7 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent {
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<optional<ESPTime> (*)()> f_;
optional<std::function<optional<ESPTime>()>> f_;
ESPPreferenceObject pref_;
};

View File

@@ -45,7 +45,7 @@ void TemplateLock::open_latch() {
this->open_trigger_->trigger();
}
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateLock::set_state_lambda(optional<lock::LockState> (*f)()) { this->f_ = f; }
void TemplateLock::set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }

View File

@@ -13,7 +13,7 @@ class TemplateLock : public lock::Lock, public Component {
void dump_config() override;
void set_state_lambda(optional<lock::LockState> (*f)());
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
Trigger<> *get_lock_trigger() const;
Trigger<> *get_unlock_trigger() const;
Trigger<> *get_open_trigger() const;
@@ -26,7 +26,7 @@ class TemplateLock : public lock::Lock, public Component {
void control(const lock::LockCall &call) override;
void open_latch() override;
optional<optional<lock::LockState> (*)()> f_;
optional<std::function<optional<lock::LockState>()>> f_;
bool optimistic_{false};
Trigger<> *lock_trigger_;
Trigger<> *unlock_trigger_;

View File

@@ -10,7 +10,7 @@ namespace template_ {
class TemplateNumber : public number::Number, public PollingComponent {
public:
void set_template(optional<float> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
@@ -28,7 +28,7 @@ class TemplateNumber : public number::Number, public PollingComponent {
float initial_value_{NAN};
bool restore_value_{false};
Trigger<float> *set_trigger_ = new Trigger<float>();
optional<optional<float> (*)()> f_;
optional<std::function<optional<float>()>> f_;
ESPPreferenceObject pref_;
};

View File

@@ -73,18 +73,11 @@ async def to_code(config):
cg.add(var.set_template(template_))
else:
# Only set if non-default to avoid bloating setup() function
if config[CONF_OPTIMISTIC]:
cg.add(var.set_optimistic(True))
initial_option_index = config[CONF_OPTIONS].index(config[CONF_INITIAL_OPTION])
# Only set if non-zero to avoid bloating setup() function
# (initial_option_index_ is zero-initialized in the header)
if initial_option_index != 0:
cg.add(var.set_initial_option_index(initial_option_index))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION]))
# Only set if True (default is False)
if config.get(CONF_RESTORE_VALUE):
cg.add(var.set_restore_value(True))
if CONF_RESTORE_VALUE in config:
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
if CONF_SET_ACTION in config:
await automation.build_automation(

View File

@@ -10,21 +10,26 @@ void TemplateSelect::setup() {
if (this->f_.has_value())
return;
size_t index = this->initial_option_index_;
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
size_t restored_index;
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
index = restored_index;
ESP_LOGD(TAG, "State from restore: %s", this->at(index).value().c_str());
} else {
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->at(index).value().c_str());
}
std::string value;
if (!this->restore_value_) {
value = this->initial_option_;
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
} else {
ESP_LOGD(TAG, "State from initial: %s", this->at(index).value().c_str());
size_t index;
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
if (!this->pref_.load(&index)) {
value = this->initial_option_;
ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str());
} else if (!this->has_index(index)) {
value = this->initial_option_;
ESP_LOGD(TAG, "State from initial (restored index %d out of bounds): %s", index, value.c_str());
} else {
value = this->at(index).value();
ESP_LOGD(TAG, "State from restore: %s", value.c_str());
}
}
this->publish_state(this->at(index).value());
this->publish_state(value);
}
void TemplateSelect::update() {
@@ -64,8 +69,7 @@ void TemplateSelect::dump_config() {
" Optimistic: %s\n"
" Initial Option: %s\n"
" Restore Value: %s",
YESNO(this->optimistic_), this->at(this->initial_option_index_).value().c_str(),
YESNO(this->restore_value_));
YESNO(this->optimistic_), this->initial_option_.c_str(), YESNO(this->restore_value_));
}
} // namespace template_

View File

@@ -10,7 +10,7 @@ namespace template_ {
class TemplateSelect : public select::Select, public PollingComponent {
public:
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
@@ -19,16 +19,16 @@ class TemplateSelect : public select::Select, public PollingComponent {
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
void control(const std::string &value) override;
bool optimistic_ = false;
size_t initial_option_index_{0};
std::string initial_option_;
bool restore_value_ = false;
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
optional<optional<std::string> (*)()> f_;
optional<std::function<optional<std::string>()>> f_;
ESPPreferenceObject pref_;
};

View File

@@ -17,7 +17,7 @@ void TemplateSensor::update() {
}
}
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
void TemplateSensor::set_template(optional<float> (*f)()) { this->f_ = f; }
void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
void TemplateSensor::dump_config() {
LOG_SENSOR("", "Template Sensor", this);
LOG_UPDATE_INTERVAL(this);

View File

@@ -8,7 +8,7 @@ namespace template_ {
class TemplateSensor : public sensor::Sensor, public PollingComponent {
public:
void set_template(optional<float> (*f)());
void set_template(std::function<optional<float>()> &&f);
void update() override;
@@ -17,7 +17,7 @@ class TemplateSensor : public sensor::Sensor, public PollingComponent {
float get_setup_priority() const override;
protected:
optional<optional<float> (*)()> f_;
optional<std::function<optional<float>()>> f_;
};
} // namespace template_

View File

@@ -35,7 +35,7 @@ void TemplateSwitch::write_state(bool state) {
}
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
void TemplateSwitch::set_state_lambda(optional<bool> (*f)()) { this->f_ = f; }
void TemplateSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }

View File

@@ -14,7 +14,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
void setup() override;
void dump_config() override;
void set_state_lambda(optional<bool> (*f)());
void set_state_lambda(std::function<optional<bool>()> &&f);
Trigger<> *get_turn_on_trigger() const;
Trigger<> *get_turn_off_trigger() const;
void set_optimistic(bool optimistic);
@@ -28,7 +28,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
void write_state(bool state) override;
optional<optional<bool> (*)()> f_;
optional<std::function<optional<bool>()>> f_;
bool optimistic_{false};
bool assumed_state_{false};
Trigger<> *turn_on_trigger_;

View File

@@ -61,7 +61,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
class TemplateText : public text::Text, public PollingComponent {
public:
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
@@ -78,7 +78,7 @@ class TemplateText : public text::Text, public PollingComponent {
bool optimistic_ = false;
std::string initial_value_;
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
optional<optional<std::string> (*)()> f_{nullptr};
optional<std::function<optional<std::string>()>> f_{nullptr};
TemplateTextSaverBase *pref_ = nullptr;
};

View File

@@ -16,7 +16,7 @@ void TemplateTextSensor::update() {
}
}
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
void TemplateTextSensor::set_template(optional<std::string> (*f)()) { this->f_ = f; }
void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); }
} // namespace template_

View File

@@ -9,7 +9,7 @@ namespace template_ {
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
public:
void set_template(optional<std::string> (*f)());
void set_template(std::function<optional<std::string>()> &&f);
void update() override;
@@ -18,7 +18,7 @@ class TemplateTextSensor : public text_sensor::TextSensor, public PollingCompone
void dump_config() override;
protected:
optional<optional<std::string> (*)()> f_{};
optional<std::function<optional<std::string>()>> f_{};
};
} // namespace template_

View File

@@ -55,7 +55,7 @@ void TemplateValve::loop() {
void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
void TemplateValve::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
void TemplateValve::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }

View File

@@ -17,7 +17,7 @@ class TemplateValve : public valve::Valve, public Component {
public:
TemplateValve();
void set_state_lambda(optional<float> (*f)());
void set_state_lambda(std::function<optional<float>()> &&f);
Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger() const;
@@ -42,7 +42,7 @@ class TemplateValve : public valve::Valve, public Component {
void stop_prev_trigger_();
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
optional<optional<float> (*)()> state_f_;
optional<std::function<optional<float>()>> state_f_;
bool assumed_state_{false};
bool optimistic_{false};
Trigger<> *open_trigger_;

View File

@@ -57,7 +57,6 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
# Filters
Filter = text_sensor_ns.class_("Filter")
LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = text_sensor_ns.class_("StatelessLambdaFilter", Filter)
ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter)
ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
@@ -71,7 +70,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda(
config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string)
)
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
return cg.new_Pvariable(filter_id, lambda_)
@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {})

View File

@@ -62,21 +62,6 @@ class LambdaFilter : public Filter {
lambda_filter_t lambda_filter_;
};
/** Optimized lambda filter for stateless lambdas (no capture).
*
* Uses function pointer instead of std::function to reduce memory overhead.
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
*/
class StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {}
optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); }
protected:
optional<std::string> (*lambda_filter_)(std::string);
};
/// A simple filter that converts all text to uppercase
class ToUpperFilter : public Filter {
public:

View File

@@ -67,9 +67,7 @@ void TuyaClimate::setup() {
}
if (this->eco_id_.has_value()) {
this->parent_->register_listener(*this->eco_id_, [this](const TuyaDatapoint &datapoint) {
// Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both cases
this->eco_ = datapoint.value_bool;
this->eco_type_ = datapoint.type;
ESP_LOGV(TAG, "MCU reported eco is: %s", ONOFF(this->eco_));
this->compute_preset_();
this->compute_target_temperature_();
@@ -178,11 +176,7 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
if (this->eco_id_.has_value()) {
const bool eco = preset == climate::CLIMATE_PRESET_ECO;
ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco));
if (this->eco_type_ == TuyaDatapointType::ENUM) {
this->parent_->set_enum_datapoint_value(*this->eco_id_, eco);
} else {
this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco);
}
this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco);
}
if (this->sleep_id_.has_value()) {
const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP;

View File

@@ -104,7 +104,6 @@ class TuyaClimate : public climate::Climate, public Component {
optional<uint8_t> eco_id_{};
optional<uint8_t> sleep_id_{};
optional<float> eco_temperature_{};
TuyaDatapointType eco_type_{};
uint8_t active_state_;
uint8_t fan_state_;
optional<uint8_t> swing_vertical_id_{};

View File

@@ -99,26 +99,10 @@ void IDFUARTComponent::setup() {
}
void IDFUARTComponent::load_settings(bool dump_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
);
uart_config_t uart_config = this->get_config_();
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
if (err != ESP_OK) {
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
@@ -135,12 +119,10 @@ 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) {
@@ -156,6 +138,26 @@ 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));
@@ -171,32 +173,24 @@ 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); // per docs, must be called only after uart_driver_install()
err = uart_set_mode(this->uart_num_, mode);
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, "Reloaded UART %u", this->uart_num_);
ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_);
this->dump_config();
}
}
void IDFUARTComponent::dump_config() {
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
LOG_PIN(" TX Pin: ", this->tx_pin_);
LOG_PIN(" RX Pin: ", this->rx_pin_);
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
LOG_PIN(" TX Pin: ", tx_pin_);
LOG_PIN(" RX Pin: ", rx_pin_);
LOG_PIN(" Flow Control Pin: ", flow_control_pin_);
if (this->rx_pin_ != nullptr) {
ESP_LOGCONFIG(TAG,
" RX Buffer Size: %u\n"

View File

@@ -3,7 +3,7 @@ from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components.const import CONF_USE_PSRAM
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
from esphome.components.network import ip_address_literal
from esphome.components.network import IPAddress
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.config_validation import only_with_esp_idf
@@ -140,21 +140,17 @@ EAP_AUTH_SCHEMA = cv.All(
cv.has_at_least_one_key(CONF_IDENTITY, CONF_CERTIFICATE),
)
CONF_AP_TIMEOUT = "ap_timeout"
CONF_SSID_DATA_ID = "ssid_data_id"
CONF_PASSWORD_DATA_ID = "password_data_id"
WIFI_NETWORK_BASE = cv.Schema(
{
cv.GenerateID(): cv.declare_id(WiFiAP),
cv.GenerateID(CONF_SSID_DATA_ID): cv.declare_id(cg.uint8),
cv.GenerateID(CONF_PASSWORD_DATA_ID): cv.declare_id(cg.uint8),
cv.Optional(CONF_SSID): cv.ssid,
cv.Optional(CONF_PASSWORD): validate_password,
cv.Optional(CONF_CHANNEL): validate_channel,
cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA,
}
)
CONF_AP_TIMEOUT = "ap_timeout"
WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend(
{
cv.Optional(
@@ -338,7 +334,9 @@ def eap_auth(config):
def safe_ip(ip):
return ip_address_literal(ip)
if ip is None:
return IPAddress(0, 0, 0, 0)
return IPAddress(str(ip))
def manual_ip(config):
@@ -356,23 +354,9 @@ def manual_ip(config):
def wifi_network(config, ap, static_ip):
if CONF_SSID in config:
ssid = config[CONF_SSID]
if CORE.is_esp8266:
# On ESP8266, store SSID in PROGMEM to save RAM
prog_arr = cg.progmem_array(config[CONF_SSID_DATA_ID], ssid)
cg.add(ap.set_ssid_flash(prog_arr))
else:
# On ESP32/RP2040, string literals are already in rodata (flash)
cg.add(ap.set_ssid_flash(ssid))
cg.add(ap.set_ssid(config[CONF_SSID]))
if CONF_PASSWORD in config:
password = config[CONF_PASSWORD]
if CORE.is_esp8266:
# On ESP8266, store password in PROGMEM to save RAM
prog_arr = cg.progmem_array(config[CONF_PASSWORD_DATA_ID], password)
cg.add(ap.set_password_flash(prog_arr))
else:
# On ESP32/RP2040, string literals are already in rodata (flash)
cg.add(ap.set_password_flash(password))
cg.add(ap.set_password(config[CONF_PASSWORD]))
if CONF_EAP in config:
cg.add(ap.set_eap(eap_auth(config[CONF_EAP])))
cg.add_define("USE_WIFI_WPA2_EAP")

View File

@@ -888,68 +888,19 @@ void WiFiComponent::save_fast_connect_settings_() {
}
#endif
void WiFiAP::set_ssid(const std::string &ssid) {
this->ssid_storage_ = ssid;
this->ssid_ = this->ssid_storage_->c_str();
}
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; }
void WiFiAP::set_bssid(optional<bssid_t> bssid) { this->bssid_ = bssid; }
void WiFiAP::set_password(const std::string &password) {
this->password_storage_ = password;
this->password_ = this->password_storage_->c_str();
}
#ifdef USE_ESP8266
void WiFiAP::set_ssid_flash(const uint8_t *ssid) {
this->ssid_storage_.reset();
this->ssid_ = reinterpret_cast<const char *>(ssid);
}
void WiFiAP::set_password_flash(const uint8_t *password) {
this->password_storage_.reset();
this->password_ = reinterpret_cast<const char *>(password);
}
#else
void WiFiAP::set_ssid_flash(const char *ssid) {
this->ssid_storage_.reset();
this->ssid_ = ssid;
}
void WiFiAP::set_password_flash(const char *password) {
this->password_storage_.reset();
this->password_ = password;
}
#endif
void WiFiAP::set_password(const std::string &password) { this->password_ = password; }
#ifdef USE_WIFI_WPA2_EAP
void WiFiAP::set_eap(optional<EAPAuth> eap_auth) { this->eap_ = std::move(eap_auth); }
#endif
void WiFiAP::set_channel(optional<uint8_t> channel) { this->channel_ = channel; }
void WiFiAP::set_manual_ip(optional<ManualIP> manual_ip) { this->manual_ip_ = manual_ip; }
void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; }
std::string WiFiAP::get_ssid() const {
#ifdef USE_ESP8266
if (!this->ssid_storage_.has_value()) {
// Flash storage - read from PROGMEM
size_t len = strlen_P(this->ssid_);
std::string result;
result.resize(len);
memcpy_P(&result[0], this->ssid_, len);
return result;
}
#endif
return std::string(this->ssid_);
}
const std::string &WiFiAP::get_ssid() const { return this->ssid_; }
const optional<bssid_t> &WiFiAP::get_bssid() const { return this->bssid_; }
std::string WiFiAP::get_password() const {
#ifdef USE_ESP8266
if (!this->password_storage_.has_value()) {
// Flash storage - read from PROGMEM
size_t len = strlen_P(this->password_);
std::string result;
result.resize(len);
memcpy_P(&result[0], this->password_, len);
return result;
}
#endif
return std::string(this->password_);
}
const std::string &WiFiAP::get_password() const { return this->password_; }
#ifdef USE_WIFI_WPA2_EAP
const optional<EAPAuth> &WiFiAP::get_eap() const { return this->eap_; }
#endif

View File

@@ -135,18 +135,6 @@ class WiFiAP {
void set_bssid(bssid_t bssid);
void set_bssid(optional<bssid_t> bssid);
void set_password(const std::string &password);
/// Set SSID from flash string (PROGMEM on ESP8266, rodata on ESP32)
#ifdef USE_ESP8266
void set_ssid_flash(const uint8_t *ssid);
#else
void set_ssid_flash(const char *ssid);
#endif
/// Set password from flash string (PROGMEM on ESP8266, rodata on ESP32)
#ifdef USE_ESP8266
void set_password_flash(const uint8_t *password);
#else
void set_password_flash(const char *password);
#endif
#ifdef USE_WIFI_WPA2_EAP
void set_eap(optional<EAPAuth> eap_auth);
#endif // USE_WIFI_WPA2_EAP
@@ -154,9 +142,9 @@ class WiFiAP {
void set_priority(float priority) { priority_ = priority; }
void set_manual_ip(optional<ManualIP> manual_ip);
void set_hidden(bool hidden);
std::string get_ssid() const;
const std::string &get_ssid() const;
const optional<bssid_t> &get_bssid() const;
std::string get_password() const;
const std::string &get_password() const;
#ifdef USE_WIFI_WPA2_EAP
const optional<EAPAuth> &get_eap() const;
#endif // USE_WIFI_WPA2_EAP
@@ -166,10 +154,8 @@ class WiFiAP {
bool get_hidden() const;
protected:
const char *ssid_{nullptr};
const char *password_{nullptr};
optional<std::string> ssid_storage_;
optional<std::string> password_storage_;
std::string ssid_;
std::string password_;
optional<bssid_t> bssid_;
#ifdef USE_WIFI_WPA2_EAP
optional<EAPAuth> eap_;

View File

@@ -236,25 +236,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
struct station_config conf {};
memset(&conf, 0, sizeof(conf));
std::string ssid = ap.get_ssid();
std::string password = ap.get_password();
size_t ssid_len = ssid.length();
size_t password_len = password.length();
if (ssid_len > sizeof(conf.ssid)) {
if (ap.get_ssid().size() > sizeof(conf.ssid)) {
ESP_LOGE(TAG, "SSID too long");
return false;
}
if (password_len > sizeof(conf.password)) {
if (ap.get_password().size() > sizeof(conf.password)) {
ESP_LOGE(TAG, "Password too long");
return false;
}
memcpy(conf.ssid, ssid.c_str(), ssid_len);
memcpy(conf.password, password.c_str(), password_len);
memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
if (ap.get_bssid().has_value()) {
conf.bssid_set = 1;
@@ -800,35 +791,27 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
return false;
struct softap_config conf {};
std::string ssid = ap.get_ssid();
std::string password = ap.get_password();
size_t ssid_len = ssid.length();
size_t password_len = password.length();
if (ssid_len > sizeof(conf.ssid)) {
if (ap.get_ssid().size() > sizeof(conf.ssid)) {
ESP_LOGE(TAG, "AP SSID too long");
return false;
}
memcpy(conf.ssid, ssid.c_str(), ssid_len);
conf.ssid_len = static_cast<uint8>(ssid_len);
memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
conf.channel = ap.get_channel().value_or(1);
conf.ssid_hidden = ap.get_hidden();
conf.max_connection = 5;
conf.beacon_interval = 100;
if (password_len == 0) {
if (ap.get_password().empty()) {
conf.authmode = AUTH_OPEN;
*conf.password = 0;
} else {
conf.authmode = AUTH_WPA2_PSK;
if (password_len > sizeof(conf.password)) {
if (ap.get_password().size() > sizeof(conf.password)) {
ESP_LOGE(TAG, "AP password too long");
return false;
}
memcpy(conf.password, password.c_str(), password_len);
memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
}
ETS_UART_INTR_DISABLE();

View File

@@ -26,10 +26,10 @@ class ZephyrGPIOPin : public InternalGPIOPin {
protected:
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_{};
gpio::Flags flags_{};
const device *gpio_{nullptr};
bool value_{false};
bool inverted_;
gpio::Flags flags_;
const device *gpio_ = nullptr;
bool value_ = false;
};
} // namespace zephyr

View File

@@ -4,8 +4,6 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include <concepts>
#include <functional>
#include <utility>
#include <vector>
@@ -29,20 +27,11 @@ template<typename T, typename... X> class TemplatableValue {
public:
TemplatableValue() : type_(NONE) {}
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) {
new (&this->value_) T(std::move(value));
}
// For stateless lambdas (convertible to function pointer): use function pointer
template<typename F>
TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)>
: type_(STATELESS_LAMBDA) {
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas (not convertible to function pointer): use std::function
template<typename F>
TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) : type_(LAMBDA) {
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) {
this->f_ = new std::function<T(X...)>(std::move(f));
}
@@ -52,8 +41,6 @@ template<typename T, typename... X> class TemplatableValue {
new (&this->value_) T(other.value_);
} else if (type_ == LAMBDA) {
this->f_ = new std::function<T(X...)>(*other.f_);
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
}
@@ -64,8 +51,6 @@ template<typename T, typename... X> class TemplatableValue {
} else if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
@@ -93,23 +78,16 @@ template<typename T, typename... X> class TemplatableValue {
} else if (type_ == LAMBDA) {
delete this->f_;
}
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty, not heap-allocated)
}
bool has_value() { return this->type_ != NONE; }
T value(X... x) {
switch (this->type_) {
case STATELESS_LAMBDA:
return this->stateless_f_(x...); // Direct function pointer call
case LAMBDA:
return (*this->f_)(x...); // std::function call
case VALUE:
return this->value_;
case NONE:
default:
return T{};
if (this->type_ == LAMBDA) {
return (*this->f_)(x...);
}
// return value also when none
return this->type_ == VALUE ? this->value_ : T{};
}
optional<T> optional_value(X... x) {
@@ -131,13 +109,11 @@ template<typename T, typename... X> class TemplatableValue {
NONE,
VALUE,
LAMBDA,
STATELESS_LAMBDA,
} type_;
union {
T value_;
std::function<T(X...)> *f_;
T (*stateless_f_)(X...);
};
};

View File

@@ -79,18 +79,6 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
std::function<bool(Ts...)> f_;
};
/// Optimized lambda condition for stateless lambdas (no capture).
/// Uses function pointer instead of std::function to reduce memory overhead.
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
public:
explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
bool check(Ts... x) override { return this->f_(x...); }
protected:
bool (*f_)(Ts...);
};
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
public:
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
@@ -202,19 +190,6 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> {
std::function<void(Ts...)> f_;
};
/// Optimized lambda action for stateless lambdas (no capture).
/// Uses function pointer instead of std::function to reduce memory overhead.
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
public:
explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
void play(Ts... x) override { this->f_(x...); }
protected:
void (*f_)(Ts...);
};
template<typename... Ts> class IfAction : public Action<Ts...> {
public:
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}

View File

@@ -87,7 +87,6 @@
#define USE_MDNS_STORE_SERVICES
#define MDNS_SERVICE_COUNT 3
#define MDNS_DYNAMIC_TXT_COUNT 3
#define SNTP_SERVER_COUNT 3
#define USE_MEDIA_PLAYER
#define USE_NEXTION_TFT_UPLOAD
#define USE_NUMBER
@@ -123,7 +122,6 @@
#define USE_API_NOISE
#define USE_API_PLAINTEXT
#define USE_API_SERVICES
#define USE_API_CUSTOM_SERVICES
#define API_MAX_SEND_QUEUE 8
#define USE_MD5
#define USE_SHA256

View File

@@ -105,9 +105,7 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
config[CONF_NAME],
platform,
)
# Only set disabled_by_default if True (default is False)
if config[CONF_DISABLED_BY_DEFAULT]:
add(var.set_disabled_by_default(True))
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
add(var.set_internal(config[CONF_INTERNAL]))
if CONF_ICON in config:

View File

@@ -46,18 +46,24 @@ struct tm ESPTime::to_c_tm() {
return c_tm;
}
std::string ESPTime::strftime(const char *format) {
std::string ESPTime::strftime(const std::string &format) {
std::string timestr;
timestr.resize(format.size() * 4);
struct tm c_tm = this->to_c_tm();
char buf[128];
size_t len = ::strftime(buf, sizeof(buf), format, &c_tm);
if (len > 0) {
return std::string(buf, len);
size_t len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
while (len == 0) {
if (timestr.size() >= 128) {
// strftime has failed for reasons unrelated to the size of the buffer
// so return a formatting error
return "ERROR";
}
timestr.resize(timestr.size() * 2);
len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
}
return "ERROR";
timestr.resize(len);
return timestr;
}
std::string ESPTime::strftime(const std::string &format) { return this->strftime(format.c_str()); }
bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
uint16_t year;
uint8_t month;

View File

@@ -44,19 +44,17 @@ struct ESPTime {
size_t strftime(char *buffer, size_t buffer_len, const char *format);
/** Convert this ESPTime struct to a string as specified by the format argument.
* @see https://en.cppreference.com/w/c/chrono/strftime
* @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
*
* @warning This method returns a dynamically allocated string which can cause heap fragmentation with some
* @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
* microcontrollers.
*
* @warning This method can return "ERROR" when the underlying strftime() call fails or when the
* output exceeds 128 bytes.
* @warning This method can return "ERROR" when the underlying strftime() call fails, e.g. when the
* format string contains unsupported specifiers or when the format string doesn't produce any
* output.
*/
std::string strftime(const std::string &format);
/// @copydoc strftime(const std::string &format)
std::string strftime(const char *format);
/// Check if this ESPTime is valid (all fields in range and year is greater than 2018)
bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); }

View File

@@ -198,8 +198,6 @@ class LambdaExpression(Expression):
self.return_type = safe_exp(return_type) if return_type is not None else None
def __str__(self):
# Stateless lambdas (empty capture) implicitly convert to function pointers
# when assigned to function pointer types - no unary + needed
cpp = f"[{self.capture}]({self.parameters})"
if self.return_type is not None:
cpp += f" -> {self.return_type}"
@@ -702,12 +700,6 @@ async def process_lambda(
parts[i * 3 + 1] = var
parts[i * 3 + 2] = ""
# All id() references are global variables in generated C++ code.
# Global variables should not be captured - they're accessible everywhere.
# Use empty capture instead of capture-by-value.
if capture == "=":
capture = ""
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
location = value.esp_range.start_mark
location.line += value.content_offset

View File

@@ -23,7 +23,6 @@ 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

@@ -224,37 +224,36 @@ def resolve_ip_address(
return res
# Process hosts
cached_addresses: list[str] = []
uncached_hosts: list[str] = []
has_cache = address_cache is not None
for h in hosts:
if is_ip_address(h):
_add_ip_addresses_to_addrinfo([h], port, res)
if has_cache:
# If we have a cache, treat IPs as cached
cached_addresses.append(h)
else:
# If no cache, pass IPs through to resolver with hostnames
uncached_hosts.append(h)
elif address_cache and (cached := address_cache.get_addresses(h)):
_add_ip_addresses_to_addrinfo(cached, port, res)
# Found in cache
cached_addresses.extend(cached)
else:
# Not cached, need to resolve
if address_cache and address_cache.has_cache():
_LOGGER.info("Host %s not in cache, will need to resolve", h)
uncached_hosts.append(h)
# Process cached addresses (includes direct IPs and cached lookups)
_add_ip_addresses_to_addrinfo(cached_addresses, port, res)
# If we have uncached hosts (only non-IP hostnames), resolve them
if uncached_hosts:
from aioesphomeapi.host_resolver import AddrInfo as AioAddrInfo
from esphome.core import EsphomeError
from esphome.resolver import AsyncResolver
resolver = AsyncResolver(uncached_hosts, port)
addr_infos: list[AioAddrInfo] = []
try:
addr_infos = resolver.resolve()
except EsphomeError as err:
if not res:
# No pre-resolved addresses available, DNS resolution is fatal
raise
_LOGGER.info("%s (using %d already resolved IP addresses)", err, len(res))
addr_infos = resolver.resolve()
# Convert aioesphomeapi AddrInfo to our format
for addr_info in addr_infos:
sockaddr = addr_info.sockaddr

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.name, key_file.name)
context.load_cert_chain(cert_file, key_file)
client.tls_set_context(context)
try:

View File

@@ -19,9 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3",
"Topic :: Home Automation",
]
# Python 3.14 is currently not supported by IDF <= 5.5.1, see https://github.com/esphome/esphome/issues/11502
requires-python = ">=3.11.0,<3.14"
requires-python = ">=3.11.0"
dynamic = ["dependencies", "optional-dependencies", "version"]

View File

@@ -15,7 +15,7 @@ esphome-dashboard==20251013.0
aioesphomeapi==42.3.0
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.18.16 # dashboard_import
ruamel.yaml==0.18.15 # dashboard_import
ruamel.yaml.clib==0.2.14 # dashboard_import
esphome-glyphsets==0.2.0
pillow==11.3.0

View File

@@ -77,7 +77,6 @@ ISOLATED_COMPONENTS = {
"esphome": "Defines devices/areas in esphome: section that are referenced in other sections - breaks when merged",
"ethernet": "Defines ethernet: which conflicts with wifi: used by most components",
"ethernet_info": "Related to ethernet component which conflicts with wifi",
"gps": "TinyGPSPlus library declares millis() function that creates ambiguity with ESPHome millis() macro when merged with components using millis() in lambdas",
"lvgl": "Defines multiple SDL displays on host platform that conflict when merged with other display configs",
"mapping": "Uses dict format for image/display sections incompatible with standard list format - ESPHome merge_config cannot handle",
"openthread": "Conflicts with wifi: used by most components",

View File

@@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main):
def test_text_config_lamda_is_set(generate_main):
"""
Test if lambda is set for lambda mode (optimized with stateless lambda)
Test if lambda is set for lambda mode
"""
# Given
@@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main):
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
# Then
assert "it_4->set_template([]() -> esphome::optional<std::string> {" in main_cpp
assert "it_4->set_template([=]() -> esphome::optional<std::string> {" in main_cpp
assert 'return std::string{"Hello"};' in main_cpp

View File

@@ -6,14 +6,14 @@ sensor:
- platform: template
id: template_humidity
lambda: |-
if (millis() > 10000) {
if (esphome::millis() > 10000) {
return 0.6;
}
return 0.0;
- platform: template
id: template_temperature
lambda: |-
if (millis() > 10000) {
if (esphome::millis() > 10000) {
return 42.0;
}
return 0.0;

View File

@@ -3,7 +3,7 @@ sensor:
id: template_sensor
name: Template Sensor
lambda: |-
if (millis() > 10000) {
if (esphome::millis() > 10000) {
return 42.0;
}
return 0.0;

Some files were not shown because too many files have changed in this diff Show More