TKK_E32230176/Source-Code-Web/lib/providers/mqtt_provider.dart

336 lines
10 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_browser_client.dart';
import 'package:typed_data/typed_data.dart';
class MQTTProvider extends ChangeNotifier {
static const String MQTT_BROKER = "smartac.local"; // atau IP Raspberry Pi
static const int MQTT_PORT = 9001;
static const String PRESENCE_TOPIC = "classroom/presence";
static const String AC_STATUS_TOPIC = "classroom/ac/status";
static const String AC_CONTROL_TOPIC = "classroom/ac/control";
static const String WEBCAM_TOPIC = "classroom/webcam";
late MqttBrowserClient client;
// Status
String _connectionStatus = "Disconnected";
String _presenceStatus = "tidak ada";
String _acStatus = "off";
DateTime _lastUpdate = DateTime.now();
List<Map<String, dynamic>> _history = [];
bool _isAutoMode = true;
// Detail AC
int _currentTemp = 24;
String _currentMode = "cool";
String _activeTime = "00:00:00";
DateTime? _acStartTime;
Timer? _activeTimeTimer;
// Timer Delay
int _delayTimer = 5;
bool _isDelayActive = false;
int _delayRemaining = 0;
// Webcam
String? _webcamImage;
int _detectionCount = 0;
// Getter
String get connectionStatus => _connectionStatus;
String get presenceStatus => _presenceStatus;
String get acStatus => _acStatus;
DateTime get lastUpdate => _lastUpdate;
List<Map<String, dynamic>> get history => _history;
bool get isAutoMode => _isAutoMode;
int get currentTemp => _currentTemp;
String get currentMode => _currentMode;
String get activeTime => _activeTime;
String? get webcamImage => _webcamImage;
int get detectionCount => _detectionCount;
int get delayTimer => _delayTimer;
bool get isDelayActive => _isDelayActive;
int get delayRemaining => _delayRemaining;
Color get presenceColor =>
_presenceStatus == "ada" ? Colors.green : Colors.grey;
Color get acColor => _acStatus == "on" ? Colors.orange : Colors.grey;
IconData get acIcon =>
_acStatus == "on" ? Icons.ac_unit : Icons.ac_unit_outlined;
MQTTProvider() {
connect();
_startActiveTimeUpdater();
}
void _startActiveTimeUpdater() {
_activeTimeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_acStatus == "on" && _acStartTime != null) {
final duration = DateTime.now().difference(_acStartTime!);
final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
_activeTime =
'${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
notifyListeners();
}
});
}
Future<void> connect() async {
try {
_connectionStatus = "Connecting...";
notifyListeners();
client = MqttBrowserClient(
'ws://$MQTT_BROKER:$MQTT_PORT',
'flutter_web_${DateTime.now().millisecondsSinceEpoch}',
);
client.port = MQTT_PORT;
client.websocketProtocols = ['mqtt', 'mqttv3.1'];
client.keepAlivePeriod = 20;
client.connectTimeoutPeriod = 5000;
client.logging(on: false);
final connMess = MqttConnectMessage()
.withClientIdentifier(
'flutter_web_${DateTime.now().millisecondsSinceEpoch}')
.startClean()
.withWillQos(MqttQos.atLeastOnce);
client.connectionMessage = connMess;
await client.connect();
if (client.connectionStatus?.state == MqttConnectionState.connected) {
_connectionStatus = "Connected";
_subscribeToTopics();
requestWebcamFrame();
} else {
_connectionStatus = "Connection Failed";
}
} catch (e) {
_connectionStatus = "Error";
print('Connection Error: $e');
}
notifyListeners();
}
void _subscribeToTopics() {
if (client.connectionStatus?.state != MqttConnectionState.connected) return;
client.subscribe(PRESENCE_TOPIC, MqttQos.atLeastOnce);
client.subscribe(AC_STATUS_TOPIC, MqttQos.atLeastOnce);
client.subscribe(WEBCAM_TOPIC, MqttQos.atLeastOnce);
client.updates!.listen((List<MqttReceivedMessage<MqttMessage?>>? messages) {
if (messages == null || messages.isEmpty) return;
final recMess = messages[0];
final message = recMess.payload as MqttPublishMessage;
final topic = recMess.topic;
final payloadBytes = message.payload.message;
_lastUpdate = DateTime.now();
if (topic == PRESENCE_TOPIC) {
_presenceStatus = String.fromCharCodes(payloadBytes).trim();
print('📥 Presence: $_presenceStatus');
_addToHistory('presence', _presenceStatus);
notifyListeners();
} else if (topic == AC_STATUS_TOPIC) {
final payload = String.fromCharCodes(payloadBytes).trim();
print('📥 AC Status: $payload');
_handleACStatus(payload);
} else if (topic == WEBCAM_TOPIC) {
try {
_webcamImage = String.fromCharCodes(payloadBytes).trim();
_detectionCount++;
print('📸 Webcam frame: ${_detectionCount}');
notifyListeners();
} catch (e) {
print('Webcam error: $e');
}
}
});
}
void _handleACStatus(String payload) {
try {
Map<String, dynamic> data = jsonDecode(payload);
// Update status AC
if (data.containsKey('ac_state')) {
final newStatus = data['ac_state'];
if (_acStatus != newStatus) {
print('🔄 Status AC berubah: $_acStatus -> $newStatus');
_acStatus = newStatus;
// Update waktu aktif
if (_acStatus == "on" && _acStartTime == null) {
_acStartTime = DateTime.now();
} else if (_acStatus == "off") {
_acStartTime = null;
_activeTime = "00:00:00";
}
}
}
// Update timer delay
if (data.containsKey('delay_active')) {
_isDelayActive = data['delay_active'] ?? false;
}
if (data.containsKey('delay_remaining')) {
_delayRemaining = data['delay_remaining'] ?? 0;
}
// Update suhu
if (data.containsKey('temperature')) {
_currentTemp = data['temperature'];
}
// Update mode
if (data.containsKey('mode')) {
_currentMode = data['mode'];
}
// Update auto mode
if (data.containsKey('auto_mode')) {
_isAutoMode = data['auto_mode'];
}
} catch (e) {
print('Error parsing AC status: $e');
// Fallback: mungkin payload plain text
if (payload == "on" || payload == "off") {
if (_acStatus != payload) {
print('🔄 Status AC berubah (plain): $_acStatus -> $payload');
_acStatus = payload;
}
}
}
_addToHistory('ac', _acStatus);
notifyListeners();
}
void setDelayTimer(int minutes) {
if (minutes < 1) minutes = 1;
if (minutes > 60) minutes = 60;
_delayTimer = minutes;
_addToHistory('setting', 'Timer delay diubah ke $minutes menit');
print('⚙️ Timer: $minutes menit');
controlAC("delay_$minutes", manual: true);
notifyListeners();
}
void controlAC(String command, {bool manual = true}) {
if (client.connectionStatus?.state != MqttConnectionState.connected) {
_connectionStatus = "Disconnected";
notifyListeners();
return;
}
print('📤 Perintah: $command ${manual ? "(Manual)" : "(Auto)"}');
final buffer = Uint8Buffer();
buffer.addAll(command.codeUnits);
client.publishMessage(AC_CONTROL_TOPIC, MqttQos.atLeastOnce, buffer);
_addToHistory('command', command, manual: manual);
}
void setTemperature(int temp) {
if (_currentTemp != temp) {
controlAC("temp_$temp", manual: true);
_currentTemp = temp;
notifyListeners();
}
}
void setMode(String mode) {
if (_currentMode != mode) {
controlAC("mode_$mode", manual: true);
_currentMode = mode;
notifyListeners();
}
}
void toggleAutoMode() {
_isAutoMode = !_isAutoMode;
if (_isAutoMode) {
print('🤖 Mode AUTO diaktifkan');
controlAC("auto_on", manual: true);
} else {
print('👆 Mode MANUAL diaktifkan');
controlAC("auto_off", manual: true);
}
notifyListeners();
}
void requestWebcamFrame() {
if (client.connectionStatus?.state == MqttConnectionState.connected) {
final buffer = Uint8Buffer();
buffer.addAll("request".codeUnits);
client.publishMessage(
"classroom/webcam/request", MqttQos.atLeastOnce, buffer);
}
}
void _addToHistory(String type, String value, {bool manual = false}) {
String display = value;
if (type == 'presence') {
display = value == "ada" ? "👥 Orang terdeteksi" : "🚪 Ruangan kosong";
} else if (type == 'command') {
if (value == "on")
display = "🔛 AC Dinyalakan";
else if (value == "off")
display = "🔴 AC Dimatikan";
else if (value == "auto_on")
display = "🤖 Mode Auto diaktifkan";
else if (value == "auto_off")
display = "👆 Mode Manual diaktifkan";
else if (value.startsWith('temp_'))
display = "🌡️ Suhu ${value.split('_')[1]}°C";
else if (value.startsWith('mode_'))
display = "🎛️ Mode ${value.split('_')[1].toUpperCase()}";
else if (value.startsWith('delay_'))
display = "⏰ Timer ${value.split('_')[1]} menit";
} else if (type == 'ac') {
display = value == "on" ? "❄️ AC Menyala" : "⭕ AC Mati";
} else if (type == 'setting') {
display = "⚙️ $value";
}
_history.insert(0, {
'time': DateTime.now(),
'type': type,
'value': display,
'mode': manual ? 'manual' : 'auto',
});
if (_history.length > 50) _history.removeLast();
}
void disconnect() {
_activeTimeTimer?.cancel();
if (client.connectionStatus?.state == MqttConnectionState.connected) {
client.disconnect();
}
}
@override
void dispose() {
disconnect();
super.dispose();
}
}