Revert "tes"

This reverts commit fff7f8b5db, reversing
changes made to 2675ed7bd4.
This commit is contained in:
kleponijo 2026-05-09 15:43:31 +07:00
parent fff7f8b5db
commit 6a6b6a43cd
14 changed files with 137 additions and 820 deletions

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,3 @@
import 'package:monitoring_repository/monitoring_repository.dart';
class TimeSeriesMapper {
/// =========================
/// 📅 DAILY (24 JAM)
@ -144,30 +142,4 @@ class TimeSeriesMapper {
return result;
}
// Tambahkan ini setelah method smooth()
/// Shortcut untuk model yang implement HasTimestamp.
/// Dari: TimeSeriesMapper.toDaily(data: h, getTime: (e) => e.timestamp, getValue: (e) => e.speed)
/// Ke: TimeSeriesMapper.dailyFrom(h, (e) => e.speed)
static List<double> dailyFrom<T extends HasTimestamp>(
List<T> data,
double Function(T) getValue,
) =>
smooth(
toDaily(data: data, getTime: (e) => e.timestamp, getValue: getValue));
static List<double> weeklyFrom<T extends HasTimestamp>(
List<T> data,
double Function(T) getValue,
) =>
smooth(toWeekly(
data: data, getTime: (e) => e.timestamp, getValue: getValue));
static List<double> monthlyFrom<T extends HasTimestamp>(
List<T> data,
double Function(T) getValue,
) =>
smooth(toMonthly(
data: data, getTime: (e) => e.timestamp, getValue: getValue));
}

View File

@ -48,16 +48,32 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
final history = await _repository.getSensorHistory(
'anemometer/history',
(json) => MyWindSpeed.fromJson(json),
orderByChild: 'timestamp',
limit: 500,
);
final dailyGraph =
TimeSeriesMapper.dailyFrom<MyWindSpeed>(history, (e) => e.speed);
final weekly =
TimeSeriesMapper.weeklyFrom<MyWindSpeed>(history, (e) => e.speed);
final monthly =
TimeSeriesMapper.monthlyFrom<MyWindSpeed>(history, (e) => e.speed);
// Ini saja yang benar
final dailyGraph = TimeSeriesMapper.smooth(
TimeSeriesMapper.toDaily(
data: history,
getTime: (e) => e.timestamp,
getValue: (e) => e.speed,
),
);
final weekly = TimeSeriesMapper.smooth(
TimeSeriesMapper.toWeekly(
data: history,
getTime: (e) => e.timestamp,
getValue: (e) => e.speed,
),
);
final monthly = TimeSeriesMapper.smooth(
TimeSeriesMapper.toMonthly(
data: history,
getTime: (e) => e.timestamp,
getValue: (e) => e.speed,
),
);
// Cek kondisi awal dari history
if (history.isNotEmpty) {
@ -125,11 +141,48 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
/// =========================
/// 📊 CHANGE PERIOD
/// =========================
void _onPeriodChanged(
Future<void> _onPeriodChanged(
WindSpeedPeriodChanged event,
Emitter<WindSpeedState> emit,
) {
) async {
emit(state.copyWith(selectedPeriod: event.period));
final history = state.history;
List<double> raw;
if (event.period == "Minggu Ini") {
raw = TimeSeriesMapper.toWeekly(
data: history,
getTime: (e) => e.timestamp,
getValue: (e) => e.speed,
);
} else if (event.period == "Bulan Ini") {
raw = TimeSeriesMapper.toMonthly(
data: history,
getTime: (e) => e.timestamp,
getValue: (e) => e.speed,
);
} else {
raw = TimeSeriesMapper.toDaily(
data: history,
getTime: (e) => e.timestamp,
getValue: (e) => e.speed,
);
}
/// 🔥 baru di-smooth
final updatedGraph = TimeSeriesMapper.smooth(raw);
emit(state.copyWith(
dailySpeeds:
event.period == "Hari Ini" ? updatedGraph : state.dailySpeeds,
weeklySpeeds:
event.period == "Minggu Ini" ? updatedGraph : state.weeklySpeeds,
monthlySpeeds:
event.period == "Bulan Ini" ? updatedGraph : state.monthlySpeeds,
isLoading: false,
));
}
@override

View File

@ -3,4 +3,3 @@ library monitoring_repository;
export 'src/monitoring_repo.dart';
export 'src/firebase_monitoring_repo.dart';
export 'src/models/models.dart';
export 'src/utils/timestamp_parser.dart';

View File

@ -1,61 +1,72 @@
// import 'dart:developer';
import 'dart:async';
// import 'package:rxdart/rxdart.dart';
import 'package:firebase_database/firebase_database.dart';
import 'monitoring_repo.dart';
import 'models/has_timestamp.dart';
import 'models/models.dart';
import 'package:monitoring_repository/monitoring_repository.dart';
/// === ambil data dari realtime database firebase === ///
class FirebaseMonitoringRepo implements MonitoringRepository {
final FirebaseDatabase _db = FirebaseDatabase.instance;
// Satu fungsi untuk semua jenis sensor
// Kamu cukup masukkan "path" database-nya saja
@override
Stream<T> getSensorStream<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper,
) {
/// === ambil data mentah dari firebase === ///
return _db.ref(path).onValue.map((event) {
final value = event.snapshot.value;
return mapper(value is Map ? value : {});
final Object? value = event.snapshot.value;
if (value is Map) {
return mapper(value);
} else {
// Jika data kosong atau bukan Map, berikan Map kosong agar mapper tidak crash
return mapper({});
}
});
}
@override
// Jika ingin mengambil data sekali saja (bukan stream)
Future<T> getSensorSnapshot<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper,
) async {
final snapshot = await _db.ref(path).get();
return mapper(snapshot.value as Map<dynamic, dynamic>? ?? {});
final data = snapshot.value as Map<dynamic, dynamic>? ?? {};
return mapper(data);
}
@override
Future<List<T>> getSensorHistory<T extends HasTimestamp>(
Future<List<T>> getSensorHistory<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper, {
String? orderByChild,
int limit = 500,
}) async {
T Function(Map<dynamic, dynamic> json) mapper,
) async {
try {
Query query = _db.ref(path);
// Ambil data dari path history
final snapshot = await _db.ref(path).get();
if (orderByChild != null) {
query = query.orderByChild(orderByChild).limitToLast(limit);
} else {
query = query.limitToLast(limit);
if (snapshot.exists && snapshot.value is Map) {
final Map<dynamic, dynamic> data = snapshot.value as Map;
final list = data.values.map((item) {
return mapper(item as Map<dynamic, dynamic>);
}).toList();
// Sort by timestamp Firebase push tidak jamin urutan
list.sort((a, b) {
if (a is MyWindSpeed && b is MyWindSpeed) {
return a.timestamp.compareTo(b.timestamp);
}
return 0;
});
return list;
}
final snapshot = await query.get();
if (!snapshot.exists || snapshot.value is! Map) return [];
final data = snapshot.value as Map<dynamic, dynamic>;
final list = data.values
.whereType<Map>()
.map((item) => mapper(item))
.toList();
// Sort generik works untuk SEMUA sensor karena T extends HasTimestamp
list.sort((a, b) => a.timestamp.compareTo(b.timestamp));
return list;
} catch (_) {
return [];
} catch (e) {
return [];
}
}

View File

@ -1,13 +1,7 @@
import 'has_timestamp.dart';
import '../utils/timestamp_parser.dart';
class Evaporasi implements HasTimestamp {
class Evaporasi {
final double evaporasi;
final double suhu;
final double tinggiAir;
final String status;
@override
final DateTime timestamp;
Evaporasi({
@ -15,7 +9,6 @@ class Evaporasi implements HasTimestamp {
required this.suhu,
required this.tinggiAir,
required this.timestamp,
this.status = '',
});
static final empty = Evaporasi(
@ -26,22 +19,27 @@ class Evaporasi implements HasTimestamp {
);
factory Evaporasi.fromJson(Map<dynamic, dynamic> json) {
// Handle perbedaan field name antara history dan realtime:
// History path : suhu, tinggi, evaporasi, waktu
// Realtime path : suhu_air, tinggi_air, evaporasi, waktu, status
final suhu = (json['suhu'] ?? json['suhu_air'] ?? 0).toDouble();
final tinggi = (json['tinggi'] ?? json['tinggi_air'] ?? 0).toDouble();
final now = DateTime.now();
DateTime timestamp = now;
// Prioritas: timestamp (Unix, kalau firmware sudah diupdate)
// fallback ke waktu ("HH:MM:SS")
final rawTime = json['timestamp'] ?? json['waktu'];
final waktuStr = json['waktu'] as String?;
if (waktuStr != null) {
final parts = waktuStr.split(':');
if (parts.length >= 2) {
final jam = int.tryParse(parts[0]) ?? 0;
final menit = int.tryParse(parts[1]) ?? 0;
final detik = parts.length >= 3 ? (int.tryParse(parts[2]) ?? 0) : 0;
timestamp = DateTime(now.year, now.month, now.day, jam, menit, detik);
}
}
return Evaporasi(
evaporasi: (json['evaporasi'] ?? 0).toDouble(),
suhu: suhu,
tinggiAir: tinggi,
status: json['status'] ?? '',
timestamp: TimestampParser.parse(rawTime), // pakai TimestampParser
suhu: (json['suhu_air'] ?? 0).toDouble(),
tinggiAir: (json['tinggi_air'] ?? 0).toDouble(),
/// 🔥 bikin timestamp dari jam & menit
timestamp: timestamp,
);
}
}

View File

@ -1,5 +0,0 @@
/// Semua model sensor wajib implement ini.
/// Memungkinkan sorting & TimeSeriesMapper yang generik.
abstract class HasTimestamp {
DateTime get timestamp;
}

View File

@ -1,4 +1,3 @@
export 'wind_speed.dart';
export 'evaporasi.dart';
export 'atmospheric_conditions.dart';
export 'has_timestamp.dart';

View File

@ -1,23 +1,23 @@
import 'has_timestamp.dart';
import '../utils/timestamp_parser.dart';
class MyWindSpeed {
double speed;
class MyWindSpeed implements HasTimestamp {
final double speed;
@override
final DateTime timestamp;
DateTime timestamp;
MyWindSpeed({required this.speed, required this.timestamp});
static final empty = MyWindSpeed(
speed: 0.0,
timestamp: DateTime.fromMillisecondsSinceEpoch(0),
);
factory MyWindSpeed.fromJson(Map<dynamic, dynamic> json) {
return MyWindSpeed(
speed: (json['speed'] ?? 0).toDouble(),
timestamp: TimestampParser.parse(json['timestamp']),
timestamp: DateTime.fromMillisecondsSinceEpoch(
(json['timestamp'] ?? 0) * 1000, // kalau dari Unix detik
).toLocal(),
);
}
}

View File

@ -1,22 +1,21 @@
/// file monitoring_repo.dart di "C:\App_project\klimatologi\packages\monitoring_repository\lib\src\monitoring_repo.dart"
import 'models/has_timestamp.dart';
// import 'models/models.dart';
abstract class MonitoringRepository {
// fungsi untuk ambil data berkali/streaming
Stream<T> getSensorStream<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper,
);
// fungsi untuk ambil sensor sekali
Future<T> getSensorSnapshot<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper,
);
Future<List<T>> getSensorHistory<T extends HasTimestamp>(
// Fungsi untuk ambil riwayat (List)
Future<List<T>> getSensorHistory<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper, {
String? orderByChild,
int limit = 500,
});
T Function(Map<dynamic, dynamic> json) mapper,
);
}

View File

@ -1,46 +0,0 @@
/// Mengubah berbagai format waktu Firebase DateTime.
///
/// Format yang didukung:
/// int Unix detik 1777807041 (anemometer)
/// int Unix milidetik 1777950497446 (sensor/atmospheric)
/// String "HH:MM:SS" "11:09:00" (Monitoring/evaporasi, intraday only)
/// String ISO 8601 "2025-03-05T14:26" (untuk masa depan)
class TimestampParser {
// Angka > ini dianggap milidetik (threshold: ~Nov 2001 dalam detik)
static const int _msThreshold = 10000000000;
static DateTime parse(dynamic raw) {
if (raw == null) return DateTime.now();
if (raw is int) {
if (raw <= 0) return DateTime.now();
if (raw > _msThreshold) {
return DateTime.fromMillisecondsSinceEpoch(raw).toLocal();
}
return DateTime.fromMillisecondsSinceEpoch(raw * 1000).toLocal();
}
if (raw is double) return parse(raw.toInt());
if (raw is String) {
// "HH:MM:SS" pakai tanggal hari ini (intraday only)
final m = RegExp(r'^(\d{1,2}):(\d{2}):(\d{2})$').firstMatch(raw);
if (m != null) {
final now = DateTime.now();
return DateTime(
now.year,
now.month,
now.day,
int.parse(m.group(1)!),
int.parse(m.group(2)!),
int.parse(m.group(3)!),
);
}
try {
return DateTime.parse(raw).toLocal();
} catch (_) {}
}
return DateTime.now();
}
}