How to use Websockets to control an ESP8266 and a Raspberry Pi with a web browser

For a current project of mine, I had to evaluate how high the latency is when using websockets. In my case, I wanted to use a small single board computer as the server and any device, that can run a web browser, as the client. This tutorial illustrates how a Raspberry Pi and an ESP8266 can be used to act as a websocket server that can be controlled with a standard webbrowser.

Using an ESP8266/ESP32

I used this library for the Arduino compatible version of a generic ESP8266 board. The code is pretty simple to understand, but I’ll explain the most important pieces step by step:

First, let’s start with some imports, defines and objects:

#include <ESP8266WiFi.h>
#include <WebSocketsServer.h>
#include <string>
#include <iostream>

#define LEFT_LED D1
#define RIGHT_LED D2

WebSocketsServer sock = WebSocketsServer(80);

char *ssid = "YOUR_WIFI_SSID";
char *pass = "YOUR_WIFI_PASSWORD";

I created a websocket server on port 80 and defined some additional values for two LEDs that will be controlled from a webpage. I chose this simple display method to test the delay between an input on the client and the action on the server side. Don’t forget to change ssid and pass!

void setup()
{
  Serial.begin(9600);
  
  pinMode(LEFT_LED, OUTPUT);
  pinMode(RIGHT_LED, OUTPUT);
  
  digitalWrite(LEFT_LED, LOW);
  digitalWrite(RIGHT_LED, LOW);

  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED)
    delay(250);

  Serial.printf("Connected to network: %s\n", ssid);
 
  // A client will connect to this server to
  // change the state of the LEDs
  Serial.print("Server started with address: ");
  Serial.print("ws://");
  Serial.print(WiFi.localIP());
  Serial.println("/");

  sock.begin();
  sock.onEvent(webSocketEvent);
}

As you can see, I set up the two digital pins for the LEDs and set both to low. Then, I connected to the supplied WIFI network and waited for the connection to be established. Then I printed the address of the websocket server. Note, that this library only supports unsecured connections.

After that, I started the socket server and added an event that will handle incoming messages:

void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
{
  IPAddress ip = sock.remoteIP(num);
  
  switch(type)
  {
      case WStype_DISCONNECTED:
          Serial.println("Disconnected!");
          break;
      case WStype_CONNECTED:
          Serial.printf("New Client: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
          break;
      case WStype_TEXT:
      {
          String s = (char*)payload;

          if(s == "ArrowLeft")
              digitalWrite(LEFT_LED, HIGH);
          if(s == "ArrowRight")
              digitalWrite(RIGHT_LED, HIGH);
          if(s == "ArrowRightUp")
              digitalWrite(RIGHT_LED, LOW);
          if(s == "ArrowLeftUp")
              digitalWrite(LEFT_LED, LOW);
          break;
      }
      default:
          break;
  }
}

This event method gets called whenever a websocket related event occurs. The switch block inside it determines what type of event was raised. I only handled three types: when a client connects, disconnects, or sends a mesage.

For now, let’s take a look at the parameter list above. The first parameter is the number of the client that raised the event. The second one describes the type of event that occured. The third one is a pointer to the data that was transmitted, if there’s any and the fourth parameter contains the length of the supplied data.

Back to the switch inside the method: For the first two cases, a simple text gets printed to the console. When a message was received, the contents get checked. Note, that this case only handles incoming plain text. Binary data will require a different case.

If the incoming text is: ArrowLeft, the left LED is turned on. If it’s ArrowLeftUp, it gets turned off again. The same goes for the right LED

The loop is the shortest part of this program as it simply contains one line:

void loop()
{
  sock.loop();
}

This will make sure, that the socket can handle incoming connections and disconnected clients properly.

A simple client that runs in any web browser

Before we can test the implementation from above, we’ll have to create a simple client. The goal was to control the LEDs from any web browser. And while there are a few frameworks for easily creating websockets, I chose to write this simple client with standard HTML and JavaScript. You can always extend the example and use a framework, if you want to.

However, the HTML code looks like this:

<html>
    <body>
        <div id="connectionStatusDiv">Not connected</div>
        <h2>WebSocket Testclient</h2>
        <div id="connectFormDiv">
            <input type="text" id="serverAddressTBox"> 
            <button onclick="connect()" id="connectBtn">Connect to Server</button>
	</div>
	<div id="messageFormDiv">
	    <input type="text" id="messageTBox" value=""><br/>
	    <button onclick="sendMessage()">Send</button>
	</div>
	<script src="./script.js"></script>
    </body>
</html>

As you can see, it’s fairly simple too. No nice formatting or stylesheets. The page will look something like this when not connected to a server:

Figure 1: The simple websocket client website

The script, linked at the end of the HTML file, contains a few methods. Let’s start with the connect method, which gets called when the button, displayed above, gets clicked:

function connect() {
	if(connected) {
		disconnect();
		return;
	}

	serverAddress = addressTBox.value;
	sock = new WebSocket("ws://" + serverAddress + "/");

	sock.onopen = function(even) {
		connected = true;
		infoDiv.innerHTML = "Connected to: " + serverAddress;
		addressTBox.disabled = true;
		connectBtn.firstChild.data = "Disconnect";
		messageFormDiv.style.visibility = "visible";
	};
}

As you can see, the method starts with an if-block that calls another method, called disconnect, when the button is pressed and the client is already connected. I did this, because the same button is used to connect and disconnect the client to/from a server.

The next two lines get the address from the input field and try to connect to a server. The connection itself happens asynchronously and therefore I added a callback function that gets executed when the connection was established.

The callback will change the website so it looks like this

Figure 2: The client website after it was connected to a server

Anyway, the disconnect method, mentioned above, looks like this:

function disconnect() {
	sock.close();
	infoDiv.innerHTML = "Not connected";
	addressTBox.disabled = false;
	connectBtn.firstChild.data = "Connect to Server";
	messageFormDiv.style.visibility = "hidden";
	connected = false;
}

It will, again, change the website so that it looks like fig. 1. Furthermore, it sets the connected flag back to false and disconnects from the server.

Now we can connect to the server and close the connection. What about sending messages? That’s done in the following method:

function sendMessage() {
	if(!connected)
		return;

	sock.send(messageTBox.value);
	messageTBox.value = "";
}

Furthermore, I wanted to turn the LEDs on and off by pressing and releasing the arrow keys on the keyboard. Therefore I added the following listeners:

function handleKeyDown(e) {
	if(e.code == "ArrowLeft" || e.code == "ArrowRight")
		sock.send(e.code);
}

function handleKeyUp(e) {
	if(e.code == "ArrowLeft" || e.code == "ArrowRight")
		sock.send(e.code + "Up");
}

Note, that a few things are missing in the code snippets from above, for example the variables used in the JS script. You can download all code files at the end of this article.

A python websocket server on the Raspberry Pi

Of course, you can also implement the program form above on the Raspberry Pi. You can also use another language for that purpose and I decided to go with Python. The code does exactly the same as above, so I won’t explain it another time.

However, make sure to install pip as well as the necessary libraries for python to use this feature:

sudo apt-get update
sudo apt-get install python-pip
sudo pip install aiohttp
sudo pip install cchardet
sudo pip install aiodns

websockets for Python is a somewhat popular alternative to the library I used. However, I can absolutely not recommend using it due to the poor documentation and lack of several features that I, personally, would like to have in such a library.

A websocket client alternative in Python

Again, instead of using HTML and JavaScript, you can also use Python to create a websocket client. In fact, a lot of other languages have their own implementation for that purpose.

All the code files

Server – Arduino/CPP
Server – Python
Client – HTML/JavaScript

Summary

As you can see, it’s incredibly easy to control a Raspberry Pi or Arduino compatible board (or any other computer that can run these programs) from any computer that has a web browser and internet access.

The techniques, used in this tutorial, can easily be used to create complex systems that can, for example, automate your home.

Advertisements

Leave your two cents, comment here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.