520 lines
12 KiB
C++
520 lines
12 KiB
C++
#include <ESP8266WiFi.h>
|
|
#include <Firebase_ESP_Client.h>
|
|
|
|
#include "addons/TokenHelper.h"
|
|
#include "addons/RTDBHelper.h"
|
|
|
|
#include <OneWire.h>
|
|
#include <DallasTemperature.h>
|
|
#include <SimpleKalmanFilter.h>
|
|
#include <time.h>
|
|
|
|
/* ================= 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());
|
|
}
|
|
}
|
|
} |