1. Apa itu Web Bluetooth API?
Web Bluetooth API adalah standar W3C yang memungkinkan halaman web JavaScript untuk berkomunikasi dengan perangkat Bluetooth Low Energy (BLE) secara langsung dari browser. Teknologi ini menghilangkan kebutuhan akan aplikasi native atau koneksi serial untuk berinteraksi dengan perangkat IoT berbasis BLE.
Dengan menggabungkan Web Bluetooth di sisi browser dan ESP32 sebagai BLE peripheral, kita bisa membangun aplikasi IoT yang sangat mudah diakses â cukup buka URL di browser, klik connect, dan mulai berkomunikasi dengan perangkat keras.
Mengapa Web Bluetooth Penting untuk IoT?
| Aspek | Aplikasi Native | Web Bluetooth |
|---|---|---|
| Deployment | Perlu install di App Store/Play Store | Cukup buka URL |
| Cross-platform | Perlu develop per platform | Satu kode untuk semua OS |
| Update | Perlu update manual | Update otomatis di server |
| Aksesibilitas | Membutuhkan install | Instant access |
| Fitur BLE | Lengkap | GATT read/write/notify |
Web Bluetooth hanya didukung di Chrome, Edge, dan Opera di desktop. Safari (iOS/macOS) dan Firefox belum mendukung Web Bluetooth. Untuk iOS, pertimbangkan menggunakan Bridgefy SDK atau alternatif lain.
2. BLE Advertising di ESP32
BLE Advertising adalah mekanisme di mana perangkat BLE secara periodik mengirimkan paket data tanpa koneksi (connectionless). Perangkat lain yang mendengarkan (scanner) dapat membaca data advertising ini untuk mengetahui keberadaan dan informasi dasar perangkat BLE.
Pada ESP32, kita bisa mengirim Advertisement Data yang berisi nama perangkat, manufacturer-specific data, dan layanan yang tersedia.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEAdvertising.h>
// Inisialisasi BLE dengan nama perangkat
void setupBLEAdvertising() {
// Init BLE Device
BLEDevice::init("ESP32-WebBT");
// Buat server BLE
BLEServer *pServer = BLEDevice::createServer();
// Buat service
BLEService *pService = pServer->createService(
BLEUUID((uint16_t)0x181A) // Environmental Sensing
);
// Buat characteristic untuk data suhu
BLECharacteristic *pTempCharacteristic = pService->createCharacteristic(
BLEUUID((uint16_t)0x2A6E), // Temperature
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
// Mulai service
pService->start();
// Konfigurasi advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x181A));
pAdvertising->setScanResponse(true);
// Atur interval dan power
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMaxPreferred(0x12);
pAdvertising->setTxPower(4); // TX Power: +4 dBm
// Mulai advertising
BLEDevice::startAdvertising();
Serial.println("BLE Advertising dimulai...");
}
void loop() {
// Update nilai suhu
float suhu = readTemperature();
int16_t suhuRaw = (int16_t)(suhu * 100);
pTempCharacteristic->setValue((uint8_t *)&suhuRaw, 2);
pTempCharacteristic->notify();
delay(2000);
}
Tipe Advertising Data
- â Menerima koneksi dari central
- â GATT services tersedia
- â Read, Write, Notify
- â Interval lebih lambat
- â Cocok untuk Web Bluetooth
- â Tidak bisa dikoneksi
- â Tidak ada GATT services
- â Interval lebih cepat
- â Daya lebih hemat
- â Cocok untuk Beacon
3. GATT Services & Characteristics
GATT (Generic Attribute Profile) adalah struktur data yang digunakan dalam BLE untuk mengorganisir informasi. GATT terdiri dari Services yang berisi Characteristics, dan masing-masing Characteristics memiliki Descriptors.
Standard BLE UUIDs
| UUID (16-bit) | Nama | Deskripsi |
|---|---|---|
| 0x180A | Device Information | Informasi perangkat (model, serial) |
| 0x180F | Battery Service | Level baterai |
| 0x181A | Environmental Sensing | Suhu, kelembaban, tekanan |
| 0x180D | Heart Rate | Detak jantung |
| 0x1809 | Health Thermometer | Suhu dari termometer |
| 0x1800 | Generic Access | Informasi dasar perangkat |
Untuk proyek custom, gunakan Custom UUID 128-bit yang dihasilkan dari UUID generator. Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Service dan Characteristic dalam proyek yang sama harus menggunakan UUID yang konsisten.
4. Membuat BLE Server di ESP32
Sekarang kita akan membuat BLE Server lengkap di ESP32 yang akan berfungsi sebagai peripheral yang bisa diakses dari Web Bluetooth di browser.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// Custom UUIDs (generate di https://www.uuidgenerator.net/)
#define SERVICE_UUID "12345678-1234-1234-1234-123456789abc"
#define CHAR_READ_UUID "12345678-1234-1234-1234-123456789001"
#define CHAR_WRITE_UUID "12345678-1234-1234-1234-123456789002"
#define CHAR_NOTIFY_UUID "12345678-1234-1234-1234-123456789003"
BLECharacteristic *pReadChar;
BLECharacteristic *pWriteChar;
BLECharacteristic *pNotifyChar;
bool deviceConnected = false;
float suhu = 25.0;
int ledState = 0;
// Callback saat koneksi berubah
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Client terhubung!");
}
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Client terputus, mulai advertising...");
// Restart advertising setelah disconnect
pServer->getAdvertising()->start();
}
};
// Callback untuk write characteristic
class MyWriteCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String value = pCharacteristic->getValue();
if (value.length() > 0) {
Serial.print("Data diterima: ");
Serial.println(value.c_str());
// Contoh: kontrol LED berdasarkan data yang ditulis
if (value == "ON") {
digitalWrite(LED_BUILTIN, HIGH);
ledState = 1;
} else if (value == "OFF") {
digitalWrite(LED_BUILTIN, LOW);
ledState = 0;
}
}
}
};
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
// 1. Init BLE Device
BLEDevice::init("ESP32-WebBT");
// 2. Buat Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// 3. Buat Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// 4. Buat Characteristics
// Read-only characteristic (suhu)
pReadChar = pService->createCharacteristic(
CHAR_READ_UUID,
BLECharacteristic::PROPERTY_READ
);
pReadChar->setValue("25.0");
// Write-only characteristic (kontrol LED)
pWriteChar = pService->createCharacteristic(
CHAR_WRITE_UUID,
BLECharacteristic::PROPERTY_WRITE
);
pWriteChar->setCallbacks(new MyWriteCallbacks());
// Notify characteristic (data periodik)
pNotifyChar = pService->createCharacteristic(
CHAR_NOTIFY_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
pNotifyChar->addDescriptor(new BLE2902());
// 5. Mulai Service
pService->start();
// 6. Konfigurasi & Mulai Advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMaxPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE Server siap! Menunggu koneksi...");
}
void loop() {
// Kirim data suhu via notify setiap 2 detik
if (deviceConnected) {
suhu += random(-5, 6) / 10.0;
String suhuStr = String(suhu, 1);
pNotifyChar->setValue(suhuStr.c_str());
pNotifyChar->notify();
Serial.println("Suhu dikirim: " + suhuStr + " °C");
}
delay(2000);
}
Kode di atas membuat 3 characteristic: read untuk membaca suhu, write untuk mengontrol LED, dan notify untuk mengirim data suhu secara periodik ke client yang terhubung.
5. Web Bluetooth Client di Browser
Sekarang mari kita buat sisi browser yang akan terhubung ke ESP32 BLE Server menggunakan Web Bluetooth API.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>ESP32 Web Bluetooth Controller</title>
<style>
body { font-family: 'Segoe UI', sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
.card { border: 1px solid #ddd; border-radius: 12px; padding: 20px; margin: 10px 0; }
.status { padding: 10px; border-radius: 8px; margin: 10px 0; }
.connected { background: #d4edda; color: #155724; }
.disconnected { background: #f8d7da; color: #721c24; }
button { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer;
background: #007bff; color: white; font-size: 16px; margin: 5px; }
button:hover { background: #0056b3; }
button:disabled { background: #ccc; cursor: not-allowed; }
#tempDisplay { font-size: 48px; font-weight: bold; text-align: center; color: #333; }
.control-panel { display: flex; gap: 10px; flex-wrap: wrap; }
</style>
</head>
<body>
<h1>đĄī¸ ESP32 Web Bluetooth</h1>
<div class="card">
<div id="status" class="status disconnected">đ´ Terputus</div>
<button id="connectBtn" onclick="connectBLE()">đ Connect ke ESP32</button>
<button id="disconnectBtn" onclick="disconnectBLE()" disabled>â Disconnect</button>
</div>
<div class="card">
<h3>Suhu Real-time</h3>
<div id="tempDisplay">-- °C</div>
<button onclick="readTemperature()">đ Baca Suhu</button>
</div>
<div class="card">
<h3>Kontrol LED</h3>
<div class="control-panel">
<button onclick="sendCommand('ON')">đĄ LED ON</button>
<button onclick="sendCommand('OFF')">âŦ LED OFF</button>
</div>
</div>
<div class="card">
<h3>Log Aktivitas</h3>
<div id="log" style="height:150px;overflow-y:auto;background:#f5f5f5;padding:10px;
border-radius:8px;font-family:monospace;font-size:13px;"></div>
</div>
<script>
// Custom UUIDs â harus sama dengan yang di ESP32!
const SERVICE_UUID = '12345678-1234-1234-1234-123456789abc';
const CHAR_READ_UUID = '12345678-1234-1234-1234-123456789001';
const CHAR_WRITE_UUID = '12345678-1234-1234-1234-123456789002';
const CHAR_NOTIFY_UUID = '12345678-1234-1234-1234-123456789003';
let device, server, readChar, writeChar, notifyChar;
function log(msg) {
const el = document.getElementById('log');
el.innerHTML += `[${new Date().toLocaleTimeString()}] ${msg}<br>`;
el.scrollTop = el.scrollHeight;
}
function setStatus(text, connected) {
const el = document.getElementById('status');
el.textContent = connected ? `đĸ ${text}` : `đ´ ${text}`;
el.className = `status ${connected ? 'connected' : 'disconnected'}`;
}
async function connectBLE() {
try {
log('Mencari perangkat BLE...');
// Request perangkat dari user
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE_UUID] }],
optionalServices: [SERVICE_UUID]
});
log(`Perangkat ditemukan: ${device.name}`);
device.addEventListener('gattserverdisconnected', onDisconnected);
// Connect ke GATT Server
log('Menghubungkan ke GATT Server...');
server = await device.gatt.connect();
setStatus(`${device.name} terhubung!`, true);
// Dapatkan service
const service = await server.getPrimaryService(SERVICE_UUID);
log('Service ditemukan!');
// Dapatkan characteristics
readChar = await service.getCharacteristic(CHAR_READ_UUID);
writeChar = await service.getCharacteristic(CHAR_WRITE_UUID);
notifyChar = await service.getCharacteristic(CHAR_NOTIFY_UUID);
log('Semua characteristics ditemukan!');
// Aktifkan notify
await notifyChar.startNotifications();
notifyChar.addEventListener('characteristicvaluechanged', onNotify);
log('Notify diaktifkan â menunggu data...');
// Enable tombol
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
} catch (error) {
log(`Error: ${error.message}`);
setStatus('Koneksi gagal', false);
}
}
function onDisconnected() {
setStatus('Terputus', false);
log('Perangkat terputus');
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
}
function disconnectBLE() {
if (device && device.gatt.connected) {
device.gatt.disconnect();
}
}
async function readTemperature() {
if (!readChar) return;
try {
const value = await readChar.readValue();
const decoder = new TextDecoder('utf-8');
const suhu = decoder.decode(value);
document.getElementById('tempDisplay').textContent = `${suhu} °C`;
log(`Suhu dibaca: ${suhu} °C`);
} catch (error) {
log(`Error read: ${error.message}`);
}
}
function onNotify(event) {
const decoder = new TextDecoder('utf-8');
const suhu = decoder.decode(event.target.value);
document.getElementById('tempDisplay').textContent = `${suhu} °C`;
log(`Suhu notify: ${suhu} °C`);
}
async function sendCommand(cmd) {
if (!writeChar) return;
try {
const encoder = new TextEncoder();
await writeChar.writeValue(encoder.encode(cmd));
log(`Perintah dikirim: ${cmd}`);
} catch (error) {
log(`Error write: ${error.message}`);
}
}
</script>
</body>
</html>
6. Characteristic Read & Write
Operasi Read dan Write adalah operasi BLE dasar yang paling sering digunakan. Read memungkinkan client membaca nilai dari characteristic, sedangkan Write memungkinkan client mengirim data ke peripheral.
Read dengan Multiple Formats
// Membaca berbagai tipe data dari characteristic
async function readCharacteristicData(char) {
const value = await char.readValue();
// Baca sebagai unsigned 8-bit integer
const uint8 = value.getUint8(0);
console.log('Uint8:', uint8);
// Baca sebagai signed 16-bit integer (little-endian)
const int16 = value.getInt16(0, true); // true = little-endian
console.log('Int16:', int16);
// Baca sebagai float 32-bit
const float32 = value.getFloat32(0, true);
console.log('Float32:', float32);
// Baca sebagai string UTF-8
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(value);
console.log('Text:', text);
// Baca sebagai array byte
const bytes = new Uint8Array(value.buffer);
console.log('Bytes:', Array.from(bytes));
}
// Write berbagai tipe data
async function writeCharacteristicData(char, data) {
const encoder = new TextEncoder();
// Write string
await char.writeValue(encoder.encode('Hello ESP32'));
// Write angka 8-bit
const buffer8 = new Uint8Array([42]);
await char.writeValue(buffer8);
// Write angka 16-bit (little-endian)
const buffer16 = new Uint16Array([1000]);
await char.writeValue(buffer16);
// Write campuran byte
const bufferMixed = new Uint8Array([0x01, 0xFF, 0x2A, 0x00]);
await char.writeValue(bufferMixed);
// Write With Response (reliable)
await char.writeValueWithResponse(encoder.encode('confirmed!'));
// Write Without Response (unreliable, lebih cepat)
await char.writeValueWithoutResponse(encoder.encode('fire-and-forget'));
}
Write With Response menunggu konfirmasi dari peripheral bahwa data diterima dengan benar. Write Without Response mengirim data tanpa menunggu konfirmasi â lebih cepat tapi bisa kehilangan data. Untuk data penting, gunakan With Response.
7. Notify & Indicate
Selain Read/Write, BLE juga mendukung mekanisme push data dari peripheral ke client menggunakan Notify dan Indicate.
| Fitur | Notify | Indicate |
|---|---|---|
| Konfirmasi | Tidak ada konfirmasi | Ada konfirmasi (ACK) |
| Reliability | Best-effort | Reliable |
| Throughput | Lebih tinggi | Lebih rendah |
| Descriptor | CCCD bit 0 = 1 | CCCD bit 1 = 1 |
| Kasus Pakai | Sensor data periodik | Data kritis / alarm |
#include <BLE2902.h>
// Setup Notify Characteristic
BLECharacteristic *pNotifyChar = pService->createCharacteristic(
CHAR_NOTIFY_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
// Tambahkan Client Characteristic Configuration Descriptor (CCCD)
BLE2902 *pDesc = new BLE2902();
pDesc->setNotifications(true);
pDesc->setIndications(true);
pNotifyChar->addDescriptor(pDesc);
// Setup Indicate Characteristic
BLECharacteristic *pIndicateChar = pService->createCharacteristic(
CHAR_INDICATE_UUID,
BLECharacteristic::PROPERTY_INDICATE
);
pIndicateChar->addDescriptor(new BLE2902());
// Callback saat client mengaktifkan/menonaktifkan notify/indicate
class MyNotifyCallbacks : public BLECharacteristicCallbacks {
void onNotify(BLECharacteristic *pCharacteristic) {
Serial.println("Client mengaktifkan NOTIFY");
}
void onIndicate(BLECharacteristic *pCharacteristic) {
Serial.println("Client mengaktifkan INDICATE");
}
};
void loop() {
if (deviceConnected) {
// Kirim data via Notify
float suhu = readDHT22();
pNotifyChar->setValue((uint8_t *)&suhu, sizeof(float));
pNotifyChar->notify(); // Kirim tanpa konfirmasi
delay(1000);
// Kirim data via Indicate
float humidity = readDHT22_Humidity();
pIndicateChar->setValue((uint8_t *)&humidity, sizeof(float));
pIndicateChar->indicate(); // Kirim dengan konfirmasi
}
}
8. Proyek Praktis: Monitor Suhu Web Bluetooth
Mari kita buat proyek lengkap yang menggabungkan semua konsep: ESP32 sebagai BLE server yang membaca sensor DHT22, dan browser sebagai client yang menampilkan data secara real-time.
Skema Koneksi Hardware
| DHT22 Pin | ESP32 Pin | Keterangan |
|---|---|---|
| VCC | 3.3V | Voltase power |
| DATA | GPIO 4 | Data dengan pull-up 10kΊ |
| NC | - | Not connected |
| GND | GND | Ground |
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <DHT.h>
// Pin & sensor config
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// BLE UUIDs
#define SERVICE_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456789"
#define CHAR_TEMP_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456790"
#define CHAR_HUMID_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456791"
#define CHAR_CONFIG_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456792"
BLECharacteristic *pTempChar;
BLECharacteristic *pHumidChar;
BLECharacteristic *pConfigChar;
bool deviceConnected = false;
unsigned long lastSend = 0;
uint16_t sendInterval = 2000; // Default 2 detik
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
Serial.println("â Client terhubung");
}
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
Serial.println("â Client terputus");
pServer->getAdvertising()->start();
}
};
class ConfigCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pChar) {
uint8_t *data = pChar->getData();
uint16_t len = pChar->getLength();
if (len == 2) {
sendInterval = data[0] | (data[1] << 8);
sendInterval = constrain(sendInterval, 500, 30000);
Serial.printf("Interval diubah: %d ms\n", sendInterval);
}
}
};
void setup() {
Serial.begin(115200);
dht.begin();
// Init BLE
BLEDevice::init("TempMonitor-BT");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID, 10);
// Temperature characteristic (Notify)
pTempChar = pService->createCharacteristic(
CHAR_TEMP_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
pTempChar->addDescriptor(new BLE2902());
// Humidity characteristic (Notify)
pHumidChar = pService->createCharacteristic(
CHAR_HUMID_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
pHumidChar->addDescriptor(new BLE2902());
// Config characteristic (Write â set interval)
pConfigChar = pService->createCharacteristic(
CHAR_CONFIG_UUID,
BLECharacteristic::PROPERTY_WRITE
);
pConfigChar->setCallbacks(new ConfigCallbacks());
pService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Serial.println("đĄī¸ Temperature Monitor BLE siap!");
}
void loop() {
if (deviceConnected && (millis() - lastSend > sendInterval)) {
float temp = dht.readTemperature();
float humid = dht.readHumidity();
if (!isnan(temp) && !isnan(humid)) {
// Kirim temperature
pTempChar->setValue((uint8_t *)&temp, sizeof(float));
pTempChar->notify();
// Kirim humidity
pHumidChar->setValue((uint8_t *)&humid, sizeof(float));
pHumidChar->notify();
Serial.printf("T: %.1f°C H: %.1f%%\n", temp, humid);
}
lastSend = millis();
}
}
Kode Browser untuk Monitor Suhu
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Monitor Suhu Web Bluetooth</title>
<style>
* { box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: #eee; padding: 20px; }
.dashboard { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; max-width: 700px; margin: auto; }
.sensor-card { background: #16213e; border-radius: 16px; padding: 30px; text-align: center; }
.sensor-value { font-size: 56px; font-weight: 700; margin: 10px 0; }
.sensor-label { font-size: 14px; opacity: 0.7; text-transform: uppercase; letter-spacing: 2px; }
.temp-color { color: #ff6b6b; }
.humid-color { color: #4ecdc4; }
.chart-area { grid-column: 1/-1; background: #16213e; border-radius: 16px; padding: 20px; }
canvas { width: 100%; height: 200px; }
.controls { grid-column: 1/-1; display: flex; gap: 10px; justify-content: center; }
button { padding: 12px 24px; border: none; border-radius: 10px; font-size: 16px; cursor: pointer; }
.btn-connect { background: #00b894; color: white; }
.btn-config { background: #6c5ce7; color: white; }
</style>
</head>
<body>
<h1 style="text-align:center">đĄī¸ Monitor Suhu BLE</h1>
<div class="dashboard">
<div class="sensor-card">
<div class="sensor-label">Suhu</div>
<div class="sensor-value temp-color" id="temp">--°C</div>
</div>
<div class="sensor-card">
<div class="sensor-label">Kelembaban</div>
<div class="sensor-value humid-color" id="humid">--%</div>
</div>
<div class="chart-area">
<canvas id="chart"></canvas>
</div>
<div class="controls">
<button class="btn-connect" onclick="connect()" id="connBtn">đ Connect</button>
<button class="btn-config" onclick="setInterval_(1000)">1 detik</button>
<button class="btn-config" onclick="setInterval_(3000)">3 detik</button>
<button class="btn-config" onclick="setInterval_(5000)">5 detik</button>
</div>
</div>
<script>
const SERVICE = 'a0b1c2d3-e4f5-6789-abcd-ef0123456789';
const TEMP_CH = 'a0b1c2d3-e4f5-6789-abcd-ef0123456790';
const HUMID_CH = 'a0b1c2d3-e4f5-6789-abcd-ef0123456791';
const CFG_CH = 'a0b1c2d3-e4f5-6789-abcd-ef0123456792';
let tempHistory = [];
async function connect() {
const dev = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE] }]
});
const server = await dev.gatt.connect();
const service = await server.getPrimaryService(SERVICE);
// Subscribe temperature
const tempChar = await service.getCharacteristic(TEMP_CH);
await tempChar.startNotifications();
tempChar.addEventListener('characteristicvaluechanged', e => {
const val = e.target.value.getFloat32(0, true);
document.getElementById('temp').textContent = val.toFixed(1) + '°C';
tempHistory.push(val);
if (tempHistory.length > 50) tempHistory.shift();
drawChart();
});
// Subscribe humidity
const humidChar = await service.getCharacteristic(HUMID_CH);
await humidChar.startNotifications();
humidChar.addEventListener('characteristicvaluechanged', e => {
const val = e.target.value.getFloat32(0, true);
document.getElementById('humid').textContent = val.toFixed(1) + '%';
});
document.getElementById('connBtn').textContent = 'â
Connected';
window._cfgChar = await service.getCharacteristic(CFG_CH);
}
async function setInterval_(ms) {
if (!window._cfgChar) return;
const buf = new Uint16Array([ms]);
await window._cfgChar.writeValue(buf);
}
function drawChart() {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * 2;
canvas.height = 400;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (tempHistory.length < 2) return;
const min = Math.min(...tempHistory) - 1;
const max = Math.max(...tempHistory) + 1;
const w = canvas.width / (tempHistory.length - 1);
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 3;
ctx.beginPath();
tempHistory.forEach((v, i) => {
const x = i * w;
const y = canvas.height - ((v - min) / (max - min)) * canvas.height;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
</script>
</body>
</html>
9. Kompatibilitas Browser & Tips
| Browser | Platform | Web Bluetooth | Catatan |
|---|---|---|---|
| Chrome | Desktop (Win/Mac/Linux) | â Didukung | Chrome 56+ |
| Chrome | Android | â Didukung | Chrome 56+ |
| Chrome | iOS | â Tidak didukung | Batasan iOS |
| Edge | Desktop | â Didukung | Edge 79+ (Chromium) |
| Opera | Desktop | â Didukung | Berdasarkan Chromium |
| Safari | iOS/macOS | â Tidak didukung | Belum ada rencana |
| Firefox | Semua | â Tidak didukung | Belum ada rencana |
Tips & Best Practices
- HTTPS diperlukan â Web Bluetooth hanya berfungsi di halaman HTTPS atau localhost
- Handle disconnect â Selalu sertakan event listener untuk disconnect dan reconnect otomatis
- Timeout â Set timeout pada koneksi untuk menghindari hanging jika perangkat tidak ditemukan
- Error handling â Tangani semua error seperti NotSupportedError, NetworkError, dan SecurityError
- Power management â Gunakan notify/indicate daripada polling untuk menghemat daya ESP32
- Data encoding â Gunakan TextEncoder/TextDecoder untuk string, DataView untuk numerik
Karena Web Bluetooth tidak didukung di iOS, untuk proyek yang harus kompatibel dengan iOS, pertimbangkan menggunakan ESP32 dengan WiFi dan WebSocket, atau gunakan nRF Connect / LightBlue sebagai app BLE reader.
- â Dashboard monitoring
- â Setup & configuration tool
- â Quick data visualization
- â Prototype & development
- â Desktop-first application
- â iOS-first app
- â High throughput streaming
- â Background operation
- â Multi-device simultaneous
- â Production-critical systems
10. Quiz Pemahaman
Uji pemahaman kamu tentang ESP32 Web Bluetooth API: