From 9a68fd1492bb5f75cbe662d385fa8e695f3a2423 Mon Sep 17 00:00:00 2001 From: oxel Date: Thu, 23 Apr 2026 11:56:25 +0700 Subject: [PATCH] add iot.cpp logics --- android/app/src/main/AndroidManifest.xml | 4 +- .../main/res/xml/network_security_config.xml | 13 + iot.cpp | 174 ++++++++++ lib/core/services/mqtt_service.dart | 189 +++++++++++ .../presentation/pages/dashboard_page.dart | 300 +++++++++++++----- pubspec.lock | 16 + pubspec.yaml | 1 + scratch/test_mqtt_standard.dart | 38 +++ 8 files changed, 656 insertions(+), 79 deletions(-) create mode 100644 android/app/src/main/res/xml/network_security_config.xml create mode 100644 iot.cpp create mode 100644 lib/core/services/mqtt_service.dart create mode 100644 scratch/test_mqtt_standard.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 60578bf..247de97 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,9 @@ + android:icon="@mipmap/launcher_icon" + android:usesCleartextTraffic="true" + android:networkSecurityConfig="@xml/network_security_config"> + + + + + + + + broker.emqx.io + broker.hivemq.com + 3.120.100.223 + + diff --git a/iot.cpp b/iot.cpp new file mode 100644 index 0000000..cb07188 --- /dev/null +++ b/iot.cpp @@ -0,0 +1,174 @@ +#include +#include +#include + +// ================= WIFI ================= +const char* ssid = "Shanum_4G"; +const char* password = "12345678"; + +// ================= MQTT ================= +const char* mqtt_server = "broker.hivemq.com"; + +WiFiClient espClient; +PubSubClient client(espClient); + +// ================= DHT ================= +#define DHTPIN 4 +#define DHTTYPE DHT22 +DHT dht(DHTPIN, DHTTYPE); + +// ================= RELAY ================= +#define RELAY1 27 +#define RELAY2 26 + +// Relay aktif LOW +#define RELAY_ON LOW +#define RELAY_OFF HIGH + +// ================= THRESHOLD ================= +float batasPanas = 70.0; +float batasLembab = 90.0; + +// ================= GLOBAL STATE ================= +unsigned long lastMsg = 0; +String statusRelay = "INIT"; +bool isAutoMode = true; +bool manualPump = false; +bool manualLight = false; + +// ================= WIFI ================= +void setup_wifi() { + delay(10); + Serial.println("Connecting to WiFi..."); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println("\nWiFi connected!"); +} + +// ================= MQTT CALLBACK ================= +void callback(char* topic, byte* payload, unsigned int length) { + String message = ""; + for (int i = 0; i < length; i++) { + message += (char)payload[i]; + } + + Serial.print("Message arrived ["); + Serial.print(topic); + Serial.print("] "); + Serial.println(message); + + if (String(topic) == "esp32/control/mode") { + isAutoMode = (message == "auto"); + Serial.println(isAutoMode ? "Mode: AUTO" : "Mode: MANUAL"); + } + else if (String(topic) == "esp32/control/pump") { + manualPump = (message == "on"); + Serial.println(manualPump ? "Manual Pump: ON" : "Manual Pump: OFF"); + } + else if (String(topic) == "esp32/control/light") { + manualLight = (message == "on"); + Serial.println(manualLight ? "Manual Light: ON" : "Manual Light: OFF"); + } +} + +// ================= MQTT RECONNECT ================= +void reconnect() { + while (!client.connected()) { + Serial.print("Connecting MQTT..."); + + if (client.connect("ESP32Client", "esp32/status", 0, true, "offline")) { + Serial.println("connected"); + client.publish("esp32/status", "online", true); + + // Subscribe to control topics + client.subscribe("esp32/control/mode"); + client.subscribe("esp32/control/pump"); + client.subscribe("esp32/control/light"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" coba lagi..."); + delay(2000); + } + } +} + +void setup() { + Serial.begin(115200); + + pinMode(RELAY1, OUTPUT); + pinMode(RELAY2, OUTPUT); + + digitalWrite(RELAY1, RELAY_OFF); + digitalWrite(RELAY2, RELAY_OFF); + + dht.begin(); + + setup_wifi(); + client.setServer(mqtt_server, 1883); + client.setCallback(callback); +} + +void loop() { + if (!client.connected()) { + reconnect(); + } + client.loop(); + + unsigned long now = millis(); + + if (now - lastMsg > 2000) { + lastMsg = now; + + float suhu = dht.readTemperature(); + float kelembapan = dht.readHumidity(); + + if (isnan(suhu) || isnan(kelembapan)) { + Serial.println("Gagal baca DHT!"); + return; + } + + // ================= CONTROL LOGIC ================= + if (isAutoMode) { + // Automatic Mode (Sensor Based) + bool kondisiLembab = (kelembapan > batasLembab); + bool kondisiPanas = (suhu > batasPanas); + + digitalWrite(RELAY1, kondisiLembab ? RELAY_ON : RELAY_OFF); + digitalWrite(RELAY2, kondisiPanas ? RELAY_ON : RELAY_OFF); + + if (kondisiPanas && kondisiLembab) statusRelay = "PANAS+LEMBAB"; + else if (kondisiPanas) statusRelay = "PANAS"; + else if (kondisiLembab) statusRelay = "LEMBAB"; + else statusRelay = "NORMAL"; + } else { + // Manual Mode (App Based) + digitalWrite(RELAY1, manualPump ? RELAY_ON : RELAY_OFF); + digitalWrite(RELAY2, manualLight ? RELAY_ON : RELAY_OFF); + statusRelay = "MANUAL"; + } + + // ================= STATUS REPORTING ================= + bool currentPump = (digitalRead(RELAY1) == RELAY_ON); + bool currentLight = (digitalRead(RELAY2) == RELAY_ON); + + client.publish("esp32/dht/suhu", String(suhu).c_str()); + client.publish("esp32/dht/kelembapan", String(kelembapan).c_str()); + client.publish("esp32/relay/status", statusRelay.c_str()); + client.publish("esp32/status/pump", currentPump ? "on" : "off", true); + client.publish("esp32/status/light", currentLight ? "on" : "off", true); + client.publish("esp32/status/mode", isAutoMode ? "auto" : "manual", true); + + // Serial Debug + Serial.print("Mode: "); Serial.print(isAutoMode ? "AUTO" : "MANUAL"); + Serial.print(" | Hum: "); Serial.print(kelembapan); + Serial.print("% | Pump: "); Serial.print(currentPump ? "ON" : "OFF"); + Serial.print(" | Light: "); Serial.println(currentLight ? "ON" : "OFF"); + } +} \ No newline at end of file diff --git a/lib/core/services/mqtt_service.dart b/lib/core/services/mqtt_service.dart new file mode 100644 index 0000000..d39a30d --- /dev/null +++ b/lib/core/services/mqtt_service.dart @@ -0,0 +1,189 @@ +import 'dart:async'; +import 'dart:math'; +import 'package:flutter/foundation.dart'; +import 'package:mqtt_client/mqtt_client.dart'; +import 'package:mqtt_client/mqtt_server_client.dart'; + +enum AppMqttStatus { disconnected, connecting, connected, error } + +class MqttService { + static final MqttService _instance = MqttService._internal(); + factory MqttService() => _instance; + MqttService._internal(); + + MqttServerClient? client; + StreamSubscription? _subscription; + bool _isInitializing = false; + + final ValueNotifier isConnected = ValueNotifier(false); + final ValueNotifier connectionState = + ValueNotifier(AppMqttStatus.disconnected); + final ValueNotifier isHardwareOnline = ValueNotifier(false); + final ValueNotifier humidity = ValueNotifier(1.0); + final ValueNotifier relayStatus = ValueNotifier("NORMAL"); + final ValueNotifier isAutoMode = ValueNotifier(true); + final ValueNotifier isPumpOn = ValueNotifier(false); + final ValueNotifier isLightOn = ValueNotifier(false); + + Future init() async { + if (_isInitializing) return; + + _isInitializing = true; + connectionState.value = AppMqttStatus.connecting; + + final clientId = 'mj_debug_${Random().nextInt(999999)}'; + + // FINAL ROBUST CONFIG: Back to EMQX Port 1883 + client = MqttServerClient('broker.emqx.io', clientId); + client!.port = 1883; + client!.setProtocolV311(); + client!.keepAlivePeriod = 60; + client!.autoReconnect = true; + client!.resubscribeOnAutoReconnect = true; + client!.logging(on: true); + + client!.onConnected = _onConnected; + client!.onDisconnected = _onDisconnected; + client!.onSubscribed = _onSubscribed; + client!.onAutoReconnect = () => debugPrint('MQTT: Attempting auto-reconnect...'); + client!.onAutoReconnected = () => debugPrint('MQTT: Auto-reconnect successful!'); + + client!.connectionMessage = MqttConnectMessage() + .withClientIdentifier(clientId) + .startClean(); + + try { + debugPrint('MQTT: Connecting as $clientId'); + + final status = await client!.connect(); + + if (status?.state != MqttConnectionState.connected) { + debugPrint('MQTT: FAILED -> ${status?.state}'); + connectionState.value = AppMqttStatus.error; + client!.disconnect(); + return; + } + + debugPrint('MQTT: CONNECTED OK'); + } catch (e) { + debugPrint('MQTT: EXCEPTION $e'); + connectionState.value = AppMqttStatus.error; + client?.disconnect(); + } finally { + _isInitializing = false; + } + } + + void _onConnected() { + debugPrint('MQTT: Connected ✅'); + + isConnected.value = true; + connectionState.value = AppMqttStatus.connected; + + _subscribeAll(); + + _subscription?.cancel(); + + _subscription = client!.updates!.listen((events) { + final recMess = events[0].payload as MqttPublishMessage; + final topic = events[0].topic; + final payload = MqttPublishPayload.bytesToStringAsString( + recMess.payload.message, + ); + + debugPrint('MQTT RX [$topic] -> $payload'); + + _processMessage(topic, payload); + }); + } + + void _subscribeAll() { + const topics = [ + "esp32/dht/kelembapan", + "esp32/relay/status", + "esp32/status", + "esp32/status/mode", + "esp32/status/pump", + "esp32/status/light", + ]; + + for (final t in topics) { + debugPrint('MQTT: Subscribing $t'); + client!.subscribe(t, MqttQos.atLeastOnce); + } + } + + void _processMessage(String topic, String payload) { + final pt = payload.trim().toLowerCase(); + debugPrint('MQTT RX DEBUG: Topic=$topic, Payload=$pt'); + + switch (topic) { + case "esp32/dht/kelembapan": + double val = double.tryParse(payload) ?? humidity.value; + humidity.value = val < 1.0 ? 1.0 : val; + break; + + case "esp32/relay/status": + relayStatus.value = payload.toUpperCase(); + break; + + case "esp32/status": + isHardwareOnline.value = (pt == "online"); + debugPrint('MQTT: Hardware -> ${isHardwareOnline.value}'); + break; + + case "esp32/status/mode": + isAutoMode.value = (pt == "auto"); + break; + + case "esp32/status/pump": + isPumpOn.value = (pt == "on"); + break; + + case "esp32/status/light": + isLightOn.value = (pt == "on"); + break; + } + } + + void publishControl(String subTopic, String value) { + if (client == null || + client!.connectionStatus?.state != MqttConnectionState.connected) { + debugPrint('MQTT: publish blocked (not connected)'); + return; + } + + final builder = MqttClientPayloadBuilder()..addString(value); + final topic = "esp32/status/$subTopic"; + + client!.publishMessage( + topic, + MqttQos.atLeastOnce, + builder.payload!, + retain: true, + ); + + debugPrint('MQTT TX [$topic] -> $value'); + } + + void _onDisconnected() { + debugPrint('MQTT: Disconnected ❌'); + + isConnected.value = false; + isHardwareOnline.value = false; + + if (connectionState.value != AppMqttStatus.error) { + connectionState.value = AppMqttStatus.disconnected; + } + } + + + void _onSubscribed(String topic) { + debugPrint('MQTT: Subscribed $topic'); + } + + void disconnect() { + _subscription?.cancel(); + client?.disconnect(); + } +} \ No newline at end of file diff --git a/lib/features/home/presentation/pages/dashboard_page.dart b/lib/features/home/presentation/pages/dashboard_page.dart index 44ee598..beeac5f 100644 --- a/lib/features/home/presentation/pages/dashboard_page.dart +++ b/lib/features/home/presentation/pages/dashboard_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:monitoring_jamur/core/theme/app_theme.dart'; import 'package:monitoring_jamur/features/home/presentation/pages/statistics_page.dart'; +import 'package:monitoring_jamur/core/services/mqtt_service.dart'; import 'dart:math' as math; class DashboardPage extends StatefulWidget { @@ -12,7 +13,7 @@ class DashboardPage extends StatefulWidget { class _DashboardPageState extends State { // Demo humidity value - double _humidity = 85.0; + double _humidity = 1; bool _isAutoMode = true; bool _isPumpManual = false; bool _isLightManual = false; @@ -20,6 +21,50 @@ class _DashboardPageState extends State { bool get _pumpStatus => _isAutoMode ? (_humidity < 80) : _isPumpManual; bool get _lightStatus => _isAutoMode ? (_humidity > 90) : _isLightManual; + final MqttService _mqttService = MqttService(); + + @override + void initState() { + super.initState(); + _mqttService.init(); + + // Listen to all changes + _mqttService.isConnected.addListener(_onMqttChanged); + _mqttService.isHardwareOnline.addListener(_onMqttChanged); + _mqttService.humidity.addListener(_onMqttChanged); + _mqttService.relayStatus.addListener(_onMqttChanged); + _mqttService.isAutoMode.addListener(_onMqttChanged); + _mqttService.isPumpOn.addListener(_onMqttChanged); + _mqttService.isLightOn.addListener(_onMqttChanged); + } + + @override + void dispose() { + _mqttService.isConnected.removeListener(_onMqttChanged); + _mqttService.isHardwareOnline.removeListener(_onMqttChanged); + _mqttService.humidity.removeListener(_onMqttChanged); + _mqttService.relayStatus.removeListener(_onMqttChanged); + _mqttService.isAutoMode.removeListener(_onMqttChanged); + _mqttService.isPumpOn.removeListener(_onMqttChanged); + _mqttService.isLightOn.removeListener(_onMqttChanged); + super.dispose(); + } + + void _onMqttChanged() { + if (mounted) { + setState(() { + _humidity = _mqttService.humidity.value; + _isAutoMode = _mqttService.isAutoMode.value; + // In Auto mode, we follow the status reported by hardware + // In Manual mode, we show what we set locally (which will be confirmed by hardware status later) + if (_isAutoMode) { + _isPumpManual = _mqttService.isPumpOn.value; + _isLightManual = _mqttService.isLightOn.value; + } + }); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -99,82 +144,106 @@ class _DashboardPageState extends State { } Widget _buildModeSelector() { - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(5), - blurRadius: 10, - offset: const Offset(0, 4), + return ValueListenableBuilder( + valueListenable: _mqttService.isAutoMode, + builder: (context, isAuto, _) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(5), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], ), - ], - ), - child: Row( - children: [ - Expanded( - child: GestureDetector( - onTap: () => setState(() => _isAutoMode = true), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: _isAutoMode ? AppTheme.primaryGreen : Colors.transparent, - borderRadius: BorderRadius.circular(16), - ), - alignment: Alignment.center, - child: Text( - 'Otomatis', - style: TextStyle( - fontWeight: FontWeight.bold, - color: _isAutoMode ? Colors.white : AppTheme.textLight, + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => _mqttService.publishControl('mode', 'auto'), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: isAuto ? AppTheme.primaryGreen : Colors.transparent, + borderRadius: BorderRadius.circular(16), + ), + alignment: Alignment.center, + child: Text( + 'Otomatis', + style: TextStyle( + fontWeight: FontWeight.bold, + color: isAuto ? Colors.white : AppTheme.textLight, + ), + ), ), ), ), - ), - ), - Expanded( - child: GestureDetector( - onTap: () => setState(() => _isAutoMode = false), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: !_isAutoMode ? AppTheme.primaryGreen : Colors.transparent, - borderRadius: BorderRadius.circular(16), - ), - alignment: Alignment.center, - child: Text( - 'Manual', - style: TextStyle( - fontWeight: FontWeight.bold, - color: !_isAutoMode ? Colors.white : AppTheme.textLight, + Expanded( + child: GestureDetector( + onTap: () => _mqttService.publishControl('mode', 'manual'), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: !isAuto ? AppTheme.primaryGreen : Colors.transparent, + borderRadius: BorderRadius.circular(16), + ), + alignment: Alignment.center, + child: Text( + 'Manual', + style: TextStyle( + fontWeight: FontWeight.bold, + color: !isAuto ? Colors.white : AppTheme.textLight, + ), + ), ), ), ), - ), + ], ), - ], - ), + ); + }, ); } Widget _buildDeviceControls() { - return Column( - children: [ - _buildDeviceTile( - title: 'Pompa Air', - isOn: _pumpStatus, - onChanged: _isAutoMode ? null : (val) => setState(() => _isPumpManual = val), - ), - const SizedBox(height: 16), - _buildDeviceTile( - title: 'Lampu Pemanas', - isOn: _lightStatus, - onChanged: _isAutoMode ? null : (val) => setState(() => _isLightManual = val), - ), - ], + return ValueListenableBuilder( + valueListenable: _mqttService.isAutoMode, + builder: (context, isAuto, _) { + return Column( + children: [ + ValueListenableBuilder( + valueListenable: _mqttService.isPumpOn, + builder: (context, isOn, _) { + return _buildDeviceTile( + title: 'Pompa Air', + isOn: isOn, + onChanged: isAuto ? null : (val) { + _mqttService.publishControl('pump', val ? 'on' : 'off'); + }, + ); + }, + ), + const SizedBox(height: 16), + ValueListenableBuilder( + valueListenable: _mqttService.isLightOn, + builder: (context, isOn, _) { + return _buildDeviceTile( + title: 'Lampu Pemanas', + isOn: isOn, + onChanged: isAuto ? null : (val) { + _mqttService.publishControl('light', val ? 'on' : 'off'); + }, + ); + }, + ), + ], + ); + }, ); } @@ -283,7 +352,8 @@ class _DashboardPageState extends State { Widget _buildStatusCard() { return Container( width: double.infinity, - padding: const EdgeInsets.all(20), + constraints: const BoxConstraints(minHeight: 104), // Fixed minimum height + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), decoration: BoxDecoration( color: AppTheme.surfaceWhite, borderRadius: BorderRadius.circular(24), @@ -296,7 +366,9 @@ class _DashboardPageState extends State { ], ), child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ + // Fixed size image container Container( width: 64, height: 64, @@ -311,32 +383,104 @@ class _DashboardPageState extends State { ), ), const SizedBox(width: 20), - const Expanded( + // Scroll-stable text column + Expanded( child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( 'Monitor Jamur', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppTheme.primaryGreen, + height: 1.2, // Consistent leading ), ), - Text( - 'Active & Monitoring', - style: TextStyle( - fontSize: 14, - color: AppTheme.textLight, - ), + const SizedBox(height: 4), + ValueListenableBuilder( + valueListenable: _mqttService.connectionState, + builder: (context, state, _) { + return ValueListenableBuilder( + valueListenable: _mqttService.isHardwareOnline, + builder: (context, hwOnline, _) { + String statusText = ""; + Color statusColor = Colors.red; + + if (state == AppMqttStatus.connecting) { + statusText = "Connecting to Broker..."; + statusColor = Colors.orange; + } else if (state == AppMqttStatus.connected) { + if (hwOnline) { + statusText = "All Online ✅"; + statusColor = AppTheme.primaryGreen; + } else { + statusText = "Broker OK (Hardware Offline)"; + statusColor = Colors.blue; + } + } else if (state == AppMqttStatus.error) { + statusText = "Connection Error ❌"; + statusColor = Colors.red; + } else { + statusText = "MQTT Offline"; + statusColor = Colors.red; + } + + return SizedBox( + height: 20, + child: Text( + statusText, + style: TextStyle( + fontSize: 13, + color: statusColor, + fontWeight: FontWeight.bold, + height: 1.0, + ), + ), + ); + }, + ); + }, ), ], ), ), - const Icon( - Icons.check_circle, - color: AppTheme.primaryGreen, - size: 28, + // Fixed width icon slot + Container( + width: 32, + alignment: Alignment.center, + child: ValueListenableBuilder( + valueListenable: _mqttService.connectionState, + builder: (context, state, _) { + return ValueListenableBuilder( + valueListenable: _mqttService.isHardwareOnline, + builder: (context, hwOnline, _) { + IconData iconData = Icons.offline_bolt_rounded; + Color iconColor = Colors.red; + + if (state == AppMqttStatus.connecting) { + iconData = Icons.hourglass_empty_rounded; + iconColor = Colors.orange; + } else if (state == AppMqttStatus.connected) { + if (hwOnline) { + iconData = Icons.check_circle; + iconColor = AppTheme.primaryGreen; + } else { + iconData = Icons.wifi_tethering_rounded; + iconColor = Colors.blue; + } + } + + return Icon( + iconData, + color: iconColor, + size: 28, + ); + }, + ); + }, + ), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 4e8b1fd..d2bd969 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -145,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" fake_async: dependency: transitive description: @@ -360,6 +368,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mqtt_client: + dependency: "direct main" + description: + name: mqtt_client + sha256: "41c8edd3bc8efc80c1c8ebfb40081c24d12d13085faca96b9280a624eca2d893" + url: "https://pub.dev" + source: hosted + version: "10.11.11" native_toolchain_c: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a5d7d05..88a13c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: supabase_flutter: ^2.8.1 google_fonts: ^6.2.1 shared_preferences: ^2.5.5 + mqtt_client: ^10.11.11 dev_dependencies: flutter_test: diff --git a/scratch/test_mqtt_standard.dart b/scratch/test_mqtt_standard.dart new file mode 100644 index 0000000..6d5bff8 --- /dev/null +++ b/scratch/test_mqtt_standard.dart @@ -0,0 +1,38 @@ +import 'dart:io'; +import 'dart:math'; +import 'package:mqtt_client/mqtt_client.dart'; +import 'package:mqtt_client/mqtt_server_client.dart'; + +Future main() async { + print('--- MQTT WS TEST (WITH PREFIX) START ---'); + final String clientId = 'mj_test_${Random().nextInt(999)}'; + + // WAJIB pakai ws:// agar library tidak error + final client = MqttServerClient.withPort('ws://broker.hivemq.com', clientId, 8000); + + client.useWebSocket = true; + client.keepAlivePeriod = 60; + client.logging(on: true); + + final connMess = MqttConnectMessage() + .withClientIdentifier(clientId) + .startClean() + .withWillQos(MqttQos.atLeastOnce); + client.connectionMessage = connMess; + + try { + print('Connecting to ws://broker.hivemq.com:8000...'); + await client.connect(); + + if (client.connectionStatus?.state == MqttConnectionState.connected) { + print('✅✅✅ YES! CONNECTED VIA WEBSOCKET! ✅✅✅'); + client.disconnect(); + } else { + print('❌ FAILED: State is ${client.connectionStatus?.state}'); + } + } catch (e) { + print('❌ EXCEPTION: $e'); + } + print('--- TEST END ---'); + exit(0); +}