В прошлой статье РОБОТ на базе: android, arduino, bluetooth. Начало была предложена общая схема робота и представлена технология передачи и приема данных между андроидом и ардуино. А в ее завершении приведен список заказанных деталей и модулей. Детали получены (рис.1), комментарии учтены, приступим к созданию первого робота – рефлексного робота.
Рисунок 1
Статья 1. РОБОТ на базе: android, arduino, bluetooth. Начало
Статья 2. РОБОТ на базе: android, arduino, bluetooth. Рефлексный. Часть 2.
Предполагается, что человек читающий статью уже имеет представление о:
-Базовых понятиях электроники
-Предыдущей статье
Создать робота который выполняет следующий функционал:
-Имеет удаленное управление при помощи смартфона (передвижение вперед, назад, налево, направо)
-Передает на смартфон данные о расстоянии до объекта находящегося перед ним(на базе ультра звукового датчика)
-Имеет режим автономного управления: непрерывно перемещается по помещению, при встрече препятствий меняет направление своего движения, тем самым объезжая препятствие.
Наш мир является сложнейшей системой, в которой взаимодействуют между собой огромное количество объектов, подчиненных определенным законам физики, поэтому создание робота функционирующего в рамках этой системы, является очень трудоемкой задачей. Для упрощения процесса создания первого робота воспользуемся понятием абстрагирования среды(в которую помещен робот) и действий робота. В дальнейших статьях будем усложнять среду и соответственно действия робота.
Среда где будет обитать наш первый робот будет представлять собой двухмерный мир и обладать следующими характеристиками:
1) Полностью наблюдаемая, т.е. датчики робота предоставляют доступ к полной информации о состоянии среды в каждый момент времени. Полностью наблюдаемые варианты среды являются удобными, поскольку роботу не требуется поддерживать какое-либо внутреннее состояние для того, чтобы быть в курсе всего происходящего в этом мире.
2) Детерминированная. Если следующее состояние среды полностью определяется текущим состоянием и действием, выполненным роботом, то такая среда называется детерминированной; в противном случае она является стохастической.
3) Эпизодическая. В эпизодической проблемной среде опыт робота состоит из неразрывных эпизодов. Каждый эпизод включает в себя восприятие среды роботом, а затем выполнение одного действия. При этом крайне важно то, что следующий эпизод не зависит от действий, предпринятых в предыдущих эпизодах. В эпизодических вариантах среды выбор действия в каждом эпизоде зависит только от самого эпизода.
4) Статическая. Если среда может измениться в ходе того, как робот выбирает очередное действие, то такая среда называется динамической для данного робота; в противном случае она является статической.
5) Непрерывная — Различие между дискретными и непрерывными вариантами среды может относиться к состоянию среды, способу учета времени, а также восприятием и действиям агента. В нашем случае считается что состояние среды меняется непрерывно. К примеру игра в шахматы является дискретной, так как имеет конечное количество различных состояний.
6) Одноагентная это среда в которой находится один объект(робот), и другие объекты на него не влияют и не конкурируют с ним.
1) Движение – робот может передвигаться в двух направлениях(взад, вперед) и разворачиваться на месте(налево, направо)
2) Датчики робота (ультразвуковой сенсор), позволяет определить расстояние до объекта. Расстояние может быть определено от 0,02 метра до 4 метров.
Таким образом, определим, что создаваемый в этой статье робот является простым рефлексным роботом. Подобные роботы выбирают действия на основе текущего акта восприятия, игнорируя всю остальную историю актов восприятия.
Драйвер двигателей HG7881. Для управления двигателями робота необходимо устройство, которое бы преобразовывало управляющие сигналы малой мощности в токи, достаточные для управления моторами. Такое устройство называют драйвером двигателей.
HG7881 – это двухканальный драйвер двигателей, питание возможно от источника 2,5 – 12 В. Описание выходов драйвера:
Таблица 1
Для того чтобы заставить двигатели работать нужным нам образом на выводы (B-IA, B-IB, A-IA, A-IB) необходимо подавать логические сигналы (HIGH,LOW). Таблица истинности двигателей:
Таблица 2
Ультразвуковой сенсор измерения расстояния HC-SR04. Определяет расстояние до объекта, измеряя время отражения звуковой волны от объекта.
Сенсор излучает короткий ультразвуковой импульс (в момент времени 0), который отражается от объекта и принимается сенсором. Расстояние рассчитывается исходя из времени до получения эха и скорости звука в воздухе.
На вывод (Trig) подаётся импульс длительностью 10 мкс, ультразвуковой модуль излучает 8 пачек ультразвукового сигнала с частотой 40кГц и обнаруживает их эхо. Измеренное расстояние до объекта пропорционально ширине эха (Echo) и может быть рассчитано по формуле:
Ширина импульса/58 = расстояние в см.
Собираем платформу (рис.2).
Рисунок 2
Подключаем двигатели к драйверу (рис. 3). По два двигателя на один разъем драйвера, т.е. двигатели левой стороны платформы к разъему “Motor B”, двигатели правой стороны — “Motor A”. Управление платформой будет произведено аналогично гусеничной. При движении вперед и назад все двигатели работают синхронно в одном направлении, при повороте направо двигатели правой стороны платформы останавливаются, а левой двигаются синхронно, при повороте налево двигатели левой стороны останавливаются, а правой двигаются синхронно.
Рисунок 3
Прикручиваем верхнюю часть платформы. Соединяем драйвер двигателей, ардуино, аккумуляторы, БТ модуль и ультразвуковой сенсор к макетной плате (рис.4)
Рисунок 4
Схема подключения представлена на (рис.5). Питание ардуино, ультразвукового сенсора и драйвера двигателей (следовательно и самих двигателей) обеспечивают 4 подключенных последовательно аккумулятора (1,2 В., 2700 мА/ч), на БТ модуль питание подается от выхода ардуино 3,3 В.
Рисунок 5
Робот собран, необходимо его заставить выполнять команды, отправленные с андроида.
Загрузим скетч в ардуино, не забыв перед этим отключить питания от БТ модуля (в противном случае загрузить его не удастся):
Объявляем переменные: R_A_IA, R_A_IB – определяют номера выводов управляющих двигателем А (двигатели правой стороны), L_B_IA, L_B_IB — выводы управляющие двигателем B(двигатели левой стороны. Инициируем последовательное соединение и задаем скорость передачи данных в бит/c (бод) – 38400. Устанавливаем режим работы выводов управляющих двигателями – OUTPUT (выходы). Подаем на все выходы значение HIGH, что означает — двигатели отключены(таблица 2).
Определяем функции: go_forward(), go_back(), go_right(), go_left(), stop_robot(), которые запускают двигатели в прямом или обратном направлении вращения, тем самым приводя робота в движение – вперед, назад, направо, налево, стоп, соответственно.
В основном цикле программы происходит считывание и обработка данных полученных в последовательный порт от БТ модуля. В зависимости от полученной команды выполняется та или иная функция и по последовательному порту передается текст об ее выполнении.
Создаем новый проект «Android application project». Как было написано в прошлой статье, для работы с БТ необходимо выставить права на использование его нашим приложением. Для этого заходим в манифест, выбираем закладку Permissions, нажимаем add, далее Uses permission, и устанавливаем следующие права: android.permission.BLUETOOTH, android.permission.BLUETOOTH_ADMIN
Создаем основное activity, в res/layout/activity_main.xml поместим код:
Вот так будет выглядеть основное activity:
Текстовое поле «txtrobot», будет отображать всю необходимую нам информацию. Кнопки b0, b1, b2, b3, b4 будут отправлять команды в arduino (0, 1, 2, 3, 4)
Переходим в src/../MainActivity.java здесь и будет располагаться наш основной код.
В предыдущей статье на шаге 4, был представлен код позволяющий передавать и принимать данные по БТ. За основу возьмем этот код.
В состояния активити onPause() и onResume() добавим условие проверки существования БТ у андроида и определения включен ли он. В предыдущей статье это условие отсутствовало в связи, с чем при запуске приложения, если был отключен БТ, оно завершалось с ошибкой и только после этого предлагало включить БТ.
Объявим переменные для хранения кнопок:
Находим их по ID:
Напишем обработчики нажатия этих кнопок, для отправки команд:
Полный код приложения:
Данное приложение, позволяет управлять роботом при помощи андроида, отправляя команды по БТ на ардуино, и принимая текстовые ответы от него. Первая часть поставленной задачи выполнена.
Для работы с ультразвуковым сенсором, воспользуемся готовой библиотекой
ultrasonic-HC-SR04.zip
Распаковываем файлы и помещаем в каталог где расположен скетч
Подключаем библиотеку
Конструктор Ultrasonic принимает два параметра — номера выводов к которым подключены Trig и Echo, соответственно:
Получаем данные о расстоянии до объекта в сантиметрах:
Передаем данные на последовательный порт, для последующей передачи их через БТ модуль.
Символы «*» и «#» означают начало и конец передаваемого блока информации о расстоянии до объекта. Это необходимо для того чтобы четко отделять необходимые данные друг от друга, так как при их передачи часть теряется либо приходит с запозданием.
Полный скетч для загрузки в ардуино:
Добавим в основное активити кнопку «b5» (автоуправление). Код приведен ниже:
Таким образом, основное activity примет вид:
Объявим переменную b5:
И флаг позволяющий определить включен режим автоуправления или нет:
Находим ее по ID:
Создадим обработчик ее нажатия:
А также внесем изменения в обработчик кнопки «b0»(Стоп)
Осталось создать алгоритм позволяющий роботу самостоятельно перемещаться по помещению и объезжать препятствия.
Обработаем полученные данные о расстоянии до объекта отправленные ардуино. Если расстояние до объекта менее 50 см. то поворачиваем направо в противном случае едим прямо:
Ниже приведен полный код Activity:
Созданное приложение для андроида в связке с представленным скетчем ардуино, позволяет, как удаленно самостоятельно управлять роботом, так и включать режим автономного управление, при котором робот передвигается в прямом направлении и если требуется, объезжает препятствия.
Результатом проделанной работы является простейший рефлексный робот. Дальнейшее применение более сложных алгоритмов на базе приведенных шаблонных приложений и скетчей позволит создавать роботов основанных на модели, на цели, на полезности, обучающихся роботов и др.
К следующей статье я сделал заказ всего одного модуля:
ИТОГ: 524,65
В комментариях к предыдущей статье, хабра пользователь commanderxo порекомендовал не изобретать велосипед, а воспользоваться стандартным протоколом Firmata (протокол для обмена данными между ардуино и сервером). К сожалению работоспособной библиотеки, для андроида в связке с БТ, я не нашел. Написать свою библиотеку у меня не хватает времени и сил, поэтому в данной статье я продолжаю изобретать велосипед. Если кто из Хабра пользователей обладает информацией о такой библиотеке просьба поделиться.
Рисунок 1
Оглавление
Статья 1. РОБОТ на базе: android, arduino, bluetooth. Начало
Статья 2. РОБОТ на базе: android, arduino, bluetooth. Рефлексный. Часть 2.
Предполагается, что человек читающий статью уже имеет представление о:
-Базовых понятиях электроники
-Предыдущей статье
Постановка задачи
Создать робота который выполняет следующий функционал:
-Имеет удаленное управление при помощи смартфона (передвижение вперед, назад, налево, направо)
-Передает на смартфон данные о расстоянии до объекта находящегося перед ним(на базе ультра звукового датчика)
-Имеет режим автономного управления: непрерывно перемещается по помещению, при встрече препятствий меняет направление своего движения, тем самым объезжая препятствие.
Немного теории
Наш мир является сложнейшей системой, в которой взаимодействуют между собой огромное количество объектов, подчиненных определенным законам физики, поэтому создание робота функционирующего в рамках этой системы, является очень трудоемкой задачей. Для упрощения процесса создания первого робота воспользуемся понятием абстрагирования среды(в которую помещен робот) и действий робота. В дальнейших статьях будем усложнять среду и соответственно действия робота.
Абстрагирование среды
Среда где будет обитать наш первый робот будет представлять собой двухмерный мир и обладать следующими характеристиками:
1) Полностью наблюдаемая, т.е. датчики робота предоставляют доступ к полной информации о состоянии среды в каждый момент времени. Полностью наблюдаемые варианты среды являются удобными, поскольку роботу не требуется поддерживать какое-либо внутреннее состояние для того, чтобы быть в курсе всего происходящего в этом мире.
2) Детерминированная. Если следующее состояние среды полностью определяется текущим состоянием и действием, выполненным роботом, то такая среда называется детерминированной; в противном случае она является стохастической.
3) Эпизодическая. В эпизодической проблемной среде опыт робота состоит из неразрывных эпизодов. Каждый эпизод включает в себя восприятие среды роботом, а затем выполнение одного действия. При этом крайне важно то, что следующий эпизод не зависит от действий, предпринятых в предыдущих эпизодах. В эпизодических вариантах среды выбор действия в каждом эпизоде зависит только от самого эпизода.
4) Статическая. Если среда может измениться в ходе того, как робот выбирает очередное действие, то такая среда называется динамической для данного робота; в противном случае она является статической.
5) Непрерывная — Различие между дискретными и непрерывными вариантами среды может относиться к состоянию среды, способу учета времени, а также восприятием и действиям агента. В нашем случае считается что состояние среды меняется непрерывно. К примеру игра в шахматы является дискретной, так как имеет конечное количество различных состояний.
6) Одноагентная это среда в которой находится один объект(робот), и другие объекты на него не влияют и не конкурируют с ним.
Абстрагирование действий робота
1) Движение – робот может передвигаться в двух направлениях(взад, вперед) и разворачиваться на месте(налево, направо)
2) Датчики робота (ультразвуковой сенсор), позволяет определить расстояние до объекта. Расстояние может быть определено от 0,02 метра до 4 метров.
Таким образом, определим, что создаваемый в этой статье робот является простым рефлексным роботом. Подобные роботы выбирают действия на основе текущего акта восприятия, игнорируя всю остальную историю актов восприятия.
Краткая информация по используемым деталям и модулям
Драйвер двигателей HG7881. Для управления двигателями робота необходимо устройство, которое бы преобразовывало управляющие сигналы малой мощности в токи, достаточные для управления моторами. Такое устройство называют драйвером двигателей.
HG7881 – это двухканальный драйвер двигателей, питание возможно от источника 2,5 – 12 В. Описание выходов драйвера:
Таблица 1
Вывод | Описание |
B-IA | Двигатель B Вход A (IA) |
B-IB | Двигатель B Вход B (IB) |
GND | Земля (Минус) |
VCC | Рабочее напряжение 2.5-12V (Плюс) |
A-IA | Двигатель A Вход A (IA) |
A-IB | Двигатель A Вход B (IB) |
Для того чтобы заставить двигатели работать нужным нам образом на выводы (B-IA, B-IB, A-IA, A-IB) необходимо подавать логические сигналы (HIGH,LOW). Таблица истинности двигателей:
Таблица 2
IA | IB | Состояние двигателя |
L | L | Off |
H | L | Forward |
L | H | Reverse |
H | H | Off |
Ультразвуковой сенсор измерения расстояния HC-SR04. Определяет расстояние до объекта, измеряя время отражения звуковой волны от объекта.
Сенсор излучает короткий ультразвуковой импульс (в момент времени 0), который отражается от объекта и принимается сенсором. Расстояние рассчитывается исходя из времени до получения эха и скорости звука в воздухе.
На вывод (Trig) подаётся импульс длительностью 10 мкс, ультразвуковой модуль излучает 8 пачек ультразвукового сигнала с частотой 40кГц и обнаруживает их эхо. Измеренное расстояние до объекта пропорционально ширине эха (Echo) и может быть рассчитано по формуле:
Ширина импульса/58 = расстояние в см.
Сборка робота и подключение всех модулей
Собираем платформу (рис.2).
Рисунок 2
Подключаем двигатели к драйверу (рис. 3). По два двигателя на один разъем драйвера, т.е. двигатели левой стороны платформы к разъему “Motor B”, двигатели правой стороны — “Motor A”. Управление платформой будет произведено аналогично гусеничной. При движении вперед и назад все двигатели работают синхронно в одном направлении, при повороте направо двигатели правой стороны платформы останавливаются, а левой двигаются синхронно, при повороте налево двигатели левой стороны останавливаются, а правой двигаются синхронно.
Рисунок 3
Прикручиваем верхнюю часть платформы. Соединяем драйвер двигателей, ардуино, аккумуляторы, БТ модуль и ультразвуковой сенсор к макетной плате (рис.4)
Рисунок 4
Схема подключения представлена на (рис.5). Питание ардуино, ультразвукового сенсора и драйвера двигателей (следовательно и самих двигателей) обеспечивают 4 подключенных последовательно аккумулятора (1,2 В., 2700 мА/ч), на БТ модуль питание подается от выхода ардуино 3,3 В.
Рисунок 5
Робот собран, необходимо его заставить выполнять команды, отправленные с андроида.
Скетч для Arduino ШАГ 1 – Удаленное управление роботом
Загрузим скетч в ардуино, не забыв перед этим отключить питания от БТ модуля (в противном случае загрузить его не удастся):
Скетч для Arduino ШАГ 1
//Объявляем переменные
char incomingbyte; // переменная для приема данных
//motors A (RIGHT)
int R_A_IA = 9; // A-IA
int R_A_IB = 10; // A-IB
//motors B (LEFT)
int L_B_IA = 11; // B-IA
int L_B_IB = 12; // B-IB
//Инициализация переменных
void setup() {
Serial.begin(38400);
//motors RIGHT
pinMode(R_A_IA,OUTPUT);
digitalWrite(R_A_IA, HIGH);
pinMode(R_A_IB,OUTPUT);
digitalWrite(R_A_IB, HIGH);
//motors LEFT
pinMode(L_B_IA,OUTPUT);
digitalWrite(L_B_IA, HIGH);
pinMode(L_B_IB,OUTPUT);
digitalWrite(L_B_IB, HIGH);
}
void go_forward(){
//motors LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, HIGH);
//motors RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, HIGH);
}
void go_back(){
//motors LEFT
digitalWrite(L_B_IA, HIGH);
digitalWrite(L_B_IB, LOW);
//motors RIGHT
digitalWrite(R_A_IA, HIGH);
digitalWrite(R_A_IB, LOW);
}
void go_right(){
//motors LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, HIGH);
//motors RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, LOW);
}
void go_left(){
//motors LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, LOW);
//motors RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, HIGH);
}
void stop_robot(){
//motors LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, LOW);
//motors RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, LOW);
}
//Основной цикл программы
void loop() {
if (Serial.available() > 0){
incomingbyte = Serial.read();
if (incomingbyte == '1'){
go_forward();
Serial.println("FORWARD");
}
if (incomingbyte == '2'){
go_back();
Serial.println("BACK");
}
if (incomingbyte == '3'){
go_right();
Serial.println("RIGHT");
}
if (incomingbyte == '4'){
go_left();
Serial.println("LEFT");
}
if (incomingbyte=='0'){
stop_robot();
Serial.println("STOP");
}
}
}
Объявляем переменные: R_A_IA, R_A_IB – определяют номера выводов управляющих двигателем А (двигатели правой стороны), L_B_IA, L_B_IB — выводы управляющие двигателем B(двигатели левой стороны. Инициируем последовательное соединение и задаем скорость передачи данных в бит/c (бод) – 38400. Устанавливаем режим работы выводов управляющих двигателями – OUTPUT (выходы). Подаем на все выходы значение HIGH, что означает — двигатели отключены(таблица 2).
Определяем функции: go_forward(), go_back(), go_right(), go_left(), stop_robot(), которые запускают двигатели в прямом или обратном направлении вращения, тем самым приводя робота в движение – вперед, назад, направо, налево, стоп, соответственно.
В основном цикле программы происходит считывание и обработка данных полученных в последовательный порт от БТ модуля. В зависимости от полученной команды выполняется та или иная функция и по последовательному порту передается текст об ее выполнении.
Android приложение ШАГ 1 – Удаленное управление роботом
Создаем новый проект «Android application project». Как было написано в прошлой статье, для работы с БТ необходимо выставить права на использование его нашим приложением. Для этого заходим в манифест, выбираем закладку Permissions, нажимаем add, далее Uses permission, и устанавливаем следующие права: android.permission.BLUETOOTH, android.permission.BLUETOOTH_ADMIN
Создаем основное activity, в res/layout/activity_main.xml поместим код:
основное activity ШАГ 1
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/txtrobot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Поле текстовых сообщений" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/b1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Вперед" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="100" >
<Button
android:id="@+id/b4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Налево" />
<Button
android:id="@+id/b0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Стоп" />
<Button
android:id="@+id/b3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Направо" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/b2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="100"
android:text="Назад" />
</LinearLayout>
</LinearLayout>
Вот так будет выглядеть основное activity:
Текстовое поле «txtrobot», будет отображать всю необходимую нам информацию. Кнопки b0, b1, b2, b3, b4 будут отправлять команды в arduino (0, 1, 2, 3, 4)
Переходим в src/../MainActivity.java здесь и будет располагаться наш основной код.
В предыдущей статье на шаге 4, был представлен код позволяющий передавать и принимать данные по БТ. За основу возьмем этот код.
В состояния активити onPause() и onResume() добавим условие проверки существования БТ у андроида и определения включен ли он. В предыдущей статье это условие отсутствовало в связи, с чем при запуске приложения, если был отключен БТ, оно завершалось с ошибкой и только после этого предлагало включить БТ.
if (btAdapter != null){
if (btAdapter.isEnabled()){
//Код приложения
}
}
Объявим переменные для хранения кнопок:
Button b0, b1, b2, b3, b4;
Находим их по ID:
b0 = (Button) findViewById(R.id.b0);//Стоп
b1 = (Button) findViewById(R.id.b1);//Вперед
b2 = (Button) findViewById(R.id.b2);//Назад
b3 = (Button) findViewById(R.id.b3);//Направо
b4 = (Button) findViewById(R.id.b4);//Налево
Напишем обработчики нажатия этих кнопок, для отправки команд:
b0.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("0");
}
});
b1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("1");
}
});
b2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("2");
}
});
b3.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("3");
}
});
b4.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("4");
}
});
Полный код приложения:
Код приложения Шаг 1
package com.example.rob_2_3_0;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import com.example.rob_2_3_0.R;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
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;
import android.bluetooth.*;
import android.content.Intent;
public class MainActivity extends Activity {
private static final int REQUEST_ENABLE_BT = 1;
final int ArduinoData = 1;
final String LOG_TAG = "myLogs";
private BluetoothAdapter btAdapter = null;
private BluetoothSocket btSocket = null;
private static String MacAddress = "20:11:02:47:01:60"; // MAC-адрес БТ модуля
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private ConnectedThred MyThred = null;
public TextView mytext;
Button b0, b1, b2, b3, b4;
boolean fl=false;
Handler h;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btAdapter = BluetoothAdapter.getDefaultAdapter();
mytext = (TextView) findViewById(R.id.txtrobot);
if (btAdapter != null){
if (btAdapter.isEnabled()){
mytext.setText("Bluetooth включен. Все отлично.");
}else
{
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}else
{
MyError("Fatal Error", "Bluetooth ОТСУТСТВУЕТ");
}
b0 = (Button) findViewById(R.id.b0);//Стоп
b1 = (Button) findViewById(R.id.b1);//Вперед
b2 = (Button) findViewById(R.id.b2);//Назад
b3 = (Button) findViewById(R.id.b3);//Направо
b4 = (Button) findViewById(R.id.b4);//Налево
b0.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("0");
}
});
b1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("1");
}
});
b2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("2");
}
});
b3.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("3");
}
});
b4.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("4");
}
});
h = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case ArduinoData:
byte[] readBuf = (byte[]) msg.obj;
String strIncom = new String(readBuf, 0, msg.arg1);
mytext.setText("Данные от Arduino: " + strIncom);
break;
}
};
};
}
@Override
public void onResume() {
super.onResume();
if (btAdapter != null){
if (btAdapter.isEnabled()){
BluetoothDevice device = btAdapter.getRemoteDevice(MacAddress);
Log.d(LOG_TAG, "***Получили удаленный Device***"+device.getName());
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
Log.d(LOG_TAG, "...Создали сокет...");
} catch (IOException e) {
MyError("Fatal Error", "В onResume() Не могу создать сокет: " + e.getMessage() + ".");
}
btAdapter.cancelDiscovery();
Log.d(LOG_TAG, "***Отменили поиск других устройств***");
Log.d(LOG_TAG, "***Соединяемся...***");
try {
btSocket.connect();
Log.d(LOG_TAG, "***Соединение успешно установлено***");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
MyError("Fatal Error", "В onResume() не могу закрыть сокет" + e2.getMessage() + ".");
}
}
MyThred = new ConnectedThred(btSocket);
MyThred.start();
}
}
}
@Override
public void onPause() {
super.onPause();
Log.d(LOG_TAG, "...In onPause()...");
if (btAdapter != null){
if (btAdapter.isEnabled()){
if (MyThred.status_OutStrem() != null) {
MyThred.cancel();
}
try {
btSocket.close();
} catch (IOException e2) {
MyError("Fatal Error", "В onPause() Не могу закрыть сокет" + e2.getMessage() + ".");
}
}
}
}//OnPause
private void MyError(String title, String message){
Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();
finish();
}
//Отдельный поток для передачи данных
private class ConnectedThred extends Thread{
private final BluetoothSocket copyBtSocket;
private final OutputStream OutStrem;
private final InputStream InStrem;
public ConnectedThred(BluetoothSocket socket){
copyBtSocket = socket;
OutputStream tmpOut = null;
InputStream tmpIn = null;
try{
tmpOut = socket.getOutputStream();
tmpIn = socket.getInputStream();
} catch (IOException e){}
OutStrem = tmpOut;
InStrem = tmpIn;
}
public void run()
{
byte[] buffer = new byte[1024];
int bytes;
while(true){
try{
bytes = InStrem.read(buffer);
h.obtainMessage(ArduinoData, bytes, -1, buffer).sendToTarget();
}catch(IOException e){break;}
}
}
public void sendData(String message) {
byte[] msgBuffer = message.getBytes();
Log.d(LOG_TAG, "***Отправляем данные: " + message + "***" );
try {
OutStrem.write(msgBuffer);
} catch (IOException e) {}
}
public void cancel(){
try {
copyBtSocket.close();
}catch(IOException e){}
}
public Object status_OutStrem(){
if (OutStrem == null){return null;
}else{return OutStrem;}
}
}
}
Данное приложение, позволяет управлять роботом при помощи андроида, отправляя команды по БТ на ардуино, и принимая текстовые ответы от него. Первая часть поставленной задачи выполнена.
Скетч для Arduino ШАГ 2 – Режим автономного управления роботом
Для работы с ультразвуковым сенсором, воспользуемся готовой библиотекой
ultrasonic-HC-SR04.zip
Распаковываем файлы и помещаем в каталог где расположен скетч
Подключаем библиотеку
#include "Ultrasonic.h"
Конструктор Ultrasonic принимает два параметра — номера выводов к которым подключены Trig и Echo, соответственно:
Ultrasonic ultrasonic(5, 6);
Получаем данные о расстоянии до объекта в сантиметрах:
float dist_cm = ultrasonic.Ranging(CM); //Получаем расстояние до объекта
Передаем данные на последовательный порт, для последующей передачи их через БТ модуль.
Serial.print("*");
Serial.print(dist_cm);
Serial.print("#");
Символы «*» и «#» означают начало и конец передаваемого блока информации о расстоянии до объекта. Это необходимо для того чтобы четко отделять необходимые данные друг от друга, так как при их передачи часть теряется либо приходит с запозданием.
Полный скетч для загрузки в ардуино:
Скетч для Arduino ШАГ 2
#include "Ultrasonic.h"
//Объявляем переменные
char incomingbyte;
int i=0;
//motors A (LEFT)
int R_A_IA = 9; // A-IA
int R_A_IB = 10; // A-IB
//motors B (RIGHT)
int L_B_IA = 11; // B-IA
int L_B_IB = 12; // B-IB
//Конструктор для работы с ультразвуковым сенсором
Ultrasonic ultrasonic(5, 6);
//Инициализация переменных
void setup() {
Serial.begin(38400);
//RIGHT
pinMode(R_A_IA,OUTPUT);
digitalWrite(R_A_IA, HIGH);
pinMode(R_A_IB,OUTPUT);
digitalWrite(R_A_IB, HIGH);
//LEFT
pinMode(L_B_IA,OUTPUT);
digitalWrite(L_B_IA, HIGH);
pinMode(L_B_IB,OUTPUT);
digitalWrite(L_B_IB, HIGH);
}
void go_forward(){
//LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, HIGH);
//RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, HIGH);
}
void go_back(){
//LEFT
digitalWrite(L_B_IA, HIGH);
digitalWrite(L_B_IB, LOW);
//RIGHT
digitalWrite(R_A_IA, HIGH);
digitalWrite(R_A_IB, LOW);
}
void go_right(){
//LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, HIGH);
//RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, LOW);
}
void go_left(){
//LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, LOW);
//RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, HIGH);
}
void stop_robot(){
//LEFT
digitalWrite(L_B_IA, LOW);
digitalWrite(L_B_IB, LOW);
//RIGHT
digitalWrite(R_A_IA, LOW);
digitalWrite(R_A_IB, LOW);
}
//Основной цикл программы
void loop() {
if (Serial.available() > 0){
incomingbyte = Serial.read();
if (incomingbyte == '1'){
go_forward();
}
if (incomingbyte == '2'){
go_back();
}
if (incomingbyte == '3'){
go_right();
}
if (incomingbyte == '4'){
go_left();
}
if (incomingbyte=='0'){
stop_robot();
}
}
float dist_cm = ultrasonic.Ranging(CM); //Получаем расстояние до объекта
Serial.print("*");
Serial.print(dist_cm);
Serial.print("#");
}
Android приложение ШАГ 2 – Режим автономного управления роботом
Добавим в основное активити кнопку «b5» (автоуправление). Код приведен ниже:
основное activity ШАГ 2
Содержимое
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/b5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Автоуправление" />
</LinearLayout>
<TextView
android:id="@+id/txtrobot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Поле текстовых сообщений" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/b1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Вперед" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="100" >
<Button
android:id="@+id/b4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Налево" />
<Button
android:id="@+id/b0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Стоп" />
<Button
android:id="@+id/b3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:text="Направо" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/b2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="100"
android:text="Назад" />
</LinearLayout>
</LinearLayout>
Содержимое
Таким образом, основное activity примет вид:
Объявим переменную b5:
Button b0, b1, b2, b3, b4, b5;
И флаг позволяющий определить включен режим автоуправления или нет:
boolean fl=false;
Находим ее по ID:
b5 = (Button) findViewById(R.id.b5);//Автоуправление
Создадим обработчик ее нажатия:
b5.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d(LOG_TAG, "НАЖАЛИ АВТОУПРАВЛЕНИЕ");
if (!fl){
Log.d(LOG_TAG, "Если флаг опущен");
fl=true;
b1.setEnabled(false);
b2.setEnabled(false);
b3.setEnabled(false);
b4.setEnabled(false);
MyThred.sendData("1");
Log.d(LOG_TAG, "Отправили 1");
}
}
});
А также внесем изменения в обработчик кнопки «b0»(Стоп)
b0.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("0");
if (fl)
{
fl = false;
b1.setEnabled(true);
b2.setEnabled(true);
b3.setEnabled(true);
b4.setEnabled(true);
}
}
});
Осталось создать алгоритм позволяющий роботу самостоятельно перемещаться по помещению и объезжать препятствия.
Обработаем полученные данные о расстоянии до объекта отправленные ардуино. Если расстояние до объекта менее 50 см. то поворачиваем направо в противном случае едим прямо:
byte[] readBuf = (byte[]) msg.obj;
String strIncom = new String(readBuf, 0, msg.arg1);
sb.append(strIncom);// формируем строку
int beginOfLineIndex = sb.indexOf("*");//определяем символы начала строки
int endOfLineIndex = sb.indexOf("#");//определяем символы конца строки
//Если блок данных соответствует маске *данные# то выполняем код
if ((endOfLineIndex > 0) && (beginOfLineIndex == 0)) { String sbprint = sb.substring(beginOfLineIndex+1, endOfLineIndex-3);
mytext.setText("Данные от Arduino: " + sbprint);
if (fl){
int dist = Integer.parseInt(sbprint);
if (dist<50)
{
MyThred.sendData("3");
}
else
{
MyThred.sendData("1");
}
}
}
sb.delete(0, sb.length());
Ниже приведен полный код Activity:
Код приложения Шаг 2
package com.robot.rob_2_3;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.UUID;
import com.robot.rob_2_3.R;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.app.Activity;
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;
import android.bluetooth.*;
import android.content.Intent;
public class MainActivity extends Activity {
private static final int REQUEST_ENABLE_BT = 1;
final int ArduinoData = 1;
final String LOG_TAG = "myLogs";
private BluetoothAdapter btAdapter = null;
private BluetoothSocket btSocket = null;
private static String MacAddress = "20:11:02:47:01:60"; // MAC-адрес БТ модуля
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static final long MILLIS_PER_SECOND = 0;
private ConnectedThred MyThred = null;
public TextView mytext;
Button b0, b1, b2, b3, b4, b5;
boolean fl=false;
Handler h;
private StringBuilder sb = new StringBuilder();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btAdapter = BluetoothAdapter.getDefaultAdapter();
mytext = (TextView) findViewById(R.id.txtrobot);
if (btAdapter != null){
if (btAdapter.isEnabled()){
mytext.setText("Bluetooth включен. Все отлично.");
}else
{
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}else
{
MyError("Fatal Error", "Bluetooth ОТСУТСТВУЕТ");
}
b0 = (Button) findViewById(R.id.b0);//Стоп
b1 = (Button) findViewById(R.id.b1);//Вперед
b2 = (Button) findViewById(R.id.b2);//Назад
b3 = (Button) findViewById(R.id.b3);//Направо
b4 = (Button) findViewById(R.id.b4);//Налево
b5 = (Button) findViewById(R.id.b5);//Автоуправление
b0.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("0");
if (fl)
{
fl = false;
b1.setEnabled(true);
b2.setEnabled(true);
b3.setEnabled(true);
b4.setEnabled(true);
}
}
});
b1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("1");
}
});
b2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("2");
}
});
b3.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("3");
}
});
b4.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
MyThred.sendData("4");
}
});
b5.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d(LOG_TAG, "НАЖАЛИ АВТОУПРАВЛЕНИЕ");
if (!fl){
Log.d(LOG_TAG, "Если флаг опущен");
fl=true;
b1.setEnabled(false);
b2.setEnabled(false);
b3.setEnabled(false);
b4.setEnabled(false);
MyThred.sendData("1");
Log.d(LOG_TAG, "Отправили 1");
}
}
});
h = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case ArduinoData:
byte[] readBuf = (byte[]) msg.obj;
String strIncom = new String(readBuf, 0, msg.arg1);
sb.append(strIncom);// формируем строку
int beginOfLineIndex = sb.indexOf("*");//определяем символы начала строки
int endOfLineIndex = sb.indexOf("#");//определяем символы конца строки
//Если блок данных соотвествует маске *данные# то выполняем код
if ((endOfLineIndex > 0) && (beginOfLineIndex == 0)) { // если встречаем конец строки,
String sbprint = sb.substring(beginOfLineIndex+1, endOfLineIndex-3); // то извлекаем строку
mytext.setText("Данные от Arduino: " + sbprint);
if (fl){
int dist = Integer.parseInt(sbprint);
if (dist<50)
{
MyThred.sendData("3");
}
else
{
MyThred.sendData("1");
}
}
}
sb.delete(0, sb.length());
break;
}
};
};
}
@Override
public void onResume() {
super.onResume();
if (btAdapter != null){
if (btAdapter.isEnabled()){
BluetoothDevice device = btAdapter.getRemoteDevice(MacAddress);
Log.d(LOG_TAG, "***Получили удаленный Device***"+device.getName());
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
Log.d(LOG_TAG, "...Создали сокет...");
} catch (IOException e) {
MyError("Fatal Error", "В onResume() Не могу создать сокет: " + e.getMessage() + ".");
}
btAdapter.cancelDiscovery();
Log.d(LOG_TAG, "***Отменили поиск других устройств***");
Log.d(LOG_TAG, "***Соединяемся...***");
try {
btSocket.connect();
Log.d(LOG_TAG, "***Соединение успешно установлено***");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
MyError("Fatal Error", "В onResume() не могу закрыть сокет" + e2.getMessage() + ".");
}
}
MyThred = new ConnectedThred(btSocket);
MyThred.start();
}
}
}
@Override
public void onPause() {
super.onPause();
Log.d(LOG_TAG, "...In onPause()...");
if (btAdapter != null){
if (btAdapter.isEnabled()){
if (MyThred.status_OutStrem() != null) {
MyThred.cancel();
}
try {
btSocket.close();
} catch (IOException e2) {
MyError("Fatal Error", "В onPause() Не могу закрыть сокет" + e2.getMessage() + ".");
}
}
}
}//OnPause
private void MyError(String title, String message){
Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();
finish();
}
//Отдельный поток для передачи данных
private class ConnectedThred extends Thread{
private final BluetoothSocket copyBtSocket;
private final OutputStream OutStrem;
private final InputStream InStrem;
public ConnectedThred(BluetoothSocket socket){
copyBtSocket = socket;
OutputStream tmpOut = null;
InputStream tmpIn = null;
try{
tmpOut = socket.getOutputStream();
tmpIn = socket.getInputStream();
} catch (IOException e){}
OutStrem = tmpOut;
InStrem = tmpIn;
}
public void run()
{
byte[] buffer = new byte[1024];
int bytes;
while(true){
try{
bytes = InStrem.read(buffer);
h.obtainMessage(ArduinoData, bytes, -1, buffer).sendToTarget();
}catch(IOException e){break;}
}
}
public void sendData(String message) {
byte[] msgBuffer = message.getBytes();
Log.d(LOG_TAG, "***Отправляем данные: " + message + "***" );
try {
OutStrem.write(msgBuffer);
} catch (IOException e) {}
}
public void cancel(){
try {
copyBtSocket.close();
}catch(IOException e){}
}
public Object status_OutStrem(){
if (OutStrem == null){return null;
}else{return OutStrem;}
}
}
}
Созданное приложение для андроида в связке с представленным скетчем ардуино, позволяет, как удаленно самостоятельно управлять роботом, так и включать режим автономного управление, при котором робот передвигается в прямом направлении и если требуется, объезжает препятствия.
Результатом проделанной работы является простейший рефлексный робот. Дальнейшее применение более сложных алгоритмов на базе приведенных шаблонных приложений и скетчей позволит создавать роботов основанных на модели, на цели, на полезности, обучающихся роботов и др.
К следующей статье я сделал заказ всего одного модуля:
Наименование | Ссылка | Цена y.e | Цена руб | Кол-во | Сумма |
Wifi модуль | dx.com/p/hi-link-hlk-rm04-serial-port-ethernet-wi-fi-adapter-module-blue-black-214540#.UutHKD1_sd0 | 14,99 | 524,65 | 1 | 524,65 |
ИТОГ: 524,65
В комментариях к предыдущей статье, хабра пользователь commanderxo порекомендовал не изобретать велосипед, а воспользоваться стандартным протоколом Firmata (протокол для обмена данными между ардуино и сервером). К сожалению работоспособной библиотеки, для андроида в связке с БТ, я не нашел. Написать свою библиотеку у меня не хватает времени и сил, поэтому в данной статье я продолжаю изобретать велосипед. Если кто из Хабра пользователей обладает информацией о такой библиотеке просьба поделиться.