From 1d081fd5109bfd60c2e235e8306011947c5276b3 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:59:50 -0500 Subject: [PATCH] [epaper_spi] Fix update_interval: never validation error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add full_display_schema() function to display component to allow configurable default update_interval - Fix epaper_spi to use 60s default update_interval instead of 1s - Fix minimum update_interval validation to allow "never" value - Keep FULL_DISPLAY_SCHEMA constant for backward compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- esphome/components/display/__init__.py | 69 +++++++++++++----------- esphome/components/epaper_spi/display.py | 35 ++++++------ 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index ccbeedcd2fd..701cbd8a6d1 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -63,11 +63,13 @@ def validate_auto_clear(value): return cv.boolean(value) -BASIC_DISPLAY_SCHEMA = cv.Schema( - { - cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_, - } -).extend(cv.polling_component_schema("1s")) +def basic_display_schema(default_update_interval: str = "1s") -> cv.Schema: + """Create a basic display schema with configurable default update interval.""" + return cv.Schema( + { + cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_, + } + ).extend(cv.polling_component_schema(default_update_interval)) def _validate_test_card(config): @@ -81,34 +83,41 @@ def _validate_test_card(config): return config -FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( - { - cv.Optional(CONF_ROTATION): validate_rotation, - cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All( - cv.ensure_list( +def full_display_schema(default_update_interval: str = "1s") -> cv.Schema: + """Create a full display schema with configurable default update interval.""" + schema = basic_display_schema(default_update_interval).extend( + { + cv.Optional(CONF_ROTATION): validate_rotation, + cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All( + cv.ensure_list( + { + cv.GenerateID(): cv.declare_id(DisplayPage), + cv.Required(CONF_LAMBDA): cv.lambda_, + } + ), + cv.Length(min=1), + ), + cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation( { - cv.GenerateID(): cv.declare_id(DisplayPage), - cv.Required(CONF_LAMBDA): cv.lambda_, + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DisplayOnPageChangeTrigger + ), + cv.Optional(CONF_FROM): cv.use_id(DisplayPage), + cv.Optional(CONF_TO): cv.use_id(DisplayPage), } ), - cv.Length(min=1), - ), - cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayOnPageChangeTrigger - ), - cv.Optional(CONF_FROM): cv.use_id(DisplayPage), - cv.Optional(CONF_TO): cv.use_id(DisplayPage), - } - ), - cv.Optional( - CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED - ): validate_auto_clear, - cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean, - } -) -FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card) + cv.Optional( + CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED + ): validate_auto_clear, + cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean, + } + ) + schema.add_extra(_validate_test_card) + return schema + + +BASIC_DISPLAY_SCHEMA = basic_display_schema("1s") +FULL_DISPLAY_SCHEMA = full_display_schema("1s") async def setup_display_core_(var, config): diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index b7e71a3cae1..a0321964ab4 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -31,6 +31,7 @@ from esphome.const import ( CONF_TRANSFORM, CONF_UPDATE_INTERVAL, CONF_WIDTH, + SCHEDULER_DONT_RUN, ) from esphome.cpp_generator import RawExpression from esphome.final_validate import full_config @@ -72,12 +73,10 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY} def model_schema(config): model = MODELS[config[CONF_MODEL]] class_name = epaper_spi_ns.class_(model.class_name, EPaperBase) - minimum_update_interval = update_interval( - model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s") - ) cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required return ( - display.FULL_DISPLAY_SCHEMA.extend( + display.full_display_schema("60s") + .extend( spi.spi_device_schema( cs_pin_required=False, default_mode="MODE0", @@ -94,9 +93,6 @@ def model_schema(config): { cv.Optional(CONF_ROTATION, default=0): validate_rotation, cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), - cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All( - update_interval, cv.Range(min=minimum_update_interval) - ), cv.Optional(CONF_TRANSFORM): cv.Schema( { cv.Required(CONF_MIRROR_X): cv.boolean, @@ -150,15 +146,22 @@ def _final_validate(config): global_config = full_config.get() from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN - if CONF_LAMBDA not in config and CONF_PAGES not in config: - if LVGL_DOMAIN in global_config: - if CONF_UPDATE_INTERVAL not in config: - config[CONF_UPDATE_INTERVAL] = update_interval("never") - else: - # If no drawing methods are configured, and LVGL is not enabled, show a test card - config[CONF_SHOW_TEST_CARD] = True - elif CONF_UPDATE_INTERVAL not in config: - config[CONF_UPDATE_INTERVAL] = update_interval("1min") + # If no drawing methods are configured, and LVGL is not enabled, show a test card + if ( + CONF_LAMBDA not in config + and CONF_PAGES not in config + and LVGL_DOMAIN not in global_config + ): + config[CONF_SHOW_TEST_CARD] = True + + interval = config[CONF_UPDATE_INTERVAL] + if interval != SCHEDULER_DONT_RUN: + model = MODELS[config[CONF_MODEL]] + minimum = update_interval(model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s")) + if interval < minimum: + raise cv.Invalid( + f"update_interval must be at least {minimum} for {model.name}, got {interval}" + ) return config