/*
* =================================================================
* Proyek Smart Home (MODIFIKASI UNTUK WOKWI)
* =================================================================
* * Versi: 5.5-Wokwi
* * Deskripsi:
* - Kode ini disesuaikan untuk berjalan di simulator Wokwi.
* - WiFiManager dihapus dan diganti dengan koneksi WiFi langsung
* ke SSID "Wokwi-GUEST".
* - Logika sensor, MQTT, dan API tetap sama.
*/
// --- Pustaka / Libraries ---
#include <WiFi.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
// --- Konfigurasi WiFi untuk Wokwi ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// --- Konfigurasi LCD I2C ---
LiquidCrystal_I2C lcd(0x27, 16, 2);
// --- Informasi HiveMQ Cloud ---
const char* mqttServer = "a8f644a5ed4e4b568556cf30eacf5b87.s1.eu.hivemq.cloud";
const int mqttPort = 8883;
const char* mqttUser = "hivemq.webclient.1761274344168";
const char* mqttPassword = "S;3Zb1FGa2*dchkL7><C";
// --- Konfigurasi API IQAir ---
String apiKey = "0597e0d1-7f0d-439c-aaea-f7dfcf9bbddd";
String selectedCity = "Jakarta";
String stateForUrl = "Jakarta";
// --- Pengaturan Pin Hardware ---
const int PIR_PIN = 27;
const int BUZZER_PIN = 26;
const int TRIG_PIN = 19;
const int ECHO_PIN = 18;
const int WINDOW_LOOP_PIN = 25;
const int DHT_PIN = 4;
const int RELAY_PIN = 32;
// --- Inisialisasi DHT ---
#define DHTTYPE DHT22
DHT dht(DHT_PIN, DHTTYPE);
// --- Variabel Global (Tidak Berubah) ---
int pirState = 0;
bool doorIntrusion = false;
bool windowLoopBroken = false;
bool alarmActive = false;
bool systemArmed = true;
int DOOR_THRESHOLD_CM = 100;
unsigned long alarmStartTime = 0;
const long alarmMinDuration = 5000;
unsigned long lastApiFetch = 0;
const long apiFetchInterval = 900000;
String aqiDisplay = "N/A";
String securityDisplay = "Aman";
unsigned long lastLcdUpdate = 0;
const long lcdUpdateInterval = 2000;
unsigned long lastDhtRead = 0;
const long dhtReadInterval = 10000;
bool lcdPageToggle = false;
String tempDisplay = "--.- C";
String humDisplay = "--.- %";
bool apiFetchRequest = false;
// --- Inisialisasi Klien ---
WiFiClientSecure espClient;
PubSubClient client(espClient);
// --- Deklarasi Fungsi ---
void fetchDataToMQTT(String kota, String state);
void callback(char* topic, byte* payload, unsigned int length);
void reconnect();
bool getUltrasonicState();
void checkSecurity();
void publishSystemStatus();
void updateLCD();
void readAndPublishDHT();
void publishLightStatus();
// ===================================
// FUNGSI 1: Mengambil Data API IQAir
// ===================================
void fetchDataToMQTT(String kota, String state) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("API Fetch: WiFi tidak terhubung.");
return;
}
HTTPClient http;
String cityForUrl = kota;
String stateForUrl = state;
String countryForUrl = "Indonesia";
cityForUrl.replace(" ", "%20");
stateForUrl.replace(" ", "%20");
String apiUrl = "https://api.airvisual.com/v2/city?city=" + cityForUrl + "&state=" + stateForUrl + "&country=" + countryForUrl + "&key=" + apiKey;
Serial.println("Mengambil data API (via HTTPS) untuk: " + kota + ", " + state);
http.begin(apiUrl);
int httpCode = http.GET();
if (httpCode > 0) {
String payload = http.getString();
DynamicJsonDocument doc(2048);
deserializeJson(doc, payload);
if (String(doc["status"]) == "success") {
int aqi = doc["data"]["current"]["pollution"]["aqius"];
Serial.println("AQI Diterima: " + String(aqi));
char aqiString[8];
dtostrf(aqi, 1, 0, aqiString);
client.publish("proyek/data/aqi", aqiString, true);
client.publish("proyek/data/kota", kota.c_str(), true);
aqiDisplay = String(aqi);
} else {
String apiError = doc["data"]["message"];
Serial.print("API IQAir merespons dengan gagal. Pesan: ");
Serial.println(apiError);
client.publish("proyek/data/aqi", "N/A", true);
client.publish("proyek/data/kota", (kota + " (Gagal)").c_str(), true);
aqiDisplay = "N/A";
}
} else {
Serial.print("Gagal mengambil data dari API IQAir, error HTTP: ");
Serial.println(httpCode);
aqiDisplay = "Err";
}
http.end();
}
// ===================================
// FUNGSI 2: Mengecek Sensor Keamanan
// ===================================
bool getUltrasonicState() {
long duration;
int distance;
digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
duration = pulseIn(ECHO_PIN, HIGH);
distance = duration * 0.0343 / 2;
return (distance < DOOR_THRESHOLD_CM && distance > 0);
}
void checkSecurity() {
pirState = digitalRead(PIR_PIN);
doorIntrusion = getUltrasonicState();
windowLoopBroken = (digitalRead(WINDOW_LOOP_PIN) == HIGH);
bool currentTrigger = ( (pirState == HIGH && doorIntrusion == true) || (windowLoopBroken == true) );
if (systemArmed) {
if (currentTrigger && !alarmActive) {
alarmActive = true;
alarmStartTime = millis();
String alertMessage = "Tipe tidak diketahui";
if (windowLoopBroken == true) {
alertMessage = "PENYUSUP: Jendela";
Serial.println("PERINGATAN: SEBUAH JENDELA DIBUKA!");
} else if (pirState == HIGH && doorIntrusion == true) {
alertMessage = "PENYUSUP: Pintu";
Serial.println("PERINGATAN: PENYUSUP TERDETEKSI! (PIR + Pintu)");
}
digitalWrite(BUZZER_PIN, HIGH);
client.publish("proyek/data/pir", alertMessage.c_str(), true);
securityDisplay = alertMessage;
} else if (!currentTrigger && alarmActive) {
if (millis() - alarmStartTime > alarmMinDuration) {
alarmActive = false;
Serial.println("Situasi aman.");
digitalWrite(BUZZER_PIN, LOW);
client.publish("proyek/data/pir", "Aman", true);
securityDisplay = "Aman";
}
} else if (currentTrigger && alarmActive) {
alarmStartTime = millis();
}
} else {
if (alarmActive) {
Serial.println("Sistem DISARMED, alarm dimatikan.");
client.publish("proyek/data/pir", "Aman (Disarmed)", true);
}
alarmActive = false;
digitalWrite(BUZZER_PIN, LOW);
if (securityDisplay != "DISARMED") {
securityDisplay = "DISARMED";
}
}
}
// ===================================
// FUNGSI 3: MQTT Callback (Logika Active LOW)
// ===================================
void callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Pesan diterima di topik [");
Serial.print(topic);
Serial.print("]: ");
Serial.println(message);
if (String(topic) == "proyek/perintah/kota") {
selectedCity = message;
Serial.println("Nama kota diatur ke: " + selectedCity);
}
else if (String(topic) == "proyek/perintah/state") {
stateForUrl = message;
Serial.println("Nama provinsi diatur ke: " + stateForUrl);
apiFetchRequest = true;
lcd.clear();
lcd.print("Kota Diubah:");
lcd.setCursor(0, 1);
lcd.print(selectedCity.substring(0, 16));
}
else if (String(topic) == "proyek/perintah/sistem") {
if (message == "ARMED") {
systemArmed = true;
Serial.println("SISTEM DI-ARM (DIAKTIFKAN)!");
securityDisplay = "ARMED";
} else if (message == "DISARMED") {
systemArmed = false;
Serial.println("SISTEM DI-DISARM (DINONAKTIFKAN)!");
securityDisplay = "DISARMED";
}
publishSystemStatus();
}
// --- Logika Relay Active LOW ---
else if (String(topic) == "proyek/perintah/lampu") {
if (message == "ON") {
digitalWrite(RELAY_PIN, LOW); // LOW = ON
Serial.println("Lampu Dinyalakan (Active LOW)");
} else if (message == "OFF") {
digitalWrite(RELAY_PIN, HIGH); // HIGH = OFF
Serial.println("Lampu Dimatikan (Active LOW)");
}
publishLightStatus();
}
}
// ===================================
// FUNGSI 4: MQTT Reconnect
// ===================================
void reconnect() {
while (!client.connected()) {
Serial.print("Mencoba koneksi ke MQTT Broker...");
lcd.clear();
lcd.print("MQTT Broker...");
lcd.setCursor(0, 1);
lcd.print("Menghubungkan..");
if (client.connect("ESP32_SmartHome_30C_Wokwi", mqttUser, mqttPassword)) {
Serial.println("terhubung!");
lcd.clear();
lcd.print("MQTT Terhubung!");
delay(1500);
client.subscribe("proyek/perintah/kota");
client.subscribe("proyek/perintah/state");
client.subscribe("proyek/perintah/sistem");
client.subscribe("proyek/perintah/lampu");
Serial.println("Subscribe ke '.../kota', '.../state', '.../sistem' & '.../lampu'");
apiFetchRequest = true;
client.publish("proyek/data/pir", "Aman", true);
publishSystemStatus();
publishLightStatus();
} else {
Serial.print("gagal, rc=");
Serial.print(client.state());
Serial.println(" coba lagi dalam 5 detik");
lcd.clear();
lcd.print("MQTT Gagal!");
lcd.setCursor(0, 1);
lcd.print("Coba lagi 5dtk");
delay(5000);
}
}
}
// ===================================
// FUNGSI 5: PUBLISH STATUS SISTEM
// ===================================
void publishSystemStatus() {
if (systemArmed) {
client.publish("proyek/status/sistem", "ARMED", true);
Serial.println("Mengirim status ke web: ARMED");
} else {
client.publish("proyek/status/sistem", "DISARMED", true);
Serial.println("Mengirim status ke web: DISARMED");
}
}
// ===================================
// FUNGSI 6: UPDATE LAYAR LCD
// ===================================
void updateLCD() {
lcd.clear();
if (lcdPageToggle) {
lcd.setCursor(0, 0);
lcd.print("Suhu: " + tempDisplay);
lcd.setCursor(0, 1);
lcd.print("Lembap: " + humDisplay);
} else {
lcd.setCursor(0, 0);
lcd.print(securityDisplay);
lcd.setCursor(0, 1);
lcd.print("AQI: " + aqiDisplay + " (" + selectedCity.substring(0, 9) + ")");
}
lcdPageToggle = !lcdPageToggle;
}
// ===================================
// SETUP (MODIFIKASI UNTUK WOKWI)
// ===================================
void setup() {
Serial.begin(115200);
Serial.println("\n\n--- MENJALANKAN KODE V5.5 (WOKWI VERSION) ---");
Serial.println("--- PIN RELAY DIATUR KE 32 ---");
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Sistem Booting..");
pinMode(PIR_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(WINDOW_LOOP_PIN, INPUT_PULLUP);
pinMode(RELAY_PIN, OUTPUT);
dht.begin();
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(RELAY_PIN, HIGH); // HIGH = OFF (Logika Active LOW)
Serial.println("RELAY PIN 32 DIATUR KE HIGH (PERINTAH MATI)");
delay(1000);
// --- KONEKSI WIFI LANGSUNG UNTUK WOKWI ---
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Menghubungkan ke WiFi...");
lcd.clear();
lcd.print("WiFi Wokwi...");
lcd.setCursor(0, 1);
lcd.print("Menghubungkan..");
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 30) {
delay(500);
Serial.print(".");
retries++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nBerhasil terhubung!");
Serial.print("Alamat IP: ");
Serial.println(WiFi.localIP());
lcd.clear();
lcd.print("WiFi Terhubung!");
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP().toString());
delay(2000);
espClient.setInsecure(); // Perlu untuk koneksi HTTPS di Wokwi
client.setServer(mqttServer, mqttPort);
client.setCallback(callback);
Serial.println("Sistem gabungan (Loop + API + DHT + Relay) SIAP!");
} else {
Serial.println("\nGagal terhubung ke WiFi.");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Setup Gagal!");
lcd.setCursor(0, 1);
lcd.print("Restart...");
delay(3000);
ESP.restart();
}
}
// ===================================
// LOOP
// ===================================
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Koneksi WiFi terputus! Restart...");
lcd.clear();
lcd.print("WiFi Terputus!");
lcd.setCursor(0, 1);
lcd.print("Restart...");
delay(3000);
ESP.restart();
}
if (!client.connected()) {
reconnect();
}
client.loop();
checkSecurity();
if (millis() - lastApiFetch > apiFetchInterval) {
lastApiFetch = millis();
Serial.println("Sudah 15 menit, ambil data API otomatis...");
fetchDataToMQTT(selectedCity, stateForUrl);
apiFetchRequest = false;
}
if (apiFetchRequest) {
apiFetchRequest = false;
Serial.println("Ambil data API manual (dari MQTT)...");
fetchDataToMQTT(selectedCity, stateForUrl);
lastApiFetch = millis();
}
if (millis() - lastLcdUpdate > lcdUpdateInterval) {
lastLcdUpdate = millis();
updateLCD();
}
if (millis() - lastDhtRead > dhtReadInterval) {
lastDhtRead = millis();
readAndPublishDHT();
}
}
// ===================================
// FUNGSI 7: BACA DHT22
// ===================================
void readAndPublishDHT() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("Gagal membaca data dari sensor DHT!");
return;
}
char tempString[8];
char humidString[8];
dtostrf(t, 4, 1, tempString);
dtostrf(h, 4, 1, humidString);
tempDisplay = String(tempString) + " C";
humDisplay = String(humidString) + " %";
Serial.print("Suhu: ");
Serial.print(tempDisplay);
Serial.print(", Kelembapan: ");
Serial.println(humDisplay);
client.publish("proyek/data/suhu", tempString, true);
client.publish("proyek/data/kelembapan", humidString, true);
}
// ===================================
// FUNGSI 8: PUBLISH STATUS LAMPU (Logika Active LOW)
// ===================================
void publishLightStatus() {
// --- Logika Active LOW ---
if (digitalRead(RELAY_PIN) == LOW) { // LOW = ON
client.publish("proyek/status/lampu", "ON", true);
Serial.println("Mengirim status lampu: ON");
} else { // HIGH = OFF
client.publish("proyek/status/lampu", "OFF", true);
Serial.println("Mengirim status lampu: OFF");
}
}