Yarana Iot Guru
Published © MIT

Home Automation with Timer & Manual Switch – Web + App Ready

Control your home lights & devices via Web + App with Timer & Manual Switch. Smart living made easy by YaranaIot Guru! πŸ”₯

BeginnerProtip8 hours33
Home Automation with Timer & Manual Switch – Web + App Ready

Things used in this project

Software apps and online services

Arduino Web Editor
Arduino Web Editor

Story

Read more

Code

πŸ› οΈ ESP32 Arduino code (complete example)

C/C++
Requirements / libraries:

[FirebaseESP32 library by Mobizt] (install via Library Manager)
ArduinoJson (for parsing schedule JSON)
<time.h> (built-in for NTP)
/* Home Automation with Timer & Manual Switch (ESP32)
   - uses Firebase Realtime DB to read device state & schedules
   - supports manual physical switch override
   - uses NTP for accurate time
   NOTE: Replace FIREBASE_HOST, FIREBASE_AUTH, WIFI credentials
*/

#include <WiFi.h>
#include <FirebaseESP32.h>
#include <ArduinoJson.h>
#include "time.h"

#define DEVICE_ID "device1"       // change per device (device1, device2..)
const int RELAY_PIN = 23;        // relay output pin
const int SWITCH_PIN = 34;       // manual switch input (use RTC IO or input only pins)
const unsigned long MANUAL_DURATION = 3600; // seconds manual override default (1 hour)

const char* WIFI_SSID = "Your_SSID";
const char* WIFI_PASS = "Your_PASS";
const char* FIREBASE_HOST = "your-project-id.firebaseio.com";
const char* FIREBASE_AUTH = "your_database_secret_or_token";

FirebaseData fbdo;

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);

  pinMode(SWITCH_PIN, INPUT_PULLUP);

  // WiFi connect
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print("Connecting WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected. IP: " + WiFi.localIP().toString());

  // NTP time
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  Serial.println("Waiting for time sync...");
  delay(2000);

  // Firebase init
  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  Firebase.reconnectWiFi(true);

  // Optional: push initial state in Firebase if missing
  if (!Firebase.getBool(fbdo, String("/devices/") + DEVICE_ID + "/state")) {
    // If node missing, initialize safe defaults
    Firebase.setInt(fbdo, String("/devices/") + DEVICE_ID + "/state", 0);
    Firebase.setString(fbdo, String("/devices/") + DEVICE_ID + "/mode", "AUTO");
    Firebase.setInt(fbdo, String("/devices/") + DEVICE_ID + "/manual_expires", 0);
  }
}

unsigned long lastFirebaseFetch = 0;
int appliedState = 0;

bool parseScheduleAndDecide(const String &jsonSchedules, int &desiredState) {
  // jsonSchedules expected to be an array of objects
  // We'll use ArduinoJson to parse
  const size_t capacity = 4*1024; // increase if many schedules
  DynamicJsonDocument doc(capacity);
  DeserializationError err = deserializeJson(doc, jsonSchedules);
  if (err) {
    Serial.println("No schedules or parse error");
    return false;
  }

  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) return false;
  now = mktime(&timeinfo);
  int weekday = timeinfo.tm_wday + 1; // tm_wday: 0=Sun -> convert to 1..7

  int currentMinutes = timeinfo.tm_hour*60 + timeinfo.tm_min;

  bool anyMatch = false;
  desiredState = 0; // default OFF

  for (JsonVariant s : doc.as<JsonArray>()) {
    if (!s["enabled"] || s["enabled"] == 0) continue;
    const char* on_str = s["on"];
    const char* off_str = s["off"];
    JsonArray days = s["days"].as<JsonArray>();
    bool dayOk = false;
    for (int d : days) if (d == weekday) { dayOk = true; break; }
    if (!dayOk) continue;

    // parse times HH:MM
    int on_h = 0, on_m = 0, off_h = 0, off_m = 0;
    if (sscanf(on_str, "%d:%d", &on_h, &on_m) != 2) continue;
    if (sscanf(off_str, "%d:%d", &off_h, &off_m) != 2) continue;
    int onMin = on_h*60 + on_m;
    int offMin = off_h*60 + off_m;

    // handle overnight schedules (e.g., 22:00 - 06:00)
    bool active = false;
    if (onMin <= offMin) {
      active = (currentMinutes >= onMin && currentMinutes < offMin);
    } else {
      // spans midnight
      active = (currentMinutes >= onMin || currentMinutes < offMin);
    }
    if (active) {
      desiredState = 1;
      anyMatch = true;
      break;
    }
  }

  return anyMatch;
}

void applyState(int st) {
  appliedState = st;
  digitalWrite(RELAY_PIN, st ? HIGH : LOW);
  // update state + last_updated to Firebase
  String base = String("/devices/") + DEVICE_ID;
  Firebase.setInt(fbdo, base + "/state", st);
  Firebase.setInt(fbdo, base + "/last_updated", (int)time(NULL));
}

void loop() {
  // 1) Check manual switch (debounced simple)
  static int lastButtonState = HIGH;
  static unsigned long lastDebounce = 0;
  int reading = digitalRead(SWITCH_PIN);

  if (reading != lastButtonState) lastDebounce = millis();
  if ((millis() - lastDebounce) > 50) {
    // stable
    if (reading == LOW && lastButtonState == HIGH) { // button pressed
      // Toggle manual state
      String base = String("/devices/") + DEVICE_ID;
      // read current mode/state
      String mode = "AUTO";
      if (Firebase.getString(fbdo, base + "/mode")) mode = fbdo.stringData();
      int currState = 0;
      if (Firebase.getInt(fbdo, base + "/state")) currState = fbdo.intData();

      int newManualState = currState ? 0 : 1;
      // Set manual mode with expiry
      int expiresAt = (int)time(NULL) + MANUAL_DURATION; // e.g., 1 hour
      Firebase.setString(fbdo, base + "/mode", "MANUAL");
      Firebase.setInt(fbdo, base + "/manual_state", newManualState);
      Firebase.setInt(fbdo, base + "/manual_expires", expiresAt);
      Firebase.setInt(fbdo, base + "/last_updated", (int)time(NULL));

      applyState(newManualState); // immediate effect
      Serial.printf("Manual toggle: %d (expires %d)\n", newManualState, expiresAt);
    }
  }
  lastButtonState = reading;

  // 2) Periodically fetch device & schedule (e.g., every 10 sec)
  if (millis() - lastFirebaseFetch > 10000) {
    lastFirebaseFetch = millis();
    String base = String("/devices/") + DEVICE_ID;

    // read device object fields
    String mode = "AUTO";
    int manual_state = 0;
    long manual_expires = 0;
    if (Firebase.getString(fbdo, base + "/mode")) mode = fbdo.stringData();
    if (Firebase.getInt(fbdo, base + "/manual_state")) manual_state = fbdo.intData();
    if (Firebase.getInt(fbdo, base + "/manual_expires")) manual_expires = fbdo.intData();

    // If manual and not expired => maintain manual
    time_t now = time(NULL);
    if (mode == "MANUAL" && manual_expires > now) {
      applyState(manual_state);
      Serial.println("Applying MANUAL state from Firebase");
    } else {
      // if expired => set mode to AUTO in DB (clean up)
      if (mode == "MANUAL" && manual_expires <= now) {
        Firebase.setString(fbdo, base + "/mode", "AUTO");
        Firebase.setInt(fbdo, base + "/manual_expires", 0);
      }

      // fetch schedules JSON
      if (Firebase.getString(fbdo, String("/schedules/") + DEVICE_ID)) {
        String schedulesJson = fbdo.stringData();
        int desired = 0;
        bool matched = parseScheduleAndDecide(schedulesJson, desired);
        applyState(desired);
        Serial.printf("Schedule applied: %d (matched=%d)\n", desired, matched);
      } else {
        // no schedules -> keep current or turn OFF safe
        // applyState(0);
      }
    }
  }
}

Credits

Yarana Iot Guru
29 projects β€’ 0 followers
Yarana Iot Guru Yarana IoT Guru: Arduino, ESP32, GSM, NodeMCU & more. Projects, Tutorials & App Development. Innovate with us!
Thanks to Yarana Iot Guru .

Comments