432 lines
13 KiB
C++
432 lines
13 KiB
C++
#include <Wire.h>
|
|
#include <Adafruit_AHTX0.h>
|
|
#include <WiFi.h>
|
|
#include <HTTPClient.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include <ArduinoJson.h>
|
|
#include <FirebaseESP32.h>
|
|
|
|
#define SDA1 21
|
|
#define SCL1 22
|
|
#define SDA2 16
|
|
#define SCL2 17
|
|
|
|
#define RELAY_KIPAS 27
|
|
#define RELAY_POMPA 26
|
|
|
|
const char* ssid = "Oppo12";
|
|
const char* password = "12345678";
|
|
|
|
#define BOT_TOKEN "8628991486:AAEWwreXf8lXAFrb0fokQcrS_pqY6-C19_o"
|
|
#define CHAT_ID "6963743941"
|
|
|
|
#define FIREBASE_HOST "database-ta-cbb47-default-rtdb.asia-southeast1.firebasedatabase.app"
|
|
#define FIREBASE_AUTH "hCpBMPxOKsHLVv5Unyfd5YikGtFy11Z01Olx7477"
|
|
|
|
#define SUHU_ON 28.0
|
|
#define SUHU_OFF 27.5
|
|
#define RH_ON 80.0
|
|
#define RH_OFF 90.0
|
|
|
|
#define MS_BACA 2000
|
|
#define MS_PERINTAH 1000
|
|
#define MS_UPDATE 30000
|
|
#define MS_FIREBASE 25000
|
|
#define MS_KIRIM 3000
|
|
|
|
#define QUEUE_SIZE 5
|
|
String msgQueue[QUEUE_SIZE];
|
|
int qHead=0, qTail=0, qCount=0;
|
|
|
|
void queuePush(String msg) {
|
|
if (qCount >= QUEUE_SIZE) { qHead=(qHead+1)%QUEUE_SIZE; qCount--; }
|
|
msgQueue[qTail] = msg;
|
|
qTail = (qTail+1) % QUEUE_SIZE;
|
|
qCount++;
|
|
Serial.printf("[QUEUE] Antrian: %d\n", qCount);
|
|
}
|
|
bool queuePop(String &msg) {
|
|
if (qCount==0) return false;
|
|
msg = msgQueue[qHead];
|
|
qHead = (qHead+1) % QUEUE_SIZE;
|
|
qCount--;
|
|
return true;
|
|
}
|
|
|
|
FirebaseData fbData;
|
|
FirebaseConfig fbConfig;
|
|
FirebaseAuth fbAuth;
|
|
|
|
Adafruit_AHTX0 sensor1;
|
|
Adafruit_AHTX0 sensor2;
|
|
|
|
bool kipasState=false, pompaState=false;
|
|
bool prevKipas=false, prevPompa=false;
|
|
bool modeManual=false;
|
|
bool sensor1_ok=false, sensor2_ok=false;
|
|
float suhu1=0,rh1=0,suhu2=0,rh2=0,avgSuhu=0,avgRH=0;
|
|
unsigned long lastBaca=0,lastPerintah=0,lastUpdate=0;
|
|
unsigned long lastFirebase=0,lastKirim=0,dataCount=0;
|
|
long lastMsgId = 0;
|
|
|
|
|
|
void setKipas(bool nyala) {
|
|
kipasState = nyala;
|
|
digitalWrite(RELAY_KIPAS, nyala ? LOW : HIGH);
|
|
Serial.printf("[RELAY] Kipas %s (GPIO%d)\n", nyala?"ON":"OFF", RELAY_KIPAS);
|
|
}
|
|
void setPompa(bool nyala) {
|
|
pompaState = nyala;
|
|
digitalWrite(RELAY_POMPA, nyala ? LOW : HIGH);
|
|
Serial.printf("[RELAY] Pompa %s (GPIO%d)\n", nyala?"ON":"OFF", RELAY_POMPA);
|
|
}
|
|
|
|
|
|
String getKondisi() {
|
|
if (avgSuhu>SUHU_ON && avgRH<RH_ON) return "Suhu Tinggi dan RH Rendah";
|
|
if (avgSuhu>SUHU_ON) return "Suhu Tinggi";
|
|
if (avgRH<RH_ON) return "RH Rendah";
|
|
return "Normal";
|
|
}
|
|
|
|
String formatStatus() {
|
|
String msg = "";
|
|
msg += "\xF0\x9F\x93\xA1 Update Otomatis\n\n";
|
|
msg += "\xF0\x9F\x8C\xA1 Sensor 1\n";
|
|
msg += "Suhu : " + String(suhu1,1) + " C\n";
|
|
msg += "RH : " + String(rh1,1) + " %\n\n";
|
|
msg += "\xF0\x9F\x8C\xA1 Sensor 2\n";
|
|
msg += "Suhu : " + String(suhu2,1) + " C\n";
|
|
msg += "RH : " + String(rh2,1) + " %\n\n";
|
|
msg += "\xF0\x9F\x93\x8A Rata-rata\n";
|
|
msg += "Suhu : " + String(avgSuhu,1) + " C\n";
|
|
msg += "RH : " + String(avgRH,1) + " %\n\n";
|
|
msg += "\xE2\x9A\x99 Status Relay\n";
|
|
msg += "\xF0\x9F\x8C\x80 Kipas : " + String(kipasState?"ON \xE2\x9C\x85":"OFF \xE2\x9D\x8C") + "\n";
|
|
msg += "\xF0\x9F\x92\xA7 Pompa : " + String(pompaState?"ON \xE2\x9C\x85":"OFF \xE2\x9D\x8C") + "\n";
|
|
return msg;
|
|
}
|
|
|
|
String formatHelp() {
|
|
String msg = "Daftar Perintah Bot\n";
|
|
msg += "-------------------\n";
|
|
msg += "/status Data sensor dan relay\n";
|
|
msg += "/kipason Kipas DC ON\n";
|
|
msg += "/kipasoff Kipas DC OFF\n";
|
|
msg += "/pompaon Pompa Air ON\n";
|
|
msg += "/pompaoff Pompa Air OFF\n";
|
|
msg += "/auto Mode otomatis\n";
|
|
msg += "/help Daftar perintah\n\n";
|
|
msg += "Batas kendali otomatis\n";
|
|
msg += "Kipas ON Suhu lebih dari " + String(SUHU_ON,1) + " C\n";
|
|
msg += "Kipas OFF Suhu kurang " + String(SUHU_OFF,1) + " C\n";
|
|
msg += "Pompa ON RH kurang dari " + String(RH_ON,1) + " persen\n";
|
|
msg += "Pompa OFF RH lebih dari " + String(RH_OFF,1) + " persen";
|
|
return msg;
|
|
}
|
|
|
|
|
|
bool kirimTelegram(String pesan) {
|
|
if (WiFi.status() != WL_CONNECTED) return false;
|
|
|
|
pesan.replace("\\", "\\\\");
|
|
pesan.replace("\"", "\\\"");
|
|
pesan.replace("\r", "");
|
|
pesan.replace("\n", "\\n");
|
|
|
|
String body = "{\"chat_id\":\"" + String(CHAT_ID) + "\",\"text\":\"" + pesan + "\"}";
|
|
String url = "/bot" + String(BOT_TOKEN) + "/sendMessage";
|
|
|
|
WiFiClientSecure client;
|
|
client.setInsecure();
|
|
client.setTimeout(12000);
|
|
|
|
if (!client.connect("api.telegram.org", 443)) {
|
|
Serial.println("[TELEGRAM] Gagal konek");
|
|
return false;
|
|
}
|
|
|
|
client.printf("POST %s HTTP/1.1\r\n", url.c_str());
|
|
client.println("Host: api.telegram.org");
|
|
client.println("Content-Type: application/json");
|
|
client.printf("Content-Length: %d\r\n", body.length());
|
|
client.println("Connection: close");
|
|
client.println();
|
|
client.print(body);
|
|
|
|
unsigned long t = millis();
|
|
while (!client.available() && millis()-t < 8000) { delay(50); }
|
|
|
|
String statusLine = client.readStringUntil('\n');
|
|
bool ok = statusLine.indexOf("200") > 0;
|
|
Serial.printf("[TELEGRAM] %s (%s)\n", ok?"OK":"GAGAL", statusLine.substring(9,12).c_str());
|
|
|
|
client.stop();
|
|
return ok;
|
|
}
|
|
|
|
|
|
void prosesPerintah() {
|
|
if (WiFi.status() != WL_CONNECTED) return;
|
|
|
|
String url = "/bot" + String(BOT_TOKEN) +
|
|
"/getUpdates?offset=" + String(lastMsgId+1) +
|
|
"&limit=5&timeout=1";
|
|
|
|
WiFiClientSecure client;
|
|
client.setInsecure();
|
|
client.setTimeout(10000);
|
|
|
|
if (!client.connect("api.telegram.org", 443)) {
|
|
Serial.println("[BOT] Gagal konek getUpdates");
|
|
return;
|
|
}
|
|
|
|
client.printf("GET %s HTTP/1.1\r\n", url.c_str());
|
|
client.println("Host: api.telegram.org");
|
|
client.println("Connection: close");
|
|
client.println();
|
|
|
|
unsigned long t = millis();
|
|
while (client.connected() && millis()-t < 8000) {
|
|
String line = client.readStringUntil('\n');
|
|
if (line == "\r") break;
|
|
}
|
|
|
|
String json = "";
|
|
t = millis();
|
|
while (client.available() && millis()-t < 5000) {
|
|
json += client.readString();
|
|
}
|
|
client.stop();
|
|
|
|
if (json.length() < 10) return;
|
|
|
|
DynamicJsonDocument doc(2048);
|
|
if (deserializeJson(doc, json)) { Serial.println("[BOT] JSON error"); return; }
|
|
|
|
JsonArray results = doc["result"].as<JsonArray>();
|
|
for (JsonObject upd : results) {
|
|
lastMsgId = upd["update_id"].as<long>();
|
|
String chat_id = upd["message"]["chat"]["id"].as<String>();
|
|
String teks = upd["message"]["text"].as<String>();
|
|
if (chat_id != CHAT_ID) continue;
|
|
|
|
Serial.printf("[BOT] Perintah: %s\n", teks.c_str());
|
|
|
|
if (teks=="/start"||teks=="/status") queuePush(formatStatus());
|
|
else if (teks=="/help") queuePush(formatHelp());
|
|
else if (teks=="/kipason") { modeManual=true; setKipas(true); queuePush("Mode Manual\nKipas DC ON"); }
|
|
else if (teks=="/kipasoff") { modeManual=true; setKipas(false); queuePush("Mode Manual\nKipas DC OFF"); }
|
|
else if (teks=="/pompaon") { modeManual=true; setPompa(true); queuePush("Mode Manual\nPompa Air ON"); }
|
|
else if (teks=="/pompaoff") { modeManual=true; setPompa(false); queuePush("Mode Manual\nPompa Air OFF"); }
|
|
else if (teks=="/auto") { modeManual=false; queuePush("Mode Otomatis Aktif"); }
|
|
else queuePush("Perintah tidak dikenal.");
|
|
}
|
|
}
|
|
|
|
|
|
void bacaSensor() {
|
|
sensors_event_t hum, tmp;
|
|
if (sensor1_ok) {
|
|
sensor1.getEvent(&hum, &tmp);
|
|
if (tmp.temperature>-22 &&tmp.temperature<28&&
|
|
hum.relative_humidity>=80&&hum.relative_humidity<90) {
|
|
suhu1=tmp.temperature; rh1=hum.relative_humidity;
|
|
} else {
|
|
Serial.println("[SENSOR1] Nilai tidak valid, reinit...");
|
|
sensor1_ok = sensor1.begin(&Wire);
|
|
}
|
|
}
|
|
if (sensor2_ok) {
|
|
sensor2.getEvent(&hum, &tmp);
|
|
if (tmp.temperature>-22&&tmp.temperature<28&&
|
|
hum.relative_humidity>=80&&hum.relative_humidity<90) {
|
|
suhu2=tmp.temperature; rh2=hum.relative_humidity;
|
|
} else {
|
|
Serial.println("[SENSOR2] Nilai tidak valid, reinit...");
|
|
Wire1.begin(SDA2, SCL2, 400000);
|
|
sensor2_ok = sensor2.begin(&Wire1);
|
|
}
|
|
}
|
|
if (sensor1_ok&&sensor2_ok) { avgSuhu=(suhu1+suhu2)/2.0; avgRH=(rh1+rh2)/2.0; }
|
|
else if (sensor1_ok) { avgSuhu=suhu1; avgRH=rh1; }
|
|
else if (sensor2_ok) { avgSuhu=suhu2; avgRH=rh2; }
|
|
}
|
|
|
|
|
|
void kendaliRelay() {
|
|
if (modeManual) return;
|
|
prevKipas=kipasState; prevPompa=pompaState;
|
|
|
|
|
|
if (avgSuhu > SUHU_ON) setKipas(true);
|
|
else if (avgSuhu < SUHU_OFF) setKipas(false);
|
|
if (avgRH < RH_ON) setPompa(true);
|
|
else if (avgRH > RH_OFF) setPompa(false);
|
|
|
|
|
|
if (kipasState != prevKipas) {
|
|
String notif = "";
|
|
if (kipasState) {
|
|
notif += "\xF0\x9F\x92\xA7 Suhu berubah : " + String(avgSuhu,1) + " C\n\n";
|
|
}
|
|
notif += formatStatus();
|
|
queuePush(notif);
|
|
}
|
|
|
|
|
|
if (pompaState != prevPompa) {
|
|
String notif = "";
|
|
notif += "\xF0\x9F\x92\xA7 RH berubah : " + String(avgRH,1) + " %\n\n";
|
|
notif += formatStatus();
|
|
queuePush(notif);
|
|
}
|
|
}
|
|
|
|
|
|
void kirimFirebase() {
|
|
if (!Firebase.ready()) { Serial.println("[FIREBASE] Tidak siap"); return; }
|
|
|
|
Firebase.setFloat(fbData, "/kumbung/realtime/sensor1/suhu", suhu1);
|
|
Firebase.setFloat(fbData, "/kumbung/realtime/sensor1/rh", rh1);
|
|
Firebase.setFloat(fbData, "/kumbung/realtime/sensor2/suhu", suhu2);
|
|
Firebase.setFloat(fbData, "/kumbung/realtime/sensor2/rh", rh2);
|
|
Firebase.setFloat(fbData, "/kumbung/realtime/avg/suhu", avgSuhu);
|
|
Firebase.setFloat(fbData, "/kumbung/realtime/avg/rh", avgRH);
|
|
Firebase.setBool(fbData, "/kumbung/realtime/relay/kipas", kipasState);
|
|
Firebase.setBool(fbData, "/kumbung/realtime/relay/pompa", pompaState);
|
|
Firebase.setString(fbData,"/kumbung/realtime/kondisi", getKondisi());
|
|
|
|
dataCount++;
|
|
char path[50]; sprintf(path,"/kumbung/history/data_%04lu",dataCount);
|
|
FirebaseJson json;
|
|
json.set("s1t",suhu1); json.set("s1rh",rh1);
|
|
json.set("s2t",suhu2); json.set("s2rh",rh2);
|
|
json.set("avgt",avgSuhu); json.set("avgrh",avgRH);
|
|
json.set("kipas",kipasState?"ON":"OFF");
|
|
json.set("pompa",pompaState?"ON":"OFF");
|
|
json.set("kondisi",getKondisi());
|
|
|
|
Firebase.setJSON(fbData, path, json)
|
|
? Serial.printf("[FIREBASE] OK -> %s\n", path)
|
|
: Serial.printf("[FIREBASE] Gagal: %s\n", fbData.errorReason().c_str());
|
|
}
|
|
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
delay(1000);
|
|
Serial.println("\n=== MONITORING JAMUR TIRAM v7.3 FIX (ESP32) ===\n");
|
|
Serial.println("[INFO] RELAY_KIPAS = GPIO" + String(RELAY_KIPAS));
|
|
Serial.println("[INFO] RELAY_POMPA = GPIO" + String(RELAY_POMPA));
|
|
|
|
pinMode(RELAY_KIPAS, OUTPUT); digitalWrite(RELAY_KIPAS, HIGH);
|
|
pinMode(RELAY_POMPA, OUTPUT); digitalWrite(RELAY_POMPA, HIGH);
|
|
Serial.println("[OK] Relay OFF");
|
|
|
|
|
|
Wire.begin(SDA1, SCL1);
|
|
delay(200);
|
|
sensor1_ok = sensor1.begin(&Wire);
|
|
Serial.println(sensor1_ok?"[OK] Sensor 1 (Wire GPIO21/22)":"[GAGAL] Sensor 1");
|
|
|
|
|
|
Wire1.begin(SDA2, SCL2, 400000);
|
|
delay(200);
|
|
sensor2_ok = sensor2.begin(&Wire1);
|
|
if (sensor2_ok) {
|
|
Wire1.begin(SDA2, SCL2, 400000);
|
|
Serial.println("[OK] Sensor 2 (Wire1 GPIO16/17)");
|
|
} else {
|
|
Serial.println("[GAGAL] Sensor 2");
|
|
}
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.begin(ssid, password);
|
|
Serial.printf("[INFO] WiFi: %s\n", ssid);
|
|
int r=0;
|
|
while (WiFi.status()!=WL_CONNECTED && r<40) {
|
|
delay(500); Serial.print("."); r++;
|
|
}
|
|
Serial.println(WiFi.status()==WL_CONNECTED
|
|
? "\n[OK] WiFi: " + WiFi.localIP().toString()
|
|
: "\n[GAGAL] WiFi!");
|
|
|
|
fbConfig.host = FIREBASE_HOST;
|
|
fbConfig.signer.tokens.legacy_token = FIREBASE_AUTH;
|
|
Firebase.begin(&fbConfig, &fbAuth);
|
|
Firebase.reconnectWiFi(true);
|
|
fbData.setResponseSize(2048);
|
|
Serial.println(Firebase.ready()?"[OK] Firebase":"[GAGAL] Firebase");
|
|
|
|
delay(500);
|
|
bacaSensor();
|
|
kendaliRelay();
|
|
|
|
String boot = "\xE2\x9C\x85 ESP32 Monitoring Jamur Online\n\n";
|
|
boot += "WiFi : " + String(ssid) + "\n";
|
|
boot += "Sensor 1 : " + String(sensor1_ok?"OK":"Gagal") + "\n";
|
|
boot += "Sensor 2 : " + String(sensor2_ok?"OK":"Gagal") + "\n";
|
|
boot += "Firebase : " + String(Firebase.ready()?"OK":"Gagal") + "\n\n";
|
|
boot += formatStatus();
|
|
queuePush(boot);
|
|
|
|
lastBaca=lastPerintah=lastUpdate=lastFirebase=lastKirim=millis();
|
|
Serial.println("\n[INFO] Sistem siap!\n");
|
|
}
|
|
|
|
|
|
void loop() {
|
|
unsigned long now = millis();
|
|
|
|
if (now - lastKirim >= MS_KIRIM && qCount > 0) {
|
|
lastKirim = now;
|
|
String msg;
|
|
if (queuePop(msg)) {
|
|
bool ok = kirimTelegram(msg);
|
|
if (!ok) {
|
|
queuePush(msg);
|
|
lastKirim = now + 10000;
|
|
Serial.println("[TELEGRAM] Coba lagi 10 detik lagi");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (now - lastBaca >= MS_BACA) {
|
|
lastBaca = now;
|
|
bacaSensor();
|
|
kendaliRelay();
|
|
Serial.printf("[S] S1:%.1fC %.1f%% S2:%.1fC %.1f%% AVG:%.1fC %.1f%% K:%s P:%s\n",
|
|
suhu1,rh1,suhu2,rh2,avgSuhu,avgRH,
|
|
kipasState?"ON":"OFF",pompaState?"ON":"OFF");
|
|
}
|
|
|
|
if (now - lastPerintah >= MS_PERINTAH && qCount == 0) {
|
|
lastPerintah = now;
|
|
prosesPerintah();
|
|
}
|
|
|
|
if (now - lastUpdate >= MS_UPDATE) {
|
|
lastUpdate = now;
|
|
Serial.println("[INFO] Update otomatis -> queue");
|
|
queuePush(formatStatus());
|
|
}
|
|
|
|
if (now - lastFirebase >= MS_FIREBASE && qCount == 0) {
|
|
lastFirebase = now;
|
|
kirimFirebase();
|
|
}
|
|
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
Serial.println("[WARNING] WiFi putus!");
|
|
WiFi.disconnect(); delay(1000);
|
|
WiFi.begin(ssid, password);
|
|
int r=0;
|
|
while (WiFi.status()!=WL_CONNECTED && r<20) { delay(500); r++; }
|
|
Serial.println(WiFi.status()==WL_CONNECTED?"[OK] Reconnect!":"[GAGAL] Reconnect!");
|
|
}
|
|
|
|
yield();
|
|
}
|