Python Exemplary - RPi Tutorial
deutsch     english    

COMMUNICATION WITH BLUETOOTH

 

The source code of all examples can be downloaded from here.


 

Data transmission with Bluetooth

 

For data exchange between two Raspberry Pi's or a Raspberry Pi and a computer or smartphone, the Bluetooth protocol is often a good choice. It may be simpler than using TCP/ IP, because no TCP-network (WLAN, router) is needed. Some application proposals are:

  • The Raspberry Pi carries out data acquisition with sensors and continuously reports the measured data to a PC (remote sensing)
  • A moving robot (rover) using an embedded Raspberry Pi is controlled by a remote console (remote-guidance)
  • The Raspberry Pi receives control data from an external device, e.g. a smartphone, that acts like an external keyboard
  • Two (or more) Raspberry Pi's share common tasks

The latest version of the NOOPs distribution supports the built-in Bluetooth chip of the Raspberry model 3 and of the Raspberry ZeroW as well as most USB Bluetooth dongles on the Raspberry Pi model 2. The current version of the RaspiBrick firmware downloaded from here is based on this operating system version. On startup, Bluetooth is activated and placed in Discovery Mode. An external device recognizes the Raspberry Pi with the Bluetooth friendly name raspberrypi and can pair without authentication. On the other hand, the Raspberry Pi can connect itself to a visible external Bluetooth device without pairing.

As with TCP/IP, data transmission is performed using the client/server technology. It is important to know that the communication partners, the server and client are not symmetric. First the server program has to be started and only then a client can establish a connection.

Each Bluetooth device has a Bluetooth-friendly name and a unique 48-bit Bluetooth Mac address in the hexadecimal form nn: nn: nn: nn: nn: nn (n is a hex digit 0..9, A, B, C, D, E, F), which is fixed in the Bluetooth hardware. (In addition to authentication, pairing is used to determine the MAC address and store it together with the Bluetooth name.)

A Bluetooth server provides its services at start-up via a service name. An external device can perform a Bluetooth search to find the server with a particular service name and determine both the Bluetooth name and the Bluetooth-Mac address. Python Bluetooth programming is greatly simplified when using our user-friendly libraries. They are event-driven and similarly usable under standard Python (for the Raspberry Pi and PCs with Python2.7), as in TigerJython. A stream based Bluetooth library for Java SE and Android
is also available.

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

 

Aim:
On the Raspberry Pi runs a Bluetooth server waiting for a connecting client. After the connection of a PC client, the client sends the numbers 0 to 100 to the server. The received numbers are sent back unmodified (like an echo).

Program at the 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)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
The code is extremely simple: When BTServer is instantiated, the service name and the callback function are specified. In the callback, status changes are written out and the received message is sent back. Messages are always strings which are supplemented by a character \ 0 (zero-terminated 8-bit ASCII strings). The terminator character is removed transparently at the receiver.

In the client program, an instance of BTClient is created, specifying the callback function. Subsequently, the client performs a Bluetooth search with the given service name, which is limited to a maximum duration of 20 s. If the search is successful, a tuple is returned that contains the Bluetooth Mac address of the server and the Bluetooth channel. With this information, the client uses connect () to perform a connection attempt, which is limited to a interval of 20 s. Then it sends the numbers as a string and waits in a while loop to receive the echo.

Program:[►]

# 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()  
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
The output window of the clients shows:

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
...

Instead of searching with the given Bluetooth service name, the Bluetooth server name can be used:

client.findServer("raspberrypi", 20)

If the Bluetooth MAC address is known (e.g. by a previous Bluetooth search result), the serverInfo can be hard-coded:

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

This greatly decreases the connection delay.

 

 

Experiment 2: Remote-Guided Rover

 

Aim:
Via an H-bridge the Raspberry Pi controls two motors of a moving robot (see chapter DC motors). The rover that runs a Bluetooth server receives commands forward, backward, left, right, and stop from a Bluetooth client that runs on a PC, smartphone, or another Raspberry Pi. In our setup the H-bridge is wired to GPIO pins 32, 36 for the left motor, and 38 and 40 for the right motor.

Program at the 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)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
In the callback onStateChanged(), the corresponding function is called upon receipt of a command. The remote control can be performed by a PC running a program in any programming language, such as TigerJython, showing a dialog box to send the commands with buttons clicks.

bluetooth1

 

Program:[►]

# 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()  
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
Once a Bluetooth search has been made, the Bluetooth MAC address is displayed in the title bar of the dialog and you can "hard-wire" it in the program code. Instead of

serverInfo = client.findService (serviceName, 30)

we write for our rover:

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

and the connection is established in less time.

It is useful to run the program automatically when the Raspberry Pi is booting (by just naming it autostart.py) and to display certain status information of the rover with LEDs or better on a connected display (7-segment or OLED).

 

 

Experiment 3: Speed control with Android app

 

Aim:
In order to change the speed of the motors, the motors are supplied with a PWM signal via the H-bridge and remote-controlled with an Android app.

The same GPIO pins are used as in the previous program.

Program at the 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)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
Negative speeds correspond to a backward movement. It is ensured that the speeds are always in the range -100 .. 100.

The Android app uses the JDroidLib framework, which also contains a Bluetooth library. However, this library is not event-controlled, but more classically based on streams. The app can be modified and re-built on our online compiler, eliminating the need for a local Android development system. Before starting the program, the smartphone must be paired with the Raspberry Pi.

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

Program:[►]

// 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; } }
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
More information about the development of Android apps with the framework JDroidLib can be found here.

Of course, a control program can also be written with Java SE on a PC. It is advantageous to use the Bluetooth library developed by us, which is based on BlueCove (however, BlueCove is no longer compatible with the latest MacOS). More information can be found here.