// ============================================
// MODUL 4 - Smart Home Monitoring System
// Platform: ESP32 | Simulator: Wokwi
// ============================================
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
#include "ThingSpeak.h"
// ---- PIN DEFINITIONS ----
#define DHT_PIN 4
#define LDR_PIN 34 // ADC1 CH6
#define PIR_PIN 15
#define BUZZER_PIN 14
#define LED_AC 26
#define LED_LAMP 27
#define LED_ALARM 25
#define DHT_TYPE DHT22
#define TRIG_PIN 5
#define ECHO_PIN 18
// ---- THRESHOLDS ----
#define TEMP_THRESHOLD 28.0 // AC aktif di atas suhu ini
#define LIGHT_THRESHOLD 300 // Lampu menyala di bawah nilai ini
// ---- NETWORK CONFIG ----
const char* SSID = "Wokwi-GUEST";
const char* PASSWORD = "";
const char* BROKER = "broker.hivemq.com";
const char* BASE_TOPIC = "smarthome/lab/2330205030059";
// ---- THINGSPEAK CONFIG ----
unsigned long MY_CHANNEL_NUMBER = 3336752;
const char* MY_WRITE_API_KEY = "DPEPR41IVYQ99L62";
// ---- OBJECTS ----
DHT dht(DHT_PIN, DHT_TYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
// ---- SYSTEM STATE ----
struct SmartHomeState {
float temperature = 0;
float humidity = 0;
int lightLevel = 0;
bool motionDetected = false;
bool acActive = false;
bool lampActive = false;
bool alarmActive = false;
bool awayMode = true;
float waterLevel = 0;
String lastCommand = "none";
} state;
unsigned long lastSensorRead = 0;
unsigned long lastMQTTPublish = 0;
unsigned long lastLCDUpdate = 0;
// ---- MQTT CALLBACK ----
void mqttCallback(char* topic, byte* payload, unsigned int len) {
String msg = "", t = String(topic);
for (int i = 0; i < len; i++) msg += (char)payload[i];
Serial.println("[IN] " + t + " : " + msg);
StaticJsonDocument<128> doc;
if (deserializeJson(doc, msg)) return;
if (t.endsWith("control")) {
if (doc.containsKey("ac")) state.acActive = (String(doc["ac"].as<String>()) == "on");
if (doc.containsKey("lamp")) state.lampActive = (String(doc["lamp"].as<String>()) == "on");
if (doc.containsKey("alarm")) state.alarmActive = (String(doc["alarm"].as<String>()) == "on");
if (doc.containsKey("mode")) state.awayMode = (String(doc["mode"].as<String>()) == "away");
state.lastCommand = msg;
}
}
// ---- SENSOR READING ----
void readSensors() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
state.temperature = t;
state.humidity = h;
}
state.lightLevel = analogRead(LDR_PIN);
state.motionDetected = digitalRead(PIR_PIN);
}
// ---- PEMANTAUAN LEVEL AIR (Tugas 1) ----
void readUltrasonic() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
float distance = duration * 0.034 / 2;
int level = map(distance, 2, 100, 100, 0);
if (level < 0) level = 0;
if (level > 100) level = 100;
state.waterLevel = level;
}
// ---- AUTOMATION LOGIC ----
void runAutomation() {
state.acActive = (state.temperature > TEMP_THRESHOLD);
state.lampActive = (state.lightLevel < LIGHT_THRESHOLD);
if (state.awayMode && state.motionDetected) {
state.alarmActive = true;
Serial.println("[SECURITY] Gerakan terdeteksi! ALARM AKTIF.");
}
// 4. Update Aktuator
digitalWrite(LED_AC, state.acActive ? HIGH : LOW);
digitalWrite(LED_LAMP, state.lampActive ? HIGH : LOW);
digitalWrite(LED_ALARM, state.alarmActive ? HIGH : LOW);
if (state.alarmActive) {
tone(BUZZER_PIN, 500);
} else {
noTone(BUZZER_PIN);
}
}
// ---- LCD DISPLAY ----
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("T:"); lcd.print(state.temperature, 1);
lcd.print("C W:"); lcd.print((int)state.waterLevel); lcd.print("%");
lcd.setCursor(0, 1);
lcd.print(state.acActive ? "AC:ON " : "AC:OFF ");
lcd.print(state.awayMode ? "[AWY]" : "[HOM]");
if(state.alarmActive) lcd.print(" !ALM!");
}
// ---- MQTT PUBLISH ----
void publishData() {
StaticJsonDocument<400> doc;
doc["temperature"] = state.temperature;
doc["humidity"] = state.humidity;
doc["light"] = state.lightLevel;
doc["motion"] = state.motionDetected;
doc["water"] = state.waterLevel;
doc["ac"] = state.acActive ? "on" : "off";
doc["lamp"] = state.lampActive ? "on" : "off";
doc["alarm"] = state.alarmActive ? "on" : "off";
doc["mode"] = state.awayMode ? "away" : "home";
char payload[400];
serializeJson(doc, payload);
String topic = String(BASE_TOPIC) + "status";
mqtt.publish(topic.c_str(), payload, true);
Serial.println("[MQTT] Published: " + String(payload));
ThingSpeak.setField(1, state.temperature);
ThingSpeak.setField(2, state.humidity);
ThingSpeak.setField(3, state.lightLevel);
ThingSpeak.setField(4, state.waterLevel);
int x = ThingSpeak.writeFields(MY_CHANNEL_NUMBER, MY_WRITE_API_KEY);
if(x == 200){
Serial.println("[ThingSpeak] Update Successful");
} else {
Serial.println("[ThingSpeak] Error: " + String(x));
}
}
// ---- SETUP ----
void setup() {
Serial.begin(115200);
dht.begin();
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(PIR_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_AC, OUTPUT);
pinMode(LED_LAMP, OUTPUT);
pinMode(LED_ALARM, OUTPUT);
lcd.init(); lcd.backlight();
lcd.setCursor(0,0); lcd.print("Smart Home IoT");
lcd.setCursor(0,1); lcd.print(" Booting... ");
// Connect WiFi
WiFi.begin(SSID, PASSWORD);
Serial.print("WiFi connecting");
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nIP: " + WiFi.localIP().toString());
ThingSpeak.begin(wifiClient);
// Setup MQTT
mqtt.setServer(BROKER, 1883);
mqtt.setCallback(mqttCallback);
lcd.clear();
lcd.setCursor(0,0); lcd.print("System Ready!");
delay(1000);
}
// ---- MAIN LOOP ----
void loop() {
if (!mqtt.connected()) {
if (mqtt.connect("SmartHome-ESP32")) {
mqtt.subscribe((String(BASE_TOPIC) + "control").c_str());
}
}
mqtt.loop();
unsigned long now = millis();
if (now - lastSensorRead > 2000) {
readSensors();
readUltrasonic();
runAutomation();
lastSensorRead = now;
}
if (now - lastLCDUpdate > 1000) {
updateLCD();
lastLCDUpdate = now;
}
if (now - lastMQTTPublish > 16000) {
publishData();
lastMQTTPublish = now;
}
}