206 lines
5.7 KiB
Dart
206 lines
5.7 KiB
Dart
import 'dart:convert';
|
|
import 'package:mqtt_client/mqtt_client.dart';
|
|
import 'package:mqtt_client/mqtt_server_client.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
class MqttService extends ChangeNotifier {
|
|
MqttServerClient? client;
|
|
|
|
// Data sensor
|
|
String _suhu = "0";
|
|
String _kelembapan = "0";
|
|
String _intensitas = "0";
|
|
bool _isKipas1On = false;
|
|
bool _isKipas2On = false;
|
|
String _mode = "otomatis";
|
|
String _timestamp = "-";
|
|
|
|
// Batas sensor
|
|
double _maxSuhu = 45.0;
|
|
double _maxRh = 70.0;
|
|
|
|
// Getter sensor
|
|
String get suhu => _suhu;
|
|
String get kelembapan => _kelembapan;
|
|
String get intensitas => _intensitas;
|
|
bool get isKipas1On => _isKipas1On;
|
|
bool get isKipas2On => _isKipas2On;
|
|
String get mode => _mode;
|
|
String get timestamp => _timestamp;
|
|
|
|
// Getter batas
|
|
double _minSuhu = 0.0;
|
|
double _minRh = 0.0;
|
|
|
|
double get maxSuhu => _maxSuhu;
|
|
double get maxRh => _maxRh;
|
|
double get minSuhu => _minSuhu;
|
|
double get minRh => _minRh;
|
|
|
|
Function(String, bool)? onConnectionResult;
|
|
|
|
void setLimits(double suhuMax, double rhMax, double suhuMin, double rhMin) {
|
|
_maxSuhu = suhuMax;
|
|
_maxRh = rhMax;
|
|
_minSuhu = suhuMin;
|
|
_minRh = rhMin;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<bool> connect() async {
|
|
final String clientId =
|
|
'flutter_kopi_${DateTime.now().millisecondsSinceEpoch % 10000}';
|
|
|
|
client = MqttServerClient.withPort(
|
|
's1fd277a.ala.asia-southeast1.emqxsl.com', // EMQX host baru
|
|
clientId,
|
|
8883,
|
|
);
|
|
|
|
client!.secure = true;
|
|
client!.setProtocolV311();
|
|
client!.onBadCertificate = (dynamic cert) => true;
|
|
client!.keepAlivePeriod = 20;
|
|
client!.connectTimeoutPeriod = 10000;
|
|
client!.onDisconnected = onDisconnected;
|
|
client!.onConnected = onConnected;
|
|
client!.logging(on: kDebugMode);
|
|
|
|
final connMess = MqttConnectMessage()
|
|
.withClientIdentifier(clientId)
|
|
.authenticateAs('kopi_user', 'Kopikopi1')
|
|
.startClean();
|
|
client!.connectionMessage = connMess;
|
|
|
|
try {
|
|
await client!.connect();
|
|
} catch (e) {
|
|
debugPrint('MQTT Exception: $e');
|
|
onConnectionResult?.call('Koneksi Gagal: $e', false);
|
|
client!.disconnect();
|
|
return false;
|
|
}
|
|
|
|
if (client?.connectionStatus?.state == MqttConnectionState.connected) {
|
|
debugPrint('MQTT: Terhubung!');
|
|
onConnectionResult?.call(' Terhubung ke Server Kopi', true);
|
|
_subscribeToTopics();
|
|
notifyListeners();
|
|
return true;
|
|
} else {
|
|
onConnectionResult?.call(' Koneksi Gagal', false);
|
|
notifyListeners();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void _subscribeToTopics() {
|
|
client!.subscribe("kopi/sensor", MqttQos.atMostOnce);
|
|
client!.subscribe("kopi/relay/kipas1", MqttQos.atMostOnce);
|
|
client!.subscribe("kopi/relay/kipas2", MqttQos.atMostOnce);
|
|
client!.subscribe("kopi/relay/mode", MqttQos.atMostOnce);
|
|
|
|
client!.updates!.listen((List<MqttReceivedMessage<MqttMessage?>>? c) {
|
|
if (c == null || c.isEmpty) return;
|
|
|
|
final recMess = c[0].payload as MqttPublishMessage;
|
|
final pt = MqttPublishPayload.bytesToStringAsString(
|
|
recMess.payload.message);
|
|
final topic = c[0].topic;
|
|
|
|
if (topic == "kopi/sensor") {
|
|
try {
|
|
final data = jsonDecode(pt);
|
|
_suhu = data['suhu'].toString();
|
|
_kelembapan = data['kelembapan'].toString();
|
|
_intensitas = data['intensitas'].toString();
|
|
_isKipas1On = data['kipas1'] == 'ON';
|
|
_isKipas2On = data['kipas2'] == 'ON';
|
|
_mode = data['mode'] ?? 'otomatis';
|
|
_timestamp = data['timestamp'] ?? '-';
|
|
} catch (e) {
|
|
debugPrint('MQTT parse error: $e');
|
|
}
|
|
}
|
|
|
|
if (topic == "kopi/relay/kipas1") _isKipas1On = (pt == "ON");
|
|
if (topic == "kopi/relay/kipas2") _isKipas2On = (pt == "ON");
|
|
if (topic == "kopi/relay/mode") _mode = pt;
|
|
|
|
notifyListeners();
|
|
});
|
|
}
|
|
|
|
// Kirim perintah kipas manual
|
|
void perintahKipas(String nomorKipas, bool nyala) {
|
|
final payload = jsonEncode({
|
|
"kipas": nomorKipas,
|
|
"status": nyala ? "ON" : "OFF",
|
|
});
|
|
_publish("kopi/relay/manual", payload);
|
|
}
|
|
|
|
// Ganti mode otomatis/manual
|
|
void gantiMode(String mode) {
|
|
_publish("kopi/relay/mode", mode);
|
|
}
|
|
|
|
// Kirim interval foto dalam menit
|
|
// Kirim 0 untuk stop
|
|
void setIntervalFoto(int menit) {
|
|
if (menit == 0) {
|
|
_publish("kopi/setting/interval", "0");
|
|
} else {
|
|
_publish("kopi/setting/interval", menit.toString());
|
|
}
|
|
}
|
|
|
|
// Trigger foto manual
|
|
void triggerFoto() {
|
|
_publish("kopi/trigger/foto", "AMBIL");
|
|
}
|
|
|
|
// Kirim batas sensor ke ESP32 dan Node-RED
|
|
void setBatasSensor(
|
|
double suhuMin, double suhuMax, double rhMin, double rhMax) {
|
|
final payload = jsonEncode({
|
|
"suhu_min": suhuMin,
|
|
"suhu_max": suhuMax,
|
|
"rh_min": rhMin,
|
|
"rh_max": rhMax,
|
|
});
|
|
_publish("kopi/setting/batas", payload);
|
|
}
|
|
|
|
void _publish(String topic, String message) {
|
|
if (client?.connectionStatus?.state != MqttConnectionState.connected) {
|
|
debugPrint('MQTT: Tidak terhubung, tidak bisa publish');
|
|
return;
|
|
}
|
|
final builder = MqttClientPayloadBuilder();
|
|
builder.addString(message);
|
|
client!.publishMessage(topic, MqttQos.atMostOnce, builder.payload!);
|
|
debugPrint('MQTT publish → $topic : $message');
|
|
}
|
|
|
|
void onConnected() {
|
|
debugPrint('MQTT: onConnected');
|
|
notifyListeners();
|
|
}
|
|
|
|
void onDisconnected() {
|
|
debugPrint('MQTT: onDisconnected');
|
|
|
|
// 🔥 AUTO RECONNECT
|
|
Future.delayed(const Duration(seconds: 3), () {
|
|
debugPrint('MQTT: mencoba reconnect...');
|
|
connect();
|
|
});
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
void disconnect() {
|
|
client?.disconnect();
|
|
}
|
|
} |