mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 19:44:08 +00:00
[mipi_spi] Light platform for AMOLED display brightness (#20212)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1520d228bc
commit
490d478e54
@@ -4,7 +4,13 @@ from collections.abc import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components.mipi import CONF_INVERT_COLORS, CONF_PIXEL_MODE, DriverChip
|
||||
from esphome.components.mipi import (
|
||||
BRIGHTNESS,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_PIXEL_MODE,
|
||||
DriverChip,
|
||||
)
|
||||
from esphome.const import CONF_BRIGHTNESS
|
||||
|
||||
# A minimal config with no reset pin: enough for get_sequence(add_madctl=False) to run
|
||||
# without needing a full display configuration.
|
||||
@@ -62,3 +68,31 @@ def test_get_sequence_skips_reset_delay_validation_without_add_reset() -> None:
|
||||
chip = DriverChip("TEST-GET-SEQUENCE-NO-RESET", reset_delay=999)
|
||||
|
||||
chip.get_sequence(_BASE_CONFIG, add_madctl=False, add_reset=False)
|
||||
|
||||
|
||||
def _has_brightness_command(sequence: tuple[int, ...], value: int) -> bool:
|
||||
"""True if the flattened sequence holds a one-byte brightness (0x51) command."""
|
||||
return any(
|
||||
sequence[i : i + 3] == (BRIGHTNESS, 1, value) for i in range(len(sequence) - 2)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("brightness", [0, 0x80])
|
||||
def test_get_sequence_adds_configured_brightness(brightness: int) -> None:
|
||||
"""A configured brightness is sent at startup, including zero."""
|
||||
chip = DriverChip("TEST-GET-SEQUENCE-BRIGHTNESS")
|
||||
|
||||
sequence = chip.get_sequence(
|
||||
{**_BASE_CONFIG, CONF_BRIGHTNESS: brightness}, add_madctl=False
|
||||
)
|
||||
|
||||
assert _has_brightness_command(sequence, brightness)
|
||||
|
||||
|
||||
def test_get_sequence_omits_brightness_when_not_set() -> None:
|
||||
"""No brightness command is sent when neither config nor model sets one."""
|
||||
chip = DriverChip("TEST-GET-SEQUENCE-NO-BRIGHTNESS")
|
||||
|
||||
sequence = chip.get_sequence(_BASE_CONFIG, add_madctl=False)
|
||||
|
||||
assert not any(_has_brightness_command(sequence, v) for v in range(256))
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
"""Tests for the mipi_spi light platform validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import final_validate
|
||||
from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32
|
||||
from esphome.components.mipi_spi.display import CONFIG_SCHEMA as DISPLAY_SCHEMA
|
||||
from esphome.components.mipi_spi.light import (
|
||||
CONFIG_SCHEMA as LIGHT_SCHEMA,
|
||||
FINAL_VALIDATE_SCHEMA as LIGHT_FINAL_VALIDATE_SCHEMA,
|
||||
)
|
||||
from esphome.config import Config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_DISPLAY_ID,
|
||||
CONF_GAMMA_CORRECT,
|
||||
CONF_ID,
|
||||
CONF_MAX_BRIGHTNESS,
|
||||
CONF_MIN_BRIGHTNESS,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.types import ConfigType
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
DISPLAY_ID = "test_display"
|
||||
|
||||
_CUSTOM: ConfigType = {
|
||||
"model": "custom",
|
||||
"dimensions": {"width": 240, "height": 240},
|
||||
"init_sequence": [[0xA0, 0x01]],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _esp32(set_core_config: SetCoreConfigCallable) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
|
||||
|
||||
def _light(**extra: object) -> ConfigType:
|
||||
return {
|
||||
"name": "Display Brightness",
|
||||
CONF_DISPLAY_ID: DISPLAY_ID,
|
||||
**extra,
|
||||
}
|
||||
|
||||
|
||||
def _final_validate_with_display(display: ConfigType) -> None:
|
||||
"""Validate a display, place it in the full config, then final-validate a light on it."""
|
||||
display = DISPLAY_SCHEMA(
|
||||
{"id": DISPLAY_ID, "dc_pin": 18, **display},
|
||||
)
|
||||
full = Config()
|
||||
full["display"] = [display]
|
||||
full.declare_ids.append((display[CONF_ID], ["display", 0, CONF_ID]))
|
||||
final_validate.full_config.set(full)
|
||||
LIGHT_FINAL_VALIDATE_SCHEMA({CONF_DISPLAY_ID: ID(DISPLAY_ID, is_declaration=False)})
|
||||
|
||||
|
||||
def test_light_defaults() -> None:
|
||||
"""Gamma defaults to 1.0 and the range to the full 0-255."""
|
||||
config = LIGHT_SCHEMA(_light())
|
||||
|
||||
assert config[CONF_GAMMA_CORRECT] == 1.0
|
||||
assert config[CONF_MIN_BRIGHTNESS] == 0
|
||||
assert config[CONF_MAX_BRIGHTNESS] == 255
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("min_brightness", "max_brightness"),
|
||||
[(100, 100), (200, 100)],
|
||||
ids=["equal", "inverted"],
|
||||
)
|
||||
def test_light_rejects_bad_range(min_brightness: int, max_brightness: int) -> None:
|
||||
with pytest.raises(
|
||||
cv.Invalid, match="'min_brightness' must be less than 'max_brightness'"
|
||||
):
|
||||
LIGHT_SCHEMA(
|
||||
_light(min_brightness=min_brightness, max_brightness=max_brightness)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", [CONF_MIN_BRIGHTNESS, CONF_MAX_BRIGHTNESS])
|
||||
def test_light_rejects_out_of_range_value(key: str) -> None:
|
||||
with pytest.raises(cv.Invalid):
|
||||
LIGHT_SCHEMA(_light(**{key: 256}))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"display",
|
||||
[
|
||||
pytest.param({"model": "rm690b0"}, id="model_with_default_brightness"),
|
||||
pytest.param({**_CUSTOM, CONF_BRIGHTNESS: 0}, id="custom_with_brightness"),
|
||||
],
|
||||
)
|
||||
def test_light_accepts_display_with_brightness(display: ConfigType) -> None:
|
||||
_final_validate_with_display(display)
|
||||
|
||||
|
||||
def test_light_rejects_model_without_brightness() -> None:
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match="Display model ILI9488 does not support setting brightness",
|
||||
):
|
||||
_final_validate_with_display({"model": "ili9488"})
|
||||
|
||||
|
||||
def test_light_rejects_custom_without_brightness() -> None:
|
||||
with pytest.raises(
|
||||
cv.Invalid,
|
||||
match="The 'brightness' option must be set in the display config",
|
||||
):
|
||||
_final_validate_with_display(_CUSTOM)
|
||||
|
||||
|
||||
def test_brightness_not_allowed_on_model_without_default() -> None:
|
||||
with pytest.raises(cv.Invalid, match=r"extra keys not allowed.*brightness"):
|
||||
DISPLAY_SCHEMA(
|
||||
{"id": DISPLAY_ID, "dc_pin": 18, "model": "ili9488", CONF_BRIGHTNESS: 10}
|
||||
)
|
||||
Reference in New Issue
Block a user