#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <WiFi.h>
#include "DHTesp.h"
// ==================== PIN CONFIGURATION ====================
const int DHT_PIN = 9;
const int LED_PIN = 2;
const int BUZZER_PIN = 19;
const int BTN_EMERGENCY = 23; // Tombol Emergency
const int BTN_JENDELA = 22; // Sensor Jendela (J1)
const int BTN_PINTU = 21; // Sensor Pintu (P1)
// ==================== WIFI & MQTT CONFIG ====================
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
// MQTT Topics
const char* TOPIC_STATUS = "home/alpha/indoor-1/status"; // LWT Status
const char* TOPIC_TELEMETRY = "home/alpha/indoor-1/telemetry"; // Sensor data
const char* TOPIC_ALARM = "home/alpha/indoor-1/alarm"; // Alarm status
const char* TOPIC_CMD_BUZZER = "home/alpha/indoor-1/cmd/buzzer"; // Command buzzer
const char* TOPIC_CMD_ARMED = "home/alpha/indoor-1/cmd/armed"; // Arm/Disarm system
// Client ID
String clientId = "ESP32-SecurityHome-" + String((uint32_t)ESP.getEfuseMac(), HEX);
// ==================== GLOBAL OBJECTS ====================
DHTesp dhtSensor;
WiFiClient espClient;
PubSubClient client(espClient);
// ==================== TIMING VARIABLES ====================
unsigned long lastTelemetry = 0;
unsigned long lastHeartbeat = 0;
unsigned long lastReconnect = 0;
const unsigned long TELEMETRY_INTERVAL = 2000; // 2 detik
const unsigned long HEARTBEAT_INTERVAL = 15000; // 15 detik
const unsigned long RECONNECT_INTERVAL = 5000; // 5 detik
// ==================== SYSTEM STATE ====================
bool systemArmed = false; // Status sistem armed/disarmed
bool alarmActive = false; // Status alarm aktif
bool buzzerState = false; // Status buzzer
// ==================== FORWARD DECLARATIONS ====================
void publishAlarmStatus(String status);
void triggerAlarm(String reason);
void reconnect();
void callback(char* topic, byte* payload, unsigned int length);
// ==================== BUTTON DEBOUNCE CLASS ====================
class Button {
private:
int pin;
int state;
int lastState;
int counter;
unsigned long lastTime;
const int debounceDelay = 10;
const int debounceCount = 10;
public:
Button(int p) : pin(p), state(HIGH), lastState(HIGH), counter(0), lastTime(0) {
pinMode(pin, INPUT_PULLUP);
}
bool isPressed() {
unsigned long currentTime = millis();
if (currentTime - lastTime > debounceDelay) {
lastTime = currentTime;
int reading = digitalRead(pin);
if (reading != lastState) {
counter++;
if (counter > debounceCount) {
if (reading != state) {
state = reading;
lastState = reading;
counter = 0;
// Return true only on press (transition to LOW)
return (state == LOW);
}
}
} else {
counter = 0;
}
lastState = reading;
}
return false;
}
bool getState() {
return (digitalRead(pin) == LOW);
}
};
// Initialize buttons
Button btnEmergency(BTN_EMERGENCY);
Button btnJendela(BTN_JENDELA);
Button btnPintu(BTN_PINTU);
// ==================== WIFI SETUP ====================
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to WiFi: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nā WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Signal strength (RSSI): ");
Serial.println(WiFi.RSSI());
} else {
Serial.println("\nā WiFi connection failed!");
}
}
// ==================== PUBLISH ALARM STATUS ====================
void publishAlarmStatus(String status) {
if (!client.connected()) return;
JsonDocument doc;
doc["status"] = status;
doc["armed"] = systemArmed;
doc["alarm_active"] = alarmActive;
doc["timestamp"] = millis();
char buffer[128];
serializeJson(doc, buffer);
client.publish(TOPIC_ALARM, buffer, true); // Retained
Serial.print("š Alarm Status: ");
Serial.println(buffer);
}
// ==================== TRIGGER ALARM ====================
void triggerAlarm(String reason) {
if (!systemArmed) return; // Jangan trigger jika tidak armed
if (!alarmActive) {
alarmActive = true;
buzzerState = true;
// digitalWrite(BUZZER_PIN, HIGH);
tone(BUZZER_PIN, 262); // Plays 262Hz tone for 0.250 seconds
Serial.print("šØ ALARM TRIGGERED: ");
Serial.println(reason);
// Publish alarm event
JsonDocument doc;
doc["event"] = "alarm_triggered";
doc["reason"] = reason;
doc["timestamp"] = millis();
char buffer[128];
serializeJson(doc, buffer);
client.publish(TOPIC_ALARM, buffer);
}
}
// ==================== MQTT CALLBACK ====================
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("šØ Message [");
Serial.print(topic);
Serial.print("]: ");
String message;
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// Handle buzzer command
if (String(topic) == TOPIC_CMD_BUZZER) {
if (message == "ON" || message == "1") {
buzzerState = true;
digitalWrite(BUZZER_PIN, HIGH);
tone(BUZZER_PIN, 262); // Plays 262Hz tone for 0.250 seconds
Serial.println("š Buzzer ON");
} else if (message == "OFF" || message == "0") {
buzzerState = false;
digitalWrite(BUZZER_PIN, LOW);
noTone(BUZZER_PIN);
alarmActive = false;
Serial.println("š Buzzer OFF");
}
}
// Handle armed/disarmed command
if (String(topic) == TOPIC_CMD_ARMED) {
if (message == "ARM" || message == "1") {
systemArmed = true;
digitalWrite(LED_PIN, HIGH); // LED ON when armed
Serial.println("š”ļø System ARMED");
publishAlarmStatus("armed");
} else if (message == "DISARM" || message == "0") {
systemArmed = false;
alarmActive = false;
digitalWrite(LED_PIN, LOW); // LED OFF when disarmed
// digitalWrite(BUZZER_PIN, LOW);
noTone(BUZZER_PIN);
buzzerState = false;
Serial.println("š System DISARMED");
publishAlarmStatus("disarmed");
}
}
}
// ==================== MQTT RECONNECT (NON-BLOCKING with LWT) ====================
void reconnect() {
unsigned long now = millis();
// Non-blocking: retry setiap 5 detik
if (now - lastReconnect < RECONNECT_INTERVAL) {
return;
}
lastReconnect = now;
if (!client.connected()) {
Serial.print("ā” Attempting MQTT connection... ");
// Connect dengan LWT (Last Will Testament)
if (client.connect(clientId.c_str(),
TOPIC_STATUS, // LWT topic
1, // LWT QoS
true, // LWT retain
"offline")) { // LWT message
Serial.println("ā Connected!");
// Publish online status (retained)
client.publish(TOPIC_STATUS, "online", true);
// Subscribe to command topics
client.subscribe(TOPIC_CMD_BUZZER);
client.subscribe(TOPIC_CMD_ARMED);
// Publish initial state
publishAlarmStatus(systemArmed ? "armed" : "disarmed");
Serial.println("š” MQTT fully initialized");
} else {
Serial.print("ā failed, rc=");
Serial.print(client.state());
Serial.println(" - retry in 5s");
}
}
}
// ==================== PUBLISH TELEMETRY ====================
void publishTelemetry() {
TempAndHumidity data = dhtSensor.getTempAndHumidity();
// Read sensor states
bool emergency = btnEmergency.getState();
bool jendela = btnJendela.getState();
bool pintu = btnPintu.getState();
// Create JSON payload
JsonDocument doc;
doc["temperature"] = round(data.temperature * 10) / 10.0;
doc["humidity"] = round(data.humidity * 10) / 10.0;
doc["emergency"] = emergency;
doc["window"] = jendela;
doc["door"] = pintu;
doc["armed"] = systemArmed;
doc["alarm"] = alarmActive;
doc["rssi"] = WiFi.RSSI();
doc["uptime"] = millis() / 1000;
char buffer[256];
serializeJson(doc, buffer);
// Publish
if (client.publish(TOPIC_TELEMETRY, buffer)) {
Serial.print("š¤ Telemetry: ");
Serial.println(buffer);
}
}
// ==================== PUBLISH HEARTBEAT ====================
void publishHeartbeat() {
JsonDocument doc;
doc["type"] = "heartbeat";
doc["uptime"] = millis() / 1000;
doc["free_heap"] = ESP.getFreeHeap();
doc["rssi"] = WiFi.RSSI();
doc["ip"] = WiFi.localIP().toString();
char buffer[128];
serializeJson(doc, buffer);
client.publish(TOPIC_STATUS, buffer);
Serial.println("š Heartbeat sent");
}
// ==================== SETUP ====================
void setup() {
// Initialize pins
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // LED OFF initially
digitalWrite(BUZZER_PIN, LOW); // Buzzer OFF initially
// Initialize serial
Serial.begin(115200);
delay(100);
Serial.println("\n\n=================================");
Serial.println("š Security Home System v2.0");
Serial.println("=================================");
// Initialize DHT sensor
dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
// Connect WiFi
setup_wifi();
// Setup MQTT
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
client.setBufferSize(512);
Serial.println("ā Setup complete!\n");
}
// ==================== MAIN LOOP ====================
void loop() {
unsigned long now = millis();
// Maintain MQTT connection (non-blocking)
if (!client.connected()) {
reconnect();
} else {
client.loop();
}
// Check buttons for security events
if (btnEmergency.isPressed()) {
Serial.println("šØ EMERGENCY BUTTON PRESSED!");
triggerAlarm("emergency_button");
}
if (btnJendela.isPressed() && systemArmed) {
Serial.println("šŖ WINDOW OPENED!");
triggerAlarm("window_opened");
}
if (btnPintu.isPressed() && systemArmed) {
Serial.println("šŖ DOOR OPENED!");
triggerAlarm("door_opened");
}
// Publish telemetry every 2 seconds
if (now - lastTelemetry >= TELEMETRY_INTERVAL) {
lastTelemetry = now;
if (client.connected()) {
publishTelemetry();
}
}
// Publish heartbeat every 15 seconds
if (now - lastHeartbeat >= HEARTBEAT_INTERVAL) {
lastHeartbeat = now;
if (client.connected()) {
publishHeartbeat();
}
}
// Auto-stop alarm after 30 seconds if still active
static unsigned long alarmStartTime = 0;
if (alarmActive) {
if (alarmStartTime == 0) {
alarmStartTime = now;
}
if (now - alarmStartTime > 30000) { // 30 detik
alarmActive = false;
buzzerState = false;
// digitalWrite(BUZZER_PIN, LOW);
noTone(BUZZER_PIN);
alarmStartTime = 0;
Serial.println("ā° Alarm auto-stopped after 30s");
}
} else {
alarmStartTime = 0;
}
}