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

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


Прикрепленные файлы:

Добавить комментарий

Ваш адрес email не будет опубликован.