Ensure we can send batches where the first message exceeds MAX_PACKET_SIZE (#9068)

This commit is contained in:
J. Nick Koston 2025-06-15 16:22:14 -05:00 committed by Jesse Hills
parent 242b02a416
commit 497d66f7ec
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
6 changed files with 570 additions and 5 deletions

View File

@ -1807,7 +1807,7 @@ void APIConnection::process_batch_() {
this->batch_first_message_ = true;
size_t items_processed = 0;
uint32_t remaining_size = MAX_PACKET_SIZE;
uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
// Track where each message's header padding begins in the buffer
// For plaintext: this is where the 6-byte header padding starts
@ -1832,11 +1832,15 @@ void APIConnection::process_batch_() {
packet_info.emplace_back(item.message_type, current_offset, proto_payload_size);
// Update tracking variables
items_processed++;
// After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation
if (items_processed == 1) {
remaining_size = MAX_PACKET_SIZE;
}
remaining_size -= payload_size;
// Calculate where the next message's header padding will start
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size;
items_processed++;
}
if (items_processed == 0) {

View File

@ -15,7 +15,7 @@ import sys
import tempfile
from typing import TextIO
from aioesphomeapi import APIClient, APIConnectionError, ReconnectLogic
from aioesphomeapi import APIClient, APIConnectionError, LogParser, ReconnectLogic
import pytest
import pytest_asyncio
@ -365,11 +365,21 @@ async def _read_stream_lines(
stream: asyncio.StreamReader, lines: list[str], output_stream: TextIO
) -> None:
"""Read lines from a stream, append to list, and echo to output stream."""
log_parser = LogParser()
while line := await stream.readline():
decoded_line = line.decode("utf-8", errors="replace")
decoded_line = (
line.replace(b"\r", b"")
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
lines.append(decoded_line.rstrip())
# Echo to stdout/stderr in real-time
print(decoded_line.rstrip(), file=output_stream, flush=True)
# Print without newline to avoid double newlines
print(
log_parser.parse_line(decoded_line, timestamp=""),
file=output_stream,
flush=True,
)
@asynccontextmanager

View File

@ -0,0 +1,161 @@
esphome:
name: message-size-batching-test
host:
api:
# Default batch_delay to test batching
logger:
# Create entities that will produce different protobuf header sizes
# Header size depends on: 1 byte indicator + varint(payload_size) + varint(message_type)
# 4-byte header: type < 128, payload < 128
# 5-byte header: type < 128, payload 128-16383 OR type 128+, payload < 128
# 6-byte header: type 128+, payload 128-16383
# Small select with few options - produces small message
select:
- platform: template
name: "Small Select"
id: small_select
optimistic: true
options:
- "Option A"
- "Option B"
initial_option: "Option A"
update_interval: 5.0s
# Medium select with more options - produces medium message
- platform: template
name: "Medium Select"
id: medium_select
optimistic: true
options:
- "Option 001"
- "Option 002"
- "Option 003"
- "Option 004"
- "Option 005"
- "Option 006"
- "Option 007"
- "Option 008"
- "Option 009"
- "Option 010"
- "Option 011"
- "Option 012"
- "Option 013"
- "Option 014"
- "Option 015"
- "Option 016"
- "Option 017"
- "Option 018"
- "Option 019"
- "Option 020"
initial_option: "Option 001"
update_interval: 5.0s
# Large select with many options - produces larger message
- platform: template
name: "Large Select with Many Options to Create Larger Payload"
id: large_select
optimistic: true
options:
- "Long Option Name 001 - This is a longer option name to increase message size"
- "Long Option Name 002 - This is a longer option name to increase message size"
- "Long Option Name 003 - This is a longer option name to increase message size"
- "Long Option Name 004 - This is a longer option name to increase message size"
- "Long Option Name 005 - This is a longer option name to increase message size"
- "Long Option Name 006 - This is a longer option name to increase message size"
- "Long Option Name 007 - This is a longer option name to increase message size"
- "Long Option Name 008 - This is a longer option name to increase message size"
- "Long Option Name 009 - This is a longer option name to increase message size"
- "Long Option Name 010 - This is a longer option name to increase message size"
- "Long Option Name 011 - This is a longer option name to increase message size"
- "Long Option Name 012 - This is a longer option name to increase message size"
- "Long Option Name 013 - This is a longer option name to increase message size"
- "Long Option Name 014 - This is a longer option name to increase message size"
- "Long Option Name 015 - This is a longer option name to increase message size"
- "Long Option Name 016 - This is a longer option name to increase message size"
- "Long Option Name 017 - This is a longer option name to increase message size"
- "Long Option Name 018 - This is a longer option name to increase message size"
- "Long Option Name 019 - This is a longer option name to increase message size"
- "Long Option Name 020 - This is a longer option name to increase message size"
- "Long Option Name 021 - This is a longer option name to increase message size"
- "Long Option Name 022 - This is a longer option name to increase message size"
- "Long Option Name 023 - This is a longer option name to increase message size"
- "Long Option Name 024 - This is a longer option name to increase message size"
- "Long Option Name 025 - This is a longer option name to increase message size"
- "Long Option Name 026 - This is a longer option name to increase message size"
- "Long Option Name 027 - This is a longer option name to increase message size"
- "Long Option Name 028 - This is a longer option name to increase message size"
- "Long Option Name 029 - This is a longer option name to increase message size"
- "Long Option Name 030 - This is a longer option name to increase message size"
- "Long Option Name 031 - This is a longer option name to increase message size"
- "Long Option Name 032 - This is a longer option name to increase message size"
- "Long Option Name 033 - This is a longer option name to increase message size"
- "Long Option Name 034 - This is a longer option name to increase message size"
- "Long Option Name 035 - This is a longer option name to increase message size"
- "Long Option Name 036 - This is a longer option name to increase message size"
- "Long Option Name 037 - This is a longer option name to increase message size"
- "Long Option Name 038 - This is a longer option name to increase message size"
- "Long Option Name 039 - This is a longer option name to increase message size"
- "Long Option Name 040 - This is a longer option name to increase message size"
- "Long Option Name 041 - This is a longer option name to increase message size"
- "Long Option Name 042 - This is a longer option name to increase message size"
- "Long Option Name 043 - This is a longer option name to increase message size"
- "Long Option Name 044 - This is a longer option name to increase message size"
- "Long Option Name 045 - This is a longer option name to increase message size"
- "Long Option Name 046 - This is a longer option name to increase message size"
- "Long Option Name 047 - This is a longer option name to increase message size"
- "Long Option Name 048 - This is a longer option name to increase message size"
- "Long Option Name 049 - This is a longer option name to increase message size"
- "Long Option Name 050 - This is a longer option name to increase message size"
initial_option: "Long Option Name 001 - This is a longer option name to increase message size"
update_interval: 5.0s
# Text sensors with different value lengths
text_sensor:
- platform: template
name: "Short Text Sensor"
id: short_text_sensor
lambda: |-
return {"OK"};
update_interval: 5.0s
- platform: template
name: "Medium Text Sensor"
id: medium_text_sensor
lambda: |-
return {"This is a medium length text sensor value that should produce a medium sized message"};
update_interval: 5.0s
- platform: template
name: "Long Text Sensor with Very Long Value"
id: long_text_sensor
lambda: |-
return {"This is a very long text sensor value that contains a lot of text to ensure we get a larger protobuf message. The message should be long enough to require a 2-byte varint for the payload size, which happens when the payload exceeds 127 bytes. Let's add even more text here to make sure we exceed that threshold and test the batching of messages with different header sizes properly."};
update_interval: 5.0s
# Text input which can have various lengths
text:
- platform: template
name: "Test Text Input"
id: test_text_input
optimistic: true
mode: text
min_length: 0
max_length: 255
initial_value: "Initial value"
update_interval: 5.0s
# Number entity to add variety (different message type number)
# The ListEntitiesNumberResponse has message type 49
# The NumberStateResponse has message type 50
number:
- platform: template
name: "Test Number with Long Name to Increase Message Size"
id: test_number
optimistic: true
min_value: 0
max_value: 1000
step: 0.1
initial_value: 42.0
update_interval: 5.0s

View File

@ -0,0 +1,137 @@
esphome:
name: large-message-test
host:
api:
logger:
# Create a select entity with many options to exceed 1390 bytes
select:
- platform: template
name: "Large Select"
id: large_select
optimistic: true
options:
- "Option 000 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 001 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 002 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 003 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 004 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 005 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 006 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 007 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 008 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 009 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 010 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 011 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 012 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 013 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 014 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 015 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 016 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 017 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 018 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 019 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 020 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 021 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 022 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 023 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 024 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 025 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 026 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 027 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 028 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 029 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 030 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 031 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 032 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 033 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 034 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 035 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 036 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 037 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 038 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 039 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 040 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 041 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 042 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 043 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 044 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 045 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 046 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 047 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 048 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 049 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 050 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 051 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 052 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 053 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 054 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 055 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 056 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 057 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 058 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 059 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 060 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 061 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 062 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 063 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 064 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 065 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 066 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 067 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 068 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 069 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 070 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 071 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 072 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 073 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 074 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 075 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 076 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 077 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 078 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 079 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 080 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 081 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 082 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 083 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 084 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 085 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 086 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 087 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 088 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 089 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 090 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 091 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 092 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 093 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 094 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 095 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 096 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 097 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 098 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
- "Option 099 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
initial_option: "Option 000 - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
# Add some other entities to test batching with the large select
sensor:
- platform: template
name: "Test Sensor"
id: test_sensor
lambda: |-
return 42.0;
update_interval: 1s
binary_sensor:
- platform: template
name: "Test Binary Sensor"
id: test_binary_sensor
lambda: |-
return true;
switch:
- platform: template
name: "Test Switch"
id: test_switch
optimistic: true

View File

@ -0,0 +1,194 @@
"""Integration test for API batching with various message sizes."""
from __future__ import annotations
import asyncio
from aioesphomeapi import EntityState, NumberInfo, SelectInfo, TextInfo, TextSensorInfo
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_api_message_size_batching(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test API can batch messages of various sizes correctly."""
# 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 == "message-size-batching-test"
# List entities - this will batch various sized messages together
entity_info, services = await asyncio.wait_for(
client.list_entities_services(), timeout=5.0
)
# Count different entity types
selects = []
text_sensors = []
text_inputs = []
numbers = []
other_entities = []
for entity in entity_info:
if isinstance(entity, SelectInfo):
selects.append(entity)
elif isinstance(entity, TextSensorInfo):
text_sensors.append(entity)
elif isinstance(entity, TextInfo):
text_inputs.append(entity)
elif isinstance(entity, NumberInfo):
numbers.append(entity)
else:
other_entities.append(entity)
# Verify we have our test entities - exact counts
assert len(selects) == 3, (
f"Expected exactly 3 select entities, got {len(selects)}"
)
assert len(text_sensors) == 3, (
f"Expected exactly 3 text sensor entities, got {len(text_sensors)}"
)
assert len(text_inputs) == 1, (
f"Expected exactly 1 text input entity, got {len(text_inputs)}"
)
# Collect all select entity object_ids for error messages
select_ids = [s.object_id for s in selects]
# Find our specific test entities
small_select = None
medium_select = None
large_select = None
for select in selects:
if select.object_id == "small_select":
small_select = select
elif select.object_id == "medium_select":
medium_select = select
elif (
select.object_id
== "large_select_with_many_options_to_create_larger_payload"
):
large_select = select
assert small_select is not None, (
f"Could not find small_select entity. Found: {select_ids}"
)
assert medium_select is not None, (
f"Could not find medium_select entity. Found: {select_ids}"
)
assert large_select is not None, (
f"Could not find large_select entity. Found: {select_ids}"
)
# Verify the selects have the expected number of options
assert len(small_select.options) == 2, (
f"Expected 2 options for small_select, got {len(small_select.options)}"
)
assert len(medium_select.options) == 20, (
f"Expected 20 options for medium_select, got {len(medium_select.options)}"
)
assert len(large_select.options) == 50, (
f"Expected 50 options for large_select, got {len(large_select.options)}"
)
# Collect all text sensor object_ids for error messages
text_sensor_ids = [t.object_id for t in text_sensors]
# Verify text sensors with different value lengths
short_text_sensor = None
medium_text_sensor = None
long_text_sensor = None
for text_sensor in text_sensors:
if text_sensor.object_id == "short_text_sensor":
short_text_sensor = text_sensor
elif text_sensor.object_id == "medium_text_sensor":
medium_text_sensor = text_sensor
elif text_sensor.object_id == "long_text_sensor_with_very_long_value":
long_text_sensor = text_sensor
assert short_text_sensor is not None, (
f"Could not find short_text_sensor. Found: {text_sensor_ids}"
)
assert medium_text_sensor is not None, (
f"Could not find medium_text_sensor. Found: {text_sensor_ids}"
)
assert long_text_sensor is not None, (
f"Could not find long_text_sensor. Found: {text_sensor_ids}"
)
# Check text input which can have a long max_length
text_input = None
text_input_ids = [t.object_id for t in text_inputs]
for ti in text_inputs:
if ti.object_id == "test_text_input":
text_input = ti
break
assert text_input is not None, (
f"Could not find test_text_input. Found: {text_input_ids}"
)
assert text_input.max_length == 255, (
f"Expected max_length 255, got {text_input.max_length}"
)
# Verify total entity count - messages of various sizes were batched successfully
# We have: 3 selects + 3 text sensors + 1 text input + 1 number = 8 total
total_entities = len(entity_info)
assert total_entities == 8, f"Expected exactly 8 entities, got {total_entities}"
# Check we have the expected entity types
assert len(numbers) == 1, (
f"Expected exactly 1 number entity, got {len(numbers)}"
)
assert len(other_entities) == 0, (
f"Unexpected entity types found: {[type(e).__name__ for e in other_entities]}"
)
# Subscribe to state changes to verify batching works
# Collect keys from entity info to know what states to expect
expected_keys = {entity.key for entity in entity_info}
assert len(expected_keys) == 8, (
f"Expected 8 unique entity keys, got {len(expected_keys)}"
)
received_keys: set[int] = set()
states_future: asyncio.Future[None] = loop.create_future()
def on_state(state: EntityState) -> None:
"""Track when states are received."""
received_keys.add(state.key)
# Check if we've received states from all expected entities
if expected_keys.issubset(received_keys) and not states_future.done():
states_future.set_result(None)
client.subscribe_states(on_state)
# Wait for states with timeout
try:
await asyncio.wait_for(states_future, timeout=5.0)
except asyncio.TimeoutError:
missing_keys = expected_keys - received_keys
pytest.fail(
f"Did not receive states from all entities within 5 seconds. "
f"Missing keys: {missing_keys}, "
f"Received {len(received_keys)} of {len(expected_keys)} expected states"
)
# Verify we received states from all entities
assert expected_keys.issubset(received_keys)
# Check that various message sizes were handled correctly
# Small messages (4-byte header): type < 128, payload < 128
# Medium messages (5-byte header): type < 128, payload 128-16383 OR type 128+, payload < 128
# Large messages (6-byte header): type 128+, payload 128-16383

View File

@ -0,0 +1,59 @@
"""Integration test for API handling of large messages exceeding batch size."""
from __future__ import annotations
from aioesphomeapi import SelectInfo
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_large_message_batching(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test API can handle large messages (>1390 bytes) in batches."""
# Write, compile and run the ESPHome device, then connect to API
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 == "large-message-test"
# List entities - this will include our select with many options
entity_info, services = await client.list_entities_services()
# Find our large select entity
large_select = None
for entity in entity_info:
if isinstance(entity, SelectInfo) and entity.object_id == "large_select":
large_select = entity
break
assert large_select is not None, "Could not find large_select entity"
# Verify the select has all its options
# We created 100 options with long names
assert len(large_select.options) == 100, (
f"Expected 100 options, got {len(large_select.options)}"
)
# Verify all options are present and correct
for i in range(100):
expected_option = f"Option {i:03d} - This is a very long option name to make the message larger than the typical batch size of 1390 bytes"
assert expected_option in large_select.options, (
f"Missing option: {expected_option}"
)
# Also verify we can still receive other entities in the same batch
# Count total entities - should have at least our select plus some sensors
entity_count = len(entity_info)
assert entity_count >= 4, f"Expected at least 4 entities, got {entity_count}"
# Verify we have different entity types (not just selects)
entity_types = {type(entity).__name__ for entity in entity_info}
assert len(entity_types) >= 2, (
f"Expected multiple entity types, got {entity_types}"
)