Передача данных по Bluetooth между Android и Arduino

В статье Arduino и Bluetooth был рассмотрен один из способов передачи информации между Android-устройством и ПК по Bluetooth-соединению. Там же, в двух словах было упомянуто и Android-устройство, но для принятия и передачи данных использовался Android Bluetooth терминал. Однако, для реальных устройств необходима полноценная программа (не будем же мы управлять тем же роботом из терминала…), написанная для Android’а. В данной статье хотелось бы затронуть тему программного обеспечения для работы с Bluetooth, с применением языка Java и среды разработки Eclipse. Установка и настройка Eclipse хорошо описана в этой статье: Android и Arduino. Программное обеспечение.

Arduino

Я буду использовать Bluetooth модуль HC-06, однако для других модулей HC-04, HC-05 и т.п. схема подключения такая же (за исключением светодиода). Плата Arduino Nano V3.

Подключение Bluetooth модуля HC-06 к Arduino

Для наглядности, к плате Arduino я подключил красный светодиод, к 12-пину, но можно использовать и встроенный LED (обычно 13 пин).

Скетч для Arduino следующий:

 char incomingByte;  // входящие данные int  LED = 12;      // LED подключен к 12 пину  void setup() {   Serial.begin(9600); // инициализация порта   pinMode(LED, OUTPUT);   Serial.println("Press 1 to LED ON or 0 to LED OFF..."); }  void loop() {   if (Serial.available() > 0) {  //если пришли данные     incomingByte = Serial.read(); // считываем байт     if(incomingByte == '0') {        digitalWrite(LED, LOW);  // если 1, то выключаем LED        Serial.println("LED OFF. Press 1 to LED ON!");  // и выводим обратно сообщение     }     if(incomingByte == '1') {        digitalWrite(LED, HIGH); // если 0, то включаем LED        Serial.println("LED ON. Press 0 to LED OFF!");     }   } } 

Программа работает очень просто. После запуска или сброса устройства, в последовательный порт выводится сообщение с предложением нажать 1 или 0. В зависимости от нажатой (принятой) цифры светодиод будет загораться или гаснуть. В общем программа абсолютно такая же как и в статье: Arduino и Bluetooth.

Теперь, что касается Android. Мы рассмотрим два примера, в первом мы будем передавать данные от Android-устройства к arduino, а во втором примере мы рассмотрим двусторонний обмен данными между устройствами. Второй пример сложнее и в части понимания и по сложности кода, т.к. используются потоки (thread).

Мы будем использовать Java код, с явным указанием MAC-адреса устройства, к которому мы будем подключаться. Т.к. если делать интерфейс обнаружения Bluetooth-устройств, их выбора, подключения к ним и т.д., то код будет очень большой и для некоторых читателей труднопонимаем. Но для тех, кому интересно могут посмотреть стандартный пример Bluetooth Chat.

Узнать MAC-адрес можно к примеру в программе для Android’а: Bluetooth Terminal:

MAC-адрес

Нас интересует устройство BOLUTEK (наш модуль HC-06, подключенный к Arduino), его MAC адрес: 00:15:FF:F2:19:4C. Его и надо будет в дальнейшем прописать в программе.

Android — передаем данные в Arduino

Первая программа очень простая, главное окно активити будет содержать 2 кнопки: включить LED и выключить LED. При нажатии на кнопку включения LED, по Bluetooth будет передаваться «1», при нажатии на выключение LED — «0».

Главное активити

В файле манифеста необходимо прописать 2 строки разрешения работы с Bluetooth:
 

Сам код главного активити:

 package com.example.bluetooth1;  import java.io.IOException; import java.io.OutputStream; import java.util.UUID;   import com.example.bluetooth1.R;   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.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast;   public class MainActivity extends Activity {   private static final String TAG = "bluetooth1";       Button btnOn, btnOff;       private static final int REQUEST_ENABLE_BT = 1;   private BluetoothAdapter btAdapter = null;   private BluetoothSocket btSocket = null;   private OutputStream outStream = null;       // SPP UUID сервиса    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");     // MAC-адрес Bluetooth модуля   private static String address = "00:15:FF:F2:19:4C";       /** Called when the activity is first created. */   @Override   public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);       setContentView(R.layout.activity_main);       btnOn = (Button) findViewById(R.id.btnOn);     btnOff = (Button) findViewById(R.id.btnOff);           btAdapter = BluetoothAdapter.getDefaultAdapter();     checkBTState();       btnOn.setOnClickListener(new OnClickListener() {       public void onClick(View v) {         sendData("1");         Toast.makeText(getBaseContext(), "Включаем LED", Toast.LENGTH_SHORT).show();       }     });       btnOff.setOnClickListener(new OnClickListener() {       public void onClick(View v) {         sendData("0");         Toast.makeText(getBaseContext(), "Выключаем LED", Toast.LENGTH_SHORT).show();       }     });   }       @Override   public void onResume() {     super.onResume();       Log.d(TAG, "...onResume - попытка соединения...");         // Set up a pointer to the remote node using it's address.     BluetoothDevice device = btAdapter.getRemoteDevice(address);         // Two things are needed to make a connection:     //   A MAC address, which we got above.     //   A Service ID or UUID.  In this case we are using the     //     UUID for SPP.     try {       btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);     } catch (IOException e) {       errorExit("Fatal Error", "In onResume() and socket create failed: " + e.getMessage() + ".");     }         // Discovery is resource intensive.  Make sure it isn't going on     // when you attempt to connect and pass your message.     btAdapter.cancelDiscovery();         // Establish the connection.  This will block until it connects.     Log.d(TAG, "...Соединяемся...");     try {       btSocket.connect();       Log.d(TAG, "...Соединение установлено и готово к передачи данных...");     } catch (IOException e) {       try {         btSocket.close();       } catch (IOException e2) {         errorExit("Fatal Error", "In onResume() and unable to close socket during connection failure" + e2.getMessage() + ".");       }     }           // Create a data stream so we can talk to server.     Log.d(TAG, "...Создание Socket...");       try {       outStream = btSocket.getOutputStream();     } catch (IOException e) {       errorExit("Fatal Error", "In onResume() and output stream creation failed:" + e.getMessage() + ".");     }   }     @Override   public void onPause() {     super.onPause();       Log.d(TAG, "...In onPause()...");       if (outStream != null) {       try {         outStream.flush();       } catch (IOException e) {         errorExit("Fatal Error", "In onPause() and failed to flush output stream: " + e.getMessage() + ".");       }     }       try     {       btSocket.close();     } catch (IOException e2) {       errorExit("Fatal Error", "In onPause() and failed to close socket." + e2.getMessage() + ".");     }   }       private void checkBTState() {     // Check for Bluetooth support and then check to make sure it is turned on     // Emulator doesn't support Bluetooth and will return null     if(btAdapter==null) {        errorExit("Fatal Error", "Bluetooth не поддерживается");     } else {       if (btAdapter.isEnabled()) {         Log.d(TAG, "...Bluetooth включен...");       } else {         //Prompt user to turn on Bluetooth         Intent enableBtIntent = new Intent(btAdapter.ACTION_REQUEST_ENABLE);         startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);       }     }   }     private void errorExit(String title, String message){     Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();     finish();   }     private void sendData(String message) {     byte[] msgBuffer = message.getBytes();       Log.d(TAG, "...Посылаем данные: " + message + "...");       try {       outStream.write(msgBuffer);     } catch (IOException e) {       String msg = "In onResume() and an exception occurred during write: " + e.getMessage();       if (address.equals("00:00:00:00:00:00"))          msg = msg + ".nnВ переменной address у вас прописан 00:00:00:00:00:00, вам необходимо прописать реальный MAC-адрес Bluetooth модуля";       	msg = msg +  ".nnПроверьте поддержку SPP UUID: " + MY_UUID.toString() + " на Bluetooth модуле, к которому вы подключаетесь.nn";               	errorExit("Fatal Error", msg);            }   } } 

Данный код найден на одном из зарубежных блогов и слегка модернизирован. Как видно выше, на кнопки мы вешаем обработчики событий. При нажатии на кнопку передается строка 1 или 0 через sendData() в буфер Bluetooth адаптера. Полный проект с исходными кодами приведен ниже. Для работы программы, необходим Android не ниже версии API15, т.е. 4.0.3 и выше.

Android — прием и передача данных к Arduino

А вот здесь пришлось повозиться. Дело в том, что в Android’е для приема данных от какого-либо устройства необходимо создавать отдельный фоновый поток, чтобы у нас не зависало основное активити. Для этого мы задействуем thread и все данные будут приниматься в отдельном потоке.

На окно главного активити мы добавим новый элемент TextView, который будет служить для отображения принятых данных от Arduino. Сам java-код главного активити я постарался хорошо прокомментировать, чтобы сделать его удобочитаемым:

 package com.example.bluetooth2;  import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.UUID;   import com.example.bluetooth2.R;   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.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast;   public class MainActivity extends Activity {   private static final String TAG = "bluetooth2";       Button btnOn, btnOff;   TextView txtArduino;   Handler h;       private static final int REQUEST_ENABLE_BT = 1;   final int RECIEVE_MESSAGE = 1;		// Статус для Handler   private BluetoothAdapter btAdapter = null;   private BluetoothSocket btSocket = null;   private StringBuilder sb = new StringBuilder();      private ConnectedThread mConnectedThread;       // SPP UUID сервиса    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");     // MAC-адрес Bluetooth модуля   private static String address = "00:15:FF:F2:19:4C";       /** Called when the activity is first created. */   @Override   public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);       setContentView(R.layout.activity_main);       btnOn = (Button) findViewById(R.id.btnOn);					// кнопка включения     btnOff = (Button) findViewById(R.id.btnOff);				// кнопка выключения     txtArduino = (TextView) findViewById(R.id.txtArduino);		// для вывода текста, полученного от Arduino          h = new Handler() {     	public void handleMessage(android.os.Message msg) {     		switch (msg.what) {             case RECIEVE_MESSAGE:													// если приняли сообщение в Handler             	byte[] readBuf = (byte[]) msg.obj;             	String strIncom = new String(readBuf, 0, msg.arg1);             	sb.append(strIncom);												// формируем строку             	int endOfLineIndex = sb.indexOf("rn");							// определяем символы конца строки             	if (endOfLineIndex > 0) { 											// если встречаем конец строки,             		String sbprint = sb.substring(0, endOfLineIndex);				// то извлекаем строку                     sb.delete(0, sb.length());										// и очищаем sb                 	txtArduino.setText("Ответ от Arduino: " + sbprint); 	        // обновляем TextView                 	btnOff.setEnabled(true);                 	btnOn.setEnabled(true);                  }             	//Log.d(TAG, "...Строка:"+ sb.toString() +  "Байт:" + msg.arg1 + "...");             	break;     		}         }; 	};           btAdapter = BluetoothAdapter.getDefaultAdapter();		// получаем локальный Bluetooth адаптер     checkBTState();       btnOn.setOnClickListener(new OnClickListener() {		// определяем обработчик при нажатии на кнопку       public void onClick(View v) {     	btnOn.setEnabled(false);     	mConnectedThread.write("1");	// Отправляем через Bluetooth цифру 1         //Toast.makeText(getBaseContext(), "Включаем LED", Toast.LENGTH_SHORT).show();       }     });       btnOff.setOnClickListener(new OnClickListener() {       public void onClick(View v) {     	btnOff.setEnabled(false);       	mConnectedThread.write("0");	// Отправляем через Bluetooth цифру 0         //Toast.makeText(getBaseContext(), "Выключаем LED", Toast.LENGTH_SHORT).show();       }     });   }       @Override   public void onResume() {     super.onResume();       Log.d(TAG, "...onResume - попытка соединения...");         // Set up a pointer to the remote node using it's address.     BluetoothDevice device = btAdapter.getRemoteDevice(address);         // Two things are needed to make a connection:     //   A MAC address, which we got above.     //   A Service ID or UUID.  In this case we are using the     //     UUID for SPP.     try {       btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);     } catch (IOException e) {       errorExit("Fatal Error", "In onResume() and socket create failed: " + e.getMessage() + ".");     }         // Discovery is resource intensive.  Make sure it isn't going on     // when you attempt to connect and pass your message.     btAdapter.cancelDiscovery();         // Establish the connection.  This will block until it connects.     Log.d(TAG, "...Соединяемся...");     try {       btSocket.connect();       Log.d(TAG, "...Соединение установлено и готово к передачи данных...");     } catch (IOException e) {       try {         btSocket.close();       } catch (IOException e2) {         errorExit("Fatal Error", "In onResume() and unable to close socket during connection failure" + e2.getMessage() + ".");       }     }           // Create a data stream so we can talk to server.     Log.d(TAG, "...Создание Socket...");         mConnectedThread = new ConnectedThread(btSocket);     mConnectedThread.start();   }     @Override   public void onPause() {     super.onPause();       Log.d(TAG, "...In onPause()...");        try     {       btSocket.close();     } catch (IOException e2) {       errorExit("Fatal Error", "In onPause() and failed to close socket." + e2.getMessage() + ".");     }   }       private void checkBTState() {     // Check for Bluetooth support and then check to make sure it is turned on     // Emulator doesn't support Bluetooth and will return null     if(btAdapter==null) {        errorExit("Fatal Error", "Bluetooth не поддерживается");     } else {       if (btAdapter.isEnabled()) {         Log.d(TAG, "...Bluetooth включен...");       } else {         //Prompt user to turn on Bluetooth         Intent enableBtIntent = new Intent(btAdapter.ACTION_REQUEST_ENABLE);         startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);       }     }   }     private void errorExit(String title, String message){     Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();     finish();   }     private class ConnectedThread extends Thread { 	    private final BluetoothSocket mmSocket; 	    private final InputStream mmInStream; 	    private final OutputStream mmOutStream; 	  	    public ConnectedThread(BluetoothSocket socket) { 	        mmSocket = socket; 	        InputStream tmpIn = null; 	        OutputStream tmpOut = null; 	  	        // Get the input and output streams, using temp objects because 	        // member streams are final 	        try { 	            tmpIn = socket.getInputStream(); 	            tmpOut = socket.getOutputStream(); 	        } catch (IOException e) { } 	  	        mmInStream = tmpIn; 	        mmOutStream = tmpOut; 	    } 	  	    public void run() { 	        byte[] buffer = new byte[256];  // buffer store for the stream 	        int bytes; // bytes returned from read()  	        // Keep listening to the InputStream until an exception occurs 	        while (true) { 	        	try { 	                // Read from the InputStream 	                bytes = mmInStream.read(buffer);		// Получаем кол-во байт и само собщение в байтовый массив "buffer"                     h.obtainMessage(RECIEVE_MESSAGE, bytes, -1, buffer).sendToTarget();		// Отправляем в очередь сообщений Handler 	            } catch (IOException e) { 	                break; 	            } 	        } 	    } 	  	    /* Call this from the main activity to send data to the remote device */ 	    public void write(String message) { 	    	Log.d(TAG, "...Данные для отправки: " + message + "..."); 	    	byte[] msgBuffer = message.getBytes(); 	    	try { 	            mmOutStream.write(msgBuffer); 	        } catch (IOException e) { 	            Log.d(TAG, "...Ошибка отправки данных: " + e.getMessage() + "...");      	          } 	    } 	  	    /* Call this from the main activity to shutdown the connection */ 	    public void cancel() { 	        try { 	            mmSocket.close(); 	        } catch (IOException e) { } 	    } 	} } 

В данном примере для отправки данных мы используем отдельный поток Thread. Тоже самое и для приема данных — метод run(). Также обратите внимание на класс Handler, который служит для организации очереди сообщений и их вывода в главное активити. Дело в том, что в фоновом потоке нельзя напрямую выводить что-либо в главное активити, т.к. это приведет к «крашу» программы.
Класс StringBuilder используется для формирования строки из принятых данных. После, происходит поиск конца строки с символами rn, и если они найдены, то строка отображается на активити и обьект sb очищается, чтобы не произошло склейка с последующими принятыми данными.

К статье прилагаются скомпилированные файлы для Android: bluetooth1.apk и bluetooth2.apk, а также исходники проекта для Arduino IDE и Eclipse