oke
This commit is contained in:
parent
080c0fb577
commit
c5413cdf95
|
|
@ -1,26 +1,129 @@
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||||
|
|
||||||
part 'device_setup_event.dart';
|
part 'device_setup_event.dart';
|
||||||
part 'device_setup_state.dart';
|
part 'device_setup_state.dart';
|
||||||
|
|
||||||
|
const _kDeviceIdKey = 'selected_device_id';
|
||||||
|
|
||||||
class DeviceSetupBloc extends Bloc<DeviceSetupEvent, DeviceSetupState> {
|
class DeviceSetupBloc extends Bloc<DeviceSetupEvent, DeviceSetupState> {
|
||||||
DeviceSetupBloc() : super(const DeviceSetupState()) {
|
final MonitoringRepository _repository;
|
||||||
|
|
||||||
|
final Dio _dio = Dio(BaseOptions(
|
||||||
|
connectTimeout: const Duration(seconds: 5),
|
||||||
|
receiveTimeout: const Duration(seconds: 10),
|
||||||
|
sendTimeout: const Duration(seconds: 5),
|
||||||
|
));
|
||||||
|
|
||||||
|
DeviceSetupBloc({required MonitoringRepository repository})
|
||||||
|
: _repository = repository,
|
||||||
|
super(const DeviceSetupState()) {
|
||||||
on<CheckEspConnectionEvent>(_onCheckConnection);
|
on<CheckEspConnectionEvent>(_onCheckConnection);
|
||||||
on<SendWifiCredentialsEvent>(_onSendCredentials);
|
on<SendWifiCredentialsEvent>(_onSendCredentials);
|
||||||
on<ResetDeviceSetupEvent>(_onReset);
|
on<ResetDeviceSetupEvent>(_onReset);
|
||||||
|
// Settings
|
||||||
|
on<DeviceSettingsStarted>(_onSettingsStarted);
|
||||||
|
on<DeviceIdChanged>(_onDeviceIdChanged);
|
||||||
|
on<KFaktorChanged>((e, emit) => emit(state.copyWith(kFaktor: e.value)));
|
||||||
|
on<RadiusChanged>((e, emit) => emit(state.copyWith(radiusM: e.value)));
|
||||||
|
on<IntervalRealtimeChanged>(
|
||||||
|
(e, emit) => emit(state.copyWith(intervalRealtimeMs: e.ms)));
|
||||||
|
on<IntervalHistoryChanged>(
|
||||||
|
(e, emit) => emit(state.copyWith(intervalHistoryMs: e.ms)));
|
||||||
|
on<DeviceSettingsSaved>(_onSettingsSaved);
|
||||||
|
on<DeviceLogsRefreshed>(_onLogsRefreshed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Dio instance khusus untuk komunikasi ke ESP ──────────────
|
// ════════════════════════════════════════════════════════════
|
||||||
// Timeout pendek karena ESP di jaringan lokal, harusnya cepat.
|
// Settings
|
||||||
// Kalau timeout → berarti HP belum konek ke hotspot ESP.
|
// ════════════════════════════════════════════════════════════
|
||||||
final Dio _dio = Dio(
|
|
||||||
BaseOptions(
|
Future<void> _onSettingsStarted(
|
||||||
connectTimeout: const Duration(seconds: 5),
|
DeviceSettingsStarted event,
|
||||||
receiveTimeout: const Duration(seconds: 10),
|
Emitter<DeviceSetupState> emit,
|
||||||
sendTimeout: const Duration(seconds: 5),
|
) async {
|
||||||
),
|
emit(state.copyWith(status: DeviceSetupStatus.settingsLoading));
|
||||||
);
|
try {
|
||||||
|
// Baca device ID tersimpan lokal
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final savedId = prefs.getString(_kDeviceIdKey) ?? state.deviceId;
|
||||||
|
|
||||||
|
// Baca settings dari Firebase
|
||||||
|
final s = await _repository.getAnemometerSettings();
|
||||||
|
|
||||||
|
// Baca logs
|
||||||
|
final logs = await _repository.getDeviceLogs(savedId);
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: DeviceSetupStatus.settingsLoaded,
|
||||||
|
deviceId: savedId,
|
||||||
|
kFaktor: s['k_faktor'] as double,
|
||||||
|
radiusM: s['radius_m'] as double,
|
||||||
|
intervalRealtimeMs: s['interval_realtime_ms'] as int,
|
||||||
|
intervalHistoryMs: s['interval_history_ms'] as int,
|
||||||
|
logs: logs,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: DeviceSetupStatus.settingsError,
|
||||||
|
errorMessage: 'Gagal memuat settings: $e',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDeviceIdChanged(
|
||||||
|
DeviceIdChanged event,
|
||||||
|
Emitter<DeviceSetupState> emit,
|
||||||
|
) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(_kDeviceIdKey, event.deviceId);
|
||||||
|
|
||||||
|
// Muat log device baru
|
||||||
|
emit(state.copyWith(
|
||||||
|
deviceId: event.deviceId,
|
||||||
|
logsLoading: true,
|
||||||
|
));
|
||||||
|
final logs = await _repository.getDeviceLogs(event.deviceId);
|
||||||
|
emit(state.copyWith(logs: logs, logsLoading: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSettingsSaved(
|
||||||
|
DeviceSettingsSaved event,
|
||||||
|
Emitter<DeviceSetupState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: DeviceSetupStatus.settingsSaving));
|
||||||
|
try {
|
||||||
|
await _repository.updateAnemometerSettings(
|
||||||
|
kFaktor: state.kFaktor,
|
||||||
|
radiusM: state.radiusM,
|
||||||
|
intervalRealtimeMs: state.intervalRealtimeMs,
|
||||||
|
intervalHistoryMs: state.intervalHistoryMs,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(status: DeviceSetupStatus.settingsSaved));
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
emit(state.copyWith(status: DeviceSetupStatus.settingsLoaded));
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: DeviceSetupStatus.settingsError,
|
||||||
|
errorMessage: 'Gagal menyimpan: $e',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLogsRefreshed(
|
||||||
|
DeviceLogsRefreshed event,
|
||||||
|
Emitter<DeviceSetupState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(logsLoading: true));
|
||||||
|
final logs = await _repository.getDeviceLogs(state.deviceId);
|
||||||
|
emit(state.copyWith(logs: logs, logsLoading: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════
|
||||||
|
// WiFi Setup (existing logic, tidak berubah)
|
||||||
|
// ════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
// ── Cek koneksi ke ESP ────────────────────────────────────────
|
// ── Cek koneksi ke ESP ────────────────────────────────────────
|
||||||
Future<void> _onCheckConnection(
|
Future<void> _onCheckConnection(
|
||||||
|
|
@ -137,6 +240,12 @@ class DeviceSetupBloc extends Bloc<DeviceSetupEvent, DeviceSetupState> {
|
||||||
ResetDeviceSetupEvent event,
|
ResetDeviceSetupEvent event,
|
||||||
Emitter<DeviceSetupState> emit,
|
Emitter<DeviceSetupState> emit,
|
||||||
) {
|
) {
|
||||||
emit(const DeviceSetupState());
|
emit(DeviceSetupState(
|
||||||
|
deviceId: state.deviceId,
|
||||||
|
kFaktor: state.kFaktor,
|
||||||
|
radiusM: state.radiusM,
|
||||||
|
intervalRealtimeMs: state.intervalRealtimeMs,
|
||||||
|
intervalHistoryMs: state.intervalHistoryMs,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,44 @@ class SendWifiCredentialsEvent extends DeviceSetupEvent {
|
||||||
|
|
||||||
/// Reset state ke awal (misalnya saat user keluar screen)
|
/// Reset state ke awal (misalnya saat user keluar screen)
|
||||||
class ResetDeviceSetupEvent extends DeviceSetupEvent {}
|
class ResetDeviceSetupEvent extends DeviceSetupEvent {}
|
||||||
|
|
||||||
|
// ── Sensor Settings events (new) ─────────────────────────────
|
||||||
|
|
||||||
|
/// Load device ID dari SharedPreferences + settings dari Firebase
|
||||||
|
class DeviceSettingsStarted extends DeviceSetupEvent {}
|
||||||
|
|
||||||
|
/// User pilih/ubah device ID (misal: esp_percobaan → esp_lapangan)
|
||||||
|
class DeviceIdChanged extends DeviceSetupEvent {
|
||||||
|
final String deviceId;
|
||||||
|
DeviceIdChanged(this.deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User ubah k_faktor di form
|
||||||
|
class KFaktorChanged extends DeviceSetupEvent {
|
||||||
|
final double value;
|
||||||
|
KFaktorChanged(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User ubah radius_m di form
|
||||||
|
class RadiusChanged extends DeviceSetupEvent {
|
||||||
|
final double value;
|
||||||
|
RadiusChanged(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User ubah interval realtime (ms)
|
||||||
|
class IntervalRealtimeChanged extends DeviceSetupEvent {
|
||||||
|
final int ms;
|
||||||
|
IntervalRealtimeChanged(this.ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User ubah interval history (ms)
|
||||||
|
class IntervalHistoryChanged extends DeviceSetupEvent {
|
||||||
|
final int ms;
|
||||||
|
IntervalHistoryChanged(this.ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simpan settings ke Firebase
|
||||||
|
class DeviceSettingsSaved extends DeviceSetupEvent {}
|
||||||
|
|
||||||
|
/// Refresh logs dari Firebase
|
||||||
|
class DeviceLogsRefreshed extends DeviceSetupEvent {}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ enum DeviceSetupStatus {
|
||||||
sending, // sedang kirim SSID+password ke ESP
|
sending, // sedang kirim SSID+password ke ESP
|
||||||
success, // ESP berhasil terima & akan restart
|
success, // ESP berhasil terima & akan restart
|
||||||
failure, // gagal (timeout, ESP tidak respond, dll)
|
failure, // gagal (timeout, ESP tidak respond, dll)
|
||||||
|
// Settings
|
||||||
|
settingsLoading,
|
||||||
|
settingsLoaded,
|
||||||
|
settingsSaving,
|
||||||
|
settingsSaved,
|
||||||
|
settingsError,
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceSetupState {
|
class DeviceSetupState {
|
||||||
|
|
@ -15,12 +21,42 @@ class DeviceSetupState {
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
final String? successMessage;
|
final String? successMessage;
|
||||||
final String espIp;
|
final String espIp;
|
||||||
|
// ── Sensor settings ──────────────────────────────────────────
|
||||||
|
/// ID device aktif — menentukan path Firebase yang dibaca app.
|
||||||
|
/// Default 'esp_lapangan' (ESP utama di lapangan).
|
||||||
|
/// Disimpan lokal di SharedPreferences.
|
||||||
|
final String deviceId;
|
||||||
|
|
||||||
|
/// Konstanta kalibrasi. Default 50.0 (hasil estimasi vs AWS).
|
||||||
|
final double kFaktor;
|
||||||
|
|
||||||
|
/// Jari-jari lengan anemometer dalam meter. Default 0.08 (8 cm).
|
||||||
|
final double radiusM;
|
||||||
|
|
||||||
|
/// Interval pengiriman realtime ke Firebase (ms). Default 1000 = 1 detik.
|
||||||
|
final int intervalRealtimeMs;
|
||||||
|
|
||||||
|
/// Interval push history ke Firebase (ms). Default 3600000 = 1 jam.
|
||||||
|
final int intervalHistoryMs;
|
||||||
|
|
||||||
|
// ── Logs ─────────────────────────────────────────────────────
|
||||||
|
final List<Map<String, dynamic>> logs;
|
||||||
|
final bool logsLoading;
|
||||||
|
|
||||||
const DeviceSetupState({
|
const DeviceSetupState({
|
||||||
this.status = DeviceSetupStatus.idle,
|
this.status = DeviceSetupStatus.idle,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
this.successMessage,
|
this.successMessage,
|
||||||
this.espIp = '192.168.4.1', // default IP ESP saat AP mode
|
this.espIp = '192.168.4.1', // default IP ESP saat AP mode
|
||||||
|
// Settings — default sama dengan cfg_config.h di ESP
|
||||||
|
this.deviceId = 'esp_lapangan',
|
||||||
|
this.kFaktor = 50.0,
|
||||||
|
this.radiusM = 0.08,
|
||||||
|
this.intervalRealtimeMs = 1000,
|
||||||
|
this.intervalHistoryMs = 3600000,
|
||||||
|
// Logs
|
||||||
|
this.logs = const [],
|
||||||
|
this.logsLoading = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
DeviceSetupState copyWith({
|
DeviceSetupState copyWith({
|
||||||
|
|
@ -28,12 +64,26 @@ class DeviceSetupState {
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
String? successMessage,
|
String? successMessage,
|
||||||
String? espIp,
|
String? espIp,
|
||||||
|
String? deviceId,
|
||||||
|
double? kFaktor,
|
||||||
|
double? radiusM,
|
||||||
|
int? intervalRealtimeMs,
|
||||||
|
int? intervalHistoryMs,
|
||||||
|
List<Map<String, dynamic>>? logs,
|
||||||
|
bool? logsLoading,
|
||||||
}) {
|
}) {
|
||||||
return DeviceSetupState(
|
return DeviceSetupState(
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
errorMessage: errorMessage,
|
errorMessage: errorMessage,
|
||||||
successMessage: successMessage,
|
successMessage: successMessage,
|
||||||
espIp: espIp ?? this.espIp,
|
espIp: espIp ?? this.espIp,
|
||||||
|
deviceId: deviceId ?? this.deviceId,
|
||||||
|
kFaktor: kFaktor ?? this.kFaktor,
|
||||||
|
radiusM: radiusM ?? this.radiusM,
|
||||||
|
intervalRealtimeMs: intervalRealtimeMs ?? this.intervalRealtimeMs,
|
||||||
|
intervalHistoryMs: intervalHistoryMs ?? this.intervalHistoryMs,
|
||||||
|
logs: logs ?? this.logs,
|
||||||
|
logsLoading: logsLoading ?? this.logsLoading,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
import '../../../../core/utils/time_series_mapper.dart';
|
import '../../../../core/utils/time_series_mapper.dart';
|
||||||
|
|
@ -13,6 +14,8 @@ part 'wind_speed_state.dart';
|
||||||
/// Referensi: Beaufort scale & BMKG
|
/// Referensi: Beaufort scale & BMKG
|
||||||
const double _kWindWarning = 8.0; // Waspada: 8–12.5 m/s (~29–45 km/h)
|
const double _kWindWarning = 8.0; // Waspada: 8–12.5 m/s (~29–45 km/h)
|
||||||
const double _kWindDanger = 12.5; // Bahaya: > 12.5 m/s (> 45 km/h)
|
const double _kWindDanger = 12.5; // Bahaya: > 12.5 m/s (> 45 km/h)
|
||||||
|
const _kDeviceIdKey = 'selected_device_id';
|
||||||
|
const _kDefaultDeviceId = 'esp_lapangan';
|
||||||
|
|
||||||
class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
final MonitoringRepository _repository;
|
final MonitoringRepository _repository;
|
||||||
|
|
@ -31,6 +34,12 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
on<WindSpeedDateFilterChanged>(_onDateFilterChanged); // ← baru
|
on<WindSpeedDateFilterChanged>(_onDateFilterChanged); // ← baru
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Baca device ID dari SharedPreferences ─────────────────
|
||||||
|
Future<String> _getDeviceId() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString(_kDeviceIdKey) ?? _kDefaultDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════
|
||||||
// START
|
// START
|
||||||
// ════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════
|
||||||
|
|
@ -40,6 +49,9 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
) async {
|
) async {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
|
|
||||||
|
// Baca device ID aktif (dari SharedPreferences, diset di DeviceSetupBloc)
|
||||||
|
final deviceId = await _getDeviceId();
|
||||||
|
|
||||||
final history = await _repository.getSensorHistory(
|
final history = await _repository.getSensorHistory(
|
||||||
'anemometer/esp_percobaan/history',
|
'anemometer/esp_percobaan/history',
|
||||||
(json) => MyWindSpeed.fromJson(json),
|
(json) => MyWindSpeed.fromJson(json),
|
||||||
|
|
@ -82,15 +94,14 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
|
||||||
history.isNotEmpty ? _getAlertLevel(history.last.speed) : 'Normal',
|
history.isNotEmpty ? _getAlertLevel(history.last.speed) : 'Normal',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Subscribe realtime dari path device aktif
|
||||||
await _subscription?.cancel();
|
await _subscription?.cancel();
|
||||||
_subscription = _repository
|
_subscription = _repository
|
||||||
.getSensorStream(
|
.getSensorStream(
|
||||||
'anemometer/esp_percobaan/realtime',
|
'anemometer/$deviceId/realtime',
|
||||||
(json) => MyWindSpeed.fromJson(json),
|
(json) => MyWindSpeed.fromJson(json),
|
||||||
)
|
)
|
||||||
.listen((data) {
|
.listen((data) => add(_WindSpeedRealtimeUpdated(data)));
|
||||||
add(_WindSpeedRealtimeUpdated(data));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
// import 'dart:developer';
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
// import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||||
|
|
||||||
|
|
@ -15,57 +13,46 @@ class FirebaseMonitoringRepo implements MonitoringRepository {
|
||||||
String path,
|
String path,
|
||||||
T Function(Map<dynamic, dynamic> json) mapper,
|
T Function(Map<dynamic, dynamic> json) mapper,
|
||||||
) {
|
) {
|
||||||
/// === ambil data mentah dari firebase === ///
|
|
||||||
return _db.ref(path).onValue.map((event) {
|
return _db.ref(path).onValue.map((event) {
|
||||||
final Object? value = event.snapshot.value;
|
final Object? value = event.snapshot.value;
|
||||||
|
return mapper(value is Map ? value : {});
|
||||||
if (value is Map) {
|
|
||||||
return mapper(value);
|
|
||||||
} else {
|
|
||||||
// Jika data kosong atau bukan Map, berikan Map kosong agar mapper tidak crash
|
|
||||||
return mapper({});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Snapshot ─────────────────────────────────────────────────
|
||||||
@override
|
@override
|
||||||
// Jika ingin mengambil data sekali saja (bukan stream)
|
|
||||||
Future<T> getSensorSnapshot<T>(
|
Future<T> getSensorSnapshot<T>(
|
||||||
String path,
|
String path,
|
||||||
T Function(Map<dynamic, dynamic> json) mapper,
|
T Function(Map<dynamic, dynamic> json) mapper,
|
||||||
) async {
|
) async {
|
||||||
final snapshot = await _db.ref(path).get();
|
final snapshot = await _db.ref(path).get();
|
||||||
final data = snapshot.value as Map<dynamic, dynamic>? ?? {};
|
return mapper(
|
||||||
return mapper(data);
|
snapshot.value is Map ? snapshot.value as Map<dynamic, dynamic> : {},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── History ──────────────────────────────────────────────────
|
||||||
@override
|
@override
|
||||||
Future<List<T>> getSensorHistory<T>(
|
Future<List<T>> getSensorHistory<T>(
|
||||||
String path,
|
String path,
|
||||||
T Function(Map<dynamic, dynamic> json) mapper,
|
T Function(Map<dynamic, dynamic> json) mapper,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
// Ambil data dari path history
|
|
||||||
final snapshot = await _db.ref(path).get();
|
final snapshot = await _db.ref(path).get();
|
||||||
|
|
||||||
if (snapshot.exists && snapshot.value is Map) {
|
if (snapshot.exists && snapshot.value is Map) {
|
||||||
final Map<dynamic, dynamic> data = snapshot.value as Map;
|
final data = snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
final list = data.values
|
||||||
final list = data.values.map((item) {
|
.map((item) => mapper(item as Map<dynamic, dynamic>))
|
||||||
return mapper(item as Map<dynamic, dynamic>);
|
.toList();
|
||||||
}).toList();
|
|
||||||
|
|
||||||
// ✅ Sort by timestamp — Firebase push tidak selalu menghasilkan urutan kronologis.
|
|
||||||
list.sort((a, b) {
|
list.sort((a, b) {
|
||||||
try {
|
try {
|
||||||
final aTimestamp = (a as dynamic).timestamp as DateTime;
|
final aT = (a as dynamic).timestamp as DateTime;
|
||||||
final bTimestamp = (b as dynamic).timestamp as DateTime;
|
final bT = (b as dynamic).timestamp as DateTime;
|
||||||
return aTimestamp.compareTo(bTimestamp);
|
return aT.compareTo(bT);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -74,29 +61,40 @@ class FirebaseMonitoringRepo implements MonitoringRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Baca settings dari Firebase ──────────────────────────────
|
// ── Anemometer Settings ──────────────────────────────────────
|
||||||
|
@override
|
||||||
Future<Map<String, dynamic>> getAnemometerSettings() async {
|
Future<Map<String, dynamic>> getAnemometerSettings() async {
|
||||||
|
// Default sama dengan cfg_config.h di ESP
|
||||||
|
const defaults = <String, dynamic>{
|
||||||
|
'k_faktor': 50.0,
|
||||||
|
'radius_m': 0.08,
|
||||||
|
'interval_realtime_ms': 1000,
|
||||||
|
'interval_history_ms': 3600000,
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
final snapshot = await _db.ref('anemometer/settings').get();
|
final snapshot = await _db.ref('anemometer/settings').get();
|
||||||
if (snapshot.exists && snapshot.value is Map) {
|
if (snapshot.exists && snapshot.value is Map) {
|
||||||
final raw = snapshot.value as Map<dynamic, dynamic>;
|
final raw = snapshot.value as Map<dynamic, dynamic>;
|
||||||
return {
|
return {
|
||||||
'k_faktor': (raw['k_faktor'] ?? 50.0).toDouble(),
|
'k_faktor': (raw['k_faktor'] ?? defaults['k_faktor']).toDouble(),
|
||||||
'interval_realtime_ms': (raw['interval_realtime_ms'] ?? 1000) as int,
|
'radius_m': (raw['radius_m'] ?? defaults['radius_m']).toDouble(),
|
||||||
'interval_history_ms': (raw['interval_history_ms'] ?? 3600000) as int,
|
'interval_realtime_ms':
|
||||||
|
(raw['interval_realtime_ms'] ?? defaults['interval_realtime_ms'])
|
||||||
|
as int,
|
||||||
|
'interval_history_ms':
|
||||||
|
(raw['interval_history_ms'] ?? defaults['interval_history_ms'])
|
||||||
|
as int,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
return {
|
|
||||||
'k_faktor': 50.0,
|
return defaults;
|
||||||
'interval_realtime_ms': 1000,
|
|
||||||
'interval_history_ms': 3600000,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tulis settings ke Firebase (dari app) ────────────────────
|
// ── Tulis settings ke Firebase (dari app) ────────────────────
|
||||||
Future<void> updateAnemometerSettings({
|
Future<void> updateAnemometerSettings({
|
||||||
double? kFaktor,
|
double? kFaktor,
|
||||||
|
double? radiusM,
|
||||||
int? intervalRealtimeMs,
|
int? intervalRealtimeMs,
|
||||||
int? intervalHistoryMs,
|
int? intervalHistoryMs,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
// import 'models/models.dart';
|
|
||||||
|
|
||||||
abstract class MonitoringRepository {
|
abstract class MonitoringRepository {
|
||||||
// fungsi untuk ambil data berkali/streaming
|
// ── Stream & Snapshot ────────────────────────────────────────
|
||||||
Stream<T> getSensorStream<T>(
|
Stream<T> getSensorStream<T>(
|
||||||
String path,
|
String path,
|
||||||
T Function(Map<dynamic, dynamic> json) mapper,
|
T Function(Map<dynamic, dynamic> json) mapper,
|
||||||
|
|
@ -18,4 +16,26 @@ abstract class MonitoringRepository {
|
||||||
String path,
|
String path,
|
||||||
T Function(Map<dynamic, dynamic> json) mapper,
|
T Function(Map<dynamic, dynamic> json) mapper,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ── Anemometer Settings ──────────────────────────────────────
|
||||||
|
/// Baca semua settings dari /anemometer/settings/
|
||||||
|
/// Return map dengan keys:
|
||||||
|
/// k_faktor (double), radius_m (double),
|
||||||
|
/// interval_realtime_ms (int), interval_history_ms (int)
|
||||||
|
Future<Map<String, dynamic>> getAnemometerSettings();
|
||||||
|
|
||||||
|
/// Tulis settings ke /anemometer/settings/ (field yang null tidak ditulis)
|
||||||
|
Future<void> updateAnemometerSettings({
|
||||||
|
double? kFaktor,
|
||||||
|
double? radiusM,
|
||||||
|
int? intervalRealtimeMs,
|
||||||
|
int? intervalHistoryMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Device Logs ──────────────────────────────────────────────
|
||||||
|
/// Ambil N log terakhir dari /anemometer/{deviceId}/logs
|
||||||
|
Future<List<Map<String, dynamic>>> getDeviceLogs(
|
||||||
|
String deviceId, {
|
||||||
|
int limit = 50,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue