#include #include #include #include #include #include #include #include #include #include const char* API_KEY = "AIzaSyDPRK9A-5inKhas80-d3FJ-c7p9u2Ud51Q"; const char* PROJECT_ID = "database-alat-sortasi-tomat"; #define FIREBASE_HOST "https://database-alat-sortasi-tomat-default-rtdb.firebaseio.com/" #define FIREBASE_AUTH "55Yl7pfHLt4cMC1VR2qWGCfl91gFYQwWZlMGhotv" FirebaseData firebaseData; FirebaseConfig firebaseConfig; FirebaseAuth firebaseAuth; String userEmail = "tomat@gmail.com"; String userPassword = "tomat333"; String idToken = ""; LiquidCrystal_I2C lcd(0x27, 16, 2); Servo servoMatang; Servo servoMentah; #define SERVO_MATANG_PIN 18 #define SERVO_MENTAH_PIN 19 #define S0 14 #define S1 25 #define S2 26 #define S3 27 #define sensorOut 34 #define RELAY_PIN 23 bool signInWithEmailAndPassword() { HTTPClient http; WiFiClientSecure *client = new WiFiClientSecure; client->setInsecure(); String url = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=" + String(API_KEY); String body = "{\"email\":\"" + userEmail + "\",\"password\":\"" + userPassword + "\",\"returnSecureToken\":true}"; http.begin(*client, url); http.addHeader("Content-Type", "application/json"); int httpCode = http.POST(body); if (httpCode == 200) { String payload = http.getString(); // Gunakan ArduinoJson untuk parsing aman DynamicJsonDocument doc(2048); DeserializationError error = deserializeJson(doc, payload); if (error) { Serial.print("Gagal parse JSON login: "); Serial.println(error.f_str()); return false; } idToken = doc["idToken"].as(); Serial.println("Firebase Sign-in success"); Serial.println("Token: " + idToken); return true; } else { Serial.println("Sign-in failed"); Serial.println(http.getString()); return false; } } struct TomatoColor { String kategori; int red; int green; int blue; }; int bacaWarna(bool s2, bool s3) { digitalWrite(S2, s2); digitalWrite(S3, s3); delay(100); return pulseIn(sensorOut, LOW); } void nilaiRGB(int &red, int &green, int &blue) { red = bacaWarna(LOW, LOW); green = bacaWarna(HIGH, HIGH); blue = bacaWarna(LOW, HIGH); } TomatoColor tomatKalibrasi[] = { {"Matang", 146, 274, 250}, {"Setengah Matang", 119, 198, 209}, {"Mentah", 140, 139, 173} }; const int jumlahKategori = sizeof(tomatKalibrasi) / sizeof(tomatKalibrasi[0]); int jumlahMatang = 0; int jumlahMentah = 0; int jumlahSetengahMatang = 0; void setup() { Serial.begin(115200); WiFiManager wm; if (!wm.autoConnect("ESP32_Config_AP")) { Serial.println("Gagal konek ke WiFi dan tidak ada konfigurasi baru"); delay(3000); ESP.restart(); } Serial.println("Terhubung ke WiFi"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); if (!signInWithEmailAndPassword()) { Serial.println("Login gagal saat setup. Firestore tidak akan aktif."); } firebaseConfig.database_url = FIREBASE_HOST; firebaseConfig.signer.tokens.legacy_token = FIREBASE_AUTH; Firebase.begin(&firebaseConfig, &firebaseAuth); Firebase.reconnectWiFi(true); servoMatang.attach(SERVO_MATANG_PIN); servoMentah.attach(SERVO_MENTAH_PIN); pinMode(RELAY_PIN,OUTPUT); digitalWrite(RELAY_PIN, HIGH); pinMode(S0, OUTPUT); pinMode(S1, OUTPUT); pinMode(S2, OUTPUT); pinMode(S3, OUTPUT); pinMode(sensorOut, INPUT); digitalWrite(S0, HIGH); digitalWrite(S1, LOW); Serial.println("=== Deteksi Kematangan Tomat ==="); lcd.init(); lcd.backlight(); showLoading(); lcd.setCursor(0, 0); lcd.print("Mulai Deteksi"); delay(1000); configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); Serial.print("Menunggu sinkronisasi waktu NTP"); struct tm timeinfo; int retry = 0; const int maxRetry = 20; while (!getLocalTime(&timeinfo) && retry < maxRetry) { Serial.print("."); delay(500); retry++; } if (retry == maxRetry) { Serial.println("\nGagal mendapatkan waktu dari NTP"); } else { Serial.println("\nWaktu berhasil disinkronkan"); } } String getWaktuSekarang() { struct tm timeinfo; if (!getLocalTime(&timeinfo)) { return "unknown_time"; } char buffer[32]; strftime(buffer, sizeof(buffer), "%Y-%m-%d_%H:%M:%S", &timeinfo); return String(buffer); } String deteksiTomat(int red, int green, int blue) { float jarakTerdekat = 100000.0; float threshold = 50.0; String hasil = "Tidak Terdeteksi"; for (int i = 0; i < jumlahKategori; i++) { float d = sqrt( pow(red - tomatKalibrasi[i].red, 2) + pow(green - tomatKalibrasi[i].green, 2) + pow(blue - tomatKalibrasi[i].blue, 2) ); if (d < jarakTerdekat && d < threshold) { jarakTerdekat = d; hasil = tomatKalibrasi[i].kategori; } } return hasil; } void showLoading() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Inisialisasi"); lcd.setCursor(0, 1); lcd.print("[ ]"); for (int i = 0; i <= 15; i++) { lcd.setCursor(1 + i, 1); lcd.print("="); delay(100); } lcd.setCursor(0, 0); lcd.print("Siap Sortasi "); lcd.setCursor(0, 1); lcd.print(""); delay(1000); } void aktifkanServo(String statusTomat) { if (statusTomat == "Matang") { servoMatang.write(80); delay(2000); servoMatang.write(0); } else if (statusTomat == "Mentah") { servoMentah.write(80); delay(2000); servoMentah.write(0); }else if (statusTomat == "Setengah Matang") { servoMentah.write(0); servoMatang.write(0); delay(2000); servoMentah.write(0); servoMatang.write(0); } else { Serial.println("Warna tidak dikenali. Servo tidak bergerak."); } } void onoffRelay(String statusTomat) { if (statusTomat == "Matang" || statusTomat == "Setengah Matang" || statusTomat == "Mentah") { digitalWrite(RELAY_PIN, HIGH); // Nyalakan relay } else if (statusTomat == "Tidak Terdeteksi") { digitalWrite(RELAY_PIN, LOW); // Matikan relay } } void kirimDataKeFirebase(String status) { String waktu = getWaktuSekarang(); int red, green, blue; nilaiRGB(red, green, blue); if (Firebase.setString(firebaseData, "tomat/status", status) && Firebase.setString(firebaseData, "tomat/waktu", waktu) && Firebase.setInt(firebaseData, "tomat/red", red) && Firebase.setInt(firebaseData, "tomat/green", green) && Firebase.setInt(firebaseData, "tomat/blue", blue) && Firebase.setInt(firebaseData, "tomat/jumlahMatang", jumlahMatang) && Firebase.setInt(firebaseData, "tomat/jumlahMentah", jumlahMentah) && Firebase.setInt(firebaseData, "tomat/jumlahSetengahMatang", jumlahSetengahMatang)) { Serial.println("Data berhasil dikirim ke Firebase"); } else { Serial.print("Gagal mengirim data: "); Serial.println(firebaseData.errorReason()); } kirimKeFirestore(status, waktu, red, green, blue); } void kirimKeFirestore(String status, String waktu, int red, int green, int blue) { static bool loginPernahGagal = false; // hanya retry login satu kali jika gagal if (idToken == "") { Serial.println("Token kosong, tidak kirim ke Firestore."); return; } nilaiRGB(red, green, blue); String waktuDoc = getWaktuSekarang(); String docName = "data_" + waktuDoc; String url = "https://firestore.googleapis.com/v1/projects/" + String(PROJECT_ID) + "/databases/(default)/documents/tomat/" + docName + "?key=" + String(API_KEY); HTTPClient http; WiFiClientSecure client; client.setInsecure(); http.begin(client, url); http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", "Bearer " + idToken); if (status == "Matang" || status == "Mentah" || status == "Setengah Matang") { String payload = "{" "\"fields\": {" "\"status\": {\"stringValue\": \"" + status + "\"}," "\"waktu\": {\"stringValue\": \"" + waktu + "\"}," "\"nilai_red\": {\"integerValue\": \"" + red + "\" }," "\"nilai_green\": {\"integerValue\": \"" + green + "\" }," "\"nilai_blue\": {\"integerValue\": \"" + blue + "\" }," "\"jumlahMatang\": {\"integerValue\": \"" + String(jumlahMatang) + "\"}," "\"jumlahMentah\": {\"integerValue\": \"" + String(jumlahMentah) + "\"}," "\"jumlahSetengahMatang\": {\"integerValue\": \"" + String(jumlahSetengahMatang) + "\"}" "}" "}"; int httpCode = http.PATCH(payload); Serial.printf("Firestore PATCH code: %d\n", httpCode); if ((httpCode == 401 || httpCode == 403) && !loginPernahGagal) { Serial.println("Token expired / invalid. Coba login ulang..."); loginPernahGagal = true; if (signInWithEmailAndPassword()) { // Coba ulang request 1x setelah login ulang http.end(); // harus end dulu http.begin(client, url); http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", "Bearer " + idToken); httpCode = http.POST(payload); Serial.printf("Retry Firestore POST code: %d\n", httpCode); } else { Serial.println("Login ulang gagal."); } } if (httpCode > 0) { Serial.println("Firestore response:"); Serial.println(http.getString()); } else { Serial.print("Gagal kirim ke Firestore: "); Serial.println(http.errorToString(httpCode)); } http.end(); } else { Serial.println("Status = Tidak Terdeteksi → Data tidak dikirim ke Firebase."); } } void loop() { int red, green, blue; nilaiRGB(red, green, blue); String statusTomat = deteksiTomat(red, green, blue); String waktuDeteksi = getWaktuSekarang(); if (statusTomat == "Matang") { jumlahMatang++; } else if (statusTomat == "Mentah") { jumlahMentah++; } else if (statusTomat == "Setengah Matang") { jumlahSetengahMatang++; } Serial.print("Red: "); Serial.print(red); Serial.print("\tGreen: "); Serial.print(green); Serial.print("\tBlue: "); Serial.print(blue); Serial.print("\t-> Status Tomat: "); Serial.println(statusTomat); Serial.print("\Jumlah Matang: "); Serial.println(jumlahMatang); Serial.print("\Jumlah Mentah: "); Serial.println(jumlahMentah); Serial.print("\Jumlah Setengah Matang: "); Serial.println(jumlahSetengahMatang); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Status Tomat:"); lcd.setCursor(0, 1); lcd.print(statusTomat); onoffRelay(statusTomat); aktifkanServo(statusTomat); kirimDataKeFirebase(statusTomat); delay(1000); }