Zum Inhalt springen
>_marc-oltersdorf.de

Android BLE: Gerät schaltet, aber Statusabfrage bleibt stumm – requestMtu fehlt

Marc Oltersdorf 2 Min. Lesezeit

Meine Android-App sprach ein BLE-Gerät über den Nordic-UART-Dienst an: Licht an und aus klappte, die Statusabfrage blieb ohne Antwort. Im Browser lief dieselbe Abfrage sofort. Der Unterschied: Chrome handelt eine größere MTU aus, Android-Apps müssen requestMtu() selbst aufrufen.

$ git log --graph Der Weg zur Lösung

  1. Problem Statusabfrage bleibt stumm

    Schaltbefehle gehen, die Abfrage 0x0E bekommt in der eigenen Android-App keine Antwort.

  2. Irrweg Watchdog

    Notifications neu abonnieren und neu verbinden änderte nichts.

  3. Erkenntnis Im Browser geht es

    Dort kommt die Antwort mit rund 160 Byte als ein Paket, Chrome hat die MTU selbst ausgehandelt.

  4. Lösung requestMtu aufrufen

    requestMtu(247) nach dem Verbinden, discoverServices erst in onMtuChanged. Ausgehandelt wurden 498, der Status kommt.

Ich steuere die Lichtanlage meines Fahrrads (Canyon ECLIPS) über eine eigene Android-App. Das Gerät nutzt den Nordic-UART-Dienst: Service 6E400001-…, Schreiben auf 6E400002, Antworten als Notification auf 6E400003. Schaltbefehle (ein Byte, zum Beispiel 0x03 für „vorne an“) funktionierten, das Gerät bestätigte mit der kurzen Notification „Event received“. Die Statusabfrage 0x0E blieb dagegen einfach unbeantwortet. Keine Notification, kein Fehler, kein Hinweis im Log.

Das Problem

Dieselbe Abfrage funktionierte im Browser per Web Bluetooth sofort. Dort sah man auch den Grund: Die Statusantwort ist rund 160 Byte lang und kommt als ein Paket. Bei der Standard-MTU von 23 Byte bleiben 20 Byte Nutzdaten, und da passt sie nicht hinein. Das Gerät schickt dann offenbar gar nichts. Die Bestätigung auf Schaltbefehle hat nur 19 Byte und passt gerade noch. Deshalb sah alles andere gesund aus, und ich suchte zuerst an der falschen Stelle.

Die Lösung

Nach dem Verbinden zuerst die MTU anfordern und erst im Callback die Dienste suchen:

override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
    if (newState == BluetoothProfile.STATE_CONNECTED) {
        // Die Statusantwort passt nicht in 20-Byte-Pakete: ohne groessere MTU schweigt das Geraet
        if (!g.requestMtu(247)) g.discoverServices()
    }
}

override fun onMtuChanged(g: BluetoothGatt, mtu: Int, status: Int) {
    note("MTU $mtu (Status $status)")
    g.discoverServices()
}

Im Protokoll der App steht danach:

MTU 498 (Status 0)
ECLIPS verbunden

Und die Statusantwort kommt vollständig an: Ladestand, Spannung, Strom, Licht, Laden, Tempo, Firmware.

Warum das funktioniert

Chrome handelt bei Web Bluetooth die MTU selbst aus. Android-Apps bleiben bei 23 Byte, bis sie requestMtu() aufrufen. Das ist im Android-Handbuch dokumentiert, fällt aber nicht auf, solange alle Antworten kurz sind. Dieses Gerät zerlegt lange Antworten nicht in mehrere Notifications, sondern lässt sie weg. 247 ist der übliche Wunschwert, ausgehandelt wurden hier laut Callback 498.

Stolperfallen

  • Erst requestMtu, dann discoverServices. Android arbeitet GATT-Operationen nacheinander ab, zwei gleichzeitig gehen schief.
  • Gibt requestMtu false zurück, trotzdem die Dienste suchen, sonst bleibt die Verbindung hängen.
  • Ein Watchdog, der bei fehlenden Antworten die Notifications neu abonniert oder neu verbindet, half nicht. Das Problem war nie die Verbindung.
  • Antworten können auch bei großer MTU zusammengeklebt ankommen, also zwei Meldungen ohne Zeilenumbruch hintereinander. Ein kurzer Puffer von 300 ms und ein robustes Aufteilen beim Lesen helfen.

Hat dir das geholfen?

RSS abonnieren

$ ls ../ Passt dazu

3 min

Canyon ECLIPS: App zeigt „Firmware aktuell“, obwohl 0.1.2 da ist – Ursache und Workaround

Mein ECLIPS lief mit Firmware 0.1.0, die Canyon-App meldete „aktuell“, obwohl der Hersteller längst 0.1.2 bereitstellt. Im Update-Check der App steckt ein regulärer Ausdruck, der ein Versions-Suffix verlangt, das der echte Dateiname nicht hat. Mit einem kleinen Frida-Hook auf einem gerooteten Testhandy lief danach das normale Update der App durch.

2 min

Garmin Connect IQ: BLE-Gerät mit PIN-Kopplung meldet INSUFFICIENT_AUTHENTICATION – Grenze und Ausweg

Ich wollte die Lichtanlage meines Fahrrads direkt vom Garmin Edge 1050 per Connect-IQ-BLE steuern. Gefunden, verbunden und gekoppelt wurde das Gerät, jeder Schreibzugriff endete aber mit STATUS_GATT_INSUFFICIENT_AUTHENTICATION_FAIL. Das Gerät verlangt eine Kopplung mit Passkey, die Connect IQ nicht kann. Gelöst habe ich es mit einer Android-Brücke.