8-Channel Home Automation with IR, Switch, Timer & Web Control
ESP32 Based Projects
Built a powerful ESP32-based 8-Channel Home Automation System
✅ Manual Control using Push Buttons
✅ IR Remote Control (NEC Protocol)
✅ Timer-Based Relay Switching
✅ DHT22 Temperature & Humidity Monitoring
✅ Web Dashboard (Hosted via ESP32 Access Point)
✅ EEPROM Memory for Saving States
✅ Mute/Unmute Buzzer Feature
✅ AP Credentials Update via Web Interface
Source Code
#include
#include
#include
#include
#include // IR library
/////////////////////////////////
// Pin mappings
/////////////////////////////////
const int relayPins[8] = {23, 22, 21, 19, 18, 5, 17, 4};
const int switchPins[8] = {34, 35, 32, 33, 25, 26, 27, 14};
const int led1Pin = 2; // Connection indicator LED
const int led2Pin = 16; // Timer / boot LED
const int buzzerPin = 15; // Buzzer pin
const int DHTPIN = 13; // DHT22 data pin
const int DHTTYPE = DHT22;
const int IR_PIN = 12; // IR receiver pin
/////////////////////////////////
// EEPROM layout
/////////////////////////////////
const int NUM_RELAYS = 8;
const int SSID_MAX_LEN = 32;
const int PASS_MAX_LEN = 64;
const int RELAY_EEPROM_ADDR = 0; // 0..7: each byte stores one relay state
const int SSID_EEPROM_ADDR = RELAY_EEPROM_ADDR + NUM_RELAYS; // 8..39
const int PASS_EEPROM_ADDR = SSID_EEPROM_ADDR + SSID_MAX_LEN; // 40..103
const int EEPROM_SIZE = PASS_EEPROM_ADDR + PASS_MAX_LEN; // 104; round up to 128
/////////////////////////////////
// Global variables
/////////////////////////////////
bool relayStates[NUM_RELAYS] = {false};
int lastSwitchState[NUM_RELAYS] = {HIGH};
bool timerActive[NUM_RELAYS] = {false};
unsigned long timerEndTime[NUM_RELAYS] = {0};
bool buzzerMuted = false;
String currentSSID;
String currentPassword;
WebServer server(80);
DHT dht(DHTPIN, DHTTYPE);
IRrecv irrecv(IR_PIN);
decode_results irResults;
/////////////////////////////////
// Blink / buzzer timing
/////////////////////////////////
unsigned long blinkPreviousMillis = 0;
const unsigned long blinkInterval = 500; // 500 ms on/off
bool blinkState = false;
/////////////////////////////////
// IR Remote NEC codes (example mapping 1–8 keys to relays 0–7)
/////////////////////////////////
// Replace these with the codes from your specific remote if they differ
const unsigned long IR_CODE_RELAY[NUM_RELAYS] = {
0xFF30CF, // '1' key → Relay 1
0xFF18E7, // '2' key → Relay 2
0xFF7A85, // '3' key → Relay 3
0xFF10EF, // '4' key → Relay 4
0xFF38C7, // '5' key → Relay 5
0xFF5AA5, // '6' key → Relay 6
0xFF42BD, // '7' key → Relay 7
0xFF4AB5 // '8' key → Relay 8
};
/////////////////////////////////
// Function Prototypes
/////////////////////////////////
void updateRelayEEPROM(int idx);
String readStringFromEEPROM(int addr, int maxLen);
void writeStringToEEPROM(int addr, int maxLen, const String &str);
bool anyTimerRunning();
String generateHTML();
void handleRoot();
void handleToggle();
void handleSetTimer();
void handleToggleMute();
void handleUpdateAP();
/////////////////////////////////
// Setup
/////////////////////////////////
void setup() {
Serial.begin(115200);
delay(100);
// Initialize EEPROM
EEPROM.begin(EEPROM_SIZE);
// Restore relay states from EEPROM
for (int i = 0; i < NUM_RELAYS; i++) {
pinMode(relayPins[i], OUTPUT);
byte val = EEPROM.read(RELAY_EEPROM_ADDR + i);
relayStates[i] = (val == 1);
digitalWrite(relayPins[i], relayStates[i] ? HIGH : LOW);
timerActive[i] = false;
}
// Restore AP SSID and password from EEPROM (or set defaults)
currentSSID = readStringFromEEPROM(SSID_EEPROM_ADDR, SSID_MAX_LEN);
currentPassword = readStringFromEEPROM(PASS_EEPROM_ADDR, PASS_MAX_LEN);
if (currentSSID.length() == 0) {
currentSSID = "ESP32_RELAY_AP";
currentPassword = "12345678";
writeStringToEEPROM(SSID_EEPROM_ADDR, SSID_MAX_LEN, currentSSID);
writeStringToEEPROM(PASS_EEPROM_ADDR, PASS_MAX_LEN, currentPassword);
}
// Initialize DHT22
dht.begin();
// Initialize IR receiver
irrecv.enableIRIn(); // Start the IR receiver
// Initialize switch pins with internal pull-up
for (int i = 0; i < NUM_RELAYS; i++) {
pinMode(switchPins[i], INPUT_PULLUP);
lastSwitchState[i] = digitalRead(switchPins[i]);
}
// Initialize LEDs and buzzer pins
pinMode(led1Pin, OUTPUT);
pinMode(led2Pin, OUTPUT);
pinMode(buzzerPin, OUTPUT);
digitalWrite(led1Pin, LOW);
digitalWrite(led2Pin, LOW);
digitalWrite(buzzerPin, LOW);
// Boot pattern: blink both LEDs and buzzer 3 times (200ms ON, 200ms OFF)
for (int i = 0; i < 3; i++) {
digitalWrite(led1Pin, HIGH);
digitalWrite(led2Pin, HIGH);
if (!buzzerMuted) digitalWrite(buzzerPin, HIGH);
delay(200);
digitalWrite(led1Pin, LOW);
digitalWrite(led2Pin, LOW);
digitalWrite(buzzerPin, LOW);
delay(200);
}
// Start Wi-Fi Access Point with stored credentials
WiFi.softAP(currentSSID.c_str(), currentPassword.c_str());
IPAddress IP = WiFi.softAPIP();
Serial.print("AP SSID: "); Serial.println(currentSSID);
Serial.print("AP Password: "); Serial.println(currentPassword);
Serial.print("AP IP address: "); Serial.println(IP);
// Define web server routes
server.on("/", HTTP_GET, handleRoot);
server.on("/toggle", HTTP_GET, handleToggle);
server.on("/settimer", HTTP_GET, handleSetTimer);
server.on("/togglemute", HTTP_GET, handleToggleMute);
server.on("/updateap", HTTP_GET, handleUpdateAP);
server.begin();
Serial.println("HTTP server started");
}
/////////////////////////////////
// Main loop
/////////////////////////////////
void loop() {
unsigned long now = millis();
// 1) Check manual switches
for (int i = 0; i < NUM_RELAYS; i++) {
int currentState = digitalRead(switchPins[i]);
// Detect falling edge (active LOW)
if (currentState == LOW && lastSwitchState[i] == HIGH) {
delay(50); // debounce
if (digitalRead(switchPins[i]) == LOW) {
timerActive[i] = false; // cancel any timer
relayStates[i] = !relayStates[i]; // toggle relay
digitalWrite(relayPins[i], relayStates[i] ? HIGH : LOW);
updateRelayEEPROM(i);
}
}
lastSwitchState[i] = currentState;
}
// 2) Check IR remote input
if (irrecv.decode(&irResults)) {
unsigned long code = irResults.value;
// Iterate through our mapping array
for (int i = 0; i < NUM_RELAYS; i++) {
if (code == IR_CODE_RELAY[i]) {
// Toggle relay i
timerActive[i] = false;
relayStates[i] = !relayStates[i];
digitalWrite(relayPins[i], relayStates[i] ? HIGH : LOW);
updateRelayEEPROM(i);
break;
}
}
irrecv.resume(); // Prepare to receive the next value
}
// 3) Check timers: turn OFF when elapsed
for (int i = 0; i < NUM_RELAYS; i++) {
if (timerActive[i] && now >= timerEndTime[i]) {
relayStates[i] = false;
digitalWrite(relayPins[i], LOW);
updateRelayEEPROM(i);
timerActive[i] = false;
}
}
// 4) LED & Buzzer behavior
bool timerRunning = anyTimerRunning();
bool clientConnected = (WiFi.softAPgetStationNum() > 0);
if (now - blinkPreviousMillis >= blinkInterval) {
blinkPreviousMillis = now;
blinkState = !blinkState;
if (timerRunning) {
// Blink both LEDs and buzzer every 500 ms
digitalWrite(led1Pin, blinkState ? HIGH : LOW);
digitalWrite(led2Pin, blinkState ? HIGH : LOW);
if (!buzzerMuted) digitalWrite(buzzerPin, blinkState ? HIGH : LOW);
else digitalWrite(buzzerPin, LOW);
} else {
// No timer running: handle connection LED
digitalWrite(led2Pin, LOW); // LED2 off when no timer
digitalWrite(buzzerPin, LOW); // Buzzer off when no timer
if (clientConnected) {
// Blink LED1 if at least one station is connected
digitalWrite(led1Pin, blinkState ? HIGH : LOW);
} else {
digitalWrite(led1Pin, LOW); // LED1 off when no client
}
}
}
// 5) Handle incoming HTTP requests
server.handleClient();
}
/////////////////////////////////
// Persist a single relay’s state to EEPROM
/////////////////////////////////
void updateRelayEEPROM(int idx) {
EEPROM.write(RELAY_EEPROM_ADDR + idx, relayStates[idx] ? 1 : 0);
EEPROM.commit();
}
/////////////////////////////////
// Read a null-terminated string from EEPROM
/////////////////////////////////
String readStringFromEEPROM(int addr, int maxLen) {
char buf[maxLen + 1];
for (int i = 0; i < maxLen; i++) {
buf[i] = EEPROM.read(addr + i);
if (buf[i] == '\0') break;
}
buf[maxLen] = '\0';
return String(buf);
}
/////////////////////////////////
// Write a string (with null terminator) into EEPROM
/////////////////////////////////
void writeStringToEEPROM(int addr, int maxLen, const String &str) {
int len = str.length();
if (len > maxLen - 1) len = maxLen - 1;
for (int i = 0; i < len; i++) {
EEPROM.write(addr + i, str[i]);
}
EEPROM.write(addr + len, '\0');
for (int i = len + 1; i < maxLen; i++) {
EEPROM.write(addr + i, 0);
}
EEPROM.commit();
}
/////////////////////////////////
// Check if any relay timer is active
/////////////////////////////////
bool anyTimerRunning() {
for (int i = 0; i < NUM_RELAYS; i++) {
if (timerActive[i]) return true;
}
return false;
}
/////////////////////////////////
// Generate the HTML dashboard
/////////////////////////////////
String generateHTML() {
// Read DHT22 values
float h = dht.readHumidity();
float t = dht.readTemperature();
String tempStr = isnan(t) ? "N/A" : String(t, 1) + " °C";
String humStr = isnan(h) ? "N/A" : String(h, 1) + " %";
String html =
""
""
"ESP32 Relay & DHT & IR Dashboard "
""
""
"ESP32 Relay Dashboard & DHT22 & IR Control
"
// DHT22 Display
"Temperature & Humidity
"
"Temperature: " + tempStr + "
"
"Humidity: " + humStr + "
"
""
// Relay controls + timer inputs
"Relay Controls
";
for (int i = 0; i < NUM_RELAYS; i++) {
html += "";
// ON/OFF toggle button
html += "");
// Timer input and Start button
html += " ON for (s): ";
html += "";
html += "";
}
html += "
";
html += "";
// Buzzer Mute/Unmute
html += "Buzzer Control
";
if (buzzerMuted) {
html += "";
} else {
html += "";
}
html += "";
// AP Settings
html += "Change AP Credentials
"
"SSID:
"
"Password:
"
""
"";
// IR Instruction (optional)
html += "IR Remote Control
"
"Use your IR remote’s 1–8 buttons to toggle Relay 1–8 accordingly.
"
"";
// JavaScript functions
html +=
""
"";
return html;
}
/////////////////////////////////
// Web Handlers
/////////////////////////////////
// Serve dashboard
void handleRoot() {
server.send(200, "text/html", generateHTML());
}
// Toggle relay ON/OFF manually
void handleToggle() {
if (!server.hasArg("relay")) {
server.send(400, "text/plain", "Bad Request");
return;
}
int idx = server.arg("relay").toInt();
if (idx < 0 || idx >= NUM_RELAYS) {
server.send(400, "text/plain", "Invalid Relay Index");
return;
}
timerActive[idx] = false; // cancel any timer
relayStates[idx] = !relayStates[idx];
digitalWrite(relayPins[idx], relayStates[idx] ? HIGH : LOW);
updateRelayEEPROM(idx);
server.send(200, "text/plain", "OK");
}
// Set relay ON for specified duration (seconds), then OFF
void handleSetTimer() {
if (!server.hasArg("relay") || !server.hasArg("delay")) {
server.send(400, "text/plain", "Bad Request");
return;
}
int idx = server.arg("relay").toInt();
int delaySec = server.arg("delay").toInt();
if (idx < 0 || idx >= NUM_RELAYS || delaySec < 1) {
server.send(400, "text/plain", "Invalid Parameters");
return;
}
// Turn relay ON immediately
relayStates[idx] = true;
digitalWrite(relayPins[idx], HIGH);
updateRelayEEPROM(idx);
// Schedule OFF
timerActive[idx] = true;
timerEndTime[idx] = millis() + (unsigned long)delaySec * 1000UL;
server.send(200, "text/plain", "OK");
}
// Mute / Unmute buzzer
void handleToggleMute() {
buzzerMuted = !buzzerMuted;
server.send(200, "text/plain", "OK");
}
// Update AP SSID and password
void handleUpdateAP() {
if (!server.hasArg("ssid") || !server.hasArg("pass")) {
server.send(400, "text/plain", "Bad Request");
return;
}
String newSSID = server.arg("ssid");
String newPass = server.arg("pass");
if (newSSID.length() < 1 || newSSID.length() > SSID_MAX_LEN - 1
|| newPass.length() > PASS_MAX_LEN - 1) {
server.send(400, "text/plain", "Invalid SSID/Password Length");
return;
}
// Write new credentials to EEPROM
writeStringToEEPROM(SSID_EEPROM_ADDR, SSID_MAX_LEN, newSSID);
writeStringToEEPROM(PASS_EEPROM_ADDR, PASS_MAX_LEN, newPass);
currentSSID = newSSID;
currentPassword = newPass;
// Restart AP with new credentials
WiFi.softAP(currentSSID.c_str(), currentPassword.c_str());
server.send(200, "text/plain", "OK");
}