Fundefinedollowing up on my last post, it was time to see some data exchange between my smartphone and Arduino through the Bluetooth module I had just set up. I connected the JY-MCU HC06 V1.06 Bluetooth board to my Arduino respecting the wiring diagram aside.

At first, I thought of creating a simple ping-pong application (where Arduino would reply a "pong" message whenever it receives a "ping" message), but it would be nice to have a way of visually debugging Arduino, since it won't be connected to a terminal (although it could be done connecting the Bluetooth module to Arduino's non-regular TX/RX pins, as Matt Bell did in his blog). So I decided to invert the 13th pin output value (turning on and off its LED) whenever I receive a "ping" message.

#define LED 13
String str;
byte state = 0;

void setup()
{
   pinMode(LED, OUTPUT);
   digitalWrite(LED, LOW);
   Serial.begin(9600);
}

void loop() 
{
   if(Serial.available() > 0)
   {
      str = Serial.readStringUntil('\n');
      if (str.equals("ping"))
      {
         if (state==0)
         {
            digitalWrite(LED, HIGH);
            state = 1;
         }
         else
         {
            digitalWrite(LED, LOW);
            state = 0;
         }
         Serial.write("pong\n");
      }
      else
      {
         Serial.write("ok\n");
      }
   }
}

I used Android Studio for developing the mobile App. It comes with everything you need to code and build apps for Android devices.

I'll just point out the important parts and concepts of my code, instead of explaining it line-by-line. First of all, as our app will use the Bluetooth resource, we must declare it in the App's manifest (so the user can consent and allow the app to do so, at installation time). To simplify things, our application will only list previously paired Bluetooth devices (so we don't need to handle device discovery and pairing processes). Whenever we select a Bluetooth device in the list, the app will try to establish a socket-like connection with it. This connection will be always re-established at the Activity's onResume event and should be always closed at the onPause event ("Release system resources, such as broadcast receivers, handles to sensors (like GPS), or any resources that may affect battery life while your activity is paused and the user does not need them.", from Pausing and Resuming an Activity). Once our socket connection is set, we can read (receive) and write (send) data through it's InputStream and OutputStream. As data reception is "asynchronous" in terms that we do not trigger it, it just happens, we should create a background thread for continuously checking if anything hits the InputStream. Having said that, all code not related to these features is mere decoration.

I'll put the whole Main class code here, but everything is packed up and available to download at the end of this post.

package com.carr3r.arduinobluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.text.Editable;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;


public class MainActivity extends ActionBarActivity implements AdapterView.OnItemSelectedListener {

    private static final String TAG = "carr3r.arduinobluetooth";

    private BluetoothAdapter btAdapter = null;
    private BluetoothSocket btSocket = null;
    private OutputStream outStream = null;
    private InputStream inStream = null;

    private String MACaddress = null;
    private boolean hasConnected = false;

    private Thread workerThread;
    byte[] readBuffer;
    int readBufferPosition;
    volatile boolean stopWorker;

    // SPP UUID service
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btAdapter = BluetoothAdapter.getDefaultAdapter();

        if (btAdapter == null) {
            Log.e(TAG, "Bluetooth not supported");
            add2terminal("Bluetooth not supported");
        } else {
            if (btAdapter.isEnabled()) {

                Log.i(TAG, "Bluetooth is ON");
                add2terminal("Bluetooth is ON");

            } else {
                //Prompt user to turn on Bluetooth
                Log.i(TAG, "Bluetooth is OFF. Switch it on?");
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(enableBtIntent, 1);
            }
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        if (btAdapter != null) {
            btAdapter.cancelDiscovery();

            Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
            // If there are paired devices
            if (pairedDevices != null && pairedDevices.size() > 0) {


                List<String> list = new ArrayList<String>();
                list.add("");

                // Loop through paired devices
                for (BluetoothDevice device : pairedDevices) {
                    list.add(device.getName() + " (" + device.getAddress() + ")");
                    Log.i(TAG, "Device found: " + device.getName() + " (" + device.getAddress() + ")");
                }

                ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list);
                dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                final Spinner deviceList = (Spinner) findViewById(R.id.lstBluetoothDevice);
                deviceList.setAdapter(dataAdapter);
                deviceList.setClickable(true);
                deviceList.setOnItemSelectedListener(this);
            }

            if (hasConnected)
                connect2device(MACaddress);
        }

    }

    private void connect2device(String mac) {
        BluetoothDevice device = btAdapter.getRemoteDevice(mac);

        try {

            if (Build.VERSION.SDK_INT >= 10) {
                try {
                    Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[]{UUID.class});
                    btSocket = (BluetoothSocket) m.invoke(device, MY_UUID);
                } catch (Exception e) {
                    Log.e(TAG, e.getMessage());
                }
            } else
                btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }

        if (btSocket != null)
            try {
                btSocket.connect();
            } catch (IOException e1) {
                Log.e(TAG, e1.getMessage());
                try {
                    btSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, e2.getMessage());
                }
                btSocket = null;
            }

        if (btSocket != null) {
            try {
                outStream = btSocket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }

            try {
                inStream = btSocket.getInputStream();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }

        if (inStream != null) {
            startListeningThread();
            hasConnected = true;
            MACaddress = mac;

            add2terminal("Connected to " + MACaddress + "!");
        } else {
            hasConnected = false;
            btSocket = null;
            outStream = null;
            inStream = null;

            add2terminal("It was not possible to establish a connection to " + mac);
        }

    }

    void startListeningThread() {
        final Handler handler = new Handler();
        final byte delimiter = 10; //This is the ASCII code for a newline character

        stopWorker = false;
        readBufferPosition = 0;
        readBuffer = new byte[1024];
        workerThread = new Thread(new Runnable() {
            public void run() {
                while (!Thread.currentThread().isInterrupted() && !stopWorker) {
                    try {
                        int bytesAvailable = inStream.available();
                        if (bytesAvailable > 0) {
                            byte[] packetBytes = new byte[bytesAvailable];
                            inStream.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;

                                    handler.post(new Runnable() {
                                        public void run() {
                                            add2terminal("< " + data);
                                        }
                                    });
                                } else {
                                    readBuffer[readBufferPosition++] = b;
                                }
                            }
                        }
                    } catch (IOException ex) {
                        stopWorker = true;
                    }
                }
            }
        });

        workerThread.start();
    }

    @Override
    public void onPause() {
        super.onPause();

        if (btAdapter != null) {

            if (outStream != null) {
                try {
                    outStream.close();
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage());
                }
            }

            if (inStream != null) {
                try {
                    inStream.close();
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage());
                }
            }

            if (btSocket != null)
                try {
                    btSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage());
                }

            stopWorker = true;
        }
    }

    private void sendData(String message) {

        if (outStream != null) {
            byte[] msgBuffer = message.getBytes();
            try {
                outStream.write(msgBuffer);
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }

    @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;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        return id == R.id.action_settings ? true : super.onOptionsItemSelected(item);
    }

    public void add2terminal(String message) {
        TextView term = (TextView) findViewById(R.id.edtTerminal);
        term.append(message + "\n");
    }

    @Override
    public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
        String device = ((Spinner) findViewById(R.id.lstBluetoothDevice)).getSelectedItem().toString();
        if (device.length() > 0) {
            String selectedMac = device.substring(device.indexOf(" (") + 2).replace(")", "");
            add2terminal("Trying to connect to " + selectedMac);
            connect2device(selectedMac);
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> adapterView) {

    }

    public void onBtnSendClick(View v) {
        EditText edit = (EditText) findViewById(R.id.edtSend);
        Editable text = edit.getText();
        if (text != null && text.length() > 0) {
            sendData(text.toString() + "\n");
            add2terminal("> " + text.toString());
            text.clear();
        }
    }
} 

A screenshot of the app running is shown below. At least in my MotoG, it works like a charm.

 

undefined

 

Source code: