In this tutorial, we will learn how to remotely control LEDs connected to a Xiao ESP32-C3 using a Django web application. This allows you to toggle LEDs from anywhere in the world using a web interface.
Project Overview
- Django Backend:
- Stores LED states in a database.
- Provides an API endpoint to fetch LED statuses.
- Offers a web interface to toggle LEDs.
- ESP32-C3 Firmware:
- Connects to WiFi.
- Requests the LED status from Django.
- Updates LED states accordingly.
Step 1: Setting Up Django Backend
1.1 Install Django and Required Packages
Ensure you have Python installed, then install Django:
pip install django
1.2 Create a Django Project & App
django-admin startproject led_control_project
cd led_control_project
python manage.py startapp led_control
Add led_control
to INSTALLED_APPS
in settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'led_control', # Add this line
]
1.3 Create the LED Model
In led_control/models.py
, define a model to store the LED states:
from django.db import models
class LedState(models.Model):
led1_status = models.BooleanField(default=False)
led2_status = models.BooleanField(default=False)
led3_status = models.BooleanField(default=False)
led4_status = models.BooleanField(default=False)
def __str__(self):
return f"LED1: {'ON' if self.led1_status else 'OFF'}, " \
f"LED2: {'ON' if self.led2_status else 'OFF'}, " \
f"LED3: {'ON' if self.led3_status else 'OFF'}, " \
f"LED4: {'ON' if self.led4_status else 'OFF'}"
Run migrations:
python manage.py makemigrations
python manage.py migrate
1.4 Create Views to Control LEDs
In led_control/views.py
, create views to update and fetch LED statuses:
import json
from django.http import JsonResponse
from .models import LedState
from django.shortcuts import render,redirect
def led_control(request):
# Get or create the LedState instance
led_state, created = LedState.objects.get_or_create(id=1)
# Handling LED ON/OFF toggle for each LED individually
if request.method == 'POST':
if 'led1' in request.POST:
led_state.led1_status = not led_state.led1_status
elif 'led2' in request.POST:
led_state.led2_status = not led_state.led2_status
elif 'led3' in request.POST:
led_state.led3_status = not led_state.led3_status
elif 'led4' in request.POST:
led_state.led4_status = not led_state.led4_status
# Save the updated LED state
led_state.save()
return redirect('led_control') # Redirect to the same page to update state
return render(request, 'led_control.html', {'led_state': led_state})
def get_led_status(request):
"""Fetch the status of all LEDs"""
led_state = LedState.objects.first()
if led_state:
return JsonResponse({
"led1_status": int(led_state.led1_status),
"led2_status": int(led_state.led2_status),
"led3_status": int(led_state.led3_status),
"led4_status": int(led_state.led4_status)
})
return JsonResponse({"error": "No LED state found"}, status=404)
1.5 Configure URLs
In led_control/urls.py
, add the API routes:
from django.urls import path
from .views import get_led_status, led_control
urlpatterns = [
path('', led_control, name='led_control'),
path('led-status/', get_led_status, name='get_led_status'),
]
Include them in led_control_project/urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('led_control.urls')),
]
Create a new HTML file inside your templates folder:
📂 templates/led_control.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LED Control</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
body {
font-family: 'Roboto', sans-serif;
background-color: #111;
color: #fff;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: linear-gradient(45deg, #3e8e41, #004d00);
}
.container {
background-color: #222;
border-radius: 20px;
padding: 40px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
text-align: center;
width: 450px;
}
h1 {
font-size: 28px;
margin-bottom: 20px;
color: #fff;
letter-spacing: 2px;
}
.led-button {
padding: 15px 30px;
margin: 15px;
cursor: pointer;
font-size: 22px;
color: white;
border: none;
border-radius: 50px;
transition: background-color 0.3s ease, transform 0.3s ease;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.on {
background-color: #28a745;
}
.off {
background-color: #dc3545;
}
.led-button:hover {
transform: scale(1.1);
}
.led-button:active {
transform: scale(0.95);
}
.led-button:focus {
outline: none;
}
.status-label {
font-size: 18px;
margin-bottom: 15px;
color: #ccc;
display: block;
}
.led-control-wrapper {
margin-bottom: 25px;
}
.icon {
font-size: 32px;
}
</style>
</head>
<body>
<div class="container">
<h1>LED Control Dashboard</h1>
<form method="post">
{% csrf_token %}
<div class="led-control-wrapper">
<span class="status-label">LED 1:</span>
<button type="submit" name="led1" class="led-button {% if led_state.led1_status %}on{% else %}off{% endif %}">
<i class="fas {% if led_state.led1_status %}fa-lightbulb{% else %}fa-lightbulb-slash{% endif %} icon"></i>
<span>{% if led_state.led1_status %}ON{% else %}OFF{% endif %}</span>
</button>
</div>
<div class="led-control-wrapper">
<span class="status-label">LED 2:</span>
<button type="submit" name="led2" class="led-button {% if led_state.led2_status %}on{% else %}off{% endif %}">
<i class="fas {% if led_state.led2_status %}fa-lightbulb{% else %}fa-lightbulb-slash{% endif %} icon"></i>
<span>{% if led_state.led2_status %}ON{% else %}OFF{% endif %}</span>
</button>
</div>
<div class="led-control-wrapper">
<span class="status-label">LED 3:</span>
<button type="submit" name="led3" class="led-button {% if led_state.led3_status %}on{% else %}off{% endif %}">
<i class="fas {% if led_state.led3_status %}fa-lightbulb{% else %}fa-lightbulb-slash{% endif %} icon"></i>
<span>{% if led_state.led3_status %}ON{% else %}OFF{% endif %}</span>
</button>
</div>
</form>
</div>
</body>
</html>
Run the server:
python manage.py runserver 0.0.0.0:8000
Step 2: ESP32-C3 Code to Control LEDs
2.1 Install Required Libraries
Ensure you have the Arduino IDE and the ESP32 board package installed.
2.2 ESP32-C3 Code
Upload the following code to your Xiao ESP32-C3:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h> // JSON Parsing Library
const char* ssid = "ApyCoder";
const char* password = "Asif12345";
const char* serverURL = "http://10.129.101.93:8000/led-status/";
#define LED1_PIN 10
#define LED2_PIN 9
#define LED3_PIN 8
#define LED4_PIN 7
bool lastLedState[4] = {LOW, LOW, LOW, LOW}; // Store last LED states
void setup() {
Serial.begin(115200);
// Initialize LED pins
pinMode(LED1_PIN, OUTPUT);
pinMode(LED2_PIN, OUTPUT);
pinMode(LED3_PIN, OUTPUT);
pinMode(LED4_PIN, OUTPUT);
// Set LEDs to LOW initially
digitalWrite(LED1_PIN, LOW);
digitalWrite(LED2_PIN, LOW);
digitalWrite(LED3_PIN, LOW);
digitalWrite(LED4_PIN, LOW);
// Connect to WiFi
Serial.print("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected to WiFi!");
}
void loop() {
// Check WiFi connection
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi Disconnected! Reconnecting...");
WiFi.reconnect();
delay(1000);
return;
}
// Make HTTP GET request
HTTPClient http;
http.begin(serverURL);
int httpResponseCode = http.GET();
// If request is successful
if (httpResponseCode == 200) {
String payload = http.getString();
Serial.println("Server Response: " + payload);
// Parse JSON using ArduinoJson
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error) {
int ledStates[4] = {
doc["led1_status"],
doc["led2_status"],
doc["led3_status"],
doc["led4_status"]
};
// Debug: Print LED states
Serial.print("LED States: ");
for (int i = 0; i < 4; i++) {
Serial.print(ledStates[i]);
Serial.print(" ");
}
Serial.println();
// Check if LED states have changed and update the LEDs
for (int i = 0; i < 4; i++) {
int pin = (i == 0) ? LED1_PIN : (i == 1) ? LED2_PIN : (i == 2) ? LED3_PIN : LED4_PIN;
if (ledStates[i] != lastLedState[i]) {
Serial.print("Changing LED ");
Serial.print(i + 1);
Serial.print(" to ");
Serial.println(ledStates[i] ? "ON" : "OFF");
// Update the LED pin based on the state
digitalWrite(pin, ledStates[i] ? HIGH : LOW);
lastLedState[i] = ledStates[i]; // Update the last state
}
}
} else {
Serial.println("JSON Parsing Error!");
}
} else {
Serial.print("HTTP Error: ");
Serial.println(httpResponseCode);
}
http.end();
// Add delay for stability
delay(1000); // Increase delay to 1 second for stability
}
Step 3: Running the System
- Start your Django server (
python manage.py runserver 0.0.0.0:8000
). - Power up the Xiao ESP32-C3.
- Open the Django Web Interface to toggle LEDs.
- The ESP32 will fetch the LED states and update accordingly.
Conclusion
You have successfully built an IoT system to control LEDs on a Xiao ESP32-C3 from anywhere in the world using Django. This setup can be extended to control other IoT devices such as relays, motors, or sensors. 🚀
🔗 Subscribe to my YouTube channel
💻 Check out the code on GitHub