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
- Keine Geräte gefunden: Führen Sie
bluetoothctl power onundrfkill listaus. 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. - 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
advim Callback aus, um die Rohfelder zu untersuchen. org.bluez.Error.InProgress: Ein anderer Prozess scannt bereits. Beenden Sie andere Scanner oder betreiben Sie nur eine Bridge pro Adapter.- Messwerte bleiben nach Stunden aus: BlueZ braucht auf älteren Kerneln manchmal einen Neustart. Den Betrieb der Bridge als systemd-Dienst mit
Restart=alwaysdeckt das ab, wie unter Python-MQTT-Skript beim Booten starten beschrieben. - 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.



