1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-31 23:21:54 +00:00

Merge branch 'dev' into template_value_func_pointers

This commit is contained in:
J. Nick Koston
2025-10-27 14:38:22 -05:00
committed by GitHub
22 changed files with 167 additions and 31 deletions

View File

@@ -16,7 +16,12 @@ from esphome.const import (
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.core import ID from esphome.core import ID
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType from esphome.cpp_generator import (
LambdaExpression,
MockObj,
MockObjClass,
TemplateArgsType,
)
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType from esphome.types import ConfigType
from esphome.util import Registry from esphome.util import Registry
@@ -87,6 +92,7 @@ def validate_potentially_or_condition(value):
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
@@ -97,9 +103,40 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation") Automation = cg.esphome_ns.class_("Automation")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) 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): def validate_automation(extra_schema=None, extra_validators=None, single=False):
if extra_schema is None: if extra_schema is None:
extra_schema = {} extra_schema = {}
@@ -240,7 +277,9 @@ async def lambda_condition_to_code(
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool) lambda_ = await cg.process_lambda(config, args, return_type=bool)
return cg.new_Pvariable(condition_id, template_arg, lambda_) return new_lambda_pvariable(
condition_id, lambda_, StatelessLambdaCondition, template_arg
)
@register_condition( @register_condition(
@@ -406,7 +445,7 @@ async def lambda_action_to_code(
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void) lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
@register_action( @register_action(

View File

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

View File

@@ -111,6 +111,21 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_; 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 { class SettleFilter : public Filter, public Component {
public: public:
optional<bool> new_value(bool value) override; optional<bool> new_value(bool value) override;

View File

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

View File

@@ -223,7 +223,10 @@ async def esp32_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}"))) cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
cg.add(var.set_inverted(config[CONF_INVERTED])) # 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))
if CONF_DRIVE_STRENGTH in config: if CONF_DRIVE_STRENGTH in config:
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH])) cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) 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; void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_; uint8_t pin_;
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace esp8266 } // namespace esp8266

View File

@@ -165,7 +165,10 @@ async def esp8266_pin_to_code(config):
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
mode = config[CONF_MODE] mode = config[CONF_MODE]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # 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_flags(pins.gpio_flags_expr(mode))) cg.add(var.set_flags(pins.gpio_flags_expr(mode)))
if num < 16: if num < 16:
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][ initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][

View File

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

View File

@@ -57,6 +57,9 @@ async def host_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # 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_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var return var

View File

@@ -199,6 +199,9 @@ async def component_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # 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_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var 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; void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_; uint8_t pin_;
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace libretiny } // namespace libretiny

View File

@@ -1,7 +1,7 @@
import re import re
from esphome import automation from esphome import automation
from esphome.automation import LambdaAction from esphome.automation import LambdaAction, StatelessLambdaAction
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
@@ -430,7 +430,9 @@ 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_))) 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) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
@automation.register_action( @automation.register_action(
@@ -455,7 +457,9 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
text = str(cg.statement(logger.set_log_level(level))) text = str(cg.statement(logger.set_log_level(level)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
FILTER_SOURCE_FILES = filter_source_files_from_platform( FILTER_SOURCE_FILES = filter_source_files_from_platform(

View File

@@ -74,6 +74,9 @@ async def nrf52_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # 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_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var return var

View File

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

View File

@@ -94,6 +94,9 @@ async def rp2040_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # 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_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var return var

View File

@@ -261,6 +261,7 @@ ExponentialMovingAverageFilter = sensor_ns.class_(
) )
ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component)
LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) LambdaFilter = sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter)
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter) ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
@@ -573,7 +574,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(float, "x")], return_type=cg.optional.template(float) config, [(float, "x")], return_type=cg.optional.template(float)
) )
return cg.new_Pvariable(filter_id, lambda_) return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
DELTA_SCHEMA = cv.Schema( DELTA_SCHEMA = cv.Schema(

View File

@@ -296,6 +296,21 @@ class LambdaFilter : public Filter {
lambda_filter_t lambda_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. /// A simple filter that adds `offset` to each value it receives.
class OffsetFilter : public Filter { class OffsetFilter : public Filter {
public: public:

View File

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

View File

@@ -62,6 +62,21 @@ class LambdaFilter : public Filter {
lambda_filter_t lambda_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 /// A simple filter that converts all text to uppercase
class ToUpperFilter : public Filter { class ToUpperFilter : public Filter {
public: public:

View File

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

View File

@@ -79,6 +79,18 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
std::function<bool(Ts...)> f_; 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 { template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
public: public:
explicit ForCondition(Condition<> *condition) : condition_(condition) {} explicit ForCondition(Condition<> *condition) : condition_(condition) {}
@@ -190,6 +202,19 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> {
std::function<void(Ts...)> f_; 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...> { template<typename... Ts> class IfAction : public Action<Ts...> {
public: public:
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {} explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}

View File

@@ -19,7 +19,9 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Topic :: Home Automation", "Topic :: Home Automation",
] ]
requires-python = ">=3.11.0"
# 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"
dynamic = ["dependencies", "optional-dependencies", "version"] dynamic = ["dependencies", "optional-dependencies", "version"]