#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define PIN_DHT 15
#define PIN_LDR 34
#define PIN_PIR 27
#define PIN_LED_LAMPU 25
#define PIN_LED_ALARM 26
#define PIN_BUZZER 33
#define PIN_RELAY 32
#define PIN_BUTTON 14
#define DHTTYPE DHT22
#define LDR_DARK_IS_HIGH true
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const int wifiChannel = 6;
const char* mqtt_server = "mqtt.thingsboard.cloud";
const int mqtt_port = 1883;
const char* mqtt_clid = "uts_iot";
const char* mqtt_user = "alfan";
const char* mqtt_pass = "passwordkuat123";
const char* TOPIC_TELEMETRY = "v1/devices/me/telemetry";
const char* TOPIC_ATTRIBUTE = "v1/devices/me/attributes";
const char* TOPIC_RPC_REQ = "v1/devices/me/rpc/request/+";
WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(PIN_DHT, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
float suhu = 0, hum = 0, heatIndex = 0;
int ldrRaw = 0, cahayaPct = 0;
bool gerak = false;
bool sensorError = false;
uint8_t dhtFail = 0;
float suhuMax = 32.0;
float humMax = 85.0;
int cahayaMin = 30;
bool modeAuto = true;
bool fanOn = false, lampOn = false;
bool fanManual = false, lampManual = false;
uint8_t alarmLevel = 0;
const char* alarmMsg = "NORMAL";
bool buzzerMuted = false;
bool eventPending = false;
unsigned long lastMotion = 0;
const unsigned long LAMP_HOLD_MS = 10000;
unsigned long tSensor = 0, tTelemetry = 0, tLcd = 0, tWifi = 0, tMqtt = 0;
const unsigned long SENSOR_INTERVAL = 2000;
const unsigned long TELEMETRY_INTERVAL = 5000;
const unsigned long LCD_INTERVAL = 1000;
void buzzerTone(int freq) {
if (freq > 0) tone(PIN_BUZZER, freq);
else noTone(PIN_BUZZER);
}
float r1(float x) { return round(x * 10.0) / 10.0; }
String buildTelemetryJson() {
String p = "{";
p += "\"suhu\":" + String(r1(suhu), 1) + ",";
p += "\"kelembaban\":" + String(r1(hum), 1) + ",";
p += "\"heat_index\":" + String(r1(heatIndex), 1) + ",";
p += "\"cahaya\":" + String(cahayaPct) + ",";
p += "\"gerakan\":" + String(gerak ? "true" : "false") + ",";
p += "\"kipas\":" + String(fanOn ? "true" : "false") + ",";
p += "\"lampu\":" + String(lampOn ? "true" : "false") + ",";
p += "\"mode\":\"" + String(modeAuto ? "AUTO" : "MANUAL") + "\",";
p += "\"alarm_level\":" + String(alarmLevel) + ",";
p += "\"alarm_msg\":\"" + String(alarmMsg) + "\"";
p += "}";
return p;
}
void publishTelemetry() {
if (!client.connected()) return;
String payload = buildTelemetryJson();
bool ok = client.publish(TOPIC_TELEMETRY, payload.c_str());
Serial.print("[MQTT] Telemetry "); Serial.print(ok ? "OK: " : "GAGAL: "); Serial.println(payload);
}
void publishAttributes() {
if (!client.connected()) return;
String p = "{";
p += "\"firmware\":\"1.0.0\",";
p += "\"board\":\"ESP32 DevKit\",";
p += "\"ip\":\"" + WiFi.localIP().toString() + "\",";
p += "\"suhuMax\":" + String(suhuMax, 1) + ",";
p += "\"kelembabanMax\":" + String(humMax, 1) + ",";
p += "\"cahayaMin\":" + String(cahayaMin) + ",";
p += "\"mode\":\"" + String(modeAuto ? "AUTO" : "MANUAL") + "\"";
p += "}";
client.publish(TOPIC_ATTRIBUTE, p.c_str());
}
void setMode(bool autoMode) {
modeAuto = autoMode;
if (!modeAuto) { fanManual = fanOn; lampManual = lampOn; }
Serial.print("[MODE] "); Serial.println(modeAuto ? "AUTO" : "MANUAL");
publishAttributes();
eventPending = true;
}
void updateControl() {
unsigned long now = millis();
if (gerak) lastMotion = now;
if (modeAuto) {
if (sensorError) fanOn = true;
else if (suhu >= suhuMax) fanOn = true;
else if (suhu <= suhuMax - 1) fanOn = false;
bool gelap = cahayaPct < cahayaMin;
lampOn = gelap && lastMotion != 0 && (now - lastMotion < LAMP_HOLD_MS);
} else {
fanOn = fanManual;
lampOn = lampManual;
}
digitalWrite(PIN_RELAY, fanOn ? HIGH : LOW);
digitalWrite(PIN_LED_LAMPU, lampOn ? HIGH : LOW);
}
void updateAlarm() {
uint8_t lvl = 0;
const char* msg = "NORMAL";
if (sensorError) { lvl = 2; msg = "KRITIS: DHT Error"; }
else if (suhu >= suhuMax + 5) { lvl = 2; msg = "KRITIS: Suhu Tinggi"; }
else if (suhu > suhuMax) { lvl = 1; msg = "WARN: Suhu Tinggi"; }
else if (hum > humMax) { lvl = 1; msg = "WARN: Hum Tinggi"; }
if (lvl != alarmLevel) {
alarmLevel = lvl;
alarmMsg = msg;
if (lvl == 0) buzzerMuted = false;
eventPending = true;
Serial.print("[ALARM] "); Serial.println(msg);
} else {
alarmMsg = msg;
}
}
void updateOutputs() {
unsigned long now = millis();
if (alarmLevel == 0) digitalWrite(PIN_LED_ALARM, LOW);
else if (alarmLevel == 1) digitalWrite(PIN_LED_ALARM, (now / 500) % 2);
else digitalWrite(PIN_LED_ALARM, (now / 150) % 2);
if (alarmLevel == 2 && !buzzerMuted) buzzerTone(((now / 250) % 2) ? 2000 : 0);
else buzzerTone(0);
}
void handleButton() {
static bool lastState = HIGH;
static unsigned long pressT = 0;
static bool longDone = false;
bool s = digitalRead(PIN_BUTTON);
unsigned long now = millis();
if (lastState == HIGH && s == LOW) { pressT = now; longDone = false; }
if (s == LOW && !longDone && now - pressT >= 1500) {
longDone = true;
setMode(!modeAuto);
}
if (lastState == LOW && s == HIGH && !longDone && now - pressT >= 40) {
buzzerMuted = true;
Serial.println("[BUTTON] Alarm Muted");
}
lastState = s;
}
void readSensors() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
if (dhtFail < 255) dhtFail++;
if (dhtFail >= 3) sensorError = true;
} else {
dhtFail = 0; sensorError = false;
suhu = t; hum = h;
heatIndex = dht.computeHeatIndex(t, h, false);
}
long sum = 0;
for (int i = 0; i < 8; i++) sum += analogRead(PIN_LDR);
ldrRaw = sum / 8;
cahayaPct = map(ldrRaw, 0, 4095, 0, 100);
if (LDR_DARK_IS_HIGH) cahayaPct = 100 - cahayaPct;
cahayaPct = constrain(cahayaPct, 0, 100);
gerak = digitalRead(PIN_PIR);
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String t(topic);
String p = "";
for (int i = 0; i < length; i++) p += (char)payload[i];
p.toLowerCase();
if (t.startsWith("v1/devices/me/rpc/request/")) {
String reqId = t.substring(26);
String respTopic = "v1/devices/me/rpc/response/" + reqId;
if (p.indexOf("getstatus") >= 0 || p.indexOf("getvalue") >= 0) {
client.publish(respTopic.c_str(), buildTelemetryJson().c_str());
} else if (p.indexOf("setfan") >= 0 || p.indexOf("setvalue") >= 0) {
if (!modeAuto) {
fanManual = (p.indexOf("true") >= 0 || p.indexOf("1") >= 0 || p.indexOf("on") >= 0);
updateControl(); eventPending = true;
client.publish(respTopic.c_str(), "{\"ok\":true}");
} else {
client.publish(respTopic.c_str(), "{\"ok\":false,\"msg\":\"Mode is AUTO\"}");
}
} else if (p.indexOf("setlamp") >= 0) {
if (!modeAuto) {
lampManual = (p.indexOf("true") >= 0 || p.indexOf("1") >= 0 || p.indexOf("on") >= 0);
updateControl(); eventPending = true;
client.publish(respTopic.c_str(), "{\"ok\":true}");
} else {
client.publish(respTopic.c_str(), "{\"ok\":false,\"msg\":\"Mode is AUTO\"}");
}
} else if (p.indexOf("setmode") >= 0) {
bool targetAuto = (p.indexOf("auto") >= 0 || p.indexOf("true") >= 0);
setMode(targetAuto);
client.publish(respTopic.c_str(), "{\"ok\":true}");
} else if (p.indexOf("mutebuzzer") >= 0) {
buzzerMuted = true;
client.publish(respTopic.c_str(), "{\"ok\":true}");
} else if (p.indexOf("reboot") >= 0) {
client.publish(respTopic.c_str(), "{\"ok\":true}");
delay(500); ESP.restart();
}
}
}
void wifiMaintain() {
if (WiFi.status() == WL_CONNECTED) return;
if (millis() - tWifi < 10000) return;
tWifi = millis();
WiFi.disconnect();
WiFi.begin(ssid, password, wifiChannel);
}
void mqttMaintain() {
if (client.connected()) { client.loop(); return; }
if (WiFi.status() != WL_CONNECTED) return;
if (millis() - tMqtt < 5000) return;
tMqtt = millis();
if (client.connect(mqtt_clid, mqtt_user, mqtt_pass)) {
Serial.println("[MQTT] Connected!");
client.subscribe(TOPIC_RPC_REQ);
client.subscribe(TOPIC_ATTRIBUTE);
publishAttributes();
}
}
void drawLcd() {
lcd.setCursor(0, 0);
lcd.print("T:"); lcd.print(suhu, 1);
lcd.print("C H:"); lcd.print(hum, 0);
lcd.print("% "); lcd.print(modeAuto ? "A" : "M");
lcd.setCursor(0, 1);
if (alarmLevel > 0) {
lcd.print("ALM:"); lcd.print(alarmMsg);
} else {
lcd.print("F:"); lcd.print(fanOn ? "1" : "0");
lcd.print(" L:"); lcd.print(lampOn ? "1" : "0");
lcd.print(" Lux:"); lcd.print(cahayaPct);
lcd.print("% ");
}
}
void setup() {
Serial.begin(115200);
pinMode(PIN_PIR, INPUT);
pinMode(PIN_BUTTON, INPUT_PULLUP);
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_LED_LAMPU, OUTPUT);
pinMode(PIN_LED_ALARM, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("IoT UTS Station");
lcd.setCursor(0, 1);
lcd.print("Connecting...");
dht.begin();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password, wifiChannel);
client.setServer(mqtt_server, mqtt_port);
client.setCallback(mqttCallback);
readSensors();
updateAlarm();
updateControl();
delay(1000);
lcd.clear();
}
void loop() {
unsigned long now = millis();
wifiMaintain();
mqttMaintain();
handleButton();
if (now - tSensor >= SENSOR_INTERVAL) {
tSensor = now;
readSensors();
updateAlarm();
updateControl();
}
updateOutputs();
if (now - tTelemetry >= TELEMETRY_INTERVAL || eventPending) {
tTelemetry = now;
eventPending = false;
publishTelemetry();
}
if (now - tLcd >= LCD_INTERVAL) {
tLcd = now;
drawLcd();
}
}