#include <WiFi.h>
#include <FirebaseESP32.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ArduinoJson.h>
#include <time.h>
// WiFi credentials
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// Firebase configuration
#define FIREBASE_HOST "https://dht22-bcd23-default-rtdb.firebaseio.com"
#define FIREBASE_AUTH "AIzaSyD4BRCQt5XLYbrAa1SwnX-ljSxkC0oKEGs"
// Indian Standard Time Configuration
#define IST_OFFSET_SECONDS 19800 // UTC+5:30 = 5.5 hours * 3600 seconds
#define IST_TIMEZONE_STRING "IST-5:30"
// Firebase objects
FirebaseData firebaseData;
FirebaseConfig config;
FirebaseAuth auth;
// NTP Client for time synchronization - using Indian NTP servers
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "time.nist.gov", 0, 60000); // No offset here, we'll handle IST manually
// Alternative NTP servers for better reliability in India
const char* ntpServers[] = {
"pool.ntp.org",
"time.nist.gov",
"time.google.com",
"0.in.pool.ntp.org", // Indian NTP server
"1.in.pool.ntp.org" // Indian NTP server
};
const int numNtpServers = sizeof(ntpServers) / sizeof(ntpServers[0]);
// LED pins
const int LED_PINS[] = {2, 4, 18, 19}; // LED 1, LED 2, LED 3, LED 4
const int NUM_LEDS = 4;
// Timing variables
unsigned long lastScheduleCheck = 0;
unsigned long lastFirebaseSync = 0;
unsigned long lastTimeUpdate = 0;
unsigned long lastSystemCheck = 0;
unsigned long lastTimeDisplay = 0;
const unsigned long SCHEDULE_CHECK_INTERVAL = 30000; // Check every 30 seconds
const unsigned long FIREBASE_SYNC_INTERVAL = 10000; // Sync every 10 seconds
const unsigned long TIME_UPDATE_INTERVAL = 1000; // Update time every second
const unsigned long SYSTEM_CHECK_INTERVAL = 5000; // Check system commands every 5 seconds
const unsigned long TIME_DISPLAY_INTERVAL = 1000; // Display time every second
// Structure to hold schedule data
struct Schedule {
String id;
int ledNumber;
String date;
String startTime;
String endTime;
bool isActive;
bool isScheduled;
};
// Global variables
std::vector<Schedule> schedules;
bool firebaseConnected = false;
bool ledStates[NUM_LEDS] = {false, false, false, false};
int currentNtpServerIndex = 0;
void setup() {
Serial.begin(115200);
Serial.println("ESP32 LED Scheduler Starting...");
Serial.println("Configured for Indian Standard Time (IST - UTC+5:30)");
// Initialize LED pins
for (int i = 0; i < NUM_LEDS; i++) {
pinMode(LED_PINS[i], OUTPUT);
digitalWrite(LED_PINS[i], LOW);
ledStates[i] = false;
Serial.println("LED " + String(i + 1) + " initialized on pin " + String(LED_PINS[i]));
}
// Connect to WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Initialize NTP client with IST timezone
initializeNTPClient();
// Configure system timezone for IST
configureTimezone();
// Configure Firebase
config.host = FIREBASE_HOST;
config.signer.tokens.legacy_token = FIREBASE_AUTH;
// Initialize Firebase
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
// Test Firebase connection
if (Firebase.ready()) {
Serial.println("Firebase connected successfully!");
firebaseConnected = true;
// Initialize Firebase structure
initializeFirebaseStructure();
// Load existing schedules
loadSchedulesFromAndroidApp();
} else {
Serial.println("Firebase connection failed!");
}
Serial.println("Setup complete. System ready!");
Serial.println("Current IST time: " + getCurrentDateTimeIST());
Serial.println("=====================================");
}
void initializeNTPClient() {
timeClient.begin();
// Don't set offset here - we'll handle IST conversion manually
Serial.print("Synchronizing time with NTP server");
int attempts = 0;
while (!timeClient.update() && attempts < 10) {
Serial.print(".");
delay(1000);
attempts++;
// Try different NTP servers if current one fails
if (attempts % 3 == 0 && attempts < 9) {
currentNtpServerIndex = (currentNtpServerIndex + 1) % numNtpServers;
timeClient.setPoolServerName(ntpServers[currentNtpServerIndex]);
Serial.print(" [Trying " + String(ntpServers[currentNtpServerIndex]) + "]");
}
}
if (timeClient.update()) {
Serial.println();
Serial.println("Time synchronized successfully!");
Serial.println("NTP Server: " + String(ntpServers[currentNtpServerIndex]));
Serial.println("UTC time: " + timeClient.getFormattedTime());
Serial.println("IST time: " + getCurrentTimeIST());
} else {
Serial.println();
Serial.println("Warning: Time synchronization failed. Using system time.");
}
}
void configureTimezone() {
// Configure system timezone for IST
setenv("TZ", IST_TIMEZONE_STRING, 1);
tzset();
Serial.println("System timezone configured for IST");
}
void loop() {
// Update time
timeClient.update();
// Display current time every second
if (millis() - lastTimeDisplay > TIME_DISPLAY_INTERVAL) {
Serial.println("Current IST: " + getCurrentDateTimeIST());
lastTimeDisplay = millis();
}
// Update current time in Firebase for Android app
if (millis() - lastTimeUpdate > TIME_UPDATE_INTERVAL) {
updateCurrentTimeInFirebase();
lastTimeUpdate = millis();
}
// Check Firebase connection and sync data
if (millis() - lastFirebaseSync > FIREBASE_SYNC_INTERVAL) {
if (Firebase.ready()) {
if (!firebaseConnected) {
Serial.println("Firebase reconnected!");
firebaseConnected = true;
}
syncWithFirebase();
} else {
if (firebaseConnected) {
Serial.println("Firebase connection lost!");
firebaseConnected = false;
}
}
lastFirebaseSync = millis();
}
// Check and execute schedules
if (millis() - lastScheduleCheck > SCHEDULE_CHECK_INTERVAL) {
checkAndExecuteSchedules();
lastScheduleCheck = millis();
}
// Check for system commands (including reset)
if (millis() - lastSystemCheck > SYSTEM_CHECK_INTERVAL) {
handleSystemReset();
lastSystemCheck = millis();
}
// Check for manual control commands
handleManualControl();
delay(100);
}
void initializeFirebaseStructure() {
// Initialize system status for Android app
Firebase.setBool(firebaseData, "/ledScheduler/systemRunning", true);
Firebase.setString(firebaseData, "/ledScheduler/lastUpdate", getCurrentDateTimeIST());
Firebase.setString(firebaseData, "/ledScheduler/timezone", "IST");
Firebase.setString(firebaseData, "/ledScheduler/timezoneOffset", "+05:30");
// Initialize device status and commands structure
Firebase.setString(firebaseData, "/device/status", "running");
Firebase.setString(firebaseData, "/device/lastBoot", getCurrentDateTimeIST());
Firebase.setString(firebaseData, "/device/timezone", "IST");
Firebase.setString(firebaseData, "/commands/system", "");
// Initialize LED status for Android app
for (int i = 0; i < NUM_LEDS; i++) {
String ledPath = "/ledScheduler/led" + String(i + 1);
Firebase.setBool(firebaseData, ledPath + "/state", false);
Firebase.setBool(firebaseData, ledPath + "/isScheduled", false);
Firebase.setBool(firebaseData, ledPath + "/isActive", false);
Firebase.setBool(firebaseData, ledPath + "/manualControl", false);
Firebase.setBool(firebaseData, ledPath + "/manualState", false);
}
Serial.println("Firebase structure initialized for Android app compatibility");
}
void updateCurrentTimeInFirebase() {
String currentDateTime = getCurrentDateTimeIST();
Firebase.setString(firebaseData, "/ledScheduler/currentDateTime", currentDateTime);
Firebase.setString(firebaseData, "/ledScheduler/currentTimeIST", currentDateTime);
Firebase.setString(firebaseData, "/device/lastSeen", currentDateTime);
}
void syncWithFirebase() {
// Update system status
Firebase.setBool(firebaseData, "/ledScheduler/systemRunning", true);
Firebase.setString(firebaseData, "/ledScheduler/lastUpdate", getCurrentDateTimeIST());
Firebase.setString(firebaseData, "/device/status", "running");
// Update LED states
for (int i = 0; i < NUM_LEDS; i++) {
String ledPath = "/ledScheduler/led" + String(i + 1);
Firebase.setBool(firebaseData, ledPath + "/state", ledStates[i]);
}
// Load schedules from Android app
loadSchedulesFromAndroidApp();
// Check for manual control commands
checkManualCommands();
}
void loadSchedulesFromAndroidApp() {
schedules.clear();
// Load schedules from Android app structure
for (int i = 1; i <= NUM_LEDS; i++) {
String ledPath = "/ledScheduler/led" + String(i);
// Check if LED has a schedule
if (Firebase.getBool(firebaseData, ledPath + "/isScheduled")) {
bool isScheduled = firebaseData.boolData();
if (isScheduled) {
Schedule schedule;
schedule.id = "led" + String(i);
schedule.ledNumber = i;
schedule.isScheduled = true;
// Get schedule data
if (Firebase.getString(firebaseData, ledPath + "/scheduledDate")) {
schedule.date = firebaseData.stringData();
}
if (Firebase.getString(firebaseData, ledPath + "/onTime")) {
schedule.startTime = firebaseData.stringData();
}
if (Firebase.getString(firebaseData, ledPath + "/offTime")) {
schedule.endTime = firebaseData.stringData();
}
if (Firebase.getBool(firebaseData, ledPath + "/isActive")) {
schedule.isActive = firebaseData.boolData();
}
// Only add valid schedules
if (!schedule.date.isEmpty() && !schedule.startTime.isEmpty() && !schedule.endTime.isEmpty()) {
schedules.push_back(schedule);
Serial.println("Loaded schedule for LED " + String(i) + ": " + schedule.date + " " + schedule.startTime + "-" + schedule.endTime + " (IST)");
}
}
}
}
Serial.println("Loaded " + String(schedules.size()) + " schedules from Android app");
}
void checkManualCommands() {
// Check for manual LED control commands from Android app
for (int i = 1; i <= NUM_LEDS; i++) {
String ledPath = "/ledScheduler/led" + String(i);
// Check if manual control is enabled
if (Firebase.getBool(firebaseData, ledPath + "/manualControl")) {
bool manualControl = firebaseData.boolData();
if (manualControl) {
// Get manual state
if (Firebase.getBool(firebaseData, ledPath + "/manualState")) {
bool manualState = firebaseData.boolData();
// Apply manual control
digitalWrite(LED_PINS[i-1], manualState ? HIGH : LOW);
ledStates[i-1] = manualState;
Serial.println("Manual control: LED " + String(i) + " turned " + (manualState ? "ON" : "OFF") + " at " + getCurrentTimeIST() + " IST");
// Update state in Firebase
Firebase.setBool(firebaseData, ledPath + "/state", manualState);
// Reset manual control flag
Firebase.setBool(firebaseData, ledPath + "/manualControl", false);
}
}
}
}
}
void handleManualControl() {
// Additional manual control logic can be added here
// Currently handled in checkManualCommands()
}
void handleSystemReset() {
if (Firebase.getString(firebaseData, "/commands/system")) {
String command = firebaseData.stringData();
if (command == "RESET") {
Serial.println("System reset command received at " + getCurrentDateTimeIST() + " IST");
// Update status before reset
Firebase.setString(firebaseData, "/device/status", "resetting");
Firebase.setString(firebaseData, "/device/lastReset", getCurrentDateTimeIST());
// Turn off all LEDs
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], LOW);
ledStates[i] = false;
// Update LED states in Firebase
String ledPath = "/ledScheduler/led" + String(i + 1);
Firebase.setBool(firebaseData, ledPath + "/state", false);
Firebase.setBool(firebaseData, ledPath + "/isActive", false);
}
// Clear the command
Firebase.setString(firebaseData, "/commands/system", "");
// Update final status
Firebase.setString(firebaseData, "/device/status", "reset");
Firebase.setBool(firebaseData, "/ledScheduler/systemRunning", false);
Serial.println("All LEDs turned off. Restarting system...");
delay(2000); // Give Firebase time to update
ESP.restart();
}
}
}
void checkAndExecuteSchedules() {
String currentDate = getCurrentDateIST();
String currentTime = getCurrentTimeIST();
Serial.println("Checking schedules - Current IST: " + currentDate + " " + currentTime);
for (auto& schedule : schedules) {
if (schedule.isScheduled && schedule.date == currentDate) {
int ledIndex = schedule.ledNumber - 1;
if (isCurrentTimeInSchedule(currentTime, schedule.startTime, schedule.endTime)) {
// Time to turn on LED
if (!ledStates[ledIndex]) {
digitalWrite(LED_PINS[ledIndex], HIGH);
ledStates[ledIndex] = true;
Serial.println("Schedule executed: LED " + String(schedule.ledNumber) + " turned ON at " + getCurrentDateTimeIST() + " IST");
// Update Firebase
String ledPath = "/ledScheduler/led" + String(schedule.ledNumber);
Firebase.setBool(firebaseData, ledPath + "/state", true);
Firebase.setBool(firebaseData, ledPath + "/isActive", true);
Firebase.setString(firebaseData, ledPath + "/lastActivated", getCurrentDateTimeIST());
}
} else if (isTimeAfter(currentTime, schedule.endTime)) {
// Time to turn off LED (schedule ended)
if (ledStates[ledIndex]) {
digitalWrite(LED_PINS[ledIndex], LOW);
ledStates[ledIndex] = false;
Serial.println("Schedule ended: LED " + String(schedule.ledNumber) + " turned OFF at " + getCurrentDateTimeIST() + " IST");
// Update Firebase
String ledPath = "/ledScheduler/led" + String(schedule.ledNumber);
Firebase.setBool(firebaseData, ledPath + "/state", false);
Firebase.setBool(firebaseData, ledPath + "/isActive", false);
Firebase.setBool(firebaseData, ledPath + "/isScheduled", false);
Firebase.setString(firebaseData, ledPath + "/lastCompleted", getCurrentDateTimeIST());
// Mark schedule as completed
schedule.isScheduled = false;
}
}
}
}
}
String getCurrentDateTimeIST() {
timeClient.update();
time_t epochTime = timeClient.getEpochTime();
// Add IST offset to UTC time (only once!)
epochTime += IST_OFFSET_SECONDS;
struct tm *ptm = gmtime((time_t *)&epochTime);
char dateTime[32];
sprintf(dateTime, "%04d-%02d-%02d %02d:%02d:%02d",
ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
return String(dateTime);
}
String getCurrentDateIST() {
timeClient.update();
time_t epochTime = timeClient.getEpochTime();
epochTime += IST_OFFSET_SECONDS;
struct tm *ptm = gmtime((time_t *)&epochTime);
char date[16];
sprintf(date, "%04d-%02d-%02d",
ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
return String(date);
}
String getCurrentTimeIST() {
timeClient.update();
time_t epochTime = timeClient.getEpochTime();
epochTime += IST_OFFSET_SECONDS;
struct tm *ptm = gmtime((time_t *)&epochTime);
char time[16];
sprintf(time, "%02d:%02d:%02d",
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
return String(time);
}
// Alternative method using system time (as backup)
String getSystemTimeIST() {
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char dateTime[32];
strftime(dateTime, sizeof(dateTime), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(dateTime);
}
bool isCurrentTimeInSchedule(String currentTime, String startTime, String endTime) {
int currentMinutes = timeToMinutes(currentTime);
int startMinutes = timeToMinutes(startTime);
int endMinutes = timeToMinutes(endTime);
// Handle cases where end time is on the next day
if (endMinutes < startMinutes) {
return (currentMinutes >= startMinutes || currentMinutes <= endMinutes);
}
return (currentMinutes >= startMinutes && currentMinutes <= endMinutes);
}
bool isTimeAfter(String currentTime, String compareTime) {
int currentMinutes = timeToMinutes(currentTime);
int compareMinutes = timeToMinutes(compareTime);
return currentMinutes > compareMinutes;
}
int timeToMinutes(String timeStr) {
int colonIndex = timeStr.indexOf(':');
if (colonIndex == -1) return 0;
int hours = timeStr.substring(0, colonIndex).toInt();
int minutes = timeStr.substring(colonIndex + 1).toInt();
return hours * 60 + minutes;
}