mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-23 15:13:03 -05:00
A node that lost WiFi could give up permanently. The disconnect handler counted retries in s_retry_num, which is only ever cleared on a successful IP_EVENT_STA_GOT_IP, so it accumulated across the node's entire uptime. After ten cumulative disconnects it set WIFI_FAIL_BIT and never called esp_wifi_connect() again. No backoff reset, no periodic retry, no reboot. Only a power cycle cleared it. MEASURED 2026-08-30: the AP serving the fleet took a firmware update and rebooted at ~03:38. All three wall-mounted nodes stopped delivering within three minutes of each other (nodes 0 and 1 at 03:37:03, to the second) and none recovered for 5.5-7.5 hours, until each was physically unplugged. CSI capture ran the whole time -- node 0's sync sequence advanced 16600 -> 46340 at a steady ~30/s while it was invisible to the server -- because capture is driver-level and needs no IP. The boards looked alive on a bench and dead to the aggregator. Retry is now unbounded with 500 ms -> 30 s exponential backoff, scheduled on an esp_timer one-shot so esp_wifi_connect() is never reached through a delay inside the event handler. Two things that were conflated are now separate: WIFI_BOOT_WAIT_ATTEMPTS only releases app_main from its startup wait so the rest of the node can boot; nothing stops the node retrying. The uplink watchdog covers the failures we have not diagnosed: no successful send for CONFIG_UPLINK_WATCHDOG_TIMEOUT_S restarts the node. The signal is a successful sendto(), which on UDP means the stack accepted the datagram, NOT that the server received it -- so stopping the aggregator cannot reboot the fleet. It arms on the first successful send rather than at boot, so a node powered up while the AP is down does not reboot-loop; the unbounded reconnect owns that case. The timeout is 1200 s, sized from a 112-minute soak rather than a guess: 30 observed silences across two nodes, median 225 s, one 510 s outlier that looked like two cycles running together. An earlier 120 s would have rebooted healthy nodes every few minutes, and even 600 s left only 90 s of margin over an event already seen once. Do not lower it below ~900 s while the ~225 s stalls are unexplained; a needless reboot re-runs ESP-NOW leader election and discards mesh time-sync state. Also guards thermal.c's apply_tx_power behind CONFIG_THERMAL_THROTTLE so a monitor-only build (the default, and what is on the wall) stops warning about an unused function. Kconfig gains CSI_GATE_MESH_ALIGNED here alongside the watchdog options; its implementation lands in a later commit and the option is inert until then. Co-Authored-By: claude-flow <ruv@ruv.net> (cherry picked from commit bb6d954de68fd4d542e191fd5e82c753f67a9874)
71 lines
2.0 KiB
C
71 lines
2.0 KiB
C
/**
|
|
* @file stream_sender.h
|
|
* @brief UDP stream sender for CSI frames.
|
|
*/
|
|
|
|
#ifndef STREAM_SENDER_H
|
|
#define STREAM_SENDER_H
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
|
|
/**
|
|
* Initialize the UDP sender.
|
|
* Creates a UDP socket targeting the configured aggregator.
|
|
*
|
|
* @return 0 on success, -1 on error.
|
|
*/
|
|
int stream_sender_init(void);
|
|
|
|
/**
|
|
* Initialize the UDP sender with explicit IP and port.
|
|
* Used when configuration is loaded from NVS at runtime.
|
|
*
|
|
* @param ip Aggregator IP address string (e.g. "192.168.1.20").
|
|
* @param port Aggregator UDP port.
|
|
* @return 0 on success, -1 on error.
|
|
*/
|
|
int stream_sender_init_with(const char *ip, uint16_t port);
|
|
|
|
/**
|
|
* Send a serialized CSI frame over UDP.
|
|
*
|
|
* @param data Frame data buffer.
|
|
* @param len Length of data to send.
|
|
* @return Number of bytes sent, or -1 on error.
|
|
*/
|
|
int stream_sender_send(const uint8_t *data, size_t len);
|
|
|
|
/**
|
|
* Send a low-rate control packet, bypassing the ENOMEM backoff gate (#1183).
|
|
*
|
|
* Intended for ≤48 B, ≤1 Hz control traffic (feature_state, HEALTH, mesh
|
|
* sync) that must not be starved by the global backoff the high-rate CSI
|
|
* stream triggers. An ENOMEM on this path is reported quietly and does NOT
|
|
* extend or reset the global backoff streak.
|
|
*
|
|
* @param data Frame data buffer.
|
|
* @param len Length of data to send.
|
|
* @return Number of bytes sent, or -1 on error.
|
|
*/
|
|
int stream_sender_send_priority(const uint8_t *data, size_t len);
|
|
|
|
/**
|
|
* esp_timer timestamp (microseconds) of the last sendto() the network stack
|
|
* accepted, on either the bulk or the priority path. Returns 0 if the node has
|
|
* not successfully sent anything since boot.
|
|
*
|
|
* This reports that the datagram was accepted for transmission, NOT that the
|
|
* aggregator received it -- UDP gives no such signal. Used by the uplink
|
|
* watchdog to detect a node that is powered and capturing but cannot reach the
|
|
* network.
|
|
*/
|
|
int64_t stream_sender_last_success_us(void);
|
|
|
|
/**
|
|
* Close the UDP sender socket.
|
|
*/
|
|
void stream_sender_deinit(void);
|
|
|
|
#endif /* STREAM_SENDER_H */
|