#include #include #include "addons/TokenHelper.h" #include "addons/RTDBHelper.h" #include #include #include #include /* ================= WIFI ================= */ #define WIFI_SSID "WONG PUSAT" #define WIFI_PASSWORD "probolinggo01" /* ================= FIREBASE ================= */ #define API_KEY "AIzaSyANiVsJP6_-x7EWlCyFs3wzRHiHVE92gZg" #define DATABASE_URL "https://tempe-56d26-default-rtdb.firebaseio.com/" #define FIREBASE_PROJECT_ID "tempe-56d26" /* ================= PIN ================= */ #define ONE_WIRE_BUS D4 #define RELAY_HEATER D1 #define RELAY_FAN_HOT D2 #define RELAY_FAN_COLD D5 /* ================= FIREBASE ================= */ FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; FirebaseData firestoreData; /* ================= SENSOR ================= */ OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); /* ================= LINKED LIST ================= */ class LinkedList { private: float* arr; int max_size; int current_size; public: LinkedList(int size) { max_size = size; current_size = 0; arr = new float[max_size]; } void update(float value) { if (current_size < max_size) { arr[current_size++] = value; } else { for (int i = 1; i < max_size; i++) { arr[i - 1] = arr[i]; } arr[max_size - 1] = value; } } float getIndex(int index) { if (index < 0 || index >= current_size) return 0; return arr[index]; } int size() { return current_size; } }; /* ================= REGRESI ================= */ class LinearRegression { public: LinearRegression(int max_size) { data = new LinkedList(max_size); } void update(float new_data) { data->update(new_data); } float predict(int n_next = 1) { int n = data->size(); if (n < 2) return data->getIndex(0); float sumX = 0; float sumY = 0; float sumXY = 0; float sumX2 = 0; for (int i = 0; i < n; i++) { sumX += i; sumY += data->getIndex(i); sumXY += i * data->getIndex(i); sumX2 += i * i; } float denominator = (n * sumX2 - sumX * sumX); if (denominator == 0) return data->getIndex(n - 1); float m = (n * sumXY - sumX * sumY) / denominator; float c = (sumY - m * sumX) / n; return m * (n + n_next - 1) + c; } private: LinkedList* data; }; /* ================= REGRESI ================= */ LinearRegression lr(5); /* ================= VAR ================= */ float t1, t2, t3; float tf1, tf2, tf3; float tMin, tMax, tAvg; float prediksi; /* ================= ERROR ================= */ float prediksi_lama = 0; unsigned long waktu_prediksi = 0; bool siapBanding = false; float errorTerakhir = 0; unsigned long lastUpload = 0; const unsigned long uploadInterval = 60000; // 1 menit /* ================= KALMAN ================= */ SimpleKalmanFilter kf1(2, 2, 0.01); SimpleKalmanFilter kf2(2, 2, 0.01); SimpleKalmanFilter kf3(2, 2, 0.01); /* ================= PEMBULATAN ================= */ float bulat2(float x) { return round(x * 100.0) / 100.0; } void kontrolSuhu(); void tampilSerial(); void kirimFirebase(); void kirimFirestore(); /* ================= SETUP ================= */ void setup() { Serial.begin(9600); delay(2000); pinMode(RELAY_HEATER, OUTPUT); pinMode(RELAY_FAN_HOT, OUTPUT); pinMode(RELAY_FAN_COLD, OUTPUT); digitalWrite(RELAY_HEATER, HIGH); digitalWrite(RELAY_FAN_HOT, HIGH); digitalWrite(RELAY_FAN_COLD, HIGH); sensors.begin(); Serial.println("=== SISTEM FERMENTASI TEMPE IoT ==="); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Menghubungkan WiFi"); unsigned long startTime = millis(); while (time(nullptr) < 100000 && millis() - startTime < 30000) { Serial.print("."); delay(500); } if (time(nullptr) < 100000) { Serial.println("Gagal sinkron waktu, lanjutkan program"); } else { Serial.println("Waktu berhasil sinkron"); } Serial.println("\nWiFi Terhubung"); Serial.println(WiFi.localIP()); configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); Serial.print("Sinkronisasi waktu"); while (time(nullptr) < 100000) { Serial.print("."); delay(500); } Serial.println("\nWaktu berhasil sinkron"); config.api_key = API_KEY; config.database_url = DATABASE_URL; if (Firebase.signUp(&config, &auth, "", "")) { Serial.println("Firebase signup berhasil"); } else { Serial.printf("Signup gagal: %s\n", config.signer.signupError.message.c_str()); } config.token_status_callback = tokenStatusCallback; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); Serial.print("Jumlah sensor terdeteksi: "); Serial.println(sensors.getDeviceCount()); } /* ================= LOOP ================= */ void loop() { if (millis() - lastUpload >= uploadInterval) { lastUpload = millis(); /* ===== BACA SENSOR ===== */ sensors.requestTemperatures(); t1 = sensors.getTempCByIndex(0); t2 = sensors.getTempCByIndex(1); t3 = sensors.getTempCByIndex(2); /* ===== VALIDASI SENSOR ===== */ if (t1 == -127 || t1 == 85) t1 = tf1; if (t2 == -127 || t2 == 85) t2 = tf2; if (t3 == -127 || t3 == 85) t3 = tf3; /* ===== FILTER KALMAN ===== */ tf1 = bulat2(kf1.updateEstimate(t1)); tf2 = bulat2(kf2.updateEstimate(t2)); tf3 = bulat2(kf3.updateEstimate(t3)); /* ===== STATISTIK ===== */ tMin = min(min(tf1, tf2), tf3); tMax = max(max(tf1, tf2), tf3); tMin = bulat2(tMin); tMax = bulat2(tMax); tAvg = bulat2((tf1 + tf2 + tf3) / 3.0); /* ===== REGRESI ===== */ lr.update(tAvg); prediksi = bulat2(lr.predict(5)); /* ===== ERROR PREDIKSI ===== */ if (!siapBanding) { prediksi_lama = prediksi; waktu_prediksi = millis(); siapBanding = true; } if (siapBanding && millis() - waktu_prediksi >= 300000) { errorTerakhir = abs(prediksi_lama - tAvg); siapBanding = false; } /* ===== KONTROL ===== */ kontrolSuhu(); /* ===== SERIAL ===== */ tampilSerial(); /* ===== FIREBASE ===== */ kirimFirebase(); kirimFirestore(); Serial.println("Upload berhasil"); } } void kirimFirestore() { if (!Firebase.ready() || auth.token.uid == "") return; FirebaseJson content; content.set("fields/sensor1/doubleValue", tf1); content.set("fields/sensor2/doubleValue", tf2); content.set("fields/sensor3/doubleValue", tf3); content.set("fields/min/doubleValue", tMin); content.set("fields/max/doubleValue", tMax); content.set("fields/rata_rata/doubleValue", tAvg); content.set("fields/prediksi/doubleValue", prediksi); content.set("fields/error/doubleValue", errorTerakhir); content.set( "fields/pemanas/stringValue", digitalRead(RELAY_HEATER) == LOW ? "ON" : "OFF"); content.set( "fields/kipas_panas/stringValue", digitalRead(RELAY_FAN_HOT) == LOW ? "ON" : "OFF"); content.set( "fields/kipas_dingin/stringValue", digitalRead(RELAY_FAN_COLD) == LOW ? "ON" : "OFF"); time_t now = time(nullptr); struct tm *tm_info = localtime(&now); char tanggal[11]; char jam[6]; strftime(tanggal, sizeof(tanggal), "%Y-%m-%d", tm_info); strftime(jam, sizeof(jam), "%H:%M", tm_info); content.set("fields/tanggal/stringValue", tanggal); content.set("fields/jam/stringValue", jam); String documentPath = String("monitoring/") + tanggal + "/data/" + jam; if (Firebase.Firestore.createDocument( &firestoreData, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.raw())) { Serial.println("Firestore berhasil"); } else { Serial.println("Firestore gagal"); Serial.println(firestoreData.errorReason()); } } /* ================= KONTROL ================= */ void kontrolSuhu() { if (tMin < 30) { digitalWrite(RELAY_HEATER, LOW); digitalWrite(RELAY_FAN_HOT, LOW); digitalWrite(RELAY_FAN_COLD, HIGH); } else if (tMax > 40) { digitalWrite(RELAY_HEATER, HIGH); digitalWrite(RELAY_FAN_HOT, HIGH); digitalWrite(RELAY_FAN_COLD, LOW); } else { if (prediksi < 30) { digitalWrite(RELAY_HEATER, LOW); digitalWrite(RELAY_FAN_HOT, LOW); digitalWrite(RELAY_FAN_COLD, HIGH); } else if (prediksi > 40) { digitalWrite(RELAY_HEATER, HIGH); digitalWrite(RELAY_FAN_HOT, HIGH); digitalWrite(RELAY_FAN_COLD, LOW); } else { digitalWrite(RELAY_HEATER, HIGH); digitalWrite(RELAY_FAN_HOT, HIGH); digitalWrite(RELAY_FAN_COLD, HIGH); } } } /* ================= SERIAL ================= */ void tampilSerial() { String statusPemanas = "OFF"; String statusKipasPanas = "OFF"; String statusKipasDingin = "OFF"; if (digitalRead(RELAY_HEATER) == LOW) statusPemanas = "ON"; if (digitalRead(RELAY_FAN_HOT) == LOW) statusKipasPanas = "ON"; if (digitalRead(RELAY_FAN_COLD) == LOW) statusKipasDingin = "ON"; Serial.println("\n=================================================="); Serial.println(" MONITORING FERMENTASI TEMPE"); Serial.println("=================================================="); Serial.println("\n[SENSOR SUHU]"); Serial.print(" S1 : "); Serial.print(tf1, 2); Serial.println(" °C"); Serial.print(" S2 : "); Serial.print(tf2, 2); Serial.println(" °C"); Serial.print(" S3 : "); Serial.print(tf3, 2); Serial.println(" °C"); Serial.println("\n[STATISTIK]"); Serial.print(" Min : "); Serial.print(tMin, 2); Serial.println(" °C"); Serial.print(" Max : "); Serial.print(tMax, 2); Serial.println(" °C"); Serial.print(" Rata : "); Serial.print(tAvg, 2); Serial.println(" °C"); Serial.println("\n[PREDIKSI]"); Serial.print(" 5 Menit ke depan : "); Serial.print(prediksi, 2); Serial.println(" °C"); Serial.println("\n[STATUS SISTEM]"); Serial.print(" Pemanas : "); Serial.println(statusPemanas); Serial.print(" Kipas Panas : "); Serial.println(statusKipasPanas); Serial.print(" Kipas Dingin : "); Serial.println(statusKipasDingin); Serial.println("==================================================\n"); } /* ================= FIREBASE ================= */ void kirimFirebase() { if (Firebase.ready()) { FirebaseJson json; /* ===== SENSOR ===== */ json.set("sensor1", tf1); json.set("sensor2", tf2); json.set("sensor3", tf3); /* ===== STATISTIK ===== */ json.set("min", tMin); json.set("max", tMax); json.set("rata_rata", tAvg); /* ===== PREDIKSI ===== */ json.set("prediksi", prediksi); /* ===== ERROR ===== */ json.set("error", errorTerakhir); /* ===== STATUS ===== */ json.set("status/pemanas", digitalRead(RELAY_HEATER) == LOW ? "ON" : "OFF"); json.set("status/kipas_panas", digitalRead(RELAY_FAN_HOT) == LOW ? "ON" : "OFF"); json.set("status/kipas_dingin", digitalRead(RELAY_FAN_COLD) == LOW ? "ON" : "OFF"); /* ===== KIRIM KE FIREBASE ===== */ if (Firebase.RTDB.setJSON(&fbdo, "/fermentasi", &json)) { } else { Serial.println("Firebase gagal"); Serial.println(fbdo.errorReason()); } } }