[esphome.ota] Update docstrings to match post-filter behavior

Two doc-drift fixes flagged by copilot review:

1. esphome_fast_select_set_ota_listener_sock() header comment claimed
   passing NULL 'clears the filter' so wake fires on every RCVPLUS. The
   actual code stores NULL and the conn == s_ota_listener_conn check
   never matches, so NULL means 'no wakes' not 'all wakes'. Rewrite to
   describe the actual semantics (install a listener to enable filtered
   wakes; NULL disables OTA wakes entirely).

2. ESPHomeOTAComponent::loop() docstring still claimed false wakes from
   unrelated monitored sockets are expected. Post-filter that's no longer
   true on fast-select (filtered to OTA listener netconn) or raw TCP
   (per-pcb accept_fn_). Rewrite to describe the current behavior:
   loop() runs ~once per real incoming OTA connection, with the idle
   self-disable retained as a safety net for the few narrow cases where
   a wake can land with no pending work (queued-during-session, filter
   not yet installed, host select fallback).
This commit is contained in:
J. Nick Koston
2026-04-10 17:39:37 -10:00
parent eb5da72dab
commit f75d6ab88d
2 changed files with 23 additions and 11 deletions
+16 -8
View File
@@ -99,15 +99,23 @@ void ESPHomeOTAComponent::dump_config() {
void ESPHomeOTAComponent::loop() {
// Self-disabling idle loop. On the first tick after setup() (and after every session
// cleanup and every false wake), if there's no client and the listener has nothing
// queued, we disable ourselves and go back to sleep. Socket-wake paths (LwIP fast
// select, raw TCP accept, host select) mark us pending-enable via
// App.wake_ota_component_any_context() when a monitored socket signals activity, and
// enable_pending_loops_() reactivates us.
// ends), if there's no client and the listener has nothing queued, we disable
// ourselves and go back to sleep. Wake paths (LwIP fast select with listener filter,
// raw TCP accept_fn_, host select) mark us pending-enable via
// App.wake_ota_component_any_context() when an incoming connection arrives on the OTA
// listen socket; enable_pending_loops_() reactivates us.
//
// False wakes from unrelated monitored sockets are expected — the event callbacks
// fire on every RCVPLUS across all monitored sockets, not just OTA's listener — and
// they land here with no pending work.
// On fast-select platforms (ESP32 / LibreTiny), the callback is filtered to OTA's
// listener netconn, so unrelated monitored sockets (API client data, mDNS, etc.) do
// NOT wake OTA through that path. Raw TCP (ESP8266 / RP2040) is inherently filtered:
// accept_fn_ is registered per listener pcb and only fires on completed handshakes
// to that specific listener. So in normal steady state this loop() runs exactly
// once per real incoming OTA connection.
//
// We can still land here with no pending work in a few narrow cases — a second
// connection queued on the listener while an OTA session was active, a listener
// filter that hasn't been installed yet, or the host select path (no filter) — which
// is why the idle-check and self-disable are retained as a safety net.
//
// cleanup_connection_() deliberately does NOT call disable_loop() — letting loop()
// run one more iteration after a session ends guarantees we re-read server_->ready()
+7 -3
View File
@@ -53,12 +53,16 @@ static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) {
/// The sock pointer must have been obtained from esphome_lwip_get_sock().
void esphome_lwip_hook_socket(struct lwip_sock *sock);
/// Filter the inline OTA wake hook in the fast-select callback so it only fires for
/// RCVPLUS events on this specific listener's netconn. Without this, every monitored
/// Install an OTA listener netconn as the wake-filter target for the fast-select
/// callback. After this is called, the OTA wake hook only fires for RCVPLUS events
/// whose `conn` argument matches this listener's netconn — i.e. only on actual
/// incoming connections to the OTA listen socket. Without this, every monitored
/// socket's RCVPLUS (API client data, web server, mDNS, etc.) would mark OTA
/// pending-enable and force its loop to run a wake-up tick only to re-disable itself.
/// Captured at OTA setup(); stays pointing at the listener for the device's lifetime.
/// Pass NULL to clear the filter (wake fires on every RCVPLUS, pre-filter behavior).
/// Passing NULL disables OTA wake notifications entirely (no RCVPLUS event will match
/// a null listener), which is the correct behavior before the listener is installed
/// and after it's torn down.
void esphome_fast_select_set_ota_listener_sock(struct lwip_sock *sock);
/// Set or clear TCP_NODELAY on a socket's tcp_pcb directly.