Smart home automation project using esp32

Smart Home Automation Using ESP32 - Calendar Scheduler

Smart Home Automation Project Using ESP32

Build a professional smart home automation system with calendar-based scheduling! Control your appliances from your phone via WiFi, schedule automatic ON/OFF times, and enjoy a beautiful web interface with interactive calendar and time picker.


How the System Works

This smart home automation system lets you control electrical appliances from your phone via WiFi, with a powerful calendar-based scheduling feature. Here's how it works:

  • ESP32 Web Server: Hosts a beautiful web interface accessible from any device on your network
  • Manual Control: Tap the large power button to instantly turn the appliance ON/OFF
  • Calendar Scheduler: Select a date from the interactive calendar, pick a time, and set when to turn ON/OFF
  • Weekly Repeats: Create schedules that repeat every week on selected days (Monday, Tuesday, etc.)
  • NTP Time Sync: Automatically synchronizes time from the internet for accurate scheduling
  • Real-time Status: The interface updates instantly when you manually control the relay
  • Schedule Management: View all upcoming schedules and delete or clear them as needed

The system uses an active-low relay (common type) connected to GPIO23 of the ESP32. The relay acts as a switch to control your AC appliance (light bulb, fan, etc.).


Key Features

📅 Calendar Scheduler
Schedule ON/OFF on any date
🔄 Weekly Repeat
Auto-repeat on selected days
🌐 WiFi Control
Control from any device
⏰ NTP Sync
Accurate internet time

Materials Required


Title & Components Paper Cutout Files

Download the paper cutout files for project title and component labels:

🎓 Design Your Own PCBs with Altium

For designing professional projects like this, 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 Altium

Arduino IDE Setup

Add ESP32 Board to Arduino IDE

Go to File → Preferences and add these URLs to Additional Boards Manager URLs:

https://espressif.github.io/arduino-esp32/package_esp32_index.json https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Then go to Tools → Board → Boards Manager, search for "ESP32" and install the ESP32 platform.


Circuit Diagram

Simple Wiring Instructions:

  • Relay VCC → ESP32 3V3 pin
  • Relay GND → ESP32 GND
  • Relay IN → ESP32 GPIO23
  • Relay COM → AC Live wire (Phase)
  • Relay NO → Bulb holder Live terminal
  • Bulb holder Neutral → AC Neutral wire

Note: The relay used is active-low type. When ESP32 sends LOW signal, relay turns ON; when HIGH, relay turns OFF.


Assembly Steps

1 Connect the Relay to ESP32

Connect the 5V relay module to ESP32: VCC to 3V3, GND to GND, IN to GPIO23. The relay will control your appliance.

2 Prepare the Bulb Holder

Take a 2-core cable, strip the ends, and connect one wire to the bulb holder's live terminal and the other to neutral terminal. Screw them tightly.

3 Connect AC Wiring

Connect the relay COM terminal to the AC phase (live wire). Connect the relay NO terminal to the bulb holder's live wire. Connect the bulb holder's neutral wire directly to AC neutral.

4 Install Required Libraries

No additional libraries needed! The code uses built-in WiFi, WebServer, TimeLib, NTPClient, and WiFiUdp libraries.

5 Upload Code to ESP32

Change the WiFi credentials in the code (ssid and password), select ESP32 Dev Module board and correct COM port, then click Upload.

6 Test the System

After uploading, open Serial Monitor to find the IP address. Open any browser and enter that IP to access the web interface. Test manual control and create schedules!


Pin Connections

Component ESP32 Pin Notes
Relay VCC 3V3 Power for relay coil
Relay GND GND Common ground
Relay IN (Signal) GPIO23 Control signal (Active LOW)

System Demo Video


Arduino Code

ESP32 Smart Home Automation Code
#include <WiFi.h>
#include <WebServer.h>
#include <TimeLib.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

const char* ssid = "Your_WiFi_Name";
const char* password = "Your_Password";

WebServer server(80);

// Relay pin (Active LOW relay - common type)
const int relayPin = 23;

// Relay state (true = ON, false = OFF)
bool relayState = false;

// Schedule structure
struct Schedule {
  bool enabled;
  int year;
  int month;
  int day;
  int hour;
  int minute;
  bool turnOn;
  bool repeat;  // true = repeat weekly, false = specific date
  int weekDays; // bitmask for weekly repeat
  unsigned long executeOnceTime;
};

Schedule schedules[20];
int scheduleCount = 0;

// Time variables
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "asia.pool.ntp.org", 19800, 3600000);

unsigned long lastScheduleCheck = 0;

// Month names
const char* monthNames[] = {"January", "February", "March", "April", "May", "June", 
                             "July", "August", "September", "October", "November", "December"};

// ==================== RELAY CONTROL ====================
void setRelay(bool state) {
  relayState = state;
  digitalWrite(relayPin, state ? LOW : HIGH);
  Serial.printf("Relay: %s\n", state ? "ON" : "OFF");
}

void toggleRelay() {
  setRelay(!relayState);
}

// ==================== SCHEDULE FUNCTIONS ====================
void addSchedule(int year, int month, int day, int hour, int minute, bool turnOn, bool repeat, int weekDays) {
  if (scheduleCount < 20) {
    schedules[scheduleCount].enabled = true;
    schedules[scheduleCount].year = year;
    schedules[scheduleCount].month = month;
    schedules[scheduleCount].day = day;
    schedules[scheduleCount].hour = hour;
    schedules[scheduleCount].minute = minute;
    schedules[scheduleCount].turnOn = turnOn;
    schedules[scheduleCount].repeat = repeat;
    schedules[scheduleCount].weekDays = weekDays;
    schedules[scheduleCount].executeOnceTime = 0;
    scheduleCount++;
    
    Serial.printf("Schedule added: %04d-%02d-%02d %02d:%02d - %s\n", 
                  year, month, day, hour, minute, turnOn ? "ON" : "OFF");
  }
}

void removeSchedule(int index) {
  if (index >= 0 && index < scheduleCount) {
    for (int i = index; i < scheduleCount - 1; i++) {
      schedules[i] = schedules[i + 1];
    }
    scheduleCount--;
    Serial.println("Schedule removed");
  }
}

void clearSchedules() {
  scheduleCount = 0;
  Serial.println("All schedules cleared");
}

void checkSchedules() {
  if (!timeClient.isTimeSet()) return;
  
  time_t now = timeClient.getEpochTime();
  struct tm *timeInfo = localtime(&now);
  int currentYear = timeInfo->tm_year + 1900;
  int currentMonth = timeInfo->tm_mon + 1;
  int currentDay = timeInfo->tm_mday;
  int currentHour = timeInfo->tm_hour;
  int currentMinute = timeInfo->tm_min;
  int currentWeekday = timeInfo->tm_wday;
  
  int todayBit = 0;
  if (currentWeekday == 1) todayBit = 1;
  else if (currentWeekday == 2) todayBit = 2;
  else if (currentWeekday == 3) todayBit = 4;
  else if (currentWeekday == 4) todayBit = 8;
  else if (currentWeekday == 5) todayBit = 16;
  else if (currentWeekday == 6) todayBit = 32;
  else if (currentWeekday == 0) todayBit = 64;
  
  int currentTime = currentHour * 60 + currentMinute;
  
  for (int i = 0; i < scheduleCount; i++) {
    if (!schedules[i].enabled) continue;
    
    bool shouldExecute = false;
    
    if (schedules[i].repeat) {
      // Weekly repeating schedule
      int scheduleTime = schedules[i].hour * 60 + schedules[i].minute;
      if (scheduleTime == currentTime && (schedules[i].weekDays & todayBit)) {
        shouldExecute = true;
      }
    } else {
      // Specific date schedule
      if (schedules[i].year == currentYear && 
          schedules[i].month == currentMonth && 
          schedules[i].day == currentDay &&
          schedules[i].hour == currentHour && 
          schedules[i].minute == currentMinute &&
          schedules[i].executeOnceTime == 0) {
        shouldExecute = true;
        schedules[i].executeOnceTime = now;
      }
    }
    
    if (shouldExecute) {
      setRelay(schedules[i].turnOn);
      Serial.printf("Schedule executed: %04d-%02d-%02d %02d:%02d - Relay %s\n", 
                   schedules[i].year, schedules[i].month, schedules[i].day,
                   schedules[i].hour, schedules[i].minute, 
                   schedules[i].turnOn ? "ON" : "OFF");
    }
  }
}

// ==================== 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>Smart Switch • Calendar Scheduler</title>
<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  background: #f5f5f5;
  min-height: 100vh;
  padding: 20px;
}

.container {
  max-width: 600px;
  width: 100%;
  margin: 0 auto;
}

/* Card */
.card {
  background: white;
  border-radius: 28px;
  padding: 24px;
  margin-bottom: 20px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  border: 1px solid #e9ecef;
}

/* Header */
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  flex-wrap: wrap;
  gap: 15px;
}

h1 {
  font-size: 24px;
  font-weight: 600;
  color: #1a1a2e;
}

.time-card {
  text-align: right;
}

.time {
  font-size: 28px;
  font-weight: 600;
  color: #1a1a2e;
  font-family: monospace;
}

.date {
  font-size: 13px;
  color: #666;
  margin-top: 4px;
}

/* Power Button */
.power-section {
  text-align: center;
  margin: 20px 0;
}

.power-btn {
  width: 180px;
  height: 180px;
  border-radius: 50%;
  background: #f0f0f0;
  border: none;
  box-shadow: 0 8px 20px rgba(0,0,0,0.1);
  cursor: pointer;
  transition: all 0.3s;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto;
}

.power-btn.active {
  background: #4caf50;
  box-shadow: 0 0 30px rgba(76, 175, 80, 0.3);
}

.power-svg {
  width: 80px;
  height: 80px;
}

.power-svg path,
.power-svg line {
  stroke: #666;
  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: white;
}

.status-text {
  margin-top: 16px;
  font-size: 18px;
  font-weight: 500;
  color: #666;
}

.status-text.active {
  color: #4caf50;
}

/* WiFi Badge */
.wifi-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: #e9ecef;
  padding: 5px 12px;
  border-radius: 20px;
  font-size: 12px;
}

.wifi-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #4caf50;
}

/* Schedule Header */
.schedule-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.schedule-header h2 {
  font-size: 18px;
  font-weight: 600;
  color: #1a1a2e;
}

.clear-btn {
  background: none;
  border: none;
  color: #ff4444;
  font-size: 14px;
  cursor: pointer;
  padding: 8px 12px;
}

/* Calendar */
.calendar-container {
  background: #f8f9fa;
  border-radius: 24px;
  padding: 20px;
  margin-bottom: 20px;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.calendar-nav {
  display: flex;
  gap: 12px;
}

.nav-btn {
  background: white;
  border: 1px solid #ddd;
  width: 36px;
  height: 36px;
  border-radius: 18px;
  font-size: 18px;
  cursor: pointer;
  transition: all 0.2s;
}

.nav-btn:active {
  transform: scale(0.95);
}

.current-month {
  font-size: 18px;
  font-weight: 600;
  color: #1a1a2e;
}

.weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  margin-bottom: 12px;
  font-size: 12px;
  font-weight: 500;
  color: #666;
  padding: 8px 0;
}

.calendar-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.calendar-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 500;
  color: #333;
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.2s;
  background: white;
}

.calendar-day.empty {
  background: transparent;
  cursor: default;
}

.calendar-day.selected {
  background: #4caf50;
  color: white;
}

.calendar-day.today {
  border: 2px solid #4caf50;
}

.calendar-day:not(.empty):not(.selected):active {
  background: #e9ecef;
  transform: scale(0.95);
}

/* Time Picker */
.time-picker-section {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #e9ecef;
}

.time-picker-label {
  font-size: 14px;
  color: #666;
  margin-bottom: 12px;
}

.time-picker {
  display: flex;
  gap: 20px;
  justify-content: center;
  margin-bottom: 20px;
}

.time-input {
  text-align: center;
  background: white;
  border: 1px solid #ddd;
  border-radius: 20px;
  padding: 12px 20px;
  font-size: 32px;
  font-weight: 600;
  font-family: monospace;
  width: 100px;
  outline: none;
  text-align: center;
  color: #1a1a2e;
}

.time-input::placeholder {
  color: #ccc;
  font-size: 24px;
  font-weight: normal;
}

.time-colon {
  font-size: 32px;
  font-weight: 600;
  color: #666;
  display: flex;
  align-items: center;
}

/* Action Buttons */
.action-buttons {
  display: flex;
  gap: 12px;
  margin-bottom: 20px;
}

.action-btn {
  flex: 1;
  padding: 14px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 40px;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.action-btn.on.active {
  background: #4caf50;
  color: white;
  border-color: #4caf50;
}

.action-btn.off.active {
  background: #f44336;
  color: white;
  border-color: #f44336;
}

/* Repeat Options */
.repeat-section {
  margin-bottom: 20px;
}

.repeat-option {
  display: flex;
  gap: 20px;
  align-items: center;
  padding: 8px 0;
}

.repeat-option input {
  width: 18px;
  height: 18px;
  cursor: pointer;
}

.repeat-option label {
  cursor: pointer;
  color: #666;
  font-size: 14px;
}

/* Day Chips */
.days-chips {
  display: flex;
  gap: 8px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.day-chip {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f0f0f0;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
  color: #666;
}

.day-chip.selected {
  background: #4caf50;
  color: white;
}

/* Add Button */
.add-btn {
  width: 100%;
  padding: 16px;
  background: #1a73e8;
  color: white;
  border: none;
  border-radius: 40px;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
  margin-top: 10px;
}

.add-btn:active {
  transform: scale(0.98);
}

/* Schedule List */
.schedule-list {
  max-height: 350px;
  overflow-y: auto;
  margin-top: 20px;
}

.schedule-item {
  background: #f8f9fa;
  border-radius: 20px;
  padding: 16px;
  margin-bottom: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

.schedule-date {
  font-size: 14px;
  font-weight: 600;
  color: #1a1a2e;
}

.schedule-time {
  font-size: 20px;
  font-weight: 600;
  font-family: monospace;
  color: #1a1a2e;
}

.schedule-action {
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
}

.schedule-action.on {
  background: #e8f5e9;
  color: #4caf50;
}

.schedule-action.off {
  background: #ffebee;
  color: #f44336;
}

.delete-schedule {
  background: none;
  border: none;
  color: #ff4444;
  font-size: 24px;
  cursor: pointer;
  padding: 5px 10px;
  border-radius: 8px;
}

.empty-schedule {
  text-align: center;
  color: #999;
  padding: 40px;
  font-size: 14px;
}

/* Stats */
.stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.stat-card {
  background: #f8f9fa;
  border-radius: 20px;
  padding: 16px;
  text-align: center;
}

.stat-label {
  font-size: 12px;
  color: #666;
  margin-bottom: 6px;
}

.stat-value {
  font-size: 20px;
  font-weight: 600;
  color: #1a1a2e;
}

@media (max-width: 480px) {
  .power-btn { width: 150px; height: 150px; }
  .power-svg { width: 70px; height: 70px; }
  .time-input { width: 80px; font-size: 24px; }
  .day-chip { width: 38px; height: 38px; font-size: 12px; }
}
</style>
</head>
<body>

<div class="container">
  <div class="card">
    <div class="header">
      <div>
        <h1>Smart Switch</h1>
        <div class="wifi-badge">
          <span class="wifi-dot" id="wifiDot"></span>
          <span id="wifiText">Connected</span>
        </div>
      </div>
      <div class="time-card">
        <div class="time" id="currentTime">--:--</div>
        <div class="date" id="currentDate">--/--/----</div>
      </div>
    </div>

    <div class="power-section">
      <button class="power-btn" id="powerBtn" onclick="togglePower()">
        <svg class="power-svg" viewBox="0 0 100 100">
          <line x1="50" y1="10" x2="50" y2="45"/>
          <path d="M 28 30 A 30 30 0 1 0 72 30"/>
        </svg>
      </button>
      <div class="status-text" id="statusText">OFF</div>
    </div>
  </div>

  <div class="card">
    <div class="schedule-header">
      <h2>📅 Schedule</h2>
      <button class="clear-btn" onclick="clearAllSchedules()">Clear All</button>
    </div>

    <!-- Calendar -->
    <div class="calendar-container">
      <div class="calendar-header">
        <div class="calendar-nav">
          <button class="nav-btn" onclick="changeMonth(-1)">←</button>
          <button class="nav-btn" onclick="changeMonth(1)">→</button>
        </div>
        <div class="current-month" id="currentMonthYear">March 2024</div>
      </div>
      <div class="weekdays">
        <span>S</span><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span>
      </div>
      <div class="calendar-days" id="calendarDays"></div>
    </div>

    <!-- Time Picker -->
    <div class="time-picker-section">
      <div class="time-picker-label">Select Time</div>
      <div class="time-picker">
        <input type="number" class="time-input" id="hourInput" placeholder="HH" min="0" max="23">
        <span class="time-colon">:</span>
        <input type="number" class="time-input" id="minuteInput" placeholder="MM" min="0" max="59">
      </div>
    </div>

    <!-- Action Buttons -->
    <div class="action-buttons">
      <button class="action-btn on active" id="actionOn" onclick="setAction('on')">Turn ON</button>
      <button class="action-btn off" id="actionOff" onclick="setAction('off')">Turn OFF</button>
    </div>

    <!-- Repeat Options -->
    <div class="repeat-section">
      <div class="repeat-option">
        <input type="radio" id="repeatDate" name="repeat" value="date" checked>
        <label for="repeatDate">Specific date</label>
      </div>
      <div class="repeat-option">
        <input type="radio" id="repeatWeekly" name="repeat" value="weekly">
        <label for="repeatWeekly">Repeat weekly</label>
      </div>
    </div>

    <!-- Weekly Days (hidden by default) -->
    <div id="weeklyDays" style="display: none;">
      <div class="days-chips">
        <span class="day-chip" data-day="1">M</span>
        <span class="day-chip" data-day="2">T</span>
        <span class="day-chip" data-day="4">W</span>
        <span class="day-chip" data-day="8">T</span>
        <span class="day-chip" data-day="16">F</span>
        <span class="day-chip" data-day="32">S</span>
        <span class="day-chip" data-day="64">S</span>
      </div>
    </div>

    <button class="add-btn" onclick="addSchedule()">Add Schedule</button>
  </div>

  <div class="card">
    <div class="schedule-header">
      <h2>⏰ Upcoming Schedules</h2>
    </div>
    <div id="scheduleList" class="schedule-list"></div>
  </div>

  <div class="stats">
    <div class="stat-card">
      <div class="stat-label">Active Schedules</div>
      <div class="stat-value" id="scheduleCount">0</div>
    </div>
    <div class="stat-card">
      <div class="stat-label">Next Schedule</div>
      <div class="stat-value" id="nextSchedule">--:--</div>
    </div>
  </div>
</div>

<script>
let schedules = [];
let isOn = false;
let selectedAction = 'on';
let selectedYear = 2024;
let selectedMonth = 3;
let selectedDay = null;
let selectedWeekDays = 127; // All days selected (1+2+4+8+16+32+64 = 127)
let currentDate = new Date();

// Calendar variables
let displayYear = currentDate.getFullYear();
let displayMonth = currentDate.getMonth() + 1;

// Day chips
document.querySelectorAll('.day-chip').forEach(chip => {
  chip.addEventListener('click', function() {
    const dayBit = parseInt(this.dataset.day);
    if (selectedWeekDays & dayBit) {
      selectedWeekDays &= ~dayBit;
      this.classList.remove('selected');
    } else {
      selectedWeekDays |= dayBit;
      this.classList.add('selected');
    }
  });
  chip.classList.add('selected');
});

// Repeat radio buttons
document.getElementById('repeatDate').addEventListener('change', function() {
  document.getElementById('weeklyDays').style.display = 'none';
  if (selectedDay) {
    document.querySelector(`.calendar-day[data-day="${selectedDay}"]`)?.classList.add('selected');
  }
});

document.getElementById('repeatWeekly').addEventListener('change', function() {
  document.getElementById('weeklyDays').style.display = 'block';
  // Clear calendar selection
  if (selectedDay) {
    document.querySelector(`.calendar-day[data-day="${selectedDay}"]`)?.classList.remove('selected');
    selectedDay = null;
  }
});

function setAction(action) {
  selectedAction = action;
  document.getElementById('actionOn').classList.toggle('active', action === 'on');
  document.getElementById('actionOff').classList.toggle('active', action === 'off');
}

function changeMonth(delta) {
  displayMonth += delta;
  if (displayMonth > 12) {
    displayMonth = 1;
    displayYear++;
  } else if (displayMonth < 1) {
    displayMonth = 12;
    displayYear--;
  }
  renderCalendar();
}

function renderCalendar() {
  const year = displayYear;
  const month = displayMonth;
  const firstDay = new Date(year, month - 1, 1).getDay();
  const daysInMonth = new Date(year, month, 0).getDate();
  
  document.getElementById('currentMonthYear').innerText = `${getMonthName(month)} ${year}`;
  
  const calendarDiv = document.getElementById('calendarDays');
  calendarDiv.innerHTML = '';
  
  // Empty cells for days before month starts
  for (let i = 0; i < firstDay; i++) {
    const emptyDiv = document.createElement('div');
    emptyDiv.className = 'calendar-day empty';
    calendarDiv.appendChild(emptyDiv);
  }
  
  // Fill actual days
  for (let d = 1; d <= daysInMonth; d++) {
    const dayDiv = document.createElement('div');
    dayDiv.className = 'calendar-day';
    dayDiv.textContent = d;
    dayDiv.setAttribute('data-day', d);
    
    // Check if today
    if (d === currentDate.getDate() && year === currentDate.getFullYear() && month === currentDate.getMonth() + 1) {
      dayDiv.classList.add('today');
    }
    
    // Check if selected
    if (selectedDay === d && selectedYear === year && selectedMonth === month && 
        document.getElementById('repeatDate').checked) {
      dayDiv.classList.add('selected');
    }
    
    dayDiv.onclick = (function(day) {
      return function() { selectDay(day); };
    })(d);
    
    calendarDiv.appendChild(dayDiv);
  }
}

function selectDay(day) {
  // Clear previous selection
  document.querySelectorAll('.calendar-day').forEach(d => d.classList.remove('selected'));
  
  selectedDay = day;
  selectedYear = displayYear;
  selectedMonth = displayMonth;
  
  // Highlight selected
  const selectedDiv = document.querySelector(`.calendar-day[data-day="${day}"]`);
  if (selectedDiv) selectedDiv.classList.add('selected');
}

function getMonthName(month) {
  const months = ['January', 'February', 'March', 'April', 'May', 'June', 
                  'July', 'August', 'September', 'October', 'November', 'December'];
  return months[month - 1];
}

function fetchStatus() {
  fetch('/status')
    .then(res => res.json())
    .then(data => {
      isOn = data.state;
      updateUI();
    });
}

function fetchSchedules() {
  fetch('/schedules')
    .then(res => res.json())
    .then(data => {
      schedules = data;
      renderSchedules();
      document.getElementById('scheduleCount').innerText = schedules.length;
    });
}

function fetchTime() {
  fetch('/time')
    .then(res => res.json())
    .then(data => {
      document.getElementById('currentTime').innerText = data.time;
      document.getElementById('currentDate').innerText = data.date;
      
      // Update current date for calendar
      const parts = data.date.split('/');
      if (parts.length === 3) {
        currentDate = new Date(parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]));
      }
    });
}

function updateUI() {
  const powerBtn = document.getElementById('powerBtn');
  const statusText = document.getElementById('statusText');
  
  if (isOn) {
    powerBtn.classList.add('active');
    statusText.innerHTML = 'ON';
    statusText.classList.add('active');
  } else {
    powerBtn.classList.remove('active');
    statusText.innerHTML = 'OFF';
    statusText.classList.remove('active');
  }
}

function getDayName(bit) {
  const days = {1:'Mon',2:'Tue',4:'Wed',8:'Thu',16:'Fri',32:'Sat',64:'Sun'};
  return days[bit] || '';
}

function renderSchedules() {
  const container = document.getElementById('scheduleList');
  if (schedules.length === 0) {
    container.innerHTML = '<div class="empty-schedule">No schedules yet<br>Tap + to create one</div>';
    return;
  }
  
  container.innerHTML = '';
  schedules.forEach((sch, index) => {
    const timeStr = `${sch.hour.toString().padStart(2,'0')}:${sch.minute.toString().padStart(2,'0')}`;
    
    let dateStr = '';
    if (sch.repeat) {
      const dayBits = [1,2,4,8,16,32,64];
      const activeDays = dayBits.filter(bit => sch.weekDays & bit).map(bit => getDayName(bit));
      dateStr = `Weekly · ${activeDays.join(', ')}`;
    } else {
      dateStr = `${sch.year}-${sch.month.toString().padStart(2,'0')}-${sch.day.toString().padStart(2,'0')}`;
    }
    
    const div = document.createElement('div');
    div.className = 'schedule-item';
    div.innerHTML = `
      <div style="flex:1">
        <div class="schedule-date">${dateStr}</div>
        <div style="display:flex; gap:10px; align-items:center; margin-top:8px;">
          <span class="schedule-time">${timeStr}</span>
          <span class="schedule-action ${sch.action}">${sch.action === 'on' ? 'Turn ON' : 'Turn OFF'}</span>
        </div>
      </div>
      <button class="delete-schedule" onclick="deleteSchedule(${index})">×</button>
    `;
    container.appendChild(div);
  });
}

function togglePower() {
  fetch('/toggle')
    .then(() => fetchStatus());
}

function addSchedule() {
  let hour = document.getElementById('hourInput').value;
  let minute = document.getElementById('minuteInput').value;
  let repeat = document.getElementById('repeatWeekly').checked;
  let weekDays = repeat ? selectedWeekDays : 0;
  
  if (hour === '' || minute === '') {
    alert('Please select time');
    return;
  }
  
  hour = parseInt(hour);
  minute = parseInt(minute);
  
  if (isNaN(hour) || hour < 0 || hour > 23) {
    alert('Invalid hour (0-23)');
    return;
  }
  if (isNaN(minute) || minute < 0 || minute > 59) {
    alert('Invalid minute (0-59)');
    return;
  }
  
  if (!repeat && !selectedDay) {
    alert('Please select a date from calendar');
    return;
  }
  
  let year = repeat ? 0 : selectedYear;
  let month = repeat ? 0 : selectedMonth;
  let day = repeat ? 0 : selectedDay;
  
  let url = `/addschedule?year=${year}&month=${month}&day=${day}&hour=${hour}&minute=${minute}&action=${selectedAction}&repeat=${repeat}&weekDays=${weekDays}`;
  
  fetch(url)
    .then(() => {
      fetchSchedules();
      // Clear inputs
      document.getElementById('hourInput').value = '';
      document.getElementById('minuteInput').value = '';
      // Clear calendar selection
      selectedDay = null;
      renderCalendar();
    });
}

function deleteSchedule(index) {
  fetch(`/deleteschedule?index=${index}`)
    .then(() => fetchSchedules());
}

function clearAllSchedules() {
  if (confirm('Delete all schedules?')) {
    fetch('/clearschedules')
      .then(() => fetchSchedules());
  }
}

// Initialize calendar
renderCalendar();

// Set initial date (today)
selectDay(currentDate.getDate());
selectedYear = currentDate.getFullYear();
selectedMonth = currentDate.getMonth() + 1;

// Update every second
setInterval(fetchTime, 1000);
setInterval(fetchStatus, 2000);
setInterval(fetchSchedules, 5000);

// Initial load
fetchStatus();
fetchSchedules();
fetchTime();
</script>
</body>
</html>
)rawliteral";

  return html;
}

// ==================== WEB HANDLERS ====================
void handleRoot() {
  server.send(200, "text/html", webpage());
}

void handleToggle() {
  toggleRelay();
  server.send(200, "application/json", "{\"success\":true}");
}

void handleStatus() {
  String json = "{\"state\":" + String(relayState ? "true" : "false") + "}";
  server.send(200, "application/json", json);
}

void handleSchedules() {
  String json = "[";
  for (int i = 0; i < scheduleCount; i++) {
    if (i > 0) json += ",";
    json += "{";
    json += "\"year\":" + String(schedules[i].year) + ",";
    json += "\"month\":" + String(schedules[i].month) + ",";
    json += "\"day\":" + String(schedules[i].day) + ",";
    json += "\"hour\":" + String(schedules[i].hour) + ",";
    json += "\"minute\":" + String(schedules[i].minute) + ",";
    json += "\"action\":\"" + String(schedules[i].turnOn ? "on" : "off") + "\",";
    json += "\"repeat\":" + String(schedules[i].repeat ? "true" : "false") + ",";
    json += "\"weekDays\":" + String(schedules[i].weekDays);
    json += "}";
  }
  json += "]";
  server.send(200, "application/json", json);
}

void handleAddSchedule() {
  if (server.hasArg("hour") && server.hasArg("minute") && server.hasArg("action")) {
    int year = server.hasArg("year") ? server.arg("year").toInt() : 0;
    int month = server.hasArg("month") ? server.arg("month").toInt() : 0;
    int day = server.hasArg("day") ? server.arg("day").toInt() : 0;
    int hour = server.arg("hour").toInt();
    int minute = server.arg("minute").toInt();
    bool turnOn = server.arg("action") == "on";
    bool repeat = server.hasArg("repeat") ? server.arg("repeat") == "true" : false;
    int weekDays = server.hasArg("weekDays") ? server.arg("weekDays").toInt() : 0;
    
    addSchedule(year, month, day, hour, minute, turnOn, repeat, weekDays);
    server.send(200, "text/plain", "OK");
  } else {
    server.send(400, "text/plain", "Missing parameters");
  }
}

void handleDeleteSchedule() {
  if (server.hasArg("index")) {
    int index = server.arg("index").toInt();
    removeSchedule(index);
    server.send(200, "text/plain", "OK");
  } else {
    server.send(400, "text/plain", "Missing index");
  }
}

void handleClearSchedules() {
  clearSchedules();
  server.send(200, "text/plain", "OK");
}

void handleTime() {
  if (!timeClient.isTimeSet()) {
    timeClient.update();
  }
  
  time_t now = timeClient.getEpochTime();
  struct tm *timeInfo = localtime(&now);
  
  char timeStr[10];
  char dateStr[20];
  sprintf(timeStr, "%02d:%02d", timeInfo->tm_hour, timeInfo->tm_min);
  sprintf(dateStr, "%02d/%02d/%04d", timeInfo->tm_mday, timeInfo->tm_mon + 1, timeInfo->tm_year + 1900);
  
  String json = "{\"time\":\"" + String(timeStr) + "\",";
  json += "\"date\":\"" + String(dateStr) + "\"}";
  server.send(200, "application/json", json);
}

// ==================== SETUP ====================
void setup() {
  Serial.begin(115200);
  Serial.println("\n=== Smart Home Automation Starting ===");
  
  // Configure relay - Active LOW, start OFF
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH);
  setRelay(false);
  
  // 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());
  } else {
    Serial.println("\n❌ Connection Failed");
  }
  
  // Initialize NTP
  timeClient.begin();
  timeClient.update();
  Serial.println("⏰ Time synchronized");
  
  // Setup routes
  server.on("/", handleRoot);
  server.on("/toggle", handleToggle);
  server.on("/status", handleStatus);
  server.on("/schedules", handleSchedules);
  server.on("/addschedule", handleAddSchedule);
  server.on("/deleteschedule", handleDeleteSchedule);
  server.on("/clearschedules", handleClearSchedules);
  server.on("/time", handleTime);
  
  server.begin();
  Serial.println("🚀 Server started!");
  Serial.println("===============================\n");
}

// ==================== LOOP ====================
void loop() {
  server.handleClient();
  
  // Update time every hour
  static unsigned long lastTimeUpdate = 0;
  if (millis() - lastTimeUpdate > 3600000) {
    timeClient.update();
    lastTimeUpdate = millis();
  }
  
  // Check schedules every second
  static unsigned long lastScheduleCheck = 0;
  if (millis() - lastScheduleCheck > 1000) {
    if (timeClient.isTimeSet()) {
      checkSchedules();
    }
    lastScheduleCheck = millis();
  }
  
  delay(10);
}

Code Explanation:

Key Features:

  • Interactive Calendar: Month navigation with day selection for scheduling
  • Time Picker: Hour and minute inputs for precise scheduling
  • Action Selection: Choose between turning ON or OFF at scheduled time
  • Weekly Repeat: Option to repeat schedules every week on selected days
  • NTP Time Sync: Automatic time synchronization from internet servers
  • Schedule Management: View all schedules with delete and clear all options
  • Real-time Stats: Shows active schedule count and next schedule time
  • Responsive UI: Beautiful design that works on phones and desktops

Setup Instructions:

  • Change ssid and password to 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
  • Create schedules using the calendar and time picker

Technical Specifications

  • Microcontroller: ESP32 DevKit V1 (Dual-core 240MHz)
  • Relay Type: SRD-05VDC-SL-C (10A 250VAC / 10A 30VDC)
  • Control Method: WiFi (HTTP Web Server) + Manual via Web UI
  • Time Synchronization: NTP (Network Time Protocol)
  • Schedule Capacity: Up to 20 schedules
  • Schedule Types: Specific date or weekly repeating
  • Web Interface: Responsive HTML/CSS/JavaScript
  • Operating Voltage: 5V (via USB or external power)

Applications

🏠 Home Automation
Schedule lights, fans, appliances
🌅 Sunrise/Sunset
Automated lighting schedules
🎓 Education
Learn IoT and web server programming
🔌 Smart Outlets
Retrofit existing appliances

🔧 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

Popular posts from this blog

Arduino Code

Arduino Code Car Parking System