When the client sends 6+ messages during handshake (HelloReq, AuthReq,
GetTimeResp, SubscribeLogsReq, DeviceInfoReq, ListEntitiesReq),
MAX_MESSAGES_PER_LOOP=5 caused ListEntitiesReq to remain unread in
the socket buffer.
LWIP's rcvevent counter (used by is_socket_ready) tracks pbuf dequeues,
not remaining bytes. When multiple ESPHome messages share a single TCP
segment/pbuf, reading all but the last message exhausts rcvevent to 0
while the last message's data remains in LWIP's internal lastdata cache.
is_socket_ready() then returns false, and the data is never read — the
entity listing silently stalls until something else (like a keepalive
ping) triggers new socket activity.
Fix:
- Track when the read loop hits MAX_MESSAGES_PER_LOOP and retry on the
next iteration without the is_socket_ready() gate
- Increase MAX_MESSAGES_PER_LOOP from 5 to 10 (decode cost is now 3x
cheaper, and we batch 24-34 messages per write)
- Move set_nodelay_for_message(false) before try_to_clear_buffer in
process_batch_() so NODELAY is set before draining overflow
The multi-message batch path in process_batch_multi_ bypassed
set_nodelay_for_message(), so batch data written to the socket would
sit in LWIP's Nagle buffer when log messages had previously enabled
Nagle. The remote wouldn't ACK until it sent its own data (e.g. a
ping), which could take 20+ seconds. This caused log-only API clients
(like esphome logs) to time out waiting for ListEntitiesDoneResponse.
Additionally, when LIST_ENTITIES completed without a state subscription,
the batched entity listing responses were never explicitly flushed —
they waited for the 100ms batch timer instead of being sent immediately
like the INITIAL_STATE completion path already did.
Fixes both issues:
- Call set_nodelay_for_message(false) before write_protobuf_messages
in process_batch_multi_ to ensure TCP_NODELAY is on
- Extract finalize_iterator_sync_() helper and call it when
LIST_ENTITIES completes without state_subscription
Don't reset s_crash_data_valid in crash_handler_clear() so that
additional API clients connecting during the same boot session
can still receive the crash log.
Don't clear the crash data magic marker at boot time. Previously,
crash_handler_read_and_clear() would clear the magic immediately,
so if OTA rollback triggered a reboot before an API client connected,
the crash trace was lost.
Now the magic is only cleared after crash_handler_log() delivers the
data to an API client via crash_handler_clear().