mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 11:07:33 +00:00
Include model-driven display schemas in the language schema dump (#16872)
This commit is contained in:
@@ -5,7 +5,11 @@ from esphome import core, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, spi
|
||||
from esphome.components.display import CONF_SHOW_TEST_CARD, validate_rotation
|
||||
from esphome.components.mipi import flatten_sequence, map_sequence
|
||||
from esphome.components.mipi import (
|
||||
flatten_sequence,
|
||||
map_sequence,
|
||||
model_schema_extractor,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import update_interval
|
||||
from esphome.const import (
|
||||
@@ -111,6 +115,7 @@ def model_schema(config):
|
||||
)
|
||||
|
||||
|
||||
@model_schema_extractor(MODELS, model_schema)
|
||||
def customise_schema(config):
|
||||
"""
|
||||
Create a customised config schema for a specific model and validate the configuration.
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
# Various configuration constants for MIPI displays
|
||||
# Various utility functions for MIPI DBI configuration
|
||||
|
||||
from collections.abc import Callable
|
||||
import functools
|
||||
from typing import Any, Self
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.components.const import CONF_COLOR_DEPTH
|
||||
from esphome.components.display import CONF_SHOW_TEST_CARD, display_ns
|
||||
import esphome.config_validation as cv
|
||||
@@ -18,6 +22,7 @@ from esphome.const import (
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_OFFSET_HEIGHT,
|
||||
CONF_OFFSET_WIDTH,
|
||||
CONF_PAGES,
|
||||
@@ -27,6 +32,7 @@ from esphome.const import (
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.core import TimePeriod
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
|
||||
LOGGER = cv.logging.getLogger(__name__)
|
||||
|
||||
@@ -239,6 +245,54 @@ def delay(ms):
|
||||
return DELAY_FLAG, ms
|
||||
|
||||
|
||||
# Generic placeholder model present in every DriverChip registry; skipped when
|
||||
# choosing a representative model for schema extraction.
|
||||
_CUSTOM_MODEL = "CUSTOM"
|
||||
|
||||
|
||||
def model_schema_extractor(
|
||||
models: dict[str, Any],
|
||||
model_schema: Callable[[dict[str, Any]], Any],
|
||||
extra: dict[str, Any] | None = None,
|
||||
) -> Callable[[Callable[[Any], Any]], Callable[[Any], Any]]:
|
||||
"""
|
||||
Decorate a model-driven display CONFIG_SCHEMA so the language-schema dumper
|
||||
can extract it.
|
||||
|
||||
The schema is generated per ``model`` at validation time, so the static
|
||||
dumper has nothing to walk. When the dumper passes SCHEMA_EXTRACT, resolve a
|
||||
representative schema for a real model (the generic "CUSTOM" placeholder
|
||||
over-constrains fields like init_sequence) plus any *extra* keys the model
|
||||
needs, e.g. a bus mode, and hand that back; runtime validation is untouched.
|
||||
"""
|
||||
|
||||
def decorate(config_schema: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
||||
@schema_extractor("schema")
|
||||
@functools.wraps(config_schema)
|
||||
def wrapper(config: Any) -> Any:
|
||||
if config is not SCHEMA_EXTRACT:
|
||||
return config_schema(config)
|
||||
names = sorted(models)
|
||||
representative = next((n for n in names if n != _CUSTOM_MODEL), names[0])
|
||||
schema = model_schema({CONF_MODEL: representative, **(extra or {})})
|
||||
if isinstance(schema, vol.All):
|
||||
schema = next(
|
||||
(v for v in schema.validators if isinstance(v, vol.Schema)),
|
||||
schema,
|
||||
)
|
||||
if isinstance(schema, vol.Schema):
|
||||
# The resolved schema pins ``model`` to the representative; expose
|
||||
# the full model list so the dumped enum offers every model.
|
||||
schema = schema.extend(
|
||||
{cv.Required(CONF_MODEL): cv.one_of(*names, upper=True)}
|
||||
)
|
||||
return schema
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorate
|
||||
|
||||
|
||||
class DriverChip:
|
||||
"""
|
||||
A class representing a MIPI DBI driver chip model.
|
||||
|
||||
@@ -32,6 +32,7 @@ from esphome.components.mipi import (
|
||||
dimension_schema,
|
||||
get_color_depth,
|
||||
map_sequence,
|
||||
model_schema_extractor,
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
@@ -161,6 +162,7 @@ def model_schema(config):
|
||||
)
|
||||
|
||||
|
||||
@model_schema_extractor(MODELS, model_schema)
|
||||
def _config_schema(config):
|
||||
config = cv.Schema(
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ from esphome.components.mipi import (
|
||||
DriverChip,
|
||||
dimension_schema,
|
||||
map_sequence,
|
||||
model_schema_extractor,
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
@@ -219,6 +220,7 @@ def model_schema(config):
|
||||
return schema
|
||||
|
||||
|
||||
@model_schema_extractor(MODELS, model_schema)
|
||||
def _config_schema(config):
|
||||
config = cv.Schema(
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.components.mipi import (
|
||||
dimension_schema,
|
||||
get_color_depth,
|
||||
map_sequence,
|
||||
model_schema_extractor,
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
@@ -227,6 +228,7 @@ def model_schema(config):
|
||||
return schema
|
||||
|
||||
|
||||
@model_schema_extractor(MODELS, model_schema, extra={CONF_BUS_MODE: TYPE_SINGLE})
|
||||
def customise_schema(config):
|
||||
"""
|
||||
Create a customised config schema for a specific model and validate the configuration.
|
||||
|
||||
@@ -1002,6 +1002,10 @@ def convert(schema, config_var, path):
|
||||
else:
|
||||
config_var["use_id_type"] = str(data.base)
|
||||
config_var[S_TYPE] = "use_id"
|
||||
elif schema_type == "schema":
|
||||
# A callable CONFIG_SCHEMA that returned a representative schema
|
||||
# for extraction (model-driven components); walk it as usual.
|
||||
convert(data, config_var, path)
|
||||
else:
|
||||
raise TypeError("Unknown extracted schema type")
|
||||
elif config_var.get("key") == "GeneratedID":
|
||||
|
||||
@@ -117,6 +117,23 @@ def test_convert_emits_explicit_sensitive_marker() -> None:
|
||||
assert config_var["type"] == "string"
|
||||
|
||||
|
||||
def test_convert_walks_callable_schema_extractor() -> None:
|
||||
"""A callable schema tagged for "schema" extraction is resolved and walked."""
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
|
||||
@schema_extractor("schema")
|
||||
def dynamic_schema(value):
|
||||
if value is SCHEMA_EXTRACT:
|
||||
return cv.Schema({cv.Required("foo"): cv.string})
|
||||
return value
|
||||
|
||||
config_var: dict = {}
|
||||
_bls.convert(dynamic_schema, config_var, "/test")
|
||||
|
||||
assert config_var["type"] == "schema"
|
||||
assert "foo" in config_var["schema"]["config_vars"]
|
||||
|
||||
|
||||
def test_convert_keys_emits_heuristic_sensitive_marker() -> None:
|
||||
converted: dict = {}
|
||||
_bls.convert_keys(converted, {cv.Optional("password"): cv.string}, "/root")
|
||||
|
||||
Reference in New Issue
Block a user