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, typically 13.
  • 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’s Sec-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 frame
    • 0x1: Text frame (payload is UTF-8 text)
    • 0x2: Binary frame (payload is binary data)
    • 0x8: Connection close frame
    • 0x9: 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.
0x30x7 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.
0xB0xF 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 by esp_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).
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 handle
session_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 handle
data_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 handle
error_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;
  1. Defining an event handler function.
  2. Initializing the client with esp_websocket_client_init().
  3. Registering the event handler for desired events using esp_websocket_register_events().
  4. Starting the client with esp_websocket_client_start().
  5. The client attempts to connect. Upon success, WEBSOCKET_EVENT_CONNECTED is triggered.
  6. Use esp_websocket_client_send_text() or esp_websocket_client_send_bin() to send data.
  7. When data arrives from the server, WEBSOCKET_EVENT_DATA is triggered.
  8. Handle WEBSOCKET_EVENT_ERROR and WEBSOCKET_EVENT_DISCONNECTED for robust operation.
  9. When done, call esp_websocket_client_stop() and then esp_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:

Plaintext
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:

C
#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:

  1. Replace "YOUR_WIFI_SSID" and "YOUR_WIFI_PASSWORD" with your Wi-Fi credentials.
  2. Open VS Code, ensure your ESP32 is connected.
  3. 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.

Plaintext
...
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:

Bash
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):

Plaintext
// 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:

C
// ... (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 increase task_stack. The example for WSS uses 6144 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:
  • Provide the correct and valid CA certificate for the server via cert_pem in esp_websocket_client_config_t.
  • Ensure ESP32 system time is reasonably accurate (see SNTP chapter).
  • For testing ONLY, set skip_cert_common_name_check = true (insecure for production).
  • Increase ESP-TLS log verbosity for detailed error messages.
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:
  • Increase task_stack in esp_websocket_client_config_t (e.g., to 6144 or 8192 for WSS).
  • Monitor free heap (esp_get_free_heap_size()) and task stack high water mark (uxTaskGetStackHighWaterMark()).
  • Optimize memory usage in event handlers and other parts of the application.
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:
  • For errors/disconnects: attempt reconnection (if auto-reconnect is disabled or for custom logic), clean up, log details.
  • For data: process received data, handle fragmentation if payload_len > data_len.
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:
  • Verify server is running and accessible (e.g., using a browser-based WebSocket client tool).
  • Check server logs for errors.
  • Ensure server supports the WebSocket version (13) and any subprotocols requested.

Exercises

  1. 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.
  2. 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.
  3. 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 to REQUIRES in CMakeLists.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"}.
  4. 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.

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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top