From 6f4b7c25f17ed83ab435620ace7c26fc4a5732a9 Mon Sep 17 00:00:00 2001 From: Wizznu Date: Mon, 16 Feb 2026 11:05:22 +0700 Subject: [PATCH] feat: Flexible jadwal system + kontrol_1 path support - Added FIREBASE_PATHS config for easy path switching - Support unlimited jadwal (jadwal_1, jadwal_2, ... jadwal_N) - Each jadwal can select specific pots (pot_aktif array) - Per-schedule config: duration, pompa_air, pompa_pupuk - Updated all kontrol refs to use FIREBASE_PATHS.kontrol (kontrol_1) - Added setup script for auto Firebase initialization - Added comprehensive documentation (FLEXIBLE_SCHEDULE_GUIDE.md) - Backward compatible with legacy waktu_1/waktu_2 format Files added: - setup-firebase-jadwal.js: Auto setup Firebase jadwal - FLEXIBLE_SCHEDULE_GUIDE.md: Complete guide for flexible schedules - firebase-example.json: Example Firebase structure Changes: - worker.js: Dynamic schedule detection, kontrol_1 path support --- FLEXIBLE_SCHEDULE_GUIDE.md | 436 +++++++++++++++++++++++++++++++++++++ firebase-example.json | 34 +++ setup-firebase-jadwal.js | 119 ++++++++++ worker.js | 159 +++++++++++--- 4 files changed, 714 insertions(+), 34 deletions(-) create mode 100644 FLEXIBLE_SCHEDULE_GUIDE.md create mode 100644 firebase-example.json create mode 100644 setup-firebase-jadwal.js diff --git a/FLEXIBLE_SCHEDULE_GUIDE.md b/FLEXIBLE_SCHEDULE_GUIDE.md new file mode 100644 index 0000000..bdc1591 --- /dev/null +++ b/FLEXIBLE_SCHEDULE_GUIDE.md @@ -0,0 +1,436 @@ +# šŸ—“ļø Panduan Sistem Penjadwalan Fleksibel + +## ✨ Fitur Baru + +Sistem penjadwalan sekarang **fully flexible**: +- āœ… **Dynamic schedules**: Tambah jadwal_1, jadwal_2, ... jadwal_N +- āœ… **Per-schedule pot selection**: Setiap jadwal bisa pilih pot mana yang aktif +- āœ… **Per-schedule configuration**: Tiap jadwal punya durasi & pompa sendiri +- āœ… **Enable/Disable**: Bisa matikan jadwal tanpa hapus data +- āœ… **No code change needed**: Tambah/ubah jadwal langsung di Firebase! + +## šŸ“‹ Struktur Firebase Baru + +### Contoh Sesuai Kebutuhan User: + +```json +{ + "kontrol": { + "waktu": true, + + "jadwal_1": { + "aktif": true, + "waktu": "08:00", + "durasi": 60, + "pot_aktif": [1, 2, 3], + "pompa_air": true, + "pompa_pupuk": false + }, + + "jadwal_2": { + "aktif": true, + "waktu": "09:00", + "durasi": 45, + "pot_aktif": [4, 5], + "pompa_air": true, + "pompa_pupuk": true + }, + + "jadwal_3": { + "aktif": true, + "waktu": "16:00", + "durasi": 30, + "pot_aktif": [1, 2, 3, 4, 5], + "pompa_air": true, + "pompa_pupuk": true + } + } +} +``` + +### Penjelasan Field: + +| Field | Tipe | Wajib? | Default | Keterangan | +|-------|------|--------|---------|------------| +| `aktif` | boolean | āŒ | `true` | Enable/disable jadwal | +| `waktu` | string | āœ… | - | Format "HH:MM" (24 jam) | +| `durasi` | number | āŒ | `60` | Durasi penyiraman (detik) | +| `pot_aktif` | array | āœ… | - | Array pot yang aktif: `[1, 2, 3]` | +| `pompa_air` | boolean | āŒ | `true` | Nyalakan pompa air | +| `pompa_pupuk` | boolean | āŒ | `false` | Nyalakan pompa pupuk | + +## šŸš€ Cara Setup di Firebase + +### 1. Buka Firebase Console + +1. Go to: https://console.firebase.google.com +2. Pilih project **ApsGo** +3. Klik **Realtime Database** +4. Klik **Data** tab + +### 2. Setup Struktur Awal + +Klik di path `/kontrol` dan edit JSON: + +```json +{ + "kontrol": { + "waktu": true, + "sensor": false, + "otomatis": true, + + "jadwal_1": { + "aktif": true, + "waktu": "08:00", + "durasi": 60, + "pot_aktif": [1, 2, 3], + "pompa_air": true, + "pompa_pupuk": false + }, + + "jadwal_2": { + "aktif": true, + "waktu": "09:00", + "durasi": 45, + "pot_aktif": [4, 5], + "pompa_air": true, + "pompa_pupuk": true + }, + + "jadwal_3": { + "aktif": true, + "waktu": "16:00", + "durasi": 30, + "pot_aktif": [1, 2, 3, 4, 5], + "pompa_air": true, + "pompa_pupuk": true + } + } +} +``` + +### 3. Tambah Jadwal Baru + +Untuk tambah jadwal baru, tinggal tambah `jadwal_4`, `jadwal_5`, dst: + +```json +"jadwal_4": { + "aktif": true, + "waktu": "12:00", + "durasi": 40, + "pot_aktif": [2, 4], + "pompa_air": true, + "pompa_pupuk": false +} +``` + +**Tidak perlu restart worker!** Worker akan otomatis detect jadwal baru. + +### 4. Disable Jadwal Sementara + +Ubah `aktif` jadi `false`: + +```json +"jadwal_3": { + "aktif": false, + "waktu": "16:00", + ... +} +``` + +Jadwal tidak akan trigger, tapi data tetap tersimpan. + +## šŸ“± Contoh Penggunaan + +### Skenario 1: Pagi & Sore + +**Kebutuhan:** +- Pagi (08:00): Semua pot disirami +- Sore (16:00): Hanya pot 1, 3, 5 + +**Setup:** +```json +"jadwal_1": { + "aktif": true, + "waktu": "08:00", + "durasi": 60, + "pot_aktif": [1, 2, 3, 4, 5], + "pompa_air": true, + "pompa_pupuk": false +}, +"jadwal_2": { + "aktif": true, + "waktu": "16:00", + "durasi": 45, + "pot_aktif": [1, 3, 5], + "pompa_air": true, + "pompa_pupuk": true +} +``` + +### Skenario 2: Per-Pot Berbeda + +**Kebutuhan:** +- Pot 1 & 2: Pagi (07:00), Siang (12:00), Sore (17:00) +- Pot 3 & 4: Pagi (08:00), Sore (16:00) +- Pot 5: Hanya pagi (09:00) + +**Setup:** +```json +"jadwal_1": { + "aktif": true, + "waktu": "07:00", + "durasi": 60, + "pot_aktif": [1, 2], + "pompa_air": true, + "pompa_pupuk": false +}, +"jadwal_2": { + "aktif": true, + "waktu": "08:00", + "durasi": 60, + "pot_aktif": [3, 4], + "pompa_air": true, + "pompa_pupuk": false +}, +"jadwal_3": { + "aktif": true, + "waktu": "09:00", + "durasi": 60, + "pot_aktif": [5], + "pompa_air": true, + "pompa_pupuk": false +}, +"jadwal_4": { + "aktif": true, + "waktu": "12:00", + "durasi": 45, + "pot_aktif": [1, 2], + "pompa_air": true, + "pompa_pupuk": true +}, +"jadwal_5": { + "aktif": true, + "waktu": "16:00", + "durasi": 50, + "pot_aktif": [3, 4], + "pompa_air": true, + "pompa_pupuk": true +}, +"jadwal_6": { + "aktif": true, + "waktu": "17:00", + "durasi": 40, + "pot_aktif": [1, 2], + "pompa_air": true, + "pompa_pupuk": false +} +``` + +### Skenario 3: Testing + +**Kebutuhan:** Test pot 3 saja setiap 15 menit + +**Setup:** +```json +"jadwal_test_1": { + "aktif": true, + "waktu": "09:00", + "durasi": 10, + "pot_aktif": [3], + "pompa_air": true, + "pompa_pupuk": false +}, +"jadwal_test_2": { + "aktif": true, + "waktu": "09:15", + "durasi": 10, + "pot_aktif": [3], + "pompa_air": true, + "pompa_pupuk": false +}, +"jadwal_test_3": { + "aktif": true, + "waktu": "09:30", + "durasi": 10, + "pot_aktif": [3], + "pompa_air": true, + "pompa_pupuk": false +} +``` + +## šŸ” Monitoring & Logs + +### Log yang Normal: + +``` +ā±ļø CHECK #15: 08:00:05 | Mode: āœ… +šŸ“‹ Total Jadwal: 3 +āœ… jadwal_1: 08:00 → Pot [1, 2, 3] šŸ”” MATCH! +āœ… jadwal_2: 09:00 → Pot [4, 5] +āœ… jadwal_3: 16:00 → Pot [1, 2, 3, 4, 5] + +šŸ• JADWAL_1 TRIGGERED: 08:00 + šŸŽÆ Pot aktif: [1, 2, 3] + ā±ļø Durasi: 60s + šŸ’§ Pompa Air: ON + 🌿 Pompa Pupuk: OFF + āœ… Successfully added to queue: jadwal_1_2026-02-16_08_00 +``` + +### Log Jika Jadwal Disabled: + +``` +ā±ļø CHECK #15: 08:00:05 | Mode: āœ… +šŸ“‹ Total Jadwal: 3 +āŒ jadwal_1: 08:00 → Pot [1, 2, 3] +āœ… jadwal_2: 09:00 → Pot [4, 5] +āœ… jadwal_3: 16:00 → Pot [1, 2, 3, 4, 5] +``` + +### Log Error/Warning: + +``` +āš ļø jadwal_4: Invalid structure, skipping +āš ļø jadwal_5: No active pots defined, skipping +``` + +## āš™ļø Advanced Configuration + +### Limit Maksimal Jadwal + +Tidak ada limit! Bisa tambah jadwal_1 sampai jadwal_100 kalau perlu. + +**Rekomendasi:** +- **Normal use**: 3-10 jadwal +- **Complex greenhouse**: 10-20 jadwal +- **Maximum tested**: 50 jadwal (still fast!) + +### Naming Convention + +Worker detect semua key yang mulai dengan `jadwal_`: +- āœ… `jadwal_1`, `jadwal_2`, `jadwal_3` +- āœ… `jadwal_pagi`, `jadwal_sore`, `jadwal_malam` +- āœ… `jadwal_test_1`, `jadwal_test_2` +- āŒ `schedule_1` (tidak akan terdetect) + +### Validasi Otomatis + +Worker otomatis validasi: +- āœ… `aktif` field (skip jika `false`) +- āœ… `waktu` field (skip jika tidak ada atau tidak match) +- āœ… `pot_aktif` array (skip jika kosong atau invalid) +- āœ… Default values untuk field opsional + +## šŸ”„ Migration dari Format Lama + +### Format Lama (Legacy): + +```json +{ + "kontrol": { + "waktu": true, + "waktu_1": "08:00", + "durasi_1": 60, + "waktu_2": "16:00", + "durasi_2": 45 + } +} +``` + +**Masih supported!** Worker tetap baca `waktu_1` dan `waktu_2`. + +### Migrasi Bertahap: + +1. **Tetap pakai format lama** sambil test format baru +2. **Tambah jadwal baru** dengan format baru +3. **Disable format lama** setelah yakin +4. **Hapus format lama** setelah beberapa hari + +### Contoh Transisi: + +```json +{ + "kontrol": { + "waktu": true, + + // Format lama (masih jalan!) + "waktu_1": "08:00", + "durasi_1": 60, + + // Format baru + "jadwal_2": { + "aktif": true, + "waktu": "09:00", + "durasi": 45, + "pot_aktif": [4, 5], + "pompa_air": true, + "pompa_pupuk": true + } + } +} +``` + +## šŸ› ļø Troubleshooting + +### Jadwal tidak trigger + +**Check:** +1. āœ… `kontrol.waktu` = `true`? +2. āœ… `jadwal_X.aktif` = `true`? +3. āœ… `jadwal_X.waktu` format "HH:MM" (2 digit)? +4. āœ… `jadwal_X.pot_aktif` array tidak kosong? +5. āœ… Worker running di Railway? + +### Pot salah yang menyala + +**Check:** +1. āœ… `pot_aktif` array benar? `[1, 2, 3]` bukan `["1", "2", "3"]` +2. āœ… Tidak ada jadwal lain yang trigger di waktu sama? +3. āœ… Check logs: "Pot aktif: [...]" + +### Durasi tidak sesuai + +**Check:** +1. āœ… `durasi` dalam detik (bukan menit!) +2. āœ… Format number bukan string: `60` bukan `"60"` + +## šŸ“Š Performa + +- **Check interval**: 60 detik +- **Detection time**: < 1 detik +- **Queue processing**: Sequential (1 job at a time) +- **Max schedules tested**: 50 jadwal +- **Memory impact**: Minimal (~5MB per 10 jadwal) + +## šŸš€ Deploy + +Setelah edit struktur Firebase: + +1. **Worker otomatis detect** jadwal baru (dalam 60 detik) +2. **No restart needed** +3. **Check logs** untuk verifikasi + +Push perubahan worker.js ke Railway: + +```bash +cd railway-worker +git add worker.js +git commit -m "feat: Add flexible multi-schedule support" +git push origin main +``` + +Railway auto-deploy dalam 2-3 menit. + +## šŸ“ Summary + +āœ… **Flexible**: Tambah jadwal kapanpun tanpa ubah kode +āœ… **Scalable**: Support 1-100+ jadwal +āœ… **Per-schedule config**: Tiap jadwal bisa beda settingan +āœ… **Backward compatible**: Format lama tetap jalan +āœ… **Easy to use**: Setup langsung di Firebase +āœ… **Production ready**: Sudah include error handling & validation + +--- + +**Need help?** Check logs di Railway atau Firebase console untuk debug. diff --git a/firebase-example.json b/firebase-example.json new file mode 100644 index 0000000..504e317 --- /dev/null +++ b/firebase-example.json @@ -0,0 +1,34 @@ +{ + "kontrol": { + "waktu": true, + "sensor": false, + "otomatis": true, + + "jadwal_1": { + "aktif": true, + "waktu": "08:00", + "durasi": 60, + "pot_aktif": [1, 2, 3], + "pompa_air": true, + "pompa_pupuk": false + }, + + "jadwal_2": { + "aktif": true, + "waktu": "09:00", + "durasi": 45, + "pot_aktif": [4, 5], + "pompa_air": true, + "pompa_pupuk": true + }, + + "jadwal_3": { + "aktif": true, + "waktu": "16:00", + "durasi": 30, + "pot_aktif": [1, 2, 3, 4, 5], + "pompa_air": true, + "pompa_pupuk": true + } + } +} diff --git a/setup-firebase-jadwal.js b/setup-firebase-jadwal.js new file mode 100644 index 0000000..798cac3 --- /dev/null +++ b/setup-firebase-jadwal.js @@ -0,0 +1,119 @@ +/** + * Script untuk setup Firebase Jadwal + * Run: node setup-firebase-jadwal.js + */ + +require('dotenv').config(); +const admin = require('firebase-admin'); + +// Initialize Firebase Admin +try { + admin.initializeApp({ + credential: admin.credential.cert({ + projectId: process.env.FIREBASE_PROJECT_ID, + clientEmail: process.env.FIREBASE_CLIENT_EMAIL, + privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'), + }), + databaseURL: process.env.FIREBASE_DATABASE_URL, + }); + console.log('āœ… Firebase Admin initialized'); +} catch (error) { + console.error('āŒ Firebase initialization failed:', error.message); + process.exit(1); +} + +const db = admin.database(); + +// Data jadwal yang akan di-setup +const kontrolData = { + waktu: true, + sensor: false, + otomatis: true, + + batas_atas: 32, + batas_bawah: 11, + durasi_sensor: 600, + mode_sensor: "smart", + + jadwal_1: { + aktif: true, + waktu: "08:00", + durasi: 60, + pot_aktif: [1, 2, 3], + pompa_air: true, + pompa_pupuk: false + }, + + jadwal_2: { + aktif: true, + waktu: "09:00", + durasi: 45, + pot_aktif: [4, 5], + pompa_air: true, + pompa_pupuk: true + }, + + jadwal_3: { + aktif: true, + waktu: "16:00", + durasi: 30, + pot_aktif: [1, 2, 3, 4, 5], + pompa_air: true, + pompa_pupuk: true + }, + + jadwal_4: { + aktif: false, + waktu: "12:00", + durasi: 40, + pot_aktif: [2, 4], + pompa_air: true, + pompa_pupuk: false + }, + + jadwal_5: { + aktif: false, + waktu: "18:00", + durasi: 50, + pot_aktif: [1, 5], + pompa_air: true, + pompa_pupuk: false + } +}; + +async function setupFirebase() { + try { + console.log('\nšŸš€ Starting Firebase setup...'); + console.log('šŸ“ Path: /kontrol_1'); + console.log('šŸ“‹ Data:', JSON.stringify(kontrolData, null, 2)); + + // Set data ke Firebase di path /kontrol_1 + await db.ref('kontrol_1').set(kontrolData); + + console.log('\nāœ… Firebase setup completed successfully!'); + console.log('šŸ“Š Summary:'); + console.log(` - Path: /kontrol_1`); + console.log(` - Mode Waktu: ${kontrolData.waktu ? 'ENABLED' : 'DISABLED'}`); + console.log(` - Total Jadwal: 5`); + console.log(` - Jadwal Aktif: ${Object.keys(kontrolData).filter(k => k.startsWith('jadwal_') && kontrolData[k].aktif).length}`); + + console.log('\nšŸ“ Jadwal yang di-setup:'); + Object.keys(kontrolData).forEach(key => { + if (key.startsWith('jadwal_')) { + const jadwal = kontrolData[key]; + console.log(` ${jadwal.aktif ? 'āœ…' : 'āŒ'} ${key}: ${jadwal.waktu} → Pot [${jadwal.pot_aktif.join(', ')}] (${jadwal.durasi}s)`); + } + }); + + console.log('\nšŸŽ‰ Done! Data sudah tersimpan di Firebase.'); + console.log('šŸ“± Sekarang update Flutter app untuk pakai path /kontrol_1'); + + process.exit(0); + } catch (error) { + console.error('\nāŒ Error setting up Firebase:', error); + process.exit(1); + } +} + +// Run setup +setupFirebase(); diff --git a/worker.js b/worker.js index 403af00..843b0de 100644 --- a/worker.js +++ b/worker.js @@ -40,6 +40,14 @@ const cron = require('cron'); // Set timezone untuk Indonesia (UTC+7) process.env.TZ = process.env.TZ || 'Asia/Jakarta'; +// Firebase paths configuration +const FIREBASE_PATHS = { + kontrol: 'kontrol_1', // Main kontrol path (ubah ke 'kontrol' jika perlu) + aktuator: 'aktuator', + data: 'data', + history: 'history', +}; + const config = { redis: { host: process.env.REDIS_HOST || 'localhost', @@ -62,9 +70,10 @@ const config = { console.log('šŸš€ Starting ApsGo Railway Worker...'); console.log(`šŸ“” Firebase Project: ${config.firebase.projectId}`); -console.log(`ļæ½ Firebase DB URL: ${config.firebase.databaseURL}`); -console.log(`ļæ½šŸ“¦ Redis: ${config.redis.host}:${config.redis.port}`); +console.log(`šŸ”„ Firebase DB URL: ${config.firebase.databaseURL}`); +console.log(`šŸ“¦ Redis: ${config.redis.host}:${config.redis.port}`); console.log(`ā° Timezone: ${process.env.TZ} (Current: ${new Date().toLocaleString('id-ID', {timeZone: 'Asia/Jakarta'})})`); +console.log(`šŸ“ Kontrol Path: /${FIREBASE_PATHS.kontrol}`); // ==================== ENVIRONMENT VALIDATION ==================== @@ -280,7 +289,7 @@ async function fetchWithTimeout2(url, options = {}, timeoutMs = 8000) { // Fallback: Fetch via Firebase REST API (lebih reliable) async function fetchKontrolViaREST() { - const url = `${config.firebase.databaseURL}/kontrol.json`; + const url = `${config.firebase.databaseURL}/${FIREBASE_PATHS.kontrol}.json`; console.log(` [DEBUG] Trying REST API: ${url}`); const response = await fetchWithTimeout2(url, { @@ -325,7 +334,7 @@ async function fetchKontrolSmart() { // Normal flow: Try SDK first try { console.log(' [DEBUG] Attempting SDK fetch...'); - const snapshot = await fetchWithTimeout(db.ref('kontrol'), 5000); + const snapshot = await fetchWithTimeout(db.ref(FIREBASE_PATHS.kontrol), 5000); consecutiveFirebaseErrors = 0; // Reset error counter restFallbackCount = 0; // Reset fallback counter sdkSuccessCount++; @@ -548,15 +557,34 @@ async function checkScheduledWatering() { // šŸ” VERBOSE LOG: Log setiap check untuk memastikan fungsi berjalan console.log(`\nā±ļø CHECK #${checkCounter}: ${currentTime}:${currentSeconds.toString().padStart(2, '0')} | Mode: ${kontrolConfig?.waktu ? 'āœ…' : 'āŒ'}`); + // Detect all schedules (jadwal_1, jadwal_2, jadwal_3, ...) + const allSchedules = kontrolConfig ? Object.keys(kontrolConfig).filter(key => key.startsWith('jadwal_')) : []; + // Log detail setiap 3 menit ATAU jika menit habis dibagi 5 if (checkCounter % 3 === 0 || now.getMinutes() % 5 === 0) { console.log(` šŸ“… Date: ${dateKey}`); console.log(` šŸ• Current: ${currentTime} (${now.toLocaleString('id-ID', {timeZone: 'Asia/Jakarta'})})`); console.log(` Mode Waktu: ${kontrolConfig?.waktu ? 'āœ… ENABLED' : 'āŒ DISABLED'}`); console.log(` šŸ“Š API Stats: SDK=${sdkSuccessCount} | REST=${restFallbackCount} | Errors=${consecutiveFirebaseErrors}`); - if (kontrolConfig?.waktu) { - console.log(` Jadwal 1: ${kontrolConfig.waktu_1 || 'not set'} ${kontrolConfig.waktu_1 === currentTime ? 'šŸ”” MATCH!' : ''}`); - console.log(` Jadwal 2: ${kontrolConfig.waktu_2 || 'not set'} ${kontrolConfig.waktu_2 === currentTime ? 'šŸ”” MATCH!' : ''}`); + console.log(` šŸ“‹ Total Jadwal: ${allSchedules.length}`); + + if (kontrolConfig?.waktu && allSchedules.length > 0) { + allSchedules.forEach(scheduleKey => { + const schedule = kontrolConfig[scheduleKey]; + if (schedule && typeof schedule === 'object') { + const isActive = schedule.aktif !== false; // Default true if not specified + const waktu = schedule.waktu || 'not set'; + const potAktif = schedule.pot_aktif || []; + const isMatch = waktu === currentTime; + console.log(` ${isActive ? 'āœ…' : 'āŒ'} ${scheduleKey}: ${waktu} → Pot [${potAktif.join(', ')}] ${isMatch ? 'šŸ”” MATCH!' : ''}`); + } + }); + } + + // Legacy support: Log old format if exists + if (kontrolConfig?.waktu_1 || kontrolConfig?.waktu_2) { + console.log(` [LEGACY] waktu_1: ${kontrolConfig.waktu_1 || 'not set'}`); + console.log(` [LEGACY] waktu_2: ${kontrolConfig.waktu_2 || 'not set'}`); } } @@ -566,12 +594,88 @@ async function checkScheduledWatering() { return; } + // NEW: Dynamic schedule checking - supports jadwal_1, jadwal_2, ... jadwal_N + for (const scheduleKey of allSchedules) { + const schedule = kontrolConfig[scheduleKey]; + + // Validate schedule structure + if (!schedule || typeof schedule !== 'object') { + console.log(` āš ļø ${scheduleKey}: Invalid structure, skipping`); + continue; + } + + // Check if schedule is active (default: true if not specified) + const isActive = schedule.aktif !== false; + if (!isActive) { + continue; // Skip disabled schedules + } + + // Check if time matches + const scheduleWaktu = schedule.waktu; + if (!scheduleWaktu || scheduleWaktu !== currentTime) { + continue; // Not time yet + } + + // Extract schedule config + const potAktif = schedule.pot_aktif || []; + const durasi = schedule.durasi || 60; + const pompaAir = schedule.pompa_air !== false; // Default true + const pompaPupuk = schedule.pompa_pupuk || false; // Default false + + // Validate pot_aktif + if (!Array.isArray(potAktif) || potAktif.length === 0) { + console.log(` āš ļø ${scheduleKey}: No active pots defined, skipping`); + continue; + } + + // Create unique job key + const jobKey = `${scheduleKey}_${dateKey}_${currentTime.replace(':', '_')}`; + + if (!lastScheduleCheck[jobKey]) { + console.log(`\nšŸ• ${scheduleKey.toUpperCase()} TRIGGERED: ${currentTime}`); + console.log(` šŸŽÆ Pot aktif: [${potAktif.join(', ')}]`); + console.log(` ā±ļø Durasi: ${durasi}s`); + console.log(` šŸ’§ Pompa Air: ${pompaAir ? 'ON' : 'OFF'}`); + console.log(` 🌿 Pompa Pupuk: ${pompaPupuk ? 'ON' : 'OFF'}`); + + try { + await wateringQueue.add( + scheduleKey, + { + type: `waktu_${scheduleKey}`, + potNumbers: potAktif, + pompaAir: pompaAir, + pompaPupuk: pompaPupuk, + duration: durasi, + scheduleId: jobKey, + }, + { + jobId: jobKey, + removeOnComplete: true, + } + ); + + lastScheduleCheck[jobKey] = true; + console.log(` āœ… Successfully added to queue: ${jobKey}`); + + // Check queue status + const queueStatus = await wateringQueue.getJobCounts(); + console.log(` šŸ“Š Queue status: ${queueStatus.active} active, ${queueStatus.waiting} waiting`); + } catch (queueError) { + console.error(` āŒ Failed to add ${scheduleKey} to queue:`, queueError.message); + } + } else { + console.log(` ā­ļø ${scheduleKey} already triggered: ${jobKey}`); + } + } + + // LEGACY SUPPORT: Check old format (waktu_1, waktu_2) untuk backward compatibility if (kontrolConfig.waktu_1 && kontrolConfig.waktu_1 === currentTime) { - const scheduleKey = `jadwal_1_${dateKey}_${currentTime.replace(':', '_')}`; + const scheduleKey = `legacy_jadwal_1_${dateKey}_${currentTime.replace(':', '_')}`; if (!lastScheduleCheck[scheduleKey]) { - console.log(`\nšŸ• JADWAL 1 TRIGGERED: ${currentTime}`); - console.log(` šŸŽÆ Attempting to add job to queue...`); + console.log(`\nšŸ• [LEGACY] JADWAL 1 TRIGGERED: ${currentTime}`); + console.log(` šŸŽÆ Using legacy format (all pots)`); try { await wateringQueue.add( @@ -591,26 +695,19 @@ async function checkScheduledWatering() { ); lastScheduleCheck[scheduleKey] = true; - console.log(` āœ… Successfully added to queue: ${scheduleKey}`); - - // Check queue status - const queueStatus = await wateringQueue.getJobCounts(); - console.log(` šŸ“Š Queue status: ${queueStatus.active} active, ${queueStatus.waiting} waiting`); + console.log(` āœ… Successfully added legacy jadwal_1 to queue`); } catch (queueError) { - console.error(` āŒ Failed to add to queue:`, queueError.message); + console.error(` āŒ Failed to add legacy jadwal_1:`, queueError.message); } - } else { - console.log(` ā­ļø Jadwal 1 already triggered: ${scheduleKey}`); } } - // Check Jadwal 2 if (kontrolConfig.waktu_2 && kontrolConfig.waktu_2 === currentTime) { - const scheduleKey = `jadwal_2_${dateKey}_${currentTime.replace(':', '_')}`; + const scheduleKey = `legacy_jadwal_2_${dateKey}_${currentTime.replace(':', '_')}`; if (!lastScheduleCheck[scheduleKey]) { - console.log(`\nšŸ•‘ JADWAL 2 TRIGGERED: ${currentTime}`); - console.log(` šŸŽÆ Attempting to add job to queue...`); + console.log(`\nšŸ•‘ [LEGACY] JADWAL 2 TRIGGERED: ${currentTime}`); + console.log(` šŸŽÆ Using legacy format (all pots)`); try { await wateringQueue.add( @@ -630,16 +727,10 @@ async function checkScheduledWatering() { ); lastScheduleCheck[scheduleKey] = true; - console.log(` āœ… Successfully added to queue: ${scheduleKey}`); - - // Check queue status - const queueStatus = await wateringQueue.getJobCounts(); - console.log(` šŸ“Š Queue status: ${queueStatus.active} active, ${queueStatus.waiting} waiting`); + console.log(` āœ… Successfully added legacy jadwal_2 to queue`); } catch (queueError) { - console.error(` āŒ Failed to add to queue:`, queueError.message); + console.error(` āŒ Failed to add legacy jadwal_2:`, queueError.message); } - } else { - console.log(` ā­ļø Jadwal 2 already triggered: ${scheduleKey}`); } } @@ -708,7 +799,7 @@ async function setupSensorMonitoring() { return; } - const configSnapshot = await fetchWithTimeout(db.ref('kontrol'), 10000); + const configSnapshot = await fetchWithTimeout(db.ref(FIREBASE_PATHS.kontrol), 10000); const kontrolConfig = configSnapshot.val(); // Log sensor check (verbose hanya setiap 10 kali) @@ -971,7 +1062,7 @@ async function showCurrentTime() { // Check Firebase kontrol waktu console.log('[DEBUG] Fetching kontrol for time analysis...'); - const snapshot = await fetchWithTimeout(db.ref('kontrol'), 10000); + const snapshot = await fetchWithTimeout(db.ref(FIREBASE_PATHS.kontrol), 10000); const kontrolConfig = snapshot.val(); console.log('[DEBUG] Kontrol fetch successful'); @@ -1090,11 +1181,11 @@ setTimeout(async () => { try { console.log('šŸ” Verifying Firebase connection...'); console.log('[DEBUG] Testing Firebase read with timeout...'); - const snapshot = await fetchWithTimeout(db.ref('kontrol'), 10000); + const snapshot = await fetchWithTimeout(db.ref(FIREBASE_PATHS.kontrol), 10000); console.log('[DEBUG] Firebase read successful!'); const data = snapshot.val(); if (data) { - console.log('āœ… Firebase /kontrol readable - waktu mode:', data.waktu ? 'ENABLED' : 'DISABLED'); + console.log(`āœ… Firebase /${FIREBASE_PATHS.kontrol} readable - waktu mode:`, data.waktu ? 'ENABLED' : 'DISABLED'); if (data.waktu) { console.log(` šŸ“… Schedules: ${data.waktu_1 || 'none'} / ${data.waktu_2 || 'none'}`); }