ESP32 IoT Smart Switch
ESP32 IoT Smart Switch - 4CH WiFi Relay Controller
Build a professional 4-channel IoT smart switch that lets you control electrical appliances from your phone via WiFi, while also providing manual control through onboard tactile switches. Perfect for home automation projects!
How the System Works
Imagine controlling four electrical devices from your phone… and if you don't want to use your phone, you can also control them manually using these switches.
This 4-channel ESP32 IoT smart switch lets you control appliances wirelessly or manually anytime. The custom web interface allows you to turn all lights on/off simultaneously or control each channel individually. When using the onboard manual buttons, the status updates instantly on the web interface.
The system uses a Hi-Link module to convert household AC voltage to 5V DC, a ULN2003 IC to drive the relays, and an ESP32 to host the web server and handle WiFi communication.
Key Features
Components Required
๐ง Main Components
- ESP32 DevKit V1
- ULN2003A Darlington IC
- 4x SRD-05VDC-SL-C 5V Relays
- HLK-PM01 AC-DC Converter
- 14D431K MOV (Varistor)
⚡ Protection & Filtering
- 250V Slow-Blow Fuse
- 1000ยตF Electrolytic Capacitor
- 0.1ยตF Ceramic Capacitor
- 330ฮฉ Resistors
๐ก Indicators & Controls
- 5mm Red LED Indicator
- 12mm Tactile Push Buttons (4x)
- 2-Pin Screw Terminals (5x)
- Female Header Pins
Complete Materials List with Buy Links:
- ESP32 DevKit V1 - Buy Here
- ULN2003A Darlington Transistor Array IC - Buy Here
- SRD-05VDC-SL-C 5V Relay (4x) - Buy Here
- HLK-PM01 220V AC to 5V DC Power Module - Buy Here
- 14D431K Metal Oxide Varistor (MOV) - Buy Here
- 250V Slow-Blow Fuse - Buy Here
- 1000ยตF Electrolytic Capacitor - Buy Here
- 0.1ยตF Ceramic Capacitor - Buy Here
- 330ฮฉ Resistor - Buy Here
- 5mm LED Indicator - Buy Here
- 12mm Tactile Push Button Switch (4x) - Buy Here
- 2-Pin Screw Terminal Block Connector (5x) - Buy Here
For Testing:
- Modular Holder for bulb (4x) - Buy Here
- 2 Core cable 1 sq mm - Buy Here
- Bulbs (4x) - Buy Here
- Tester - Buy Here
Schematic Diagram
Click on the image to view in full size or use the download link below:
PCB Gerber Files
Download the PCB Gerber files for manufacturing your custom circuit board:
Order PCBs using JLCPCB's professional service:
Order PCBs from JLCPCB๐ Design Your Own PCBs with Altium
For designing professional PCBs like this one, I use Altium — a platform that makes electronics design easier, faster, and more connected. It lets your team collaborate in real time by sharing designs, managing versions, and staying in sync from anywhere.
For students, they have Altium Student Lab. Just sign up with your university email for free access to step-by-step PCB courses and industry-recognized certifications that prepare you for real-world roles.
๐ Start building your future with AltiumAssembly Steps
Start with the Hi-Link module, then add the fuse, resistors, capacitors, and MOV. Apply masking tape to prevent components from falling while soldering.
Solder the ULN2003 IC, followed by the four 5V relays. Ensure no solder bridges cause short circuits.
Solder the four blue LEDs with their 330ฮฉ resistors, then add the four tactile switches for manual control.
Install the five 2-pin screw terminals - one for AC input and four for appliance connections.
Attach the ESP32 module to the female header pins. The board is now ready!
Pin Connections
| Component | ESP32 Pin | Notes |
|---|---|---|
| Relay CH1 | GPIO16 | Via ULN2003 |
| Relay CH2 | GPIO17 | Via ULN2003 |
| Relay CH3 | GPIO18 | Via ULN2003 |
| Relay CH4 | GPIO19 | Via ULN2003 |
| Button CH1 | GPIO23 | INPUT_PULLUP |
| Button CH2 | GPIO22 | INPUT_PULLUP |
| Button CH3 | GPIO33 | INPUT_PULLUP |
| Button CH4 | GPIO32 | INPUT_PULLUP |
System Demo Video
Arduino Code
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "Your_WiFi_Name";
const char* password = "Your_Password";
WebServer server(80);
// Relay pins (connected to ULN2003 inputs)
const int relayPins[4] = {16, 17, 18, 19}; // D16, D17, D18, D19
// =====================================================
// MANUAL BUTTON MAPPING - CH3 AND CH4 SWAPPED ONLY
// =====================================================
// CH1 reads from GPIO23
// CH2 reads from GPIO22
// CH3 reads from GPIO32 (was GPIO33)
// CH4 reads from GPIO33 (was GPIO32)
// =====================================================
const int buttonPins[4] = {23, 22, 33, 32};
// Relay states
bool relayStates[4] = {false, false, false, false};
// Button state tracking
int lastButtonState[4] = {HIGH, HIGH, HIGH, HIGH};
int buttonState[4] = {HIGH, HIGH, HIGH, HIGH};
unsigned long lastDebounceTime[4] = {0, 0, 0, 0};
const unsigned long debounceDelay = 100;
// ==================== RELAY CONTROL ====================
void setRelay(int channel, bool state) {
if (channel >= 0 && channel < 4) {
relayStates[channel] = state;
digitalWrite(relayPins[channel], state ? HIGH : LOW);
Serial.printf("Channel %d: %s\n", channel + 1, state ? "ON" : "OFF");
}
}
void toggleRelay(int channel) {
if (channel >= 0 && channel < 4) {
setRelay(channel, !relayStates[channel]);
}
}
// ==================== PCB HARDWARE SETUP ====================
void initPCB() {
Serial.println("\n=== 4CH IoT Switch Starting ===");
// Configure relay pins as outputs
for (int i = 0; i < 4; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], LOW);
}
// Configure button pins
for (int i = 0; i < 4; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
lastButtonState[i] = digitalRead(buttonPins[i]);
}
// Show button mapping
Serial.println("Button Mapping (CH3 & CH4 Swapped):");
Serial.println("CH1 <- GPIO23");
Serial.println("CH2 <- GPIO22");
Serial.println("CH3 <- GPIO32");
Serial.println("CH4 <- GPIO33");
}
// ==================== WEB PAGE ====================
String webpage() {
String html = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes">
<title>4CH IoT Switch</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0c10;
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
.container {
max-width: 900px;
width: 100%;
background: #14181f;
border-radius: 32px;
padding: 28px;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
border: 1px solid #2a3038;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
flex-wrap: wrap;
gap: 15px;
}
h1 {
color: #fff;
font-size: 24px;
font-weight: 600;
}
.wifi-badge {
display: flex;
align-items: center;
gap: 8px;
background: #1e242c;
padding: 10px 18px;
border-radius: 40px;
border: 1px solid #333b45;
}
.wifi-led {
width: 10px;
height: 10px;
border-radius: 50%;
background: #f44336;
box-shadow: 0 0 10px #f44336;
transition: all 0.3s;
}
.wifi-led.online {
background: #4caf50;
box-shadow: 0 0 15px #4caf50;
}
.wifi-text {
color: #e0e0e0;
font-size: 14px;
font-weight: 500;
}
/* Grid Layout */
.channels-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin: 30px 0;
}
.channel-card {
background: #1e242c;
border-radius: 24px;
padding: 24px;
border: 1px solid #2f3742;
transition: all 0.3s;
text-align: center;
}
.channel-card.active {
border-color: #4caf50;
box-shadow: 0 0 30px rgba(76, 175, 80, 0.2);
}
.channel-name {
color: #e0e0e0;
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
}
/* LED Indicator */
.web-led {
width: 60px;
height: 60px;
border-radius: 50%;
margin: 0 auto 20px;
background: #f44336;
box-shadow: 0 0 20px #f44336;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
}
.web-led.on {
background: #4caf50;
box-shadow: 0 0 40px #4caf50;
}
.web-led span {
color: white;
font-size: 24px;
font-weight: bold;
}
/* Button Style with SVG - SYMBOL BIGGER AND CENTERED */
.power-btn {
width: 100%;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #3a3f48, #151a22 90%);
border: 8px solid #4a525d;
box-shadow:
inset 0 8px 12px rgba(255,255,255,0.1),
inset 0 -8px 15px rgba(0,0,0,0.7),
0 10px 25px rgba(0,0,0,0.5);
cursor: pointer;
transition: all 0.15s;
display: flex;
align-items: center;
justify-content: center;
margin: 15px 0;
}
.power-btn:active {
transform: translateY(5px) scale(0.96);
}
/* SVG Power Icon - LARGER AND PERFECTLY CENTERED */
.power-svg {
width: 100px;
height: 100px;
display: block;
margin: 0 auto;
}
.power-svg path,
.power-svg line {
stroke: #8a929f;
stroke-width: 12;
stroke-linecap: round;
fill: none;
transition: all 0.3s;
}
.power-btn.active .power-svg path,
.power-btn.active .power-svg line {
stroke: #4caf50;
filter: drop-shadow(0 0 10px #4caf50);
}
/* Control Panel */
.control-panel {
display: flex;
gap: 16px;
justify-content: center;
margin-top: 30px;
flex-wrap: wrap;
}
.master-btn {
padding: 14px 32px;
border: none;
border-radius: 40px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.master-btn.all-on {
background: #2e7d32;
color: white;
}
.master-btn.all-off {
background: #c62828;
color: white;
}
.master-btn:active {
transform: scale(0.96);
}
@media (max-width: 600px) {
.container { padding: 20px; }
.channels-grid { grid-template-columns: 1fr; }
.power-svg { width: 80px; height: 80px; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>⚡ 4CH IoT Switch</h1>
<div class="wifi-badge">
<span class="wifi-led" id="wifiLed"></span>
<span class="wifi-text" id="wifiText">Connecting...</span>
</div>
</div>
<!-- Channels Grid -->
<div class="channels-grid" id="channelsGrid"></div>
<!-- Master Control -->
<div class="control-panel">
<button class="master-btn all-on" onclick="allOn()">
<span>⚡</span> ALL ON
</button>
<button class="master-btn all-off" onclick="allOff()">
<span>⭘</span> ALL OFF
</button>
</div>
</div>
<script>
let channels = [
{ id: 0, name: "CH1", state: false },
{ id: 1, name: "CH2", state: false },
{ id: 2, name: "CH3", state: false },
{ id: 3, name: "CH4", state: false }
];
function renderChannels() {
const grid = document.getElementById('channelsGrid');
grid.innerHTML = '';
channels.forEach(ch => {
const card = document.createElement('div');
card.className = `channel-card ${ch.state ? 'active' : ''}`;
card.id = `ch-${ch.id}`;
card.innerHTML = `
<div class="channel-name">${ch.name}</div>
<div class="web-led ${ch.state ? 'on' : ''}" id="led-${ch.id}">
<span>${ch.state ? 'ON' : 'OFF'}</span>
</div>
<div class="power-btn ${ch.state ? 'active' : ''}" onclick="toggleChannel(${ch.id})">
<svg class="power-svg" viewBox="0 0 100 100">
<!-- Vertical line -->
<line x1="50" y1="10" x2="50" y2="45"/>
<!-- Circle arc -->
<path d="M 28 30 A 30 30 0 1 0 72 30"/>
</svg>
</div>
`;
grid.appendChild(card);
});
}
function toggleChannel(id) {
fetch(`/toggle?id=${id}`)
.then(() => console.log(`Toggled CH${id+1}`))
.catch(err => console.log('Error:', err));
}
function allOn() {
fetch('/all/on')
.then(() => console.log('All ON'))
.catch(err => console.log('Error:', err));
}
function allOff() {
fetch('/all/off')
.then(() => console.log('All OFF'))
.catch(err => console.log('Error:', err));
}
function updateStatus() {
fetch('/status')
.then(res => res.json())
.then(data => {
document.getElementById('wifiLed').className = 'wifi-led online';
document.getElementById('wifiText').textContent = 'Connected';
channels.forEach((ch, i) => {
ch.state = data[i];
const card = document.getElementById(`ch-${i}`);
const led = document.getElementById(`led-${i}`);
const btn = card?.querySelector('.power-btn');
if (card) {
if (ch.state) {
card.classList.add('active');
if (led) {
led.className = 'web-led on';
led.innerHTML = '<span>ON</span>';
}
btn?.classList.add('active');
} else {
card.classList.remove('active');
if (led) {
led.className = 'web-led';
led.innerHTML = '<span>OFF</span>';
}
btn?.classList.remove('active');
}
}
});
})
.catch(() => {
document.getElementById('wifiLed').className = 'wifi-led';
document.getElementById('wifiText').textContent = 'Offline';
});
}
renderChannels();
updateStatus();
setInterval(updateStatus, 1000);
</script>
</body>
</html>
)rawliteral";
return html;
}
// ==================== WEB HANDLERS ====================
void handleRoot() {
server.send(200, "text/html", webpage());
}
void handleToggle() {
if (server.hasArg("id")) {
int id = server.arg("id").toInt();
if (id >= 0 && id < 4) {
toggleRelay(id);
server.send(200, "text/plain", "OK");
return;
}
}
server.send(400, "text/plain", "Invalid");
}
void handleStatus() {
String json = "[";
for (int i = 0; i < 4; i++) {
if (i > 0) json += ",";
json += relayStates[i] ? "true" : "false";
}
json += "]";
server.send(200, "application/json", json);
}
void handleAllOn() {
for (int i = 0; i < 4; i++) {
setRelay(i, true);
}
server.send(200, "text/plain", "OK");
}
void handleAllOff() {
for (int i = 0; i < 4; i++) {
setRelay(i, false);
}
server.send(200, "text/plain", "OK");
}
// ==================== SETUP ====================
void setup() {
Serial.begin(115200);
// Initialize PCB hardware
initPCB();
// Connect to WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n✅ WiFi Connected!");
Serial.print("๐ก IP: ");
Serial.println(WiFi.localIP());
}
// Setup routes
server.on("/", handleRoot);
server.on("/toggle", handleToggle);
server.on("/status", handleStatus);
server.on("/all/on", handleAllOn);
server.on("/all/off", handleAllOff);
server.begin();
}
// ==================== LOOP ====================
void loop() {
server.handleClient();
// Read buttons with debounce
for (int i = 0; i < 4; i++) {
int reading = digitalRead(buttonPins[i]);
if (reading != lastButtonState[i]) {
lastDebounceTime[i] = millis();
}
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
if (reading != buttonState[i]) {
buttonState[i] = reading;
if (buttonState[i] == LOW) {
toggleRelay(i);
}
}
}
lastButtonState[i] = reading;
}
delay(10);
}
Code Explanation:
Key Features:
- Built-in web server with responsive UI for mobile control
- Dual control - WiFi and manual buttons with real-time sync
- Debounced button reading for reliable manual operation
- Master ON/OFF controls for all channels
- Custom SVG power buttons with visual feedback
- Real-time status updates every second
Setup Instructions:
- Change
ssidandpasswordto your WiFi credentials - Install ESP32 board support in Arduino IDE
- Select ESP32 Dev Module and correct COM port
- Upload code and check Serial Monitor for IP address
- Access the web interface from any device on same network
Technical Specifications
- Microcontroller: ESP32 DevKit V1 (Dual-core 240MHz)
- Relay Type: 4x SRD-05VDC-SL-C (10A 250VAC / 10A 30VDC)
- Power Supply: 220V AC to 5V DC via HLK-PM01
- Driver IC: ULN2003A Darlington Array
- Manual Control: 4x 12mm tactile switches
- WiFi: 2.4GHz 802.11 b/g/n
- Web Interface: Responsive design with SVG buttons
- Protection: Fuse, MOV, filtering capacitors
Applications
๐ง Safety Warning: This project involves working with high voltage AC (220V). Always disconnect power while assembling and testing. Ensure proper insulation and use appropriate safety equipment. Double-check all connections before applying power.
Comments
Post a Comment