Python exemplarisch |
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:
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 |
Experiment 1: Bluetooth Echo Client-Server |
Ziel: 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) Bemerkungen: 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() Bemerkungen: Performing search for service name EchoServer 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) die Verbindung erstellen. |
Experiment 2: Remote-Guided Rover |
Ziel: 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) Bemerkungen:
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() Bemerkungen: serverInfo = client.findService(serviceName, 30) schreibt man für unseren Rover: serverInfo = ("B8:27:EB:05:5A:F8", 1) 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: 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) Bemerkungen: 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. 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", Bemerkungen: 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.
|