Data Transfer Between Android and Arduino over Bluetooth
Transfering data between android and arduino through bluetooth

Bluetooth Connectivity between Android and Arduino

Recently, I developed an android application that transfers data to and from an Arduino through Bluetooth. This post aims to highlight how this Bluetooth connection data transfer between the two devices is achieved.

Disclaimer: The scope of this article is limited to the android application. All Arduino hardware configurations are excluded.

This workflow will be discussed in three stages:

- Device Discovery;

- Connection and

- Data transfer

Discovery

First things first, the necessary permissions must be declared in the manifest file:

<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
...
</manifest>

On the bluetooth devices discovery Activity, a BroadcastReceiver listens for discovered devices and updates a Recyclerview showing a list of available devices (paired and unpaired). The receiver is detailed thus:

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // If it's already paired, skip it, because it's been listed already
            if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                // only add device to the list if it is not already there
                if (!mBtDevices.contains(device))
                    mBtDevices.add(device);
               // update the reccyclerview here
            }
            // When discovery is finished, 
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            setProgressBarIndeterminateVisibility(false);
        }
    }

Registration for this broadcast is done in the activity’s onResume() method:

// Register for broadcasts when a device is discovered
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);

// Register for broadcasts when discovery has finished
IntentFilter finishedFilter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, finishedFilter);

And unregistered in onPause() method

this.unregisterReceiver(mReceiver);

Connection

Bluetooth connectivity with any selected visible device is established through a service that is initialized at application launch and closed when the application is terminated as shown:

@Override
public void onCreate() {
    super.onCreate();
    // initialize
    mBluetoothCommService = new BluetoothCommunicationService();
}
@Override
public void onTerminate() {
    super.onTerminate();
    if (mBluetoothCommService != null) {
        mBluetoothCommService.stop();
    }
}

This service queries the local Bluetooth adapter for paired Bluetooth devices and establishes a connection by spinning up a thread. RFCOMM channel/socket creation and connection are performed on this thread as shown:

private class ConnectThread extends Thread {
    private final BluetoothSocket mSocket;
    private final BluetoothDevice mDevice;
    public ConnectThread(BluetoothDevice device) {
        mDevice = device;
        BluetoothSocket tmp = null;
        // Get a BluetoothSocket for a connection with the given BluetoothDevice
        try {
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            Log.e(TAG, "Socket Type: create() failed", e);
        }
        mSocket = tmp;
        mState = STATE_CONNECTING;
    }
    public void run() {
        // Always cancel discovery because it will slow down a connection
        mAdapter.cancelDiscovery();
        // Make a connection to the BluetoothSocket
        try {
            // its a blocking call and will only return on a successful connection or an exception
            mSocket.connect();
        } catch (IOException e) {
            try {
                mSocket.close();	            // Close the socket
            } catch (IOException e2) {
                Log.e(TAG, "unable to close() socket during connection failure", e2);
            }
            // Send a failure message back to the Activity
	    Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);
	    Bundle bundle = new Bundle();
	    bundle.putString(Constants.TOAST, "Unable to connect device");
	    msg.setData(bundle);
	    mHandler.sendMessage(msg);
	    mState = STATE_NONE;

            return;
        }
        // Reset the ConnectThread because we're done
        synchronized (BluetoothCommunicationService.this) {
            mConnectThread = null;
        }
	// Cancel the thread that completed the connection
	if (mConnectThread != null) {
	    mConnectThread.cancel();
	    mConnectThread = null;
	}
	// Cancel any thread currently running a connection
	if (mConnectedThread != null) {
	    mConnectedThread.cancel();
	    mConnectedThread = null;
	}
	// Start the thread to manage the connection and perform transmissions
	mConnectedThread = new ConnectedThread(socket);
	mConnectedThread.start();
	// Send the name of the connected device back to the UI Activity
	Message msg = mHandler.obtainMessage(Constants.MESSAGE_DEVICE_NAME);
	Bundle bundle = new Bundle();
	bundle.putString(Constants.DEVICE_NAME, device.getName());
	msg.setData(bundle);
	mHandler.sendMessage(msg);
    }
    public void cancel() {
        try {
            mSocket.close();
        } catch (IOException e) {
            Log.e(TAG, "close() of connect socket failed", e);
        }
    }
}

Data Transfer

With connection established, data can be transmitted through a new thread that handles the InputStreams and OutputStreams:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mSocket;
    private final InputStream mInStream;
    private final OutputStream mOutStream;
    private ConnectedThread(BluetoothSocket socket) {
        Log.d(TAG, "create ConnectedThread: " + socket);
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        // Get the BluetoothSocket input and output streams
        try {
            tmpIn = socket.getInputStream();
        } catch (IOException e) {
            Log.e(TAG, "Error occurred when creating input stream", e);
        }
        try {
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            Log.e(TAG, "Error occurred when creating output stream", e);
        }
        mInStream = tmpIn;
        mOutStream = tmpOut;
        mState = STATE_CONNECTED;
    }
    public void run() {
        byte[] buffer = new byte[1024];
        int bytes;
        // Keep listening to the InputStream while connected
         while (mState == STATE_CONNECTED) {
            try {
                // Read from the InputStream
                bytes = mInStream.read(buffer);
                // Send the obtained bytes to the UI Activity
                mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
            } catch (IOException e) {
		// Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(Constants.TOAST, "Device connection was lost");
		msg.setData(bundle);
		mHandler.sendMessage(msg);
		mState = STATE_NONE;

          	break;
            }
        }
    }
    // Write to the connected OutStream.
    private void write(byte[] buffer) {
        try {
            mOutStream.write(buffer);
            // Share the sent message back to the UI Activity
            mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
        } catch (IOException e) {
            Log.e(TAG, "Exception during write", e);
        }
    }
    private void cancel() {
        try {
            mSocket.close();
        } catch (IOException e) {
            Log.e(TAG, "close() of connect socket failed", e);
        }
    }
}

Because this service runs in the background, communication to / from the main UI thread is established by creating a handler which updates the UI (toasts, dialogs, view updated) based on the messsage received.

Each activity that requires commiunication with the service creates an instance of the handler and performs necessary action by overriding the handleMessage() method:

public class BluetoothCommunicationHandler extends Handler {
    private WeakReference<Activity> mActivity;

    public BluetoothCommunicationHandler() {
        mActivity = null;
    }

    public void setCurrentActivity(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        Activity activity = mActivity.get();
        if (activity != null) {
            switch (msg.what) {
                case Constants.MESSAGE_WRITE:
                    byte[] writeBuf = (byte[]) msg.obj;
                    // construct a string from the buffer
                    String writeMessage = new String(writeBuf);
                    break;
                case Constants.MESSAGE_READ:
                    byte[] readBuf = (byte[]) msg.obj;
                    // construct a string from the valid bytes in the buffer
                    String readMessage = new String(readBuf, 0, msg.arg1);
		// do something with the received message
                    break;
                case Constants.MESSAGE_DEVICE_NAME:
                    // save the connected device's name
                    String deviceName = msg.getData().getString(Constants.DEVICE_NAME);
                    break;
            }
        }
    }
}