diff --git a/esphome/components/mipi/__init__.py b/esphome/components/mipi/__init__.py index b9299bb8d7..f610f160b0 100644 --- a/esphome/components/mipi/__init__.py +++ b/esphome/components/mipi/__init__.py @@ -77,6 +77,7 @@ BRIGHTNESS = 0x51 WRDISBV = 0x51 RDDISBV = 0x52 WRCTRLD = 0x53 +WCE = 0x58 SWIRE1 = 0x5A SWIRE2 = 0x5B IFMODE = 0xB0 @@ -91,6 +92,7 @@ PWCTR2 = 0xC1 PWCTR3 = 0xC2 PWCTR4 = 0xC3 PWCTR5 = 0xC4 +SPIMODESEL = 0xC4 VMCTR1 = 0xC5 IFCTR = 0xC6 VMCTR2 = 0xC7 diff --git a/esphome/components/mipi_spi/models/amoled.py b/esphome/components/mipi_spi/models/amoled.py index 6fe882b584..bc95fc7f71 100644 --- a/esphome/components/mipi_spi/models/amoled.py +++ b/esphome/components/mipi_spi/models/amoled.py @@ -5,10 +5,13 @@ from esphome.components.mipi import ( PAGESEL, PIXFMT, SLPOUT, + SPIMODESEL, SWIRE1, SWIRE2, TEON, + WCE, WRAM, + WRCTRLD, DriverChip, delay, ) @@ -87,4 +90,19 @@ T4_S3_AMOLED = RM690B0.extend( bus_mode=TYPE_QUAD, ) +CO5300 = DriverChip( + "CO5300", + brightness=0xD0, + color_order=MODE_RGB, + bus_mode=TYPE_QUAD, + initsequence=( + (SLPOUT,), # Requires early SLPOUT + (PAGESEL, 0x00), + (SPIMODESEL, 0x80), + (WRCTRLD, 0x20), + (WCE, 0x00), + ), +) + + models = {} diff --git a/esphome/components/mipi_spi/models/waveshare.py b/esphome/components/mipi_spi/models/waveshare.py index 002f81f3a6..7a55027e58 100644 --- a/esphome/components/mipi_spi/models/waveshare.py +++ b/esphome/components/mipi_spi/models/waveshare.py @@ -1,6 +1,7 @@ from esphome.components.mipi import DriverChip import esphome.config_validation as cv +from .amoled import CO5300 from .ili import ILI9488_A DriverChip( @@ -140,3 +141,14 @@ ILI9488_A.extend( data_rate="20MHz", invert_colors=True, ) + +CO5300.extend( + "WAVESHARE-ESP32-S3-TOUCH-AMOLED-1.75", + width=466, + height=466, + pixel_mode="16bit", + offset_height=0, + offset_width=6, + cs_pin=12, + reset_pin=39, +) diff --git a/tests/integration/fixtures/host_mode_many_entities.yaml b/tests/integration/fixtures/host_mode_many_entities.yaml index 5e085a15c9..612186507c 100644 --- a/tests/integration/fixtures/host_mode_many_entities.yaml +++ b/tests/integration/fixtures/host_mode_many_entities.yaml @@ -373,3 +373,20 @@ button: name: "Test Button" on_press: - logger.log: "Button pressed" + +# Date, Time, and DateTime entities +datetime: + - platform: template + type: date + name: "Test Date" + initial_value: "2023-05-13" + optimistic: true + - platform: template + type: time + name: "Test Time" + initial_value: "12:30:00" + optimistic: true + - platform: template + type: datetime + name: "Test DateTime" + optimistic: true diff --git a/tests/integration/test_host_mode_many_entities.py b/tests/integration/test_host_mode_many_entities.py index aaca4555f6..fbe3dc25c8 100644 --- a/tests/integration/test_host_mode_many_entities.py +++ b/tests/integration/test_host_mode_many_entities.py @@ -4,7 +4,17 @@ from __future__ import annotations import asyncio -from aioesphomeapi import ClimateInfo, EntityState, SensorState +from aioesphomeapi import ( + ClimateInfo, + DateInfo, + DateState, + DateTimeInfo, + DateTimeState, + EntityState, + SensorState, + TimeInfo, + TimeState, +) import pytest from .types import APIClientConnectedFactory, RunCompiledFunction @@ -22,34 +32,56 @@ async def test_host_mode_many_entities( async with run_compiled(yaml_config), api_client_connected() as client: # Subscribe to state changes states: dict[int, EntityState] = {} - sensor_count_future: asyncio.Future[int] = loop.create_future() + minimum_states_future: asyncio.Future[None] = loop.create_future() def on_state(state: EntityState) -> None: states[state.key] = state - # Count sensor states specifically + # Check if we have received minimum expected states sensor_states = [ s for s in states.values() if isinstance(s, SensorState) and isinstance(s.state, float) ] - # When we have received states from at least 50 sensors, resolve the future - if len(sensor_states) >= 50 and not sensor_count_future.done(): - sensor_count_future.set_result(len(sensor_states)) + date_states = [s for s in states.values() if isinstance(s, DateState)] + time_states = [s for s in states.values() if isinstance(s, TimeState)] + datetime_states = [ + s for s in states.values() if isinstance(s, DateTimeState) + ] + + # We expect at least 50 sensors and 1 of each datetime entity type + if ( + len(sensor_states) >= 50 + and len(date_states) >= 1 + and len(time_states) >= 1 + and len(datetime_states) >= 1 + and not minimum_states_future.done() + ): + minimum_states_future.set_result(None) client.subscribe_states(on_state) - # Wait for states from at least 50 sensors with timeout + # Wait for minimum states with timeout try: - sensor_count = await asyncio.wait_for(sensor_count_future, timeout=10.0) + await asyncio.wait_for(minimum_states_future, timeout=10.0) except TimeoutError: sensor_states = [ s for s in states.values() if isinstance(s, SensorState) and isinstance(s.state, float) ] + date_states = [s for s in states.values() if isinstance(s, DateState)] + time_states = [s for s in states.values() if isinstance(s, TimeState)] + datetime_states = [ + s for s in states.values() if isinstance(s, DateTimeState) + ] + pytest.fail( - f"Did not receive states from at least 50 sensors within 10 seconds. " - f"Received {len(sensor_states)} sensor states out of {len(states)} total states" + f"Did not receive expected states within 10 seconds. " + f"Received: {len(sensor_states)} sensor states (expected >=50), " + f"{len(date_states)} date states (expected >=1), " + f"{len(time_states)} time states (expected >=1), " + f"{len(datetime_states)} datetime states (expected >=1). " + f"Total states: {len(states)}" ) # Verify we received a good number of entity states @@ -64,13 +96,25 @@ async def test_host_mode_many_entities( if isinstance(s, SensorState) and isinstance(s.state, float) ] - assert sensor_count >= 50, ( - f"Expected at least 50 sensor states, got {sensor_count}" - ) assert len(sensor_states) >= 50, ( f"Expected at least 50 sensor states, got {len(sensor_states)}" ) + # Verify we received datetime entity states + date_states = [s for s in states.values() if isinstance(s, DateState)] + time_states = [s for s in states.values() if isinstance(s, TimeState)] + datetime_states = [s for s in states.values() if isinstance(s, DateTimeState)] + + assert len(date_states) >= 1, ( + f"Expected at least 1 date state, got {len(date_states)}" + ) + assert len(time_states) >= 1, ( + f"Expected at least 1 time state, got {len(time_states)}" + ) + assert len(datetime_states) >= 1, ( + f"Expected at least 1 datetime state, got {len(datetime_states)}" + ) + # Get entity info to verify climate entity details entities = await client.list_entities_services() climate_infos = [e for e in entities[0] if isinstance(e, ClimateInfo)] @@ -89,3 +133,28 @@ async def test_host_mode_many_entities( assert "HOME" in preset_names, f"Expected 'HOME' preset, got {preset_names}" assert "AWAY" in preset_names, f"Expected 'AWAY' preset, got {preset_names}" assert "SLEEP" in preset_names, f"Expected 'SLEEP' preset, got {preset_names}" + + # Verify datetime entities exist + date_infos = [e for e in entities[0] if isinstance(e, DateInfo)] + time_infos = [e for e in entities[0] if isinstance(e, TimeInfo)] + datetime_infos = [e for e in entities[0] if isinstance(e, DateTimeInfo)] + + assert len(date_infos) >= 1, "Expected at least 1 date entity" + assert len(time_infos) >= 1, "Expected at least 1 time entity" + assert len(datetime_infos) >= 1, "Expected at least 1 datetime entity" + + # Verify the entity names + date_info = date_infos[0] + assert date_info.name == "Test Date", ( + f"Expected date entity name 'Test Date', got {date_info.name}" + ) + + time_info = time_infos[0] + assert time_info.name == "Test Time", ( + f"Expected time entity name 'Test Time', got {time_info.name}" + ) + + datetime_info = datetime_infos[0] + assert datetime_info.name == "Test DateTime", ( + f"Expected datetime entity name 'Test DateTime', got {datetime_info.name}" + )