mirror of
https://github.com/esphome/esphome.git
synced 2025-11-03 16:41:50 +00:00
Compare commits
1 Commits
wifi_ssid_
...
qualify_mi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9b08491cc |
@@ -16,12 +16,7 @@ from esphome.const import (
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import (
|
||||
LambdaExpression,
|
||||
MockObj,
|
||||
MockObjClass,
|
||||
TemplateArgsType,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import Registry
|
||||
@@ -92,7 +87,6 @@ def validate_potentially_or_condition(value):
|
||||
|
||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
|
||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||
@@ -103,40 +97,9 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
|
||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||
|
||||
|
||||
def new_lambda_pvariable(
|
||||
id_obj: ID,
|
||||
lambda_expr: LambdaExpression,
|
||||
stateless_class: MockObjClass,
|
||||
template_arg: cg.TemplateArguments | None = None,
|
||||
) -> MockObj:
|
||||
"""Create Pvariable for lambda, using stateless class if applicable.
|
||||
|
||||
Combines ID selection and Pvariable creation in one call. For stateless
|
||||
lambdas (empty capture), uses function pointer instead of std::function.
|
||||
|
||||
Args:
|
||||
id_obj: The ID object (action_id, condition_id, or filter_id)
|
||||
lambda_expr: The lambda expression object
|
||||
stateless_class: The stateless class to use for stateless lambdas
|
||||
template_arg: Optional template arguments (for actions/conditions)
|
||||
|
||||
Returns:
|
||||
The created Pvariable
|
||||
"""
|
||||
# For stateless lambdas, use function pointer instead of std::function
|
||||
if lambda_expr.capture == "":
|
||||
id_obj = id_obj.copy()
|
||||
id_obj.type = stateless_class
|
||||
|
||||
if template_arg is not None:
|
||||
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
|
||||
return cg.new_Pvariable(id_obj, lambda_expr)
|
||||
|
||||
|
||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
if extra_schema is None:
|
||||
extra_schema = {}
|
||||
@@ -277,9 +240,7 @@ async def lambda_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||
return new_lambda_pvariable(
|
||||
condition_id, lambda_, StatelessLambdaCondition, template_arg
|
||||
)
|
||||
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_condition(
|
||||
@@ -445,7 +406,7 @@ async def lambda_action_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
|
||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_action(
|
||||
|
||||
@@ -62,7 +62,6 @@ from esphome.cpp_types import ( # noqa: F401
|
||||
EntityBase,
|
||||
EntityCategory,
|
||||
ESPTime,
|
||||
FixedVector,
|
||||
GPIOPin,
|
||||
InternalGPIOPin,
|
||||
JsonObject,
|
||||
|
||||
@@ -71,12 +71,10 @@ SERVICE_ARG_NATIVE_TYPES = {
|
||||
"int": cg.int32,
|
||||
"float": float,
|
||||
"string": cg.std_string,
|
||||
"bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
|
||||
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
||||
"float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
|
||||
"string[]": cg.FixedVector.template(cg.std_string)
|
||||
.operator("const")
|
||||
.operator("ref"),
|
||||
"bool[]": cg.std_vector.template(bool),
|
||||
"int[]": cg.std_vector.template(cg.int32),
|
||||
"float[]": cg.std_vector.template(float),
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_BATCH_DELAY = "batch_delay"
|
||||
@@ -260,10 +258,6 @@ async def to_code(config):
|
||||
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
||||
cg.add_define("USE_API_SERVICES")
|
||||
|
||||
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
|
||||
if config[CONF_CUSTOM_SERVICES]:
|
||||
cg.add_define("USE_API_CUSTOM_SERVICES")
|
||||
|
||||
if config[CONF_HOMEASSISTANT_SERVICES]:
|
||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||
|
||||
@@ -271,8 +265,6 @@ async def to_code(config):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_STATES")
|
||||
|
||||
if actions := config.get(CONF_ACTIONS, []):
|
||||
# Collect all triggers first, then register all at once with initializer_list
|
||||
triggers: list[cg.Pvariable] = []
|
||||
for conf in actions:
|
||||
template_args = []
|
||||
func_args = []
|
||||
@@ -286,10 +278,8 @@ async def to_code(config):
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
||||
)
|
||||
triggers.append(trigger)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
# Register all services at once - single allocation, no reallocations
|
||||
cg.add(var.initialize_user_services(triggers))
|
||||
|
||||
if CONF_ON_CLIENT_CONNECTED in config:
|
||||
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
|
||||
|
||||
@@ -125,14 +125,8 @@ class APIServer : public Component, public Controller {
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_API_SERVICES
|
||||
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
|
||||
this->user_services_.assign(services);
|
||||
}
|
||||
#ifdef USE_API_CUSTOM_SERVICES
|
||||
// Only compile push_back method when custom_services: true (external components)
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void request_time();
|
||||
#endif
|
||||
|
||||
@@ -53,14 +53,8 @@ class CustomAPIDevice {
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
#ifdef USE_API_CUSTOM_SERVICES
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
#else
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<typename T, typename... Ts>
|
||||
@@ -92,14 +86,8 @@ class CustomAPIDevice {
|
||||
*/
|
||||
#ifdef USE_API_SERVICES
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
#ifdef USE_API_CUSTOM_SERVICES
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
#else
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
|
||||
@@ -11,58 +11,23 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
|
||||
}
|
||||
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||
|
||||
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
|
||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||
std::vector<bool> result;
|
||||
result.reserve(arg.bool_array.size());
|
||||
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
|
||||
return result;
|
||||
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end());
|
||||
}
|
||||
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
|
||||
std::vector<int32_t> result;
|
||||
result.reserve(arg.int_array.size());
|
||||
result.insert(result.end(), arg.int_array.begin(), arg.int_array.end());
|
||||
return result;
|
||||
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end());
|
||||
}
|
||||
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||
std::vector<float> result;
|
||||
result.reserve(arg.float_array.size());
|
||||
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
|
||||
return result;
|
||||
return std::vector<float>(arg.float_array.begin(), arg.float_array.end());
|
||||
}
|
||||
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(arg.string_array.size());
|
||||
result.insert(result.end(), arg.string_array.begin(), arg.string_array.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
// New FixedVector const reference versions for YAML-generated services - zero-copy
|
||||
template<>
|
||||
const FixedVector<bool> &get_execute_arg_value<const FixedVector<bool> &>(const ExecuteServiceArgument &arg) {
|
||||
return arg.bool_array;
|
||||
}
|
||||
template<>
|
||||
const FixedVector<int32_t> &get_execute_arg_value<const FixedVector<int32_t> &>(const ExecuteServiceArgument &arg) {
|
||||
return arg.int_array;
|
||||
}
|
||||
template<>
|
||||
const FixedVector<float> &get_execute_arg_value<const FixedVector<float> &>(const ExecuteServiceArgument &arg) {
|
||||
return arg.float_array;
|
||||
}
|
||||
template<>
|
||||
const FixedVector<std::string> &get_execute_arg_value<const FixedVector<std::string> &>(
|
||||
const ExecuteServiceArgument &arg) {
|
||||
return arg.string_array;
|
||||
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end());
|
||||
}
|
||||
|
||||
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
||||
|
||||
// Legacy std::vector versions for external components using custom_api_device.h
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
|
||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
||||
@@ -74,18 +39,4 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
|
||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||
}
|
||||
|
||||
// New FixedVector const reference versions for YAML-generated services
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<bool> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_BOOL_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<int32_t> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<float> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<std::string> &>() {
|
||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -155,7 +155,6 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
|
||||
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
||||
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
|
||||
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
@@ -300,7 +299,7 @@ async def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = await cg.process_lambda(
|
||||
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
||||
)
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
return cg.new_Pvariable(filter_id, lambda_)
|
||||
|
||||
|
||||
@register_filter(
|
||||
|
||||
@@ -111,21 +111,6 @@ class LambdaFilter : public Filter {
|
||||
std::function<optional<bool>(bool)> f_;
|
||||
};
|
||||
|
||||
/** Optimized lambda filter for stateless lambdas (no capture).
|
||||
*
|
||||
* Uses function pointer instead of std::function to reduce memory overhead.
|
||||
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||
*/
|
||||
class StatelessLambdaFilter : public Filter {
|
||||
public:
|
||||
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
|
||||
|
||||
optional<bool> new_value(bool value) override { return this->f_(value); }
|
||||
|
||||
protected:
|
||||
optional<bool> (*f_)(bool);
|
||||
};
|
||||
|
||||
class SettleFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
@@ -304,13 +304,9 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
||||
# format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
|
||||
# a PIO platformio/framework-espidf value
|
||||
if ver == cv.Version(5, 4, 3) or ver >= cv.Version(5, 5, 1):
|
||||
ext = "tar.xz"
|
||||
else:
|
||||
ext = "zip"
|
||||
if release:
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
|
||||
|
||||
|
||||
def _is_framework_url(source: str) -> str:
|
||||
@@ -359,7 +355,6 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
|
||||
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),
|
||||
|
||||
@@ -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,10 +223,7 @@ async def esp32_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_DRIVE_STRENGTH in config:
|
||||
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
|
||||
@@ -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,10 +165,7 @@ async def esp8266_pin_to_code(config):
|
||||
num = config[CONF_NUMBER]
|
||||
mode = config[CONF_MODE]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(mode)))
|
||||
if num < 16:
|
||||
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][
|
||||
|
||||
@@ -14,7 +14,7 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.network import IPAddress
|
||||
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -320,11 +320,11 @@ def _final_validate_spi(config):
|
||||
def manual_ip(config):
|
||||
return cg.StructInitializer(
|
||||
ManualIP,
|
||||
("static_ip", ip_address_literal(config[CONF_STATIC_IP])),
|
||||
("gateway", ip_address_literal(config[CONF_GATEWAY])),
|
||||
("subnet", ip_address_literal(config[CONF_SUBNET])),
|
||||
("dns1", ip_address_literal(config[CONF_DNS1])),
|
||||
("dns2", ip_address_literal(config[CONF_DNS2])),
|
||||
("static_ip", IPAddress(str(config[CONF_STATIC_IP]))),
|
||||
("gateway", IPAddress(str(config[CONF_GATEWAY]))),
|
||||
("subnet", IPAddress(str(config[CONF_SUBNET]))),
|
||||
("dns1", IPAddress(str(config[CONF_DNS1]))),
|
||||
("dns2", IPAddress(str(config[CONF_DNS2]))),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,9 +57,6 @@ async def host_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -169,7 +169,7 @@ class HttpRequestComponent : public Component {
|
||||
protected:
|
||||
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
|
||||
const std::string &body, const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) = 0;
|
||||
std::set<std::string> collect_headers) = 0;
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
uint16_t redirect_limit_{};
|
||||
|
||||
@@ -17,7 +17,7 @@ static const char *const TAG = "http_request.arduino";
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
|
||||
@@ -33,7 +33,7 @@ class HttpRequestArduino : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) override;
|
||||
std::set<std::string> collect_headers) override;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
|
||||
@@ -20,7 +20,7 @@ static const char *const TAG = "http_request.host";
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &response_headers) {
|
||||
std::set<std::string> response_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
|
||||
@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &response_headers) override;
|
||||
std::set<std::string> response_headers) override;
|
||||
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
|
||||
@@ -39,7 +39,7 @@ class HttpRequestIDF : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) override;
|
||||
std::set<std::string> collect_headers) override;
|
||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||
uint16_t buffer_size_rx_{};
|
||||
uint16_t buffer_size_tx_{};
|
||||
|
||||
@@ -199,9 +199,6 @@ async def component_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -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, void (*f)(AddressableLight &, Color, bool initial_run),
|
||||
AddressableLambdaLightEffect(const char *name, std::function<void(AddressableLight &, Color, bool initial_run)> f,
|
||||
uint32_t update_interval)
|
||||
: AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
|
||||
: AddressableLightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
|
||||
void start() override { this->initial_run_ = true; }
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
@@ -72,7 +72,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
|
||||
}
|
||||
|
||||
protected:
|
||||
void (*f_)(AddressableLight &, Color, bool initial_run);
|
||||
std::function<void(AddressableLight &, Color, bool initial_run)> f_;
|
||||
uint32_t update_interval_;
|
||||
uint32_t last_run_{0};
|
||||
bool initial_run_;
|
||||
|
||||
@@ -112,8 +112,8 @@ class RandomLightEffect : public LightEffect {
|
||||
|
||||
class LambdaLightEffect : public LightEffect {
|
||||
public:
|
||||
LambdaLightEffect(const char *name, void (*f)(bool initial_run), uint32_t update_interval)
|
||||
: LightEffect(name), f_(f), update_interval_(update_interval) {}
|
||||
LambdaLightEffect(const char *name, std::function<void(bool initial_run)> f, uint32_t update_interval)
|
||||
: LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
|
||||
|
||||
void start() override { this->initial_run_ = true; }
|
||||
void apply() override {
|
||||
@@ -130,7 +130,7 @@ class LambdaLightEffect : public LightEffect {
|
||||
uint32_t get_current_index() const { return this->get_index(); }
|
||||
|
||||
protected:
|
||||
void (*f_)(bool initial_run);
|
||||
std::function<void(bool initial_run)> f_;
|
||||
uint32_t update_interval_;
|
||||
uint32_t last_run_{0};
|
||||
bool initial_run_;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import re
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import LambdaAction, StatelessLambdaAction
|
||||
from esphome.automation import LambdaAction
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
@@ -430,9 +430,7 @@ async def logger_log_action_to_code(config, action_id, template_arg, args):
|
||||
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
|
||||
|
||||
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||
return automation.new_lambda_pvariable(
|
||||
action_id, lambda_, StatelessLambdaAction, template_arg
|
||||
)
|
||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@@ -457,9 +455,7 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
|
||||
text = str(cg.statement(logger.set_log_level(level)))
|
||||
|
||||
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||
return automation.new_lambda_pvariable(
|
||||
action_id, lambda_, StatelessLambdaAction, template_arg
|
||||
)
|
||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import ipaddress
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
import esphome.config_validation as cv
|
||||
@@ -12,41 +10,6 @@ AUTO_LOAD = ["mdns"]
|
||||
network_ns = cg.esphome_ns.namespace("network")
|
||||
IPAddress = network_ns.class_("IPAddress")
|
||||
|
||||
|
||||
def ip_address_literal(ip: str | int | None) -> cg.MockObj:
|
||||
"""Generate an IPAddress with compile-time initialization instead of runtime parsing.
|
||||
|
||||
This function parses the IP address in Python during code generation and generates
|
||||
a call to the 4-octet constructor (IPAddress(192, 168, 1, 1)) instead of the
|
||||
string constructor (IPAddress("192.168.1.1")). This eliminates runtime string
|
||||
parsing overhead and reduces flash usage on embedded systems.
|
||||
|
||||
Args:
|
||||
ip: IP address as string (e.g., "192.168.1.1"), ipaddress.IPv4Address, or None
|
||||
|
||||
Returns:
|
||||
IPAddress expression that uses 4-octet constructor for efficiency
|
||||
"""
|
||||
if ip is None:
|
||||
return IPAddress(0, 0, 0, 0)
|
||||
|
||||
try:
|
||||
# Parse using Python's ipaddress module
|
||||
ip_obj = ipaddress.ip_address(ip)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
# Only support IPv4 for now
|
||||
if isinstance(ip_obj, ipaddress.IPv4Address):
|
||||
# Extract octets from the packed bytes representation
|
||||
octets = ip_obj.packed
|
||||
# Generate call to 4-octet constructor: IPAddress(192, 168, 1, 1)
|
||||
return IPAddress(octets[0], octets[1], octets[2], octets[3])
|
||||
|
||||
# Fallback to string constructor if parsing fails
|
||||
return IPAddress(str(ip))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.SplitDefault(
|
||||
|
||||
@@ -540,23 +540,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
*/
|
||||
void goto_page(uint8_t page);
|
||||
|
||||
/**
|
||||
* Set the visibility of a component.
|
||||
*
|
||||
* @param component The component name.
|
||||
* @param show True to show the component, false to hide it.
|
||||
*
|
||||
* @see show_component()
|
||||
* @see hide_component()
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_visibility("textview", true); // Equivalent to show_component("textview")
|
||||
* it.set_component_visibility("textview", false); // Equivalent to hide_component("textview")
|
||||
* ```
|
||||
*/
|
||||
void set_component_visibility(const char *component, bool show) override;
|
||||
|
||||
/**
|
||||
* Hide a component.
|
||||
* @param component The component name.
|
||||
|
||||
@@ -45,7 +45,6 @@ class NextionBase {
|
||||
virtual void set_component_pressed_font_color(const char *component, Color color) = 0;
|
||||
virtual void set_component_font(const char *component, uint8_t font_id) = 0;
|
||||
|
||||
virtual void set_component_visibility(const char *component, bool show) = 0;
|
||||
virtual void show_component(const char *component) = 0;
|
||||
virtual void hide_component(const char *component) = 0;
|
||||
|
||||
|
||||
@@ -201,13 +201,13 @@ void Nextion::set_component_font(const char *component, uint8_t font_id) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%" PRIu8, component, font_id);
|
||||
}
|
||||
|
||||
void Nextion::set_component_visibility(const char *component, bool show) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_visibility", "vis %s,%d", component, show ? 1 : 0);
|
||||
void Nextion::hide_component(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component);
|
||||
}
|
||||
|
||||
void Nextion::hide_component(const char *component) { this->set_component_visibility(component, false); }
|
||||
|
||||
void Nextion::show_component(const char *component) { this->set_component_visibility(component, true); }
|
||||
void Nextion::show_component(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component);
|
||||
}
|
||||
|
||||
void Nextion::enable_component_touch(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);
|
||||
|
||||
@@ -81,11 +81,13 @@ void NextionComponent::update_component_settings(bool force_update) {
|
||||
|
||||
this->component_flags_.visible_needs_update = false;
|
||||
|
||||
this->nextion_->set_component_visibility(name_to_send.c_str(), this->component_flags_.visible);
|
||||
if (!this->component_flags_.visible) {
|
||||
if (this->component_flags_.visible) {
|
||||
this->nextion_->show_component(name_to_send.c_str());
|
||||
this->send_state_to_nextion();
|
||||
} else {
|
||||
this->nextion_->hide_component(name_to_send.c_str());
|
||||
return;
|
||||
}
|
||||
this->send_state_to_nextion();
|
||||
}
|
||||
|
||||
if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) {
|
||||
|
||||
@@ -174,6 +174,11 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
|
||||
// Check if baud rate is supported
|
||||
this->original_baud_rate_ = this->parent_->get_baud_rate();
|
||||
static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
|
||||
115200, 230400, 250000, 256000, 512000, 921600};
|
||||
if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
|
||||
baud_rate = this->original_baud_rate_;
|
||||
}
|
||||
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
|
||||
|
||||
// Define the configuration for the HTTP client
|
||||
|
||||
@@ -177,6 +177,11 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
|
||||
// Check if baud rate is supported
|
||||
this->original_baud_rate_ = this->parent_->get_baud_rate();
|
||||
static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
|
||||
115200, 230400, 250000, 256000, 512000, 921600};
|
||||
if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
|
||||
baud_rate = this->original_baud_rate_;
|
||||
}
|
||||
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
|
||||
|
||||
// Define the configuration for the HTTP client
|
||||
|
||||
@@ -74,9 +74,6 @@ async def nrf52_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -252,10 +252,7 @@ async def setup_number_core_(
|
||||
cg.add(var.traits.set_max_value(max_value))
|
||||
cg.add(var.traits.set_step(step))
|
||||
|
||||
# Only set if non-default to avoid bloating setup() function
|
||||
# (mode_ is initialized to NUMBER_MODE_AUTO in the header)
|
||||
if config[CONF_MODE] != NumberMode.NUMBER_MODE_AUTO:
|
||||
cg.add(var.traits.set_mode(config[CONF_MODE]))
|
||||
cg.add(var.traits.set_mode(config[CONF_MODE]))
|
||||
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -38,6 +38,7 @@ void Pipsolar::loop() {
|
||||
}
|
||||
if (this->state_ == STATE_COMMAND_COMPLETE) {
|
||||
if (this->check_incoming_length_(4)) {
|
||||
ESP_LOGD(TAG, "response length for command OK");
|
||||
if (this->check_incoming_crc_()) {
|
||||
// crc ok
|
||||
if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') {
|
||||
@@ -48,15 +49,15 @@ void Pipsolar::loop() {
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
|
||||
} else {
|
||||
// crc failed
|
||||
// no log message necessary, check_incoming_crc_() logs
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "command %s response length not OK: with length %zu",
|
||||
ESP_LOGD(TAG, "response length for command %s not OK: with length %zu",
|
||||
this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
@@ -65,10 +66,46 @@ void Pipsolar::loop() {
|
||||
}
|
||||
|
||||
if (this->state_ == STATE_POLL_CHECKED) {
|
||||
ESP_LOGD(TAG, "poll %s decode", this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->handle_poll_response_(this->enabled_polling_commands_[this->last_polling_command_].identifier,
|
||||
(const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
switch (this->enabled_polling_commands_[this->last_polling_command_].identifier) {
|
||||
case POLLING_QPIRI:
|
||||
ESP_LOGD(TAG, "Decode QPIRI");
|
||||
handle_qpiri_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QPIGS:
|
||||
ESP_LOGD(TAG, "Decode QPIGS");
|
||||
handle_qpigs_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QMOD:
|
||||
ESP_LOGD(TAG, "Decode QMOD");
|
||||
handle_qmod_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QFLAG:
|
||||
ESP_LOGD(TAG, "Decode QFLAG");
|
||||
handle_qflag_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QPIWS:
|
||||
ESP_LOGD(TAG, "Decode QPIWS");
|
||||
handle_qpiws_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QT:
|
||||
ESP_LOGD(TAG, "Decode QT");
|
||||
handle_qt_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QMN:
|
||||
ESP_LOGD(TAG, "Decode QMN");
|
||||
handle_qmn_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
default:
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,8 +113,6 @@ void Pipsolar::loop() {
|
||||
if (this->check_incoming_crc_()) {
|
||||
if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' &&
|
||||
this->read_buffer_[3] == 'K') {
|
||||
ESP_LOGD(TAG, "poll %s NACK", this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
|
||||
this->state_ = STATE_IDLE;
|
||||
return;
|
||||
}
|
||||
@@ -86,9 +121,6 @@ void Pipsolar::loop() {
|
||||
this->state_ = STATE_POLL_CHECKED;
|
||||
return;
|
||||
} else {
|
||||
// crc failed
|
||||
// no log message necessary, check_incoming_crc_() logs
|
||||
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
|
||||
this->state_ = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
@@ -126,19 +158,21 @@ void Pipsolar::loop() {
|
||||
// command timeout
|
||||
const char *command = this->command_queue_[this->command_queue_position_].c_str();
|
||||
this->command_start_millis_ = millis();
|
||||
ESP_LOGD(TAG, "command %s timeout", command);
|
||||
ESP_LOGD(TAG, "timeout command from queue: %s", command);
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (this->state_ == STATE_POLL) {
|
||||
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
|
||||
// command timeout
|
||||
ESP_LOGD(TAG, "poll %s timeout", this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
|
||||
ESP_LOGD(TAG, "timeout command to poll: %s",
|
||||
this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->state_ = STATE_IDLE;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +187,7 @@ uint8_t Pipsolar::check_incoming_length_(uint8_t length) {
|
||||
uint8_t Pipsolar::check_incoming_crc_() {
|
||||
uint16_t crc16;
|
||||
crc16 = this->pipsolar_crc_(read_buffer_, read_pos_ - 3);
|
||||
ESP_LOGD(TAG, "checking crc on incoming message");
|
||||
if (((uint8_t) ((crc16) >> 8)) == read_buffer_[read_pos_ - 3] &&
|
||||
((uint8_t) ((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) {
|
||||
ESP_LOGD(TAG, "CRC OK");
|
||||
@@ -218,7 +253,7 @@ bool Pipsolar::send_next_poll_() {
|
||||
this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
|
||||
// end Byte
|
||||
this->write(0x0D);
|
||||
ESP_LOGD(TAG, "Sending polling command: %s with length %d",
|
||||
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
|
||||
this->enabled_polling_commands_[this->last_polling_command_].command,
|
||||
this->enabled_polling_commands_[this->last_polling_command_].length);
|
||||
return true;
|
||||
@@ -239,38 +274,6 @@ void Pipsolar::queue_command(const std::string &command) {
|
||||
ESP_LOGD(TAG, "Command queue full dropping command: %s", command.c_str());
|
||||
}
|
||||
|
||||
void Pipsolar::handle_poll_response_(ENUMPollingCommand polling_command, const char *message) {
|
||||
switch (polling_command) {
|
||||
case POLLING_QPIRI:
|
||||
handle_qpiri_(message);
|
||||
break;
|
||||
case POLLING_QPIGS:
|
||||
handle_qpigs_(message);
|
||||
break;
|
||||
case POLLING_QMOD:
|
||||
handle_qmod_(message);
|
||||
break;
|
||||
case POLLING_QFLAG:
|
||||
handle_qflag_(message);
|
||||
break;
|
||||
case POLLING_QPIWS:
|
||||
handle_qpiws_(message);
|
||||
break;
|
||||
case POLLING_QT:
|
||||
handle_qt_(message);
|
||||
break;
|
||||
case POLLING_QMN:
|
||||
handle_qmn_(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void Pipsolar::handle_poll_error_(ENUMPollingCommand polling_command) {
|
||||
// handlers are designed in a way that an empty message sets all sensors to unknown
|
||||
this->handle_poll_response_(polling_command, "");
|
||||
}
|
||||
|
||||
void Pipsolar::handle_qpiri_(const char *message) {
|
||||
if (this->last_qpiri_) {
|
||||
this->last_qpiri_->publish_state(message);
|
||||
|
||||
@@ -204,9 +204,6 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent {
|
||||
bool send_next_command_();
|
||||
bool send_next_poll_();
|
||||
|
||||
void handle_poll_response_(ENUMPollingCommand polling_command, const char *message);
|
||||
void handle_poll_error_(ENUMPollingCommand polling_command);
|
||||
// these handlers are designed in a way that an empty message sets all sensors to unknown
|
||||
void handle_qpiri_(const char *message);
|
||||
void handle_qpigs_(const char *message);
|
||||
void handle_qmod_(const char *message);
|
||||
|
||||
@@ -4,18 +4,11 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_BUS_VOLTAGE,
|
||||
DEVICE_CLASS_APPARENT_POWER,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_BATTERY,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_FLASH,
|
||||
ICON_GAUGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
@@ -29,10 +22,6 @@ from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
ICON_SOLAR_POWER = "mdi:solar-power"
|
||||
ICON_SOLAR_PANEL = "mdi:solar-panel"
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
|
||||
# QPIRI sensors
|
||||
CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage"
|
||||
CONF_GRID_RATING_CURRENT = "grid_rating_current"
|
||||
@@ -86,19 +75,16 @@ TYPES = {
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_GRID_RATING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
@@ -112,12 +98,11 @@ TYPES = {
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema(
|
||||
@@ -146,151 +131,124 @@ TYPES = {
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_BATTERY_TYPE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
),
|
||||
CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
),
|
||||
CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PARALLEL_MAX_NUM: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_MACHINE_TYPE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_TOPOLOGY: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_OUTPUT_MODE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PV_POWER_BALANCE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_GRID_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_GRID_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_BUS_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_BATTERY,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_DC,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_SOLAR_PANEL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_SOLAR_PANEL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_DC,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
@@ -298,14 +256,12 @@ TYPES = {
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_EEPROM_VERSION: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PV_CHARGING_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_SOLAR_POWER,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -12,25 +12,6 @@
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
// IDF version 5.5.1 and above is required because of a bug in
|
||||
// the RMT encoder: https://github.com/espressif/esp-idf/issues/17244
|
||||
typedef union { // NOLINT(modernize-use-using)
|
||||
struct {
|
||||
uint16_t duration : 15;
|
||||
uint16_t level : 1;
|
||||
};
|
||||
uint16_t val;
|
||||
} rmt_symbol_half_t;
|
||||
|
||||
struct RemoteTransmitterComponentStore {
|
||||
uint32_t times{0};
|
||||
uint32_t index{0};
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
public Component
|
||||
#ifdef USE_ESP32
|
||||
@@ -75,14 +56,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
#ifdef USE_ESP32
|
||||
void configure_rmt_();
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
RemoteTransmitterComponentStore store_{};
|
||||
std::vector<rmt_symbol_half_t> rmt_temp_;
|
||||
#else
|
||||
std::vector<rmt_symbol_word_t> rmt_temp_;
|
||||
#endif
|
||||
uint32_t current_carrier_frequency_{38000};
|
||||
bool initialized_{false};
|
||||
std::vector<rmt_symbol_word_t> rmt_temp_;
|
||||
bool with_dma_{false};
|
||||
bool eot_level_{false};
|
||||
rmt_channel_handle_t channel_{NULL};
|
||||
|
||||
@@ -10,46 +10,6 @@ namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
// Maximum RMT symbol duration (15-bit field)
|
||||
static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
|
||||
rmt_symbol_word_t *symbols, bool *done, void *arg) {
|
||||
auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
|
||||
const auto *encoded = static_cast<const rmt_symbol_half_t *>(data);
|
||||
size_t length = size / sizeof(rmt_symbol_half_t);
|
||||
size_t count = 0;
|
||||
|
||||
// copy symbols
|
||||
for (size_t i = 0; i < free; i++) {
|
||||
uint16_t sym_0 = encoded[store->index++].val;
|
||||
if (store->index >= length) {
|
||||
store->index = 0;
|
||||
store->times--;
|
||||
if (store->times == 0) {
|
||||
*done = true;
|
||||
symbols[count++].val = sym_0;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
uint16_t sym_1 = encoded[store->index++].val;
|
||||
if (store->index >= length) {
|
||||
store->index = 0;
|
||||
store->times--;
|
||||
if (store->times == 0) {
|
||||
*done = true;
|
||||
symbols[count++].val = sym_0 | (sym_1 << 16);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
symbols[count++].val = sym_0 | (sym_1 << 16);
|
||||
}
|
||||
*done = false;
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
this->inverted_ = this->pin_->is_inverted();
|
||||
this->configure_rmt_();
|
||||
@@ -74,17 +34,6 @@ void RemoteTransmitterComponent::dump_config() {
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::digital_write(bool value) {
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
rmt_symbol_half_t symbol = {
|
||||
.duration = 1,
|
||||
.level = value,
|
||||
};
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = value;
|
||||
this->store_.times = 1;
|
||||
this->store_.index = 0;
|
||||
#else
|
||||
rmt_symbol_word_t symbol = {
|
||||
.duration0 = 1,
|
||||
.level0 = value,
|
||||
@@ -93,8 +42,8 @@ void RemoteTransmitterComponent::digital_write(bool value) {
|
||||
};
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.loop_count = 0;
|
||||
config.flags.eot_level = value;
|
||||
#endif
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
@@ -141,20 +90,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
gpio_pullup_dis(gpio_num_t(this->pin_->get_pin()));
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
rmt_simple_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
encoder.callback = encoder_callback;
|
||||
encoder.arg = &this->store_;
|
||||
encoder.min_chunk_size = 1;
|
||||
error = rmt_new_simple_encoder(&encoder, &this->encoder_);
|
||||
if (error != ESP_OK) {
|
||||
this->error_code_ = error;
|
||||
this->error_string_ = "in rmt_new_simple_encoder";
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
rmt_copy_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
error = rmt_new_copy_encoder(&encoder, &this->encoder_);
|
||||
@@ -164,7 +99,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
error = rmt_enable(this->channel_);
|
||||
if (error != ESP_OK) {
|
||||
@@ -196,79 +130,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
}
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) {
|
||||
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
|
||||
this->configure_rmt_();
|
||||
}
|
||||
|
||||
this->rmt_temp_.clear();
|
||||
this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
|
||||
|
||||
// encode any delay at the start of the buffer to simplify the encoder callback
|
||||
// this will be skipped the first time around
|
||||
send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
|
||||
while (send_wait > 0) {
|
||||
int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
this->rmt_temp_.push_back({
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(this->eot_level_),
|
||||
});
|
||||
send_wait -= duration;
|
||||
}
|
||||
|
||||
// encode data
|
||||
size_t offset = this->rmt_temp_.size();
|
||||
for (int32_t value : this->temp_.get_data()) {
|
||||
bool level = value >= 0;
|
||||
if (!level) {
|
||||
value = -value;
|
||||
}
|
||||
value = this->from_microseconds_(static_cast<uint32_t>(value));
|
||||
while (value > 0) {
|
||||
int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
this->rmt_temp_.push_back({
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(level ^ this->inverted_),
|
||||
});
|
||||
value -= duration;
|
||||
}
|
||||
}
|
||||
|
||||
if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
|
||||
ESP_LOGE(TAG, "Empty data");
|
||||
return;
|
||||
}
|
||||
|
||||
this->transmit_trigger_->trigger();
|
||||
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = this->eot_level_;
|
||||
this->store_.times = send_times;
|
||||
this->store_.index = offset;
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
|
||||
this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
error = rmt_tx_wait_all_done(this->channel_, -1);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
#else
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
@@ -290,7 +151,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
val = this->from_microseconds_(static_cast<uint32_t>(val));
|
||||
|
||||
do {
|
||||
int32_t item = std::min(val, int32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
int32_t item = std::min(val, int32_t(32767));
|
||||
val -= item;
|
||||
|
||||
if (rmt_i % 2 == 0) {
|
||||
@@ -319,6 +180,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
for (uint32_t i = 0; i < send_times; i++) {
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.loop_count = 0;
|
||||
config.flags.eot_level = this->eot_level_;
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
|
||||
this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
|
||||
@@ -338,7 +200,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
}
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace remote_transmitter
|
||||
} // namespace esphome
|
||||
|
||||
@@ -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,9 +94,6 @@ async def rp2040_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -261,7 +261,6 @@ ExponentialMovingAverageFilter = sensor_ns.class_(
|
||||
)
|
||||
ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component)
|
||||
LambdaFilter = sensor_ns.class_("LambdaFilter", Filter)
|
||||
StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter)
|
||||
OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
|
||||
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
|
||||
ValueListFilter = sensor_ns.class_("ValueListFilter", Filter)
|
||||
@@ -574,7 +573,7 @@ async def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = await cg.process_lambda(
|
||||
config, [(float, "x")], return_type=cg.optional.template(float)
|
||||
)
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
return cg.new_Pvariable(filter_id, lambda_)
|
||||
|
||||
|
||||
DELTA_SCHEMA = cv.Schema(
|
||||
@@ -879,9 +878,7 @@ async def setup_sensor_core_(var, config):
|
||||
cg.add(var.set_unit_of_measurement(unit_of_measurement))
|
||||
if (accuracy_decimals := config.get(CONF_ACCURACY_DECIMALS)) is not None:
|
||||
cg.add(var.set_accuracy_decimals(accuracy_decimals))
|
||||
# Only set force_update if True (default is False)
|
||||
if config[CONF_FORCE_UPDATE]:
|
||||
cg.add(var.set_force_update(True))
|
||||
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
|
||||
if config.get(CONF_FILTERS): # must exist and not be empty
|
||||
filters = await build_filters(config[CONF_FILTERS])
|
||||
cg.add(var.set_filters(filters))
|
||||
|
||||
@@ -296,21 +296,6 @@ class LambdaFilter : public Filter {
|
||||
lambda_filter_t lambda_filter_;
|
||||
};
|
||||
|
||||
/** Optimized lambda filter for stateless lambdas (no capture).
|
||||
*
|
||||
* Uses function pointer instead of std::function to reduce memory overhead.
|
||||
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||
*/
|
||||
class StatelessLambdaFilter : public Filter {
|
||||
public:
|
||||
explicit StatelessLambdaFilter(optional<float> (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {}
|
||||
|
||||
optional<float> new_value(float value) override { return this->lambda_filter_(value); }
|
||||
|
||||
protected:
|
||||
optional<float> (*lambda_filter_)(float);
|
||||
};
|
||||
|
||||
/// A simple filter that adds `offset` to each value it receives.
|
||||
class OffsetFilter : public Filter {
|
||||
public:
|
||||
|
||||
@@ -27,7 +27,7 @@ void SNTPComponent::setup() {
|
||||
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
||||
size_t i = 0;
|
||||
for (auto &server : this->servers_) {
|
||||
esp_sntp_setservername(i++, server);
|
||||
esp_sntp_setservername(i++, server.c_str());
|
||||
}
|
||||
esp_sntp_set_sync_interval(this->get_update_interval());
|
||||
esp_sntp_set_time_sync_notification_cb([](struct timeval *tv) {
|
||||
@@ -42,7 +42,7 @@ void SNTPComponent::setup() {
|
||||
|
||||
size_t i = 0;
|
||||
for (auto &server : this->servers_) {
|
||||
sntp_setservername(i++, server);
|
||||
sntp_setservername(i++, server.c_str());
|
||||
}
|
||||
|
||||
#if defined(USE_ESP8266)
|
||||
@@ -59,7 +59,7 @@ void SNTPComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SNTP Time:");
|
||||
size_t i = 0;
|
||||
for (auto &server : this->servers_) {
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server);
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str());
|
||||
}
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace sntp {
|
||||
|
||||
// Server count is calculated at compile time by Python codegen
|
||||
// SNTP_SERVER_COUNT will always be defined
|
||||
|
||||
/// The SNTP component allows you to configure local timekeeping via Simple Network Time Protocol.
|
||||
///
|
||||
/// \note
|
||||
@@ -18,7 +14,10 @@ namespace sntp {
|
||||
/// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
||||
class SNTPComponent : public time::RealTimeClock {
|
||||
public:
|
||||
SNTPComponent(const std::array<const char *, SNTP_SERVER_COUNT> &servers) : servers_(servers) {}
|
||||
SNTPComponent(const std::vector<std::string> &servers) : servers_(servers) {}
|
||||
|
||||
// Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would
|
||||
// have had no effect anyway, and making the strings immutable avoids the need to strdup their contents.
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -30,10 +29,7 @@ class SNTPComponent : public time::RealTimeClock {
|
||||
void time_synced();
|
||||
|
||||
protected:
|
||||
// Store const char pointers to string literals
|
||||
// ESP8266: strings in rodata (RAM), but avoids std::string overhead (~24 bytes each)
|
||||
// Other platforms: strings in flash
|
||||
std::array<const char *, SNTP_SERVER_COUNT> servers_;
|
||||
std::vector<std::string> servers_;
|
||||
bool has_time_{false};
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
|
||||
@@ -43,11 +43,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
servers = config[CONF_SERVERS]
|
||||
|
||||
# Define server count at compile time
|
||||
cg.add_define("SNTP_SERVER_COUNT", len(servers))
|
||||
|
||||
# Pass string literals to constructor - stored in flash/rodata by compiler
|
||||
var = cg.new_Pvariable(config[CONF_ID], servers)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import logging
|
||||
from re import Match
|
||||
from typing import Any
|
||||
|
||||
from esphome import core
|
||||
from esphome.config_helpers import Extend, Remove, merge_config, merge_dicts_ordered
|
||||
@@ -41,34 +39,7 @@ async def to_code(config):
|
||||
pass
|
||||
|
||||
|
||||
def _restore_data_base(value: Any, orig_value: ESPHomeDataBase) -> ESPHomeDataBase:
|
||||
"""This function restores ESPHomeDataBase metadata held by the original string.
|
||||
This is needed because during jinja evaluation, strings can be replaced by other types,
|
||||
but we want to keep the original metadata for error reporting and source mapping.
|
||||
For example, if a substitution replaces a string with a dictionary, we want that items
|
||||
in the dictionary to still point to the original document location
|
||||
"""
|
||||
if isinstance(value, ESPHomeDataBase):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
_restore_data_base(k, orig_value): _restore_data_base(v, orig_value)
|
||||
for k, v in value.items()
|
||||
}
|
||||
if isinstance(value, list):
|
||||
return [_restore_data_base(v, orig_value) for v in value]
|
||||
if isinstance(value, str):
|
||||
return make_data_base(value, orig_value)
|
||||
return value
|
||||
|
||||
|
||||
def _expand_jinja(
|
||||
value: str | JinjaStr,
|
||||
orig_value: str | JinjaStr,
|
||||
path,
|
||||
jinja: Jinja,
|
||||
ignore_missing: bool,
|
||||
) -> Any:
|
||||
def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
|
||||
if has_jinja(value):
|
||||
# If the original value passed in to this function is a JinjaStr, it means it contains an unresolved
|
||||
# Jinja expression from a previous pass.
|
||||
@@ -94,17 +65,10 @@ def _expand_jinja(
|
||||
f"\nSee {'->'.join(str(x) for x in path)}",
|
||||
path,
|
||||
)
|
||||
# If the original, unexpanded string, contained document metadata (ESPHomeDatabase),
|
||||
# assign this same document metadata to the resulting value.
|
||||
if isinstance(orig_value, ESPHomeDataBase):
|
||||
value = _restore_data_base(value, orig_value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _expand_substitutions(
|
||||
substitutions: dict, value: str, path, jinja: Jinja, ignore_missing: bool
|
||||
) -> Any:
|
||||
def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
||||
if "$" not in value:
|
||||
return value
|
||||
|
||||
@@ -112,14 +76,14 @@ def _expand_substitutions(
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
m: Match[str] = cv.VARIABLE_PROG.search(value, i)
|
||||
m = cv.VARIABLE_PROG.search(value, i)
|
||||
if not m:
|
||||
# No more variable substitutions found. See if the remainder looks like a jinja template
|
||||
value = _expand_jinja(value, orig_value, path, jinja, ignore_missing)
|
||||
break
|
||||
|
||||
i, j = m.span(0)
|
||||
name: str = m.group(1)
|
||||
name = m.group(1)
|
||||
if name.startswith("{") and name.endswith("}"):
|
||||
name = name[1:-1]
|
||||
if name not in substitutions:
|
||||
@@ -134,7 +98,7 @@ def _expand_substitutions(
|
||||
i = j
|
||||
continue
|
||||
|
||||
sub: Any = substitutions[name]
|
||||
sub = substitutions[name]
|
||||
|
||||
if i == 0 and j == len(value):
|
||||
# The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly
|
||||
@@ -157,13 +121,7 @@ def _expand_substitutions(
|
||||
return value
|
||||
|
||||
|
||||
def _substitute_item(
|
||||
substitutions: dict,
|
||||
item: Any,
|
||||
path: list[int | str],
|
||||
jinja: Jinja,
|
||||
ignore_missing: bool,
|
||||
) -> Any | None:
|
||||
def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
||||
if isinstance(item, ESPLiteralValue):
|
||||
return None # do not substitute inside literal blocks
|
||||
if isinstance(item, list):
|
||||
@@ -202,9 +160,7 @@ def _substitute_item(
|
||||
return None
|
||||
|
||||
|
||||
def do_substitution_pass(
|
||||
config: dict, command_line_substitutions: dict, ignore_missing: bool = False
|
||||
) -> None:
|
||||
def do_substitution_pass(config, command_line_substitutions, ignore_missing=False):
|
||||
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
||||
return
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
from ast import literal_eval
|
||||
from collections.abc import Iterator
|
||||
from itertools import chain, islice
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from types import GeneratorType
|
||||
from typing import Any
|
||||
|
||||
import jinja2 as jinja
|
||||
from jinja2.nativetypes import NativeCodeGenerator, NativeTemplate
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
from esphome.yaml_util import ESPLiteralValue
|
||||
|
||||
@@ -28,7 +24,7 @@ detect_jinja_re = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def has_jinja(st: str) -> bool:
|
||||
def has_jinja(st):
|
||||
return detect_jinja_re.search(st) is not None
|
||||
|
||||
|
||||
@@ -113,56 +109,12 @@ class TrackerContext(jinja.runtime.Context):
|
||||
return val
|
||||
|
||||
|
||||
def _concat_nodes_override(values: Iterator[Any]) -> Any:
|
||||
"""
|
||||
This function customizes how Jinja preserves native types when concatenating
|
||||
multiple result nodes together. If the result is a single node, its value
|
||||
is returned. Otherwise, the nodes are concatenated as strings. If
|
||||
the result can be parsed with `ast.literal_eval`, the parsed
|
||||
value is returned. Otherwise, the string is returned.
|
||||
This helps preserve metadata such as ESPHomeDataBase from original values
|
||||
and mimicks how HomeAssistant deals with template evaluation and preserving
|
||||
the original datatype.
|
||||
"""
|
||||
head: list[Any] = list(islice(values, 2))
|
||||
|
||||
if not head:
|
||||
return None
|
||||
|
||||
if len(head) == 1:
|
||||
raw = head[0]
|
||||
if not isinstance(raw, str):
|
||||
return raw
|
||||
else:
|
||||
if isinstance(values, GeneratorType):
|
||||
values = chain(head, values)
|
||||
raw = "".join([str(v) for v in values])
|
||||
|
||||
try:
|
||||
# Attempt to parse the concatenated string into a Python literal.
|
||||
# This allows expressions like "1 + 2" to be evaluated to the integer 3.
|
||||
# If the result is also a string or there is a parsing error,
|
||||
# fall back to returning the raw string. This is consistent with
|
||||
# Home Assistant's behavior when evaluating templates
|
||||
result = literal_eval(raw)
|
||||
if not isinstance(result, str):
|
||||
return result
|
||||
|
||||
except (ValueError, SyntaxError, MemoryError, TypeError):
|
||||
pass
|
||||
return raw
|
||||
|
||||
|
||||
class Jinja(jinja.Environment):
|
||||
class Jinja(SandboxedEnvironment):
|
||||
"""
|
||||
Wraps a Jinja environment
|
||||
"""
|
||||
|
||||
# jinja environment customization overrides
|
||||
code_generator_class = NativeCodeGenerator
|
||||
concat = staticmethod(_concat_nodes_override)
|
||||
|
||||
def __init__(self, context_vars: dict):
|
||||
def __init__(self, context_vars):
|
||||
super().__init__(
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
@@ -190,10 +142,19 @@ class Jinja(jinja.Environment):
|
||||
**SAFE_GLOBALS,
|
||||
}
|
||||
|
||||
def expand(self, content_str: str | JinjaStr) -> Any:
|
||||
def safe_eval(self, expr):
|
||||
try:
|
||||
result = literal_eval(expr)
|
||||
if not isinstance(result, str):
|
||||
return result
|
||||
except (ValueError, SyntaxError, MemoryError, TypeError):
|
||||
pass
|
||||
return expr
|
||||
|
||||
def expand(self, content_str):
|
||||
"""
|
||||
Renders a string that may contain Jinja expressions or statements
|
||||
Returns the resulting value if all variables and expressions could be resolved.
|
||||
Returns the resulting processed string if all values could be resolved.
|
||||
Otherwise, it returns a tagged (JinjaStr) string that captures variables
|
||||
in scope (upvalues), like a closure for later evaluation.
|
||||
"""
|
||||
@@ -211,7 +172,7 @@ class Jinja(jinja.Environment):
|
||||
self.context_trace = {}
|
||||
try:
|
||||
template = self.from_string(content_str)
|
||||
result = template.render(override_vars)
|
||||
result = self.safe_eval(template.render(override_vars))
|
||||
if isinstance(result, Undefined):
|
||||
print("" + result) # force a UndefinedError exception
|
||||
except (TemplateSyntaxError, UndefinedError) as err:
|
||||
@@ -240,10 +201,3 @@ class Jinja(jinja.Environment):
|
||||
content_str.result = result
|
||||
|
||||
return result, None
|
||||
|
||||
|
||||
class JinjaTemplate(NativeTemplate):
|
||||
environment_class = Jinja
|
||||
|
||||
|
||||
Jinja.template_class = JinjaTemplate
|
||||
|
||||
@@ -38,14 +38,8 @@ async def to_code(config):
|
||||
condition = await automation.build_condition(
|
||||
condition, cg.TemplateArguments(), []
|
||||
)
|
||||
# Generate a stateless lambda that calls condition.check()
|
||||
# capture="" is safe because condition is a global variable in generated C++ code
|
||||
# and doesn't need to be captured. This allows implicit conversion to function pointer.
|
||||
template_ = LambdaExpression(
|
||||
f"return {condition.check()};",
|
||||
[],
|
||||
return_type=cg.optional.template(bool),
|
||||
capture="",
|
||||
f"return {condition.check()};", [], return_type=cg.optional.template(bool)
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ static const char *const TAG = "template.binary_sensor";
|
||||
void TemplateBinarySensor::setup() { this->loop(); }
|
||||
|
||||
void TemplateBinarySensor::loop() {
|
||||
if (!this->f_.has_value())
|
||||
if (this->f_ == nullptr)
|
||||
return;
|
||||
|
||||
auto s = (*this->f_)();
|
||||
auto s = this->f_();
|
||||
if (s.has_value()) {
|
||||
this->publish_state(*s);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace template_ {
|
||||
|
||||
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void set_template(optional<bool> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<bool>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
@@ -17,7 +17,7 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
optional<optional<bool> (*)()> f_;
|
||||
std::function<optional<bool>()> f_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -63,7 +63,7 @@ void TemplateCover::loop() {
|
||||
}
|
||||
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
void TemplateCover::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
|
||||
void TemplateCover::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
|
||||
float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; }
|
||||
Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; }
|
||||
@@ -124,7 +124,7 @@ CoverTraits TemplateCover::get_traits() {
|
||||
}
|
||||
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
|
||||
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
|
||||
void TemplateCover::set_tilt_lambda(optional<float> (*tilt_f)()) { this->tilt_f_ = tilt_f; }
|
||||
void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; }
|
||||
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
|
||||
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
|
||||
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
|
||||
|
||||
@@ -17,7 +17,7 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
public:
|
||||
TemplateCover();
|
||||
|
||||
void set_state_lambda(optional<float> (*f)());
|
||||
void set_state_lambda(std::function<optional<float>()> &&f);
|
||||
Trigger<> *get_open_trigger() const;
|
||||
Trigger<> *get_close_trigger() const;
|
||||
Trigger<> *get_stop_trigger() const;
|
||||
@@ -26,7 +26,7 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
Trigger<float> *get_tilt_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
void set_assumed_state(bool assumed_state);
|
||||
void set_tilt_lambda(optional<float> (*tilt_f)());
|
||||
void set_tilt_lambda(std::function<optional<float>()> &&tilt_f);
|
||||
void set_has_stop(bool has_stop);
|
||||
void set_has_position(bool has_position);
|
||||
void set_has_tilt(bool has_tilt);
|
||||
@@ -45,8 +45,8 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
void stop_prev_trigger_();
|
||||
|
||||
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
|
||||
optional<optional<float> (*)()> state_f_;
|
||||
optional<optional<float> (*)()> tilt_f_;
|
||||
optional<std::function<optional<float>()>> state_f_;
|
||||
optional<std::function<optional<float>()>> tilt_f_;
|
||||
bool assumed_state_{false};
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace template_ {
|
||||
|
||||
class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -35,7 +35,7 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
||||
ESPTime initial_value_{};
|
||||
bool restore_value_{false};
|
||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
optional<std::function<optional<ESPTime>()>> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace template_ {
|
||||
|
||||
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -35,7 +35,7 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen
|
||||
ESPTime initial_value_{};
|
||||
bool restore_value_{false};
|
||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
optional<std::function<optional<ESPTime>()>> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace template_ {
|
||||
|
||||
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -35,7 +35,7 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
||||
ESPTime initial_value_{};
|
||||
bool restore_value_{false};
|
||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
optional<std::function<optional<ESPTime>()>> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ void TemplateLock::open_latch() {
|
||||
this->open_trigger_->trigger();
|
||||
}
|
||||
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateLock::set_state_lambda(optional<lock::LockState> (*f)()) { this->f_ = f; }
|
||||
void TemplateLock::set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
|
||||
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
|
||||
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
|
||||
|
||||
@@ -13,7 +13,7 @@ class TemplateLock : public lock::Lock, public Component {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(optional<lock::LockState> (*f)());
|
||||
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
|
||||
Trigger<> *get_lock_trigger() const;
|
||||
Trigger<> *get_unlock_trigger() const;
|
||||
Trigger<> *get_open_trigger() const;
|
||||
@@ -26,7 +26,7 @@ class TemplateLock : public lock::Lock, public Component {
|
||||
void control(const lock::LockCall &call) override;
|
||||
void open_latch() override;
|
||||
|
||||
optional<optional<lock::LockState> (*)()> f_;
|
||||
optional<std::function<optional<lock::LockState>()>> f_;
|
||||
bool optimistic_{false};
|
||||
Trigger<> *lock_trigger_;
|
||||
Trigger<> *unlock_trigger_;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace template_ {
|
||||
|
||||
class TemplateNumber : public number::Number, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<float> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -28,7 +28,7 @@ class TemplateNumber : public number::Number, public PollingComponent {
|
||||
float initial_value_{NAN};
|
||||
bool restore_value_{false};
|
||||
Trigger<float> *set_trigger_ = new Trigger<float>();
|
||||
optional<optional<float> (*)()> f_;
|
||||
optional<std::function<optional<float>()>> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -73,18 +73,11 @@ async def to_code(config):
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
# Only set if non-default to avoid bloating setup() function
|
||||
if config[CONF_OPTIMISTIC]:
|
||||
cg.add(var.set_optimistic(True))
|
||||
initial_option_index = config[CONF_OPTIONS].index(config[CONF_INITIAL_OPTION])
|
||||
# Only set if non-zero to avoid bloating setup() function
|
||||
# (initial_option_index_ is zero-initialized in the header)
|
||||
if initial_option_index != 0:
|
||||
cg.add(var.set_initial_option_index(initial_option_index))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
cg.add(var.set_initial_option(config[CONF_INITIAL_OPTION]))
|
||||
|
||||
# Only set if True (default is False)
|
||||
if config.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(True))
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -10,21 +10,26 @@ void TemplateSelect::setup() {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
|
||||
size_t index = this->initial_option_index_;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
|
||||
size_t restored_index;
|
||||
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
|
||||
index = restored_index;
|
||||
ESP_LOGD(TAG, "State from restore: %s", this->at(index).value().c_str());
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->at(index).value().c_str());
|
||||
}
|
||||
std::string value;
|
||||
if (!this->restore_value_) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial: %s", this->at(index).value().c_str());
|
||||
size_t index;
|
||||
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
|
||||
if (!this->pref_.load(&index)) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str());
|
||||
} else if (!this->has_index(index)) {
|
||||
value = this->initial_option_;
|
||||
ESP_LOGD(TAG, "State from initial (restored index %d out of bounds): %s", index, value.c_str());
|
||||
} else {
|
||||
value = this->at(index).value();
|
||||
ESP_LOGD(TAG, "State from restore: %s", value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
this->publish_state(this->at(index).value());
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void TemplateSelect::update() {
|
||||
@@ -64,8 +69,7 @@ void TemplateSelect::dump_config() {
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(this->optimistic_), this->at(this->initial_option_index_).value().c_str(),
|
||||
YESNO(this->restore_value_));
|
||||
YESNO(this->optimistic_), this->initial_option_.c_str(), YESNO(this->restore_value_));
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace template_ {
|
||||
|
||||
class TemplateSelect : public select::Select, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -19,16 +19,16 @@ class TemplateSelect : public select::Select, public PollingComponent {
|
||||
|
||||
Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
|
||||
void set_initial_option(const std::string &initial_option) { this->initial_option_ = initial_option; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
bool optimistic_ = false;
|
||||
size_t initial_option_index_{0};
|
||||
std::string initial_option_;
|
||||
bool restore_value_ = false;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<optional<std::string> (*)()> f_;
|
||||
optional<std::function<optional<std::string>()>> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ void TemplateSensor::update() {
|
||||
}
|
||||
}
|
||||
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void TemplateSensor::set_template(optional<float> (*f)()) { this->f_ = f; }
|
||||
void TemplateSensor::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||
void TemplateSensor::dump_config() {
|
||||
LOG_SENSOR("", "Template Sensor", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace template_ {
|
||||
|
||||
class TemplateSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<float> (*f)());
|
||||
void set_template(std::function<optional<float>()> &&f);
|
||||
|
||||
void update() override;
|
||||
|
||||
@@ -17,7 +17,7 @@ class TemplateSensor : public sensor::Sensor, public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
optional<optional<float> (*)()> f_;
|
||||
optional<std::function<optional<float>()>> f_;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -35,7 +35,7 @@ void TemplateSwitch::write_state(bool state) {
|
||||
}
|
||||
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
|
||||
void TemplateSwitch::set_state_lambda(optional<bool> (*f)()) { this->f_ = f; }
|
||||
void TemplateSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
|
||||
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
|
||||
Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
|
||||
Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
|
||||
|
||||
@@ -14,7 +14,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(optional<bool> (*f)());
|
||||
void set_state_lambda(std::function<optional<bool>()> &&f);
|
||||
Trigger<> *get_turn_on_trigger() const;
|
||||
Trigger<> *get_turn_off_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
@@ -28,7 +28,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
|
||||
|
||||
void write_state(bool state) override;
|
||||
|
||||
optional<optional<bool> (*)()> f_;
|
||||
optional<std::function<optional<bool>()>> f_;
|
||||
bool optimistic_{false};
|
||||
bool assumed_state_{false};
|
||||
Trigger<> *turn_on_trigger_;
|
||||
|
||||
@@ -61,7 +61,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
|
||||
|
||||
class TemplateText : public text::Text, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
|
||||
void set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -78,7 +78,7 @@ class TemplateText : public text::Text, public PollingComponent {
|
||||
bool optimistic_ = false;
|
||||
std::string initial_value_;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<optional<std::string> (*)()> f_{nullptr};
|
||||
optional<std::function<optional<std::string>()>> f_{nullptr};
|
||||
|
||||
TemplateTextSaverBase *pref_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ void TemplateTextSensor::update() {
|
||||
}
|
||||
}
|
||||
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void TemplateTextSensor::set_template(optional<std::string> (*f)()) { this->f_ = f; }
|
||||
void TemplateTextSensor::set_template(std::function<optional<std::string>()> &&f) { this->f_ = f; }
|
||||
void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); }
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace template_ {
|
||||
|
||||
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<std::string> (*f)());
|
||||
void set_template(std::function<optional<std::string>()> &&f);
|
||||
|
||||
void update() override;
|
||||
|
||||
@@ -18,7 +18,7 @@ class TemplateTextSensor : public text_sensor::TextSensor, public PollingCompone
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
optional<optional<std::string> (*)()> f_{};
|
||||
optional<std::function<optional<std::string>()>> f_{};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -55,7 +55,7 @@ void TemplateValve::loop() {
|
||||
|
||||
void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
void TemplateValve::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
|
||||
void TemplateValve::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
|
||||
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }
|
||||
|
||||
@@ -17,7 +17,7 @@ class TemplateValve : public valve::Valve, public Component {
|
||||
public:
|
||||
TemplateValve();
|
||||
|
||||
void set_state_lambda(optional<float> (*f)());
|
||||
void set_state_lambda(std::function<optional<float>()> &&f);
|
||||
Trigger<> *get_open_trigger() const;
|
||||
Trigger<> *get_close_trigger() const;
|
||||
Trigger<> *get_stop_trigger() const;
|
||||
@@ -42,7 +42,7 @@ class TemplateValve : public valve::Valve, public Component {
|
||||
void stop_prev_trigger_();
|
||||
|
||||
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
|
||||
optional<optional<float> (*)()> state_f_;
|
||||
optional<std::function<optional<float>()>> state_f_;
|
||||
bool assumed_state_{false};
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
|
||||
@@ -57,7 +57,6 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
|
||||
# Filters
|
||||
Filter = text_sensor_ns.class_("Filter")
|
||||
LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter)
|
||||
StatelessLambdaFilter = text_sensor_ns.class_("StatelessLambdaFilter", Filter)
|
||||
ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter)
|
||||
ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter)
|
||||
AppendFilter = text_sensor_ns.class_("AppendFilter", Filter)
|
||||
@@ -71,7 +70,7 @@ async def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = await cg.process_lambda(
|
||||
config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string)
|
||||
)
|
||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
||||
return cg.new_Pvariable(filter_id, lambda_)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("to_upper", ToUpperFilter, {})
|
||||
|
||||
@@ -62,21 +62,6 @@ class LambdaFilter : public Filter {
|
||||
lambda_filter_t lambda_filter_;
|
||||
};
|
||||
|
||||
/** Optimized lambda filter for stateless lambdas (no capture).
|
||||
*
|
||||
* Uses function pointer instead of std::function to reduce memory overhead.
|
||||
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||
*/
|
||||
class StatelessLambdaFilter : public Filter {
|
||||
public:
|
||||
explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {}
|
||||
|
||||
optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); }
|
||||
|
||||
protected:
|
||||
optional<std::string> (*lambda_filter_)(std::string);
|
||||
};
|
||||
|
||||
/// A simple filter that converts all text to uppercase
|
||||
class ToUpperFilter : public Filter {
|
||||
public:
|
||||
|
||||
@@ -67,9 +67,7 @@ void TuyaClimate::setup() {
|
||||
}
|
||||
if (this->eco_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->eco_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
// Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both cases
|
||||
this->eco_ = datapoint.value_bool;
|
||||
this->eco_type_ = datapoint.type;
|
||||
ESP_LOGV(TAG, "MCU reported eco is: %s", ONOFF(this->eco_));
|
||||
this->compute_preset_();
|
||||
this->compute_target_temperature_();
|
||||
@@ -178,11 +176,7 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
|
||||
if (this->eco_id_.has_value()) {
|
||||
const bool eco = preset == climate::CLIMATE_PRESET_ECO;
|
||||
ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco));
|
||||
if (this->eco_type_ == TuyaDatapointType::ENUM) {
|
||||
this->parent_->set_enum_datapoint_value(*this->eco_id_, eco);
|
||||
} else {
|
||||
this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco);
|
||||
}
|
||||
this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco);
|
||||
}
|
||||
if (this->sleep_id_.has_value()) {
|
||||
const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP;
|
||||
|
||||
@@ -104,7 +104,6 @@ class TuyaClimate : public climate::Climate, public Component {
|
||||
optional<uint8_t> eco_id_{};
|
||||
optional<uint8_t> sleep_id_{};
|
||||
optional<float> eco_temperature_{};
|
||||
TuyaDatapointType eco_type_{};
|
||||
uint8_t active_state_;
|
||||
uint8_t fan_state_;
|
||||
optional<uint8_t> swing_vertical_id_{};
|
||||
|
||||
@@ -99,26 +99,10 @@ void IDFUARTComponent::setup() {
|
||||
}
|
||||
|
||||
void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
esp_err_t err;
|
||||
|
||||
if (uart_is_driver_installed(this->uart_num_)) {
|
||||
err = uart_driver_delete(this->uart_num_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
err = uart_driver_install(this->uart_num_, // UART number
|
||||
this->rx_buffer_size_, // RX ring buffer size
|
||||
0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
|
||||
// block task until all data has been sent out
|
||||
20, // event queue size/depth
|
||||
&this->uart_event_queue_, // event queue
|
||||
0 // Flags used to allocate the interrupt
|
||||
);
|
||||
uart_config_t uart_config = this->get_config_();
|
||||
esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -135,12 +119,10 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
||||
|
||||
uint32_t invert = 0;
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
|
||||
invert |= UART_SIGNAL_TXD_INV;
|
||||
}
|
||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
|
||||
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
|
||||
invert |= UART_SIGNAL_RXD_INV;
|
||||
}
|
||||
|
||||
err = uart_set_line_inverse(this->uart_num_, invert);
|
||||
if (err != ESP_OK) {
|
||||
@@ -156,6 +138,26 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uart_is_driver_installed(this->uart_num_)) {
|
||||
uart_driver_delete(this->uart_num_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
err = uart_driver_install(this->uart_num_, /* UART RX ring buffer size. */ this->rx_buffer_size_,
|
||||
/* UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will
|
||||
block task until all data have been sent out.*/
|
||||
0,
|
||||
/* UART event queue size/depth. */ 20, &(this->uart_event_queue_),
|
||||
/* Flags used to allocate the interrupt. */ 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
|
||||
@@ -171,32 +173,24 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
}
|
||||
|
||||
auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
|
||||
err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install()
|
||||
err = uart_set_mode(this->uart_num_, mode);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uart_config_t uart_config = this->get_config_();
|
||||
err = uart_param_config(this->uart_num_, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dump_config) {
|
||||
ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
|
||||
ESP_LOGCONFIG(TAG, "UART %u was reloaded.", this->uart_num_);
|
||||
this->dump_config();
|
||||
}
|
||||
}
|
||||
|
||||
void IDFUARTComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
|
||||
LOG_PIN(" TX Pin: ", this->tx_pin_);
|
||||
LOG_PIN(" RX Pin: ", this->rx_pin_);
|
||||
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
|
||||
LOG_PIN(" TX Pin: ", tx_pin_);
|
||||
LOG_PIN(" RX Pin: ", rx_pin_);
|
||||
LOG_PIN(" Flow Control Pin: ", flow_control_pin_);
|
||||
if (this->rx_pin_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" RX Buffer Size: %u\n"
|
||||
|
||||
@@ -3,7 +3,7 @@ from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_USE_PSRAM
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.network import IPAddress
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import only_with_esp_idf
|
||||
@@ -140,21 +140,17 @@ EAP_AUTH_SCHEMA = cv.All(
|
||||
cv.has_at_least_one_key(CONF_IDENTITY, CONF_CERTIFICATE),
|
||||
)
|
||||
|
||||
CONF_AP_TIMEOUT = "ap_timeout"
|
||||
CONF_SSID_DATA_ID = "ssid_data_id"
|
||||
CONF_PASSWORD_DATA_ID = "password_data_id"
|
||||
|
||||
WIFI_NETWORK_BASE = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(WiFiAP),
|
||||
cv.GenerateID(CONF_SSID_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.GenerateID(CONF_PASSWORD_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.Optional(CONF_SSID): cv.ssid,
|
||||
cv.Optional(CONF_PASSWORD): validate_password,
|
||||
cv.Optional(CONF_CHANNEL): validate_channel,
|
||||
cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
CONF_AP_TIMEOUT = "ap_timeout"
|
||||
WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
@@ -338,7 +334,9 @@ def eap_auth(config):
|
||||
|
||||
|
||||
def safe_ip(ip):
|
||||
return ip_address_literal(ip)
|
||||
if ip is None:
|
||||
return IPAddress(0, 0, 0, 0)
|
||||
return IPAddress(str(ip))
|
||||
|
||||
|
||||
def manual_ip(config):
|
||||
@@ -356,23 +354,9 @@ def manual_ip(config):
|
||||
|
||||
def wifi_network(config, ap, static_ip):
|
||||
if CONF_SSID in config:
|
||||
ssid = config[CONF_SSID]
|
||||
if CORE.is_esp8266:
|
||||
# On ESP8266, store SSID in PROGMEM to save RAM
|
||||
prog_arr = cg.progmem_array(config[CONF_SSID_DATA_ID], ssid)
|
||||
cg.add(ap.set_ssid_flash(prog_arr))
|
||||
else:
|
||||
# On ESP32/RP2040, string literals are already in rodata (flash)
|
||||
cg.add(ap.set_ssid_flash(ssid))
|
||||
cg.add(ap.set_ssid(config[CONF_SSID]))
|
||||
if CONF_PASSWORD in config:
|
||||
password = config[CONF_PASSWORD]
|
||||
if CORE.is_esp8266:
|
||||
# On ESP8266, store password in PROGMEM to save RAM
|
||||
prog_arr = cg.progmem_array(config[CONF_PASSWORD_DATA_ID], password)
|
||||
cg.add(ap.set_password_flash(prog_arr))
|
||||
else:
|
||||
# On ESP32/RP2040, string literals are already in rodata (flash)
|
||||
cg.add(ap.set_password_flash(password))
|
||||
cg.add(ap.set_password(config[CONF_PASSWORD]))
|
||||
if CONF_EAP in config:
|
||||
cg.add(ap.set_eap(eap_auth(config[CONF_EAP])))
|
||||
cg.add_define("USE_WIFI_WPA2_EAP")
|
||||
|
||||
@@ -888,68 +888,19 @@ void WiFiComponent::save_fast_connect_settings_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void WiFiAP::set_ssid(const std::string &ssid) {
|
||||
this->ssid_storage_ = ssid;
|
||||
this->ssid_ = this->ssid_storage_->c_str();
|
||||
}
|
||||
void WiFiAP::set_ssid(const std::string &ssid) { this->ssid_ = ssid; }
|
||||
void WiFiAP::set_bssid(bssid_t bssid) { this->bssid_ = bssid; }
|
||||
void WiFiAP::set_bssid(optional<bssid_t> bssid) { this->bssid_ = bssid; }
|
||||
void WiFiAP::set_password(const std::string &password) {
|
||||
this->password_storage_ = password;
|
||||
this->password_ = this->password_storage_->c_str();
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
void WiFiAP::set_ssid_flash(const uint8_t *ssid) {
|
||||
this->ssid_storage_.reset();
|
||||
this->ssid_ = reinterpret_cast<const char *>(ssid);
|
||||
}
|
||||
void WiFiAP::set_password_flash(const uint8_t *password) {
|
||||
this->password_storage_.reset();
|
||||
this->password_ = reinterpret_cast<const char *>(password);
|
||||
}
|
||||
#else
|
||||
void WiFiAP::set_ssid_flash(const char *ssid) {
|
||||
this->ssid_storage_.reset();
|
||||
this->ssid_ = ssid;
|
||||
}
|
||||
void WiFiAP::set_password_flash(const char *password) {
|
||||
this->password_storage_.reset();
|
||||
this->password_ = password;
|
||||
}
|
||||
#endif
|
||||
void WiFiAP::set_password(const std::string &password) { this->password_ = password; }
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
void WiFiAP::set_eap(optional<EAPAuth> eap_auth) { this->eap_ = std::move(eap_auth); }
|
||||
#endif
|
||||
void WiFiAP::set_channel(optional<uint8_t> channel) { this->channel_ = channel; }
|
||||
void WiFiAP::set_manual_ip(optional<ManualIP> manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
void WiFiAP::set_hidden(bool hidden) { this->hidden_ = hidden; }
|
||||
std::string WiFiAP::get_ssid() const {
|
||||
#ifdef USE_ESP8266
|
||||
if (!this->ssid_storage_.has_value()) {
|
||||
// Flash storage - read from PROGMEM
|
||||
size_t len = strlen_P(this->ssid_);
|
||||
std::string result;
|
||||
result.resize(len);
|
||||
memcpy_P(&result[0], this->ssid_, len);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return std::string(this->ssid_);
|
||||
}
|
||||
const std::string &WiFiAP::get_ssid() const { return this->ssid_; }
|
||||
const optional<bssid_t> &WiFiAP::get_bssid() const { return this->bssid_; }
|
||||
std::string WiFiAP::get_password() const {
|
||||
#ifdef USE_ESP8266
|
||||
if (!this->password_storage_.has_value()) {
|
||||
// Flash storage - read from PROGMEM
|
||||
size_t len = strlen_P(this->password_);
|
||||
std::string result;
|
||||
result.resize(len);
|
||||
memcpy_P(&result[0], this->password_, len);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
return std::string(this->password_);
|
||||
}
|
||||
const std::string &WiFiAP::get_password() const { return this->password_; }
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
const optional<EAPAuth> &WiFiAP::get_eap() const { return this->eap_; }
|
||||
#endif
|
||||
|
||||
@@ -135,18 +135,6 @@ class WiFiAP {
|
||||
void set_bssid(bssid_t bssid);
|
||||
void set_bssid(optional<bssid_t> bssid);
|
||||
void set_password(const std::string &password);
|
||||
/// Set SSID from flash string (PROGMEM on ESP8266, rodata on ESP32)
|
||||
#ifdef USE_ESP8266
|
||||
void set_ssid_flash(const uint8_t *ssid);
|
||||
#else
|
||||
void set_ssid_flash(const char *ssid);
|
||||
#endif
|
||||
/// Set password from flash string (PROGMEM on ESP8266, rodata on ESP32)
|
||||
#ifdef USE_ESP8266
|
||||
void set_password_flash(const uint8_t *password);
|
||||
#else
|
||||
void set_password_flash(const char *password);
|
||||
#endif
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
void set_eap(optional<EAPAuth> eap_auth);
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
@@ -154,9 +142,9 @@ class WiFiAP {
|
||||
void set_priority(float priority) { priority_ = priority; }
|
||||
void set_manual_ip(optional<ManualIP> manual_ip);
|
||||
void set_hidden(bool hidden);
|
||||
std::string get_ssid() const;
|
||||
const std::string &get_ssid() const;
|
||||
const optional<bssid_t> &get_bssid() const;
|
||||
std::string get_password() const;
|
||||
const std::string &get_password() const;
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
const optional<EAPAuth> &get_eap() const;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
@@ -166,10 +154,8 @@ class WiFiAP {
|
||||
bool get_hidden() const;
|
||||
|
||||
protected:
|
||||
const char *ssid_{nullptr};
|
||||
const char *password_{nullptr};
|
||||
optional<std::string> ssid_storage_;
|
||||
optional<std::string> password_storage_;
|
||||
std::string ssid_;
|
||||
std::string password_;
|
||||
optional<bssid_t> bssid_;
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
optional<EAPAuth> eap_;
|
||||
|
||||
@@ -236,25 +236,16 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
|
||||
struct station_config conf {};
|
||||
memset(&conf, 0, sizeof(conf));
|
||||
|
||||
std::string ssid = ap.get_ssid();
|
||||
std::string password = ap.get_password();
|
||||
|
||||
size_t ssid_len = ssid.length();
|
||||
size_t password_len = password.length();
|
||||
|
||||
if (ssid_len > sizeof(conf.ssid)) {
|
||||
if (ap.get_ssid().size() > sizeof(conf.ssid)) {
|
||||
ESP_LOGE(TAG, "SSID too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password_len > sizeof(conf.password)) {
|
||||
if (ap.get_password().size() > sizeof(conf.password)) {
|
||||
ESP_LOGE(TAG, "Password too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(conf.ssid, ssid.c_str(), ssid_len);
|
||||
memcpy(conf.password, password.c_str(), password_len);
|
||||
memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
|
||||
memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
|
||||
|
||||
if (ap.get_bssid().has_value()) {
|
||||
conf.bssid_set = 1;
|
||||
@@ -800,35 +791,27 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
return false;
|
||||
|
||||
struct softap_config conf {};
|
||||
|
||||
std::string ssid = ap.get_ssid();
|
||||
std::string password = ap.get_password();
|
||||
|
||||
size_t ssid_len = ssid.length();
|
||||
size_t password_len = password.length();
|
||||
|
||||
if (ssid_len > sizeof(conf.ssid)) {
|
||||
if (ap.get_ssid().size() > sizeof(conf.ssid)) {
|
||||
ESP_LOGE(TAG, "AP SSID too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(conf.ssid, ssid.c_str(), ssid_len);
|
||||
conf.ssid_len = static_cast<uint8>(ssid_len);
|
||||
memcpy(reinterpret_cast<char *>(conf.ssid), ap.get_ssid().c_str(), ap.get_ssid().size());
|
||||
conf.ssid_len = static_cast<uint8>(ap.get_ssid().size());
|
||||
conf.channel = ap.get_channel().value_or(1);
|
||||
conf.ssid_hidden = ap.get_hidden();
|
||||
conf.max_connection = 5;
|
||||
conf.beacon_interval = 100;
|
||||
|
||||
if (password_len == 0) {
|
||||
if (ap.get_password().empty()) {
|
||||
conf.authmode = AUTH_OPEN;
|
||||
*conf.password = 0;
|
||||
} else {
|
||||
conf.authmode = AUTH_WPA2_PSK;
|
||||
if (password_len > sizeof(conf.password)) {
|
||||
if (ap.get_password().size() > sizeof(conf.password)) {
|
||||
ESP_LOGE(TAG, "AP password too long");
|
||||
return false;
|
||||
}
|
||||
memcpy(conf.password, password.c_str(), password_len);
|
||||
memcpy(reinterpret_cast<char *>(conf.password), ap.get_password().c_str(), ap.get_password().size());
|
||||
}
|
||||
|
||||
ETS_UART_INTR_DISABLE();
|
||||
|
||||
@@ -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,8 +4,6 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -29,20 +27,11 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
public:
|
||||
TemplatableValue() : type_(NONE) {}
|
||||
|
||||
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
|
||||
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) {
|
||||
new (&this->value_) T(std::move(value));
|
||||
}
|
||||
|
||||
// For stateless lambdas (convertible to function pointer): use function pointer
|
||||
template<typename F>
|
||||
TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)>
|
||||
: type_(STATELESS_LAMBDA) {
|
||||
this->stateless_f_ = f; // Implicit conversion to function pointer
|
||||
}
|
||||
|
||||
// For stateful lambdas (not convertible to function pointer): use std::function
|
||||
template<typename F>
|
||||
TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) : type_(LAMBDA) {
|
||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) {
|
||||
this->f_ = new std::function<T(X...)>(std::move(f));
|
||||
}
|
||||
|
||||
@@ -52,8 +41,6 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
new (&this->value_) T(other.value_);
|
||||
} else if (type_ == LAMBDA) {
|
||||
this->f_ = new std::function<T(X...)>(*other.f_);
|
||||
} else if (type_ == STATELESS_LAMBDA) {
|
||||
this->stateless_f_ = other.stateless_f_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +51,6 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
} else if (type_ == LAMBDA) {
|
||||
this->f_ = other.f_;
|
||||
other.f_ = nullptr;
|
||||
} else if (type_ == STATELESS_LAMBDA) {
|
||||
this->stateless_f_ = other.stateless_f_;
|
||||
}
|
||||
other.type_ = NONE;
|
||||
}
|
||||
@@ -93,23 +78,16 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
} else if (type_ == LAMBDA) {
|
||||
delete this->f_;
|
||||
}
|
||||
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty, not heap-allocated)
|
||||
}
|
||||
|
||||
bool has_value() { return this->type_ != NONE; }
|
||||
|
||||
T value(X... x) {
|
||||
switch (this->type_) {
|
||||
case STATELESS_LAMBDA:
|
||||
return this->stateless_f_(x...); // Direct function pointer call
|
||||
case LAMBDA:
|
||||
return (*this->f_)(x...); // std::function call
|
||||
case VALUE:
|
||||
return this->value_;
|
||||
case NONE:
|
||||
default:
|
||||
return T{};
|
||||
if (this->type_ == LAMBDA) {
|
||||
return (*this->f_)(x...);
|
||||
}
|
||||
// return value also when none
|
||||
return this->type_ == VALUE ? this->value_ : T{};
|
||||
}
|
||||
|
||||
optional<T> optional_value(X... x) {
|
||||
@@ -131,13 +109,11 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
NONE,
|
||||
VALUE,
|
||||
LAMBDA,
|
||||
STATELESS_LAMBDA,
|
||||
} type_;
|
||||
|
||||
union {
|
||||
T value_;
|
||||
std::function<T(X...)> *f_;
|
||||
T (*stateless_f_)(X...);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -79,18 +79,6 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
|
||||
std::function<bool(Ts...)> f_;
|
||||
};
|
||||
|
||||
/// Optimized lambda condition for stateless lambdas (no capture).
|
||||
/// Uses function pointer instead of std::function to reduce memory overhead.
|
||||
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||
template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
|
||||
bool check(Ts... x) override { return this->f_(x...); }
|
||||
|
||||
protected:
|
||||
bool (*f_)(Ts...);
|
||||
};
|
||||
|
||||
template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
|
||||
public:
|
||||
explicit ForCondition(Condition<> *condition) : condition_(condition) {}
|
||||
@@ -202,19 +190,6 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> {
|
||||
std::function<void(Ts...)> f_;
|
||||
};
|
||||
|
||||
/// Optimized lambda action for stateless lambdas (no capture).
|
||||
/// Uses function pointer instead of std::function to reduce memory overhead.
|
||||
/// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
||||
template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
|
||||
|
||||
void play(Ts... x) override { this->f_(x...); }
|
||||
|
||||
protected:
|
||||
void (*f_)(Ts...);
|
||||
};
|
||||
|
||||
template<typename... Ts> class IfAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
#define USE_MDNS_STORE_SERVICES
|
||||
#define MDNS_SERVICE_COUNT 3
|
||||
#define MDNS_DYNAMIC_TXT_COUNT 3
|
||||
#define SNTP_SERVER_COUNT 3
|
||||
#define USE_MEDIA_PLAYER
|
||||
#define USE_NEXTION_TFT_UPLOAD
|
||||
#define USE_NUMBER
|
||||
@@ -123,7 +122,6 @@
|
||||
#define USE_API_NOISE
|
||||
#define USE_API_PLAINTEXT
|
||||
#define USE_API_SERVICES
|
||||
#define USE_API_CUSTOM_SERVICES
|
||||
#define API_MAX_SEND_QUEUE 8
|
||||
#define USE_MD5
|
||||
#define USE_SHA256
|
||||
|
||||
@@ -105,9 +105,7 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
|
||||
config[CONF_NAME],
|
||||
platform,
|
||||
)
|
||||
# Only set disabled_by_default if True (default is False)
|
||||
if config[CONF_DISABLED_BY_DEFAULT]:
|
||||
add(var.set_disabled_by_default(True))
|
||||
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_ICON in config:
|
||||
|
||||
@@ -46,18 +46,24 @@ struct tm ESPTime::to_c_tm() {
|
||||
return c_tm;
|
||||
}
|
||||
|
||||
std::string ESPTime::strftime(const char *format) {
|
||||
std::string ESPTime::strftime(const std::string &format) {
|
||||
std::string timestr;
|
||||
timestr.resize(format.size() * 4);
|
||||
struct tm c_tm = this->to_c_tm();
|
||||
char buf[128];
|
||||
size_t len = ::strftime(buf, sizeof(buf), format, &c_tm);
|
||||
if (len > 0) {
|
||||
return std::string(buf, len);
|
||||
size_t len = ::strftime(×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);
|
||||
}
|
||||
return "ERROR";
|
||||
timestr.resize(len);
|
||||
return timestr;
|
||||
}
|
||||
|
||||
std::string ESPTime::strftime(const std::string &format) { return this->strftime(format.c_str()); }
|
||||
|
||||
bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
|
||||
@@ -44,19 +44,17 @@ struct ESPTime {
|
||||
size_t strftime(char *buffer, size_t buffer_len, const char *format);
|
||||
|
||||
/** Convert this ESPTime struct to a string as specified by the format argument.
|
||||
* @see https://en.cppreference.com/w/c/chrono/strftime
|
||||
* @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
|
||||
*
|
||||
* @warning This method returns a dynamically allocated string which can cause heap fragmentation with some
|
||||
* @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
|
||||
* microcontrollers.
|
||||
*
|
||||
* @warning This method can return "ERROR" when the underlying strftime() call fails or when the
|
||||
* output exceeds 128 bytes.
|
||||
* @warning This method can return "ERROR" when the underlying strftime() call fails, e.g. when the
|
||||
* format string contains unsupported specifiers or when the format string doesn't produce any
|
||||
* output.
|
||||
*/
|
||||
std::string strftime(const std::string &format);
|
||||
|
||||
/// @copydoc strftime(const std::string &format)
|
||||
std::string strftime(const char *format);
|
||||
|
||||
/// Check if this ESPTime is valid (all fields in range and year is greater than 2018)
|
||||
bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); }
|
||||
|
||||
|
||||
@@ -198,8 +198,6 @@ class LambdaExpression(Expression):
|
||||
self.return_type = safe_exp(return_type) if return_type is not None else None
|
||||
|
||||
def __str__(self):
|
||||
# Stateless lambdas (empty capture) implicitly convert to function pointers
|
||||
# when assigned to function pointer types - no unary + needed
|
||||
cpp = f"[{self.capture}]({self.parameters})"
|
||||
if self.return_type is not None:
|
||||
cpp += f" -> {self.return_type}"
|
||||
@@ -702,12 +700,6 @@ async def process_lambda(
|
||||
parts[i * 3 + 1] = var
|
||||
parts[i * 3 + 2] = ""
|
||||
|
||||
# All id() references are global variables in generated C++ code.
|
||||
# Global variables should not be captured - they're accessible everywhere.
|
||||
# Use empty capture instead of capture-by-value.
|
||||
if capture == "=":
|
||||
capture = ""
|
||||
|
||||
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
||||
location = value.esp_range.start_mark
|
||||
location.line += value.content_offset
|
||||
|
||||
@@ -23,7 +23,6 @@ size_t = global_ns.namespace("size_t")
|
||||
const_char_ptr = global_ns.namespace("const char *")
|
||||
NAN = global_ns.namespace("NAN")
|
||||
esphome_ns = global_ns # using namespace esphome;
|
||||
FixedVector = esphome_ns.class_("FixedVector")
|
||||
App = esphome_ns.App
|
||||
EntityBase = esphome_ns.class_("EntityBase")
|
||||
Component = esphome_ns.class_("Component")
|
||||
|
||||
@@ -224,37 +224,36 @@ def resolve_ip_address(
|
||||
return res
|
||||
|
||||
# Process hosts
|
||||
|
||||
cached_addresses: list[str] = []
|
||||
uncached_hosts: list[str] = []
|
||||
has_cache = address_cache is not None
|
||||
|
||||
for h in hosts:
|
||||
if is_ip_address(h):
|
||||
_add_ip_addresses_to_addrinfo([h], port, res)
|
||||
if has_cache:
|
||||
# If we have a cache, treat IPs as cached
|
||||
cached_addresses.append(h)
|
||||
else:
|
||||
# If no cache, pass IPs through to resolver with hostnames
|
||||
uncached_hosts.append(h)
|
||||
elif address_cache and (cached := address_cache.get_addresses(h)):
|
||||
_add_ip_addresses_to_addrinfo(cached, port, res)
|
||||
# Found in cache
|
||||
cached_addresses.extend(cached)
|
||||
else:
|
||||
# Not cached, need to resolve
|
||||
if address_cache and address_cache.has_cache():
|
||||
_LOGGER.info("Host %s not in cache, will need to resolve", h)
|
||||
uncached_hosts.append(h)
|
||||
|
||||
# Process cached addresses (includes direct IPs and cached lookups)
|
||||
_add_ip_addresses_to_addrinfo(cached_addresses, port, res)
|
||||
|
||||
# If we have uncached hosts (only non-IP hostnames), resolve them
|
||||
if uncached_hosts:
|
||||
from aioesphomeapi.host_resolver import AddrInfo as AioAddrInfo
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.resolver import AsyncResolver
|
||||
|
||||
resolver = AsyncResolver(uncached_hosts, port)
|
||||
addr_infos: list[AioAddrInfo] = []
|
||||
try:
|
||||
addr_infos = resolver.resolve()
|
||||
except EsphomeError as err:
|
||||
if not res:
|
||||
# No pre-resolved addresses available, DNS resolution is fatal
|
||||
raise
|
||||
_LOGGER.info("%s (using %d already resolved IP addresses)", err, len(res))
|
||||
|
||||
addr_infos = resolver.resolve()
|
||||
# Convert aioesphomeapi AddrInfo to our format
|
||||
for addr_info in addr_infos:
|
||||
sockaddr = addr_info.sockaddr
|
||||
|
||||
@@ -120,7 +120,7 @@ def prepare(
|
||||
cert_file.flush()
|
||||
key_file.write(config[CONF_MQTT].get(CONF_CLIENT_CERTIFICATE_KEY))
|
||||
key_file.flush()
|
||||
context.load_cert_chain(cert_file.name, key_file.name)
|
||||
context.load_cert_chain(cert_file, key_file)
|
||||
client.tls_set_context(context)
|
||||
|
||||
try:
|
||||
|
||||
@@ -19,9 +19,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Home Automation",
|
||||
]
|
||||
|
||||
# Python 3.14 is currently not supported by IDF <= 5.5.1, see https://github.com/esphome/esphome/issues/11502
|
||||
requires-python = ">=3.11.0,<3.14"
|
||||
requires-python = ">=3.11.0"
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies", "version"]
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ esphome-dashboard==20251013.0
|
||||
aioesphomeapi==42.3.0
|
||||
zeroconf==0.148.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.18.16 # dashboard_import
|
||||
ruamel.yaml==0.18.15 # dashboard_import
|
||||
ruamel.yaml.clib==0.2.14 # dashboard_import
|
||||
esphome-glyphsets==0.2.0
|
||||
pillow==11.3.0
|
||||
|
||||
@@ -77,7 +77,6 @@ ISOLATED_COMPONENTS = {
|
||||
"esphome": "Defines devices/areas in esphome: section that are referenced in other sections - breaks when merged",
|
||||
"ethernet": "Defines ethernet: which conflicts with wifi: used by most components",
|
||||
"ethernet_info": "Related to ethernet component which conflicts with wifi",
|
||||
"gps": "TinyGPSPlus library declares millis() function that creates ambiguity with ESPHome millis() macro when merged with components using millis() in lambdas",
|
||||
"lvgl": "Defines multiple SDL displays on host platform that conflict when merged with other display configs",
|
||||
"mapping": "Uses dict format for image/display sections incompatible with standard list format - ESPHome merge_config cannot handle",
|
||||
"openthread": "Conflicts with wifi: used by most components",
|
||||
|
||||
@@ -58,7 +58,7 @@ def test_text_config_value_mode_set(generate_main):
|
||||
|
||||
def test_text_config_lamda_is_set(generate_main):
|
||||
"""
|
||||
Test if lambda is set for lambda mode (optimized with stateless lambda)
|
||||
Test if lambda is set for lambda mode
|
||||
"""
|
||||
# Given
|
||||
|
||||
@@ -66,5 +66,5 @@ def test_text_config_lamda_is_set(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert "it_4->set_template([]() -> esphome::optional<std::string> {" in main_cpp
|
||||
assert "it_4->set_template([=]() -> esphome::optional<std::string> {" in main_cpp
|
||||
assert 'return std::string{"Hello"};' in main_cpp
|
||||
|
||||
@@ -6,14 +6,14 @@ sensor:
|
||||
- platform: template
|
||||
id: template_humidity
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
if (esphome::millis() > 10000) {
|
||||
return 0.6;
|
||||
}
|
||||
return 0.0;
|
||||
- platform: template
|
||||
id: template_temperature
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
if (esphome::millis() > 10000) {
|
||||
return 42.0;
|
||||
}
|
||||
return 0.0;
|
||||
|
||||
@@ -3,7 +3,7 @@ sensor:
|
||||
id: template_sensor
|
||||
name: Template Sensor
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
if (esphome::millis() > 10000) {
|
||||
return 42.0;
|
||||
}
|
||||
return 0.0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user