ESP8266 NodeMCU Stepper Motor WebSocket Web Server using Arduino IDE

In this ESP8266 web server project, we will learn how to build an ESP8266 stepper motor control WebSocket web server that will control the rotation of the motor. This will be achieved through a user filled form we will add to the web server page. The user will specify the direction of rotation as well as the number of steps. In response, the stepper motor connected with the ESP8266 GPIO pins will rotate. Additionally, the web server will also show an arrow icon which will simultaneously rotate and stop with the stepper motor.

Moreover, we will build our ESP8266 web server using the WebSocket protocol. Through WebSocket communication protocol, several clients will be connected to the server concurrently without blocking each other. We will see that, unlike HTTP, a WebSocket server updates the web page to all clients automatically.

Moreover, to make our ESP8266 stepper motor control web server project more convenient and practical we will use the feature of ESP8266 LittleFS. We will save the HTML, CSS and JavaScript files on the ESP8266 LittleFS that will be used to build the web server. We can use this to store files in NodeMCU flash file system without having to use any external memory with our ESP module.

We have a similar guide with ESP32:

ESP8266 Stepper Motor Control WebSocket Web Server Project Overview

We will create a web server based on the WebSocket protocol consisting of a form through which the user will control the stepper motor. We aim to build a webpage through which the user will specify the type of rotation (clockwise or anticlockwise) and enter the number of steps to change the motor direction and its rotation time.

The web server will consist of a title: “Stepper Motor Control Web Server”, a form, the motor state and an arrow icon. The form has two features for the user to change. One is the direction of rotation and the other is the number of steps the motor will move. Pressing the green button will cause the motor to start rotating. Next, we have a paragraph which shows the state of the motor (either it is rotating or it has stopped). Additionally, the web server also displays an arrow icon which rotates accordingly with the stepper motor.

ESP stepper motor control project overview
ESP8266 Stepper Motor Control Web Server Overview

Working Process

  • First, the user will specify the direction of rotation by selecting either ‘Clockwise’ or ‘Anti-Clockwise’ option from the form. The user will also have the option to enter the number of steps for the motor to move. After specifying both the options, the user will press the “Press to rotate” button.
  • When the button will be pressed, the web server will send the message request message through the WebSocket communication protocol. It will consist of the direction and the step value. For example if the user selected the option of ‘Clockwise’ and entered ‘2048’ as the number of steps, then “2048/Clockwise” message will be sent.
  • This will cause the motor state to change to “Motor is rotating…” and the arrow icon will start rotating in the specified direction.
  • Our ESP8266 board will receive this request and start the motor movement.
  • When the motor stops rotating, the ESP8266 will send the new motor state of the web page to all the connected clients with the help of WebSocket.
  • Immediately all the clients will receive the new message and their respective web pages will update to the new current motor state and the arrow icon will also stop.
ESP8266 stepper motor control web server working process
Working Process

You can learn more about ESP8266 WebSocket web server here:

Required Components

  1. ESP8266 development board
  2. 28BYJ-48 stepper motor with ULN2003 Motor Driver
  3. 5V external power supply
  4. Connecting Wires

ESP8266 Stepper Motor Control WebSocket Web Server Schematic Diagram

Follow the schematic diagram below to assemble your circuit.

ESP8266 28BYJ-48 stepper motor with ULN2003 driver connection diagram
Schematic diagram

To connect the ESP8266 development board with the stepper motor and driver we will use the input pins IN1-IN4, the power supply pins, and the motor connection header. The 28BYJ-48 stepper motor already comes attached with the motor driver via the motor connector header. Now we will connect four GPIO pins of our ESP8266 board with the input pins (IN1-IN4) of the driver. We have used the following GPIO pins to connect with each input pin.

ESP8266Motor Driver
GPIO14(D5)IN1
GPIO12(D6)IN2
GPIO13(D7)IN3
GPIO15(D8)IN4

You can use any suitable ESP8266 GPIO pins as well.

Additionally, we will power the motor driver with 5V external power supply. Both the grounds of the power supply and the ESP8266 board will be in common.

ESP8266 NodeMCU 28BYJ-48 stepper motor and ULN2003 driver

You can read this for more details:

Setting up Arduino IDE for ESP8266 Stepper Motor Control WebSocket Web Server

We will use Arduino IDE to program our ESP8266 development board. Thus, you should have the latest version of Arduino IDE. Additionally, you also need to install the ESP8266 plugin. You can visit the link shown below to have a look.

Also, you will have to download and install the ESP8266 Filesystem Uploader Plugin in your Arduino IDE so that you are able to upload the LittleFS files on your ESP8266 board. If you haven’t, you can follow this tutorial:

Installing Libraries

We will need the following libraries to build our ESP8266 Stepper Motor Control web server with WebSocket protocol.

  1. ESPAsyncWebServer library
  2. ESPAsyncTCP library
  3. AccelStepper library

The ESPAsyncWebServer library will help us in creating our web server easily. With this library, we will set up an asynchronous web server. AsyncTCP library will also be incorporated as it a dependency for the ESPAsyncWebServer library. These two libraries are not available in the Arduino library manager. Therefore, we will have to download and load them on our ESP8266 board ourselves.

  • To install the ESPAsyncWebServer library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘ESPAsyncWebServer.’ Then, transfer this folder to the installation library folder in your Arduino IDE.
  • To install the ESPAsyncTCP library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘ESPAsyncTCP.’ Then, transfer this folder to the installation library folder in your Arduino IDE.

Likewise, you can also go to Sketch > Include Library > Add .zip Library inside the IDE to add the libraries as well.

We will require the AccelStepper library present in Arduino Library Manager to control the stepper motor using ESP8266. This library will provides us with useful functions to set the maximum speed, acceleration, and steps per revolution to rotate the motor in both directions.

  • To install the library, we will use the Arduino Library Manager. Open your Arduino IDE and go to Sketch > Include Libraries > Manage Libraries. Type ‘Accelstepper’ in the search bar and install the latest version.

After installation of the libraries, restart your IDE.

Creating Files for LittleFS

In most of our web server projects with ESP8266, we have included HTML and CSS files inside our Arduino sketch. We save these HTML documents inside Arduino sketch by converting them into strings. Whenever a web client makes an HTTTP request, we send this string as a response which is basically a web page. On the contrary, with LittleFS, we can save HTML, CSS, and JavaScript files in ESP8266 flash file system. Whenever a web client makes a request, we can serve these files directly from LittleFS. It is convenient to have separate HTML, CSS and JavaScript files saved on the ESP8266 LittleFS. All of these three files will later be linked together. The LittleFS will help us access the flash memory of the ESP8266 core. We will store our files in the flash memory of the ESP8266 board and serve them in our web server.

To build an ESP8266 web server using LittleFS, we will create three different files: HTML, CSS, JavaScript and Arduino sketch and organize them in a project folder like shown below:

ESP stepper motor control project creating files for SPIFFS

Note: You should place HTML and CSS files inside the data folder. Otherwise, LittleFS library will not be able to read these files.

You can read our previous SPIFFS web server tutorial with ESP8266 NodeMCU:

Creating HTML file

Inside our HTML file, we will specify the title of the web page, the form, motor state and the arrow icon. The form will consist of the option to choose the direction of rotation and the number of steps.

Now, create an index.html file and replicate the code given below in that file.

<!DOCTYPE html>
<html>
<head>
  <title>ESP Stepper Motor Control</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" type="text/css" href="style.css">
  <link rel="icon" type="image/png" href="favicon.png">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
</head>
<body>
  <div class="topnav">
    <h1>Stepper Motor Control Web Server</h1>
  </div>
  <div class="content">
        <form>
          <input type="radio" id="Clockwise" name="direction" value="Clockwise" checked>
          <label for="Clockwise">Clockwise</label>
          <input type="radio" id="Anticlockwise" name="direction" value="Anticlockwise">
          <label for="Clockwise">Anti-clockwise</label><br><br><br>
          <label for="steps">Enter number of steps</label>
          <input type="number" id="steps" name="steps">
        </form>
        <button onclick="submitForm()">Press to rotate</button>
        <p>Motor state: <span id="motor-state">Stopped</span></p>
        <p><i id="arrow_icon" class="fas fa-arrow-circle-up"></i> </p>
  </div>
 http://script.js
</body>
</html>

Building the Web page

We will start with the title of the web page. The tag will indicate the beginning of the title and the </tile> tag will indicate the ending. In between these tags, we will specify “ESP Stepper Motor Control” which will be displayed in the browser’s title bar </p>.


<title>ESP Stepper Motor Control</title>

Next, we will create a meta tag to make sure our web server is available for all browsers e.g., smartphones, laptops, computers etc.

<meta name="viewport" content="width=device-width, initial-scale=1">

Between the <head></head> tags, we will reference the CSS file as we’ll be creating different files for both HTML and CSS by using the <link> tag. This tag will be used so that we can link with an external style sheet which we will specify as a CSS file. It will consist of three attributes. The first one is rel which shows the relationship between the current file and the one which is being linked.

We will specify “stylesheet” which will change the visuals of the web page. The second attribute is type which we will specify as “text/css” as we will be using the CSS file for styling purpose. The third attribute is href and it will specify the location of the linked file. Both of the files (HTML & CSS) will be saved in the same folder (data) so we will just specify the name of the CSS file that is “style.css.”

rel="stylesheet" type="text/css" href="style.css">

Inside the HTML web page body, we will include the heading, the form, the button and two paragraphs, one for the motor state and the other for the arrow icon. This will go inside the <body></body> tags which will mark the beginning and the ending of the script.

We will include the heading of our webpage inside the <h1></h1> tags and it will be “Stepper Motor Control Web Server”.

 <h1>Stepper Motor Control Web Server</h1>

Now we will create the form in between <form> </form> tags.

        <form>
          <input type="radio" id="Clockwise" name="direction" value="Clockwise" checked>
          <label for="Clockwise">Clockwise</label>
          <input type="radio" id="Anticlockwise" name="direction" value="Anticlockwise">
          <label for="Clockwise">Anti-clockwise</label><br><br><br>
          <label for="steps">Enter number of steps</label>
          <input type="number" id="steps" name="steps">
        </form>

Now inside the form first the user has to select the direction of rotation. It can be either clockwise or anti-clockwise. Thus, we will create two radio buttons for each of the two options so that that the user is able to select only one of them. We will create a group of radio buttons but make sure they have the same name. In our case we have set it to ‘direction.’ The first radio button for the Clockwise rotation will have the id ‘Clockwise’ associated with it. Likewise the second radio button for the Anti-clockwise rotation will have id ‘Anticlockwise’ associated with it.

<input type="radio" id="Clockwise" name="direction" value="Clockwise" checked>
          <label for="Clockwise">Clockwise</label>
<input type="radio" id="Anticlockwise" name="direction" value="Anticlockwise">
          <label for="Clockwise">Anti-clockwise</label><br><br><br>

Next, we will have an input field where the user will enter the number of steps. The input field will be of type number. We have associated both the id and name as ‘steps’ for this particular input field.

<label for="steps">Enter number of steps</label>
<input type="number" id="steps" name="steps">

Then we will have a button that will send the message in the form of direction and steps to the server through the WebSocket protocol, whenever it will be clicked. This will be achieved through the submitForm() function that we have created in our JavaScript file.

<button onclick="submitForm()">Press to rotate</button>

Next, we have two paragraphs. One showing the motor state and the other displaying the arrow icon. Notice that the motor state in in between <span> </span> tags so we are able to change its value. It has the id ‘motor-state’ associated with it.

The arrow having the id ‘arrow_icon’ is taken from Font Awesome and will be used to demonstrate the rotation of the motor.

 <p>Motor state: <span id="motor-state">Stopped</span></p>
 <p><i id="arrow_icon" class="fas fa-arrow-circle-up"></i> </p>

Creating CSS File

Next, create another file “style.css” in the data folder and copy the code given below in that file. CSS is used to give styles to a web page.

html {
  font-family: New Times Roman;
}

h1 {
  font-size: 1.8rem;
  color: white;
}

p{
  font-size: 20px;
  text-align: center;
}

.topnav {
  overflow: hidden;
  background-color: #800303;
  text-align: center;
}

body {
  margin: 0;
}

.content {
  padding: 20px;
  max-width: max-content;
  margin: 0 auto;
}

input[type=number], select {
  width: 100%;
  padding: 12px 20px;
  margin: 8px 0;
  display: inline-block;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

form{
  border-radius: 5px;
  background-color: #ffd1d1;
  padding: 20px;
}

button {
  background-color: #21b555;
  border: none;
  padding: 14px 20px;
  text-align: center;
  font-size: 20px;
  border-radius: 4px;
  transition-duration: 0.4s;
  width: 100%;
  color: white;
  cursor: pointer;
}

button:hover {
    background-color: #1282A2;
}

input[type="radio"] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border-radius: 50%;
  width: 16px;
  height: 16px;
  border: 2px solid #999;
  transition: 0.2s all linear;
  margin-right: 5px;
  position: relative;
  top: 4px;
  }

input[type="radio"]:checked{
  border: 6px solid #de1010;
}

#motor-state{
  font-weight: bold;
  color: red;
}

#arrow_icon{
  font-size:100px;
  color:#1280a2;
}

.spin {
  -webkit-animation:spin 4s linear infinite;
  -moz-animation:spin 4s linear infinite;
  animation:spin 4s linear infinite;
}

.spin-back {
  -webkit-animation:spin-back 4s linear infinite;
  -moz-animation:spin-back 4s linear infinite;
  animation:spin-back 4s linear infinite;
}

@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }

@-moz-keyframes spin-back { 100% { -moz-transform: rotate(-360deg); } }
@-webkit-keyframes spin-back { 100% { -webkit-transform: rotate(-360deg); } }
@keyframes spin-back { 100% { -webkit-transform: rotate(-360deg); transform:rotate(-360deg); } }

To add CSS files in head tags, we will use <style></style> tags to mark the beginning and the end. We will set the display text to font type Times New Roman and align it in the centre of the webpage. The font size, colour and positioning of the heading, paragraphs, and the form will also be set.

To move the arrow icon, we will create two separate classes ‘spin’ and ‘spin-back.’ One is associated with clockwise rotation and the other is for anti-clockwise rotation. These two classes will be linked with the ‘arrow-icon’. We have used the animation property to rotate the arrow for both the ‘spin’ and the ‘spin-back’ classes.

.spin {
  -webkit-animation:spin 4s linear infinite;
  -moz-animation:spin 4s linear infinite;
  animation:spin 4s linear infinite;
}

.spin-back {
  -webkit-animation:spin-back 4s linear infinite;
  -moz-animation:spin-back 4s linear infinite;
  animation:spin-back 4s linear infinite;
}

@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }

@-moz-keyframes spin-back { 100% { -moz-transform: rotate(-360deg); } }
@-webkit-keyframes spin-back { 100% { -webkit-transform: rotate(-360deg); } }
@keyframes spin-back { 100% { -webkit-transform: rotate(-360deg); transform:rotate(-360deg); } }

Creating JavaScript File

Now create another file in the data folder and name it ‘script.js.’ This is the JavaScript file responsible for handling the web socket communication.

var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onload);
var direction;

function onload(event) {
    initWebSocket();
}

function initWebSocket() {
    console.log('Trying to open a WebSocket connection…');
    websocket = new WebSocket(gateway);
    websocket.onopen = onOpen;
    websocket.onclose = onClose;
    websocket.onmessage = onMessage;
}

function onOpen(event) {
    console.log('Connection opened');
}

function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
}

function submitForm(){
    const rbs = document.querySelectorAll('input[name="direction"]');
    direction;
    for (const rb of rbs) {
        if (rb.checked) {
            direction = rb.value;
            break;
        }
    }

    document.getElementById("motor-state").innerHTML = "Motor is rotating...";
    document.getElementById("motor-state").style.color = "green";
    if (direction=="Clockwise"){
        document.getElementById("arrow_icon").classList.add("spin");
    }
    else{
        document.getElementById("arrow_icon").classList.add("spin-back");
    }
    
    var steps = document.getElementById("steps").value;
    websocket.send(steps+"/"+direction);
}

function onMessage(event) {
    console.log(event.data);
    direction = event.data;
    if (direction=="stop"){ 
      document.getElementById("motor-state").innerHTML = "Motor stopped"
      document.getElementById("motor-state").style.color = "red";
      document.getElementById("arrow_icon").classList.remove("spin", "spin-back");
    }
    else if(direction=="Clockwise" || direction=="Anticlockwise"){
        document.getElementById("motor-state").innerHTML = "Motor is rotating...";
        document.getElementById("motor-state").style.color = "green";
        if (direction=="Clockwise"){
            document.getElementById("arrow_icon").classList.add("spin");
        }
        else{
            document.getElementById("arrow_icon").classList.add("spin-back");
        }
    }
}

Inside the <script></script> tags, we will include the JavaScript to handle the WebSocket. Data exchange as well as initiating the two-way connection between the client-server will be accomplished through this script when the webpage will be formed.

Firstly, we will create a gateway that will act as the entrance to the WebSocket interface. We will specify the webserver IP address (window.location.hostname) for the gateway.

var gateway = `ws://${window.location.hostname}/ws`;

Then we will create a new variable of type ‘var’ named ‘websocket’. Through the window.addwindowListener()function we will call the onload function. This will occur when the webserver will be loaded.

var websocket;
window.addEventListener('load', onload);

Another variable of type ‘var’ named ‘direction’ will also be defined. This will be used to monitor the current state of the motor’s movement. It will either be ‘Clockwise’, ‘AntiClockwise’ or ‘stop.’

var direction;

Next, we will define the onload() function. This takes in the parameter ‘event’ and calls the initWebSocket() function. The initWebsocket() function will be used to initialize the WebSocket.

function onload(event) {
    initWebSocket();
}

Initializing WebSocket

For the initWebSocket() function, we will use it to initialize the WebSocket connection. Three different callback functions will also be included to cater when a message will be received or the connection is opened/closed. Next, we will define each callback function separately.

function initWebSocket() {
    console.log('Trying to open a WebSocket connection…');
    websocket = new WebSocket(gateway);
    websocket.onopen = onOpen;
    websocket.onclose = onClose;
    websocket.onmessage = onMessage;
}

Now, we will define the onOpen() function in which ‘event’ is passed as an argument . When the WebSocket connection is opened, the console.log() function is called. This will write the message which is passed as an argument into the browser console. In our case, the message is ‘Connection opened.’

function onOpen(event) {
    console.log('Connection opened');
}

For the onClose() function, event will be passed as argument. When the WebSocket connection is closed, console.log() function is called which will log ‘Connection closed’ in the browser log. Additionally, the initWebSocket() function will be called after every 2 seconds to initiate the WebSocket connection.

function onClose(event) {
    console.log('Connection closed');
    setTimeout(initWebSocket, 2000);
}

Sending Message to Server

Then we will define the submitForm() function. This will be called whenever the user will press the ‘Press to rotate’ button on the web server. The message the user will submit, will consist of the direction of rotation and the number of steps. Firstly, the direction of rotation selected will get saved in the ‘direction’ variable. The motor state paragraph on the web page will now display ‘Motor is rotating…’ in green colour.

If the direction corresponds to ‘Clockwise’ then the arrow on the web server will start rotating in the clockwise direction. Likewise, if the direction corresponds to ‘Anticlockwise’ then the arrow on the web server will start rotating in the anti-clockwise direction.

Additionally, another variable of type ‘var’ named ‘steps’ will obtain the value for the number of steps entered by the user.

As you can see, the direction of rotation and the number of steps are obtained by referring to their particular ‘ids’. The number of steps and the direction of rotation with a “/” in between is sent to the server via the websocket communication. So for example, if the user entered the number of steps as 2048 and selected clockwise as the direction then 2048/Clockwise message will be sent to the ESP8266 board.

function submitForm(){
    const rbs = document.querySelectorAll('input[name="direction"]');
    direction;
    for (const rb of rbs) {
        if (rb.checked) {
            direction = rb.value;
            break;
        }
    }

    document.getElementById("motor-state").innerHTML = "Motor is rotating...";
    document.getElementById("motor-state").style.color = "green";
    if (direction=="Clockwise"){
        document.getElementById("arrow_icon").classList.add("spin");
    }
    else{
        document.getElementById("arrow_icon").classList.add("spin-back");
    }
    
    var steps = document.getElementById("steps").value;
    websocket.send(steps+"/"+direction);
}

onMessage()

The onMessage() function is called whenever a new message will be received. This function is responsible for updating the motor state and the arrow animation on the web server. When the motor state changes, the ESP8266 board will send a message to the client which will get updated in the ‘direction’ variable. If the direction corresponds to ‘stop’ then the arrow icon will stop rotating and the paragraph showing the motor state on the web page will display ‘Motor stopped’ in red colour. However, if the direction corresponds to either ‘Clockwise’ or ‘Anticlockwise’ then the the paragraph showing the motor state on the web page will display ‘Motor is rotating…’ in green colour. Additionally, the arrow icon will rotate according to the direction of rotation.

function onMessage(event) {
    console.log(event.data);
    direction = event.data;
    if (direction=="stop"){ 
      document.getElementById("motor-state").innerHTML = "Motor stopped"
      document.getElementById("motor-state").style.color = "red";
      document.getElementById("arrow_icon").classList.remove("spin", "spin-back");
    }
    else if(direction=="Clockwise" || direction=="Anticlockwise"){
        document.getElementById("motor-state").innerHTML = "Motor is rotating...";
        document.getElementById("motor-state").style.color = "green";
        if (direction=="Clockwise"){
            document.getElementById("arrow_icon").classList.add("spin");
        }
        else{
            document.getElementById("arrow_icon").classList.add("spin-back");
        }
    }
}

Arduino Sketch

Now, open your Arduino IDE and go to File > New to open a new file. Copy the code given below in that file and save it as ‘ESP_Stepper_Motor_Control’.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"
#include <AccelStepper.h>

#define IN1 D5
#define IN2 D6
#define IN3 D7
#define IN4 D8

AccelStepper motor(AccelStepper::HALF4WIRE, IN1, IN3, IN2, IN4);

String message = "";

const char* ssid = "PTCL-BB";
const char* password = "44332211";

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

String direction ="STOP";
String steps;

bool motor_stop = false;

void notifyClients(String state) {
  ws.textAll(state);
}

void handleWebSocketMessage(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) {
    data[len] = 0;
    message = (char*)data;
    steps = message.substring(0, message.indexOf("/"));
    direction = message.substring(message.indexOf("/")+1, message.length());
    Serial.print("steps");
    Serial.println(steps);
    Serial.print("direction");
    Serial.println(direction);
    notifyClients(direction);
    motor_stop = true;
    if (direction == "Clockwise"){
      Serial.print("Clockwise: ");
      motor.move(steps.toInt());
    }
    else{
      Serial.print("Anti-clockwise: ");
      motor.move(-steps.toInt());
    }
  }
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      //Notify client of motor current state when it first connects
      notifyClients(direction);
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
        handleWebSocketMessage(arg, data, len);
        break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
     break;
  }
}

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
  
  initWebSocket();
  
   if (!LittleFS.begin()) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  else{
    Serial.println("LittleFS mounted successfully");
  }
  motor.setMaxSpeed(500);
  motor.setAcceleration(50);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/index.html", "text/html");
  });
  
  server.serveStatic("/", LittleFS, "/");

  server.begin();
}

void loop() {
  if (motor.distanceToGo() == 0 && motor_stop == true){  
    direction = "stop";
    notifyClients(direction);
    motor_stop = false;
  }
  ws.cleanupClients();
  motor.run();
}

How the Code Works?

Firstly, we will include all the libraries required for this project. The AccelStepper.h library provides useful functions that make it easy to control the stepper motor. As we have to connect our ESP8266 to a wireless network, we need ESP8266WiFi.h library for that purpose. The ESPAsyncWebServer library is the one that we recently downloaded and will be required to build the asynchronous web server. Also, the LittleFS library will allow us to access the flash memory file system of our ESP8266 core.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"
#include <AccelStepper.h>

Next, we will define the input pins of the motor connections with the ESP8266 board. As you can see we have used the GPIO pins 14,12,13 and 15 to connect with IN1, IN2, IN3 and IN4 respectively. However, you can use any other suitable ESP8266 GPIO pins as well.

#define IN1 D5
#define IN2 D6
#define IN3 D7
#define IN4 D8

Now we will create an instance of the AccelStepper library called motor() and pass HALF4WIRE to indicate that we want to control the first four wires and the individual motor input pins as arguments. Make sure you specify the input pins in their correct sequence. It is IN1, IN3, IN2, and IN4 for 28BYJ-48 stepper motor.

AccelStepper motor(AccelStepper::HALF4WIRE, IN1, IN3, IN2, IN4);

We will define a string variable called ‘message’ and initially set it as an empty string.

String message = "";

Next, we will create two global variables, one for the SSID and another for the password. These will hold our network credentials which will be used to connect to our wireless router. Replace both of them with your credentials to ensure a successful connection.

const char* ssid = "Your_SSID";
const char* password = "Your_Password";
Creating AsyncWebServer and Socket Objects

The AsyncWebServer object will be used to set up the ESP8266 web server. We will pass the default HTTP port which is 80, as the input to the constructor. This will be the port where the server will listen to the requests. We will also create an object for the AsyncWebSocket called ‘ws’. This will be used on the /ws path to handle connections appropriately.

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

Defining Variables

We will define some string variables and a bool variable that will be used later on in the sketch. The string variables hold the direction and steps values.

String direction ="STOP";
String steps;

bool motor_stop = false;

Through this function, we want to notify all the connected clients of the updated state of the motor. We will achieve this through the textAll() method which will send the same string (state) to all the clients on board.

void notifyClients(String state) {
  ws.textAll(state);
}

Next, we will define the handleWebSocketMessage(). This will act as a callback function. When new information is received by our ESP8266 through the WebSocket, this callback function will run.

To obtain the direction from the message variable, we will use string manipulation as shown below:

direction = message.substring(message.indexOf("/")+1, message.length());

Inside the notifyClients() function, we will pass the direction variable as a parameter. This will notify all the clients of the direction of rotation. The connected clients will then be able to change the motor state paragraph on the web page accordingly.

Additionally, we will set the bool variable ‘motor_stop’ to true. Initially, it was set as ‘false’.

If the direction is clockwise then the stepper motor will start rotating clockwise by using the move() method on the motor instance. We will pass the number of steps saved in the ‘steps’ variable and convert it to an integer as the argument inside it. Otherwise, the stepper motor will start rotating anticlockwise by using the move() method on the motor instance and passing the number of steps with a negative sign inside the step() method. Additionally, we will print the type of rotation in the serial monitor as well.

void handleWebSocketMessage(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) {
    data[len] = 0;
    message = (char*)data;
    steps = message.substring(0, message.indexOf("/"));
    direction = message.substring(message.indexOf("/")+1, message.length());
    Serial.print("steps");
    Serial.println(steps);
    Serial.print("direction");
    Serial.println(direction);
    notifyClients(direction);
    motor_stop = true;
    if (direction == "Clockwise"){
      Serial.print("Clockwise: ");
      motor.move(steps.toInt());
    }
    else{
      Serial.print("Anti-clockwise: ");
      motor.move(-steps.toInt());
    }
  }
}
onEvent()

The following section of code will define the onEvent() function. This will enable an event listener which will handle the Websocket connection properly. There are five different values that the event (type) can take up. These are shown below:

  1. WS-EVT-CONNECT: when a client will log in
  2. WS_EVT_DISCONNECT: when the client will log out
  3. WS_EVT_DATA: when data will be received from the client
  4. WS_EVT_PONG: when a ping request will come up
  5. WS_EVT_ERROR: when an error will be received from the client
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      //Notify client of motor current state when it first connects
      notifyClients(direction);
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
        handleWebSocketMessage(arg, data, len);
        break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
     break;
  }
}
initWebSocket() for ESP8266 side

This function will initialize our WebSocket protocol for the server side.

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}
setup()

Inside the setup() function, we will open a serial connection at a baud rate of 115200.

Serial.begin(115200);

The following section of code will connect our ESP8266 board with the local network whose network credentials we already specified above. After the connection will be established, the IP address of the ESP8266 board will get printed on the serial monitor. This will help us to make a request to the server.

WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());

Also, we will call the initWebSocket() to initialize the WebSocket communication protocol.

initWebSocket();

These lines of code will initialize the LittleFS.

 if (!LittleFS.begin()) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  else{
    Serial.println("LittleFS mounted successfully");
  }

We will use the setMaxSpeed() method on the motor instance and pass the speed of the motor in steps per second as an argument inside it. In our case we are setting the stepper motor speed to 500 steps per second. We will also set the acceleration of the motor using setAcceleration() and pass the acceleration in steps per second per second.

motor.setMaxSpeed(500);
motor.setAcceleration(50);

We will use the on() method on the server object to listen to the incoming HTTP requests and execute functions accordingly. The send() method uses to return the HTTP response. The index.html file will send to the client, whenever the server will receive a request on the “/” URL.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/index.html", "text/html");
  });
  
  server.serveStatic("/", LittleFS, "/");

To start the server, we will call the begin() on our server object.

server.begin();

loop()

Inside the loop() function, we will check if the stepper motor has stopped or not. If it has stopped then the direction variable will be set to ‘stop.’ Additionally, the notifyClients() function will be called with ‘direction’ as the parameter inside it. This will notify all the connected clients that the motor has stopped. The ‘motor_stop’ variable will again be set to false

if (motor.distanceToGo() == 0 && motor_stop == true){  
    direction = "stop";
    notifyClients(direction);
    motor_stop = false;
  }

The run() method will be responsible to rotate the motor every one step at a time.

  motor.run();

We will use the cleanupClients() method inside our loop(). When the maximum number of clients is exceeded this function closes the oldest client. As a result, it helps in limiting the number of clients so the server does not exhaust.

ws.cleanupClients();

Uploading Files to ESP8266 LittleFS

Now follow the steps closely to ensure all the HTML,CSS and JavaScript files are properly uploaded to the flash memory of your development boards.

Save the Arduino sketch given above as ‘ESP_Stepper_Motor_Control.’

Choose the correct board and COM port. Go to Tools > Board and select NodeMCU 1.0. Next, go to Tools > Port and select the appropriate port through which your board is connected.

Before uploading the sketch to your ESP8266 board, first go to Sketch > Show Sketch Folder.

This will open up the folder where your Arduino sketch is saved. Now create a new folder inside that folder and save it as ‘data.’

ESP stepper motor control project creating files for SPIFFS pic2

Place all the files inside the data folder. Otherwise, the LittleFS library will not be able to read them.

ESP stepper motor control project creating files for SPIFFS pic3

Now, we will upload the files to our ESP8266 board. Go to Tools > ESP8266 LittleFS Data Upload.

LittleFS sketch data upload option Arduino IDE

After a few moments, the file will be uploaded. You will receive the message ‘LittleFS Image Uploaded’ on the debugging window.

Note: Make sure that the total size of the data file is within the flash memory size. Otherwise you will get an error while uploading the files onto LittleFS.

Demonstration

Click on the upload button to upload the code to the ESP8266 development board.
After you have uploaded your code to the development board, press its RST button.

select ESP8266 NodeMCU board

In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP8266 module.

ESP8266 stepper motor control web server serial monitor demo
Serial Monitor

Type that IP address in a web browser and press enter. The web server will open up.

ESP8266 stepper motor control web server

Now specify the direction of rotation and enter the number of steps. Then press the button. The arrow icon will start rotating in the specified direction and the motor state will also update. At the same time, the stepper motor will also start rotating. Both the arrow and the steeper motor will stop once the number of steps completes. The motor state will update accordingly. The web server will immediately update in all the clients.

ESP8266 stepper motor control web server demo

Have a look at the demonstration video below:

You may like to check other web server projects for ESP8266 NodeMCU:

Leave a Comment