mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 11:07:33 +00:00
[epaper_spi] Metadata, bug fixes, new model (#16950)
This commit is contained in:
@@ -13,6 +13,7 @@ from esphome.components.mipi import (
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import update_interval
|
||||
from esphome.const import (
|
||||
CONF_AUTO_CLEAR_ENABLED,
|
||||
CONF_BUSY_PIN,
|
||||
CONF_CS_PIN,
|
||||
CONF_DATA_RATE,
|
||||
@@ -129,7 +130,23 @@ def customise_schema(config):
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
return model_schema(config)(config)
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
config = model_schema(config)(config)
|
||||
width, height = model.get_dimensions(config)
|
||||
display.add_metadata(
|
||||
config[CONF_ID],
|
||||
width,
|
||||
height,
|
||||
has_hardware_rotation=True,
|
||||
byte_order=cv.UNDEFINED,
|
||||
has_writer=config.get(CONF_AUTO_CLEAR_ENABLED) is True
|
||||
or config.get(CONF_PAGES) is not None
|
||||
or config.get(CONF_LAMBDA) is not None
|
||||
or config.get(CONF_SHOW_TEST_CARD) is True,
|
||||
rotation=config.get(CONF_ROTATION, 0),
|
||||
draw_rounding=0,
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = customise_schema
|
||||
@@ -197,6 +214,9 @@ async def to_code(config):
|
||||
if busy_pin := config.get(CONF_BUSY_PIN):
|
||||
busy = await cg.gpio_pin_expression(busy_pin)
|
||||
cg.add(var.set_busy_pin(busy))
|
||||
if enable_pin := config.get(CONF_ENABLE_PIN):
|
||||
enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
|
||||
cg.add(var.set_enable_pins(enable))
|
||||
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||
if CONF_RESET_DURATION in config:
|
||||
cg.add(var.set_reset_duration(config[CONF_RESET_DURATION]))
|
||||
|
||||
@@ -38,6 +38,10 @@ bool EPaperBase::init_buffer_(size_t buffer_length) {
|
||||
}
|
||||
|
||||
void EPaperBase::setup_pins_() const {
|
||||
for (auto *pin : this->enable_pins_) {
|
||||
pin->setup();
|
||||
pin->digital_write(true);
|
||||
}
|
||||
this->dc_pin_->setup(); // OUTPUT
|
||||
this->dc_pin_->digital_write(false);
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class EPaperBase : public Display,
|
||||
float get_setup_priority() const override;
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
|
||||
void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
|
||||
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
|
||||
void set_transform(uint8_t transform) {
|
||||
this->transform_ = transform;
|
||||
@@ -177,6 +178,7 @@ class EPaperBase : public Display,
|
||||
GPIOPin *dc_pin_{};
|
||||
GPIOPin *busy_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
std::vector<GPIOPin *> enable_pins_{};
|
||||
bool waiting_for_idle_{};
|
||||
uint32_t delay_until_{}; // timestamp until which to delay processing
|
||||
uint16_t next_delay_{}; // milliseconds to delay before next state
|
||||
|
||||
@@ -10,11 +10,11 @@ class SSD1677(EpaperModel):
|
||||
|
||||
# fmt: off
|
||||
def get_init_sequence(self, config: dict):
|
||||
width, _height = self.get_dimensions(config)
|
||||
_width, height = self.get_dimensions(config)
|
||||
return (
|
||||
(0x18, 0x80), # Select internal Temp sensor
|
||||
(0x0C, 0xAE, 0xC7, 0xC3, 0xC0, 0x80), # inrush current level 2
|
||||
(0x01, (width - 1) % 256, (width - 1) // 256, 0x02), # Set column gate limit
|
||||
(0x01, (height - 1) % 256, (height - 1) // 256, 0x02), # Set gate limit (number of rows-1)
|
||||
(0x3C, 0x01), # Set border waveform
|
||||
(0x11, 3), # Set transform
|
||||
)
|
||||
@@ -51,3 +51,16 @@ ssd1677.extend(
|
||||
height=480,
|
||||
mirror_x=True,
|
||||
)
|
||||
|
||||
ssd1677.extend(
|
||||
"seeed-reterminal-sticky",
|
||||
width=800,
|
||||
height=480,
|
||||
mirror_x=True,
|
||||
enable_pin=47,
|
||||
cs_pin=15,
|
||||
dc_pin=16,
|
||||
reset_pin=17,
|
||||
busy_pin=18,
|
||||
data_rate="10MHz",
|
||||
)
|
||||
|
||||
@@ -104,6 +104,44 @@ def set_component_config() -> Callable[[str, Any], None]:
|
||||
return setter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def choose_variant_with_pins() -> Generator[Callable[[list], None]]:
|
||||
"""Set the ESP32 variant to the first one on which all the given pins are valid.
|
||||
|
||||
For ESP32 only, since the other platforms do not have variants. The core
|
||||
configuration must already have been set up for an ESP32 target.
|
||||
Using local imports to avoid importing when ESP32 is not the target.
|
||||
"""
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANTS
|
||||
from esphome.components.esp32.gpio import validate_gpio_pin
|
||||
from esphome.const import CONF_INPUT, CONF_OUTPUT
|
||||
from esphome.pins import gpio_pin_schema
|
||||
|
||||
def chooser(pins: list) -> None:
|
||||
for variant in VARIANTS:
|
||||
try:
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
|
||||
for pin in pins:
|
||||
if pin is not None:
|
||||
pin = gpio_pin_schema(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: True,
|
||||
},
|
||||
internal=True,
|
||||
)(pin)
|
||||
validate_gpio_pin(pin)
|
||||
return
|
||||
except cv.Invalid:
|
||||
continue
|
||||
raise cv.Invalid(
|
||||
f"No compatible variant found for pins: {', '.join(map(str, pins))}"
|
||||
)
|
||||
|
||||
yield chooser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def component_fixture_path(request: pytest.FixtureRequest) -> Callable[[str], Path]:
|
||||
"""Return a function to get absolute paths relative to the component's fixtures directory."""
|
||||
|
||||
24
tests/component_tests/epaper_spi/config/enable_pin_test.yaml
Normal file
24
tests/component_tests/epaper_spi/config/enable_pin_test.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
spi:
|
||||
clk_pin: GPIO18
|
||||
mosi_pin: GPIO19
|
||||
|
||||
display:
|
||||
- platform: epaper_spi
|
||||
id: epaper_display
|
||||
model: ssd1677
|
||||
dc_pin: GPIO21
|
||||
busy_pin: GPIO22
|
||||
reset_pin: GPIO23
|
||||
cs_pin: GPIO5
|
||||
enable_pin:
|
||||
- GPIO25
|
||||
- GPIO26
|
||||
dimensions:
|
||||
width: 200
|
||||
height: 200
|
||||
156
tests/component_tests/epaper_spi/test_display_metadata.py
Normal file
156
tests/component_tests/epaper_spi/test_display_metadata.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""Tests for display metadata created by the epaper_spi component."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.display import get_all_display_metadata, get_display_metadata
|
||||
from esphome.components.epaper_spi.display import CONFIG_SCHEMA
|
||||
from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32
|
||||
from esphome.const import PlatformFramework
|
||||
from esphome.types import ConfigType
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
def _base_config(**overrides: Any) -> ConfigType:
|
||||
"""Build a minimal valid ssd1677 config, allowing field overrides."""
|
||||
config: ConfigType = {
|
||||
"id": "test_display",
|
||||
"model": "ssd1677",
|
||||
"dc_pin": 21,
|
||||
"busy_pin": 22,
|
||||
"reset_pin": 23,
|
||||
"cs_pin": 5,
|
||||
"dimensions": {"width": 200, "height": 300},
|
||||
}
|
||||
config.update(overrides)
|
||||
return config
|
||||
|
||||
|
||||
def test_metadata_dimensions_and_defaults(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""Metadata picks up explicit dimensions and epaper_spi defaults."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
config = CONFIG_SCHEMA(_base_config())
|
||||
meta = get_display_metadata(config["id"])
|
||||
|
||||
assert meta is not None
|
||||
assert meta.width == 200
|
||||
assert meta.height == 300
|
||||
# epaper_spi always reports full hardware rotation
|
||||
assert meta.has_hardware_rotation is True
|
||||
# epaper_spi does not declare a byte order
|
||||
assert meta.byte_order is cv.UNDEFINED
|
||||
assert meta.draw_rounding == 0
|
||||
# no drawing methods configured -> no writer
|
||||
assert meta.has_writer is False
|
||||
|
||||
|
||||
def test_metadata_default_dimensions_from_model(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""A model with built-in dimensions reports those without explicit dimensions."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
# waveshare-4.26in is an ssd1677 derivative with default 800x480 dimensions
|
||||
config = CONFIG_SCHEMA(
|
||||
{
|
||||
"id": "wave_display",
|
||||
"model": "waveshare-4.26in",
|
||||
"dc_pin": 21,
|
||||
"busy_pin": 22,
|
||||
"reset_pin": 23,
|
||||
"cs_pin": 5,
|
||||
}
|
||||
)
|
||||
meta = get_display_metadata(config["id"])
|
||||
|
||||
assert meta is not None
|
||||
assert meta.width == 800
|
||||
assert meta.height == 480
|
||||
|
||||
|
||||
def test_metadata_has_writer_with_auto_clear(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""A display with auto_clear_enabled reports has_writer=True."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
config = CONFIG_SCHEMA(_base_config(auto_clear_enabled=True))
|
||||
meta = get_display_metadata(config["id"])
|
||||
|
||||
assert meta is not None
|
||||
assert meta.has_writer is True
|
||||
|
||||
|
||||
def test_metadata_rotation_propagated(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""The configured rotation is stored in the metadata."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
config = CONFIG_SCHEMA(_base_config(rotation=90))
|
||||
meta = get_display_metadata(config["id"])
|
||||
|
||||
assert meta is not None
|
||||
assert meta.rotation == 90
|
||||
|
||||
|
||||
def test_metadata_multiple_displays_independent(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""Each display gets its own independent metadata entry."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
CONFIG_SCHEMA(_base_config(id="disp_a", dimensions={"width": 200, "height": 300}))
|
||||
CONFIG_SCHEMA(_base_config(id="disp_b", dimensions={"width": 400, "height": 480}))
|
||||
|
||||
all_meta = get_all_display_metadata()
|
||||
assert all_meta["disp_a"].width == 200
|
||||
assert all_meta["disp_a"].height == 300
|
||||
assert all_meta["disp_b"].width == 400
|
||||
assert all_meta["disp_b"].height == 480
|
||||
|
||||
|
||||
def test_metadata_via_code_generation(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Full code generation registers metadata for the configured display."""
|
||||
generate_main(component_config_path("enable_pin_test.yaml"))
|
||||
|
||||
all_meta = get_all_display_metadata()
|
||||
assert len(all_meta) == 1
|
||||
meta = next(iter(all_meta.values()))
|
||||
# enable_pin_test.yaml: ssd1677 at 200x200
|
||||
assert meta.width == 200
|
||||
assert meta.height == 200
|
||||
assert meta.has_hardware_rotation is True
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Tests for epaper_spi configuration validation."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -11,17 +13,13 @@ from esphome.components.epaper_spi.display import (
|
||||
FINAL_VALIDATE_SCHEMA,
|
||||
MODELS,
|
||||
)
|
||||
from esphome.components.esp32 import (
|
||||
KEY_BOARD,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.esp32 import KEY_BOARD, KEY_VARIANT, VARIANT_ESP32
|
||||
from esphome.const import (
|
||||
CONF_BUSY_PIN,
|
||||
CONF_CS_PIN,
|
||||
CONF_DC_PIN,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_HEIGHT,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_RESET_PIN,
|
||||
@@ -31,6 +29,30 @@ from esphome.const import (
|
||||
from esphome.types import ConfigType
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
# Pin options whose values must be valid on the chosen ESP32 variant.
|
||||
_PIN_CONF_KEYS = (
|
||||
CONF_CS_PIN,
|
||||
CONF_DC_PIN,
|
||||
CONF_RESET_PIN,
|
||||
CONF_BUSY_PIN,
|
||||
CONF_ENABLE_PIN,
|
||||
)
|
||||
|
||||
|
||||
def _pins_for(model: Any, config: ConfigType) -> list:
|
||||
"""Collect every GPIO the config will actually use (model defaults or injected)."""
|
||||
pins: list = []
|
||||
for key in _PIN_CONF_KEYS:
|
||||
# An injected value in the config takes precedence over the model default.
|
||||
value = config[key] if key in config else model.get_default(key)
|
||||
if not value: # get_default returns False for pins the model omits
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
pins.extend(value)
|
||||
else:
|
||||
pins.append(value)
|
||||
return pins
|
||||
|
||||
|
||||
def run_schema_validation(
|
||||
config: ConfigType, with_final_validate: bool = False
|
||||
@@ -90,29 +112,20 @@ def test_basic_configuration_errors(
|
||||
def test_all_predefined_models(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
choose_variant_with_pins: Callable[[list], None],
|
||||
) -> None:
|
||||
"""Test all predefined epaper models validate successfully with appropriate defaults."""
|
||||
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
|
||||
# Configure SPI component which is required by epaper_spi
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
# Test all models, providing default values where necessary
|
||||
for name, model in MODELS.items():
|
||||
# SEEED models are designed for ESP32-S3 hardware
|
||||
if name in ("SEEED-EE04-MONO-4.26", "SEEED-RETERMINAL-E1002"):
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={
|
||||
KEY_BOARD: "esp32-s3-devkitc-1",
|
||||
KEY_VARIANT: VARIANT_ESP32S3,
|
||||
},
|
||||
)
|
||||
else:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
|
||||
# Configure SPI component which is required by epaper_spi
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
config = {"model": name}
|
||||
|
||||
# Add ID field
|
||||
@@ -141,6 +154,10 @@ def test_all_predefined_models(
|
||||
if not model.get_default(CONF_CS_PIN):
|
||||
config[CONF_CS_PIN] = 5
|
||||
|
||||
# Select an ESP32 variant on which all of this model's pins are valid
|
||||
# (some models default to high-numbered pins only present on the S3).
|
||||
choose_variant_with_pins(_pins_for(model, config))
|
||||
|
||||
run_schema_validation(config)
|
||||
|
||||
|
||||
@@ -152,27 +169,19 @@ def test_individual_models(
|
||||
model_name: str,
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
choose_variant_with_pins: Callable[[list], None],
|
||||
) -> None:
|
||||
"""Test each epaper model individually to ensure it validates correctly."""
|
||||
# SEEED models are designed for ESP32-S3 hardware
|
||||
if model_name in ("SEEED-EE04-MONO-4.26", "SEEED-RETERMINAL-E1002"):
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={
|
||||
KEY_BOARD: "esp32-s3-devkitc-1",
|
||||
KEY_VARIANT: VARIANT_ESP32S3,
|
||||
},
|
||||
)
|
||||
else:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
model = MODELS[model_name]
|
||||
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
|
||||
# Configure SPI component which is required by epaper_spi
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
model = MODELS[model_name]
|
||||
config: dict[str, Any] = {"model": model_name, "id": "test_display"}
|
||||
|
||||
# Add required fields based on model defaults
|
||||
@@ -195,6 +204,10 @@ def test_individual_models(
|
||||
if not model.get_default(CONF_CS_PIN):
|
||||
config[CONF_CS_PIN] = 5
|
||||
|
||||
# Select an ESP32 variant on which all of this model's pins are valid
|
||||
# (some models default to high-numbered pins only present on the S3).
|
||||
choose_variant_with_pins(_pins_for(model, config))
|
||||
|
||||
# This should not raise any exceptions
|
||||
run_schema_validation(config)
|
||||
|
||||
@@ -342,3 +355,102 @@ def test_busy_pin_input_mode_ssd1677(
|
||||
reset_pin_config = result[CONF_RESET_PIN]
|
||||
assert "mode" in reset_pin_config
|
||||
assert reset_pin_config["mode"]["output"] is True
|
||||
|
||||
|
||||
def test_enable_pin_single(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""Test that a single enable_pin is accepted and normalised to a list of output pins."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
|
||||
# Configure SPI component which is required by epaper_spi
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
result = run_schema_validation(
|
||||
{
|
||||
"id": "test_display",
|
||||
"model": "ssd1677",
|
||||
"dc_pin": 21,
|
||||
"busy_pin": 22,
|
||||
"reset_pin": 23,
|
||||
"cs_pin": 5,
|
||||
"enable_pin": 25,
|
||||
"dimensions": {
|
||||
"width": 200,
|
||||
"height": 200,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# A single pin is normalised to a list by cv.ensure_list
|
||||
assert CONF_ENABLE_PIN in result
|
||||
enable_pins = result[CONF_ENABLE_PIN]
|
||||
assert isinstance(enable_pins, list)
|
||||
assert len(enable_pins) == 1
|
||||
# enable pins are configured as outputs
|
||||
assert enable_pins[0]["mode"]["output"] is True
|
||||
|
||||
|
||||
def test_enable_pin_multiple(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
set_component_config: Callable[[str, Any], None],
|
||||
) -> None:
|
||||
"""Test that a list of enable_pins is accepted."""
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32},
|
||||
)
|
||||
|
||||
# Configure SPI component which is required by epaper_spi
|
||||
set_component_config("spi", {"id": "spi_bus", "clk_pin": 18, "mosi_pin": 19})
|
||||
|
||||
result = run_schema_validation(
|
||||
{
|
||||
"id": "test_display",
|
||||
"model": "ssd1677",
|
||||
"dc_pin": 21,
|
||||
"busy_pin": 22,
|
||||
"reset_pin": 23,
|
||||
"cs_pin": 5,
|
||||
"enable_pin": [25, 26],
|
||||
"dimensions": {
|
||||
"width": 200,
|
||||
"height": 200,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
assert CONF_ENABLE_PIN in result
|
||||
enable_pins = result[CONF_ENABLE_PIN]
|
||||
assert isinstance(enable_pins, list)
|
||||
assert len(enable_pins) == 2
|
||||
assert all(pin["mode"]["output"] is True for pin in enable_pins)
|
||||
|
||||
|
||||
def test_enable_pin_code_generation(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Test that enable_pins are wired up in the generated C++ code."""
|
||||
main_cpp = generate_main(component_config_path("enable_pin_test.yaml"))
|
||||
|
||||
# Derive the auto-generated pin variable names from the set_pin() lines
|
||||
# rather than hard-coding them, so the test does not break when unrelated
|
||||
# codegen details shift the generated IDs.
|
||||
def pin_var_for(gpio_num: int) -> str:
|
||||
match = re.search(rf"(\w+)->set_pin\(::GPIO_NUM_{gpio_num}\);", main_cpp)
|
||||
assert match is not None, (
|
||||
f"GPIO_NUM_{gpio_num} pin not set up in generated code"
|
||||
)
|
||||
return match.group(1)
|
||||
|
||||
pin_25 = pin_var_for(25)
|
||||
pin_26 = pin_var_for(26)
|
||||
|
||||
# Both pin objects must be passed to the display via set_enable_pins() as a
|
||||
# std::vector initializer list, in the configured order.
|
||||
assert f"set_enable_pins({{{pin_25}, {pin_26}}});" in main_cpp
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
"""Tests for mpip_spi configuration validation."""
|
||||
|
||||
from collections.abc import Callable, Generator
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANTS
|
||||
from esphome.components.esp32.gpio import validate_gpio_pin
|
||||
from esphome.const import CONF_INPUT, CONF_OUTPUT
|
||||
from esphome.core import CORE
|
||||
from esphome.pins import gpio_pin_schema
|
||||
# choose_variant_with_pins is provided by the shared parent conftest.
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -21,34 +15,3 @@ def mock_spi_final_validate():
|
||||
return_value=lambda config: None,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def choose_variant_with_pins() -> Generator[Callable[[list], None]]:
|
||||
"""
|
||||
Set the ESP32 variant for the given model based on pins. For ESP32 only since the other platforms
|
||||
do not have variants.
|
||||
"""
|
||||
|
||||
def chooser(pins: list) -> None:
|
||||
for variant in VARIANTS:
|
||||
try:
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
|
||||
for pin in pins:
|
||||
if pin is not None:
|
||||
pin = gpio_pin_schema(
|
||||
{
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: True,
|
||||
},
|
||||
internal=True,
|
||||
)(pin)
|
||||
validate_gpio_pin(pin)
|
||||
return
|
||||
except cv.Invalid:
|
||||
continue
|
||||
raise cv.Invalid(
|
||||
f"No compatible variant found for pins: {', '.join(map(str, pins))}"
|
||||
)
|
||||
|
||||
yield chooser
|
||||
|
||||
Reference in New Issue
Block a user