WebSocket Server Module
Section titled “WebSocket Server Module”The websocket_server module provides the ESP3D-X pendant with two independent WebSocket endpoints hosted on the same HTTP port. It is a server-side transport — distinct from the outbound websocket_client.md used for CNC connectivity. Together the two endpoints allow a remote browser-based WebUI and external tooling (PC utilities, file managers) to communicate with the pendant simultaneously over WiFi.
Build guard: the two services are independently controlled by compile-time flags.
ESP3D_WEBUI_SERVER_FEATURE→ enablesESP3DWebUiService(/ws)ESP3D_WS_SERVER_SERVICE_FEATURE→ enablesESP3DWsDataService(/wsdata)Both require WiFi and the HTTP server (
ESP3DHttpService) to be running. See Network_&_Web_Services.md for feature compatibility constraints.
Architecture Overview
Section titled “Architecture Overview”ESP3DHttpService (esp_httpd) │ ├── /ws URI handler ──► ESP3DWebUiService (subprotocol: webui-v3) │ │ │ inherits from │ │ └── /wsdata URI handler ──► ESP3DWsDataService (subprotocol: esp3d-v1) │ inherits from │ ESP3DWsService (base class) │ ┌─────────┴──────────┐ │ │ esp3dCommands ESP3DAuthenticationService (message routing) (session management)Endpoint Summary
Section titled “Endpoint Summary”| Endpoint | Class | Subprotocol | Frame modes | File transfer |
|---|---|---|---|---|
/ws | ESP3DWebUiService | webui-v3 | TEXT (commands, notifications) · BINARY (raw CNC stream) | No |
/wsdata | ESP3DWsDataService | esp3d-v1 | TEXT (ESP commands, G-code) · BINARY (V1 upload/download) | Yes |
Class Hierarchy
Section titled “Class Hierarchy”ESP3DWsService — Base
Section titled “ESP3DWsService — Base”class ESP3DWsService Public: begin(config) → bool // allocate client pool, record server handle handle() // periodic zombie purge end() // disconnect all clients, free memory http_handler(req) → esp_err_t // single URI handler entry point process(msg) // outbound: broadcast or unicast message onOpen(req) → esp_err_t // virtual — called on WS upgrade onMessage(req) → esp_err_t // virtual — called on DATA frame onClose(fd) → esp_err_t // virtual — called on disconnect pushMsgToRxQueue(...) → bool // inbound: authenticate + dispatch to command pipeline pushMsgTxt(fd, msg) → esp_err_t // unicast text frame pushMsgBin(fd, msg) → esp_err_t // unicast binary frame BroadcastTxt(msg) → esp_err_t // text to all connected clients BroadcastBin(msg) → esp_err_t // binary to all connected clients clientsConnected() → uint getClientSocketId(i) → int // fd of slot i, or -1 (mutex-protected snapshot) purgeZombieClients() // drop stale lwIP fds revoke(fd) // [ESP500]LOGOUT: stop broadcast data to fd (AUTHENTICATION) Protected: processTextFrame(fd, buf, len) // buffer accumulation + line-flush to rx queue socketLevel(fd, has_session) // per-line / per-frame level (AUTHENTICATION) setHttpSession(fd, id) / getHttpSession(fd, out) // /ws: HTTP session the socket was opened with setAuthorized(fd, bool) // outbound filtering state checkSessions() // once a second from handle()ESP3DWebUiService — /ws endpoint
Section titled “ESP3DWebUiService — /ws endpoint”class ESP3DWebUiService : public ESP3DWsService process(msg) // override: broadcast/unicast to WebUI clients pushNotification(msg) // wrap as NOTIFICATION:<msg> TEXT frame, send with pushText() pushText(msg) // TEXT to every /ws client (session checked per recipient) handle() // base handle() + close sockets whose HTTP session is gone onOpen(req) // override: check HTTP session, register client, send currentID/activeID frames onMessage(req) // override: accept TEXT commands + forward raw binary stream Private: hasValidSession(fd) // AUTHENTICATION: true if the HTTP session is valid, else closes fdGlobal instance: esp3dWsWebUiService
ESP3DWsDataService — /wsdata endpoint
Section titled “ESP3DWsDataService — /wsdata endpoint”class ESP3DWsDataService : public ESP3DWsService onOpen(req) // override: register client, send welcome TEXT frame onMessage(req) // override: route TEXT → base processTextFrame, // BINARY → V1 opcode dispatch onClose(fd) // override: abortTransfer() then base onClose Private: _transfer // ESP3DWsTransferInfo — single in-progress transfer slot handleStatusRequest() handleUploadStart() handleUploadPacket() handleUploadEnd() handleDownloadStart() handleDownloadPacketAck() handleDownloadEndAck() handleNak() handleCommand() refuseGuestFrame() // AUTHENTICATION: status L reply to a guest binary frame sendFrame() sendDownloadPacket() abortTransfer()Global instance: esp3dWsServerDataService
Core Data Structures
Section titled “Core Data Structures”ESP3DWebSocketConfig
Section titled “ESP3DWebSocketConfig”Passed to ESP3DWsService::begin() to initialise a service instance.
struct ESP3DWebSocketConfig { httpd_handle_t server_handle; // shared esp_httpd handle from ESP3DHttpService uint max_clients; // pool size; each slot allocates a per-client receive buffer esp3dSocketType type; // websocket_server_webui or websocket_server_data};ESP3DWebSocketInfos
Section titled “ESP3DWebSocketInfos”One slot per connected client, allocated as a flat array at begin() time.
struct ESP3DWebSocketInfos { int socket_id; // lwIP fd; -1 = slot free struct sockaddr_storage source_addr; // IPv4-in-IPv6 address filled on connect char *buffer; // per-client receive accumulation buffer uint buf_position; // current write position in buffer // Only present when ESP3D_AUTHENTICATION_FEATURE is enabled: char session_id[25]; // /wsdata: session OWNED by the socket (pwd= login), cleared on close char http_session_id[25]; // /ws: HTTP cookie session the socket was opened with, // REFERENCED (never cleared on close); the socket is // closed as soon as that session is no longer valid bool authorized; // /wsdata outbound filtering: broadcast data only when true uint32_t last_rx_ms; // last received line / frame (stream inactivity timeout)};authorized is a cached copy, updated on every received line or frame, because the send path (process(), also used by the WebSocket log backend from the logging task) must never call the authentication service: that service logs while holding its mutex, so the call could deadlock.
Memory: each slot performs two heap allocations at
begin()— the array element and the receive buffer. These happen at startup before the heap fragments. The ESP32 in Bluetooth mode can have as little as 10 KB free; prefer smallmax_clientsvalues. See the project’sdocs/guides/esp32_memory_constraints.mdguide.
ESP3DWsTransferInfo
Section titled “ESP3DWsTransferInfo”Owned by ESP3DWsDataService. Tracks the single active file transfer.
struct ESP3DWsTransferInfo { ESP3DWsTransferState state; // idle | uploading | downloading int client_fd; // fd of the transferring client (-1 = none) FILE *file; // open file handle uint32_t total_size; // declared by client (upload) or from stat (download) uint32_t processed_bytes; // bytes written (upload) or sent (download) uint32_t last_packet_id; // last successfully handled packet id char full_path[512]; // ESP3D_WS_TRANSFER_PATH_MAX — path + '/' + filename};Component Details
Section titled “Component Details”Base Service — ESP3DWsService
Section titled “Base Service — ESP3DWsService”Files: main/modules/websocket_server/esp3d_ws_service.h/.cpp
The base class provides all infrastructure shared by both endpoints.
Lifecycle
Section titled “Lifecycle”begin() validates the config, allocates _max_clients slots and their receive buffers, and marks the service started. end() triggers close on every connected client, clears authentication sessions for this instance’s client type, and frees all allocations. end() is idempotent — it is safe to call on a stopped service.
Locking scheme (a14)
Section titled “Locking scheme (a14)”_clients[] is used by three tasks: the httpd task (onOpen() / onMessage() / onClose()), the network services task (handle() → purgeZombieClients(), closeClients()) and the command dispatch task (process() → BroadcastTxt(), pushMsgTxt() from HTTP handlers). It is protected by _clients_mutex, a plain non-recursive pthread mutex:
- every read or change of a slot happens under the mutex;
- network calls (
httpd_ws_send_frame_async,httpd_sess_trigger_close,httpd_ws_get_fd_info,getpeername) are never made while holding it: the slot state (fd) is copied under the lock, the send or close happens unlocked, then the lock is taken again to revalidate or release; - broadcasts take each slot’s fd under the lock and send outside it;
onClose()checkssocket_id == fdunder the lock before freeing a slot, so a recycled fd can never free the wrong session; - no callback called under the mutex re-enters the class.
Use getClientSocketId(index) when only the fd is needed: a raw ESP3DWebSocketInfos* from getClientInfos() is not safe once the lock is released. The authentication service is always called without this lock (it has its own).
Client pool
Section titled “Client pool”A flat array of ESP3DWebSocketInfos with socket ids initialised to -1 (free). addClient() claims the first free slot and records the peer IP address via getpeername(). onClose() resets the slot back to free. When getFreeClientIndex() finds no free slot, the new connection is rejected immediately via httpd_sess_trigger_close().
Zombie purge
Section titled “Zombie purge”purgeZombieClients() calls httpd_ws_get_fd_info() for every occupied slot. Any fd no longer marked HTTPD_WS_CLIENT_WEBSOCKET is removed via onClose(). Called from handle() periodically and opportunistically before every unicast or broadcast send, preventing stale fds from accumulating in the pool.
HTTP handler
Section titled “HTTP handler”http_handler() is the single URI callback registered with esp_httpd. Dispatch logic:
HTTP_GETwith a validUpgrade: websocketheader →onOpen()HTTP_GETwithout that header →400 Bad Request(rejects plain HTTP access to the WS URI)- Any other method (the IDF DATA event for established connections) →
onMessage()
Frame receive
Section titled “Frame receive”onMessage() probes the pending frame type by calling httpd_ws_recv_frame() with length 0, retrying in order: TEXT, BINARY, PING, PONG, CLOSE. On IDF 5.4, ESP_ERR_INVALID_STATE is returned when the probed type does not match — the loop tries each type until one succeeds. An empty content_len is rejected with ESP_FAIL to avoid the tight callback loop seen on IDF 5.4. Frames larger than ESP3D_WS_RECV_FRAME_BUF_SIZE fall back to a heap allocation with an OOM log if the allocation fails.
Text frame accumulation
Section titled “Text frame accumulation”processTextFrame() copies bytes into the per-client buffer one at a time, under the clients mutex. On \n, \r, or when the buffer is full, it flushes the accumulated content to pushMsgToRxQueue() and resets the position. This allows commands to span multiple WebSocket frames as long as each line is terminated. ESP3DWebUiService::onMessage() now uses the same function instead of its own copy of the loop.
Send helpers
Section titled “Send helpers”pushMsgTxt() and pushMsgBin() use httpd_ws_send_frame_async(). A failed send logs a warning but does not close the socket — transient send failures under concurrent HTTP upload load must not tear down the WebUI connection.
Broadcast
Section titled “Broadcast”BroadcastTxt() / BroadcastBin() iterate all occupied slots and call the matching unicast sender. An ignore fd parameter (default -1) excludes one connection, typically used to prevent echoing a message back to its sender.
pushMsgToRxQueue() — Inbound message routing
Section titled “pushMsgToRxQueue() — Inbound message routing”This is the boundary between the WebSocket transport and the firmware command pipeline.
Incoming TEXT line (flushed from processTextFrame) │ ▼ESP3DClient::rxAdmit() — low-memory admission; a refused line is dropped │ ▼[ESP3D_AUTHENTICATION_FEATURE enabled] — checked on EVERY line socketLevel(fd) → session valid? level (inactivity timer restarted) session gone? guest, slot session cleared no session → extract pwd= param → getAuthenticatedLevel() guest? not an ESP command → send AUTH_REQUIRED_MSG, return false ESP command → dispatched (each command checks its own level; this is how a guest logs in or reads [ESP500]) not guest and no session → create session id + createRecord() (ERROR_MSG if it fails), setAuthorized(true) pwd= on an ESP command with a session → authorized follows that level[no auth feature] → authentication_level = admin │ ▼ESP3DClient::newMsg() Set: origin = authClientType() webui_websocket (for /ws) websocket_server (for /wsdata) target = stream type = unique request_id.id = socket fd │ is_esp_command(msg)? yes → target = command │ ▼esp3dCommands.process(msg)Responses return asynchronously through process(). A message addressed to one fd (msg->request_id.id, the answer to its own command) is always sent to it. A broadcast (request_id.id == 0) goes, with authentication, only to sockets that are authorized and have received something within the stream timeout (getStreamSessionTimeout(), [ESP510]STREAM=, 0 = no expiry); without authentication it is BroadcastTxt(). The filter only takes the slots lock, never the authentication service.
handle() calls checkSessions() once a second: a socket whose record is gone (evicted, cleared elsewhere) loses authorized. revoke(fd) (called by [ESP500]LOGOUT) drops it at once.
See esp3d_x_stream_authentication.md for the full model.
WebUI Service — ESP3DWebUiService
Section titled “WebUI Service — ESP3DWebUiService”File: main/modules/websocket_server/esp3d_webui_service.h
Guard: ESP3D_WEBUI_SERVER_FEATURE
Extends the base class for the browser-facing endpoint.
| Method | Purpose |
|---|---|
onOpen() | With authentication, first requires a valid, non-guest HTTP session from the ESPSESSIONID cookie (/ws carries the machine stream); otherwise the upgrade is refused. The check runs before the previous client is replaced, so a guest can never kick out an authenticated page. Then registers the client, stores the cookie session with setHttpSession(), and sends currentID and activeID synchronisation frames so browsers can coordinate which tab holds the active session. |
onMessage() | Accepts TEXT commands from the browser (delegated to processTextFrame()); forwards inbound BINARY frames as raw CNC stream bytes without any modification. A PING checks the session the socket was opened with (not the id named in the PING); if it is gone the socket is closed, otherwise the PING counts as activity and the answer gives the time that was left. |
process(msg) | Receives responses from the firmware pipeline; broadcasts when msg->request_id.id == 0, unicasts to the specific fd otherwise. With authentication the session is checked per recipient (hasValidSession()) at the moment the data leaves, so there is no window between a logout or expiry and the end of the stream. |
pushText(msg) | TEXT to every /ws client, with the same per-recipient session check (BroadcastTxt() without authentication). Used by pushNotification() and by the HTTP upload error path (pushError()). |
pushNotification(msg) | Wraps msg as a NOTIFICATION:<msg> TEXT frame and sends it with pushText(). Called by the [ESP600] command handler. |
handle() | Base handle(), then (authentication) once a second closes every socket whose HTTP session is gone: WebUI logout, [ESP510]HTTP= timeout reached. hasValidSession() is read-only: neither outgoing data nor this check keeps a session alive. |
For the full /ws text-frame vocabulary (currentID, activeID, PING/PONG, NOTIFICATION, ERROR, SENSOR) see docs/architecture/websockets_protocol.md §1.
Data Service — ESP3DWsDataService
Section titled “Data Service — ESP3DWsDataService”File: main/modules/websocket_server/esp3d_ws_data_service.h
Guard: ESP3D_WS_SERVER_SERVICE_FEATURE
Extends the base class to add the ESP3D Binary Protocol V1 for file upload and download. TEXT frames on this socket continue to carry ESP commands and G-code via the inherited processTextFrame() path — both frame types are multiplexed on the same connection.
Key constants
Section titled “Key constants”| Constant | Value | Description |
|---|---|---|
ESP3D_WS_TRANSFER_PACKET_SIZE | 1024 | Maximum data payload bytes per binary frame |
ESP3D_WS_TRANSFER_PATH_MAX | 512 | Maximum file path length in transfer frames |
WS_BINARY_PROTOCOL_V1 | 1 | Protocol revision byte returned in RS idle response |
ESP3D_WS_DATA_URL | "/wsdata" | Registered URI |
ESP3D_WS_DATA_SUBPROTOCOL | "esp3d-v1" | WebSocket subprotocol negotiated on this endpoint |
Binary frame header
Section titled “Binary frame header”All V1 binary frames share a fixed 4-byte header:
Offset Size Content 0 2 Opcode — 2 ASCII bytes (e.g. 'S','R') 2 2 Payload length — little-endian uint16 4 N Payload (N bytes, value from the length field)Binary protocol opcodes
Section titled “Binary protocol opcodes”| Opcode | Direction | Meaning |
|---|---|---|
SR | client → ESP | Status request |
RS | ESP → client | Status response |
SU | client → ESP | Start upload (path + filename + total size) |
US | ESP → client | Upload start ACK |
UP | client → ESP | Upload data packet (4-byte packet id + data) |
PU | ESP → client | Upload packet ACK (status byte + 4-byte packet id) |
EU | client → ESP | End of upload |
UE | ESP → client | Upload end ACK |
SD | client → ESP | Start download (path + filename) |
DS | ESP → client | Download start ACK + file size |
DP | ESP → client | Download data packet (4-byte packet id + data) |
PD | client → ESP | Download packet ACK |
ED | ESP → client | End of download |
DE | client → ESP | Download end ACK |
NK | either | NAK / retransmit request |
CM | client → ESP | Command byte — A (0x41) = abort |
Status bytes used in payloads
Section titled “Status bytes used in payloads”| Byte | Hex | Used in | Meaning |
|---|---|---|---|
O | 0x4F | US, PU, UE, DS, RS | idle / ok |
E | 0x45 | US, PU, UE, DS, RS | error |
B | 0x42 | US, DS, RS | busy (another transfer in progress) |
A | 0x41 | UE, RS | aborted |
U | 0x55 | RS payload | upload in progress |
D | 0x44 | RS payload | download in progress |
L | 0x4C | RS, US, UE, DS, PU | login required (authentication enabled, socket not authenticated), same as ESP3D |
Binary frames and authentication (a14)
Section titled “Binary frames and authentication (a14)”With authentication, every binary frame goes through the same session gate as TEXT (socketLevel()) before any dispatch, so a guest never reaches a handler. An accepted frame restarts the session inactivity timeout, so a long upload or download never expires halfway. A guest frame is answered by refuseGuestFrame(), with the same mapping as ESP3D:
| Request | Reply |
|---|---|
SR | RS L (no version byte: a refusal, not a capability probe) |
SU / EU / SD | US / UE / DS L |
UP | PU L + packet id (0xFFFFFFFF when missing) |
PD, DE, NK, CM, unknown | no reply, ignored |
If the guest is the owner of the running transfer (it lost its session: logout, wrong password, timeout), the transfer is aborted instead of keeping the file and the busy state.
Transfer state machine
Section titled “Transfer state machine”Only one transfer is active at a time. A second client attempting to start while one is in progress receives B (busy) in the ACK.
┌─────────────────────────────────────┐ │ idle │ │ SR → RS(O + version byte = 1) │ └──────┬───────────────┬──────────────┘ │ │ SU received SD received send US(O) send DS(O) + file size │ │ ┌────────▼──┐ ┌───────▼────────┐ │ uploading │ │ downloading │ │ │ │ │ │ UP → PU │ │ PD → send DP │ │ (O or E) │ │ │ └─────┬─────┘ └──────┬─────────┘ │ │ EU / CM(A) / error / disconnect │ ────────────────────────────► │ │ ED / CM(A) / error / disconnect ──────────────────────────────► idleabortTransfer() closes the open file handle, resets _transfer.state to idle, and clears _transfer.client_fd. It is invoked from:
onClose()— client disconnects while a transfer is in progresshandleCommand()— client sendsCM A(abort command)- the binary session gate — the transfer owner lost its session
At the end of an upload, webUiPsramCache.invalidate(full_path) drops a stale cached copy when the file is served from the WebUI PSRAM cache (no-op otherwise, see http_service.md).
For the complete frame-by-frame sequence, RS payload layouts, capability probe handshake, and testing instructions see docs/architecture/websockets_protocol.md §2.
Connection Lifecycle
Section titled “Connection Lifecycle”Client ESP3DHttpService / ESP3DWsService │ │ │── HTTP GET /ws (Upgrade: websocket) ──►│ │ │ http_handler() → onOpen() │ │ addClient(fd) │ │ getpeername() → record peer IP │◄── 101 Switching Protocols ────────────│ │◄── TEXT: welcome frame ────────────────│ WebUiService: currentID + activeID │ │ DataService: "Welcome to ESP3D-X V…" │ │ │── TEXT: [ESP720]\n ────────────────────►│ │ │ onMessage() → processTextFrame() │ │ buffer → flush on \n │ │ pushMsgToRxQueue() │ │ authenticate / look up session │ │ esp3dCommands.process(msg) │◄── TEXT: response ─────────────────────│ process(response_msg) → pushMsgTxt(fd) │ │ │── BINARY: SR (DataService only) ───────►│ │◄── BINARY: RS(O, version=1) ───────────│ │ │ │── WS CLOSE ────────────────────────────►│ │ │ onClose(fd) │ │ abortTransfer() if uploading/downloading │ │ clearSession(session_id) [if auth enabled] │ │ free slot (_clients[i].socket_id = -1)Authentication Integration
Section titled “Authentication Integration”When ESP3D_AUTHENTICATION_FEATURE is enabled:
/ws(WebUI): the socket is accepted only with a valid, non-guest HTTP cookie session. It references that session (http_session_id) and is closed as soon as the session is no longer valid (checked on every send and once a second). Logging out of the WebUI or reaching[ESP510]HTTP=therefore closes the stream./wsdata: a client logs in withpwd=<password>in a line.pushMsgToRxQueue()gets the level withgetAuthenticatedLevel()and on success callscreateRecord(); the session id is stored inESP3DWebSocketInfos::session_id.- The level is checked on every line and binary frame (
socketLevel()), not only the first one. A session that is gone (inactivity timeout[ESP510]STREAM=, logout, wrong password) makes the socket a guest again, and a newpwd=opens a new session. A guest may only send ESP commands. - Broadcast data goes only to authorized sockets (
authorizedflag, stream timeout); answers to a socket’s own commands are always sent.[ESP500]LOGOUTcallsrevoke(fd). onClose()callsclearSession(session_id)for the owned session.end()callsclearSessions(authClientType())to purge residual records when the service stops, without touching the other service’s sessions. The/wsHTTP session is never cleared there (it belongs to the WebUI pages).- The two services register sessions under distinct
ESP3DClientTypevalues —webui_websocketfor/wsandwebsocket_serverfor/wsdata— so their records are independent and never collide.
Full model: esp3d_x_stream_authentication.md.
Integration with HTTP Service
Section titled “Integration with HTTP Service”ESP3DWsService does not open its own TCP socket. It borrows the httpd_handle_t from ESP3DHttpService and registers URI handlers against it. ESP3DHttpService::begin() constructs the ESP3DWebSocketConfig structs and calls begin() on each WebSocket service after the HTTP server is up.
ESP3DHttpService::begin() │ ├── httpd_register_uri_handler(/ws) → esp3dWsWebUiService.http_handler └── httpd_register_uri_handler(/wsdata) → esp3dWsServerDataService.http_handlerPractical consequences:
- Both WebSocket endpoints share the same TCP port as HTTP (default 80).
- Stopping
ESP3DHttpServiceimplicitly terminates all WebSocket connections. - WebSocket connections count against
esp_httpd’smax_open_sockets, shared with HTTP clients.
Build Configuration
Section titled “Build Configuration”| CMake option | C define | Effect |
|---|---|---|
WEBUI_SERVER ON | ESP3D_WEBUI_SERVER_FEATURE | Compiles ESP3DWebUiService, registers /ws handler |
WS_SERVER_SERVICE ON | ESP3D_WS_SERVER_SERVICE_FEATURE | Compiles ESP3DWsDataService, registers /wsdata handler |
SOCKET_CLIENT_SERVICE ON | — | Forbidden with either WS server option — cmake/sanity_check.cmake will error |
sanity_check.cmakerejects builds that combineWS_SERVER_SERVICEorWEBUI_SERVERwithSOCKET_CLIENT_SERVICE. The two CNC-over-WiFi transport models (TCP socket client vs. WebSocket server) cannot safely share the lwIP stack on a no-PSRAM board.
Key Constants
Section titled “Key Constants”| Symbol | Default | Description |
|---|---|---|
ESP3D_WS_RX_BUFFER_SIZE | sdkconfig | Per-client text accumulation buffer, allocated once per slot at begin() |
ESP3D_WS_RECV_FRAME_BUF_SIZE | sdkconfig | Static receive frame buffer; frames larger than this fall back to heap allocation |
ESP3D_WS_TRANSFER_PACKET_SIZE | 1024 | Binary upload/download payload bytes per packet |
ESP3D_WS_TRANSFER_PATH_MAX | 512 | Maximum file path length in transfer frames |
ESP3D_WS_DATA_URL | "/wsdata" | URI registered for the data endpoint |
ESP3D_WS_DATA_SUBPROTOCOL | "esp3d-v1" | WebSocket subprotocol negotiated on /wsdata |
Related Documentation
Section titled “Related Documentation”| Document | Relationship |
|---|---|
| websocket_client.md | Outbound WebSocket client — connects pendant to a remote CNC WebSocket server; sibling transport module |
| Network_&_Web_Services.md | Parent module overview covering all network transports and feature flag compatibility |
| network.md | ESP3DNetwork orchestrator that starts the HTTP service and therefore these WS endpoints |
| wifi.md | WiFi client/AP — prerequisite for any WebSocket server operation |
| socket_server.md | Raw TCP socket server — alternative to WebSocket for tool connectivity without HTTP overhead |
| esp3d_core.md | ESP3DClient / ESP3DMessage types used throughout the inbound routing path |
| esp3d_commands.md | esp3dCommands.process() — the command dispatcher called from pushMsgToRxQueue() |
docs/architecture/websockets_protocol.md | Authoritative frame-by-frame V1 binary protocol reference, /ws text vocabulary, capability probe sequence, and testing instructions — read before implementing a client |