1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 08:31:47 +00:00

Compare commits

..

9 Commits

Author SHA1 Message Date
J. Nick Koston
dd616872a1 Merge branch 'dev' into max6956_gpio_cache_banks 2025-10-29 00:39:06 -05:00
J. Nick Koston
0272b53a17 cleanup 2025-09-04 16:08:20 -05:00
J. Nick Koston
2b2fb5958d cleanup 2025-09-04 16:07:50 -05:00
J. Nick Koston
3f6a490cf4 fixes 2025-09-04 15:55:41 -05:00
J. Nick Koston
b424c16dde fixes 2025-09-04 15:55:34 -05:00
J. Nick Koston
dd861ab9b6 align 2025-09-04 15:51:28 -05:00
J. Nick Koston
938a3d8188 align 2025-09-04 15:49:10 -05:00
J. Nick Koston
bdc299789f fix 2025-09-04 15:39:29 -05:00
J. Nick Koston
6e2c3f4981 [max6956] Migrate to CachedGpioExpander to reduce I2C bus usage 2025-09-04 15:35:26 -05:00
71 changed files with 327 additions and 632 deletions

View File

@@ -180,7 +180,6 @@ jobs:
memory_impact: ${{ steps.determine.outputs.memory-impact }}
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -215,7 +214,6 @@ jobs:
echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
integration-tests:
name: Run integration tests
@@ -460,7 +458,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
strategy:
fail-fast: false
max-parallel: 2
max-parallel: 1
matrix:
include:
- id: clang-tidy
@@ -538,18 +536,59 @@ jobs:
run: script/ci-suggest-changes
if: always()
test-build-components-splitter:
name: Split components for intelligent grouping (40 weighted per batch)
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
outputs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Split components intelligently based on bus configurations
id: split
run: |
. venv/bin/activate
# Use intelligent splitter that groups components with same bus configs
components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
# Only isolate directly changed components when targeting dev branch
# For beta/release branches, group everything for faster CI
if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then
directly_changed='[]'
echo "Target branch: ${{ github.base_ref }} - grouping all components"
else
directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}'
echo "Target branch: ${{ github.base_ref }} - isolating directly changed components"
fi
echo "Splitting components intelligently..."
output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github)
echo "$output" >> $GITHUB_OUTPUT
test-build-components-split:
name: Test components batch (${{ matrix.components }})
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
- test-build-components-splitter
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
strategy:
fail-fast: false
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
matrix:
components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
steps:
- name: Show disk space
run: |
@@ -941,6 +980,7 @@ jobs:
- clang-tidy-nosplit
- clang-tidy-split
- determine-jobs
- test-build-components-splitter
- test-build-components-split
- pre-commit-ci-lite
- memory-impact-target-branch

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with:
category: "/language:${{matrix.language}}"

View File

@@ -207,14 +207,14 @@ def choose_upload_log_host(
if has_mqtt_logging():
resolved.append("MQTT")
if has_api() and has_non_ip_address() and has_resolvable_address():
if has_api() and has_non_ip_address():
resolved.extend(_resolve_with_cache(CORE.address, purpose))
elif purpose == Purpose.UPLOADING:
if has_ota() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_ota() and has_non_ip_address() and has_resolvable_address():
if has_ota() and has_non_ip_address():
resolved.extend(_resolve_with_cache(CORE.address, purpose))
else:
resolved.append(device)
@@ -318,17 +318,7 @@ def has_resolvable_address() -> bool:
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
if CORE.address is None:
return False
if has_ip_address():
return True
if has_mdns():
return True
# .local mDNS hostnames are only resolvable if mDNS is enabled
return not CORE.address.endswith(".local")
return CORE.address is not None
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):

View File

@@ -176,117 +176,7 @@ class Display;
class DisplayPage;
class DisplayOnPageChangeTrigger;
/** Optimized display writer that uses function pointers for stateless lambdas.
*
* Similar to TemplatableValue but specialized for display writer callbacks.
* Saves ~8 bytes per stateless lambda on 32-bit platforms (16 bytes std::function → ~8 bytes discriminator+pointer).
*
* Supports both:
* - Stateless lambdas (from YAML) → function pointer (4 bytes)
* - Stateful lambdas/std::function (from C++ code) → std::function* (heap allocated)
*
* @tparam T The display type (e.g., Display, Nextion, GPIOLCDDisplay)
*/
template<typename T> class DisplayWriter {
public:
DisplayWriter() : type_(NONE) {}
// For stateless lambdas (convertible to function pointer): use function pointer (4 bytes)
template<typename F>
DisplayWriter(F f) requires std::invocable<F, T &> && std::convertible_to<F, void (*)(T &)>
: type_(STATELESS_LAMBDA) {
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated)
// This handles backwards compatibility with external components
template<typename F>
DisplayWriter(F f) requires std::invocable<F, T &> &&(!std::convertible_to<F, void (*)(T &)>) : type_(LAMBDA) {
this->f_ = new std::function<void(T &)>(std::move(f));
}
// Copy constructor
DisplayWriter(const DisplayWriter &other) : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = new std::function<void(T &)>(*other.f_);
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
}
// Move constructor
DisplayWriter(DisplayWriter &&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;
}
// Assignment operators
DisplayWriter &operator=(const DisplayWriter &other) {
if (this != &other) {
this->~DisplayWriter();
new (this) DisplayWriter(other);
}
return *this;
}
DisplayWriter &operator=(DisplayWriter &&other) noexcept {
if (this != &other) {
this->~DisplayWriter();
new (this) DisplayWriter(std::move(other));
}
return *this;
}
~DisplayWriter() {
if (type_ == LAMBDA) {
delete this->f_;
}
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty)
}
bool has_value() const { return this->type_ != NONE; }
void call(T &display) const {
switch (this->type_) {
case STATELESS_LAMBDA:
this->stateless_f_(display); // Direct function pointer call
break;
case LAMBDA:
(*this->f_)(display); // std::function call
break;
case NONE:
default:
break;
}
}
// Operator() for convenience
void operator()(T &display) const { this->call(display); }
// Operator* for backwards compatibility with (*writer_)(*this) pattern
DisplayWriter &operator*() { return *this; }
const DisplayWriter &operator*() const { return *this; }
protected:
enum : uint8_t {
NONE,
LAMBDA,
STATELESS_LAMBDA,
} type_;
union {
std::function<void(T &)> *f_;
void (*stateless_f_)(T &);
};
};
// Type alias for Display writer - uses optimized DisplayWriter instead of std::function
using display_writer_t = DisplayWriter<Display>;
using display_writer_t = std::function<void(Display &)>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
@@ -788,7 +678,7 @@ class Display : public PollingComponent {
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
display_writer_t writer_{};
optional<display_writer_t> writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;

View File

@@ -3,8 +3,6 @@
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"
#include <algorithm>
namespace esphome {
namespace e131 {
@@ -78,14 +76,14 @@ void E131Component::loop() {
}
void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
if (std::find(light_effects_.begin(), light_effects_.end(), light_effect) != light_effects_.end()) {
if (light_effects_.count(light_effect)) {
return;
}
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_last_universe());
light_effects_.push_back(light_effect);
light_effects_.insert(light_effect);
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
join_(universe);
@@ -93,17 +91,14 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
}
void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
auto it = std::find(light_effects_.begin(), light_effects_.end(), light_effect);
if (it == light_effects_.end()) {
if (!light_effects_.count(light_effect)) {
return;
}
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_last_universe());
// Swap with last element and pop for O(1) removal (order doesn't matter)
*it = light_effects_.back();
light_effects_.pop_back();
light_effects_.erase(light_effect);
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
leave_(universe);

View File

@@ -7,6 +7,7 @@
#include <cinttypes>
#include <map>
#include <memory>
#include <set>
#include <vector>
namespace esphome {
@@ -46,8 +47,9 @@ class E131Component : public esphome::Component {
E131ListenMethod listen_method_{E131_MULTICAST};
std::unique_ptr<socket::Socket> socket_;
std::vector<E131AddressableLightEffect *> light_effects_;
std::set<E131AddressableLightEffect *> light_effects_;
std::map<int, int> universe_consumers_;
std::map<int, E131Packet> universe_packets_;
};
} // namespace e131

View File

@@ -94,8 +94,6 @@ async def to_code(config):
)
use_interrupt = False
cg.add(var.set_use_interrupt(use_interrupt))
if use_interrupt:
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
else:
# Only generate call when disabling interrupts (default is true)
cg.add(var.set_use_interrupt(use_interrupt))

View File

@@ -125,7 +125,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() {
case IMAGE_TYPE_RGB:
#if LV_COLOR_DEPTH == 32
switch (this->transparency_) {
switch (this->transparent_) {
case TRANSPARENCY_ALPHA_CHANNEL:
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA;
break;
@@ -156,8 +156,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() {
break;
}
#else
this->dsc_.header.cf =
this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565;
this->dsc_.header.cf = this->transparent_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGB565A8 : LV_IMG_CF_RGB565;
#endif
break;
}

View File

@@ -2,18 +2,13 @@
#include "esphome/core/hal.h"
#include "esphome/components/lcd_base/lcd_display.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace lcd_gpio {
class GPIOLCDDisplay;
using gpio_lcd_writer_t = display::DisplayWriter<GPIOLCDDisplay>;
class GPIOLCDDisplay : public lcd_base::LCDDisplay {
public:
void set_writer(gpio_lcd_writer_t &&writer) { this->writer_ = std::move(writer); }
void set_writer(std::function<void(GPIOLCDDisplay &)> &&writer) { this->writer_ = std::move(writer); }
void setup() override;
void set_data_pins(GPIOPin *d0, GPIOPin *d1, GPIOPin *d2, GPIOPin *d3) {
this->data_pins_[0] = d0;
@@ -48,7 +43,7 @@ class GPIOLCDDisplay : public lcd_base::LCDDisplay {
GPIOPin *rw_pin_{nullptr};
GPIOPin *enable_pin_{nullptr};
GPIOPin *data_pins_[8]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
gpio_lcd_writer_t writer_;
std::function<void(GPIOLCDDisplay &)> writer_;
};
} // namespace lcd_gpio

View File

@@ -3,18 +3,13 @@
#include "esphome/core/component.h"
#include "esphome/components/lcd_base/lcd_display.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace lcd_pcf8574 {
class PCF8574LCDDisplay;
using pcf8574_lcd_writer_t = display::DisplayWriter<PCF8574LCDDisplay>;
class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice {
public:
void set_writer(pcf8574_lcd_writer_t &&writer) { this->writer_ = std::move(writer); }
void set_writer(std::function<void(PCF8574LCDDisplay &)> &&writer) { this->writer_ = std::move(writer); }
void setup() override;
void dump_config() override;
void backlight();
@@ -29,7 +24,7 @@ class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice {
// Stores the current state of the backlight.
uint8_t backlight_value_;
pcf8574_lcd_writer_t writer_;
std::function<void(PCF8574LCDDisplay &)> writer_;
};
} // namespace lcd_pcf8574

View File

@@ -5,7 +5,6 @@ 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
@@ -13,7 +12,6 @@ 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
@@ -44,13 +42,7 @@ 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()
# 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 expr[6:][:-1].strip()
return f"{lamb}()"
@@ -73,20 +65,10 @@ class LValidator:
return cv.returning_lambda(value)
return self.validator(value)
async def process(
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
) -> Expression:
async def process(self, value, args=()):
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)

View File

@@ -1,5 +1,3 @@
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
@@ -19,7 +17,6 @@ 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 (
@@ -391,23 +388,11 @@ class TextValidator(LValidator):
return value
return super().__call__(value)
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()
async def process(self, value, args=()):
if isinstance(value, dict):
if format_str := value.get(CONF_FORMAT):
str_args = [str(x) for x in value[CONF_ARGS]]
arg_expr = cg.RawExpression(",".join(str_args))
args = [str(x) for x in value[CONF_ARGS]]
arg_expr = cg.RawExpression(",".join(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):

View File

@@ -164,9 +164,6 @@ 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)
@@ -181,8 +178,9 @@ class LvContext(LambdaContext):
added_lambda_count = 0
def __init__(self):
super().__init__(parameters=LVGL_COMP_ARG)
def __init__(self, args=None):
self.args = args or LVGL_COMP_ARG
super().__init__(parameters=self.args)
async def __aexit__(self, exc_type, exc_val, exc_tb):
await super().__aexit__(exc_type, exc_val, exc_tb)
@@ -191,11 +189,6 @@ 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)

View File

@@ -5,6 +5,7 @@ from ..defines import CONF_WIDGET
from ..lvcode import (
API_EVENT,
EVENT_ARG,
LVGL_COMP_ARG,
UPDATE_EVENT,
LambdaContext,
LvContext,
@@ -29,7 +30,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():
async with LvContext(LVGL_COMP_ARG):
lv_add(
lvgl_static.add_event_cb(
widget.obj,

View File

@@ -33,7 +33,7 @@ from ..lv_validation import (
pixels,
size,
)
from ..lvcode import LocalVariable, lv, lv_assign, lv_expr
from ..lvcode import LocalVariable, lv, lv_assign
from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema
from ..types import LvType, ObjUpdateAction, WidgetType
from . import Widget, get_widgets
@@ -70,18 +70,15 @@ class CanvasType(WidgetType):
width = config[CONF_WIDTH]
height = config[CONF_HEIGHT]
use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else ""
buf_size = literal(
f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})"
lv.canvas_set_buffer(
w.obj,
lv.custom_mem_alloc(
literal(f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})")
),
width,
height,
literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"),
)
with LocalVariable("buf", cg.void, lv_expr.custom_mem_alloc(buf_size)) as buf:
cg.add(cg.RawExpression(f"memset({buf}, 0, {buf_size});"))
lv.canvas_set_buffer(
w.obj,
buf,
width,
height,
literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"),
)
canvas_spec = CanvasType()

View File

@@ -15,6 +15,7 @@ from esphome.const import (
CODEOWNERS = ["@looping40"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["gpio_expander"]
MULTI_CONF = True
CONF_BRIGHTNESS_MODE = "brightness_mode"

View File

@@ -40,14 +40,59 @@ void MAX6956::setup() {
ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
}
bool MAX6956::digital_read(uint8_t pin) {
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
uint8_t value = 0;
this->read_reg_(reg_addr, &value);
return (value & MASK_1PORT_VALUE);
void MAX6956::loop() {
// Invalidate cache at the start of each loop
this->reset_pin_cache_();
}
void MAX6956::digital_write(uint8_t pin, bool value) {
bool MAX6956::digital_read_hw(uint8_t pin) {
// MAX6956 pins start at MAX6956_MIN
if (pin < MAX6956_MIN || pin > MAX6956_MAX) {
return false;
}
// Calculate bank index based on the base class view (no offset adjustment)
uint8_t bank_index = pin / MAX6956_BANK_SIZE;
static const uint8_t BANK_REGS[4] = {
MAX6956_4PORTS_4_7, // Bank 0: 4 ports 4-7 (bits D0-D3, D4-D7 read as 0)
MAX6956_8PORTS_8_15, // Bank 1: 8 ports 8-15 (bits D0-D7)
MAX6956_8PORTS_16_23, // Bank 2: 8 ports 16-23 (bits D0-D7)
MAX6956_8PORTS_24_31, // Bank 3: 8 ports 24-31 (bits D0-D7)
};
// Read the appropriate register
uint8_t value = 0;
if (!this->read_reg_(BANK_REGS[bank_index], &value)) {
return false;
}
// Store in cache with proper alignment
if (bank_index == 0) {
// Special case for bank 0: pins 4-7 are in bits D0-D3, shift them to bits 4-7
this->input_banks_[0] = value << MAX6956_BANK0_SHIFT;
} else {
// Banks 1-3 map directly
this->input_banks_[bank_index] = value;
}
return true;
}
bool MAX6956::digital_read_cache(uint8_t pin) {
// MAX6956 pins start at MAX6956_MIN
if (pin < MAX6956_MIN || pin > MAX6956_MAX) {
return false;
}
// Use the base class's view of banks (no offset adjustment)
uint8_t bank_index = pin / MAX6956_BANK_SIZE;
uint8_t bit_position = pin % MAX6956_BANK_SIZE;
return (this->input_banks_[bank_index] & (1 << bit_position)) != 0;
}
void MAX6956::digital_write_hw(uint8_t pin, bool value) {
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
this->write_reg_(reg_addr, value);
}

View File

@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/gpio_expander/cached_gpio.h"
namespace esphome {
namespace max6956 {
@@ -21,29 +22,39 @@ enum MAX6956GPIORange : uint8_t {
MAX6956_MAX = 31,
};
/// Bank configuration for MAX6956
static constexpr uint8_t MAX6956_BANK_SIZE = 8;
static constexpr uint8_t MAX6956_TOTAL_PINS = 32; // Includes pins 0-3 (unused) for cache alignment
static constexpr uint8_t MAX6956_BANK0_SHIFT = 4;
enum MAX6956GPIORegisters {
MAX6956_GLOBAL_CURRENT = 0x02,
MAX6956_CONFIGURATION = 0x04,
MAX6956_TRANSITION_DETECT_MASK = 0x06,
MAX6956_DISPLAY_TEST = 0x07,
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
MAX6956_CURRENT_START = 0x12, // Current054
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4-11 (data bits D0-D7)
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
MAX6956_CURRENT_START = 0x12, // Current054
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
// 8-port bulk read registers aligned with base class banks
MAX6956_4PORTS_4_7 = 0x40, // 4 ports 4-7 (bits D0-D3, D4-D7 read as 0)
MAX6956_8PORTS_8_15 = 0x48, // 8 ports 8-15 (bits D0-D7)
MAX6956_8PORTS_16_23 = 0x50, // 8 ports 16-23 (bits D0-D7)
MAX6956_8PORTS_24_31 = 0x58, // 8 ports 24-31 (bits D0-D7)
};
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 };
class MAX6956 : public Component, public i2c::I2CDevice {
class MAX6956 : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint8_t, MAX6956_TOTAL_PINS> {
public:
MAX6956() = default;
void setup() override;
void loop() override;
bool digital_read(uint8_t pin);
void digital_write(uint8_t pin, bool value);
void pin_mode(uint8_t pin, gpio::Flags flags);
void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags);
@@ -58,6 +69,11 @@ class MAX6956 : public Component, public i2c::I2CDevice {
void write_brightness_global();
void write_brightness_mode();
// CachedGpioExpander implementation
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
void digital_write_hw(uint8_t pin, bool value) override;
protected:
// read a given register
bool read_reg_(uint8_t reg, uint8_t *value);
@@ -66,6 +82,13 @@ class MAX6956 : public Component, public i2c::I2CDevice {
max6956::MAX6956CURRENTMODE brightness_mode_;
uint8_t global_brightness_;
// Cache for the 4 banks of 8 pins each (aligned with base class view)
// Bank 0: bits 0-7 (bits 0-3 unused as MAX6956 pins start at 4, bits 4-7 = pins 4-7)
// Bank 1: bits 8-15 (pins 8-15)
// Bank 2: bits 16-23 (pins 16-23)
// Bank 3: bits 24-31 (pins 24-31)
uint8_t input_banks_[4] = {0, 0, 0, 0};
private:
int8_t prev_bright_[28] = {0};
};

View File

@@ -4,14 +4,13 @@
#include "esphome/core/time.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace max7219 {
class MAX7219Component;
using max7219_writer_t = display::DisplayWriter<MAX7219Component>;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
class MAX7219Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -58,7 +57,7 @@ class MAX7219Component : public PollingComponent,
uint8_t num_chips_{1};
uint8_t *buffer_;
bool reverse_{false};
max7219_writer_t writer_{};
optional<max7219_writer_t> writer_{};
};
} // namespace max7219

View File

@@ -23,7 +23,7 @@ enum ScrollMode {
class MAX7219Component;
using max7219_writer_t = display::DisplayWriter<MAX7219Component>;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
class MAX7219Component : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -117,7 +117,7 @@ class MAX7219Component : public display::DisplayBuffer,
uint32_t last_scroll_ = 0;
uint16_t stepsleft_;
size_t get_buffer_length_();
max7219_writer_t writer_local_{};
optional<max7219_writer_t> writer_local_{};
};
} // namespace max7219digit

View File

@@ -9,7 +9,6 @@
#include "esphome/components/uart/uart.h"
#include "nextion_base.h"
#include "nextion_component.h"
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#ifdef USE_NEXTION_TFT_UPLOAD
@@ -32,7 +31,7 @@ namespace nextion {
class Nextion;
class NextionComponentBase;
using nextion_writer_t = display::DisplayWriter<Nextion>;
using nextion_writer_t = std::function<void(Nextion &)>;
static const std::string COMMAND_DELIMITER{static_cast<char>(255), static_cast<char>(255), static_cast<char>(255)};
@@ -1472,7 +1471,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
CallbackManager<void(uint8_t, uint8_t, bool)> touch_callback_{};
CallbackManager<void()> buffer_overflow_callback_{};
nextion_writer_t writer_;
optional<nextion_writer_t> writer_;
optional<float> brightness_;
#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO

View File

@@ -3,7 +3,6 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/display/display.h"
#include <cinttypes>
@@ -30,7 +29,7 @@ enum UNIT {
UNIT_DEG_E, ///< show "°E"
};
using pvvx_writer_t = display::DisplayWriter<PVVXDisplay>;
using pvvx_writer_t = std::function<void(PVVXDisplay &)>;
class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
public:
@@ -127,7 +126,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
esp32_ble_tracker::ESPBTUUID char_uuid_ =
esp32_ble_tracker::ESPBTUUID::from_raw("00001f1f-0000-1000-8000-00805f9b34fb");
pvvx_writer_t writer_{};
optional<pvvx_writer_t> writer_{};
};
} // namespace pvvx_mithermometer

View File

@@ -1,5 +1,3 @@
import logging
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import esp32, esp32_rmt, remote_base
@@ -20,12 +18,9 @@ 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
@@ -70,25 +65,11 @@ 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),
@@ -114,7 +95,6 @@ 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:

View File

@@ -54,7 +54,6 @@ 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_; };
@@ -75,7 +74,6 @@ 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_{};
@@ -92,7 +90,6 @@ 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_;

View File

@@ -196,29 +196,12 @@ 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_();
@@ -229,7 +212,6 @@ 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));
@@ -247,7 +229,6 @@ 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));
@@ -279,12 +260,13 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
} else {
this->status_clear_warning();
}
if (this->non_blocking_) {
this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); });
} else {
this->wait_for_rmt_();
error = rmt_tx_wait_all_done(this->channel_, -1);
if (error != ESP_OK) {
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
this->status_set_warning();
}
this->complete_trigger_->trigger();
}
#else
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {

View File

@@ -9,7 +9,7 @@ namespace st7920 {
class ST7920;
using st7920_writer_t = display::DisplayWriter<ST7920>;
using st7920_writer_t = std::function<void(ST7920 &)>;
class ST7920 : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
@@ -44,7 +44,7 @@ class ST7920 : public display::DisplayBuffer,
void end_transaction_();
int16_t width_ = 128, height_ = 64;
st7920_writer_t writer_local_{};
optional<st7920_writer_t> writer_local_{};
};
} // namespace st7920

View File

@@ -6,21 +6,17 @@ namespace template_ {
static const char *const TAG = "template.binary_sensor";
void TemplateBinarySensor::setup() {
if (!this->f_.has_value()) {
this->disable_loop();
} else {
this->loop();
}
}
void TemplateBinarySensor::setup() { this->loop(); }
void TemplateBinarySensor::loop() {
auto s = this->f_();
if (!this->f_.has_value())
return;
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_

View File

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

View File

@@ -33,27 +33,28 @@ void TemplateCover::setup() {
break;
}
}
if (!this->state_f_.has_value() && !this->tilt_f_.has_value())
this->disable_loop();
}
void TemplateCover::loop() {
bool changed = false;
auto s = this->state_f_();
if (s.has_value()) {
auto pos = clamp(*s, 0.0f, 1.0f);
if (pos != this->position) {
this->position = pos;
changed = true;
if (this->state_f_.has_value()) {
auto s = (*this->state_f_)();
if (s.has_value()) {
auto pos = clamp(*s, 0.0f, 1.0f);
if (pos != this->position) {
this->position = pos;
changed = true;
}
}
}
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 (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;
changed = true;
}
}
}
@@ -62,6 +63,7 @@ void TemplateCover::loop() {
}
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
void TemplateCover::set_state_lambda(optional<float> (*f)()) { this->state_f_ = f; }
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_; }
@@ -122,6 +124,7 @@ CoverTraits TemplateCover::get_traits() {
}
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
void TemplateCover::set_tilt_lambda(optional<float> (*tilt_f)()) { this->tilt_f_ = tilt_f; }
void TemplateCover::set_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; }

View File

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

View File

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

View File

@@ -9,14 +9,13 @@
#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:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
void setup() override;
void update() override;
@@ -36,7 +35,7 @@ class TemplateDate : public datetime::DateEntity, public PollingComponent {
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
TemplateLambda<ESPTime> f_;
optional<optional<ESPTime> (*)()> f_;
ESPPreferenceObject pref_;
};

View File

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

View File

@@ -9,14 +9,13 @@
#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:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
void setup() override;
void update() override;
@@ -36,7 +35,7 @@ class TemplateDateTime : public datetime::DateTimeEntity, public PollingComponen
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
TemplateLambda<ESPTime> f_;
optional<optional<ESPTime> (*)()> f_;
ESPPreferenceObject pref_;
};

View File

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

View File

@@ -9,14 +9,13 @@
#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:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<ESPTime> (*f)()) { this->f_ = f; }
void setup() override;
void update() override;
@@ -36,7 +35,7 @@ class TemplateTime : public datetime::TimeEntity, public PollingComponent {
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
TemplateLambda<ESPTime> f_;
optional<optional<ESPTime> (*)()> f_;
ESPPreferenceObject pref_;
};

View File

@@ -11,16 +11,14 @@ static const char *const TAG = "template.lock";
TemplateLock::TemplateLock()
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
void TemplateLock::setup() {
if (!this->f_.has_value())
this->disable_loop();
}
void TemplateLock::loop() {
auto val = this->f_();
if (val.has_value()) {
this->publish_state(*val);
}
if (!this->f_.has_value())
return;
auto val = (*this->f_)();
if (!val.has_value())
return;
this->publish_state(*val);
}
void TemplateLock::control(const lock::LockCall &call) {
if (this->prev_trigger_ != nullptr) {
@@ -47,6 +45,7 @@ void TemplateLock::open_latch() {
this->open_trigger_->trigger();
}
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateLock::set_state_lambda(optional<lock::LockState> (*f)()) { this->f_ = f; }
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_; }

View File

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

View File

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

View File

@@ -4,14 +4,13 @@
#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:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<float> (*f)()) { this->f_ = f; }
void setup() override;
void update() override;
@@ -29,7 +28,7 @@ class TemplateNumber : public number::Number, public PollingComponent {
float initial_value_{NAN};
bool restore_value_{false};
Trigger<float> *set_trigger_ = new Trigger<float>();
TemplateLambda<float> f_;
optional<optional<float> (*)()> f_;
ESPPreferenceObject pref_;
};

View File

@@ -31,14 +31,16 @@ void TemplateSelect::update() {
if (!this->f_.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);
auto val = (*this->f_)();
if (!val.has_value())
return;
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) {

View File

@@ -4,14 +4,13 @@
#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:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
void setup() override;
void update() override;
@@ -29,7 +28,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>();
TemplateLambda<std::string> f_;
optional<optional<std::string> (*)()> f_;
ESPPreferenceObject pref_;
};

View File

@@ -11,14 +11,13 @@ 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);

View File

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

View File

@@ -9,10 +9,13 @@ static const char *const TAG = "template.switch";
TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
void TemplateSwitch::loop() {
auto s = this->f_();
if (s.has_value()) {
this->publish_state(*s);
}
if (!this->f_.has_value())
return;
auto s = (*this->f_)();
if (!s.has_value())
return;
this->publish_state(*s);
}
void TemplateSwitch::write_state(bool state) {
if (this->prev_trigger_ != nullptr) {
@@ -32,13 +35,11 @@ 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()) {

View File

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

View File

@@ -7,8 +7,10 @@ namespace template_ {
static const char *const TAG = "template.text";
void TemplateText::setup() {
if (this->f_.has_value())
return;
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());
@@ -24,13 +26,17 @@ 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()) {
this->publish_state(*val);
}
auto val = (*this->f_)();
if (!val.has_value())
return;
this->publish_state(*val);
}
void TemplateText::control(const std::string &value) {

View File

@@ -4,7 +4,6 @@
#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_ {
@@ -62,7 +61,7 @@ template<uint8_t SZ> class TextSaver : public TemplateTextSaverBase {
class TemplateText : public text::Text, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<std::string> (*f)()) { this->f_ = f; }
void setup() override;
void update() override;
@@ -79,7 +78,7 @@ class TemplateText : public text::Text, public PollingComponent {
bool optimistic_ = false;
std::string initial_value_;
Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
TemplateLambda<std::string> f_{};
optional<optional<std::string> (*)()> f_{nullptr};
TemplateTextSaverBase *pref_ = nullptr;
};

View File

@@ -10,14 +10,13 @@ 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_

View File

@@ -2,7 +2,6 @@
#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 {
@@ -10,7 +9,7 @@ namespace template_ {
class TemplateTextSensor : public text_sensor::TextSensor, public PollingComponent {
public:
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
void set_template(optional<std::string> (*f)());
void update() override;
@@ -19,7 +18,7 @@ class TemplateTextSensor : public text_sensor::TextSensor, public PollingCompone
void dump_config() override;
protected:
TemplateLambda<std::string> f_{};
optional<optional<std::string> (*)()> f_{};
};
} // namespace template_

View File

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

View File

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

View File

@@ -3,14 +3,13 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace tm1621 {
class TM1621Display;
using tm1621_writer_t = display::DisplayWriter<TM1621Display>;
using tm1621_writer_t = std::function<void(TM1621Display &)>;
class TM1621Display : public PollingComponent {
public:
@@ -60,7 +59,7 @@ class TM1621Display : public PollingComponent {
GPIOPin *cs_pin_;
GPIOPin *read_pin_;
GPIOPin *write_pin_;
tm1621_writer_t writer_{};
optional<tm1621_writer_t> writer_{};
char row_[2][12];
uint8_t state_;
uint8_t device_;

View File

@@ -4,7 +4,6 @@
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/time.h"
#include "esphome/components/display/display.h"
#include <vector>
@@ -20,7 +19,7 @@ class TM1637Display;
class TM1637Key;
#endif
using tm1637_writer_t = display::DisplayWriter<TM1637Display>;
using tm1637_writer_t = std::function<void(TM1637Display &)>;
class TM1637Display : public PollingComponent {
public:
@@ -79,7 +78,7 @@ class TM1637Display : public PollingComponent {
uint8_t length_;
bool inverted_;
bool on_{true};
tm1637_writer_t writer_{};
optional<tm1637_writer_t> writer_{};
uint8_t buffer_[6] = {0};
#ifdef USE_BINARY_SENSOR
std::vector<TM1637Key *> tm1637_keys_{};

View File

@@ -5,7 +5,6 @@
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/time.h"
#include "esphome/components/display/display.h"
#include <vector>
@@ -19,7 +18,7 @@ class KeyListener {
class TM1638Component;
using tm1638_writer_t = display::DisplayWriter<TM1638Component>;
using tm1638_writer_t = std::function<void(TM1638Component &)>;
class TM1638Component : public PollingComponent {
public:
@@ -71,7 +70,7 @@ class TM1638Component : public PollingComponent {
GPIOPin *stb_pin_;
GPIOPin *dio_pin_;
uint8_t *buffer_ = new uint8_t[8];
tm1638_writer_t writer_{};
optional<tm1638_writer_t> writer_{};
std::vector<KeyListener *> listeners_{};
};

View File

@@ -319,9 +319,6 @@ 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])

View File

@@ -1,51 +0,0 @@
#pragma once
#include "esphome/core/optional.h"
namespace esphome {
/** Lightweight wrapper for template platform lambdas (stateless function pointers only).
*
* This optimizes template platforms by storing only a function pointer (4 bytes on ESP32)
* instead of std::function (16-32 bytes).
*
* IMPORTANT: This only supports stateless lambdas (no captures). The set_template() method
* is an internal API used by YAML codegen, not intended for external use.
*
* Lambdas must return optional<T> 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 (nullptr check).
*
* @tparam T The return type (e.g., float for sensor values)
* @tparam Args Optional arguments for the lambda
*/
template<typename T, typename... Args> class TemplateLambda {
public:
TemplateLambda() : f_(nullptr) {}
/** Set the lambda function pointer.
* INTERNAL API: Only for use by YAML codegen.
* Only stateless lambdas (no captures) are supported.
*/
void set(optional<T> (*f)(Args...)) { this->f_ = f; }
/** Check if a lambda is set */
bool has_value() const { return this->f_ != nullptr; }
/** Call the lambda, returning nullopt if no lambda is set */
optional<T> operator()(Args &&...args) {
if (this->f_ == nullptr)
return nullopt;
return this->f_(std::forward<Args>(args)...);
}
/** Alias for operator() for compatibility */
optional<T> call(Args &&...args) { return (*this)(std::forward<Args>(args)...); }
protected:
optional<T> (*f_)(Args...); // Function pointer (4 bytes on ESP32)
};
} // namespace esphome

View File

@@ -43,14 +43,12 @@ from enum import StrEnum
from functools import cache
import json
import os
from pathlib import Path
import subprocess
import sys
from typing import Any
from helpers import (
CPP_FILE_EXTENSIONS,
ESPHOME_TESTS_COMPONENTS_PATH,
PYTHON_FILE_EXTENSIONS,
changed_files,
core_changed,
@@ -67,17 +65,12 @@ from helpers import (
parse_test_filename,
root_path,
)
from split_components_for_ci import create_intelligent_batches
# Threshold for splitting clang-tidy jobs
# For small PRs (< 65 files), use nosplit for faster CI
# For large PRs (>= 65 files), use split for better parallelization
CLANG_TIDY_SPLIT_THRESHOLD = 65
# Component test batch size (weighted)
# Isolated components count as 10x, groupable components count as 1x
COMPONENT_TEST_BATCH_SIZE = 40
class Platform(StrEnum):
"""Platform identifiers for memory impact analysis."""
@@ -693,22 +686,6 @@ def main() -> None:
# Determine which C++ unit tests to run
cpp_run_all, cpp_components = determine_cpp_unit_tests(args.branch)
# Split components into batches for CI testing
# This intelligently groups components with similar bus configurations
component_test_batches: list[str]
if changed_components_with_tests:
tests_dir = Path(root_path) / ESPHOME_TESTS_COMPONENTS_PATH
batches, _ = create_intelligent_batches(
components=changed_components_with_tests,
tests_dir=tests_dir,
batch_size=COMPONENT_TEST_BATCH_SIZE,
directly_changed=directly_changed_with_tests,
)
# Convert batches to space-separated strings for CI matrix
component_test_batches = [" ".join(batch) for batch in batches]
else:
component_test_batches = []
output: dict[str, Any] = {
"integration_tests": run_integration,
"clang_tidy": run_clang_tidy,
@@ -726,7 +703,6 @@ def main() -> None:
"memory_impact": memory_impact,
"cpp_unit_tests_run_all": cpp_run_all,
"cpp_unit_tests_components": cpp_components,
"component_test_batches": component_test_batches,
}
# Output as JSON

View File

@@ -62,10 +62,6 @@ def create_intelligent_batches(
) -> tuple[list[list[str]], dict[tuple[str, str], list[str]]]:
"""Create batches optimized for component grouping.
IMPORTANT: This function is called from both split_components_for_ci.py (standalone script)
and determine-jobs.py (integrated into job determination). Be careful when refactoring
to ensure changes work in both contexts.
Args:
components: List of component names to batch
tests_dir: Path to tests/components directory

View File

@@ -18,8 +18,7 @@ def test_gpio_binary_sensor_basic_setup(
assert "new gpio::GPIOBinarySensor();" in main_cpp
assert "App.register_binary_sensor" in main_cpp
# set_use_interrupt(true) should NOT be generated (uses C++ default)
assert "bs_gpio->set_use_interrupt(true);" not in main_cpp
assert "bs_gpio->set_use_interrupt(true);" in main_cpp
assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
@@ -52,8 +51,8 @@ def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt(
"tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml"
)
# GPIO5 should still use interrupts (default, so no setter call)
assert "bs_gpio5->set_use_interrupt(true);" not in main_cpp
# GPIO5 should still use interrupts
assert "bs_gpio5->set_use_interrupt(true);" in main_cpp
assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp

View File

@@ -1,6 +1,5 @@
display:
- platform: sdl
id: image_display
auto_clear_enabled: false
dimensions:
width: 480

View File

@@ -52,19 +52,6 @@ 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
@@ -123,21 +110,3 @@ 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();

View File

@@ -257,30 +257,7 @@ 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

View File

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

View File

@@ -13,14 +13,11 @@ display:
binary_sensor:
- platform: sdl
sdl_id: sdl_display
id: key_up
key: SDLK_a
- platform: sdl
sdl_id: sdl_display
id: key_down
key: SDLK_d
- platform: sdl
sdl_id: sdl_display
id: key_enter
key: SDLK_s

View File

@@ -9,13 +9,6 @@ esphome:
id: template_sens
state: !lambda "return 42.0;"
# Test C++ API: set_template() with stateless lambda (no captures)
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
- lambda: |-
id(template_sens).set_template([]() -> esphome::optional<float> {
return 123.0f;
});
- datetime.date.set:
id: test_date
date:
@@ -222,7 +215,6 @@ cover:
number:
- platform: template
id: template_number
name: "Template number"
optimistic: true
min_value: 0

View File

@@ -32,7 +32,6 @@ switch:
name: "Test Switch"
id: test_switch
optimistic: true
lambda: return false;
interval:
- interval: 0.5s

View File

@@ -152,14 +152,6 @@ def test_main_all_tests_should_run(
assert output["memory_impact"]["should_run"] == "false"
assert output["cpp_unit_tests_run_all"] is False
assert output["cpp_unit_tests_components"] == ["wifi", "api", "sensor"]
# component_test_batches should be present and be a list of space-separated strings
assert "component_test_batches" in output
assert isinstance(output["component_test_batches"], list)
# Each batch should be a space-separated string of component names
for batch in output["component_test_batches"]:
assert isinstance(batch, str)
# Should contain at least one component (no empty batches)
assert len(batch) > 0
def test_main_no_tests_should_run(
@@ -217,9 +209,6 @@ def test_main_no_tests_should_run(
assert output["memory_impact"]["should_run"] == "false"
assert output["cpp_unit_tests_run_all"] is False
assert output["cpp_unit_tests_components"] == []
# component_test_batches should be empty list
assert "component_test_batches" in output
assert output["component_test_batches"] == []
def test_main_with_branch_argument(

View File

@@ -261,17 +261,6 @@ 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.

View File

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

View File

@@ -744,7 +744,7 @@ def test_choose_upload_log_host_ota_local_all_options() -> None:
check_default=None,
purpose=Purpose.UPLOADING,
)
assert result == ["MQTTIP"]
assert result == ["MQTTIP", "test.local"]
@pytest.mark.usefixtures("mock_serial_ports")
@@ -794,7 +794,7 @@ def test_choose_upload_log_host_ota_local_all_options_logging() -> None:
check_default=None,
purpose=Purpose.LOGGING,
)
assert result == ["MQTTIP", "MQTT"]
assert result == ["MQTTIP", "MQTT", "test.local"]
@pytest.mark.usefixtures("mock_no_mqtt_logging")
@@ -1564,7 +1564,7 @@ def test_has_resolvable_address() -> None:
setup_core(
config={CONF_MDNS: {CONF_DISABLED: True}}, address="esphome-device.local"
)
assert has_resolvable_address() is False
assert has_resolvable_address() is True
# Test with mDNS disabled and regular DNS hostname (resolvable)
setup_core(config={CONF_MDNS: {CONF_DISABLED: True}}, address="device.example.com")