In this ESP32 web server project, we will learn how to build an ESP32 stepper motor WebSocket web server that will control the rotation of the stepper 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 ESP32 GPIO pins will rotate either in a clockwise or anti-clockwise direction. 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 ESP32 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 ESP32 stepper motor control web server project more convenient and practical we will use the feature of ESP32 SPI flash file system (SPIFFS). We will save the HTML, CSS, and JavaScript files on the ESP32 SPIFFS that will be used to build the web server. We can use SPIFFS to store files in SPI flash without having to use any external memory with our ESP module.
ESP32 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.
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 ESP32 board will receive this request and start the motor movement.
- When the motor stops rotating, the ESP32 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.
You can learn more about ESP32 WebSocket web server here:
Required Components
- ESP32 development board
- 28BYJ-48 stepper motor with ULN2003 Motor Driver
- 5V external power supply
- Connecting Wires
ESP32 Stepper Motor Control WebSocket Web Server Schematic Diagram
Follow the schematic diagram below to assemble your circuit.
To connect the ESP32 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 ESP32 board with the input pins (IN1-IN4) of the driver. We have used the following GPIO pins to connect with each input pin.
ESP32 | Motor Driver |
GPIO26 | IN1 |
GPIO25 | IN2 |
GPIO33 | IN3 |
GPIO32 | IN4 |
You can use any suitable ESP32 GPIO pins as well.
Additionally, we will power the motor driver with a 5V external power supply. Both the grounds of the power supply and the ESP32 board will be in common.
You can read this:
Setting up Arduino IDE for ESP32 Stepper Motor Control WebSocket Web Server
We will use Arduino IDE to program our ESP32 development board. Thus, you should have the latest version of Arduino IDE. Additionally, you also need to install the ESP32 plugin. You can visit the link shown below to have a look.
Also, you will have to download and install the ESP32 Filesystem Uploader Plugin in your Arduino IDE so that you are able to upload the SPIFFS files on your ESP32 board. If you haven’t, you can follow this tutorial:
Installing Libraries
We will need the following libraries to build our ESP32 Stepper Motor Control web server with WebSocket protocol.
- ESPAsyncWebServer library
- AsyncTCP 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 ESP32 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 Async TCP library for free, click here to download. You will download the library as a .zip folder which you will extract and rename as ‘AsyncTCP.’ 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.
After installation of the libraries, restart your IDE.
Creating Files for SPIFFS
In most of our web server projects with ESP32, 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 SPIFFS, we can save HTML, CSS, and JavaScript files in ESP32 flash file system. Whenever a web client makes a request, we can serve these files directly from SPIFFS. It is convenient to have separate HTML, CSS, and JavaScript files saved on the ESP32 SPIFFS. All of these three files will later be linked together. The SPIFFS will help us access the flash memory of the ESP32 core. We will store our files in the flash memory of the ESP32 board and serve them in our web server.
To build an ESP32 web server using SPIFFS, we will create three different files: HTML, CSS, JavaScript and Arduino sketch and organize them in a project folder like shown below:
Note: You should place HTML and CSS files inside the data folder. Otherwise, SPIFFS library will not be able to read these files.
You can read our previous SPIFFS web server tutorial with ESP32:
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 ESP32 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 ESP32 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 <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Stepper.h>
const int steps_per_rev = 2048;
#define IN1 26
#define IN2 25
#define IN3 33
#define IN4 32
Stepper motor(steps_per_rev, 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 new_request = 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);
new_request = true;
}
}
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());
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.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
//initialize WebSocket
initWebSocket();
//initialize SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println("An error has occurred while mounting SPIFFS");
}
else{
Serial.println("SPIFFS mounted successfully");
}
motor.setSpeed(10);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
});
server.serveStatic("/", SPIFFS, "/");
server.begin();
}
void loop() {
if (new_request){
if (direction == "Clockwise"){
motor.step(steps.toInt());
Serial.print("Rotating Clockwise");
}
else{
motor.step(-steps.toInt());
Serial.print("Rotating Anti-Clockwise");
}
new_request = false;
notifyClients("stop");
}
ws.cleanupClients();
}
How the Code Works?
Firstly, we will include all the libraries required for this project. The Stepper.h library provides useful functions that make it easy to control the stepper motor. As we have to connect our ESP32 to a wireless network, we need WiFi.h library for that purpose. The ESPAsyncWebServer and AsyncTCP libraries are the ones that we recently downloaded and will be required to build the asynchronous web server. Also, the SPIFFS library will allow us to access the flash memory file system of our ESP32 core.
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Stepper.h>
The next step is to define the steps per revolution. This is the number of steps our motor requires to move one complete revolution. Initially, we have set the value to ‘2048.’
const int steps_per_rev = 2048;
Next, we will define the input pins of the motor connections with the ESP32 board. As you can see we have used the GPIO pins 26,25,33 and 32 to connect with IN1, IN2, IN3 and IN4 respectively. However, you can use any other suitable ESP32 GPIO pins as well.
#define IN1 26
#define IN2 25
#define IN3 33
#define IN4 32
Now we will create an instance of the Stepper library called motor() and pass the steps per revolution 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.
Stepper motor(steps_per_rev, 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 ESP32 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 new_request = 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 ESP32 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 ‘new_request’ to true. Initially, it was set as ‘false’.
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);
new_request = true;
}
}
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:
- WS-EVT-CONNECT: when a client will log in
- WS_EVT_DISCONNECT: when the client will log out
- WS_EVT_DATA: when data will be received from the client
- WS_EVT_PONG: when a ping request will come up
- 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());
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 ESP32 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 ESP32 board with the local network whose network credentials we already specified above. After the connection will be established, the IP address of the ESP32 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.print("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 SPIFFS.
if (!SPIFFS.begin(true)) {
Serial.println("An error has occurred while mounting SPIFFS");
}
else{
Serial.println("SPIFFS mounted successfully");
}
We will use the setSpeed() method on the motor instance and pass the speed of the motor in rpm as an argument inside it. In our case we are setting the stepper motor speed to 10 revolutions per minute.
motor.setSpeed(10);
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(SPIFFS, "/index.html", "text/html");
});
server.serveStatic("/", SPIFFS, "/");
To start the server, we will call the begin() on our server object.
server.begin();
loop()
Inside the loop() function, we will check if a new request has been made. Then we will check through an if statement the direction of the motor. If the direction is clockwise then the stepper motor will start rotating clockwise by using the step() 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 step() 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.
After this, ‘new_request’ variable will again be set to false and the notifyClients() function will be called with ‘stop’ as the parameter inside it. This will notify all the connected clients that the motor has stopped.
if (new_request){
if (direction == "Clockwise"){
motor.step(steps.toInt());
Serial.print("Rotating Clockwise");
}
else{
motor.step(-steps.toInt());
Serial.print("Rotating Anti-Clockwise");
}
new_request = false;
notifyClients("stop");
}
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 ESP32 SPIFFS
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 ESP32 Dev Module. Next, go to Tools > Port and select the appropriate port through which your board is connected.
Before uploading the sketch to your ESP32 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.’
Place all the files inside the data folder. Otherwise, the SPIFFS library will not be able to read them.
Now, we will upload the files to our ESP32 board. Go to Tools > ESP32 Sketch Data Upload.
After a few moments, the file will be uploaded. You will receive the message ‘SPIFFS 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 SPIFFS.
Demonstration
Click on the upload button to upload the code to the ESP32 development board.
After you have uploaded your code to the development board, press its ENABLE button.
In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP32 module.
Type that IP address in a web browser and press enter. The web server will open up.
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.
Have a look at the demonstration video below:
Other ESP32 tutorials and web server projects:
- ESP32 DHT11 and DHT22 Web Server using Arduino IDE
- ESP-MESH Getting Started using painlessMesh Library and ESP32/ESP8266
- ESP32 PWM Slider Web Server – Control LED Brightness
- ESP32 Multiple Sliders WebSocket Web Server – PWM Control LEDs Brightness
- IoT based Soil Moisture Monitoring System with ESP32 and Adafruit IO