TKK_E32231105/lib/services/panen_scheduler.dart

364 lines
11 KiB
Dart

// import 'package:workmanager/workmanager.dart'; // TODO: Enable when workmanager is added back
import 'package:flutter/foundation.dart';
import 'package:firebase_database/firebase_database.dart';
// import '../providers/panen_provider.dart'; // TODO: Enable when workmanager is added back
final FirebaseDatabase _database = FirebaseDatabase.instance;
/// Scheduler service untuk otomasi capture panen pada jam 09:00 dan 15:00
class PanenScheduler {
static const String pagiTaskId = 'panen_pagi_task';
static const String soreTaskId = 'panen_sore_task';
/// Inisialisasi Workmanager dan setup background tasks
/// Panggil ini di main.dart saat app start
static Future<void> initialize() async {
try {
// TODO: Enable when workmanager is added back
// await Workmanager().initialize(
// callbackDispatcher,
// isInDebugMode: kDebugMode,
// );
// Setup periodic tasks
await setupPanenSchedules();
if (kDebugMode) {
print('✅ PanenScheduler initialized successfully');
}
} catch (e) {
if (kDebugMode) {
print('❌ Error initializing PanenScheduler: $e');
}
}
}
/// Setup scheduled tasks untuk panen pagi (09:00) dan sore (15:00)
static Future<void> setupPanenSchedules() async {
try {
// TODO: Enable when workmanager is added back
// Panen Pagi: Setiap hari jam 09:00
// await Workmanager().registerPeriodicTask(
// pagiTaskId,
// 'panenPagiTask',
// frequency: const Duration(hours: 24),
// initialDelay: _calculateDelay(9, 0), // Delay ke jam 09:00 berikutnya
// constraints: Constraints(networkType: NetworkType.connected),
// backoffPolicy: BackoffPolicy.exponential,
// backoffPolicyDelay: const Duration(minutes: 15),
// );
// Panen Sore: Setiap hari jam 15:00
// await Workmanager().registerPeriodicTask(
// soreTaskId,
// 'panenSoreTask',
// frequency: const Duration(hours: 24),
// initialDelay: _calculateDelay(15, 0), // Delay ke jam 15:00 berikutnya
// constraints: Constraints(networkType: NetworkType.connected),
// backoffPolicy: BackoffPolicy.exponential,
// backoffPolicyDelay: const Duration(minutes: 15),
// );
if (kDebugMode) {
print('✅ Panen schedules registered:');
print(' - Panen Pagi: 09:00 setiap hari');
print(' - Panen Sore: 15:00 setiap hari');
}
} catch (e) {
if (kDebugMode) {
print('❌ Error setting up schedules: $e');
}
}
}
/// Hitung delay sampai waktu target (hour:minute) berikutnya
// ignore: unused_element
static Duration _calculateDelay(int targetHour, int targetMinute) {
final now = DateTime.now();
final target = DateTime(
now.year,
now.month,
now.day,
targetHour,
targetMinute,
);
// Jika waktu target sudah lewat hari ini, target untuk besok
if (target.isBefore(now)) {
return target.add(const Duration(days: 1)).difference(now);
}
return target.difference(now);
}
/// Hentikan semua scheduler
static Future<void> cancelAllTasks() async {
try {
// TODO: Enable when workmanager is added back
// await Workmanager().cancelAll();
if (kDebugMode) {
print('✅ All tasks cancelled');
}
} catch (e) {
if (kDebugMode) {
print('❌ Error cancelling tasks: $e');
}
}
}
/// Hentikan task tertentu
static Future<void> cancelTask(String taskId) async {
try {
// TODO: Enable when workmanager is added back
// await Workmanager().cancelByTag(taskId);
if (kDebugMode) {
print('✅ Task $taskId cancelled');
}
} catch (e) {
if (kDebugMode) {
print('❌ Error cancelling task $taskId: $e');
}
}
}
// ========== DEBUG METHODS ==========
/// Manual trigger untuk panen pagi (untuk testing/debug)
static Future<Map<String, dynamic>> triggerPanenPagiManual() async {
try {
if (kDebugMode) {
print('🔧 [DEBUG] Manual trigger: Panen Pagi');
}
await _executePanenPagi();
return {
'success': true,
'message': 'Panen Pagi berhasil disimpan',
'timestamp': DateTime.now().toIso8601String(),
};
} catch (e) {
if (kDebugMode) {
print('❌ [DEBUG] Error manual panen pagi: $e');
}
return {
'success': false,
'message': 'Error: $e',
'timestamp': DateTime.now().toIso8601String(),
};
}
}
/// Manual trigger untuk panen sore (untuk testing/debug)
static Future<Map<String, dynamic>> triggerPanenSoreManual() async {
try {
if (kDebugMode) {
print('🔧 [DEBUG] Manual trigger: Panen Sore');
}
await _executePanenSore();
return {
'success': true,
'message': 'Panen Sore berhasil disimpan',
'timestamp': DateTime.now().toIso8601String(),
};
} catch (e) {
if (kDebugMode) {
print('❌ [DEBUG] Error manual panen sore: $e');
}
return {
'success': false,
'message': 'Error: $e',
'timestamp': DateTime.now().toIso8601String(),
};
}
}
/// Get snapshot panen hari ini (untuk debug/monitoring)
static Future<Map<String, dynamic>?> getTodaySnapshot() async {
try {
final snapshotRef = _database.ref('panen_snapshot/${_getTodayKey()}');
final snapshot = await snapshotRef.get();
if (snapshot.exists) {
return Map<String, dynamic>.from(snapshot.value as Map);
}
return null;
} catch (e) {
if (kDebugMode) {
print('❌ Error getting snapshot: $e');
}
return null;
}
}
/// Get next scheduled time (untuk tampilan UI)
static Map<String, String> getNextScheduledTimes() {
final now = DateTime.now();
// Calculate next pagi (09:00)
DateTime nextPagi = DateTime(now.year, now.month, now.day, 9, 0);
if (nextPagi.isBefore(now)) {
nextPagi = nextPagi.add(const Duration(days: 1));
}
// Calculate next sore (15:00)
DateTime nextSore = DateTime(now.year, now.month, now.day, 15, 0);
if (nextSore.isBefore(now)) {
nextSore = nextSore.add(const Duration(days: 1));
}
return {
'pagi':
'${nextPagi.year}-${nextPagi.month.toString().padLeft(2, '0')}-${nextPagi.day.toString().padLeft(2, '0')} 09:00',
'sore':
'${nextSore.year}-${nextSore.month.toString().padLeft(2, '0')}-${nextSore.day.toString().padLeft(2, '0')} 15:00',
};
}
}
/// Callback dispatcher untuk background tasks
/// PENTING: Ini harus top-level function atau static method
/// Tidak bisa di-refactor menjadi method di class
@pragma('vm:entry-point')
void callbackDispatcher() {
// TODO: Enable when workmanager is added back
/*
Workmanager().executeTask((taskName, inputData) async {
try {
if (kDebugMode) {
print('🔔 Running task: $taskName at ${DateTime.now()}');
}
switch (taskName) {
case 'panenPagiTask':
await _executePanenPagi();
break;
case 'panenSoreTask':
await _executePanenSore();
break;
default:
if (kDebugMode) {
print('⚠️ Unknown task: $taskName');
}
}
return true; // Task berhasil
} catch (e) {
if (kDebugMode) {
print('❌ Error executing task $taskName: $e');
}
return false; // Task gagal, akan di-retry
}
});
*/
}
/// Eksekusi panen pagi: ambil nilai sensor saat ini
Future<void> _executePanenPagi() async {
try {
if (kDebugMode) {
print('📍 Executing Panen Pagi (09:00)');
}
// Baca nilai sensor current dari Firebase
final dataRef = _database.ref('data');
final snapshot = await dataRef.get();
if (snapshot.exists) {
final data = Map<dynamic, dynamic>.from(snapshot.value as Map);
// Ambil setiap kandang dan capture sensor value
// Data mapping: infra1 untuk kandang_1, infra2 untuk kandang_2, dst
final infra1Value = data['infra1'] ?? 0;
final infra2Value = data['infra2'] ?? 0;
// Simpan snapshot pagi di Firebase untuk reference
await _database.ref('panen_snapshot/${_getTodayKey()}').set({
'pagi': {
'kandang_1': infra1Value,
'kandang_2': infra2Value,
'timestamp': DateTime.now().toIso8601String(),
},
});
if (kDebugMode) {
print(
'✅ Panen Pagi snapshot saved: kandang_1=$infra1Value, kandang_2=$infra2Value',
);
}
// TODO: Trigger PanenProvider.addPanenForSchedule() untuk kandang_1 dan kandang_2
// (Akan dipanggil dari main app via PanenProvider)
}
} catch (e) {
if (kDebugMode) {
print('❌ Error in _executePanenPagi: $e');
}
}
}
/// Eksekusi panen sore: hitung delta dari pagi
Future<void> _executePanenSore() async {
try {
if (kDebugMode) {
print('📍 Executing Panen Sore (15:00)');
}
// Baca nilai sensor current
final dataRef = _database.ref('data');
final snapshot = await dataRef.get();
if (snapshot.exists) {
final data = Map<dynamic, dynamic>.from(snapshot.value as Map);
final infra1Value = data['infra1'] ?? 0;
final infra2Value = data['infra2'] ?? 0;
// Baca snapshot pagi hari ini untuk delta calculation
final pagiSnapshotRef = _database.ref(
'panen_snapshot/${_getTodayKey()}/pagi',
);
final pagiSnapshot = await pagiSnapshotRef.get();
int nilaiPagiKandang1 = 0;
int nilaiPagiKandang2 = 0;
if (pagiSnapshot.exists) {
final pagiData = Map<dynamic, dynamic>.from(pagiSnapshot.value as Map);
nilaiPagiKandang1 = pagiData['kandang_1'] ?? 0;
nilaiPagiKandang2 = pagiData['kandang_2'] ?? 0;
}
// Hitung delta
final deltaKandang1 = infra1Value - nilaiPagiKandang1;
final deltaKandang2 = infra2Value - nilaiPagiKandang2;
// Simpan snapshot sore di Firebase
await _database.ref('panen_snapshot/${_getTodayKey()}').update({
'sore': {
'kandang_1': infra1Value,
'kandang_2': infra2Value,
'delta_kandang_1': deltaKandang1,
'delta_kandang_2': deltaKandang2,
'timestamp': DateTime.now().toIso8601String(),
},
});
if (kDebugMode) {
print(
'✅ Panen Sore calculated: delta_kandang_1=$deltaKandang1, delta_kandang_2=$deltaKandang2',
);
}
// TODO: Trigger PanenProvider.addPanenForSchedule() dengan delta values
}
} catch (e) {
if (kDebugMode) {
print('❌ Error in _executePanenSore: $e');
}
}
}
/// Helper: Get today's date key (format: yyyy-MM-dd)
String _getTodayKey() {
final now = DateTime.now();
return '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
}