Fix notifikasi dan tambah auto-disconnect detection
- Backend: mapping status ESP32 (SELESAI -> COMPLETED) - Backend: auto-detect disconnect setelah 30 detik no data - ESP32: kirim status relay setelah pengeringan selesai - Flutter: tambah debug log dan UI untuk status DISCONNECTED - Flutter: perbaiki overflow di device card - Notifikasi pengeringan selesai sudah berfungsi dengan baik
This commit is contained in:
parent
ef9a79df9e
commit
349a275594
|
|
@ -153,6 +153,16 @@ async function insertSensorData(data) {
|
|||
const relay3 = data.relay3 ? 1 : 0;
|
||||
const relay4 = data.relay4 ? 1 : 0;
|
||||
|
||||
// Debug logging
|
||||
console.log('📝 Inserting sensor data:');
|
||||
console.log(' Raw relay values:', {
|
||||
relay1_raw: data.relay1,
|
||||
relay2_raw: data.relay2,
|
||||
relay3_raw: data.relay3,
|
||||
relay4_raw: data.relay4
|
||||
});
|
||||
console.log(' Converted relay values:', { relay1, relay2, relay3, relay4 });
|
||||
|
||||
const [result] = await pool.query(
|
||||
`INSERT INTO sensor_data (suhu, berat, target, relay1, relay2, relay3, relay4, status, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
console.log('🔄 SERVER STARTING - Version 1.0.3-relay-fix - ' + new Date().toISOString());
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const morgan = require('morgan');
|
||||
|
|
@ -50,6 +52,30 @@ let latestData = {
|
|||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Last data received timestamp
|
||||
let lastDataTimestamp = Date.now();
|
||||
|
||||
// Connection timeout (30 seconds)
|
||||
const CONNECTION_TIMEOUT = 30000;
|
||||
|
||||
// Check connection status periodically
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
const timeSinceLastData = now - lastDataTimestamp;
|
||||
|
||||
// If no data received for more than CONNECTION_TIMEOUT, mark as disconnected
|
||||
if (timeSinceLastData > CONNECTION_TIMEOUT && latestData.status !== 'DISCONNECTED') {
|
||||
console.log('⚠️ No data received for', Math.floor(timeSinceLastData / 1000), 'seconds');
|
||||
console.log(' Marking as DISCONNECTED');
|
||||
latestData.status = 'DISCONNECTED';
|
||||
// Optionally reset relay states
|
||||
latestData.relay1 = false;
|
||||
latestData.relay2 = false;
|
||||
latestData.relay3 = false;
|
||||
latestData.relay4 = false;
|
||||
}
|
||||
}, 5000); // Check every 5 seconds
|
||||
|
||||
// Initialize database on startup
|
||||
(async () => {
|
||||
try {
|
||||
|
|
@ -148,23 +174,62 @@ mqttClient.on('message', async (topic, message) => {
|
|||
try {
|
||||
const data = JSON.parse(msg);
|
||||
|
||||
// Debug: log parsed data
|
||||
console.log('📦 Parsed MQTT data:', JSON.stringify(data, null, 2));
|
||||
console.log(' Data type check:', {
|
||||
relay1_type: typeof data.relay1,
|
||||
relay1_value: data.relay1,
|
||||
relay2_type: typeof data.relay2,
|
||||
relay2_value: data.relay2,
|
||||
relay3_type: typeof data.relay3,
|
||||
relay3_value: data.relay3,
|
||||
relay4_type: typeof data.relay4,
|
||||
relay4_value: data.relay4
|
||||
});
|
||||
|
||||
// Get current time in WIB (UTC+7)
|
||||
const now = new Date();
|
||||
const wibTime = new Date(now.getTime() + (7 * 60 * 60 * 1000));
|
||||
|
||||
// Update last data timestamp
|
||||
lastDataTimestamp = Date.now();
|
||||
|
||||
// Helper function to map ESP32 status to backend status
|
||||
const mapStatus = (esp32Status) => {
|
||||
if (!esp32Status) return latestData.status;
|
||||
const statusMap = {
|
||||
'SELESAI': 'COMPLETED',
|
||||
'BERJALAN': 'RUNNING',
|
||||
'SCANNING': 'SCANNING',
|
||||
'READY': 'READY',
|
||||
'CONNECTED': 'CONNECTED',
|
||||
'ERROR': 'ERROR'
|
||||
};
|
||||
return statusMap[esp32Status.toUpperCase()] || esp32Status;
|
||||
};
|
||||
|
||||
// Update latestData dengan data dari ESP32 (termasuk status relay)
|
||||
// Use explicit check for undefined/null to handle false values correctly
|
||||
latestData = {
|
||||
suhu: data.suhu || 0,
|
||||
berat: data.berat || 0,
|
||||
target: data.target || 0,
|
||||
relay1: data.relay1 !== undefined ? data.relay1 : latestData.relay1, // Update dari ESP32
|
||||
relay2: data.relay2 !== undefined ? data.relay2 : latestData.relay2,
|
||||
relay3: data.relay3 !== undefined ? data.relay3 : latestData.relay3,
|
||||
relay4: data.relay4 !== undefined ? data.relay4 : latestData.relay4,
|
||||
status: latestData.status, // Keep current status
|
||||
relay1: (data.relay1 !== undefined && data.relay1 !== null) ? Boolean(data.relay1) : latestData.relay1,
|
||||
relay2: (data.relay2 !== undefined && data.relay2 !== null) ? Boolean(data.relay2) : latestData.relay2,
|
||||
relay3: (data.relay3 !== undefined && data.relay3 !== null) ? Boolean(data.relay3) : latestData.relay3,
|
||||
relay4: (data.relay4 !== undefined && data.relay4 !== null) ? Boolean(data.relay4) : latestData.relay4,
|
||||
status: mapStatus(data.status), // Map ESP32 status to backend status
|
||||
timestamp: wibTime.toISOString()
|
||||
};
|
||||
|
||||
console.log(' Final latestData relay values:', {
|
||||
relay1: latestData.relay1,
|
||||
relay2: latestData.relay2,
|
||||
relay3: latestData.relay3,
|
||||
relay4: latestData.relay4,
|
||||
status: latestData.status
|
||||
});
|
||||
|
||||
// Save to database
|
||||
try {
|
||||
await db.insertSensorData(latestData);
|
||||
|
|
@ -305,16 +370,34 @@ aedes.on('publish', async (packet, client) => {
|
|||
try {
|
||||
const data = JSON.parse(message);
|
||||
|
||||
// Update last data timestamp
|
||||
lastDataTimestamp = Date.now();
|
||||
|
||||
// Helper function to map ESP32 status to backend status
|
||||
const mapStatus = (esp32Status) => {
|
||||
if (!esp32Status) return latestData.status;
|
||||
const statusMap = {
|
||||
'SELESAI': 'COMPLETED',
|
||||
'BERJALAN': 'RUNNING',
|
||||
'SCANNING': 'SCANNING',
|
||||
'READY': 'READY',
|
||||
'CONNECTED': 'CONNECTED',
|
||||
'ERROR': 'ERROR'
|
||||
};
|
||||
return statusMap[esp32Status.toUpperCase()] || esp32Status;
|
||||
};
|
||||
|
||||
// Update latestData dengan data dari ESP32 (termasuk status relay)
|
||||
// Use explicit check for undefined/null to handle false values correctly
|
||||
latestData = {
|
||||
suhu: data.suhu || 0,
|
||||
berat: data.berat || 0,
|
||||
target: data.target || 0,
|
||||
relay1: data.relay1 !== undefined ? data.relay1 : latestData.relay1, // Update dari ESP32
|
||||
relay2: data.relay2 !== undefined ? data.relay2 : latestData.relay2,
|
||||
relay3: data.relay3 !== undefined ? data.relay3 : latestData.relay3,
|
||||
relay4: data.relay4 !== undefined ? data.relay4 : latestData.relay4,
|
||||
status: latestData.status, // Keep current status
|
||||
relay1: (data.relay1 !== undefined && data.relay1 !== null) ? Boolean(data.relay1) : latestData.relay1,
|
||||
relay2: (data.relay2 !== undefined && data.relay2 !== null) ? Boolean(data.relay2) : latestData.relay2,
|
||||
relay3: (data.relay3 !== undefined && data.relay3 !== null) ? Boolean(data.relay3) : latestData.relay3,
|
||||
relay4: (data.relay4 !== undefined && data.relay4 !== null) ? Boolean(data.relay4) : latestData.relay4,
|
||||
status: mapStatus(data.status), // Map ESP32 status to backend status
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
|
|
@ -466,7 +549,7 @@ app.get('/', (req, res) => {
|
|||
res.json({
|
||||
status: 'OK',
|
||||
message: 'Pengering Ikan Backend Server - MQTT Client Enabled',
|
||||
version: '1.0.1',
|
||||
version: '1.0.3-relay-fix', // Updated version with relay boolean fix
|
||||
uptime: process.uptime(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
|
@ -783,7 +866,7 @@ app.use((err, req, res, next) => {
|
|||
const server = app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log('');
|
||||
console.log('='.repeat(60));
|
||||
console.log('🚀 PENGERING IKAN BACKEND SERVER');
|
||||
console.log('🚀 PENGERING IKAN BACKEND SERVER v1.0.3-relay-fix');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`📡 REST API: http://0.0.0.0:${PORT}`);
|
||||
console.log(`🔗 MQTT Broker: ${MQTT_BROKER_URL}`);
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ DHT dht(DHTPIN, DHTTYPE);
|
|||
// =====================================================
|
||||
#define DT1 25
|
||||
#define SCK1 26
|
||||
#define DT2 32
|
||||
#define SCK2 33
|
||||
#define DT2 33 // Sesuai dengan kode test yang working
|
||||
#define SCK2 32 // Sesuai dengan kode test yang working
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
|
|
@ -64,10 +64,10 @@ HX711 scale2;
|
|||
#define RELAY_EXHAUST 27 // RELAY4 - Exhaust
|
||||
|
||||
// =====================================================
|
||||
// KALIBRASI
|
||||
// KALIBRASI LOADCELL
|
||||
// =====================================================
|
||||
float calibration_factor1 = 208.0;
|
||||
float calibration_factor2 = 208.0;
|
||||
float calibration_factor1 = 709.0; // Sesuai dengan kode test yang working
|
||||
float calibration_factor2 = 709.0; // Sesuai dengan kode test yang working
|
||||
|
||||
// =====================================================
|
||||
// VARIABEL
|
||||
|
|
@ -100,7 +100,7 @@ void setup_wifi() {
|
|||
lcd.setCursor(0, 0);
|
||||
lcd.print("Connecting WiFi");
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Please wait...");
|
||||
lcd.print("Please wait");
|
||||
|
||||
Serial.print("Connecting to: ");
|
||||
Serial.println(ssid);
|
||||
|
|
@ -108,14 +108,31 @@ void setup_wifi() {
|
|||
WiFi.begin(ssid, password);
|
||||
|
||||
int attempts = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
|
||||
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();
|
||||
Serial.println("WiFi Connected");
|
||||
Serial.print("IP Address : ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
|
@ -129,7 +146,6 @@ void setup_wifi() {
|
|||
lcd.print(WiFi.localIP());
|
||||
delay(2000);
|
||||
} else {
|
||||
Serial.println();
|
||||
Serial.println("WiFi Connection Failed!");
|
||||
|
||||
// Tampilkan error di LCD
|
||||
|
|
@ -231,24 +247,34 @@ void callback(char* topic, byte* payload, unsigned int length) {
|
|||
Serial.print("Scale 2 ready: ");
|
||||
Serial.println(scale2Ready ? "YES" : "NO");
|
||||
|
||||
if (scale1Ready) {
|
||||
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);
|
||||
Serial.println("Scale 1 tared");
|
||||
} else {
|
||||
Serial.println("Scale 1 NOT READY - skipping tare");
|
||||
}
|
||||
|
||||
if (scale2Ready) {
|
||||
|
||||
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("Scale 2 tared");
|
||||
|
||||
Serial.println("Scales tare complete");
|
||||
} else {
|
||||
Serial.println("Scale 2 NOT READY - skipping tare");
|
||||
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");
|
||||
}
|
||||
|
||||
Serial.println("Scales tare complete");
|
||||
|
||||
modePengeringan = true;
|
||||
pengeringanSelesai = false;
|
||||
beratTersimpan = false;
|
||||
|
|
@ -280,6 +306,47 @@ void callback(char* topic, byte* payload, unsigned int length) {
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,16 +355,25 @@ void callback(char* topic, byte* payload, unsigned int length) {
|
|||
// =====================================================
|
||||
void reconnect() {
|
||||
int attempts = 0;
|
||||
while (!client.connected() && attempts < 3) {
|
||||
Serial.println("Connecting MQTT...");
|
||||
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 saat connecting MQTT pertama kali
|
||||
if (attempts == 0 && modePengeringan == false && pengeringanSelesai == false) {
|
||||
// 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("Please wait...");
|
||||
lcd.print("Attempt ");
|
||||
lcd.print(attempts + 1);
|
||||
lcd.print("/");
|
||||
lcd.print(maxAttempts);
|
||||
}
|
||||
|
||||
// Generate unique client ID
|
||||
|
|
@ -326,12 +402,39 @@ void reconnect() {
|
|||
lcd.print("System Ready");
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
return; // Keluar dari fungsi setelah berhasil connect
|
||||
}
|
||||
else {
|
||||
Serial.print("MQTT Failed : ");
|
||||
Serial.println(client.state());
|
||||
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++;
|
||||
delay(2000);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -389,21 +492,107 @@ void setup() {
|
|||
dht.begin();
|
||||
delay(100);
|
||||
|
||||
// Setup HX711 - SKIP TARE untuk cepat boot
|
||||
// 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);
|
||||
delay(100);
|
||||
Serial.println("HX711 #2 begin OK");
|
||||
|
||||
scale1.set_scale(calibration_factor1);
|
||||
scale2.set_scale(calibration_factor2);
|
||||
delay(100);
|
||||
Serial.println("Calibration factor applied");
|
||||
|
||||
// SKIP tare - akan dilakukan saat mulai pengeringan
|
||||
// scale1.tare();
|
||||
// scale2.tare();
|
||||
// Update LCD
|
||||
lcd.clear();
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("Checking scales");
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Please wait...");
|
||||
|
||||
Serial.println("HX711 initialized (tare skipped)");
|
||||
// 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);
|
||||
|
|
@ -454,13 +643,15 @@ void loop() {
|
|||
if (!modePengeringan && !pengeringanSelesai) {
|
||||
Serial.println("START PENGERINGAN");
|
||||
|
||||
// Tare HX711 saat mulai pengeringan dengan timeout
|
||||
// Tare HX711 saat mulai pengeringan (style kode test)
|
||||
Serial.println("Taring scales...");
|
||||
lcd.clear();
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.print("Calibrating...");
|
||||
lcd.print("Zeroing...");
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Please wait");
|
||||
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();
|
||||
|
|
@ -471,20 +662,50 @@ void loop() {
|
|||
Serial.print("Scale 2 ready: ");
|
||||
Serial.println(scale2Ready ? "YES" : "NO");
|
||||
|
||||
if (scale1Ready) {
|
||||
bool tareSuccess = false;
|
||||
|
||||
if (scale1Ready && scale2Ready) {
|
||||
lcd.setCursor(0, 1);
|
||||
lcd.print("Calibrating... ");
|
||||
|
||||
scale1.tare();
|
||||
delay(100);
|
||||
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 {
|
||||
Serial.println("Scale 1 NOT READY - skipping tare");
|
||||
if (!scale1Ready) Serial.println("Scale 1 not ready!");
|
||||
if (!scale2Ready) Serial.println("Scale 2 not ready!");
|
||||
}
|
||||
|
||||
if (scale2Ready) {
|
||||
scale2.tare();
|
||||
delay(100);
|
||||
Serial.println("Scale 2 tared");
|
||||
} else {
|
||||
Serial.println("Scale 2 NOT READY - skipping tare");
|
||||
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");
|
||||
|
|
@ -541,21 +762,14 @@ void loop() {
|
|||
float berat1 = 0;
|
||||
float berat2 = 0;
|
||||
|
||||
// Cek scale ready dengan timeout untuk menghindari hang
|
||||
if (scale1.wait_ready_timeout(200)) {
|
||||
if (scale1.is_ready()) {
|
||||
berat1 = scale1.get_units(5);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Scale 1 timeout");
|
||||
// Cek dan baca scale 1 (style kode test)
|
||||
if (scale1.is_ready()) {
|
||||
berat1 = scale1.get_units(5);
|
||||
}
|
||||
|
||||
if (scale2.wait_ready_timeout(200)) {
|
||||
if (scale2.is_ready()) {
|
||||
berat2 = scale2.get_units(5);
|
||||
}
|
||||
} else {
|
||||
Serial.println("Scale 2 timeout");
|
||||
// Cek dan baca scale 2 (style kode test)
|
||||
if (scale2.is_ready()) {
|
||||
berat2 = scale2.get_units(5);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
|
|
@ -622,6 +836,9 @@ void loop() {
|
|||
Serial.println("PENGERINGAN SELESAI");
|
||||
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);
|
||||
|
|
@ -674,20 +891,35 @@ void loop() {
|
|||
}
|
||||
|
||||
// =====================================================
|
||||
// RELAY CONTROL berdasarkan suhu
|
||||
// Heater 1 & 2 ON, Fan ON
|
||||
// Exhaust ON jika suhu >= 60°C
|
||||
// RELAY CONTROL berdasarkan status dan suhu
|
||||
// Relay HANYA ON setelah berat_awal & target tersimpan
|
||||
// =====================================================
|
||||
digitalWrite(RELAY_HEATER1, LOW); // HEATER 1 ON
|
||||
digitalWrite(RELAY_HEATER2, LOW); // HEATER 2 ON
|
||||
digitalWrite(RELAY_FAN, LOW); // FAN ON
|
||||
|
||||
// Exhaust kontrol otomatis berdasarkan suhu
|
||||
if (suhu >= 60.0) {
|
||||
digitalWrite(RELAY_EXHAUST, LOW); // EXHAUST ON
|
||||
Serial.println("EXHAUST ON (Suhu >= 60C)");
|
||||
if (beratTersimpan) {
|
||||
// Berat sudah tersimpan, mulai pengeringan
|
||||
|
||||
// FAN selalu ON saat pengeringan
|
||||
digitalWrite(RELAY_FAN, LOW); // FAN ON
|
||||
|
||||
// Kontrol HEATER dan EXHAUST berdasarkan suhu
|
||||
if (suhu >= 60.0) {
|
||||
// Suhu sudah tinggi (≥60°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 >= 60C: HEATER OFF, EXHAUST ON");
|
||||
} else {
|
||||
// Suhu masih rendah (<60°C)
|
||||
digitalWrite(RELAY_HEATER1, LOW); // HEATER 1 ON
|
||||
digitalWrite(RELAY_HEATER2, LOW); // HEATER 2 ON
|
||||
digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF
|
||||
Serial.println("Suhu < 60C: HEATER ON, EXHAUST OFF");
|
||||
}
|
||||
} else {
|
||||
digitalWrite(RELAY_EXHAUST, HIGH); // EXHAUST OFF
|
||||
// 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
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
|
|
@ -737,7 +969,19 @@ void kirimStatusRelay(float suhu, float berat) {
|
|||
bool fan_on = (digitalRead(RELAY_FAN) == LOW);
|
||||
bool exhaust_on = (digitalRead(RELAY_EXHAUST) == LOW);
|
||||
|
||||
// Format JSON dengan status relay
|
||||
// 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) + ",";
|
||||
|
|
@ -745,7 +989,8 @@ void kirimStatusRelay(float suhu, float berat) {
|
|||
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 += "\"relay4\":" + String(exhaust_on ? "true" : "false") + ",";
|
||||
payload += "\"status\":\"" + statusString + "\"";
|
||||
payload += "}";
|
||||
|
||||
// Publish ke topic data
|
||||
|
|
|
|||
|
|
@ -101,13 +101,21 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
// Update status dan trigger notifikasi
|
||||
String newStatus = data.status;
|
||||
|
||||
// Notifikasi berdasarkan perubahan status
|
||||
// Notifikasi hanya untuk pengeringan selesai
|
||||
if (newStatus != _previousStatus) {
|
||||
if (newStatus == 'READY' && _previousStatus != 'READY') {
|
||||
_notificationManager.notifyPengeringanDimulai();
|
||||
} else if (newStatus == 'BERJALAN' && _previousStatus != 'BERJALAN') {
|
||||
_notificationManager.notifyIkanTerdeteksi(berat);
|
||||
} else if (newStatus == 'SELESAI' && _previousStatus != 'SELESAI') {
|
||||
// HAPUS notifikasi untuk status lain
|
||||
// if (newStatus == 'READY' && _previousStatus != 'READY') {
|
||||
// _notificationManager.notifyPengeringanDimulai();
|
||||
// } else if (newStatus == 'BERJALAN' && _previousStatus != 'BERJALAN') {
|
||||
// _notificationManager.notifyIkanTerdeteksi(berat);
|
||||
// }
|
||||
|
||||
// HANYA notifikasi pengeringan selesai
|
||||
if (newStatus == 'SELESAI' && _previousStatus != 'SELESAI') {
|
||||
print('🔔 Dashboard: Status changed to SELESAI');
|
||||
print(' Previous: $_previousStatus → Current: $newStatus');
|
||||
print(' Berat: $berat');
|
||||
print(' Triggering notification...');
|
||||
_notificationManager.notifyPengeringanSelesai(berat);
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +280,7 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
Widget _buildStatusCard() {
|
||||
Color statusColor;
|
||||
IconData statusIcon;
|
||||
String statusText = statusSistem;
|
||||
|
||||
switch (statusSistem) {
|
||||
case 'SELESAI':
|
||||
|
|
@ -297,6 +306,7 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
case 'DISCONNECTED':
|
||||
statusColor = Colors.red;
|
||||
statusIcon = Icons.error_outline;
|
||||
statusText = 'TERPUTUS';
|
||||
break;
|
||||
case 'CONNECTING':
|
||||
statusColor = Colors.orange;
|
||||
|
|
@ -348,7 +358,7 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
statusSistem,
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -357,7 +367,9 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
lastStatusMessage,
|
||||
statusSistem == 'DISCONNECTED'
|
||||
? 'ESP32 tidak terhubung. Periksa koneksi perangkat.'
|
||||
: lastStatusMessage,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
|
|
@ -462,7 +474,7 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: status ? iconColor.withOpacity(0.1) : Colors.grey.shade100,
|
||||
shape: BoxShape.circle,
|
||||
|
|
@ -470,29 +482,31 @@ class _DashboardScreenWithApiState extends State<DashboardScreenWithApi> {
|
|||
child: Icon(
|
||||
icon,
|
||||
color: status ? iconColor : Colors.grey,
|
||||
size: 28,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const SizedBox(height: 3),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: status ? iconColor.withOpacity(0.15) : Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
status ? 'ON' : 'OFF',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: status ? iconColor : Colors.grey,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
|
|
@ -57,29 +58,43 @@ class NotificationService {
|
|||
required String title,
|
||||
required String body,
|
||||
String? payload,
|
||||
bool playSound = true,
|
||||
bool showBadge = true,
|
||||
}) async {
|
||||
if (!_initialized) {
|
||||
await initialize();
|
||||
}
|
||||
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'pengering_ikan_channel',
|
||||
'Pengering Ikan',
|
||||
channelDescription: 'Notifikasi untuk sistem pengering ikan',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
showWhen: true,
|
||||
enableVibration: true,
|
||||
playSound: true,
|
||||
playSound: playSound,
|
||||
enableLights: true,
|
||||
color: const Color(0xFF2196F3),
|
||||
ledColor: const Color(0xFF2196F3),
|
||||
ledOnMs: 1000,
|
||||
ledOffMs: 500,
|
||||
ongoing: false,
|
||||
autoCancel: true,
|
||||
channelShowBadge: showBadge,
|
||||
visibility: NotificationVisibility.public,
|
||||
fullScreenIntent: true,
|
||||
);
|
||||
|
||||
const iosDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
sound: 'default',
|
||||
badgeNumber: 1,
|
||||
);
|
||||
|
||||
const details = NotificationDetails(
|
||||
final details = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
|
@ -92,51 +107,31 @@ class NotificationService {
|
|||
details,
|
||||
payload: payload,
|
||||
);
|
||||
print('📱 Notification shown: $title');
|
||||
print('📱 Notification shown: $title - $body');
|
||||
} catch (e) {
|
||||
print('❌ Error showing notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Show notification for pengeringan dimulai
|
||||
Future<void> notifyPengeringanDimulai() async {
|
||||
await showNotification(
|
||||
id: 1,
|
||||
title: '🚀 Pengeringan Dimulai',
|
||||
body: 'Proses pengeringan ikan telah dimulai',
|
||||
payload: 'pengeringan_dimulai',
|
||||
);
|
||||
}
|
||||
|
||||
// Show notification for ikan terdeteksi
|
||||
Future<void> notifyIkanTerdeteksi(double berat) async {
|
||||
await showNotification(
|
||||
id: 2,
|
||||
title: '🐟 Ikan Terdeteksi',
|
||||
body: 'Berat ikan: ${berat.toStringAsFixed(0)} gram',
|
||||
payload: 'ikan_terdeteksi',
|
||||
);
|
||||
}
|
||||
|
||||
// Show notification for pengeringan berjalan
|
||||
Future<void> notifyPengeringanBerjalan(double berat, double target) async {
|
||||
double progress = ((1 - (berat - target) / (berat - target)) * 100).clamp(0, 100);
|
||||
await showNotification(
|
||||
id: 3,
|
||||
title: '⏳ Pengeringan Berjalan',
|
||||
body: 'Berat: ${berat.toStringAsFixed(0)}g | Target: ${target.toStringAsFixed(0)}g',
|
||||
payload: 'pengeringan_berjalan',
|
||||
);
|
||||
}
|
||||
|
||||
// Show notification for pengeringan selesai
|
||||
Future<void> notifyPengeringanSelesai(double beratAkhir) async {
|
||||
await showNotification(
|
||||
id: 4,
|
||||
title: '✅ Pengeringan Selesai',
|
||||
body: 'Target tercapai! Berat akhir: ${beratAkhir.toStringAsFixed(0)} gram',
|
||||
payload: 'pengeringan_selesai',
|
||||
);
|
||||
print('🔔 NotificationService: Showing pengeringan selesai notification');
|
||||
print(' Initialized: $_initialized');
|
||||
print(' Berat akhir: $beratAkhir');
|
||||
|
||||
try {
|
||||
await showNotification(
|
||||
id: 4,
|
||||
title: '✅ Pengeringan Selesai',
|
||||
body: 'Target tercapai! Berat akhir: ${beratAkhir.toStringAsFixed(0)} gram',
|
||||
payload: 'pengeringan_selesai',
|
||||
playSound: true,
|
||||
showBadge: true,
|
||||
);
|
||||
print('✅ NotificationService: Notification sent successfully');
|
||||
} catch (e) {
|
||||
print('❌ NotificationService: Error sending notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Show notification for suhu tinggi
|
||||
|
|
|
|||
Loading…
Reference in New Issue