Python exemplarisch - RPi Tutorial
deutsch     english

KOMMUNIKATION MIT BLUETOOTH

 

Alle Programme können von hier heruntergeladen werden.


 

Datenübertragung mit Bluetooth

 

Für den Informationsaustausch zwischen zwei Raspberry Pi oder einem Raspberry Pi und einem Computer oder Smartphone kann das Bluetooth Protokoll verwendet werden. Dies ist einfacher als mit TCP/IP, weil in diesem Fall die beiden Geräte nicht über TCP/IP verbunden werden müssen. Folgende Anwendungen sind denkbar:

  • Raspberry Pi führt mit Sensoren eine Datenakquisition durch und meldet die Messdaten laufend an einen PC (Remote Sensing)
  • Mit einem PC oder Smartphone wird ein Roboter mit einem Raspberry Pi fremdgesteuert (Remote Control)
  • Das Programm auf dem Raspberry Pi wird mit Steuerungsdaten von einem externen Gerät, z.B. einem Smartphone, beeinflusst, beispielsweise mit Eingaben ähnlich einer Tastatur
  • Zwei (oder mehrere) Raspberry Pi teilen sich gemeinsame Aufgaben

Die neusten Version der NOOPs-Distritubution unterstützt sowohl den eingebauten Bluetooth-Chip des Raspberry Modell 3 und des Raspberry ZeroW, wie auch die meisten USB Bluetooth-Dongles auf dem Raspberry Pi Modell 2. Die aktuelle Version der RaspiBrick-Firmware, die von hier heruntergeladen werden kann, verwendet diese Betriebssystem-Version. Beim Start wird Bluetooth aktiviert und in den Discovery-Modus versetzt. Ein externes Gerät erkennt den Raspberry Pi mit dem Namen raspberrypi und kann sich ohne Authentifizierung paaren. Der Raspberry Pi kann sich seinerseits mit einem externen Bluetooth-Gerät, das sichtbar ist, verbinden, ohne dass eine Paarung nötig ist.

Wie mit TCP/IP erfolgt die Datenübertragung auch mit Bluetooth unter Verwendung der Client-/Server-Technologie. Dabei ist wichtig, dass die Kommunikationspartner, Server und Client nicht symmetrisch sind. Zuerst muss das Serverprogramm gestartet werden und erst dann kann der Client eine Verbindung aufbauen.

Jedes Bluetooth-Gerät besitzt einen Bluetooth-Namen (Bluetooth friendly name) und ein eindeutige 48-bit lange Bluetooth-Mac-Adresse in der hexadezimalen Form nn:nn:nn:nn:nn:nn (n ist eine Hexziffer 0..9, A, B, C, D, E, F), die in der Bluetooth-Hardware fest eingebrannt ist. (Das Paaren dient neben der Authentifizierung dazu, die MAC-Adresse zu ermitteln und sie zusammen mit dem Bluetooth-Namen abzuspeichern.)

Ein Bluetooth-Server stellt beim Start seine Dienste über einen Servicenamen zur Verfügung. Ein externes Gerät kann mit einer Bluetooth-Suche den Server unter diesem Servicenamen finden und sowohl den Bluetoothnamen wie die Bluetooth-Mac-Adresse ermitteln. Die Programmierung wird unter Verwendung der von uns entwickelten Bluetooth-Libraries stark vereinfacht. Sie sind eventgesteuert geschrieben und sowohl unter Standard-Python (für den Raspberry Pi und PCs mit Python2.7), wie in TigerJython verfügbar. Eine klassische stream-basierte Bluetooth-Library wurde für Java SE und Android entwickelt.

Python 2.7 Bluetooth library (inkl Doc), Doc online
TigerJython Bluetooth library (included in TigerJython distribution), Doc online
Java SE Bluetooth library Java + Doc, Doc online
Android Bluetooth is part of JDroidLib

 

 

Experiment 1: Bluetooth Echo Client-Server

 

Ziel:
Auf dem Raspberry Pi läuft ein Bluetooth-Server, der auf einen Client wartet. Nach der Verbindungsaufnahme eines PC-Clients sendet dieser die Zahlen 0 bis 100 an den Server. Dieser sendet die empfangenen Daten unverändert wieder zurück (wie ein Echo).

Programm auf dem Raspberry Pi:[►]

# BtEchoServer.py

from btpycom import *

def onStateChanged(state, msg):
    if state == "LISTENING":
        print "Server is listening"
    elif state == "CONNECTED":
        print "Connection established to", msg
    elif state == "MESSAGE":
        print "Got message", msg
        server.sendMessage(msg)
       
serviceName = "EchoServer"
server = BTServer(serviceName, stateChanged = onStateChanged)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Der Code ist extrem einfach: Beim Instanziieren von BTServer wird der Servicename und die Callback-Funktion angegeben. Im Callback werden Zustandsänderungen ausgeschrieben und die empfangene Message wieder zurück gesendet. Messages sind immer Strings, die beim Sender mit einem Zeichen \0 ergänzt werden (nullterminierte 8-bit ASCII-Strings). Beim Empfänger wird das Terminierungszeichen wieder entfernt.

Im Client-Programm wird zuerst eine Instant des BTClients unter Angabe der Callback-Funktion erstellt. Nachfolgend führt der Client eine Bluetooth-Suche mit dem gegebenen Servicenamen durch, die auf maximal 20 s begrenzt ist. Falls die Suche erfolgreich ist, wird ein Tupel mit dem Bluetooth-Mac-Adresse des Servers und dem Bluetooth-Kanal zurückgegeben. Mit dieser Information führt der Client mit connect() einen Verbindungsversuch durch, der auf eine Dauer von 20 s begrenzt ist. Dann sendet er die Zahlen als String und wartet in einer while-Schleife jeweils auf den Empfang des Echos.

Programm:[►]

# BtEchoClient.py

from btcom import *  # TigerJython
from btpycom import *  # Standard-Python (PC, Raspi, ...)

def onStateChanged(state, msg):
    global reply
    if state == "CONNECTING":
        print "Connecting", msg
    elif state == "CONNECTION_FAILED":
        print "Connection failed", msg
    elif state == "CONNECTED":
        print "Connected", msg
    elif state == "DISCONNECTED":
        print "Disconnected", msg
    elif state == "MESSAGE":
        print "Message", msg
        reply = msg
       
serviceName = "EchoServer"
print "Performing search for service name", serviceName
client = BTClient(stateChanged = onStateChanged)
serverInfo = client.findService(serviceName, 20)
if serverInfo == None:
    print "Service search failed"
else:
    print "Got server info", serverInfo
    if client.connect(serverInfo, 20):
        for n in range(0, 101):
            client.sendMessage(str(n))
            reply = ""
            while reply == "":
                time.sleep(0.001)
        client.disconnect()  
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Im Ausgabefenster des Clients erscheinen folgende Informationen:

Performing search for service name EchoServer
Got server info (u'B8:27:EB:04:A6:7E', 1)
Connecting (u'B8:27:EB:04:A6:7E', 1)
Connected (u'B8:27:EB:04:A6:7E', 1)
Message 0
Message 1
...

Statt mit dem Servicenamen zu suchen, kann man auch mit

client.findServer("raspberrypi", 20)

mit dem Servernamen die Bluetooth-Suche durchführen. Kennt man die Bluetooth-Mac-Adresse des Servers, kann man auch ohne Bluetooth-Suche direkt mit

serverInfo = ("B8:27:EB:04:A6:7E", 1)
connect(serverInfo, 20)

die Verbindung erstellen.

 

 

Experiment 2: Remote-Guided Rover

 

Ziel:
Der Raspberry Pi schaltet über eine H-Brücke zwei Motoren eines Rovers (siehe Kapitel Gleichstrommotoren). Der Rover, auf dem ein Bluetooth-Server läuft, erhält die Befehle für vorwärts, rückwärts, links, rechts und stop von einem Bluetooth-Client, der auf einem PC, einem Smartphone oder einem anderen Raspberry Pi läuft. Die H-Brücke ist hier an GPIO-Pins 32, 36 für den linken Motor und 38 und 40 für den rechten Motor angeschlossen.

Programm auf dem Raspberry Pi:[►]

# BTRover.py

import RPi.GPIO as GPIO
from btpycom import *

def onStateChanged(state, msg):
    if state == "LISTENING":
        print "Waiting for connecting controller..."
        stop()
    elif state == "CONNECTED":
        print "Connection to", msg, "established"
    elif state == "MESSAGE":
        print "Got command:", msg    
        if msg == "FORWARD":
            forward()
        elif msg == "BACKWARD":    
            backward()
        elif msg == "STOP":    
            stop()
        elif msg == "LEFT":    
            left()
        elif msg == "RIGHT":    
            right()

# adapt to your rover
P_MOTA1 = 40 # right motor
P_MOTA2 = 38 # right motor
P_MOTB1 = 36 # left motor
P_MOTB2 = 32 # left motor

def forward():
    GPIO.output(P_MOTA1, GPIO.HIGH)
    GPIO.output(P_MOTA2, GPIO.LOW)
    GPIO.output(P_MOTB1, GPIO.HIGH)
    GPIO.output(P_MOTB2, GPIO.LOW)

def backward():        
    GPIO.output(P_MOTA1, GPIO.LOW)
    GPIO.output(P_MOTA2, GPIO.HIGH)
    GPIO.output(P_MOTB1, GPIO.LOW)
    GPIO.output(P_MOTB2, GPIO.HIGH)

def left():
    GPIO.output(P_MOTA1, GPIO.HIGH)
    GPIO.output(P_MOTA2, GPIO.LOW)
    GPIO.output(P_MOTB1, GPIO.LOW)
    GPIO.output(P_MOTB2, GPIO.HIGH)

def right():
    GPIO.output(P_MOTA1, GPIO.LOW)
    GPIO.output(P_MOTA2, GPIO.HIGH)
    GPIO.output(P_MOTB1, GPIO.HIGH)
    GPIO.output(P_MOTB2, GPIO.LOW)
    
def stop():
    GPIO.output(P_MOTA1, GPIO.LOW)
    GPIO.output(P_MOTA2, GPIO.LOW)
    GPIO.output(P_MOTB1, GPIO.LOW)
    GPIO.output(P_MOTB2, GPIO.LOW)

def setup():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setwarnings(False)
    GPIO.setup(P_MOTA1, GPIO.OUT)
    GPIO.setup(P_MOTA2, GPIO.OUT)
    GPIO.setup(P_MOTB1, GPIO.OUT)
    GPIO.setup(P_MOTB2, GPIO.OUT)
    
setup()
serviceName = "BTRover"
BTServer(serviceName, stateChanged = onStateChanged)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Im Callback onStateChanged() wird beim Empfang eines Befehls die entsprechende Funktion aufgerufen. Die Fernsteuerung kann von einem PC erfolgen, auf dem ein Programm in irgendeiner Programmiersprache läuft, beispielsweise mit TigerJython und einem Dialogfenster, in dem die Befehle mit Buttons gesendet werden können.

bluetooth1

 

Programm:[►]

# RoverControl.py
# TigerJython

from entrydialog import *
from btcom import *

def showDialog():
    global dlg, buttons
    buttons = [ButtonEntry("Left"), ButtonEntry("Forward"), ButtonEntry("Stop"),
               ButtonEntry("Backward"), ButtonEntry("Right")]
    pane = EntryPane(buttons[0], buttons[1], buttons[2], buttons[3], buttons[4])
    dlg = EntryDialog(pane)
    dlg.setTitle("Remote Control")
    dlg.show()

showDialog()
commands = ["LEFT", "FORWARD", "STOP", "BACKWARD", "RIGHT"]

def onStateChanged(state, msg):
    pass
       
serviceName = "BTRover"
client = BTClient(stateChanged = onStateChanged)
dlg.setTitle("Searching for rover. Please wait...")
serverInfo = client.findService(serviceName, 30)
if serverInfo == None:
    dlg.setTitle("Rover not found")
else:
    if client.connect(serverInfo, 20):
        dlg.setTitle("Connected to " + serverInfo[0])
        while not dlg.isDisposed():
            for n in range(len(buttons)):
                if buttons[n].isTouched():
                    client.sendMessage(commands[n])
        client.disconnect()  
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Nachdem einmal ein Bluetooth-Suche gemacht wurde, wird die Bluetooth-Mac-Adresse in der Titelzeile des Dialogs angezeigt und man kann sie im Programm fest "verdrahten". Statt

serverInfo = client.findService(serviceName, 30)

schreibt man für unseren Rover:

serverInfo = ("B8:27:EB:05:5A:F8", 1)

und die Verbindung wird schneller erstellt.

Es ist sinnvoll, das Programm auf dem Rover beim Booten automatisch auszuführen (indem man es autostart.py nennt) und bestimmte Status-Informationen des Rovers mit LEDs oder besser auf einem angeschlossenen Display (7-Segment oder OLED) anzuzeigen.

 

 

Experiment 3: Geschwindigkeitsteuerung mit Android App

 

Ziel:
Um die Geschwindigkeit der Motoren zu verändern, werden die Motoren über die H-Brücke mit einem PWM-Signal versorgt und mit einer Android-App ferngesteuert.

bluetooth3

Es werden dieselben GPIO-Pins wie im vorhergehenden Programm verwendet.

Programm auf dem Raspberry Pi:[►]

# BTRover1.py

import RPi.GPIO as GPIO
from btpycom import *

def onStateChanged(state, msg):
    global speed
    if state == "LISTENING":
        print "Waiting for connecting controller..."
        stop()
    elif state == "CONNECTED":
        print "Connection to", msg, "established"
    elif state == "MESSAGE":
        print "Got command:", msg    
        if msg == "FASTER":
            if speed < 100:
                speed += 10
            if speed > 0:
                forward(speed)
            else:
                backward(-speed)
        elif msg == "SLOWER":    
            if speed > -100:
               speed -= 10
            if speed > 0:
                forward(speed)
            else:
                backward(-speed)
        elif msg == "STOP":
            speed = 0    
            stop()
        elif msg == "LEFT":
            left(30)
        elif msg == "RIGHT":
            right(30)

P_MOTA1 = 40
P_MOTA2 = 38
P_MOTB1 = 36
P_MOTB2 = 32
fPWM = 60  # Hz

def setup():
    global pwm_a1, pwm_a2, pwm_b1, pwm_b2
    GPIO.setmode(GPIO.BOARD)
    GPIO.setwarnings(False)
    GPIO.setup(P_MOTA1, GPIO.OUT)
    pwm_a1 = GPIO.PWM(P_MOTA1, fPWM)
    pwm_a1.start(0)
    GPIO.setup(P_MOTA2, GPIO.OUT)
    pwm_a2 = GPIO.PWM(P_MOTA2, fPWM)
    pwm_a2.start(0)
    GPIO.setup(P_MOTB1, GPIO.OUT)
    pwm_b1 = GPIO.PWM(P_MOTB1, fPWM)
    pwm_b1.start(0)
    GPIO.setup(P_MOTB2, GPIO.OUT)
    pwm_b2 = GPIO.PWM(P_MOTB2, fPWM)
    pwm_b2.start(0)

def stop():
    forward(0)
       
def forward(speed):
    pwm_a1.ChangeDutyCycle(speed)
    pwm_a2.ChangeDutyCycle(0)
    pwm_b1.ChangeDutyCycle(speed)
    pwm_b2.ChangeDutyCycle(0)

def left(speed):
    pwm_a1.ChangeDutyCycle(speed)
    pwm_a2.ChangeDutyCycle(0)
    pwm_b1.ChangeDutyCycle(0)
    pwm_b2.ChangeDutyCycle(speed)

def right(speed):
    pwm_a1.ChangeDutyCycle(0)
    pwm_a2.ChangeDutyCycle(speed)
    pwm_b1.ChangeDutyCycle(speed)
    pwm_b2.ChangeDutyCycle(0)
    
def backward(speed):
    pwm_a2.ChangeDutyCycle(speed)
    pwm_a1.ChangeDutyCycle(0)
    pwm_b2.ChangeDutyCycle(speed)
    pwm_b1.ChangeDutyCycle(0)
 
speed = 0    
setup()
serviceName = "BTRover"
BTServer(serviceName, stateChanged = onStateChanged)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Negative Geschwindigkeiten entsprechen einer Rückwärtsfahrt. Es wird sichergestellt, dass die Geschwindigkeiten immer im Bereich -100 .. 100 liegen.

Die Android-App verwendet das Framework JDroidLib, das auch eine Bluetooth-Library enthält. Allerdings ist diese nicht event-gesteuert, sondern klassisch mit Streams aufgebaut. Die App kann auf unserem Online-Compiler modifiziert und neu erstellt werden, sodass sich ein lokales Android-Entwicklungssystem erübrigt. Vor der Verwendung muss das Smartphone mit dem Raspberry Pi gepaart werden.

Open source in Online-Compiler.
Create QR code to download Android app to your smartphone

Programm:[►]

// RoverControlApp.java

package rovercontrol.app;

import ch.aplu.android.*;
import android.graphics.Color;
import android.bluetooth.*;
import ch.aplu.android.bluetooth.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;


public class RoverControlApp extends GameGrid
  implements GGPushButtonListener
{
  private GGPushButton faster;
  private GGPushButton slower;
  private GGPushButton left;
  private GGPushButton right;
  private GGPushButton stop;
  private GGTextField tf;
  private BluetoothClient bc;
  private DataInputStream dis;
  private DataOutputStream dos;

  public RoverControlApp()
  {
    super(21, 21, 0);
    setScreenOrientation(PORTRAIT);
  }

  public void main()
  {
    String serverName = askName();
    getBg().clear(Color.BLUE);
    new GGTextField("Rover Control App",
      new Location(0, 1), true).show();
    addButtons();
    tf = new GGTextField("Trying to connect...", new Location(4, 19), true);
    tf.show();
      
    // Search device   
    BluetoothDevice serverDevice = searchDevice(serverName);
    if (serverDevice != null)
    {
      bc = new BluetoothClient(serverDevice);
      boolean rc = bc.connect();
      if (!rc)
      {
         tf.hide();
         tf = new GGTextField("Connection failed", new Location(4, 19), true);
         tf.show();
         return;
      }
      tf.hide();
      tf = new GGTextField("Connection established", new Location(3, 19), true);
      tf.show();
      dis = new DataInputStream(bc.getInputStream());
      dos = new DataOutputStream(bc.getOutputStream());
      enableButtons();
  }  
  }
  
  private String askName()
  {
    GGPreferences prefs = new GGPreferences(this);
    String oldName = prefs.retrieveString("BluetoothName");
    String newName = null;
    while (newName == null || newName.equals(""))
    {
      newName = GGInputDialog.show(this, "Rover Control App", 
"Enter Bluetooth Name", oldName == null ? "raspberrypi" : oldName); } prefs.storeString("BluetoothName", newName); return newName; } private void addButtons() { faster = new GGPushButton("faster"); faster.setRepeatPeriod(100); addActor(faster, new Location(10, 5)); slower = new GGPushButton("slower"); slower.setRepeatPeriod(100); addActor(slower, new Location(10, 15)); left = new GGPushButton("left"); addActor(left, new Location(3, 10)); right = new GGPushButton("right"); addActor(right, new Location(17, 10)); stop = new GGPushButton("stop"); addActor(stop, new Location(10, 10)); } private void enableButtons() { faster.addPushButtonListener(this); slower.addPushButtonListener(this); left.addPushButtonListener(this); right.addPushButtonListener(this); stop.addPushButtonListener(this); } public void onPause() { super.onPause(); } public void buttonPressed(GGPushButton button) { if (button == faster) faster(); if (button == slower) slower(); if (button == left) turnLeft(); if (button == right) turnRight(); if (button == stop) doStop(); } public void buttonReleased(GGPushButton button) { } public void buttonClicked(GGPushButton button) { } public void buttonRepeated(GGPushButton button) { if (button == faster) faster(); if (button == slower) slower(); } private void faster() { sendMessage("FASTER"); } private void slower() { sendMessage("SLOWER"); } private void doStop() { sendMessage("STOP"); } private void turnLeft() { sendMessage("LEFT"); } private void turnRight() { sendMessage("RIGHT"); } private void sendMessage(String msg) { try { dos.writeBytes(msg + "\0"); dos.flush(); } catch (IOException ex) { tf.hide(); tf = new GGTextField("Connection lost", new Location(3, 19), true); tf.show(); } } // Searches for device in set of paired devices.
//Returns null, if search fails.
private BluetoothDevice searchDevice(String btName) { BluetoothAdapter adapter = BluetoothDiscovery.getBluetoothAdapter(this); if (adapter == null) return null; if (!adapter.isEnabled()) { requestBluetoothEnable(); if (!adapter.isEnabled()) return null; } BluetoothDiscovery bd = new BluetoothDiscovery(this); BluetoothDevice device = bd.getPairedDevice(btName); return device; } }
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Weitere Information zur Entwicklung von Android-Apps mit dem Framework JDroidLib findet man auf Deutsch hier und auf Englisch hier.

Selbstverständlich kann ein Steuerungsprogramm auch mit Java SE für einem PC erstellt werden. Es ist dabei vorteilhaft, die von uns entwickelte Bluetooth-Library zu verwenden, die auf BlueCove basiert (allerdings ist BlueCove mit den neusten MacOS nicht mehr kompatibel). Mehr Informationen findet man hier.