Arduino Uno R4 WiFi Web Server Library Reference

Overview

The UnoR4WiFi_WebServer library provides a comprehensive solution for creating multi-page web servers with WebSocket support on Arduino Uno R4 WiFi and DIYables STEM V4 IoT boards.

Hardware Required

1×Arduino UNO R4 WiFi
1×Alternatively, DIYables STEM V4 IoT
1×USB Cable Type-C
Disclosure: Some links in this section are Amazon affiliate links. If you make a purchase through these links, we may earn a commission at no extra cost to you.
Additionally, some links direct to products from our own brand, DIYables .

Installation

Quick Steps

Follow these instructions step by step:

  • If this is your first time using the Arduino Uno R4 WiFi/Minima, refer to the tutorial on setting up the environment for Arduino Uno R4 WiFi/Minima in the Arduino IDE.
  • Connect the Arduino Uno R4 board to your computer using a USB cable.
  • Launch the Arduino IDE on your computer.
  • Select the appropriate Arduino Uno R4 board (e.g., Arduino Uno R4 WiFi) and COM port.
  • Open the Library Manager by clicking on the Library Manager icon on the left side of the Arduino IDE.
  • Search for Web Server for Arduino Uno R4 WiFi and locate the mWebSockets by DIYables.
  • Click on the Install button to add the mWebSockets library.

https://arduinogetstarted.com/images/tutorial/arduino-uno-r4-web-server-library.jpg

WebSocket Support (Built-in)

WebSocket functionality is now integrated directly into this library!

The WebSocket implementation is based on the excellent mWebSockets library by skaarj1989, which has been integrated and optimized specifically for Arduino Uno R4 WiFi to make it easy to use:

  • No additional library installation required - WebSocket support is built-in
  • Optimized for Arduino Uno R4 WiFi - Simplified platform-specific implementation
  • WiFiS3 compatibility - Uses Arduino Uno R4's native WiFi stack
  • RFC 6455 compliant - Full WebSocket protocol support
  • Real-time communication - Bidirectional data exchange

Usage:

  • For everything (Web Server + WebSocket): Use #include <UnoR4WiFi_WebServer.h>
  • That's it! - WebSocket functionality is automatically available when needed
  • Memory efficient - Unused WebSocket code is automatically optimized out by the compiler

Credits: WebSocket implementation adapted from mWebSockets library (LGPL-2.1 License) by skaarj1989, modified and integrated for seamless Arduino Uno R4 WiFi compatibility.

Library Classes

UnoR4WiFi_WebServer Class

The main class for creating and managing web servers.

Constructor

UnoR4WiFi_WebServer(int port = 80)

Creates a web server instance on the specified port (default: 80).

Methods

begin()
void begin() void begin(const char* ssid, const char* password)

Starts the web server and begins listening for incoming connections.

Overloaded versions:

  • begin(): Start server only (WiFi must be connected separately by your application)
  • begin(ssid, password): Connect to WiFi and start server in one call (legacy approach)
setNotFoundHandler()
void setNotFoundHandler(RouteHandler handler)

Sets a custom handler for 404 Not Found responses.

printWifiStatus()
void printWifiStatus()

Prints WiFi connection status and IP address to Serial Monitor.

handleClient()
void handleClient()

Processes incoming client requests. This should be called repeatedly in the main loop.

on()
void on(const String &uri, HTTPMethod method, THandlerFunction fn) void on(const String &uri, THandlerFunction fn) // defaults to GET

Registers a handler function for a specific URI and HTTP method.

Parameters:

  • uri: The URI path (e.g., "/", "/led", "/api/data")
  • method: HTTP method (GET, POST, PUT, DELETE, etc.)
  • fn: Handler function to execute when the route is accessed

Note: This library uses addRoute() method instead of on(). See below for correct usage.

addRoute()
void addRoute(const String &uri, RouteHandler handler)

Registers a handler function for a specific URI. This is the actual method used in the library.

RouteHandler Function Format:

Route handlers must follow this exact signature:

void handlerFunction(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData)

Parameters:

  • client: WiFiClient reference for sending responses
  • method: HTTP method as string ("GET", "POST", etc.)
  • request: Full request URI
  • params: Query parameters (QueryParams object)
  • jsonData: JSON payload for POST requests (empty for GET)

Handler Implementation Examples:

  1. Basic GET Handler:
void handleHome(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { String response = "<html><body><h1>Hello World</h1></body></html>"; server.sendResponse(client, response.c_str()); } } void setup() { server.addRoute("/", handleHome); }
  1. JSON API Handler (GET and POST):
void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "POST") { if (jsonData.length() == 0) { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"No JSON data received\"}"); return; } // Process JSON data StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, jsonData); if (error) { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Invalid JSON\"}"); return; } const char* key = doc["key"] | "none"; String response = "{\"status\":\"success\",\"received_key\":\"" + String(key) + "\"}"; server.sendResponse(client, response.c_str(), "application/json"); } else if (method == "GET") { String response = "{\"status\":\"success\",\"message\":\"GET request received\"}"; server.sendResponse(client, response.c_str(), "application/json"); } else { client.println("HTTP/1.1 405 Method Not Allowed"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Method not allowed\"}"); } } void setup() { server.addRoute("/api/data", handleApiData); }
  1. Query Parameters Handler:
void handleLedControl(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { // Access query parameters String action = params.getValue("action"); // ?action=on if (action == "on") { digitalWrite(LED_PIN, HIGH); server.sendResponse(client, "LED turned ON"); } else if (action == "off") { digitalWrite(LED_PIN, LOW); server.sendResponse(client, "LED turned OFF"); } else { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: text/plain"); client.println("Connection: close"); client.println(); client.print("Invalid action. Use ?action=on or ?action=off"); } } } void setup() { server.addRoute("/led", handleLedControl); }
sendResponse()
void sendResponse(WiFiClient& client, const char* content, const char* contentType = "text/html")

Sends an HTTP response to the client.

Parameters:

  • client: WiFiClient reference (provided in handler function)
  • content: Response body content
  • contentType: MIME type of the response (default: "text/html")

Usage Examples:

// Send HTML response server.sendResponse(client, "<h1>Hello World</h1>"); // Send JSON response server.sendResponse(client, "{\"status\":\"ok\"}", "application/json"); // Send plain text server.sendResponse(client, "Success", "text/plain");
Manual Response Sending

For more control over HTTP headers and status codes:

void sendCustomResponse(WiFiClient& client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Access-Control-Allow-Origin: *"); client.println("Connection: close"); client.println(); client.print("{\"custom\":\"response\"}"); }

Query Parameters Access

QueryParams Structure

The QueryParams object contains parsed query parameters from the URL:

struct QueryParams { int count; // Number of parameters struct { const char* key; // Parameter name const char* value; // Parameter value } params[MAX_PARAMS]; }

Accessing Query Parameters

void handleWithParams(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { // Access specific parameter by looping through String unit = "C"; // default value for (int i = 0; i < params.count; i++) { if (String(params.params[i].key) == "unit") { unit = params.params[i].value; break; } } // Use the parameter String response = "Unit selected: " + unit; server.sendResponse(client, response.c_str()); } // URL examples: // /temperature?unit=F // /led?state=on&brightness=50

Parameter Helper Functions

Create helper functions for easier parameter access:

String getParam(const QueryParams& params, const String& key, const String& defaultValue = "") { for (int i = 0; i < params.count; i++) { if (String(params.params[i].key) == key) { return String(params.params[i].value); } } return defaultValue; } bool hasParam(const QueryParams& params, const String& key) { for (int i = 0; i < params.count; i++) { if (String(params.params[i].key) == key) { return true; } } return false; } // Usage in handlers: void handleLed(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { String state = getParam(params, "state", "off"); int brightness = getParam(params, "brightness", "100").toInt(); if (state == "on") { digitalWrite(LED_PIN, HIGH); server.sendResponse(client, "LED turned ON with brightness " + String(brightness)); } else { digitalWrite(LED_PIN, LOW); server.sendResponse(client, "LED turned OFF"); } }

WebSocket Classes (Built-in)

WebSocketServer
WebSocketServer wsServer(81); // Port 81 for WebSocket

Alias for net::WebSocketServer - simplified for beginner use.

WebSocket
WebSocket ws;

Alias for net::WebSocket - represents a WebSocket connection.

WebSocket Methods
begin()
void begin()

Starts the WebSocket server.

loop()
void loop()

Processes WebSocket events. Call this in your main loop.

onConnection()
void onConnection([](WebSocket &ws) { // Handle new WebSocket connection });

Sets callback for new WebSocket connections.

onMessage()
void onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) { // Handle incoming WebSocket message });

Sets callback for incoming WebSocket messages.

onClose()
void onClose([](WebSocket &ws, const WebSocket::CloseCode code, const char *reason, uint16_t length) { // Handle WebSocket connection close });

Sets callback for WebSocket connection closures.

send()
void send(const String &message) void send(const char *message, size_t length)

Sends a message through the WebSocket.

close()
void close()

Closes the WebSocket connection.

Additional WebSocket Methods
broadcastTXT()
void broadcastTXT(const char* payload) void broadcastTXT(const String& payload)

Broadcasts text message to all connected WebSocket clients.

broadcastBIN()
void broadcastBIN(const uint8_t* payload, size_t length)

Broadcasts binary data to all connected WebSocket clients.

connectedClients()
size_t connectedClients()

Returns the number of currently connected WebSocket clients.

isListening()
bool isListening()

Returns true if the WebSocket server is actively listening for connections.

WebSocket Event Types
DataType Enum
  • WebSocket::DataType::TEXT - Text message type
  • WebSocket::DataType::BINARY - Binary data type
CloseCode Enum

Standard WebSocket close codes for connection termination reasons.

Advanced WebSocket Usage

Event Handler Setup

void setup() { Serial.begin(9600); // Initialize WiFi and servers WiFi.begin(ssid, password); server.begin(); wsServer.begin(); // Set up WebSocket event handlers wsServer.onConnection([](WebSocket &ws) { Serial.print("Client connected from: "); Serial.println(ws.getRemoteIP()); ws.send("{\"type\":\"welcome\",\"message\":\"Connected to Arduino\"}"); }); wsServer.onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) { handleWebSocketMessage(ws, message, length); }); wsServer.onClose([](WebSocket &ws, const WebSocket::CloseCode code, const char *reason, uint16_t length) { Serial.println("Client disconnected"); }); }

Message Processing

void handleWebSocketMessage(WebSocket &ws, const char *message, uint16_t length) { String msg = String(message); Serial.println("Received: " + msg); // JSON message handling if (msg.indexOf("\"type\":\"led\"") >= 0) { if (msg.indexOf("\"action\":\"on\"") >= 0) { digitalWrite(LED_PIN, HIGH); ws.send("{\"type\":\"led_status\",\"status\":\"on\"}"); } else if (msg.indexOf("\"action\":\"off\"") >= 0) { digitalWrite(LED_PIN, LOW); ws.send("{\"type\":\"led_status\",\"status\":\"off\"}"); } } // Echo functionality String response = "Echo: " + msg; ws.send(response.c_str()); }

Broadcasting Sensor Data

void loop() { server.handleClient(); wsServer.loop(); // Broadcast sensor data every 5 seconds static unsigned long lastBroadcast = 0; if (millis() - lastBroadcast > 5000) { if (wsServer.connectedClients() > 0) { float temperature = getTemperature(); String sensorData = "{"; sensorData += "\"type\":\"sensor\","; sensorData += "\"temperature\":" + String(temperature, 1) + ","; sensorData += "\"timestamp\":" + String(millis()); sensorData += "}"; wsServer.broadcastTXT(sensorData); } lastBroadcast = millis(); } }

HTTP Methods

The library supports standard HTTP methods:

  • HTTP_GET
  • HTTP_POST
  • HTTP_PUT
  • HTTP_DELETE
  • HTTP_PATCH
  • HTTP_HEAD
  • HTTP_OPTIONS

Client-Side JavaScript Integration

Basic WebSocket Connection

// Connect to Arduino WebSocket server const ws = new WebSocket('ws://your-arduino-ip:81'); ws.onopen = function(event) { console.log('Connected to Arduino WebSocket'); document.getElementById('status').textContent = 'Connected'; }; ws.onmessage = function(event) { console.log('Received:', event.data); try { const data = JSON.parse(event.data); handleArduinoMessage(data); } catch (e) { // Handle plain text messages console.log('Text message:', event.data); } }; ws.onclose = function(event) { console.log('Disconnected from Arduino'); document.getElementById('status').textContent = 'Disconnected'; // Auto-reconnect after 3 seconds setTimeout(connectWebSocket, 3000); }; ws.onerror = function(error) { console.error('WebSocket error:', error); };

Sending Commands

// Send LED control command function controlLED(action) { if (ws.readyState === WebSocket.OPEN) { const command = { type: 'led', action: action, timestamp: Date.now() }; ws.send(JSON.stringify(command)); } } // Send sensor request function requestSensorData() { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({type: 'get_sensor'})); } }

Message Handling

function handleArduinoMessage(data) { switch(data.type) { case 'sensor': updateTemperatureDisplay(data.temperature); break; case 'led_status': updateLEDStatus(data.status); break; case 'welcome': console.log('Welcome message:', data.message); break; default: console.log('Unknown message type:', data); } }

WebSocket Examples

Simple Echo Server

wsServer.onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) { String response = "Echo: " + String(message); ws.send(response.c_str()); });

JSON Command Processing

void processWebSocketCommand(WebSocket &ws, const String& message) { if (message.indexOf("\"type\":\"led\"") >= 0) { if (message.indexOf("\"action\":\"on\"") >= 0) { digitalWrite(LED_PIN, HIGH); ws.send("{\"type\":\"led_status\",\"status\":\"on\",\"success\":true}"); } else if (message.indexOf("\"action\":\"off\"") >= 0) { digitalWrite(LED_PIN, LOW); ws.send("{\"type\":\"led_status\",\"status\":\"off\",\"success\":true}"); } } else if (message.indexOf("\"type\":\"get_sensor\"") >= 0) { float temp = getTemperature(); String response = "{\"type\":\"sensor\",\"temperature\":" + String(temp, 1) + "}"; ws.send(response.c_str()); } }

Heartbeat Implementation

void setupHeartbeat() { static unsigned long lastHeartbeat = 0; if (millis() - lastHeartbeat > 30000) { // Every 30 seconds if (wsServer.connectedClients() > 0) { String heartbeat = "{\"type\":\"heartbeat\",\"timestamp\":" + String(millis()) + "}"; wsServer.broadcastTXT(heartbeat); } lastHeartbeat = millis(); } }

WebSocket Troubleshooting

Common Issues

WebSocket Connection Failed

  • Verify WebSocket server port (default: 81) is accessible
  • Check firewall settings on both client and Arduino network
  • Ensure Arduino IP address is correct and reachable
  • Use browser developer tools to check WebSocket connection errors

Messages Not Being Received

  • Check Serial Monitor for WebSocket event logs
  • Verify JSON message format is correct
  • Test with simple text messages before using JSON
  • Ensure message length doesn't exceed buffer limits

Connection Drops Frequently

  • Implement heartbeat/ping mechanism
  • Check WiFi signal strength and stability
  • Add connection retry logic in client code
  • Monitor memory usage on Arduino

High Memory Usage

  • Limit number of concurrent WebSocket connections
  • Clear message buffers regularly
  • Use efficient String handling (avoid frequent concatenation)
  • Monitor free heap memory

Debug Helpers

void debugWebSocket() { Serial.println("=== WebSocket Debug Info ==="); Serial.println("Connected clients: " + String(wsServer.connectedClients())); Serial.println("Server listening: " + String(wsServer.isListening() ? "Yes" : "No")); Serial.println("Free memory: " + String(ESP.getFreeHeap()) + " bytes"); Serial.println("Uptime: " + String(millis() / 1000) + " seconds"); Serial.println("============================"); }

Performance Monitoring

void monitorPerformance() { static unsigned long lastCheck = 0; static int messageCount = 0; messageCount++; if (millis() - lastCheck > 10000) { // Every 10 seconds Serial.println("Messages/10s: " + String(messageCount)); Serial.println("Clients: " + String(wsServer.connectedClients())); messageCount = 0; lastCheck = millis(); } }

The library supports HTML templates with placeholder replacement:

String response = HTML_TEMPLATE; response.replace("%TEMPERATURE%", String(temperature)); response.replace("%LED_STATUS%", ledStatus ? "ON" : "OFF"); server.send(200, "text/html", response);

Common placeholders:

  • %TEMPERATURE% - Temperature value
  • %LED_STATUS% - LED status
  • %QUERY_PARAM% - Query parameter values

Advanced Web Server Features

CORS Support

Enable cross-origin requests for web applications:

// Handle preflight OPTIONS requests server.on("/api/data", HTTP_OPTIONS, []() { server.sendHeader("Access-Control-Allow-Origin", "*"); server.sendHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); server.sendHeader("Access-Control-Allow-Headers", "Content-Type"); server.send(200); }); // Add CORS headers to all responses server.sendHeader("Access-Control-Allow-Origin", "*"); server.sendHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); server.sendHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");

JSON Response Helpers

Simplify JSON API development:

void sendJsonResponse(int statusCode, const String& json) { server.send(statusCode, "application/json", json); } void sendError(int statusCode, const String& message) { String error = "{\"error\":\"" + message + "\"}"; sendJsonResponse(statusCode, error); } // Usage sendJsonResponse(200, "{\"status\":\"success\",\"data\":\"value\"}"); sendError(400, "Invalid request format");

Request Validation

Implement robust input validation:

bool isValidJsonAction(const String& action) { return (action == "on" || action == "off" || action == "toggle"); } bool validateRequiredFields(const String& jsonData, const String& field) { return (jsonData.indexOf("\"" + field + "\":") >= 0); } server.on("/api/control", HTTP_POST, []() { if (!server.hasArg("plain")) { sendError(400, "JSON body required"); return; } String body = server.arg("plain"); if (!validateRequiredFields(body, "action")) { sendError(400, "Missing required field: action"); return; } // Process valid request... });

Enhanced JSON Processing

For complex JSON handling with ArduinoJson library:

#include <ArduinoJson.h> void handleJsonRequest() { String requestBody = server.arg("plain"); StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, requestBody); if (error) { sendError(400, "Invalid JSON format"); return; } // Extract values safely const char* action = doc["action"] | "none"; int value = doc["value"] | 0; bool enabled = doc["enabled"] | false; // Create response StaticJsonDocument<200> response; response["status"] = "success"; response["received_action"] = action; response["received_value"] = value; String responseString; serializeJson(response, responseString); sendJsonResponse(200, responseString); }

Error Handling

Default 404 Handler

The library provides a default 404 error page. You can override it:

server.onNotFound([]() { server.send(404, "text/html", "<h1>Custom 404 Page</h1>"); });

Best Practices

  1. Memory Management: Use F() macro for string literals stored in flash memory
  2. Non-blocking Code: Keep handler functions lightweight to avoid blocking the server
  3. Security: Validate input parameters and sanitize output
  4. Performance: Use appropriate HTTP status codes and content types
  5. WebSocket: Handle connection states properly and implement reconnection logic

Debugging

Enable serial debugging to monitor server activity:

void setup() { Serial.begin(9600); // ... rest of setup } void loop() { server.handleClient(); if (Serial.available()) { String command = Serial.readString(); Serial.println("Debug: " + command); } }

Compatibility

  • Arduino Uno R4 WiFi: Fully supported
  • DIYables STEM V4 IoT: Fully supported
  • WiFiS3 Library: Required (included with Arduino IDE)
  • Memory Requirements: Minimum 32KB flash, 2KB RAM

Limitations

Web Server Limitations

  • Maximum simultaneous HTTP connections: 4 (hardware limitation)
  • Maximum URL length: 256 characters
  • Template placeholders: No nested replacements

WebSocket Limitations

  • Maximum WebSocket message size: 1KB per message
  • Maximum concurrent WebSocket connections: 4-6 (depending on available memory)
  • Fragment messages: Automatically handled but may impact performance
  • Binary message size: Limited by available RAM
  • Connection timeout: 60 seconds default (configurable)

Memory Constraints

  • Minimum flash memory required: 32KB
  • Minimum RAM required: 2KB for basic functionality
  • WebSocket overhead: ~200-500 bytes per connection
  • Message buffering: ~1KB per active connection

ARDUINO BUY RECOMMENDATION

Arduino UNO R3
Arduino Starter Kit

※ OUR MESSAGES