From 2d9922496cd94ed43a0a5eec7193ff9f581c48a8 Mon Sep 17 00:00:00 2001 From: Diorcet Yann Date: Fri, 27 Mar 2026 17:02:45 +0100 Subject: [PATCH 01/38] [git] Add support for subpath to computed destination directory (#15135) --- esphome/git.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/git.py b/esphome/git.py index a45768b5cd..096ff483a7 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -102,6 +102,7 @@ def clone_or_update( username: str = None, password: str = None, submodules: list[str] | None = None, + subpath: Path | None = None, _recover_broken: bool = True, ) -> tuple[Path, Callable[[], None] | None]: key = f"{url}@{ref}" @@ -112,6 +113,9 @@ def clone_or_update( ) repo_dir = _compute_destination_path(key, domain) + if subpath: + repo_dir = repo_dir / subpath + if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) From 73e939ffb5fa41be407812fa069be76f45d968e6 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:13:24 -0400 Subject: [PATCH 02/38] [sgp4x] Fix NOx index_offset default (should be 1, not 100) (#15212) --- esphome/components/sgp4x/sensor.py | 39 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py index ab78ab59d9..8d52ffb4f2 100644 --- a/esphome/components/sgp4x/sensor.py +++ b/esphome/components/sgp4x/sensor.py @@ -44,20 +44,27 @@ def validate_sensors(config): return config -GAS_SENSOR = cv.Schema( - { - cv.Optional(CONF_ALGORITHM_TUNING): cv.Schema( - { - cv.Optional(CONF_INDEX_OFFSET, default=100): cv.int_, - cv.Optional(CONF_LEARNING_TIME_OFFSET_HOURS, default=12): cv.int_, - cv.Optional(CONF_LEARNING_TIME_GAIN_HOURS, default=12): cv.int_, - cv.Optional(CONF_GATING_MAX_DURATION_MINUTES, default=720): cv.int_, - cv.Optional(CONF_STD_INITIAL, default=50): cv.int_, - cv.Optional(CONF_GAIN_FACTOR, default=230): cv.int_, - } - ) - } -) +def _gas_sensor_schema(index_offset_default: int): + return cv.Schema( + { + cv.Optional(CONF_ALGORITHM_TUNING): cv.Schema( + { + cv.Optional( + CONF_INDEX_OFFSET, default=index_offset_default + ): cv.int_, + cv.Optional(CONF_LEARNING_TIME_OFFSET_HOURS, default=12): cv.int_, + cv.Optional(CONF_LEARNING_TIME_GAIN_HOURS, default=12): cv.int_, + cv.Optional(CONF_GATING_MAX_DURATION_MINUTES, default=720): cv.int_, + cv.Optional(CONF_STD_INITIAL, default=50): cv.int_, + cv.Optional(CONF_GAIN_FACTOR, default=230): cv.int_, + } + ) + } + ) + + +VOC_SENSOR = _gas_sensor_schema(100) +NOX_SENSOR = _gas_sensor_schema(1) CONFIG_SCHEMA = cv.All( cv.Schema( @@ -68,13 +75,13 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, - ).extend(GAS_SENSOR), + ).extend(VOC_SENSOR), cv.Optional(CONF_NOX): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_AQI, state_class=STATE_CLASS_MEASUREMENT, - ).extend(GAS_SENSOR), + ).extend(NOX_SENSOR), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_COMPENSATION): cv.Schema( From 1e65165e48274acfd30a8cfd18867608b6dff414 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:19:58 -1000 Subject: [PATCH 03/38] [safe_mode] Migrate SafeModeTrigger to callback automation (#15197) --- esphome/components/safe_mode/__init__.py | 13 ++++--------- esphome/components/safe_mode/automation.h | 10 ---------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py index e868985054..da36d21eb7 100644 --- a/esphome/components/safe_mode/__init__.py +++ b/esphome/components/safe_mode/__init__.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_NUM_ATTEMPTS, CONF_REBOOT_TIMEOUT, CONF_SAFE_MODE, - CONF_TRIGGER_ID, KEY_PAST_SAFE_MODE, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority @@ -20,7 +19,6 @@ CONF_ON_SAFE_MODE = "on_safe_mode" safe_mode_ns = cg.esphome_ns.namespace("safe_mode") SafeModeComponent = safe_mode_ns.class_("SafeModeComponent", cg.Component) -SafeModeTrigger = safe_mode_ns.class_("SafeModeTrigger", automation.Trigger.template()) MarkSuccessfulAction = safe_mode_ns.class_("MarkSuccessfulAction", automation.Action) @@ -43,11 +41,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ON_SAFE_MODE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SafeModeTrigger), - } - ), + cv.Optional(CONF_ON_SAFE_MODE): automation.validate_automation({}), } ).extend(cv.COMPONENT_SCHEMA), _remove_id_if_disabled, @@ -80,8 +74,9 @@ async def to_code(config): if on_safe_mode_config := config.get(CONF_ON_SAFE_MODE): cg.add_define("USE_SAFE_MODE_CALLBACK") for conf in on_safe_mode_config: - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_safe_mode_callback", [], conf + ) condition = var.should_enter_safe_mode( config[CONF_NUM_ATTEMPTS], diff --git a/esphome/components/safe_mode/automation.h b/esphome/components/safe_mode/automation.h index dee02c64a0..79b53c0881 100644 --- a/esphome/components/safe_mode/automation.h +++ b/esphome/components/safe_mode/automation.h @@ -1,19 +1,9 @@ #pragma once -#include "esphome/core/defines.h" #include "esphome/core/automation.h" #include "safe_mode.h" namespace esphome::safe_mode { -#ifdef USE_SAFE_MODE_CALLBACK -class SafeModeTrigger final : public Trigger<> { - public: - explicit SafeModeTrigger(SafeModeComponent *parent) { - parent->add_on_safe_mode_callback([this]() { trigger(); }); - } -}; -#endif // USE_SAFE_MODE_CALLBACK - template class MarkSuccessfulAction : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->mark_successful(); } From b0f6a94df51a40c163627aa7e12a1c3947369573 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:20:11 -1000 Subject: [PATCH 04/38] [sml] Migrate DataTrigger to callback automation (#15233) --- esphome/components/sml/__init__.py | 23 +++++------------------ esphome/components/sml/automation.h | 19 ------------------- 2 files changed, 5 insertions(+), 37 deletions(-) delete mode 100644 esphome/components/sml/automation.h diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py index eaeddce390..1bf0d97d65 100644 --- a/esphome/components/sml/__init__.py +++ b/esphome/components/sml/__init__.py @@ -4,7 +4,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_DATA, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_DATA CODEOWNERS = ["@alengwenus"] @@ -18,24 +18,11 @@ CONF_SML_ID = "sml_id" CONF_OBIS_CODE = "obis_code" CONF_SERVER_ID = "server_id" -sml_ns = cg.esphome_ns.namespace("sml") - -DataTrigger = sml_ns.class_( - "DataTrigger", - automation.Trigger.template( - cg.std_vector.template(cg.uint8).operator("ref"), cg.bool_ - ), -) - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(Sml), - cv.Optional(CONF_ON_DATA): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DataTrigger), - } - ), + cv.Optional(CONF_ON_DATA): automation.validate_automation({}), } ).extend(uart.UART_DEVICE_SCHEMA) @@ -45,9 +32,9 @@ async def to_code(config): await cg.register_component(var, config) await uart.register_uart_device(var, config) for conf in config.get(CONF_ON_DATA, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, + await automation.build_callback_automation( + var, + "add_on_data_callback", [ ( cg.std_vector.template(cg.uint8).operator("ref").operator("const"), diff --git a/esphome/components/sml/automation.h b/esphome/components/sml/automation.h deleted file mode 100644 index d51063065d..0000000000 --- a/esphome/components/sml/automation.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "sml.h" - -#include - -namespace esphome { -namespace sml { - -class DataTrigger : public Trigger &, bool> { - public: - explicit DataTrigger(Sml *sml) { - sml->add_on_data_callback([this](const std::vector &data, bool valid) { this->trigger(data, valid); }); - } -}; - -} // namespace sml -} // namespace esphome From b41634e19af272b8c146d3912edf2cba3191b2eb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:20:24 -1000 Subject: [PATCH 05/38] [alarm_control_panel] Migrate triggers to callback automation (#15198) --- .../alarm_control_panel/__init__.py | 162 +++++------------- .../alarm_control_panel.cpp | 4 +- .../alarm_control_panel/alarm_control_panel.h | 4 +- .../alarm_control_panel/automation.h | 69 ++------ .../mqtt/mqtt_alarm_control_panel.cpp | 3 +- 5 files changed, 68 insertions(+), 174 deletions(-) diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index aefb18d25c..4ee073a15b 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_ID, CONF_MQTT_ID, CONF_ON_STATE, - CONF_TRIGGER_ID, CONF_WEB_SERVER, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority @@ -34,39 +33,9 @@ CONF_ON_READY = "on_ready" alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel") AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase) -StateTrigger = alarm_control_panel_ns.class_( - "StateTrigger", automation.Trigger.template() -) -TriggeredTrigger = alarm_control_panel_ns.class_( - "TriggeredTrigger", automation.Trigger.template() -) -ClearedTrigger = alarm_control_panel_ns.class_( - "ClearedTrigger", automation.Trigger.template() -) -ArmingTrigger = alarm_control_panel_ns.class_( - "ArmingTrigger", automation.Trigger.template() -) -PendingTrigger = alarm_control_panel_ns.class_( - "PendingTrigger", automation.Trigger.template() -) -ArmedHomeTrigger = alarm_control_panel_ns.class_( - "ArmedHomeTrigger", automation.Trigger.template() -) -ArmedNightTrigger = alarm_control_panel_ns.class_( - "ArmedNightTrigger", automation.Trigger.template() -) -ArmedAwayTrigger = alarm_control_panel_ns.class_( - "ArmedAwayTrigger", automation.Trigger.template() -) -DisarmedTrigger = alarm_control_panel_ns.class_( - "DisarmedTrigger", automation.Trigger.template() -) -ChimeTrigger = alarm_control_panel_ns.class_( - "ChimeTrigger", automation.Trigger.template() -) -ReadyTrigger = alarm_control_panel_ns.class_( - "ReadyTrigger", automation.Trigger.template() -) +StateAnyForwarder = alarm_control_panel_ns.class_("StateAnyForwarder") +StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder") +AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState") ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) @@ -89,61 +58,17 @@ _ALARM_CONTROL_PANEL_SCHEMA = ( cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( mqtt.MQTTAlarmControlPanelComponent ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), - } - ), - cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), - } - ), - cv.Optional(CONF_ON_ARMING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), - } - ), - cv.Optional(CONF_ON_PENDING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), - } - ), - cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), - } - ), - cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), - } - ), - cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), - } - ), - cv.Optional(CONF_ON_DISARMED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), - } - ), - cv.Optional(CONF_ON_CLEARED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), - } - ), - cv.Optional(CONF_ON_CHIME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), - } - ), - cv.Optional(CONF_ON_READY): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), - } - ), + cv.Optional(CONF_ON_STATE): automation.validate_automation({}), + cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMING): automation.validate_automation({}), + cv.Optional(CONF_ON_PENDING): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation({}), + cv.Optional(CONF_ON_DISARMED): automation.validate_automation({}), + cv.Optional(CONF_ON_CLEARED): automation.validate_automation({}), + cv.Optional(CONF_ON_CHIME): automation.validate_automation({}), + cv.Optional(CONF_ON_READY): automation.validate_automation({}), } ) ) @@ -189,38 +114,39 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id( @setup_entity("alarm_control_panel") async def setup_alarm_control_panel_core_(var, config): for conf in config.get(CONF_ON_STATE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_TRIGGERED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_PENDING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMED_HOME, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMED_NIGHT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMED_AWAY, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_DISARMED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_state_callback", [], conf, forwarder=StateAnyForwarder + ) + _STATE_ENTER_MAP = { + CONF_ON_TRIGGERED: AlarmControlPanelState.ACP_STATE_TRIGGERED, + CONF_ON_ARMING: AlarmControlPanelState.ACP_STATE_ARMING, + CONF_ON_PENDING: AlarmControlPanelState.ACP_STATE_PENDING, + CONF_ON_ARMED_HOME: AlarmControlPanelState.ACP_STATE_ARMED_HOME, + CONF_ON_ARMED_NIGHT: AlarmControlPanelState.ACP_STATE_ARMED_NIGHT, + CONF_ON_ARMED_AWAY: AlarmControlPanelState.ACP_STATE_ARMED_AWAY, + CONF_ON_DISARMED: AlarmControlPanelState.ACP_STATE_DISARMED, + } + for conf_key, state_enum in _STATE_ENTER_MAP.items(): + for conf in config.get(conf_key, []): + await automation.build_callback_automation( + var, + "add_on_state_callback", + [], + conf, + forwarder=StateEnterForwarder.template(state_enum), + ) for conf in config.get(CONF_ON_CLEARED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_cleared_callback", [], conf + ) for conf in config.get(CONF_ON_CHIME, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_chime_callback", [], conf + ) for conf in config.get(CONF_ON_READY, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_ready_callback", [], conf + ) if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) if mqtt_id := config.get(CONF_MQTT_ID): diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index 623241851a..fc72c13ce3 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -35,8 +35,8 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { LOG_STR_ARG(alarm_control_panel_state_to_string(state)), LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); this->current_state_ = state; - // Single state callback - triggers check get_state() for specific states - this->state_callback_.call(); + // Single state callback - listeners receive the new state as an argument + this->state_callback_.call(state); #if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_alarm_control_panel_update(this); #endif diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index cf99d359e7..e748b8621b 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -145,8 +145,8 @@ class AlarmControlPanel : public EntityBase { uint32_t last_update_; // the call control function virtual void control(const AlarmControlPanelCall &call) = 0; - // state callback - triggers check get_state() for specific state - LazyCallbackManager state_callback_{}; + // state callback - passes the new state to listeners + LazyCallbackManager state_callback_{}; // clear callback - fires when leaving TRIGGERED state LazyCallbackManager cleared_callback_{}; // chime callback diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index 4ff34de0d5..022d2650d2 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -5,60 +5,27 @@ namespace esphome::alarm_control_panel { -/// Trigger on any state change -class StateTrigger : public Trigger<> { - public: - explicit StateTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_state_callback([this]() { this->trigger(); }); +/// Callback forwarder that triggers an Automation<> on any state change. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +struct StateAnyForwarder { + Automation<> *automation; + void operator()(AlarmControlPanelState /*state*/) const { this->automation->trigger(); } +}; + +/// Callback forwarder that triggers an Automation<> only when the alarm enters a specific state. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +template struct StateEnterForwarder { + Automation<> *automation; + void operator()(AlarmControlPanelState state) const { + if (state == State) + this->automation->trigger(); } }; -/// Template trigger that fires when entering a specific state -template class StateEnterTrigger : public Trigger<> { - public: - explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) { - alarm_control_panel->add_on_state_callback([this]() { - if (this->alarm_control_panel_->get_state() == State) - this->trigger(); - }); - } - - protected: - AlarmControlPanel *alarm_control_panel_; -}; - -// Type aliases for state-specific triggers -using TriggeredTrigger = StateEnterTrigger; -using ArmingTrigger = StateEnterTrigger; -using PendingTrigger = StateEnterTrigger; -using ArmedHomeTrigger = StateEnterTrigger; -using ArmedNightTrigger = StateEnterTrigger; -using ArmedAwayTrigger = StateEnterTrigger; -using DisarmedTrigger = StateEnterTrigger; - -/// Trigger when leaving TRIGGERED state (alarm cleared) -class ClearedTrigger : public Trigger<> { - public: - explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); }); - } -}; - -/// Trigger on chime event (zone opened while disarmed) -class ChimeTrigger : public Trigger<> { - public: - explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); }); - } -}; - -/// Trigger on ready state change -class ReadyTrigger : public Trigger<> { - public: - explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); }); - } -}; +static_assert(sizeof(StateAnyForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(StateEnterForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v>); template class ArmAwayAction : public Action { public: diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 74a60b3624..f059360e23 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -48,7 +48,8 @@ static bool apply_command(AlarmControlPanelCall &call, const char *state) { } void MQTTAlarmControlPanelComponent::setup() { - this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); }); + this->alarm_control_panel_->add_on_state_callback( + [this](AlarmControlPanelState /*state*/) { this->publish_state(); }); this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { auto call = this->alarm_control_panel_->make_call(); if (!payload.empty() && payload[0] == '{') { From dea8fdd906a7fc79648d05dfeb74d6aaadad55e8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:20:35 -1000 Subject: [PATCH 06/38] [lock] Migrate LockStateTrigger to callback automation (#15199) --- esphome/components/copy/lock/copy_lock.cpp | 2 +- esphome/components/lock/__init__.py | 34 ++++++++++------------ esphome/components/lock/automation.h | 22 ++++++-------- esphome/components/lock/lock.cpp | 2 +- esphome/components/lock/lock.h | 4 +-- esphome/components/mqtt/mqtt_lock.cpp | 3 +- 6 files changed, 30 insertions(+), 37 deletions(-) diff --git a/esphome/components/copy/lock/copy_lock.cpp b/esphome/components/copy/lock/copy_lock.cpp index 25bd8c33ef..c846954510 100644 --- a/esphome/components/copy/lock/copy_lock.cpp +++ b/esphome/components/copy/lock/copy_lock.cpp @@ -7,7 +7,7 @@ namespace copy { static const char *const TAG = "copy.lock"; void CopyLock::setup() { - source_->add_on_state_callback([this]() { this->publish_state(source_->state); }); + source_->add_on_state_callback([this](lock::LockState state) { this->publish_state(state); }); traits.set_assumed_state(source_->traits.get_assumed_state()); traits.set_requires_code(source_->traits.get_requires_code()); diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py index fe4db23ae3..0df4b20cba 100644 --- a/esphome/components/lock/__init__.py +++ b/esphome/components/lock/__init__.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_MQTT_ID, CONF_ON_LOCK, CONF_ON_UNLOCK, - CONF_TRIGGER_ID, CONF_WEB_SERVER, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority @@ -31,8 +30,7 @@ OpenAction = lock_ns.class_("OpenAction", automation.Action) LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action) LockCondition = lock_ns.class_("LockCondition", Condition) -LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template()) -LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template()) +LockStateForwarder = lock_ns.class_("LockStateForwarder") LockState = lock_ns.enum("LockState") @@ -52,16 +50,8 @@ _LOCK_SCHEMA = ( .extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent), - cv.Optional(CONF_ON_LOCK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger), - } - ), - cv.Optional(CONF_ON_UNLOCK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger), - } - ), + cv.Optional(CONF_ON_LOCK): automation.validate_automation({}), + cv.Optional(CONF_ON_UNLOCK): automation.validate_automation({}), } ) ) @@ -93,12 +83,18 @@ def lock_schema( @setup_entity("lock") async def _setup_lock_core(var, config): - for conf in config.get(CONF_ON_LOCK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_UNLOCK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + for conf_key, state_enum in ( + (CONF_ON_LOCK, LockState.LOCK_STATE_LOCKED), + (CONF_ON_UNLOCK, LockState.LOCK_STATE_UNLOCKED), + ): + for conf in config.get(conf_key, []): + await automation.build_callback_automation( + var, + "add_on_state_callback", + [], + conf, + forwarder=LockStateForwarder.template(state_enum), + ) if mqtt_id := config.get(CONF_MQTT_ID): mqtt_ = cg.new_Pvariable(mqtt_id, var) diff --git a/esphome/components/lock/automation.h b/esphome/components/lock/automation.h index 6f3c422693..c140bc568f 100644 --- a/esphome/components/lock/automation.h +++ b/esphome/components/lock/automation.h @@ -49,21 +49,17 @@ template class LockCondition : public Condition { bool state_; }; -template class LockStateTrigger : public Trigger<> { - public: - explicit LockStateTrigger(Lock *a_lock) : lock_(a_lock) { - a_lock->add_on_state_callback([this]() { - if (this->lock_->state == State) { - this->trigger(); - } - }); +/// Callback forwarder that triggers an Automation<> only when a specific lock state is entered. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +template struct LockStateForwarder { + Automation<> *automation; + void operator()(LockState state) const { + if (state == State) + this->automation->trigger(); } - - protected: - Lock *lock_; }; -using LockLockTrigger = LockStateTrigger; -using LockUnlockTrigger = LockStateTrigger; +static_assert(sizeof(LockStateForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v>); } // namespace esphome::lock diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index 90937485b9..3ff131af3d 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -42,7 +42,7 @@ void Lock::publish_state(LockState state) { this->state = state; this->rtc_.save(&this->state); ESP_LOGV(TAG, "'%s' >> %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state))); - this->state_callback_.call(); + this->state_callback_.call(state); #if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_lock_update(this); #endif diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index 707431d543..543a4b51a8 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -148,7 +148,7 @@ class Lock : public EntityBase { /** Set callback for state changes. * - * @param callback The void(bool) callback. + * @param callback The void(LockState) callback. */ template void add_on_state_callback(F &&callback) { this->state_callback_.add(std::forward(callback)); @@ -178,7 +178,7 @@ class Lock : public EntityBase { */ virtual void control(const LockCall &call) = 0; - LazyCallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 45d8e4698f..7920187f92 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -28,7 +28,8 @@ void MQTTLockComponent::setup() { this->status_momentary_warning("state", 5000); } }); - this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); }); + this->lock_->add_on_state_callback( + [this](LockState /*state*/) { this->defer("send", [this]() { this->publish_state(); }); }); } void MQTTLockComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str()); From 2e42547d32edce07150f925e7bc8fd0c03f7b814 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:20:46 -1000 Subject: [PATCH 07/38] [media_player] Migrate triggers to callback automation (#15200) Co-authored-by: Claude Opus 4.6 (1M context) --- esphome/components/media_player/__init__.py | 42 ++++++++++--------- esphome/components/media_player/automation.h | 41 ++++++++---------- .../components/media_player/media_player.cpp | 2 +- .../components/media_player/media_player.h | 2 +- .../voice_assistant/voice_assistant.cpp | 4 +- 5 files changed, 44 insertions(+), 47 deletions(-) diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index a5baca2994..767916ad88 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -9,7 +9,6 @@ from esphome.const import ( CONF_ON_STATE, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, - CONF_TRIGGER_ID, CONF_VOLUME, ) from esphome.core import CORE @@ -65,15 +64,19 @@ _COMMAND_ACTIONS = [ "clear_playlist", ] -# State triggers: (config_key, C++ class name) +StateAnyForwarder = media_player_ns.class_("StateAnyForwarder") +StateEnterForwarder = media_player_ns.class_("StateEnterForwarder") +MediaPlayerState = media_player_ns.enum("MediaPlayerState") + +# State triggers: (config_key, state enum or None for any-state) _STATE_TRIGGERS = [ - (CONF_ON_STATE, "StateTrigger"), - (CONF_ON_IDLE, "IdleTrigger"), - (CONF_ON_PLAY, "PlayTrigger"), - (CONF_ON_PAUSE, "PauseTrigger"), - (CONF_ON_ANNOUNCEMENT, "AnnouncementTrigger"), - (CONF_ON_TURN_ON, "OnTrigger"), - (CONF_ON_TURN_OFF, "OffTrigger"), + (CONF_ON_STATE, None), + (CONF_ON_IDLE, MediaPlayerState.MEDIA_PLAYER_STATE_IDLE), + (CONF_ON_PLAY, MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING), + (CONF_ON_PAUSE, MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED), + (CONF_ON_ANNOUNCEMENT, MediaPlayerState.MEDIA_PLAYER_STATE_ANNOUNCING), + (CONF_ON_TURN_ON, MediaPlayerState.MEDIA_PLAYER_STATE_ON), + (CONF_ON_TURN_OFF, MediaPlayerState.MEDIA_PLAYER_STATE_OFF), ] # State conditions that all share the same schema and codegen handler @@ -98,10 +101,15 @@ VolumeSetAction = media_player_ns.class_( @setup_entity("media_player") async def setup_media_player_core_(var, config): - for conf_key, _ in _STATE_TRIGGERS: + for conf_key, state_enum in _STATE_TRIGGERS: for conf in config.get(conf_key, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + if state_enum is None: + forwarder = StateAnyForwarder + else: + forwarder = StateEnterForwarder.template(state_enum) + await automation.build_callback_automation( + var, "add_on_state_callback", [], conf, forwarder=forwarder + ) async def register_media_player(var, config): @@ -120,14 +128,8 @@ async def new_media_player(config, *args): _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { - cv.Optional(conf_key): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - media_player_ns.class_(class_name, automation.Trigger.template()) - ), - } - ) - for conf_key, class_name in _STATE_TRIGGERS + cv.Optional(conf_key): automation.validate_automation({}) + for conf_key, _ in _STATE_TRIGGERS } ) diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index 031f6657f4..658381ef90 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -71,32 +71,27 @@ template class VolumeSetAction : public Action, public Pa void play(const Ts &...x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); } }; -class StateTrigger : public Trigger<> { - public: - explicit StateTrigger(MediaPlayer *player) { - player->add_on_state_callback([this]() { this->trigger(); }); +/// Callback forwarder that triggers an Automation<> on any state change. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +struct StateAnyForwarder { + Automation<> *automation; + void operator()(MediaPlayerState /*state*/) const { this->automation->trigger(); } +}; + +/// Callback forwarder that triggers an Automation<> only when a specific media player state is entered. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +template struct StateEnterForwarder { + Automation<> *automation; + void operator()(MediaPlayerState state) const { + if (state == State) + this->automation->trigger(); } }; -template class MediaPlayerStateTrigger : public Trigger<> { - public: - explicit MediaPlayerStateTrigger(MediaPlayer *player) : player_(player) { - player->add_on_state_callback([this]() { - if (this->player_->state == State) - this->trigger(); - }); - } - - protected: - MediaPlayer *player_; -}; - -using IdleTrigger = MediaPlayerStateTrigger; -using PlayTrigger = MediaPlayerStateTrigger; -using PauseTrigger = MediaPlayerStateTrigger; -using AnnouncementTrigger = MediaPlayerStateTrigger; -using OnTrigger = MediaPlayerStateTrigger; -using OffTrigger = MediaPlayerStateTrigger; +static_assert(sizeof(StateAnyForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(StateEnterForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v>); template class IsIdleCondition : public Condition, public Parented { public: diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index a0eb7b5500..48d23fa0b1 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -199,7 +199,7 @@ MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) { } void MediaPlayer::publish_state() { - this->state_callback_.call(); + this->state_callback_.call(this->state); #if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_media_player_update(this); #endif diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 26eca469e7..d5d0020797 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -168,7 +168,7 @@ class MediaPlayer : public EntityBase { virtual void control(const MediaPlayerCall &call) = 0; - LazyCallbackManager state_callback_{}; + LazyCallbackManager state_callback_{}; }; } // namespace media_player diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 15124e422f..ddce606b2c 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -39,8 +39,8 @@ void VoiceAssistant::setup() { #ifdef USE_MEDIA_PLAYER if (this->media_player_ != nullptr) { - this->media_player_->add_on_state_callback([this]() { - switch (this->media_player_->state) { + this->media_player_->add_on_state_callback([this](media_player::MediaPlayerState state) { + switch (state) { case media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING: if (this->media_player_response_state_ == MediaPlayerResponseState::URL_SENT) { // State changed to announcing after receiving the url From a2d452684a0cc6620e0a3e1bce746b0ef6a80ff3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:21:03 -1000 Subject: [PATCH 08/38] [ld2450] Migrate LD2450DataTrigger to callback automation (#15201) Co-authored-by: Claude Opus 4.6 (1M context) --- esphome/components/ld2450/__init__.py | 14 +++++--------- esphome/components/ld2450/ld2450.h | 8 -------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/esphome/components/ld2450/__init__.py b/esphome/components/ld2450/__init__.py index 5854a5794c..37bf12bafc 100644 --- a/esphome/components/ld2450/__init__.py +++ b/esphome/components/ld2450/__init__.py @@ -2,7 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_DATA, CONF_THROTTLE, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_ON_DATA, CONF_THROTTLE AUTO_LOAD = ["ld24xx"] DEPENDENCIES = ["uart"] @@ -12,7 +12,6 @@ MULTI_CONF = True ld2450_ns = cg.esphome_ns.namespace("ld2450") LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice) -LD2450DataTrigger = ld2450_ns.class_("LD2450DataTrigger", automation.Trigger.template()) CONF_LD2450_ID = "ld2450_id" @@ -23,11 +22,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_THROTTLE): cv.invalid( f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead" ), - cv.Optional(CONF_ON_DATA): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LD2450DataTrigger), - } - ), + cv.Optional(CONF_ON_DATA): automation.validate_automation({}), } ) .extend(uart.UART_DEVICE_SCHEMA) @@ -54,5 +49,6 @@ async def to_code(config): await cg.register_component(var, config) await uart.register_uart_device(var, config) for conf in config.get(CONF_ON_DATA, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_data_callback", [], conf + ) diff --git a/esphome/components/ld2450/ld2450.h b/esphome/components/ld2450/ld2450.h index e774dd9c75..cbcdec10b3 100644 --- a/esphome/components/ld2450/ld2450.h +++ b/esphome/components/ld2450/ld2450.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/automation.h" #include "esphome/core/defines.h" #include "esphome/core/component.h" #ifdef USE_SENSOR @@ -201,11 +200,4 @@ class LD2450Component : public Component, public uart::UARTDevice { LazyCallbackManager data_callback_; }; -class LD2450DataTrigger : public Trigger<> { - public: - explicit LD2450DataTrigger(LD2450Component *parent) { - parent->add_on_data_callback([this]() { this->trigger(); }); - } -}; - } // namespace esphome::ld2450 From 83b3187126be87ac2d7a97db9dd340d8679180e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:21:16 -1000 Subject: [PATCH 09/38] [rtttl] Migrate FinishedPlaybackTrigger to callback automation (#15202) --- esphome/components/rtttl/__init__.py | 25 +++++-------------------- esphome/components/rtttl/rtttl.h | 7 ------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 3566734200..638e950ba6 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -5,14 +5,7 @@ import esphome.codegen as cg from esphome.components.output import FloatOutput from esphome.components.speaker import Speaker import esphome.config_validation as cv -from esphome.const import ( - CONF_GAIN, - CONF_ID, - CONF_OUTPUT, - CONF_PLATFORM, - CONF_SPEAKER, - CONF_TRIGGER_ID, -) +from esphome.const import CONF_GAIN, CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_SPEAKER import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) @@ -26,9 +19,6 @@ rtttl_ns = cg.esphome_ns.namespace("rtttl") Rtttl = rtttl_ns.class_("Rtttl", cg.Component) PlayAction = rtttl_ns.class_("PlayAction", automation.Action) StopAction = rtttl_ns.class_("StopAction", automation.Action) -FinishedPlaybackTrigger = rtttl_ns.class_( - "FinishedPlaybackTrigger", automation.Trigger.template() -) IsPlayingCondition = rtttl_ns.class_("IsPlayingCondition", automation.Condition) MULTI_CONF = True @@ -40,13 +30,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_OUTPUT): cv.use_id(FloatOutput), cv.Optional(CONF_SPEAKER): cv.use_id(Speaker), cv.Optional(CONF_GAIN, default="0.6"): cv.percentage, - cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FinishedPlaybackTrigger - ), - } - ), + cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({}), } ).extend(cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_OUTPUT, CONF_SPEAKER), @@ -103,8 +87,9 @@ async def to_code(config): cg.add(var.set_gain(config[CONF_GAIN])) for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_finished_playback_callback", [], conf + ) @automation.register_action( diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index bff43d2edd..98ed9ba1bf 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -131,11 +131,4 @@ template class IsPlayingCondition : public Condition, pub bool check(const Ts &...x) override { return this->parent_->is_playing(); } }; -class FinishedPlaybackTrigger : public Trigger<> { - public: - explicit FinishedPlaybackTrigger(Rtttl *parent) { - parent->add_on_finished_playback_callback([this]() { this->trigger(); }); - } -}; - } // namespace esphome::rtttl From 4493d2efb6582f020b6ed73879ab56566c088779 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:21:27 -1000 Subject: [PATCH 10/38] [online_image] Migrate triggers to callback automation (#15216) --- esphome/components/online_image/__init__.py | 41 ++++--------------- .../components/online_image/online_image.h | 14 ------- 2 files changed, 9 insertions(+), 46 deletions(-) diff --git a/esphome/components/online_image/__init__.py b/esphome/components/online_image/__init__.py index 292e2bb3bb..5b8294c70e 100644 --- a/esphome/components/online_image/__init__.py +++ b/esphome/components/online_image/__init__.py @@ -7,14 +7,7 @@ from esphome.components.const import CONF_REQUEST_HEADERS from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent from esphome.components.image import CONF_TRANSPARENCY, add_metadata import esphome.config_validation as cv -from esphome.const import ( - CONF_BUFFER_SIZE, - CONF_ID, - CONF_ON_ERROR, - CONF_TRIGGER_ID, - CONF_TYPE, - CONF_URL, -) +from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_ON_ERROR, CONF_TYPE, CONF_URL from esphome.core import Lambda AUTO_LOAD = ["image", "runtime_image"] @@ -41,14 +34,6 @@ ReleaseImageAction = online_image_ns.class_( "OnlineImageReleaseAction", automation.Action, cg.Parented.template(OnlineImage) ) -# Triggers -DownloadFinishedTrigger = online_image_ns.class_( - "DownloadFinishedTrigger", automation.Trigger.template() -) -DownloadErrorTrigger = online_image_ns.class_( - "DownloadErrorTrigger", automation.Trigger.template() -) - ONLINE_IMAGE_SCHEMA = ( runtime_image.runtime_image_schema(OnlineImage) @@ -61,18 +46,8 @@ ONLINE_IMAGE_SCHEMA = ( cv.Optional(CONF_REQUEST_HEADERS): cv.All( cv.Schema({cv.string: cv.templatable(cv.string)}) ), - cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DownloadFinishedTrigger - ), - } - ), - cv.Optional(CONF_ON_ERROR): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DownloadErrorTrigger), - } - ), + cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation({}), + cv.Optional(CONF_ON_ERROR): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("never")) @@ -165,9 +140,11 @@ async def to_code(config): cg.add(var.add_request_header(key, value)) for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(bool, "cached")], conf) + await automation.build_callback_automation( + var, "add_on_finished_callback", [(bool, "cached")], conf + ) for conf in config.get(CONF_ON_ERROR, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_error_callback", [], conf + ) diff --git a/esphome/components/online_image/online_image.h b/esphome/components/online_image/online_image.h index 3a348cbb07..816d6525ea 100644 --- a/esphome/components/online_image/online_image.h +++ b/esphome/components/online_image/online_image.h @@ -129,18 +129,4 @@ template class OnlineImageReleaseAction : public Action { OnlineImage *parent_; }; -class DownloadFinishedTrigger : public Trigger { - public: - explicit DownloadFinishedTrigger(OnlineImage *parent) { - parent->add_on_finished_callback([this](bool cached) { this->trigger(cached); }); - } -}; - -class DownloadErrorTrigger : public Trigger<> { - public: - explicit DownloadErrorTrigger(OnlineImage *parent) { - parent->add_on_error_callback([this]() { this->trigger(); }); - } -}; - } // namespace esphome::online_image From 54283a2599cf5f6958bbb329745afd0dbb800f2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:21:41 -1000 Subject: [PATCH 11/38] [rotary_encoder] Migrate triggers to callback automation (#15217) --- .../rotary_encoder/rotary_encoder.h | 14 -------- esphome/components/rotary_encoder/sensor.py | 34 +++++-------------- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 4b776fe55e..6f4a4fd83c 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -118,19 +118,5 @@ template class RotaryEncoderSetValueAction : public Action { - public: - explicit RotaryEncoderClockwiseTrigger(RotaryEncoderSensor *parent) { - parent->add_on_clockwise_callback([this]() { this->trigger(); }); - } -}; - -class RotaryEncoderAnticlockwiseTrigger : public Trigger<> { - public: - explicit RotaryEncoderAnticlockwiseTrigger(RotaryEncoderSensor *parent) { - parent->add_on_anticlockwise_callback([this]() { this->trigger(); }); - } -}; - } // namespace rotary_encoder } // namespace esphome diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index be315db55d..e64e44f7c1 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_PIN_B, CONF_RESOLUTION, CONF_RESTORE_MODE, - CONF_TRIGGER_ID, CONF_VALUE, ICON_ROTATE_RIGHT, UNIT_STEPS, @@ -43,13 +42,6 @@ RotaryEncoderSetValueAction = rotary_encoder_ns.class_( "RotaryEncoderSetValueAction", automation.Action ) -RotaryEncoderClockwiseTrigger = rotary_encoder_ns.class_( - "RotaryEncoderClockwiseTrigger", automation.Trigger -) -RotaryEncoderAnticlockwiseTrigger = rotary_encoder_ns.class_( - "RotaryEncoderAnticlockwiseTrigger", automation.Trigger -) - def validate_min_max_value(config): if CONF_MIN_VALUE in config and CONF_MAX_VALUE in config: @@ -81,20 +73,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_ZERO"): cv.enum( RESTORE_MODES, upper=True, space="_" ), - cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - RotaryEncoderClockwiseTrigger - ), - } - ), - cv.Optional(CONF_ON_ANTICLOCKWISE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - RotaryEncoderAnticlockwiseTrigger - ), - } - ), + cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation({}), + cv.Optional(CONF_ON_ANTICLOCKWISE): automation.validate_automation({}), } ) .extend(cv.COMPONENT_SCHEMA), @@ -123,11 +103,13 @@ async def to_code(config): cg.add(var.set_max_value(config[CONF_MAX_VALUE])) for conf in config.get(CONF_ON_CLOCKWISE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_clockwise_callback", [], conf + ) for conf in config.get(CONF_ON_ANTICLOCKWISE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_anticlockwise_callback", [], conf + ) @automation.register_action( From 514df6c99af94915523d26e45ad489d4a5ff60d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:21:52 -1000 Subject: [PATCH 12/38] [dfplayer] Migrate FinishedPlaybackTrigger to callback automation (#15218) --- esphome/components/dfplayer/__init__.py | 18 +++++------------- esphome/components/dfplayer/dfplayer.h | 7 ------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 9df108c9c0..c49420f060 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -2,16 +2,13 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_TRIGGER_ID, CONF_VOLUME +from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_VOLUME DEPENDENCIES = ["uart"] CODEOWNERS = ["@glmnet"] dfplayer_ns = cg.esphome_ns.namespace("dfplayer") DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component) -DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_( - "DFPlayerFinishedPlaybackTrigger", automation.Trigger.template() -) DFPlayerIsPlayingCondition = dfplayer_ns.class_( "DFPlayerIsPlayingCondition", automation.Condition ) @@ -58,13 +55,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(DFPlayer), - cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DFPlayerFinishedPlaybackTrigger - ), - } - ), + cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({}), } ).extend(uart.UART_DEVICE_SCHEMA) ) @@ -79,8 +70,9 @@ async def to_code(config): await uart.register_uart_device(var, config) for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_finished_playback_callback", [], conf + ) @automation.register_action( diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index 2c4ee03470..0d240566c3 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -171,12 +171,5 @@ template class DFPlayerIsPlayingCondition : public Conditionparent_->is_playing(); } }; -class DFPlayerFinishedPlaybackTrigger : public Trigger<> { - public: - explicit DFPlayerFinishedPlaybackTrigger(DFPlayer *parent) { - parent->add_on_finished_playback_callback([this]() { this->trigger(); }); - } -}; - } // namespace dfplayer } // namespace esphome From 623408bbfe2bff8c41964b73afa2a23fbd208e10 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:22:02 -1000 Subject: [PATCH 13/38] [hlk_fm22x] Migrate triggers to callback automation (#15219) --- esphome/components/hlk_fm22x/__init__.py | 109 ++++++----------------- esphome/components/hlk_fm22x/hlk_fm22x.h | 46 ---------- 2 files changed, 28 insertions(+), 127 deletions(-) diff --git a/esphome/components/hlk_fm22x/__init__.py b/esphome/components/hlk_fm22x/__init__.py index cb6d5cdfd6..c0349319d1 100644 --- a/esphome/components/hlk_fm22x/__init__.py +++ b/esphome/components/hlk_fm22x/__init__.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_NAME, CONF_ON_ENROLLMENT_DONE, CONF_ON_ENROLLMENT_FAILED, - CONF_TRIGGER_ID, ) CODEOWNERS = ["@OnFreund"] @@ -28,33 +27,6 @@ HlkFm22xComponent = hlk_fm22x_ns.class_( "HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice ) -FaceScanMatchedTrigger = hlk_fm22x_ns.class_( - "FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string) -) - -FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_( - "FaceScanUnmatchedTrigger", automation.Trigger.template() -) - -FaceScanInvalidTrigger = hlk_fm22x_ns.class_( - "FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8) -) - -FaceInfoTrigger = hlk_fm22x_ns.class_( - "FaceInfoTrigger", - automation.Trigger.template( - cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16 - ), -) - -EnrollmentDoneTrigger = hlk_fm22x_ns.class_( - "EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8) -) - -EnrollmentFailedTrigger = hlk_fm22x_ns.class_( - "EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8) -) - EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action) DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action) DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action) @@ -65,46 +37,14 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(HlkFm22xComponent), - cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FaceScanMatchedTrigger - ), - } - ), + cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation({}), cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FaceScanUnmatchedTrigger - ), - } - ), - cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FaceScanInvalidTrigger - ), - } - ), - cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentDoneTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentFailedTrigger - ), - } + {} ), + cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation({}), + cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("50ms")) @@ -118,23 +58,27 @@ async def to_code(config): await uart.register_uart_device(var, config) for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf + await automation.build_callback_automation( + var, + "add_on_face_scan_matched_callback", + [(cg.int16, "face_id"), (cg.std_string, "name")], + conf, ) for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_face_scan_unmatched_callback", [], conf + ) for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint8, "error")], conf) + await automation.build_callback_automation( + var, "add_on_face_scan_invalid_callback", [(cg.uint8, "error")], conf + ) for conf in config.get(CONF_ON_FACE_INFO, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, + await automation.build_callback_automation( + var, + "add_on_face_info_callback", [ (cg.int16, "status"), (cg.int16, "left"), @@ -149,14 +93,17 @@ async def to_code(config): ) for conf in config.get(CONF_ON_ENROLLMENT_DONE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf + await automation.build_callback_automation( + var, + "add_on_enrollment_done_callback", + [(cg.int16, "face_id"), (cg.uint8, "direction")], + conf, ) for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint8, "error")], conf) + await automation.build_callback_automation( + var, "add_on_enrollment_failed_callback", [(cg.uint8, "error")], conf + ) @automation.register_action( diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.h b/esphome/components/hlk_fm22x/hlk_fm22x.h index d897d51881..fd8257b435 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.h +++ b/esphome/components/hlk_fm22x/hlk_fm22x.h @@ -141,52 +141,6 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice { CallbackManager enrollment_failed_callback_; }; -class FaceScanMatchedTrigger : public Trigger { - public: - explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_scan_matched_callback( - [this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); }); - } -}; - -class FaceScanUnmatchedTrigger : public Trigger<> { - public: - explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); }); - } -}; - -class FaceScanInvalidTrigger : public Trigger { - public: - explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); }); - } -}; - -class FaceInfoTrigger : public Trigger { - public: - explicit FaceInfoTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_info_callback( - [this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch, - int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); }); - } -}; - -class EnrollmentDoneTrigger : public Trigger { - public: - explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) { - parent->add_on_enrollment_done_callback( - [this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); }); - } -}; - -class EnrollmentFailedTrigger : public Trigger { - public: - explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) { - parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); }); - } -}; - template class EnrollmentAction : public Action, public Parented { public: TEMPLATABLE_VALUE(std::string, name) From a4a8fa3027088c2a9c3ef8cc96c004cfabfe36b5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:22:14 -1000 Subject: [PATCH 14/38] [pn532] Migrate PN532OnFinishedWriteTrigger to callback automation (#15220) --- esphome/components/pn532/__init__.py | 17 ++++------------- esphome/components/pn532/pn532.h | 7 ------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index 6f679ed10a..4ccda49a72 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -19,10 +19,6 @@ CONF_PN532_ID = "pn532_id" pn532_ns = cg.esphome_ns.namespace("pn532") PN532 = pn532_ns.class_("PN532", cg.PollingComponent) -PN532OnFinishedWriteTrigger = pn532_ns.class_( - "PN532OnFinishedWriteTrigger", automation.Trigger.template() -) - PN532IsWritingCondition = pn532_ns.class_( "PN532IsWritingCondition", automation.Condition ) @@ -35,13 +31,7 @@ PN532_SCHEMA = cv.Schema( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), } ), - cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - PN532OnFinishedWriteTrigger - ), - } - ), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}), cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), @@ -77,8 +67,9 @@ async def setup_pn532(var, config): ) for conf in config.get(CONF_ON_FINISHED_WRITE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_finished_write_callback", [], conf + ) @automation.register_condition( diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 1f6a6b3bc3..b76cbb1946 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -133,13 +133,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { bool found_{false}; }; -class PN532OnFinishedWriteTrigger : public Trigger<> { - public: - explicit PN532OnFinishedWriteTrigger(PN532 *parent) { - parent->add_on_finished_write_callback([this]() { this->trigger(); }); - } -}; - template class PN532IsWritingCondition : public Condition, public Parented { public: bool check(const Ts &...x) override { return this->parent_->is_writing(); } From 985477f2cfa40cc31a97f3169364b73db4d34a91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:22:25 -1000 Subject: [PATCH 15/38] [pn7150][pn7160] Migrate triggers to callback automation (#15221) --- esphome/components/pn7150/__init__.py | 34 ++++++-------------------- esphome/components/pn7150/automation.h | 14 ----------- esphome/components/pn7160/__init__.py | 34 ++++++-------------------- esphome/components/pn7160/automation.h | 14 ----------- 4 files changed, 16 insertions(+), 80 deletions(-) diff --git a/esphome/components/pn7150/__init__.py b/esphome/components/pn7150/__init__.py index 6af1412881..c8723dc31c 100644 --- a/esphome/components/pn7150/__init__.py +++ b/esphome/components/pn7150/__init__.py @@ -50,14 +50,6 @@ SetWriteMessageAction = pn7150_ns.class_("SetWriteMessageAction", automation.Act SetWriteModeAction = pn7150_ns.class_("SetWriteModeAction", automation.Action) -PN7150OnEmulatedTagScanTrigger = pn7150_ns.class_( - "PN7150OnEmulatedTagScanTrigger", automation.Trigger.template() -) - -PN7150OnFinishedWriteTrigger = pn7150_ns.class_( - "PN7150OnFinishedWriteTrigger", automation.Trigger.template() -) - PN7150IsWritingCondition = pn7150_ns.class_( "PN7150IsWritingCondition", automation.Condition ) @@ -83,20 +75,8 @@ SET_MESSAGE_ACTION_SCHEMA = cv.Schema( PN7150_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(PN7150), - cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - PN7150OnEmulatedTagScanTrigger - ), - } - ), - cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - PN7150OnFinishedWriteTrigger - ), - } - ), + cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}), cv.Optional(CONF_ON_TAG): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), @@ -215,12 +195,14 @@ async def setup_pn7150(var, config): ) for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_emulated_tag_scan_callback", [], conf + ) for conf in config.get(CONF_ON_FINISHED_WRITE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_finished_write_callback", [], conf + ) @automation.register_condition( diff --git a/esphome/components/pn7150/automation.h b/esphome/components/pn7150/automation.h index 21329a998a..a8c65ae633 100644 --- a/esphome/components/pn7150/automation.h +++ b/esphome/components/pn7150/automation.h @@ -7,20 +7,6 @@ namespace esphome { namespace pn7150 { -class PN7150OnEmulatedTagScanTrigger : public Trigger<> { - public: - explicit PN7150OnEmulatedTagScanTrigger(PN7150 *parent) { - parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); }); - } -}; - -class PN7150OnFinishedWriteTrigger : public Trigger<> { - public: - explicit PN7150OnFinishedWriteTrigger(PN7150 *parent) { - parent->add_on_finished_write_callback([this]() { this->trigger(); }); - } -}; - template class PN7150IsWritingCondition : public Condition, public Parented { public: bool check(const Ts &...x) override { return this->parent_->is_writing(); } diff --git a/esphome/components/pn7160/__init__.py b/esphome/components/pn7160/__init__.py index 54e4b74796..e382594b93 100644 --- a/esphome/components/pn7160/__init__.py +++ b/esphome/components/pn7160/__init__.py @@ -52,14 +52,6 @@ SetWriteMessageAction = pn7160_ns.class_("SetWriteMessageAction", automation.Act SetWriteModeAction = pn7160_ns.class_("SetWriteModeAction", automation.Action) -PN7160OnEmulatedTagScanTrigger = pn7160_ns.class_( - "PN7160OnEmulatedTagScanTrigger", automation.Trigger.template() -) - -PN7160OnFinishedWriteTrigger = pn7160_ns.class_( - "PN7160OnFinishedWriteTrigger", automation.Trigger.template() -) - PN7160IsWritingCondition = pn7160_ns.class_( "PN7160IsWritingCondition", automation.Condition ) @@ -85,20 +77,8 @@ SET_MESSAGE_ACTION_SCHEMA = cv.Schema( PN7160_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(PN7160), - cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - PN7160OnEmulatedTagScanTrigger - ), - } - ), - cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - PN7160OnFinishedWriteTrigger - ), - } - ), + cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}), cv.Optional(CONF_ON_TAG): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), @@ -227,12 +207,14 @@ async def setup_pn7160(var, config): ) for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_emulated_tag_scan_callback", [], conf + ) for conf in config.get(CONF_ON_FINISHED_WRITE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_finished_write_callback", [], conf + ) @automation.register_condition( diff --git a/esphome/components/pn7160/automation.h b/esphome/components/pn7160/automation.h index 08148c2311..7759da8f53 100644 --- a/esphome/components/pn7160/automation.h +++ b/esphome/components/pn7160/automation.h @@ -7,20 +7,6 @@ namespace esphome { namespace pn7160 { -class PN7160OnEmulatedTagScanTrigger : public Trigger<> { - public: - explicit PN7160OnEmulatedTagScanTrigger(PN7160 *parent) { - parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); }); - } -}; - -class PN7160OnFinishedWriteTrigger : public Trigger<> { - public: - explicit PN7160OnFinishedWriteTrigger(PN7160 *parent) { - parent->add_on_finished_write_callback([this]() { this->trigger(); }); - } -}; - template class PN7160IsWritingCondition : public Condition, public Parented { public: bool check(const Ts &...x) override { return this->parent_->is_writing(); } From a5416df6155172ff80869caa8e183cd58e552e18 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:22:36 -1000 Subject: [PATCH 16/38] [sim800l] Migrate triggers to callback automation (#15222) --- esphome/components/sim800l/__init__.py | 93 +++++++------------------- esphome/components/sim800l/sim800l.h | 35 ---------- 2 files changed, 23 insertions(+), 105 deletions(-) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index ebb74302a9..91771047e1 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -2,7 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MESSAGE, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_MESSAGE DEPENDENCIES = ["uart"] CODEOWNERS = ["@glmnet"] @@ -11,28 +11,6 @@ MULTI_CONF = True sim800l_ns = cg.esphome_ns.namespace("sim800l") Sim800LComponent = sim800l_ns.class_("Sim800LComponent", cg.Component) -Sim800LReceivedMessageTrigger = sim800l_ns.class_( - "Sim800LReceivedMessageTrigger", - automation.Trigger.template(cg.std_string, cg.std_string), -) -Sim800LIncomingCallTrigger = sim800l_ns.class_( - "Sim800LIncomingCallTrigger", - automation.Trigger.template(cg.std_string), -) -Sim800LCallConnectedTrigger = sim800l_ns.class_( - "Sim800LCallConnectedTrigger", - automation.Trigger.template(), -) -Sim800LCallDisconnectedTrigger = sim800l_ns.class_( - "Sim800LCallDisconnectedTrigger", - automation.Trigger.template(), -) - -Sim800LReceivedUssdTrigger = sim800l_ns.class_( - "Sim800LReceivedUssdTrigger", - automation.Trigger.template(cg.std_string), -) - # Actions Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) Sim800LSendUssdAction = sim800l_ns.class_("Sim800LSendUssdAction", automation.Action) @@ -55,41 +33,11 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(Sim800LComponent), - cv.Optional(CONF_ON_SMS_RECEIVED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - Sim800LReceivedMessageTrigger - ), - } - ), - cv.Optional(CONF_ON_INCOMING_CALL): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - Sim800LIncomingCallTrigger - ), - } - ), - cv.Optional(CONF_ON_CALL_CONNECTED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - Sim800LCallConnectedTrigger - ), - } - ), - cv.Optional(CONF_ON_CALL_DISCONNECTED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - Sim800LCallDisconnectedTrigger - ), - } - ), - cv.Optional(CONF_ON_USSD_RECEIVED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - Sim800LReceivedUssdTrigger - ), - } - ), + cv.Optional(CONF_ON_SMS_RECEIVED): automation.validate_automation({}), + cv.Optional(CONF_ON_INCOMING_CALL): automation.validate_automation({}), + cv.Optional(CONF_ON_CALL_CONNECTED): automation.validate_automation({}), + cv.Optional(CONF_ON_CALL_DISCONNECTED): automation.validate_automation({}), + cv.Optional(CONF_ON_USSD_RECEIVED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("5s")) @@ -106,23 +54,28 @@ async def to_code(config): await uart.register_uart_device(var, config) for conf in config.get(CONF_ON_SMS_RECEIVED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.std_string, "message"), (cg.std_string, "sender")], conf + await automation.build_callback_automation( + var, + "add_on_sms_received_callback", + [(cg.std_string, "message"), (cg.std_string, "sender")], + conf, ) for conf in config.get(CONF_ON_INCOMING_CALL, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "caller_id")], conf) + await automation.build_callback_automation( + var, "add_on_incoming_call_callback", [(cg.std_string, "caller_id")], conf + ) for conf in config.get(CONF_ON_CALL_CONNECTED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_on_call_connected_callback", [], conf + ) for conf in config.get(CONF_ON_CALL_DISCONNECTED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_on_call_disconnected_callback", [], conf + ) for conf in config.get(CONF_ON_USSD_RECEIVED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "ussd")], conf) + await automation.build_callback_automation( + var, "add_on_ussd_received_callback", [(cg.std_string, "ussd")], conf + ) SIM800L_SEND_SMS_SCHEMA = cv.Schema( diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index d79279ea72..d0da123039 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -121,41 +121,6 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { CallbackManager ussd_received_callback_; }; -class Sim800LReceivedMessageTrigger : public Trigger { - public: - explicit Sim800LReceivedMessageTrigger(Sim800LComponent *parent) { - parent->add_on_sms_received_callback( - [this](const std::string &message, const std::string &sender) { this->trigger(message, sender); }); - } -}; - -class Sim800LIncomingCallTrigger : public Trigger { - public: - explicit Sim800LIncomingCallTrigger(Sim800LComponent *parent) { - parent->add_on_incoming_call_callback([this](const std::string &caller_id) { this->trigger(caller_id); }); - } -}; - -class Sim800LCallConnectedTrigger : public Trigger<> { - public: - explicit Sim800LCallConnectedTrigger(Sim800LComponent *parent) { - parent->add_on_call_connected_callback([this]() { this->trigger(); }); - } -}; - -class Sim800LCallDisconnectedTrigger : public Trigger<> { - public: - explicit Sim800LCallDisconnectedTrigger(Sim800LComponent *parent) { - parent->add_on_call_disconnected_callback([this]() { this->trigger(); }); - } -}; -class Sim800LReceivedUssdTrigger : public Trigger { - public: - explicit Sim800LReceivedUssdTrigger(Sim800LComponent *parent) { - parent->add_on_ussd_received_callback([this](const std::string &ussd) { this->trigger(ussd); }); - } -}; - template class Sim800LSendSmsAction : public Action { public: Sim800LSendSmsAction(Sim800LComponent *parent) : parent_(parent) {} From 6ffb5af60ced80ff47720fc572c4476475954f97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:22:47 -1000 Subject: [PATCH 17/38] [fingerprint_grow] Migrate triggers to callback automation (#15223) --- .../components/fingerprint_grow/__init__.py | 142 +++++------------- .../fingerprint_grow/fingerprint_grow.h | 58 ------- 2 files changed, 36 insertions(+), 164 deletions(-) diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index 2637097be8..0b01ba7cab 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -21,7 +21,6 @@ from esphome.const import ( CONF_SENSING_PIN, CONF_SPEED, CONF_STATE, - CONF_TRIGGER_ID, ) CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"] @@ -38,38 +37,6 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_( "FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice ) -FingerScanStartTrigger = fingerprint_grow_ns.class_( - "FingerScanStartTrigger", automation.Trigger.template() -) - -FingerScanMatchedTrigger = fingerprint_grow_ns.class_( - "FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16) -) - -FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_( - "FingerScanUnmatchedTrigger", automation.Trigger.template() -) - -FingerScanMisplacedTrigger = fingerprint_grow_ns.class_( - "FingerScanMisplacedTrigger", automation.Trigger.template() -) - -FingerScanInvalidTrigger = fingerprint_grow_ns.class_( - "FingerScanInvalidTrigger", automation.Trigger.template() -) - -EnrollmentScanTrigger = fingerprint_grow_ns.class_( - "EnrollmentScanTrigger", automation.Trigger.template(cg.uint8, cg.uint16) -) - -EnrollmentDoneTrigger = fingerprint_grow_ns.class_( - "EnrollmentDoneTrigger", automation.Trigger.template(cg.uint16) -) - -EnrollmentFailedTrigger = fingerprint_grow_ns.class_( - "EnrollmentFailedTrigger", automation.Trigger.template(cg.uint16) -) - EnrollmentAction = fingerprint_grow_ns.class_("EnrollmentAction", automation.Action) CancelEnrollmentAction = fingerprint_grow_ns.class_( "CancelEnrollmentAction", automation.Action @@ -125,62 +92,22 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, cv.Optional(CONF_PASSWORD): cv.uint32_t, cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t, - cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanStartTrigger - ), - } - ), + cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation({}), cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanMatchedTrigger - ), - } + {} ), cv.Optional(CONF_ON_FINGER_SCAN_UNMATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanUnmatchedTrigger - ), - } + {} ), cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanMisplacedTrigger - ), - } + {} ), cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanInvalidTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentScanTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentDoneTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentFailedTrigger - ), - } + {} ), + cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("500ms")) @@ -214,40 +141,43 @@ async def to_code(config): cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms)) for conf in config.get(CONF_ON_FINGER_SCAN_START, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_on_finger_scan_start_callback", [], conf + ) for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint16, "finger_id"), (cg.uint16, "confidence")], conf + await automation.build_callback_automation( + var, + "add_on_finger_scan_matched_callback", + [(cg.uint16, "finger_id"), (cg.uint16, "confidence")], + conf, ) - for conf in config.get(CONF_ON_FINGER_SCAN_UNMATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_on_finger_scan_unmatched_callback", [], conf + ) for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_on_finger_scan_misplaced_callback", [], conf + ) for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_on_finger_scan_invalid_callback", [], conf + ) for conf in config.get(CONF_ON_ENROLLMENT_SCAN, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint8, "scan_num"), (cg.uint16, "finger_id")], conf + await automation.build_callback_automation( + var, + "add_on_enrollment_scan_callback", + [(cg.uint8, "scan_num"), (cg.uint16, "finger_id")], + conf, ) - for conf in config.get(CONF_ON_ENROLLMENT_DONE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf) - + await automation.build_callback_automation( + var, "add_on_enrollment_done_callback", [(cg.uint16, "finger_id")], conf + ) for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf) + await automation.build_callback_automation( + var, "add_on_enrollment_failed_callback", [(cg.uint16, "finger_id")], conf + ) @automation.register_action( diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index 63839534f6..947c701c98 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -210,64 +210,6 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic CallbackManager enrollment_failed_callback_; }; -class FingerScanStartTrigger : public Trigger<> { - public: - explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_start_callback([this]() { this->trigger(); }); - } -}; - -class FingerScanMatchedTrigger : public Trigger { - public: - explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_matched_callback( - [this](uint16_t finger_id, uint16_t confidence) { this->trigger(finger_id, confidence); }); - } -}; - -class FingerScanUnmatchedTrigger : public Trigger<> { - public: - explicit FingerScanUnmatchedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_unmatched_callback([this]() { this->trigger(); }); - } -}; - -class FingerScanMisplacedTrigger : public Trigger<> { - public: - explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); }); - } -}; - -class FingerScanInvalidTrigger : public Trigger<> { - public: - explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_invalid_callback([this]() { this->trigger(); }); - } -}; - -class EnrollmentScanTrigger : public Trigger { - public: - explicit EnrollmentScanTrigger(FingerprintGrowComponent *parent) { - parent->add_on_enrollment_scan_callback( - [this](uint8_t scan_num, uint16_t finger_id) { this->trigger(scan_num, finger_id); }); - } -}; - -class EnrollmentDoneTrigger : public Trigger { - public: - explicit EnrollmentDoneTrigger(FingerprintGrowComponent *parent) { - parent->add_on_enrollment_done_callback([this](uint16_t finger_id) { this->trigger(finger_id); }); - } -}; - -class EnrollmentFailedTrigger : public Trigger { - public: - explicit EnrollmentFailedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_enrollment_failed_callback([this](uint16_t finger_id) { this->trigger(finger_id); }); - } -}; - template class EnrollmentAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint16_t, finger_id) From a95f9f41fb418a66d9d3d550b69aa29d1fc303ed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:22:58 -1000 Subject: [PATCH 18/38] [ltr_als_ps] Migrate triggers to callback automation (#15224) --- esphome/components/ltr_als_ps/ltr_als_ps.h | 36 +++++----------------- esphome/components/ltr_als_ps/sensor.py | 33 ++++++-------------- 2 files changed, 18 insertions(+), 51 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 2e24a14283..8aa5c9f24b 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -58,6 +58,14 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; } void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; } + template void add_on_ps_high_trigger_callback(F &&callback) { + this->on_ps_high_trigger_callback_.add(std::forward(callback)); + } + + template void add_on_ps_low_trigger_callback(F &&callback) { + this->on_ps_low_trigger_callback_.add(std::forward(callback)); + } + protected: // // Internal state machine, used to split all the actions into @@ -151,36 +159,8 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { } bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; } - // - // Trigger section for the automations - // - friend class LTRPsHighTrigger; - friend class LTRPsLowTrigger; - CallbackManager on_ps_high_trigger_callback_; CallbackManager on_ps_low_trigger_callback_; - - template void add_on_ps_high_trigger_callback_(F &&callback) { - this->on_ps_high_trigger_callback_.add(std::forward(callback)); - } - - template void add_on_ps_low_trigger_callback_(F &&callback) { - this->on_ps_low_trigger_callback_.add(std::forward(callback)); - } -}; - -class LTRPsHighTrigger : public Trigger<> { - public: - explicit LTRPsHighTrigger(LTRAlsPsComponent *parent) { - parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); }); - } -}; - -class LTRPsLowTrigger : public Trigger<> { - public: - explicit LTRPsLowTrigger(LTRAlsPsComponent *parent) { - parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); }); - } }; } // namespace ltr_als_ps } // namespace esphome diff --git a/esphome/components/ltr_als_ps/sensor.py b/esphome/components/ltr_als_ps/sensor.py index 0dbcff1bfb..57503772a1 100644 --- a/esphome/components/ltr_als_ps/sensor.py +++ b/esphome/components/ltr_als_ps/sensor.py @@ -14,7 +14,6 @@ from esphome.const import ( CONF_INTEGRATION_TIME, CONF_NAME, CONF_REPEAT, - CONF_TRIGGER_ID, CONF_TYPE, DEVICE_CLASS_ILLUMINANCE, ICON_BRIGHTNESS_5, @@ -93,11 +92,6 @@ PS_GAINS = { "64X": PsGain.PS_GAIN_64, } -LTRPsHighTrigger = ltr_als_ps_ns.class_( - "LTRPsHighTrigger", automation.Trigger.template() -) -LTRPsLowTrigger = ltr_als_ps_ns.class_("LTRPsLowTrigger", automation.Trigger.template()) - def validate_integration_time(value): value = cv.positive_time_period_milliseconds(value).total_milliseconds @@ -143,16 +137,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range( min=0, max=65535 ), - cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger), - } - ), - cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger), - } - ), + cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation({}), + cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation({}), cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value( sensor.sensor_schema( unit_of_measurement=UNIT_LUX, @@ -244,13 +230,14 @@ async def to_code(config): sens = await sensor.new_sensor(prox_cnt_config) cg.add(var.set_proximity_counts_sensor(sens)) - for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []): - trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], prox_high_tr) - - for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []): - trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], prox_low_tr) + for conf in config.get(CONF_ON_PS_HIGH_THRESHOLD, []): + await automation.build_callback_automation( + var, "add_on_ps_high_trigger_callback", [], conf + ) + for conf in config.get(CONF_ON_PS_LOW_THRESHOLD, []): + await automation.build_callback_automation( + var, "add_on_ps_low_trigger_callback", [], conf + ) cg.add(var.set_ltr_type(config[CONF_TYPE])) From a73c67e4763c971573e89c0d5b4f7e6971ef2341 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:23:17 -1000 Subject: [PATCH 19/38] [ltr501] Migrate triggers to callback automation (#15225) --- esphome/components/ltr501/ltr501.h | 36 +++++++---------------------- esphome/components/ltr501/sensor.py | 31 ++++++++----------------- 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/esphome/components/ltr501/ltr501.h b/esphome/components/ltr501/ltr501.h index 2bd838a0fe..2b91463108 100644 --- a/esphome/components/ltr501/ltr501.h +++ b/esphome/components/ltr501/ltr501.h @@ -58,6 +58,14 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice { void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; } void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; } + template void add_on_ps_high_trigger_callback(F &&callback) { + this->on_ps_high_trigger_callback_.add(std::forward(callback)); + } + + template void add_on_ps_low_trigger_callback(F &&callback) { + this->on_ps_low_trigger_callback_.add(std::forward(callback)); + } + protected: // // Internal state machine, used to split all the actions into @@ -151,36 +159,8 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice { } bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; } - // - // Trigger section for the automations - // - friend class LTRPsHighTrigger; - friend class LTRPsLowTrigger; - CallbackManager on_ps_high_trigger_callback_; CallbackManager on_ps_low_trigger_callback_; - - template void add_on_ps_high_trigger_callback_(F &&callback) { - this->on_ps_high_trigger_callback_.add(std::forward(callback)); - } - - template void add_on_ps_low_trigger_callback_(F &&callback) { - this->on_ps_low_trigger_callback_.add(std::forward(callback)); - } -}; - -class LTRPsHighTrigger : public Trigger<> { - public: - explicit LTRPsHighTrigger(LTRAlsPs501Component *parent) { - parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); }); - } -}; - -class LTRPsLowTrigger : public Trigger<> { - public: - explicit LTRPsLowTrigger(LTRAlsPs501Component *parent) { - parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); }); - } }; } // namespace ltr501 } // namespace esphome diff --git a/esphome/components/ltr501/sensor.py b/esphome/components/ltr501/sensor.py index adaf669a72..712810222c 100644 --- a/esphome/components/ltr501/sensor.py +++ b/esphome/components/ltr501/sensor.py @@ -14,7 +14,6 @@ from esphome.const import ( CONF_INTEGRATION_TIME, CONF_NAME, CONF_REPEAT, - CONF_TRIGGER_ID, CONF_TYPE, DEVICE_CLASS_DISTANCE, DEVICE_CLASS_ILLUMINANCE, @@ -87,9 +86,6 @@ PS_GAINS = { "16X": PsGain.PS_GAIN_16, } -LTRPsHighTrigger = ltr501_ns.class_("LTRPsHighTrigger", automation.Trigger.template()) -LTRPsLowTrigger = ltr501_ns.class_("LTRPsLowTrigger", automation.Trigger.template()) - def validate_integration_time(value): value = cv.positive_time_period_milliseconds(value).total_milliseconds @@ -146,16 +142,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range( min=0, max=65535 ), - cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger), - } - ), - cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger), - } - ), + cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation({}), + cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation({}), cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value( sensor.sensor_schema( unit_of_measurement=UNIT_LUX, @@ -252,13 +240,14 @@ async def to_code(config): sens = await sensor.new_sensor(prox_cnt_config) cg.add(var.set_proximity_counts_sensor(sens)) - for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []): - trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], prox_high_tr) - - for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []): - trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], prox_low_tr) + for conf in config.get(CONF_ON_PS_HIGH_THRESHOLD, []): + await automation.build_callback_automation( + var, "add_on_ps_high_trigger_callback", [], conf + ) + for conf in config.get(CONF_ON_PS_LOW_THRESHOLD, []): + await automation.build_callback_automation( + var, "add_on_ps_low_trigger_callback", [], conf + ) cg.add(var.set_ltr_type(config[CONF_TYPE])) From f5cd1e5e76831637ecf987439e205243a32c1fb6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:23:26 -1000 Subject: [PATCH 20/38] [ld2450] Fix flaky integration test race condition (#15226) --- tests/integration/test_uart_mock_ld2450.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_uart_mock_ld2450.py b/tests/integration/test_uart_mock_ld2450.py index b1aa2f6952..2469273e0a 100644 --- a/tests/integration/test_uart_mock_ld2450.py +++ b/tests/integration/test_uart_mock_ld2450.py @@ -83,11 +83,18 @@ async def test_uart_mock_ld2450( ], ) - # Signal when we see recovery frame values (target 1 distance ≈ 500mm) + # Signal when we see all recovery frame values + # Must wait for ALL values to avoid race where some arrive after the waiter fires recovery_received = collector.add_waiter( lambda: ( pytest.approx(500.0, abs=1.0) in collector.sensor_states["target_1_distance"] + and pytest.approx(300.0) in collector.sensor_states["target_1_x"] + and pytest.approx(400.0) in collector.sensor_states["target_1_y"] + and pytest.approx(30.0) in collector.sensor_states["target_1_speed"] + and pytest.approx(1.0) in collector.sensor_states["target_count"] + and pytest.approx(1.0) in collector.sensor_states["moving_target_count"] + and pytest.approx(0.0) in collector.sensor_states["still_target_count"] ) ) From d77bf23c76b7257d20cd0c9541eabcf2613adf69 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:23:37 -1000 Subject: [PATCH 21/38] [nextion] Migrate triggers to callback automation (#15227) --- esphome/components/nextion/automation.h | 44 ------------ esphome/components/nextion/display.py | 90 +++++++------------------ 2 files changed, 25 insertions(+), 109 deletions(-) diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h index 8e85e15823..9f52507d67 100644 --- a/esphome/components/nextion/automation.h +++ b/esphome/components/nextion/automation.h @@ -5,50 +5,6 @@ namespace esphome { namespace nextion { -class BufferOverflowTrigger : public Trigger<> { - public: - explicit BufferOverflowTrigger(Nextion *nextion) { - nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); }); - } -}; - -class SetupTrigger : public Trigger<> { - public: - explicit SetupTrigger(Nextion *nextion) { - nextion->add_setup_state_callback([this]() { this->trigger(); }); - } -}; - -class SleepTrigger : public Trigger<> { - public: - explicit SleepTrigger(Nextion *nextion) { - nextion->add_sleep_state_callback([this]() { this->trigger(); }); - } -}; - -class WakeTrigger : public Trigger<> { - public: - explicit WakeTrigger(Nextion *nextion) { - nextion->add_wake_state_callback([this]() { this->trigger(); }); - } -}; - -class PageTrigger : public Trigger { - public: - explicit PageTrigger(Nextion *nextion) { - nextion->add_new_page_callback([this](const uint8_t page_id) { this->trigger(page_id); }); - } -}; - -class TouchTrigger : public Trigger { - public: - explicit TouchTrigger(Nextion *nextion) { - nextion->add_touch_event_callback([this](uint8_t page_id, uint8_t component_id, bool touch_event) { - this->trigger(page_id, component_id, touch_event); - }); - } -}; - template class NextionSetBrightnessAction : public Action { public: explicit NextionSetBrightnessAction(Nextion *component) : component_(component) {} diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 5b2dfc488d..506eb1202b 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -2,13 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import display, esp32, uart import esphome.config_validation as cv -from esphome.const import ( - CONF_BRIGHTNESS, - CONF_ID, - CONF_LAMBDA, - CONF_ON_TOUCH, - CONF_TRIGGER_ID, -) +from esphome.const import CONF_BRIGHTNESS, CONF_ID, CONF_LAMBDA, CONF_ON_TOUCH from esphome.core import CORE, TimePeriod from . import ( # noqa: F401 pylint: disable=unused-import @@ -55,14 +49,6 @@ def AUTO_LOAD() -> list[str]: NextionSetBrightnessAction = nextion_ns.class_( "NextionSetBrightnessAction", automation.Action ) -SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template()) -SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) -WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) -PageTrigger = nextion_ns.class_("PageTrigger", automation.Trigger.template()) -TouchTrigger = nextion_ns.class_("TouchTrigger", automation.Trigger.template()) -BufferOverflowTrigger = nextion_ns.class_( - "BufferOverflowTrigger", automation.Trigger.template() -) def _validate_tft_upload(config): @@ -101,38 +87,12 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_MAX_COMMANDS_PER_LOOP): cv.uint16_t, cv.Optional(CONF_MAX_QUEUE_SIZE): cv.positive_int, - cv.Optional(CONF_ON_BUFFER_OVERFLOW): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - BufferOverflowTrigger - ), - } - ), - cv.Optional(CONF_ON_PAGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PageTrigger), - } - ), - cv.Optional(CONF_ON_SETUP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger), - } - ), - cv.Optional(CONF_ON_SLEEP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger), - } - ), - cv.Optional(CONF_ON_TOUCH): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TouchTrigger), - } - ), - cv.Optional(CONF_ON_WAKE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger), - } - ), + cv.Optional(CONF_ON_BUFFER_OVERFLOW): automation.validate_automation({}), + cv.Optional(CONF_ON_PAGE): automation.validate_automation({}), + cv.Optional(CONF_ON_SETUP): automation.validate_automation({}), + cv.Optional(CONF_ON_SLEEP): automation.validate_automation({}), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation({}), + cv.Optional(CONF_ON_WAKE): automation.validate_automation({}), cv.Optional(CONF_SKIP_CONNECTION_HANDSHAKE, default=False): cv.boolean, cv.Optional(CONF_STARTUP_OVERRIDE_MS, default="8000ms"): cv.All( cv.positive_time_period_milliseconds, @@ -273,25 +233,25 @@ async def to_code(config): await display.register_display(var, config) for conf in config.get(CONF_ON_SETUP, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_setup_state_callback", [], conf + ) for conf in config.get(CONF_ON_SLEEP, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_sleep_state_callback", [], conf + ) for conf in config.get(CONF_ON_WAKE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - + await automation.build_callback_automation( + var, "add_wake_state_callback", [], conf + ) for conf in config.get(CONF_ON_PAGE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint8, "x")], conf) - + await automation.build_callback_automation( + var, "add_new_page_callback", [(cg.uint8, "x")], conf + ) for conf in config.get(CONF_ON_TOUCH, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, + await automation.build_callback_automation( + var, + "add_touch_event_callback", [ (cg.uint8, "page_id"), (cg.uint8, "component_id"), @@ -299,7 +259,7 @@ async def to_code(config): ], conf, ) - for conf in config.get(CONF_ON_BUFFER_OVERFLOW, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automation( + var, "add_buffer_overflow_event_callback", [], conf + ) From 2f3c21c7c16b37d2ad1f57e4d90883129a50c86c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:23:50 -1000 Subject: [PATCH 22/38] [ezo] Migrate triggers to callback automation (#15228) --- esphome/components/ezo/automation.h | 53 ---------------- esphome/components/ezo/sensor.py | 94 ++++++++--------------------- 2 files changed, 25 insertions(+), 122 deletions(-) delete mode 100644 esphome/components/ezo/automation.h diff --git a/esphome/components/ezo/automation.h b/esphome/components/ezo/automation.h deleted file mode 100644 index a4a6fa3014..0000000000 --- a/esphome/components/ezo/automation.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include - -#include "esphome/core/automation.h" -#include "ezo.h" - -namespace esphome { -namespace ezo { - -class LedTrigger : public Trigger { - public: - explicit LedTrigger(EZOSensor *ezo) { - ezo->add_led_state_callback([this](bool value) { this->trigger(value); }); - } -}; - -class CustomTrigger : public Trigger { - public: - explicit CustomTrigger(EZOSensor *ezo) { - ezo->add_custom_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class TTrigger : public Trigger { - public: - explicit TTrigger(EZOSensor *ezo) { - ezo->add_t_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class CalibrationTrigger : public Trigger { - public: - explicit CalibrationTrigger(EZOSensor *ezo) { - ezo->add_calibration_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class SlopeTrigger : public Trigger { - public: - explicit SlopeTrigger(EZOSensor *ezo) { - ezo->add_slope_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class DeviceInformationTrigger : public Trigger { - public: - explicit DeviceInformationTrigger(EZOSensor *ezo) { - ezo->add_device_infomation_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -} // namespace ezo -} // namespace esphome diff --git a/esphome/components/ezo/sensor.py b/esphome/components/ezo/sensor.py index cf240faec3..7c81f9c848 100644 --- a/esphome/components/ezo/sensor.py +++ b/esphome/components/ezo/sensor.py @@ -2,7 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.const import CONF_ID CODEOWNERS = ["@ssieb"] @@ -21,61 +21,16 @@ EZOSensor = ezo_ns.class_( "EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice ) -CustomTrigger = ezo_ns.class_( - "CustomTrigger", automation.Trigger.template(cg.std_string) -) - - -TTrigger = ezo_ns.class_("TTrigger", automation.Trigger.template(cg.std_string)) - -SlopeTrigger = ezo_ns.class_("SlopeTrigger", automation.Trigger.template(cg.std_string)) - -CalibrationTrigger = ezo_ns.class_( - "CalibrationTrigger", automation.Trigger.template(cg.std_string) -) - -DeviceInformationTrigger = ezo_ns.class_( - "DeviceInformationTrigger", automation.Trigger.template(cg.std_string) -) - -LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_)) - CONFIG_SCHEMA = ( sensor.sensor_schema(EZOSensor) .extend( { - cv.Optional(CONF_ON_CUSTOM): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger), - } - ), - cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CalibrationTrigger), - } - ), - cv.Optional(CONF_ON_SLOPE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SlopeTrigger), - } - ), - cv.Optional(CONF_ON_T): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TTrigger), - } - ), - cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DeviceInformationTrigger - ), - } - ), - cv.Optional(CONF_ON_LED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LedTrigger), - } - ), + cv.Optional(CONF_ON_CUSTOM): automation.validate_automation({}), + cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation({}), + cv.Optional(CONF_ON_SLOPE): automation.validate_automation({}), + cv.Optional(CONF_ON_T): automation.validate_automation({}), + cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation({}), + cv.Optional(CONF_ON_LED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("60s")) @@ -90,25 +45,26 @@ async def to_code(config): await i2c.register_i2c_device(var, config) for conf in config.get(CONF_ON_CUSTOM, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - + await automation.build_callback_automation( + var, "add_custom_callback", [(cg.std_string, "x")], conf + ) for conf in config.get(CONF_ON_LED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(bool, "x")], conf) - + await automation.build_callback_automation( + var, "add_led_state_callback", [(bool, "x")], conf + ) for conf in config.get(CONF_ON_DEVICE_INFORMATION, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - + await automation.build_callback_automation( + var, "add_device_infomation_callback", [(cg.std_string, "x")], conf + ) for conf in config.get(CONF_ON_SLOPE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - + await automation.build_callback_automation( + var, "add_slope_callback", [(cg.std_string, "x")], conf + ) for conf in config.get(CONF_ON_CALIBRATION, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - + await automation.build_callback_automation( + var, "add_calibration_callback", [(cg.std_string, "x")], conf + ) for conf in config.get(CONF_ON_T, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + await automation.build_callback_automation( + var, "add_t_callback", [(cg.std_string, "x")], conf + ) From 39509265bc76a0eadce17c9e28a4b032fc3597fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:24:03 -1000 Subject: [PATCH 23/38] [haier] Migrate triggers to callback automation (#15229) --- esphome/components/haier/climate.py | 62 ++++++++------------------ esphome/components/haier/haier_base.h | 7 --- esphome/components/haier/hon_climate.h | 16 ------- 3 files changed, 18 insertions(+), 67 deletions(-) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index caaaa18dd6..9c2c999f25 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -22,7 +22,6 @@ from esphome.const import ( CONF_SUPPORTED_SWING_MODES, CONF_TARGET_TEMPERATURE, CONF_TEMPERATURE_STEP, - CONF_TRIGGER_ID, CONF_VISUAL, CONF_WIFI, ) @@ -122,21 +121,6 @@ SUPPORTED_HON_CONTROL_METHODS = { "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, } -HaierAlarmStartTrigger = haier_ns.class_( - "HaierAlarmStartTrigger", - automation.Trigger.template(cg.uint8, cg.const_char_ptr), -) - -HaierAlarmEndTrigger = haier_ns.class_( - "HaierAlarmEndTrigger", - automation.Trigger.template(cg.uint8, cg.const_char_ptr), -) - -StatusMessageTrigger = haier_ns.class_( - "StatusMessageTrigger", - automation.Trigger.template(cg.const_char_ptr, cg.size_t), -) - def validate_visual(config): if CONF_VISUAL in config: @@ -203,13 +187,7 @@ def _base_config_schema(class_: MockObjClass) -> cv.Schema: cv.Optional( CONF_ANSWER_TIMEOUT, ): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - StatusMessageTrigger - ), - } - ), + cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation({}), } ) .extend(uart.UART_DEVICE_SCHEMA) @@ -264,19 +242,9 @@ CONFIG_SCHEMA = cv.All( f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead" ), cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - HaierAlarmStartTrigger - ), - } - ), - cv.Optional(CONF_ON_ALARM_END): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - HaierAlarmEndTrigger - ), - } + {} ), + cv.Optional(CONF_ON_ALARM_END): automation.validate_automation({}), } ), }, @@ -530,19 +498,25 @@ async def to_code(config): var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE]) ) for conf in config.get(CONF_ON_ALARM_START, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf + await automation.build_callback_automation( + var, + "add_alarm_start_callback", + [(cg.uint8, "code"), (cg.const_char_ptr, "message")], + conf, ) for conf in config.get(CONF_ON_ALARM_END, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf + await automation.build_callback_automation( + var, + "add_alarm_end_callback", + [(cg.uint8, "code"), (cg.const_char_ptr, "message")], + conf, ) for conf in config.get(CONF_ON_STATUS_MESSAGE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf + await automation.build_callback_automation( + var, + "add_status_message_callback", + [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], + conf, ) # https://github.com/paveldn/HaierProtocol cg.add_library("pavlodn/HaierProtocol", "0.9.31") diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index 87aa1d65ef..0c416623c0 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -177,12 +177,5 @@ class HaierClimateBase : public esphome::Component, ESPPreferenceObject base_rtc_; }; -class StatusMessageTrigger : public Trigger { - public: - explicit StatusMessageTrigger(HaierClimateBase *parent) { - parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); }); - } -}; - } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index 7c48a3748b..7a87f27b66 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -200,21 +200,5 @@ class HonClimate : public HaierClimateBase { SwitchState quiet_mode_state_{SwitchState::OFF}; }; -class HaierAlarmStartTrigger : public Trigger { - public: - explicit HaierAlarmStartTrigger(HonClimate *parent) { - parent->add_alarm_start_callback( - [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); - } -}; - -class HaierAlarmEndTrigger : public Trigger { - public: - explicit HaierAlarmEndTrigger(HonClimate *parent) { - parent->add_alarm_end_callback( - [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); - } -}; - } // namespace haier } // namespace esphome From f9d41bd36adf4923f0509db82da47c321ffafcac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:24:15 -1000 Subject: [PATCH 24/38] [modbus_controller] Migrate triggers to callback automation (#15230) --- .../components/modbus_controller/__init__.py | 64 ++++++------------- .../components/modbus_controller/automation.h | 35 ---------- 2 files changed, 19 insertions(+), 80 deletions(-) delete mode 100644 esphome/components/modbus_controller/automation.h diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index aea79b2053..dfc43bf23b 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -5,14 +5,7 @@ import esphome.codegen as cg from esphome.components import modbus from esphome.components.const import CONF_ENABLED import esphome.config_validation as cv -from esphome.const import ( - CONF_ADDRESS, - CONF_ID, - CONF_LAMBDA, - CONF_NAME, - CONF_OFFSET, - CONF_TRIGGER_ID, -) +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET from esphome.cpp_helpers import logging from .const import ( @@ -135,17 +128,6 @@ CPP_TYPE_REGISTER_MAP = { "FP32_R": cg.float_, } -ModbusCommandSentTrigger = modbus_controller_ns.class_( - "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) -) - -ModbusOnlineTrigger = modbus_controller_ns.class_( - "ModbusOnlineTrigger", automation.Trigger.template(cg.int_, cg.int_) -) - -ModbusOfflineTrigger = modbus_controller_ns.class_( - "ModbusOfflineTrigger", automation.Trigger.template(cg.int_, cg.int_) -) _LOGGER = logging.getLogger(__name__) @@ -182,23 +164,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_SERVER_REGISTERS, ): cv.ensure_list(ModbusServerRegisterSchema), - cv.Optional(CONF_ON_COMMAND_SENT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ModbusCommandSentTrigger - ), - } - ), - cv.Optional(CONF_ON_ONLINE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger), - } - ), - cv.Optional(CONF_ON_OFFLINE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOfflineTrigger), - } - ), + cv.Optional(CONF_ON_COMMAND_SENT): automation.validate_automation({}), + cv.Optional(CONF_ON_ONLINE): automation.validate_automation({}), + cv.Optional(CONF_ON_OFFLINE): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("60s")) @@ -363,19 +331,25 @@ async def to_code(config): cg.add(var.add_server_register(server_register_var)) await register_modbus_device(var, config) for conf in config.get(CONF_ON_COMMAND_SENT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf + await automation.build_callback_automation( + var, + "add_on_command_sent_callback", + [(cg.int_, "function_code"), (cg.int_, "address")], + conf, ) for conf in config.get(CONF_ON_ONLINE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf + await automation.build_callback_automation( + var, + "add_on_online_callback", + [(cg.int_, "function_code"), (cg.int_, "address")], + conf, ) for conf in config.get(CONF_ON_OFFLINE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf + await automation.build_callback_automation( + var, + "add_on_offline_callback", + [(cg.int_, "function_code"), (cg.int_, "address")], + conf, ) diff --git a/esphome/components/modbus_controller/automation.h b/esphome/components/modbus_controller/automation.h deleted file mode 100644 index b3338192cc..0000000000 --- a/esphome/components/modbus_controller/automation.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "esphome/components/modbus_controller/modbus_controller.h" - -namespace esphome { -namespace modbus_controller { - -class ModbusCommandSentTrigger : public Trigger { - public: - ModbusCommandSentTrigger(ModbusController *a_modbuscontroller) { - a_modbuscontroller->add_on_command_sent_callback( - [this](int function_code, int address) { this->trigger(function_code, address); }); - } -}; - -class ModbusOnlineTrigger : public Trigger { - public: - ModbusOnlineTrigger(ModbusController *a_modbuscontroller) { - a_modbuscontroller->add_on_online_callback( - [this](int function_code, int address) { this->trigger(function_code, address); }); - } -}; - -class ModbusOfflineTrigger : public Trigger { - public: - ModbusOfflineTrigger(ModbusController *a_modbuscontroller) { - a_modbuscontroller->add_on_offline_callback( - [this](int function_code, int address) { this->trigger(function_code, address); }); - } -}; - -} // namespace modbus_controller -} // namespace esphome From 0d67f91facbe654bbb634d95aa16c791ff52a3f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:24:25 -1000 Subject: [PATCH 25/38] [rf_bridge] Migrate triggers to callback automation (#15231) --- esphome/components/rf_bridge/__init__.py | 37 +++++++----------------- esphome/components/rf_bridge/rf_bridge.h | 14 --------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/esphome/components/rf_bridge/__init__.py b/esphome/components/rf_bridge/__init__.py index 934f24b789..c6eb1749c3 100644 --- a/esphome/components/rf_bridge/__init__.py +++ b/esphome/components/rf_bridge/__init__.py @@ -12,7 +12,6 @@ from esphome.const import ( CONF_PROTOCOL, CONF_RAW, CONF_SYNC, - CONF_TRIGGER_ID, ) DEPENDENCIES = ["uart"] @@ -26,14 +25,6 @@ RFBridgeComponent = rf_bridge_ns.class_( RFBridgeData = rf_bridge_ns.struct("RFBridgeData") RFBridgeAdvancedData = rf_bridge_ns.struct("RFBridgeAdvancedData") -RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_( - "RFBridgeReceivedCodeTrigger", automation.Trigger.template(RFBridgeData) -) -RFBridgeReceivedAdvancedCodeTrigger = rf_bridge_ns.class_( - "RFBridgeReceivedAdvancedCodeTrigger", - automation.Trigger.template(RFBridgeAdvancedData), -) - RFBridgeSendCodeAction = rf_bridge_ns.class_( "RFBridgeSendCodeAction", automation.Action ) @@ -65,19 +56,9 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(RFBridgeComponent), - cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - RFBridgeReceivedCodeTrigger - ), - } - ), + cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation({}), cv.Optional(CONF_ON_ADVANCED_CODE_RECEIVED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - RFBridgeReceivedAdvancedCodeTrigger - ), - } + {} ), } ) @@ -92,13 +73,15 @@ async def to_code(config): await uart.register_uart_device(var, config) for conf in config.get(CONF_ON_CODE_RECEIVED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(RFBridgeData, "data")], conf) - + await automation.build_callback_automation( + var, "add_on_code_received_callback", [(RFBridgeData, "data")], conf + ) for conf in config.get(CONF_ON_ADVANCED_CODE_RECEIVED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(RFBridgeAdvancedData, "data")], conf + await automation.build_callback_automation( + var, + "add_on_advanced_code_received_callback", + [(RFBridgeAdvancedData, "data")], + conf, ) diff --git a/esphome/components/rf_bridge/rf_bridge.h b/esphome/components/rf_bridge/rf_bridge.h index e5780c9ebe..571ac6c385 100644 --- a/esphome/components/rf_bridge/rf_bridge.h +++ b/esphome/components/rf_bridge/rf_bridge.h @@ -77,20 +77,6 @@ class RFBridgeComponent : public uart::UARTDevice, public Component { CallbackManager advanced_data_callback_; }; -class RFBridgeReceivedCodeTrigger : public Trigger { - public: - explicit RFBridgeReceivedCodeTrigger(RFBridgeComponent *parent) { - parent->add_on_code_received_callback([this](RFBridgeData data) { this->trigger(data); }); - } -}; - -class RFBridgeReceivedAdvancedCodeTrigger : public Trigger { - public: - explicit RFBridgeReceivedAdvancedCodeTrigger(RFBridgeComponent *parent) { - parent->add_on_advanced_code_received_callback([this](const RFBridgeAdvancedData &data) { this->trigger(data); }); - } -}; - template class RFBridgeSendCodeAction : public Action { public: RFBridgeSendCodeAction(RFBridgeComponent *parent) : parent_(parent) {} From 5a8d6931a8d8a0f1fe05727e2e5d00098aa2dbb4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 08:24:35 -1000 Subject: [PATCH 26/38] [factory_reset] Migrate FastBootTrigger to callback automation (#15232) --- esphome/components/factory_reset/__init__.py | 21 ++++++------------- .../components/factory_reset/factory_reset.h | 6 ------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py index 5784d09ce6..20b191a2b7 100644 --- a/esphome/components/factory_reset/__init__.py +++ b/esphome/components/factory_reset/__init__.py @@ -1,10 +1,9 @@ -from esphome.automation import Trigger, build_automation, validate_automation +from esphome import automation import esphome.codegen as cg from esphome.components.esp8266 import CONF_RESTORE_FROM_FLASH, KEY_ESP8266 import esphome.config_validation as cv from esphome.const import ( CONF_ID, - CONF_TRIGGER_ID, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -18,7 +17,6 @@ CODEOWNERS = ["@anatoly-savchenkov"] factory_reset_ns = cg.esphome_ns.namespace("factory_reset") FactoryResetComponent = factory_reset_ns.class_("FactoryResetComponent", cg.Component) -FastBootTrigger = factory_reset_ns.class_("FastBootTrigger", Trigger, cg.Component) CONF_MAX_DELAY = "max_delay" CONF_RESETS_REQUIRED = "resets_required" @@ -55,11 +53,7 @@ CONFIG_SCHEMA = cv.All( ), ), cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int, - cv.Optional(CONF_ON_INCREMENT): validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FastBootTrigger), - } - ), + cv.Optional(CONF_ON_INCREMENT): automation.validate_automation({}), } ).extend(cv.COMPONENT_SCHEMA), _validate, @@ -88,12 +82,9 @@ async def to_code(config): ) await cg.register_component(var, config) for conf in config.get(CONF_ON_INCREMENT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await build_automation( - trigger, - [ - (cg.uint8, "x"), - (cg.uint8, "target"), - ], + await automation.build_callback_automation( + var, + "add_increment_callback", + [(cg.uint8, "x"), (cg.uint8, "target")], conf, ) diff --git a/esphome/components/factory_reset/factory_reset.h b/esphome/components/factory_reset/factory_reset.h index 34f89d73b6..41ee627c4b 100644 --- a/esphome/components/factory_reset/factory_reset.h +++ b/esphome/components/factory_reset/factory_reset.h @@ -30,12 +30,6 @@ class FactoryResetComponent : public Component { uint8_t required_count_; // The number of boot attempts before fast boot is enabled }; -class FastBootTrigger : public Trigger { - public: - explicit FastBootTrigger(FactoryResetComponent *parent) { - parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); }); - } -}; } // namespace esphome::factory_reset #endif // !defined(USE_RP2040) && !defined(USE_HOST) From 810c046cc68dba48b3748f4a60de2049146beee7 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:25:38 -0400 Subject: [PATCH 27/38] [multiple] Fix misc hardware register bugs (#15208) --- esphome/components/mcp23008/mcp23008.cpp | 4 +++- esphome/components/mcp23017/mcp23017.cpp | 6 ++++-- esphome/components/mcp23s08/mcp23s08.cpp | 15 ++++++++++----- esphome/components/mcp23s17/mcp23s17.cpp | 22 +++++++++++++--------- esphome/components/mmc5603/mmc5603.cpp | 6 +++--- esphome/components/sx1509/sx1509.cpp | 4 ++-- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/esphome/components/mcp23008/mcp23008.cpp b/esphome/components/mcp23008/mcp23008.cpp index 0c34e4971a..64b120daa4 100644 --- a/esphome/components/mcp23008/mcp23008.cpp +++ b/esphome/components/mcp23008/mcp23008.cpp @@ -6,6 +6,8 @@ namespace mcp23008 { static const char *const TAG = "mcp23008"; +static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin + void MCP23008::setup() { uint8_t iocon; if (!this->read_reg(mcp23x08_base::MCP23X08_IOCON, &iocon)) { @@ -18,7 +20,7 @@ void MCP23008::setup() { if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe - this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); + this->write_reg(mcp23x08_base::MCP23X08_IOCON, iocon | IOCON_ODR); } } diff --git a/esphome/components/mcp23017/mcp23017.cpp b/esphome/components/mcp23017/mcp23017.cpp index 1ad2036939..e14e317d44 100644 --- a/esphome/components/mcp23017/mcp23017.cpp +++ b/esphome/components/mcp23017/mcp23017.cpp @@ -6,6 +6,8 @@ namespace mcp23017 { static const char *const TAG = "mcp23017"; +static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin + void MCP23017::setup() { uint8_t iocon; if (!this->read_reg(mcp23x17_base::MCP23X17_IOCONA, &iocon)) { @@ -19,8 +21,8 @@ void MCP23017::setup() { if (this->open_drain_ints_) { // enable open-drain interrupt pins, 3.3V-safe - this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); - this->write_reg(mcp23x17_base::MCP23X17_IOCONB, 0x04); + this->write_reg(mcp23x17_base::MCP23X17_IOCONA, iocon | IOCON_ODR); + this->write_reg(mcp23x17_base::MCP23X17_IOCONB, iocon | IOCON_ODR); } } diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index 3d944b45d5..1c17b66637 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -6,6 +6,11 @@ namespace mcp23s08 { static const char *const TAG = "mcp23s08"; +// IOCON register bits +static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode +static constexpr uint8_t IOCON_HAEN = 0x08; // Hardware address enable +static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin + void MCP23S08::set_device_address(uint8_t device_addr) { if (device_addr != 0) { this->device_opcode_ |= ((device_addr & 0x03) << 1); @@ -15,19 +20,19 @@ void MCP23S08::set_device_address(uint8_t device_addr) { void MCP23S08::setup() { this->spi_setup(); + // Enable HAEN (broadcast to all chips since HAEN isn't active yet) this->enable(); - uint8_t cmd = 0b01000000; - this->transfer_byte(cmd); + this->transfer_byte(0b01000000); this->transfer_byte(mcp23x08_base::MCP23X08_IOCON); - this->transfer_byte(0b00011000); // Enable HAEN pins for addressing + this->transfer_byte(IOCON_SEQOP | IOCON_HAEN); this->disable(); // Read current output register state this->read_reg(mcp23x08_base::MCP23X08_OLAT, &this->olat_); if (this->open_drain_ints_) { - // enable open-drain interrupt pins, 3.3V-safe - this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); + // enable open-drain interrupt pins, 3.3V-safe (addressed, only this chip) + this->write_reg(mcp23x08_base::MCP23X08_IOCON, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR); } } diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp index 1624eda9e4..c6abd7ad59 100644 --- a/esphome/components/mcp23s17/mcp23s17.cpp +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -6,6 +6,11 @@ namespace mcp23s17 { static const char *const TAG = "mcp23s17"; +// IOCON register bits +static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode +static constexpr uint8_t IOCON_HAEN = 0x08; // Hardware address enable +static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin + void MCP23S17::set_device_address(uint8_t device_addr) { if (device_addr != 0) { this->device_opcode_ |= ((device_addr & 0b111) << 1); @@ -15,18 +20,17 @@ void MCP23S17::set_device_address(uint8_t device_addr) { void MCP23S17::setup() { this->spi_setup(); + // Enable HAEN (broadcast to addresses 0 and 4 since HAEN isn't active yet) this->enable(); - uint8_t cmd = 0b01000000; - this->transfer_byte(cmd); + this->transfer_byte(0b01000000); this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA); - this->transfer_byte(0b00011000); // Enable HAEN pins for addressing + this->transfer_byte(IOCON_SEQOP | IOCON_HAEN); this->disable(); this->enable(); - cmd = 0b01001000; - this->transfer_byte(cmd); + this->transfer_byte(0b01001000); this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA); - this->transfer_byte(0b00011000); // Enable HAEN pins for addressing + this->transfer_byte(IOCON_SEQOP | IOCON_HAEN); this->disable(); // Read current output register state @@ -34,9 +38,9 @@ void MCP23S17::setup() { this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); if (this->open_drain_ints_) { - // enable open-drain interrupt pins, 3.3V-safe - this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); - this->write_reg(mcp23x17_base::MCP23X17_IOCONB, 0x04); + // enable open-drain interrupt pins, 3.3V-safe (addressed, only this chip) + this->write_reg(mcp23x17_base::MCP23X17_IOCONA, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR); + this->write_reg(mcp23x17_base::MCP23X17_IOCONB, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR); } } diff --git a/esphome/components/mmc5603/mmc5603.cpp b/esphome/components/mmc5603/mmc5603.cpp index 1cbc84191f..51b94eb767 100644 --- a/esphome/components/mmc5603/mmc5603.cpp +++ b/esphome/components/mmc5603/mmc5603.cpp @@ -126,21 +126,21 @@ void MMC5603Component::update() { int32_t raw_x = 0; raw_x |= buffer[0] << 12; raw_x |= buffer[1] << 4; - raw_x |= buffer[2] << 0; + raw_x |= buffer[2] & 0x0F; const float x = 0.00625 * (raw_x - 524288); int32_t raw_y = 0; raw_y |= buffer[3] << 12; raw_y |= buffer[4] << 4; - raw_y |= buffer[5] << 0; + raw_y |= buffer[5] & 0x0F; const float y = 0.00625 * (raw_y - 524288); int32_t raw_z = 0; raw_z |= buffer[6] << 12; raw_z |= buffer[7] << 4; - raw_z |= buffer[8] << 0; + raw_z |= buffer[8] & 0x0F; const float z = 0.00625 * (raw_z - 524288); diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index dfe1277297..1cdae76eaf 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -309,8 +309,8 @@ void SX1509Component::set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8 set_debounce_time_(time); for (uint16_t i = 0; i < num_rows; i++) set_debounce_pin_(i); - for (uint16_t i = 0; i < (8 + num_cols); i++) - set_debounce_pin_(i); + for (uint16_t i = 0; i < num_cols; i++) + set_debounce_pin_(i + 8); } } // namespace sx1509 From 0a607b9c93c0a1c8504a73de09b564c723a258b2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:36:16 -0400 Subject: [PATCH 28/38] [esp32_ble_server] Fix wrong union member in STOP_EVT handler (#15239) --- esphome/components/esp32_ble_server/ble_service.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 96fedf2346..8956c87b3e 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -159,7 +159,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g break; } case ESP_GATTS_STOP_EVT: { - if (param->start.service_handle == this->handle_) { + if (param->stop.service_handle == this->handle_) { this->state_ = STOPPED; } break; From 4b9467cd0cd03bf33aa24ec8be48bc9395976a6a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:37:33 -0400 Subject: [PATCH 29/38] [esp32_ble_client] Fix wrong union member in OPEN_EVT handler (#15236) --- esphome/components/esp32_ble_client/ble_client_base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 9d6e079d92..7f0f2c624d 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -350,7 +350,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // For V3_WITHOUT_CACHE, we already set fast params before connecting // No need to update them again here this->log_event_("Searching for services"); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); + esp_ble_gattc_search_service(esp_gattc_if, param->open.conn_id, nullptr); break; } case ESP_GATTC_CONNECT_EVT: { From 53bd57f3c2557d75fade7864b7371a2de27cabc5 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:37:54 -0400 Subject: [PATCH 30/38] [pid] Fix inverted debug log conditions and broken smoothing formula (#15240) --- esphome/components/pid/pid_autotuner.cpp | 4 ++-- esphome/components/pid/pid_simulator.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index e1ddd1d7c6..3b971e6559 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -101,10 +101,10 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce if (!zc_symmetrical || !amplitude_convergent) { // The frequency/amplitude is not fully accurate yet, try to wait // until the fault clears, or terminate after a while anyway - if (zc_symmetrical) { + if (!zc_symmetrical) { ESP_LOGVV(TAG, "%s: ZC is not symmetrical", this->id_.c_str()); } - if (amplitude_convergent) { + if (!amplitude_convergent) { ESP_LOGVV(TAG, "%s: Amplitude is not convergent", this->id_.c_str()); } uint32_t phase = this->relay_function_.phase_count; diff --git a/esphome/components/pid/pid_simulator.h b/esphome/components/pid/pid_simulator.h index 30222f2f7a..629784cea5 100644 --- a/esphome/components/pid/pid_simulator.h +++ b/esphome/components/pid/pid_simulator.h @@ -59,7 +59,7 @@ class PIDSimulator : public PollingComponent, public output::FloatOutput { delayed_temps.erase(delayed_temps.begin()); float prev_temp = this->delayed_temps[0]; float alpha = 0.1f; - float ret = (1 - alpha) * prev_temp + alpha * prev_temp; + float ret = (1 - alpha) * prev_temp + alpha * temperature; return ret; } From 951ad91cb259262675dbfbf7f71d1d6fb27d596a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:39:30 -0400 Subject: [PATCH 31/38] [atm90e32] Fix phase angle precision loss and remove unused member (#15238) --- esphome/components/atm90e32/atm90e32.cpp | 4 ++-- esphome/components/atm90e32/atm90e32.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index ee7fe5ce75..db29702c54 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -550,8 +550,8 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) { } float ATM90E32Component::get_phase_angle_(uint8_t phase) { - uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0; - return (val > 180) ? (float) (val - 360.0f) : (float) val; + float val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0f; + return (val > 180.0f) ? val - 360.0f : val; } float ATM90E32Component::get_phase_peak_current_(uint8_t phase) { diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index 2524616470..c44a11e3ed 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -134,7 +134,6 @@ class ATM90E32Component : public PollingComponent, void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; } #endif uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier); - int32_t last_periodic_millis = millis(); protected: #ifdef USE_NUMBER From 05c15f4241d20c78d3f8eb40a19d3046723aeaf7 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:44:40 -0400 Subject: [PATCH 32/38] [remote_base] Fix gobox uint64_t format specifier (#15237) --- esphome/components/remote_base/gobox_protocol.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/remote_base/gobox_protocol.cpp b/esphome/components/remote_base/gobox_protocol.cpp index 4f6de5e59e..0e1617659d 100644 --- a/esphome/components/remote_base/gobox_protocol.cpp +++ b/esphome/components/remote_base/gobox_protocol.cpp @@ -1,5 +1,6 @@ #include "gobox_protocol.h" #include "esphome/core/log.h" +#include namespace esphome { namespace remote_base { @@ -25,7 +26,7 @@ void GoboxProtocol::encode(RemoteTransmitData *dst, const GoboxData &data) { dst->set_carrier_frequency(38000); dst->reserve((HEADER_SIZE + CODE_SIZE + 1) * 2); uint64_t code = (HEADER << CODE_SIZE) | (data.code & ((1UL << CODE_SIZE) - 1)); - ESP_LOGI(TAG, "Send Gobox: code=0x%Lx", code); + ESP_LOGI(TAG, "Send Gobox: code=0x%016" PRIx64, code); for (int16_t i = (HEADER_SIZE + CODE_SIZE - 1); i >= 0; i--) { if (code & ((uint64_t) 1 << i)) { dst->item(BIT_MARK_US, BIT_ONE_SPACE_US); From f0db0c105424e31022e9521b44eb577cfa18d2d5 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:48:08 -0400 Subject: [PATCH 33/38] [esp32] Add ESP-IDF 5.5.4 and 6.0.0 version mappings (#15241) --- esphome/components/esp32/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 91eb913e3d..0ce1117262 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -690,9 +690,15 @@ ARDUINO_IDF_VERSION_LOOKUP = { ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { "recommended": cv.Version(5, 5, 3, "1"), "latest": cv.Version(5, 5, 3, "1"), - "dev": cv.Version(5, 5, 3, "1"), + "dev": cv.Version(5, 5, 4), } ESP_IDF_PLATFORM_VERSION_LOOKUP = { + cv.Version( + 6, 0, 0 + ): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6", + cv.Version( + 5, 5, 4 + ): "https://github.com/pioarduino/platform-espressif32.git#develop", cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37), cv.Version(5, 5, 3): cv.Version(55, 3, 37), cv.Version(5, 5, 2): cv.Version(55, 3, 37), From 7532e1f957499ca880c71c0e8c1f693c585b4cf3 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:58:41 -0400 Subject: [PATCH 34/38] [multiple] Fix uninitialized members and error constant types (#15235) --- esphome/components/max44009/max44009.cpp | 18 +++++++++--------- esphome/components/max44009/max44009.h | 4 ++-- .../modbus_controller/output/modbus_output.h | 4 ++-- esphome/components/tuya/climate/tuya_climate.h | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/esphome/components/max44009/max44009.cpp b/esphome/components/max44009/max44009.cpp index 8b8e38c1ea..cbce053519 100644 --- a/esphome/components/max44009/max44009.cpp +++ b/esphome/components/max44009/max44009.cpp @@ -8,17 +8,17 @@ namespace max44009 { static const char *const TAG = "max44009.sensor"; // REGISTERS -static const uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02; -static const uint8_t MAX44009_LUX_READING_HIGH = 0x03; -static const uint8_t MAX44009_LUX_READING_LOW = 0x04; +static constexpr uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02; +static constexpr uint8_t MAX44009_LUX_READING_HIGH = 0x03; +static constexpr uint8_t MAX44009_LUX_READING_LOW = 0x04; // CONFIGURATION MASKS -static const uint8_t MAX44009_CFG_CONTINUOUS = 0x80; +static constexpr uint8_t MAX44009_CFG_CONTINUOUS = 0x80; // ERROR CODES -static const uint8_t MAX44009_OK = 0; -static const uint8_t MAX44009_ERROR_WIRE_REQUEST = -10; -static const uint8_t MAX44009_ERROR_OVERFLOW = -20; -static const uint8_t MAX44009_ERROR_HIGH_BYTE = -30; -static const uint8_t MAX44009_ERROR_LOW_BYTE = -31; +static constexpr int8_t MAX44009_OK = 0; +static constexpr int8_t MAX44009_ERROR_WIRE_REQUEST = -10; +static constexpr int8_t MAX44009_ERROR_OVERFLOW = -20; +static constexpr int8_t MAX44009_ERROR_HIGH_BYTE = -30; +static constexpr int8_t MAX44009_ERROR_LOW_BYTE = -31; void MAX44009Sensor::setup() { bool state_ok = false; diff --git a/esphome/components/max44009/max44009.h b/esphome/components/max44009/max44009.h index 59eea66ed9..d0ffd7bc70 100644 --- a/esphome/components/max44009/max44009.h +++ b/esphome/components/max44009/max44009.h @@ -28,8 +28,8 @@ class MAX44009Sensor : public sensor::Sensor, public PollingComponent, public i2 uint8_t read_(uint8_t reg); void write_(uint8_t reg, uint8_t value); - int error_; - MAX44009Mode mode_; + int8_t error_{0}; + MAX44009Mode mode_{MAX44009_MODE_AUTO}; }; } // namespace max44009 diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 0fb4bb89ea..3f3cadfe2f 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -15,7 +15,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; - this->bitmask = bitmask; + this->bitmask = 0xFFFFFFFF; this->register_count = register_count; this->sensor_value_type = value_type; this->skip_updates = 0; @@ -47,7 +47,7 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public ModbusBinaryOutput(uint16_t start_address, uint8_t offset) { this->register_type = ModbusRegisterType::COIL; this->start_address = start_address; - this->bitmask = bitmask; + this->bitmask = 0xFFFFFFFF; this->sensor_value_type = SensorValueType::BIT; this->skip_updates = 0; this->register_count = 1; diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index 31bef57639..09f3fd30c3 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -105,8 +105,8 @@ class TuyaClimate : public climate::Climate, public Component { optional sleep_id_{}; optional eco_temperature_{}; TuyaDatapointType eco_type_{}; - uint8_t active_state_; - uint8_t fan_state_; + uint8_t active_state_{0}; + uint8_t fan_state_{0}; optional swing_vertical_id_{}; optional swing_horizontal_id_{}; optional fan_speed_id_{}; @@ -119,9 +119,9 @@ class TuyaClimate : public climate::Climate, public Component { bool swing_horizontal_{false}; bool heating_state_{false}; bool cooling_state_{false}; - float manual_temperature_; - bool eco_; - bool sleep_; + float manual_temperature_{NAN}; + bool eco_{false}; + bool sleep_{false}; bool reports_fahrenheit_{false}; }; From 3016cd363617d080e7eb6ed6532e5668cb07bdbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:29:08 -1000 Subject: [PATCH 35/38] Bump github/codeql-action from 4.34.1 to 4.35.1 (#15245) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6baab70b42..67f4690ac9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: category: "/language:${{matrix.language}}" From a2dee21e8e8f43c9ba68ab5a7b99908d7cd22eaf Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:24:19 +0100 Subject: [PATCH 36/38] [nextion] Replace `std::deque` queues with `std::list` (#15211) --- esphome/components/nextion/nextion.cpp | 14 ++++++-------- esphome/components/nextion/nextion.h | 12 ++++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fa1582c209..964dbfb660 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -841,10 +841,10 @@ void Nextion::process_nextion_commands_() { if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() && ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) { - for (size_t i = 0; i < this->nextion_queue_.size(); i++) { - NextionComponentBase *component = this->nextion_queue_[i]->component; - if (ms - this->nextion_queue_[i]->queue_time > this->max_q_age_ms_) { - if (this->nextion_queue_[i]->queue_time == 0) { + for (auto it = this->nextion_queue_.begin(); it != this->nextion_queue_.end();) { + NextionComponentBase *component = (*it)->component; + if (ms - (*it)->queue_time > this->max_q_age_ms_) { + if ((*it)->queue_time == 0) { ESP_LOGD(TAG, "Remove old queue '%s':'%s' (t=0)", component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); } @@ -863,10 +863,8 @@ void Nextion::process_nextion_commands_() { delete component; // NOLINT(cppcoreguidelines-owning-memory) } - delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory) - - this->nextion_queue_.erase(this->nextion_queue_.begin() + i); - i--; + delete *it; // NOLINT(cppcoreguidelines-owning-memory) + it = this->nextion_queue_.erase(it); } else { break; diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 217d2e605d..b5aaecd667 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1,16 +1,16 @@ #pragma once -#include +#include #include +#include "esphome/components/display/display.h" +#include "esphome/components/display/display_color_utils.h" +#include "esphome/components/uart/uart.h" #include "esphome/core/defines.h" #include "esphome/core/time.h" -#include "esphome/components/uart/uart.h" #include "nextion_base.h" #include "nextion_component.h" -#include "esphome/components/display/display.h" -#include "esphome/components/display/display_color_utils.h" #ifdef USE_NEXTION_TFT_UPLOAD #ifdef USE_ESP32 @@ -1391,8 +1391,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void process_pending_in_queue_(); #endif // USE_NEXTION_COMMAND_SPACING - std::deque nextion_queue_; - std::deque waveform_queue_; + std::list nextion_queue_; + std::list waveform_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint32_t comok_sent_ = 0; From d245b9f123e37618e51f027412f9cc860309478a Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:24:03 -0400 Subject: [PATCH 37/38] [sm2135] Fix copy-paste error in setup pin mode (#15248) --- esphome/components/sm2135/sm2135.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sm2135/sm2135.cpp b/esphome/components/sm2135/sm2135.cpp index 1293c3f321..c3d10e70c2 100644 --- a/esphome/components/sm2135/sm2135.cpp +++ b/esphome/components/sm2135/sm2135.cpp @@ -25,7 +25,7 @@ void SM2135::setup() { this->data_pin_->pin_mode(gpio::FLAG_OUTPUT); this->clock_pin_->setup(); this->clock_pin_->digital_write(false); - this->data_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->clock_pin_->pin_mode(gpio::FLAG_OUTPUT); this->data_pin_->pin_mode(gpio::FLAG_PULLUP); this->clock_pin_->pin_mode(gpio::FLAG_PULLUP); From 24b8a95340d79ad00157c261e0e3c9f92998439e Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:24:15 -0400 Subject: [PATCH 38/38] [pid] Remove unused PIDSimulator class (#15247) --- esphome/components/pid/pid_autotuner.h | 1 - esphome/components/pid/pid_simulator.h | 77 -------------------------- 2 files changed, 78 deletions(-) delete mode 100644 esphome/components/pid/pid_simulator.h diff --git a/esphome/components/pid/pid_autotuner.h b/esphome/components/pid/pid_autotuner.h index 98dc02bcc4..1db9ca7138 100644 --- a/esphome/components/pid/pid_autotuner.h +++ b/esphome/components/pid/pid_autotuner.h @@ -3,7 +3,6 @@ #include "esphome/core/component.h" #include "esphome/core/optional.h" #include "pid_controller.h" -#include "pid_simulator.h" #include diff --git a/esphome/components/pid/pid_simulator.h b/esphome/components/pid/pid_simulator.h deleted file mode 100644 index 629784cea5..0000000000 --- a/esphome/components/pid/pid_simulator.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/helpers.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/output/float_output.h" - -#include - -namespace esphome { -namespace pid { - -class PIDSimulator : public PollingComponent, public output::FloatOutput { - public: - PIDSimulator() : PollingComponent(1000) {} - - float surface = 1; /// surface area in m² - float mass = 3; /// mass of simulated object in kg - float temperature = 21; /// current temperature of object in °C - float efficiency = 0.98; /// heating efficiency, 1 is 100% efficient - float thermal_conductivity = 15; /// thermal conductivity of surface are in W/(m*K), here: steel - float specific_heat_capacity = 4.182; /// specific heat capacity of mass in kJ/(kg*K), here: water - float heat_power = 500; /// Heating power in W - float ambient_temperature = 20; /// Ambient temperature in °C - float update_interval = 1; /// The simulated updated interval in seconds - std::vector delayed_temps; /// storage of past temperatures for delaying temperature reading - size_t delay_cycles = 15; /// how many update cycles to delay the output - float output_value = 0.0; /// Current output value of heating element - sensor::Sensor *sensor = new sensor::Sensor(); - - float delta_t(float power) { - // P = Q / t - // Q = c * m * 𝚫t - // 𝚫t = (P*t) / (c*m) - float c = this->specific_heat_capacity; - float t = this->update_interval; - float p = power / 1000; // in kW - float m = this->mass; - return (p * t) / (c * m); - } - - float update_temp() { - float value = clamp(output_value, 0.0f, 1.0f); - - // Heat - float power = value * heat_power * efficiency; - temperature += this->delta_t(power); - - // Cool - // Q = k_w * A * (T_mass - T_ambient) - // P = Q / t - float dt = temperature - ambient_temperature; - float cool_power = (thermal_conductivity * surface * dt) / update_interval; - temperature -= this->delta_t(cool_power); - - // Delay temperature readings - delayed_temps.push_back(temperature); - if (delayed_temps.size() > delay_cycles) - delayed_temps.erase(delayed_temps.begin()); - float prev_temp = this->delayed_temps[0]; - float alpha = 0.1f; - float ret = (1 - alpha) * prev_temp + alpha * temperature; - return ret; - } - - void setup() override { sensor->publish_state(this->temperature); } - void update() override { - float new_temp = this->update_temp(); - sensor->publish_state(new_temp); - } - - protected: - void write_state(float state) override { this->output_value = state; } -}; - -} // namespace pid -} // namespace esphome