#include <WiFi.h>
#include <PubSubClient.h>
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>
// ---------- WiFi / MQTT ----------
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "test.mosquitto.org";
WiFiClient espClient;
PubSubClient client(espClient);
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ---------- Pins ----------
#define PIR_PIN 4
#define LDR_PIN 36 // ADC1_CH0 (VP)
#define TRIG_PIN 18
#define ECHO_PIN 19
#define RELAY_PIN 23
#define LED_RED 32
#define LED_GREEN 33
#define LED_BLUE 14
#define LED_WHITE 2 // NEW: extra white LED (wired to GPIO2)
#define BUZZER_PIN 13
// ---------- Actuators ----------
AccelStepper stepper(AccelStepper::FULL4WIRE, 27, 26, 25, 12);
// ---------- Tunables ----------
const uint16_t LDR_THRESHOLD = 500; // lower value = darker
const uint8_t DIST_THRESHOLD_CM = 20; // motion by distance
const uint16_t BLINK_MS = 500; // blue LED blink when PIR is HIGH
// ---------- State ----------
unsigned long lastMsg = 0;
unsigned long lastBlink = 0;
bool blueLedState = LOW;
// ---------- WiFi ----------
void setup_wifi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(250);
}
// ---------- MQTT callback (optional command control) ----------
void callback(char* topic, byte* payload, unsigned int length) {
// Example: topic "home/cmd/relay" payload "ON"/"OFF"
String t(topic);
String p;
for (unsigned int i = 0; i < length; i++) p += (char)payload[i];
if (t == "home/cmd/relay") {
digitalWrite(RELAY_PIN, (p == "ON") ? HIGH : LOW);
} else if (t == "home/cmd/stepper") {
if (p == "OPEN") stepper.moveTo(200);
if (p == "CLOSE") stepper.moveTo(0);
}
}
// ---------- MQTT reconnect ----------
void reconnect() {
while (!client.connected()) {
if (client.connect("ESP32Client")) {
client.subscribe("home/cmd/relay");
client.subscribe("home/cmd/stepper");
} else {
delay(2000);
}
}
}
// ---------- Distance (median of 3, timeout-protected) ----------
static float readDistanceOnce() {
digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Timeout 30ms (~5m max range) to avoid blocking forever
unsigned long dur = pulseIn(ECHO_PIN, HIGH, 30000UL);
if (dur == 0) return 9999.0; // no echo
return (dur * 0.034f * 0.5f);
}
float readDistance() {
float a = readDistanceOnce();
float b = readDistanceOnce();
float c = readDistanceOnce();
// median
if ((a <= b && b <= c) || (c <= b && b <= a)) return b;
if ((b <= a && a <= c) || (c <= a && a <= b)) return a;
return c;
}
void setup() {
// I/O directions
pinMode(PIR_PIN, INPUT);
pinMode(LDR_PIN, INPUT);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(RELAY_PIN, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(LED_WHITE, OUTPUT); // NEW: white LED
pinMode(BUZZER_PIN, OUTPUT);
// Safe defaults
digitalWrite(RELAY_PIN, LOW);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_BLUE, LOW);
digitalWrite(LED_WHITE, LOW); // start OFF
digitalWrite(BUZZER_PIN, LOW);
// LCD
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Home Automation");
// Stepper config
stepper.setMaxSpeed(1000);
stepper.setAcceleration(500);
// Network
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) reconnect();
client.loop();
const unsigned long now = millis();
// ---- Periodic sensing, display, and telemetry ----
if (now - lastMsg > 2000) {
lastMsg = now;
const int pirState = digitalRead(PIR_PIN);
const int ldrValue = analogRead(LDR_PIN);
const float distance = readDistance();
// White LED = "darkness indicator" (turns on when ambient is low)
digitalWrite(LED_WHITE, (ldrValue < LDR_THRESHOLD) ? HIGH : LOW);
// LCD status
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dist:");
lcd.print(distance, 0);
lcd.print("cm");
lcd.setCursor(0, 1);
lcd.print("Light:");
lcd.print(ldrValue);
// Rule: ACTIVE if near object OR motion OR dark
const bool active = (distance < DIST_THRESHOLD_CM) || (pirState == HIGH) || (ldrValue < LDR_THRESHOLD);
if (active) {
digitalWrite(RELAY_PIN, HIGH); // power appliance
digitalWrite(LED_RED, HIGH); // alert
digitalWrite(LED_GREEN, LOW);
digitalWrite(BUZZER_PIN, HIGH); // beep/alarm
stepper.moveTo(200); // e.g., open/advance
client.publish("home/status", "ACTIVE");
} else {
digitalWrite(RELAY_PIN, LOW);
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, HIGH); // safe/idle
digitalWrite(BUZZER_PIN, LOW);
stepper.moveTo(0); // close/home
client.publish("home/status", "IDLE");
}
// Telemetry
client.publish("home/distance", String(distance, 0).c_str());
client.publish("home/light", String(ldrValue).c_str());
client.publish("home/motion", pirState ? "DETECTED" : "NONE");
client.publish("home/dark", (ldrValue < LDR_THRESHOLD) ? "TRUE" : "FALSE");
}
// ---- Blue LED: blink while PIR detects motion (non-blocking) ----
if (digitalRead(PIR_PIN)) {
if (now - lastBlink >= BLINK_MS) {
lastBlink = now;
blueLedState = !blueLedState;
digitalWrite(LED_BLUE, blueLedState);
}
} else {
digitalWrite(LED_BLUE, LOW);
}
// ---- Stepper service (non-blocking) ----
stepper.run();
}