Android и Arduino. Обмен данными

В предыдущих двух частях мы рассмотрели вопрос передачи информации от Android-устройства в плату Arduino, и обратную задачу: передача информации от Arduino платы на устройство с Android. Настало время для объединения этих двух методик, чтобы получить полноценный двухсторонний обмен информацией между Android и Arduino.

Для этого, мы соберем простенький проект с использованием ультразвукового дальномера и пьезо-буззера.

Ультразвуковой дальномер

Алгоритм работы

На экране устройства с Android отображается активити с кнопкой «Измерить», при нажатии на которую, на плату Arduino приходит соответствующая команда. Плата Arduino обрабатывает команду и начинает цикл измерений, после чего вычисляется средняя дистанция до препятствия в сантиметрах. Данное расстояние до обьекта, передается обратно в Android-устройство, где отображается в виде текста, а также на ползунке (ProgressBar).
В Android также происходит обработка данных: если расстояние до обьекта меньше 20 см, то происходит передача управляющего сигнала на Arduino для включения буззера. Это естественно можно было бы сделать и в коде Arduino, но для наглядности я возложил эту задачу на плечи Android устройства. В общем получился небольшой парктроник.

Итак, для начала необходимо определится с управляющими командами. Они должны быть одинаково определены и в Arduino и в Android устройстве. Я выбрал следующие числа:
1 — команда разрешения передачи
2 — команда запрета передачи
3 — команда включения буззера

С первыми двумя командами получилось немного запутано, т.к. я не смог заставить Android корректно принимать один посыл с данными (пробовал и в цикле передавать и по времени, но Android упорно не хочет принимать данные, подозреваю, что это связанно с ADB и при использовании Accessory Mode таких проблем быть не должно). Поэтому когда Arduino принял команду 1 (разрешение передачи) он производит замер расстояния и включает беспрерывную передачу данных в цикле. Как только Android принял данные, он передает к Arduino команду 2, чтобы тот остановил передачу данных.

Программа для Arduino

Скетч для Arduino:

 #include  #include   // Adb connection. Connection * connection;             // Adb connection.  #define COMMAND_SEND_TRUE  1   // команда разрешения передачи #define COMMAND_SEND_FALSE 2   // команда запрета передачи #define COMMAND_PLAY_BEEP  3   // команда включения буззера  const int numOfReadings = 10;        // кол-во замеров (элементов массива) int readings[numOfReadings];         // значения измерений в массиве int arrayIndex = 0;                  // индекс элемента в массиве int total = 0;                       // всего значений int averageDistance = 0;             // средняя дистанция  // настройка пинов и переменных для УЗ датчика int echoPin = 2;                     // DYP_ME007 ECHO pin int initPin = 3;                     // DYP_ME007 TRIG pin int BeeperPin = 8;                   // pin буззера unsigned long pulseTime = 0;         // длительность пульса в микросекундах unsigned long distance = 0;          // расстояние в (см)  boolean SendToAndroid = false;  void setup() {   pinMode(initPin, OUTPUT);   pinMode(echoPin, INPUT);   pinMode(BeeperPin, OUTPUT);  // Буззер    // формируем массив   for (int thisReading = 0; thisReading < numOfReadings; thisReading++) {     readings[thisReading] = 0;   }      Serial.begin(115200);      // Инициализация подсистемы ADB.     ADB::init();    // Open an ADB stream to the phone's shell. Auto-reconnect. Use any unused port number eg:4568   connection = ADB::addConnection("tcp:4568", true, adbEventHandler);      }   void loop() {   if(SendToAndroid == true) makeDimension();   ADB::poll();    // Poll the ADB subsystem. }  void adbEventHandler(Connection * connection, adb_eventType event, uint16_t length, uint8_t * data) {   if (event == ADB_CONNECTION_RECEIVE)   // Если приняли данные   {     Serial.print("data:"); // Вывод в Serial Monitor для отладки     Serial.println(data[0],DEC);     if((data[0]) == COMMAND_SEND_TRUE) SendToAndroid = true;   // Флаг, что надо вкл. передачу данных     else if ((data[0]) == COMMAND_SEND_FALSE) SendToAndroid = false;  //Флаг, что данные приняты и откл. передачу данных     else if ((data[0]) == COMMAND_PLAY_BEEP) playBeep();    }   else if (event == ADB_CONNECTION_OPEN) Serial.println("ADB connection open");   else if (event == ADB_CONNECTION_CLOSE) Serial.println("ADB connection close");   else {     Serial.println(event);   } }  void makeDimension() {   for (int i = 0; i < numOfReadings; i++) {      digitalWrite(initPin, HIGH);              // посылаем импульс длительностью 10мс     delayMicroseconds(10);      digitalWrite(initPin, LOW);          pulseTime = pulseIn(echoPin, HIGH);             // Считываем длительность пришедшего импульса     distance = pulseTime/58;                        // Дистанция = (длит. импульса / 58) см     total= total - readings[arrayIndex];     readings[arrayIndex] = distance;     total= total + readings[arrayIndex];     arrayIndex = arrayIndex + 1;     // После того, как достигли последнего элемента, начинаем сначала     if (arrayIndex >= numOfReadings)  {       arrayIndex = 0;     }     //Serial.println(distance, DEC);   }    averageDistance = total / numOfReadings;      // вычисляем среднюю дистанцию    //Serial.println(averageDistance, DEC);   connection->write(2,(uint8_t*)&averageDistance);  // Отсылаем 2 байта   delay(10); }  void playBeep() {   for (int j = 0; j < 10; j++) {      analogWrite(BeeperPin, 20);      delay(50);     analogWrite(BeeperPin, 0);     delay(150);    } } 

В самом начале мы определяем 3 константы — это команды для передачи сообщений между устройствами: COMMAND_SEND_TRUE = 1, COMMAND_SEND_FALSE = 2, COMMAND_PLAY_BEEP = 3

Обработчик adbEventHandler() вызывается каждый раз при принятии данных и при наступлении других событий от ADB (открытие и закрытие соединения).

Функция makeDimension() производит 10 замеров расстояний, а затем вычисляет по ним среднее значение, которое через команду connection->write >() 2-мя байтами отправляется в Android устройство.

С функцией playBeep() все просто — она предназначена для проигрывания 10-ти коротких звуков через буззер.

Программа для Android

Наше окно активити будет состоять из следующих ключевых элементов:
кнопка (Button) — для посылки команды измерения расстояния
текстовое поле (TextView) — для отображения полученного расстояния
прогресс-бар (ProgressBar) — для визуального отображения расстояния (максимум — 500 см)
иконка соединения (ImageView) — отображается при активном соединении с Android устройством.

Активити

XML файл данного активити см. в прикрепленных файлах

Файл для главного Activity содержит следующий код:

 package com.example.arduino54;  import java.io.IOException;  import org.microbridge.server.Server; import org.microbridge.server.AbstractServerListener;  import com.example.arduino54.R;  import android.os.AsyncTask; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Button;  public class MainActivity extends Activity {          private int Distance = 0;     public final String APP_NAME = "arduino54";          public final byte COMMAND_SEND_TRUE = 1;    // Команда разрешения передачи     public final byte COMMAND_SEND_FALSE = 2;   // Команда запрета передачи     public final byte COMMAND_PLAY_BEEP = 3;   	// Команда включения буззера          public final int SYS_COMMAND_DATA = 0;   		// Внутренняя команда: передача данных     public final int SYS_COMMAND_CONNECTED = 1;		// Внутренняя команда: соединение установлено     public final int SYS_COMMAND_DISCONNECTED = 2;	// Внутренняя команда: соединение потеряно          Server server = null;     ImageView connectedImage;      @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);                         // Создаем TCP сервер (на основе сервера MicroBridge LightWeight)         try         {         	server = new Server(4568); //Этот же порт необходимо использовать и на ADK-плате         	server.start();                     } catch (IOException e)         {         	Log.e(APP_NAME, "Unable to start TCP server", e);         	System.exit(-1);         }                  connectedImage = (ImageView) findViewById(R.id.imageConnected);         connectedImage.setAlpha(20);                   Button Button1 = (Button)findViewById(R.id.button1);         Button1.setOnClickListener(new View.OnClickListener() {         	public void onClick(View v) {         		try         		{         			server.send(new byte[] {(byte) COMMAND_SEND_TRUE});	//Посылаем данные         			//Log.d(APP_NAME, "data_send:"+bSend);         		} catch (IOException e)         		{         			Log.e(APP_NAME, "Problem sending TCP message", e);         		}	         	} 		});                             server.addListener(new AbstractServerListener() {              @Override             public void onReceive(org.microbridge.server.Client client, byte[] data)             { 				Log.d(APP_NAME, "data0:"+data[0]+"; data1:"+data[1]); 				if (data.length<2) Log.e(APP_NAME, "Размер данных менее 2-х байт:"+data.length); 				else { 					try 					{ 						server.send(new byte[] {(byte) COMMAND_SEND_FALSE});	//Посылаем данные 					} catch (IOException e) 					{ 						Log.e(APP_NAME, "Problem sending TCP message", e); 					}	 				}  				Distance = ((data[1] << 8) | (data[0] & 0xFF));	// Формируем слово из 2-х байт                      //Any update to UI can not be carried out in a non UI thread like the one used                 //for Server. Hence runOnUIThread is used.                 runOnUiThread(new Runnable() {                     //@Override                     public void run() {                         new UpdateData().execute(Distance,SYS_COMMAND_DATA);                     }                 });             }                          //@Override             public void onClientConnect(org.microbridge.server.Server server, org.microbridge.server.Client client){             	Log.d(APP_NAME, "ClientConnected");             	runOnUiThread(new Runnable() {                     public void run() {                         new UpdateData().execute(0,SYS_COMMAND_CONNECTED);                     }                 });             }                          public void onClientDisconnect(org.microbridge.server.Server server, org.microbridge.server.Client client){             	Log.d(APP_NAME, "ClientDisconnected");             	runOnUiThread(new Runnable() {                     public void run() {                         new UpdateData().execute(0,SYS_COMMAND_DISCONNECTED);                     }                 });             }                         });          }          @Override     protected void onDestroy (){     	super.onDestroy();     	server.stop();     }          class UpdateData extends AsyncTask< Integer, Integer, Integer[]> {         // Called to initiate the background activity         @Override         protected Integer[] doInBackground(Integer... ArdState) {             if((ArdState[0] < 20) && (ArdState[0] != 0)){	//Если расстояние меньше 20см             	try 				{ 					server.send(new byte[] {(byte) COMMAND_PLAY_BEEP}); 				} catch (IOException e) 				{ 					Log.e(APP_NAME, "Problem sending TCP message", e); 				}	             }         	return (ArdState);  //Возвращаем в onPostExecute()         }                  @Override         protected void onProgressUpdate(Integer... values) {             super.onProgressUpdate(values);             // Not used in this case         }                  @Override         protected void onPostExecute(Integer... result) {      	         	Log.d(APP_NAME, "onPostExecute[0]:"+result[0]);         	Log.d(APP_NAME, "onPostExecute[1]:"+result[1]);         	          	if(result[1] == 1){         		connectedImage.setAlpha(255);         	}         	else if(result[1] == 2){         		connectedImage.setAlpha(20);         	}         	                                  TextView txt_Distance_Arduino = (TextView) findViewById(R.id.textDistance);             txt_Distance_Arduino.setText(String.valueOf(result[0]+" см"));    // Выводим на activity дистанцию                                    ProgressBar mProgressBar = (ProgressBar)findViewById(R.id.progressBar1);             mProgressBar.setProgress(result[0]);         }     } } 

Здесь мы для класса server определяем метод server.addListener(new AbstractServerListener() {}), а также: onReceive(), onClientConnect() и onClientDisconnect() который вызывается при получении данных от сервера MicroBridge, при соединении и разьединении.

На кнопке Button1 мы вешаем обработчик события нажатия setOnClickListener(). При нажатии на кнопку вызывается данный метод и посылает на плату Arduino команду COMMAND_SEND_TRUE, по которой Arduino производит измерение расстояния и передачу значения расстояния.

В методе onReceive(), как только мы приняли данные, то мы сразу же отсылаем обратно команду COMMAND_SEND_FALSE, для того, чтобы Arduino выключил передачу пакетов. Об этом алгоритме я писал выше.

Обратите внимание, что для передачи данных отдельному потоку, мы используем внутренние системные команды SYS_COMMAND_DATA, SYS_COMMAND_CONNECTED и SYS_COMMAND_DISCONNECTED . Команды передаются 2-м элементом массива, а в первом элементе содержится измеренное расстояние, полученное от Arduino.

При срабатывании события onClientConnect(), создается новый поток в который передается массив с командой SYS_COMMAND_CONNECTED (в нашем случае 0), и в методе onPostExecute() путем установки значения Alpha в максимальное 255, происходит отображения иконки соединения. При поступлении команды SYS_COMMAND_DISCONNECTED устанавливается Alpha в значение 20, иконка становится блеклой и ее почти не видно, это означает что соединение не установлено. Прозрачность Alpha устанавливается методом setAlpha(int).

Когда поток принимает данные из метода onReceive, то в методе doInBackground() происходит сравнение условия, и если расстояние не рано нулю и меньше 20 см, то методом server.send() посылается команда COMMAND_PLAY_BEEP для включения буззера на плате Arduino.

В методе onPostExecute() происходит вывод UI элементов для отображения численного значения расстояния и полоски на прогрессбаре.

Фото макетной платы

В прикрепленном файле вы можете скачать проекты для Arduino и Android, а также все необходимые библиотеки