diff --git a/tests/integration/fixtures/host_mode_batch_delay.yaml b/tests/integration/fixtures/host_mode_batch_delay.yaml new file mode 100644 index 0000000000..9da7d37b5c --- /dev/null +++ b/tests/integration/fixtures/host_mode_batch_delay.yaml @@ -0,0 +1,55 @@ +esphome: + name: host-batch-delay-test +host: +api: + batch_delay: 0ms +logger: + +# Add multiple sensors to test batching +sensor: + - platform: template + name: "Test Sensor 1" + id: test_sensor1 + lambda: |- + return 1.0; + update_interval: 0.1s + - platform: template + name: "Test Sensor 2" + id: test_sensor2 + lambda: |- + return 2.0; + update_interval: 0.1s + - platform: template + name: "Test Sensor 3" + id: test_sensor3 + lambda: |- + return 3.0; + update_interval: 0.1s + +binary_sensor: + - platform: template + name: "Test Binary Sensor 1" + id: test_binary_sensor1 + lambda: |- + return true; + - platform: template + name: "Test Binary Sensor 2" + id: test_binary_sensor2 + lambda: |- + return false; + +switch: + - platform: template + name: "Test Switch 1" + id: test_switch1 + turn_on_action: + - logger.log: "Switch 1 turned on" + turn_off_action: + - logger.log: "Switch 1 turned off" + - platform: template + name: "Test Switch 2" + id: test_switch2 + turn_on_action: + - logger.log: "Switch 2 turned on" + turn_off_action: + - logger.log: "Switch 2 turned off" diff --git a/tests/integration/fixtures/host_mode_many_entities.yaml b/tests/integration/fixtures/host_mode_many_entities.yaml index df7631d1d6..cfc0311971 100644 --- a/tests/integration/fixtures/host_mode_many_entities.yaml +++ b/tests/integration/fixtures/host_mode_many_entities.yaml @@ -13,203 +13,203 @@ sensor: - platform: template name: "Test Sensor 1" lambda: return 1.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 2" lambda: return 2.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 3" lambda: return 3.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 4" lambda: return 4.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 5" lambda: return 5.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 6" lambda: return 6.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 7" lambda: return 7.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 8" lambda: return 8.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 9" lambda: return 9.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 10" lambda: return 10.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 11" lambda: return 11.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 12" lambda: return 12.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 13" lambda: return 13.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 14" lambda: return 14.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 15" lambda: return 15.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 16" lambda: return 16.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 17" lambda: return 17.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 18" lambda: return 18.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 19" lambda: return 19.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 20" lambda: return 20.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 21" lambda: return 21.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 22" lambda: return 22.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 23" lambda: return 23.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 24" lambda: return 24.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 25" lambda: return 25.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 26" lambda: return 26.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 27" lambda: return 27.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 28" lambda: return 28.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 29" lambda: return 29.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 30" lambda: return 30.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 31" lambda: return 31.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 32" lambda: return 32.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 33" lambda: return 33.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 34" lambda: return 34.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 35" lambda: return 35.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 36" lambda: return 36.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 37" lambda: return 37.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 38" lambda: return 38.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 39" lambda: return 39.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 40" lambda: return 40.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 41" lambda: return 41.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 42" lambda: return 42.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 43" lambda: return 43.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 44" lambda: return 44.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 45" lambda: return 45.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 46" lambda: return 46.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 47" lambda: return 47.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 48" lambda: return 48.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 49" lambda: return 49.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 50" lambda: return 50.0; - update_interval: 2s + update_interval: 0.1s # Mixed entity types for comprehensive batching test binary_sensor: diff --git a/tests/integration/fixtures/host_mode_many_entities_multiple_connections.yaml b/tests/integration/fixtures/host_mode_many_entities_multiple_connections.yaml index 3f6f0afaaa..5f34c17142 100644 --- a/tests/integration/fixtures/host_mode_many_entities_multiple_connections.yaml +++ b/tests/integration/fixtures/host_mode_many_entities_multiple_connections.yaml @@ -13,83 +13,83 @@ sensor: - platform: template name: "Test Sensor 1" lambda: return 1.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 2" lambda: return 2.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 3" lambda: return 3.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 4" lambda: return 4.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 5" lambda: return 5.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 6" lambda: return 6.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 7" lambda: return 7.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 8" lambda: return 8.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 9" lambda: return 9.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 10" lambda: return 10.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 11" lambda: return 11.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 12" lambda: return 12.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 13" lambda: return 13.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 14" lambda: return 14.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 15" lambda: return 15.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 16" lambda: return 16.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 17" lambda: return 17.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 18" lambda: return 18.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 19" lambda: return 19.0; - update_interval: 2s + update_interval: 0.1s - platform: template name: "Test Sensor 20" lambda: return 20.0; - update_interval: 2s + update_interval: 0.1s # Mixed entity types for comprehensive batching test binary_sensor: diff --git a/tests/integration/test_host_mode_batch_delay.py b/tests/integration/test_host_mode_batch_delay.py new file mode 100644 index 0000000000..5165b90e47 --- /dev/null +++ b/tests/integration/test_host_mode_batch_delay.py @@ -0,0 +1,80 @@ +"""Integration test for API batch_delay setting.""" + +from __future__ import annotations + +import asyncio +import time + +from aioesphomeapi import EntityState +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_host_mode_batch_delay( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test API with batch_delay set to 0ms - messages should be sent immediately without batching.""" + # Write, compile and run the ESPHome device, then connect to API + loop = asyncio.get_running_loop() + async with run_compiled(yaml_config), api_client_connected() as client: + # Verify we can get device info + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "host-batch-delay-test" + + # Subscribe to state changes + states: dict[int, EntityState] = {} + state_timestamps: dict[int, float] = {} + entity_count_future: asyncio.Future[int] = loop.create_future() + + def on_state(state: EntityState) -> None: + """Track when states are received.""" + states[state.key] = state + state_timestamps[state.key] = time.monotonic() + # When we have received all expected entities, resolve the future + if len(states) >= 7 and not entity_count_future.done(): + entity_count_future.set_result(len(states)) + + client.subscribe_states(on_state) + + # Wait for states from all entities with timeout + try: + entity_count = await asyncio.wait_for(entity_count_future, timeout=5.0) + except asyncio.TimeoutError: + pytest.fail( + f"Did not receive states from at least 7 entities within 5 seconds. " + f"Received {len(states)} states" + ) + + # Verify we received all states + assert entity_count >= 7, f"Expected at least 7 entities, got {entity_count}" + assert len(states) >= 7 # 3 sensors + 2 binary sensors + 2 switches + + # When batch_delay is 0ms, states are sent immediately without batching + # This means each state arrives in its own packet, which may actually be slower + # than batching due to network overhead + if state_timestamps: + first_timestamp = min(state_timestamps.values()) + last_timestamp = max(state_timestamps.values()) + time_spread = last_timestamp - first_timestamp + + # With batch_delay=0ms, states arrive individually which may take longer + # We just verify they all arrive within a reasonable time + assert time_spread < 1.0, f"States took {time_spread:.3f}s to arrive" + + # Also test list_entities - with batch_delay=0ms each entity is sent separately + start_time = time.monotonic() + entity_info, services = await client.list_entities_services() + end_time = time.monotonic() + + list_time = end_time - start_time + + # Verify we got all expected entities + assert len(entity_info) >= 7 # 3 sensors + 2 binary sensors + 2 switches + + # With batch_delay=0ms, listing sends each entity separately which may be slower + assert list_time < 1.0, f"list_entities took {list_time:.3f}s" diff --git a/tests/integration/test_host_mode_many_entities.py b/tests/integration/test_host_mode_many_entities.py index 727faa5e17..6481357f68 100644 --- a/tests/integration/test_host_mode_many_entities.py +++ b/tests/integration/test_host_mode_many_entities.py @@ -18,10 +18,11 @@ async def test_host_mode_many_entities( ) -> None: """Test API batching with many entities of different types.""" # Write, compile and run the ESPHome device, then connect to API + loop = asyncio.get_running_loop() async with run_compiled(yaml_config), api_client_connected() as client: # Subscribe to state changes states: dict[int, EntityState] = {} - entity_count_future: asyncio.Future[int] = asyncio.Future() + entity_count_future: asyncio.Future[int] = loop.create_future() def on_state(state: EntityState) -> None: states[state.key] = state diff --git a/tests/integration/test_host_mode_many_entities_multiple_connections.py b/tests/integration/test_host_mode_many_entities_multiple_connections.py index e32f85b410..a4e5f8a45c 100644 --- a/tests/integration/test_host_mode_many_entities_multiple_connections.py +++ b/tests/integration/test_host_mode_many_entities_multiple_connections.py @@ -18,6 +18,7 @@ async def test_host_mode_many_entities_multiple_connections( ) -> None: """Test shared buffer optimization with multiple API connections.""" # Write, compile and run the ESPHome device + loop = asyncio.get_running_loop() async with ( run_compiled(yaml_config), api_client_connected() as client1, @@ -27,8 +28,8 @@ async def test_host_mode_many_entities_multiple_connections( states1: dict[int, EntityState] = {} states2: dict[int, EntityState] = {} - client1_ready = asyncio.Future() - client2_ready = asyncio.Future() + client1_ready = loop.create_future() + client2_ready = loop.create_future() def on_state1(state: EntityState) -> None: states1[state.key] = state