mirror of
https://github.com/esphome/esphome.git
synced 2025-11-09 19:41:49 +00:00
Compare commits
29 Commits
usb_memory
...
ci_test_om
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a84895774 | ||
|
|
b1166b916f | ||
|
|
08b8454555 | ||
|
|
0119e17f04 | ||
|
|
c3f40de844 | ||
|
|
7dd829cfca | ||
|
|
da19673f51 | ||
|
|
f5e32d03d0 | ||
|
|
f3b69383fd | ||
|
|
aba72809d3 | ||
|
|
85205a28d2 | ||
|
|
285e006637 | ||
|
|
5647f36900 | ||
|
|
1e9309ffff | ||
|
|
ce8a6a6c43 | ||
|
|
dfb4b31bf9 | ||
|
|
31b1b50ad9 | ||
|
|
3377080272 | ||
|
|
d65ad69338 | ||
|
|
dfa69173ea | ||
|
|
f44615cc8d | ||
|
|
bda4769bd3 | ||
|
|
14b057f54e | ||
|
|
e26b5874d7 | ||
|
|
00f22e5c36 | ||
|
|
51e080c2d3 | ||
|
|
3c18558003 | ||
|
|
7394cbf773 | ||
|
|
1577a46efd |
@@ -16,7 +16,12 @@ from esphome.const import (
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
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.types import ConfigType
|
||||
from esphome.util import Registry
|
||||
@@ -87,6 +92,7 @@ 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)
|
||||
@@ -97,9 +103,40 @@ 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 = {}
|
||||
@@ -240,7 +277,9 @@ async def lambda_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
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(
|
||||
@@ -406,7 +445,7 @@ async def lambda_action_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
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(
|
||||
|
||||
@@ -62,6 +62,7 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
EntityBase,
|
||||
EntityCategory,
|
||||
ESPTime,
|
||||
FixedVector,
|
||||
GPIOPin,
|
||||
InternalGPIOPin,
|
||||
JsonObject,
|
||||
|
||||
@@ -71,10 +71,12 @@ SERVICE_ARG_NATIVE_TYPES = {
|
||||
"int": cg.int32,
|
||||
"float": float,
|
||||
"string": cg.std_string,
|
||||
"bool[]": cg.std_vector.template(bool),
|
||||
"int[]": cg.std_vector.template(cg.int32),
|
||||
"float[]": cg.std_vector.template(float),
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
"bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
|
||||
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
||||
"float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
|
||||
"string[]": cg.FixedVector.template(cg.std_string)
|
||||
.operator("const")
|
||||
.operator("ref"),
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_BATCH_DELAY = "batch_delay"
|
||||
@@ -258,6 +260,10 @@ 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")
|
||||
|
||||
@@ -265,6 +271,8 @@ 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 = []
|
||||
@@ -278,8 +286,10 @@ async def to_code(config):
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
triggers.append(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")
|
||||
|
||||
@@ -125,8 +125,14 @@ 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
|
||||
|
||||
@@ -53,8 +53,14 @@ 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>
|
||||
@@ -86,8 +92,14 @@ 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) {
|
||||
|
||||
@@ -11,23 +11,58 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
|
||||
}
|
||||
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||
|
||||
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
|
||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end());
|
||||
std::vector<bool> result;
|
||||
result.reserve(arg.bool_array.size());
|
||||
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
|
||||
return result;
|
||||
}
|
||||
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
|
||||
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end());
|
||||
std::vector<int32_t> result;
|
||||
result.reserve(arg.int_array.size());
|
||||
result.insert(result.end(), arg.int_array.begin(), arg.int_array.end());
|
||||
return result;
|
||||
}
|
||||
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||
return std::vector<float>(arg.float_array.begin(), arg.float_array.end());
|
||||
std::vector<float> result;
|
||||
result.reserve(arg.float_array.size());
|
||||
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
|
||||
return result;
|
||||
}
|
||||
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
||||
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end());
|
||||
std::vector<std::string> result;
|
||||
result.reserve(arg.string_array.size());
|
||||
result.insert(result.end(), arg.string_array.begin(), arg.string_array.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
// New FixedVector const reference versions for YAML-generated services - zero-copy
|
||||
template<>
|
||||
const FixedVector<bool> &get_execute_arg_value<const FixedVector<bool> &>(const ExecuteServiceArgument &arg) {
|
||||
return arg.bool_array;
|
||||
}
|
||||
template<>
|
||||
const FixedVector<int32_t> &get_execute_arg_value<const FixedVector<int32_t> &>(const ExecuteServiceArgument &arg) {
|
||||
return arg.int_array;
|
||||
}
|
||||
template<>
|
||||
const FixedVector<float> &get_execute_arg_value<const FixedVector<float> &>(const ExecuteServiceArgument &arg) {
|
||||
return arg.float_array;
|
||||
}
|
||||
template<>
|
||||
const FixedVector<std::string> &get_execute_arg_value<const FixedVector<std::string> &>(
|
||||
const ExecuteServiceArgument &arg) {
|
||||
return arg.string_array;
|
||||
}
|
||||
|
||||
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
||||
|
||||
// Legacy std::vector versions for external components using custom_api_device.h
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
|
||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
||||
@@ -39,4 +74,18 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
|
||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||
}
|
||||
|
||||
// New FixedVector const reference versions for YAML-generated services
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<bool> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_BOOL_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<int32_t> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<float> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<std::string> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -155,6 +155,7 @@ 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__)
|
||||
@@ -299,7 +300,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 cg.new_Pvariable(filter_id, lambda_)
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
|
||||
|
||||
@register_filter(
|
||||
|
||||
@@ -111,6 +111,21 @@ 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;
|
||||
|
||||
@@ -96,8 +96,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
BLEClientWriteAction(BLEClient *ble_client) {
|
||||
ble_client->register_ble_node(this);
|
||||
ble_client_ = ble_client;
|
||||
this->construct_simple_value_();
|
||||
}
|
||||
|
||||
~BLEClientWriteAction() { this->destroy_simple_value_(); }
|
||||
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
@@ -106,14 +109,18 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
this->destroy_simple_value_();
|
||||
this->value_.template_func = func;
|
||||
this->has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const std::vector<uint8_t> &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
if (!this->has_simple_value_) {
|
||||
this->construct_simple_value_();
|
||||
}
|
||||
this->value_.simple = value;
|
||||
this->has_simple_value_ = true;
|
||||
}
|
||||
|
||||
void play(Ts... x) override {}
|
||||
@@ -121,7 +128,7 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
void play_complex(Ts... x) override {
|
||||
this->num_running_++;
|
||||
this->var_ = std::make_tuple(x...);
|
||||
auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...);
|
||||
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
|
||||
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
||||
if (!write(value))
|
||||
this->play_next_(x...);
|
||||
@@ -194,10 +201,22 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
}
|
||||
|
||||
private:
|
||||
void construct_simple_value_() { new (&this->value_.simple) std::vector<uint8_t>(); }
|
||||
|
||||
void destroy_simple_value_() {
|
||||
if (this->has_simple_value_) {
|
||||
this->value_.simple.~vector();
|
||||
}
|
||||
}
|
||||
|
||||
BLEClient *ble_client_;
|
||||
bool has_simple_value_ = true;
|
||||
std::vector<uint8_t> value_simple_;
|
||||
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
|
||||
union Value {
|
||||
std::vector<uint8_t> simple;
|
||||
std::vector<uint8_t> (*template_func)(Ts...);
|
||||
Value() {} // trivial constructor
|
||||
~Value() {} // trivial destructor - we manage lifetime via discriminator
|
||||
} value_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
std::tuple<Ts...> var_{};
|
||||
@@ -213,9 +232,9 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
|
||||
void play(Ts... x) override {
|
||||
uint32_t passkey;
|
||||
if (has_simple_value_) {
|
||||
passkey = this->value_simple_;
|
||||
passkey = this->value_.simple;
|
||||
} else {
|
||||
passkey = this->value_template_(x...);
|
||||
passkey = this->value_.template_func(x...);
|
||||
}
|
||||
if (passkey > 999999)
|
||||
return;
|
||||
@@ -224,21 +243,23 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
|
||||
esp_ble_passkey_reply(remote_bda, true, passkey);
|
||||
}
|
||||
|
||||
void set_value_template(std::function<uint32_t(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
void set_value_template(uint32_t (*func)(Ts...)) {
|
||||
this->value_.template_func = func;
|
||||
this->has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const uint32_t &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
this->value_.simple = value;
|
||||
this->has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
bool has_simple_value_ = true;
|
||||
uint32_t value_simple_{0};
|
||||
std::function<uint32_t(Ts...)> value_template_{};
|
||||
union {
|
||||
uint32_t simple;
|
||||
uint32_t (*template_func)(Ts...);
|
||||
} value_{.simple = 0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
|
||||
@@ -249,27 +270,29 @@ template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Ac
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
if (has_simple_value_) {
|
||||
esp_ble_confirm_reply(remote_bda, this->value_simple_);
|
||||
esp_ble_confirm_reply(remote_bda, this->value_.simple);
|
||||
} else {
|
||||
esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
|
||||
esp_ble_confirm_reply(remote_bda, this->value_.template_func(x...));
|
||||
}
|
||||
}
|
||||
|
||||
void set_value_template(std::function<bool(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
void set_value_template(bool (*func)(Ts...)) {
|
||||
this->value_.template_func = func;
|
||||
this->has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const bool &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
this->value_.simple = value;
|
||||
this->has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
bool has_simple_value_ = true;
|
||||
bool value_simple_{false};
|
||||
std::function<bool(Ts...)> value_template_{};
|
||||
union {
|
||||
bool simple;
|
||||
bool (*template_func)(Ts...);
|
||||
} value_{.simple = false};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
|
||||
|
||||
@@ -117,9 +117,9 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
}
|
||||
|
||||
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
||||
if (this->data_to_value_func_.has_value()) {
|
||||
if (this->has_data_to_value_) {
|
||||
std::vector<uint8_t> data(value, value + value_len);
|
||||
return (*this->data_to_value_func_)(data);
|
||||
return this->data_to_value_func_(data);
|
||||
} else {
|
||||
return value[0];
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
|
||||
|
||||
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
void loop() override;
|
||||
@@ -33,13 +31,17 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
|
||||
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
|
||||
void set_data_to_value(float (*lambda)(const std::vector<uint8_t> &)) {
|
||||
this->data_to_value_func_ = lambda;
|
||||
this->has_data_to_value_ = true;
|
||||
}
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
float parse_data_(uint8_t *value, uint16_t value_len);
|
||||
optional<data_to_value_t> data_to_value_func_{};
|
||||
bool has_data_to_value_{false};
|
||||
float (*data_to_value_func_)(const std::vector<uint8_t> &){};
|
||||
bool notify_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -223,7 +223,10 @@ 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}")))
|
||||
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:
|
||||
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
|
||||
@@ -461,7 +461,9 @@ async def parse_value(value_config, args):
|
||||
if isinstance(value, str):
|
||||
value = list(value.encode(value_config[CONF_STRING_ENCODING]))
|
||||
if isinstance(value, list):
|
||||
return cg.std_vector.template(cg.uint8)(value)
|
||||
# Generate initializer list {1, 2, 3} instead of std::vector<uint8_t>({1, 2, 3})
|
||||
# This calls the set_value(std::initializer_list<uint8_t>) overload
|
||||
return cg.ArrayInitializer(*value)
|
||||
val = cg.RawExpression(f"{value_config[CONF_TYPE]}({cg.safe_exp(value)})")
|
||||
return ByteBuffer_ns.wrap(val, value_config[CONF_ENDIANNESS])
|
||||
|
||||
|
||||
@@ -35,13 +35,18 @@ BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties)
|
||||
|
||||
void BLECharacteristic::set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); }
|
||||
|
||||
void BLECharacteristic::set_value(const std::vector<uint8_t> &buffer) {
|
||||
void BLECharacteristic::set_value(std::vector<uint8_t> &&buffer) {
|
||||
xSemaphoreTake(this->set_value_lock_, 0L);
|
||||
this->value_ = buffer;
|
||||
this->value_ = std::move(buffer);
|
||||
xSemaphoreGive(this->set_value_lock_);
|
||||
}
|
||||
|
||||
void BLECharacteristic::set_value(std::initializer_list<uint8_t> data) {
|
||||
this->set_value(std::vector<uint8_t>(data)); // Delegate to move overload
|
||||
}
|
||||
|
||||
void BLECharacteristic::set_value(const std::string &buffer) {
|
||||
this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end()));
|
||||
this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end())); // Delegate to move overload
|
||||
}
|
||||
|
||||
void BLECharacteristic::notify() {
|
||||
|
||||
@@ -33,7 +33,8 @@ class BLECharacteristic {
|
||||
~BLECharacteristic();
|
||||
|
||||
void set_value(ByteBuffer buffer);
|
||||
void set_value(const std::vector<uint8_t> &buffer);
|
||||
void set_value(std::vector<uint8_t> &&buffer);
|
||||
void set_value(std::initializer_list<uint8_t> data);
|
||||
void set_value(const std::string &buffer);
|
||||
|
||||
void set_broadcast_property(bool value);
|
||||
|
||||
@@ -46,15 +46,17 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
|
||||
this->state_ = CREATING;
|
||||
}
|
||||
|
||||
void BLEDescriptor::set_value(std::vector<uint8_t> buffer) {
|
||||
size_t length = buffer.size();
|
||||
void BLEDescriptor::set_value(std::vector<uint8_t> &&buffer) { this->set_value_impl_(buffer.data(), buffer.size()); }
|
||||
|
||||
void BLEDescriptor::set_value(std::initializer_list<uint8_t> data) { this->set_value_impl_(data.begin(), data.size()); }
|
||||
|
||||
void BLEDescriptor::set_value_impl_(const uint8_t *data, size_t length) {
|
||||
if (length > this->value_.attr_max_len) {
|
||||
ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", length, this->value_.attr_max_len);
|
||||
return;
|
||||
}
|
||||
this->value_.attr_len = length;
|
||||
memcpy(this->value_.attr_value, buffer.data(), length);
|
||||
memcpy(this->value_.attr_value, data, length);
|
||||
}
|
||||
|
||||
void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
|
||||
@@ -27,7 +27,8 @@ class BLEDescriptor {
|
||||
void do_create(BLECharacteristic *characteristic);
|
||||
ESPBTUUID get_uuid() const { return this->uuid_; }
|
||||
|
||||
void set_value(std::vector<uint8_t> buffer);
|
||||
void set_value(std::vector<uint8_t> &&buffer);
|
||||
void set_value(std::initializer_list<uint8_t> data);
|
||||
void set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); }
|
||||
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
@@ -42,6 +43,8 @@ class BLEDescriptor {
|
||||
}
|
||||
|
||||
protected:
|
||||
void set_value_impl_(const uint8_t *data, size_t length);
|
||||
|
||||
BLECharacteristic *characteristic_{nullptr};
|
||||
ESPBTUUID uuid_;
|
||||
uint16_t handle_{0xFFFF};
|
||||
|
||||
@@ -270,8 +270,8 @@ void ESP32ImprovComponent::set_error_(improv::Error error) {
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
|
||||
this->rpc_response_->set_value(ByteBuffer::wrap(response));
|
||||
void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &&response) {
|
||||
this->rpc_response_->set_value(std::move(response));
|
||||
if (this->state_ != improv::STATE_STOPPED)
|
||||
this->rpc_response_->notify();
|
||||
}
|
||||
@@ -409,10 +409,8 @@ void ESP32ImprovComponent::check_wifi_connection_() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Pass to build_rpc_response using vector constructor from iterators to avoid extra copies
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(
|
||||
improv::WIFI_SETTINGS, std::vector<std::string>(url_strings, url_strings + url_count));
|
||||
this->send_response_(data);
|
||||
this->send_response_(improv::build_rpc_response(improv::WIFI_SETTINGS,
|
||||
std::vector<std::string>(url_strings, url_strings + url_count)));
|
||||
} else if (this->is_active() && this->state_ != improv::STATE_PROVISIONED) {
|
||||
ESP_LOGD(TAG, "WiFi provisioned externally");
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
|
||||
void set_state_(improv::State state, bool update_advertising = true);
|
||||
void set_error_(improv::Error error);
|
||||
improv::State get_initial_state_() const;
|
||||
void send_response_(std::vector<uint8_t> &response);
|
||||
void send_response_(std::vector<uint8_t> &&response);
|
||||
void process_incoming_data_();
|
||||
void on_wifi_connect_timeout_();
|
||||
void check_wifi_connection_();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -165,7 +165,10 @@ async def esp8266_pin_to_code(config):
|
||||
num = config[CONF_NUMBER]
|
||||
mode = config[CONF_MODE]
|
||||
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)))
|
||||
if num < 16:
|
||||
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][
|
||||
|
||||
@@ -14,7 +14,7 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.network import IPAddress
|
||||
from esphome.components.network import ip_address_literal
|
||||
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", 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]))),
|
||||
("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])),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,6 +57,9 @@ async def host_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
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])))
|
||||
return var
|
||||
|
||||
@@ -124,7 +124,7 @@ class HttpRequestComponent : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
||||
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
||||
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
|
||||
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
|
||||
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
|
||||
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
||||
@@ -173,7 +173,7 @@ class HttpRequestComponent : public Component {
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
uint16_t redirect_limit_{};
|
||||
uint16_t timeout_{4500};
|
||||
uint32_t timeout_{4500};
|
||||
uint32_t watchdog_timeout_{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -199,6 +199,9 @@ async def component_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
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])))
|
||||
return var
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,9 +57,9 @@ class AddressableLightEffect : public LightEffect {
|
||||
|
||||
class AddressableLambdaLightEffect : public AddressableLightEffect {
|
||||
public:
|
||||
AddressableLambdaLightEffect(const char *name, std::function<void(AddressableLight &, Color, bool initial_run)> f,
|
||||
AddressableLambdaLightEffect(const char *name, void (*f)(AddressableLight &, Color, bool initial_run),
|
||||
uint32_t update_interval)
|
||||
: AddressableLightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
|
||||
: AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
|
||||
void start() override { this->initial_run_ = true; }
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
@@ -72,7 +72,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<void(AddressableLight &, Color, bool initial_run)> f_;
|
||||
void (*f_)(AddressableLight &, Color, bool initial_run);
|
||||
uint32_t update_interval_;
|
||||
uint32_t last_run_{0};
|
||||
bool initial_run_;
|
||||
|
||||
@@ -112,8 +112,8 @@ class RandomLightEffect : public LightEffect {
|
||||
|
||||
class LambdaLightEffect : public LightEffect {
|
||||
public:
|
||||
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) {}
|
||||
LambdaLightEffect(const char *name, void (*f)(bool initial_run), uint32_t update_interval)
|
||||
: LightEffect(name), f_(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:
|
||||
std::function<void(bool initial_run)> f_;
|
||||
void (*f_)(bool initial_run);
|
||||
uint32_t update_interval_;
|
||||
uint32_t last_run_{0};
|
||||
bool initial_run_;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import re
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import LambdaAction
|
||||
from esphome.automation import LambdaAction, StatelessLambdaAction
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
||||
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_)))
|
||||
|
||||
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(
|
||||
@@ -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)))
|
||||
|
||||
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(
|
||||
|
||||
@@ -33,8 +33,8 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor,
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
using transform_func_t = std::function<optional<bool>(ModbusBinarySensor *, bool, const std::vector<uint8_t> &)>;
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
using transform_func_t = optional<bool> (*)(ModbusBinarySensor *, bool, const std::vector<uint8_t> &);
|
||||
void set_template(transform_func_t f) { this->transform_func_ = f; }
|
||||
|
||||
protected:
|
||||
optional<transform_func_t> transform_func_{nullopt};
|
||||
|
||||
@@ -31,10 +31,10 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
|
||||
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
||||
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
|
||||
|
||||
using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>;
|
||||
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
using transform_func_t = optional<float> (*)(ModbusNumber *, float, const std::vector<uint8_t> &);
|
||||
using write_transform_func_t = optional<float> (*)(ModbusNumber *, float, std::vector<uint16_t> &);
|
||||
void set_template(transform_func_t f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -29,8 +29,8 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
|
||||
// Do nothing
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override{};
|
||||
|
||||
using write_transform_func_t = std::function<optional<float>(ModbusFloatOutput *, float, std::vector<uint16_t> &)>;
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
using write_transform_func_t = optional<float> (*)(ModbusFloatOutput *, float, std::vector<uint16_t> &);
|
||||
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
@@ -60,8 +60,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
|
||||
// Do nothing
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override{};
|
||||
|
||||
using write_transform_func_t = std::function<optional<bool>(ModbusBinaryOutput *, bool, std::vector<uint8_t> &)>;
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
using write_transform_func_t = optional<bool> (*)(ModbusBinaryOutput *, bool, std::vector<uint8_t> &);
|
||||
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -26,16 +26,15 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
|
||||
this->mapping_ = std::move(mapping);
|
||||
}
|
||||
|
||||
using transform_func_t =
|
||||
std::function<optional<std::string>(ModbusSelect *const, int64_t, const std::vector<uint8_t> &)>;
|
||||
using write_transform_func_t =
|
||||
std::function<optional<int64_t>(ModbusSelect *const, const std::string &, int64_t, std::vector<uint16_t> &)>;
|
||||
using transform_func_t = optional<std::string> (*)(ModbusSelect *const, int64_t, const std::vector<uint8_t> &);
|
||||
using write_transform_func_t = optional<int64_t> (*)(ModbusSelect *const, const std::string &, int64_t,
|
||||
std::vector<uint16_t> &);
|
||||
|
||||
void set_parent(ModbusController *const parent) { this->parent_ = parent; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
void set_template(transform_func_t f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
|
||||
|
||||
void dump_config() override;
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
|
||||
@@ -25,9 +25,9 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem
|
||||
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
void dump_config() override;
|
||||
using transform_func_t = std::function<optional<float>(ModbusSensor *, float, const std::vector<uint8_t> &)>;
|
||||
using transform_func_t = optional<float> (*)(ModbusSensor *, float, const std::vector<uint8_t> &);
|
||||
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
void set_template(transform_func_t f) { this->transform_func_ = f; }
|
||||
|
||||
protected:
|
||||
optional<transform_func_t> transform_func_{nullopt};
|
||||
|
||||
@@ -34,10 +34,10 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
||||
|
||||
using transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, const std::vector<uint8_t> &)>;
|
||||
using write_transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, std::vector<uint8_t> &)>;
|
||||
void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
using transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, const std::vector<uint8_t> &);
|
||||
using write_transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, std::vector<uint8_t> &);
|
||||
void set_template(transform_func_t f) { this->publish_transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -30,9 +30,8 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi
|
||||
void dump_config() override;
|
||||
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
using transform_func_t =
|
||||
std::function<optional<std::string>(ModbusTextSensor *, std::string, const std::vector<uint8_t> &)>;
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
using transform_func_t = optional<std::string> (*)(ModbusTextSensor *, std::string, const std::vector<uint8_t> &);
|
||||
void set_template(transform_func_t f) { this->transform_func_ = f; }
|
||||
|
||||
protected:
|
||||
optional<transform_func_t> transform_func_{nullopt};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ipaddress
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
import esphome.config_validation as cv
|
||||
@@ -10,6 +12,41 @@ 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(
|
||||
|
||||
@@ -540,6 +540,23 @@ 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.
|
||||
|
||||
@@ -45,6 +45,7 @@ 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;
|
||||
|
||||
|
||||
@@ -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::hide_component(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component);
|
||||
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::show_component(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", 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::enable_component_touch(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);
|
||||
|
||||
@@ -81,13 +81,11 @@ void NextionComponent::update_component_settings(bool force_update) {
|
||||
|
||||
this->component_flags_.visible_needs_update = false;
|
||||
|
||||
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());
|
||||
this->nextion_->set_component_visibility(name_to_send.c_str(), this->component_flags_.visible);
|
||||
if (!this->component_flags_.visible) {
|
||||
return;
|
||||
}
|
||||
this->send_state_to_nextion();
|
||||
}
|
||||
|
||||
if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) {
|
||||
|
||||
@@ -174,11 +174,6 @@ 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
|
||||
|
||||
@@ -177,11 +177,6 @@ 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
|
||||
|
||||
@@ -74,6 +74,9 @@ async def nrf52_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
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])))
|
||||
return var
|
||||
|
||||
@@ -252,7 +252,10 @@ async def setup_number_core_(
|
||||
cg.add(var.traits.set_max_value(max_value))
|
||||
cg.add(var.traits.set_step(step))
|
||||
|
||||
cg.add(var.traits.set_mode(config[CONF_MODE]))
|
||||
# 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]))
|
||||
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -17,6 +17,7 @@ from esphome.const import (
|
||||
CONF_FAMILY,
|
||||
CONF_GROUP,
|
||||
CONF_ID,
|
||||
CONF_INDEX,
|
||||
CONF_INVERTED,
|
||||
CONF_LEVEL,
|
||||
CONF_MAGNITUDE,
|
||||
@@ -616,6 +617,49 @@ async def dooya_action(var, config, args):
|
||||
cg.add(var.set_check(template_))
|
||||
|
||||
|
||||
# Dyson
|
||||
DysonData, DysonBinarySensor, DysonTrigger, DysonAction, DysonDumper = declare_protocol(
|
||||
"Dyson"
|
||||
)
|
||||
DYSON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CODE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_INDEX, default=0xFF): cv.hex_uint8_t,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("dyson", DysonBinarySensor, DYSON_SCHEMA)
|
||||
def dyson_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
DysonData,
|
||||
("code", config[CONF_CODE]),
|
||||
("index", config[CONF_INDEX]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("dyson", DysonTrigger, DysonData)
|
||||
def dyson_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("dyson", DysonDumper)
|
||||
def dyson_dumper(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_action("dyson", DysonAction, DYSON_SCHEMA)
|
||||
async def dyson_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_CODE], args, cg.uint16)
|
||||
cg.add(var.set_code(template_))
|
||||
template_ = await cg.templatable(config[CONF_INDEX], args, cg.uint8)
|
||||
cg.add(var.set_index(template_))
|
||||
|
||||
|
||||
# JVC
|
||||
JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol("JVC")
|
||||
JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t})
|
||||
|
||||
71
esphome/components/remote_base/dyson_protocol.cpp
Normal file
71
esphome/components/remote_base/dyson_protocol.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "dyson_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.dyson";
|
||||
|
||||
// pulsewidth [µs]
|
||||
constexpr uint32_t PW_MARK_US = 780;
|
||||
constexpr uint32_t PW_SHORT_US = 720;
|
||||
constexpr uint32_t PW_LONG_US = 1500;
|
||||
constexpr uint32_t PW_START_US = 2280;
|
||||
|
||||
// MSB of 15 bit dyson code
|
||||
constexpr uint16_t MSB_DYSON = (1 << 14);
|
||||
|
||||
// required symbols in transmit buffer = (start_symbol + 15 data_symbols)
|
||||
constexpr uint32_t N_SYMBOLS_REQ = 2u * (1 + 15);
|
||||
|
||||
void DysonProtocol::encode(RemoteTransmitData *dst, const DysonData &data) {
|
||||
uint32_t raw_code = (data.code << 2) + (data.index & 3);
|
||||
dst->set_carrier_frequency(36000);
|
||||
dst->reserve(N_SYMBOLS_REQ + 1);
|
||||
dst->item(PW_START_US, PW_SHORT_US);
|
||||
for (uint16_t mask = MSB_DYSON; mask != 0; mask >>= 1) {
|
||||
if (mask == (mask & raw_code)) {
|
||||
dst->item(PW_MARK_US, PW_LONG_US);
|
||||
} else {
|
||||
dst->item(PW_MARK_US, PW_SHORT_US);
|
||||
}
|
||||
}
|
||||
dst->mark(PW_MARK_US); // final carrier pulse
|
||||
}
|
||||
|
||||
optional<DysonData> DysonProtocol::decode(RemoteReceiveData src) {
|
||||
uint32_t n_received = static_cast<uint32_t>(src.size());
|
||||
uint16_t raw_code = 0;
|
||||
DysonData data{
|
||||
.code = 0,
|
||||
.index = 0,
|
||||
};
|
||||
if (n_received < N_SYMBOLS_REQ)
|
||||
return {}; // invalid frame length
|
||||
if (!src.expect_item(PW_START_US, PW_SHORT_US))
|
||||
return {}; // start not found
|
||||
for (uint16_t mask = MSB_DYSON; mask != 0; mask >>= 1) {
|
||||
if (src.expect_item(PW_MARK_US, PW_SHORT_US)) {
|
||||
raw_code &= ~mask; // zero detected
|
||||
} else if (src.expect_item(PW_MARK_US, PW_LONG_US)) {
|
||||
raw_code |= mask; // one detected
|
||||
} else {
|
||||
return {}; // invalid data item
|
||||
}
|
||||
}
|
||||
data.code = raw_code >> 2; // extract button code
|
||||
data.index = raw_code & 3; // extract rolling index
|
||||
if (src.expect_mark(PW_MARK_US)) { // check total length
|
||||
return data;
|
||||
}
|
||||
return {}; // frame not complete
|
||||
}
|
||||
|
||||
void DysonProtocol::dump(const DysonData &data) {
|
||||
ESP_LOGI(TAG, "Dyson: code=0x%x rolling index=%d", data.code, data.index);
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
46
esphome/components/remote_base/dyson_protocol.h
Normal file
46
esphome/components/remote_base/dyson_protocol.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "remote_base.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static constexpr uint8_t IGNORE_INDEX = 0xFF;
|
||||
|
||||
struct DysonData {
|
||||
uint16_t code; // the button, e.g. power, swing, fan++, ...
|
||||
uint8_t index; // the rolling index counter
|
||||
bool operator==(const DysonData &rhs) const {
|
||||
if (IGNORE_INDEX == index || IGNORE_INDEX == rhs.index) {
|
||||
return code == rhs.code;
|
||||
}
|
||||
return code == rhs.code && index == rhs.index;
|
||||
}
|
||||
};
|
||||
|
||||
class DysonProtocol : public RemoteProtocol<DysonData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const DysonData &data) override;
|
||||
optional<DysonData> decode(RemoteReceiveData src) override;
|
||||
void dump(const DysonData &data) override;
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Dyson)
|
||||
|
||||
template<typename... Ts> class DysonAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, code)
|
||||
TEMPLATABLE_VALUE(uint8_t, index)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
DysonData data{};
|
||||
data.code = this->code_.value(x...);
|
||||
data.index = this->index_.value(x...);
|
||||
DysonProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
@@ -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
|
||||
|
||||
@@ -94,6 +94,9 @@ async def rp2040_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
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])))
|
||||
return var
|
||||
|
||||
@@ -261,6 +261,7 @@ 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)
|
||||
@@ -573,7 +574,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 cg.new_Pvariable(filter_id, lambda_)
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
|
||||
|
||||
DELTA_SCHEMA = cv.Schema(
|
||||
|
||||
@@ -296,6 +296,21 @@ 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:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import logging
|
||||
from re import Match
|
||||
from typing import Any
|
||||
|
||||
from esphome import core
|
||||
from esphome.config_helpers import Extend, Remove, merge_config, merge_dicts_ordered
|
||||
@@ -39,7 +41,34 @@ async def to_code(config):
|
||||
pass
|
||||
|
||||
|
||||
def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
|
||||
def _restore_data_base(value: Any, orig_value: ESPHomeDataBase) -> ESPHomeDataBase:
|
||||
"""This function restores ESPHomeDataBase metadata held by the original string.
|
||||
This is needed because during jinja evaluation, strings can be replaced by other types,
|
||||
but we want to keep the original metadata for error reporting and source mapping.
|
||||
For example, if a substitution replaces a string with a dictionary, we want that items
|
||||
in the dictionary to still point to the original document location
|
||||
"""
|
||||
if isinstance(value, ESPHomeDataBase):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
_restore_data_base(k, orig_value): _restore_data_base(v, orig_value)
|
||||
for k, v in value.items()
|
||||
}
|
||||
if isinstance(value, list):
|
||||
return [_restore_data_base(v, orig_value) for v in value]
|
||||
if isinstance(value, str):
|
||||
return make_data_base(value, orig_value)
|
||||
return value
|
||||
|
||||
|
||||
def _expand_jinja(
|
||||
value: str | JinjaStr,
|
||||
orig_value: str | JinjaStr,
|
||||
path,
|
||||
jinja: Jinja,
|
||||
ignore_missing: bool,
|
||||
) -> Any:
|
||||
if has_jinja(value):
|
||||
# If the original value passed in to this function is a JinjaStr, it means it contains an unresolved
|
||||
# Jinja expression from a previous pass.
|
||||
@@ -65,10 +94,17 @@ def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
|
||||
f"\nSee {'->'.join(str(x) for x in path)}",
|
||||
path,
|
||||
)
|
||||
# If the original, unexpanded string, contained document metadata (ESPHomeDatabase),
|
||||
# assign this same document metadata to the resulting value.
|
||||
if isinstance(orig_value, ESPHomeDataBase):
|
||||
value = _restore_data_base(value, orig_value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
||||
def _expand_substitutions(
|
||||
substitutions: dict, value: str, path, jinja: Jinja, ignore_missing: bool
|
||||
) -> Any:
|
||||
if "$" not in value:
|
||||
return value
|
||||
|
||||
@@ -76,14 +112,14 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
m = cv.VARIABLE_PROG.search(value, i)
|
||||
m: Match[str] = cv.VARIABLE_PROG.search(value, i)
|
||||
if not m:
|
||||
# No more variable substitutions found. See if the remainder looks like a jinja template
|
||||
value = _expand_jinja(value, orig_value, path, jinja, ignore_missing)
|
||||
break
|
||||
|
||||
i, j = m.span(0)
|
||||
name = m.group(1)
|
||||
name: str = m.group(1)
|
||||
if name.startswith("{") and name.endswith("}"):
|
||||
name = name[1:-1]
|
||||
if name not in substitutions:
|
||||
@@ -98,7 +134,7 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
||||
i = j
|
||||
continue
|
||||
|
||||
sub = substitutions[name]
|
||||
sub: Any = substitutions[name]
|
||||
|
||||
if i == 0 and j == len(value):
|
||||
# The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly
|
||||
@@ -121,7 +157,13 @@ def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
||||
return value
|
||||
|
||||
|
||||
def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
||||
def _substitute_item(
|
||||
substitutions: dict,
|
||||
item: Any,
|
||||
path: list[int | str],
|
||||
jinja: Jinja,
|
||||
ignore_missing: bool,
|
||||
) -> Any | None:
|
||||
if isinstance(item, ESPLiteralValue):
|
||||
return None # do not substitute inside literal blocks
|
||||
if isinstance(item, list):
|
||||
@@ -160,7 +202,9 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
||||
return None
|
||||
|
||||
|
||||
def do_substitution_pass(config, command_line_substitutions, ignore_missing=False):
|
||||
def do_substitution_pass(
|
||||
config: dict, command_line_substitutions: dict, ignore_missing: bool = False
|
||||
) -> None:
|
||||
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
||||
return
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
from ast import literal_eval
|
||||
from collections.abc import Iterator
|
||||
from itertools import chain, islice
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from types import GeneratorType
|
||||
from typing import Any
|
||||
|
||||
import jinja2 as jinja
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
from jinja2.nativetypes import NativeCodeGenerator, NativeTemplate
|
||||
|
||||
from esphome.yaml_util import ESPLiteralValue
|
||||
|
||||
@@ -24,7 +28,7 @@ detect_jinja_re = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def has_jinja(st):
|
||||
def has_jinja(st: str) -> bool:
|
||||
return detect_jinja_re.search(st) is not None
|
||||
|
||||
|
||||
@@ -109,12 +113,56 @@ class TrackerContext(jinja.runtime.Context):
|
||||
return val
|
||||
|
||||
|
||||
class Jinja(SandboxedEnvironment):
|
||||
def _concat_nodes_override(values: Iterator[Any]) -> Any:
|
||||
"""
|
||||
This function customizes how Jinja preserves native types when concatenating
|
||||
multiple result nodes together. If the result is a single node, its value
|
||||
is returned. Otherwise, the nodes are concatenated as strings. If
|
||||
the result can be parsed with `ast.literal_eval`, the parsed
|
||||
value is returned. Otherwise, the string is returned.
|
||||
This helps preserve metadata such as ESPHomeDataBase from original values
|
||||
and mimicks how HomeAssistant deals with template evaluation and preserving
|
||||
the original datatype.
|
||||
"""
|
||||
head: list[Any] = list(islice(values, 2))
|
||||
|
||||
if not head:
|
||||
return None
|
||||
|
||||
if len(head) == 1:
|
||||
raw = head[0]
|
||||
if not isinstance(raw, str):
|
||||
return raw
|
||||
else:
|
||||
if isinstance(values, GeneratorType):
|
||||
values = chain(head, values)
|
||||
raw = "".join([str(v) for v in values])
|
||||
|
||||
try:
|
||||
# Attempt to parse the concatenated string into a Python literal.
|
||||
# This allows expressions like "1 + 2" to be evaluated to the integer 3.
|
||||
# If the result is also a string or there is a parsing error,
|
||||
# fall back to returning the raw string. This is consistent with
|
||||
# Home Assistant's behavior when evaluating templates
|
||||
result = literal_eval(raw)
|
||||
if not isinstance(result, str):
|
||||
return result
|
||||
|
||||
except (ValueError, SyntaxError, MemoryError, TypeError):
|
||||
pass
|
||||
return raw
|
||||
|
||||
|
||||
class Jinja(jinja.Environment):
|
||||
"""
|
||||
Wraps a Jinja environment
|
||||
"""
|
||||
|
||||
def __init__(self, context_vars):
|
||||
# jinja environment customization overrides
|
||||
code_generator_class = NativeCodeGenerator
|
||||
concat = staticmethod(_concat_nodes_override)
|
||||
|
||||
def __init__(self, context_vars: dict):
|
||||
super().__init__(
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
@@ -142,19 +190,10 @@ class Jinja(SandboxedEnvironment):
|
||||
**SAFE_GLOBALS,
|
||||
}
|
||||
|
||||
def safe_eval(self, expr):
|
||||
try:
|
||||
result = literal_eval(expr)
|
||||
if not isinstance(result, str):
|
||||
return result
|
||||
except (ValueError, SyntaxError, MemoryError, TypeError):
|
||||
pass
|
||||
return expr
|
||||
|
||||
def expand(self, content_str):
|
||||
def expand(self, content_str: str | JinjaStr) -> Any:
|
||||
"""
|
||||
Renders a string that may contain Jinja expressions or statements
|
||||
Returns the resulting processed string if all values could be resolved.
|
||||
Returns the resulting value if all variables and expressions could be resolved.
|
||||
Otherwise, it returns a tagged (JinjaStr) string that captures variables
|
||||
in scope (upvalues), like a closure for later evaluation.
|
||||
"""
|
||||
@@ -172,7 +211,7 @@ class Jinja(SandboxedEnvironment):
|
||||
self.context_trace = {}
|
||||
try:
|
||||
template = self.from_string(content_str)
|
||||
result = self.safe_eval(template.render(override_vars))
|
||||
result = template.render(override_vars)
|
||||
if isinstance(result, Undefined):
|
||||
print("" + result) # force a UndefinedError exception
|
||||
except (TemplateSyntaxError, UndefinedError) as err:
|
||||
@@ -201,3 +240,10 @@ class Jinja(SandboxedEnvironment):
|
||||
content_str.result = result
|
||||
|
||||
return result, None
|
||||
|
||||
|
||||
class JinjaTemplate(NativeTemplate):
|
||||
environment_class = Jinja
|
||||
|
||||
|
||||
Jinja.template_class = JinjaTemplate
|
||||
|
||||
@@ -38,8 +38,14 @@ 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)
|
||||
f"return {condition.check()};",
|
||||
[],
|
||||
return_type=cg.optional.template(bool),
|
||||
capture="",
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ static const char *const TAG = "template.binary_sensor";
|
||||
void TemplateBinarySensor::setup() { this->loop(); }
|
||||
|
||||
void TemplateBinarySensor::loop() {
|
||||
if (this->f_ == nullptr)
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto s = this->f_();
|
||||
auto s = (*this->f_)();
|
||||
if (s.has_value()) {
|
||||
this->publish_state(*s);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace template_ {
|
||||
|
||||
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
|
||||
void set_template(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:
|
||||
std::function<optional<bool>()> f_{nullptr};
|
||||
optional<optional<bool> (*)()> f_;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -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(std::function<optional<float>()> &&f) { this->state_f_ = f; }
|
||||
void TemplateCover::set_state_lambda(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(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; }
|
||||
void TemplateCover::set_tilt_lambda(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; }
|
||||
|
||||
@@ -17,7 +17,7 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
public:
|
||||
TemplateCover();
|
||||
|
||||
void set_state_lambda(std::function<optional<float>()> &&f);
|
||||
void set_state_lambda(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(std::function<optional<float>()> &&tilt_f);
|
||||
void set_tilt_lambda(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<std::function<optional<float>()>> state_f_;
|
||||
optional<std::function<optional<float>()>> tilt_f_;
|
||||
optional<optional<float> (*)()> state_f_;
|
||||
optional<optional<float> (*)()> tilt_f_;
|
||||
bool assumed_state_{false};
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace template_ {
|
||||
|
||||
class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||
void set_template(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<std::function<optional<ESPTime>()>> f_;
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace template_ {
|
||||
|
||||
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||
void set_template(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<std::function<optional<ESPTime>()>> f_;
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace template_ {
|
||||
|
||||
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||
void set_template(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<std::function<optional<ESPTime>()>> f_;
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -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(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
|
||||
void TemplateLock::set_state_lambda(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_; }
|
||||
|
||||
@@ -13,7 +13,7 @@ class TemplateLock : public lock::Lock, public Component {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
|
||||
void set_state_lambda(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<std::function<optional<lock::LockState>()>> f_;
|
||||
optional<optional<lock::LockState> (*)()> f_;
|
||||
bool optimistic_{false};
|
||||
Trigger<> *lock_trigger_;
|
||||
Trigger<> *unlock_trigger_;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace template_ {
|
||||
|
||||
class TemplateNumber : public number::Number, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||
void set_template(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<std::function<optional<float>()>> f_;
|
||||
optional<optional<float> (*)()> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -73,11 +73,18 @@ async def to_code(config):
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION]))
|
||||
# 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))
|
||||
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
||||
# Only set if True (default is False)
|
||||
if config.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(True))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -10,26 +10,21 @@ void TemplateSelect::setup() {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
|
||||
std::string value;
|
||||
if (!this->restore_value_) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
||||
} else {
|
||||
size_t index;
|
||||
size_t index = this->initial_option_index_;
|
||||
if (this->restore_value_) {
|
||||
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());
|
||||
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 {
|
||||
value = this->at(index).value();
|
||||
ESP_LOGD(TAG, "State from restore: %s", value.c_str());
|
||||
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->at(index).value().c_str());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial: %s", this->at(index).value().c_str());
|
||||
}
|
||||
|
||||
this->publish_state(value);
|
||||
this->publish_state(this->at(index).value());
|
||||
}
|
||||
|
||||
void TemplateSelect::update() {
|
||||
@@ -69,7 +64,8 @@ void TemplateSelect::dump_config() {
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(this->optimistic_), this->initial_option_.c_str(), YESNO(this->restore_value_));
|
||||
YESNO(this->optimistic_), this->at(this->initial_option_index_).value().c_str(),
|
||||
YESNO(this->restore_value_));
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace template_ {
|
||||
|
||||
class TemplateSelect : public select::Select, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
void set_template(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(const std::string &initial_option) { this->initial_option_ = initial_option; }
|
||||
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
bool optimistic_ = false;
|
||||
std::string initial_option_;
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<std::function<optional<std::string>()>> f_;
|
||||
optional<optional<std::string> (*)()> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ void TemplateSensor::update() {
|
||||
}
|
||||
}
|
||||
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||
void TemplateSensor::set_template(optional<float> (*f)()) { this->f_ = f; }
|
||||
void TemplateSensor::dump_config() {
|
||||
LOG_SENSOR("", "Template Sensor", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace template_ {
|
||||
|
||||
class TemplateSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<float>()> &&f);
|
||||
void set_template(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<std::function<optional<float>()>> f_;
|
||||
optional<optional<float> (*)()> f_;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -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(std::function<optional<bool>()> &&f) { this->f_ = f; }
|
||||
void TemplateSwitch::set_state_lambda(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_; }
|
||||
|
||||
@@ -14,7 +14,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(std::function<optional<bool>()> &&f);
|
||||
void set_state_lambda(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<std::function<optional<bool>()>> f_;
|
||||
optional<optional<bool> (*)()> f_;
|
||||
bool optimistic_{false};
|
||||
bool assumed_state_{false};
|
||||
Trigger<> *turn_on_trigger_;
|
||||
|
||||
@@ -61,7 +61,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
|
||||
|
||||
class TemplateText : public text::Text, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
void set_template(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<std::function<optional<std::string>()>> f_{nullptr};
|
||||
optional<optional<std::string> (*)()> f_{nullptr};
|
||||
|
||||
TemplateTextSaverBase *pref_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ void TemplateTextSensor::update() {
|
||||
}
|
||||
}
|
||||
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
void TemplateTextSensor::set_template(optional<std::string> (*f)()) { this->f_ = f; }
|
||||
void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); }
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace template_ {
|
||||
|
||||
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
||||
public:
|
||||
void set_template(std::function<optional<std::string>()> &&f);
|
||||
void set_template(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<std::function<optional<std::string>()>> f_{};
|
||||
optional<optional<std::string> (*)()> f_{};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -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(std::function<optional<float>()> &&f) { this->state_f_ = f; }
|
||||
void TemplateValve::set_state_lambda(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_; }
|
||||
|
||||
@@ -17,7 +17,7 @@ class TemplateValve : public valve::Valve, public Component {
|
||||
public:
|
||||
TemplateValve();
|
||||
|
||||
void set_state_lambda(std::function<optional<float>()> &&f);
|
||||
void set_state_lambda(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<std::function<optional<float>()>> state_f_;
|
||||
optional<optional<float> (*)()> state_f_;
|
||||
bool assumed_state_{false};
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
|
||||
@@ -57,6 +57,7 @@ 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)
|
||||
@@ -70,7 +71,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 cg.new_Pvariable(filter_id, lambda_)
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {})
|
||||
|
||||
@@ -62,6 +62,21 @@ 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:
|
||||
|
||||
@@ -67,7 +67,9 @@ 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_();
|
||||
@@ -176,7 +178,11 @@ 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));
|
||||
this->parent_->set_boolean_datapoint_value(*this->eco_id_, 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);
|
||||
}
|
||||
}
|
||||
if (this->sleep_id_.has_value()) {
|
||||
const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP;
|
||||
|
||||
@@ -104,6 +104,7 @@ 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_{};
|
||||
|
||||
@@ -99,10 +99,26 @@ void IDFUARTComponent::setup() {
|
||||
}
|
||||
|
||||
void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
uart_config_t uart_config = this->get_config_();
|
||||
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
|
||||
esp_err_t err;
|
||||
|
||||
if (uart_is_driver_installed(this->uart_num_)) {
|
||||
err = uart_driver_delete(this->uart_num_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
err = uart_driver_install(this->uart_num_, // UART number
|
||||
this->rx_buffer_size_, // RX ring buffer size
|
||||
0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
|
||||
// block task until all data has been sent out
|
||||
20, // event queue size/depth
|
||||
&this->uart_event_queue_, // event queue
|
||||
0 // Flags used to allocate the interrupt
|
||||
);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -119,10 +135,12 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
||||
|
||||
uint32_t invert = 0;
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_TXD_INV;
|
||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
|
||||
}
|
||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_RXD_INV;
|
||||
}
|
||||
|
||||
err = uart_set_line_inverse(this->uart_num_, invert);
|
||||
if (err != ESP_OK) {
|
||||
@@ -138,26 +156,6 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uart_is_driver_installed(this->uart_num_)) {
|
||||
uart_driver_delete(this->uart_num_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
err = uart_driver_install(this->uart_num_, /* UART RX ring buffer size. */ this->rx_buffer_size_,
|
||||
/* UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will
|
||||
block task until all data have been sent out.*/
|
||||
0,
|
||||
/* UART event queue size/depth. */ 20, &(this->uart_event_queue_),
|
||||
/* Flags used to allocate the interrupt. */ 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
||||
@@ -173,24 +171,32 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
}
|
||||
|
||||
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
|
||||
err = uart_set_mode(this->uart_num_, mode);
|
||||
err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install()
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uart_config_t uart_config = this->get_config_();
|
||||
err = uart_param_config(this->uart_num_, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dump_config) {
|
||||
ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_);
|
||||
ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
|
||||
this->dump_config();
|
||||
}
|
||||
}
|
||||
|
||||
void IDFUARTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
||||
LOG_PIN(" TX Pin: ", tx_pin_);
|
||||
LOG_PIN(" RX Pin: ", rx_pin_);
|
||||
LOG_PIN(" Flow Control Pin: ", flow_control_pin_);
|
||||
LOG_PIN(" TX Pin: ", this->tx_pin_);
|
||||
LOG_PIN(" RX Pin: ", this->rx_pin_);
|
||||
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
|
||||
if (this->rx_pin_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" RX Buffer Size: %u\n"
|
||||
|
||||
@@ -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 IPAddress
|
||||
from esphome.components.network import ip_address_literal
|
||||
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
|
||||
@@ -334,9 +334,7 @@ def eap_auth(config):
|
||||
|
||||
|
||||
def safe_ip(ip):
|
||||
if ip is None:
|
||||
return IPAddress(0, 0, 0, 0)
|
||||
return IPAddress(str(ip))
|
||||
return ip_address_literal(ip)
|
||||
|
||||
|
||||
def manual_ip(config):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -27,11 +29,20 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
public:
|
||||
TemplatableValue() : type_(NONE) {}
|
||||
|
||||
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) {
|
||||
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
|
||||
new (&this->value_) T(std::move(value));
|
||||
}
|
||||
|
||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) {
|
||||
// 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) {
|
||||
this->f_ = new std::function<T(X...)>(std::move(f));
|
||||
}
|
||||
|
||||
@@ -41,6 +52,8 @@ 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_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +64,8 @@ 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;
|
||||
}
|
||||
@@ -78,16 +93,23 @@ 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) {
|
||||
if (this->type_ == LAMBDA) {
|
||||
return (*this->f_)(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{};
|
||||
}
|
||||
// return value also when none
|
||||
return this->type_ == VALUE ? this->value_ : T{};
|
||||
}
|
||||
|
||||
optional<T> optional_value(X... x) {
|
||||
@@ -109,11 +131,13 @@ 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...);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -79,6 +79,18 @@ 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) {}
|
||||
@@ -190,6 +202,19 @@ 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) {}
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
#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
|
||||
|
||||
@@ -46,24 +46,18 @@ struct tm ESPTime::to_c_tm() {
|
||||
return c_tm;
|
||||
}
|
||||
|
||||
std::string ESPTime::strftime(const std::string &format) {
|
||||
std::string timestr;
|
||||
timestr.resize(format.size() * 4);
|
||||
std::string ESPTime::strftime(const char *format) {
|
||||
struct tm c_tm = this->to_c_tm();
|
||||
size_t len = ::strftime(×tr[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(×tr[0], timestr.size(), format.c_str(), &c_tm);
|
||||
char buf[128];
|
||||
size_t len = ::strftime(buf, sizeof(buf), format, &c_tm);
|
||||
if (len > 0) {
|
||||
return std::string(buf, len);
|
||||
}
|
||||
timestr.resize(len);
|
||||
return timestr;
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -44,17 +44,19 @@ 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://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
|
||||
* @see https://en.cppreference.com/w/c/chrono/strftime
|
||||
*
|
||||
* @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
|
||||
* @warning This method returns a dynamically allocated string which can cause heap fragmentation with some
|
||||
* microcontrollers.
|
||||
*
|
||||
* @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.
|
||||
* @warning This method can return "ERROR" when the underlying strftime() call fails or when the
|
||||
* output exceeds 128 bytes.
|
||||
*/
|
||||
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(); }
|
||||
|
||||
|
||||
@@ -198,6 +198,8 @@ 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}"
|
||||
@@ -700,6 +702,12 @@ 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
|
||||
|
||||
@@ -23,6 +23,7 @@ size_t = global_ns.namespace("size_t")
|
||||
const_char_ptr = global_ns.namespace("const char *")
|
||||
NAN = global_ns.namespace("NAN")
|
||||
esphome_ns = global_ns # using namespace esphome;
|
||||
FixedVector = esphome_ns.class_("FixedVector")
|
||||
App = esphome_ns.App
|
||||
EntityBase = esphome_ns.class_("EntityBase")
|
||||
Component = esphome_ns.class_("Component")
|
||||
|
||||
@@ -120,7 +120,7 @@ def prepare(
|
||||
cert_file.flush()
|
||||
key_file.write(config[CONF_MQTT].get(CONF_CLIENT_CERTIFICATE_KEY))
|
||||
key_file.flush()
|
||||
context.load_cert_chain(cert_file, key_file)
|
||||
context.load_cert_chain(cert_file.name, key_file.name)
|
||||
client.tls_set_context(context)
|
||||
|
||||
try:
|
||||
|
||||
@@ -19,7 +19,9 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"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"]
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ import sys
|
||||
from typing import Any
|
||||
|
||||
from helpers import (
|
||||
BASE_BUS_COMPONENTS,
|
||||
CPP_FILE_EXTENSIONS,
|
||||
PYTHON_FILE_EXTENSIONS,
|
||||
changed_files,
|
||||
@@ -453,7 +452,7 @@ def detect_memory_impact_config(
|
||||
# Get actually changed files (not dependencies)
|
||||
files = changed_files(branch)
|
||||
|
||||
# Find all changed components (excluding core and base bus components)
|
||||
# Find all changed components (excluding core)
|
||||
# Also collect platform hints from platform-specific filenames
|
||||
changed_component_set: set[str] = set()
|
||||
has_core_cpp_changes = False
|
||||
@@ -462,13 +461,13 @@ def detect_memory_impact_config(
|
||||
for file in files:
|
||||
component = get_component_from_path(file)
|
||||
if component:
|
||||
# Skip base bus components as they're used across many builds
|
||||
if component not in BASE_BUS_COMPONENTS:
|
||||
changed_component_set.add(component)
|
||||
# Check if this is a platform-specific file
|
||||
platform_hint = _detect_platform_hint_from_filename(file)
|
||||
if platform_hint:
|
||||
platform_hints.append(platform_hint)
|
||||
# Add all changed components, including base bus components
|
||||
# Base bus components (uart, i2c, spi, etc.) should still be analyzed
|
||||
# when directly changed, even though they're also used as dependencies
|
||||
changed_component_set.add(component)
|
||||
# Check if this is a platform-specific file
|
||||
if platform_hint := _detect_platform_hint_from_filename(file):
|
||||
platform_hints.append(platform_hint)
|
||||
elif file.startswith("esphome/") and file.endswith(CPP_FILE_EXTENSIONS):
|
||||
# Core ESPHome C++ files changed (not component-specific)
|
||||
# Only C++ files affect memory usage
|
||||
|
||||
@@ -90,16 +90,18 @@ def get_component_from_path(file_path: str) -> str | None:
|
||||
"""Extract component name from a file path.
|
||||
|
||||
Args:
|
||||
file_path: Path to a file (e.g., "esphome/components/wifi/wifi.cpp")
|
||||
file_path: Path to a file (e.g., "esphome/components/wifi/wifi.cpp"
|
||||
or "tests/components/uart/test.esp32-idf.yaml")
|
||||
|
||||
Returns:
|
||||
Component name if path is in components directory, None otherwise
|
||||
Component name if path is in components or tests directory, None otherwise
|
||||
"""
|
||||
if not file_path.startswith(ESPHOME_COMPONENTS_PATH):
|
||||
return None
|
||||
parts = file_path.split("/")
|
||||
if len(parts) >= 3:
|
||||
return parts[2]
|
||||
if file_path.startswith(ESPHOME_COMPONENTS_PATH) or file_path.startswith(
|
||||
ESPHOME_TESTS_COMPONENTS_PATH
|
||||
):
|
||||
parts = file_path.split("/")
|
||||
if len(parts) >= 3 and parts[2]:
|
||||
return parts[2]
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
Test if lambda is set for lambda mode (optimized with stateless lambda)
|
||||
"""
|
||||
# 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
|
||||
|
||||
@@ -3,3 +3,52 @@ esp32_ble_tracker:
|
||||
ble_client:
|
||||
- mac_address: 01:02:03:04:05:06
|
||||
id: test_blec
|
||||
on_connect:
|
||||
- ble_client.ble_write:
|
||||
id: test_blec
|
||||
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
|
||||
characteristic_uuid: "abcd1235-abcd-1234-abcd-abcd12345678"
|
||||
value: !lambda |-
|
||||
return std::vector<uint8_t>{0x01, 0x02, 0x03};
|
||||
- ble_client.ble_write:
|
||||
id: test_blec
|
||||
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
|
||||
characteristic_uuid: "abcd1235-abcd-1234-abcd-abcd12345678"
|
||||
value: [0x04, 0x05, 0x06]
|
||||
on_passkey_request:
|
||||
- ble_client.passkey_reply:
|
||||
id: test_blec
|
||||
passkey: !lambda |-
|
||||
return 123456;
|
||||
- ble_client.passkey_reply:
|
||||
id: test_blec
|
||||
passkey: 654321
|
||||
on_numeric_comparison_request:
|
||||
- ble_client.numeric_comparison_reply:
|
||||
id: test_blec
|
||||
accept: !lambda |-
|
||||
return true;
|
||||
- ble_client.numeric_comparison_reply:
|
||||
id: test_blec
|
||||
accept: false
|
||||
|
||||
sensor:
|
||||
- platform: ble_client
|
||||
ble_client_id: test_blec
|
||||
type: characteristic
|
||||
id: test_sensor_lambda
|
||||
name: "BLE Sensor with Lambda"
|
||||
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
|
||||
characteristic_uuid: "abcd1236-abcd-1234-abcd-abcd12345678"
|
||||
lambda: |-
|
||||
if (x.size() >= 2) {
|
||||
return (float)(x[0] | (x[1] << 8)) / 100.0;
|
||||
}
|
||||
return NAN;
|
||||
- platform: ble_client
|
||||
ble_client_id: test_blec
|
||||
type: characteristic
|
||||
id: test_sensor_no_lambda
|
||||
name: "BLE Sensor without Lambda"
|
||||
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
|
||||
characteristic_uuid: "abcd1237-abcd-1234-abcd-abcd12345678"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# comment
|
||||
|
||||
modbus:
|
||||
id: mod_bus1
|
||||
flow_control_pin: ${flow_control_pin}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user