Upload files to "/"
This commit is contained in:
commit
56479e1499
|
@ -0,0 +1,390 @@
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <ESP32Servo.h>
|
||||||
|
#include <LiquidCrystal_I2C.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <FirebaseESP32.h>
|
||||||
|
#include <Wifi.h>
|
||||||
|
#include <WiFiManager.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
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<String>();
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Reference in New Issue