# 🎨 SISTEM SCHEDULER - VISUAL DIAGRAMS ## 1. Arsitektur Sistem ``` ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 📱 FLUTTER APP (TelurKu) │ │ ├─ main.dart │ │ │ ├─ PanenScheduler.initialize() ← Background task setup │ │ │ ├─ PanenAutoCaptureService.initialize() ← Restore state │ │ │ └─ MultiProvider (PanenProvider, KandangProvider, ...) │ │ │ │ │ ├─ lib/services/ │ │ │ ├─ panen_scheduler.dart ← Workmanager setup │ │ │ └─ panen_auto_capture_service.dart ← Bridge layer │ │ │ │ │ ├─ lib/providers/ │ │ │ ├─ panen_provider.dart ← Time-window + snapshot │ │ │ ├─ kandang_provider.dart ← Sensor reading │ │ │ └─ ... │ │ │ │ │ └─ lib/screens/ │ │ ├─ dashboard_page.dart ← Show status & last harvest │ ├─ riwayat_page.dart ← Show history │ │ └─ kontrol_page.dart ← Manual test buttons │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 🔄 BACKGROUND SERVICE (Workmanager) │ │ ├─ Task ID: "panenPagiTask" (09:00 setiap hari) │ │ ├─ Task ID: "panenSoreTask" (15:00 setiap hari) │ │ └─ Execute: callbackDispatcher() → trigger capture │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 🔥 FIREBASE │ │ ├─ /data/ ← Real-time sensor data │ │ │ ├─ infra1: 45 │ │ │ └─ infra2: 68 │ │ │ │ │ ├─ /panen_snapshot/YYYY-MM-DD/ ← Snapshot per hari │ │ │ ├─ pagi: {kandang_1: 45, kandang_2: 68} │ │ │ └─ sore: {kandang_1: 100, kandang_2: 150, delta...} │ │ │ │ │ └─ /riwayat/ ← Panen records │ │ ├─ -abc123: {Panen object pagi kandang_1} │ │ ├─ -def456: {Panen object sore kandang_1} │ │ └─ ... │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 2. Alur Executable Pagi (09:00) ``` ┌───────────────────────────────────────────────────────────────┐ │ TIMELINE: 08:45 - 09:15 │ └───────────────────────────────────────────────────────────────┘ 08:45:59 ↓ 08:59:59 (Workmanager countdown) ↓ 09:00:00 ✅ TRIGGER TIME HIT! ↓ ┌─────────────────────────────────────────┐ │ callbackDispatcher() start execution │ │ └─→ panenPagiTask │ │ └─→ _executePanenPagi() │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ Step 1: Read Firebase /data/ │ │ ├─ infra1: 45 ← sensor value kandang 1 │ │ └─ infra2: 68 ← sensor value kandang 2 │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ Step 2: Save snapshot pagi to Firebase │ │ /panen_snapshot/2026-03-07/pagi/ │ │ ├─ kandang_1: 45 │ │ ├─ kandang_2: 68 │ │ └─ timestamp: 2026-03-07T09:00:00Z │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ Step 3: Trigger Capture Methods │ │ └─ PanenAutoCaptureService │ │ .triggerMorningCapture() │ │ ├─ kandang_1 + 45 telur │ │ └─ kandang_2 + 68 telur │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ Step 4: Process for kandang_1 │ │ ├─ captureScheduledPanenPagi() │ │ ├─ jumlahTelur = 45 │ │ ├─ jenisPanen = 'pagi' │ │ ├─ sensorSnapshot = 45 │ │ ├─ Save _snapshotPagiHariIni['k1'] = 45│ │ ├─ Add to _panens list │ │ ├─ Save to Firebase /riwayat/ │ │ └─ notifyListeners() │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ Step 5: Process for kandang_2 (sama) │ │ ├─ jumlahTelur = 68 │ │ ├─ Save _snapshotPagiHariIni['k2'] = 68│ │ └─ Save to Firebase /riwayat/ │ └─────────────────────────────────────────┘ ↓ 09:00:15 ✅ SELESAI! ↓ Firebase state: /panen_snapshot/2026-03-07/pagi/ = {kandang_1: 45, kandang_2: 68} /riwayat/ = +2 records (pagi untuk k1 & k2) Provider state: _snapshotPagiHariIni = {kandang_1: 45, kandang_2: 68} _panens = +2 items (pagi) UI state: Dashboard: ✅ Panen Pagi (done) Last Harvest: Kandang 1 = 45 telur (pagi) Next: Tunggu jam 15:00 untuk panen sore... ``` --- ## 3. Alur Executable Sore (15:00) dengan Delta ``` ┌───────────────────────────────────────────────────────────────┐ │ TIMELINE: 14:45 - 15:15 │ │ (Antara pagi dan sore, ayam terus bertelur) │ └───────────────────────────────────────────────────────────────┘ 09:15 (Setelah panen pagi) ↓ Ayam terus bertelur selama 6 jam... Sensor infra1: 45 → 60 → 80 → 95 → 100 (15:00) Sensor infra2: 68 → 75 → 95 → 120 → 150 (15:00) ↓ 14:59:59 (Workmanager countdown) ↓ 15:00:00 ✅ TRIGGER TIME HIT! ↓ ┌──────────────────────────────────────────┐ │ callbackDispatcher() start execution │ │ └─→ panenSoreTask │ │ └─→ _executePanenSore() │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 1: Read Firebase /data/ │ │ ├─ infra1: 100 ← sensor value sekarang │ │ └─ infra2: 150 │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 2: Get snapshot pagi dari Firebase │ │ /panen_snapshot/2026-03-07/pagi/ │ │ ├─ kandang_1: 45 ← nilai pagi │ │ └─ kandang_2: 68 │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 3: Calculate DELTA │ │ Kandang 1: 100 - 45 = 55 telur sore │ │ Kandang 2: 150 - 68 = 82 telur sore │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 4: Save snapshot sore + delta │ │ /panen_snapshot/2026-03-07/sore/ │ │ ├─ kandang_1: 100 │ │ ├─ kandang_2: 150 │ │ ├─ delta_kandang_1: 55 │ │ ├─ delta_kandang_2: 82 │ │ └─ timestamp: 2026-03-07T15:00:00Z │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 5: Trigger Afternoon Capture │ │ PanenAutoCaptureService │ │ .triggerAfternoonCapture() │ │ ├─ kandang_1 + delta 55 │ │ └─ kandang_2 + delta 82 │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 6: Process kandang_1 (sore) │ │ ├─ captureScheduledPanenSore() │ │ ├─ Get _snapshotPagiHariIni['k1'] = 45 │ │ ├─ jumlahTelur = 100 - 45 = 55 │ │ ├─ jenisPanen = 'sore' │ │ ├─ sensorSnapshot = 100 │ │ ├─ panenSebelumnya = 45 ← untuk ref │ │ ├─ Save _snapshotSoreHariIni['k1'] = 100│ │ ├─ Add to _panens list │ │ ├─ Save to Firebase /riwayat/ │ │ └─ notifyListeners() │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 7: Process kandang_2 (sore) (sama) │ │ ├─ jumlahTelur = 150 - 68 = 82 │ │ ├─ Save _snapshotSoreHariIni['k2'] = 150│ │ └─ Save to Firebase /riwayat/ │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Step 8: Reset daily snapshots │ │ ├─ _snapshotPagiHariIni.clear() │ │ ├─ _snapshotSoreHariIni.clear() │ │ └─ Persiapan untuk hari berikutnya │ └──────────────────────────────────────────┘ ↓ 15:00:30 ✅ SELESAI! ↓ Firebase state: /panen_snapshot/2026-03-07/ = { pagi: {kandang_1: 45, kandang_2: 68}, sore: {kandang_1: 100, kandang_2: 150, delta_k1: 55, delta_k2: 82} } /riwayat/ = +2 records (sore untuk k1 & k2) Provider state: _snapshotPagiHariIni = {} (cleared) _snapshotSoreHariIni = {} (cleared) _panens = +4 items total (2 pagi + 2 sore) UI state: Dashboard: ✅ Panen Sore (done) Last Harvest: Kandang 1 = 55 telur (sore, delta) Riwayat: TOTAL = 45 + 55 + 68 + 82 = 250 telur hari ini Next: Besok jam 09:00, cycle berulang... ``` --- ## 4. Data Flow Chart ``` 🌍 FIREBASE │ ┌──────────┼──────────┐ ↓ ↓ ↓ /data/ /panen_ /riwayat/ (real-time snapshot) (history) │ │ │ │ │ │ ↓ ↓ ↓ ┌──────────────────────────────────────┐ │ PanenScheduler (Background) │ │ Jam 09:00 | Jam 15:00 │ └──────────────────────────────────────┘ │ │ └──┬───────┘ ↓ ┌──────────────────────────────────────┐ │ PanenAutoCaptureService (Bridge) │ │ - triggerMorningCapture() │ │ - triggerAfternoonCapture() │ └──────────────────────────────────────┘ │ ↓ ┌──────────────────────────────────────┐ │ PanenProvider (Snapshot) │ │ _snapshotPagiHariIni │ │ _snapshotSoreHariIni │ │ captureScheduledPanenPagi() │ │ captureScheduledPanenSore() │ └──────────────────────────────────────┘ │ ├─→ _panens list (memory) │ └─→ Firebase /riwayat/ │ ↓ ┌─────────────────────┐ │ DashboardPage │ │ - Last harvest │ │ - Current status │ └─────────────────────┘ │ ┌─────────────────────┐ │ RiwayatPage │ │ - History list │ │ - Daily totals │ │ - Grouped by date │ └─────────────────────┘ ``` --- ## 5. Time-Window Illustration ``` KEMARIN HARI INI BESOK │ │ │ ├─ 15:01 ──────────────────────→├─ 09:00 (CAPTURE PAGI) │ │ Start Accumulation │ │ │ │ │ ├─ 45 telur = Total 6+ jam │ │ │ │ │ │ (Ayam terus bertelur │ ├─ 09:01 ──────────────→ │ │ selama 18 jam di kandang) │ │ Start Accumulation │ │ │ │ │ ├─ 23:59 ────────────────────→ │ ├─ 15:00 (CAPTURE SORE) │ │ │ │ Delta: 100 - 45 = 55 │ │ Sensor di kandang 1: │ │ │ │ Terus naik 45 → 100 │ │ Snapshot sore selesai │ │ │ │ Reset untuk besok │ └──────────────────────────────┴──────────────────────────────┴───── WINDOW PAGI = 18 jam | WINDOW SORE = 6 jam | (Lebih banyak telur pagi) Akumulasi lebih banyak | Akumulasi lebih sedikit | ``` --- ## 6. Provider State Machine ``` APP START ↓ ├─ PanenProvider created ├─ loadTodaySnapshots() ← Restore dari Firebase ├─ restorePanenHistoryFromFirebase() ← Load historical data └─ Ready! │ ├─→ State: _snapshotPagiHariIni = {} ├─→ State: _snapshotSoreHariIni = {} └─→ State: _panens = [... all historical items] JAM 08:59:59 ↓ Waiting for 09:00... JAM 09:00:00 ✅ ↓ Scheduler trigger → captureScheduledPanenPagi() │ ├─ Check: isPanenPagiRecordedToday('k1') ? │ └─ Jika false → lanjut │ └─ Jika true → skip (sudah tercatat) │ ├─ Add Panen object ke _panens ├─ Save _snapshotPagiHariIni['k1'] = 45 ├─ Save ke Firebase ├─ notifyListeners() ← UI update │ └─ State: ├─ _snapshotPagiHariIni = {kandang_1: 45} ├─ _panens = [+1 item pagi] └─ isPanenPagiRecordedToday('k1') = true ✅ JAM 09:00 → 14:59 ↓ Waiting for 15:00... (Sensor terus naik: 45 → 100) JAM 15:00:00 ✅ ↓ Scheduler trigger → captureScheduledPanenSore() │ ├─ Get _snapshotPagiHariIni['k1'] = 45 ├─ Calculate: delta = 100 - 45 = 55 ├─ Add Panen object (sore) ke _panens ├─ Save _snapshotSoreHariIni['k1'] = 100 ├─ Save ke Firebase ├─ notifyListeners() ← UI update │ └─ State: ├─ _snapshotPagiHariIni = {kandang_1: 45} (masih ada) ├─ _snapshotSoreHariIni = {kandang_1: 100} ├─ _panens = [pagi item, +1 item sore] └─ isPanenSoreRecordedToday('k1') = true ✅ JAM 15:00:05 (After reset) ↓ resetDailySnapshots() └─ State: ├─ _snapshotPagiHariIni.clear() → {} ├─ _snapshotSoreHariIni.clear() → {} └─ Ready untuk besok pagi! BESOK JAM 09:00 ↓ Cycle berulang... ♻️ ``` --- ## 7. Firebase Structure ``` Firebase Realtime Database: telurku-fa78c (your project) │ ├─ data/ │ ├─ infra1: 45 ← Real-time sensor kandang 1 │ └─ infra2: 68 ← Real-time sensor kandang 2 │ ├─ panen_snapshot/ │ └─ 2026-03-07/ ← Date key (YYYY-MM-DD) │ ├─ pagi/ │ │ ├─ kandang_1: 45 │ │ ├─ kandang_2: 68 │ │ └─ timestamp: "2026-03-07T09:00:00Z" │ └─ sore/ │ ├─ kandang_1: 100 │ ├─ kandang_2: 150 │ ├─ delta_kandang_1: 55 │ ├─ delta_kandang_2: 82 │ └─ timestamp: "2026-03-07T15:00:00Z" │ └─ riwayat/ ├─ -abc123def/ │ ├─ id: "panen_1" │ ├─ kandang_id: "kandang_1" │ ├─ kandang_nama: "Kandang 1" │ ├─ jumlah_telur: 45 │ ├─ jam: "09:00" │ ├─ jenis_panen: "pagi" │ ├─ sensor_snapshot: 45 │ ├─ panen_sebelumnya: null │ ├─ tanggal_panen: "2026-03-07T09:00:00Z" │ └─ catatan: "Auto-capture PAGI dari scheduler" │ ├─ -def456ghi/ (sore record) │ ├─ id: "panen_2" │ ├─ kandang_id: "kandang_1" │ ├─ jumlah_telur: 55 ← DELTA! (100-45) │ ├─ jam: "15:00" │ ├─ jenis_panen: "sore" │ ├─ sensor_snapshot: 100 ← Current (100) │ ├─ panen_sebelumnya: 45 ← Previous (45) for delta │ └─ catatan: "Auto-capture SORE dari scheduler (delta: 100 - 45)" │ └─ ... (more records) ``` --- Sekarang konsep jauh lebih jelas dengan visual! 🎨