The main class for creating and managing web servers.
UnoR4WiFi_WebServer(int port = 80)
Creates a web server instance on the specified port (default: 80).
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)
void setNotFoundHandler(RouteHandler handler)
Sets a custom handler for 404 Not Found responses.
Prints WiFi connection status and IP address to Serial Monitor.
Processes incoming client requests. This should be called repeatedly in the main loop.
void on(const String &uri, HTTPMethod method, THandlerFunction fn)
void on(const String &uri, THandlerFunction fn)
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.
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:
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);
}
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;
}
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);
}
Query Parameters Handler:
void handleLedControl(WiFiClient& client, const String& method, const String& request,
const QueryParams& params, const String& jsonData) {
if (method == "GET") {
String action = params.getValue("action");
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);
}
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:
server.sendResponse(client, "<h1>Hello World</h1>");
server.sendResponse(client, "{\"status\":\"ok\"}", "application/json");
server.sendResponse(client, "Success", "text/plain");
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\"}");
}
The QueryParams object contains parsed query parameters from the URL:
struct QueryParams {
int count;
struct {
const char* key;
const char* value;
} params[MAX_PARAMS];
}
void handleWithParams(WiFiClient& client, const String& method, const String& request,
const QueryParams& params, const String& jsonData) {
String unit = "C";
for (int i = 0; i < params.count; i++) {
if (String(params.params[i].key) == "unit") {
unit = params.params[i].value;
break;
}
}
String response = "Unit selected: " + unit;
server.sendResponse(client, response.c_str());
}
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;
}
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");
}
}
WebSocketServer wsServer(81);
Alias for net::WebSocketServer - simplified for beginner use.
Alias for net::WebSocket - represents a WebSocket connection.
Starts the WebSocket server.
Processes WebSocket events. Call this in your main loop.
void onConnection([](WebSocket &ws) {
});
Sets callback for new WebSocket connections.
void onMessage([](WebSocket &ws, const WebSocket::DataType dataType, const char *message, uint16_t length) {
});
Sets callback for incoming WebSocket messages.
void onClose([](WebSocket &ws, const WebSocket::CloseCode code, const char *reason, uint16_t length) {
});
Sets callback for WebSocket connection closures.
void send(const String &message)
void send(const char *message, size_t length)
Sends a message through the WebSocket.
Closes the WebSocket connection.
void broadcastTXT(const char* payload)
void broadcastTXT(const String& payload)
Broadcasts text message to all connected WebSocket clients.
void broadcastBIN(const uint8_t* payload, size_t length)
Broadcasts binary data to all connected WebSocket clients.
size_t connectedClients()
Returns the number of currently connected WebSocket clients.
Returns true if the WebSocket server is actively listening for connections.
Standard WebSocket close codes for connection termination reasons.
void setup() {
Serial.begin(9600);
WiFi.begin(ssid, password);
server.begin();
wsServer.begin();
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");
});
}
void handleWebSocketMessage(WebSocket &ws, const char *message, uint16_t length) {
String msg = String(message);
Serial.println("Received: " + msg);
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\"}");
}
}
String response = "Echo: " + msg;
ws.send(response.c_str());
}
void loop() {
server.handleClient();
wsServer.loop();
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();
}
}
The library supports standard HTTP methods:
HTTP_GET
HTTP_POST
HTTP_PUT
HTTP_DELETE
HTTP_PATCH
HTTP_HEAD
HTTP_OPTIONS
const ws = new WebSocket('ws:
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) {
console.log('Text message:', event.data);
}
};
ws.onclose = function(event) {
console.log('Disconnected from Arduino');
document.getElementById('status').textContent = 'Disconnected';
setTimeout(connectWebSocket, 3000);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
function controlLED(action) {
if (ws.readyState === WebSocket.OPEN) {
const command = {
type: 'led',
action: action,
timestamp: Date.now()
};
ws.send(JSON.stringify(command));
}
}
function requestSensorData() {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({type: 'get_sensor'}));
}
}
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);
}
}
wsServer.onMessage([](WebSocket &ws, const WebSocket::DataType dataType,
const char *message, uint16_t length) {
String response = "Echo: " + String(message);
ws.send(response.c_str());
});
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());
}
}
void setupHeartbeat() {
static unsigned long lastHeartbeat = 0;
if (millis() - lastHeartbeat > 30000) {
if (wsServer.connectedClients() > 0) {
String heartbeat = "{\"type\":\"heartbeat\",\"timestamp\":" + String(millis()) + "}";
wsServer.broadcastTXT(heartbeat);
}
lastHeartbeat = millis();
}
}
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
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("============================");
}
void monitorPerformance() {
static unsigned long lastCheck = 0;
static int messageCount = 0;
messageCount++;
if (millis() - lastCheck > 10000) {
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
Enable cross-origin requests for web applications:
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);
});
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");
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);
}
sendJsonResponse(200, "{\"status\":\"success\",\"data\":\"value\"}");
sendError(400, "Invalid request format");
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;
}
});
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;
}
const char* action = doc["action"] | "none";
int value = doc["value"] | 0;
bool enabled = doc["enabled"] | false;
StaticJsonDocument<200> response;
response["status"] = "success";
response["received_action"] = action;
response["received_value"] = value;
String responseString;
serializeJson(response, responseString);
sendJsonResponse(200, responseString);
}
The library provides a default 404 error page. You can override it:
server.onNotFound([]() {
server.send(404, "text/html", "<h1>Custom 404 Page</h1>");
});
Memory Management: Use F() macro for string literals stored in flash memory
Non-blocking Code: Keep handler functions lightweight to avoid blocking the server
Security: Validate input parameters and sanitize output
Performance: Use appropriate HTTP status codes and content types
WebSocket: Handle connection states properly and implement reconnection logic
Enable serial debugging to monitor server activity:
void setup() {
Serial.begin(9600);
}
void loop() {
server.handleClient();
if (Serial.available()) {
String command = Serial.readString();
Serial.println("Debug: " + command);
}
}
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
Maximum simultaneous HTTP connections: 4 (hardware limitation)
Maximum URL length: 256 characters
Template placeholders: No nested replacements
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)
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