8 Channel Relay Control With Timer

What is Home Automation System

A Home Automation System is a technology that allows you to automatically control household devices and systems β€” such as lights, fans, air conditioners, security cameras, and doors β€” using a smartphone, computer, or voice commands.

Β 

Simple Definition:

Home automation means making your home β€œsmart” by connecting devices to a network so they can be controlled remotely or work automatically without manual effort.

Β 

Main Components:

  • Sensors – Detect changes (like motion, light, or temperature).
  • Controllers – Devices or apps (like a smartphone or smart hub) used to send commands.
  • Actuators – Devices (like motors, switches, or relays) that carry out the actions.

Β 

Common Functions:

  • Lighting control – Lights turn on/off automatically.
  • Temperature control – Smart thermostats adjust room temperature.
  • Security – Cameras, smart locks, and alarms protect the home.
  • Appliance control – Turn appliances on or off remotely.

Examples of Use:

  • Turning on lights with your phone.
  • Automatically locking doors when you leave.
  • Scheduling the air conditioner to turn off at night.

Β 

Advantages:

  • Convenience and comfort
  • Energy savings
  • Improved safety and security
  • Remote access and contr

8 Channel Relay With Timer Projects Programing Code

Projects Cost Rs 65,200/ (Developer Saikat Biswas)

				
					#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <ArduinoJson.h>
#include <time.h>

// --- 8-Channel Relay and Schedule Definitions ---
#define MAX_RELAYS 8
#define MAX_SCHEDULES 8
#define MAX_HISTORY 20

const int RELAY_PINS[MAX_RELAYS] = {2, 12, 14, 27, 26, 25, 33, 32};
const char* RELAY_NAMES[MAX_RELAYS] = {"Relay 1", "Relay 2", "Relay 3", "Relay 4", "Relay 5", "Relay 6", "Relay 7", "Relay 8"};

// Schedule Structure
struct ScheduledTask {
  int relay; // Relay number (1-8)
  int startHour;
  int startMinute;
  int endHour;
  int endMinute;
  String date; // "YYYY-MM-DD" or empty for daily
  String stateDuring; // "ON" or "OFF"
};
ScheduledTask schedules[MAX_SCHEDULES];
int scheduleCount = 0;

// Event history
String historyLog[MAX_HISTORY];
int historyCount = 0;

// General ESP32 and WebServer variables
Preferences preferences;
const char* ssid = "saikat sumita";
const char* password = "tiger@7700";
WebServer server(80);

String sessionToken = "";
unsigned long sessionStartTime = 0;
const unsigned long SESSION_TIMEOUT = 30 * 60 * 1000;
bool isLoggedIn = false;
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 19800; 
const int daylightOffset_sec = 0;

// --- Function Prototypes ---
String generateSessionToken();
bool isSessionValid();
String loginPage();
String dashboardPage();
String getFormattedTime();
void handleRoot();
void handleDashboard();
void handleLogin();
void handleLogout();
void handleRelayToggle();
void handleGetState();
void handleSaveSchedule();
void handleSchedule();
void addHistoryEntry(String event);
void loadSchedules();
void saveSchedules();
void handleTime();
void handleDeleteSchedule();
int calculateRelayState(int relay, int nowInMinutes, const String& currentDate);

const char* tailwindCSS = R"rawliteral(
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Fallback styles */
body { font-family: Arial, sans-serif; background: #f9fafb; color: #1f2937; }
button, a { cursor: pointer; }
/* Tailwind classes */
.bg-gray-50 { background-color: #f9fafb; }
.min-h-screen { min-height: 100vh; }
.bg-gradient-to-br { background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); }
.from-blue-500 { --tw-gradient-from: #3b82f6; }
.to-purple-600 { --tw-gradient-to: #9333ea; }
.from-red-500 { --tw-gradient-from: #ef4444; }
.to-pink-600 { --tw-gradient-to: #ec4899; }
.from-green-500 { --tw-gradient-from: #22c55e; }
.to-green-600 { --tw-gradient-to: #16a34a; }
.from-orange-500 { --tw-gradient-from: #f97316; }
.to-orange-600 { --tw-gradient-to: #ea580c; }
.from-teal-500 { --tw-gradient-from: #14b8a6; }
.to-teal-600 { --tw-gradient-to: #0d9488; }
.from-gray-500 { --tw-gradient-from: #6b7280; }
.to-gray-600 { --tw-gradient-to: #4b5563; }
.from-blue-500 { --tw-gradient-from: #3b82f6; }
.to-blue-600 { --tw-gradient-to: #2563eb; }
.from-yellow-500 { --tw-gradient-from: #eab308; }
.to-yellow-600 { --tw-gradient-to: #ca8a04; }
.bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); }
.from-blue-600 { --tw-gradient-from: #2563eb; }
.to-purple-600 { --tw-gradient-to: #9333ea; }
.from-green-50 { --tw-gradient-from: #f0fdf4; }
.to-blue-50 { --tw-gradient-to: #eff6ff; }
.from-blue-50 { --tw-gradient-from: #eff6ff; }
.to-purple-50 { --tw-gradient-to: #f5f3ff; }
.to-pink-50 { --tw-gradient-to: #fdf2f8; }
.bg-gradient-to-t { background-image: linear-gradient(to top, var(--tw-gradient-stops)); }
.to-blue-300 { --tw-gradient-to: #93c5fd; }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.bg-white\/90 { background-color: rgba(255, 255, 255, 0.9); }
.backdrop-blur-sm { backdrop-filter: blur(4px); }
.p-8 { padding: 2rem; }
.p-6 { padding: 1.5rem; }
.p-4 { padding: 1rem; }
.p-3 { padding: 0.75rem; }
.p-2 { padding: 0.5rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.rounded-2xl { border-radius: 1rem; }
.rounded-xl { border-radius: 0.75rem; }
.rounded-lg { border-radius: 0.5rem; }
.rounded-full { border-radius: 9999px; }
.shadow-2xl { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
.shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
.w-full { width: 100%; }
.max-w-md { max-width: 28rem; }
.w-16 { width: 4rem; }
.h-16 { height: 4rem; }
.w-32 { width: 8rem; }
.h-48 { height: 12rem; }
.h-64 { height: 16rem; }
.max-h-64 { max-height: 16rem; }
.border { border-width: 1px; }
.border-4 { border-width: 4px; }
.border-white\/20 { border-color: rgba(255, 255, 255, 0.2); }
.border-gray-300 { border-color: #d1d5db; }
.border-gray-400 { border-color: #9ca3af; }
.border-green-200 { border-color: #bbf7d0; }
.border-blue-200 { border-color: #bfdbfe; }
.border-purple-200 { border-color: #e9d5ff; }
.border-yellow-500 { border-color: #eab308; }
.border-l-4 { border-left-width: 4px; }
.text-center { text-align: center; }
.mb-8 { margin-bottom: 2rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mr-2 { margin-right: 0.5rem; }
.mx-auto { margin-left: auto; margin-right: auto; }
.pt-6 { padding-top: 1.5rem; }
.border-t { border-top-width: 1px; }
.border-gray-200 { border-color: #e5e7eb; }
.text-3xl { font-size: 1.875rem; }
.text-2xl { font-size: 1.5rem; }
.text-xl { font-size: 1.25rem; }
.text-6xl { font-size: 3.75rem; }
.text-sm { font-size: 0.875rem; }
.font-bold { font-weight: 700; }
.font-semibold { font-weight: 600; }
.font-medium { font-weight: 500; }
.bg-clip-text { background-clip: text; }
.text-transparent { color: transparent; }
.text-gray-600 { color: #4b5563; }
.text-gray-700 { color: #374151; }
.text-gray-800 { color: #1f2937; }
.text-gray-500 { color: #6b7280; }
.text-blue-600 { color: #2563eb; }
.text-blue-100 { color: #dbeafe; }
.text-white { color: #ffffff; }
.text-green-600 { color: #16a34a; }
.text-green-100 { color: #dcfce7; }
.text-orange-100 { color: #ffedd5; }
.text-red-100 { color: #fee2e2; }
.text-teal-100 { color: #ccfbf1; }
.text-red-600 { color: #dc2626; }
.text-purple-600 { color: #9333ea; }
.text-yellow-700 { color: #a16207; }
.text-green-500 { color: #22c55e; }
.text-red-500 { color: #ef4444; }
.bg-green-600 { background-color: #16a34a; }
.bg-red-600 { background-color: #dc2626; }
.bg-white { background-color: #ffffff; }
.bg-gray-100 { background-color: #f3f4f6; }
.bg-blue-50 { background-color: #eff6ff; }
.bg-green-50 { background-color: #f0fdf4; }
.bg-yellow-100 { background-color: #fefcbf; }
.bg-gray-800 { background-color: #1f2937; }
.text-gray-300 { color: #d1d5db; }
.text-gray-400 { color: #9ca3af; }
.text-blue-400 { color: #60a5fa; }
.space-y-6 > * + * { margin-top: 1.5rem; }
.space-x-4 > * + * { margin-left: 1rem; }
.space-y-2 > * + * { margin-top: 0.5rem; }
.space-y-3 > * + * { margin-top: 0.75rem; }
.block { display: block; }
.focus\:outline-none:focus { outline: none; }
.focus\:ring-2:focus { box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); }
.transition-all { transition: all 0.3s ease; }
.hover\:from-blue-700:hover { --tw-gradient-from: #1d4ed8; }
.hover\:to-purple-700:hover { --tw-gradient-to: #7e22ce; }
.hover\:from-green-600:hover { --tw-gradient-from: #16a34a; }
.hover\:to-green-700:hover { --tw-gradient-to: #15803d; }
.hover\:from-orange-600:hover { --tw-gradient-from: #ea580c; }
.hover\:to-orange-700:hover { --tw-gradient-to: #c2410c; }
.hover\:from-red-600:hover { --tw-gradient-from: #dc2626; }
.hover\:to-red-700:hover { --tw-gradient-to: #b91c1c; }
.hover\:shadow-xl:hover { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); }
.transform { transform: translateZ(0); }
.hover\:-translate-y-0\.5:hover { transform: translateY(-0.125rem); }
.container { max-width: 1280px; margin: 0 auto; }
.relative { position: relative; }
.absolute { position: absolute; }
.bottom-0 { bottom: 0; }
.left-0 { left: 0; }
.right-0 { right: 0; }
.inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
.mix-blend-difference { mix-blend-mode: difference; }
.overflow-hidden { overflow: hidden; }
.overflow-y-auto { overflow-y: auto; }
.grid { display: grid; }
.grid-cols-1 { grid-template-columns: 1fr; }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.md\:grid-cols-4 { @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); } }
.lg\:grid-cols-2 { @media (min-width: 1024px) { grid-template-columns: repeat(2, 1fr); } }
.gap-4 { gap: 1rem; }
.gap-3 { gap: 0.75rem; }
.gap-6 { gap: 1.5rem; }
.animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
/* New Toggle Switch CSS */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}
.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 34px;
}
.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}
input:checked + .slider {
  background-color: #2196F3;
}
input:checked + .slider:before {
  transform: translateX(26px);
}
</style>
)rawliteral";

String generateSessionToken() {
  String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  String token = "";
  for (int i = 0; i < 16; i++) {
    token += chars[random(0, chars.length())];
  }
  return token;
}

String loginPage() {
  String page = String(R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <title>Saikat  Smart Home - Login</title>
    <script src="https://cdn.tailwindcss.com"></script>
)rawliteral") + tailwindCSS + R"rawliteral(
</head>
<body class="bg-gradient-to-br from-blue-500 to-purple-600 min-h-screen flex items-center justify-center">
    <div class="bg-white/90 backdrop-blur-sm p-8 rounded-2xl shadow-2xl w-full max-w-md border border-white/20">
        <div class="text-center mb-8">
            <div class="bg-gradient-to-r from-blue-600 to-purple-600 w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center">
                <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.4 4.4 0 003 15z"></path>
                </svg>
            </div>
            <h1 class="text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">Saikat Smart Home</h1>
            <p class="text-gray-600 mt-2">Enter your credentials to access dashboard</p>
        </div>
        <form action="/login" method="post" class="space-y-6">
            <div>
                <label class="block text-sm font-medium text-gray-700 mb-2">Username</label>
                <input name="username" type="text" autocomplete="username" required class="w-full p-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" placeholder="Enter username">
            </div>
            <div>
                <label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
                <input name="password" type="password" autocomplete="current-password" required class="w-full p-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" placeholder="Enter password">
            </div>
            <button type="submit" class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white p-3 rounded-xl hover:from-blue-700 hover:to-purple-700 transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
                Access Dashboard
            </button>
        </form>
        <div class="text-center mt-6 pt-6 border-t border-gray-200">
            <p class="text-sm text-gray-500">Created by <span class="font-semibold text-blue-600">Saikat Biswas</span></p>
        </div>
    </div>
</body>
</html>
)rawliteral";
  return page;
}

String dashboardPage() {
  String page = String(R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <title>Saikat Smart Home Dashboard</title>
    <script src="https://cdn.tailwindcss.com"></script>
)rawliteral") + tailwindCSS + R"rawliteral(
</head>
<body class="bg-gray-50 min-h-screen">
    <header class="bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-lg">
        <div class="container mx-auto px-4 py-6">
            <div class="flex items-center justify-between">
                <div class="flex items-center space-x-4">
                    <div class="bg-white/20 p-3 rounded-lg">
                        <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.4 4.4 0 003 15z"></path>
                        </svg>
                    </div>
                    <div>
                        <h1 class="text-2xl font-bold">Saikat Smart Home</h1>
                        <p class="text-blue-100">Advanced IoT Control System</p>
                    </div>
                </div>
                <div class="flex items-center space-x-4">
                    <span id="currentTime" class="text-white text-sm font-semibold"></span>
                    <a href="/logout" class="bg-gradient-to-r from-red-500 to-red-600 text-white p-2 rounded-lg hover:from-red-600 hover:to-red-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5">
                        Logout
                    </a>
                </div>
            </div>
        </div>
    </header>

    <div class="container mx-auto p-4 space-y-6">
        <div class="bg-white p-6 rounded-xl shadow-lg">
            <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
                <span class="mr-2">⚑</span> Device Controls
            </h2>
            <div class="grid grid-cols-2 gap-4">
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>πŸ’‘ Light 1:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch1" data-relay="1">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>πŸ’‘ Light 2:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch2" data-relay="2">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>πŸ’‘ Light 3:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch3" data-relay="3">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>πŸ’‘ Light 4:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch4" data-relay="4">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>❄ Fan 5:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch5" data-relay="5">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>❄ Fan 6:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch6" data-relay="6">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>πŸ’‘ Light 7:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch7" data-relay="7">
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="flex items-center justify-between p-4 bg-gray-100 rounded-lg">
                    <span>🌊 wPump 8:</span>
                    <label class="toggle-switch">
                        <input type="checkbox" id="toggleSwitch8" data-relay="8">
                        <span class="slider"></span>
                    </label>
                </div>
            </div>
        </div>

        <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
            <div class="bg-white p-6 rounded-xl shadow-lg">
                <h2 class="text-xl font-semibold text-gray-800 mb-4 text-center">Set Timer Schedule</h2>
                <form id="scheduleForm" class="space-y-4">
                    <div>
                        <label class="block text-sm font-bold text-gray-700">Select Relay</label>
                        <select name="relay" id="relaySelect" class="w-full px-4 py-2 border rounded-md">
                            <option value="1">Relay 1</option>
                            <option value="2">Relay 2</option>
                            <option value="3">Relay 3</option>
                            <option value="4">Relay 4</option>
                            <option value="5">Relay 5</option>
                            <option value="6">Relay 6</option>
                            <option value="7">Relay 7</option>
                            <option value="8">Relay 8</option>
                        </select>
                    </div>
                    <div>
                        <label class="block text-sm font-bold text-gray-700">Start Time</label>
                        <input type="time" name="startTime" id="startTime" class="w-full px-4 py-2 border rounded-md" required>
                    </div>
                    <div>
                        <label class="block text-sm font-bold text-gray-700">End Time</label>
                        <input type="time" name="endTime" id="endTime" class="w-full px-4 py-2 border rounded-md" required>
                    </div>
                    <div>
                        <label class="block text-sm font-bold text-gray-700">State during period</label>
                        <select name="state" id="stateSelect" class="w-full px-4 py-2 border rounded-md">
                            <option value="ON">ON</option>
                            <option value="OFF">OFF</option>
                        </select>
                    </div>
                    <div>
                        <label class="block text-sm font-bold text-gray-700">Date (optional for daily)</label>
                        <input type="date" name="date" id="date" class="w-full px-4 py-2 border rounded-md">
                    </div>
                    <input type="hidden" name="token" id="sessionToken" value="">
                    <button type="submit" class="w-full bg-blue-600 text-white py-2 rounded-md hover:bg-blue-700 transition-all">Save Schedule</button>
                </form>
            </div>

            <div class="bg-white p-6 rounded-xl shadow-lg">
                <h2 class="text-xl font-semibold text-gray-800 mb-4 text-center">Current Schedules</h2>
                <div id="scheduleList" class="space-y-2 max-h-48 overflow-y-auto"></div>
                <h2 class="text-xl font-semibold text-gray-800 mt-4 mb-2 text-center">Event History</h2>
                <div id="historyLog" class="space-y-2 max-h-48 overflow-y-auto"></div>
            </div>
        </div>
    </div>

    <footer class="bg-gray-800 text-white py-6 mt-8">
        <div class="container mx-auto px-4 text-center">
            <p class="text-gray-300">©️ 2025 Saikat Smart Home System</p>
            <p class="text-sm text-gray-400 mt-1">Developed with ❀️ by <span class="text-blue-400 font-semibold">Saikat Biswas</span></p>
        </div>
    </footer>

    <script>
        const MAX_RELAYS = 8;
        const RELAY_NAMES = ["Relay 1", "Relay 2", "Relay 3", "Relay 4", "Relay 5", "Relay 6", "Relay 7", "Relay 8"];
        
        function updateUI(data) {
            for (let i = 0; i < MAX_RELAYS; i++) {
                const toggleSwitch = document.getElementById('toggleSwitch' + (i + 1));
                if (!toggleSwitch) continue;
                const state = data.relayStates[i];
                toggleSwitch.checked = (state === "ON");
            }
            
            const scheduleList = document.getElementById('scheduleList');
            scheduleList.innerHTML = '';
            if (data.schedules.length === 0) {
                scheduleList.innerHTML = '<p class="text-center text-gray-500 text-sm">No schedules set.</p>';
            } else {
                data.schedules.forEach((schedule, index) => {
                    const item = document.createElement('div');
                    item.className = 'bg-gray-100 p-2 rounded-md text-sm flex justify-between items-center';
                    const startTime = `${String(schedule.startHour).padStart(2, '0')}:${String(schedule.startMinute).padStart(2, '0')}`;
                    const endTime = `${String(schedule.endHour).padStart(2, '0')}:${String(schedule.endMinute).padStart(2, '0')}`;
                    item.innerHTML = `<span><b>${RELAY_NAMES[schedule.relay - 1]}</b> ${schedule.stateDuring} from ${startTime} to ${endTime}${schedule.date ? ` on ${schedule.date}` : ' (daily)'}</span>
                                      <button class="bg-red-500 text-white px-2 py-1 rounded text-xs" onclick="deleteSchedule(${index})">Delete</button>`;
                    scheduleList.appendChild(item);
                });
            }

            const historyLog = document.getElementById('historyLog');
            historyLog.innerHTML = '';
            if (data.history.length === 0) {
                historyLog.innerHTML = '<p class="text-center text-gray-500 text-sm">No history yet.</p>';
            } else {
                data.history.forEach(entry => {
                    const item = document.createElement('div');
                    item.className = 'bg-gray-100 p-2 rounded-md text-xs';
                    item.innerText = entry;
                    historyLog.appendChild(item);
                });
            }
        }
    
        function fetchAllData() {
            fetch('/getState')
                .then(res => res.json())
                .then(data => updateUI(data))
                .catch(error => console.error('Error fetching state:', error));
        }

        function handleToggleClick(relay) {
            const token = document.getElementById("sessionToken").value || "";
            fetch(`/relay?ch=${relay}&token=${token}`)
                .then(res => res.json())
                .then(() => {
                    // Refresh UI from server to ensure state is correct
                    fetchAllData();
                })
                .catch(error => console.error('Error toggling:', error));
        }

        document.getElementById('scheduleForm').addEventListener('submit', function(e) {
            e.preventDefault();
            const relay = document.getElementById('relaySelect').value;
            const startTime = document.getElementById('startTime').value;
            const endTime = document.getElementById('endTime').value;
            const state = document.getElementById('stateSelect').value;
            const date = document.getElementById('date').value;
            const token = document.getElementById("sessionToken").value || "";
            const formData = `relay=${relay}&startTime=${startTime}&endTime=${endTime}&state=${state}&date=${date}&token=${token}`;

            fetch('/saveSchedule', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: formData
            })
            .then(res => res.text())
            .then(message => {
                alert(message);
                fetchAllData();
            })
            .catch(error => console.error('Error saving schedule:', error));
        });
    
        const urlParams = new URLSearchParams(window.location.search);
        const token = urlParams.get('token');
        if (token) {
            document.getElementById('sessionToken').value = token;
        }

        function updateTime() {
            fetch('/time')
                .then(res => res.text())
                .then(time => {
                    document.getElementById('currentTime').innerText = time.substring(11);
                })
                .catch(error => console.error('Error fetching time:', error));
        }

        window.deleteSchedule = function(index) {
            const token = document.getElementById('sessionToken').value;
            fetch(`/deleteSchedule?id=${index}&token=${token}`)
                .then(res => res.text())
                .then(() => fetchAllData())
                .catch(err => console.error('Error deleting schedule:', err));
        }

        fetchAllData();
        updateTime();
        setInterval(fetchAllData, 5000);
        setInterval(updateTime, 1000);

        // Add event listeners to toggle switches
        for (let i = 1; i <= MAX_RELAYS; i++) {
            const toggle = document.getElementById(`toggleSwitch${i}`);
            if (toggle) {
                toggle.addEventListener('change', () => handleToggleClick(i));
            }
        }
    </script>
</body>
</html>
)rawliteral";
  return page;
}

bool isSessionValid() {
  if (server.hasArg("token")) {
    String token = server.arg("token");
    if (token == sessionToken && !sessionToken.isEmpty()) {
      if (millis() - sessionStartTime < SESSION_TIMEOUT) {
        sessionStartTime = millis();
        return true;
      }
    }
  }
  return false;
}

void handleRoot() {
  isLoggedIn = isSessionValid();
  if (!isLoggedIn) {
    server.send(200, "text/html", loginPage());
  } else {
    server.sendHeader("Location", "/dashboard?token=" + sessionToken);
    server.send(303);
  }
}

void handleDashboard() {
  if (!isSessionValid()) {
    server.sendHeader("Location", "/");
    server.send(303);
    return;
  }
  String page = dashboardPage();
  server.send(200, "text/html", page);
}

void handleLogin() {
  if (server.method() == HTTP_POST) {
    String user = server.arg("username");
    String pass = server.arg("password");
    if (user == "admin" && pass == "tiger@7700") {
      sessionToken = generateSessionToken();
      sessionStartTime = millis();
      isLoggedIn = true;
      server.sendHeader("Location", "/dashboard?token=" + sessionToken);
      server.send(303);
    } else {
      String errorPage = String(R"rawliteral(
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Login Failed</title>)rawliteral") + tailwindCSS + R"rawliteral(</head>
<body class="bg-gradient-to-br from-red-500 to-pink-600 min-h-screen flex items-center justify-center">
<div class="bg-white/90 p-8 rounded-2xl shadow-2xl text-center max-w-md w-full">
<div class="text-6xl mb-4">❌</div><h2 class="text-2xl font-bold text-red-600 mb-4">Access Denied!</h2>
<p class="gray-600 mb-6">Invalid username or password. Please try again.</p>
<a href="/" class="bg-gradient-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-lg hover:from-blue-700 hover:to-purple-700 transition-all">Try Again</a>
</div></body></html>
)rawliteral";
      server.send(200, "text/html", errorPage);
    }
  }
}

void handleLogout() {
  sessionToken = "";
  isLoggedIn = false;
  server.sendHeader("Location", "/");
  server.send(303);
}

void handleRelayToggle() {
    if (!isSessionValid()) {
        server.send(401, "text/plain", "Unauthorized");
        return;
    }
    if (server.hasArg("ch")) {
        int ch = server.arg("ch").toInt();
        if (ch >= 1 && ch <= MAX_RELAYS) {
            int pin = RELAY_PINS[ch - 1];
            bool isOn = digitalRead(pin) == LOW; // true if ON (active LOW)
            bool newIsOn = !isOn;
            digitalWrite(pin, newIsOn ? LOW : HIGH);
            addHistoryEntry("Relay " + String(ch) + " manually " + (newIsOn ? "ON" : "OFF"));
            StaticJsonDocument<64> doc;
            doc["status"] = "OK";
            doc["state"] = newIsOn ? "ON" : "OFF";
            String output;
            serializeJson(doc, output);
            server.send(200, "application/json", output);
        } else {
            server.send(400, "text/plain", "Invalid relay number.");
        }
    } else {
        server.send(400, "text/plain", "Missing relay parameter.");
    }
}

void handleGetState() {
  DynamicJsonDocument doc(2048);
  JsonArray relayStatesArray = doc.createNestedArray("relayStates");
  for (int i = 0; i < MAX_RELAYS; i++) {
    relayStatesArray.add(digitalRead(RELAY_PINS[i]) == LOW ? "ON" : "OFF");
  }
  JsonArray schedulesArray = doc.createNestedArray("schedules");
  for (int i = 0; i < scheduleCount; i++) {
    JsonObject obj = schedulesArray.createNestedObject();
    obj["relay"] = schedules[i].relay;
    obj["startHour"] = schedules[i].startHour;
    obj["startMinute"] = schedules[i].startMinute;
    obj["endHour"] = schedules[i].endHour;
    obj["endMinute"] = schedules[i].endMinute;
    obj["date"] = schedules[i].date;
    obj["stateDuring"] = schedules[i].stateDuring;
  }
  JsonArray historyArray = doc.createNestedArray("history");
  for (int i = 0; i < historyCount; i++) {
    historyArray.add(historyLog[i]);
  }
  String output;
  serializeJson(doc, output);
  server.send(200, "application/json", output);
}

void handleSaveSchedule() {
    if (!isSessionValid()) {
        server.send(401, "text/plain", "Unauthorized");
        return;
    }
    if (server.hasArg("relay") && server.hasArg("startTime") && server.hasArg("endTime") && server.hasArg("state")) {
        int relayNum = server.arg("relay").toInt();
        String startTimeStr = server.arg("startTime");
        String endTimeStr = server.arg("endTime");
        String stateStr = server.arg("state");
        if (stateStr != "ON" && stateStr != "OFF") {
            server.send(400, "text/plain", "Invalid state.");
            return;
        }
        String dateStr = server.hasArg("date") ? server.arg("date") : "";
        
        if (scheduleCount < MAX_SCHEDULES) {
            schedules[scheduleCount].relay = relayNum;
            schedules[scheduleCount].startHour = startTimeStr.substring(0, 2).toInt();
            schedules[scheduleCount].startMinute = startTimeStr.substring(3, 5).toInt();
            schedules[scheduleCount].endHour = endTimeStr.substring(0, 2).toInt();
            schedules[scheduleCount].endMinute = endTimeStr.substring(3, 5).toInt();
            schedules[scheduleCount].stateDuring = stateStr;
            schedules[scheduleCount].date = dateStr;
            scheduleCount++;
            saveSchedules();
            String scheduleDesc = "Schedule added for " + String(RELAY_NAMES[relayNum-1]) + ": " + stateStr + " from " + String(schedules[scheduleCount-1].startHour) + ":" + String(schedules[scheduleCount-1].startMinute) + " to " + String(schedules[scheduleCount-1].endHour) + ":" + String(schedules[scheduleCount-1].endMinute);
            if (!dateStr.isEmpty()) scheduleDesc += " on " + dateStr;
            addHistoryEntry(scheduleDesc);
            server.send(200, "text/plain", "Schedule saved successfully!");
        } else {
            server.send(400, "text/plain", "Error! Max schedules reached.");
        }
    } else {
        server.send(400, "text/plain", "Error! Invalid data.");
    }
}

int calculateRelayState(int relay, int nowInMinutes, const String& currentDate) {
  bool hasOnPeriod = false;
  bool hasOffPeriod = false;
  for (int i = 0; i < scheduleCount; i++) {
    if (schedules[i].relay != relay) continue;
    if (!schedules[i].date.isEmpty() && schedules[i].date != currentDate) continue;
    int startInMinutes = schedules[i].startHour * 60 + schedules[i].startMinute;
    int endInMinutes = schedules[i].endHour * 60 + schedules[i].endMinute;
    bool inPeriod;
    if (startInMinutes < endInMinutes) {
      inPeriod = (nowInMinutes >= startInMinutes && nowInMinutes < endInMinutes);
    } else {
      inPeriod = (nowInMinutes >= startInMinutes || nowInMinutes < endInMinutes);
    }
    if (inPeriod) {
      if (schedules[i].stateDuring == "ON") hasOnPeriod = true;
      else hasOffPeriod = true;
    }
  }
  if (hasOnPeriod || hasOffPeriod) {
    return hasOnPeriod ? 1 : 0;
  }
  return -1; // no active schedule
}

void handleSchedule() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    return;
  }
  int currentHour = timeinfo.tm_hour;
  int currentMinute = timeinfo.tm_min;
  int nowInMinutes = currentHour * 60 + currentMinute;
  char date_buf[11];
  strftime(date_buf, sizeof(date_buf), "%Y-%m-%d", &timeinfo);
  String currentDate = date_buf;

  static bool firstCall = true;
  static int previousMinutes = -1;
  static String previousDate = "";

  if (firstCall) {
    firstCall = false;
    previousMinutes = nowInMinutes;
    previousDate = currentDate;
    for (int r = 1; r <= MAX_RELAYS; r++) {
      int isState = calculateRelayState(r, nowInMinutes, currentDate);
      bool shouldBeOn;
      if (isState == -1) {
        shouldBeOn = false; // default OFF
        digitalWrite(RELAY_PINS[r-1], HIGH);
        addHistoryEntry(String(RELAY_NAMES[r-1]) + " initialized to OFF");
      } else {
        shouldBeOn = (isState == 1);
        digitalWrite(RELAY_PINS[r-1], shouldBeOn ? LOW : HIGH);
        addHistoryEntry(String(RELAY_NAMES[r-1]) + " initialized to " + (shouldBeOn ? "ON" : "OFF") + " by schedule");
      }
    }
    return;
  }

  if (nowInMinutes == previousMinutes && currentDate == previousDate) return;

  for (int r = 1; r <= MAX_RELAYS; r++) {
    int isState = calculateRelayState(r, nowInMinutes, currentDate);
    if (isState != -1) {
      bool targetOn = (isState == 1);
      bool currentOn = (digitalRead(RELAY_PINS[r-1]) == LOW);
      if (targetOn != currentOn) {
        digitalWrite(RELAY_PINS[r-1], targetOn ? LOW : HIGH);
        addHistoryEntry(String(RELAY_NAMES[r-1]) + " turned " + (targetOn ? "ON" : "OFF") + " by schedule");
      }
    }
  }

  previousMinutes = nowInMinutes;
  previousDate = currentDate;
}

void addHistoryEntry(String event) {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    return;
  }
  char time_str[30];
  strftime(time_str, sizeof(time_str), "%H:%M:%S", &timeinfo);
  String newEntry = String(time_str) + " -> " + event;
  for (int i = MAX_HISTORY - 1; i > 0; i--) {
    historyLog[i] = historyLog[i-1];
  }
  historyLog[0] = newEntry;
  if (historyCount < MAX_HISTORY) {
    historyCount++;
  }
}

void loadSchedules() {
  preferences.begin("schedules", false);
  String jsonString = preferences.getString("schedule_data", "[]");
  preferences.end();
  DynamicJsonDocument doc(2048);
  DeserializationError error = deserializeJson(doc, jsonString);
  if (!error) {
    JsonArray array = doc.as<JsonArray>();
    scheduleCount = 0;
    for (JsonObject obj : array) {
      if (scheduleCount < MAX_SCHEDULES) {
        schedules[scheduleCount].relay = obj["r"];
        schedules[scheduleCount].startHour = obj["sh"];
        schedules[scheduleCount].startMinute = obj["sm"];
        schedules[scheduleCount].endHour = obj["eh"];
        schedules[scheduleCount].endMinute = obj["em"];
        schedules[scheduleCount].date = obj["d"] | "";
        schedules[scheduleCount].stateDuring = obj["s"].as<String>();
        scheduleCount++;
      }
    }
  }
}

void saveSchedules() {
  DynamicJsonDocument doc(2048);
  JsonArray array = doc.to<JsonArray>();
  for (int i = 0; i < scheduleCount; i++) {
    JsonObject obj = array.createNestedObject();
    obj["r"] = schedules[i].relay;
    obj["sh"] = schedules[i].startHour;
    obj["sm"] = schedules[i].startMinute;
    obj["eh"] = schedules[i].endHour;
    obj["em"] = schedules[i].endMinute;
    obj["d"] = schedules[i].date;
    obj["s"] = schedules[i].stateDuring;
  }
  String jsonString;
  serializeJson(doc, jsonString);
  preferences.begin("schedules", false);
  preferences.putString("schedule_data", jsonString);
  preferences.end();
}

String getFormattedTime() {
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo)) {
        return "Time unavailable";
    }
    char timeString[64];
    strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", &timeinfo);
    return String(timeString);
}

void handleTime() {
  server.send(200, "text/plain", getFormattedTime());
}

void handleDeleteSchedule() {
    if (!isSessionValid()) {
        server.send(401, "text/plain", "Unauthorized");
        return;
    }
    if (server.hasArg("id")) {
        int id = server.arg("id").toInt();
        if (id >= 0 && id < scheduleCount) {
            for (int i = id; i < scheduleCount - 1; i++) {
                schedules[i] = schedules[i+1];
            }
            scheduleCount--;
            saveSchedules();
            addHistoryEntry("Schedule deleted.");
            server.send(200, "text/plain", "Schedule deleted successfully.");
        } else {
            server.send(400, "text/plain", "Invalid schedule ID.");
        }
    } else {
        server.send(400, "text/plain", "Missing ID parameter.");
    }
}

void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(0));
  preferences.begin("schedules", false);
  loadSchedules();
  preferences.end();
  
  for (int i = 0; i < MAX_RELAYS; i++) {
    pinMode(RELAY_PINS[i], OUTPUT);
    digitalWrite(RELAY_PINS[i], HIGH); // Initial OFF (active LOW)
  }
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  // Call handleSchedule to set initial states
  delay(2000); // Wait for time sync
  handleSchedule();

  server.on("/", handleRoot);
  server.on("/dashboard", handleDashboard);
  server.on("/login", HTTP_POST, handleLogin);
  server.on("/logout", handleLogout);
  server.on("/relay", handleRelayToggle);
  server.on("/getState", handleGetState);
  server.on("/saveSchedule", HTTP_POST, handleSaveSchedule);
  server.on("/time", handleTime);
  server.on("/deleteSchedule", handleDeleteSchedule);
  
  server.onNotFound([]() {
    String notFoundPage = String(R"rawliteral(<!DOCTYPE html><html><head><meta charset="UTF-8"><title>404 - Not Found</title>)rawliteral") + tailwindCSS + R"rawliteral(</head><body class="bg-gray-100 flex items-center justify-center h-screen"><div class="text-center"><h1 class="text-6xl font-bold text-gray-800">404</h1><p class="text-xl text-gray-600 mt-4">Page not found</p><a href="/" class="bg-blue-600 text-white px-6 py-3 rounded-lg mt-6 inline-block hover:bg-blue-700">Go Home</a></div></body></html>)rawliteral";
    server.send(404, "text/html", notFoundPage);
  });
  server.begin();
}

void loop() {
  server.handleClient();
  handleSchedule();
}
				
			

8 Channel Realy Manual With Sensor Programing Code

				
					#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <ArduinoJson.h>
#include <time.h>
#include <DHT.h>

#define TRIG_PIN 5
#define ECHO_PIN 18
#define PIR_PIN 21
#define LED1 2
#define LED2 4
#define RELAY1 13
#define RELAY2 12
#define RELAY3 14
#define RELAY4 27
#define DHT_PIN 22
#define DHT_TYPE DHT11 // Change to DHT22 if using DHT22 sensor

// New sensors and LEDs
#define SMOKE_PIN 23
#define RAIN_PIN 25
#define LDR_PIN 34 // Analog pin for LDR
#define LED3 15 // For Smoke
#define LED4 16 // For Rain
#define LED5 17 // For LDR

// Additional relays
#define RELAY5 26
#define RELAY6 19
#define RELAY7 32
#define RELAY8 33

// LDR detection threshold (adjust as needed; higher value might mean darker depending on circuit)
#define LDR_THRESHOLD 2000 // Detect "dark" if analogRead > 2000

// Water tank constants
#define TANK_HEIGHT 200.0 // Total tank height in cm
#define TANK_CAPACITY 1000.0 // Tank capacity in liters

Preferences preferences;
DHT dht(DHT_PIN, DHT_TYPE);
const char* ssid = "saikat sumita";
const char* password = "tiger@7700";
WebServer server(80);

String sessionToken = "";
unsigned long sessionStartTime = 0;
const unsigned long SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes in milliseconds
bool isLoggedIn = false;
int totalDetections = 0;
bool midnightReset = false;
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 19800; // GMT+5:30 for India
const int daylightOffset_sec = 0;

// Separate trigger times for each sensor
unsigned long lastTriggerUltra = 0;
unsigned long lastTriggerPIR = 0;
unsigned long lastTriggerSmoke = 0;
unsigned long lastTriggerRain = 0;
unsigned long lastTriggerLDR = 0;

// Minimal Tailwind CSS with essential classes
const char* tailwindCSS = R"rawliteral(
<style>
  /* Fallback styles */
  body { font-family: Arial, sans-serif; background: #f9fafb; color: #1f2937; }
  button, a { cursor: pointer; }
  /* Tailwind classes */
  .bg-gray-50 { background-color: #f9fafb; }
  .min-h-screen { min-height: 100vh; }
  .bg-gradient-to-br { background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)); }
  .from-blue-500 { --tw-gradient-from: #3b82f6; }
  .to-purple-600 { --tw-gradient-to: #9333ea; }
  .from-red-500 { --tw-gradient-from: #ef4444; }
  .to-pink-600 { --tw-gradient-to: #ec4899; }
  .from-green-500 { --tw-gradient-from: #22c55e; }
  .to-green-600 { --tw-gradient-to: #16a34a; }
  .from-orange-500 { --tw-gradient-from: #f97316; }
  .to-orange-600 { --tw-gradient-to: #ea580c; }
  .from-teal-500 { --tw-gradient-from: #14b8a6; }
  .to-teal-600 { --tw-gradient-to: #0d9488; }
  .from-gray-500 { --tw-gradient-from: #6b7280; }
  .to-gray-600 { --tw-gradient-to: #4b5563; }
  .from-blue-500 { --tw-gradient-from: #3b82f6; }
  .to-blue-600 { --tw-gradient-to: #2563eb; }
  .from-yellow-500 { --tw-gradient-from: #eab308; }
  .to-yellow-600 { --tw-gradient-to: #ca8a04; }
  .bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); }
  .from-blue-600 { --tw-gradient-from: #2563eb; }
  .to-purple-600 { --tw-gradient-to: #9333ea; }
  .from-green-50 { --tw-gradient-from: #f0fdf4; }
  .to-blue-50 { --tw-gradient-to: #eff6ff; }
  .from-blue-50 { --tw-gradient-from: #eff6ff; }
  .to-purple-50 { --tw-gradient-to: #f5f3ff; }
  .from-purple-50 { --tw-gradient-from: #f5f3ff; }
  .to-pink-50 { --tw-gradient-to: #fdf2f8; }
  .bg-gradient-to-t { background-image: linear-gradient(to top, var(--tw-gradient-stops)); }
  .to-blue-300 { --tw-gradient-to: #93c5fd; }
  .flex { display: flex; }
  .items-center { align-items: center; }
  .justify-center { justify-content: center; }
  .justify-between { justify-content: space-between; }
  .bg-white\/90 { background-color: rgba(255, 255, 255, 0.9); }
  .backdrop-blur-sm { backdrop-filter: blur(4px); }
  .p-8 { padding: 2rem; }
  .p-6 { padding: 1.5rem; }
  .p-4 { padding: 1rem; }
  .p-3 { padding: 0.75rem; }
  .p-2 { padding: 0.5rem; }
  .px-4 { padding-left: 1rem; padding-right: 1rem; }
  .py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
  .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
  .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
  .rounded-2xl { border-radius: 1rem; }
  .rounded-xl { border-radius: 0.75rem; }
  .rounded-lg { border-radius: 0.5rem; }
  .rounded-full { border-radius: 9999px; }
  .shadow-2xl { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
  .shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
  .shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
  .w-full { width: 100%; }
  .max-w-md { max-width: 28rem; }
  .w-16 { width: 4rem; }
  .h-16 { height: 4rem; }
  .w-32 { width: 8rem; }
  .h-48 { height: 12rem; }
  .h-64 { height: 16rem; }
  .max-h-64 { max-height: 16rem; }
  .border { border-width: 1px; }
  .border-4 { border-width: 4px; }
  .border-white\/20 { border-color: rgba(255, 255, 255, 0.2); }
  .border-gray-300 { border-color: #d1d5db; }
  .border-gray-400 { border-color: #9ca3af; }
  .border-green-200 { border-color: #bbf7d0; }
  .border-blue-200 { border-color: #bfdbfe; }
  .border-purple-200 { border-color: #e9d5ff; }
  .border-yellow-500 { border-color: #eab308; }
  .border-l-4 { border-left-width: 4px; }
  .text-center { text-align: center; }
  .mb-8 { margin-bottom: 2rem; }
  .mb-4 { margin-bottom: 1rem; }
  .mb-2 { margin-bottom: 0.5rem; }
  .mt-2 { margin-top: 0.5rem; }
  .mt-4 { margin-top: 1rem; }
  .mt-6 { margin-top: 1.5rem; }
  .mt-8 { margin-top: 2rem; }
  .mr-2 { margin-right: 0.5rem; }
  .mx-auto { margin-left: auto; margin-right: auto; }
  .pt-6 { padding-top: 1.5rem; }
  .border-t { border-top-width: 1px; }
  .border-gray-200 { border-color: #e5e7eb; }
  .text-3xl { font-size: 1.875rem; }
  .text-2xl { font-size: 1.5rem; }
  .text-xl { font-size: 1.25rem; }
  .text-6xl { font-size: 3.75rem; }
  .text-sm { font-size: 0.875rem; }
  .font-bold { font-weight: 700; }
  .font-semibold { font-weight: 600; }
  .font-medium { font-weight: 500; }
  .bg-clip-text { background-clip: text; }
  .text-transparent { color: transparent; }
  .text-gray-600 { color: #4b5563; }
  .text-gray-700 { color: #374151; }
  .text-gray-800 { color: #1f2937; }
  .text-gray-500 { color: #6b7280; }
  .text-blue-600 { color: #2563eb; }
  .text-blue-100 { color: #dbeafe; }
  .text-white { color: #ffffff; }
  .text-green-600 { color: #16a34a; }
  .text-green-100 { color: #dcfce7; }
  .text-orange-100 { color: #ffedd5; }
  .text-red-100 { color: #fee2e2; }
  .text-teal-100 { color: #ccfbf1; }
  .text-red-600 { color: #dc2626; }
  .text-purple-600 { color: #9333ea; }
  .text-yellow-700 { color: #a16207; }
  .text-green-500 { color: #22c55e; }
  .text-red-500 { color: #ef4444; }
  .bg-green-600 { background-color: #16a34a; }
  .bg-red-600 { background-color: #dc2626; }
  .bg-white { background-color: #ffffff; }
  .bg-gray-100 { background-color: #f3f4f6; }
  .bg-blue-50 { background-color: #eff6ff; }
  .bg-green-50 { background-color: #f0fdf4; }
  .bg-yellow-100 { background-color: #fefcbf; }
  .bg-gray-800 { background-color: #1f2937; }
  .text-gray-300 { color: #d1d5db; }
  .text-gray-400 { color: #9ca3af; }
  .text-blue-400 { color: #60a5fa; }
  .space-y-6 > * + * { margin-top: 1.5rem; }
  .space-x-4 > * + * { margin-left: 1rem; }
  .space-y-2 > * + * { margin-top: 0.5rem; }
  .space-y-3 > * + * { margin-top: 0.75rem; }
  .block { display: block; }
  .focus\:outline-none:focus { outline: none; }
  .focus\:ring-2:focus { box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); }
  .transition-all { transition: all 0.3s ease; }
  .hover\:from-blue-700:hover { --tw-gradient-from: #1d4ed8; }
  .hover\:to-purple-700:hover { --tw-gradient-to: #7e22ce; }
  .hover\:from-green-600:hover { --tw-gradient-from: #16a34a; }
  .hover\:to-green-700:hover { --tw-gradient-to: #15803d; }
  .hover\:from-orange-600:hover { --tw-gradient-from: #ea580c; }
  .hover\:to-orange-700:hover { --tw-gradient-to: #c2410c; }
  .hover\:from-red-600:hover { --tw-gradient-from: #dc2626; }
  .hover\:to-red-700:hover { --tw-gradient-to: #b91c1c; }
  .hover\:shadow-xl:hover { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); }
  .transform { transform: translateZ(0); }
  .hover\:-translate-y-0\.5:hover { transform: translateY(-0.125rem); }
  .container { max-width: 1280px; margin: 0 auto; }
  .relative { position: relative; }
  .absolute { position: absolute; }
  .bottom-0 { bottom: 0; }
  .left-0 { left: 0; }
  .right-0 { right: 0; }
  .inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
  .mix-blend-difference { mix-blend-mode: difference; }
  .overflow-hidden { overflow: hidden; }
  .overflow-y-auto { overflow-y: auto; }
  .grid { display: grid; }
  .grid-cols-1 { grid-template-columns: 1fr; }
  .grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
  .grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
  .md\:grid-cols-4 { @media (min-width: 768px) { grid-template-columns: repeat(4, 1fr); } }
  .lg\:grid-cols-2 { @media (min-width: 1024px) { grid-template-columns: repeat(2, 1fr); } }
  .gap-4 { gap: 1rem; }
  .gap-3 { gap: 0.75rem; }
  .gap-6 { gap: 1.5rem; }
  .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
  @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
</style>
)rawliteral";

// Generate a simple random session token
String generateSessionToken() {
  String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  String token = "";
  for (int i = 0; i < 16; i++) {
    token += chars[random(0, chars.length())];
  }
  return token;
}

// HTML Login Page
String loginPage() {
  String page = String(R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Expires" content="0">
  <title>Yarana Smart Home - Login</title>
  <script src="https://cdn.tailwindcss.com"></script>
   <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            'yarana-blue': '#1e40af',
            'yarana-light': '#3b82f6',
          }
        }
      }
    }
  </script>
)rawliteral") + tailwindCSS + R"rawliteral(
</head>
<body class="bg-gradient-to-br from-blue-500 to-purple-600 min-h-screen flex items-center justify-center">
  <div class="bg-white/90 backdrop-blur-sm p-8 rounded-2xl shadow-2xl w-full max-w-md border border-white/20">
    <div class="text-center mb-8">
      <div class="bg-gradient-to-r from-blue-600 to-purple-600 w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center">
        <svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.4 4.4 0 003 15z"></path>
        </svg>
      </div>
      <h1 class="text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">Yarana Smart Home</h1>
      <p class="text-gray-600 mt-2">Enter your credentials to access dashboard</p>
    </div>
    <form action="/login" method="post" class="space-y-6">
      <div>
        <label class="block text-sm font-medium text-gray-700 mb-2">Username</label>
        <input name="username" type="text" autocomplete="username" required class="w-full p-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" placeholder="Enter username">
      </div>
      <div>
        <label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
        <input name="password" type="password" autocomplete="current-password" required class="w-full p-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" placeholder="Enter password">
      </div>
      <button type="submit" class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white p-3 rounded-xl hover:from-blue-700 hover:to-purple-700 transition-all duration-300 font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
        Access Dashboard
      </button>
    </form>
    <div class="text-center mt-6 pt-6 border-t border-gray-200">
      <p class="text-sm text-gray-500">Created by <span class="font-semibold text-blue-600">Abhishek Maurya</span></p>
    </div>
  </div>
</body>
</html>
)rawliteral";
  Serial.println("Serving login page, CSS length: " + String(strlen(tailwindCSS)) + ", HTML length: " + String(page.length()));
  return page;
}

// Enhanced Dashboard Page with Relay State
String dashboardPage() {
  String logs = "";
  String chartData = "";
  String tempChartData = "";
  String humidityChartData = "";
  String timeLabels = "";
  
  // Get sensor history - reverse to have oldest first for charts
  for (int i = 9; i >= 0; i--) {
    float distance = preferences.getFloat(("d" + String(i)).c_str(), 0);
    float temperature = preferences.getFloat(("temp" + String(i)).c_str(), 0);
    float humidity = preferences.getFloat(("hum" + String(i)).c_str(), 0);
    String timestamp = preferences.getString(("t" + String(i)).c_str(), "");
    if (distance > 0) {
      logs += "<div class='flex justify-between items-center p-3 bg-gray-50 rounded-lg mb-2'>";
      logs += "<span class='text-sm text-gray-600'>" + timestamp + "</span>";
      logs += "<span class='font-semibold text-blue-600'>" + String(distance, 1) + " cm | " + (isnan(temperature) ? "N/A" : String(temperature, 1) + "Β°C") + " | " + (isnan(humidity) ? "N/A" : String(humidity, 1) + "%") + "</span>";
      logs += "</div>";
    }
    chartData += String(distance, 1);
    tempChartData += isnan(temperature) ? "0" : String(temperature, 1);
    humidityChartData += isnan(humidity) ? "0" : String(humidity, 1);
    timeLabels += "'" + (timestamp.length() > 0 ? timestamp.substring(11, 16) : String(9 - i)) + "'";
    if (i > 0) {
      chartData += ",";
      tempChartData += ",";
      humidityChartData += ",";
      timeLabels += ",";
    }
  }

  // Calculate water level percentage
  float currentDistance = preferences.getFloat("d0", 0);
  float waterLevel = ((TANK_HEIGHT - currentDistance) / TANK_HEIGHT) * 100;
  if (waterLevel < 0) waterLevel = 0;
  if (waterLevel > 100) waterLevel = 100;
  
  float waterVolume = (waterLevel / 100) * TANK_CAPACITY;
  
  // Get detection stats
  int todayDetections = preferences.getInt("todayDet", 0);
  String lastDetection = preferences.getString("lastDet", "Never");

  // Check for DHT issues
  String dhtWarning = "";
  float temp = dht.readTemperature();
  if (isnan(temp)) {
    dhtWarning = "<div class='bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-4 rounded-lg' role='alert'>" +
                 String("<p class='font-bold'>Warning</p><p>DHT sensor not responding. Check connections to GPIO ") + String(DHT_PIN) + ".</p></div>";
  }

  // Get relay states
  String relay1State = digitalRead(RELAY1) == LOW ? "ON" : "OFF";
  String relay2State = digitalRead(RELAY2) == LOW ? "ON" : "OFF";
  String relay3State = digitalRead(RELAY3) == LOW ? "ON" : "OFF";
  String relay4State = digitalRead(RELAY4) == LOW ? "ON" : "OFF";
  String relay5State = digitalRead(RELAY5) == LOW ? "ON" : "OFF";
  String relay6State = digitalRead(RELAY6) == LOW ? "ON" : "OFF";
  String relay7State = digitalRead(RELAY7) == LOW ? "ON" : "OFF";
  String relay8State = digitalRead(RELAY8) == LOW ? "ON" : "OFF";
  String relay1Class = digitalRead(RELAY1) == LOW ? "text-green-500" : "text-red-500";
  String relay2Class = digitalRead(RELAY2) == LOW ? "text-green-500" : "text-red-500";
  String relay3Class = digitalRead(RELAY3) == LOW ? "text-green-500" : "text-red-500";
  String relay4Class = digitalRead(RELAY4) == LOW ? "text-green-500" : "text-red-500";
  String relay5Class = digitalRead(RELAY5) == LOW ? "text-green-500" : "text-red-500";
  String relay6Class = digitalRead(RELAY6) == LOW ? "text-green-500" : "text-red-500";
  String relay7Class = digitalRead(RELAY7) == LOW ? "text-green-500" : "text-red-500";
  String relay8Class = digitalRead(RELAY8) == LOW ? "text-green-500" : "text-red-500";

  // Get token for dashboard links
  String tokenParam = sessionToken.isEmpty() ? "" : "?token=" + sessionToken;

  String page = String(R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Expires" content="0">
  <title>Saikat Smart Home Dashboard</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            'yarana-blue': '#1e40af',
            'yarana-light': '#3b82f6',
          }
        }
      }
    }
  </script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
)rawliteral") + tailwindCSS + R"rawliteral(
</head>
<body class="bg-gray-50 min-h-screen">
  <!-- Header -->
  <header class="bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-lg">
    <div class="container mx-auto px-4 py-6">
      <div class="flex items-center justify-between">
        <div class="flex items-center space-x-4">
          <div class="bg-white/20 p-3 rounded-lg">
            <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.4 4.4 0 003 15z"></path>
            </svg>
          </div>
          <div>
            <h1 class="text-2xl font-bold">Saikat Smart Home</h1>
            <p class="text-blue-100">Advanced IoT Control System</p>
          </div>
        </div>
        <div class="flex items-center space-x-4">
          <p id="currentTime" class="text-sm text-blue-100">Current Time: Loading...</p>
          <div class="text-right">
            <p class="text-sm text-blue-100">Created by</p>
            <p class="font-semibold">Saikat Biswas</p>
          </div>
          <a href="/logout)rawliteral" + tokenParam + R"rawliteral(" class="bg-gradient-to-r from-red-500 to-red-600 text-white p-2 rounded-lg hover:from-red-600 hover:to-red-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5">
            Logout
          </a>
        </div>
      </div>
    </div>
  </header>

  <div class="container mx-auto p-4 space-y-6">
    )rawliteral" + dhtWarning + R"rawliteral(
    <!-- Status Cards Row -->
    <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
      <!-- Water Tank Status -->
      <div class="bg-gradient-to-br from-blue-500 to-blue-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-blue-100">Water Level</p>
            <p class="text-2xl font-bold" id="waterLevel">)rawliteral" + String(waterLevel, 0) + R"rawliteral(%</p>
          </div>
          <div class="text-3xl opacity-80">πŸ’§</div>
        </div>
      </div>
      
      <!-- Distance Reading -->
      <div class="bg-gradient-to-br from-green-500 to-green-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-green-100">Distance</p>
            <p class="text-2xl font-bold" id="distance">...</p>
          </div>
          <div class="text-3xl opacity-80">πŸ“</div>
        </div>
      </div>
      
      <!-- PIR Status -->
      <div class="bg-gradient-to-br from-orange-500 to-orange-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-orange-100">Motion</p>
            <p class="text-xl font-bold" id="pir">...</p>
          </div>
          <div class="text-3xl opacity-80" id="motionIcon">πŸ‘οΈ</div>
        </div>
      </div>
      
      <!-- Temperature -->
      <div class="bg-gradient-to-br from-red-500 to-red-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-red-100">Temperature</p>
            <p class="text-2xl font-bold" id="temperature">...</p>
          </div>
          <div class="text-3xl opacity-80">🌑️</div>
        </div>
      </div>
      
      <!-- Humidity -->
      <div class="bg-gradient-to-br from-teal-500 to-teal-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-teal-100">Humidity</p>
            <p class="text-2xl font-bold" id="humidity">...</p>
          </div>
          <div class="text-3xl opacity-80">πŸ’§</div>
        </div>
      </div>

      <!-- Smoke Status -->
      <div class="bg-gradient-to-br from-gray-500 to-gray-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-gray-100">Smoke</p>
            <p class="text-xl font-bold" id="smoke">...</p>
          </div>
          <div class="text-3xl opacity-80" id="smokeIcon">πŸ”₯</div>
        </div>
      </div>

      <!-- Rain Status -->
      <div class="bg-gradient-to-br from-blue-500 to-blue-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-blue-100">Rain</p>
            <p class="text-xl font-bold" id="rain">...</p>
          </div>
          <div class="text-3xl opacity-80" id="rainIcon">🌧️</div>
        </div>
      </div>

      <!-- LDR Status -->
      <div class="bg-gradient-to-br from-yellow-500 to-yellow-600 text-white p-6 rounded-xl shadow-lg">
        <div class="flex items-center justify-between">
          <div>
            <p class="text-yellow-100">Light</p>
            <p class="text-xl font-bold" id="ldr">...</p>
          </div>
          <div class="text-3xl opacity-80" id="ldrIcon">πŸ’‘</div>
        </div>
      </div>
    </div>

    <!-- Water Tank Visualization & Controls Row -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
      
      <!-- Water Tank Visual -->
      <div class="bg-white p-6 rounded-xl shadow-lg">
        <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
          <span class="mr-2">🏠</span> Water Tank Monitor
        </h2>
        <div class="text-center">
          <div class="relative mx-auto w-32 h-48 border-4 border-gray-400 rounded-lg bg-gray-100 overflow-hidden">
            <div id="waterFill" class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-blue-500 to-blue-300 transition-all duration-1000" style="height: )rawliteral" + String(waterLevel) + R"rawliteral(%;"></div>
            <div class="absolute inset-0 flex items-center justify-center text-white font-bold text-sm mix-blend-difference">
              <span id="waterPercent">)rawliteral" + String(waterLevel, 0) + R"rawliteral(%</span>
            </div>
          </div>
          <div class="mt-4 grid grid-cols-2 gap-4 text-sm">
            <div class="bg-blue-50 p-3 rounded-lg">
              <p class="text-blue-600 font-semibold">Volume</p>
              <p class="text-gray-800" id="waterVolume">)rawliteral" + String(waterVolume, 0) + R"rawliteral(L</p>
            </div>
            <div class="bg-green-50 p-3 rounded-lg">
              <p class="text-green-600 font-semibold">Capacity</p>
              <p class="text-gray-800">)rawliteral" + String(TANK_CAPACITY, 0) + R"rawliteral(L</p>
            </div>
          </div>
        </div>
      </div>
      
      <!-- Relay Controls with State -->
      <div class="bg-white p-6 rounded-xl shadow-lg">
        <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
          <span class="mr-2">⚑</span> Device Controls
        </h2>
        <div class="grid grid-cols-2 gap-3">
          <div class="flex items-center justify-between">
            <button onclick="toggle(1)" id="relay1Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY1) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">πŸ’‘</span> Light 1
            </button>
            <span id="relay1State" class="text-sm font-semibold )rawliteral" + relay1Class + R"rawliteral(">)rawliteral" + relay1State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(2)" id="relay2Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY2) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">🌊</span> Pump
            </button>
            <span id="relay2State" class="text-sm font-semibold )rawliteral" + relay2Class + R"rawliteral(">)rawliteral" + relay2State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(3)" id="relay3Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY3) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">πŸ”₯</span> Heater
            </button>
            <span id="relay3State" class="text-sm font-semibold )rawliteral" + relay3Class + R"rawliteral(">)rawliteral" + relay3State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(4)" id="relay4Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY4) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">❄️</span> Fan
            </button>
            <span id="relay4State" class="text-sm font-semibold )rawliteral" + relay4Class + R"rawliteral(">)rawliteral" + relay4State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(5)" id="relay5Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY5) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">⚑</span> Device 5
            </button>
            <span id="relay5State" class="text-sm font-semibold )rawliteral" + relay5Class + R"rawliteral(">)rawliteral" + relay5State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(6)" id="relay6Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY6) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">⚑</span> Device 6
            </button>
            <span id="relay6State" class="text-sm font-semibold )rawliteral" + relay6Class + R"rawliteral(">)rawliteral" + relay6State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(7)" id="relay7Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY7) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">⚑</span> Device 7
            </button>
            <span id="relay7State" class="text-sm font-semibold )rawliteral" + relay7Class + R"rawliteral(">)rawliteral" + relay7State + R"rawliteral(</span>
          </div>
          <div class="flex items-center justify-between">
            <button onclick="toggle(8)" id="relay8Button" class="bg-gradient-to-r )rawliteral" + (digitalRead(RELAY8) == LOW ? "from-green-500 to-green-600" : "from-red-500 to-red-600") + R"rawliteral( text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4">
              <span class="mr-2">⚑</span> Device 8
            </button>
            <span id="relay8State" class="text-sm font-semibold )rawliteral" + relay8Class + R"rawliteral(">)rawliteral" + relay8State + R"rawliteral(</span>
          </div>
        </div>
      </div>
    </div>


    <!-- Charts Row -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
      
      <!-- Distance History Chart -->
      <div class="bg-white p-6 rounded-xl shadow-lg">
        <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
          <span class="mr-2">πŸ“ˆ</span> Distance History
        </h2>
        <canvas id="distanceChart" class="w-full h-64"></canvas>
      </div>
      
      <!-- Temperature and Humidity Chart -->
      <div class="bg-white p-6 rounded-xl shadow-lg">
        <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
          <span class="mr-2">🌑️</span> Environment Monitor
        </h2>
        <canvas id="envChart" class="w-full h-64"></canvas>
      </div>
    </div>

    <!-- Motion Detection and Activity Log -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
      
      <!-- Motion Detection Log -->
      <div class="bg-white p-6 rounded-xl shadow-lg">
        <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
          <span class="mr-2">🚨</span> Motion Detection Stats
        </h2>
        <div class="space-y-3">
          <div class="bg-gradient-to-r from-green-50 to-blue-50 p-4 rounded-lg border border-green-200">
            <div class="flex justify-between items-center">
              <span class="text-gray-700">Today's Detections:</span>
              <span class="font-bold text-green-600" id="todayDetectionsDetail">)rawliteral" + String(todayDetections) + R"rawliteral(</span>
            </div>
          </div>
          <div class="bg-gradient-to-r from-blue-50 to-purple-50 p-4 rounded-lg border border-blue-200">
            <div class="flex justify-between items-center">
              <span class="text-gray-700">Last Detection:</span>
              <span class="font-bold text-blue-600" id="lastDetection">)rawliteral" + lastDetection + R"rawliteral(</span>
            </div>
          </div>
          <div class="bg-gradient-to-r from-purple-50 to-pink-50 p-4 rounded-lg border border-purple-200">
            <div class="flex justify-between items-center">
              <span class="text-gray-700">Security Status:</span>
              <span class="font-bold text-purple-600" id="securityStatus">Active</span>
            </div>
          </div>
        </div>
      </div>
      
      <!-- Recent Activity Log -->
      <div class="bg-white p-6 rounded-xl shadow-lg">
        <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
          <span class="mr-2">πŸ“‹</span> Recent Activity Logs
        </h2>
        <div class="max-h-64 overflow-y-auto space-y-2" id="activityLogs">
          )rawliteral" + logs + R"rawliteral(
        </div>
      </div>
    </div>
  </div>

  <!-- Footer -->
  <footer class="bg-gray-800 text-white py-6 mt-8">
    <div class="container mx-auto px-4 text-center">
      <p class="text-gray-300">©️ 2025 Yarana Smart Home System</p>
      <p class="text-sm text-gray-400 mt-1">Developed with ❀️ by <span class="text-blue-400 font-semibold">Abhishek Maurya</span></p>
    </div>
  </footer>

  <script>
    // Distance Chart Configuration
    const distanceCtx = document.getElementById('distanceChart').getContext('2d');
    const distanceChart = new Chart(distanceCtx, {
      type: 'line',
      data: {
        labels: [)rawliteral" + timeLabels + R"rawliteral(],
        datasets: [{
          label: 'Distance (cm)',
          data: [)rawliteral" + chartData + R"rawliteral(],
          borderColor: '#3b82f6',
          backgroundColor: 'rgba(59, 130, 246, 0.1)',
          fill: true,
          tension: 0.4,
          pointBackgroundColor: '#3b82f6',
          pointBorderColor: '#ffffff',
          pointBorderWidth: 2,
          pointRadius: 6,
          pointHoverRadius: 8
        }]
      },
      options: {
        responsive: true,
        plugins: {
          legend: { display: true, position: 'top' }
        },
        scales: {
          y: { 
            beginAtZero: true, 
            title: { display: true, text: 'Distance (cm)', font: { weight: 'bold' } },
            grid: { color: 'rgba(0,0,0,0.1)' }
          },
          x: { 
            title: { display: true, text: 'Time', font: { weight: 'bold' } },
            grid: { color: 'rgba(0,0,0,0.1)' }
          }
        },
        animation: { duration: 1000, easing: 'easeInOutCubic' }
      }
    });

    // Environment Chart Configuration
    const envCtx = document.getElementById('envChart').getContext('2d');
    const envChart = new Chart(envCtx, {
      type: 'line',
      data: {
        labels: [)rawliteral" + timeLabels + R"rawliteral(],
        datasets: [
          {
            label: 'Temperature (Β°C)',
            data: [)rawliteral" + tempChartData + R"rawliteral(],
            borderColor: '#ef4444',
            backgroundColor: 'rgba(239, 68, 68, 0.1)',
            fill: true,
            tension: 0.4,
            pointBackgroundColor: '#ef4444',
            pointBorderColor: '#ffffff',
            pointBorderWidth: 2,
            pointRadius: 6,
            pointHoverRadius: 8
          },
          {
            label: 'Humidity (%)',
            data: [)rawliteral" + humidityChartData + R"rawliteral(],
            borderColor: '#14b8a6',
            backgroundColor: 'rgba(20, 184, 166, 0.1)',
            fill: true,
            tension: 0.4,
            pointBackgroundColor: '#14b8a6',
            pointBorderColor: '#ffffff',
            pointBorderWidth: 2,
            pointRadius: 6,
            pointHoverRadius: 8
          }
        ]
      },
      options: {
        responsive: true,
        plugins: {
          legend: { display: true, position: 'top' }
        },
        scales: {
          y: { 
            beginAtZero: true, 
            title: { display: true, text: 'Value', font: { weight: 'bold' } },
            grid: { color: 'rgba(0,0,0,0.1)' }
          },
          x: { 
            title: { display: true, text: 'Time', font: { weight: 'bold' } },
            grid: { color: 'rgba(0,0,0,0.1)' }
          }
        },
        animation: { duration: 1000, easing: 'easeInOutCubic' }
      }
    });

    // Fetch Sensor Data
    function fetchData() {
      const token = new URLSearchParams(window.location.search).get("token") || "";
      fetch("/sensor?token=" + token)
        .then(res => {
          if (!res.ok) throw new Error("Network response was not ok");
          return res.json();
        })
        .then(data => {
          document.getElementById("distance").innerText = data.distance.toFixed(1) + " cm";
          
          const pirElement = document.getElementById("pir");
          const motionIcon = document.getElementById("motionIcon");
          if (data.pir) {
            pirElement.innerText = "Detected!";
            motionIcon.innerText = "🚨";
            pirElement.parentElement.parentElement.className = pirElement.parentElement.parentElement.className.replace('from-orange-500 to-orange-600', 'from-red-500 to-red-600');
          } else {
            pirElement.innerText = "Clear";
            motionIcon.innerText = "πŸ‘οΈ";
            pirElement.parentElement.parentElement.className = pirElement.parentElement.parentElement.className.replace('from-red-500 to-red-600', 'from-orange-500 to-orange-600');
          }
          
          const waterLevel = Math.max(0, Math.min(100, ((200 - data.distance) / 200) * 100));
          document.getElementById("waterLevel").innerText = waterLevel.toFixed(0) + "%";
          document.getElementById("waterPercent").innerText = waterLevel.toFixed(0) + "%";
          document.getElementById("waterFill").style.height = waterLevel + "%";
          document.getElementById("waterVolume").innerText = ((waterLevel / 100) * 1000).toFixed(0) + "L";
          
          document.getElementById("temperature").innerText = isNaN(data.temperature) ? "N/A" : data.temperature.toFixed(1) + "Β°C";
          document.getElementById("humidity").innerText = isNaN(data.humidity) ? "N/A" : data.humidity.toFixed(1) + "%";
          
          document.getElementById("todayDetectionsDetail").innerText = data.detections || 0;
          document.getElementById("lastDetection").innerText = data.lastDetection || "Never";
          
          document.getElementById("currentTime").innerText = "Current Time: " + data.timestamp;
          
          // Update relay states
          const relays = [
            { id: "relay1", state: data.relay1State, button: document.getElementById("relay1Button") },
            { id: "relay2", state: data.relay2State, button: document.getElementById("relay2Button") },
            { id: "relay3", state: data.relay3State, button: document.getElementById("relay3Button") },
            { id: "relay4", state: data.relay4State, button: document.getElementById("relay4Button") },
            { id: "relay5", state: data.relay5State, button: document.getElementById("relay5Button") },
            { id: "relay6", state: data.relay6State, button: document.getElementById("relay6Button") },
            { id: "relay7", state: data.relay7State, button: document.getElementById("relay7Button") },
            { id: "relay8", state: data.relay8State, button: document.getElementById("relay8Button") }
          ];
          
          relays.forEach(relay => {
            const stateElement = document.getElementById(relay.id + "State");
            if (stateElement) {
              stateElement.innerText = relay.state || "OFF";
              stateElement.className = relay.state === "ON" ? "text-sm font-semibold text-green-500" : "text-sm font-semibold text-red-500";
            }
            if (relay.button) {
              relay.button.className = relay.state === "ON" ?
                "bg-gradient-to-r from-green-500 to-green-600 text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4" :
                "bg-gradient-to-r from-red-500 to-red-600 text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4";
            }
          });
          
          // Update new sensors
          const smokeElement = document.getElementById("smoke");
          const smokeIcon = document.getElementById("smokeIcon");
          if (data.smokeDetect) {
            smokeElement.innerText = "Detected!";
            smokeIcon.innerText = "🚨";
            smokeElement.parentElement.parentElement.className = smokeElement.parentElement.parentElement.className.replace(/from-gray-500 to-gray-600/, 'from-red-500 to-red-600');
          } else {
            smokeElement.innerText = "Clear";
            smokeIcon.innerText = "πŸ”₯";
            smokeElement.parentElement.parentElement.className = smokeElement.parentElement.parentElement.className.replace(/from-red-500 to-red-600/, 'from-gray-500 to-gray-600');
          }

          const rainElement = document.getElementById("rain");
          const rainIcon = document.getElementById("rainIcon");
          if (data.rainDetect) {
            rainElement.innerText = "Raining!";
            rainIcon.innerText = "🚨";
            rainElement.parentElement.parentElement.className = rainElement.parentElement.parentElement.className.replace(/from-blue-500 to-blue-600/, 'from-red-500 to-red-600');
          } else {
            rainElement.innerText = "Dry";
            rainIcon.innerText = "🌧️";
            rainElement.parentElement.parentElement.className = rainElement.parentElement.parentElement.className.replace(/from-red-500 to-red-600/, 'from-blue-500 to-blue-600');
          }

          const ldrElement = document.getElementById("ldr");
          const ldrIcon = document.getElementById("ldrIcon");
          if (data.ldrDetect) {
            ldrElement.innerText = "Dark!";
            ldrIcon.innerText = "🚨";
            ldrElement.parentElement.parentElement.className = ldrElement.parentElement.parentElement.className.replace(/from-yellow-500 to-yellow-600/, 'from-red-500 to-red-600');
          } else {
            ldrElement.innerText = "Bright";
            ldrIcon.innerText = "πŸ’‘";
            ldrElement.parentElement.parentElement.className = ldrElement.parentElement.parentElement.className.replace(/from-red-500 to-red-600/, 'from-yellow-500 to-yellow-600');
          }
          
          if (data.history && data.history.length > 0) {
            distanceChart.data.datasets[0].data = data.history;
            distanceChart.update('none');
          }
          if (data.tempHistory && data.tempHistory.length > 0) {
            envChart.data.datasets[0].data = data.tempHistory;
            envChart.data.datasets[1].data = data.humidityHistory;
            envChart.update('none');
          }
        })
        .catch(err => {
          console.error('Failed to fetch sensor data:', err);
        });
    }

    // Toggle Relay
    function toggle(relay) {
      const button = event.target.closest('button');
      button.classList.add('animate-pulse');
      setTimeout(() => button.classList.remove('animate-pulse'), 500);
      
      const token = new URLSearchParams(window.location.search).get("token") || "";
      fetch("/relay?ch=" + relay + "&token=" + token)
        .then(res => res.json())
        .then(data => {
          if (data.status === "OK") {
            const stateElement = document.getElementById("relay" + relay + "State");
            const buttonElement = document.getElementById("relay" + relay + "Button");
            if (stateElement && buttonElement) {
              stateElement.innerText = data.state;
              stateElement.className = data.state === "ON" ? "text-sm font-semibold text-green-500" : "text-sm font-semibold text-red-500";
              buttonElement.className = data.state === "ON" ?
                "bg-gradient-to-r from-green-500 to-green-600 text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4" :
                "bg-gradient-to-r from-red-500 to-red-600 text-white p-3 rounded-lg hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5 flex items-center justify-center w-3/4";
            }
          }
        })
        .catch(err => {
          console.error('Failed to toggle relay:', err);
        });
    }

    // Animate cards
    document.addEventListener('DOMContentLoaded', () => {
      const cards = document.querySelectorAll('.bg-white, .bg-gradient-to-br');
      cards.forEach((card, index) => {
        card.style.opacity = '0';
        card.style.transform = 'translateY(20px)';
        setTimeout(() => {
          card.style.transition = 'all 0.6s ease-out';
          card.style.opacity = '1';
          card.style.transform = 'translateY(0)';
        }, index * 100);
      });
      fetchData(); // Initial data fetch
    });

    // Auto refresh data every 2 seconds
    setInterval(fetchData, 2000);
  </script>
</body>
</html>
)rawliteral";
  Serial.println("Serving dashboard page, CSS length: " + String(strlen(tailwindCSS)) + ", HTML length: " + String(page.length()));
  return page;
}

// Check if session is valid
bool isSessionValid() {
  if (server.hasArg("token")) {
    String token = server.arg("token");
    Serial.println("Received Token: " + token + " for URI: " + server.uri());
    if (token == sessionToken && !sessionToken.isEmpty()) {
      if (millis() - sessionStartTime < SESSION_TIMEOUT) {
        sessionStartTime = millis();
        Serial.println("Session valid, token: " + sessionToken + ", extended timeout");
        return true;
      } else {
        Serial.println("Session expired for token: " + sessionToken);
      }
    } else {
      Serial.println("Invalid token. Expected: " + sessionToken);
    }
  } else {
    Serial.println("No token received for URI: " + server.uri());
  }
  return false;
}

// Handle Root
void handleRoot() {
  isLoggedIn = isSessionValid();
  if (!isLoggedIn) {
    Serial.println("Root accessed, no valid session, serving login page");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    String page = loginPage();
    server.send(200, "text/html", page);
    Serial.println("Login page sent, size: " + String(page.length()) + " bytes");
  } else {
    Serial.println("Root accessed, valid session, redirecting to dashboard");
    server.sendHeader("Location", "/dashboard?token=" + sessionToken);
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(303);
  }
}

// Handle Dashboard
void handleDashboard() {
  if (!isSessionValid()) {
    Serial.println("Dashboard accessed, no valid session, redirecting to login");
    server.sendHeader("Location", "/");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(303);
    return;
  }
  Serial.println("Dashboard accessed, serving dashboard page");
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "0");
  String page = dashboardPage();
  server.send(200, "text/html", page);
  Serial.println("Dashboard page sent, size: " + String(page.length()) + " bytes");
}

// Handle Login
void handleLogin() {
  if (server.method() == HTTP_POST) {
    String user = server.arg("username");
    String pass = server.arg("password");
    Serial.println("Login attempt - Username: " + user + ", Password: " + pass);
    if (user == "admin" && pass == "1234") {
      sessionToken = generateSessionToken();
      sessionStartTime = millis();
      isLoggedIn = true;
      Serial.println("Login successful, token: " + sessionToken);
      server.sendHeader("Location", "/dashboard?token=" + sessionToken);
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "0");
      server.send(303);
    } else {
      String errorMessage = user.isEmpty() || pass.isEmpty() ? "Username or password field is empty" : "Invalid username or password";
      Serial.println("Login failed: " + errorMessage);
      String errorPage = String(R"rawliteral(
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"><meta http-equiv="Pragma" content="no-cache"><meta http-equiv="Expires" content="0"><title>Login Failed</title>)rawliteral") + tailwindCSS + R"rawliteral(</head>
<body class="bg-gradient-to-br from-red-500 to-pink-600 min-h-screen flex items-center justify-center">
<div class="bg-white/90 p-8 rounded-2xl shadow-2xl text-center max-w-md w-full">
<div class="text-6xl mb-4">❌</div>
<h2 class="text-2xl font-bold text-red-600 mb-4">Access Denied!</h2>
<p class="text-gray-600 mb-6">)rawliteral" + errorMessage + R"rawliteral(. Please try again.</p>
<p class="text-gray-600 mb-6">If login fails repeatedly, ensure you are accessing via http://<ESP32_IP_ADDRESS> and try a different browser.</p>
<a href="/" class="bg-gradient-to-r from-blue-600 to-purple-600 text-white px-6 py-3 rounded-lg hover:from-blue-700 hover:to-purple-700 transition-all">Try Again</a>
</div>
</body></html>
)rawliteral";
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "0");
      server.send(200, "text/html", errorPage);
      Serial.println("Error page sent, size: " + String(errorPage.length()) + " bytes");
    }
  } else {
    Serial.println("Invalid request method for /login");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(405, "text/plain", "Method Not Allowed");
  }
}

// Get formatted time
String getFormattedTime() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    return "Time unavailable";
  }
  char timeString[64];
  strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", &timeinfo);
  return String(timeString);
}

// Parse datetime-local to time_t
time_t parseDateTime(String dt) {
  struct tm tm;
  sscanf(dt.c_str(), "%d-%d-%dT%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min);
  tm.tm_year -= 1900;
  tm.tm_mon -= 1;
  tm.tm_sec = 0;
  tm.tm_isdst = -1;
  return mktime(&tm) + gmtOffset_sec;
}

// Handle Relay
void handleRelay() {
  if (!isSessionValid()) {
    Serial.println("Relay accessed, no valid session, unauthorized");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(401, "text/plain", "Unauthorized");
    return;
  }
  
  if (!server.hasArg("ch")) {
    Serial.println("Relay accessed, missing channel parameter");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(400, "text/plain", "Missing channel");
    return;
  }
  
  int ch = server.arg("ch").toInt();
  int pin;
  String deviceName;
  
  switch (ch) {
    case 1: pin = RELAY1; deviceName = "Light 1"; break;
    case 2: pin = RELAY2; deviceName = "Pump"; break;
    case 3: pin = RELAY3; deviceName = "Heater"; break;
    case 4: pin = RELAY4; deviceName = "Fan"; break;
    case 5: pin = RELAY5; deviceName = "Device 5"; break;
    case 6: pin = RELAY6; deviceName = "Device 6"; break;
    case 7: pin = RELAY7; deviceName = "Device 7"; break;
    case 8: pin = RELAY8; deviceName = "Device 8"; break;
    default: 
      Serial.println("Relay accessed, invalid channel: " + String(ch));
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "0");
      server.send(400, "text/plain", "Invalid channel"); 
      return;
  }
  
  digitalWrite(pin, !digitalRead(pin));
  String state = digitalRead(pin) == LOW ? "ON" : "OFF";
  
  String timestamp = getFormattedTime();
  Serial.println("Relay " + String(ch) + " (" + deviceName + ") toggled to " + state + " at " + timestamp);
  
  StaticJsonDocument<200> doc;
  doc["status"] = "OK";
  doc["state"] = state;
  String json;
  serializeJson(doc, json);
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "0");
  server.send(200, "application/json", json);
}

// Handle Sensor Data
void handleSensor() {
  if (!isSessionValid()) {
    Serial.println("Sensor accessed, no valid session, unauthorized");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(401, "text/plain", "Unauthorized");
    return;
  }

  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 30000);
  float distance = duration > 0 ? duration * 0.034 / 2 : 999;

  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  if (isnan(temperature) || isnan(humidity)) {
    temperature = 0;
    humidity = 0;
    Serial.println("Failed to read from DHT sensor on GPIO " + String(DHT_PIN));
  }

  String currentTime = getFormattedTime();
  
  for (int i = 9; i > 0; i--) {
    float val = preferences.getFloat(("d" + String(i - 1)).c_str(), 0);
    float tempVal = preferences.getFloat(("temp" + String(i - 1)).c_str(), 0);
    float humVal = preferences.getFloat(("hum" + String(i - 1)).c_str(), 0);
    String time = preferences.getString(("t" + String(i - 1)).c_str(), "");
    preferences.putFloat(("d" + String(i)).c_str(), val);
    preferences.putFloat(("temp" + String(i)).c_str(), tempVal);
    preferences.putFloat(("hum" + String(i)).c_str(), humVal);
    preferences.putString(("t" + String(i)).c_str(), time);
  }
  preferences.putFloat("d0", distance);
  preferences.putFloat("temp0", temperature);
  preferences.putFloat("hum0", humidity);
  preferences.putString("t0", currentTime);

  bool motion = digitalRead(PIR_PIN);
  Serial.println("PIR Sensor value: " + String(motion) + " at " + currentTime);
  
  // New sensor readings
  bool smokeDetect = digitalRead(SMOKE_PIN) == HIGH; // Changed to HIGH assuming active HIGH to fix reverse issue
  bool rainDetect = digitalRead(RAIN_PIN) == LOW;
  int ldrValue = analogRead(LDR_PIN);
  bool ldrDetect = ldrValue > LDR_THRESHOLD;
  Serial.println("LDR Sensor value: " + String(ldrValue) + ", Detect: " + String(ldrDetect) + " at " + currentTime);
  
  // Handle ultrasonic detection for LED1
  if (distance > 0 && distance <= 6 && millis() - lastTriggerUltra > 3000) {
    digitalWrite(LED1, HIGH);
    lastTriggerUltra = millis();
    Serial.println("Ultrasonic detected distance <= 6cm, LED1 ON at " + currentTime);
  }

  // Handle motion detection for LED2
  if (motion && millis() - lastTriggerPIR > 3000) {
    totalDetections++;
    int todayDetections = preferences.getInt("todayDet", 0);
    preferences.putInt("todayDet", todayDetections + 1);
    preferences.putString("lastDet", currentTime);
    
    digitalWrite(LED2, HIGH);
    lastTriggerPIR = millis();
    Serial.println("Motion detected, LED2 ON at " + currentTime);
  }

  // Handle smoke detection for LED3
  if (smokeDetect && millis() - lastTriggerSmoke > 3000) {
    digitalWrite(LED3, HIGH);
    lastTriggerSmoke = millis();
    Serial.println("Smoke detected, LED3 ON at " + currentTime);
  }

  // Handle rain detection for LED4
  if (rainDetect && millis() - lastTriggerRain > 3000) {
    digitalWrite(LED4, HIGH);
    lastTriggerRain = millis();
    Serial.println("Rain detected, LED4 ON at " + currentTime);
  }

  // Handle LDR detection for LED5
  if (ldrDetect && millis() - lastTriggerLDR > 3000) {
    digitalWrite(LED5, HIGH);
    lastTriggerLDR = millis();
    Serial.println("Dark detected (LDR), LED5 ON at " + currentTime);
  }

  StaticJsonDocument<600> doc;
  doc["distance"] = distance;
  doc["temperature"] = isnan(temperature) ? 0 : temperature;
  doc["humidity"] = isnan(humidity) ? 0 : humidity;
  doc["pir"] = motion;
  doc["timestamp"] = currentTime;
  doc["detections"] = preferences.getInt("todayDet", 0);
  doc["lastDetection"] = preferences.getString("lastDet", "Never");
  doc["relay1State"] = digitalRead(RELAY1) == LOW ? "ON" : "OFF";
  doc["relay2State"] = digitalRead(RELAY2) == LOW ? "ON" : "OFF";
  doc["relay3State"] = digitalRead(RELAY3) == LOW ? "ON" : "OFF";
  doc["relay4State"] = digitalRead(RELAY4) == LOW ? "ON" : "OFF";
  doc["relay5State"] = digitalRead(RELAY5) == LOW ? "ON" : "OFF";
  doc["relay6State"] = digitalRead(RELAY6) == LOW ? "ON" : "OFF";
  doc["relay7State"] = digitalRead(RELAY7) == LOW ? "ON" : "OFF";
  doc["relay8State"] = digitalRead(RELAY8) == LOW ? "ON" : "OFF";
  doc["smokeDetect"] = smokeDetect;
  doc["rainDetect"] = rainDetect;
  doc["ldrDetect"] = ldrDetect;
  
  JsonArray history = doc.createNestedArray("history");
  JsonArray tempHistory = doc.createNestedArray("tempHistory");
  JsonArray humidityHistory = doc.createNestedArray("humidityHistory");
  for (int i = 9; i >= 0; i--) { // Reverse for oldest first
    history.add(preferences.getFloat(("d" + String(i)).c_str(), 0));
    tempHistory.add(preferences.getFloat(("temp" + String(i)).c_str(), 0));
    humidityHistory.add(preferences.getFloat(("hum" + String(i)).c_str(), 0));
  }

  String json;
  serializeJson(doc, json);
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "0");
  server.send(200, "application/json", json);
  Serial.println("Sensor data sent, JSON size: " + String(json.length()) + " bytes");
}


// Handle Logout
void handleLogout() {
  sessionToken = "";
  isLoggedIn = false;
  Serial.println("User logged out at " + getFormattedTime());
  server.sendHeader("Location", "/");
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "0");
  server.send(303);
}

// Handle System Reset
void handleReset() {
  if (!isSessionValid()) {
    Serial.println("Reset accessed, no valid session, unauthorized");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(401, "text/plain", "Unauthorized");
    return;
  }
  
  preferences.clear();
  totalDetections = 0;
  Serial.println("System data reset at " + getFormattedTime());
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "0");
  server.send(200, "text/plain", "System reset completed");
}

// Handle System Stats
void handleStats() {
  if (!isSessionValid()) {
    Serial.println("Stats accessed, no valid session, unauthorized");
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(401, "text/plain", "Unauthorized");
    return;
  }
  
  StaticJsonDocument<300> doc;
  doc["uptime"] = millis();
  doc["freeHeap"] = ESP.getFreeHeap();
  doc["chipModel"] = ESP.getChipModel();
  doc["cpuFreq"] = ESP.getCpuFreqMHz();
  doc["wifiRSSI"] = WiFi.RSSI();
  doc["todayDetections"] = preferences.getInt("todayDet", 0);
  doc["totalDetections"] = totalDetections;
  
  String json;
  serializeJson(doc, json);
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "0");
  server.send(200, "application/json", json);
}

void setup() {
  Serial.begin(115200);
  Serial.println("\n=== Yarana Smart Home System Starting ===");
  Serial.println("Created by: Abhishek Maurya");
  Serial.println("=========================================");

  randomSeed(analogRead(0));
  dht.begin();
  Serial.println("DHT sensor initialized on GPIO " + String(DHT_PIN));

  preferences.begin("yarana", false);
  totalDetections = preferences.getInt("totalDet", 0);

  pinMode(RELAY1, OUTPUT);
  pinMode(RELAY2, OUTPUT);
  pinMode(RELAY3, OUTPUT);
  pinMode(RELAY4, OUTPUT);
  pinMode(RELAY5, OUTPUT);
  pinMode(RELAY6, OUTPUT);
  pinMode(RELAY7, OUTPUT);
  pinMode(RELAY8, OUTPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(PIR_PIN, INPUT);
  pinMode(SMOKE_PIN, INPUT);
  pinMode(RAIN_PIN, INPUT);
  // No pinMode for analog LDR_PIN
  pinMode(LED3, OUTPUT);
  pinMode(LED4, OUTPUT);
  pinMode(LED5, OUTPUT);

  digitalWrite(RELAY1, HIGH);
  digitalWrite(RELAY2, HIGH);
  digitalWrite(RELAY3, HIGH);
  digitalWrite(RELAY4, HIGH);
  digitalWrite(RELAY5, HIGH);
  digitalWrite(RELAY6, HIGH);
  digitalWrite(RELAY7, HIGH);
  digitalWrite(RELAY8, HIGH);
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);
  digitalWrite(LED3, LOW);
  digitalWrite(LED4, LOW);
  digitalWrite(LED5, LOW);

  Serial.println("GPIO pins initialized");

  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi: " + String(ssid));
  
  int wifiAttempts = 0;
  while (WiFi.status() != WL_CONNECTED && wifiAttempts < 30) {
    delay(500);
    Serial.print(".");
    wifiAttempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi Connected Successfully!");
    Serial.println("IP Address: " + WiFi.localIP().toString());
    Serial.println("Signal Strength: " + String(WiFi.RSSI()) + " dBm");
    Serial.println("Access dashboard at: http://" + WiFi.localIP().toString());
  } else {
    Serial.println("\nWiFi Connection Failed!");
    Serial.println("Starting in AP mode...");
    WiFi.softAP("Yarana_SmartHome", "yarana123");
    Serial.println("AP IP: " + WiFi.softAPIP().toString());
    Serial.println("Access dashboard at: http://" + WiFi.softAPIP().toString());
  }

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  Serial.println("NTP time synchronization started");

  server.on("/", handleRoot);
  server.on("/dashboard", handleDashboard);
  server.on("/login", handleLogin);
  server.on("/logout", handleLogout);
  server.on("/relay", handleRelay);
  server.on("/sensor", handleSensor);
  server.on("/reset", handleReset);
  server.on("/stats", handleStats);
  
  server.onNotFound([]() {
    Serial.println("Not Found: " + server.uri());
    String notFoundPage = String(R"rawliteral(
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"><meta http-equiv="Pragma" content="no-cache"><meta http-equiv="Expires" content="0"><title>404 - Not Found</title>)rawliteral") + tailwindCSS + R"rawliteral(</head>
<body class="bg-gray-100 flex items-center justify-center h-screen">
<div class="text-center"><h1 class="text-6xl font-bold text-gray-800">404</h1>
<p class="text-xl text-gray-600 mt-4">Page not found</p>
<a href="/" class="bg-blue-600 text-white px-6 py-3 rounded-lg mt-6 inline-block hover:bg-blue-700">Go Home</a>
</div>
</body></html>
)rawliteral";
    server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
    server.sendHeader("Pragma", "no-cache");
    server.sendHeader("Expires", "0");
    server.send(404, "text/html", notFoundPage);
    Serial.println("404 page sent, size: " + String(notFoundPage.length()) + " bytes");
  });

  server.begin();
  Serial.println("Web server started successfully");
  Serial.println("Default credentials: admin / 1234");
  Serial.println("CSS length: " + String(strlen(tailwindCSS)) + " bytes");
  Serial.println("=========================================");
  
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    delay(200);
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, LOW);
    delay(200);
  }
}

void loop() {
  server.handleClient();
  
  // Turn off LEDs individually after timeout
  if (millis() - lastTriggerUltra > 3000) {
    digitalWrite(LED1, LOW);
  }
  if (millis() - lastTriggerPIR > 3000) {
    digitalWrite(LED2, LOW);
  }
  if (millis() - lastTriggerSmoke > 3000) {
    digitalWrite(LED3, LOW);
  }
  if (millis() - lastTriggerRain > 3000) {
    digitalWrite(LED4, LOW);
  }
  if (millis() - lastTriggerLDR > 3000) {
    digitalWrite(LED5, LOW);
  }
  
  // Check schedules every second
  static unsigned long lastScheduleCheck = 0;
  if (millis() - lastScheduleCheck > 1000) {
    lastScheduleCheck = millis();
    time_t now = time(nullptr) + gmtOffset_sec;
    for (int relay = 1; relay <= 8; relay++) {
      String key = "sched" + String(relay);
      String sched = preferences.getString(key.c_str(), "");
      if (!sched.isEmpty()) {
        int colon1 = sched.indexOf(':');
        int colon2 = sched.indexOf(':', colon1 + 1);
        if (colon1 > 0 && colon2 > 0) {
          String startStr = sched.substring(0, colon1);
          String endStr = sched.substring(colon1 + 1, colon2);
          String action = sched.substring(colon2 + 1);
          time_t startTime = parseDateTime(startStr);
          time_t endTime = parseDateTime(endStr);
          int pin;
          switch (relay) {
            case 1: pin = RELAY1; break;
            case 2: pin = RELAY2; break;
            case 3: pin = RELAY3; break;
            case 4: pin = RELAY4; break;
            case 5: pin = RELAY5; break;
            case 6: pin = RELAY6; break;
            case 7: pin = RELAY7; break;
            case 8: pin = RELAY8; break;
          }
          if (now >= startTime && now < endTime) {
            digitalWrite(pin, action == "ON" ? LOW : HIGH);
            Serial.println("Schedule activated for relay " + String(relay) + " to " + action);
          } else if (now >= endTime) {
            preferences.remove(key.c_str()); // Remove expired schedule
          }
        }
      }
    }
  }
  
  static unsigned long lastMidnightCheck = 0;
  if (millis() - lastMidnightCheck > 60000) {
    lastMidnightCheck = millis();
    struct tm timeinfo;
    if (getLocalTime(&timeinfo)) {
      if (timeinfo.tm_hour == 0 && timeinfo.tm_min == 0) {
        if (!midnightReset) {
          preferences.putInt("todayDet", 0);
          midnightReset = true;
          Serial.println("Daily detection count reset at midnight");
        }
      } else {
        midnightReset = false;
      }
    }
  }
  
  static unsigned long lastStatusLog = 0;
  if (millis() - lastStatusLog > 300000) {
    lastStatusLog = millis();
    Serial.println("=== System Status ===");
    Serial.println("Uptime: " + String(millis() / 1000) + " seconds");
    Serial.println("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
    Serial.println("WiFi Signal: " + String(WiFi.RSSI()) + " dBm");
    Serial.println("Today's Detections: " + String(preferences.getInt("todayDet", 0)));
    Serial.println("Total Detections: " + String(totalDetections));
    Serial.println("====================");
  }
}