This is an old revision of the document!
In this project we'll program the ESP32 Sparrow board to act as a web server on your local network and deliver a simple web page where you can control the on-board RGB led. The web server is mobile responsive and can be accessed with any device that can open a browser in the local network.
So, as a general outline and plan of what you will achieve during this lesson:
Run this example code on your board, but replace the SSID and PASSWORD fields with your own.
Test the code works in your phone's or computer's browser and switch on/off the LEDs.
The previous web server example works well, but it's not a good practice to hard-code HTML directly into C code, as it makes things hard to debug and doesn't scale well if we want to create more elaborate web pages. Luckily we can use the 4MB on-board Flash memory of the ESP32 module to store separate files and access them through the SPIFFS.
You will need to add a plugin to your Arduino IDE (at this moment works only with the 1.x versions of the IDE). Please use this tutorial to install the plugin.
From now on, we will build web servers that use the following libraries: ESPAsyncWebServer and AsyncTCP.
These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch > Include Library > Add .zip Library and select the libraries you’ve just downloaded.
Now, let's build a new webserver that shows a slider control which you can use to change the color of one of the on-board leds.
This is how the project should work:
First step is to download the sketch below and save it in a folder named “webserver”.
// Import required libraries #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include "SPIFFS.h" // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; const int output = 14; //red led String sliderValue = "0"; // setting PWM properties const int freq = 5000; const int ledChannel = 0; const int resolution = 8; const char* PARAM_INPUT = "value"; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); // Replaces placeholder with button section in your web page String processor(const String& var){ Serial.println(var); /*if (var == "SLIDERVALUE"){ return sliderValue; }*/ return String(); } void setup(){ // Serial port for debugging purposes Serial.begin(115200); // Initialize SPIFFS if(!SPIFFS.begin(true)){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } // configure LED PWM functionalitites ledcSetup(ledChannel, freq, resolution); // attach the channel to the GPIO to be controlled ledcAttachPin(output, ledChannel); ledcWrite(ledChannel, sliderValue.toInt()); // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print ESP Local IP Address Serial.println(WiFi.localIP()); // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.html", String(), false, processor); }); // Send a GET request to <ESP_IP>/slider?value=<inputMessage> server.on("/slider_r", HTTP_GET, [] (AsyncWebServerRequest *request) { String inputMessage; // GET input1 value on <ESP_IP>/slider?value=<inputMessage> if (request->hasParam(PARAM_INPUT)) { inputMessage = request->getParam(PARAM_INPUT)->value(); sliderValue = inputMessage; Serial.println(sliderValue.toInt()); ledcWrite(ledChannel, 255 - sliderValue.toInt()); } else { inputMessage = "No message sent"; } request->send(200, "text/plain", "OK"); }); // Start server server.begin(); } void loop() { }
This is the index.html file that the webserver will send anytime a client connects to it. Download the file and save it in your current sketch folder in a sub-folder named “data”. So, the path to it should be (..)/webserver/data/index.html
<!DOCTYPE HTML><html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP Web Server</title> <style> html {font-family: Arial; display: inline-block; text-align: center;} h2 {font-size: 2.3rem;} p {font-size: 1.9rem;} body {max-width: 400px; margin:0px auto; padding-bottom: 25px;} .slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C; outline: none; -webkit-transition: .2s; transition: opacity .2s;} .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;} .slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; } </style> </head> <body> <h1>ESP32 Web Server</h1> <h3>Red Channel</h3> <p><span id="textSliderValueR">%SLIDERVALUE_R%</span></p> <p><input type="range" onchange="updateSliderR(this)" id="pwmSlider_r" min="0" max="255" value="%SLIDERVALUE_R%" step="1" class="slider"></p> <h3>Green Channel</h3> <p><span id="textSliderValueG">%SLIDERVALUE_G%</span></p> <p><input type="range" onchange="updateSliderG(this)" id="pwmSlider_g" min="0" max="255" value="%SLIDERVALUE_G%" step="1" class="slider"></p> <h3>Blue Channel</h3> <p><span id="textSliderValueB">%SLIDERVALUE_B%</span></p> <p><input type="range" onchange="updateSliderB(this)" id="pwmSlider_b" min="0" max="255" value="%SLIDERVALUE_B%" step="1" class="slider"></p> <script> function updateSliderR(element) { var sliderValue = document.getElementById("pwmSlider_r").value; document.getElementById("textSliderValueR").innerHTML = sliderValue; console.log(sliderValue); var xhr = new XMLHttpRequest(); xhr.open("GET", "/slider_r?value="+sliderValue, true); xhr.send(); } function updateSliderG(element) { var sliderValue = document.getElementById("pwmSlider_g").value; document.getElementById("textSliderValueG").innerHTML = sliderValue; console.log(sliderValue); var xhr = new XMLHttpRequest(); xhr.open("GET", "/slider_g?value="+sliderValue, true); xhr.send(); } function updateSliderB(element) { var sliderValue = document.getElementById("pwmSlider_b").value; document.getElementById("textSliderValueB").innerHTML = sliderValue; console.log(sliderValue); var xhr = new XMLHttpRequest(); xhr.open("GET", "/slider_b?value="+sliderValue, true); xhr.send(); } </script> </body> </html>
Next, we'll use the data provided by the on-board sensors to generate dynamic web pages that plot sensor reading in real-time.
We will use the LTR308 light sensor to supply the light intensity values that we would want to display in the web page in a plot.
The way this works is similar to the previous examples:
#include <WiFi.h> #include <ESPAsyncWebServer.h> #include <SPIFFS.h> #include <LTR308.h> #include <Wire.h> LTR308 light; unsigned char ID; // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); void initLight(){ light.begin(); // To start taking measurements, power up the sensor light.setPowerUp(); // Allow for a slight delay in power-up sequence (typ. 5ms from the datasheet) delay(10); light.setGain(0); light.setMeasurementRate(0, 3); } String readLight(){ unsigned long rawData; double lux; // Resulting lux value boolean good; // True if sensor is not saturated light.getData(rawData); good = light.getLux(0, 0, rawData, lux); if(good) return String(lux); else { Serial.println("Failed to read light sensor!"); return "-1"; } } void setup(){ // Serial port for debugging purposes Serial.begin(115200); light.begin(); if (light.getPartID(ID)) { Serial.print("Got Sensor Part ID: 0X"); Serial.print(ID, HEX); Serial.println(); } else { Serial.println("Could not initialize LTR308 sensor"); while(1); } initLight(); // Initialize SPIFFS if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print ESP32 Local IP Address Serial.println(WiFi.localIP()); // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.html"); }); server.on("/light", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/plain", readLight().c_str()); }); // Start server server.begin(); } void loop(){ }
<!DOCTYPE HTML><html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://code.highcharts.com/highcharts.js"></script> <style> body { min-width: 310px; max-width: 800px; height: 400px; margin: 0 auto; } h2 { font-family: Arial; font-size: 2.5rem; text-align: center; } </style> </head> <body> <h2>ESP Weather Station</h2> <div id="chart-light" class="container"></div> </body> <script> var chartL = new Highcharts.Chart({ chart:{ renderTo : 'chart-light' }, title: { text: 'LTR308 Light Intensity' }, series: [{ showInLegend: false, data: [] }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#059e8a' } }, xAxis: { type: 'datetime', dateTimeLabelFormats: { second: '%H:%M:%S' } }, yAxis: { title: { text: 'Illumination (lux)' } }, credits: { enabled: false } }); setInterval(function ( ) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var x = (new Date()).getTime(), y = parseFloat(this.responseText); //console.log(this.responseText); if(chartL.series[0].data.length > 40) { chartL.series[0].addPoint([x, y], true, true, true); } else { chartL.series[0].addPoint([x, y], true, false, true); } } }; xhttp.open("GET", "/light", true); xhttp.send(); }, 3000 ) ; </script> </html>