Skip to content

Tools — Communication Clients: WebSocket (`tools_communication_clients_websocket`)

Tools — Communication Clients: WebSocket (tools_communication_clients_websocket)

Section titled “Tools — Communication Clients: WebSocket (tools_communication_clients_websocket)”

The tools_communication_clients_websocket module provides a standalone Python development and testing tool — tools/websocket/ws_transfer_test.py — that implements the ESP3D WebSocket Protocol V1 binary file transfer protocol. It targets the /wsdata WebSocket endpoint exposed by the firmware’s data WebSocket service and is the primary host-side reference implementation for binary upload, download, and protocol capability probing.

This tool is used during development to validate the firmware’s WebSocket binary channel, benchmark transfer integrity, and debug file I/O on both the internal flash filesystem and the SD card — all over a standard Wi-Fi TCP connection to a live pendant device.

Scope boundary: This tool targets /wsdata with subprotocol esp3d-v1 (binary V1 protocol) exclusively. The WebUI socket at /ws with subprotocol webui-v3 uses a different binary stream format for live status updates and is not covered here. See tools_communication_clients_serial_bridge and tools_communication_clients_bt_clients for the other communication client tools in this family.


Commands

Welcome TEXT frame

binary_capable=True

any path

CLI Entry Point

(main)

WebSocket Connection

ws://host:port/wsdata

subprotocol: esp3d-v1

Binary Capability Probe

exchange_sr_rs

SR → RS

Text Channel

ESP720 / ESP740

list_files

Binary Channel

V1 Frame Protocol

upload_file / download_file

Abort Handler

abort_transfer

CM(A)

status

run_status

list

run_list

upload

run_upload

download

run_download

roundtrip

run_roundtrip


tools/websocket/
└── ws_transfer_test.py # Single-file tool — all logic self-contained
FunctionRole
mainCLI argument parser and command dispatcher
run_statusOpens WebSocket, runs SR/RS probe, reports capability
run_listOpens WebSocket, sends ESP720/ESP740 text commands, parses response
run_uploadOpens WebSocket, optionally probes, then uploads a local file
run_downloadOpens WebSocket, optionally probes, then downloads a remote file
run_roundtripUpload + download in one session, then MD5 comparison
upload_fileCore upload state machine (SU→US→UP/PU loop→EU→UE)
download_fileCore download state machine (SD→DS→DP/PD loop→ED/DE)
abort_transferSends CM(A) abort command to interrupt an in-progress transfer
exchange_sr_rsSends SR binary frame, awaits RS response, returns parsed info dict
ensure_binary_file_transferWraps exchange_sr_rs; raises WsTransferError on failure
interpret_rsParses RS payload fields (state, version, progress)
make_frameBuilds 4-byte header + payload binary frame
parse_frameDecodes incoming binary frame into (opcode, payload)
recv_binaryAwaits next binary WebSocket message; skips interleaved text frames
recv_text_responsesCollects text frames until end marker, timeout, or error stop
recv_json_documentAccumulates text frames until valid JSON is assembled
list_filesSends ESP720/ESP740 command; handles JSON or legacy text response

The tool connects using the websockets Python library:

ParameterValue
URLws://<host>:<port>/wsdata
Subprotocolesp3d-v1
First frameTEXT welcome message from the server
Subsequent framesBinary (V1 protocol) or TEXT (ESP command responses)

Every binary message (in both directions) is a fixed 4-byte header followed by an optional payload:

┌──────────────┬──────────────────────┬─────────────────────┐
│ Opcode[2B] │ Payload Length[2B] │ Payload[N bytes] │
│ ASCII chars │ Little-Endian u16 │ (protocol-specific)│
└──────────────┴──────────────────────┴─────────────────────┘
  • Opcode: 2 ASCII characters (e.g., SR, US, DP)
  • Payload Length: uint16 little-endian — number of bytes in the payload
  • Payload: 0 to N bytes, format depends on opcode
OpcodeDirectionMeaning
SRClient → ServerStatus Request (probe)
RSServer → ClientStatus Response
SUClient → ServerStart Upload
USServer → ClientUpload Start ACK
UPClient → ServerUpload Packet
PUServer → ClientUpload Packet ACK
EUClient → ServerEnd Upload
UEServer → ClientUpload End ACK
SDClient → ServerStart Download
DSServer → ClientDownload Start ACK
DPServer → ClientDownload Packet
PDClient → ServerDownload Packet ACK
EDServer → ClientEnd Download
DEClient → ServerDownload End ACK
NKClient → ServerNegative ACK (request retransmit)
CMClient → ServerCommand (e.g., A = abort)

The first byte of most ACK payloads is a status indicator:

ByteASCIIMeaning
O (0x4F)OKOperation accepted / succeeded
E (0x45)ERRORGeneral I/O or path error
B (0x42)BUSYAnother transfer already in progress
A (0x41)ABORTTransfer was aborted
U (0x55)UPLOADServer currently in an upload transfer
D (0x44)DLOADServer currently in a download transfer
L (0x4C)LOGINLogin required: authentication enabled and the socket is not authenticated (a14, S_LOGIN)

Upload packets carry exactly PACKET_SIZE = 1024 data bytes per UP frame (the last packet may be shorter). This constant must match ESP3D_WS_TRANSFER_PACKET_SIZE on the firmware side.


ESP32 Firmware/wsdata handlerws_transfer_test.pyESP32 Firmware/wsdata handlerws_transfer_test.pyopt[--pwd PASSWORD (a14)]Handshake complete - connection readyWebSocket upgrade (HTTP GET /wsdata, Sec-WebSocket-Protocol: esp3d-v1)101 Switching ProtocolsTEXT "Welcome to ESP3D..." (welcome frame)TEXT "[ESP500] json=yes pwd=<password>"JSON {"status":"ok","data":"user" or "admin"}

Authentication (a14): with authentication enabled on the ESP every /wsdata socket starts as guest. open_session() reads the welcome and, when --pwd is given (user or admin password), sends [ESP500] json=yes pwd=<password> (any ESP command with a valid pwd= authenticates the socket; [ESP500] is used because it answers the resulting level). A wrong password stops the tool with ERROR: Login failed. Without --pwd, SR is answered by RS L: status reports LOGIN REQUIRED (a refusal, not missing V1 support), and upload / download / roundtrip stop with an explicit error. Examples: python ws_transfer_test.py --host 192.168.1.100 --port 80 --pwd admin status.

ESP32 Firmwarews_transfer_test.pyESP32 Firmwarews_transfer_test.pyinterpret_rs → binary_capable=Trueinterpret_rs → binary_capable=False, timeout=Truealt[RS arrives within binary_timeout (default 3s)][Timeout]BINARY [SR] (make_frame SR, empty payload)BINARY [RS] payload[0]=status_byte, payload[1]=protocol_version

The RS payload when state is OK:

Byte 0 : status byte (S_OK = 'O')
Byte 1 : protocol version (1 = V1)

When state is UPLOAD or DLOAD (a resumable transfer is in progress), the RS payload carries extended progress data:

Byte 0 : status byte (S_UPLOAD or S_DLOAD)
Bytes 1-4 : total bytes (uint32 LE)
Bytes 5-8 : done bytes (uint32 LE)
Bytes 9-12: last packet ID (uint32 LE)
ESP32 Firmwarews_transfer_test.pyESP32 Firmwarews_transfer_test.pyBegin reading local file in 1024-byte chunksloop[For each 1024-byte chunk]Upload complete[SU] path_len + path + name_len + name + file_size(u32 LE)[US] status=OK[UP] packet_id(u32 LE) + data(≤1024 B)[PU] status=OK + ack_packet_id(u32 LE)[EU] (no payload)[UE] status=OK

SU payload layout:

Byte 0 : len(path)
Bytes 1..N : path (UTF-8)
Byte N+1 : len(filename)
Bytes N+2..M : filename (UTF-8)
Bytes M+1.. : file_size (uint32 little-endian)
ESP32 Firmwarews_transfer_test.pyESP32 Firmwarews_transfer_test.pyRetransmit from expected IDalt[Packet in sequence][Out of sequence]loop[Until ED received]Download complete, file written to disk[SD] path_len + path + name_len + name[DS] status=OK + file_size(u32 LE)[DP] packet_id(u32 LE) + data(≤1024 B)[PD] status=OK + packet_id(u32 LE)[NK] expected_packet_id(u32 LE)[ED] (end of file)[DE] status=OK ESP32 Firmwarews_transfer_test.pyESP32 Firmwarews_transfer_test.pySingle WebSocket session for both transfersPhase 1 - UploadPhase 2 - DownloadMD5(original) == MD5(received)?SR probeRS (binary capable)SU + UP packets + EUUS + PU ACKs + UESD + PD ACKs + DEDS + DP packets + ED ESP32 Firmwarews_transfer_test.pyESP32 Firmwarews_transfer_test.pyNo binary SR/RS probe - text-only pathalt[SD path (starts with /sd)][Flash path]recv_json_document assembles frames until valid JSONprint_listing_from_json renders outputalt[--text flag (legacy mode)]TEXT "[ESP740]/sd/gcodes json=yes\n"TEXT "[ESP720]/ json=yes\n"TEXT (one or more frames containing JSON)TEXT "[ESP720]/ \n"TEXT lines (D:/dir F:/file size)TEXT "Total:... Available:..."TEXT "ok"

ESP32 Pendant Firmware

Host (Developer Machine)

ws://host:port/wsdata

WebSocket frames

Binary V1 Protocol

outbound CNC connection

ws_transfer_test.py

CLI Tool

Python asyncio +

websockets library

WebSocket Data Service

esp3d_ws_data_service.h

/wsdata endpoint

WebSocket Client Module

esp3d_websocket_client.h

(outbound - not used here)

HTTP Service

esp3d_http_service.h

Flash Filesystem

esp3d_flash.h

SD Card

esp3d_sd.h

GCode Host

esp3d_gcode_host_service.h

Note: This tool connects to the firmware’s WebSocket server (esp3d_ws_data_service). The separate WebSocket client (esp3d_websocket_client) is an outbound transport used by the pendant to connect to a CNC controller and is unrelated to this tool. See websocket_client for the firmware-side outbound client and Network_&_Web_Services for the full server-side architecture.


A typed exception class that wraps all expected protocol-level failures. The _run_async() wrapper catches it and exits with code 1 after printing to stderr. This separates clean protocol rejections (wrong path, file missing, server busy) from unexpected Python or network errors.

StatusMeaningDetected At
E ERRORFile missing, path invalid, or I/O errorSU, SD, UP, EU responses
B BUSYAnother transfer already in progressSU, SD responses
A ABORTTransfer was abortedSU, SD responses
Opcode mismatchUnexpected response opcode receivedAny ACK step
Empty payloadACK frame arrived with no status byteAny ACK step
Packet ID mismatchESP acknowledged a different packet ID than sentUP/PU loop
Frame too shortFrame header under 4 bytesparse_frame

The recv_text_responses helper recognises these strings from the ESP as terminal error conditions (no trailing ok is expected after them):

  • No SD — SD card missing or not mounted
  • SD busy — SD card in use by another operation
  • Flash not available / Flash partition not mounted
  • Cannot open : (prefix match)
  • Path incorrect / Path inccorrect (firmware typo variant also handled)

Terminal window
pip install websockets
OptionDefaultDescription
--host192.168.1.100IP address of the ESP32 pendant
--port8282WebSocket port
--binary-timeout3.0Seconds to wait for SR→RS binary response
--skip-binary-checkfalseSkip SR/RS probe before upload/download/roundtrip
Terminal window
python ws_transfer_test.py --host 192.168.1.100 --port 8282 status
python ws_transfer_test.py --host 192.168.1.100 --port 8282 --binary-timeout 5 status

Performs the two-step verification:

  1. WebSocket upgrade to /wsdata with subprotocol esp3d-v1 — checks the data endpoint is present and the server accepts the subprotocol
  2. SR → RS binary probe — confirms the server responds to the V1 binary protocol
Terminal window
# Flash root — json=yes by default (structured, fast)
python ws_transfer_test.py --host 192.168.1.100 --port 8282 list /
python ws_transfer_test.py --host 192.168.1.100 --port 8282 list /fs # alias for /
# SD card subdirectory
python ws_transfer_test.py --host 192.168.1.100 --port 8282 list /sd/gcodes
# Print the raw JSON document instead of formatted output
python ws_transfer_test.py --host 192.168.1.100 --port 8282 list / --raw-json
# Legacy line-by-line text mode (no json=yes)
python ws_transfer_test.py --host 192.168.1.100 --port 8282 list / --text

Uses [ESP720] for flash paths and [ESP740] for SD paths. /fs is normalised to / for ESP720 compatibility.

Terminal window
python ws_transfer_test.py --host 192.168.1.100 --port 8282 upload myfile.txt /fs
python ws_transfer_test.py --host 192.168.1.100 --port 8282 --skip-binary-check upload myfile.txt /fs

Runs the SR/RS probe first (unless --skip-binary-check), then streams the file in 1024-byte packets with per-packet acknowledgement.

Terminal window
# Save as ./test.txt in the current directory (default)
python ws_transfer_test.py --host 192.168.1.100 --port 8282 download /fs test.txt
# Save to an explicit local path
python ws_transfer_test.py --host 192.168.1.100 --port 8282 download /fs test.txt ./received.txt

Runs SR/RS probe first (unless --skip-binary-check), streams packets with per-packet ACK, sends NAK for any out-of-sequence packet.

Terminal window
python ws_transfer_test.py --host 192.168.1.100 --port 8282 roundtrip myfile.txt /fs

In a single WebSocket session: uploads the file, downloads it back to a .received temporary file, computes MD5 of both files, prints pass/fail, and removes the temporary file on success. Exits with code 1 on MD5 mismatch.


WebSocket open + welcome received

SR→RS OK (or check skipped)

RS not received / error

SU frame sent

US OK

US ERROR / BUSY / ABORT

UP packet sent

PU OK (more data)

PU OK (last packet)

PU ERROR / ID mismatch

EU frame sent

UE OK

UE ERROR

Connecting

Probing

SendingSU

Failed

WaitingUS

SendingPackets

WaitingPU

SendingEU

WaitingUE

Done

WebSocket open + welcome received

SR→RS OK (or check skipped)

RS not received / error

SD frame sent

DS OK (file_size known)

DS ERROR / BUSY / ABORT

DP packet received

In sequence → write chunk + send ACK

Out of sequence

NAK sent → await retransmit

more packets expected

ED end-of-file received

DE sent

Connecting

Probing

SendingSD

Failed

WaitingDS

ReceivingPackets

CheckSequence

SendingPD

SendingNAK

SendingDE

Done


PackagePurpose
websocketsAsync WebSocket client (pip install websockets)
asyncioCoroutine runtime for all async I/O operations
argparseCLI argument parsing
hashlibMD5 checksum computation for roundtrip comparison
structBinary packing/unpacking of frame headers and payload fields
jsonParsing ESP720/ESP740 JSON directory listings
os, sysFile I/O, path utilities, and process exit codes
Firmware ComponentDocumentationRelationship
esp3d_ws_data_service.hwebsocket_serverThe /wsdata server endpoint this tool connects to
esp3d_websocket_client.hwebsocket_clientOutbound WS transport for CNC — separate, not tested here
esp3d_flash.hStorage_&_ConfigurationFlash filesystem accessed server-side during upload/download
esp3d_sd.hStorage_&_ConfigurationSD card accessed server-side during upload/download
esp3d_ws_service.hNetwork_&_Web_ServicesUnderlying WebSocket server infrastructure

ModuleDocumentationNotes
Serial Bridgetools_communication_clients_serial_bridgeHost ↔ serial bridge for UART debugging
BT Clientstools_communication_clients_bt_clientsBLE/SPP Bluetooth test clients
Firmware Simulatortools_firmware_simulatorMock CNC firmware for offline testing
WebSocket ServerNetwork_&_Web_ServicesFirmware WS server (/wsdata, /ws)
WebSocket Clientwebsocket_clientFirmware-side outbound WS transport to CNC
Communication TransportsCommunication_TransportsAll firmware transport modules overview

LocationTool path argumentESP command used
Flash root/ or /fsESP720 with path /
Flash subdirectory/subdirESP720 with path /subdir
SD root/sdESP740 with path /sd
SD subdirectory/sd/gcodesESP740 with path /sd/gcodes

/fs is automatically normalised to / for ESP720 flash listings, matching the firmware’s VFS root convention.

The firmware may send unsolicited TEXT frames (status updates, log lines) at any time during a binary transfer. The recv_binary helper transparently skips and prints these frames so they never corrupt the binary protocol state machine.

The default --binary-timeout of 3 seconds is intentionally short. If the server is running a build without the data handler enabled, the RS frame will never arrive and a long timeout would stall the tool. Increase to 5–10 seconds only when debugging slow or congested network conditions.

The /wsdata binary transfer capability is only present in firmware builds where the WebSocket data service is compiled in. Consult docs/features/feature_resource_matrix.md and the project CMakeLists.txt options to confirm the target build includes this service before running upload or download commands.