Fase 5 · Minggu 15

Proyek Akhir: Hybrid IoT System

Gabungkan semua koneksi — USB, Bluetooth, WiFi, MQTT — menjadi satu aplikasi Flutter pintar.

Deskripsi Proyek

Buat Smart Environment Monitor — system yang bisa berkomunikasi via multiple koneksi, auto-switch connection, dan menyimpan data history.

Smart Environment Monitor — Architecture ESP32 DevKit 🌡️ DHT11 ☀️ LDR 💡 LED 🔌 Relay Serial (USB) Bluetooth (BLE) WiFi (WebSocket) MQTT (via Router) 📱 Flutter App Connection Manager Auto-detect & switch koneksi Dashboard Controls History Settings Provider / Riverpod 💾 SQLite / Hive DB Broker Mosquitto Fitur Utama: Auto-switch koneksi • Sensor dashboard real-time • LED/Relay kontrol • Data history + chart • Settings page

Arsitektur lengkap: ESP32 multi-connection → Flutter App → Local DB

Connection Manager Pattern

Satu abstraksi untuk mengelola semua jenis koneksi.

Dart/// Abstraksi koneksi — semua tipe koneksi implementasi ini
abstract class DeviceConnection {
  String get name;
  bool get isConnected;

  Future<bool> connect();
  void disconnect();
  void sendCommand(Map<String, dynamic> data);
  Stream<Map<String, dynamic>> get dataStream;
}

/// USB Serial connection
class UsbConnection implements DeviceConnection {
  @override String get name => 'USB Serial';
  // ... implementasi dari Minggu 5-6
}

/// Bluetooth BLE connection
class BleConnection implements DeviceConnection {
  @override String get name => 'Bluetooth BLE';
  // ... implementasi dari Minggu 9-10
}

/// WiFi WebSocket connection
class WsConnection implements DeviceConnection {
  @override String get name => 'WiFi WebSocket';
  // ... implementasi dari Minggu 13
}

/// MQTT connection
class MqttConnection implements DeviceConnection {
  @override String get name => 'MQTT';
  // ... implementasi dari Minggu 14
}
Dartimport 'package:flutter/material.dart';

enum ConnectionType { usb, ble, wifi, mqtt }

class ConnectionManager extends ChangeNotifier {
  final Map<ConnectionType, DeviceConnection> _connections = {};
  ConnectionType? _activeType;
  DeviceConnection? _active;

  ConnectionType? get activeType => _activeType;
  DeviceConnection? get active => _active;
  bool get isConnected => _active?.isConnected ?? false;

  ConnectionManager() {
    _connections[ConnectionType.usb] = UsbConnection();
    _connections[ConnectionType.ble] = BleConnection();
    _connections[ConnectionType.wifi] = WsConnection();
    _connections[ConnectionType.mqtt] = MqttConnection();
  }

  /// Connect via tipe tertentu
  Future<bool> connectVia(ConnectionType type) async {
    // Disconnect yang lama
    _active?.disconnect();

    final conn = _connections[type]!;
    final ok = await conn.connect();

    if (ok) {
      _active = conn;
      _activeType = type;
      notifyListeners();
    }
    return ok;
  }

  /// Auto-detect: coba koneksi dari yang paling baik
  Future<bool> autoConnect() async {
    // Prioritas: MQTT > WiFi > BLE > USB
    final priority = [
      ConnectionType.mqtt,
      ConnectionType.wifi,
      ConnectionType.ble,
      ConnectionType.usb,
    ];

    for (final type in priority) {
      final ok = await connectVia(type);
      if (ok) return true;
    }
    return false;
  }

  /// Kirim command via koneksi aktif
  void send(Map<String, dynamic> data) {
    _active?.sendCommand(data);
  }

  /// Stream data dari koneksi aktif
  Stream<Map<String, dynamic>>? get dataStream =>
      _active?.dataStream;

  void disconnectAll() {
    for (final conn in _connections.values) {
      conn.disconnect();
    }
    _active = null;
    _activeType = null;
    notifyListeners();
  }
}

ESP32: Multi-Protocol Firmware

C++ (Arduino)#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

// ======= PINS =======
#define LED_PIN    2
#define RELAY_PIN  4
#define LDR_PIN    34
#define DHT_PIN    15

// ======= WiFi =======
const char* AP_SSID = "ESP32_SmartEnv";
const char* AP_PASS = "12345678";
const char* STA_SSID = "WiFi_Rumah";
const char* STA_PASS = "password123";

// ======= MQTT =======
const char* MQTT_SERVER = "192.168.1.100";
const int MQTT_PORT = 1883;

// ======= BLE =======
#define SERVICE_UUID        "12345678-1234-1234-1234-123456789abc"
#define CHAR_SENSOR_UUID    "12345678-1234-1234-1234-123456789ab1"
#define CHAR_CONTROL_UUID   "12345678-1234-1234-1234-123456789ab2"

// ======= Objects =======
AsyncWebServer httpServer(80);
AsyncWebSocket ws("/ws");
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
BLECharacteristic* sensorChar;
BLECharacteristic* controlChar;
bool bleClientConnected = false;

unsigned long lastSensorRead = 0;

// ======= Sensor data struct =======
struct SensorData {
  float suhu;
  float kelembaban;
  int cahaya;
  bool ledState;
  bool relayState;
} sensorData;

// ======= Build JSON from sensor data =======
String buildJson() {
  JsonDocument doc;
  doc["suhu"] = sensorData.suhu;
  doc["kelembaban"] = sensorData.kelembaban;
  doc["cahaya"] = sensorData.cahaya;
  doc["led"] = sensorData.ledState;
  doc["relay"] = sensorData.relayState;
  doc["uptime"] = millis() / 1000;

  String json;
  serializeJson(doc, json);
  return json;
}

// ======= Handle command (from any connection) =======
void handleCommand(const char* json) {
  JsonDocument doc;
  deserializeJson(doc, json);

  if (doc.containsKey("led")) {
    sensorData.ledState = doc["led"].as<bool>();
    digitalWrite(LED_PIN, sensorData.ledState ? HIGH : LOW);
  }
  if (doc.containsKey("relay")) {
    sensorData.relayState = doc["relay"].as<bool>();
    digitalWrite(RELAY_PIN, sensorData.relayState ? HIGH : LOW);
  }

  // Broadcast perubahan ke semua koneksi
  broadcastAll();
}

// ======= Broadcast ke semua koneksi aktif =======
void broadcastAll() {
  String json = buildJson();

  // Serial
  Serial.println(json);

  // WebSocket
  ws.textAll(json);

  // BLE
  if (bleClientConnected && sensorChar) {
    sensorChar->setValue(json.c_str());
    sensorChar->notify();
  }

  // MQTT
  if (mqtt.connected()) {
    mqtt.publish("sensor/data", json.c_str());
  }
}

// ======= Setup semua koneksi =======
void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);

  // 1. WiFi AP+STA mode
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(AP_SSID, AP_PASS);
  WiFi.begin(STA_SSID, STA_PASS);
  Serial.printf("AP IP: %s\n",
    WiFi.softAPIP().toString().c_str());

  // 2. WebSocket
  ws.onEvent([](AsyncWebSocket *s, AsyncWebSocketClient *c,
    AwsEventType type, void *arg, uint8_t *data, size_t len) {
    if (type == WS_EVT_DATA) {
      handleCommand((char*)data);
    }
  });
  httpServer.addHandler(&ws);
  httpServer.begin();

  // 3. MQTT
  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setCallback([](char* topic, byte* payload,
                      unsigned int len) {
    char buf[256];
    memcpy(buf, payload, len);
    buf[len] = '\0';
    handleCommand(buf);
  });

  // 4. BLE setup
  BLEDevice::init("ESP32_SmartEnv");
  BLEServer* bleServer = BLEDevice::createServer();
  BLEService* bleService =
    bleServer->createService(SERVICE_UUID);
  sensorChar = bleService->createCharacteristic(
    CHAR_SENSOR_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_NOTIFY);
  sensorChar->addDescriptor(new BLE2902());
  controlChar = bleService->createCharacteristic(
    CHAR_CONTROL_UUID,
    BLECharacteristic::PROPERTY_WRITE);
  // BLE control callback (simplified)
  bleService->start();
  BLEAdvertising* adv = BLEDevice::getAdvertising();
  adv->start();

  Serial.println("All protocols ready!");
}

void loop() {
  // MQTT reconnect
  if (WiFi.status() == WL_CONNECTED && !mqtt.connected()) {
    mqtt.connect("ESP32_SmartEnv");
    mqtt.subscribe("control/#");
  }
  mqtt.loop();
  ws.cleanupClients();

  // Serial command
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    handleCommand(cmd.c_str());
  }

  // Baca sensor periodik
  if (millis() - lastSensorRead > 1000) {
    lastSensorRead = millis();
    sensorData.cahaya = analogRead(LDR_PIN);
    // DHT read (pseudo — gunakan library DHT)
    // sensorData.suhu = dht.readTemperature();
    // sensorData.kelembaban = dht.readHumidity();
    sensorData.suhu = 25.0 + random(-20, 20) / 10.0;
    sensorData.kelembaban = 60.0 + random(-50, 50) / 10.0;

    broadcastAll();
  }
}

Flutter: Struktur Proyek

Folder Structurelib/
├── main.dart
├── models/
│   └── sensor_data.dart          # Data class
├── services/
│   ├── connection_manager.dart   # Abstract + manager
│   ├── usb_connection.dart       # USB Serial
│   ├── ble_connection.dart       # BLE
│   ├── ws_connection.dart        # WebSocket
│   ├── mqtt_connection.dart      # MQTT
│   └── db_service.dart           # Local database
├── providers/
│   └── app_provider.dart         # ChangeNotifier
└── pages/
    ├── home_page.dart            # Dashboard
    ├── controls_page.dart        # LED / Relay kontrol
    ├── history_page.dart         # Data chart
    └── settings_page.dart        # Connection setting
Dart — main.dartimport 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => ConnectionManager(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Smart Environment Monitor',
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: const Color(0xFF0f172a),
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFF1e293b),
        ),
      ),
      home: const HomePage(),
    );
  }
}

Milestones Minggu 15

#TaskTarget
1ESP32 multi-protocol firmwareSerial + BLE + WS + MQTT aktif bersamaan
2ConnectionManager + ProviderAuto-detect dan switch koneksi
3Dashboard pageReal-time sensor cards + mini chart
4Controls pageLED + Relay toggle via koneksi aktif
5History pageChart + tabel data tersimpan (SQLite/Hive)
6Settings pagePilih koneksi, atur IP broker, scan BLE
💡 Strategi: Mulai dari yang paling familiar (misalnya WiFi WebSocket). Pastikan satu koneksi bekerja penuh, baru tambahkan tipe koneksi lain ke ConnectionManager.

Checklist Minggu 15