[mipi_spi] Rotation and buffer size changes (#15047)

This commit is contained in:
Clyde Stubbs
2026-04-03 11:28:45 +10:00
committed by GitHub
parent 710186998b
commit af662da90d
18 changed files with 557 additions and 224 deletions
@@ -0,0 +1,81 @@
"""Tests for display component metadata functions."""
from unittest.mock import patch
from esphome.components.display import (
DisplayMetaData,
add_metadata,
get_all_display_metadata,
get_display_metadata,
)
from esphome.cpp_generator import MockObj
def test_add_metadata_with_string_id():
"""Test adding metadata with a plain string ID."""
with patch("esphome.components.display.CORE.data", {}):
add_metadata("my_display", 320, 240, True)
meta = get_display_metadata("my_display")
assert meta == DisplayMetaData(
width=320, height=240, has_writer=True, has_hardware_rotation=False
)
def test_add_metadata_with_mockobj_id():
"""Test adding metadata with a MockObj ID (converted via str())."""
with patch("esphome.components.display.CORE.data", {}):
mock_id = MockObj("my_display_obj")
add_metadata(mock_id, 480, 320, False, has_hardware_rotation=True)
meta = get_display_metadata("my_display_obj")
assert meta == DisplayMetaData(
width=480, height=320, has_writer=False, has_hardware_rotation=True
)
def test_add_metadata_hardware_rotation_default():
"""Test that has_hardware_rotation defaults to False."""
with patch("esphome.components.display.CORE.data", {}):
add_metadata("disp", 128, 64, False)
meta = get_display_metadata("disp")
assert meta.has_hardware_rotation is False
def test_get_display_metadata_missing_returns_none():
"""Test that querying a non-existent ID returns None."""
with patch("esphome.components.display.CORE.data", {}):
data = get_display_metadata("no_such_display")
assert data.width == 0
assert data.height == 0
assert data.has_writer is False
assert data.has_hardware_rotation is False
def test_add_multiple_displays():
"""Test adding metadata for multiple displays."""
with patch("esphome.components.display.CORE.data", {}):
add_metadata("disp_a", 320, 240, True)
add_metadata("disp_b", 128, 64, False, has_hardware_rotation=True)
all_meta = get_all_display_metadata()
assert len(all_meta) == 2
assert all_meta["disp_a"] == DisplayMetaData(320, 240, True, False)
assert all_meta["disp_b"] == DisplayMetaData(128, 64, False, True)
def test_add_metadata_overwrites_existing():
"""Test that adding metadata for the same ID overwrites the previous entry."""
with patch("esphome.components.display.CORE.data", {}):
add_metadata("disp", 320, 240, True)
add_metadata("disp", 640, 480, False, has_hardware_rotation=True)
meta = get_display_metadata("disp")
assert meta == DisplayMetaData(640, 480, False, True)
def test_metadata_is_frozen():
"""Test that DisplayMetaData instances are immutable (frozen dataclass)."""
meta = DisplayMetaData(320, 240, True, False)
try:
meta.width = 640
assert False, "Expected FrozenInstanceError"
except AttributeError:
pass
@@ -0,0 +1,200 @@
"""Tests for display metadata created by mipi_spi component."""
from collections.abc import Callable
from pathlib import Path
from esphome.components.display import (
DisplayMetaData,
get_all_display_metadata,
get_display_metadata,
)
from esphome.components.esp32 import (
KEY_BOARD,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32S3,
)
from esphome.components.mipi_spi.display import (
CONFIG_SCHEMA,
FINAL_VALIDATE_SCHEMA,
get_instance,
)
from esphome.const import PlatformFramework
from tests.component_tests.types import SetCoreConfigCallable
def validated_config(config):
"""Run schema + final validation and return the validated config."""
return FINAL_VALIDATE_SCHEMA(CONFIG_SCHEMA(config))
def test_metadata_native_quad_default_test_card(
set_core_config: SetCoreConfigCallable,
) -> None:
"""A quad-mode display with no explicit drawing gets a test card from final validation."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
)
config = validated_config({"model": "JC3636W518"})
get_instance(config)
meta = get_display_metadata(str(config["id"]))
assert meta is not None
assert meta.width == 360
assert meta.height == 360
# final validation auto-enables show_test_card when no drawing methods are configured
assert meta.has_writer is True
assert meta.has_hardware_rotation is True
def test_metadata_single_mode_with_dc_pin(
set_core_config: SetCoreConfigCallable,
) -> None:
"""A single-mode display with no explicit drawing gets a test card from final validation."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
)
config = validated_config(
{
"model": "ST7735",
"dc_pin": 18,
}
)
get_instance(config)
meta = get_display_metadata(str(config["id"]))
assert meta is not None
assert meta.width == 128
assert meta.height == 160
assert meta.has_writer is True
assert meta.has_hardware_rotation is True
def test_metadata_custom_dimensions(
set_core_config: SetCoreConfigCallable,
) -> None:
"""A custom model picks up explicit dimensions."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
)
config = validated_config(
{
"model": "custom",
"dc_pin": 18,
"dimensions": {"width": 480, "height": 320},
"init_sequence": [[0xA0, 0x01]],
}
)
get_instance(config)
meta = get_display_metadata(str(config["id"]))
assert meta is not None
assert meta.width == 480
assert meta.height == 320
# final validation auto-enables show_test_card
assert meta.has_writer is True
assert meta.has_hardware_rotation is True
def test_metadata_with_test_card_has_writer(
set_core_config: SetCoreConfigCallable,
) -> None:
"""When show_test_card is enabled, has_writer should be True."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
)
config = validated_config(
{
"model": "custom",
"dc_pin": 18,
"dimensions": {"width": 240, "height": 240},
"init_sequence": [[0xA0, 0x01]],
"show_test_card": True,
}
)
get_instance(config)
meta = get_display_metadata(str(config["id"]))
assert meta is not None
assert meta.has_writer is True
def test_metadata_no_swap_xy_not_full_hardware_rotation(
set_core_config: SetCoreConfigCallable,
) -> None:
"""A model that disables swap_xy should report has_hardware_rotation=False."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32-s3-devkitc-1", KEY_VARIANT: VARIANT_ESP32S3},
)
# JC3248W535 has swap_xy=cv.UNDEFINED -> transforms={mirror_x, mirror_y} only
config = validated_config({"model": "JC3248W535"})
get_instance(config)
meta = get_display_metadata(str(config["id"]))
assert meta is not None
assert meta.has_hardware_rotation is False
def test_metadata_multiple_displays_independent(
set_core_config: SetCoreConfigCallable,
) -> None:
"""Multiple displays each get their own metadata entry."""
set_core_config(
PlatformFramework.ESP32_IDF,
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
)
config_a = validated_config(
{
"id": "disp_a",
"model": "custom",
"dc_pin": 18,
"dimensions": {"width": 320, "height": 240},
"init_sequence": [[0xA0, 0x01]],
}
)
config_b = validated_config(
{
"id": "disp_b",
"model": "custom",
"dc_pin": 19,
"dimensions": {"width": 128, "height": 64},
"init_sequence": [[0xA0, 0x01]],
}
)
get_instance(config_a)
get_instance(config_b)
all_meta = get_all_display_metadata()
# final validation auto-enables show_test_card for both
assert all_meta["disp_a"] == DisplayMetaData(320, 240, True, True)
assert all_meta["disp_b"] == DisplayMetaData(128, 64, True, True)
def test_metadata_via_code_generation_native(
generate_main: Callable[[str | Path], str],
component_fixture_path: Callable[[str], Path],
) -> None:
"""Full code generation for native.yaml should produce correct metadata."""
generate_main(component_fixture_path("native.yaml"))
all_meta = get_all_display_metadata()
# native.yaml: model JC3636W518 -> 360x360, no writer, full hardware rotation
assert len(all_meta) == 1
meta = next(iter(all_meta.values()))
assert meta == DisplayMetaData(
width=360, height=360, has_writer=True, has_hardware_rotation=True
)
def test_metadata_via_code_generation_lvgl(
generate_main: Callable[[str | Path], str],
component_fixture_path: Callable[[str], Path],
) -> None:
"""Full code generation for lvgl.yaml should produce correct metadata."""
generate_main(component_fixture_path("lvgl.yaml"))
all_meta = get_all_display_metadata()
# lvgl.yaml: model ST7735 -> 128x160, no writer (lvgl draws directly), full hw rotation
assert len(all_meta) == 1
meta = next(iter(all_meta.values()))
assert meta == DisplayMetaData(
width=128, height=160, has_writer=False, has_hardware_rotation=True
)
+2 -7
View File
@@ -204,11 +204,6 @@ def test_transform_and_init_sequence_errors(
r"extra keys not allowed @ data\['brightness'\]",
id="brightness_not_supported",
),
pytest.param(
{"model": "T-DISPLAY-S3-PRO"},
"PSRAM is required for this display",
id="psram_required",
),
],
)
def test_esp32s3_specific_errors(
@@ -319,7 +314,7 @@ def test_native_generation(
main_cpp = generate_main(component_fixture_path("native.yaml"))
assert (
"mipi_spi::MipiSpiBuffer<uint16_t, mipi_spi::PIXEL_MODE_16, true, mipi_spi::PIXEL_MODE_16, mipi_spi::BUS_TYPE_QUAD, 360, 360, 0, 1, display::DISPLAY_ROTATION_0_DEGREES, 1, 1>()"
"mipi_spi::MipiSpiBuffer<uint16_t, mipi_spi::PIXEL_MODE_16, true, mipi_spi::PIXEL_MODE_16, mipi_spi::BUS_TYPE_QUAD, 360, 360, 0, 1, 0, true, 1, 1>()"
in main_cpp
)
assert "set_init_sequence({240, 1, 8, 242" in main_cpp
@@ -335,7 +330,7 @@ def test_lvgl_generation(
main_cpp = generate_main(component_fixture_path("lvgl.yaml"))
assert (
"mipi_spi::MipiSpi<uint16_t, mipi_spi::PIXEL_MODE_16, true, mipi_spi::PIXEL_MODE_16, mipi_spi::BUS_TYPE_SINGLE, 128, 160, 0, 0>();"
"mipi_spi::MipiSpi<uint16_t, mipi_spi::PIXEL_MODE_16, true, mipi_spi::PIXEL_MODE_16, mipi_spi::BUS_TYPE_SINGLE, 128, 160, 0, 0, 0, true>();"
in main_cpp
)
assert "set_init_sequence({1, 0, 10, 255, 177" in main_cpp