#include "HX711.h"
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <Wire.h>
#include <RTClib.h>
#include "time.h"

// ======================================================
// WIFI
// ======================================================
const char* ssid = "Rio";
const char* password = "Rio1234567";

// ======================================================
// FIREBASE
// ======================================================
#define API_KEY "AIzaSyC-YYniuYaVJAZl0LywCbC-HKvMED0_uUY"
#define DATABASE_URL "https://poultryrail-1205e-default-rtdb.firebaseio.com/"
#define USER_EMAIL "esp32@gmail.com"
#define USER_PASSWORD "12345678"
#define FIREBASE_PROJECT_ID "poultryrail-1205e"

FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

// ======================================================
// RTC
// ======================================================
RTC_DS3231 rtc;

// ======================================================
// STEPPER A4988
// ======================================================
#define STEP_PIN 4
#define DIR_PIN 2
#define EN_PIN 5

long totalSteps = 3900;
long posisiStepper = 0;

// ======================================================
// HX711
// ======================================================
#define DT1 25
#define SCK1 26

#define DT2 32
#define SCK2 33

HX711 scale1;
HX711 scale2;

float calibration_factor1 = 422;
float calibration_factor2 = 399;

// ======================================================
// TIMER
// ======================================================
unsigned long lastRTC = 0;
unsigned long lastRead = 0;
unsigned long lastJadwal = 0;
unsigned long lastHeartbeat = 0;
unsigned long lastManual = 0;

void updateHeartbeat() {

  if (millis() - lastHeartbeat >= 5000) {

    lastHeartbeat = millis();

    Firebase.RTDB.setInt(
      &fbdo,
      "/status/heartbeat",
      millis()
    );
  }
}

String lastTrigger = "";

bool wifiTerhubungSebelumnya = true;

void kirimNotifikasi(String pesan) {

  Firebase.RTDB.setString(
    &fbdo,
    "/notifikasi/pesan",
    pesan
  );

  Firebase.RTDB.setString(
    &fbdo,
    "/notifikasi/status",
    "baru"
  );
}

void simpanRiwayat(
  String waktu,
  String jam,
  String tanggal,
  String status
);

void simpanRiwayatFirestore(
  String waktu,
  String jam,
  String tanggal,
  String status,
  float beratKanan,
  float beratKiri
);

// ======================================================
// STEPPER
// ======================================================
void stepMotor(long jumlahStep,
int speedDelay,
bool maju) {

for (long i = 0; i < jumlahStep; i++) {

  if (i % 500 == 0) {
    updateHeartbeat();
  }


digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(speedDelay);

digitalWrite(STEP_PIN, LOW);
delayMicroseconds(speedDelay);
}

// simpan posisi stepper
if (maju) {

posisiStepper += jumlahStep;
}
else {

posisiStepper -= jumlahStep;

if (posisiStepper < 0) {
posisiStepper = 0;
}
}
}

// ======================================================
// BACA BERAT
// ======================================================
float bacaBerat1() {

float b1 = scale1.get_units(3);

if (abs(b1) < 10) b1 = 0;

return round(b1 * 10) / 10.0;
}

float bacaBerat2() {

float b2 = scale2.get_units(3);

if (abs(b2) < 10) b2 = 0;

return round(b2 * 10) / 10.0;
}


// BACA JARAK DARI FIREBASE

float bacaJarakFirebase() {

if (Firebase.RTDB.getFloat(
&fbdo,
"/sensor/jarak")) {

return fbdo.floatData();
}

return 999;
}

// ======================================================
// HENTAKAN PAKAN
// ======================================================
void hentakanPakan() {

// maju sedikit
digitalWrite(DIR_PIN, HIGH);

stepMotor(50, 800, true);

delay(50);

// mundur sedikit
digitalWrite(DIR_PIN, LOW);

stepMotor(50, 800, false);

delay(50);

// balik maju lagi
digitalWrite(DIR_PIN, HIGH);
}

void prosesPemberianPakan(
    String key,
    String waktu,
    String jam,
    String tanggal
)
{
float jarakAwal = bacaJarakFirebase();

Serial.print("Jarak Awal: ");
Serial.println(jarakAwal);

// stok kurang dari 15%
if (jarakAwal >= 13) {

Serial.println("STOK PAKAN KURANG");
Serial.println("JADWAL DIBATALKAN");

kirimNotifikasi(
  "Pemberian pakan gagal karena stok habis"
);

Firebase.RTDB.setString(
&fbdo,
"/jadwal/" + key + "/status",
"Pemberian Pakan gagal dijalankan karena stok habis"
);

simpanRiwayatFirestore(
waktu,
jam,
tanggal,
"Pemberian Pakan gagal dijalankan karena stok habis",
bacaBerat1(), // kanan
bacaBerat2()  // kiri
);

Firebase.RTDB.deleteNode(
    &fbdo,
    "/jadwal/" + key
);

return;
}

float sisaKanan = bacaBerat1();
float sisaKiri  = bacaBerat2();

bool isiKanan = (sisaKanan <= 30);
bool isiKiri  = (sisaKiri <= 30);

// jika keduanya masih banyak
if (!isiKanan && !isiKiri) {

  String pesan =
      "Pemberian pakan gagal karena kedua wadah masih diatas 30 gram";

  Serial.println(pesan);

  if (key != "manual") {

    Firebase.RTDB.setString(
      &fbdo,
      "/jadwal/" + key + "/status",
      pesan
    );

    Firebase.RTDB.deleteNode(
      &fbdo,
      "/jadwal/" + key
    );
  }

  simpanRiwayatFirestore(
    waktu,
    jam,
    tanggal,
    pesan,
    sisaKanan,
    sisaKiri
  );

  return;
}

Firebase.RTDB.setString(
&fbdo,
"/jadwal/" + key + "/status",
"running"
);

// ======================================================
// PAGI / SORE
// ======================================================
int jumlahTitik;
long langkahPerTitik;

if (waktu == "Pagi") {

jumlahTitik = 3;
langkahPerTitik = 1250;
}
else {

jumlahTitik = 3;
langkahPerTitik = 1250;
}

// ======================================================
// TARGET
// ======================================================
float maxKeluar1 = 5.0;
float maxKeluar2 = 5.0;

// ======================================================
// DRIVER ON
// ======================================================
digitalWrite(EN_PIN, LOW);

// ======================================================
// ARAH MAJU
// ======================================================
digitalWrite(DIR_PIN, HIGH);

// ======================================================
// LOOP TITIK
// ======================================================
for (int titik = 1; titik <= jumlahTitik; titik++) {

updateHeartbeat();

// CEK STOK SAAT BERJALAN
float jarakSekarang =
bacaJarakFirebase();

Serial.print("Jarak Sekarang: ");
Serial.println(jarakSekarang);

// stok habis
// ==========================================
// CEK ULANG STOK
// ==========================================
int deteksiHabis = 0;

for (int i = 0; i < 3; i++) {

float cekUlang = bacaJarakFirebase();

Serial.print("Cek Ulang ");
Serial.print(i);
Serial.print(" : ");
Serial.println(cekUlang);

if (cekUlang >= 13) {

deteksiHabis++;
}

delay(50);
}

// ==========================================
// JIKA 3X TERDETEK HABIS
// ==========================================
if (deteksiHabis >= 3) {

Serial.println("PAKAN HABIS");
Serial.println("KEMBALI KE AWAL");

kirimNotifikasi(
  "Pakan habis saat proses pemberian berjalan"
);

Firebase.RTDB.setString(
&fbdo,
"/servo1/action",
"close"
);

Firebase.RTDB.setString(
&fbdo,
"/servo2/action",
"close"
);

delay(300);

digitalWrite(DIR_PIN, LOW);

stepMotor(posisiStepper, 800, false);

digitalWrite(EN_PIN, HIGH);

Firebase.RTDB.setString(
&fbdo,
"/jadwal/" + key + "/status",
"Pemberian pakan belum tercapai stok habis dijalan"
);

simpanRiwayatFirestore(
waktu,
jam,
tanggal,
"Pemberian pakan belum tercapai stok habis dijalan",
bacaBerat1(), // kanan
bacaBerat2()  // kiri
);

Firebase.RTDB.deleteNode(
    &fbdo,
    "/jadwal/" + key
);

return;
}

Serial.print("TITIK KE : ");
Serial.println(titik);

// ==========================================
// STEPPER MAJU
// ==========================================
stepMotor(langkahPerTitik, 700, true);

delay(300);

// ==========================================
// BERAT AWAL
// ==========================================
float awal1 = bacaBerat1();
float awal2 = bacaBerat2();

// ==========================================
// BUKA SERVO
// ==========================================
bool close1 = !isiKanan;
bool close2 = !isiKiri;

if (isiKanan) {

  Firebase.RTDB.setString(
    &fbdo,
    "/servo2/action",
    "open"
  );

  Serial.println("SERVO1 OPEN");
}

if (isiKiri) {

  Firebase.RTDB.setString(
    &fbdo,
    "/servo1/action",
    "open"
  );

  Serial.println("SERVO2 OPEN");
}

// ==========================================
// LOOP PAKAN
// ==========================================
while (!close1 || !close2) {

updateHeartbeat();

hentakanPakan();

float sekarang1 = bacaBerat1();
float sekarang2 = bacaBerat2();

float keluar1 =
sekarang1 - awal1;

float keluar2 =
sekarang2 - awal2;

if (keluar1 < 0) keluar1 = 0;
if (keluar2 < 0) keluar2 = 0;

Firebase.RTDB.setFloat(
&fbdo,
"/sensor/keluar1",
keluar1
);

Firebase.RTDB.setFloat(
&fbdo,
"/sensor/keluar2",
keluar2
);

Serial.print("Keluar1: ");
Serial.print(keluar1);

Serial.print(" | Keluar2: ");
Serial.println(keluar2);

// ==========================================
// SERVO1 CLOSE
// ==========================================
if (isiKanan &&
    !close1 &&
    keluar1 >= maxKeluar1)
{
  Firebase.RTDB.setString(
    &fbdo,
    "/servo2/action",
    "close"
  );

  close1 = true;

  Serial.println("SERVO2 CLOSE");
}

// ==========================================
// SERVO2 CLOSE
// ==========================================
if (isiKiri &&
    !close2 &&
    keluar2 >= maxKeluar2)
{
  Firebase.RTDB.setString(
    &fbdo,
    "/servo1/action",
    "close"
  );

  close2 = true;

  Serial.println("SERVO1 CLOSE");
}

delay(10);
}

Serial.println("SEMUA SERVO TUTUP");

delay(300);
}

// ======================================================
// PAKSA TUTUP
// ======================================================
Firebase.RTDB.setString(
&fbdo,
"/servo1/action",
"close"
);

Firebase.RTDB.setString(
&fbdo,
"/servo2/action",
"close"
);

delay(500);

// ======================================================
// BALIK AWAL
// ======================================================
digitalWrite(DIR_PIN, LOW);

stepMotor(posisiStepper, 800, false);

// ======================================================
// DRIVER OFF
// ======================================================
digitalWrite(EN_PIN, HIGH);

// ======================================================
// DONE
// ======================================================
if (key != "manual") {

  Firebase.RTDB.setString(
  &fbdo,
  "/jadwal/" + key + "/status",
  "done"
  );

  Firebase.RTDB.deleteNode(
  &fbdo,
  "/jadwal/" + key
  );
}

simpanRiwayatFirestore(
waktu,
jam,
tanggal,
"done",
bacaBerat1(), // kanan
bacaBerat2()  // kiri
);

Firebase.RTDB.deleteNode(
    &fbdo,
    "/jadwal/" + key
);

kirimNotifikasi(
  "Pemberian pakan selesai"
);

Serial.println("SELESAI");
}

// ======================================================
// CEK JADWAL
// ======================================================
void cekJadwal() {

if (!Firebase.ready()) return;

if (!Firebase.RTDB.getJSON(&fbdo, "/jadwal")) {
return;
}

FirebaseJson &json = fbdo.jsonObject();

size_t len = json.iteratorBegin();

if (len == 0) {

json.iteratorEnd();
return;
}

DateTime now = rtc.now();

char jamNow[6];

sprintf(jamNow,
"%02d:%02d",
now.hour(),
now.minute());

String tglFix =
String(now.day()) + "-" +
String(now.month()) + "-" +
String(now.year());


// ======================================================
// LOOP JADWAL
// ======================================================
for (size_t i = 0; i < len; i++) {

String key, value;
int type;

json.iteratorGet(i, type, key, value);

FirebaseJsonData data;
json.get(data, key);

FirebaseJson obj;
obj.setJsonData(data.stringValue);

FirebaseJsonData jam, tanggal, status, waktu;

obj.get(jam, "jam");
obj.get(tanggal, "tanggal");
obj.get(status, "status");
obj.get(waktu, "waktu");

bool matchJam =
(jam.stringValue.substring(0,5)
== String(jamNow));

bool matchTanggal =
(tanggal.stringValue == tglFix);

bool statusIdle =
(status.stringValue == "idle" ||
status.stringValue == "");

String id = key + String(jamNow);

// ======================================================
// JALANKAN
// ======================================================
if (matchJam &&
    matchTanggal &&
    statusIdle &&
    id != lastTrigger)
{
    lastTrigger = id;

    Serial.println("JADWAL JALAN");

    Firebase.RTDB.setString(
      &fbdo,
      "/jadwal/" + key + "/status",
      "running"
    );

    prosesPemberianPakan(
      key,
      waktu.stringValue,
      jam.stringValue,
      tanggal.stringValue
    );
}

}
json.iteratorEnd();
}

void simpanRiwayatFirestore(
  String waktu,
  String jam,
  String tanggal,
  String status,
  float beratKanan,
  float beratKiri
) {

FirebaseJson content;

content.set("fields/waktu/stringValue", waktu);
content.set("fields/jam/stringValue", jam);
content.set("fields/tanggal/stringValue", tanggal);
content.set("fields/status/stringValue", status);

content.set("fields/beratKanan/doubleValue", beratKanan);
content.set("fields/beratKiri/doubleValue", beratKiri);

content.set(
  "fields/timestamp/integerValue",
  String(time(nullptr))
);

  String documentPath =
      "riwayat/" + String(millis());

  Firebase.Firestore.createDocument(
      &fbdo,
      FIREBASE_PROJECT_ID,
      "",
      documentPath.c_str(),
      content.raw());
}

void cekManual() {

  if (Firebase.RTDB.getString(&fbdo, "/manual/action")) {

    String cmd = fbdo.stringData();

    if (cmd == "start") {

      Firebase.RTDB.setString(
        &fbdo,
        "/manual/action",
        "running"
      );

      DateTime now = rtc.now();

      char jamManual[6];
      sprintf(
        jamManual,
        "%02d:%02d",
        now.hour(),
        now.minute()
      );

      String tanggalManual =
        String(now.day()) + "-" +
        String(now.month()) + "-" +
        String(now.year());

      prosesPemberianPakan(
        "manual",
        "Manual",
        String(jamManual),
        tanggalManual
      );

      Firebase.RTDB.setString(
        &fbdo,
        "/manual/action",
        "done"
      );
    }
  }
}

// ======================================================
// SETUP
// ======================================================
void setup() {

Serial.begin(115200);

// ======================================================
// STEPPER
// ======================================================
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(EN_PIN, OUTPUT);

digitalWrite(EN_PIN, HIGH);

// ======================================================
// HX711
// ======================================================
scale1.begin(DT1, SCK1);
scale2.begin(DT2, SCK2);

delay(1000);

scale1.set_scale(calibration_factor1);
scale2.set_scale(calibration_factor2);

scale1.tare();
scale2.tare();

// ======================================================
// RTC
// ======================================================
Wire.begin(21, 22);

if (!rtc.begin()) {

  Serial.println("RTC ERROR");

  kirimNotifikasi(
    "RTC DS3231 tidak terdeteksi"
  );

  while (1);
}

// ======================================================
// WIFI
// ======================================================
WiFi.begin(ssid, password);

Serial.print("CONNECT WIFI");

while (WiFi.status() != WL_CONNECTED) {

delay(500);
Serial.print(".");
}

Serial.println("");
Serial.println("WIFI CONNECT");

// ======================================================
// WAKTU INTERNET
// ======================================================
configTime(7 * 3600, 0,
"pool.ntp.org",
"time.nist.gov");

struct tm timeinfo;

if (getLocalTime(&timeinfo)) {

rtc.adjust(DateTime(
timeinfo.tm_year + 1900,
timeinfo.tm_mon + 1,
timeinfo.tm_mday,
timeinfo.tm_hour,
timeinfo.tm_min,
timeinfo.tm_sec
));
}

// ======================================================
// FIREBASE
// ======================================================
config.api_key = API_KEY;
config.database_url = DATABASE_URL;

auth.user.email = USER_EMAIL;
auth.user.password = USER_PASSWORD;

Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);

Serial.println("READY");
}

// ======================================================
// LOOP
// ======================================================
void loop() {

// =======================================
// CEK WIFI
// =======================================
if (WiFi.status() != WL_CONNECTED) {

  if (wifiTerhubungSebelumnya) {

    Serial.println("WIFI PUTUS");

    wifiTerhubungSebelumnya = false;
  }

  WiFi.reconnect();

  delay(1000);

  return;
}
else {

  // WiFi baru tersambung lagi
  if (!wifiTerhubungSebelumnya) {

    wifiTerhubungSebelumnya = true;

    kirimNotifikasi(
      "Koneksi WiFi kembali normal"
    );
  }
}

// =======================================
// CEK FIREBASE
// =======================================
if (!Firebase.ready()) {

  kirimNotifikasi(
    "Koneksi ke Firebase terputus"
  );

  return;
}

// =======================================
// HEARTBEAT
// =======================================
if (millis() - lastHeartbeat > 5000) {

  lastHeartbeat = millis();

  Firebase.RTDB.setInt(
    &fbdo,
    "/status/heartbeat",
    millis()
  );

}

// ======================================================
// RTC
// ======================================================
if (millis() - lastRTC > 5000) {

  Firebase.RTDB.setInt(
  &fbdo,
  "/status/heartbeat",
  millis()
);

lastRTC = millis();

DateTime now = rtc.now();

char waktu[9];

sprintf(waktu,
"%02d:%02d:%02d",
now.hour(),
now.minute(),
now.second());

Firebase.RTDB.setString(
&fbdo,
"/rtc/time",
waktu
);
}


// ======================================================
// CEK JADWAL
// ======================================================
if (millis() - lastJadwal > 5000) {

  lastJadwal = millis();

  cekJadwal();
}

if (millis() - lastManual > 1000) {

  lastManual = millis();

  cekManual();
}

// ======================================================
// BERAT REALTIME
// ======================================================
if (millis() - lastRead > 1000) {

lastRead = millis();

float b1 = bacaBerat1();
float b2 = bacaBerat2();

Firebase.RTDB.setFloat(
&fbdo,
"/sensor/berat1",
b1
);

Firebase.RTDB.setFloat(
&fbdo,
"/sensor/berat2",
b2
);

Serial.print("B1: ");
Serial.print(b1);

Serial.print(" | B2: ");
Serial.println(b2);
}
}