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:
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
run: git diff
|
||||
- if: failure()
|
||||
name: Archive artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: generated-proto-files
|
||||
path: |
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -849,7 +849,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload memory analysis JSON
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: memory-analysis-target
|
||||
path: memory-analysis-target.json
|
||||
@@ -913,7 +913,7 @@ jobs:
|
||||
--platform "$platform"
|
||||
|
||||
- name: Upload memory analysis JSON
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: memory-analysis-pr
|
||||
path: memory-analysis-pr.json
|
||||
@@ -943,13 +943,13 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Download target analysis JSON
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: memory-analysis-target
|
||||
path: ./memory-analysis
|
||||
continue-on-error: true
|
||||
- name: Download PR analysis JSON
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: memory-analysis-pr
|
||||
path: ./memory-analysis
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||
uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
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@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||
uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -138,7 +138,7 @@ jobs:
|
||||
# version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: digests-${{ matrix.platform.arch }}
|
||||
path: /tmp/digests
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
pattern: digests-*
|
||||
path: /tmp/digests
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.1
|
||||
rev: v0.14.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -172,12 +172,6 @@ def alarm_control_panel_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(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(AlarmControlPanel),
|
||||
|
||||
@@ -548,11 +548,6 @@ def binary_sensor_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):
|
||||
await setup_entity(var, config, "binary_sensor")
|
||||
|
||||
|
||||
@@ -84,11 +84,6 @@ def button_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):
|
||||
await setup_entity(var, config, "button")
|
||||
|
||||
|
||||
@@ -270,11 +270,6 @@ def climate_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):
|
||||
await setup_entity(var, config, "climate")
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import logging
|
||||
|
||||
from esphome import core
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import climate, remote_base, sensor
|
||||
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
|
||||
|
||||
_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):
|
||||
await cg.register_component(var, config)
|
||||
await remote_base.register_transmittable(var, config)
|
||||
|
||||
@@ -151,11 +151,6 @@ def cover_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):
|
||||
await setup_entity(var, config, "cover")
|
||||
|
||||
|
||||
@@ -85,11 +85,6 @@ def event_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]):
|
||||
await setup_entity(var, config, "event")
|
||||
|
||||
|
||||
@@ -189,10 +189,6 @@ def fan_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(
|
||||
cv.ensure_list(cv.string_strict),
|
||||
cv.Length(min=1),
|
||||
|
||||
@@ -91,11 +91,6 @@ def lock_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):
|
||||
await setup_entity(var, config, "lock")
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
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_types import uint32
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import Expression, SafeExpType
|
||||
|
||||
from .helpers import requires_component
|
||||
|
||||
@@ -42,7 +44,13 @@ def static_cast(type, value):
|
||||
def call_lambda(lamb: LambdaExpression):
|
||||
expr = lamb.content.strip()
|
||||
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}()"
|
||||
|
||||
|
||||
@@ -65,10 +73,20 @@ class LValidator:
|
||||
return cv.returning_lambda(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:
|
||||
return None
|
||||
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(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
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.helpers import cpp_string_escape
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import Expression, SafeExpType
|
||||
|
||||
from . import types as ty
|
||||
from .defines import (
|
||||
@@ -388,11 +391,23 @@ class TextValidator(LValidator):
|
||||
return 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 format_str := value.get(CONF_FORMAT):
|
||||
args = [str(x) for x in value[CONF_ARGS]]
|
||||
arg_expr = cg.RawExpression(",".join(args))
|
||||
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||
arg_expr = cg.RawExpression(",".join(str_args))
|
||||
format_str = cpp_string_escape(format_str)
|
||||
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
|
||||
if time_format := value.get(CONF_TIME_FORMAT):
|
||||
|
||||
@@ -164,6 +164,9 @@ class LambdaContext(CodeContext):
|
||||
code_text.append(text)
|
||||
return code_text
|
||||
|
||||
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
|
||||
return self.parameters
|
||||
|
||||
async def __aenter__(self):
|
||||
await super().__aenter__()
|
||||
add_line_marks(self.where)
|
||||
@@ -178,9 +181,8 @@ class LvContext(LambdaContext):
|
||||
|
||||
added_lambda_count = 0
|
||||
|
||||
def __init__(self, args=None):
|
||||
self.args = args or LVGL_COMP_ARG
|
||||
super().__init__(parameters=self.args)
|
||||
def __init__(self):
|
||||
super().__init__(parameters=LVGL_COMP_ARG)
|
||||
|
||||
async def __aexit__(self, 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)
|
||||
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):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from ..defines import CONF_WIDGET
|
||||
from ..lvcode import (
|
||||
API_EVENT,
|
||||
EVENT_ARG,
|
||||
LVGL_COMP_ARG,
|
||||
UPDATE_EVENT,
|
||||
LambdaContext,
|
||||
LvContext,
|
||||
@@ -30,7 +29,7 @@ async def to_code(config):
|
||||
await wait_for_widgets()
|
||||
async with LambdaContext(EVENT_ARG) as lamb:
|
||||
lv_add(sensor.publish_state(widget.get_value()))
|
||||
async with LvContext(LVGL_COMP_ARG):
|
||||
async with LvContext():
|
||||
lv_add(
|
||||
lvgl_static.add_event_cb(
|
||||
widget.obj,
|
||||
|
||||
@@ -192,10 +192,6 @@ def media_player_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(
|
||||
cv.Schema(
|
||||
{
|
||||
|
||||
@@ -238,11 +238,6 @@ def number_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_(
|
||||
var, config, *, min_value: float, max_value: float, step: float
|
||||
):
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, esp32_rmt, remote_base
|
||||
@@ -18,9 +20,12 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AUTO_LOAD = ["remote_base"]
|
||||
|
||||
CONF_EOT_LEVEL = "eot_level"
|
||||
CONF_NON_BLOCKING = "non_blocking"
|
||||
CONF_ON_TRANSMIT = "on_transmit"
|
||||
CONF_ON_COMPLETE = "on_complete"
|
||||
CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID
|
||||
@@ -65,11 +70,25 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
esp32_c6=48,
|
||||
esp32_h2=48,
|
||||
): 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_COMPLETE): automation.validate_automation(single=True),
|
||||
}
|
||||
).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(
|
||||
{
|
||||
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent),
|
||||
@@ -95,6 +114,7 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
var = cg.new_Pvariable(config[CONF_ID], pin)
|
||||
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:
|
||||
cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION]))
|
||||
if CONF_USE_DMA in config:
|
||||
|
||||
@@ -54,6 +54,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
#if defined(USE_ESP32)
|
||||
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_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; }
|
||||
#endif
|
||||
|
||||
Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; };
|
||||
@@ -74,6 +75,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
|
||||
#ifdef USE_ESP32
|
||||
void configure_rmt_();
|
||||
void wait_for_rmt_();
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
RemoteTransmitterComponentStore store_{};
|
||||
@@ -90,6 +92,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
esp_err_t error_code_{ESP_OK};
|
||||
std::string error_string_{""};
|
||||
bool inverted_{false};
|
||||
bool non_blocking_{false};
|
||||
#endif
|
||||
uint8_t carrier_duty_percent_;
|
||||
|
||||
|
||||
@@ -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)
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
uint64_t total_duration = 0;
|
||||
|
||||
if (this->is_failed()) {
|
||||
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()) {
|
||||
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
|
||||
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
|
||||
// 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));
|
||||
while (send_wait > 0) {
|
||||
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) {
|
||||
value = -value;
|
||||
}
|
||||
total_duration += value * send_times;
|
||||
value = this->from_microseconds_(static_cast<uint32_t>(value));
|
||||
while (value > 0) {
|
||||
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 {
|
||||
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
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
|
||||
@@ -86,11 +86,6 @@ def select_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]):
|
||||
await setup_entity(var, config, "select")
|
||||
|
||||
|
||||
@@ -369,11 +369,6 @@ def sensor_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_))
|
||||
async def offset_filter_to_code(config, filter_id):
|
||||
template_ = await cg.templatable(config, [], float)
|
||||
|
||||
@@ -139,11 +139,6 @@ def switch_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):
|
||||
await setup_entity(var, config, "switch")
|
||||
|
||||
|
||||
@@ -25,6 +25,20 @@ void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor,
|
||||
this->sensor_data_.push_back(sd);
|
||||
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
|
||||
|
||||
void TemplateAlarmControlPanel::dump_config() {
|
||||
@@ -46,35 +60,20 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
" Supported Features: %" PRIu32,
|
||||
(this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features());
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto sensor_info : this->sensor_map_) {
|
||||
ESP_LOGCONFIG(TAG, " Binary Sensor:");
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Binary Sensor:\n"
|
||||
" Name: %s\n"
|
||||
" Type: %s\n"
|
||||
" Armed home bypass: %s\n"
|
||||
" Armed night bypass: %s\n"
|
||||
" Auto bypass: %s\n"
|
||||
" Chime mode: %s",
|
||||
sensor_info.first->get_name().c_str(),
|
||||
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
|
||||
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
|
||||
TRUEFALSE(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
|
||||
TRUEFALSE(sensor_info.second.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);
|
||||
sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(info.type)),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_CHIME));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -123,39 +122,37 @@ void TemplateAlarmControlPanel::loop() {
|
||||
bool instant_sensor_faulted = false;
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
// Test all of the sensors in the list regardless of the alarm panel state
|
||||
for (auto sensor_info : this->sensor_map_) {
|
||||
// Test all of the sensors regardless of the alarm panel state
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
// 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
|
||||
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
|
||||
if (this->current_state_ == ACP_STATE_DISARMED) {
|
||||
this->chime_callback_.call();
|
||||
}
|
||||
}
|
||||
// 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
|
||||
if (sensor_info.first->state) { // Sensor triggered?
|
||||
if (sensor->state) {
|
||||
// Skip if auto bypassed
|
||||
if (std::count(this->bypassed_sensor_indicies_.begin(), this->bypassed_sensor_indicies_.end(),
|
||||
sensor_info.second.store_index) == 1) {
|
||||
info.store_index) == 1) {
|
||||
continue;
|
||||
}
|
||||
// Skip if bypass armed home
|
||||
if (this->current_state_ == ACP_STATE_ARMED_HOME &&
|
||||
(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
|
||||
if ((this->current_state_ == ACP_STATE_ARMED_HOME) && (info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME)) {
|
||||
continue;
|
||||
}
|
||||
// Skip if bypass armed night
|
||||
if (this->current_state_ == ACP_STATE_ARMED_NIGHT &&
|
||||
(sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) {
|
||||
if ((this->current_state_ == ACP_STATE_ARMED_NIGHT) && (info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (sensor_info.second.type) {
|
||||
switch (info.type) {
|
||||
case ALARM_SENSOR_TYPE_INSTANT_ALWAYS:
|
||||
next_state = ACP_STATE_TRIGGERED;
|
||||
[[fallthrough]];
|
||||
@@ -247,11 +244,11 @@ void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_p
|
||||
|
||||
void TemplateAlarmControlPanel::bypass_before_arming() {
|
||||
#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
|
||||
if ((sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor_info.first->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor_info.first->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(sensor_info.second.store_index);
|
||||
if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(info.store_index);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,17 +6,21 @@ namespace template_ {
|
||||
|
||||
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() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto s = (*this->f_)();
|
||||
auto s = this->f_();
|
||||
if (s.has_value()) {
|
||||
this->publish_state(*s);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Template Binary Sensor", this); }
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -8,7 +9,10 @@ namespace template_ {
|
||||
|
||||
class TemplateBinarySensor : public Component, public binary_sensor::BinarySensor {
|
||||
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 loop() override;
|
||||
@@ -17,7 +21,7 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
optional<optional<bool> (*)()> f_;
|
||||
TemplateLambda<bool> f_;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -33,12 +33,13 @@ void TemplateCover::setup() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this->state_f_.has_value() && !this->tilt_f_.has_value())
|
||||
this->disable_loop();
|
||||
}
|
||||
void TemplateCover::loop() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->state_f_.has_value()) {
|
||||
auto s = (*this->state_f_)();
|
||||
auto s = this->state_f_();
|
||||
if (s.has_value()) {
|
||||
auto pos = clamp(*s, 0.0f, 1.0f);
|
||||
if (pos != this->position) {
|
||||
@@ -46,24 +47,21 @@ void TemplateCover::loop() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->tilt_f_.has_value()) {
|
||||
auto s = (*this->tilt_f_)();
|
||||
if (s.has_value()) {
|
||||
auto tilt = clamp(*s, 0.0f, 1.0f);
|
||||
if (tilt != this->tilt) {
|
||||
this->tilt = tilt;
|
||||
|
||||
auto tilt = this->tilt_f_();
|
||||
if (tilt.has_value()) {
|
||||
auto tilt_val = clamp(*tilt, 0.0f, 1.0f);
|
||||
if (tilt_val != this->tilt) {
|
||||
this->tilt = tilt_val;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->publish_state();
|
||||
}
|
||||
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
void TemplateCover::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
|
||||
float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; }
|
||||
Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; }
|
||||
@@ -124,7 +122,6 @@ CoverTraits TemplateCover::get_traits() {
|
||||
}
|
||||
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
|
||||
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
|
||||
void TemplateCover::set_tilt_lambda(optional<float> (*tilt_f)()) { this->tilt_f_ = tilt_f; }
|
||||
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
|
||||
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
|
||||
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/cover/cover.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +18,14 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
public:
|
||||
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_close_trigger() const;
|
||||
Trigger<> *get_stop_trigger() const;
|
||||
@@ -26,7 +34,6 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
Trigger<float> *get_tilt_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
void set_assumed_state(bool assumed_state);
|
||||
void set_tilt_lambda(optional<float> (*tilt_f)());
|
||||
void set_has_stop(bool has_stop);
|
||||
void set_has_position(bool has_position);
|
||||
void set_has_tilt(bool has_tilt);
|
||||
@@ -45,8 +52,8 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
void stop_prev_trigger_();
|
||||
|
||||
TemplateCoverRestoreMode restore_mode_{COVER_RESTORE};
|
||||
optional<optional<float> (*)()> state_f_;
|
||||
optional<optional<float> (*)()> tilt_f_;
|
||||
TemplateLambda<float> state_f_;
|
||||
TemplateLambda<float> tilt_f_;
|
||||
bool assumed_state_{false};
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
|
||||
@@ -40,15 +40,14 @@ void TemplateDate::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->year_ = val->year;
|
||||
this->month_ = val->month;
|
||||
this->day_ = val->day_of_month;
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateDate::control(const datetime::DateCall &call) {
|
||||
bool has_year = call.get_year().has_value();
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
||||
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 update() override;
|
||||
@@ -35,7 +36,7 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent {
|
||||
ESPTime initial_value_{};
|
||||
bool restore_value_{false};
|
||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
TemplateLambda<ESPTime> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -43,10 +43,8 @@ void TemplateDateTime::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->year_ = val->year;
|
||||
this->month_ = val->month;
|
||||
this->day_ = val->day_of_month;
|
||||
@@ -55,6 +53,7 @@ void TemplateDateTime::update() {
|
||||
this->second_ = val->second;
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateDateTime::control(const datetime::DateTimeCall &call) {
|
||||
bool has_year = call.get_year().has_value();
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponent {
|
||||
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 update() override;
|
||||
@@ -35,7 +36,7 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen
|
||||
ESPTime initial_value_{};
|
||||
bool restore_value_{false};
|
||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
TemplateLambda<ESPTime> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -40,15 +40,14 @@ void TemplateTime::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->hour_ = val->hour;
|
||||
this->minute_ = val->minute;
|
||||
this->second_ = val->second;
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateTime::control(const datetime::TimeCall &call) {
|
||||
bool has_hour = call.get_hour().has_value();
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
||||
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 update() override;
|
||||
@@ -35,7 +36,7 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent {
|
||||
ESPTime initial_value_{};
|
||||
bool restore_value_{false};
|
||||
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
|
||||
optional<optional<ESPTime> (*)()> f_;
|
||||
TemplateLambda<ESPTime> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -12,14 +12,11 @@ TemplateLock::TemplateLock()
|
||||
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
|
||||
|
||||
void TemplateLock::loop() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
void TemplateLock::control(const lock::LockCall &call) {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
@@ -45,7 +42,6 @@ void TemplateLock::open_latch() {
|
||||
this->open_trigger_->trigger();
|
||||
}
|
||||
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateLock::set_state_lambda(optional<lock::LockState> (*f)()) { this->f_ = f; }
|
||||
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
|
||||
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -13,7 +14,10 @@ class TemplateLock : public lock::Lock, public Component {
|
||||
|
||||
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_unlock_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 open_latch() override;
|
||||
|
||||
optional<optional<lock::LockState> (*)()> f_;
|
||||
TemplateLambda<lock::LockState> f_;
|
||||
bool optimistic_{false};
|
||||
Trigger<> *lock_trigger_;
|
||||
Trigger<> *unlock_trigger_;
|
||||
|
||||
@@ -30,12 +30,11 @@ void TemplateNumber::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateNumber::control(float value) {
|
||||
this->set_trigger_->trigger(value);
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateNumber : public number::Number, public PollingComponent {
|
||||
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 update() override;
|
||||
@@ -28,7 +29,7 @@ class TemplateNumber : public number::Number, public PollingComponent {
|
||||
float initial_value_{NAN};
|
||||
bool restore_value_{false};
|
||||
Trigger<float> *set_trigger_ = new Trigger<float>();
|
||||
optional<optional<float> (*)()> f_;
|
||||
TemplateLambda<float> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -31,17 +31,15 @@ void TemplateSelect::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
if (!this->has_option(*val)) {
|
||||
ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateSelect::control(const std::string &value) {
|
||||
this->set_trigger_->trigger(value);
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateSelect : public select::Select, public PollingComponent {
|
||||
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 update() override;
|
||||
@@ -28,7 +29,7 @@ class TemplateSelect : public select::Select, public PollingComponent {
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<optional<std::string> (*)()> f_;
|
||||
TemplateLambda<std::string> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
@@ -11,13 +11,14 @@ void TemplateSensor::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
|
||||
float TemplateSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void TemplateSensor::set_template(optional<float> (*f)()) { this->f_ = f; }
|
||||
|
||||
void TemplateSensor::dump_config() {
|
||||
LOG_SENSOR("", "Template Sensor", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -8,7 +9,7 @@ namespace template_ {
|
||||
|
||||
class TemplateSensor : public sensor::Sensor, public PollingComponent {
|
||||
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;
|
||||
|
||||
@@ -17,7 +18,7 @@ class TemplateSensor : public sensor::Sensor, public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
optional<optional<float> (*)()> f_;
|
||||
TemplateLambda<float> f_;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -9,14 +9,11 @@ static const char *const TAG = "template.switch";
|
||||
TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
|
||||
|
||||
void TemplateSwitch::loop() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
auto s = (*this->f_)();
|
||||
if (!s.has_value())
|
||||
return;
|
||||
|
||||
auto s = this->f_();
|
||||
if (s.has_value()) {
|
||||
this->publish_state(*s);
|
||||
}
|
||||
}
|
||||
void TemplateSwitch::write_state(bool state) {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
@@ -35,11 +32,13 @@ void TemplateSwitch::write_state(bool state) {
|
||||
}
|
||||
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
|
||||
void TemplateSwitch::set_state_lambda(optional<bool> (*f)()) { this->f_ = f; }
|
||||
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
|
||||
Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
|
||||
Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
|
||||
void TemplateSwitch::setup() {
|
||||
if (!this->f_.has_value())
|
||||
this->disable_loop();
|
||||
|
||||
optional<bool> initial_state = this->get_initial_state_with_restore_mode();
|
||||
|
||||
if (initial_state.has_value()) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -14,7 +15,10 @@ class TemplateSwitch : public switch_::Switch, public Component {
|
||||
void setup() 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_off_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
@@ -28,7 +32,7 @@ class TemplateSwitch : public switch_::Switch, public Component {
|
||||
|
||||
void write_state(bool state) override;
|
||||
|
||||
optional<optional<bool> (*)()> f_;
|
||||
TemplateLambda<bool> f_;
|
||||
bool optimistic_{false};
|
||||
bool assumed_state_{false};
|
||||
Trigger<> *turn_on_trigger_;
|
||||
|
||||
@@ -7,10 +7,8 @@ namespace template_ {
|
||||
static const char *const TAG = "template.text";
|
||||
|
||||
void TemplateText::setup() {
|
||||
if (!(this->f_ == nullptr)) {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
}
|
||||
std::string value = this->initial_value_;
|
||||
if (!this->pref_) {
|
||||
ESP_LOGD(TAG, "State from initial: %s", value.c_str());
|
||||
@@ -26,18 +24,14 @@ void TemplateText::setup() {
|
||||
}
|
||||
|
||||
void TemplateText::update() {
|
||||
if (this->f_ == nullptr)
|
||||
return;
|
||||
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateText::control(const std::string &value) {
|
||||
this->set_trigger_->trigger(value);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
@@ -61,7 +62,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
|
||||
|
||||
class TemplateText : public text::Text, public PollingComponent {
|
||||
public:
|
||||
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
|
||||
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -78,7 +79,7 @@ class TemplateText : public text::Text, public PollingComponent {
|
||||
bool optimistic_ = false;
|
||||
std::string initial_value_;
|
||||
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
|
||||
optional<optional<std::string> (*)()> f_{nullptr};
|
||||
TemplateLambda<std::string> f_{};
|
||||
|
||||
TemplateTextSaverBase *pref_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -10,13 +10,14 @@ void TemplateTextSensor::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -9,7 +10,7 @@ namespace template_ {
|
||||
|
||||
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
||||
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;
|
||||
|
||||
@@ -18,7 +19,7 @@ class TemplateTextSensor : public text_sensor::TextSensor, public PollingCompone
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
optional<optional<std::string> (*)()> f_{};
|
||||
TemplateLambda<std::string> f_{};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -33,13 +33,14 @@ void TemplateValve::setup() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this->state_f_.has_value())
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void TemplateValve::loop() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->state_f_.has_value()) {
|
||||
auto s = (*this->state_f_)();
|
||||
auto s = this->state_f_();
|
||||
if (s.has_value()) {
|
||||
auto pos = clamp(*s, 0.0f, 1.0f);
|
||||
if (pos != this->position) {
|
||||
@@ -47,7 +48,6 @@ void TemplateValve::loop() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->publish_state();
|
||||
@@ -55,7 +55,6 @@ void TemplateValve::loop() {
|
||||
|
||||
void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
void TemplateValve::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
|
||||
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/template_lambda.h"
|
||||
#include "esphome/components/valve/valve.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +18,10 @@ class TemplateValve : public valve::Valve, public Component {
|
||||
public:
|
||||
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_close_trigger() const;
|
||||
Trigger<> *get_stop_trigger() const;
|
||||
@@ -42,7 +46,7 @@ class TemplateValve : public valve::Valve, public Component {
|
||||
void stop_prev_trigger_();
|
||||
|
||||
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
|
||||
optional<optional<float> (*)()> state_f_;
|
||||
TemplateLambda<float> state_f_;
|
||||
bool assumed_state_{false};
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
|
||||
@@ -84,11 +84,6 @@ def text_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_(
|
||||
var,
|
||||
config,
|
||||
|
||||
@@ -193,11 +193,6 @@ def text_sensor_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):
|
||||
return await cg.build_registry_list(FILTER_REGISTRY, config)
|
||||
|
||||
|
||||
@@ -84,11 +84,6 @@ def update_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):
|
||||
await setup_entity(var, config, "update")
|
||||
|
||||
|
||||
@@ -129,11 +129,6 @@ def valve_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):
|
||||
await setup_entity(var, config, "valve")
|
||||
|
||||
|
||||
@@ -319,6 +319,9 @@ def iter_ids(config, path=None):
|
||||
yield from iter_ids(item, path + [i])
|
||||
elif isinstance(config, dict):
|
||||
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):
|
||||
yield key, path
|
||||
yield from iter_ids(value, path + [key])
|
||||
|
||||
@@ -2195,26 +2195,3 @@ def rename_key(old_key, new_key):
|
||||
return config
|
||||
|
||||
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
|
||||
|
||||
120
esphome/core/template_lambda.h
Normal file
120
esphome/core/template_lambda.h
Normal 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
|
||||
@@ -1,6 +1,6 @@
|
||||
pylint==4.0.2
|
||||
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
|
||||
pre-commit
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ def fix_remote_receiver():
|
||||
remote_receiver_schema["CONFIG_SCHEMA"] = {
|
||||
"type": "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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -52,6 +52,19 @@ number:
|
||||
widget: spinbox_id
|
||||
id: 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:
|
||||
- platform: lvgl
|
||||
@@ -110,3 +123,21 @@ text:
|
||||
platform: lvgl
|
||||
widget: hello_label
|
||||
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();
|
||||
|
||||
@@ -257,7 +257,30 @@ lvgl:
|
||||
text: "Hello shiny day"
|
||||
text_color: 0xFFFFFF
|
||||
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
|
||||
- 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:
|
||||
align: center
|
||||
arc_opa: COVER
|
||||
|
||||
@@ -2,6 +2,7 @@ remote_transmitter:
|
||||
- id: xmitr
|
||||
pin: ${pin}
|
||||
carrier_duty_percent: 50%
|
||||
non_blocking: true
|
||||
clock_resolution: ${clock_resolution}
|
||||
rmt_symbols: ${rmt_symbols}
|
||||
|
||||
|
||||
@@ -9,6 +9,27 @@ esphome:
|
||||
id: template_sens
|
||||
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:
|
||||
id: test_date
|
||||
date:
|
||||
@@ -215,6 +236,7 @@ cover:
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
id: template_number
|
||||
name: "Template number"
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
|
||||
@@ -261,6 +261,17 @@ def test_device_duplicate_id(
|
||||
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:
|
||||
"""Test that _add_platform_defines runs after globals.
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
host:
|
||||
|
||||
substitutions:
|
||||
support_switches:
|
||||
- platform: gpio
|
||||
id: some_switch_id
|
||||
pin: 12
|
||||
|
||||
switch: $support_switches
|
||||
Reference in New Issue
Block a user