Raspberry Pi

Python ve Bleak ile Raspberry Pi BLE'den MQTT'ye Köprü

Raspberry Pi'nin dahili Bluetooth'unu, bleak kütüphanesini ve paho-mqtt'yi kullanarak BLE sensör yayın paketlerini tarayın, sıcaklık ve nemi çözün ve hız sınırlı ölçümleri MQTT'ye yayınlayın.

Küçük sensörlerden Bluetooth sinyalleri alan Raspberry Pi; Raspberry Pi BLE'den MQTT'ye köprüyü gösteren illüstrasyon
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Bir Raspberry Pi BLE MQTT köprüsü, ucuz ve pille çalışan Bluetooth sensörlerini grafiğe dökebileceğiniz, alarm kurabileceğiniz ve otomasyona bağlayabileceğiniz ağa bağlı cihazlara dönüştürür. Bu eğitimde Pi'nin dahili Bluetooth radyosunu, bleak kütüphanesini ve paho-mqtt'yi kullanarak sensör yayın paketlerini (advertisement) dinleyecek, sıcaklık, nem ve pil değerlerini çözecek ve bunları makul bir hızda bir MQTT broker'a yayınlayacaksınız. Önce broker.connectmq.com üzerinde test edecek, ardından özel kimlik bilgilerine geçeceksiniz.

BLE'yi Neden MQTT'ye Köprülemeli?

Bluetooth Low Energy sensörleri küçük, ucuzdur ve bir düğme pille bir yıl veya daha uzun süre çalışır; ancak menzilleri bir iki odadır ve internete kendi başlarına ulaşamazlar. Dahili radyoya sahip bir Raspberry Pi (Pi 3B ve sonrası, Zero W ve Zero 2 W) bir evin, seranın veya soğuk odanın ortasına yerleştirilip yakındaki her sensörü duyabilir ve verileri Wi-Fi veya Ethernet üzerinden iletebilir.

Çoğu sensör ölçümlerini yayın paketlerinde (advertisement) duyurur. Bu kullanışlıdır: köprünün sensöre hiçbir zaman bağlanması gerekmez, böylece onlarca cihaz pillerini tüketmeden pasif olarak okunabilir. Bağlantı tabanlı (GATT) okuma bleak ile hâlâ mümkündür, ancak daha yavaştır, sensörde daha fazla güç tüketir ve aynı anda kaç cihazı yönetebileceğinizi sınırlar. Bu rehber yayın paketlerine odaklanıyor; sonda GATT hakkında bir not var.

İhtiyacınız Olanlar

Öğe Notlar
Bluetooth'lu Raspberry Pi Pi 3B, 4, 5, Zero W veya Zero 2 W; bir USB BLE dongle da çalışır
Raspberry Pi OS Bookworm BlueZ varsayılan olarak kuruludur
BLE sensörleri Bu örnek, popüler açık kaynaklı özel firmware çalıştıran Xiaomi LYWSD03MMC termometrelerini çözer
Python 3.11+ Bookworm ile birlikte gelir

Çözme mantığı cihaza özgüdür, ancak köprü yapısı değildir. Farklı bir sensörünüz varsa yalnızca bir fonksiyonu değiştirirsiniz.

bleak ve paho-mqtt Kurulumu

bleak, Linux'ta BlueZ'yi D-Bus üzerinden kullanır; bu nedenle standart bir kurulumda root erişimi veya ek sistem paketleri gerekmez. Radyonun açık olduğunu kontrol edin, ardından bir sanal ortam oluşturun:

bluetoothctl show | grep -E "Powered|Name"
sudo rfkill unblock bluetooth
python3 -m venv ~/ble-bridge/.venv
source ~/ble-bridge/.venv/bin/activate
pip install bleak "paho-mqtt>=2.0"

Hızlı bir tarama, bleak'in cihazları görebildiğini doğrular:

python -c "import asyncio; from bleak import BleakScanner; print(*asyncio.run(BleakScanner.discover(timeout=8)), sep='\n')"

Yayın Paketlerini Çözmek

LYWSD03MMC sensörleri için özel firmware, ölçümleri Environmental Sensing UUID'si 0x181A altında service data olarak yayınlar. İki düzen yaygındır ve bunları uzunluklarından ayırt edebilirsiniz:

  • pvvx formatı, 15 bayt, little-endian: MAC (6 bayt, ters), 0,01 °C biriminde int16 sıcaklık, yüzde 0,01 biriminde uint16 nem, uint16 olarak pil milivoltu, pil yüzdesi, bir çerçeve sayacı ve bayraklar.
  • ATC1441 formatı, 13 bayt, big-endian: MAC (6 bayt), 0,1 °C biriminde int16 sıcaklık, nem yüzdesi, pil yüzdesi, uint16 olarak pil milivoltu ve bir sayaç.

Bunları struct ile çözmek kısa ve test edilebilirdir. Diğer cihazlar için yayın paketindeki manufacturer_data sözlüğüne (şirket kimliğine göre anahtarlanmış) veya service_data sözlüğüne (UUID dizesine göre anahtarlanmış) bakın ve üreticinin belgelediği formatı uygulayın.

Raspberry Pi BLE MQTT Köprüsü Betiği

Betik, bir algılama callback'iyle sürekli çalışan bir tarayıcı çalıştırır. Bilinen bir sensörden gelen her yayın paketi çözülüp saklanır; ayrı bir async görev her sensörün en son ölçümünü en fazla 30 saniyede bir yayınlar. paho-mqtt, loop_start() ile kendi ağ iş parçacığını çalıştırır; böylece asyncio ile sorunsuz bir arada çalışır.

import asyncio
import json
import os
import struct
import time
import uuid

import paho.mqtt.client as mqtt
from bleak import BleakScanner

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"ble-bridge-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/b4n8r2/ble")
PUBLISH_EVERY = 30  # seconds per sensor

ESS_UUID = "0000181a-0000-1000-8000-00805f9b34fb"
latest = {}        # mac -> reading
last_sent = {}     # mac -> timestamp


def decode_181a(data: bytes):
    if len(data) == 15:  # pvvx custom format
        temp, hum, mv, batt = struct.unpack_from("<hHHB", data, 6)
        return {"temperature": temp / 100, "humidity": hum / 100,
                "battery_mv": mv, "battery": batt}
    if len(data) == 13:  # ATC1441 format
        temp, hum, batt, mv = struct.unpack_from(">hBBH", data, 6)
        return {"temperature": temp / 10, "humidity": hum,
                "battery_mv": mv, "battery": batt}
    return None


def on_advertisement(device, adv):
    data = adv.service_data.get(ESS_UUID)
    if not data:
        return
    reading = decode_181a(data)
    if reading:
        reading["rssi"] = adv.rssi
        reading["ts"] = int(time.time())
        latest[device.address.replace(":", "").lower()] = reading


def on_connect(client, userdata, flags, reason_code, properties):
    print("MQTT connected:", reason_code)
    client.publish(f"{PREFIX}/bridge/status", "online", qos=1, retain=True)


def make_client():
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
    if USERNAME:
        c.username_pw_set(USERNAME, PASSWORD)
    if PORT == 8883:
        c.tls_set()
    c.will_set(f"{PREFIX}/bridge/status", "offline", qos=1, retain=True)
    c.reconnect_delay_set(1, 60)
    c.on_connect = on_connect
    c.connect_async(BROKER, PORT, keepalive=60)
    c.loop_start()
    return c


async def publisher(client):
    while True:
        now = time.time()
        for mac, reading in list(latest.items()):
            if now - last_sent.get(mac, 0) >= PUBLISH_EVERY and client.is_connected():
                client.publish(f"{PREFIX}/{mac}", json.dumps(reading), qos=1, retain=True)
                last_sent[mac] = now
        await asyncio.sleep(1)


async def main():
    client = make_client()
    scanner = BleakScanner(detection_callback=on_advertisement)
    await scanner.start()
    print("scanning...")
    try:
        await publisher(client)
    finally:
        await scanner.stop()
        client.publish(f"{PREFIX}/bridge/status", "offline", qos=1, retain=True)
        client.loop_stop()
        client.disconnect()


if __name__ == "__main__":
    asyncio.run(main())

Gelen ölçümleri izlemek için dizüstü bilgisayarınızdan abone olun:

mosquitto_sub -h broker.connectmq.com -p 1883 -i ble-viewer-b4n8r2 -t "connectmq/demo/b4n8r2/ble/#" -v

Her sensör MAC adresiyle adlandırılmış kendi topic'ine sahip olur ve ölçüm retained olarak tutulur; böylece daha sonra bağlanan bir pano en son değeri hemen görür. Anlaşılır adlar tercih ediyorsanız MAC adreslerini oda adlarına eşleyen küçük bir sözlük tutun ve topic'te bunları kullanın.

Önemli Tasarım Tercihleri

Cihaz başına hız sınırı koyun. Sensörler birkaç saniyede bir yayın yapabilir. Her birini yayınlamak bant genişliğini boşa harcar ve çok sayıda sensörünüz olduğunda ücretsiz genel MQTT broker üzerinde istemci başına saniyede 10 mesaj sınırına hızla ulaşır. Oda iklimi için sensör başına 30 saniyelik bir pencere fazlasıyla yeterlidir.

Geçmişi değil, en son durumu retained tutun. Retained mesajlar yeni abonelere güncel değeri anında verir. Geçmiş bir veritabanına aittir: Raspberry Pi Grafana ve InfluxDB rehberindeki gibi SQLite veya InfluxDB'ye yazan bir abone bunun için doğru yerdir.

RSSI ekleyin. Sinyal gücü, bir sensörün menzil dışına çıkmak üzere olduğunu veya köprünün yerinin değiştirilmesi gerektiğini söyler. Ayrıca pil değeri düşmeden önce ölmekte olan bir sensörü tespit etmenin ucuz bir yoludur.

Sessizliğe dikkat edin. Yayın yapmayı bırakan bir sensör köprü için görünmez olur. Panolara ts alanını ekleyin veya bu değer eskidiğinde alarm verin. ConnectMQ eşik alarm kuralları sayısal değerler üzerinde çalışır; bu yüzden sensör başına "son ölçümden bu yana geçen saniye" yayınlayan bir abone size basit bir bayatlık alarmı sağlar.

Sorun Giderme

  1. Hiç cihaz bulunamıyor: bluetoothctl power on ve rfkill list komutlarını çalıştırın. Wi-Fi ve Bluetooth'un tek bir çipi paylaştığı bir Pi'de yoğun 2,4 GHz Wi-Fi trafiği BLE alımını azaltabilir; Ethernet veya 5 GHz Wi-Fi yardımcı olur.
  2. Cihazlar bulunuyor ama service data yok: sensör, yayın paketlerini şifreleyen fabrika firmware'ini çalıştırıyor veya farklı bir format kullanıyor. Ham alanları incelemek için callback içinde adv değerini yazdırın.
  3. org.bluez.Error.InProgress: başka bir süreç zaten tarama yapıyor. Diğer tarayıcıları durdurun veya adaptör başına yalnızca bir köprü çalıştırın.
  4. Ölçümler saatler sonra duruyor: BlueZ eski çekirdeklerde bazen yeniden başlatma gerektirir. Açılışta Python MQTT betiği çalıştırma yazısında anlatıldığı gibi köprüyü Restart=always ile bir systemd servisi olarak çalıştırmak bu durumu karşılar.
  5. Zayıf menzil: Bir dongle'ı Pi'nin metal kasasından ve USB 3 portlarından uzaklaştırmak için bir USB uzatma kablosu belirgin fark yaratabilir; çünkü USB 3'ün 2,4 GHz paraziti oluşturduğu bilinmektedir.

GATT Karakteristiklerini Okumak

Bazı cihazlar verileri yalnızca bir bağlantı üzerinden sunar. bleak bunu BleakClient ile yönetir:

from bleak import BleakClient

async def read_battery(address):
    async with BleakClient(address) as client:
        value = await client.read_gatt_char("00002a19-0000-1000-8000-00805f9b34fb")
        return value[0]

Bağlantılar maliyetlidir; bu yüzden GATT değerlerini yavaş bir takvimle okuyun ve adaptörünüz ikisini aynı anda yapmakta zorlanıyorsa bağlanırken taramayı durdurun. bleak dokümantasyonu, bağlı verileri aktarmanın verimli yolu olan bildirimleri (notification) ele alıyor.

Üretime Geçiş

Köprü çalıştığında, herkes tarafından okunabilen genel broker'dan ayrılın. ConnectMQ üzerinde özel bir broker oluşturun, portaldan aldığınız değerlerle MQTT_HOST, MQTT_PORT=8883, MQTT_USERNAME, MQTT_PASSWORD ve MQTT_CLIENT_ID değişkenlerini ayarlayın ve topic önekini home/ble gibi bir şeyle değiştirin. Köprünün gelen bağlantı portlarına ihtiyacı yoktur, yalnızca giden bir TLS bağlantısı gerekir; bu yüzden herhangi bir ev yönlendiricisinin veya hücresel modemin arkasında çalışır. Yönetilen bir MQTT broker ayrıca Pi üzerinde bakımını yapmanız gereken bir broker süreci olmaması anlamına gelir.

Pi'de MQTT'ye yeniyseniz Raspberry Pi MQTT eğitimi temelleri ele alıyor; Raspberry Pi IoT ağ geçidi yazısı ise BLE'yi tek bir cihazda diğer protokollerle nasıl birleştireceğinizi gösteriyor.

BLE Sensörlerinizi ConnectMQ'ya Bağlayın

app.connectmq.com adresinden kredi kartı gerekmeden ücretsiz Serverless plana kaydolun; birkaç dakika içinde broker kimlik bilgilerinize sahip olursunuz. Köprüyü bunlara yönlendirin ve her sensörün kendi topic'i altında belirdiğini görmek için canlı topic gezginini açın.

Sık Sorulan Sorular

Bir Raspberry Pi kaç BLE sensörünü yönetebilir?

Pasif yayın paketi taramasında Pi yalnızca dinlediği için radyo menzili içindeki onlarca sensör olağandır. Genellikle işlemciden önce menzil ve duvarlar sizi sınırlar.

Sensörleri eşleştirmem gerekiyor mu?

Hayır. Yayın paketi tabanlı sensörler dinleyen herkese yayın yapar; bu yüzden eşleştirme veya bağlantı gerekmez. İstisna, cihazın anahtarını gerektiren şifreli yayın paketleridir.

Bunu bir Raspberry Pi Zero 2 W'de çalıştırabilir miyim?

Evet. Köprü az işlemci ve bellek kullanır; Zero 2 W'de dahili Bluetooth ve Wi-Fi bulunur, bu da onu oda veya bina başına düşük maliyetli iyi bir köprü yapar.

Sensör ölçümleri için QoS 0 mı yoksa QoS 1 mi kullanmalıyım?

En az bir kez teslim edilmesini istediğiniz ölçümler için QoS 1 iyi bir varsayılandır. Bir sonraki ölçümün öncekinin yerini aldığı çok sık değerler için QoS 0 yeterlidir.

İlgili yazılar

RC522 Okuyucu ile Raspberry Pi RFID MQTT Erişim Kaydı

Bir RC522 RFID okuyucuyu Raspberry Pi'ye bağlayın, etiketleri Python ile okuyun, yerel bir izin listesiyle karşılaştırın ve her erişim denemesini kayıt araçlarının, panellerin ve uyarı araçlarının tüketebileceği bir MQTT olayı olarak yayınlayın.

6 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.