Files
wadamesh/src/helpers/esp32/WebSocketCompanionServer.h
T
Kaj SchittecatandClaude Fable 5 b58cecab5c touch: beta_32 — non-blocking TCP/WS companion writes (Wi-Fi freeze fix) + chunked history flush + stall diagnostics
Root cause of the beta_31 periodic 1-2s freezes with Wi-Fi on: every
received mesh packet pushes an RX-log frame to all connected TCP/WS
companion clients from the loop thread, and writeAllBytes called
WiFiClient::write() unguarded — against a stale peer (phone asleep,
half-open socket) that blocks in the framework's 1s select() retries.
The 120ms app timeout only bounded attempts, not the first call.

- TCP+WS servers: probe writability (zero-timeout select on the client
  fd) before every write; lwIP reports writable only with ~2.8KB free,
  a whole frame is ~180B, so probed writes complete instantly.
- Reap clients whose writes fail continuously for 10s (half-open
  sockets previously stayed "connected" forever, eating the write
  budget on every pushed frame).
- Chat-history ring: chunked writer + wider flush spacing (same
  treatment saveContacts got in beta_30).
- Stall tracer: 16-entry ring of >=0.2s loop stalls, shown in About.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:55:42 +02:00

75 lines
2.0 KiB
C++

#pragma once
#include <helpers/BaseSerialInterface.h>
#include <WiFi.h>
#ifndef WS_COMPANION_MAX_CLIENTS
#define WS_COMPANION_MAX_CLIENTS 2
#endif
#ifndef WS_HANDSHAKE_MAX_LEN
#define WS_HANDSHAKE_MAX_LEN 1536
#endif
#ifndef WS_HANDSHAKE_TIMEOUT_MS
#define WS_HANDSHAKE_TIMEOUT_MS 5000
#endif
// Per-client: HTTP upgrade handshake or WebSocket mode with companion state machine (plain ws:// only).
struct WSClientState {
mutable WiFiClient client;
bool in_use;
uint32_t accept_ms;
bool handshake_done;
uint16_t handshake_len;
char handshake_buf[WS_HANDSHAKE_MAX_LEN];
uint8_t ws_state;
uint8_t ws_opcode;
uint64_t ws_payload_len;
uint32_t ws_payload_read;
uint8_t ws_mask[4];
uint8_t comp_state;
uint16_t comp_frame_len;
uint16_t comp_rx_len;
uint8_t comp_rx_buf[MAX_FRAME_SIZE];
uint32_t stall_ms; // start of a continuous write-failure run; 0 = healthy
};
// WebSocket server for companion protocol. Same logical protocol as TCP; transport is RFC 6455 (plain WS).
class WebSocketCompanionServer {
public:
WebSocketCompanionServer();
void begin(uint16_t port);
void stop();
/** Close only the listen socket; existing WebSocket clients stay connected. */
void pauseListen();
/** Re-open listen socket after pauseListen(); no-op if already listening. */
void resumeListen();
size_t pollRecvFrame(uint8_t dest[], int* client_index_out);
/** Accept new clients and prune disconnects; call from main loop for timely handshakes. */
void tickHandshake();
size_t writeToClient(int client_index, const uint8_t src[], size_t len);
size_t writeToAllClients(const uint8_t src[], size_t len);
bool isClientConnected(int client_index) const;
int connectedCount() const;
void disconnectClient(int client_index);
private:
WiFiServer _server;
mutable WSClientState _clients[WS_COMPANION_MAX_CLIENTS];
uint16_t _port;
bool _listening;
int _poll_start_idx;
void acceptNewClients();
void pruneDisconnected();
bool doHandshake(int idx);
};