114 lines
4.4 KiB
Dart
114 lines
4.4 KiB
Dart
import 'package:dio/dio.dart';
|
|
import '../models/sensor_model.dart';
|
|
import '../core/api_config.dart';
|
|
import 'iot_service.dart';
|
|
|
|
/// Implementasi IotService yang terhubung ke Backend Express
|
|
class ApiIotService implements IotService {
|
|
final Dio _dio;
|
|
final String deviceId;
|
|
|
|
ApiIotService(this.deviceId, [Dio? dio]) : _dio = dio ?? Dio();
|
|
|
|
@override
|
|
Future<SensorModel> fetchSensorData() async {
|
|
try {
|
|
// Ambil beberapa data terbaru agar bisa skip entry yang null (misal dari relay log)
|
|
final response = await _dio.get('${ApiConfig.baseUrl}/history/$deviceId?limit=30');
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = response.data;
|
|
if (data.isEmpty) {
|
|
return SensorModel(temperature: 0, humidity: 0, timestamp: DateTime.now());
|
|
}
|
|
|
|
final firstItem = data.first;
|
|
// Cari entry pertama yang punya nilai sensor valid (bukan null)
|
|
final validItem = data.firstWhere(
|
|
(item) => item['temperature'] != null && item['humidity'] != null,
|
|
orElse: () => null,
|
|
);
|
|
|
|
if (validItem != null) {
|
|
return SensorModel(
|
|
temperature: (validItem['temperature'] as num).toDouble(),
|
|
humidity: (validItem['humidity'] as num).toDouble(),
|
|
relayOn: firstItem['relay_state'] as bool? ?? false,
|
|
mode: firstItem['mode'] as String? ?? 'auto',
|
|
timestamp: DateTime.parse(firstItem['created_at']).toLocal(),
|
|
);
|
|
} else {
|
|
// Belum ada data sensor valid — kembalikan placeholder tapi relay state tetap valid
|
|
return SensorModel(
|
|
temperature: 0,
|
|
humidity: 0,
|
|
relayOn: firstItem['relay_state'] as bool? ?? false,
|
|
mode: firstItem['mode'] as String? ?? 'auto',
|
|
timestamp: DateTime.parse(firstItem['created_at']).toLocal()
|
|
);
|
|
}
|
|
} else {
|
|
throw Exception('Failed to fetch sensor data: ${response.statusCode}');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Error fetching data: $e');
|
|
}
|
|
}
|
|
|
|
// Fungsi tambahan untuk mengambil histori secara lengkap
|
|
Future<List<SensorModel>> fetchHistory({int limit = 100}) async {
|
|
try {
|
|
final response = await _dio.get('${ApiConfig.baseUrl}/history/$deviceId?limit=$limit');
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = response.data;
|
|
// Data dari API diurutkan DESC (terbaru di atas),
|
|
// kita reverse agar berurutan ASC (terlama ke terbaru) untuk grafik.
|
|
// Skip entry yang temperature/humidity-nya null (misal dari relay log)
|
|
final validData = data.where(
|
|
(item) => item['temperature'] != null && item['humidity'] != null,
|
|
).toList();
|
|
return validData.reversed.map((item) => SensorModel(
|
|
temperature: (item['temperature'] as num).toDouble(),
|
|
humidity: (item['humidity'] as num).toDouble(),
|
|
relayOn: item['relay_state'] as bool? ?? false,
|
|
mode: item['mode'] as String? ?? 'auto',
|
|
timestamp: DateTime.parse(item['created_at']).toLocal(),
|
|
)).toList();
|
|
} else {
|
|
throw Exception('Failed to fetch history: ${response.statusCode}');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Error fetching history: $e');
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk mengambil agregasi harian
|
|
Future<List<Map<String, dynamic>>> fetchDailyHistory({int days = 30}) async {
|
|
try {
|
|
final response = await _dio.get('${ApiConfig.baseUrl}/history/$deviceId/daily?days=$days');
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = response.data;
|
|
return data.cast<Map<String, dynamic>>();
|
|
} else {
|
|
throw Exception('Failed to fetch daily history: ${response.statusCode}');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Error fetching daily history: $e');
|
|
}
|
|
}
|
|
|
|
// Fungsi untuk mengambil agregasi per jam
|
|
Future<List<Map<String, dynamic>>> fetchHourlyHistory({int days = 7}) async {
|
|
try {
|
|
final response = await _dio.get('${ApiConfig.baseUrl}/history/$deviceId/hourly?days=$days');
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = response.data;
|
|
return data.cast<Map<String, dynamic>>();
|
|
} else {
|
|
throw Exception('Failed to fetch hourly history: ${response.statusCode}');
|
|
}
|
|
} catch (e) {
|
|
throw Exception('Error fetching hourly history: $e');
|
|
}
|
|
}
|
|
}
|