Week 14 Documentation for Fab Lab
Project Overview
The goal of this project is to control an onboard LED on an ESP32-S3 via a web interface. The project began by attempting to use Blynk to control the LED, but we later moved on to creating a web server using the ESP32 and custom HTML/CSS for the interface.
Blynk
Blynk Interface
Adding a new device
Setting up the dashboard
Setting up Datastream
Virtual pin setup
Step 1: Initial Attempt Using Blynk (ESP32-S3)
I started by trying to control the ESP32's onboard LED using the Blynk app on the phone.
Code 1: ESP32 + Blynk (Initial Attempt)
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
char ssid[] = "your-SSID";
char pass[] = "your-PASSWORD";
char auth[] = "your-BLYNK-AUTH-TOKEN";
void setup()
{
Serial.begin(115200);
Blynk.begin(auth, ssid, pass);
}
void loop()
{
Blynk.run();
}
Issues Encountered
- The Blynk library (BlynkSimpleEsp32.h) was not found in the Arduino IDE, and this led to the first error during compilation: "BlynkSimpleEsp32.h not found".
-
There was also an issue with the BLYNK_AUTH_TOKEN not being declared in the scope, leading to an error in setting up the Blynk connection.
-
I later found out that the ESP32 favoured an android based hotspot in contrast to an IOS based hotspot, I took it from a friend and had limited access to it, so I could only establish a connection, but not control the working of an LED. I could, however, see the board being online on both Blynk website and phone app.
Moving On to Web Server with ESP32
Since we couldn't proceed with Blynk, I moved on to using the ESP32 as a web server to control the onboard LED.
Code 2: Simple Web Server for LED Control
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "your-SSID";
const char* password = "your-PASSWORD";
AsyncWebServer server(80);
void setup(){
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi!");
// Serve HTML page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String html = "<html><body><h1>ESP32 LED Control</h1><button onclick=\"toggleLED()\">Toggle LED</button><script>";
html += "function toggleLED(){fetch('/toggle');}</script></body></html>";
request->send(200, "text/html", html);
});
// Handle LED control
server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request){
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
request->send(200, "text/plain", "LED Toggled");
});
// Start server
server.begin();
}
void loop(){}


Errors Encountered
-
Device Offline: Initially, after uploading the code, the ESP32 was showing as offline in the Serial Monitor. This was resolved by checking the Wi-Fi credentials and ensuring they were correct.
-
Webpage not responsive: We observed that the webpage served by the ESP32 was functional, but the design was very basic. We wanted to make it more futuristic and aesthetically pleasing.
Step 3: Making the Webpage Futuristic with a PWM Slider for LED Brightness
I decided to add a PWM slider on the webpage to control the brightness of the onboard LED.
Code 3: Futuristic Webpage with PWM Slider
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "your-SSID";
const char* password = "your-PASSWORD";
const int LED_PIN = LED_BUILTIN;
AsyncWebServer server(80);
void setup(){
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi!");
pinMode(LED_PIN, OUTPUT);
// Serve HTML page with PWM slider
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String html = "<html><head><style>body { font-family: Arial, sans-serif; background-color: #1c1c1c; color: white; text-align: center; }</style></head><body>";
html += "<h1>ESP32 LED Control</h1><input type='range' min='0' max='255' value='128' id='pwmSlider' onchange='updatePWM(this.value)'><br>";
html += "<span id='pwmValue'>128</span><script>";
html += "function updatePWM(val){fetch('/setPWM?value='+val); document.getElementById('pwmValue').innerText = val;}</script>";
html += "</body></html>";
request->send(200, "text/html", html);
});
// Handle PWM value change
server.on("/setPWM", HTTP_GET, [](AsyncWebServerRequest *request){
String pwmValue = request->getParam("value")->value();
int pwm = pwmValue.toInt();
analogWrite(LED_PIN, pwm); // Set the LED brightness using PWM
request->send(200, "text/plain", "PWM set to " + pwmValue);
});
// Start server
server.begin();
}
void loop(){}
Issues Encountered
Slider not controlling brightness: Initially, the slider would not control the brightness of the LED as expected. This was fixed by ensuring that we were using analogWrite() correctly to set the PWM value for the LED.
Step 4: Finding a Workaround for PWM Control Without Using ledcAttachPin
After facing issues with the ledcAttachPin() function, we decided to work around this without using the LED control library.
Workaround Code
We used analogWrite() for controlling the brightness instead of using the ledcAttachPin() function.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
const char* ssid = "your-SSID";
const char* password = "your-PASSWORD";
const int LED_PIN = LED_BUILTIN;
AsyncWebServer server(80);
void setup(){
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi!");
pinMode(LED_PIN, OUTPUT);
// Serve HTML page with PWM slider
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String html = "<html><head><style>body { font-family: Arial, sans-serif; background-color: #1c1c1c; color: white; text-align: center; }</style></head><body>";
html += "<h1>ESP32 LED Control</h1><input type='range' min='0' max='255' value='128' id='pwmSlider' onchange='updatePWM(this.value)'><br>";
html += "<span id='pwmValue'>128</span><script>";
html += "function updatePWM(val){fetch('/setPWM?value='+val); document.getElementById('pwmValue').innerText = val;}</script>";
html += "</body></html>";
request->send(200, "text/html", html);
});
// Handle PWM value change
server.on("/setPWM", HTTP_GET, [](AsyncWebServerRequest *request){
String pwmValue = request->getParam("value")->value();
int pwm = pwmValue.toInt();
analogWrite(LED_PIN, pwm); // Set the LED brightness using PWM
request->send(200, "text/plain", "PWM set to " + pwmValue);
});
// Start server
server.begin();
}
void loop(){}

Issues and Fixes
-
Slider Not Working: I fixed issues with the slider by ensuring the proper use of analogWrite() and setting up the HTML for dynamic updates.
-
Webpage Aesthetics: I used custom CSS to improve the look and feel of the webpage, making it more futuristic and aesthetic.
Processing
I tried making a simple game on processing, that indicates its progress through the controller, via some output device.
The basic principle of the game was to avoid obstacles in the game, and if you hit them the LED connected to the board will light up.
I could not make Processing and Arduino IDE communicate even when using a serial communication.
I also switched boards from the ESP32 to RP2040 to check if that was an issue but still no luck.
Arduino code
#define LED_PIN D2 // Built-in LED on Raspberry Pi Pico
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
}
void loop() {
if (Serial.available()) {
String command = Serial.readStringUntil('\n');
command.trim();
if (command == "HIT") {
digitalWrite(LED_PIN, HIGH);
delay(200); // Brief flash
digitalWrite(LED_PIN, LOW);
}
}
}
Processing code
import processing.serial.*;
Serial myPort;
float x = 100;
float y = 200;
float r = 50;
float enemyX = 600;
float enemyY = 200;
boolean hit = false;
void setup() {
size(800, 400);
background(0);
println(Serial.list()); // Print available ports
// Choose the correct port manually if needed
// Example: myPort = new Serial(this, Serial.list()[1], 115200);
myPort = new Serial(this, Serial.list()[0]"", 115200);
myPort.clear(); // Clear old serial buffer
delay(1000); // Let port settle
}
void draw() {
background(20);
// Draw player
fill(0, 255, 0);
ellipse(x, y, r, r);
// Draw enemy
fill(hit ? color(255, 0, 0) : color(255));
ellipse(enemyX, enemyY, r, r);
// Check collision
if (dist(x, y, enemyX, enemyY) < r) {
if (!hit) {
println("Sending HIT to RP2040 🚀");
myPort.write("HIT\n");
hit = true;
}
} else {
hit = false;
}
// Move enemy
enemyX -= 3;
if (enemyX < -r) {
enemyX = width + r;
enemyY = random(100, height - 100);
}
}
void keyPressed() {
if (key == 'w') y -= 10;
if (key == 's') y += 10;
if (key == 'a') x -= 10;
if (key == 'd') x += 10;
}
Processing interface
Gameplay