В статье 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.
Для наглядности, к плате 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:
Нас интересует устройство 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 + ".\n\nВ переменной address у вас прописан 00:00:00:00:00:00, вам необходимо прописать реальный MAC-адрес Bluetooth модуля"; msg = msg + ".\n\nПроверьте поддержку SPP UUID: " + MY_UUID.toString() + " на Bluetooth модуле, к которому вы подключаетесь.\n\n"; 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("\r\n"); // определяем символы конца строки 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 используется для формирования строки из принятых данных. После, происходит поиск конца строки с символами \r\n, и если они найдены, то строка отображается на активити и обьект sb очищается, чтобы не произошло склейка с последующими принятыми данными.
К статье прилагаются скомпилированные файлы для Android: bluetooth1.apk и bluetooth2.apk, а также исходники проекта для Arduino IDE и Eclipse
- arduino64.rar (1371 Кб)
- Bluetooth1.apk (161 Кб)
- Bluetooth2.apk (161 Кб)
Комментарии (50) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
Подключаю через bluetooth bee. Данные не принимаются, диод не горит. Но во время посылки данных еле заметно мигает ТХ на модуле wireless proto shield. Помогите плиз. У меня ардуино леонардо
И, кстати, установил просто вашу версию программы (изменив MAC адрес блютуз модуля) и тоже ерунда какая то...не принимает сообщения мой телефон
Делать подключение в OnResume не есть хорошо, тогда смысла нет от CheckBT.
Плюс неплохо было бы добавить способ подключения модулем к устройству в режиме мастера, так как телефон может тупо не отображать устройства неизвестного типа.
Статья отличная и очень полезная, так что её стоит поправить
Весь проект с исходниками есть на гитхабе, можете форкнуть и поправить как нужно и как правильно.
Теперь - читать книжки как это все можно использовать. Еще раз спасибо
Как андроид поймет, что передавать надо с такой скоростью?
Очень полезная статья. У меня возникла проблема. Использую Bluetooth HC-05. При передаче данных если увеличить расстояние между смартфоном и HC-05 прекращается поток данных (все логично). Далее если уменьшить расстояние, то поток не возобновляется и закрытие соединения на смартфоне не приводит к закрытию соединения на Bluetooth-модуле.
mConnectedThread.interrupt();
}
onResume();
Думаю эту проверочку и перезапуск потока куда то вставит. Вроде работает.
Пойдет?
Приложение сейчас активно переписывается, вся логика работы и соединение полностью уже переделаны, убрана необходимость вбивания MAC-адреса, теперь в приложении происходит поиск и подключение. В скором времени приложение будет выложено здесь и на Goolge Play
Хотя в конструкторе статус сокета CONNECTED и в методе write mmOutStream тоже CONNECTED. Все скопировал как у вас, но среда android studio. В чем проблема, как ее решить
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.inapps.akseldom.akseldom.Bluetooth$ConnectedThread.write(byte[])' on a null object reference
Перепробовал всё что пришло в головуб идей уже кончились. Как эту ошибку устранить,
Скажем, если я на ардуине намерен считвать АЦП и выплёвывать в TX значение байта. Ведь имею право? Как тогда на андроиде заставить не дожидаться окончания передачи пакета, а сразу на каждый байт реагировать?