🛠️ IMPROVE: Add retry logic and better error handling for mosvet trigger

- Add retry logic (3x attempts) to updateFirebaseSmart() for better stability
- Add Firebase verification to confirm mosvet updates actually wrote to database
- Add exponential backoff (1s, 2s, 3s) between retry attempts
- Improve error logging with full error stack traces
- Add critical alerts when safety OFF fails (potential stuck mosvet)
- Better validation of kontrol config with NULL checks
- Enhanced diagnostics for all updateFirebaseSmart calls
- Ensure pump stops are retried if initial attempt fails

This fixes issues where mosvet would not trigger despite logs showing 'success'
This commit is contained in:
Wizznu 2026-04-10 12:55:07 +07:00
parent 568c4dd981
commit 789f80ffe0
1 changed files with 132 additions and 53 deletions

View File

@ -219,7 +219,37 @@ const wateringWorker = new Worker(
}); });
} }
await updateFirebaseSmart('aktuator', updates); // Update with retry logic (3 attempts)
await updateFirebaseSmart('aktuator', updates, 3);
// Verify the update was written to Firebase
console.log(` 🔍 Verifying Firebase update...`);
const verifyAttempts = 3;
let verified = false;
for (let i = 0; i < verifyAttempts; i++) {
try {
const currentState = await readFirebaseSmart('aktuator');
const allSet = Object.keys(updates).every(key => currentState[key] === updates[key]);
if (allSet) {
console.log(` ✅ VERIFIED: All values correctly written to Firebase!`);
verified = true;
break;
} else {
console.warn(` ⚠️ Verification attempt ${i + 1}/${verifyAttempts} failed: values not yet synced`);
if (i < verifyAttempts - 1) {
await sleep(500); // Wait 500ms before retry
}
}
} catch (verifyError) {
console.warn(` ⚠️ Verification read failed (attempt ${i + 1}/${verifyAttempts}): ${verifyError.message}`);
}
}
if (!verified) {
console.warn(` ⚠️ WARNING: Could not verify Firebase update after ${verifyAttempts} attempts`);
}
console.log(` 🚀 ALL VALVES STARTED SIMULTANEOUSLY: ${Object.keys(updates).filter(k => k.startsWith('mosvet_')).join(', ')}`); console.log(` 🚀 ALL VALVES STARTED SIMULTANEOUSLY: ${Object.keys(updates).filter(k => k.startsWith('mosvet_')).join(', ')}`);
// SMART MODE: Monitor sensor and stop pots TOGETHER when they reach target // SMART MODE: Monitor sensor and stop pots TOGETHER when they reach target
@ -264,8 +294,13 @@ const wateringWorker = new Worker(
stopUpdates[`mosvet_${pot + 2}`] = false; stopUpdates[`mosvet_${pot + 2}`] = false;
} }
await updateFirebaseSmart('aktuator', stopUpdates); try {
await updateFirebaseSmart('aktuator', stopUpdates, 2); // 2 attempts for stop
console.log(` 🔴 STOPPED TOGETHER: ${Object.keys(stopUpdates).join(', ')} (Pots: [${potsToStop.join(', ')}])`); console.log(` 🔴 STOPPED TOGETHER: ${Object.keys(stopUpdates).join(', ')} (Pots: [${potsToStop.join(', ')}])`);
} catch (stopError) {
console.error(` ❌ FAILED to stop pots: ${stopError.message}`);
throw stopError; // Re-throw to safety handler
}
// Remove stopped pots from active list // Remove stopped pots from active list
activePots = activePots.filter(p => !potsToStop.includes(p)); activePots = activePots.filter(p => !potsToStop.includes(p));
@ -289,8 +324,13 @@ const wateringWorker = new Worker(
for (const pot of activePots) { for (const pot of activePots) {
timeoutStops[`mosvet_${pot + 2}`] = false; timeoutStops[`mosvet_${pot + 2}`] = false;
} }
await updateFirebaseSmart('aktuator', timeoutStops); try {
await updateFirebaseSmart('aktuator', timeoutStops, 2); // 2 attempts for force stop
console.log(` 🔴 Force stopped TOGETHER: ${Object.keys(timeoutStops).join(', ')}`); console.log(` 🔴 Force stopped TOGETHER: ${Object.keys(timeoutStops).join(', ')}`);
} catch (forceStopError) {
console.error(` ❌ FAILED to force stop pots: ${forceStopError.message}`);
throw forceStopError;
}
} }
// Finally, stop pumps // Finally, stop pumps
@ -298,8 +338,13 @@ const wateringWorker = new Worker(
if (pompaAir) pumpStop['mosvet_1'] = false; if (pompaAir) pumpStop['mosvet_1'] = false;
if (pompaPupuk) pumpStop['mosvet_2'] = false; if (pompaPupuk) pumpStop['mosvet_2'] = false;
if (Object.keys(pumpStop).length > 0) { if (Object.keys(pumpStop).length > 0) {
await updateFirebaseSmart('aktuator', pumpStop); try {
await updateFirebaseSmart('aktuator', pumpStop, 2); // 2 attempts for pump stop
console.log(' 🔴 Pumps stopped:', Object.keys(pumpStop).join(', ')); console.log(' 🔴 Pumps stopped:', Object.keys(pumpStop).join(', '));
} catch (pumpStopError) {
console.error(` ❌ FAILED to stop pumps: ${pumpStopError.message}`);
throw pumpStopError;
}
} }
console.log(' ✅ Smart mode completed, now logging history...'); console.log(' ✅ Smart mode completed, now logging history...');
@ -322,8 +367,14 @@ const wateringWorker = new Worker(
offUpdates[key] = false; offUpdates[key] = false;
} }
console.log(' 🔴 Turning OFF:', Object.keys(offUpdates).join(', ')); console.log(' 🔴 Turning OFF:', Object.keys(offUpdates).join(', '));
await updateFirebaseSmart('aktuator', offUpdates); try {
console.log(' ✅ Turn OFF completed, now logging history...'); await updateFirebaseSmart('aktuator', offUpdates, 2); // 2 attempts for turn off
console.log(' ✅ Turn OFF completed successfully');
} catch (offError) {
console.error(` ❌ FAILED to turn OFF: ${offError.message}`);
throw offError;
}
console.log(' ✅ Now logging history...');
} }
// Log history // Log history
@ -340,10 +391,10 @@ const wateringWorker = new Worker(
return { success: true, duration, pots: potNumbers }; return { success: true, duration, pots: potNumbers };
} catch (error) { } catch (error) {
console.error(` ❌ Job failed:`, error.message); console.error(` ❌ Job failed:`, error.message);
console.error(` [ERROR DETAILS] Stack:`, error.stack);
// Safety: Turn OFF everything // Safety: Turn OFF everything with retry
try { const safetyUpdates = {
await updateFirebaseSmart('aktuator', {
mosvet_1: false, mosvet_1: false,
mosvet_2: false, mosvet_2: false,
mosvet_3: false, mosvet_3: false,
@ -352,10 +403,15 @@ const wateringWorker = new Worker(
mosvet_6: false, mosvet_6: false,
mosvet_7: false, mosvet_7: false,
mosvet_8: false, // Pengaduk mosvet_8: false, // Pengaduk
}); };
console.log(' 🛡️ Safety: All aktuators turned OFF');
try {
console.log(` 🛡️ Safety: Attempting to turn OFF all aktuators...`);
await updateFirebaseSmart('aktuator', safetyUpdates, 2); // 2 attempts for safety
console.log(' 🛡️ Safety: All aktuators turned OFF successfully');
} catch (safetyError) { } catch (safetyError) {
console.error(' ⚠️ Safety OFF failed:', safetyError.message); console.error(' ❌ CRITICAL: Safety OFF failed:', safetyError.message);
console.error(' ❌ CRITICAL: Penyiraman mungkin terjebak ON - manual intervention mungkin diperlukan!');
} }
throw error; throw error;
@ -528,43 +584,58 @@ async function updateWithTimeout(ref, updates, timeoutMs = 5000) {
} }
// Smart update: Try SDK first, fallback to REST if timeout // Smart update: Try SDK first, fallback to REST if timeout
async function updateFirebaseSmart(path, updates) { // WITH RETRY LOGIC for stability
async function updateFirebaseSmart(path, updates, maxAttempts = 3) {
const updateStr = JSON.stringify(updates); const updateStr = JSON.stringify(updates);
console.log(` [UPDATE START] Path: ${path}, Data: ${updateStr}`); console.log(` [UPDATE START] Path: /${path}, Data: ${updateStr}`);
console.log(` [UPDATE] Max attempts: ${maxAttempts}`);
let lastError = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
// If SDK is consistently failing, skip it for updates too // If SDK is consistently failing, skip it for updates too
const shouldSkipSDK = consecutiveFirebaseErrors >= SKIP_SDK_THRESHOLD; const shouldSkipSDK = consecutiveFirebaseErrors >= SKIP_SDK_THRESHOLD;
if (shouldSkipSDK) { if (shouldSkipSDK) {
console.log(` [UPDATE] Using REST API directly (SDK disabled)`); console.log(` [UPDATE Attempt ${attempt}/${maxAttempts}] Using REST API directly (SDK disabled)`);
try {
await updateFirebaseViaREST(path, updates); await updateFirebaseViaREST(path, updates);
console.log(` ✅ [UPDATE] REST API successful!`); console.log(` ✅ [UPDATE] REST API successful on attempt ${attempt}!`);
return true; return true;
} catch (restError) {
console.error(` ❌ [UPDATE] REST failed: ${restError.message}`);
throw new Error('REST update failed');
}
} }
try { // Normal flow: Try SDK first
console.log(` [UPDATE] Step 1: Attempting SDK update...`); console.log(` [UPDATE Attempt ${attempt}/${maxAttempts}] Attempting SDK update...`);
await updateWithTimeout(db.ref(path), updates, 5000); await updateWithTimeout(db.ref(path), updates, 5000);
console.log(` ✅ [UPDATE] Step 2: SDK update successful!`); console.log(` ✅ [UPDATE] SDK update successful on attempt ${attempt}!`);
return true; return true;
} catch (sdkError) { } catch (sdkError) {
console.warn(` ⚠️ [UPDATE] Step 2: SDK failed (${sdkError.message}), trying REST API...`); lastError = sdkError;
console.warn(` ⚠️ [UPDATE Attempt ${attempt}/${maxAttempts}] SDK failed: ${sdkError.message}`);
// Try REST API as fallback
try { try {
console.log(` [UPDATE] Step 3: Attempting REST API...`); console.log(` [UPDATE Attempt ${attempt}/${maxAttempts}] Fallback to REST API...`);
await updateFirebaseViaREST(path, updates); await updateFirebaseViaREST(path, updates);
console.log(` ✅ [UPDATE] Step 4: REST API successful!`); console.log(` ✅ [UPDATE] REST API successful on attempt ${attempt}!`);
return true; return true;
} catch (restError) { } catch (restError) {
console.error(` ❌ [UPDATE] Step 4: REST failed (${restError.message}) - BOTH METHODS FAILED!`); lastError = restError;
throw new Error('Both SDK and REST update failed'); console.error(` ❌ [UPDATE Attempt ${attempt}/${maxAttempts}] REST failed: ${restError.message}`);
if (attempt < maxAttempts) {
const delayMs = attempt * 1000; // 1s, 2s, 3s delay between attempts
console.log(` ⏳ [UPDATE] Retrying after ${delayMs}ms...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
} }
} }
}
}
// All attempts failed
console.error(` ❌ [UPDATE] ALL ${maxAttempts} ATTEMPTS FAILED!`);
console.error(` ❌ [UPDATE] Last error: ${lastError.message}`);
throw new Error(`Firebase update failed after ${maxAttempts} attempts: ${lastError.message}`);
} }
// Helper: Set Firebase via REST API (PUT for overwrite) // Helper: Set Firebase via REST API (PUT for overwrite)
@ -712,6 +783,11 @@ async function checkScheduledWatering() {
console.log(` [DEBUG] Kontrol config received:`, kontrolConfig ? 'EXISTS' : 'NULL'); console.log(` [DEBUG] Kontrol config received:`, kontrolConfig ? 'EXISTS' : 'NULL');
if (!kontrolConfig) {
console.warn(' ⚠️ Kontrol config is NULL - schedule check aborted');
return;
}
if (kontrolConfig) { if (kontrolConfig) {
console.log(` [DEBUG] Kontrol data:`, JSON.stringify(kontrolConfig, null, 2)); console.log(` [DEBUG] Kontrol data:`, JSON.stringify(kontrolConfig, null, 2));
} }
@ -737,6 +813,10 @@ async function checkScheduledWatering() {
// Detect all schedules (jadwal_1, jadwal_2, jadwal_3, ...) // Detect all schedules (jadwal_1, jadwal_2, jadwal_3, ...)
const allSchedules = kontrolConfig ? Object.keys(kontrolConfig).filter(key => key.startsWith('jadwal_')) : []; const allSchedules = kontrolConfig ? Object.keys(kontrolConfig).filter(key => key.startsWith('jadwal_')) : [];
if (allSchedules.length === 0) {
console.warn(' ⚠️ No jadwal found in kontrol config! Check Firebase structure.');
}
// Log detail setiap 3 menit ATAU jika menit habis dibagi 5 // Log detail setiap 3 menit ATAU jika menit habis dibagi 5
if (checkCounter % 3 === 0 || now.getMinutes() % 5 === 0) { if (checkCounter % 3 === 0 || now.getMinutes() % 5 === 0) {
console.log(` 📅 Date: ${dateKey}`); console.log(` 📅 Date: ${dateKey}`);
@ -1565,4 +1645,3 @@ setInterval(() => {
showCurrentTime(); showCurrentTime();
} }
}, 30000); }, 30000);