Android: Bluetoothkommunikation

Die Kommunikation über den BluetoothAdapter  möchte ich in diesem Tutorial beschreiben.

Es gibt im Internet und vorallem bei YoutTube sehr gute Videos welche die Kommunikation über Bluetooth Low Energy erläutern, diese Art der Kommunikation ist jedoch aufwändiger ABER zukunftssicherer obwohl hier sich die API auch bei Bluetooth LE geändert hat denn einige Methoden sind auch dort deprecated.

Ziel

Mein Ziel ist es eine Verbindung zwischen einem Android Gerät (Android Version 4.3) und einem Arduino UNO mit dem Bluetoothmodul HC-06 herzustellen.
Dazu habe ich mir eine kleine Wetterstation aufgebaut welche im Tutorial Arduino Lektion 22: Bluetooth Wetterstation beschrieben ist.
In diesem Tutorial möchte ich auf den Aufbau einer Bluetoothverbindung eingehen und wie ich die Android App Bluetooth-Terminal entwickelt habe.

Zugang zum Quellcode

Den Quellcode der Android App habe ich Quellcode offen auf GitHub unter https://github.com/StefanDraeger/bluetoothterminal veröffentlicht.

Hinweise zum verwenden von Virtuellen Geräten

Virtuelle Geräte können nicht verwendet werden, da Android das virtualisieren von Bluetoothschnittstellen nicht unterstützt.

Berechtigungen

Für die Verwendung des Bluetooth Adapters im Android Gerät müssen die Berechtigungen

in der AndroidManifest.xml gesetzt werden.

Mit diesen Berechtigungen können wir den Bluetooth Adapter starten und Verbindungen zu Geräten (BluetoothDevices) aufbauen.

Prüfen auf Bluetooth Unterstützung

Nicht jedes Android Gerät hat eine Bluetoothschnittstelle, somit muss beim Starten der App geprüft werden ob das verwendete Gerät Bluetooth unterstützt.

Als erstes holt man sich den BluetoothAdapter und prüft diesen auf NULL wenn dieser NULL ist wird Bluetooth nicht von dem Gerät unterstützt. Es wird dann eine entsprechende Nachricht auf den Display ausgegeben. Theoretisch kann man hier auch die App beenden.

Aktivieren der Bluetoothschnittstelle

Im Normalfall ist die Bluetoothschnittstelle nicht aktiviert daher muss für geprüft werden in welchem Status sich der BluetoothAdapter befindet.

Wenn der BluetoothAdapter nicht verfügbar ist (aber !=null) dann soll diese Schnittstelle aktiviert werden. Zum Aktivieren wird die Anwendung verlassen und der Dialog von Android wird angezeigt.

Dialog zum aktivieren der Bluetoothschnittstelle.
Dialog zum aktivieren der Bluetoothschnittstelle.

Nachdem die Bluetoothschnittstelle aktiviert wurde können die gekoppelten und nicht gekoppelten Geräte gesucht werden.

Suchen von Geräten

Bluetoothgeräte können 2 Status haben, „gekoppelt“ und „nicht gekoppelt“. Als erstes möchte ich erläutern wie man gekoppelte Geräte findet.

Auflisten von gekoppelten Geräten

Die gekoppelten Geräte kann man bequem aus dem Bluetooth Adapter lesen. Dazu ruft man die Methode getBondedDevices() auf. Diese Methode liefert ein Set mit den BluetoothDevices.
Wenn der BluetoothAdapter nicht aktiviert ist wird ein leeres Set geliefert.

In der Methode findBondedBluetoothDevices() durchlaufe ich das Set mit einer ForEach Schleife und kapsel dieses in einem WrapperObjekt  „XBluetoothDevice“ aber dazu später mehr.

Suchen von nicht gekoppelten Geräten

Das suchen von nicht gekoppelten Geräten übernimmt ein BroadcastReceiver  welcher in der Activity Registriert werden muss. Was ein BroadcastReceiver ist wird in dem Wikiartikel https://de.wikibooks.org/wiki/Googles_Android/_BroadcastReceiver sehr gut erläutert.

Der BroadcastReceiver zum suchen von nicht gekoppelten Bluetoothgeräten.

Im Konstruktor wird die Ausführende Activity übergeben. Diese wird benötigt damit auf den ApplicationContext zugegriffen und der ProgressDialog angezeigt werden kann. Die Methode onReceive(); wird aufgerufen wenn ein nicht gekoppeltes Bluetoothgerät gefunden wurde. Hier wurden nicht alle Status abgefragt da ich hier nicht mit Bluetooth LE (LowEnergy) arbeite (zum Thema Bluetooth Low Energy wird es später ein ausführliches Tutorial geben.)

Ablauf:

Wenn der Suchlauf gestartet wird (Status BluetoothAdapter.ACTION_DISCOVERY_STARTED) wird der ProgressDialog mit einer Nachricht und einem Spinner  angezeigt.
Der Status BluetoothAdapter.ACTION_FOUND wird gesetzt wenn ein Gerät gefunden wurde, in diesem Fall wrappe ich das gefundene Gerät (BluetoothDevice) in mein XBluetoothDevice und füge es meiner Activity hinzu. Der Suchlauf wird hier nicht abgebrochen.
Wenn keine weiteren Geräte gefunden wurden wird der Status BluetoothAdapter.ACTION_DISCOVERY_FINISHED  geliefert, in diesem Fall schließe ich den Dialog und aktualisiere das Label welches die gefundenen Geräte auf der Activity repräsentiert.

Das starten des Suchlaufs für nicht gekoppelte Geräte übernimmt folgende Methode welche ich in einem Menü aufrufe.

In der Methode onDestroy() der Activity muss der registrierte BroadcastReceiver wieder unregistriert werden.

Koppeln von Bluetoothgeräten

Da bisher nur das nicht gekoppelte Gerät gefunden wurde, muss dieses um es verwenden zu können gekoppelt werden. Dazu benötigt man die Adresse des Gerätes.

Da mein Zielgerät ein Samsung Galaxy S3 ist und dieses mit der Version Andorid 4.3 (SDK 19) läuft muss ich hier mit Reflektion arbeiten denn die implementierung ist vorhanden aber das Interface bietet keine öffentlichen Methoden dafür.

Entkoppeln von Geräten

Nachdem ein Gerät gekoppelt wurde kann dieses auf fast dem gleichen wege wieder entkoppelt werden.

 

Bluetoothkommunikation

Nachdem Bluetoothgeräte gefunden wurden, kann nun mit diesen einen Verbindung aufgebaut werden. Mein Ziel ist es mit dem Bluetooth Modul HC-06 und einem Arduino UNO eine Verbindung aufzubauen. Wenn man sich mit diesem Thema auseinander setzt und im Internet nach einer Lösung forscht findet man viele brauchbare Ideen.

Aufbau der Verbindung zum Gerät

Für den Aufbau der Verbindung benutze ich einen AsyncTask dieser Task wird gestartet und läuft im Hintergrund. Wenn dieser Task die Verbindung aufgebaut hat kehrt er an der Aufrufenden stelle zurück und liefert in diesem Fall einen BluetoothSocket.

Wenn ein gültiges Objekt (d.h. nicht null und die Verbindung wurde aufgebaut) zurück geliefert wurde, starte ich einen java.lang.Thread welcher auf dieser Connection lauscht und eingehende Datenströme entgegennimmt und verarbeitet.

Wenn es hier zu einem Fehler kommt (Exception) so wird der Status „ConnectionFailed“ auf der Oberfläche angezeigt.

Des Weiteren möchte ich diesen Thread von außen auch wieder stoppen können, dazu habe ich eine Boolsche Variable angelegt welche in der run Methode abgefragt wird.

Dieses hat den Vorteil das man sicher einen Thread „beenden“ kann.

Ein Thread sollte niemals beendet werden, sonder soll sich selber beenden.

Lesen von Datenströmen

Die Datenströme der Bluetoothschnittstelle ließt man üben den BluetoothSocket und dem InputStream.
Hier gibt es zwei Möglichkeiten wie man diese verarbeitet:

Das lesen eines Byte Arrays

Eine weit verbreitete Lösung ist das schreiben in ein Byte Array:

Gehen wir die Zeilen durch und schauen was hier passiert:

Zeile 1:

Es wird eine Variable byte[] einem maximalen Platz von 4096 Bytes reserviert (ca. 4 MB), dieser Wert kann natürlich beliebig verändert werden oder man nimmt aus dem InputStream mit der Methode available(). Da man hier nicht mit einem java.lang.InputStream arbeitet sondern mit einem BluetoothInputStream ist die Implementierung hier anders, denn diese Methode liefert (jedenfalls in meinen Tests) immer min. 0 zurück.

Zeile 2:

Die Variable bytes wird angelegt damit der später in der while Schleife nicht immer eine neue Variable angelegt werden muss, dieses hat einen minimalen Resourcenvorteil.

Zeile 3:

Eröffnung der while Schleife es wird hier ein Boolean.True übergeben, für das Beispiel ist es ausreichend jedoch kann man hier diese nicht von außen steuern.

Zeile 4:

In der Zeile 4 werden die Daten aus dem InputStream gelesen und die Bytes in der Variable bytes zwischen gespeichert.

Zeile 5:

Die gelesenen Bytes werden nun lesbar in ein String umgewandelt dafür nutzt man den Konstruktor der Klasse java.lang.String mit den Parametern für

  • byte Array,
  • gelesene bytes

Als Ergebnis bekommt man nun den Wert als String.

Zeile 6:

Als letztes wird nun noch die zuvor geöffnete while Schleife geschlossen.

Warum ich von dieser Methode abgewichen bin

Diese Methode funktioniert und liefert die Werte jedoch ist es nicht zuverlässig das alle Werte sofort und mit einem rutsch gelesen werden, d.h. in meinem Fall der Wetterstation sende ich folgenden String „Temperatur – 25°C“, jedoch bekomme ich diesen abgehackt d.h. es kommt der Wert „Temp“ und dann „eratur“ und so weiter…. Für meinen Fall ist das nicht brauchbar und ich habe mich für die Lösung mit einem BufferedReader entschieden.

BufferedReader zum lesen der Datenströme

Mit folgender Lösung kann man die gesamten Daten mit einem rutsch lesen.

Schauen wir uns auch hier die einzelnen Zeilen genauer an:

Zeile 1:

Eröffnen der while Schleife sowie prüfen ob

  • der Thread weiterhin ausgeführt werden soll,
  • eine Bluetoothconnection besteht

Zeile 2:

Erzeugen eines BufferedReaders und zuweisen eines InputStreamReader aus dem InputStream des BluetoothSocket.

Zeile 3:

Erzeugen eines StringBuilders, für das spätere hinzufügen von den Textwerten aus dem BufferedReader.

Zeile 4:

Hinzufügen einer Textzeile aus dem BufferedReader zu dem StringBuilder.

Lösung für die Verwendung eines SDK > Version 18

Die oben dargestellte Lösung ist für ein Android SDK 18 gedacht, wenn das Zielgerät jedoch ein neueres ist so kann man auch die Neuerungen von Java nutzen.
Da die Klassen InputStreamReader und BufferedReader das Interface Closeable implementieren können diese als Resource im try catch Block verwendet werden dieses macht ein schließen im finally Block überflüssig.

Darstellen der gelesenen Werte aus dem Datenstrom auf der GUI

Nun kann man den Wert aus dem StringBuilder weiterverwenden und auf der GUI darstellen.

Dazu übergibt man dem Thread die Activity und kann dann auf den UI Thread wie folgt zugreifen:

In der Activity habe ich eine Hilfsmethode  generateNewMessage diese bekommt als Parameter die empfangene Nachricht und einen Booleschen Wert ob diese Nachricht empfangen oder gesendet wurde.

 

Verwandte Beiträge

Ersten Kommentar schreiben

Antworten

Deine E-Mail-Adresse wird nicht veröffentlicht.


*