11.14. Zusammenfassung

Du hast Bluetooth Low Energy vom Funk bis hinauf zur Python-API durchlaufen, die zu seiner Ansteuerung verwendet wird:

  • Die Motivation – BLE ist die Antwort, wenn die Kamera mit etwas in der Nähe sprechen möchte, ohne jegliche Infrastruktur dazwischen. Ein Telefon im selben Raum, ein Wearable am Handgelenk, ein Beacon an einer Wand. Kurze Reichweite, kein Netzwerk, dem man beitreten muss, fast keine Leistung.

  • Der Funk – 2,4 GHz, 40 Kanäle: drei für Advertising, 37 für Verbindungsdaten, gehoppt nach einer pseudozufälligen Sequenz mit adaptiver Vermeidung verrauschter Kanäle. Kurze Pakete, größtenteils schlafende Funkmodule.

  • Die Link-Schicht – Paket-Framing, Adressierung, Verbindungs-Scheduling, Neuübertragung und Verschlüsselung auf Link-Ebene. Nichts davon wird von Python aus konfiguriert; alles davon zeigt sich in den Verbindungsparametern und der MTU.

  • Generic Access Profile (GAP) – Discovery und Verbindungsverwaltung. Vier Rollen: Peripheral und Broadcaster (senden), Central und Observer (scannen). Advertising-Payloads tragen den lokalen Namen, Service-UUIDs, Appearance und herstellerspezifische Daten – 31 Bytes plus eine optionale 31-Byte-Scan-Response. Das Verbindungsintervall, die Peripheral-Latenz und das Supervision-Timeout bestimmen, wie sich eine offene Verbindung anfühlt.

  • Generic Attribute Profile (GATT) – ein Baum aus Services, von denen jeder Charakteristiken hält, die jeweils optional Deskriptoren halten, identifiziert durch UUIDs (16-Bit für Bluetooth-SIG-Standards, 128-Bit für eigene). Fünf Operationen: read und write (ziehen, Client-initiiert), notify und indicate (schieben, Server-initiiert, abonniert über den Client Characteristic Configuration Descriptor). Die Payload-Größe ist durch die ausgehandelte MTU begrenzt.

  • Die Python-APIaioble verwandelt jedes BLE-Muster in eine asyncio-Coroutine. Ein Peripheral ist aioble.advertise(), das über Verbindungen schleift, mit Service / Characteristic-Objekten, die einmal erstellt und durch aioble.register_services() festgeschrieben werden. Ein Central ist aioble.scan(), um einen Peer zu finden, connect(), um die Verbindung zu öffnen, service() und characteristic(), um den entfernten GATT-Baum zu durchlaufen, und dann read() / write() / subscribe() / notified() für die eigentlichen Daten. Trennungen zeigen sich als aioble.DeviceDisconnectedError innerhalb der Coroutine, die gerade gewartet hat.

  • L2CAP-Kanäle – die Notluke für Massen-Byte-Streams, die nicht in GATTs Schlüssel/Wert-Modell passen. aioble.DeviceConnection.l2cap_accept() / l2cap_connect() öffnen einen anwendungsspezifischen Kanal auf Basis der GAP-Verbindung, mit kreditflussgesteuertem Send / Recv und einer größeren MTU, als GATT tragen kann.

  • Pairing und Verschlüsselung – BLE-Verbindungen sind standardmäßig öffentlich. aioble.DeviceConnection.pair() initiiert einen Schlüsselaustausch, der eine verschlüsselte Verbindung erzeugt; bond=True (der Standard) persistiert die Schlüssel, sodass nachfolgende Verbindungen den Handshake überspringen. Ohne mitm=True und eine nutzbare IO-Fähigkeit schützt die Verschlüsselung vor passiven Lauschern, aber nicht vor einer aktiven Umleitung während des ursprünglichen Pairings.

Das genügt, um Kamera-Anwendungen zu schreiben, die als Peripheral Status veröffentlichen, als Central Sensordaten lesen, Live-Werte über BLE an ein Telefon schieben, die Verbindung mit einem Pair-and-Bond-Schritt absichern und – für den seltenen Fall der Massenübertragung – von GATT in einen L2CAP-Kanal wechseln.

11.14.1. Fehlerbehebung

BLE-Fehler sind meist Diskrepanzen zwischen dem, was die beiden Seiten erwarten, und ein Inspector auf der Telefonseite ist der schnellste Weg, um zu sehen, wessen Erwartungen abweichen. Das Standardwerkzeug ist nRF Connect for Mobile (Nordic Semiconductor, kostenlos auf Android und iOS): es scannt, verbindet sich, durchläuft die GATT-Datenbank, liest und schreibt Charakteristiken und abonniert Notifications – so kann das Verhalten auf der Kameraseite isoliert getestet werden, ganz ohne eine Begleit-App zu schreiben.

Die häufigen Fehlermodi:

  • „Mein Gerät taucht im Scanner auf, lässt sich aber nicht verbinden.“ Meistens hat das Advertising-Paket connectable=False (Broadcaster-Modus), oder eine vorherige Verbindung ist noch offen und die Kamera ist bereits über aioble.advertise() hinaus. Füge print-Anweisungen rund um den advertise-Aufruf hinzu, um das zu bestätigen.

  • „exchange_mtu(512) lief, aber meine Notifications sind immer noch auf 20 Bytes begrenzt.“ Die ausgehandelte MTU ist min(local, peer) – das Telefon oder die Central-Bibliothek hat möglicherweise auf ihrer Seite keine größere MTU angefordert, in welchem Fall die Verbindung bei 23 bleibt. Inspiziere mtu, nachdem exchange_mtu() zurückkehrt. Beachte außerdem, dass exchange_mtu() nur einmal pro Verbindung funktioniert; rufe es vor der ersten großen Operation auf.

  • „Pairing schlägt mit einem generischen Fehler fehl.“ Zwei übliche Übeltäter: die IO-Fähigkeits-Diskrepanz (mitm=True für eine Kamera anfordern, die io=3 / keine Eingabe, keine Ausgabe deklariert – es gibt keine Möglichkeit, den numerischen Code zu bestätigen, also bricht die Pairing-Engine ab), und eine völlig falsche Echtzeituhr auf der Kamera, wenn der Peer sie benötigt. Stelle die Uhr mit ntptime.settime() vor dem ersten Pairing-Versuch.

  • „Notifications kommen nie beim Client an.“ Zwei Dinge sind zu prüfen, in dieser Reihenfolge: (a) wurde die Charakteristik mit notify=True deklariert? – das Eigenschaftsbit muss auf der Serverseite gesetzt sein; (b) hat der Client subscribe() aufgerufen? – ohne das Schreiben des Client Characteristic Configuration Descriptor (CCCD) wird dem Server mitgeteilt, dass kein Client Notifications möchte, und er verwirft sie stillschweigend.

  • „Der beworbene Name ist abgeschnitten oder fehlt.“ Die Advertising-Payload umfasst 31 Bytes, und die Felder Flags + Service-UUID + Appearance nehmen jeweils Bytes davon weg. Ein langer name= plus mehrere Service-UUIDs läuft über. Kürze entweder den Namen oder verwende aktives Scannen, damit die Scan-Response (weitere 31 Bytes) den Überlauf trägt. nRF Connect zeigt beide Hälften separat an, was die Aufteilung offensichtlich macht.

  • „L2CAP connect löst sofort eine Ausnahme aus.“ Üblicherweise eine PSM-Diskrepanz – beide Seiten müssen sich Out-of-Band auf dieselbe PSM-Nummer einigen. Ein L2CAPConnectionError trägt den Bluetooth-Statuscode als erstes Argument; Status 2 („PSM not supported“) ist das verräterische Zeichen.

  • „Gebondete Verbindungen lösen bei jedem Wiederverbinden immer noch einen vollständigen Pairing-Handshake aus.“ aioble.security.load_secrets() wurde beim Start nicht aufgerufen. Ohne ihn liegen die gespeicherten Schlüssel zwar im Flash, werden aber nie in den Speicher geladen, sodass die Identität des Peers unbekannt ist und das Pairing jedes Mal von Grund auf läuft.

Wenn alles andere fehlschlägt, stellt das Low-Level-Modul bluetooth einen IRQ-Callback bereit, der für jedes zugrunde liegende Ereignis ausgelöst wird; ihn kurz zu abonnieren und die Ereignisse auszugeben ist das Äquivalent eines Wireshark-Traces für die Kameraseite.

11.14.2. Diese Referenz später nutzen

Behandle die Bluetooth-Kapitel als Referenzmaterial; für das genaue Layout der Advertising-Payload eines Peripherals oder den Central-Scan-and-Subscribe-Ablauf zurückzukommen, ist der vorgesehene Verwendungszweck. Die Referenzseiten aioble — Async BLE und bluetooth — Low-Level-Bluetooth listen jede Methode, jedes Flag und jede Konstante an einem Ort auf, wenn die Frage nur lautet „wie heißt dieser Aufruf genau“.