Hosting Dynamic Webpages on ESP32

Posted on December 1, 2022 by Maxwell Yun
...

It’s quite amazing that one can buy a self-contained Wi-Fi/Bluetooth transceiver and powerful microcontroller for… merely $2. This is the Espressif ESP32 module, packing a lot of features with a small size and small cost. Let’s put this miracle module to work this week!

This week I programmed the ESP32 module to host a Wi-Fi network, and simultaneously host a website that dynamically updates. The programming was difficult, and I ended up adapting two sketches from Random Nerd Tutorials to make this work.

Voila, it worked the fifth try! On my phone, I connected to the Wi-Fi network “derpebike”, hosted on my ESP32 module. I connected to 192.168.4.1, the IP address of the ESP32, and was greeted by a nice webpage. The webpage auto-updates every 3 seconds (programmable) and increments a counter for this version, counting up every 3 seconds.

...

I decided to one-up this and stream real-time data from my e-bike’s motor controller to the webpage. The VESC motor controller on my e-bike has a UART serial interface that can (in theory) stream real-time data over, ranging from battery voltage to motor RPM to motor temperature. The latter is especially important because, as I learned from my scooter builds, the motors can easily overheat and keeping a close eye on the motor temperature is important.

The header for the UART pins was not soldered, so I had to disassemble the VESC to solder the pins on. That felt like performing surgery, as I didn’t want to break my expensive VESC.

...

I used an Arduino library written by Github user solidgeek (link here) and merged the library into my server code. In the function that’s called when the website updates, I replaced my earlier counter with code that pulls new data from the VESC.

Before flashing the more complicated server code onto the ESP32, I flashed a simpler test program that reads data from the VESC and streams it to the serial monitor. Unfortunately this didn’t work; the ESP32 couldn’t read any data from the VESC.

Unsurprisingly, the server code didn’t work. I wasn’t able to stream live VESC data to the webpage, although that would be cool. One potential issue is that my VESC 6 is much newer than the VESC 4’s that the library is written for, which could be an incompatibility. I’d need to write my own library to work with my VESC in that case.

Overall, I was able to get the ESP32 to simultaneously host a Wi-Fi network and dynamic webpage. I tried to connect the ESP32 to my VESC motor controller’s UART interface but that didn’t work out. Code that pulls data from the VESC is listed below.

/*********
 Rui Santos // Maxwell Yun
 Complete project details at https://randomnerdtutorials.com
 From:
 - https://randomnerdtutorials.com/esp32-web-server-sent-events-sse/
 - https://randomnerdtutorials.com/esp32-access-point-ap-web-server/
 - https://github.com/SolidGeek/VescUart
*********/

// Load Wi-Fi library
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <VescUart.h>

// Replace with your network credentials
const char* ssid   = "derpebike";
const char* password = "";

VescUart UART;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create an Event Source on /events
AsyncEventSource events("/events");

// Timer variables
unsigned long lastTime = 0; 
unsigned long timerDelay = 3000;

float motor_temp = 0;
float batt_voltage = 0;
float motor_rpm = 0;

void getSensorReadings(){
 if ( UART.getVescValues() ) {
  motor_temp = UART.data.tempMotor;
  batt_voltage = UART.data.inpVoltage;
  motor_rpm = UART.data.rpm;
 }
}

String processor(const String& var){
 getSensorReadings();
 Serial.println(var);
 if(var == "MOTOR_TEMP"){
  return String(motor_temp);
 }
 else if(var == "BATT_VOLTAGE"){
  return String(batt_voltage);
 }
 else if(var == "MOTOR_RPM"){
  return String(motor_rpm);
 }
 return String();
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
 <title>ESP Web Server</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
 <link rel="icon" href="data:,">
 <style>
  html {font-family: Arial; display: inline-block; text-align: center;}
  p { font-size: 1.2rem;}
  body { margin: 0;}
  .topnav { overflow: hidden; background-color: #50B8B4; color: white; font-size: 1rem; }
  .content { padding: 20px; }
  .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
  .cards { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
  .reading { font-size: 1.4rem; }
 </style>
</head>
<body>
 <div class="topnav">
  <h1>DERP-E-BIKE DIAGNOSTICS</h1>
 </div>
 <div class="content">
  <div class="cards">
   <div class="card">
    <p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> MOTOR TEMP</p><p><span class="reading"><span id="temp">%MOTOR_TEMP%</span> °C</span></p>
   </div>
   <div class="card">
    <p><i class="fas fa-tint" style="color:#00add6;"></i> BATTERY VOLTAGE</p><p><span class="reading"><span id="volt">%BATT_VOLTAGE%</span> V</span></p>
   </div>
   <div class="card">
    <p><i class="fas fa-angle-double-down" style="color:#e1e437;"></i> MOTOR_RPM</p><p><span class="reading"><span id="rpm">%MOTOR_RPM%</span> rpm</span></p>
   </div>
  </div>
 </div>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');

source.addEventListener('open', function(e) {
 console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
 if (e.target.readyState != EventSource.OPEN) {
  console.log("Events Disconnected");
 }
}, false);

source.addEventListener('message', function(e) {
 console.log("message", e.data);
}, false);

source.addEventListener('motor_temp', function(e) {
 console.log("motor_temp", e.data);
 document.getElementById("temp").innerHTML = e.data;
}, false);

source.addEventListener('batt_voltage', function(e) {
 console.log("batt_voltage", e.data);
 document.getElementById("volt").innerHTML = e.data;
}, false);

source.addEventListener('motor_rpm', function(e) {
 console.log("motor_rpm", e.data);
 document.getElementById("rpm").innerHTML = e.data;
}, false);
}
</script>
</body>
</html>)rawliteral";

void init_wifi_and_server() {
  // Connect to Wi-Fi network with SSID and password
 Serial.print("Setting AP (Access Point)…");
 // Remove the password parameter, if you want the AP (Access Point) to be open
 WiFi.softAP(ssid, password);

 IPAddress IP = WiFi.softAPIP();
 Serial.print("AP IP address: ");
 Serial.println(IP);


 // Handle Web Server
 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", index_html, processor);
 });

 // Handle Web Server Events
 events.onConnect([](AsyncEventSourceClient *client){
  if(client->lastId()){
   Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
  }
  // send event with message "hello!", id current millis
  // and set reconnect delay to 1 second
  client->send("hello!", NULL, millis(), 10000);
 });
 server.addHandler(&events);
 server.begin();
}

void setup() {
 Serial.begin(9600);
 Serial1.begin(19200);
 while (!Serial) {;}
 UART.setSerialPort(&Serial1);
 init_wifi_and_server();
}

void loop(){
if ((millis() - lastTime) > timerDelay) {
  getSensorReadings();
  Serial.printf("Motor Temp = %.2f ºC \n", motor_temp);
  Serial.printf("Batt Voltage = %.2f V \n", batt_voltage);
  Serial.printf("Motor RPM = %.2f RPM\n", motor_rpm);
  Serial.println();

  // Send Events to the Web Server with the Sensor Readings
  events.send("ping",NULL,millis());
  events.send(String(motor_temp).c_str(),"motor_temp",millis());
  events.send(String(batt_voltage).c_str(),"batt_voltage",millis());
  events.send(String(motor_rpm).c_str(),"motor_rpm",millis());
  
  lastTime = millis();
 }
}
Key Takeaways
  • Arduino ESP32 networking
  • UART Serial