logic output thermal sensor

This commit is contained in:
Muhammad Iqbal 2025-05-18 02:07:07 +07:00
parent 8cb669b7b1
commit 8aa455758b
14 changed files with 490 additions and 72 deletions

View File

@ -1,5 +1,8 @@
plugins { plugins {
id "com.android.application" id "com.android.application"
// START: FlutterFire Configuration
id 'com.google.gms.google-services'
// END: FlutterFire Configuration
id "kotlin-android" id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"

View File

@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "144700563392",
"project_id": "hamaguard-e911d",
"storage_bucket": "hamaguard-e911d.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:144700563392:android:913dfe0c4205cca8e4b225",
"android_client_info": {
"package_name": "com.example.hamaguard"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDgtq_bU0aNjKwErdns50EldaRRSdd5Sg0"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -18,7 +18,10 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false id "com.android.application" version "8.2.1" apply false
// START: FlutterFire Configuration
id "com.google.gms.google-services" version "4.3.15" apply false
// END: FlutterFire Configuration
id "org.jetbrains.kotlin.android" version "1.8.22" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false
} }

1
firebase.json Normal file
View File

@ -0,0 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"hamaguard-e911d","appId":"1:144700563392:android:913dfe0c4205cca8e4b225","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"hamaguard-e911d","configurations":{"android":"1:144700563392:android:913dfe0c4205cca8e4b225"}}}}}}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyBVDd8ZqMe_PK1So6pxng8zRHz8qzuToNE</string>
<key>GCM_SENDER_ID</key>
<string>144700563392</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.hamaguard</string>
<key>PROJECT_ID</key>
<string>hamaguard-e911d</string>
<key>STORAGE_BUCKET</key>
<string>hamaguard-e911d.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:144700563392:ios:76ebcf9db01d01cfe4b225</string>
</dict>
</plist>

62
lib/firebase_options.dart Normal file
View File

@ -0,0 +1,62 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for web - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for ios - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyDgtq_bU0aNjKwErdns50EldaRRSdd5Sg0',
appId: '1:144700563392:android:913dfe0c4205cca8e4b225',
messagingSenderId: '144700563392',
projectId: 'hamaguard-e911d',
storageBucket: 'hamaguard-e911d.firebasestorage.app',
);
}

View File

@ -1,7 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'screens/splash/splash_screen.dart'; import 'screens/splash/splash_screen.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
} catch (e) {
// Bisa log error atau tampilkan pesan khusus
debugPrint('Firebase init error: $e');
}
runApp(const MyApp()); runApp(const MyApp());
} }
@ -17,7 +29,7 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green), colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true, useMaterial3: true,
), ),
home: const SplashScreen(), // << mulai dari splash home: const SplashScreen(),
); );
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../widgets/costum_header.dart'; import '../../widgets/costum_header.dart';
import '../../screens/notification/notification_screen.dart';
class ControlScreen extends StatefulWidget { class ControlScreen extends StatefulWidget {
const ControlScreen({super.key}); const ControlScreen({super.key});
@ -50,9 +51,19 @@ class _ControlScreenState extends State<ControlScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
appBar: const CustomHeader( appBar: CustomHeader(
deviceName: 'HamaGuard', deviceName: 'HamaGuard',
notificationCount: 5,
onNotificationTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationScreen(),
), ),
);
},
),
body: Padding( body: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: Column(

View File

@ -1,38 +1,103 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../widgets/thermal_grid.dart'; import 'package:firebase_database/firebase_database.dart';
import '../../widgets/thermal_heatmap.dart';
import '../../widgets/costum_header.dart'; import '../../widgets/costum_header.dart';
import '../notification/notification_screen.dart';
class DashboardScreen extends StatelessWidget { class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key}); const DashboardScreen({super.key});
@override @override
Widget build(BuildContext context) { State<DashboardScreen> createState() => _DashboardScreenState();
// Dummy data sementara }
final List<double> thermalData =
List.generate(64, (index) => 25 + Random().nextDouble() * 10);
final Map<String, dynamic> sensorData = const { class _DashboardScreenState extends State<DashboardScreen> {
'PIR': 'Tidak', late DatabaseReference _sensorRef;
'Ultrasonik': '50 cm', late DatabaseReference _thermalRef;
'Status Pengusir': 'Nonaktif',
Map<String, dynamic> sensorData = {
'PIR': 'Memuat...',
'Ultrasonik': 'Memuat...',
'Status Pengusir': 'Memuat...',
}; };
List<double> thermalData = List.filled(64, 0.0);
@override
void initState() {
super.initState();
_sensorRef = FirebaseDatabase.instance.ref('sensor');
_thermalRef = FirebaseDatabase.instance.ref('thermal/temperatures');
// Listener sensor data
_sensorRef.onValue.listen((event) {
final data = event.snapshot.value;
if (data != null && data is Map) {
setState(() {
sensorData = {
'PIR': (data['pir'] == true) ? 'Terdeteksi' : 'Tidak',
'Ultrasonik':
data['ultrasonik'] != null ? '${data['ultrasonik']} cm' : '-',
'Status Pengusir':
(data['pengusir'] == true) ? 'Aktif' : 'Nonaktif',
};
});
}
});
// Listener thermal data
_thermalRef.onValue.listen((event) {
final data = event.snapshot.value;
if (data != null) {
// Firebase RTDB terkadang mengirim List<dynamic> atau Map<dynamic,dynamic>
List<double> temps = [];
if (data is List) {
temps = data.map<double>((e) {
if (e is num) return e.toDouble();
return 0.0;
}).toList();
} else if (data is Map) {
// Jika data disimpan sebagai Map index:string -> value
temps = List<double>.filled(64, 0);
data.forEach((key, value) {
int idx = int.tryParse(key) ?? -1;
if (idx >= 0 && idx < 64) {
if (value is num) {
temps[idx] = value.toDouble();
}
}
});
}
if (temps.length == 64) {
setState(() {
thermalData = temps;
});
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
appBar: CustomHeader( appBar: CustomHeader(
deviceName: 'HamaGuard', deviceName: 'HamaGuard',
notificationCount: 5, notificationCount: 5,
onNotificationTap: () { onNotificationTap: () {
// Aksi saat lonceng ditekan Navigator.push(
context,
MaterialPageRoute(builder: (context) => NotificationScreen()),
);
}, },
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
children: [ children: [
// Thermal Sensor dalam Card // Thermal Sensor
Card( Card(
elevation: 4, elevation: 4,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -49,27 +114,25 @@ class DashboardScreen extends StatelessWidget {
Text( Text(
'Thermal Sensor', 'Thermal Sensor',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold, fontSize: 16),
fontSize: 16,
),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
ThermalGrid(temperatures: thermalData), ThermalHeatmap(temperatures: thermalData),
], ],
), ),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// PIR & Ultrasonik side by side // PIR & Ultrasonik
Row( Row(
children: [ children: [
Expanded( Expanded(
child: SensorCard( child: SensorCard(
title: 'PIR', title: 'PIR',
value: sensorData['PIR']!, value: sensorData['PIR'] ?? '-',
icon: Icons.motion_photos_on_outlined, icon: Icons.motion_photos_on_outlined,
color: Colors.orange, color: Colors.orange,
), ),
@ -78,7 +141,7 @@ class DashboardScreen extends StatelessWidget {
Expanded( Expanded(
child: SensorCard( child: SensorCard(
title: 'Ultrasonik', title: 'Ultrasonik',
value: sensorData['Ultrasonik']!, value: sensorData['Ultrasonik'] ?? '-',
icon: Icons.straighten, icon: Icons.straighten,
color: Colors.blue, color: Colors.blue,
), ),
@ -90,7 +153,7 @@ class DashboardScreen extends StatelessWidget {
// Status Pengusir // Status Pengusir
SensorCard( SensorCard(
title: 'Status Pengusir', title: 'Status Pengusir',
value: sensorData['Status Pengusir']!, value: sensorData['Status Pengusir'] ?? '-',
icon: Icons.speaker, icon: Icons.speaker,
color: Colors.green, color: Colors.green,
), ),
@ -122,7 +185,7 @@ class SensorCard extends StatelessWidget {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: color.withOpacity(0.15), backgroundColor: color.withAlpha(15),
child: Icon(icon, color: color), child: Icon(icon, color: color),
), ),
title: Text(title), title: Text(title),

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
class NotificationScreen extends StatelessWidget {
final List<Map<String, String>> notifications = [
{
"title": "Peringatan!",
"message": "Hama terdeteksi di area sawah A.",
"time": "16 Mei 2025 - 10:15"
},
{
"title": "Status Aman",
"message": "Tidak ada aktivitas hama selama 2 jam terakhir.",
"time": "16 Mei 2025 - 09:00"
},
{
"title": "Mode Otomatis Aktif",
"message": "Sistem kembali ke mode otomatis.",
"time": "15 Mei 2025 - 17:20"
},
];
NotificationScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Notifikasi'),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 1,
),
body: notifications.isEmpty
? const Center(
child: Text(
'Tidak ada notifikasi',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
)
: ListView.separated(
itemCount: notifications.length,
padding: const EdgeInsets.all(16),
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final notif = notifications[index];
return ListTile(
leading: const Icon(Icons.notifications, color: Colors.blue),
title: Text(
notif["title"]!,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(notif["message"]!),
trailing: Text(
notif["time"]!,
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
);
},
),
);
}
}

View File

@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
class ThermalGrid extends StatelessWidget {
final List<double> temperatures;
const ThermalGrid({super.key, required this.temperatures});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: GridView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: 64,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 8,
),
itemBuilder: (context, index) {
final temp = temperatures[index];
final color = getColorForTemperature(temp);
return Container(
margin: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(2),
),
);
},
),
);
}
Color getColorForTemperature(double temp) {
if (temp < 26) return Colors.blue[200]!;
if (temp < 28) return Colors.lightBlue;
if (temp < 30) return Colors.green;
if (temp < 32) return Colors.yellow;
if (temp < 34) return Colors.orange;
return Colors.red;
}
}

View File

@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
class ThermalHeatmap extends StatelessWidget {
final List<double> temperatures;
const ThermalHeatmap({super.key, required this.temperatures});
@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
width: 300,
height: 300,
child: CustomPaint(
painter: _ThermalPainter(temperatures),
),
),
);
}
}
class _ThermalPainter extends CustomPainter {
final List<double> temps;
_ThermalPainter(this.temps);
@override
void paint(Canvas canvas, Size size) {
final cellWidth = size.width / 8;
final cellHeight = size.height / 8;
final blurPaint = Paint()
..isAntiAlias = true
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2);
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
int i = y * 8 + x;
double temp = i < temps.length ? temps[i] : 0.0;
final color = _getColorForTemp(temp);
final rect = Rect.fromLTWH(
x * cellWidth,
y * cellHeight,
cellWidth,
cellHeight,
);
canvas.drawRect(rect, blurPaint..color = color.withOpacity(0.95));
}
}
final center = Offset(size.width / 2, size.height / 2);
final plusPaint = Paint()
..color = Colors.white.withOpacity(0.95)
..strokeWidth = 2
..strokeCap = StrokeCap.round;
canvas.drawLine(center - const Offset(10, 0), center + const Offset(10, 0), plusPaint);
canvas.drawLine(center - const Offset(0, 10), center + const Offset(0, 10), plusPaint);
}
Color _getColorForTemp(double t) {
double norm = ((t - 20) / 20).clamp(0.0, 1.0);
return HSVColor.lerp(
HSVColor.fromColor(Colors.blue),
HSVColor.fromColor(Colors.red),
norm,
)!
.toColor();
}
@override
bool shouldRepaint(covariant _ThermalPainter oldDelegate) {
if (oldDelegate.temps.length != temps.length) return true;
for (int i = 0; i < temps.length; i++) {
if (oldDelegate.temps[i] != temps[i]) return true;
}
return false;
}
}

View File

@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: de9ecbb3ddafd446095f7e833c853aff2fa1682b017921fe63a833f9d6f0e422
url: "https://pub.dev"
source: hosted
version: "1.3.54"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -33,6 +41,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
cloud_firestore:
dependency: "direct main"
description:
name: cloud_firestore
sha256: "6ca737017131a77940fdcca3e13bf6efb5633d50b83514bba24f5fd68e7f340f"
url: "https://pub.dev"
source: hosted
version: "5.6.7"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
sha256: "082c9aa0602efc1ee8c44c4e8a29a456b198f79a7f62657df39144c97e364cfb"
url: "https://pub.dev"
source: hosted
version: "6.6.7"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
sha256: e1ddeacedd1c5a2e54089bbeb2d95d03b0096eccf8889024598f09f092ed8746
url: "https://pub.dev"
source: hosted
version: "4.4.7"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -57,6 +89,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe"
url: "https://pub.dev"
source: hosted
version: "3.13.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
url: "https://pub.dev"
source: hosted
version: "5.4.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "129a34d1e0fb62e2b488d988a1fc26cc15636357e50944ffee2862efe8929b23"
url: "https://pub.dev"
source: hosted
version: "2.22.0"
firebase_database:
dependency: "direct main"
description:
name: firebase_database
sha256: "182ce4713d47ffc5f19a5a7b934867d1fae9c33081febcec8c062cb89fc14652"
url: "https://pub.dev"
source: hosted
version: "11.3.5"
firebase_database_platform_interface:
dependency: transitive
description:
name: firebase_database_platform_interface
sha256: b65f416dd2c8ac2d5322241e5411a24ed3da43d0f38aaf9ab6c211d72e52261b
url: "https://pub.dev"
source: hosted
version: "0.2.6+5"
firebase_database_web:
dependency: transitive
description:
name: firebase_database_web
sha256: "5203141fe00a1edfaed5f8e0444b8e4ef807a8ec6eca925621b1cab69b6c06e4"
url: "https://pub.dev"
source: hosted
version: "0.2.6+11"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -75,6 +155,11 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -147,6 +232,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -216,6 +309,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.3.0" version: "14.3.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sdks: sdks:
dart: ">=3.6.2 <4.0.0" dart: ">=3.6.2 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.22.0"

View File

@ -37,6 +37,9 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
firebase_core: ^3.13.0
firebase_database: ^11.3.5
cloud_firestore: ^5.6.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: