Android Linux / Raspberry Pi Bluetooth communication

This post explores my attempt to get bidirectional bluetooth communication going between an android device and a linux computer (in this case a Raspberry Pi running Raspbian). This post is part of the documentation of knowledge acquired while building an “AquaPi” – a raspberry pi that will act as a control center for an aquarium. Here is a video showing a working example:


https://www.youtube.com/watch?v=hvykOO73LbM


Hardware:

Software:

  • On the raspberry pi: setting up and using pybluez

Note: these instructions have also been confirmed to work on an ubuntu workstation with in-built bluetooth support.

First, plugin the bluetooth dongle and fire up the raspberry pi. Next, install the pre-requisites to get bluetooth support on the raspberry pi by running:

sudo apt-get install bluez python-bluez

There are plenty of guides in the internet on how to get bluetooth working, but the only method that worked consistently for me is the following:

1. Disable bluetooth pnat support as there seems to be a bug which stops proper operation with pnat enabled. Full details can be found here: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=690749

A workaround is to add the following to /etc/bluetooth/main.conf:
   DisablePlugins = pnat

Not following the above will give errors similar to:

 (111, 'Connection refused')

Incidentally, the “Steps to Reproduce” section of the bug link are a very good way of testing your bluetooth stack and making sure you have the proper groundwork in place

2. Make note of the following two commands which will be useful when pairing your raspberry pi to the android phone:

sudo hciconfig hci0 piscan [make your device discover-able]
sudo hciconfig hci0 name 'Device Name' [change your device name to something else you fancy]

3. Download the following python script someplace on your raspberry pi. The script is a modified version of the “simple rfcomm server” script found under /usr/share/doc/python-bluez/examples/simple/rfcomm-server.py


import os
import glob
import time
import RPi.GPIO as GPIO
from bluetooth import *
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'
def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c
#while True:
# print(read_temp())
# time.sleep(1)
server_sock=BluetoothSocket( RFCOMM )
server_sock.bind(("",PORT_ANY))
server_sock.listen(1)
port = server_sock.getsockname()[1]
uuid = "94f39d29-7d6d-437d-973b-fba39e49d4ee"
advertise_service( server_sock, "AquaPiServer",
service_id = uuid,
service_classes = [ uuid, SERIAL_PORT_CLASS ],
profiles = [ SERIAL_PORT_PROFILE ],
# protocols = [ OBEX_UUID ]
)
while True:
print "Waiting for connection on RFCOMM channel %d" % port
client_sock, client_info = server_sock.accept()
print "Accepted connection from ", client_info
try:
data = client_sock.recv(1024)
if len(data) == 0: break
print "received [%s]" % data
if data == 'temp':
data = str(read_temp())+'!'
elif data == 'lightOn':
GPIO.output(17,False)
data = 'light on!'
elif data == 'lightOff':
GPIO.output(17,True)
data = 'light off!'
else:
data = 'WTF!'
client_sock.send(data)
print "sending [%s]" % data
except IOError:
pass
except KeyboardInterrupt:
print "disconnected"
client_sock.close()
server_sock.close()
print "all done"
break

view raw

aquaPi.py

hosted with ❤ by GitHub

Note that as previously stated, this script stems from a build to control an aquarium. So you may need to modify the if/elif/else statements beginning at line 60 to do something more meaningful in your particular case. Simply run the server via:

sudo python /path/to/script.py
  • On android: testing bluetooth and writing an app

Before proceeding, you must make sure to pair your android device to your raspberry pi. To do this, ensure that your raspberry pi is discover able (see step 2 above), and search for the device in the bluetooth settings in your android. After successful pairing, you should be able to continue.

1. Install “BlueTerm” from the playstore (https://play.google.com/store/apps/details?id=es.pymasde.blueterm). This will allow you to test your bluetooth setup up this stage. Your raspberry pi should accept connections and even respond to typed commands

2. Write an android app to establish a bluetooth rfcomm channel and send commands to the raspberry pi as well as read information. Following is a sample main activity from the mobile app written for the video demo:


package me.davidvassallo.aquarium;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
BluetoothSocket mmSocket;
BluetoothDevice mmDevice = null;
final byte delimiter = 33;
int readBufferPosition = 0;
public void sendBtMsg(String msg2send){
//UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); //Standard SerialPortService ID
UUID uuid = UUID.fromString("94f39d29-7d6d-437d-973b-fba39e49d4ee"); //Standard SerialPortService ID
try {
mmSocket = mmDevice.createRfcommSocketToServiceRecord(uuid);
if (!mmSocket.isConnected()){
mmSocket.connect();
}
String msg = msg2send;
//msg += "\n";
OutputStream mmOutputStream = mmSocket.getOutputStream();
mmOutputStream.write(msg.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Handler handler = new Handler();
final TextView myLabel = (TextView) findViewById(R.id.btResult);
final Button tempButton = (Button) findViewById(R.id.tempButton);
final Button lightOnButton = (Button) findViewById(R.id.lightOn);
final Button lightOffButton = (Button) findViewById(R.id.lightOff);
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
final class workerThread implements Runnable {
private String btMsg;
public workerThread(String msg) {
btMsg = msg;
}
public void run()
{
sendBtMsg(btMsg);
while(!Thread.currentThread().isInterrupted())
{
int bytesAvailable;
boolean workDone = false;
try {
final InputStream mmInputStream;
mmInputStream = mmSocket.getInputStream();
bytesAvailable = mmInputStream.available();
if(bytesAvailable > 0)
{
byte[] packetBytes = new byte[bytesAvailable];
Log.e("Aquarium recv bt","bytes available");
byte[] readBuffer = new byte[1024];
mmInputStream.read(packetBytes);
for(int i=0;i<bytesAvailable;i++)
{
byte b = packetBytes[i];
if(b == delimiter)
{
byte[] encodedBytes = new byte[readBufferPosition];
System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
final String data = new String(encodedBytes, "US-ASCII");
readBufferPosition = 0;
//The variable data now contains our full command
handler.post(new Runnable()
{
public void run()
{
myLabel.setText(data);
}
});
workDone = true;
break;
}
else
{
readBuffer[readBufferPosition++] = b;
}
}
if (workDone == true){
mmSocket.close();
break;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
// start temp button handler
tempButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on temp button click
(new Thread(new workerThread("temp"))).start();
}
});
//end temp button handler
//start light on button handler
lightOnButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on temp button click
(new Thread(new workerThread("lightOn"))).start();
}
});
//end light on button handler
//start light off button handler
lightOffButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on temp button click
(new Thread(new workerThread("lightOff"))).start();
}
});
// end light off button handler
if(!mBluetoothAdapter.isEnabled())
{
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth, 0);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0)
{
for(BluetoothDevice device : pairedDevices)
{
if(device.getName().equals("raspberrypi-0")) //Note, you will need to change this to match the name of your device
{
Log.e("Aquarium",device.getName());
mmDevice = device;
break;
}
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

Notes on the android script:

  • Line 33: note the UUID. This must match the UUID being advertised by the rfcomm server – which is the python script (line 46)
  • Lines 31-51: this is a function which will send information to the raspberry pi (hence the android here is acting as a “client”) . If not already connected, the function creates an RFCOMM socket, gets a writeable stream, and pushes the message through the socket. The message to be sent is passed as an argument to the function
  • Lines 67-141: this is the runnable (thread) which is used to send and receive information over the bluetooth RFCOMM channel. We need to run this in a seperate thread so as to avoid ANR (application not responding) notifications, and hence leaving the GUI responsive. The thread first sends a message using the sendBtMsg function (line 77), and then opens an input stream and waits for response data. The app waits for response data by creating a buffer (of 1024 bytes in this case – line 95). When data is received, the app checks if the delimiter character is present (in this case the delimiter character is the exclamation mark [!] – ascii character 33 – line 27). We implement this pattern so as to be sure that we received the entire intended data stream before displaying the information to the end user. Obviously, you may want to change my choice of using ! as a delimiter to something more suited to your needs.
  • Line 113: here we set a text view to the received data from the raspberry Pi
  • Line 129: if the data is received and displayed to the user, close the bluetooth connection and break out of the listener loop to finish the thread
  • Lines 144-179: here we are simply attaching onClickListeners to buttons on the GUI to send commands to the raspberry pi. These differ only in the string which is sent over the bluetooth RFCOMM connection to the raspberry pi
  • Lines 184-185: here we check if bluetooth is enabled. If not, we fire off an intent to ask the user to allow bluetooth use
  • Lines 188-199: go through the list of paired devices, and find the raspberry pi. Note, you may need to change the name in the code if you didnt use the standard (line 193). Once the raspberry pi is found, assign it as the device to be used (line 196)

13 thoughts on “Android Linux / Raspberry Pi Bluetooth communication

  1. Hey this is great! I’ve been looking for a way to control linear actuators with Bluetooth and android. Just out of curiosity, what did you used to compile the android code?

  2. Thanks for the tutorial! I’m still struggling connecting with my android device (note 3) to the raspberry PI 2. I’ve disabled pnat and followed bluez, but instantly disconnect after connecting. Any ideas?

  3. really nice David, thanks a lot. The only problem I got is that i can only request once, after that says the socket timeout. In order to do multiple requests I have to reset hci0 before every time

  4. Thank you David. This was really useful. Only comment I have about Python code (and which Josue Martinez above noted) is that the way its coded, it only takes one request. To fix this, “while True” loop need to be under “try:” code block as used in /usr/share/doc/pythonbluez/examples/simple/rfcomm-server.py

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.