GATT, Services, Characteristics, ESP32 BLE Server, dan Flutter flutter_blue_plus.
BLE menggunakan arsitektur GATT (Generic Attribute Profile) yang terdiri dari Services dan Characteristics.
GATT Architecture: Server (ESP32) menyediakan Services โ Client (Flutter) baca/tulis
| Istilah | Penjelasan | Analogi |
|---|---|---|
Service | Grup fitur (sensor, LED, dll) | Folder |
Characteristic | Data individual (suhu, status LED) | File dalam folder |
Read | Client baca data dari server | Download file |
Write | Client kirim data ke server | Upload file |
Notify | Server push data otomatis ke client | Auto-sync |
UUID | ID unik untuk service/characteristic | Alamat |
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);
}
}
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'),
),
],
),
),
);
}
}