308 lines
8.4 KiB
C++
308 lines
8.4 KiB
C++
#include <WiFi.h>
|
|
#include <HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
#include <time.h>
|
|
#include <ESP32Servo.h>
|
|
|
|
// WiFi credentials
|
|
const char* ssid = "KOPI";
|
|
const char* password = "digoreng123";
|
|
|
|
// Firebase configuration
|
|
const String firebaseHost = "https://my-fish-7db48-default-rtdb.firebaseio.com/";
|
|
const String apiKey = "AIzaSyCGMJfu0R3LmJp6IIxoTyspgJpC-0slcCE";
|
|
|
|
// Pakan sensor & actuator
|
|
#define TRIG_PAKAN 12
|
|
#define ECHO_PAKAN 13
|
|
#define SERVO_PIN 15
|
|
#define BUTTON_PIN 4
|
|
#define LED_PIN 2
|
|
#define POMPA_RELAY 19
|
|
#define PEMBERSIH_RELAY 18
|
|
|
|
Servo servoPakan;
|
|
|
|
// Flags
|
|
bool kontrolPakan = false;
|
|
bool kontrolPembersih = false;
|
|
bool statusPembersihManual = false;
|
|
bool pompaOverride = false;
|
|
|
|
// Timing
|
|
unsigned long lastSensorSend = 0;
|
|
unsigned long intervalSensor = 60000;
|
|
unsigned long lastCleaner = 0;
|
|
unsigned long intervalPembersih = 60 * 60000;
|
|
unsigned long lastJadwalUpdate = 0;
|
|
unsigned long lastBlink = 0;
|
|
bool ledState = false;
|
|
unsigned long waktuPembersihBerikutnya = 0;
|
|
int intervalSetelahPakan = 3 * 60 * 1000;
|
|
|
|
// Settings
|
|
const float tinggiPakanMax = 7.0; // cm
|
|
|
|
// Jadwal pakan
|
|
#define MAX_JADWAL 10
|
|
struct JadwalPakan {
|
|
int jam;
|
|
int menit;
|
|
int takar;
|
|
};
|
|
|
|
JadwalPakan jadwalPakan[MAX_JADWAL];
|
|
int jumlahJadwal = 0;
|
|
int lastFeedingDay[MAX_JADWAL];
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
pinMode(TRIG_PAKAN, OUTPUT);
|
|
pinMode(ECHO_PAKAN, INPUT);
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
pinMode(LED_PIN, OUTPUT);
|
|
pinMode(POMPA_RELAY, OUTPUT);
|
|
pinMode(PEMBERSIH_RELAY, OUTPUT);
|
|
|
|
digitalWrite(POMPA_RELAY, HIGH);
|
|
digitalWrite(PEMBERSIH_RELAY, HIGH);
|
|
servoPakan.attach(SERVO_PIN);
|
|
|
|
connectToWiFi();
|
|
setupNTP();
|
|
ambilJadwalPakanFirebase();
|
|
ambilSettingFirebase();
|
|
}
|
|
|
|
void loop() {
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
if (millis() - lastBlink >= 500) {
|
|
ledState = !ledState;
|
|
digitalWrite(LED_PIN, ledState);
|
|
lastBlink = millis();
|
|
}
|
|
} else {
|
|
digitalWrite(LED_PIN, HIGH);
|
|
}
|
|
|
|
ambilSettingFirebase();
|
|
ambilKontrolFirebase();
|
|
cekDanEksekusiJadwal();
|
|
|
|
if (millis() - lastSensorSend >= intervalSensor) {
|
|
float jarak = bacaUltrasonik(TRIG_PAKAN, ECHO_PAKAN);
|
|
if (jarak != -1) {
|
|
float tinggiPakan = hitungTinggiPakan(jarak);
|
|
Serial.printf("Tinggi Pakan: %.2f cm\n", tinggiPakan);
|
|
kirimKeFirebase(tinggiPakan);
|
|
} else {
|
|
Serial.println("Gagal membaca sensor ultrasonik.");
|
|
}
|
|
lastSensorSend = millis();
|
|
}
|
|
|
|
if (millis() - lastCleaner >= intervalPembersih) {
|
|
Serial.println("Pembersih otomatis aktif...");
|
|
pompaOverride = true;
|
|
digitalWrite(PEMBERSIH_RELAY, LOW);
|
|
digitalWrite(POMPA_RELAY, LOW);
|
|
delay(15000);
|
|
digitalWrite(PEMBERSIH_RELAY, HIGH);
|
|
digitalWrite(POMPA_RELAY, HIGH);
|
|
pompaOverride = false;
|
|
lastCleaner = millis();
|
|
}
|
|
|
|
if (millis() >= waktuPembersihBerikutnya && waktuPembersihBerikutnya > 0) {
|
|
Serial.println("Pembersih otomatis setelah pakan aktif...");
|
|
pompaOverride = true;
|
|
digitalWrite(PEMBERSIH_RELAY, LOW);
|
|
digitalWrite(POMPA_RELAY, LOW);
|
|
delay(15000);
|
|
digitalWrite(PEMBERSIH_RELAY, HIGH);
|
|
digitalWrite(POMPA_RELAY, HIGH);
|
|
pompaOverride = false;
|
|
waktuPembersihBerikutnya = 0;
|
|
}
|
|
|
|
static bool lastFirebasePembersih = false;
|
|
if (kontrolPembersih != lastFirebasePembersih) {
|
|
lastFirebasePembersih = kontrolPembersih;
|
|
digitalWrite(PEMBERSIH_RELAY, kontrolPembersih ? LOW : HIGH);
|
|
digitalWrite(POMPA_RELAY, kontrolPembersih ? LOW : HIGH);
|
|
pompaOverride = kontrolPembersih;
|
|
Serial.println(kontrolPembersih ? "Pembersih manual ON" : "OFF");
|
|
}
|
|
|
|
if (kontrolPakan) {
|
|
tuangPakan(5);
|
|
delay(1000);
|
|
}
|
|
|
|
static bool lastButtonState = HIGH;
|
|
bool currentButtonState = digitalRead(BUTTON_PIN);
|
|
if (lastButtonState == HIGH && currentButtonState == LOW) {
|
|
statusPembersihManual = !statusPembersihManual;
|
|
digitalWrite(PEMBERSIH_RELAY, statusPembersihManual ? LOW : HIGH);
|
|
digitalWrite(POMPA_RELAY, statusPembersihManual ? LOW : HIGH);
|
|
pompaOverride = statusPembersihManual;
|
|
Serial.println(statusPembersihManual ? "Tombol Pembersih: ON" : "OFF");
|
|
}
|
|
lastButtonState = currentButtonState;
|
|
|
|
if (millis() - lastJadwalUpdate > 100000) {
|
|
ambilJadwalPakanFirebase();
|
|
lastJadwalUpdate = millis();
|
|
}
|
|
|
|
delay(100);
|
|
}
|
|
|
|
// ======================== FUNGSI =============================
|
|
|
|
void connectToWiFi() {
|
|
WiFi.begin(ssid, password);
|
|
while (WiFi.status() != WL_CONNECTED) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
}
|
|
Serial.println("\nWiFi Terhubung");
|
|
}
|
|
|
|
void setupNTP() {
|
|
configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
|
}
|
|
|
|
float bacaUltrasonik(int trigPin, int echoPin) {
|
|
digitalWrite(trigPin, LOW);
|
|
delayMicroseconds(2);
|
|
digitalWrite(trigPin, HIGH);
|
|
delayMicroseconds(10);
|
|
digitalWrite(trigPin, LOW);
|
|
long duration = pulseIn(echoPin, HIGH, 30000);
|
|
if (duration == 0) return -1; // Tidak terbaca
|
|
return duration * 0.034 / 2;
|
|
}
|
|
|
|
float hitungTinggiPakan(float jarak) {
|
|
if (jarak <= 0.0 || jarak > tinggiPakanMax) return 0.0;
|
|
return tinggiPakanMax - jarak;
|
|
}
|
|
|
|
void kirimKeFirebase(float tinggiPakan) {
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
HTTPClient http;
|
|
String url = firebaseHost + "sensor_data.json?auth=" + apiKey;
|
|
String payload = "{\"tinggi_pakan\":" + String(tinggiPakan, 2) + "}";
|
|
http.begin(url);
|
|
http.addHeader("Content-Type", "application/json");
|
|
http.PUT(payload);
|
|
http.end();
|
|
}
|
|
}
|
|
|
|
void ambilKontrolFirebase() {
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
HTTPClient http;
|
|
String url = firebaseHost + "kontrol_alat.json?auth=" + apiKey;
|
|
http.begin(url);
|
|
int httpCode = http.GET();
|
|
if (httpCode == HTTP_CODE_OK) {
|
|
String response = http.getString();
|
|
DynamicJsonDocument doc(512);
|
|
deserializeJson(doc, response);
|
|
kontrolPakan = doc["pakan"];
|
|
kontrolPembersih = doc["pembersih"];
|
|
}
|
|
http.end();
|
|
}
|
|
}
|
|
|
|
void ambilSettingFirebase() {
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
HTTPClient http;
|
|
String url = firebaseHost + "setting.json?auth=" + apiKey;
|
|
http.begin(url);
|
|
int httpCode = http.GET();
|
|
if (httpCode == HTTP_CODE_OK) {
|
|
String response = http.getString();
|
|
DynamicJsonDocument doc(512);
|
|
deserializeJson(doc, response);
|
|
intervalSensor = doc["interval_data"] | 60000;
|
|
intervalPembersih = (doc["interval_pembersih"] | 60) * 60000;
|
|
}
|
|
http.end();
|
|
}
|
|
}
|
|
|
|
void ambilJadwalPakanFirebase() {
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
HTTPClient http;
|
|
String url = firebaseHost + "jadwal_pakan.json?auth=" + apiKey;
|
|
http.begin(url);
|
|
int httpCode = http.GET();
|
|
if (httpCode == HTTP_CODE_OK) {
|
|
String response = http.getString();
|
|
DynamicJsonDocument doc(2048);
|
|
if (!deserializeJson(doc, response)) {
|
|
jumlahJadwal = 0;
|
|
struct tm timeinfo;
|
|
getLocalTime(&timeinfo);
|
|
int nowJam = timeinfo.tm_hour;
|
|
int nowMenit = timeinfo.tm_min;
|
|
int today = timeinfo.tm_yday;
|
|
|
|
for (JsonPair kv : doc.as<JsonObject>()) {
|
|
if (jumlahJadwal < MAX_JADWAL) {
|
|
String jamStr = kv.value()["jam"].as<String>();
|
|
int takar = kv.value()["takar"].as<int>();
|
|
int jam = 0, menit = 0;
|
|
sscanf(jamStr.c_str(), "%d:%d", &jam, &menit);
|
|
jadwalPakan[jumlahJadwal] = {jam, menit, takar};
|
|
lastFeedingDay[jumlahJadwal] = (jam < nowJam || (jam == nowJam && menit < nowMenit)) ? today : -1;
|
|
jumlahJadwal++;
|
|
}
|
|
}
|
|
|
|
for (JsonPair kv : doc.as<JsonObject>()) {
|
|
if (kv.value().containsKey("interval_set")) {
|
|
intervalSetelahPakan = kv.value()["interval_set"].as<int>() * 60 * 1000;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
http.end();
|
|
}
|
|
}
|
|
|
|
void cekDanEksekusiJadwal() {
|
|
struct tm timeinfo;
|
|
if (!getLocalTime(&timeinfo)) return;
|
|
int nowJam = timeinfo.tm_hour;
|
|
int nowMenit = timeinfo.tm_min;
|
|
int today = timeinfo.tm_yday;
|
|
|
|
for (int i = 0; i < jumlahJadwal; i++) {
|
|
if (jadwalPakan[i].jam == nowJam && jadwalPakan[i].menit == nowMenit) {
|
|
if (lastFeedingDay[i] != today) {
|
|
Serial.printf("Tuang pakan (jadwal ke-%d): %02d:%02d, takar %d\n", i + 1, nowJam, nowMenit, jadwalPakan[i].takar);
|
|
tuangPakan(jadwalPakan[i].takar);
|
|
lastFeedingDay[i] = today;
|
|
waktuPembersihBerikutnya = millis() + intervalSetelahPakan;
|
|
delay(1000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void tuangPakan(int takar) {
|
|
for (int i = 0; i < takar; i++) {
|
|
servoPakan.write(0);
|
|
delay(500);
|
|
servoPakan.write(90);
|
|
delay(500);
|
|
}
|
|
kontrolPakan = false;
|
|
}
|