Chapter 96: WebSocket Client Implementation
Chapter Objectives
After completing this chapter, you will be able to:
- Understand the WebSocket protocol and its advantages for real-time communication.
- Explain the WebSocket handshake process and data framing.
- Implement a WebSocket client on an ESP32 device using the ESP-IDF
esp_websocket_client
component. - Connect to WebSocket servers, send, and receive messages.
- Handle various WebSocket events, including connection, disconnection, data reception, and errors.
- Implement secure WebSocket (WSS) connections using TLS.
- Identify common issues and troubleshoot WebSocket client applications.
- Apply this knowledge to build interactive, real-time applications with your ESP32.
Introduction
In the realm of modern web and IoT applications, real-time, two-way communication between a client and a server is often a critical requirement. Traditional HTTP, being a request-response protocol, has limitations for such scenarios, often resorting to polling techniques that are inefficient and introduce latency. WebSockets provide a sophisticated solution by establishing a persistent, full-duplex communication channel over a single TCP connection.
%%{init: {"flowchart": {"htmlLabels": true}} }%% graph LR subgraph WebSocket_Communication ["WebSocket Communication"] direction LR Client2[Client] Server2[Server] Client2 -- "1- HTTP GET (Upgrade Request)" --> Server2 Server2 -- "2- HTTP 101 (Switching Protocols)" --> Client2 Client2 <-. "Persistent Full-Duplex Channel" .-> Server2 Client2 -- "Data Frame ->" --> Server2 Server2 -- "<- Data Frame" --> Client2 Client2 -- "Data Frame ->" --> Server2 end subgraph HTTP_Polling ["HTTP Polling"] direction LR Client1[Client] Server1[Server] Client1 -- "1- HTTP GET Request" --> Server1 Server1 -- "2- HTTP Response (Data/No Data)" --> Client1 Client1 -. "Wait (Polling Interval)" .-> Client1 Client1 -- "3- HTTP GET Request (Repeat)" --> Server1 Server1 -- "4- HTTP Response (Data/No Data)" --> Client1 end classDef default fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; class Client1,Server1,Client2,Server2 startEnd;
This chapter will guide you through understanding the WebSocket protocol and implementing a WebSocket client on your ESP32 device using the ESP-IDF framework. This capability allows your ESP32 to engage in low-latency, bidirectional conversations with servers, opening doors for applications like live data streaming, remote control systems, interactive dashboards, and much more. We will cover both standard WebSockets (ws) and secure WebSockets (wss), ensuring your data remains confidential.
Theory
What are WebSockets?
The WebSocket protocol, standardized by the IETF as RFC 6455, enables a two-way interactive communication session (full-duplex) between a user’s browser (or any client) and a server. Unlike HTTP, where a new connection is typically established for each request-response cycle, WebSockets maintain a single, long-lived TCP connection. Once this connection is established, both the client and the server can send data to each other independently and simultaneously, with minimal overhead.
Key Advantages of WebSockets:
- Low Latency: Data can be sent and received much faster than with HTTP polling because the connection is already open.
- Full-Duplex Communication: Both client and server can send messages at any time without waiting for the other.
- Reduced Overhead: After the initial handshake, WebSocket messages have smaller framing data compared to HTTP requests, leading to less network traffic.
- Efficiency: Reduces server load and network congestion compared to techniques like long polling.
Feature | WebSockets | Traditional HTTP Polling |
---|---|---|
Communication Model | Full-duplex (bidirectional) | Request-response (unidirectional per cycle) |
Connection Persistence | Single, long-lived TCP connection | New connection or keep-alive for each poll |
Latency | Low, near real-time | Higher, dependent on polling interval |
Overhead | Minimal after initial handshake (small frame headers) | Higher (full HTTP headers for each poll) |
Efficiency | High, reduces server load and network traffic | Lower, can lead to unnecessary requests and server strain |
Server Push | Server can send data proactively at any time | Server waits for client poll to send updates |
WebSockets are ideal for applications requiring continuous data exchange, such as:
- Live sensor data dashboards (e.g., temperature, humidity).
- Real-time notifications and alerts.
- Remote control and monitoring of IoT devices.
- Multiplayer online games.
- Collaborative applications.
Application Category | Description | ESP32 Use Case Example |
---|---|---|
Live Data Streaming | Continuous transmission of data as it’s generated. | ESP32 sending sensor readings (temperature, humidity, motion) to a real-time dashboard. |
Real-Time Notifications | Instant alerts and updates pushed from server to client. | ESP32 receiving alerts for critical events (e.g., security breach, system failure). |
Remote Control & Monitoring | Bidirectional control and status updates for devices. | Controlling an ESP32-based robot or home automation system remotely and receiving status feedback. |
Interactive Dashboards | User interfaces that reflect live data and allow interaction. | A web dashboard displaying ESP32 operational data and allowing users to send commands. |
Multiplayer/Interactive Systems | Low-latency communication for shared experiences. | Simple networked games or collaborative tools where ESP32s interact. |
Chat Applications | Instant messaging between users or devices. | ESP32 acting as a client in a chat system, sending/receiving messages. |
The WebSocket Handshake
A WebSocket connection begins with an HTTP-compatible handshake. The client sends an HTTP GET request to the server, including specific headers that indicate an intention to upgrade the connection from HTTP to WebSocket.
Key headers in the client’s handshake request:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key
: A randomly generated Base64-encoded value. This key is not for security but helps prevent misconfigured proxies from caching the response.Sec-WebSocket-Version
: Specifies the WebSocket protocol version, typically13
.Origin
(optional but common): Indicates the origin of the WebSocket request, helping the server prevent unauthorized cross-origin requests.Sec-WebSocket-Protocol
(optional): A comma-separated list of application-level subprotocols the client wishes to use, ordered by preference.
Header | Example Value | Purpose |
---|---|---|
Upgrade |
websocket |
Indicates the client’s desire to upgrade the protocol to WebSocket. |
Connection |
Upgrade |
Signals that this is an upgrade request for the connection itself. |
Sec-WebSocket-Key |
dGhlIHNhbXBsZSBub25jZQ== |
A randomly generated Base64-encoded value. Used by the server to generate the Sec-WebSocket-Accept response header, preventing proxy caching issues. Not for security. |
Sec-WebSocket-Version |
13 |
Specifies the WebSocket protocol version the client wishes to use (typically 13). |
Origin (Optional) |
http://example.com |
Indicates the origin of the WebSocket request, used by the server for security (e.g., to prevent unauthorized cross-origin requests). |
Sec-WebSocket-Protocol (Optional) |
chat-v1, json |
A comma-separated list of application-level subprotocols the client supports, in order of preference. |
If the server supports WebSockets and agrees to the upgrade, it responds with an HTTP 101 Switching Protocols
status code.
Key headers in the server’s handshake response:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept
: A value derived from the client’sSec-WebSocket-Key
and a specific globally unique identifier (GUID) defined in the WebSocket RFC. This confirms that the server understood the WebSocket handshake.Sec-WebSocket-Protocol
(optional): If the client requested subprotocols and the server supports one, this header indicates the chosen subprotocol.
Header | Example Value | Purpose |
---|---|---|
HTTP Status Code | 101 Switching Protocols |
Indicates the server agrees to upgrade the connection to WebSocket. |
Upgrade |
websocket |
Confirms the protocol upgrade to WebSocket. |
Connection |
Upgrade |
Confirms the connection upgrade. |
Sec-WebSocket-Accept |
s3pPLMBiTxaQ9kYGzzhZRbK+xOo= |
A value derived by hashing the client’s Sec-WebSocket-Key with a standard GUID. Confirms the server understood the WebSocket handshake. |
Sec-WebSocket-Protocol (Optional) |
json |
If the client requested subprotocols and the server supports one, this header indicates the subprotocol chosen by the server from the client’s list. |
%%{init: {"sequence": {"showSequenceNumbers": true}} }%% sequenceDiagram participant Client participant Server Client->>+Server: HTTP GET /chat HTTP/1.1<br>Host: server.example.com<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==<br>Sec-WebSocket-Version: 13<br>[Other headers...] Server-->>-Client: HTTP/1.1 101 Switching Protocols<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=<br>[Other headers...] Note over Client,Server: TCP connection is now a WebSocket channel Client-)Server: Send WebSocket Frame (e.g., Text) Server-)Client: Send WebSocket Frame (e.g., Text) Client-)Server: Send WebSocket Frame (e.g., Binary)
Once the handshake is successful, the TCP connection is repurposed for WebSocket traffic, and both client and server can start sending WebSocket data frames.
WebSocket Frames
Data transmitted over a WebSocket connection is structured into frames. Each frame has a specific format that includes metadata about the payload.
Basic Frame Structure:
- FIN bit (1 bit): Indicates if this is the final fragment of a message. A message can be split into multiple frames.
- RSV1, RSV2, RSV3 bits (1 bit each): Reserved for extensions. Must be 0 unless an extension is negotiated.
- Opcode (4 bits): Defines how to interpret the payload data. Common opcodes:
0x0
: Continuation frame0x1
: Text frame (payload is UTF-8 text)0x2
: Binary frame (payload is binary data)0x8
: Connection close frame0x9
: Ping frame (for keep-alive)0xA
: Pong frame (response to a Ping)
Opcode (Hex) | Opcode (Decimal) | Frame Type | Description |
---|---|---|---|
0x0 |
0 | Continuation | Indicates that this frame is a continuation of a fragmented message. |
0x1 |
1 | Text | The payload data is UTF-8 encoded text. |
0x2 |
2 | Binary | The payload data is arbitrary binary data. |
0x3 – 0x7 |
3-7 | Reserved | Reserved for future non-control frame opcodes. |
0x8 |
8 | Connection Close | Signals the termination of the WebSocket connection. May contain a body indicating the reason for closure. |
0x9 |
9 | Ping | A control frame used to check if the connection is still alive (heartbeat). The recipient should respond with a Pong frame. |
0xA |
10 | Pong | A control frame sent in response to a Ping frame. Can also be sent unsolicited as a unidirectional heartbeat. |
0xB – 0xF |
11-15 | Reserved | Reserved for future control frame opcodes. |
- MASK bit (1 bit): Indicates whether the payload is masked. All frames sent from the client to the server must be masked. Frames from server to client must not be masked.
- Payload length (7 bits, 7+16 bits, or 7+64 bits): The length of the payload data.
- Masking key (0 or 4 bytes): If MASK bit is 1, this contains the 32-bit key used to mask the payload.
- Payload data (variable length): The actual application data. If masked, the data is XORed with the masking key.

The ESP-IDF esp_websocket_client
component handles the complexities of framing and deframing, so you typically work with sending and receiving payload data directly.
Subprotocols
WebSockets can support application-level subprotocols. During the handshake, the client can suggest a list of subprotocols it supports using the Sec-WebSocket-Protocol
header. If the server also supports one or more of these, it will select one and indicate its choice in its handshake response. This allows for more structured communication if both client and server agree on a specific message format or convention (e.g., json
, mqtt
, chat-v1
).
Security: WebSocket Secure (WSS)
WebSocket connections can be secured using Transport Layer Security (TLS), similar to how HTTP is secured with HTTPS. A secure WebSocket connection uses the wss://
URI scheme instead of ws://
. The WSS handshake involves establishing a TLS layer over the TCP connection before the HTTP upgrade request is sent. This encrypts all WebSocket traffic, protecting it from eavesdropping and tampering.
%%{init: {"sequence": {"showSequenceNumbers": true}} }%% sequenceDiagram participant Client participant Server activate Client activate Server Note over Client,Server: 1. TCP Connection Establishment Client->>Server: TCP SYN Server->>Client: TCP SYN-ACK Client->>Server: TCP ACK Note over Client,Server: 2. TLS Handshake (over TCP) Client->>Server: ClientHello (TLS parameters, ciphersuites) Server->>Client: ServerHello (Chosen ciphersuite), Certificate, ServerHelloDone Client->>Server: ClientKeyExchange, ChangeCipherSpec, EncryptedHandshakeMessage Server->>Client: ChangeCipherSpec, EncryptedHandshakeMessage Note over Client,Server: TLS Encrypted Tunnel Established Note over Client,Server: 3. WebSocket Handshake (over TLS encrypted tunnel) Client->>Server: HTTP GET /chat HTTP/1.1 (Encrypted)<br>Host: server.example.com<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==<br>... Server->>Client: HTTP/1.1 101 Switching Protocols (Encrypted)<br>Upgrade: websocket<br>Connection: Upgrade<br>Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=<br>... Note over Client,Server: Secure WebSocket (WSS) Channel Ready Client-)Server: Send Encrypted WebSocket Frame Server-)Client: Send Encrypted WebSocket Frame deactivate Client deactivate Server
Implementing WSS on the ESP32 requires managing TLS certificates, as covered in Chapter 95: “Certificate Management and Verification.”
ESP-IDF esp_websocket_client
API
The ESP-IDF provides the esp_websocket_client
component to simplify WebSocket client development. It handles the handshake, framing, PING/PONG keep-alives, and data transfer.
Key Structures and Functions:
esp_websocket_client_config_t
: This structure is used to configure the WebSocket client. Important fields include:
Field Name | Type | Description |
---|---|---|
uri |
const char* |
Full WebSocket server URI (e.g., “ws://echo.websocket.events/.ws”, “wss://secure.server.com/path”). Takes precedence over host/port/path. |
host |
const char* |
Server hostname or IP address. Used if uri is NULL. |
port |
int |
Server port number. Used if uri is NULL. |
path |
const char* |
Server path (e.g., “/.ws”). Used if uri is NULL. Defaults to “/” if NULL. |
event_handle |
esp_websocket_event_handler_t |
Deprecated. Use esp_websocket_register_events() instead. Pointer to the event handler function. |
user_context |
void* |
User-defined context data passed to the event handler. |
subprotocol |
const char* |
Comma-separated string specifying desired subprotocols (e.g., “json,chat”). |
user_agent |
const char* |
User-Agent header string for the HTTP handshake. |
headers |
const char* |
Custom headers for the HTTP handshake, formatted as “Header1: Value1\r\nHeader2: Value2\r\n”. |
disable_auto_reconnect |
bool |
Set to true to disable automatic reconnection attempts. Default is false (auto-reconnect enabled). |
network_timeout_ms |
int |
Timeout for network operations in milliseconds. Default is 10000 ms. |
cert_pem |
const char* |
Pointer to the server’s CA certificate PEM string for WSS connections. Set to NULL for WS. |
client_cert_pem |
const char* |
Pointer to client certificate PEM string for client-side TLS authentication (mTLS). |
client_key_pem |
const char* |
Pointer to client private key PEM string for client-side TLS authentication (mTLS). |
task_stack |
int |
Stack size for the internal WebSocket client task. Default is 4096 bytes. WSS may require more. |
task_prio |
int |
Priority for the internal WebSocket client task. Default is 5. |
skip_cert_common_name_check |
bool |
For WSS, set to true to skip server certificate common name (CN) check. Use with caution, for testing only. |
transport |
esp_websocket_transport_t |
Transport type (WEBSOCKET_TRANSPORT_OVER_TCP , WEBSOCKET_TRANSPORT_OVER_SSL ). Automatically inferred from URI scheme (ws:// or wss://) if not set. |
esp_websocket_client_handle_t
: An opaque handle representing the WebSocket client instance, returned byesp_websocket_client_init()
.esp_websocket_client_init(const esp_websocket_client_config_t *config)
: Initializes the WebSocket client with the given configuration and returns a client handle.esp_websocket_register_events(esp_websocket_client_handle_t client, esp_websocket_event_id_t event, esp_event_handler_t event_handler, void *event_handler_arg)
: Registers an event handler for specific WebSocket events. This is the preferred way to handle events.- Events (
esp_websocket_event_id_t
):WEBSOCKET_EVENT_CONNECTED
: Fired when the WebSocket connection is successfully established.WEBSOCKET_EVENT_DISCONNECTED
: Fired when the connection is closed.WEBSOCKET_EVENT_DATA
: Fired when data is received from the server. The event data (esp_websocket_event_data_t*
) contains:data_ptr
: Pointer to the received data.data_len
: Length of the received data in this frame.op_code
: Opcode of the received frame (e.g., TEXT, BINARY).payload_len
: Total length of the message payload (if fragmented).payload_offset
: Offset of this fragment in the total payload.
WEBSOCKET_EVENT_ERROR
: Fired when an error occurs.WEBSOCKET_EVENT_BEFORE_CONNECT
: Fired before the client attempts to connect. Allows for last-minute configuration. (Less commonly used for basic setups).
- Events (
Event ID | Description | Event Data (esp_websocket_event_data_t* ) Fields |
---|---|---|
WEBSOCKET_EVENT_CONNECTED |
Fired when the WebSocket connection is successfully established (after handshake). |
client : client handlesession_handle : session handle (internal)
|
WEBSOCKET_EVENT_DISCONNECTED |
Fired when the connection is closed by either client or server, or due to network issues. |
client : client handle
|
WEBSOCKET_EVENT_DATA |
Fired when data is received from the server. |
client : client handledata_ptr : Pointer to received data buffer.data_len : Length of data in data_ptr for this frame/fragment.op_code : Opcode of the received frame (e.g., TEXT, BINARY, PING, PONG, CLOSE).payload_len : Total length of the message payload (relevant if fragmented).payload_offset : Offset of this fragment within the total payload (if fragmented).fin : FIN bit from the frame (true if this is the final fragment).
|
WEBSOCKET_EVENT_ERROR |
Fired when an error occurs (e.g., connection failure, TLS handshake error). |
client : client handleerror_handle : Pointer to esp_tls_error_handle_t containing error details (if TLS error).esp_ws_error_type_t : WebSocket specific error type.
|
WEBSOCKET_EVENT_BEFORE_CONNECT |
Fired just before the client attempts to establish a connection. Allows for last-minute configuration changes to the client handle. |
client : client handle
|
WEBSOCKET_EVENT_ANY |
A special identifier to register a handler for all WebSocket events. | Varies depending on the specific event that occurred. |
esp_websocket_client_start(esp_websocket_client_handle_t client)
: Starts the WebSocket client connection process. This function creates a dedicated task to handle WebSocket communication.esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
: Sends a text message to the server.len
: Length of the data. If -1,strlen(data)
is used.
esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
: Sends a binary message to the server.esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout, esp_websocket_client_opcodes_t op_code)
: A more generic send function allowing specification of the opcode.esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout)
: Sends a WebSocket close frame and waits for the server to acknowledge.esp_websocket_client_stop(esp_websocket_client_handle_t client)
: Stops the WebSocket client, closes the connection abruptly if necessary, and frees resources associated with the connection task.esp_websocket_client_destroy(esp_websocket_client_handle_t client)
: Destroys the WebSocket client instance and frees all allocated resources.
Summary of the Functions for ESP Websocket:
Function Name | Return Type | Brief Description |
---|---|---|
esp_websocket_client_init(config) |
esp_websocket_client_handle_t |
Initializes the WebSocket client with the given configuration. Returns a client handle or NULL on failure. |
esp_websocket_register_events(client, event, handler, arg) |
esp_err_t |
Registers an event handler function for specific WebSocket events or all events (WEBSOCKET_EVENT_ANY ). |
esp_websocket_client_start(client) |
esp_err_t |
Starts the WebSocket client connection process. Creates a dedicated task for communication. |
esp_websocket_client_send_text(client, data, len, timeout) |
int |
Sends a text message (UTF-8) to the server. Returns bytes sent or -1 on error. len = -1 for auto strlen. |
esp_websocket_client_send_bin(client, data, len, timeout) |
int |
Sends a binary message to the server. Returns bytes sent or -1 on error. |
esp_websocket_client_send(client, data, len, timeout, opcode) |
int |
Generic send function allowing specification of the opcode (e.g., for PING, PONG, fragmented messages). |
esp_websocket_client_send_fin(client, data, len, timeout, opcode) |
int |
Sends data with a specific opcode and FIN bit set to true. Useful for sending the final frame of a fragmented message. |
esp_websocket_client_close(client, timeout) |
esp_err_t |
Sends a WebSocket CLOSE frame and waits for the server to acknowledge. Graceful shutdown. |
esp_websocket_client_stop(client) |
esp_err_t |
Stops the WebSocket client, closes the connection (abruptly if needed), and frees resources associated with the connection task. |
esp_websocket_client_destroy(client) |
esp_err_t |
Destroys the WebSocket client instance and frees all allocated resources. Must be called after esp_websocket_client_stop() . |
esp_websocket_client_is_connected(client) |
bool |
Checks if the WebSocket client is currently connected. |
Event Handling Flow:
The typical flow involves:
%%{init: {"flowchart": {"htmlLabels": true}} }%% graph TD A[Start: Define Event Handler Function] --> B("Initialize Client:<br><b>esp_websocket_client_init()</b>"); B --> C{Client Handle OK?}; C -- Yes --> D("Register Event Handler:<br><b>esp_websocket_register_events()</b><br>for WEBSOCKET_EVENT_ANY or specific events"); C -- No --> Z[Error: Initialization Failed]; D --> E("Start Client:<br><b>esp_websocket_client_start()</b>"); E --> F{Start OK?}; F -- No --> Y[Error: Start Failed, Destroy Client]; F -- Yes --> G[Client Task Attempts Connection...]; subgraph "Event Loop (Handled by Client Task & Your Handler)" G --> H{Connection Attempt}; H -- Success --> I(<b>WEBSOCKET_EVENT_CONNECTED</b><br>Triggered); H -- Failure --> J(<b>WEBSOCKET_EVENT_ERROR</b><br>Triggered); I --> K["User Code in Handler:<br>e.g., Send initial message<br><b>esp_websocket_client_send_text()</b>"]; K --> L{Waiting for Server Data /<br>User Actions}; L -- Server Sends Data --> M(<b>WEBSOCKET_EVENT_DATA</b><br>Triggered); M --> N["User Code in Handler:<br>Process data (data_ptr, data_len, op_code)<br>Handle fragmentation (payload_len, payload_offset)"]; N --> L; L -- User Calls Send --> O("<b>esp_websocket_client_send_text()</b> / _bin()"); O --> L; L -- Network Issue / Server Close --> P(<b>WEBSOCKET_EVENT_DISCONNECTED</b><br>Triggered); P --> Q["User Code in Handler:<br>Cleanup, attempt reconnect?"]; Q --> R{Auto-Reconnect Enabled?}; R -- Yes --> G; R -- No --> S[Client Stopped or Needs Manual Restart]; J --> Q; end S --> T("Stop Client:<br><b>esp_websocket_client_stop()</b>"); T --> U("Destroy Client:<br><b>esp_websocket_client_destroy()</b>"); U --> V[End]; Y --> U; classDef default fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef event fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef error fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; class A,V startEnd; class B,D,E,G,K,N,O,Q,S,T,U process; class C,F,H,L,R decision; class I,J,M,P event; class Z,Y error;
- Defining an event handler function.
- Initializing the client with
esp_websocket_client_init()
. - Registering the event handler for desired events using
esp_websocket_register_events()
. - Starting the client with
esp_websocket_client_start()
. - The client attempts to connect. Upon success,
WEBSOCKET_EVENT_CONNECTED
is triggered. - Use
esp_websocket_client_send_text()
oresp_websocket_client_send_bin()
to send data. - When data arrives from the server,
WEBSOCKET_EVENT_DATA
is triggered. - Handle
WEBSOCKET_EVENT_ERROR
andWEBSOCKET_EVENT_DISCONNECTED
for robust operation. - When done, call
esp_websocket_client_stop()
and thenesp_websocket_client_destroy()
.
Practical Examples
Before running these examples, ensure your ESP32 is configured to connect to a Wi-Fi network. Refer to Volume 2, Chapter 28: “WiFi Station Connection Flow” for setting up Wi-Fi.
Example 1: Basic WebSocket Client (Echo)
This example demonstrates connecting to a public WebSocket echo server (ws://echo.websocket.events/.ws
), sending a message, and receiving the echo.
1. Project Setup and CMakeLists.txt
:
Create a new ESP-IDF project. In your main component’s CMakeLists.txt
(e.g., main/CMakeLists.txt
), ensure you have the following components listed:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES esp_wifi esp_event esp_log nvs_flash netif_stack esp_netif esp_websocket_client)
You also need to ensure that esp_websocket_client
is added as a requirement in your project’s main CMakeLists.txt
if it’s not already implicitly included. Typically, listing it in the component’s REQUIRES
is sufficient.
2. main.c
Implementation:
#include <stdio.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_websocket_client.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h" // For semaphores if needed for synchronization
// Define your Wi-Fi credentials
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
static const char *TAG = "WEBSOCKET_CLIENT_EXAMPLE";
// Global handle for the WebSocket client
static esp_websocket_client_handle_t client = NULL;
// Semaphore to signal connection
static SemaphoreHandle_t connection_semaphore;
// WebSocket Event Handler
static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
switch (event_id) {
case WEBSOCKET_EVENT_CONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
// Signal that connection is established
if (connection_semaphore != NULL) {
xSemaphoreGive(connection_semaphore);
}
// Send a test message
const char *msg = "Hello ESP32 WebSocket Client!";
ESP_LOGI(TAG, "Sending message: %s", msg);
esp_websocket_client_send_text(client, msg, strlen(msg), portMAX_DELAY);
break;
case WEBSOCKET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
// Optionally, try to reconnect or clean up
break;
case WEBSOCKET_EVENT_DATA:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
if (data->op_code == 0x08 && data->data_len == 2) { // Close frame
ESP_LOGW(TAG, "Received closed frame with code=%d", (data->data_ptr[0] << 8) | data->data_ptr[1]);
} else {
ESP_LOGI(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
// If the message is fragmented, data->payload_len will be the total length
// and data->payload_offset will be the offset of this fragment.
ESP_LOGI(TAG, "Total payload length=%d, data_len=%d, current payload_offset=%d\r\n",
data->payload_len, data->data_len, data->payload_offset);
// Example: Echo back if it's a text message
if (data->op_code == WS_TRANSPORT_OPCODES_TEXT) {
// char *response = (char *)malloc(data->data_len + 1);
// strncpy(response, (char*)data->data_ptr, data->data_len);
// response[data->data_len] = 0; // Null-terminate
// ESP_LOGI(TAG, "Echoing back: %s", response);
// esp_websocket_client_send_text(client, response, strlen(response), portMAX_DELAY);
// free(response);
}
}
break;
case WEBSOCKET_EVENT_ERROR:
ESP_LOGE(TAG, "WEBSOCKET_EVENT_ERROR");
// Log the error code if available
if (data->error_handle) {
ESP_LOGE(TAG, "Error reason: %s", esp_err_to_name(data->error_handle->error_type));
}
break;
default:
ESP_LOGI(TAG, "Unhandled WebSocket event: %d", event_id);
break;
}
}
static void websocket_app_start(void) {
connection_semaphore = xSemaphoreCreateBinary();
if (connection_semaphore == NULL) {
ESP_LOGE(TAG, "Failed to create connection semaphore");
return;
}
esp_websocket_client_config_t websocket_cfg = {
.uri = "ws://echo.websocket.events/.ws", // Public echo server
// .host = "echo.websocket.events", // Alternative to uri
// .port = 80, // Alternative to uri
// .path = "/.ws", // Alternative to uri
.user_agent = "ESP32 WebSocket Client/1.0",
.task_prio = 5,
.task_stack = 4096,
// .disable_auto_reconnect = true, // Uncomment to disable auto-reconnect
};
ESP_LOGI(TAG, "Initializing WebSocket client to URI: %s", websocket_cfg.uri);
client = esp_websocket_client_init(&websocket_cfg);
if (client == NULL) {
ESP_LOGE(TAG, "Failed to initialize WebSocket client");
xSemaphoreDelete(connection_semaphore);
connection_semaphore = NULL;
return;
}
// Register event handlers
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
ESP_LOGI(TAG, "Starting WebSocket client...");
esp_err_t err = esp_websocket_client_start(client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WebSocket client: %s", esp_err_to_name(err));
esp_websocket_client_destroy(client);
client = NULL;
xSemaphoreDelete(connection_semaphore);
connection_semaphore = NULL;
return;
}
ESP_LOGI(TAG, "Waiting for connection...");
// Wait for connection event (optional, for synchronous behavior in this example)
if (xSemaphoreTake(connection_semaphore, pdMS_TO_TICKS(10000)) == pdTRUE) {
ESP_LOGI(TAG, "Connected to WebSocket server.");
// You can send more messages here or in response to other events/tasks
// For this example, the first message is sent in WEBSOCKET_EVENT_CONNECTED
} else {
ESP_LOGE(TAG, "Failed to connect to WebSocket server within timeout.");
// Cleanup if connection failed
esp_websocket_client_stop(client);
esp_websocket_client_destroy(client);
client = NULL;
}
// The client task will keep running and handling events.
// xSemaphoreDelete(connection_semaphore); // Delete if only used for initial connect
// connection_semaphore = NULL;
}
// Basic Wi-Fi Connection (Simplified - adapt from Volume 2 for robustness)
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
ESP_LOGI(TAG, "Attempting to connect to Wi-Fi...");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected from Wi-Fi. Retrying...");
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Wi-Fi connected. Starting WebSocket client application...");
websocket_app_start(); // Start WebSocket client after Wi-Fi is connected
}
}
void wifi_init_sta(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // Adjust as per your network
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
}
void app_main(void) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WEBSOCKET_CLIENT_EXAMPLE");
// Initialize Wi-Fi
wifi_init_sta();
// The WebSocket client will be started by the Wi-Fi event handler upon successful connection.
// If you need to stop and destroy the client later:
// if (client) {
// ESP_LOGI(TAG, "Stopping and destroying client...");
// esp_websocket_client_stop(client);
// esp_websocket_client_destroy(client);
// client = NULL;
// if(connection_semaphore) xSemaphoreDelete(connection_semaphore);
// }
}
3. Build and Flash:
- Replace
"YOUR_WIFI_SSID"
and"YOUR_WIFI_PASSWORD"
with your Wi-Fi credentials. - Open VS Code, ensure your ESP32 is connected.
- Use the Espressif IDF Extension:
- Select the correct ESP32 target (e.g., ESP32, ESP32-S3).
- Click “Build”.
- Click “Flash”.
- Click “Monitor”.
4. Observe Output:
You should see log messages indicating Wi-Fi connection, then WebSocket client initialization, connection to the server, sending “Hello ESP32 WebSocket Client!”, and receiving the same message back from the echo server.
...
I (WEBSOCKET_CLIENT_EXAMPLE): Got IP address: 192.168.1.100
I (WEBSOCKET_CLIENT_EXAMPLE): Wi-Fi connected. Starting WebSocket client application...
I (WEBSOCKET_CLIENT_EXAMPLE): Initializing WebSocket client to URI: ws://echo.websocket.events/.ws
I (WEBSOCKET_CLIENT_EXAMPLE): Starting WebSocket client...
I (WEBSOCKET_CLIENT_EXAMPLE): Waiting for connection...
I (WEBSOCKET_CLIENT_EXAMPLE): WEBSOCKET_EVENT_CONNECTED
I (WEBSOCKET_CLIENT_EXAMPLE): Sending message: Hello ESP32 WebSocket Client!
I (WEBSOCKET_CLIENT_EXAMPLE): Connected to WebSocket server.
I (WEBSOCKET_CLIENT_EXAMPLE): WEBSOCKET_EVENT_DATA
I (WEBSOCKET_CLIENT_EXAMPLE): Received opcode=1
I (WEBSOCKET_CLIENT_EXAMPLE): Received=Hello ESP32 WebSocket Client!
I (WEBSOCKET_CLIENT_EXAMPLE): Total payload length=28, data_len=28, current payload_offset=0
...
Tip: The
echo.websocket.events
server is a public service. Its availability or behavior might change. You can set up your own WebSocket server for more reliable testing (see Chapter 97: WebSocket Server Implementation).
Example 2: Secure WebSocket Client (WSS)
This example demonstrates connecting to a secure WebSocket echo server (wss://echo.websocket.events/.ws
).
1. Obtain Server CA Certificate:
For WSS, the client needs to verify the server’s identity using its CA certificate.
You can obtain the CA certificate for echo.websocket.events using your web browser or OpenSSL:
openssl s_client -showcerts -connect echo.websocket.events:443 < /dev/null 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
Usually, the last certificate in the chain presented by s_client
is the root CA, or an intermediate CA. You need the root CA that your ESP32 should trust. For echo.websocket.events
, it’s often a common public CA like Let’s Encrypt’s ISRG Root X1 or another widely trusted root.
For simplicity in this example, we will embed the server’s certificate directly.
The certificate for echo.websocket.events (ISRG Root X1) can be found online.
Here is an example of how it would look (this is a placeholder, get the actual current one):
// Example CA Certificate for echo.websocket.events (ISRG Root X1)
// -----BEGIN CERTIFICATE-----
// MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
// ... (rest of the certificate data) ...
// -----END CERTIFICATE-----
static const char *server_cert_pem_start = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
// ... (Insert the actual PEM data here, properly formatted as a C string) ...
"-----END CERTIFICATE-----\n";
Warning: Embedding certificates directly in code increases firmware size. For production, consider using the ESP-IDF certificate bundle feature or NVS for certificate storage if certificates change often. See Chapter 95 for more on certificate management.
2. Modify websocket_app_start()
:
Update the esp_websocket_client_config_t
in your websocket_app_start
function:
// ... (previous includes and definitions) ...
// CA certificate for wss://echo.websocket.events/.ws
// This is ISRG Root X1, which signed Let's Encrypt certificates.
// Obtain the current valid certificate for the server you are connecting to.
// You can often find this by inspecting the certificate in your browser
// when visiting https://echo.websocket.events
static const char *echo_websocket_events_cert_pem = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \
"h77ct984kIxuPOZXoHj3AntSoXUrrSg4KeGslPeXrV7wPzYpLxxe2Nl9gMQppDLP\n" \
"1e8o+vjfnGssl54+sy2eFVdABEgt6vkbydxJSqgSkCZl9AjDJOgEAgX7pyMdElAo\n" \
"qOI3gHwstl80SAFbchhtNCe0QayZรรvb2ACAAkCAwEAAaNCMEAwDwYDVR0T\n" \
"AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQrwrNA0sPq5NB2\n" \
"lOb3r72js8YGDANBgkqhkiG9w0BAQsFAAOCAgEAM3PJ6xYGB+uP9G+N2deK9s7w\n" \
"095/8G6u6gSTuA73zyZkSquY0yX4slpHQ9svVfT38KkGD7rSR2S1AIWXFjY64SQh\n" \
"PAnP2rAlOxdxZ3Z1mXNCMV7a4ab1oFqG7rScvj0bFHN1j0Sry42qEod229qJ9lG6\n" \
"AUCAm2hAIZ2bLx2z6dp0zXYgs3e1H2oDfZGpeMWhBayoKDVZ9g2w7IEOtTz3gQoB\n" \
"L9gC8FzID14E2DLDU2M91jH6zG00K2y7NmYmY+M72V7b7LBlPZ7gdSVVEVBV1WVF\n" \
"jVHMUHGXm2TvtM2cTqbXdgClRjddLGfT0r4I+DqL1xND2aF+HNL7N5oH26ZtKiCM\n" \
"gEJC/+3u9xVb7b9OzU14B2iH23nU+92zT00LD3zH80Un4S6E8LMgGciGLJ0iWOT2\n" \
"G6A7QYmKjOsgyjV0R2XyPjLzH2nNqMso6uRAtP4kG4Pyz3nB2WpL0vqv92MIsWIm\n" \
"GZaszF79SggyLAkAM6LqCSgZOh4s1686pX0THkYxMM2fJmPvClht00pDkGMAg0k+\n" \
"iIKPZk0Da4RzU2002j9z/s1UfL8hbftVry4Q1QjUeNl2z00w00Z0000000000000\n" \
"0000000000000000000000000000000000000000000000000000000000000000\n" \
"000000000000000000000000000000000000000000000000000000000000000\n" \
"-----END CERTIFICATE-----\n";
// Note: The above certificate is illustrative. Ensure you have the correct and current one.
// The string needs to be properly escaped if it contains quotes or backslashes.
static void websocket_secure_app_start(void) {
connection_semaphore = xSemaphoreCreateBinary(); // Assuming declared globally or passed
esp_websocket_client_config_t websocket_cfg = {
.uri = "wss://echo.websocket.events/.ws", // Use WSS URI
.cert_pem = echo_websocket_events_cert_pem, // Pointer to CA cert
// .skip_cert_common_name_check = true, // For testing if CN mismatch, not recommended for production
.user_agent = "ESP32 WSS Client/1.0",
.task_prio = 5,
.task_stack = 6144, // WSS might need more stack
};
ESP_LOGI(TAG, "Initializing Secure WebSocket client to URI: %s", websocket_cfg.uri);
client = esp_websocket_client_init(&websocket_cfg);
if (client == NULL) {
ESP_LOGE(TAG, "Failed to initialize WSS client");
if(connection_semaphore) xSemaphoreDelete(connection_semaphore);
connection_semaphore = NULL;
return;
}
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
ESP_LOGI(TAG, "Starting Secure WebSocket client...");
esp_err_t err = esp_websocket_client_start(client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WSS client: %s", esp_err_to_name(err));
esp_websocket_client_destroy(client);
client = NULL;
if(connection_semaphore) xSemaphoreDelete(connection_semaphore);
connection_semaphore = NULL;
return;
}
ESP_LOGI(TAG, "Waiting for secure connection...");
if (xSemaphoreTake(connection_semaphore, pdMS_TO_TICKS(20000)) == pdTRUE) { // Increased timeout for WSS
ESP_LOGI(TAG, "Connected to Secure WebSocket server.");
} else {
ESP_LOGE(TAG, "Failed to connect to Secure WebSocket server within timeout.");
esp_websocket_client_stop(client);
esp_websocket_client_destroy(client);
client = NULL;
}
// if(connection_semaphore) xSemaphoreDelete(connection_semaphore);
// connection_semaphore = NULL;
}
In app_main
, call websocket_secure_app_start()
instead of websocket_app_start()
after Wi-Fi connects.
3. Build, Flash, and Observe:
The process is the same. You should see logs indicating a successful WSS connection. If there are errors, they often relate to the certificate (e.g., “esp-tls-mbedtls: handshake failed,” “Failed to verify peer certificate”).
Tip: For WSS, ensure your ESP32’s system time is reasonably accurate. While not always strictly enforced by all servers for basic validation, an incorrect time can cause issues with certificate validity checks (not before/not after dates). See Chapter 98: “SNTP and Time Synchronization.”
Variant Notes
The esp_websocket_client
component is part of the ESP-IDF and is generally compatible across ESP32 variants that support Wi-Fi or Ethernet connectivity, including:
- ESP32: Full support.
- ESP32-S2: Full support. Has hardware crypto acceleration which can benefit WSS performance.
- ESP32-S3: Full support. Also has hardware crypto acceleration. Its dual-core architecture can handle networking tasks efficiently.
- ESP32-C3: Full support. Being a single-core RISC-V MCU, resource management (RAM, CPU for WSS) might be more critical for complex applications. Hardware crypto acceleration is present.
- ESP32-C6: Full support. Wi-Fi 6 capable. RISC-V architecture with hardware crypto. Similar resource considerations to ESP32-C3 for intensive applications.
- ESP32-H2: This variant primarily features IEEE 802.15.4 (Thread, Zigbee) and Bluetooth 5 LE. It does not have built-in Wi-Fi or Ethernet. Therefore, directly using the
esp_websocket_client
(which relies on a TCP/IP stack typically provided by Wi-Fi/Ethernet) is not straightforward.- To use WebSockets with an ESP32-H2, it would typically need to communicate through a gateway device that bridges its 802.15.4 network to an IP network (Wi-Fi/Ethernet). The WebSocket client would run on the gateway or the ESP32-H2 would use a different protocol (e.g., CoAP, MQTT over 6LoWPAN) to talk to the gateway, which then communicates via WebSockets.
- Alternatively, if an external Ethernet PHY is somehow interfaced and an IP stack is brought up (highly custom for H2), then
esp_websocket_client
could theoretically be used. However, this is not a standard use case for ESP32-H2.
General Considerations for all variants:
- Memory: WSS connections, due to TLS, consume more RAM (for TLS buffers, certificate storage) and Flash (for TLS libraries and certificates) compared to WS connections. This is particularly relevant for variants with less available RAM like the ESP32-C3.
- Processing Power: TLS handshakes are computationally intensive. Variants with hardware cryptographic acceleration (most modern ESP32s) perform better.
- Stack Size: The task running the WebSocket client, especially for WSS, might require a larger stack size. The default in
esp_websocket_client_config_t
is usually sufficient, but for complex event handlers or applications, you might need to increasetask_stack
. The example for WSS uses6144
bytes.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Incorrect WebSocket URI Scheme |
Connection fails immediately. Error logs might mention “invalid argument” or protocol errors. |
Mistake: Using http:// or https:// .Fix: Always use ws:// for unencrypted WebSockets and wss:// for secure WebSockets in the uri field of esp_websocket_client_config_t .
|
Wi-Fi Not Connected / No IP Address |
WEBSOCKET_EVENT_ERROR occurs.Logs may show “DNS lookup failed”, “Network is unreachable”, or TCP connection timeouts. |
Mistake: Calling esp_websocket_client_start() before Wi-Fi is fully connected and an IP address is obtained.Fix: Ensure Wi-Fi connection is established (e.g., wait for IP_EVENT_STA_GOT_IP event) before initializing and starting the WebSocket client.
|
WSS Certificate Issues |
For WSS connections (wss:// ):WEBSOCKET_EVENT_ERROR occurs during handshake.Logs show TLS errors like esp-tls-mbedtls: handshake failed , Failed to verify peer certificate , or Certificate common name mismatch .
|
Mistake: cert_pem is NULL , points to an incorrect/expired CA certificate, or server’s certificate CN doesn’t match URI.Fix:
|
Firewall Blocking Connections |
Connection attempts time out. No response from server. |
Mistake: Network firewall (local, corporate, or ISP) blocking outbound connections on WebSocket port (typically 80 for ws , 443 for wss ).Fix: Ensure firewall allows outbound TCP connections to the server’s IP and port. Test from a different network (e.g., mobile hotspot) if possible. |
Insufficient Task Stack or Memory |
Client task crashes (Guru Meditation Error). Stack overflow errors in logs. malloc failures.
|
Mistake: WebSocket client task (especially for WSS due to TLS) runs out of stack space or heap memory. Fix:
|
Not Handling Events Correctly |
Application becomes unresponsive on disconnects. Fails to recover from errors. Missed incoming messages. |
Mistake: Missing or incomplete handlers for WEBSOCKET_EVENT_ERROR , WEBSOCKET_EVENT_DISCONNECTED , or WEBSOCKET_EVENT_DATA .Fix: Implement robust logic in the event handler:
|
Blocking Operations in Event Handler |
WebSocket client seems unresponsive. Watchdog timeouts. Delayed or missed PING/PONG leading to disconnects. |
Mistake: Performing long-running tasks (file I/O, complex calculations, long delays) directly within the WebSocket event handler callback. Fix: Offload lengthy processing to a separate FreeRTOS task. The event handler should do minimal work: copy data, set a flag, or send an event/item to another task’s queue. |
Server Not Responding or Misconfigured |
Connection refused. Handshake failure (e.g., server sends unexpected HTTP status). Server closes connection unexpectedly. |
Mistake: Issue is on the server side (not running, wrong port, incorrect WebSocket implementation, resource limits). Fix:
|
Exercises
- Connect to a Different Echo Server:
- Find another public WebSocket echo server (e.g.,
ws://websockets.chilkat.io/wsChilkatEcho.ashx
). - Modify the basic client example to connect to this new server, send a message, and verify the echo.
- Find another public WebSocket echo server (e.g.,
- Periodic Sensor Data Transmission:
- Create a FreeRTOS task that simulates reading a sensor (e.g., generates a random number or reads an actual sensor if available).
- Modify the WebSocket client to send this “sensor reading” as a text message to the echo server every 5 seconds after a successful connection. Ensure the WebSocket client is connected before attempting to send.
- JSON Message Handling:
- Assume the WebSocket server sends messages formatted as JSON strings, e.g.,
{"command": "set_led", "state": 1}
. - In the
WEBSOCKET_EVENT_DATA
handler, check if the received data is text. - Use a JSON parsing library (ESP-IDF includes
cJSON
component, add it toREQUIRES
inCMakeLists.txt
) to parse the incoming message. - Extract and log the values of “command” and “state”.
- Bonus: Send a JSON formatted response back, e.g.,
{"status": "received", "command_echo": "set_led"}
.
- Assume the WebSocket server sends messages formatted as JSON strings, e.g.,
- Secure WSS Client to
socketsbay.com
:- The server
wss://socketsbay.com/wss/v2/1/demo/
is a public WSS echo server. - Obtain its CA certificate (similar to how you did for
echo.websocket.events
). - Modify the WSS client example to connect to this server.
- Send a message and verify the echo. Pay attention to any certificate-related logs. This server might use a different CA than
echo.websocket.events
.
- The server
Summary
- WebSockets provide a persistent, full-duplex communication channel over a single TCP connection, ideal for real-time applications.
- The connection starts with an HTTP-based handshake to upgrade the protocol.
- Data is transmitted in frames, with opcodes defining the data type (text, binary, control frames like ping/pong/close).
- WSS (WebSocket Secure) uses TLS to encrypt communication, requiring certificate management.
- ESP-IDF provides the
esp_websocket_client
component for simplified WebSocket client implementation. - Key steps include configuring (
esp_websocket_client_config_t
), initializing (esp_websocket_client_init
), registering event handlers (esp_websocket_register_events
), starting (esp_websocket_client_start
), sending data (esp_websocket_client_send_text
/_bin
), and handling events (connected, disconnected, data, error). - Proper error handling and resource management are crucial for robust WebSocket applications.
- Most ESP32 variants with Wi-Fi/Ethernet support the WebSocket client, with resource considerations for WSS on more constrained devices. ESP32-H2 typically requires a gateway for WebSocket communication.
Further Reading
- ESP-IDF WebSocket Client Documentation: Navigate to the API reference section for
esp_websocket_client.h
in your local ESP-IDF installation docs or on the Espressif website. (e.g.,https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/protocols/esp_websocket_client.html
) - RFC 6455 – The WebSocket Protocol: The official specification for deep understanding. (
https://tools.ietf.org/html/rfc6455
) - MDN Web Docs – WebSockets: A good general overview of WebSockets from a web development perspective. (
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
) - cJSON Component Documentation: For Exercise 3, refer to the cJSON documentation usually found within the ESP-IDF components directory or its official repository. (
https://github.com/DaveGamble/cJSON
)