Android Send/Receive data with Arduino using Bluetooth – Part 2

To get things moving along quickly I am going to use the same activity that I used in the last blog to find my paired devices. This can be found here. Only one thing will change and that is the activity name. Call it DeviceListActivity, as it is no longer the main activity.

import java.util.Set;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
 
public class DeviceListActivity extends Activity {
    // Debugging for LOGCAT
    private static final String TAG = "DeviceListActivity";
    private static final boolean D = true;
 
    // declare button for launching website and textview for connection status
    Button tlbutton;
    TextView textView1;
 
    // EXTRA string to send on to mainactivity
    public static String EXTRA_DEVICE_ADDRESS = "device_address";
 
    // Member fields
    private BluetoothAdapter mBtAdapter;
    private ArrayAdapter<String> mPairedDevicesArrayAdapter;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.device_list);
    }
 
    @Override
    public void onResume()
    {
        super.onResume();
        //***************
        checkBTState();
 
        textView1 = (TextView) findViewById(R.id.connecting);
        textView1.setTextSize(40);
        textView1.setText(" ");
 
        // Initialize array adapter for paired devices
        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
 
        // Find and set up the ListView for paired devices
        ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
        pairedListView.setAdapter(mPairedDevicesArrayAdapter);
        pairedListView.setOnItemClickListener(mDeviceClickListener);
 
        // Get the local Bluetooth adapter
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();
 
        // Get a set of currently paired devices and append to 'pairedDevices'
        Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
 
        // Add previosuly paired devices to the array
        if (pairedDevices.size() > 0) {
            findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);//make title viewable
            for (BluetoothDevice device : pairedDevices) {
                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        } else {
            String noDevices = getResources().getText(R.string.none_paired).toString();
            mPairedDevicesArrayAdapter.add(noDevices);
        }
  }
 
    // Set up on-click listener for the list (nicked this - unsure)
    private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
 
            textView1.setText("Connecting...");
            // Get the device MAC address, which is the last 17 chars in the View
            String info = ((TextView) v).getText().toString();
            String address = info.substring(info.length() - 17);
 
            // Make an intent to start next activity while taking an extra which is the MAC address.
            Intent i = new Intent(DeviceListActivity.this, MainActivity.class);
            i.putExtra(EXTRA_DEVICE_ADDRESS, address);
            startActivity(i);
        }
    };
 
    private void checkBTState() {
        // Check device has Bluetooth and that it is turned on
         mBtAdapter=BluetoothAdapter.getDefaultAdapter(); // CHECK THIS OUT THAT IT WORKS!!!
        if(mBtAdapter==null) {
            Toast.makeText(getBaseContext(), "Device does not support Bluetooth", Toast.LENGTH_SHORT).show();
        } else {
          if (mBtAdapter.isEnabled()) {
            Log.d(TAG, "...Bluetooth ON...");
          } else {
            //Prompt user to turn on Bluetooth
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, 1);
 
            }
          }
        }
}

Also make sure that you update your permissions in the manifest to allow bluetooth (and the DeviceListActivity name if you didn\’t start a new project from part 1).

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.arduinosensors"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.arduinosensors.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait" >

</activity>
<activity
android:name="com.example.arduinosensors.DeviceListActivity"
android:label="@string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

Now on to the new stuff! We are making an activity that will allow us to turn on/off an LED on the Arduino as well as receive data via bluetooth. When finished it will look like the image below.

As we are sending and receiving data this time through Bluetooth rather than just sending there are a few more things to take care of.

First of all is the fact that we cannot simply wait for a bluetooth message to come along on the main thread of our activity. If we did we would most likely get an \’Application Not Responding\’ message. So we need to run a new thread for the bluetooth data receiving to take place on, as well as a handler to update the UI when relevant data has been received. To get a better idea of how threads and handlers work read this.

Another thing to concern ourselves with may be that we are now getting data in from our Arduino. What do we do with it!!!! If you remember from part 1, the data will be arriving in the form: #1.23+2.34+3.23+2.43+~  So from this we can use the fact that ~ is the last character, therefore the end of that transmission. # is the beginning of the transmission so we can say if the first character of the string is # we are good to go on reading the data.

Finally we can work out where our data is in the string from #. The data for sensor 0 will be at elements 1-5 in the string so we can extract what we need with the use of substring. This procedure is repeated for the remaining three sensors. With that information now acquired we can use setText to display the sensor values in the corresponding textViews.

You will also notice I have added two textViews at the bottom of the app that display the actual data string received from the Arduino sensors. The other view shows the length of the string received, ideally this should be 21 every time. I put this in while debugging but thought it was useful. I have tried to comment a fair bit in the code below so it is clear what part is doing what. However, if you have any questions feel free to send me a message. So create a new blank activity and layout file and add the following code. Your manifest should update automatically referencing the new activity. First off is the xml layout file:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" >

<TextView
android:id="@+id/testView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/txtString"
android:text=""
android:textSize="15sp" />

<TextView
android:id="@+id/txtString"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_alignLeft="@+id/testView1"
android:layout_alignParentBottom="true"
android:text=""
android:textSize="15sp" />

<Button
android:id="@+id/buttonOn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="LED ON" />

<Button
android:id="@+id/buttonOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/buttonOn"
android:layout_alignParentRight="true"
android:text="LED OFF" />

<TextView
android:id="@+id/sensorView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/sensorView0"
android:layout_centerHorizontal="true"
android:text="Sensor 1 Voltage = ????"
android:textAppearance="?android:attr/textAppearanceMedium" />

<TextView
android:id="@+id/sensorView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/sensorView1"
android:layout_centerHorizontal="true"
android:text="Sensor 2 Voltage = ????"
android:textAppearance="?android:attr/textAppearanceMedium" />

<TextView
android:id="@+id/sensorView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/sensorView2"
android:layout_centerHorizontal="true"
android:text="Sensor 3 Voltage = ????"
android:textAppearance="?android:attr/textAppearanceMedium" />

<TextView
android:id="@+id/sensorView0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="196dp"
android:text="Sensor 0 Voltage = ????"
android:textAppearance="?android:attr/textAppearanceMedium" />

</RelativeLayout>

And now the java…

package com.example.arduinosensors;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends Activity {
 
  Button btnOn, btnOff;
  TextView txtArduino, txtString, txtStringLength, sensorView0, sensorView1, sensorView2, sensorView3;
  Handler bluetoothIn;
 
  final int handlerState = 0;                        //used to identify handler message
  private BluetoothAdapter btAdapter = null;
  private BluetoothSocket btSocket = null;
  private StringBuilder recDataString = new StringBuilder();
 
  private ConnectedThread mConnectedThread;
 
  // SPP UUID service - this should work for most devices
  private static final UUID BTMODULEUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
 
  // String for MAC address
  private static String address;
 
@Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    setContentView(R.layout.activity_main);
 
    //Link the buttons and textViews to respective views
    btnOn = (Button) findViewById(R.id.buttonOn);
    btnOff = (Button) findViewById(R.id.buttonOff);
    txtString = (TextView) findViewById(R.id.txtString);
    txtStringLength = (TextView) findViewById(R.id.testView1);
    sensorView0 = (TextView) findViewById(R.id.sensorView0);
    sensorView1 = (TextView) findViewById(R.id.sensorView1);
    sensorView2 = (TextView) findViewById(R.id.sensorView2);
    sensorView3 = (TextView) findViewById(R.id.sensorView3); 
 
    bluetoothIn = new Handler() {
        public void handleMessage(android.os.Message msg) {
            if (msg.what == handlerState) {                                     //if message is what we want
                String readMessage = (String) msg.obj;                                                                // msg.arg1 = bytes from connect thread
                recDataString.append(readMessage);                                      //keep appending to string until ~
                int endOfLineIndex = recDataString.indexOf("~");                    // determine the end-of-line
                if (endOfLineIndex > 0) {                                           // make sure there data before ~
                    String dataInPrint = recDataString.substring(0, endOfLineIndex);    // extract string
                    txtString.setText("Data Received = " + dataInPrint);
                    int dataLength = dataInPrint.length();                          //get length of data received
                    txtStringLength.setText("String Length = " + String.valueOf(dataLength));
 
                    if (recDataString.charAt(0) == '#')                             //if it starts with # we know it is what we are looking for
                    {
                        String sensor0 = recDataString.substring(1, 5);             //get sensor value from string between indices 1-5
                        String sensor1 = recDataString.substring(6, 10);            //same again...
                        String sensor2 = recDataString.substring(11, 15);
                        String sensor3 = recDataString.substring(16, 20);
 
                        sensorView0.setText(" Sensor 0 Voltage = " + sensor0 + "V");    //update the textviews with sensor values
                        sensorView1.setText(" Sensor 1 Voltage = " + sensor1 + "V");
                        sensorView2.setText(" Sensor 2 Voltage = " + sensor2 + "V");
                        sensorView3.setText(" Sensor 3 Voltage = " + sensor3 + "V");
                    }
                    recDataString.delete(0, recDataString.length());                    //clear all string data
                   // strIncom =" ";
                    dataInPrint = " ";
                }
            }
        }
    };
 
    btAdapter = BluetoothAdapter.getDefaultAdapter();       // get Bluetooth adapter
    checkBTState(); 
 
  // Set up onClick listeners for buttons to send 1 or 0 to turn on/off LED
    btnOff.setOnClickListener(new OnClickListener() {
      public void onClick(View v) {
        mConnectedThread.write("0");    // Send "0" via Bluetooth
        Toast.makeText(getBaseContext(), "Turn off LED", Toast.LENGTH_SHORT).show();
      }
    });
 
    btnOn.setOnClickListener(new OnClickListener() {
      public void onClick(View v) {
        mConnectedThread.write("1");    // Send "1" via Bluetooth
        Toast.makeText(getBaseContext(), "Turn on LED", Toast.LENGTH_SHORT).show();
      }
    });
  }
 
  private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
 
      return  device.createRfcommSocketToServiceRecord(BTMODULEUUID);
      //creates secure outgoing connecetion with BT device using UUID
  }
 
  @Override
  public void onResume() {
    super.onResume();
 
    //Get MAC address from DeviceListActivity via intent
    Intent intent = getIntent();
 
    //Get the MAC address from the DeviceListActivty via EXTRA
    address = intent.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
 
    //create device and set the MAC address
    BluetoothDevice device = btAdapter.getRemoteDevice(address);
 
    try {
        btSocket = createBluetoothSocket(device);
    } catch (IOException e) {
        Toast.makeText(getBaseContext(), "Socket creation failed", Toast.LENGTH_LONG).show();
    }
    // Establish the Bluetooth socket connection.
    try
    {
      btSocket.connect();
    } catch (IOException e) {
      try
      {
        btSocket.close();
      } catch (IOException e2)
      {
        //insert code to deal with this
      }
    }
    mConnectedThread = new ConnectedThread(btSocket);
    mConnectedThread.start();
 
    //I send a character when resuming.beginning transmission to check device is connected
    //If it is not an exception will be thrown in the write method and finish() will be called
    mConnectedThread.write("x");
  }
 
  @Override
  public void onPause()
  {
    super.onPause();
    try
    {
    //Don't leave Bluetooth sockets open when leaving activity
      btSocket.close();
    } catch (IOException e2) {
        //insert code to deal with this
    }
  }
 
 //Checks that the Android device Bluetooth is available and prompts to be turned on if off
  private void checkBTState() {
 
    if(btAdapter==null) {
        Toast.makeText(getBaseContext(), "Device does not support bluetooth", Toast.LENGTH_LONG).show();
    } else {
      if (btAdapter.isEnabled()) {
      } else {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, 1);
      }
    }
  }
 
  //create new class for connect thread
  private class ConnectedThread extends Thread {
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
 
        //creation of the connect thread
        public ConnectedThread(BluetoothSocket socket) {
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
 
            try {
                //Create I/O streams for connection
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) { }
 
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }
 
        public void run() {
            byte[] buffer = new byte[256];
            int bytes; 
 
            // Keep looping to listen for received messages
            while (true) {
                try {
                    bytes = mmInStream.read(buffer);            //read bytes from input buffer
                    String readMessage = new String(buffer, 0, bytes);
                    // Send the obtained bytes to the UI Activity via handler
                    bluetoothIn.obtainMessage(handlerState, bytes, -1, readMessage).sendToTarget();
                } catch (IOException e) {
                    break;
                }
            }
        }
        //write method
        public void write(String input) {
            byte[] msgBuffer = input.getBytes();           //converts entered String into bytes
            try {
                mmOutStream.write(msgBuffer);                //write bytes over BT connection via outstream
            } catch (IOException e) {
                //if you cannot write, close the application
                Toast.makeText(getBaseContext(), "Connection Failure", Toast.LENGTH_LONG).show();
                finish();
 
              }
            }
        }
}

One small thing to add. I used to send the buffer from my run method in the connectThread but found that I would sometimes get garbled data with anything but slow data rates. It turns out this was because the buffer was getting full and struggling to keep up. A much better method is to create a string in the run method from the buffer and then send that to the UI thread via the handler.

Turn on your Arduino and bluetooth module and run your Android app. If all goes well your sensor values will go from ? to voltage values. If I have missed anything give me a shout.

Just a reminder that all files used are available on GitHub here.

I used these blogs to make this project:

http://english.cxem.net/arduino/arduino5.php

http://bellcode.wordpress.com/2012/01/02/android-and-arduino-bluetooth-communication/