00 BLE temel kavramlar — Advertising, GAP, GATT, ATT
Bluetooth Low Energy (BLE), Bluetooth 4.0 ile gelen düşük güç tüketimli versiyondur. IoT sensör düğümleri için tasarlanmıştır.
BLE Protokol Yığını ────────────────────────────────────────── GATT Generic Attribute Profile → Servis ve karakteristik tanımları GAP Generic Access Profile → Advertising, connection, security ATT Attribute Protocol → GATT'ın taşıma katmanı (client/server) L2CAP Logical Link Control and Adaptation → Paket segmentasyonu, channel multiplexing HCI Host Controller Interface LL Link Layer → advertising, connections PHY Physical Layer → 2.4 GHz RF
Advertising (reklam yayını)
BLE cihazları sürekli bağlantı tutmak yerine periyodik olarak advertisement paketleri yayınlar. Central (merkez) cihaz bu paketleri dinler (scanning) ve bağlanmak isterse Connection Request gönderir.
GAP rolleri
Peripheral (sensör) Central (telefon/Pi)
│ │
│── ADV_IND ──────────────────►│ advertising
│ │
│◄─ CONNECT_REQ ──────────────│ central bağlanmak istiyor
│ │
│══════ Connection ════════════│ bağlantı kuruldu
│ │
│◄─ ATT Read Request ─────────│ central veri okuyor
│── ATT Read Response ────────►│
│ │
│── ATT Handle Value Notification ──►│ peripheral bildirim gönderiyor
01 GATT hiyerarşisi — Profile, Service, Characteristic, Descriptor
GATT, BLE veri modelinin omurgasıdır. Attribute tablosunu hiyerarşik olarak organize eder.
Profile (koleksiyon — örn: Health Thermometer Profile) └── Service (işlevsel grup — örn: Temperature Service, 0x1809) ├── Characteristic (veri birimi — örn: Temperature Measurement, 0x2A1C) │ ├── Value (ham veri: bytes) │ └── Descriptor (metadata — örn: CCCD, Unit, Description) └── Characteristic (örn: Measurement Interval, 0x2A21)
ATT handle ve attribute tablosu
Her attribute (service, characteristic, descriptor) bir 16-bit handle'a sahiptir. ATT protokolü bu handle'lar üzerinden çalışır:
Handle Type UUID Value
────── ──────────────────────────────────────────────────────────
0x0001 2800 (Primary Service) 1809 (Health Thermometer Service)
0x0002 2803 (Characteristic) Properties: NOTIFY + READ
0x0003 2A1C (Temp Measurement) [value bytes]
0x0004 2902 (CCCD) [00 00] → notify kapalı / [01 00] → açık
0x0005 2803 (Characteristic) Properties: READ
0x0006 2A21 (Meas. Interval) [0A 00] → 10 saniye
Temel servis UUID'leri (Bluetooth SIG)
02 BlueZ D-Bus GATT server API
BlueZ, GATT server oluşturmak için uygulama tarafından D-Bus nesneleri export edilmesini bekler. "D-Bus object model" yaklaşımı.
Uygulama (Python) bluetoothd
──────────────────────────────────────────────────────
D-Bus'a nesne kaydet: RegisterApplication() ile
/app/service0 → GATT nesnelerini keşfeder
/app/service0/char0 ve BLE stack'e ekler
/app/service0/char0/desc0
↓
org.bluez.GattService1
org.bluez.GattCharacteristic1
org.bluez.GattDescriptor1
Gerekli D-Bus arayüzleri
03 Python ile custom GATT service — sıcaklık sensörü
BlueZ D-Bus GATT API kullanarak sıcaklık değerini BLE üzerinden yayınlayan tam çalışır örnek.
#!/usr/bin/env python3
"""BLE GATT Sıcaklık Sensörü — BlueZ D-Bus API ile."""
import dbus
import dbus.exceptions
import dbus.mainloop.glib
import dbus.service
from gi.repository import GLib
import struct
import random
BLUEZ_SERVICE = 'org.bluez'
GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
GATT_SERVICE_IFACE = 'org.bluez.GattService1'
GATT_CHAR_IFACE = 'org.bluez.GattCharacteristic1'
GATT_DESC_IFACE = 'org.bluez.GattDescriptor1'
LE_ADV_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
LE_ADV_IFACE = 'org.bluez.LEAdvertisement1'
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
# Sıcaklık servisi için UUID'ler (SIG standartları)
TEMP_SERVICE_UUID = '0000180d-0000-1000-8000-00805f9b34fb' # Heart Rate → örnek
TEMP_CHAR_UUID = '00002a37-0000-1000-8000-00805f9b34fb'
# Gerçek Environmental Sensing için:
ENV_SERVICE_UUID = '0000181a-0000-1000-8000-00805f9b34fb'
TEMP_MEAS_UUID = '00002a6e-0000-1000-8000-00805f9b34fb' # Temperature 2A6E
CCCD_UUID = '00002902-0000-1000-8000-00805f9b34fb'
class InvalidArgsException(dbus.exceptions.DBusException):
_dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
class Application(dbus.service.Object):
"""GATT uygulaması — tüm servislerin kök nesnesi."""
def __init__(self, bus):
self.path = '/'
self.services = []
dbus.service.Object.__init__(self, bus, self.path)
self.add_service(TemperatureService(bus, 0))
def get_path(self):
return dbus.ObjectPath(self.path)
def add_service(self, service):
self.services.append(service)
@dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
def GetManagedObjects(self):
response = {}
for service in self.services:
response[service.get_path()] = service.get_properties()
chrcs = service.get_characteristics()
for chrc in chrcs:
response[chrc.get_path()] = chrc.get_properties()
descs = chrc.get_descriptors()
for desc in descs:
response[desc.get_path()] = desc.get_properties()
return response
class Service(dbus.service.Object):
PATH_BASE = '/org/bluez/example/service'
def __init__(self, bus, index, uuid, primary):
self.path = self.PATH_BASE + str(index)
self.bus = bus
self.uuid = uuid
self.primary = primary
self.characteristics = []
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_SERVICE_IFACE: {
'UUID': self.uuid,
'Primary': self.primary,
'Characteristics': dbus.Array(
[c.get_path() for c in self.characteristics],
signature='o')
}
}
def get_path(self):
return dbus.ObjectPath(self.path)
def add_characteristic(self, characteristic):
self.characteristics.append(characteristic)
def get_characteristics(self):
return self.characteristics
@dbus.service.method(DBUS_PROP_IFACE, in_signature='s',
out_signature='a{sv}')
def GetAll(self, interface):
if interface != GATT_SERVICE_IFACE:
raise InvalidArgsException()
return self.get_properties()[GATT_SERVICE_IFACE]
class Characteristic(dbus.service.Object):
def __init__(self, bus, index, uuid, flags, service):
self.path = service.path + '/char' + str(index)
self.bus = bus
self.uuid = uuid
self.service = service
self.flags = flags
self.descriptors = []
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_CHAR_IFACE: {
'Service': self.service.get_path(),
'UUID': self.uuid,
'Flags': self.flags,
'Descriptors': dbus.Array(
[d.get_path() for d in self.descriptors],
signature='o')
}
}
def get_path(self):
return dbus.ObjectPath(self.path)
def add_descriptor(self, descriptor):
self.descriptors.append(descriptor)
def get_descriptors(self):
return self.descriptors
@dbus.service.method(DBUS_PROP_IFACE, in_signature='s',
out_signature='a{sv}')
def GetAll(self, interface):
if interface != GATT_CHAR_IFACE:
raise InvalidArgsException()
return self.get_properties()[GATT_CHAR_IFACE]
@dbus.service.signal(DBUS_PROP_IFACE, signature='sa{sv}as')
def PropertiesChanged(self, interface, changed, invalidated):
pass
class TemperatureService(Service):
"""Environmental Sensing Service — sıcaklık ölçümü."""
def __init__(self, bus, index):
Service.__init__(self, bus, index, ENV_SERVICE_UUID, True)
self.add_characteristic(TemperatureCharacteristic(bus, 0, self))
class TemperatureCharacteristic(Characteristic):
"""Sıcaklık karakteristiği — READ + NOTIFY."""
def __init__(self, bus, index, service):
Characteristic.__init__(
self, bus, index, TEMP_MEAS_UUID,
['read', 'notify'], service)
self.notifying = False
self.add_descriptor(CCCDescriptor(bus, 0, self))
def get_temperature(self):
"""Gerçek sistemde DS18B20 veya BME280'den oku."""
temp_c = 23.5 + random.uniform(-0.5, 0.5) # Simüle
# GATT Temperature 2A6E: int16, 0.01 °C çözünürlük
raw = int(temp_c * 100)
return struct.pack('<h', raw) # little-endian signed 16-bit
@dbus.service.method(GATT_CHAR_IFACE, in_signature='a{sv}',
out_signature='ay')
def ReadValue(self, options):
data = self.get_temperature()
print(f"Sıcaklık okuma: {struct.unpack('<h', data)[0] / 100:.2f} °C")
return dbus.Array(data, signature='y')
@dbus.service.method(GATT_CHAR_IFACE, in_signature='', out_signature='')
def StartNotify(self):
if self.notifying:
return
print("Notify başladı.")
self.notifying = True
GLib.timeout_add(2000, self._send_notification) # 2 saniyede bir
@dbus.service.method(GATT_CHAR_IFACE, in_signature='', out_signature='')
def StopNotify(self):
print("Notify durdu.")
self.notifying = False
def _send_notification(self):
if not self.notifying:
return False
data = self.get_temperature()
self.PropertiesChanged(
GATT_CHAR_IFACE,
{'Value': dbus.Array(data, signature='y')},
[])
return True # GLib timeout devam etsin
class CCCDescriptor(dbus.service.Object):
"""Client Characteristic Configuration Descriptor (CCCD) — 0x2902."""
def __init__(self, bus, index, characteristic):
self.path = characteristic.path + '/desc' + str(index)
self.bus = bus
self.characteristic = characteristic
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_DESC_IFACE: {
'Characteristic': self.characteristic.get_path(),
'UUID': CCCD_UUID,
'Flags': ['read', 'write'],
}
}
def get_path(self):
return dbus.ObjectPath(self.path)
@dbus.service.method(GATT_DESC_IFACE, in_signature='a{sv}',
out_signature='ay')
def ReadValue(self, options):
notify = self.characteristic.notifying
return dbus.Array([0x01 if notify else 0x00, 0x00], signature='y')
@dbus.service.method(GATT_DESC_IFACE, in_signature='aya{sv}',
out_signature='')
def WriteValue(self, value, options):
if value[0]:
self.characteristic.StartNotify()
else:
self.characteristic.StopNotify()
@dbus.service.method(DBUS_PROP_IFACE, in_signature='s',
out_signature='a{sv}')
def GetAll(self, interface):
return self.get_properties()[GATT_DESC_IFACE]
def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
# Adapter al
adapter_obj = bus.get_object(BLUEZ_SERVICE, '/org/bluez/hci0')
gatt_manager = dbus.Interface(adapter_obj, GATT_MANAGER_IFACE)
app = Application(bus)
mainloop = GLib.MainLoop()
def register_ok():
print("GATT uygulaması kaydedildi. BLE yayını başladı.")
def register_err(error):
print(f"GATT kaydı hatası: {error}")
mainloop.quit()
gatt_manager.RegisterApplication(
app.get_path(), {},
reply_handler=register_ok,
error_handler=register_err)
print("Bağlantı bekleniyor... (Ctrl+C ile durdur)")
try:
mainloop.run()
except KeyboardInterrupt:
gatt_manager.UnregisterApplication(app.get_path())
print("Durduruldu.")
if __name__ == '__main__':
main()
04 UUID tanımlama — SIG vs özel 128-bit
BLE UUID'leri iki türdür: Bluetooth SIG tarafından atanmış 16-bit UUID'ler ve üreticinin tanımladığı 128-bit özel UUID'ler.
SIG UUID'leri (16-bit)
# 16-bit kısa form
0x180A Device Information Service
0x180F Battery Service
0x1809 Health Thermometer Service
0x181A Environmental Sensing Service
0x2A29 Manufacturer Name String
0x2A24 Model Number String
0x2A19 Battery Level
0x2A6E Temperature (0.01 °C çözünürlük, int16)
0x2A6F Humidity (0.01 % çözünürlük, uint16)
0x2A75 Pollen Concentration
# 128-bit tam form (Base UUID ile)
# 0x0000XXXX-0000-1000-8000-00805F9B34FB
# Örnek: 0x180F →
0000180F-0000-1000-8000-00805F9B34FB
Özel 128-bit UUID tanımlama
# Rastgele UUID üret
uuidgen
# 12345678-1234-5678-1234-567812345678
# Python ile
python3 -c "import uuid; print(uuid.uuid4())"
# a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Özel bir IoT cihazı için UUID hiyerarşisi tanımla
# Ana servis UUID (vendor-specific)
CUSTOM_SERVICE_UUID = 'a1b2c3d4-0001-0000-0000-000000000001'
# Karakteristikler
SENSOR_READ_UUID = 'a1b2c3d4-0001-0000-0000-000000000002' # Oku
CONFIG_WRITE_UUID = 'a1b2c3d4-0001-0000-0000-000000000003' # Yaz
STATUS_NOTIFY_UUID = 'a1b2c3d4-0001-0000-0000-000000000004' # Bildirim
# UUID naming convention önerisi:
# [vendor_prefix]-[service_id]-[version]-[reserved]-[char_index]
# Bu şekilde çakışma olmadan genişletilebilir.
UUID'yi Wireshark'ta filtrele
# Bluetooth capture için
# sudo btmon -w /tmp/bt.log
# Wireshark'ta aç: btsnoop formatı
# Filtreler:
btatt.uuid16 == 0x2a6e # Sıcaklık karakteristiği
btatt.opcode == 0x12 # Handle Value Notification
btle.advertising_header.pdu_type == 0 # ADV_IND paketleri
05 Characteristic properties — READ, WRITE, NOTIFY
Her karakteristiğin hangi işlemlere izin verdiğini belirleyen özellikler (flags/properties).
Örnek flag kombinasyonları
# Salt okunur sensör verisi
flags = ['read']
# Yazılabilir konfigürasyon (ör: örnekleme aralığı)
flags = ['read', 'write']
# Hızlı akış verisi (titreşim, mikrofon)
flags = ['read', 'notify']
# Kritik komut (aktüatör kontrolü)
flags = ['write', 'indicate']
# Güvenli yönetim kanalı
flags = ['encrypt-read', 'encrypt-write']
# Üretim kalibrasyonu (fabrika erişimi)
flags = ['secure-read', 'secure-write']
06 Notify/Indicate mekanizması ve MTU
Notify, sensör verilerini sürekli polling'e gerek kalmadan push etmenin temel yoludur.
Notify akışı
Client Server (sensör)
│ │
│── ATT Write (CCCD = 01 00) ────►│ notify etkinleştir
│ │
│◄── ATT Handle Value Notif. ─────│ sunucu veri gönderir (her 2s)
│◄── ATT Handle Value Notif. ─────│
│◄── ATT Handle Value Notif. ─────│
│ │
│── ATT Write (CCCD = 00 00) ────►│ notify durdur
Indicate vs Notify
# NOTIFY: tek yönlü, onaysız (hızlı, düşük overhead)
# ATT Opcode: 0x1B (Handle Value Notification)
self.PropertiesChanged(
GATT_CHAR_IFACE,
{'Value': dbus.Array(data, signature='y')},
[])
# INDICATE: onaylı (güvenilir, yüksek overhead)
# ATT Opcode: 0x1D (Handle Value Indication) → client 0x1E (Confirmation) gönderir
# BlueZ D-Bus'ta indicate için flag 'indicate' kullanılır, aynı PropertiesChanged ile tetiklenir
# Konfirmasyon alınana kadar yeni indication gönderilemez
MTU (Maximum Transmission Unit)
# BLE varsayılan MTU: 23 bayt (3 bayt ATT header = 20 bayt payload)
# Modern cihazlar 256-517 bayt MTU destekler (BLE 4.2+ Data Length Extension)
# ReadValue() options içinde negotiated MTU gelir
@dbus.service.method(GATT_CHAR_IFACE, in_signature='a{sv}', out_signature='ay')
def ReadValue(self, options):
mtu = options.get('mtu', 23)
print(f"MTU: {mtu}")
# Büyük veri için MTU'ya göre parçala (BLE stack otomatik yapar)
data = self.get_large_data()
return dbus.Array(data[:mtu-3], signature='y')
# WriteValue() ile alınan offset (Long Write için)
@dbus.service.method(GATT_CHAR_IFACE, in_signature='aya{sv}', out_signature='')
def WriteValue(self, value, options):
offset = options.get('offset', 0)
data = bytes(value)
print(f"Yazılan: offset={offset}, {len(data)} bayt")
Notify veri hızı optimizasyonu
# Connection interval, notify throughput'u sınırlar
# Default: 7.5ms - 4000ms (tipik: 50ms)
# Yüksek throughput için: iw/hciconfig ile bağlantı param ayarla
# BlueZ ile connection parameters (sadece central role'da)
from pydbus import SystemBus
bus = SystemBus()
device = bus.get('org.bluez', '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_01')
# Peripheral olarak biz notify ediyoruz, central bağlantı parametrelerini kontrol eder
# GLib timeout aralığını bağlantı interval'a yakın seç
# 10ms interval → ~100 notify/saniye teorik maksimum
GLib.timeout_add(10, send_sensor_data)
07 C ile GATT client — libbluetooth
Düşük seviye C kodu ile BLE cihaza bağlanma ve GATT karakteristiği okuma. libbluetooth (bluez-dev paketi) gerektirir.
libbluetooth, BlueZ'in alt seviye C kütüphanesidir. Doğrudan HCI soketi açar. Yeni projeler için D-Bus API önerilir — ancak libbluetooth minimal ortamlarda (daemon olmadan) kullanışlıdır.
apt-get install libbluetooth-dev
gcc gatt_client.c -o gatt_client -lbluetooth
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/l2cap.h>
/* ATT opcodes */
#define ATT_OP_READ_REQ 0x0A
#define ATT_OP_READ_RSP 0x0B
#define ATT_OP_WRITE_CMD 0x52
/* Sıcaklık karakteristiği handle (önceden bilinmeli) */
#define TEMP_CHAR_HANDLE 0x0003
int att_connect(const char *addr) {
int sock;
struct sockaddr_l2 sa;
sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (sock < 0) {
perror("socket");
return -1;
}
memset(&sa, 0, sizeof(sa));
sa.l2_family = AF_BLUETOOTH;
sa.l2_psm = 0; /* ATT için PSM = 0 */
sa.l2_cid = htobs(4); /* ATT CID = 4 */
sa.l2_bdaddr_type = BDADDR_LE_PUBLIC;
str2ba(addr, &sa.l2_bdaddr);
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("connect");
close(sock);
return -1;
}
return sock;
}
int att_read(int sock, uint16_t handle, uint8_t *out, int maxlen) {
uint8_t req[3];
uint8_t rsp[256];
ssize_t len;
/* ATT Read Request */
req[0] = ATT_OP_READ_REQ;
req[1] = handle & 0xFF;
req[2] = (handle >> 8) & 0xFF;
if (write(sock, req, sizeof(req)) < 0) return -1;
len = read(sock, rsp, sizeof(rsp));
if (len < 1) return -1;
if (rsp[0] != ATT_OP_READ_RSP) {
fprintf(stderr, "Beklenmedik opcode: 0x%02x\n", rsp[0]);
return -1;
}
int data_len = len - 1;
if (data_len > maxlen) data_len = maxlen;
memcpy(out, rsp + 1, data_len);
return data_len;
}
int main(void) {
const char *addr = "AA:BB:CC:DD:EE:01";
int sock;
uint8_t data[32];
int len;
printf("BLE cihazına bağlanılıyor: %s\n", addr);
sock = att_connect(addr);
if (sock < 0) {
fprintf(stderr, "Bağlantı başarısız\n");
return 1;
}
printf("Bağlandı.\n");
/* Sıcaklık oku */
len = att_read(sock, TEMP_CHAR_HANDLE, data, sizeof(data));
if (len == 2) {
int16_t raw;
memcpy(&raw, data, 2);
printf("Sıcaklık: %.2f °C\n", raw / 100.0);
} else {
printf("Okunan veri: %d bayt\n", len);
}
close(sock);
return 0;
}
08 Embedded — BlueZ Yocto entegrasyonu ve minimal BLE advertiser
Yocto ile özel imaj oluşturma ve D-Bus/Python'a gerek duymayan minimal BLE advertiser scripti.
Yocto'ya BlueZ ekleme
# local.conf veya image recipe'ye ekle
IMAGE_INSTALL:append = " \
bluez5 \
bluez5-testtools \
python3-pydbus \
python3-pygobject \
kernel-module-bluetooth \
kernel-module-hci-uart \
kernel-module-btbcm \
kernel-module-rfkill \
"
# BlueZ kernel fragmenti
# meta-custom/recipes-kernel/linux/linux-%.bbappend:
SRC_URI += "file://bluetooth.cfg"
# bluetooth.cfg içeriği:
# CONFIG_BT=y
# CONFIG_BT_LE=y
# CONFIG_BT_RFCOMM=y
# CONFIG_BT_HCIUART=y
# CONFIG_BT_HCIUART_BCM=y (Raspberry Pi için)
Minimal BLE Advertiser scripti (btmgmt ile)
#!/bin/sh
# minimal_ble_advertiser.sh
# Python/D-Bus gerektirmez — btmgmt komut satırı aracı
# Adaptörü aç
btmgmt power on
# LE advertising etkinleştir
btmgmt le on
# Connectable advertising
btmgmt connectable on
# Advertising veri ayarla (Local Name: "IoT_Sensor")
btmgmt add-adv \
--adv-data 02010602094954535f53656e736f72 \
1
# 02 01 06 → Flags: LE General Discoverable, BR/EDR Not Supported
# 02 09 ... → Complete Local Name: bytes of "IoT_Sensor"
echo "BLE advertising başladı."
echo "hcidump -i hci0 -X ile izleyebilirsin"
hcitool / hciconfig ile çok düşük seviye advertiser
# HCI komutları ile BLE advertising (bluetoothd gerekmez!)
# LE set advertising parameters
hcitool -i hci0 cmd 0x08 0x0006 \
A0 00 A0 00 00 00 00 00 00 00 00 00 00 07 00
# LE set advertising data (max 31 bayt)
# Payload: 0x02 0x01 0x06 (Flags) + 0x0A 0x09 "IoTSensor"
hcitool -i hci0 cmd 0x08 0x0008 \
0F 02 01 06 0A 09 49 6F 54 53 65 6E 73 6F 72 \
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# LE set advertise enable
hcitool -i hci0 cmd 0x08 0x000A 01
echo "BLE advertiser aktif (bluetoothd olmadan)"
Buildroot konfigürasyonu
# BlueZ 5 userspace
BR2_PACKAGE_BLUEZ5_UTILS=y
BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y # bluetoothctl
BR2_PACKAGE_BLUEZ5_UTILS_MONITOR=y # btmon
BR2_PACKAGE_BLUEZ5_DAEMON=y # bluetoothd
# Python GATT için
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON_DBUS=y
BR2_PACKAGE_PYTHON_PYGOBJECT=y
# D-Bus gerekli
BR2_PACKAGE_DBUS=y
# Kernel: BT_HCIUART sürücüsü (UART Bluetooth için)
# Linux kernel .config'e ekle:
# CONFIG_BT_HCIUART=y
# CONFIG_BT_HCIUART_BCM=y (Broadcom/RPi)
# CONFIG_BT_HCIUART_LL=y (Texas Instruments)
GATT servisi geliştirirken ilk aşamada Raspberry Pi 4 üzerinde test et. Android'de "nRF Connect" uygulaması, iOS'ta "LightBlue" uygulaması ile karakteristikleri anlık izleyebilirsin. Stable olduktan sonra hedef gömülü platforma geç. btmon çıktısı ve Wireshark btsnoop formatı invaluable debug araçlarıdır.