Fase 3 ยท Minggu 9

Bluetooth Low Energy (BLE)

GATT, Services, Characteristics, ESP32 BLE Server, dan Flutter flutter_blue_plus.

Konsep BLE: GATT Architecture

BLE menggunakan arsitektur GATT (Generic Attribute Profile) yang terdiri dari Services dan Characteristics.

BLE Server (ESP32) ๐Ÿ“ฆ Service: Sensor (UUID: 0x1810) ๐Ÿ“„ Suhu (Read, Notify) ๐Ÿ“„ Kelembaban (Read) ๐Ÿ“„ Cahaya (Read, Notify) ๐Ÿ“ฆ Service: LED Control (UUID: 0x1815) ๐Ÿ“„ LED State (Read, Write) โ€” "ON" / "OFF" GATT = Profile โ†’ Service(s) โ†’ Characteristic(s) ๐Ÿ“ฑ BLE Client (Flutter App) โ€ข Scan for services โ€ข Read characteristics โ€ข Write to LED char โ€ข Subscribe notify โ€ข Auto receive updates Read Write

GATT Architecture: Server (ESP32) menyediakan Services โ†’ Client (Flutter) baca/tulis

IstilahPenjelasanAnalogi
ServiceGrup fitur (sensor, LED, dll)Folder
CharacteristicData individual (suhu, status LED)File dalam folder
ReadClient baca data dari serverDownload file
WriteClient kirim data ke serverUpload file
NotifyServer push data otomatis ke clientAuto-sync
UUIDID unik untuk service/characteristicAlamat

ESP32: BLE Server

C++ (Arduino)#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// UUID untuk service & characteristics
#define SERVICE_UUID        "12345678-1234-1234-1234-123456789abc"
#define CHAR_SENSOR_UUID    "12345678-1234-1234-1234-123456789abd"
#define CHAR_LED_UUID       "12345678-1234-1234-1234-123456789abe"

#define LED_PIN 2
#define LDR_PIN 34

BLECharacteristic *sensorCharacteristic;
BLECharacteristic *ledCharacteristic;
bool deviceConnected = false;

// Callback saat client connect/disconnect
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    Serial.println("Client connected!");
  }
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    Serial.println("Client disconnected!");
    BLEDevice::startAdvertising(); // restart advertising
  }
};

// Callback saat LED characteristic ditulis
class LedCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    String value = pCharacteristic->getValue().c_str();
    if (value == "ON") {
      digitalWrite(LED_PIN, HIGH);
      Serial.println("BLE: LED ON");
    } else if (value == "OFF") {
      digitalWrite(LED_PIN, LOW);
      Serial.println("BLE: LED OFF");
    }
  }
};

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  // Init BLE
  BLEDevice::init("ESP32_BLE_IoT");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Sensor characteristic (Read + Notify)
  sensorCharacteristic = pService->createCharacteristic(
    CHAR_SENSOR_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_NOTIFY
  );
  sensorCharacteristic->addDescriptor(new BLE2902());

  // LED characteristic (Read + Write)
  ledCharacteristic = pService->createCharacteristic(
    CHAR_LED_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_WRITE
  );
  ledCharacteristic->setCallbacks(new LedCallbacks());
  ledCharacteristic->setValue("OFF");

  // Start
  pService->start();
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();
  Serial.println("BLE Server ready! Waiting for client...");
}

void loop() {
  if (deviceConnected) {
    // Update sensor data & notify
    int cahaya = analogRead(LDR_PIN);
    float suhu = 25.0 + random(0, 50) / 10.0;

    String data = String(suhu, 1) + "," + String(cahaya);
    sensorCharacteristic->setValue(data.c_str());
    sensorCharacteristic->notify();

    delay(2000);
  }
}

Flutter: BLE Client dengan flutter_blue_plus

YAML โ€” pubspec.yamldependencies:
  flutter:
    sdk: flutter
  flutter_blue_plus: ^1.31.0
Dart โ€” lib/main.dart (ringkasan)import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

const serviceUUID = "12345678-1234-1234-1234-123456789abc";
const sensorUUID  = "12345678-1234-1234-1234-123456789abd";
const ledUUID     = "12345678-1234-1234-1234-123456789abe";

class BLEPage extends StatefulWidget {
  const BLEPage({super.key});
  @override
  State<BLEPage> createState() => _BLEPageState();
}

class _BLEPageState extends State<BLEPage> {
  BluetoothDevice? _device;
  BluetoothCharacteristic? _ledChar;
  BluetoothCharacteristic? _sensorChar;
  bool _connected = false;
  bool _ledOn = false;
  String _sensorData = "---";

  // Scan for ESP32
  void _startScan() {
    FlutterBluePlus.startScan(timeout: const Duration(seconds: 5));

    FlutterBluePlus.scanResults.listen((results) {
      for (var r in results) {
        if (r.device.platformName == "ESP32_BLE_IoT") {
          FlutterBluePlus.stopScan();
          _connectDevice(r.device);
          break;
        }
      }
    });
  }

  // Connect & discover services
  Future<void> _connectDevice(BluetoothDevice device) async {
    await device.connect();
    List<BluetoothService> services = await device.discoverServices();

    for (var service in services) {
      if (service.uuid.toString() == serviceUUID) {
        for (var char in service.characteristics) {
          if (char.uuid.toString() == ledUUID) {
            _ledChar = char;
          }
          if (char.uuid.toString() == sensorUUID) {
            _sensorChar = char;
            // Subscribe ke notify
            await char.setNotifyValue(true);
            char.onValueReceived.listen((value) {
              String data = utf8.decode(value);
              setState(() => _sensorData = data);
            });
          }
        }
      }
    }

    setState(() {
      _device = device;
      _connected = true;
    });
  }

  // Write to LED characteristic
  void _toggleLed() async {
    if (_ledChar == null) return;
    String cmd = _ledOn ? "OFF" : "ON";
    await _ledChar!.write(utf8.encode(cmd));
    setState(() => _ledOn = !_ledOn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('BLE Sensor Monitor')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Connection status
            Text(_connected ? 'Connected via BLE' : 'Not connected',
              style: TextStyle(color: _connected
                  ? Colors.greenAccent : Colors.grey)),
            const SizedBox(height: 20),
            // Sensor data
            Text('Sensor: $_sensorData',
              style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 20),
            // LED control
            Icon(Icons.lightbulb, size: 80,
              color: _ledOn ? Colors.amber : Colors.grey[700]),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _connected ? _toggleLed : _startScan,
              child: Text(_connected
                  ? (_ledOn ? 'Turn OFF' : 'Turn ON')
                  : 'Scan & Connect'),
            ),
          ],
        ),
      ),
    );
  }
}

Kapan Pakai BLE vs Classic?

Gunakan BT Classic jika: โ€ข Data banyak & kontinu โ€ข Tidak perlu hemat baterai โ€ข Kode lebih sederhana โ€ข Jarak dekat (<10m) Gunakan BLE jika: โ€ข Perlu hemat baterai โ€ข Data kecil / periodik โ€ข Support iOS juga โ€ข Multiple clients
โ„น๏ธ Catatan iOS: iOS tidak mendukung Bluetooth Classic (SPP). Jika kamu ingin app yang jalan di iOS juga, gunakan BLE.

Checklist Minggu 9