NodeMCU Tutorial Series Part II: NodeMCU Server Control Over Local Area Network

Blog Post Index:


In the previous entry of this tutorial series I introduced the NodeMCU framework and demonstrated how to program one of its microcontrollers using the Arduino IDE [tutorial link here]. In this tutorial, I assume the user has a basic understanding of the NodeMCU v1.0 board and how to program it using the ESP8266 library in the Arduino IDE. This tutorial picks up where the previous left off, taking full advantage of the ESP8266 WiFi chip by serving a local webpage to control the general purpose input and output (GPIO) pins on a NodeMCU microcontroller.

Some basic HTML and CSS programming methods will be utilized to create a stylish webpage that is both asynchronous (AJAX) and input-driven - this will give the user the ability to control the pins on the microcontroller. For the current example, an electromagnet and LED will be controlled using pulse width modulation (PWM) and simple high/low logic, respectively. The PWM control allows the user to change the voltage to the component, altering the magnetic field of the electromagnet. For the LED, the traditional digitalWrite() method will turn the LED on and off.

A great deal of this tutorial will focus on the implementation of HTML methods in the NodeMCU webpage. Some exploration and knowledge of HTML and web development is recommended, but not needed for developing this project, however, a strong background in Arduino in programming is required.

NodeMCU Setup with Webpage Control on iPhone.

NodeMCU Setup with Webpage Control on iPhone.


Control Components and Wiring

The electromagnet is a relatively inexpensive and available component from electronics sources like Seeed Studio, Adafruit, and Amazon. The PWM control on the NodeMCU board has 10-bit resolution (2 bits higher than the standard Arduino PWM resolution), giving us range from 0-1023. There is one caveat to the NodeMCU, and that’s its voltage limitation at 3.3V. And since the electromagnets are almost always 5V or higher, we will use a buck converter to go from 3.3V to 5V. Along with the control components, I have listed a breadboard, jumper wires, and a resistor - standard components for LED control and prototyping. The full parts list is below:

Seeed Studio Electromagnet will serve as the PWM-controlled component from the WiFi server.

Seeed Studio Electromagnet will serve as the PWM-controlled component from the WiFi server.

  1. Seeed Studio Electromagnet - $8.90 [Seeed Studio]

  2. NodeMCU - $12.99 (for 2) [Amazon]

  3. 3.3V to 5V Boost Converter - $7.99 (for 2) [Amazon, or 5 for $10 not tested: Amazon]

  4. LED - $9.95 (600 pcs) [Amazon]

  5. Breadboard - $7.99 (3 pcs) [Amazon]

  6. Jumper Wires - $6.98 (120 pcs) [Amazon]

  7. 100-1k Ohm Resistor - $10.86 (525 pcs) [Amazon]

 
 

After gathering the required parts, we need to decide which pins we can and want to use for the control sequences. Since the project requires the use of PWM, we are restricted to GPIO pins 4, 12, 13, 14, 15. Other pins may be capable of handling PWM depending on board version, but I will stick to the pinout which corresponds to the board I’ll be using (shown below).

NodeMCU v1.0 Board Pinout [Image courtesy of: https://www.teachmemicro.com/nodemcu-pinout/]

I have arbitrarily used GPIO4 as the PWM pin and GPIO 15 as the output pin for the LED switch. The wiring is shown below:

NodeMCU_control_wiring_diagram.png

Wiring for NodeMCU Server Control


In the next section I will introduce the full code for controlling both the electromagnet (via PWM) and the LED. The codes are fairly involved in both NodeMCU jargon and HTML code, so I recommend copying and pasting as is, then altering after verifying that everything functions as expected. A large portion of the difficulty of working with NodeMCU is understanding the WiFi and HTML side of things, not the microcontroller side.


Arduino NodeMCU Control Server Code

The code below should be uploaded to the NodeMCU board via the Arduino IDE. Be sure that the ESP8266 library is downloaded and the correct options are selected under the board parameters in the IDE.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

#include "pwm_slider_button.h" // file where html page is written

#define STASSID "ABC-123" // name of router
#define STAPSK  "pwd_56789" // pwd for router

#define PWM_pin 4  // PWM pin
#define button_pin 15 // button pin for LED etc.

const char* ssid = STASSID;
const char* password = STAPSK;

ESP8266WebServer server(80); // port 80 (standard)

// prints out main page with pwm slider and button etc...

void handle_root() {
 String root_code = html_page; // html code from .h file
 server.send(200, "text/html", root_code); // upload code to show webpage
}

// handling button press on webpage

void handle_button() {
 String button_state = server.arg("button_state"); // on page ./setButton we are reading the variable 'button_state'
 
 String curr_state = "OFF"; // for sending back to the page
 if(button_state == "1")
 {
  digitalWrite(button_pin,HIGH); //LED ON
  curr_state = "ON";
 }
 else
 {
  digitalWrite(button_pin,LOW); //LED OFF
  curr_state = "OFF";  
 }
 
 server.send(200, "text/plane", curr_state); //Send state back to page
}

// handling PWM: 

void handle_pwm() {
 String pwm_val = server.arg("PWMval"); // reading from slider on html pagae
 Serial.print("slider val: ");
 Serial.println(pwm_val);
 analogWrite(PWM_pin,pwm_val.toInt()); // chaning the value on the NodeMCU board
 server.send(200,"text/plane","0"); // handling the webpage update
}


// setup for server and NodeMCU pins

void setup(void){
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);     //Connect to your WiFi router using ssid and pwd given above
  Serial.println("");

  // choose output for both PWM and button pins
  pinMode(PWM_pin,OUTPUT);
  pinMode(button_pin,OUTPUT); 

  // Connection wait
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // print some parameters pertaining to server IP etc.
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  // IP address given to NodeMCU
 
  server.on("/", handle_root);      // primary page where html from .h file will be shown
  server.on("/setPWM", handle_pwm); // handles the PWM values
  server.on("/setButton", handle_button); // handles button values

  server.begin();                  // begin server
  Serial.println("HTTP server started");
}

// loop for constant update of server pages
void loop(void){
  server.handleClient();          //Handle tapping of buttons and sliders
}

The majority of the Arduino code is formality with the traditional library imports, preallocations, and setup() and loop() functions. Beyond the standard code, there is an instantiation of the ESP8266 called ‘server’ - and this is where the majority of the handling is done. The server houses the interactivity between the webpage and the NodeMCU microcontroller. Thus, it is the most important and pivotal part of working with the NodeMCU board.

For example, the following lines of code setup three separate pages for handling data and interactivity with the server:

server.on("/", handle_root);      // primary page where html from .h file will be shown
server.on("/setPWM", handle_pwm); // handles the PWM values
server.on("/setButton", handle_button); // handles button values
In our case, the "/" indicates the root page of the server. Whereas the "/setPWM" handles the change in slider value for the PWM pin, and "/setButton" handles the button presses. Each of these uses a function in Arduino, 'handle_root', 'handle_pwm', 'handle_button' to listen to each page and control the NodeMCU board after the server page registers specific changes, i.e. a button press or slider change. As such, the three functions are very important for making the NodeMCU do what you what it to do after interaction with the web page.

In practice, on the PWM page ("/setPWM"), we look to its corresponding function handle_pwm() to listen for a change in variable called "PWMval", which is changed when the PWM slider is changed on the web page. In the code below we are taking the new value of the PWM slider, and using 'analogWrite()' to update the PWM pin on the NodeMCU microcontroller, consequently changing the value of the electromagnet's magnetic field:
void handle_pwm() {
 String pwm_val = server.arg("PWMval"); // reading from slider on html pagae
 Serial.print("slider val: ");
 Serial.println(pwm_val);
 analogWrite(PWM_pin,pwm_val.toInt()); // chaning the value on the NodeMCU board
 server.send(200,"text/plane","0"); // handling the webpage update
}

This is the method that should be used for any desired control from a NodeMCU web page. In the next section, I will discuss the server-side HTML, which creates the interaction and updates seen by the human interacting with the web page.


HTML, CSS, and Javascript for NodeMCU Webpage Control

A large portion of the HTML code in the web page contains style lines that are not particularly necessary for understanding the communication between the web page and microcontroller. The full code is below:

const char html_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.slidecontainer {
  width: 100%;
}

.slider {
  -webkit-appearance: none;
  width: 100%;
  height: 15px;
  border-radius: 5px;
  background: #d3d3d3;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .0s;
  transition: opacity .2s;
}

.slider:hover {
  opacity: 1;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #326C88;
  cursor: pointer;
}

.slider::-moz-range-thumb {
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #326C88;
  cursor: pointer;
}

/* Rounded switch */

.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input { 
  opacity: 0;
  width: 0;
  height: 0;
}

.slider1 {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider1:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .2s;
  transition: .2s;
}

input:checked + .slider1 {
  background-color: #326C88;
}

input:focus + .slider1 {
  box-shadow: 0 0 1px #326C88;
}

input:checked + .slider1:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

.slider1.round {
  border-radius: 34px;
}

.slider1.round:before {
  border-radius: 50%;
}

</style>
</head>
<body>

<h2>NodeMCU Server Control</h2>

<div class="slidecontainer">
  <p>GPIO 5 PWM Value: <span id="slider_p"></span></p>
  <input type="range" min="0" max="1023" value="0" class="slider" id="myRange">

<p>GPIO 15 LED Switch: <span id="led_state">OFF</span></p> 
<label class="switch">
  <input type="checkbox" onchange="state_change(this)">
  <span class="slider1 round"></span>
</label>
</div>

<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>


<script>
var slider = document.getElementById("myRange");
var output = document.getElementById("slider_p");
output.innerHTML = slider.value;

slider.onchange = function() {
  output.innerHTML = this.value;
  pwm_change(this.value);
}
</script>

<script>
function pwm_change(val) {
  var xhttp = new XMLHttpRequest();
  xhttp.open("GET", "setPWM?PWMval="+val, true);
  xhttp.send();
}
</script>

<script>
function state_change(element) {
  var xhttp = new XMLHttpRequest();
  
  if (element.checked){
    xhttp.open("GET", "setButton?button_state=1", true);
    document.getElementById("led_state").innerHTML = "ON";
  } else if (!element.checked){
    xhttp.open("GET", "setButton?button_state=0", true);
    document.getElementById("led_state").innerHTML = "OFF";
  }
  xhttp.send();
}
</script>

</body>

</html>
)=====";

The primary functionality occurs in the <body> of the HTML file, where the headers and <div> lines occur. In our case, we use an input range slider from 0-1023 (to match the 10-bit resolution) for the electromagnet and a simple checkbox switch for turning the LED on and off. Below is the code series in the HTML <body> where the interactive controls exist. Notice the classes involved with each component in the code.

<h2>NodeMCU Server Control</h2>

<div class="slidecontainer">
  <p>GPIO 5 PWM Value: <span id="slider_p"></span></p>
  <input type="range" min="0" max="1023" value="0" class="slider" id="myRange">

<p>GPIO 15 LED Switch: <span id="led_state">OFF</span></p> 
<label class="switch">
  <input type="checkbox" onchange="state_change(this)">
  <span class="slider1 round"></span>
</label>
</div>
An example of the interplay between the buttons and sliders can be demonstrated by examining one component and its corresponding function.

Let's inspect the following line:

<input type="checkbox" onchange="state_change(this)">

The code waits for the user's input and calls the function state_change(element). The function is as follows:

function state_change(element) {
  var xhttp = new XMLHttpRequest();
  
  if (element.checked){
    xhttp.open("GET", "setButton?button_state=1", true);
    document.getElementById("led_state").innerHTML = "ON";
  } else if (!element.checked){
    xhttp.open("GET", "setButton?button_state=0", true);
    document.getElementById("led_state").innerHTML = "OFF";
  }
  xhttp.send();
}
The functions are particularly important because they notify the NodeMCU’s WiFi module that the user has changed the state of a particular input. After clicking the button, the ‘state_change(element)’ function notifies the Arduino NodeMCU’s server page on "/setButton". On that page, we are also updating the parameter "button_state" to either 1 or 0. Subsequently, the Arduino reads this update and changes its digitalWrite() value to either HIGH or LOW. And this is the same process the server follows when updating the PWM slider as well.

Local Host Verification and Testing of NodeMCU Server Control

At this point, I have demonstrated the wiring, programming, and functionality of the NodeMCU server for PWM and simple digitalWrite() processes. After uploading the Arduino code to the NodeMCU board and ensure everything was successful, we must test that the code and board are functioning properly and as expected. We can do this by first opening the serial port on the Arduino IDE and identifying the IP address of the NodeMCU ESP8266 WiFi server. You should see a printout that looks similar to the text below:

Connected to ABC-123
IP address: 192.168.0.15
HTTP server started

This lets the user know several things:

  1. Which WiFi network the ESP8266 is connected to

  2. The IP address on the network

  3. Whether the server has started or not

Now the user can open a device on the WiFi network and type in the server IP address above. The page loaded should be identical to the one shown below:

 
NodeMCU Arduino Server PWM and LED Switch with AJAX
 

After verifying the visual layout, the user should test the server for functionality with some LEDs to be certain both the PWM and on/off are operating properly. Below is a demo video of the server with an iPhone over a local area network. It shows the LED turning on and off with the GPIO 15 switch, and it also shows the PWM working for the electromagnet, which is able to pick up certain objects with differing amount of modulation off the PWM slider.


Conclusion and Future Work

This tutorial continued the discussion and implementation around the NodeMCU framework within Arduino. During the article, I outlined several methods on the Arduino end and server-side for controlling the NodeMCU microcontroller. I used PWM to modulate the strength of an electromagnet while also using a simple switch to turn an LED on and off. I only briefly discussed the HTML and programming methods involved in server communication, but introduced enough so that the user can edit and alter Arduino and HTML code to produce their desired effect. This tutorial was the second in a series of tutorials where I explore the power and capabilities of the NodeMCU framework, and this tutorial was focused on WiFi server control methods. One may desire to alter the controls used here to fit their own needs whether it be in robotics, or home automation - the applications are limitless. The primary objective was to create a communicating framework with live-updating web inputs to control the microcontroller, which we were able to do with a little knowledge of Arduino, HTML, and some javascript.

In the next entry, we will explore data acquisition and visualization methods, which essentially inverts the methods shown here by reading in data from the NodeMCU microcontroller and updates a web page using that data. For the first entry into the series, see the link below!

Setup showing electromagnet, NodeMCU, and LED used in this tutorial for server-side control of the NodeMCU Arduino WiFi board.

Setup showing electromagnet, NodeMCU, and LED used in this tutorial for server-side control of the NodeMCU Arduino WiFi board.

 

See More in NodeMCU and IoT: