From 56479e1499cae83a638b211b1de03ec03be1ec6b Mon Sep 17 00:00:00 2001 From: ridhofaisalh Date: Fri, 15 Aug 2025 13:41:16 +0700 Subject: [PATCH] Upload files to "/" --- TomatWM.ino | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 TomatWM.ino diff --git a/TomatWM.ino b/TomatWM.ino new file mode 100644 index 0000000..1f28a9d --- /dev/null +++ b/TomatWM.ino @@ -0,0 +1,390 @@ +#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); +} \ No newline at end of file