1147 lines
34 KiB
C++
1147 lines
34 KiB
C++
#include <WiFi.h>
|
|
#include <PubSubClient.h>
|
|
#include <Wire.h>
|
|
#include <LiquidCrystal_PCF8574.h>
|
|
#include <DHT.h>
|
|
#include "HX711.h"
|
|
|
|
// =====================================================
|
|
// WIFI
|
|
// =====================================================
|
|
const char* ssid = "Bemby 1";
|
|
const char* password = "BISMILLAH88";
|
|
|
|
// =====================================================
|
|
// MQTT
|
|
// =====================================================
|
|
const char* mqtt_server = "broker.hivemq.com";
|
|
const int mqtt_port = 1883;
|
|
WiFiClient espClient;
|
|
PubSubClient client(espClient);
|
|
|
|
// =====================================================
|
|
// MQTT TOPIC (Sesuai dengan Backend Railway)
|
|
// =====================================================
|
|
const char* topicData = "novil/pengering/data";
|
|
const char* topicStatus = "novil/pengering/status";
|
|
const char* topicControl = "novil/pengering/control";
|
|
const char* topicButton = "novil/pengering/button";
|
|
|
|
// =====================================================
|
|
// LCD
|
|
// =====================================================
|
|
LiquidCrystal_PCF8574 lcd(0x27);
|
|
|
|
// =====================================================
|
|
// BUTTON
|
|
// =====================================================
|
|
#define BUTTON_PIN 18
|
|
bool lastButtonState = HIGH;
|
|
|
|
// =====================================================
|
|
// DHT22
|
|
// =====================================================
|
|
#define DHTPIN 4
|
|
#define DHTTYPE DHT22
|
|
DHT dht(DHTPIN, DHTTYPE);
|
|
|
|
// =====================================================
|
|
// HX711
|
|
// =====================================================
|
|
#define DT1 25
|
|
#define SCK1 26
|
|
#define DT2 33 // Sesuai dengan kode test yang working
|
|
#define SCK2 32 // Sesuai dengan kode test yang working
|
|
HX711 scale1;
|
|
HX711 scale2;
|
|
|
|
// =====================================================
|
|
// RELAY
|
|
// =====================================================
|
|
#define RELAY_HEATER1 13 // RELAY1 - Heater 1
|
|
#define RELAY_HEATER2 12 // RELAY2 - Heater 2
|
|
#define RELAY_FAN 14 // RELAY3 - Fan
|
|
#define RELAY_EXHAUST 27 // RELAY4 - Exhaust
|
|
|
|
// =====================================================
|
|
// KALIBRASI LOADCELL
|
|
// =====================================================
|
|
float calibration_factor1 = 709.0; // Sesuai dengan kode test yang working
|
|
float calibration_factor2 = 709.0; // Sesuai dengan kode test yang working
|
|
|
|
// =====================================================
|
|
// VARIABEL
|
|
// =====================================================
|
|
float berat_awal = 0;
|
|
float berat_trigger = 0;
|
|
bool modePengeringan = false;
|
|
bool pengeringanSelesai = false;
|
|
bool beratTersimpan = false;
|
|
unsigned long waktuMulaiBerat = 0;
|
|
|
|
// =====================================================
|
|
// FUNCTION
|
|
// =====================================================
|
|
void tampilReady();
|
|
void tampilSelesai();
|
|
void kirimStatusRelay(float suhu, float berat);
|
|
void kirimDataMQTT(float suhu, float berat);
|
|
|
|
// =====================================================
|
|
// WIFI CONNECT
|
|
// =====================================================
|
|
void setup_wifi() {
|
|
Serial.println();
|
|
Serial.println("Starting WiFi connection...");
|
|
|
|
// Tampilkan status connecting di LCD
|
|
lcd.clear();
|
|
delay(50);
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Connecting WiFi");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Please wait");
|
|
|
|
Serial.print("Connecting to: ");
|
|
Serial.println(ssid);
|
|
|
|
WiFi.begin(ssid, password);
|
|
|
|
int attempts = 0;
|
|
int maxAttempts = 20;
|
|
|
|
// Loop dengan update LCD setiap detik untuk mencegah stuck
|
|
while (WiFi.status() != WL_CONNECTED && attempts < maxAttempts) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
|
|
// Update LCD setiap 2 attempts (1 detik)
|
|
if (attempts % 2 == 0) {
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(" "); // Clear line
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Wait ");
|
|
lcd.print(attempts / 2);
|
|
lcd.print("s/");
|
|
lcd.print(maxAttempts / 2);
|
|
lcd.print("s");
|
|
}
|
|
|
|
attempts++;
|
|
}
|
|
|
|
Serial.println(); // New line after dots
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
Serial.println("WiFi Connected");
|
|
Serial.print("IP Address : ");
|
|
Serial.println(WiFi.localIP());
|
|
|
|
// Tampilkan WiFi connected di LCD
|
|
lcd.clear();
|
|
delay(50);
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("WiFi Connected!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(WiFi.localIP());
|
|
delay(2000);
|
|
} else {
|
|
Serial.println("WiFi Connection Failed!");
|
|
|
|
// Tampilkan error di LCD
|
|
lcd.clear();
|
|
delay(50);
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("WiFi Failed!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Check settings");
|
|
delay(3000);
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// MQTT CALLBACK
|
|
// =====================================================
|
|
void callback(char* topic, byte* payload, unsigned int length) {
|
|
String message = "";
|
|
for (int i = 0; i < length; i++) {
|
|
message += (char)payload[i];
|
|
}
|
|
|
|
Serial.print("MQTT Message [");
|
|
Serial.print(topic);
|
|
Serial.print("] : ");
|
|
Serial.println(message);
|
|
|
|
if (String(topic) == topicControl) {
|
|
// =================================================
|
|
// HEATER 1
|
|
// =================================================
|
|
if (message == "HEATER_ON") {
|
|
digitalWrite(RELAY_HEATER1, LOW);
|
|
digitalWrite(RELAY_HEATER2, LOW);
|
|
Serial.println("HEATER 1 & 2 ON");
|
|
}
|
|
else if (message == "HEATER_OFF") {
|
|
digitalWrite(RELAY_HEATER1, HIGH);
|
|
digitalWrite(RELAY_HEATER2, HIGH);
|
|
Serial.println("HEATER 1 & 2 OFF");
|
|
}
|
|
// =================================================
|
|
// FAN
|
|
// =================================================
|
|
else if (message == "FAN_ON") {
|
|
digitalWrite(RELAY_FAN, LOW);
|
|
Serial.println("FAN ON");
|
|
}
|
|
else if (message == "FAN_OFF") {
|
|
digitalWrite(RELAY_FAN, HIGH);
|
|
Serial.println("FAN OFF");
|
|
}
|
|
// =================================================
|
|
// LAMP (tidak digunakan, untuk kompatibilitas)
|
|
// =================================================
|
|
else if (message == "LAMP_ON") {
|
|
Serial.println("LAMP ON (tidak tersedia)");
|
|
}
|
|
else if (message == "LAMP_OFF") {
|
|
Serial.println("LAMP OFF (tidak tersedia)");
|
|
}
|
|
// =================================================
|
|
// EXHAUST
|
|
// =================================================
|
|
else if (message == "EXHAUST_ON") {
|
|
digitalWrite(RELAY_EXHAUST, LOW);
|
|
Serial.println("EXHAUST ON");
|
|
}
|
|
else if (message == "EXHAUST_OFF") {
|
|
digitalWrite(RELAY_EXHAUST, HIGH);
|
|
Serial.println("EXHAUST OFF");
|
|
}
|
|
// =================================================
|
|
// START MQTT (dari Flutter via Backend)
|
|
// =================================================
|
|
else if (message == "START") {
|
|
if (modePengeringan || pengeringanSelesai) {
|
|
Serial.println("START DIABAIKAN - Sistem sedang berjalan atau selesai");
|
|
client.publish(topicStatus, "TIDAK BISA START SEKARANG");
|
|
return;
|
|
}
|
|
|
|
Serial.println("START MQTT (dari aplikasi)");
|
|
|
|
// Tare HX711 saat mulai pengeringan dengan timeout
|
|
Serial.println("Taring scales...");
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Calibrating...");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Please wait");
|
|
|
|
// Cek apakah scale ready sebelum tare
|
|
bool scale1Ready = scale1.is_ready();
|
|
bool scale2Ready = scale2.is_ready();
|
|
|
|
Serial.print("Scale 1 ready: ");
|
|
Serial.println(scale1Ready ? "YES" : "NO");
|
|
Serial.print("Scale 2 ready: ");
|
|
Serial.println(scale2Ready ? "YES" : "NO");
|
|
|
|
if (scale1Ready && scale2Ready) {
|
|
// Tare dengan tracking waktu
|
|
unsigned long tareStart = millis();
|
|
scale1.tare();
|
|
unsigned long tare1Time = millis() - tareStart;
|
|
Serial.print("Scale 1 tared in ");
|
|
Serial.print(tare1Time);
|
|
Serial.println("ms");
|
|
delay(100);
|
|
|
|
tareStart = millis();
|
|
scale2.tare();
|
|
unsigned long tare2Time = millis() - tareStart;
|
|
Serial.print("Scale 2 tared in ");
|
|
Serial.print(tare2Time);
|
|
Serial.println("ms");
|
|
delay(100);
|
|
|
|
Serial.println("Scales tare complete");
|
|
} else {
|
|
Serial.println("Scale(s) NOT READY - skipping tare");
|
|
|
|
if (!scale1Ready) Serial.println("Scale 1 not responding");
|
|
if (!scale2Ready) Serial.println("Scale 2 not responding");
|
|
|
|
client.publish(topicStatus, "WARNING: Scale not ready");
|
|
}
|
|
|
|
modePengeringan = true;
|
|
pengeringanSelesai = false;
|
|
beratTersimpan = false;
|
|
berat_awal = 0;
|
|
berat_trigger = 0;
|
|
waktuMulaiBerat = 0;
|
|
lcd.clear();
|
|
client.publish(topicStatus, "PENGERINGAN DIMULAI");
|
|
}
|
|
// =================================================
|
|
// RESET MQTT (dari Flutter via Backend)
|
|
// =================================================
|
|
else if (message == "RESET") {
|
|
Serial.println("RESET MQTT (dari aplikasi)");
|
|
modePengeringan = false;
|
|
pengeringanSelesai = false;
|
|
beratTersimpan = false;
|
|
berat_awal = 0;
|
|
berat_trigger = 0;
|
|
waktuMulaiBerat = 0;
|
|
|
|
// Matikan semua relay
|
|
digitalWrite(RELAY_HEATER1, HIGH);
|
|
digitalWrite(RELAY_HEATER2, HIGH);
|
|
digitalWrite(RELAY_FAN, HIGH);
|
|
digitalWrite(RELAY_EXHAUST, HIGH);
|
|
|
|
lcd.clear();
|
|
tampilReady();
|
|
client.publish(topicStatus, "PENGERINGAN SIAP");
|
|
}
|
|
// =================================================
|
|
// TARE COMMAND (Kalibrasi ulang load cell ke 0)
|
|
// =================================================
|
|
else if (message == "TARE") {
|
|
Serial.println("TARE COMMAND received");
|
|
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Taring...");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Remove all load");
|
|
|
|
delay(2000);
|
|
|
|
bool scale1Ready = scale1.wait_ready_timeout(1000);
|
|
bool scale2Ready = scale2.wait_ready_timeout(1000);
|
|
|
|
if (scale1Ready && scale2Ready) {
|
|
scale1.tare(10);
|
|
delay(100);
|
|
scale2.tare(10);
|
|
delay(100);
|
|
|
|
Serial.println("Manual tare successful");
|
|
client.publish(topicStatus, "TARE COMPLETED");
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Done! ");
|
|
delay(1000);
|
|
} else {
|
|
Serial.println("TARE FAILED - scales not ready");
|
|
client.publish(topicStatus, "TARE FAILED");
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Failed! ");
|
|
delay(2000);
|
|
}
|
|
|
|
lcd.clear();
|
|
tampilReady();
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// MQTT CONNECT
|
|
// =====================================================
|
|
void reconnect() {
|
|
int attempts = 0;
|
|
int maxAttempts = 3;
|
|
|
|
while (!client.connected() && attempts < maxAttempts) {
|
|
Serial.print("Connecting MQTT (attempt ");
|
|
Serial.print(attempts + 1);
|
|
Serial.print("/");
|
|
Serial.print(maxAttempts);
|
|
Serial.println(")...");
|
|
|
|
// Tampilkan status di LCD dengan update setiap attempt
|
|
if (modePengeringan == false && pengeringanSelesai == false) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Connecting MQTT");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Attempt ");
|
|
lcd.print(attempts + 1);
|
|
lcd.print("/");
|
|
lcd.print(maxAttempts);
|
|
}
|
|
|
|
// Generate unique client ID
|
|
String clientId = "ESP32_PENGERING_";
|
|
clientId += String(random(0xffff), HEX);
|
|
|
|
if (client.connect(clientId.c_str())) {
|
|
Serial.println("MQTT Connected");
|
|
Serial.print("Client ID: ");
|
|
Serial.println(clientId);
|
|
|
|
// Subscribe ke topic control
|
|
client.subscribe(topicControl);
|
|
Serial.print("Subscribed to: ");
|
|
Serial.println(topicControl);
|
|
|
|
// Publish status connected
|
|
client.publish(topicStatus, "ESP32 CONNECTED");
|
|
|
|
// Tampilkan MQTT connected di LCD
|
|
if (modePengeringan == false && pengeringanSelesai == false) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("MQTT Connected!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("System Ready");
|
|
delay(2000);
|
|
}
|
|
|
|
return; // Keluar dari fungsi setelah berhasil connect
|
|
}
|
|
else {
|
|
Serial.print("MQTT Failed, rc=");
|
|
Serial.print(client.state());
|
|
Serial.println(" retrying...");
|
|
|
|
// Update LCD menunjukkan gagal
|
|
if (modePengeringan == false && pengeringanSelesai == false) {
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Failed! Retry...");
|
|
}
|
|
|
|
attempts++;
|
|
|
|
if (attempts < maxAttempts) {
|
|
delay(2000); // Tunggu sebelum retry
|
|
}
|
|
}
|
|
}
|
|
|
|
// Jika gagal setelah semua attempts
|
|
if (!client.connected()) {
|
|
Serial.println("MQTT Connection failed after all attempts");
|
|
|
|
if (modePengeringan == false && pengeringanSelesai == false) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("MQTT Failed!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Check network");
|
|
delay(3000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// SETUP
|
|
// =====================================================
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
delay(1000); // Beri waktu serial monitor untuk ready
|
|
|
|
Serial.println();
|
|
Serial.println("=================================");
|
|
Serial.println("ESP32 Pengering Ikan - Railway");
|
|
Serial.println("=================================");
|
|
|
|
// Setup I2C untuk LCD
|
|
Serial.println("Initializing I2C...");
|
|
Wire.begin(21, 22);
|
|
delay(100);
|
|
|
|
// Setup LCD dengan error handling
|
|
Serial.println("Initializing LCD...");
|
|
lcd.begin(16, 2);
|
|
delay(100);
|
|
|
|
lcd.setBacklight(255);
|
|
delay(100);
|
|
|
|
lcd.clear();
|
|
delay(100);
|
|
|
|
// Test LCD - tampilkan pesan booting
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("ESP32 Pengering");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Initializing...");
|
|
delay(2000);
|
|
|
|
Serial.println("LCD initialized successfully");
|
|
|
|
// Setup WiFi
|
|
setup_wifi();
|
|
|
|
// Setup MQTT
|
|
client.setServer(mqtt_server, mqtt_port);
|
|
client.setCallback(callback);
|
|
client.setKeepAlive(60);
|
|
|
|
// Setup Button
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
|
|
// Setup DHT22
|
|
Serial.println("Initializing DHT22...");
|
|
dht.begin();
|
|
delay(100);
|
|
|
|
// Setup HX711
|
|
Serial.println("Initializing HX711...");
|
|
|
|
// Inisialisasi HX711 (style kode test yang working)
|
|
scale1.begin(DT1, SCK1);
|
|
Serial.println("HX711 #1 begin OK");
|
|
|
|
scale2.begin(DT2, SCK2);
|
|
Serial.println("HX711 #2 begin OK");
|
|
|
|
scale1.set_scale(calibration_factor1);
|
|
scale2.set_scale(calibration_factor2);
|
|
Serial.println("Calibration factor applied");
|
|
|
|
// Update LCD
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Checking scales");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Please wait...");
|
|
|
|
// Menunggu HX711 siap dengan timeout 10 detik (style kode test)
|
|
Serial.println("Menunggu HX711 siap...");
|
|
unsigned long startTime = millis();
|
|
bool scale1Ready = false;
|
|
bool scale2Ready = false;
|
|
|
|
while ((!scale1.is_ready() || !scale2.is_ready()) &&
|
|
millis() - startTime < 10000) {
|
|
|
|
scale1Ready = scale1.is_ready();
|
|
scale2Ready = scale2.is_ready();
|
|
|
|
Serial.print("HX1: ");
|
|
Serial.print(scale1Ready ? "1" : "0");
|
|
Serial.print(" | HX2: ");
|
|
Serial.println(scale2Ready ? "1" : "0");
|
|
|
|
// Update LCD dengan status
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("HX1:");
|
|
lcd.print(scale1Ready ? "OK" : "..");
|
|
lcd.print(" HX2:");
|
|
lcd.print(scale2Ready ? "OK" : "..");
|
|
lcd.print(" ");
|
|
|
|
delay(500);
|
|
|
|
// Jika keduanya sudah ready, keluar dari loop
|
|
if (scale1Ready && scale2Ready) break;
|
|
}
|
|
|
|
Serial.println();
|
|
|
|
// Check final status
|
|
scale1Ready = scale1.is_ready();
|
|
scale2Ready = scale2.is_ready();
|
|
|
|
if (!scale1Ready) {
|
|
Serial.println("ERROR: HX711 #1 tidak terdeteksi!");
|
|
} else {
|
|
Serial.println("HX711 #1 siap");
|
|
}
|
|
|
|
if (!scale2Ready) {
|
|
Serial.println("ERROR: HX711 #2 tidak terdeteksi!");
|
|
} else {
|
|
Serial.println("HX711 #2 siap");
|
|
}
|
|
|
|
// Tare hanya jika siap (style kode test)
|
|
if (scale1Ready) {
|
|
Serial.println("Tare HX711 #1...");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Taring scale 1..");
|
|
scale1.tare();
|
|
delay(100);
|
|
}
|
|
|
|
if (scale2Ready) {
|
|
Serial.println("Tare HX711 #2...");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Taring scale 2..");
|
|
scale2.tare();
|
|
delay(100);
|
|
}
|
|
|
|
if (scale1Ready || scale2Ready) {
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Tare complete! ");
|
|
delay(1000);
|
|
} else {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Scale Warning!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Check wiring");
|
|
delay(3000);
|
|
}
|
|
|
|
Serial.println("HX711 Setup selesai");
|
|
|
|
// Setup Relay (Active LOW)
|
|
pinMode(RELAY_HEATER1, OUTPUT);
|
|
pinMode(RELAY_HEATER2, OUTPUT);
|
|
pinMode(RELAY_FAN, OUTPUT);
|
|
pinMode(RELAY_EXHAUST, OUTPUT);
|
|
|
|
// Matikan semua relay
|
|
digitalWrite(RELAY_HEATER1, HIGH);
|
|
digitalWrite(RELAY_HEATER2, HIGH);
|
|
digitalWrite(RELAY_FAN, HIGH);
|
|
digitalWrite(RELAY_EXHAUST, HIGH);
|
|
|
|
// Tampilkan ready
|
|
lcd.clear();
|
|
delay(100);
|
|
tampilReady();
|
|
delay(500);
|
|
|
|
Serial.println("Setup Complete!");
|
|
Serial.println("=================================");
|
|
}
|
|
|
|
// =====================================================
|
|
// LOOP
|
|
// =====================================================
|
|
void loop() {
|
|
// =====================================================
|
|
// MQTT Connection Check
|
|
// =====================================================
|
|
if (!client.connected()) {
|
|
reconnect();
|
|
}
|
|
client.loop();
|
|
|
|
// =====================================================
|
|
// BUTTON
|
|
// =====================================================
|
|
bool buttonState = digitalRead(BUTTON_PIN);
|
|
if (lastButtonState == HIGH && buttonState == LOW) {
|
|
Serial.println("BUTTON PRESSED");
|
|
client.publish(topicButton, "BUTTON PRESSED");
|
|
|
|
// =================================================
|
|
// STATE: READY TO DRY
|
|
// Tekan button → mulai pengeringan
|
|
// =================================================
|
|
if (!modePengeringan && !pengeringanSelesai) {
|
|
Serial.println("START PENGERINGAN");
|
|
|
|
// Tare HX711 saat mulai pengeringan (style kode test)
|
|
Serial.println("Taring scales...");
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Zeroing...");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Remove all load");
|
|
|
|
delay(2000); // Kasih waktu user untuk pastikan rak kosong
|
|
|
|
// Cek apakah scale ready sebelum tare
|
|
bool scale1Ready = scale1.is_ready();
|
|
bool scale2Ready = scale2.is_ready();
|
|
|
|
Serial.print("Scale 1 ready: ");
|
|
Serial.println(scale1Ready ? "YES" : "NO");
|
|
Serial.print("Scale 2 ready: ");
|
|
Serial.println(scale2Ready ? "YES" : "NO");
|
|
|
|
bool tareSuccess = false;
|
|
|
|
if (scale1Ready && scale2Ready) {
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Calibrating... ");
|
|
|
|
scale1.tare();
|
|
Serial.println("Scale 1 tared");
|
|
delay(100);
|
|
|
|
scale2.tare();
|
|
Serial.println("Scale 2 tared");
|
|
delay(100);
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Done! Put fish ");
|
|
delay(1000);
|
|
|
|
tareSuccess = true;
|
|
} else {
|
|
if (!scale1Ready) Serial.println("Scale 1 not ready!");
|
|
if (!scale2Ready) Serial.println("Scale 2 not ready!");
|
|
}
|
|
|
|
if (!tareSuccess) {
|
|
Serial.println("WARNING: Tare FAILED");
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Scale Error! ");
|
|
delay(2000);
|
|
|
|
// Batal start jika scale tidak ready
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Cannot Start!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Check scales");
|
|
delay(3000);
|
|
|
|
lcd.clear();
|
|
tampilReady();
|
|
|
|
lastButtonState = buttonState;
|
|
return; // Keluar dari loop, jangan lanjut start
|
|
}
|
|
|
|
Serial.println("Scales tare complete");
|
|
|
|
// Publish ke topicButton dan topicControl
|
|
client.publish(topicButton, "START_BUTTON");
|
|
client.publish(topicControl, "START");
|
|
|
|
modePengeringan = true;
|
|
pengeringanSelesai = false;
|
|
beratTersimpan = false;
|
|
berat_awal = 0;
|
|
berat_trigger = 0;
|
|
waktuMulaiBerat = 0;
|
|
|
|
lcd.clear();
|
|
client.publish(topicStatus, "PENGERINGAN DIMULAI");
|
|
}
|
|
|
|
delay(300);
|
|
}
|
|
lastButtonState = buttonState;
|
|
|
|
// =====================================================
|
|
// MODE READY — belum ada button press
|
|
// =====================================================
|
|
if (!modePengeringan) {
|
|
tampilReady();
|
|
delay(500);
|
|
return;
|
|
}
|
|
|
|
// =====================================================
|
|
// MODE PENGERINGAN
|
|
// =====================================================
|
|
|
|
// =====================================================
|
|
// DHT22
|
|
// =====================================================
|
|
float suhu = dht.readTemperature();
|
|
if (isnan(suhu)) {
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("DHT ERROR");
|
|
Serial.println("DHT ERROR");
|
|
client.publish(topicStatus, "DHT ERROR");
|
|
delay(1000);
|
|
return;
|
|
}
|
|
|
|
// =====================================================
|
|
// HX711
|
|
// =====================================================
|
|
float berat1 = 0;
|
|
float berat2 = 0;
|
|
bool scale1Valid = false;
|
|
bool scale2Valid = false;
|
|
|
|
// Cek dan baca scale 1 (style kode test)
|
|
if (scale1.is_ready()) {
|
|
berat1 = scale1.get_units(5);
|
|
scale1Valid = true;
|
|
} else {
|
|
Serial.println("WARNING: Scale 1 not ready!");
|
|
}
|
|
|
|
// Cek dan baca scale 2 (style kode test)
|
|
if (scale2.is_ready()) {
|
|
berat2 = scale2.get_units(5);
|
|
scale2Valid = true;
|
|
} else {
|
|
Serial.println("WARNING: Scale 2 not ready!");
|
|
}
|
|
|
|
// Jika kedua scale tidak ready, skip loop ini
|
|
if (!scale1Valid && !scale2Valid) {
|
|
Serial.println("ERROR: Kedua load cell tidak ready!");
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Scale Error!");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Check wiring");
|
|
client.publish(topicStatus, "LOAD CELL ERROR");
|
|
delay(2000);
|
|
return;
|
|
}
|
|
|
|
// =====================================================
|
|
// ANTI NOISE
|
|
// =====================================================
|
|
if (abs(berat1) < 0.5) berat1 = 0;
|
|
if (abs(berat2) < 0.5) berat2 = 0;
|
|
|
|
// =====================================================
|
|
// TOTAL BERAT (Jumlah dari kedua load cell)
|
|
// =====================================================
|
|
float berat = berat1 + berat2;
|
|
|
|
// =====================================================
|
|
// DETEKSI IKAN
|
|
// =====================================================
|
|
if (!beratTersimpan && berat > 50 && waktuMulaiBerat == 0) {
|
|
waktuMulaiBerat = millis();
|
|
Serial.println("IKAN TERDETEKSI");
|
|
client.publish(topicStatus, "SCAN BERAT...");
|
|
}
|
|
|
|
// =====================================================
|
|
// SIMPAN BERAT AWAL
|
|
// =====================================================
|
|
if (!beratTersimpan &&
|
|
waktuMulaiBerat > 0 &&
|
|
millis() - waktuMulaiBerat >= 5000) {
|
|
|
|
berat_awal = berat;
|
|
float berat_daging = berat_awal * 0.3;
|
|
float berat_air_awal = berat_awal * 0.7;
|
|
float sisa_air = berat_air_awal * 0.4;
|
|
berat_trigger = berat_daging + sisa_air;
|
|
beratTersimpan = true;
|
|
|
|
Serial.println("==================================");
|
|
Serial.println("BERAT AWAL TERSIMPAN");
|
|
Serial.print("Berat Awal : ");
|
|
Serial.print(berat_awal, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Berat Daging : ");
|
|
Serial.print(berat_daging, 2);
|
|
Serial.println(" g (30%)");
|
|
Serial.print("Berat Air Awal : ");
|
|
Serial.print(berat_air_awal, 2);
|
|
Serial.println(" g (70%)");
|
|
Serial.print("Sisa Air Target : ");
|
|
Serial.print(sisa_air, 2);
|
|
Serial.println(" g (40% dari air)");
|
|
Serial.print("Target Akhir : ");
|
|
Serial.print(berat_trigger, 2);
|
|
Serial.println(" g");
|
|
Serial.println("==================================");
|
|
|
|
String msg = "PENGERINGAN BERJALAN - Awal:";
|
|
msg += String(berat_awal, 0);
|
|
msg += "g Target:";
|
|
msg += String(berat_trigger, 0);
|
|
msg += "g";
|
|
client.publish(topicStatus, msg.c_str());
|
|
}
|
|
|
|
// =====================================================
|
|
// TARGET TERCAPAI → SELESAI
|
|
// =====================================================
|
|
if (beratTersimpan && berat <= berat_trigger) {
|
|
|
|
static unsigned long targetReachedTime = 0;
|
|
static bool targetReachedFlag = false;
|
|
|
|
// Pertama kali target tercapai
|
|
if (!targetReachedFlag) {
|
|
targetReachedTime = millis();
|
|
targetReachedFlag = true;
|
|
Serial.println("==================================");
|
|
Serial.println("TARGET TERCAPAI - Verifikasi 10 detik...");
|
|
Serial.print("Berat Sekarang: ");
|
|
Serial.print(berat, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Target: ");
|
|
Serial.print(berat_trigger, 2);
|
|
Serial.println(" g");
|
|
Serial.println("==================================");
|
|
client.publish(topicStatus, "Verifikasi target...");
|
|
}
|
|
|
|
// Tunggu 10 detik untuk memastikan berat stabil di bawah target
|
|
if (millis() - targetReachedTime >= 10000) {
|
|
// Setelah 10 detik masih di bawah target, baru selesai
|
|
modePengeringan = false;
|
|
pengeringanSelesai = true;
|
|
targetReachedFlag = false; // Reset flag
|
|
|
|
// Matikan heater 1, 2 dan fan, nyalakan exhaust untuk cooling
|
|
digitalWrite(RELAY_HEATER1, HIGH);
|
|
digitalWrite(RELAY_HEATER2, HIGH);
|
|
digitalWrite(RELAY_FAN, HIGH);
|
|
digitalWrite(RELAY_EXHAUST, LOW); // EXHAUST ON
|
|
|
|
Serial.println("==================================");
|
|
Serial.println("PENGERINGAN SELESAI");
|
|
Serial.print("Berat Awal : ");
|
|
Serial.print(berat_awal, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Berat Akhir : ");
|
|
Serial.print(berat, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Target : ");
|
|
Serial.print(berat_trigger, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Susut : ");
|
|
Serial.print(berat_awal - berat, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Persen Susut: ");
|
|
Serial.print(((berat_awal - berat) / berat_awal) * 100, 1);
|
|
Serial.println(" %");
|
|
Serial.println("==================================");
|
|
|
|
client.publish(topicStatus, "PENGERINGAN SELESAI");
|
|
|
|
// Kirim data dengan status COMPLETED untuk trigger notifikasi
|
|
kirimStatusRelay(suhu, berat);
|
|
|
|
// Tampilkan selesai selama 5 detik
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("PENGERINGAN");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("SELESAI");
|
|
delay(5000);
|
|
|
|
// Exhaust jalan 20 detik untuk buang panas
|
|
lcd.clear();
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Cooling down...");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("20 seconds");
|
|
|
|
// Countdown 20 detik
|
|
for (int i = 20; i > 0; i--) {
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(String(i) + " seconds left");
|
|
delay(1000);
|
|
|
|
// Tetap kirim MQTT status relay saat cooling
|
|
kirimStatusRelay(suhu, berat);
|
|
client.loop();
|
|
}
|
|
|
|
// Matikan exhaust dan kembali ke ready
|
|
digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF
|
|
|
|
// Reset variabel
|
|
pengeringanSelesai = false;
|
|
beratTersimpan = false;
|
|
berat_awal = 0;
|
|
berat_trigger = 0;
|
|
waktuMulaiBerat = 0;
|
|
|
|
lcd.clear();
|
|
tampilReady();
|
|
client.publish(topicStatus, "PENGERINGAN SIAP");
|
|
|
|
// Kirim status relay setelah reset
|
|
kirimStatusRelay(suhu, berat);
|
|
|
|
Serial.println("Sistem kembali ke READY");
|
|
|
|
delay(500);
|
|
return;
|
|
}
|
|
} else {
|
|
// Berat naik lagi atau di atas target, reset flag verifikasi
|
|
static bool targetReachedFlag = false;
|
|
targetReachedFlag = false;
|
|
}
|
|
|
|
// =====================================================
|
|
// RELAY CONTROL berdasarkan status dan suhu
|
|
// Relay HANYA ON setelah berat_awal & target tersimpan
|
|
// =====================================================
|
|
if (beratTersimpan) {
|
|
// Debug: Print status berat setiap 5 detik
|
|
static unsigned long lastDebug = 0;
|
|
if (millis() - lastDebug > 5000) {
|
|
Serial.println("--- STATUS PENGERINGAN ---");
|
|
Serial.print("Berat Sekarang : ");
|
|
Serial.print(berat, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Target : ");
|
|
Serial.print(berat_trigger, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Selisih : ");
|
|
Serial.print(berat - berat_trigger, 2);
|
|
Serial.println(" g");
|
|
Serial.print("Kondisi : ");
|
|
if (berat <= berat_trigger) {
|
|
Serial.println("TARGET TERCAPAI ✓");
|
|
} else {
|
|
Serial.println("MASIH MENGERINGKAN...");
|
|
}
|
|
Serial.println("-------------------------");
|
|
lastDebug = millis();
|
|
}
|
|
|
|
// Berat sudah tersimpan, mulai pengeringan
|
|
|
|
// FAN selalu ON saat pengeringan
|
|
digitalWrite(RELAY_FAN, LOW); // FAN ON
|
|
|
|
// Kontrol HEATER dan EXHAUST berdasarkan suhu
|
|
if (suhu >= 55.0) {
|
|
// Suhu sudah tinggi (≥55°C)
|
|
digitalWrite(RELAY_HEATER1, HIGH); // HEATER 1 OFF
|
|
digitalWrite(RELAY_HEATER2, HIGH); // HEATER 2 OFF
|
|
digitalWrite(RELAY_EXHAUST, LOW); // EXHAUST ON untuk buang panas
|
|
// Serial.println("Suhu >= 55C: HEATER OFF, EXHAUST ON");
|
|
} else {
|
|
// Suhu masih rendah (<55°C)
|
|
digitalWrite(RELAY_HEATER1, LOW); // HEATER 1 ON
|
|
digitalWrite(RELAY_HEATER2, LOW); // HEATER 2 ON
|
|
digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF
|
|
// Serial.println("Suhu < 55C: HEATER ON, EXHAUST OFF");
|
|
}
|
|
} else {
|
|
// Masih scanning berat, relay tetap OFF
|
|
digitalWrite(RELAY_HEATER1, HIGH); // HEATER 1 OFF
|
|
digitalWrite(RELAY_HEATER2, HIGH); // HEATER 2 OFF
|
|
digitalWrite(RELAY_FAN, HIGH); // FAN OFF
|
|
digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF
|
|
}
|
|
|
|
// =====================================================
|
|
// LCD BARIS 1
|
|
// =====================================================
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("S:");
|
|
lcd.print(suhu, 1);
|
|
lcd.print("C");
|
|
lcd.setCursor(9, 0);
|
|
lcd.print("B:");
|
|
lcd.print(berat, 0);
|
|
lcd.print("g");
|
|
|
|
// =====================================================
|
|
// LCD BARIS 2
|
|
// =====================================================
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 1);
|
|
if (!beratTersimpan) {
|
|
lcd.print("Scan Berat...");
|
|
}
|
|
else {
|
|
lcd.print("T:");
|
|
lcd.print(berat_trigger, 0);
|
|
lcd.print("g");
|
|
}
|
|
|
|
// =====================================================
|
|
// MQTT DATA (kirim ke Backend Railway)
|
|
// =====================================================
|
|
kirimStatusRelay(suhu, berat);
|
|
|
|
delay(1000);
|
|
}
|
|
|
|
// =====================================================
|
|
// MQTT DATA dengan Status Relay (Format JSON untuk Backend Railway)
|
|
// =====================================================
|
|
void kirimStatusRelay(float suhu, float berat) {
|
|
// Baca status relay saat ini
|
|
bool heater1_on = (digitalRead(RELAY_HEATER1) == LOW);
|
|
bool heater2_on = (digitalRead(RELAY_HEATER2) == LOW);
|
|
bool fan_on = (digitalRead(RELAY_FAN) == LOW);
|
|
bool exhaust_on = (digitalRead(RELAY_EXHAUST) == LOW);
|
|
|
|
// Tentukan status string
|
|
String statusString = "UNKNOWN";
|
|
if (pengeringanSelesai) {
|
|
statusString = "SELESAI";
|
|
} else if (modePengeringan && beratTersimpan) {
|
|
statusString = "BERJALAN";
|
|
} else if (modePengeringan && !beratTersimpan) {
|
|
statusString = "SCANNING";
|
|
} else {
|
|
statusString = "READY";
|
|
}
|
|
|
|
// Format JSON dengan status relay DAN status
|
|
String payload = "{";
|
|
payload += "\"suhu\":" + String(suhu, 1) + ",";
|
|
payload += "\"berat\":" + String(berat, 0) + ",";
|
|
payload += "\"target\":" + String(berat_trigger, 0) + ",";
|
|
payload += "\"relay1\":" + String(heater1_on ? "true" : "false") + ",";
|
|
payload += "\"relay2\":" + String(heater2_on ? "true" : "false") + ",";
|
|
payload += "\"relay3\":" + String(fan_on ? "true" : "false") + ",";
|
|
payload += "\"relay4\":" + String(exhaust_on ? "true" : "false") + ",";
|
|
payload += "\"status\":\"" + statusString + "\"";
|
|
payload += "}";
|
|
|
|
// Publish ke topic data
|
|
bool success = client.publish(topicData, payload.c_str());
|
|
|
|
if (success) {
|
|
Serial.print("MQTT Data Sent: ");
|
|
Serial.println(payload);
|
|
} else {
|
|
Serial.println("MQTT Data Failed to Send");
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// MQTT DATA (Backward compatibility - deprecated)
|
|
// =====================================================
|
|
void kirimDataMQTT(float suhu, float berat) {
|
|
kirimStatusRelay(suhu, berat);
|
|
}
|
|
|
|
// =====================================================
|
|
// READY LCD
|
|
// =====================================================
|
|
void tampilReady() {
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("Ready to Dry");
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("Press Button");
|
|
}
|
|
|
|
// =====================================================
|
|
// SELESAI LCD
|
|
// =====================================================
|
|
void tampilSelesai() {
|
|
lcd.setCursor(0, 0);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 0);
|
|
lcd.print("PENGERINGAN");
|
|
|
|
lcd.setCursor(0, 1);
|
|
lcd.print(" ");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("SELESAI");
|
|
}
|