ESP8266 Websocket server: How to control GPIO Pins

This tutorial covers how to implement an ESP8266 Websocket server to control ESP8266 GPIO Pins. In more detail, we will describe how to build a web page that controls ESP8266 Pins using Websocket. To do it, we will use a simple LED that can be turned on or off remotely. The ESP8266 Websocket server will be developed using PlatformIO.

Project overview

To describe how to enable a real-time data transfer from the web page to the ESP8266 Websocket server, we will suppose that we want to create a web page that has a switch to turn on or off a LED controlling the GPIO Pin. The pin status code is sent using the Websocket protocol. We have used this example in several posts and you can read them if you want to know the different approaches we can use when we have to send data from a client to a server:

The image below describes the project we are going go build:

ESP8266 websocket server: how to control GPIO pins and notify clients using websocket

If you don’t need a two way data exchange, you can use ESP8266 with SSE.

Setting up the Project

Let’s create a new PlatformIO project setting as development board ESP8266 Dev and add the following libraries in the platformio.ini file:

lib_deps = ESPAsyncTCP ESP Async WebServer

This ESP8266 Websocket project uses the followng libraries:

If you prefer you can test the project without using the LEDS.

ESP8266 Websocket Server source code

To implement a Websocket Server using ESP8266 we need the following source code:

#include <Arduino.h> #include <ESP8266WiFi.h> #include <ESPAsyncTCP.h> #include <ESPAsyncWebServer.h> // WiFi config const char *SSID = "your_wifi_ssid"; const char *PWD = "your_wifi_password"; #define LED_PIN 5 const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP8266 Websocket</title> <script language="javascript"> function check() { val = document.getElementById("s1").checked; if (val) webSocket.send('1'); else webSocket.send('0'); } var gwUrl = "ws://" + location.host + "/ws"; var webSocket = new WebSocket(gwUrl); webSocket.onopen = function(e) { console.log("open"); } webSocket.onclose = function(e) { console.log("close"); } webSocket.onmessage = function(e) { console.log("message"); var data = e.data; if (data == '1') document.getElementById('s1').checked = true; else document.getElementById('s1').checked = false; } </script> <style> h2 {background: #3285DC; color: #FFFFFF; align:center; } .content { border: 1px solid #164372; padding: 5px; } .button { background-color: #00b300; border: none; color: white; padding: 8px 10px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; } /* The switch - the box around the slider */ .switch { position: relative; display: inline-block; width: 60px; height: 34px; } /* Hide default HTML checkbox */ .switch input { opacity: 0; width: 0; height: 0; } /* The slider */ .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: .4s; transition: .4s; } .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .4s; transition: .4s; } input:checked + .slider { background-color: #2196F3; } input:focus + .slider { box-shadow: 0 0 1px #2196F3; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); } /* Rounded sliders */ .slider.round { border-radius: 34px; } .slider.round:before { border-radius: 50%; } </style> </head> <body> <h2>ESP8266 Websocket</h2> <div class="content"> <p><h4>Turn on Led</h4> <label class="switch"> <input type="checkbox" id="s1" onchange="check()" /> <span class="slider round"></span> </label> </p> </div> </body> </html> )rawliteral"; // Web server running on port 80 AsyncWebServer server(80); // Web socket AsyncWebSocket ws("/ws"); void connectToWiFi() { Serial.print("Connecting to "); Serial.println(SSID); WiFi.begin(SSID, PWD); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); // we can even make the ESP8266 to sleep } Serial.print("Connected. IP: "); Serial.println(WiFi.localIP()); } void notifyClient(int status) { ws.textAll(String((char) status)); } void handlingIncomingData(void *arg, uint8_t *data, size_t len) { AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { if (data[0] == '1') digitalWrite(LED_PIN, HIGH); else digitalWrite(LED_PIN, LOW); notifyClient(data[0]); } } // Callback for incoming event void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ switch(type) { case WS_EVT_CONNECT: Serial.printf("Client connected: \n\tClient id:%u\n\tClient IP:%s\n", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Serial.printf("Client disconnected:\n\tClient id:%u\n", client->id()); break; case WS_EVT_DATA: handlingIncomingData(arg, data, len); break; case WS_EVT_PONG: Serial.printf("Pong:\n\tClient id:%u\n", client->id()); break; case WS_EVT_ERROR: Serial.printf("Error:\n\tClient id:%u\n", client->id()); break; } } void setup() { Serial.begin(9600); connectToWiFi(); ws.onEvent(onEvent); server.addHandler(&ws); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, NULL); }); server.begin(); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); } void loop() { ws.cleanupClients(); }
Code language: C++ (cpp)

Below the code explanation.

Configuring the AsyncWebServer and the AsyncWebSocket

The ESP8266 has to create two different servers:

  • AsyncWebServer that responds on the port 80 and provides the Web page so that the user can pick the color
  • AsyncWebSocket to handle the Websocket protocol between the web browser and the ESP8266 server
// Web server running on port 80 AsyncWebServer server(80); // Web socket AsyncWebSocket ws("/ws");
Code language: C++ (cpp)

In this way the ESP8266 handles the websocket channel on the path /ws.

Handling Websocket events on the ESP8266

Once the connection is established between the ESP8266 and the browser, it is necessary to handle a set of Websocket events such as:

  • connection
  • disconnection

and so on. The code is shown below:

// Callback for incoming event void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ switch(type) { case WS_EVT_CONNECT: Serial.printf("Client connected: \n\tClient id:%u\n\tClient IP:%s\n", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Serial.printf("Client disconnected:\n\tClient id:%u\n", client->id()); break; case WS_EVT_DATA: handlingIncomingData(arg, data, len); break; case WS_EVT_PONG: Serial.printf("Pong:\n\tClient id:%u\n", client->id()); break; case WS_EVT_ERROR: Serial.printf("Error:\n\tClient id:%u\n", client->id()); break; } }
Code language: C++ (cpp)

As you can notice, we simply log the events without taking any actions except when a new data packet arrives. This data packet, sent by the browser, holds the RGB color information. We handle this event in the handleIncomingData(..) Here we will handle the GPIO Pin status:

void handlingIncomingData(void *arg, uint8_t *data, size_t len) { AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { if (data[0] == '1') digitalWrite(LED_PIN, HIGH); else digitalWrite(LED_PIN, LOW); notifyClient(data[0]); } }
Code language: C++ (cpp)

How to notify the clients the GPIO Pin status using Websocket

Once the ESP8266 has changed the LED status, it is necessary to notify all other clients that the LED has a new status so that the web page switches the control accordingly:

void notifyClient(int status) { ws.textAll(String((char) status)); }
Code language: C++ (cpp)

In this way, the ESP8266 broadcasts the new GPIO Pin status to all the clients.

How to provide the Web page

The ESP8266 has to provide the Web page to the browser that will have all the logic in javascript to handle the web socket. As you remember, the ESP8266 behaves not only as a WebSocket server but also as a Web server listening on port 80:

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, NULL); });
Code language: C++ (cpp)

Setup() method in the ESP8266 Websocket server

The last part is gluing all the pieces so that the Webserver and the Websocket server starts correctly and the ESP8266 connects to the WiFi:

void setup() { Serial.begin(9600); connectToWiFi(); ws.onEvent(onEvent); server.addHandler(&ws); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, NULL); }); server.begin(); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); } void loop() { ws.cleanupClients(); }
Code language: C++ (cpp)

This piece of code is very simple.

Web page with Javascript to handle the Websocket

The ESP8266 provides a web page that is loaded by the browser as it connects to the Web server. This page implements the logic to handle the Websocket channel:

<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP8266 Websocket</title> <script language="javascript"> function check() { val = document.getElementById("s1").checked; if (val) webSocket.send('1'); else webSocket.send('0'); } var gwUrl = "ws://" + location.host + "/ws"; var webSocket = new WebSocket(gwUrl); webSocket.onopen = function(e) { console.log("open"); } webSocket.onclose = function(e) { console.log("close"); } webSocket.onmessage = function(e) { console.log("message"); var data = e.data; if (data == '1') document.getElementById('s1').checked = true; else document.getElementById('s1').checked = false; } </script> <style> h2 {background: #3285DC; color: #FFFFFF; align:center; } .content { border: 1px solid #164372; padding: 5px; } .button { background-color: #00b300; border: none; color: white; padding: 8px 10px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; } /* The switch - the box around the slider */ .switch { position: relative; display: inline-block; width: 60px; height: 34px; } /* Hide default HTML checkbox */ .switch input { opacity: 0; width: 0; height: 0; } /* The slider */ .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: .4s; transition: .4s; } .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .4s; transition: .4s; } input:checked + .slider { background-color: #2196F3; } input:focus + .slider { box-shadow: 0 0 1px #2196F3; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); } /* Rounded sliders */ .slider.round { border-radius: 34px; } .slider.round:before { border-radius: 50%; } </style> </head> <body> <h2>ESP8266 Websocket</h2> <div class="content"> <p><h4>Turn on Led</h4> <label class="switch"> <input type="checkbox" id="s1" onchange="check()" /> <span class="slider round"></span> </label> </p> </div> </body> </html>
Code language: HTML, XML (xml)

This is how the web page looks in your smartphone:

ESP8266 Websocket server controlling GPIO Pins

If we inspect the HTML page content, we can notice an interesting piece of code:

<script language="javascript"> function check() { val = document.getElementById("s1").checked; if (val) webSocket.send('1'); else webSocket.send('0'); } var gwUrl = "ws://" + location.name + "/ws"; var webSocket = new WebSocket(gwUrl); webSocket.onopen = function(e) { console.log("open"); } webSocket.onclose = function(e) { console.log("close"); } webSocket.onmessage = function(e) { console.log("message"); var data = e.data; if (data == '1') document.getElementById('s1').checked = true; else document.getElementById('s1').checked = false; } </script>
Code language: JavaScript (javascript)

This the code that handles the WebSocket protocol between the web browser and the ESP8266 server. Notice that we use the path ws:// to connect to the server. Moreover, in this piece of code, we handle several events as we did previously in the ESP8266 websocket server code. At the end, the code sends the color to the server:

webSocket.send('1');
Code language: JavaScript (javascript)

or

webSocket.send('0');
Code language: JavaScript (javascript)

You should notice that we have implemented the onmessage() function to handle the WebSocket server notification. In this function, the web page sets the switch status according to the GPIO Pin if it was changed by another client.

Wrapping up

At the end of this tutorial, you have learned how to implement an ESP8266 Websocket server. We have discovered how to send real-time data from a web browser to the ESP8266 using the WebSocket protocol. Now you can develop a more complex use case using WebSocket protocol to exchange data from the web browser to the ESP8266 WebSocket server.