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

Merge branch 'more_flexible_template' into integration

This commit is contained in:
J. Nick Koston
2025-10-29 14:59:42 -05:00
68 changed files with 517 additions and 329 deletions

View File

@@ -62,7 +62,7 @@ jobs:
run: git diff run: git diff
- if: failure() - if: failure()
name: Archive artifacts name: Archive artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: generated-proto-files name: generated-proto-files
path: | path: |

View File

@@ -849,7 +849,7 @@ jobs:
fi fi
- name: Upload memory analysis JSON - name: Upload memory analysis JSON
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: memory-analysis-target name: memory-analysis-target
path: memory-analysis-target.json path: memory-analysis-target.json
@@ -913,7 +913,7 @@ jobs:
--platform "$platform" --platform "$platform"
- name: Upload memory analysis JSON - name: Upload memory analysis JSON
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: memory-analysis-pr name: memory-analysis-pr
path: memory-analysis-pr.json path: memory-analysis-pr.json
@@ -943,13 +943,13 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Download target analysis JSON - name: Download target analysis JSON
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
name: memory-analysis-target name: memory-analysis-target
path: ./memory-analysis path: ./memory-analysis
continue-on-error: true continue-on-error: true
- name: Download PR analysis JSON - name: Download PR analysis JSON
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
name: memory-analysis-pr name: memory-analysis-pr
path: ./memory-analysis path: ./memory-analysis

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -138,7 +138,7 @@ jobs:
# version: ${{ needs.init.outputs.tag }} # version: ${{ needs.init.outputs.tag }}
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: digests-${{ matrix.platform.arch }} name: digests-${{ matrix.platform.arch }}
path: /tmp/digests path: /tmp/digests
@@ -171,7 +171,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Download digests - name: Download digests
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with: with:
pattern: digests-* pattern: digests-*
path: /tmp/digests path: /tmp/digests

View File

@@ -11,7 +11,7 @@ ci:
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.14.1 rev: v0.14.2
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@@ -172,12 +172,6 @@ def alarm_control_panel_schema(
return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema) return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
# Remove before 2025.11.0
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
cv.deprecated_schema_constant("alarm_control_panel")
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id( ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
{ {
cv.GenerateID(): cv.use_id(AlarmControlPanel), cv.GenerateID(): cv.use_id(AlarmControlPanel),

View File

@@ -548,11 +548,6 @@ def binary_sensor_schema(
return _BINARY_SENSOR_SCHEMA.extend(schema) return _BINARY_SENSOR_SCHEMA.extend(schema)
# Remove before 2025.11.0
BINARY_SENSOR_SCHEMA = binary_sensor_schema()
BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
async def setup_binary_sensor_core_(var, config): async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config, "binary_sensor") await setup_entity(var, config, "binary_sensor")

View File

@@ -84,11 +84,6 @@ def button_schema(
return _BUTTON_SCHEMA.extend(schema) return _BUTTON_SCHEMA.extend(schema)
# Remove before 2025.11.0
BUTTON_SCHEMA = button_schema(Button)
BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
async def setup_button_core_(var, config): async def setup_button_core_(var, config):
await setup_entity(var, config, "button") await setup_entity(var, config, "button")

View File

@@ -270,11 +270,6 @@ def climate_schema(
return _CLIMATE_SCHEMA.extend(schema) return _CLIMATE_SCHEMA.extend(schema)
# Remove before 2025.11.0
CLIMATE_SCHEMA = climate_schema(Climate)
CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
async def setup_climate_core_(var, config): async def setup_climate_core_(var, config):
await setup_entity(var, config, "climate") await setup_entity(var, config, "climate")

View File

@@ -1,10 +1,9 @@
import logging import logging
from esphome import core
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import climate, remote_base, sensor from esphome.components import climate, remote_base, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -52,26 +51,6 @@ def climate_ir_with_receiver_schema(
) )
# Remove before 2025.11.0
def deprecated_schema_constant(config):
type: str = "unknown"
if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID):
type = str(id.type).split("::", maxsplit=1)[0]
_LOGGER.warning(
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
"Component using this schema: %s",
type,
)
return config
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
async def register_climate_ir(var, config): async def register_climate_ir(var, config):
await cg.register_component(var, config) await cg.register_component(var, config)
await remote_base.register_transmittable(var, config) await remote_base.register_transmittable(var, config)

View File

@@ -151,11 +151,6 @@ def cover_schema(
return _COVER_SCHEMA.extend(schema) return _COVER_SCHEMA.extend(schema)
# Remove before 2025.11.0
COVER_SCHEMA = cover_schema(Cover)
COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
async def setup_cover_core_(var, config): async def setup_cover_core_(var, config):
await setup_entity(var, config, "cover") await setup_entity(var, config, "cover")

View File

@@ -85,11 +85,6 @@ def event_schema(
return _EVENT_SCHEMA.extend(schema) return _EVENT_SCHEMA.extend(schema)
# Remove before 2025.11.0
EVENT_SCHEMA = event_schema()
EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event"))
async def setup_event_core_(var, config, *, event_types: list[str]): async def setup_event_core_(var, config, *, event_types: list[str]):
await setup_entity(var, config, "event") await setup_entity(var, config, "event")

View File

@@ -189,10 +189,6 @@ def fan_schema(
return _FAN_SCHEMA.extend(schema) return _FAN_SCHEMA.extend(schema)
# Remove before 2025.11.0
FAN_SCHEMA = fan_schema(Fan)
FAN_SCHEMA.add_extra(cv.deprecated_schema_constant("fan"))
_PRESET_MODES_SCHEMA = cv.All( _PRESET_MODES_SCHEMA = cv.All(
cv.ensure_list(cv.string_strict), cv.ensure_list(cv.string_strict),
cv.Length(min=1), cv.Length(min=1),

View File

@@ -91,11 +91,6 @@ def lock_schema(
return _LOCK_SCHEMA.extend(schema) return _LOCK_SCHEMA.extend(schema)
# Remove before 2025.11.0
LOCK_SCHEMA = lock_schema()
LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
async def _setup_lock_core(var, config): async def _setup_lock_core(var, config):
await setup_entity(var, config, "lock") await setup_entity(var, config, "lock")

View File

@@ -5,6 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
""" """
import logging import logging
from typing import TYPE_CHECKING, Any
from esphome import codegen as cg, config_validation as cv from esphome import codegen as cg, config_validation as cv
from esphome.const import CONF_ITEMS from esphome.const import CONF_ITEMS
@@ -12,6 +13,7 @@ from esphome.core import ID, Lambda
from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_generator import LambdaExpression, MockObj
from esphome.cpp_types import uint32 from esphome.cpp_types import uint32
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import Expression, SafeExpType
from .helpers import requires_component from .helpers import requires_component
@@ -42,7 +44,13 @@ def static_cast(type, value):
def call_lambda(lamb: LambdaExpression): def call_lambda(lamb: LambdaExpression):
expr = lamb.content.strip() expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"): if expr.startswith("return") and expr.endswith(";"):
return expr[6:][:-1].strip() return expr[6:-1].strip()
# If lambda has parameters, call it with those parameter names
# Parameter names come from hardcoded component code (like "x", "it", "event")
# not from user input, so they're safe to use directly
if lamb.parameters and lamb.parameters.parameters:
param_names = ", ".join(str(param.id) for param in lamb.parameters.parameters)
return f"{lamb}({param_names})"
return f"{lamb}()" return f"{lamb}()"
@@ -65,10 +73,20 @@ class LValidator:
return cv.returning_lambda(value) return cv.returning_lambda(value)
return self.validator(value) return self.validator(value)
async def process(self, value, args=()): async def process(
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
) -> Expression:
if value is None: if value is None:
return None return None
if isinstance(value, Lambda): if isinstance(value, Lambda):
# Local import to avoid circular import
from .lvcode import CodeContext, LambdaContext
if TYPE_CHECKING:
# CodeContext does not have get_automation_parameters
# so we need to assert the type here
assert isinstance(CodeContext.code_context, LambdaContext)
args = args or CodeContext.code_context.get_automation_parameters()
return cg.RawExpression( return cg.RawExpression(
call_lambda( call_lambda(
await cg.process_lambda(value, args, return_type=self.rtype) await cg.process_lambda(value, args, return_type=self.rtype)

View File

@@ -1,3 +1,5 @@
from typing import TYPE_CHECKING, Any
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import image from esphome.components import image
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
@@ -17,6 +19,7 @@ from esphome.cpp_generator import MockObj
from esphome.cpp_types import ESPTime, int32, uint32 from esphome.cpp_types import ESPTime, int32, uint32
from esphome.helpers import cpp_string_escape from esphome.helpers import cpp_string_escape
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import Expression, SafeExpType
from . import types as ty from . import types as ty
from .defines import ( from .defines import (
@@ -388,11 +391,23 @@ class TextValidator(LValidator):
return value return value
return super().__call__(value) return super().__call__(value)
async def process(self, value, args=()): async def process(
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
) -> Expression:
# Local import to avoid circular import at module level
from .lvcode import CodeContext, LambdaContext
if TYPE_CHECKING:
# CodeContext does not have get_automation_parameters
# so we need to assert the type here
assert isinstance(CodeContext.code_context, LambdaContext)
args = args or CodeContext.code_context.get_automation_parameters()
if isinstance(value, dict): if isinstance(value, dict):
if format_str := value.get(CONF_FORMAT): if format_str := value.get(CONF_FORMAT):
args = [str(x) for x in value[CONF_ARGS]] str_args = [str(x) for x in value[CONF_ARGS]]
arg_expr = cg.RawExpression(",".join(args)) arg_expr = cg.RawExpression(",".join(str_args))
format_str = cpp_string_escape(format_str) format_str = cpp_string_escape(format_str)
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()") return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
if time_format := value.get(CONF_TIME_FORMAT): if time_format := value.get(CONF_TIME_FORMAT):

View File

@@ -164,6 +164,9 @@ class LambdaContext(CodeContext):
code_text.append(text) code_text.append(text)
return code_text return code_text
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
return self.parameters
async def __aenter__(self): async def __aenter__(self):
await super().__aenter__() await super().__aenter__()
add_line_marks(self.where) add_line_marks(self.where)
@@ -178,9 +181,8 @@ class LvContext(LambdaContext):
added_lambda_count = 0 added_lambda_count = 0
def __init__(self, args=None): def __init__(self):
self.args = args or LVGL_COMP_ARG super().__init__(parameters=LVGL_COMP_ARG)
super().__init__(parameters=self.args)
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type, exc_val, exc_tb):
await super().__aexit__(exc_type, exc_val, exc_tb) await super().__aexit__(exc_type, exc_val, exc_tb)
@@ -189,6 +191,11 @@ class LvContext(LambdaContext):
cg.add(expression) cg.add(expression)
return expression return expression
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
# When generating automations, we don't want the `lv_component` parameter to be passed
# to the lambda.
return []
def __call__(self, *args): def __call__(self, *args):
return self.add(*args) return self.add(*args)

View File

@@ -5,7 +5,6 @@ from ..defines import CONF_WIDGET
from ..lvcode import ( from ..lvcode import (
API_EVENT, API_EVENT,
EVENT_ARG, EVENT_ARG,
LVGL_COMP_ARG,
UPDATE_EVENT, UPDATE_EVENT,
LambdaContext, LambdaContext,
LvContext, LvContext,
@@ -30,7 +29,7 @@ async def to_code(config):
await wait_for_widgets() await wait_for_widgets()
async with LambdaContext(EVENT_ARG) as lamb: async with LambdaContext(EVENT_ARG) as lamb:
lv_add(sensor.publish_state(widget.get_value())) lv_add(sensor.publish_state(widget.get_value()))
async with LvContext(LVGL_COMP_ARG): async with LvContext():
lv_add( lv_add(
lvgl_static.add_event_cb( lvgl_static.add_event_cb(
widget.obj, widget.obj,

View File

@@ -192,10 +192,6 @@ def media_player_schema(
return _MEDIA_PLAYER_SCHEMA.extend(schema) return _MEDIA_PLAYER_SCHEMA.extend(schema)
# Remove before 2025.11.0
MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer)
MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player"))
MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id(
cv.Schema( cv.Schema(
{ {

View File

@@ -238,11 +238,6 @@ def number_schema(
return _NUMBER_SCHEMA.extend(schema) return _NUMBER_SCHEMA.extend(schema)
# Remove before 2025.11.0
NUMBER_SCHEMA = number_schema(Number)
NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number"))
async def setup_number_core_( async def setup_number_core_(
var, config, *, min_value: float, max_value: float, step: float var, config, *, min_value: float, max_value: float, step: float
): ):

View File

@@ -1,3 +1,5 @@
import logging
from esphome import automation, pins from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32, esp32_rmt, remote_base from esphome.components import esp32, esp32_rmt, remote_base
@@ -18,9 +20,12 @@ from esphome.const import (
) )
from esphome.core import CORE from esphome.core import CORE
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["remote_base"] AUTO_LOAD = ["remote_base"]
CONF_EOT_LEVEL = "eot_level" CONF_EOT_LEVEL = "eot_level"
CONF_NON_BLOCKING = "non_blocking"
CONF_ON_TRANSMIT = "on_transmit" CONF_ON_TRANSMIT = "on_transmit"
CONF_ON_COMPLETE = "on_complete" CONF_ON_COMPLETE = "on_complete"
CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID
@@ -65,11 +70,25 @@ CONFIG_SCHEMA = cv.Schema(
esp32_c6=48, esp32_c6=48,
esp32_h2=48, esp32_h2=48,
): cv.All(cv.only_on_esp32, cv.int_range(min=2)), ): cv.All(cv.only_on_esp32, cv.int_range(min=2)),
cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean),
cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
def _validate_non_blocking(config):
if CORE.is_esp32 and CONF_NON_BLOCKING not in config:
_LOGGER.warning(
"'non_blocking' is not set for 'remote_transmitter' and will default to 'true'.\n"
"The default behavior changed in 2025.11.0; previously blocking mode was used.\n"
"To silence this warning, explicitly set 'non_blocking: true' (or 'false')."
)
config[CONF_NON_BLOCKING] = True
FINAL_VALIDATE_SCHEMA = _validate_non_blocking
DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value( DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value(
{ {
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent), cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent),
@@ -95,6 +114,7 @@ async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32:
var = cg.new_Pvariable(config[CONF_ID], pin) var = cg.new_Pvariable(config[CONF_ID], pin)
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
cg.add(var.set_non_blocking(config[CONF_NON_BLOCKING]))
if CONF_CLOCK_RESOLUTION in config: if CONF_CLOCK_RESOLUTION in config:
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
if CONF_USE_DMA in config: if CONF_USE_DMA in config:

View File

@@ -54,6 +54,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#if defined(USE_ESP32) #if defined(USE_ESP32)
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; }
void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; }
#endif #endif
Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; };
@@ -74,6 +75,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#ifdef USE_ESP32 #ifdef USE_ESP32
void configure_rmt_(); void configure_rmt_();
void wait_for_rmt_();
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
RemoteTransmitterComponentStore store_{}; RemoteTransmitterComponentStore store_{};
@@ -90,6 +92,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
esp_err_t error_code_{ESP_OK}; esp_err_t error_code_{ESP_OK};
std::string error_string_{""}; std::string error_string_{""};
bool inverted_{false}; bool inverted_{false};
bool non_blocking_{false};
#endif #endif
uint8_t carrier_duty_percent_; uint8_t carrier_duty_percent_;

View File

@@ -196,12 +196,29 @@ void RemoteTransmitterComponent::configure_rmt_() {
} }
} }
void RemoteTransmitterComponent::wait_for_rmt_() {
esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning();
}
this->complete_trigger_->trigger();
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
uint64_t total_duration = 0;
if (this->is_failed()) { if (this->is_failed()) {
return; return;
} }
// if the timeout was cancelled, block until the tx is complete
if (this->non_blocking_ && this->cancel_timeout("complete")) {
this->wait_for_rmt_();
}
if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) {
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
this->configure_rmt_(); this->configure_rmt_();
@@ -212,6 +229,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
// encode any delay at the start of the buffer to simplify the encoder callback // encode any delay at the start of the buffer to simplify the encoder callback
// this will be skipped the first time around // this will be skipped the first time around
total_duration += send_wait * (send_times - 1);
send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait)); send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
while (send_wait > 0) { while (send_wait > 0) {
int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX)); int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
@@ -229,6 +247,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (!level) { if (!level) {
value = -value; value = -value;
} }
total_duration += value * send_times;
value = this->from_microseconds_(static_cast<uint32_t>(value)); value = this->from_microseconds_(static_cast<uint32_t>(value));
while (value > 0) { while (value > 0) {
int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX)); int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
@@ -260,13 +279,12 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
} else { } else {
this->status_clear_warning(); this->status_clear_warning();
} }
error = rmt_tx_wait_all_done(this->channel_, -1);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning();
}
this->complete_trigger_->trigger(); if (this->non_blocking_) {
this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); });
} else {
this->wait_for_rmt_();
}
} }
#else #else
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {

View File

@@ -86,11 +86,6 @@ def select_schema(
return _SELECT_SCHEMA.extend(schema) return _SELECT_SCHEMA.extend(schema)
# Remove before 2025.11.0
SELECT_SCHEMA = select_schema(Select)
SELECT_SCHEMA.add_extra(cv.deprecated_schema_constant("select"))
async def setup_select_core_(var, config, *, options: list[str]): async def setup_select_core_(var, config, *, options: list[str]):
await setup_entity(var, config, "select") await setup_entity(var, config, "select")

View File

@@ -369,11 +369,6 @@ def sensor_schema(
return _SENSOR_SCHEMA.extend(schema) return _SENSOR_SCHEMA.extend(schema)
# Remove before 2025.11.0
SENSOR_SCHEMA = sensor_schema()
SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("sensor"))
@FILTER_REGISTRY.register("offset", OffsetFilter, cv.templatable(cv.float_)) @FILTER_REGISTRY.register("offset", OffsetFilter, cv.templatable(cv.float_))
async def offset_filter_to_code(config, filter_id): async def offset_filter_to_code(config, filter_id):
template_ = await cg.templatable(config, [], float) template_ = await cg.templatable(config, [], float)

View File

@@ -139,11 +139,6 @@ def switch_schema(
return _SWITCH_SCHEMA.extend(schema) return _SWITCH_SCHEMA.extend(schema)
# Remove before 2025.11.0
SWITCH_SCHEMA = switch_schema(Switch)
SWITCH_SCHEMA.add_extra(cv.deprecated_schema_constant("switch"))
async def setup_switch_core_(var, config): async def setup_switch_core_(var, config):
await setup_entity(var, config, "switch") await setup_entity(var, config, "switch")

View File

@@ -25,6 +25,20 @@ void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor,
this->sensor_data_.push_back(sd); this->sensor_data_.push_back(sd);
this->sensor_map_[sensor].store_index = this->next_store_index_++; this->sensor_map_[sensor].store_index = this->next_store_index_++;
}; };
static const LogString *sensor_type_to_string(AlarmSensorType type) {
switch (type) {
case ALARM_SENSOR_TYPE_INSTANT:
return LOG_STR("instant");
case ALARM_SENSOR_TYPE_DELAYED_FOLLOWER:
return LOG_STR("delayed_follower");
case ALARM_SENSOR_TYPE_INSTANT_ALWAYS:
return LOG_STR("instant_always");
case ALARM_SENSOR_TYPE_DELAYED:
default:
return LOG_STR("delayed");
}
}
#endif #endif
void TemplateAlarmControlPanel::dump_config() { void TemplateAlarmControlPanel::dump_config() {
@@ -46,35 +60,20 @@ void TemplateAlarmControlPanel::dump_config() {
" Supported Features: %" PRIu32, " Supported Features: %" PRIu32,
(this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features()); (this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features());
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
for (auto sensor_info : this->sensor_map_) { for (auto const &[sensor, info] : this->sensor_map_) {
ESP_LOGCONFIG(TAG, " Binary Sensor:");
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Binary Sensor:\n"
" Name: %s\n" " Name: %s\n"
" Type: %s\n"
" Armed home bypass: %s\n" " Armed home bypass: %s\n"
" Armed night bypass: %s\n" " Armed night bypass: %s\n"
" Auto bypass: %s\n" " Auto bypass: %s\n"
" Chime mode: %s", " Chime mode: %s",
sensor_info.first->get_name().c_str(), sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(info.type)),
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME), TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT), TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO), TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)); TRUEFALSE(info.flags & BINARY_SENSOR_MODE_CHIME));
const char *sensor_type;
switch (sensor_info.second.type) {
case ALARM_SENSOR_TYPE_INSTANT:
sensor_type = "instant";
break;
case ALARM_SENSOR_TYPE_DELAYED_FOLLOWER:
sensor_type = "delayed_follower";
break;
case ALARM_SENSOR_TYPE_INSTANT_ALWAYS:
sensor_type = "instant_always";
break;
case ALARM_SENSOR_TYPE_DELAYED:
default:
sensor_type = "delayed";
}
ESP_LOGCONFIG(TAG, " Sensor type: %s", sensor_type);
} }
#endif #endif
} }
@@ -123,39 +122,37 @@ void TemplateAlarmControlPanel::loop() {
bool instant_sensor_faulted = false; bool instant_sensor_faulted = false;
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
// Test all of the sensors in the list regardless of the alarm panel state // Test all of the sensors regardless of the alarm panel state
for (auto sensor_info : this->sensor_map_) { for (auto const &[sensor, info] : this->sensor_map_) {
// Check for chime zones // Check for chime zones
if ((sensor_info.second.flags & BINARY_SENSOR_MODE_CHIME)) { if (info.flags & BINARY_SENSOR_MODE_CHIME) {
// Look for the transition from closed to open // Look for the transition from closed to open
if ((!this->sensor_data_[sensor_info.second.store_index].last_chime_state) && (sensor_info.first->state)) { if ((!this->sensor_data_[info.store_index].last_chime_state) && (sensor->state)) {
// Must be disarmed to chime // Must be disarmed to chime
if (this->current_state_ == ACP_STATE_DISARMED) { if (this->current_state_ == ACP_STATE_DISARMED) {
this->chime_callback_.call(); this->chime_callback_.call();
} }
} }
// Record the sensor state change // Record the sensor state change
this->sensor_data_[sensor_info.second.store_index].last_chime_state = sensor_info.first->state; this->sensor_data_[info.store_index].last_chime_state = sensor->state;
} }
// Check for faulted sensors // Check for faulted sensors
if (sensor_info.first->state) { // Sensor triggered? if (sensor->state) {
// Skip if auto bypassed // Skip if auto bypassed
if (std::count(this->bypassed_sensor_indicies_.begin(), this->bypassed_sensor_indicies_.end(), if (std::count(this->bypassed_sensor_indicies_.begin(), this->bypassed_sensor_indicies_.end(),
sensor_info.second.store_index) == 1) { info.store_index) == 1) {
continue; continue;
} }
// Skip if bypass armed home // Skip if bypass armed home
if (this->current_state_ == ACP_STATE_ARMED_HOME && if ((this->current_state_ == ACP_STATE_ARMED_HOME) && (info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
continue; continue;
} }
// Skip if bypass armed night // Skip if bypass armed night
if (this->current_state_ == ACP_STATE_ARMED_NIGHT && if ((this->current_state_ == ACP_STATE_ARMED_NIGHT) && (info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) {
(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) {
continue; continue;
} }
switch (sensor_info.second.type) { switch (info.type) {
case ALARM_SENSOR_TYPE_INSTANT_ALWAYS: case ALARM_SENSOR_TYPE_INSTANT_ALWAYS:
next_state = ACP_STATE_TRIGGERED; next_state = ACP_STATE_TRIGGERED;
[[fallthrough]]; [[fallthrough]];
@@ -247,11 +244,11 @@ void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_p
void TemplateAlarmControlPanel::bypass_before_arming() { void TemplateAlarmControlPanel::bypass_before_arming() {
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
for (auto sensor_info : this->sensor_map_) { for (auto const &[sensor, info] : this->sensor_map_) {
// Check for faulted bypass_auto sensors and remove them from monitoring // Check for faulted bypass_auto sensors and remove them from monitoring
if ((sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor_info.first->state)) { if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor_info.first->get_name().c_str()); ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str());
this->bypassed_sensor_indicies_.push_back(sensor_info.second.store_index); this->bypassed_sensor_indicies_.push_back(info.store_index);
} }
} }
#endif #endif

View File

@@ -6,17 +6,21 @@ namespace template_ {
static const char *const TAG = "template.binary_sensor"; static const char *const TAG = "template.binary_sensor";
void TemplateBinarySensor::setup() { this->loop(); } void TemplateBinarySensor::setup() {
if (!this->f_.has_value()) {
this->disable_loop();
} else {
this->loop();
}
}
void TemplateBinarySensor::loop() { void TemplateBinarySensor::loop() {
if (!this->f_.has_value()) auto s = this->f_();
return;
auto s = (*this->f_)();
if (s.has_value()) { if (s.has_value()) {
this->publish_state(*s); this->publish_state(*s);
} }
} }
void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); } void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); }
} // namespace template_ } // namespace template_

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome { namespace esphome {
@@ -8,7 +9,10 @@ namespace template_ {
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor { class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
public: public:
void set_template(optional<bool> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) {
this->f_.set(std::forward<F>(f));
this->enable_loop();
}
void setup() override; void setup() override;
void loop() override; void loop() override;
@@ -17,7 +21,7 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected: protected:
optional<optional<bool> (*)()> f_; TemplateLambda<bool> f_;
}; };
} // namespace template_ } // namespace template_

View File

@@ -33,28 +33,27 @@ void TemplateCover::setup() {
break; break;
} }
} }
if (!this->state_f_.has_value() && !this->tilt_f_.has_value())
this->disable_loop();
} }
void TemplateCover::loop() { void TemplateCover::loop() {
bool changed = false; bool changed = false;
if (this->state_f_.has_value()) { auto s = this->state_f_();
auto s = (*this->state_f_)(); if (s.has_value()) {
if (s.has_value()) { auto pos = clamp(*s, 0.0f, 1.0f);
auto pos = clamp(*s, 0.0f, 1.0f); if (pos != this->position) {
if (pos != this->position) { this->position = pos;
this->position = pos; changed = true;
changed = true;
}
} }
} }
if (this->tilt_f_.has_value()) {
auto s = (*this->tilt_f_)(); auto tilt = this->tilt_f_();
if (s.has_value()) { if (tilt.has_value()) {
auto tilt = clamp(*s, 0.0f, 1.0f); auto tilt_val = clamp(*tilt, 0.0f, 1.0f);
if (tilt != this->tilt) { if (tilt_val != this->tilt) {
this->tilt = tilt; this->tilt = tilt_val;
changed = true; changed = true;
}
} }
} }
@@ -63,7 +62,6 @@ void TemplateCover::loop() {
} }
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
void TemplateCover::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; } Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; }
Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; } Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; }
@@ -124,7 +122,6 @@ CoverTraits TemplateCover::get_traits() {
} }
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; } Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
void TemplateCover::set_tilt_lambda(optional<float> (*tilt_f)()) { this->tilt_f_ = tilt_f; }
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; } void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }

View File

@@ -2,6 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/cover/cover.h" #include "esphome/components/cover/cover.h"
namespace esphome { namespace esphome {
@@ -17,7 +18,14 @@ class TemplateCover : public cover::Cover, public Component {
public: public:
TemplateCover(); TemplateCover();
void set_state_lambda(optional<float> (*f)()); template<typename F> void set_state_lambda(F &&f) {
this->state_f_.set(std::forward<F>(f));
this->enable_loop();
}
template<typename F> void set_tilt_lambda(F &&f) {
this->tilt_f_.set(std::forward<F>(f));
this->enable_loop();
}
Trigger<> *get_open_trigger() const; Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const; Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger() const; Trigger<> *get_stop_trigger() const;
@@ -26,7 +34,6 @@ class TemplateCover : public cover::Cover, public Component {
Trigger<float> *get_tilt_trigger() const; Trigger<float> *get_tilt_trigger() const;
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void set_tilt_lambda(optional<float> (*tilt_f)());
void set_has_stop(bool has_stop); void set_has_stop(bool has_stop);
void set_has_position(bool has_position); void set_has_position(bool has_position);
void set_has_tilt(bool has_tilt); void set_has_tilt(bool has_tilt);
@@ -45,8 +52,8 @@ class TemplateCover : public cover::Cover, public Component {
void stop_prev_trigger_(); void stop_prev_trigger_();
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE}; TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
optional<optional<float> (*)()> state_f_; TemplateLambda<float> state_f_;
optional<optional<float> (*)()> tilt_f_; TemplateLambda<float> tilt_f_;
bool assumed_state_{false}; bool assumed_state_{false};
bool optimistic_{false}; bool optimistic_{false};
Trigger<> *open_trigger_; Trigger<> *open_trigger_;

View File

@@ -40,14 +40,13 @@ void TemplateDate::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (!val.has_value()) if (val.has_value()) {
return; this->year_ = val->year;
this->month_ = val->month;
this->year_ = val->year; this->day_ = val->day_of_month;
this->month_ = val->month; this->publish_state();
this->day_ = val->day_of_month; }
this->publish_state();
} }
void TemplateDate::control(const datetime::DateCall &call) { void TemplateDate::control(const datetime::DateCall &call) {

View File

@@ -9,13 +9,14 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {
class TemplateDate : public datetime::DateEntity, public PollingComponent { class TemplateDate : public datetime::DateEntity, public PollingComponent {
public: public:
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void setup() override; void setup() override;
void update() override; void update() override;
@@ -35,7 +36,7 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent {
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>(); Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<optional<ESPTime> (*)()> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };

View File

@@ -43,17 +43,16 @@ void TemplateDateTime::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (!val.has_value()) if (val.has_value()) {
return; this->year_ = val->year;
this->month_ = val->month;
this->year_ = val->year; this->day_ = val->day_of_month;
this->month_ = val->month; this->hour_ = val->hour;
this->day_ = val->day_of_month; this->minute_ = val->minute;
this->hour_ = val->hour; this->second_ = val->second;
this->minute_ = val->minute; this->publish_state();
this->second_ = val->second; }
this->publish_state();
} }
void TemplateDateTime::control(const datetime::DateTimeCall &call) { void TemplateDateTime::control(const datetime::DateTimeCall &call) {

View File

@@ -9,13 +9,14 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent { class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
public: public:
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void setup() override; void setup() override;
void update() override; void update() override;
@@ -35,7 +36,7 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>(); Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<optional<ESPTime> (*)()> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };

View File

@@ -40,14 +40,13 @@ void TemplateTime::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (!val.has_value()) if (val.has_value()) {
return; this->hour_ = val->hour;
this->minute_ = val->minute;
this->hour_ = val->hour; this->second_ = val->second;
this->minute_ = val->minute; this->publish_state();
this->second_ = val->second; }
this->publish_state();
} }
void TemplateTime::control(const datetime::TimeCall &call) { void TemplateTime::control(const datetime::TimeCall &call) {

View File

@@ -9,13 +9,14 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {
class TemplateTime : public datetime::TimeEntity, public PollingComponent { class TemplateTime : public datetime::TimeEntity, public PollingComponent {
public: public:
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void setup() override; void setup() override;
void update() override; void update() override;
@@ -35,7 +36,7 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent {
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>(); Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<optional<ESPTime> (*)()> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };

View File

@@ -12,13 +12,10 @@ TemplateLock::TemplateLock()
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {} : lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
void TemplateLock::loop() { void TemplateLock::loop() {
if (!this->f_.has_value()) auto val = this->f_();
return; if (val.has_value()) {
auto val = (*this->f_)(); this->publish_state(*val);
if (!val.has_value()) }
return;
this->publish_state(*val);
} }
void TemplateLock::control(const lock::LockCall &call) { void TemplateLock::control(const lock::LockCall &call) {
if (this->prev_trigger_ != nullptr) { if (this->prev_trigger_ != nullptr) {
@@ -45,7 +42,6 @@ void TemplateLock::open_latch() {
this->open_trigger_->trigger(); this->open_trigger_->trigger();
} }
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateLock::set_state_lambda(optional<lock::LockState> (*f)()) { this->f_ = f; }
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; } Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; } Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }

View File

@@ -2,6 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/lock/lock.h" #include "esphome/components/lock/lock.h"
namespace esphome { namespace esphome {
@@ -13,7 +14,10 @@ class TemplateLock : public lock::Lock, public Component {
void dump_config() override; void dump_config() override;
void set_state_lambda(optional<lock::LockState> (*f)()); template<typename F> void set_state_lambda(F &&f) {
this->f_.set(std::forward<F>(f));
this->enable_loop();
}
Trigger<> *get_lock_trigger() const; Trigger<> *get_lock_trigger() const;
Trigger<> *get_unlock_trigger() const; Trigger<> *get_unlock_trigger() const;
Trigger<> *get_open_trigger() const; Trigger<> *get_open_trigger() const;
@@ -26,7 +30,7 @@ class TemplateLock : public lock::Lock, public Component {
void control(const lock::LockCall &call) override; void control(const lock::LockCall &call) override;
void open_latch() override; void open_latch() override;
optional<optional<lock::LockState> (*)()> f_; TemplateLambda<lock::LockState> f_;
bool optimistic_{false}; bool optimistic_{false};
Trigger<> *lock_trigger_; Trigger<> *lock_trigger_;
Trigger<> *unlock_trigger_; Trigger<> *unlock_trigger_;

View File

@@ -30,11 +30,10 @@ void TemplateNumber::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (!val.has_value()) if (val.has_value()) {
return; this->publish_state(*val);
}
this->publish_state(*val);
} }
void TemplateNumber::control(float value) { void TemplateNumber::control(float value) {

View File

@@ -4,13 +4,14 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {
class TemplateNumber : public number::Number, public PollingComponent { class TemplateNumber : public number::Number, public PollingComponent {
public: public:
void set_template(optional<float> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void setup() override; void setup() override;
void update() override; void update() override;
@@ -28,7 +29,7 @@ class TemplateNumber : public number::Number, public PollingComponent {
float initial_value_{NAN}; float initial_value_{NAN};
bool restore_value_{false}; bool restore_value_{false};
Trigger<float> *set_trigger_ = new Trigger<float>(); Trigger<float> *set_trigger_ = new Trigger<float>();
optional<optional<float> (*)()> f_; TemplateLambda<float> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };

View File

@@ -31,16 +31,14 @@ void TemplateSelect::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (!val.has_value()) if (val.has_value()) {
return; if (!this->has_option(*val)) {
ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
if (!this->has_option(*val)) { return;
ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str()); }
return; this->publish_state(*val);
} }
this->publish_state(*val);
} }
void TemplateSelect::control(const std::string &value) { void TemplateSelect::control(const std::string &value) {

View File

@@ -4,13 +4,14 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {
class TemplateSelect : public select::Select, public PollingComponent { class TemplateSelect : public select::Select, public PollingComponent {
public: public:
void set_template(optional<std::string> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void setup() override; void setup() override;
void update() override; void update() override;
@@ -28,7 +29,7 @@ class TemplateSelect : public select::Select, public PollingComponent {
size_t initial_option_index_{0}; size_t initial_option_index_{0};
bool restore_value_ = false; bool restore_value_ = false;
Trigger<std::string> *set_trigger_ = new Trigger<std::string>(); Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
optional<optional<std::string> (*)()> f_; TemplateLambda<std::string> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };

View File

@@ -11,13 +11,14 @@ void TemplateSensor::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (val.has_value()) { if (val.has_value()) {
this->publish_state(*val); this->publish_state(*val);
} }
} }
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
void TemplateSensor::set_template(optional<float> (*f)()) { this->f_ = f; }
void TemplateSensor::dump_config() { void TemplateSensor::dump_config() {
LOG_SENSOR("", "Template Sensor", this); LOG_SENSOR("", "Template Sensor", this);
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
namespace esphome { namespace esphome {
@@ -8,7 +9,7 @@ namespace template_ {
class TemplateSensor : public sensor::Sensor, public PollingComponent { class TemplateSensor : public sensor::Sensor, public PollingComponent {
public: public:
void set_template(optional<float> (*f)()); template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void update() override; void update() override;
@@ -17,7 +18,7 @@ class TemplateSensor : public sensor::Sensor, public PollingComponent {
float get_setup_priority() const override; float get_setup_priority() const override;
protected: protected:
optional<optional<float> (*)()> f_; TemplateLambda<float> f_;
}; };
} // namespace template_ } // namespace template_

View File

@@ -9,13 +9,10 @@ static const char *const TAG = "template.switch";
TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
void TemplateSwitch::loop() { void TemplateSwitch::loop() {
if (!this->f_.has_value()) auto s = this->f_();
return; if (s.has_value()) {
auto s = (*this->f_)(); this->publish_state(*s);
if (!s.has_value()) }
return;
this->publish_state(*s);
} }
void TemplateSwitch::write_state(bool state) { void TemplateSwitch::write_state(bool state) {
if (this->prev_trigger_ != nullptr) { if (this->prev_trigger_ != nullptr) {
@@ -35,11 +32,13 @@ void TemplateSwitch::write_state(bool state) {
} }
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
bool TemplateSwitch::assumed_state() { return this->assumed_state_; } bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
void TemplateSwitch::set_state_lambda(optional<bool> (*f)()) { this->f_ = f; }
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
void TemplateSwitch::setup() { void TemplateSwitch::setup() {
if (!this->f_.has_value())
this->disable_loop();
optional<bool> initial_state = this->get_initial_state_with_restore_mode(); optional<bool> initial_state = this->get_initial_state_with_restore_mode();
if (initial_state.has_value()) { if (initial_state.has_value()) {

View File

@@ -2,6 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/switch/switch.h" #include "esphome/components/switch/switch.h"
namespace esphome { namespace esphome {
@@ -14,7 +15,10 @@ class TemplateSwitch : public switch_::Switch, public Component {
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void set_state_lambda(optional<bool> (*f)()); template<typename F> void set_state_lambda(F &&f) {
this->f_.set(std::forward<F>(f));
this->enable_loop();
}
Trigger<> *get_turn_on_trigger() const; Trigger<> *get_turn_on_trigger() const;
Trigger<> *get_turn_off_trigger() const; Trigger<> *get_turn_off_trigger() const;
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
@@ -28,7 +32,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
void write_state(bool state) override; void write_state(bool state) override;
optional<optional<bool> (*)()> f_; TemplateLambda<bool> f_;
bool optimistic_{false}; bool optimistic_{false};
bool assumed_state_{false}; bool assumed_state_{false};
Trigger<> *turn_on_trigger_; Trigger<> *turn_on_trigger_;

View File

@@ -7,10 +7,8 @@ namespace template_ {
static const char *const TAG = "template.text"; static const char *const TAG = "template.text";
void TemplateText::setup() { void TemplateText::setup() {
if (!(this->f_ == nullptr)) { if (this->f_.has_value())
if (this->f_.has_value()) return;
return;
}
std::string value = this->initial_value_; std::string value = this->initial_value_;
if (!this->pref_) { if (!this->pref_) {
ESP_LOGD(TAG, "State from initial: %s", value.c_str()); ESP_LOGD(TAG, "State from initial: %s", value.c_str());
@@ -26,17 +24,13 @@ void TemplateText::setup() {
} }
void TemplateText::update() { void TemplateText::update() {
if (this->f_ == nullptr)
return;
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (!val.has_value()) if (val.has_value()) {
return; this->publish_state(*val);
}
this->publish_state(*val);
} }
void TemplateText::control(const std::string &value) { void TemplateText::control(const std::string &value) {

View File

@@ -4,6 +4,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {
@@ -61,7 +62,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
class TemplateText : public text::Text, public PollingComponent { class TemplateText : public text::Text, public PollingComponent {
public: public:
void set_template(optional<std::string> (*f)()) { this->f_ = f; } template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void setup() override; void setup() override;
void update() override; void update() override;
@@ -78,7 +79,7 @@ class TemplateText : public text::Text, public PollingComponent {
bool optimistic_ = false; bool optimistic_ = false;
std::string initial_value_; std::string initial_value_;
Trigger<std::string> *set_trigger_ = new Trigger<std::string>(); Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
optional<optional<std::string> (*)()> f_{nullptr}; TemplateLambda<std::string> f_{};
TemplateTextSaverBase *pref_ = nullptr; TemplateTextSaverBase *pref_ = nullptr;
}; };

View File

@@ -10,13 +10,14 @@ void TemplateTextSensor::update() {
if (!this->f_.has_value()) if (!this->f_.has_value())
return; return;
auto val = (*this->f_)(); auto val = this->f_();
if (val.has_value()) { if (val.has_value()) {
this->publish_state(*val); this->publish_state(*val);
} }
} }
float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
void TemplateTextSensor::set_template(optional<std::string> (*f)()) { this->f_ = f; }
void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); } void TemplateTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Template Sensor", this); }
} // namespace template_ } // namespace template_

View File

@@ -2,6 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/text_sensor/text_sensor.h"
namespace esphome { namespace esphome {
@@ -9,7 +10,7 @@ namespace template_ {
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent { class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
public: public:
void set_template(optional<std::string> (*f)()); template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void update() override; void update() override;
@@ -18,7 +19,7 @@ class TemplateTextSensor : public text_sensor::TextSensor, public PollingCompone
void dump_config() override; void dump_config() override;
protected: protected:
optional<optional<std::string> (*)()> f_{}; TemplateLambda<std::string> f_{};
}; };
} // namespace template_ } // namespace template_

View File

@@ -33,19 +33,19 @@ void TemplateValve::setup() {
break; break;
} }
} }
if (!this->state_f_.has_value())
this->disable_loop();
} }
void TemplateValve::loop() { void TemplateValve::loop() {
bool changed = false; bool changed = false;
if (this->state_f_.has_value()) { auto s = this->state_f_();
auto s = (*this->state_f_)(); if (s.has_value()) {
if (s.has_value()) { auto pos = clamp(*s, 0.0f, 1.0f);
auto pos = clamp(*s, 0.0f, 1.0f); if (pos != this->position) {
if (pos != this->position) { this->position = pos;
this->position = pos; changed = true;
changed = true;
}
} }
} }
@@ -55,7 +55,6 @@ void TemplateValve::loop() {
void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
void TemplateValve::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; } Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }

View File

@@ -2,6 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/valve/valve.h" #include "esphome/components/valve/valve.h"
namespace esphome { namespace esphome {
@@ -17,7 +18,10 @@ class TemplateValve : public valve::Valve, public Component {
public: public:
TemplateValve(); TemplateValve();
void set_state_lambda(optional<float> (*f)()); template<typename F> void set_state_lambda(F &&f) {
this->state_f_.set(std::forward<F>(f));
this->enable_loop();
}
Trigger<> *get_open_trigger() const; Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const; Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger() const; Trigger<> *get_stop_trigger() const;
@@ -42,7 +46,7 @@ class TemplateValve : public valve::Valve, public Component {
void stop_prev_trigger_(); void stop_prev_trigger_();
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE}; TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
optional<optional<float> (*)()> state_f_; TemplateLambda<float> state_f_;
bool assumed_state_{false}; bool assumed_state_{false};
bool optimistic_{false}; bool optimistic_{false};
Trigger<> *open_trigger_; Trigger<> *open_trigger_;

View File

@@ -84,11 +84,6 @@ def text_schema(
return _TEXT_SCHEMA.extend(schema) return _TEXT_SCHEMA.extend(schema)
# Remove before 2025.11.0
TEXT_SCHEMA = text_schema()
TEXT_SCHEMA.add_extra(cv.deprecated_schema_constant("text"))
async def setup_text_core_( async def setup_text_core_(
var, var,
config, config,

View File

@@ -193,11 +193,6 @@ def text_sensor_schema(
return _TEXT_SENSOR_SCHEMA.extend(schema) return _TEXT_SENSOR_SCHEMA.extend(schema)
# Remove before 2025.11.0
TEXT_SENSOR_SCHEMA = text_sensor_schema()
TEXT_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("text_sensor"))
async def build_filters(config): async def build_filters(config):
return await cg.build_registry_list(FILTER_REGISTRY, config) return await cg.build_registry_list(FILTER_REGISTRY, config)

View File

@@ -84,11 +84,6 @@ def update_schema(
return _UPDATE_SCHEMA.extend(schema) return _UPDATE_SCHEMA.extend(schema)
# Remove before 2025.11.0
UPDATE_SCHEMA = update_schema()
UPDATE_SCHEMA.add_extra(cv.deprecated_schema_constant("update"))
async def setup_update_core_(var, config): async def setup_update_core_(var, config):
await setup_entity(var, config, "update") await setup_entity(var, config, "update")

View File

@@ -129,11 +129,6 @@ def valve_schema(
return _VALVE_SCHEMA.extend(schema) return _VALVE_SCHEMA.extend(schema)
# Remove before 2025.11.0
VALVE_SCHEMA = valve_schema()
VALVE_SCHEMA.add_extra(cv.deprecated_schema_constant("valve"))
async def _setup_valve_core(var, config): async def _setup_valve_core(var, config):
await setup_entity(var, config, "valve") await setup_entity(var, config, "valve")

View File

@@ -319,6 +319,9 @@ def iter_ids(config, path=None):
yield from iter_ids(item, path + [i]) yield from iter_ids(item, path + [i])
elif isinstance(config, dict): elif isinstance(config, dict):
for key, value in config.items(): for key, value in config.items():
if len(path) == 0 and key == CONF_SUBSTITUTIONS:
# Ignore IDs in substitution definitions.
continue
if isinstance(key, core.ID): if isinstance(key, core.ID):
yield key, path yield key, path
yield from iter_ids(value, path + [key]) yield from iter_ids(value, path + [key])

View File

@@ -2195,26 +2195,3 @@ def rename_key(old_key, new_key):
return config return config
return validator return validator
# Remove before 2025.11.0
def deprecated_schema_constant(entity_type: str):
def validator(config):
type: str = "unknown"
if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID):
type = str(id.type).split("::", maxsplit=1)[0]
_LOGGER.warning(
"Using `%s.%s_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
"Please use `%s.%s_schema(...)` instead. "
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
"Component using this schema: %s",
entity_type,
entity_type.upper(),
entity_type,
entity_type,
type,
)
return config
return validator

View File

@@ -0,0 +1,120 @@
#pragma once
#include <functional>
#include <concepts>
#include "esphome/core/optional.h"
namespace esphome {
/** Helper class for template platforms that stores either a stateless lambda (function pointer)
* or a stateful lambda (std::function pointer).
*
* This provides backward compatibility with PR #11555 while maintaining the optimization:
* - Stateless lambdas (no capture) → function pointer (4 bytes on ESP32)
* - Stateful lambdas (with capture) → pointer to std::function (4 bytes on ESP32)
* Total size: enum (1 byte) + union (4 bytes) + padding = 8 bytes (same as PR #11555)
*
* Both lambda types must return optional<T> (as YAML codegen does) to support the pattern:
* return {}; // Don't publish a value
* return 42.0; // Publish this value
*
* operator() returns optional<T>, returning nullopt when no lambda is set (type == NONE).
* This eliminates redundant "is lambda set" checks by reusing optional's discriminator.
*
* @tparam T The return type (e.g., float for TemplateLambda<optional<float>>)
* @tparam Args Optional arguments for the lambda
*/
template<typename T, typename... Args> class TemplateLambda {
public:
TemplateLambda() : type_(NONE) {}
// For stateless lambdas: use function pointer
template<typename F>
requires std::invocable<F, Args...> && std::convertible_to < F, optional<T>(*)
(Args...) > void set(F f) {
this->reset();
this->type_ = STATELESS_LAMBDA;
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas: use std::function pointer
template<typename F>
requires std::invocable<F, Args...> &&
(!std::convertible_to<F, optional<T> (*)(Args...)>) &&std::convertible_to<std::invoke_result_t<F, Args...>,
optional<T>> void set(F &&f) {
this->reset();
this->type_ = LAMBDA;
this->f_ = new std::function<optional<T>(Args...)>(std::forward<F>(f));
}
~TemplateLambda() { this->reset(); }
// Copy constructor
TemplateLambda(const TemplateLambda &) = delete;
TemplateLambda &operator=(const TemplateLambda &) = delete;
// Move constructor
TemplateLambda(TemplateLambda &&other) noexcept : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
TemplateLambda &operator=(TemplateLambda &&other) noexcept {
if (this != &other) {
this->reset();
this->type_ = other.type_;
if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
return *this;
}
bool has_value() const { return this->type_ != NONE; }
// Returns optional<T>, returning nullopt if no lambda is set
optional<T> operator()(Args... args) {
switch (this->type_) {
case STATELESS_LAMBDA:
return this->stateless_f_(args...); // Direct function pointer call
case LAMBDA:
return (*this->f_)(args...); // std::function call via pointer
case NONE:
default:
return nullopt; // No lambda set
}
}
optional<T> call(Args... args) { return (*this)(args...); }
protected:
void reset() {
if (this->type_ == LAMBDA) {
delete this->f_;
this->f_ = nullptr;
}
this->type_ = NONE;
}
enum : uint8_t {
NONE,
STATELESS_LAMBDA,
LAMBDA,
} type_;
union {
optional<T> (*stateless_f_)(Args...); // Function pointer (4 bytes on ESP32)
std::function<optional<T>(Args...)> *f_; // Pointer to std::function (4 bytes on ESP32)
};
};
} // namespace esphome

View File

@@ -1,6 +1,6 @@
pylint==4.0.2 pylint==4.0.2
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
ruff==0.14.1 # also change in .pre-commit-config.yaml when updating ruff==0.14.2 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.0 # also change in .pre-commit-config.yaml when updating
pre-commit pre-commit

View File

@@ -300,7 +300,7 @@ def fix_remote_receiver():
remote_receiver_schema["CONFIG_SCHEMA"] = { remote_receiver_schema["CONFIG_SCHEMA"] = {
"type": "schema", "type": "schema",
"schema": { "schema": {
"extends": ["binary_sensor.BINARY_SENSOR_SCHEMA", "core.COMPONENT_SCHEMA"], "extends": ["binary_sensor._BINARY_SENSOR_SCHEMA", "core.COMPONENT_SCHEMA"],
"config_vars": output["remote_base"].pop("binary"), "config_vars": output["remote_base"].pop("binary"),
}, },
} }

View File

@@ -52,6 +52,19 @@ number:
widget: spinbox_id widget: spinbox_id
id: lvgl_spinbox_number id: lvgl_spinbox_number
name: LVGL Spinbox Number name: LVGL Spinbox Number
- platform: template
id: test_brightness
name: "Test Brightness"
min_value: 0
max_value: 255
step: 1
optimistic: true
# Test lambda in automation accessing x parameter directly
# This is a real-world pattern from user configs
on_value:
- lambda: !lambda |-
// Direct use of x parameter in automation
ESP_LOGD("test", "Brightness: %.0f", x);
light: light:
- platform: lvgl - platform: lvgl
@@ -110,3 +123,21 @@ text:
platform: lvgl platform: lvgl
widget: hello_label widget: hello_label
mode: text mode: text
text_sensor:
- platform: template
id: test_text_sensor
name: "Test Text Sensor"
# Test nested lambdas in LVGL actions can access automation parameters
on_value:
- lvgl.label.update:
id: hello_label
text: !lambda return x.c_str();
- lvgl.label.update:
id: hello_label
text: !lambda |-
// Test complex lambda with conditionals accessing x parameter
if (x == "*") {
return "WILDCARD";
}
return x.c_str();

View File

@@ -257,7 +257,30 @@ lvgl:
text: "Hello shiny day" text: "Hello shiny day"
text_color: 0xFFFFFF text_color: 0xFFFFFF
align: bottom_mid align: bottom_mid
- label:
id: setup_lambda_label
# Test lambda in widget property during setup (LvContext)
# Should NOT receive lv_component parameter
text: !lambda |-
char buf[32];
snprintf(buf, sizeof(buf), "Setup: %d", 42);
return std::string(buf);
align: top_mid
text_font: space16 text_font: space16
- label:
id: chip_info_label
# Test complex setup lambda (real-world pattern)
# Should NOT receive lv_component parameter
text: !lambda |-
// Test conditional compilation and string formatting
char buf[64];
#ifdef USE_ESP_IDF
snprintf(buf, sizeof(buf), "IDF: v%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR);
#else
snprintf(buf, sizeof(buf), "Arduino");
#endif
return std::string(buf);
align: top_left
- obj: - obj:
align: center align: center
arc_opa: COVER arc_opa: COVER

View File

@@ -2,6 +2,7 @@ remote_transmitter:
- id: xmitr - id: xmitr
pin: ${pin} pin: ${pin}
carrier_duty_percent: 50% carrier_duty_percent: 50%
non_blocking: true
clock_resolution: ${clock_resolution} clock_resolution: ${clock_resolution}
rmt_symbols: ${rmt_symbols} rmt_symbols: ${rmt_symbols}

View File

@@ -9,6 +9,27 @@ esphome:
id: template_sens id: template_sens
state: !lambda "return 42.0;" state: !lambda "return 42.0;"
# Test C++ API: set_template() with stateless lambda (no captures)
- lambda: |-
id(template_sens).set_template([]() -> esphome::optional<float> {
return 123.0f;
});
# Test C++ API: set_template() with stateful lambda (with captures)
# This is the regression test for issue #11555
- lambda: |-
float captured_value = 456.0f;
id(template_sens).set_template([captured_value]() -> esphome::optional<float> {
return captured_value;
});
# Test C++ API: set_template() with more complex capture
- lambda: |-
auto sensor_id = id(template_sens);
id(template_number).set_template([sensor_id]() -> esphome::optional<float> {
return sensor_id->state * 2.0f;
});
- datetime.date.set: - datetime.date.set:
id: test_date id: test_date
date: date:
@@ -215,6 +236,7 @@ cover:
number: number:
- platform: template - platform: template
id: template_number
name: "Template number" name: "Template number"
optimistic: true optimistic: true
min_value: 0 min_value: 0

View File

@@ -261,6 +261,17 @@ def test_device_duplicate_id(
assert "ID duplicate_device redefined!" in captured.out assert "ID duplicate_device redefined!" in captured.out
def test_substitution_with_id(
yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that a ids coming from substitutions do not cause false positive ID redefinition."""
load_config_from_fixture(
yaml_file, "id_collision_with_substitution.yaml", FIXTURES_DIR
)
captured = capsys.readouterr()
assert "ID some_switch_id redefined!" not in captured.out
def test_add_platform_defines_priority() -> None: def test_add_platform_defines_priority() -> None:
"""Test that _add_platform_defines runs after globals. """Test that _add_platform_defines runs after globals.

View File

@@ -0,0 +1,12 @@
esphome:
name: test
host:
substitutions:
support_switches:
- platform: gpio
id: some_switch_id
pin: 12
switch: $support_switches