Friday, March 11, 2022

ESP webserver tutorial part 5 - Neopixel control

For a complete index to all my articles click this text.

This is a series on building a webpage with the ESP8266 and the ESP32. When this series is completed all the information on building a webpage with buttons, textfields, sliders and color pickers witch have interaction with the ESP8266 or ESP32 will be discussed and demonstrated.

This fifth part leans heavy on the previous four parts in this series, so I urge you to read these stories first:

ESP webserver tutorial part 1 - textfields
ESP webserver tutorial part 2 - button
ESP webserver tutorial part 3 - button with save
ESP webserver tutorial part 4 - slider

The last part showed you how to build a webpage with the ESP8266 and ESP32 that incorporates a slider with which you can dim a led.



As promised in that story I am going to show you here how to build a webpage with 3 sliders with which you can control the colors of a ledstrip. The ledstrip I am going to use here is a strip of Neopixels.


Again I am writing the program in Arduino language (C++)

As a bonus there is a nice trick build into the software that makes the colors change automatically when you alter the slider settings. You do not have to press a confirmation button as was required in the previous story.

The ESP8266 breadboard setup.

The breadboard setup is off course different from the setup in the previous stories because we are goiung to attach a neopixels strip to the ESP8266. You can find a full blown explanation/tutorial on Neopixels in a previous story on my weblog which you can find here:
http://lucstechblog.blogspot.com/2015/10/neopixels-ws2812-intro.html



In this setup I used an ESP8266 (the3 Wemos D1 mini version). The Neopixelstrip is connected to D7 with a current delimiting resistor of 470 Ohm. And a large capacitor for stabilising the current is connected over the power lines (mind the polarity). I used the symbol of an existing neopixel strip from the fritzing library but you may use a ring of strip or even matrix of your own liking. Keep the amount of neopixels low (like 7 to 8) and you can get the current directly from the ESP8266. If you want to attach a larger amount of neopixels make sure you give them an external power supply.

The ESP32 breadboard setup.

The ESP32 breadboard setup is equally simple.



I am using the ESP32 Devkit board. These boards will not fit a breadboard. Therefore I use 2 breadboards. From one of these breadboards I stripped the power rail as discussed in the story you can read here:
https://lucstechblog.blogspot.com/2018/08/breadboard-hack-for-esp32-and-esp8266.html

The led is attached to GPIO22 through a 470 Ohm current delimiting resistor, and there is a large capacitor connected to the power rail to stabilising the current.
I attached an external USB power connector because the ESP32 consumes a lot of power in itself and attaching a ledstrip might impose too much to its power leads.
You can find a detailed explanation on powering the ESP32 in my book (which is in Dutch or English) called ESP32 uitgelegd and in English ESP32 Simplified on which you can find detailed info here:
http://lucstechblog.blogspot.com/2019/07/esp32-uitgelegd-nu-verkrijgbaar.html 

and here https://lucstechblog.blogspot.com/2020/10/esp32-simplified-world-wide-available.html

You can find a story on external powering the ESP microcontrollers here:
https://lucstechblog.blogspot.com/2017/10/powering-your-project.html

The program

Just like the previous entries in this series the program is written in Arduino language (C++) and is almost identical for the ESP8266 and the ESP32. The changes for the ESP32 are really minimal. I will give you the ESP8266 program as a whole and you can find the few lines which have to be modified for the ESP32 at the end of this story.


// ======================================================
// Libraries needed
// ======================================================

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

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS      7

// Your credentials
ESP8266WebServer server(80);
const char* ssid = "XXXXXXXXXXXXXXX";
const char* password = "YYYYYYYYYYYYYY";

int neoPin = D7; 

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

String responseHTML = ""
  "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
  "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
  "be redirected here.</p></body></html>";

// ==========================================
// initial variables
// ========================================== 

int sliderREDvalue = 0;
int sliderGREENvalue = 0;
int sliderBLUEvalue = 0;

// =========================================
// Build the webpage
// =========================================
String getPage()
  {
  String page = "<!DOCTYPE HTML>";
  page += "<html>";
  page += "<head>";
  page += "<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0 maximum-scale = 2.5, user-scalable=1\">";
  page += "<title>Luc's Neopixels slider demo</title>";
  page += "<style>";
  page += "body { background-color: powderblue}";
  page += "</style>";
  page += "</head>";
  page += "<body>";
  page += "<h1 style='color:red'>Luc's Neopixels slider demo</h1>";

  page += "<form id=\"myForm\" action=\"/\" method=\"post\">";
  page += "Move the slider for choosing the color :<br><br>";
  page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";
  page += sliderREDvalue;
  page +="\" > <br>";
  
  page += "GREEN&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"slidergreen\" name=\"slidergreen\" value=\"";
  page += sliderGREENvalue;
  page +="\" > <br>";
  
  page += "BLUE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderblue\" name=\"sliderblue\" value=\"";
  page += sliderBLUEvalue;
  page +="\" > <br>";
  page += "</form>";
  page += "<br>";

  page += "<script>";
  page += "function myFunction() {";
  page += "document.getElementById(\"myForm\").submit()";
  page +="}";
  page +="</script>";

  page += "</body>";
  page += "</html>";
  return page;
  }




// ==========================================
// Handle if unknown command
// ==========================================
void handleNotFound()
{
  server.send(200, "text/html", getPage());
}


// ===========================================
// Submit page
// ===========================================
void handleSubmit()
  {
  if (server.hasArg("slidergreen"))
     {
      sliderGREENvalue = server.arg("slidergreen").toInt();
      Serial.print("The slider Green has value: ");
      Serial.println(sliderGREENvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.

    pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }

   if (server.hasArg("sliderred"))
     {
      sliderREDvalue = server.arg("sliderred").toInt();
      Serial.print("The slider Red has value: ");
      Serial.println(sliderREDvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }     

   if (server.hasArg("sliderblue"))
     {
      sliderBLUEvalue = server.arg("sliderblue").toInt();
      Serial.print("The slider Blue has value: ");
      Serial.println(sliderBLUEvalue);
      for(int i=0;i<NUMPIXELS;i++)
        {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
        }
      } 
  server.send(200, "text/html", getPage());       //Response to the HTTP request
  }  

// =============================================
// Test if commands received
// =============================================
void handleRoot() 
    {   
    if (server.args() ) 
       {
       handleSubmit();
       } 
       else 
       {
       server.send(200, "text/html", getPage());  
       }
    }

// =============================================
// Setup
// =============================================
void setup()
    {
    delay(1000);
    Serial.begin(115200);

    // WiFi initilisation part
    // Connect to Wi-Fi network with SSID and password
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid,password);
    while (WiFi.status() != WL_CONNECTED) 
          {
          delay(500);
          Serial.print(".");
          }
     // Print local IP address and start web server
     Serial.println("");
     Serial.println("WiFi connected.");
     Serial.println("IP address: ");
     Serial.println(WiFi.localIP());
     server.begin();
     server.on("/", handleRoot);
     server.onNotFound(handleNotFound);
     delay(500);

     pixels.begin(); // This initializes the NeoPixel library.
 
     pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue);
     pixels.show();
     }


// ===============================================
// LOOP
// =============================================== 
void loop()
  {  
  server.handleClient(); 
  delay(50);
  }

The program is almost identical to the program in the previous stories. All explanation therefore has been done in those stories except for the few lines that are required for putting the three sliders on the screen. This is done in the <form> part of the webpage.

First we need to activate the neopixels code and that is done by these entries at the beginning of the program:


#include <Adafruit_NeoPixel.h>

int neoPin = D7;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

As you can see the neopixels code is attached to I/O pin D7.

int sliderREDvalue = 0;
int sliderGREENvalue = 0;
int sliderBLUEvalue = 0;

The above lines make sure that the neopixels are set off when the program starts.

In the setup() routine at the end of the program the IP adress is printed in the Serialmonitor so you will know which IP adress you should fill in on your computers or phones browser to find the webpage.

  pixels.begin(); // This initializes the NeoPixel library.


  pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue);

  pixels.show();

These lines activate the neopixels and set the color to the initial values which were defined at the start of the program (0,0,0).

The important changes are in the HTML code where the form is made and the sliders defined.

  page += "<form id=\"myForm\" action=\"/\" method=\"post\">";

  page += "Move the slider for choosing the color :<br><br>";

    page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";

    page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";

  page += sliderREDvalue;

  page +="\" > <br>";

    page += "GREEN&nbsp;&nbsp;&nbsp;";

    page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"slidergreen\" name=\"slidergreen\" value=\"";

  page += sliderGREENvalue;

  page +="\" > <br>";

    page += "BLUE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";

    page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderblue\" name=\"sliderblue\" value=\"";

  page += sliderBLUEvalue;

  page +="\" > <br>";

  page += "</form>";

As you can see there are some things different from the program in the previous story.

page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";

After each name of the slider I put some &nbsp commands wich will neatly lineup the sliders. You could do this even better by putting the names of the sliders and the sliders itself into a table. A nice HTML excercise for those who want to expand their knowledge.

Now lets look at the html code that defines the red slider.

page += "<input type=\"range\" min=\"0\" max=\"255\" onmouseleave=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";

The first part is easy to understand. The type of input is range which means a slider and the minimum and maximum values are defined.
onmouseleave is an HTML function that is activated when the mouse moves over the slider and then leaves its aerea. When that happens the function myFunction() is called.

The code for the green and blue sliders is almost identical as you can see. The difference is in the id and name settings.

page += "<script>";

page += "function myFunction() {";

 page += "document.getElementById(\"myForm\").submit()";

page +="}";

page +="</script>";

At the end of the HTML page the Javascript function myFunction is called when the mouse has left the area of one of the sliders. This code actually takes the values of the sliders from the form and uses submit() to send them to the webserver.

In the handlesubmit() routine the webserver receives the information from the form on the webpage and the slider settings are analysed. Every slider info is analysed individually and processed. Lets look at the information from the green slider coming in:

  if (server.hasArg("slidergreen"))
     {
      sliderGREENvalue = server.arg("slidergreen").toInt();
      Serial.print("The slider Green has value: ");
      Serial.println(sliderGREENvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }

As you can see the name slidergreen is the name that is tested for. That is why each slider got his own name and ID in the above mentioned HTML code.
The received value is put into the sliderGREENvalue variable and printed in the Serialmonitor.
Then the information is send to the neopixels strip. All slider values are send so the settings of the other colors is not changed unless their sliders have been changed.

Dont forget the pixels.show() command. that will actually activate the new settings.

ESP32 code

Well there is not much difference in the ESP8266 and ESP32 code on the software side.

The same Adafruit Neopixel library is used as for the ESP8266 as its compatible for both. We only have to alter the pin assignment as you can see in the breadboard setup. The Neopixels are attached to pin 22.

int neoPin = D7;

And for the webserver we only have to Change the following lines:

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

ESP8266WebServer server(80);

into:

#include <WiFi.h>;
#include <WebServer.h>;

WebServer server(80);

Thats all.

Pitfall.

The above code works flawlessly on my PC with Windows 10 in the Firefox and Chrome browsers. There is however a problem with this code which is not really interesting when you use it to control a ledstrip or neopixels. It might impose a problem if you are going to use this for steering a motor, pump or likewise.


As you can see the slidervalues are send very often to the webserver and processed. Indeed evrytime you move your mouse over a slider even if you did not alter it. So if you alter and use this code to send PWM values to a motor or pump the motor might hick up everytime the info is send and processed in the webserver.

You can avoid this by changing onmouseleave in onblur like this in the red slider code:


page += "<input type=\"range\" min=\"0\" max=\"255\" onblur=\"myFunction()\" id=\"sliderred\" name=\"sliderred\" value=\"";

Pitfall 2

Another pitfall is that this code does not work on Android phones (and Iphones for that) because the html onmouse functions are not compatble with touch screens.

So to get this working on PC and on Android systems we sadly have to revert to the old system where a button is pressed after the slider has been changed. I will give you the code for this here in its total without any explanation and am sure you can figure it out yourself.


// ======================================================
// Libraries needed
// ======================================================

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

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS      7

// Your credentials
ESP8266WebServer server(80);
const char* ssid = "ZyXEL9680AC";
const char* password = "B9BB53BCA049";

int neoPin = D7; 

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, neoPin, NEO_GRB + NEO_KHZ800);

String responseHTML = ""
  "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
  "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
  "be redirected here.</p></body></html>";

// ==========================================
// initial variables
// ========================================== 

int sliderREDvalue = 0;
int sliderGREENvalue = 0;
int sliderBLUEvalue = 0;

// =========================================
// Build the webpage
// =========================================
String getPage()
  {
  String page = "<!DOCTYPE HTML>";
  page += "<html>";
  page += "<head>";
  page += "<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0 maximum-scale = 2.5, user-scalable=1\">";
  page += "<title>Luc's Neopixels slider demo</title>";
  page += "<style>";
  page += "body { background-color: powderblue}";
  page += "</style>";
  page += "</head>";
  page += "<body>";
  page += "<h1 style='color:red'>Luc's Neopixels slider demo</h1>";

  page += "<form action=\"/\" method=\"post\">";
  page += "Move the slider for choosing the color :<br><br>";
  page += "RED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" id=\"sliderred\" name=\"sliderred\" value=\"";
  page += sliderREDvalue;
  page +="\" class=\"slider\"> <br>";
  
  page += "GREEN&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" id=\"slidergreen\" name=\"slidergreen\" value=\"";
  page += sliderGREENvalue;
  page +="\" class=\"slider\"> <br>";
  
  page += "BLUE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  page += "<input type=\"range\" min=\"0\" max=\"255\" id=\"sliderblue\" name=\"sliderblue\" value=\"";
  page += sliderBLUEvalue;
  page +="\" class=\"slider\"> <br><br>";
  page += "<input type=\"submit\" value=\"Confirm\">";
  page += "</form>";
  page += "<br>";
  page += "<br>";
  page += "<br>";

  page += "</body>";
  page += "</html>";
  return page;
  }




// ==========================================
// Handle if unknown command
// ==========================================
void handleNotFound()
  {
  server.send(200, "text/html", getPage());
  }


// ===========================================
// Submit page
// ===========================================
void handleSubmit()
  {
  if (server.hasArg("slidergreen"))
     {
      sliderGREENvalue = server.arg("slidergreen").toInt();
      Serial.print("The slider has value: ");
      Serial.println(sliderGREENvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
          // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
          pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
          pixels.show(); // This sends the updated pixel color to the hardware.
         }
      }

   if (server.hasArg("sliderred"))
      {
      sliderREDvalue = server.arg("sliderred").toInt();
      Serial.print("The slider has value: ");
      Serial.println(sliderREDvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
         // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
         pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
         pixels.show(); // This sends the updated pixel color to the hardware.
        }
      }     

   if (server.hasArg("sliderblue"))
      {
      sliderBLUEvalue = server.arg("sliderblue").toInt();
      Serial.print("The slider has value: ");
      Serial.println(sliderBLUEvalue);
      for(int i=0;i<NUMPIXELS;i++)
         {
         // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
         pixels.setPixelColor(i, pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue)); // Moderately bright green color.
         pixels.show(); // This sends the updated pixel color to the hardware.
         }
      } 

  server.send(200, "text/html", getPage());       //Response to the HTTP request
}  

// =============================================
// Test if commands received
// =============================================
void handleRoot() 
     {   
     if (server.args() ) 
        {
        handleSubmit();
        } 
     else 
        {
      server.send(200, "text/html", getPage());  
        }
     }

// =============================================
// Setup
// =============================================
void setup()
  {
  delay(1000);
  Serial.begin(115200);

  // WiFi initilisation part
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid,password);
  while (WiFi.status() != WL_CONNECTED) 
       {
       delay(500);
       Serial.print(".");
       }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);

  server.begin();
  delay(500);

  pixels.begin(); // This initializes the NeoPixel library.
 
  pixels.Color(sliderREDvalue,sliderGREENvalue,sliderBLUEvalue);
  pixels.show();
  }


//++++++++++++++++++++++++++++++++++++++++++++++//
//              LOOP                            //
//++++++++++++++++++++++++++++++++++++++++++++++//
void loop()
  {  
  server.handleClient(); 
  delay(50);
  }


This will look on your PC screen like this.



On android it looks like this.



Going further.

So there you have it a complete means to put sliders on a webpage and control the ESP8266 or ESP32 with them.

There are several uses for this. You can change the hardware and code to use this with RGB ledstrips in stead of Neopixels. The webserver software should not be difficult to adapt. You can find the hardware here:
https://lucstechblog.blogspot.com/2018/04/rgb-strip-control-over-wifi-part-2.html

You could also use this to rebuild the remote controlled car which I showed you in a previous story and in which the software was written in ESPBasic which you can find here: http://lucstechblog.blogspot.com/2019/02/remote-controlled-car-with-wifi.html

And combining buttons and a slider you could even rewrite the tea-timer I build in this story: https://lucstechblog.blogspot.com/2018/08/teatimer.html

And like stated earlier in this story you could make the webpage more structured by putting the texts and sliders in a table. You can learn how to do that and everything else about HTML and javascript from this website: https://www.w3schools.com/

Now you have all the tools to make a decent webpage and control your ESP8266 or ESP32 from this page and even put all kind of sensor values on this page. 


In the past I always have used ESPBasic as my favorite devellopment environment. Unfortunately ESPBasic is not further develloped and there is no ESP32 version available. This method of building a webserver makes it easy to replace ESPBasic with the standard Arduino environment for both ESP versions. So it is slowly time to say goodbye to ESPBasic. 

Go ahead and build a project using this. I will give you a complete project in an upcoming story. So stay tuned.

Have fun.

Luc Volders