Raspberry Pi

Raspberry Pi BLE zu MQTT: Bridge mit Python und Bleak

Nutzen Sie das integrierte Bluetooth des Raspberry Pi, die Bibliothek bleak und paho-mqtt, um BLE-Advertisements von Sensoren zu scannen, Temperatur und Luftfeuchte zu dekodieren und gedrosselte Messwerte per MQTT zu veröffentlichen.

Raspberry Pi empfängt Bluetooth-Signale von kleinen Sensoren als Illustration einer Raspberry Pi BLE-zu-MQTT-Bridge
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

Eine Raspberry Pi BLE-zu-MQTT-Bridge macht aus günstigen, batteriebetriebenen Bluetooth-Sensoren vernetzte Geräte, deren Werte Sie visualisieren, überwachen und automatisieren können. In diesem Tutorial nutzen Sie das integrierte Bluetooth-Funkmodul des Pi, die Bibliothek bleak und paho-mqtt, um Sensor-Advertisements zu empfangen, Temperatur, Luftfeuchte und Batteriewerte zu dekodieren und sie in sinnvollem Takt an einen MQTT-Broker zu veröffentlichen. Sie testen zuerst gegen broker.connectmq.com und wechseln dann auf private Zugangsdaten.

Warum BLE zu MQTT überbrücken

Bluetooth-Low-Energy-Sensoren sind klein, preiswert und laufen ein Jahr oder länger mit einer Knopfzelle, aber ihre Reichweite beträgt nur ein bis zwei Räume, und sie erreichen das Internet nicht allein. Ein Raspberry Pi mit integriertem Funkmodul (ab Pi 3B sowie Zero W und Zero 2 W) kann mitten im Haus, im Gewächshaus oder im Kühlraum stehen, jeden Sensor in der Nähe empfangen und die Daten über WLAN oder Ethernet weiterleiten.

Die meisten Sensoren senden ihre Messwerte in Advertisement-Paketen. Das ist praktisch: Die Bridge muss sich nie mit dem Sensor verbinden, sodass Dutzende Geräte passiv ausgelesen werden können, ohne ihre Batterien zu belasten. Verbindungsbasiertes Auslesen (GATT) ist mit bleak weiterhin möglich, aber langsamer, verbraucht mehr Strom auf dem Sensor und begrenzt, wie viele Geräte Sie gleichzeitig bedienen können. Dieser Leitfaden konzentriert sich auf Advertisements, mit einem Hinweis zu GATT am Ende.

Was Sie benötigen

Komponente Hinweise
Raspberry Pi mit Bluetooth Pi 3B, 4, 5, Zero W oder Zero 2 W; ein USB-BLE-Dongle funktioniert ebenfalls
Raspberry Pi OS Bookworm BlueZ ist standardmäßig installiert
BLE-Sensoren Dieses Beispiel dekodiert Xiaomi-LYWSD03MMC-Thermometer mit der verbreiteten Open-Source-Custom-Firmware
Python 3.11+ Wird mit Bookworm ausgeliefert

Die Dekodierlogik ist gerätespezifisch, die Struktur der Bridge nicht. Haben Sie einen anderen Sensor, ändern Sie nur eine Funktion.

bleak und paho-mqtt installieren

bleak nutzt unter Linux BlueZ über D-Bus, daher sind bei einer Standardinstallation weder Root-Rechte noch zusätzliche Systempakete nötig. Prüfen Sie, ob das Funkmodul aktiv ist, und legen Sie eine virtuelle Umgebung an:

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"

Ein kurzer Scan bestätigt, dass bleak Geräte sieht:

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

Advertisements dekodieren

Die Custom-Firmware für LYWSD03MMC-Sensoren sendet Messwerte als Service Data unter der Environmental-Sensing-UUID 0x181A. Zwei Layouts sind verbreitet, und Sie unterscheiden sie an der Länge:

  • pvvx-Format, 15 Byte, Little-Endian: MAC (6 Byte, umgekehrt), Temperatur als int16 in 0,01 Grad C, Luftfeuchte als uint16 in 0,01 Prozent, Batteriespannung in Millivolt als uint16, Batterie in Prozent, ein Frame-Zähler und Flags.
  • ATC1441-Format, 13 Byte, Big-Endian: MAC (6 Byte), Temperatur als int16 in 0,1 Grad C, Luftfeuchte in Prozent, Batterie in Prozent, Batteriespannung in Millivolt als uint16 und ein Zähler.

Die Dekodierung mit struct ist kurz und testbar. Für andere Geräte sehen Sie sich im Advertisement das Dictionary manufacturer_data (Schlüssel: Company ID) oder service_data (Schlüssel: UUID-String) an und wenden das dokumentierte Format des Herstellers an.

Das Skript für die Raspberry Pi BLE-zu-MQTT-Bridge

Das Skript betreibt einen kontinuierlichen Scanner mit Detection-Callback. Jedes Advertisement eines bekannten Sensors wird dekodiert und gespeichert; ein separater async-Task veröffentlicht den jeweils neuesten Messwert jedes Sensors höchstens einmal alle 30 Sekunden. paho-mqtt betreibt über loop_start() einen eigenen Netzwerk-Thread und verträgt sich so problemlos mit asyncio.

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  # Sekunden pro Sensor

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


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())

Abonnieren Sie auf Ihrem Laptop, um die eintreffenden Messwerte zu verfolgen:

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

Jeder Sensor erhält ein eigenes Topic, benannt nach seiner MAC-Adresse, und der Messwert wird als Retained Message gespeichert, sodass ein später verbundenes Dashboard sofort den neuesten Wert sieht. Wenn Sie sprechende Namen bevorzugen, pflegen Sie ein kleines Dictionary, das MAC-Adressen Raumnamen zuordnet, und verwenden Sie diese im Topic.

Designentscheidungen, auf die es ankommt

Pro Gerät drosseln. Sensoren senden womöglich alle paar Sekunden. Jedes Advertisement zu veröffentlichen verschwendet Bandbreite und stößt auf dem kostenlosen öffentlichen MQTT-Broker bei vielen Sensoren schnell an das Limit von 10 Nachrichten pro Sekunde und Client. Ein 30-Sekunden-Fenster pro Sensor reicht für das Raumklima völlig aus.

Den aktuellen Zustand behalten, nicht die Historie. Retained Messages liefern neuen Abonnenten sofort den aktuellen Wert. Die Historie gehört in eine Datenbank: Ein Subscriber, der in SQLite oder InfluxDB schreibt – etwa wie im Leitfaden zu Grafana und InfluxDB auf dem Raspberry Pi –, ist dafür der richtige Ort.

RSSI mitsenden. Die Signalstärke zeigt, wann ein Sensor kurz davor ist, aus der Reichweite zu fallen, oder wann die Bridge versetzt werden muss. Sie ist außerdem ein günstiger Weg, einen sterbenden Sensor zu erkennen, bevor sein Batteriewert sinkt.

Auf Funkstille achten. Ein Sensor, der keine Advertisements mehr sendet, ist für die Bridge unsichtbar. Zeigen Sie das Feld ts in Dashboards an oder alarmieren Sie, wenn es zu alt wird. Die Schwellwert-Alarmregeln von ConnectMQ arbeiten mit numerischen Werten, daher liefert ein Subscriber, der pro Sensor „Sekunden seit dem letzten Messwert“ veröffentlicht, einen einfachen Alarm für veraltete Daten.

Fehlerbehebung

  1. Keine Geräte gefunden: Führen Sie bluetoothctl power on und rfkill list aus. Auf einem Pi, bei dem sich WLAN und Bluetooth einen Chip teilen, kann starker 2,4-GHz-WLAN-Verkehr den BLE-Empfang verschlechtern; Ethernet oder 5-GHz-WLAN hilft.
  2. Geräte gefunden, aber keine Service Data: Der Sensor läuft mit der Originalfirmware, die ihre Advertisements verschlüsselt, oder nutzt ein anderes Format. Geben Sie adv im Callback aus, um die Rohfelder zu untersuchen.
  3. org.bluez.Error.InProgress: Ein anderer Prozess scannt bereits. Beenden Sie andere Scanner oder betreiben Sie nur eine Bridge pro Adapter.
  4. Messwerte bleiben nach Stunden aus: BlueZ braucht auf älteren Kerneln manchmal einen Neustart. Den Betrieb der Bridge als systemd-Dienst mit Restart=always deckt das ab, wie unter Python-MQTT-Skript beim Booten starten beschrieben.
  5. Schwache Reichweite: Ein USB-Verlängerungskabel, das den Dongle vom Metallgehäuse des Pi und von den USB-3-Ports entfernt, kann einen spürbaren Unterschied machen, da USB 3 bekanntermaßen 2,4-GHz-Störungen verursacht.

GATT-Characteristics auslesen

Manche Geräte stellen Daten nur über eine Verbindung bereit. bleak erledigt das mit BleakClient:

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]

Verbindungen sind aufwendig, lesen Sie GATT-Werte daher in einem langsamen Takt und pausieren Sie den Scan während des Verbindens, falls Ihr Adapter mit beidem gleichzeitig überfordert ist. Die bleak-Dokumentation behandelt Notifications, den effizienten Weg, Daten über eine Verbindung zu streamen.

Der Weg in den Produktivbetrieb

Wenn die Bridge funktioniert, verlassen Sie den öffentlichen MQTT-Broker, der für jeden lesbar ist. Erstellen Sie einen privaten Broker bei ConnectMQ, setzen Sie MQTT_HOST, MQTT_PORT=8883, MQTT_USERNAME, MQTT_PASSWORD und MQTT_CLIENT_ID mit den Werten aus dem Portal und ändern Sie das Topic-Präfix auf etwas wie home/ble. Die Bridge braucht keine eingehenden Ports, nur eine ausgehende TLS-Verbindung, und funktioniert daher hinter jedem Heimrouter oder Mobilfunkmodem. Mit einem verwalteten MQTT-Broker gibt es außerdem keinen Broker-Prozess auf dem Pi, den Sie pflegen müssten.

Wenn MQTT auf dem Pi neu für Sie ist, behandelt das Raspberry Pi MQTT Tutorial die Grundlagen, und der Artikel zum Raspberry Pi als IoT-Gateway zeigt, wie Sie BLE mit anderen Protokollen auf einem Gerät kombinieren.

Verbinden Sie Ihre BLE-Sensoren mit ConnectMQ

Registrieren Sie sich unter app.connectmq.com für den kostenlosen Tarif Serverless (Free) ohne Kreditkarte, und Sie haben in wenigen Minuten Broker-Zugangsdaten. Richten Sie die Bridge darauf aus und öffnen Sie den Live-Topic-Explorer, um jeden Sensor unter seinem eigenen Topic erscheinen zu sehen.

Häufig gestellte Fragen

Wie viele BLE-Sensoren kann ein Raspberry Pi verarbeiten?

Beim passiven Scannen von Advertisements sind Dutzende Sensoren in Funkreichweite üblich, da der Pi nur zuhört. Reichweite und Wände setzen meist früher Grenzen als die CPU.

Muss ich die Sensoren koppeln?

Nein. Advertisement-basierte Sensoren senden an jeden, der zuhört, daher ist weder Kopplung noch Verbindung nötig. Verschlüsselte Advertisements sind die Ausnahme und erfordern den Schlüssel des Geräts.

Kann ich das auf einem Raspberry Pi Zero 2 W betreiben?

Ja. Die Bridge braucht wenig CPU und Speicher, und der Zero 2 W hat integriertes Bluetooth und WLAN – damit ist er eine gute, kostengünstige Bridge pro Raum oder Gebäude.

Sollte ich für Sensorwerte QoS 0 oder QoS 1 verwenden?

QoS 1 ist ein guter Standard für Messwerte, die mindestens einmal ankommen sollen. Bei sehr häufigen Werten, bei denen der nächste Messwert den letzten ersetzt, ist QoS 0 in Ordnung.

Verwandte Artikel

Raspberry Pi RFID MQTT: Zutrittsprotokollierung mit dem RC522-Leser

Schließen Sie einen RC522-RFID-Leser an einen Raspberry Pi an, lesen Sie Tags mit Python, prüfen Sie sie gegen eine lokale Allowlist und veröffentlichen Sie jeden Zutrittsversuch als MQTT-Ereignis, das Logger, Dashboards und Alarmwerkzeuge verarbeiten können.

7 Min. Lesezeit

Pico W vs. Pico 2 W für MQTT: RP2040 vs. RP2350 bei Leistung, Speicher und TLS

Der Pico 2 W behält Formfaktor und WLAN-Chip des Pico W, ersetzt aber den RP2040 durch den schnelleren RP2350. Dieser Vergleich zeigt, was das für MQTT-Geräte bedeutet: CPU, Speicher, TLS, Sicherheitsfunktionen, Softwareunterstützung und welches Board für welches Projekt.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.