mirror of
https://github.com/esphome/esphome.git
synced 2026-05-30 07:16:11 +08:00
[mipi_spi] Rotation and buffer size changes (#15047)
This commit is contained in:
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user