290 lines
11 KiB
Dart
290 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
|
import 'mqtt_manager.dart';
|
|
|
|
class ModeKontrolPage extends StatefulWidget {
|
|
final String title;
|
|
|
|
const ModeKontrolPage({Key? key, required this.title}) : super(key: key);
|
|
|
|
@override
|
|
_ModeKontrolPageState createState() => _ModeKontrolPageState();
|
|
}
|
|
|
|
class _ModeKontrolPageState extends State<ModeKontrolPage> {
|
|
bool relaySwitch = false;
|
|
bool autoSwitch = false;
|
|
int parameterMinimal = 0;
|
|
int parameterMaximal = 0;
|
|
String _soil = '0'; // Nilai default untuk menghindari format exception
|
|
late MQTTManager _mqttManager;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final clientId = 'flutter_client_${DateTime.now().millisecondsSinceEpoch}';
|
|
_mqttManager = MQTTManager(
|
|
serverUri: '192.168.43.27', // URI server
|
|
clientId: clientId, // ID Client unik
|
|
onDataReceived: _onDataReceived,
|
|
);
|
|
_mqttManager.connect();
|
|
}
|
|
|
|
void _onDataReceived(String soil, String humidity, String temperature,
|
|
String intensity, String batLevel) {
|
|
double adcValue = double.tryParse(soil) ?? 0;
|
|
double adcMin = 570;
|
|
double adcMax = 650;
|
|
|
|
// Hitung persentase kelembapan menggunakan rumus kalibrasi
|
|
double soilPercentage = ((adcMax - adcValue) / (adcMax - adcMin)) * 100;
|
|
|
|
// Pastikan nilai persentase dalam rentang 0-100%
|
|
if (soilPercentage < 0) soilPercentage = 0;
|
|
if (soilPercentage > 100) soilPercentage = 100;
|
|
|
|
setState(() {
|
|
_soil = soilPercentage.toStringAsFixed(2);
|
|
});
|
|
|
|
if (autoSwitch) {
|
|
if (soilPercentage < parameterMinimal) {
|
|
_publishSwitchStatus(true); // Nyalakan pompa
|
|
} else if (soilPercentage > parameterMaximal) {
|
|
_publishSwitchStatus(false); // Matikan pompa
|
|
}
|
|
}
|
|
}
|
|
|
|
void _publishSwitchStatus(bool status) {
|
|
String switchStatus = status ? 'On' : 'Off';
|
|
_mqttManager.publishMessage('home/switch/status', switchStatus);
|
|
setState(() {
|
|
relaySwitch = status;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
double soilValue = double.tryParse(_soil) ?? 0;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () {
|
|
Navigator.pushReplacementNamed(context, '/home');
|
|
},
|
|
),
|
|
backgroundColor: Colors.brown,
|
|
title: Text(
|
|
widget.title,
|
|
style: TextStyle(
|
|
fontFamily: 'Quicksand',
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Center(
|
|
child: Column(
|
|
children: [
|
|
const Text(
|
|
'Kelembaban Tanah',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
AnimatedSwitcher(
|
|
duration: Duration(milliseconds: 500),
|
|
child: SfRadialGauge(
|
|
key: ValueKey(soilValue),
|
|
axes: <RadialAxis>[
|
|
RadialAxis(
|
|
minimum: 0,
|
|
maximum: 100,
|
|
ranges: <GaugeRange>[
|
|
GaugeRange(
|
|
startValue: 0,
|
|
endValue: 30,
|
|
color: Colors.red),
|
|
GaugeRange(
|
|
startValue: 30,
|
|
endValue: 70,
|
|
color: Colors.orange),
|
|
GaugeRange(
|
|
startValue: 70,
|
|
endValue: 100,
|
|
color: Colors.green),
|
|
],
|
|
pointers: <GaugePointer>[
|
|
NeedlePointer(value: soilValue),
|
|
],
|
|
annotations: <GaugeAnnotation>[
|
|
GaugeAnnotation(
|
|
widget: Container(
|
|
child: Text(
|
|
'$_soil %',
|
|
style: TextStyle(
|
|
fontSize: 25,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
angle: 90,
|
|
positionFactor: 0.5,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
const Divider(),
|
|
const SizedBox(height: 20),
|
|
Card(
|
|
elevation: 5,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10.0),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Kontrol Manual',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text('Kontrol Pompa:'),
|
|
Switch(
|
|
value: relaySwitch,
|
|
onChanged:
|
|
!autoSwitch // Nonaktifkan kontrol manual saat kontrol otomatis aktif
|
|
? (bool value) {
|
|
setState(() {
|
|
relaySwitch = value;
|
|
});
|
|
_publishSwitchStatus(value);
|
|
}
|
|
: null,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Card(
|
|
elevation: 5,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10.0),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Kontrol Otomatis',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text('Kontrol Otomatis:'),
|
|
Switch(
|
|
value: autoSwitch,
|
|
onChanged: (bool value) {
|
|
setState(() {
|
|
autoSwitch = value;
|
|
if (autoSwitch) {
|
|
relaySwitch =
|
|
false; // Matikan kontrol manual saat kontrol otomatis aktif
|
|
_publishSwitchStatus(
|
|
false); // Pastikan pompa mati saat kontrol otomatis aktif
|
|
}
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Parameter Minimum:'),
|
|
Container(
|
|
width: 100,
|
|
child: TextFormField(
|
|
initialValue: parameterMinimal.toString(),
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
parameterMinimal =
|
|
int.tryParse(value) ?? parameterMinimal;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Parameter Maksimum:'),
|
|
Container(
|
|
width: 100,
|
|
child: TextFormField(
|
|
initialValue: parameterMaximal.toString(),
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(),
|
|
),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
parameterMaximal =
|
|
int.tryParse(value) ?? parameterMaximal;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|