Программирование Android Arduino, HM-10 и App Inventor 2 Thu, November 21 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Arduino, HM-10 и App Inventor 2 Печать
Добавил(а) microsin   

Здесь приведен перевод руководства [1] с описанием создания простых приложений для Android, которые позволяют управлять устройствами на основе Arduino и модулей Bluetooth BLE HM-10. Программирование на Android реализовано в среде визуальной разработки App Inventor 2 (AI2).

Стандарт беспроводных устройств Bluetooth BLE это не апгрейд обычного протокола Bluetooth (Bluetooth Classic), это другая система, предназначенная для других целей. BLE работает совсем по другому принципу, не так как ранние версии Bluetooth. BLE специально разработано для приложений, ориентированных на низкое потребление энергии, и это реализовано путем не часто отправляемых коротких пакетов данных. Таким образом, BLE не было разработано для поддержки постоянных длительных подключений и больших объемов передаваемых данных. Для этой цели Bluetooth Classic будет лучшим выбором. В сущности BLE потребляет мало энергии за счет того, что не реализует передачу по радио слишком часто в отличие от подключения Bluetooth Classic, которое поддерживает постоянное подключение по радио.

Для устройств BLE есть 2 метода радиообмена данными: Broadcaster + Observer и Central + Peripheral. Модули HM-10 могут использовать оба метода.

Broadcaster + Observer это нестандартное подключение. Broadcaster, обычно представляющий какой-нибудь датчик, отправляет периодические сигналы (пакеты оповещения), которые прослушивает Observer. Broadcaster ничего не знает о том, принимает кто-нибудь его сообщения или нет.

Сценарий Central + Peripheral больше похож на классическое соединение (однако оно не такое же). Устройство Central (master, главное устройство) инициирует подключение, когда находит устройство Peripheral (slave, подчиненное устройство), к которому хочет подключиться, после чего устройство Central управляет соединением и его интервалами времени передачи и приема.

Поскольку Bluetooth Classic подразумевает, что Вы собираетесь установить одиночное соединение, которое будет использоваться некоторое, то не обязательно очень быстро устанавливать соединение. BLE, с другой стороны, разработано для создания большого количества коротких соединений, которые очень быстро соединяются и разъединяются.

BLE использует концепцию служб (service) и характеристик (characteristic). Служба это коллекция связанных характеристик, и характеристика это место, где хранятся передаваемые данные. Типовое устройство BLE может иметь стандартную службу с набором таких характеристик, как имя производителя (manufacture name), имя устройства (device name), идентификатор программного обеспечения (firmware ID) и/или номер версии (version number). Также устройство может иметь вторую службу, в которой сгруппированы характеристики наподобие температуры, влажности, яркости освещения и т. п., которые содержат реальные полезные данные. Это означает, что для использования BLE нужно знать о характеристиках, которые Вы хотите использовать, а точнее идентификаторы (UUID) этих характеристик.

Протокол BLE делает постоянно доступными значения этих свойств устройства. Если Вы хотите знать имя производителя, то прочитайте характеристику manufactures name. Если хотите знать температуру, то прочитайте её значение их характеристики температуры. Это очень отличается от принципа работы классического Bluetooth, в котором у Вас есть один канал обмена данными, через который передаются все данные.

Каждая служба и характеристика имеют уникальный идентификатор, который называется UUID, в основном это 24-разрядное число. Ниже будет показаны две пользовательские характеристики модулей HM-10 с идентификаторами 0000FFE1-0000-1000-8000-00805F9B34FB и 0000FFE2-0000-1000-8000-00805F9B34FB. Иногда эти длинные числа можно сократить до FFE1 и FFE2.

HM-10 это недорогой BLE-модуль от производителя Jinan Huamao [4]. У этого модуля есть последовательный интерфейс UART, который имеет свои достоинства и недостатки, в зависимости от того, что Вы собираетесь делать. Слой UART работает поверх слоя BLE, и позволяет осуществить очень простой обмен данными с устройствами наподобие Arduino. Получается идеальный вариант для создания простых соединений или использования базовой функции iBeacon с HM-10 [3], особенно для хобби-проектов на Arduino. К сожалению, слой UART скрывает слой BLE от пользователя, и ограничивает использование его возможностей. Мы ограничены только тем набором функций BLE, который предоставляет нам Jinan Huamao.

Когда мы создаем классическое соединение с помощью двух модулей HM-10, то следует иметь в виду, что BLE не было разработано для этого, и для таких целей лучше подойдут модули Bluetooth Classic наподобие HC-05 или HC-06. Создание стандартного соединения на основе BLE не учитывает предназначение BLE и отбрасывает его достоинства, включая аспекты низкого энергопотребления.

Для обычного использования в передаче данных у модуля HM-10 есть 2 пользовательские характеристики (custom characteristics), находящиеся в пользовательской службе (custom service):

CUSTOM SERVICE
• UUID: 0000FFE0-0000-1000-8000-00805F9B34FB
 
CUSTOM CHARACTERISTICS
• UUID: 0000FFE1-0000-1000-8000-00805F9B34FB
• CUSTOM CHARACTERISTIC
• READ/WRITE/NOTIFY
 
• UUID: 0000FFE2-0000-1000-8000-00805F9B34FB
• CUSTOM CHARACTERISTIC
• WRITE

Характеристика FFE1 активна по умолчанию. FFE2 не активна, и должна быть включена перед использованием. Обратите внимание, что характеристика FFE2 доступна только на запись. Это означает, что Вы можете использовать её для отправки данных в HM-10, но не можете использовать для отправки данных в обратном направлении, из HM-10. В последующих примерах характеристика и идентификатором UUID FFE1 используется для чтения и записи данных.

Из-за того, что слой UART HM-10 ограничен в использовании пользовательской характеристикой, в примерах будет использоваться одна характеристика для передачи всех данных. Это означает, что HM-10 на самом деле не использует возможности BLE так, как предусмотрено стандартом BLE. Если делать то же самое на другом чипе или модуле, который позволяет больше возможностей по управлению протоколом BLE (наподобие модулей Nordic BLE или Arduino 101), то можно было бы создать отдельные характеристики для разных устройств; например, светодиоды (LED) имели бы свою характеристику, кнопки управления свою, и так далее. Можно было бы даже создать отдельную характеристику для каждого светодиода или кнопки. Если Вы хотите управлять только двумя лампочками, было бы удобно применить для них отдельные характеристики. Если нужно управлять большим количеством лампочек, то создание отдельной характеристики для каждой лампочки было бы слишком трудоемким.

Подробнее про модули HM-10 см. статью [4].

Среда App Inventor 2 (AI2) предоставляет довольно простой способ создания приложений для Android. Она использует принцип блочного визуального программирования Blockly вместо текста кода. Хотя блочный принцип программирования кажется простым, с помощью него можно создавать удивительно сложные приложения. AI2 постоянно разрабатывается и обновляется, и одним из новых обновлений стало расширение BLE. Несмотря на экспериментальный статус, это расширение работает вполне надежно.

Чтобы было проще работать с примерами из этой статьи, необходимо подробнее познакомится с блочным принципом программирования App Inventor (см. обучающие руководства [5]).

Новое расширение BLE входит как часть в AI2 IOT (IOT расшифровывается как Internet of Things, "Интернет Вещей", см. [6]). Подробную информацию по расширению и блокам можно найти на страничке BluetoothLE Extension [7]. Новое BLE extension можно загрузить в файле edu.mit.appinventor.ble.aix. Имейте в виду, что хотя это расширение довольно хорошо проверено, оно все еще может находиться на стадии тестирования и многое может поменяться. Вы можете загрузить версию, который использовал автор статьи [1] для своих примеров, описанных ниже. Когда Вы сохраняете файл *.aia, то расширения сохраняются вместе с ним. Это означает, что не нужно постоянно импортировать соединения каждый раз, когда Вы работаете с приложением.

Автор [1] использовал aia-файл BaseConnect как начальную точку для создания приложения. Это пустое приложение, разработанное как шаблон, что позволяет быстро просканировать окружающее пространство и подключиться к устройствам BLE. BaseConnect aia-файл уже содержит BLE extension, но может быть в нем используется более старая версия расширения BLE, поэтому Вы должны обновить это расширение перед использованием приложения.

App Inventor IOT официально поддерживает Arduino 101 и BBC micro:bit, и для этих плат есть дополнительная поддержка, однако может использоваться любой модуль BLE, такой как HM-10.

Примечание: все примеры программ AI2 и скетчей Arduino можно скачать по ссылкам из оригинальной статьи [1] или целиком в архиве [11].

[Пример 1: дистанционное управление светодиодом на плате Arduino]

Начнем с очень простого приложения, которое позволит управлять одним светодиодом (LED): включать его и выключать.

ARD HM 10 AI2 01 00.gif

Схема. Соединения очень простые, система состоит из платы Arduino, модуля HM-10 светодиода LED и резистора. Модуль HM-10 подключен к Arduino через его цифровые порты D8 и D9. К порту D2 через резистор 330 Ом подключен светодиод LED. Arduino D8 (прием AltSoftSerial) подключен напрямую к HM-10 TX, Arduino D9 (передача AltSoftSerial) подключен к HM-10 RX через делитель напряжения для согласования логических уровней (у Arduino уровни логики 5V, а у HM-10 3.3V, подробнее см. [4]).

Примечание: для связи с HM-10 использовалась программная реализация UART на библиотеке AltSoftSerial вместо стандартного аппаратного порта Arduino. Это было сделано для того, чтобы освободить стандартный порт Arduino для вывода отладочных сообщений.

ARD HM 10 AI2 01 20 Circuit

Скетч Arduino. Как и схема, программа Arduino также очень простая. Она в цикле постоянно выполняет следующие действия:

1. Проверяет приходящие от HM-10 данные на предмет наличия в них ASCII-символов "0" или "1".
2. Если пришел символ "0", то светодиод выключается.
3. Если пришел символ "1", то светодиод включается.

Программа использует библиотеку AltSoftSerial [8], и она должна быть загружена и установлена в среде Arduino IDE, иначе скетч не скомпилируется. С платами Arduino на основе ATmega328, такими как Nano, библиотека AltSoftSerial использует цифровые порты 8 и 9. Другие типы плат Arduino могут использовать другие выводы, подробную информацию см. на страницах AltSoftSerial.

// Arduino, HM-10, App Inventor 2
//
// Example Project Part 1: включение и выключение LED.
// Автор Martyn Currey. www.martyncurrey.com
//
// Используемые выводы:
// BT VCC - Arduino 5V.
// BT GND - Arduino GND.
// Arduino D8 (ASS RX) - BT TX без делителя напряжения.
// Arduino D9 (ASS TX) - BT RX через делитель напряжения.
// Arduino D2 - резистор + LED.// https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
#include < AltSoftSerial.h>
AltSoftSerial ASSserial; 
 
byte LEDPin = 2;
char c=' ';
 
void setup() 
{
   Serial.begin(9600);
   Serial.print("Sketch:   ");   Serial.println(__FILE__);
   Serial.print("Uploaded: ");   Serial.println(__DATE__);
   Serial.println(" ");
 
   ASSserial.begin(9600);  
   Serial.println("ASSserial started at 9600");
   Serial.println(" ");
 
   pinMode(LEDPin, OUTPUT);
}
 
void loop()
{
   // Чтение данных из модуля Bluetooth HM-10 и управление светодиодом LED.
   if (ASSserial.available())
   {
      c = ASSserial.read();
      Serial.println(c);
 
      // Число 48 это ASCII-код символа 0.
      if (c==48) { digitalWrite(LEDPin, LOW); }
      // Число 49 это ASCII-код символа 1.
      if (c==49) { digitalWrite(LEDPin, HIGH); }
   }
}

Вы можете проверить, что скетч работает, в окне Serial Monitor. Все, что получено от HM-10, будет отображаться в окне терминала.

ARD HM 10 AI2 01 50 serialMonitor

Приложение Android. Начнем с очень простого приложения, которое будет отправлять через Bluetooth BLE два кода управления, "0" и "1". "0" будет служить командой для выключения светодиода (LED), и "1" командой на включение LED. Это означает, что программе понадобится какой-то способ отправить эти коды, и плате Arduino потребуется способ их принять и обработать. Сначала мы напишем совсем простое приложение, оно ничего не будет знать первоначальном состоянии LED. Приложение просто отправляет эти коды, когда пользователь нажимает на кнопки. Продвинутые функции будут добавлены позже.

Чтобы упростить процесс программирования, программа создавалась на основе шаблона BaseConnect, созданного командой AI2. Файл aia шаблона приложения BaseConnect нужен для сканирования и подключения устройств BLE. Есть некоторые неудобства в таком решении (наподобие размера текста в списке устройств), однако это хорошая точка для быстрого старта. При разработке приложений BLE Вам не обязательно использовать файл BaseConnect, но его нужно использовать как образец, если Вы начинаете разрабатывать приложение BLE в среде AI2.

Вы можете загрузить шаблон BaseConnect по ссылке IoT_BaseConnect.aia или в архиве [11].

ARD HM 10 AI2 01 10 BaseConnect Откройте App Inventer, откройте aia-файл BaseConnect, и сохраните его как ARD_HM10_AI2_Single_LED_01.aia. Модуль HM-10 доступен для подключения на устройстве Android, если использовать приложение MIT AI2 Companion. Кликните Scan, и увидите, какие устройства можно найти в сети Bluetooth BLE.
















ARD HM 10 AI2 01 11 BaseConnect На этом скриншоте видны разные устройства BLE, включая HM-10 (первый в списке). К сожалению, текст очень мал и неудобен для чтения.

















В Дизайнере (Designer) выберите ListBLE, и поменяйте размер текста TextSize на 48. Теперь можно прочитать имена найденных устройств BLE.

ARD HM 10 AI2 01 12a BaseConnect ARD HM 10 AI2 01 13a BaseConnect

Приложение BaseConnect очень простое, и в секции Blocks мы можем увидеть, что оно состоит 7 блоков или процедур. То, что делает каждая процедура, должно быть самодокументированным и понятным.

ARD HM 10 AI2 01 16 BaseConnect

Добавление кнопок управления LED. Следующий шаг - добавление органов управления светодиодом, чтобы можно было его включать и выключать. Вместе с кнопками автор использовал текстовые метки из пробелов для организации интервалов между кнопками. Кнопки расположены горизонтально (Horizontal Arrangement).

ARD HM 10 AI2 01 14 BaseConnect ARD HM 10 AI2 01 15 BaseConnect

Нужно переименовать элементы:

ARD HM 10 AI2 01 30 ARD HM 10 AI2 01 31

После того, как Вы добавили элементы управления, сохраните проект в файл ARD_HM10_AI2_Single_LED_02.aia.

Теперь нужно добавить функции для отправки кодов управления. Это делается не настолько прямолинейно, как можно было бы ожидать, особенно если Вы раньше использовали Bluetooth Classic. В протоколе Bluetooth Classic после того как было сделано соединение, данные просто записываться в канал соединения. В протоколе Bluetooth BLE нужно записывать данные в специальную характеристику, и это означает, что Вам нужно знать корректный UUID для этой характеристики, и также UUID службы, которой эта характеристика принадлежит [4]. У BLE нет канала связи, есть характеристики, которые можно читать и/или записывать (в зависимости от свойств характеристики).

В этом примере у модуля HM-10 задействована пользовательская характеристика по умолчанию (default custom characteristic) 0000FFE1-0000-1000-8000-00805F9B34FB, которая находится в пользовательской службе (custom service) 0000FFE0-0000-1000-8000-00805F9B34FB.

Расширение AI2 BLE позволяет нам записывать различные типы данных, и для этого примера мы могли бы использовать байты или строки, потому что для передачи "0" и "1" подойдет любой из этих вариантов. Автор использовал строки, потому что при таком варианте в будущем можно было бы применить более сложные управляющие коды без внесения лишних изменений.

ARD HM 10 AI2 01 32

Блоки BLE Write обычно ожидают данные, переданные в списке. К счастью, если Вы используете простые данные (plain data), AI2 будет их прозрачно преобразовывать в нужный тип.

Чтобы можно было записать данные, нам нужно знать service UUID и characteristic UUID.

Функция WriteStrings слепая, она просто отправляет данные, и у неё нет никакого понятия о том, принимает ли их HM-10, или нет. В расширении BLE мы можем проверить завершение передачи путем использования блока WriteStringsWithResponse.

ARD HM 10 AI2 01 33

Это инструктирует модуль HM-10 отправить обратно подтверждение. AI2 получает это подтверждение, и это вызывает срабатывание события в AI2, что мы можем проверить. Пока нам это не нужно, будем использовать только функцию WriteStrings.

В редакторе блоков (Blocks editor), добавьте несколько глобальных переменных, чтобы в них держать номера UUID и добавьте блоки события клика на кнопках управления LED:

ARD HM 10 AI2 01 34

Теперь Вы должны реализовать действия при нажатии кнопки ON, чтобы отправлялся код "1", и при нажатии на кнопку OFF отправлялся код "0".

Откройте приложение, и дайте ему запуститься. Оно будет работать, пока на Вашем устройстве Android разрешен Bluetooth, и Вы подключились к HM-10 перед тем, как кликать на кнопки управления LED. Если Вы попробуете просканировать сеть, когда Bluetooth выключен, то получите системную ошибку (system error). Если Вы кликните на кнопки управления перед установкой соединения, то также получите системную ошибку.

Когда Вы попытаетесь просканировать сеть с отключенным Bluetooth, то при получении системной ошибки операционная система Android запросит, хотите ли Вы разрешить Bluetooth. В настоящее время при использовании расширения BLE нет очевидного способа проверить, включен ли Bluetooth. Есть способ проверки с Bluetooth Classic. Как обходной путь мы можем добавить клиента Bluetooth Classic, и с помощью этого увидеть, разрешен ли Bluetooth.

Сохраните приложение как ARD_HM10_AI2_Single_LED_03.aia перед тем, как двинуться дальше.

Добавьте Bluetooth Client и затем добавьте оповещатель (notifier):

ARD HM 10 AI2 01 35 add BT ARD HM 10 AI2 01 36 add notifier

Теперь нам нужно поменять, что будет происходить по клику на кнопке Scan. Когда на этой кнопке осуществлен клик, нам сначала надо проверить, разрешен ли Bluetooth, если разрешен, то можно сканировать. Если нет, то нам нужно выдать сообщение об ошибке. Проверка, разрешен ли Bluetooth, делается с помощью блока BluetoothClient.Enabled.

В редакторе блоков добавьте проверку Bluetooth в процедуру ButtonScan.Click. Одновременно добавьте блок else к блоку if/then, и вызовите оповещатель. Вызов оповещателя отобразит сообщение об ошибке, если Bluetooth на устройстве Android не разрешен.

ARD HM 10 AI2 01 37

Теперь если мы попробуем запустить сканирование перед включением Bluetooth, то получим предупреждение:

ARD HM 10 AI2 01 38

С кнопками управления LED нам нужно проверить, активно ли подключение, перед попыткой отправки кодов управления "0" и "1". Это можно сделать в блоке BLE isDeviceConnected.

ARD HM 10 AI2 01 39

Теперь если кликнуть на кнопку ON или OFF, когда HM-10 не подключен, то ничего не произойдет. Вы могли бы, если бы захотели, отобразить сообщение об ошибке. Но скорее всего иметь слишком много подобных сообщений не очень хорошо, поэтому лучше ничего не делать. Как вариант, можно сделать эти кнопки не активными до тех пор, пока не будет активизировано подключение.

Запустите приложение еще раз, теперь оно будет выглядеть лучше. Когда Bluetooth не разрешен, мы получим отличное сообщение, говорящее нам, что нужно его включить. Когда мы кликнем на кнопки управления LED без наличия соединения, больше не будем получать системное сообщение об ошибке. При клике на другие кнопки с выключенным Bluetooth мы получим больше системных сообщений об ошибках.

ARD HM 10 AI2 01 40 ARD HM 10 AI2 01 41 ARD HM 10 AI2 01 42

Сейчас у нас есть одна кнопка для запуска сканирования, и еще одна для остановки сканирования. Их можно объединить одной переключающей кнопкой (toggle button), где будет меняться надпись в зависимости от текущего состояния, SCAN и STOP SCAN. Приложение запустится с надписью на кнопке SCAN, и если на неё кликнуть, то при включенном Bluetooth запустится сканирование, и на кнопке поменяется надпись на STOP SCAN. Это означает, что пользователь может нажать только на эту же единственную кнопку с надписью STOP SCAN, если он до этого нажал на кнопку SCAN. Этот метод также означает, что текст на кнопке можно использовать как некий флаг состояния. То же самое можно проделать с кнопками Connect и Disconnect, заменив их на одну кнопку.

В Дизайнере поменяйте текст на кнопке Scan, и удалите кнопку Stop Scan. В редакторе блоков удалите функцию обработки события ButtonStopScan.Click, она больше не нужна.

ARD HM 10 AI2 01 43

Процедуру события ButtonScan.Click нужно расширить следующим образом:

ARD HM 10 AI2 01 44

Теперь при клике функция сначала проверяет текст на кнопке, если этот текст "SCAN", то мы знаем, что нужно запустить сканирование. Если текст не "SCAN", то этот текст должен быть "STOP SCAN", и нужно установить сканирование.

То же самое проделайте с кнопками Connect и Disconnect. Объедините две кнопки в одну переключающую, которая обеспечит такое поведение, что пользователь может выполнить отключение только в том случае, если перед этим он активизировал подключение. Однако это не становит пользователя от нажатия на "Connect", если до этого он не выбрал устройство, с которым он будет осуществлять соединение. Мы разберемся с этим после комбинирования кнопок.

В функции события ButtonConnect.Click добавьте проверку текста на кнопке, и переместите инструкции подключения:

ARD HM 10 AI2 01 46

В редакторе блоков (Blocks editor) удалите функцию обработки события ButtonDisconnect.Click, и затем удалите кнопку ButtonDisconnect в Дизайнере. Поменяйте текст на кнопке ButtonConnect на "CONNECT".

ARD HM 10 AI2 01 45

Если Вы попробуете запустить это приложение, то увидите, что подключение и отключение работает, но все еще будет появляться ошибка, если Вы попробуете подключиться перед сканированием и выбором устройства для подключения. Мы это попробуем исправить позже.

У элемента AI2 ListView есть несколько свойств, которые мы можем использовать, если выбран один из элементов списка: List.Selection и List.SelectionIndex. Перед тем, как сделан выбор в списке, значение List.SelectionIndex равно 0 и List.Selection равно null или "". Используя SelectionIndex, мы можем легко увидеть, что если SelectionIndex больше 0, то пользователь что-то выбрал в списке.

ARD HM 10 AI2 01 47

Давайте попробуем, наш алгоритм должен работать. Как минимум это сработает, когда Вы попробуете подключиться первый раз. Это произойдет, если Вы выполните сканирование, подключение, затем отключение, и затем снова подключение без сканирования. Приложение попробует подключиться снова к тому же самому устройству. Приложение помнит, какой элемент в списке выбран, и использует его снова. Вы можете либо все оставить как есть, как стандартное поведение, или можете выбрать сбрасывать индекс списка, когда было сделано соединение.

В настоящий момент когда пользователь кликает на кнопку CONNECT/DISCONNECT, на кнопке будет соответствующим образом меняться текст. Он поменяется даже в том случае, когда есть проблема с соединением, и оно по какой-то причине не произошло. Чтобы исправить это, переместите инструкции изменения текста в функции BluetoothLE1.Connected и BluetoothLE1.Disonnected. Это должно выглядеть примерно так:

ARD HM 10 AI2 01 48

В функции обработки события ButtonConect.Click сейчас есть инструкция для установки текста LabelStatus в "Status: Connecting", что можно также при желании сделать и с кнопкой CONNECT/DISCONNECT.

После всех изменений приложение будет выглядеть чище и проще:

ARD HM 10 AI2 01 49

ARD HM10 AI2 Single LED 03 designer

Вот так выглядят все блоки:

ARD HM10 AI2 Single LED 03 blocks

Сохраните текущий проект в ARD_HM10_AI2_Single_LED_04.aia.

Следующим шагом, конечно будет комбинирование двух кнопок управления LED в одну переключаемую кнопку, на которой будет текст либо "ON", либо "OFF".

Чтобы объединить две кнопки в одну мы выполним точно такие же действия, которые делали раньше. При клике на кнопку нужно проверить текст, который на ней присутствует. Если этот текст "ON", то мы знаем, что LED светится, его нужно выключить и поменять текст на кнопке на "OFF". В противном случае на кнопке будет текст "OFF", LED выключен, нам надо его включить и поменять текст на кнопке на "ON".

Будем использовать кнопку включения, переименуйте её в BTN_LED.

Следует заметить, что кнопка управления LED сейчас отображает состояние LED, а не отправляемую команду. Это означает, что отправляемые коды надо поменять на обратные. Раньше кнопка ON отправляла команду включения LED. Теперь все наоборот: если на кнопке надпись "ON", то это значит, что LED включен, и надо его выключить, т. е. отправить команду выключения.

Можно теперь удалить кнопку OFF и пустые метки, которые использовались ранее для интервалов между кнопками:

ARD HM 10 AI2 01 52

После добавления оператора if/then для проверки текста на кнопке и перемещения блоков мы получим следующий код:

ARD HM 10 AI2 01 51

Это отлично работает, кнопка ON/OFF полностью управляет светодиодом LED. Однако есть еще несколько вещей, которые хорошо бы исправить.

1. Когда приложение запускается, то на кнопке присутствует текст "ON", хотя LED по умолчанию выключен после подачи питания на плату Arduino.
2. Цвет у кнопки не меняется.
3. Нам нужно в двух местах кода проверять наличие соединения.

Поменяйте в Дизайнере текст кнопки на на "OFF", и текст фона кнопки (background colour) на красный (red). Теперь при старте приложения по умолчанию на кнопке будет текст OFF, и кнопка будет красная.

Чтобы поменять цвет кнопки, мы будем использовать её свойство BackgroundColor. Цвета выбираются в меню Color.

ARD HM 10 AI2 01 53

Примечание: AI2 дает больше возможностей для выбора цвета, но в этом примере используются базовые цвета, которые можно выбрать в меню.

Ниже показан код с командами смены цвета. Кнопка теперь вместе с текстом будет менять также и свой цвет.

ARD HM 10 AI2 01 54

ARD HM 10 AI2 01 55

Вместо того, чтобы делать проверку соединения после проверки текста на кнопке, мы можем сначала проверять соединение. В таком случае нам понадобится делать проверку соединения только 1 раз. Для этой цели просто переместите соответствующий оператор if/then выше проверки текста на кнопке.

ARD HM 10 AI2 01 56

После сделанных изменений поведение приложения не поменяется, но код станет немного лучше. 

Есть еще кое-что, на что следует обратить внимание. Когда Вы кликаете на кнопку SCAN, приложение продолжает сканировать, пока Вы не кликните на кнопку STOP SCAN. Когда Вы осуществили соединение, приложение все еще продолжает сканирование, хотя это уже не нужно. Чтобы исправить это, сделаем так, что когда мы кликаем на кнопку CONNECT, сканирование будет останавливаться.

Посмотрите на функцию кнопки SCAN:

ARD HM 10 AI2 01 57

Если в момент нажатия на кнопку CONNECT на кнопке ButtonScan на кнопке ButtonScan присутствует текст "SCAN", то он должен быть заменен на "STOP SCAN", и сканирование должно быть остановлено.

ARD HM 10 AI2 01 58

Мы могли бы просто скопировать эти операторы из обработчика кнопки сканирования, однако есть способ получше: следует переместить эти операторы в отдельную процедуру, и делать её вызов.

Схватите блок создания новой процедуры, и переместите туда блоки остановки сканирования. Переименуйте эту процедуру в StopScan.

ARD HM 10 AI2 01 59

После того, как новая процедура создана, она будет добавлена в меню процедур:

ARD HM 10 AI2 01 60

Теперь у нас есть процедурный блок StopScan, который можно вызвать. Перетащите его в процедуру ButtonScan.Click.

ARD HM 10 AI2 01 61

Чтобы остановить сканирование, когда осуществляется клик на кнопке CONNECT, добавьте вызов процедуры StopScan в процедуру ButtonConnect.Click в секцию CONNECT после проверки индекса ListBLE.SecitionIndex.

ARD HM 10 AI2 01 62

Последнее, что следуют сделать, это добавить отображаемое имя приложения (title). В Дизайнере добавьте 3 метки в самой верхней части экрана.

ARD HM 10 AI2 01 63

Переименуйте их:

ARD HM 10 AI2 01 64

Отредактируйте свойства меток пробелов (LBL_TITLE_SPCER_01 и LBL_TITLE_SPCER_02):

Height = 10 пикселей
Width = Fill Parent (занимать всю область родителя)
Text = ""

Отредактируйте свойства метки LBL_MAIN_TITLE: 

Height = automatic
Width = Fill Parent
Text Alignment = center
Text = "LED CONTROL"
FontSize = 30

ARD HM 10 AI2 01 66

Получится следующая форма приложения:

ARD HM 10 AI2 01 65

Теперь у нас есть приложение, с помощью которого можно управлять по радио светодиодом, подключенным к плате Arduino, принимающей команды управления через модуль HM-10. В примере 2 будет добавлена кнопка к плате Arduino, и будет показан второй способ управления.

[Пример 2: второй метод управления]

В этом примере к плате Arduino будет добавлена кнопка, и появится возможность управления светодиодом как с этой кнопки, так и из приложения Android. Кнопка Arduino будет работать на переключение (точно так же, как и кнопка в приложении Android), управляя светодиодом локально. Это означает, что в Arduino должен присутствовать следующий код:

1. Опрос кнопки.
2. Проверка: если кнопка нажата, то поменять состояние LED.
3. Отправка в приложение Android информации об изменении состояния LED.

Информацию о состоянии светодиода мы будем передавать в приложение теми же кодами управления. "0" будет означать, что LED выключен, и "1" что включен. Приложение Android должно проверять приходящие данные на наличие в них "0" или "1", и соответственно устанавливать цвет и текст своей кнопки управления LED.

Схема. В схеме была добавлена кнопка, подключенная к порту D3, и резистор 10 кОм, подтягивающий входной уровень D3 к лог. 0.

ARD HM 10 AI2 02 01 Circuit ButtonSwitch

ARD HM 10 AI2 02 02 Breadboard ButtonSwitch

Скетч Arduino. Код проверки состояния кнопки оформлен в виде отдельной функции checkSwitch(), которая вызывается в главном цикле программы (loop). Функция checkSwitch() проверяет, нажата ли кнопка, и если нажатие определено, то она меняет состояние LED, и одновременно отправляет соответствующий код управления в приложение Android через UART-соединение с модулем HM-10.

Код, который проверяет принятые данные, остался таким же, как раньше, просто был перенесен в отдельную функцию checkRecievedData(). Использование отдельных функций подобного рода делает основной код программы красивым и ясным для понимания. Это также дает возможность повторного использования кода (что может сэкономить память программ) и снижает вероятность ошибок.

// Arduino, HM-10, App Inventor 2
//
// Example Project Part 2: Turn an LED on and off 2 way control 01
// By Martyn Currey. www.martyncurrey.com
//
// Используемые подключения:
// BT VCC - Arduino 5V.
// BT GND - Arduino GND.
// Arduino D8 (RX) - BT TX без делителя напряжения.
// Arduino D9 (TX) - BT RX через делитель напряжения.
// Arduino D2 - резистор 330 Ом + LED
// Arduino D3 - pull down резистор 10 кОм + кнопка
 
// Библиотека AltSoftSerial использует D9 для передачи (UART TX) и D8 для приема
// (UART RX). При использовании AltSoftSerial порт D10 не может использоваться
// для формирования ШИМ (PWM).
// Библиотеку AltSoftSerial можно загрузить по ссылке:
// https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html#include < AltSoftSerial.h>
AltSoftSerial ASSserial;
 
// Константы для используемых ножек портов:
const byte LEDPin = 2;
const byte SwitchPin = 3;
 
// Глобальные переменные флагов:
boolean LED_State = false;
boolean switch_State = false;
boolean oldswitch_State = false;
 
char c=' ';
 
void setup()  
{
   Serial.begin(9600);
   Serial.print("Sketch:   ");   Serial.println(__FILE__);
   Serial.print("Uploaded: ");   Serial.println(__DATE__);
   Serial.println(" ");
 
   ASSserial.begin(9600);
   Serial.println("AltSoftSerial started at 9600");
   Serial.println(" ");
 
   pinMode(LEDPin, OUTPUT);
   digitalWrite(LEDPin,LOW);
 
   pinMode(SwitchPin, INPUT);
}
 
void loop()
{
   checkSwitch();
   checkRecievedData();
}void checkSwitch()
{
   // Функция переключения по нажатию кнопки, с простым подавлением
   // дребезга контактов.
   boolean state1 = digitalRead(SwitchPin); delay(1);
   boolean state2 = digitalRead(SwitchPin); delay(1);
   boolean state3 = digitalRead(SwitchPin); 
 
   if ((state1 == state2) && (state1==state3))   
   {
      switch_State = state1;  
      if ( (switch_State == HIGH) && (oldswitch_State == LOW) )
      {
         // Переключение в противоположное состояние LED_State.
         LED_State = ! LED_State;  
 
         // Если LED_State в лог. 1, то LED надо включить.
         if (LED_State == HIGH)
         {
            // Включение LED:
            digitalWrite(LEDPin,HIGH);
            // Передача в приложение Android информации,
            // что LED включен:
            ASSserial.print("1" );
            Serial.println("Sent - 1");
         }
         else
         {
            // Выключение LED:
            digitalWrite(LEDPin,LOW);
            // Передача в приложение Android информации,
            // что LED выключен:
            ASSserial.print("0");
            Serial.println("Sent - 0");    
         }
      }
      oldswitch_State = switch_State;
   }
}
 
void checkRecievedData()
{
   // Чтение из модуля Bluetooth, и включение и выключение LED.
   if (ASSserial.available())
   {
      c = ASSserial.read();
      Serial.println(c);
      // Число 48 это ACSII-код символа "0":
      if (c== 48) { digitalWrite(LEDPin, LOW);     LED_State = LOW;  }
      // Число 49 это ACSII-код символа "1":
      if (c== 49) { digitalWrite(LEDPin, HIGH);    LED_State = HIGH; }
   }
}

Обновление приложения Android. Перед тем, как продолжить, сохраните проект как ARD_HM10_AI2_Single_LED_05.aia.

В настоящий момент приложение может только отправлять коды управления, но не может их принимать. Надо добавить возможность приема данных.

Чтобы принимать данные из устройства BLE (мы используем Bluetooth BLE модуль HM-10), нужно знать о нем несколько вещей.

– Идентификатор службы (service UUID).
– Идентификатор характеристики (characteristic UUID)
– Тип данных (data type): байт, строка, целое число, число с плавающей точкой 16-битное число, 32-битное число (byte, string, integer, float, short, long).

Мы будем использовать пользовательскую службу по умолчанию модуля HM-10 (default custom service) и его характеристику, так что нам надо знать их идентификаторы UUID. В качестве данных используем строки, так что тип данных мы знаем (string).

Расширение BLE среды AI2 имеет различные блоки для приема данных различных типов. Поскольку мы используем строки, то понадобится блок .StringsReceived:

ARD HM 10 AI2 02 03

Сам по себе блок .StringsReceived использовать нельзя, он полагается на событие приема данных (data received event), которое создается блоком .RegisterForStrings:

ARD HM 10 AI2 02 04

Обратите внимание, что это в свою очередь полагается на характеристику, у которой есть свойство оповещения (notify property). Среда AI2 неявно говорит модулю BLE включить оповещения, и затем использует эти оповещения для генерации событий приема данных.

Когда мы завершаем работу с соединением, то хорошей практикой будет снять регистрацию или отменить событие приема данных, что реализуется с помощью блока .UnregisterForValues.

ARD HM 10 AI2 02 06

Мы не можем зарегистрировать событие до запуска соединения, поэтому хорошее место для блока .RegisterForStrings - тело процедуры BluetoothLE1.Connected. Затем после того, как соединение установится, сразу вызывается блок .RegisterForStrings.

Блок .UnregisterForValues должен быть вызван перед вызовом отключения. Если Вы попытаетесь вызвать .UnregisterForValues после разрыва соединения, то получите ошибку.

В результате получится следующий код:

ARD HM 10 AI2 02 08 Corrected 1

ARD HM 10 AI2 02 08 Corrected 2

У нового приложения есть 2 возможных способа поменять вид своей кнопки управления LED:

1 – клик на эту кнопку,
2 – через обработку поступающих кодов управления.

Это означает, что у нас есть 2 части приложения, которые меняют текст на кнопке, одна часть меняет текст на "ON", и вторая на "OFF". Вместо того, чтобы дублировать одинаковый код, были сделаны отдельные процедуры для изменения свойств кнопки LED:

ARD HM 10 AI2 02 09

Теперь для изменения кнопки мы просто вызываем LED_BUTTON_ON или LED_BUTTON_OFF в процедуре события BTN_LED.Click:

ARD HM 10 AI2 02 10

Осталось проверять приходящие данные и обновлять кнопку управления LED. Как уже упоминалось ранее, блок .StringsReceived вызывается, когда были приняты новые данные.

ARD HM 10 AI2 02 03

Это дает нам список вместо простых данных, так что придется обрабатывать этот список. Сначала делается двойная проверка, что у нас действительно есть список, с помощью блока "is a list?" (это список?).

ARD HM 10 AI2 02 11

Затем делается цикл по всем элементам списка в блоке "for each item in list" (для каждого элемента в списке). В списке находятся значения stringValues.

ARD HM 10 AI2 02 12

Примечание: у нас должен быть только 1 символ в принятых данных, так что в списке должен быть только 1 элемент, и Вам может показаться, что не нужно делать цикл по всем элементам списка. Для этого простого примера возможно так и есть (и здесь мы так и делаем), но хорошей практикой было бы просматривать весь список, потому что в будущем может появиться приложение, которое очень быстро получает много данных. Кроме того, что может произойти, если кто-то будет нажимать кнопку на Arduino очень быстро?

Теперь нам нужно добавить проверку данных на "1" или "0", и если такие данные пришли, то соответственно менять в приложении вид кнопки управления LED.

ARD HM 10 AI2 02 13

Обратите внимание, что здесь у нас 2 оператора if вместо одного оператора if/then. Один проверяет данные на "0", и второй на "1". Такой способ проверки данных отбрасывает все не корректные входные данные.

На этом все, у нас получилось два способа управления:

ARD HM 10 AI2 02 14

Блоки приложения:

ARD HM10 AI2 Single LED 05 blocks CORRECTED

Теперь Вы можете использовать этот метод для дополнительных односимвольных кодов управления:

0 и 1 – выключить/включить LED 1.
2 и 3 – выключить/включить LED 2.
4 и 5 – выключить/включить еще что-нибудь.
6 и 7 – ...
8 и 9 – ...

После того, как цифровые символы кончатся, можно перейти на буквы. Если Вам надо управлять чем-то, не относящимся к выключению и включению, то может понадобится передавать более сложные данные, которые будут обрабатываться по-другому.

[Пример 3: усложнение кодов управления]

В этом примере мы только поменяем используемые коды управления. Функционирование приложения Android и скетча Arduino останется таким же, как раньше, просто управляющие данные будут передаваться и обрабатываться более сложным способом. Изменение кодов управления позволит нам реализовать более сложные функции наподобие управления яркостью светодиода или изменение цвета RGB LED.

Новые коды управления. Вместо одиночного символа, который использовался раньше, теперь будут использоваться многосимвольные коды. Для LED будут применяться коды из 3 символов "L10" и "L11".

L10 – L обозначает светодиод (LED), второй символ 1 обозначает LED №1, третий символ 0 обозначает выключение.
L11 – L обозначает светодиод (LED), второй символ 1 обозначает LED №1, третий символ 1 обозначает включение.

По такому протоколу мы можем добавить управление вторым светодиодом, используя коды "L20" и "L21". Для третьего светодиода будут коды "L30" и "L31", и так далее.

Обработка многосимвольных кодов не будет такой же, как раньше. Нам понадобится обрабатывать все приходящие байты данных (не только один байт) в определенной последовательности. Это код для светодиода? Какой это светодиод? Надо его включить или выключить? Здесь у нас появляется другая проблема. При использовании последовательного соединения нельзя гарантировать, что в каждой проверке будут в наличии все данные, которые должны передаваться с удаленного конца. Может быть ситуация, когда при проверке будет доступна только часть данных. Т. е. сначала придет только символ "L", за которым придет набор символов "11", или сначала придет два символа "L1", за которыми придет "0". Это одинаково верно как при обработке в приложении Android, так и при обработке в скетче Arduino, и у нас должен быть способ получить полный код, чтобы можно было осуществить его проверку. Есть несколько способов реализации этого. Например, можно использовать маркеры начала и конца посылки кода. Тогда нам нужно принимать данные, пока не будут приняты символы от начального маркера до последнего, тогда можно быть уверенным, что пришел весь код управления. В примере ниже в качестве кодов начала и конца используются символы "[" и "]".

На стороне Arduino (обработка кодов управления, передаваемые плате Arduino) будут использоваться коды фиксированной длины, "[L10]" и "[L11]". На стороне приложения AI2 используются данные, отделяемые друг от друга запятыми "[L,1,0]" и "[L,1,1]". В среде AI2 есть более удобные механизмы функций манипулирования данными, с помощью которых можно напрямую передавать переменной данные, находящиеся между запятыми. Это делается в виде преобразования строки с разделителями в список отдельных значений (в качестве разделителя можно использовать любой символ, не только запятую).

В скетче Arduino используется функция recvWithStartEndMarkers(). Эта функция была найдена в посте на форуме Arduino [9]. Необходимо только поменять символы start и end на "[" и "]". Если Вы хотите больше узнать про последовательный обмен данными Arduino, в этом посте найдете много полезной информации.

В приложении Android автором была реализована блоковая версия функции recvWithStartEndMarkers(). Она не точно такая же, но делает то же самое.

Схема. В схеме не поменялось ничего, она точно такая же, как в примере 2. Один светодиод и одна кнопка подключены к плате Arduino. Через порты D8 и D9 подключен модуль HM-10.

ARD HM 10 AI2 02 01 Circuit ButtonSwitch

ARD HM 10 AI2 02 02 Breadboard ButtonSwitch

Скетч Arduino. В новом скетче у нас добавляются новые переменные, помогающие обрабатывать приходящие данные: maxDataLength, receivedChars[] и newCommand.

Переменная maxDataLength используется для сохранения максимальной длины приходящих данных. Это означает, что команды не могут быть длиннее значения maxDataLength.

Массив receivedChars[] хранит полностью принятую команду.

newCommand это флаг, используется, чтобы указать скетчу, новая команда или нет.

Добавлена новая функция recvWithStartEndMarkers(). Она проверяет приходящие данные на маркеры начала и конца, и затем копирует любые допустимые данные, если они найдены, в массив символов receivedChars[].

Внутри функции processCommand() проверяется принятая команда. Если команда допустима, то предпринимается соответствующее действие.

if (strcmp ("L10", receivedChars) == 0)
if (strcmp ("L11", receivedChars) == 0)

Возможно, что делать такие проверки - не самый эффективный способ. Так или иначе, проверки здесь строк "L10" и "L11" делают то же, что и предыдущие проверки символов "1" и "0". С этим разберемся подробнее в четвертом примере.

Также следует заметить, что коды, которые посылает Arduino, имеют немного другой формат по сравнению с кодами, которые он получает.

// Arduino, HM-10, App Inventor 2
//
// Example Project Part 3: Complex control codes
// By Martyn Currey. www.martyncurrey.com
//
// Используемые выводы:
// BT VCC - Arduino 5V.
// BT GND - Arduino GND.
// Arduino D8 (RX) - BT TX без делителя напряжения.
// Arduino D9 (TX) - BT RX через делитель напряжения.
// Arduino D2 - резистор 330 Ом + LED.
// Arduino D3 - 10 кОм pull down резистор + кнопка.
 
// Библиотека AltSoftSerial использует ножки портов D9 для TX UART
// и D8 для RX UART. В то же время при использовании AltSoftSerial
// порт D10 нельзя задействовать для генерации ШИМ (PWM).
#include < AltSoftSerial.h>
AltSoftSerial ASSserial; 
 
// Переменные, используемые для приходящих данных:
const byte maxDataLength = 20;
char receivedChars[21] ;
boolean newCommand = false;
 
// Константы для именования номеров используемых портов:
const byte LEDPin = 2;
const byte SwitchPin = 3;
 
// Глобальные флаги:
boolean LED_State = false;
boolean switch_State = false;
boolean oldswitch_State = false;
 
void setup()  
{
   Serial.begin(9600);
   Serial.print("Sketch:   ");   Serial.println(__FILE__);
   Serial.print("Uploaded: ");   Serial.println(__DATE__);
   Serial.println(" ");
 
   ASSserial.begin(9600);
   Serial.println("AltSoftSerial started at 9600");
   Serial.println(" ");
 
   pinMode(LEDPin, OUTPUT);
   digitalWrite(LEDPin,LOW);
 
   pinMode(SwitchPin, INPUT);
}
 
void loop()  
{
   // Опрос кнопки:
   checkSwitch();
   // Проверка, принята ли новая команда:
   recvWithStartEndMarkers();
   // Если у нас есть новая команда, то выполним её обработку:
   if (newCommand) { processCommand(); }
}
 
/******************************************
* Функция checkSwitch()
* проверяет состояние кнопки и включает и выключает
* LED в зависимости от состояния кнопки.
*
* Используемые глобальные переменные:
*      switch1_State
*      LED1_State
*      oldswitch1_State
*
* Устанавливает:
*      switch1_State
*      LED1_State
*      oldswitch1_State
*/
void checkSwitch()
{
   // Реализация кнопки переключения (триггер) с простым
   // подавлением дребезга контактов.
   boolean state1 = digitalRead(SwitchPin); delay(1);
   boolean state2 = digitalRead(SwitchPin); delay(1);
   boolean state3 = digitalRead(SwitchPin);
   if ((state1 == state2) && (state1==state3))
   {
      switch_State = state1;  
      if ( (switch_State == HIGH) && (oldswitch_State == LOW) )
      {
         LED_State = ! LED_State;
         if ( LED_State == HIGH)
         {
            ASSserial.print("[L,1,1]" );
            digitalWrite(LEDPin,HIGH);
            Serial.println("Sent - [L,1,1]");
         }
         else
         {
            ASSserial.print("[L,1,0]");
            digitalWrite(LEDPin,LOW);
            Serial.println("Sent - [L,1,0]");
         }
      }
      oldswitch_State = switch_State;
   }
}
 
/*****************************************
* Функция processCommand
* осуществляет парсинг данных команд, содержащихся
* в массиве receivedChars[]. Массив receivedChars[]
* не проверяется на наличие ошибок.
*
* Глобальные переменные:
*       receivedChars[]
*       newData
*
* Устанавливает:
*       receivedChars[]
*       newData
*/
void processCommand()
{
   if (strcmp ("L10",receivedChars) == 0) 
   {
      digitalWrite(LEDPin,LOW);
      LED_State = LOW; 
      Serial.println("LED1 LOW");  
   }
   else if (strcmp ("L11",receivedChars) == 0)
   {
      digitalWrite(LEDPin,HIGH);
      LED_State = HIGH; 
      Serial.println("LED1 HIGH");  
   }
   receivedChars[0] = '\0';
   newCommand = false;
}
 
// Функция recvWithStartEndMarkers от Robin2 с форума Arduino,
// см.  http://forum.arduino.cc/index.php?topic=288234.0
/*****************************************
* Функция recvWithStartEndMarkers
* читает последовательные данные и возвращает содержимое
* между маркерами начала и конца команды.
*
* Глобальные переменные:
*       receivedChars[]
*       newData
*
* Устанавливает:
*       newData
*       receivedChars
*/
void recvWithStartEndMarkers()
{
   static boolean recvInProgress = false;
   static byte ndx = 0;
   char startMarker = '[';
   char endMarker = ']';
   char rc;
 
   if (ASSserial.available() > 0) 
   {
      rc = ASSserial.read();
      if (recvInProgress == true) 
      {
         if (rc != endMarker) 
         {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx > maxDataLength) { ndx = maxDataLength; }
         }
         else 
         {
            receivedChars[ndx] = '\0'; // завершение строки
            recvInProgress = false;
            ndx = 0;
            newCommand = true;
         }
      }
      else if (rc == startMarker) { recvInProgress = true; }
   }
}

Обновление приложения Android. Перед тем, как продолжить, сохраните проект под новым именем ARD_HM10_AI2_Single_LED_06.aia.

Чтобы увидеть, как работает скетч Arduino, в процедуре BTN_LED.Click поменяйте коды, которые посылает приложения, на "[L10]" и "[L11]".

ARD HM 10 AI2 03 01

Если Вы запустите приложение, и попробуете его в работе, то увидите, что оно может управлять светодиодом, но не меняет вид своей кнопки управления светодиодом. Причина в том, что Arduino посылает теперь новые коды управления, и приложение ничего пока про это не знает. Давайте это исправим.

В предыдущей версии приложения оно обрабатывало только одиночные символы, и поэтому их прием и проверки были довольно просты. Теперь у нас в команде больше одного символа, и нужен, как и в скетче Arduino, какой-то способ получить все данные команды и гарантировать, что получена команда целиком.

В скетче Arduino переменная receivedChars использовалась для сохранения принятых данных. В приложении Android мы будем использовать нечто подобное, глобальную переменную с именем BT_receivedData_Buffer.

ARD HM 10 AI2 03 02

Когда мы будем получать новые данные, то будем добавлять их в этот буфер. В процедуре BluetoothLE1.StringsReceived удалите блоки, которые проверяют появление символов "1" и "0", и замените их на блок:

ARD HM 10 AI2 03 04

В результате получится следующее:

ARD HM 10 AI2 03 03

Теперь новые принятые данные добавляются в буфер вместо немедленной обработки. Следовательно, что нам нужно потом что-то делать с этим буфером.

Чтобы обработать буфер, создайте (перетаскиванием из меню) новую процедуру и переименуйте её в PROCESS_BUFFER. В этом месте может быть хорошей идеей подумать о том, что у нас есть и что с этим делать.

У нас есть переменная, в которой может находиться или не находиться код команды. Мы знаем что полная команда находится между маркерами начала и конца, так что с помощью этой информации мы можем определить, получена ли команда полностью. Если в буфере содержится маркер начала "[" и также в нем есть маркер конца "]", и маркер начала находится перед маркером конца, то это хороший шанс, что в буфере находится вся команда целиком. Тогда мы можем взять из буфера эту команду и разобрать, что она означает. Если она означает какое-то действие, то это действие должно быть выполнено.

Вот код блока процедуры целиком, после этого разберем, как он работает:

ARD HM 10 AI2 03 05

Сначала создаются локальные (временные) переменные:

ARD HM 10 AI2 03 06

Как только эти локальные переменные созданы, присваиваются значения переменным startMarkerPos и endMarkerPos. Если символы маркера не найдены, то startMarkerPos и/или endMarkerPos будут равны 0.

Далее проверяется позиция маркеров. Если маркеры найдены, то их позиции будут > 0, следующая проверка подтверждает, что маркер начала находится перед маркером конца.

ARD HM 10 AI2 03 07

После успеха этих проверок реальная команда копируется в локальную переменную command.

ARD HM 10 AI2 03 08

И затем данные команды (включая маркеры начала и конца) удаляются из буфера приема.

ARD HM 10 AI2 03 09

В этом месте переменная command хранит себе команду, которая была отправлена кодом Arduino в процедуре processCommand. Аналогичная процедура здесь пока не создана.

ARD HM 10 AI2 03 15

И наконец, здесь реализована проверка, есть ли в буфере еще маркеры начала и конца, и если есть, то весь процесс повторяется.

Все это содержится в цикле while.

ARD HM 10 AI2 03 13

Это удобный способ зацикливания, когда есть больше одного условия для выхода из цикла. Автор использовал переменную Done, которая устанавливается в false в начале, и по завершении она устанавливается в true, чтобы выйти из цикла. В результате получается "while not done" ("пока не будет готово"). Этот код проще читать, и поэтому такая организация цикла имеет смысл.

Затем нам нужно вызывать процедуру PROCESS_BUFFER из функции BluetoothLE1.StringsReceived.

ARD HM 10 AI2 03 10

Все это будет хорошо работать, пока маркер начала будет находиться перед маркером конца. Если по какой-то причине сначала в буфере окажется маркер конца, то приложение ничего не обработает. Оно найдет оба маркера, но начальный маркер начала не будет первым, и произойдет выход из цикла. В следующей проверке буфера будет получен точно такой же результат.

Чтобы исключить такую ситуацию, была добавлена функция обрезки до маркера начала TrimToStartMarker. Она обрезает буфер таким образом, что первым символом будет начальный маркер.

ARD HM 10 AI2 03 11

Вызов TrimToStartMarker был помещен в начало процедуры PROCESS_BUFFER.

ARD HM 10 AI2 03 12

Все, что осталось сделать, проверить принятые коды и если они корректные, менять внешний вид кнопки. Это делается в процедуре PROCESS_COMMAND.

ARD HM 10 AI2 03 14

Вот так выглядят получившиеся блоки:

ARD HM10 AI2 Single LED 06 blocks CORRECTED

В четвертом примере мы рассмотрим обработку кодов управления, что должно быть очень простым. В примере 5 добавим больше светодиодов и кнопок. В примере 6 попробуем работу слайдеров, чтобы управлять яркостью светодиода.

[Пример 4: обработка кодов]

В этой секции мы подготовимся к добавлению дополнительных управляемых устройств, например чтобы можно было управлять большим количеством светодиодов.

В настоящий момент команда управления обрабатывается как один элемент, например "L10" и "L11". Здесь мы разобьем команды на отдельные символы, и обработаем каждый символ отдельно:

L для светодиода (LED)
1 для номера светодиода
1/0 чтобы включить или выключить светодиод.

Если Вы захотите переключать 2 или 3 светодиода, то это можно легко реализовать такими дополнительными проверками.

Скетч Arduino. Здесь изменилась только функция processCommand().

void processCommand()
{
   // Команда для управления LED состоит из 3 символов:
   // chr 1 = L для LED
   // chr 2 = 1 для номера LED
   // chr 3 = 0 или 1 для выключения или включения
 
   if (receivedChars[0] =='L')
   {
      // Выполняем действия для управления светодиодом:
      if (receivedChars[1]=='1')
      {
         // Управляться должен светодиод № 1:
         receivedChars[2] = receivedChars[2]-48;
         // Вычитание 48 превращает код ASCII в реальные значения.
         // "0" превращается в 0 и "1" превращается в 1.
         LED_State = receivedChars[2];
         digitalWrite(LEDPin,LED_State);
         Serial.print("LED1 state = "); Serial.println(LED_State);
      }
   }
   receivedChars[0] = '\0';
   newCommand = false;
}

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

if (receivedChars[0] =='L')

Затем осуществляется проверка, каким именно светодиодом надо управлять. Если у нас только один светодиод, то номер светодиода должен быть только 1:

if (receivedChars[1]=='1')

В последней проверке проверяется завершающий символ команды - что нужно сделать со светодиодом, выключить или включить. Поскольку у светодиода может быть только 2 состояния, и у символа команды тоже может быть только 2 состояния, то этот последний символ используется напрямую после преобразования его в реальное двоичное значение 0 или 1 (путем вычитания кода символа "0", т. е. числа 48). Однако если Вы не уверены на 100% в том, какое значение придет в третьем проверяемом символе, то нужно делать полные проверки на коды "0" и "1".

Если Вы хотите больше узнать о том, как управлять несколькими светодиодами и обрабатывать нажатия нескольких кнопок, см. статью [10].

Обновление приложения Android. Перед тем, как продолжить, сохраните текущий проект в новый файл ARD_HM10_AI2_Single_LED_07.aia.

Измененный блок целиком:

ARD HM 10 AI2 04 01

Вот что здесь изменено: сначала команда разбивается на список, при разбиении запятая используется в качестве разделителя. В каждом элементе списка окажется один символ команды.

ARD HM 10 AI2 04 03

Затем первый элемент списка сравнивается с "L":

ARD HM 10 AI2 04 04

Если это действительно "L", то вызывается процедура COMMAND_LED:

ARD HM 10 AI2 04 02

COMMAND_LED это новая процедура, она обрабатывает только команды LED. Сначала она проверяет номер LED (второй элемент массива):

ARD HM 10 AI2 04 05

После чего она меняет вид кнопки управления LED в зависимости от того, какой символ поступил в третьем элементе списка:

ARD HM 10 AI2 04 06

Также здесь была добавлена обработка ошибок. С помощью двух операторов if и оператора else перехватываются некорректные значения. Здесь для упрощения просто вставлен блок комментария, который может быть заменен выводом всплывающего окна сообщения об ошибке.

Вот так выглядят все блоки программы:

ARD HM10 AI2 Single LED 07 blocks

В следующем примере будет показано, как скетч и приложение Android можно легко доработать для управления несколькими устройствами.

[Пример 5: 3 светодиода и 3 кнопки]

Добавим больше светодиодов и кнопок, и расширим команды управления, чтобы этим можно было управлять.

Схема. Дополнительные кнопки были подключены к выводам портов D3 и D4. Светодиоды были добавлены на порты D6 и D7.

ARD HM 10 AI2 05 00 Breadboard

ARD HM 10 AI2 05 01 Circuit

Скетч Arduino. Здесь не будет описан процесс создания скетча, код создавался по тем же принципам, как и в предыдущих примерах. Подробно об этом можно узнать в статье [10].

Однако здесь все-таки нужно сделать несколько замечаний. Создается необходимая команда для отправки в приложение Android. Для этого используется временная переменная команды TMPcmd, и значения заменяются перед отправкой команды.

Для принимаемых команд предполагается, что принятые значения корректны и находятся в нужном диапазоне, поэтому напрямую используются значения из команды без дополнительных проверок. Порты светодиодов сохранены в массиве, поэтому можно использовать номер светодиода из команды в качестве индекса в этом массиве, и никаких проверок номера светодиода не делается.

/**************************************************************
*  Arduino, HM-10, App Inventor 2
*
*  Example Project Part 5: 3 leds and 3 switches
*  By Martyn Currey. www.martyncurrey.com
*
* Три светодиода включаются и выключаются как из приложения Android,
* так и от физических кнопок, подключенных к портам Arduino.
*
* Используются следующие цифровые порты:
* D9 - AltsoftSerial RX
* D8 - AltsoftSerial TX
* D7 - LED + резистор
* D6 - LED + резистор
* D5 - LED + резистор
* D4 - кнопка
* D3 - кнопка
* D2 - кнопка
*/
 
// Библиотека AltSoftSerial использует ножки портов D9 для TX UART
// и D8 для RX UART. В то же время при использовании AltSoftSerial
// порт D10 нельзя задействовать для генерации ШИМ (PWM).
// Библиотеку AltSoftSerial загрузите по ссылке
// https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
#include < AltSoftSerial.h>
AltSoftSerial BTserial;
 
// Переменные, используемые для приходящих данных:
const byte maxDataLength = 20;
char receivedChars[21];
boolean newData = false;
 
// Константы для выводов портов:
const byte SWITCH_PIN[] = {2,3,4};
const byte LED_PIN[] = {5,6,7}; 
 
// Глобальные флаги:
boolean LED_State[] = {false,false,false};
boolean switch_State[] = {false,false,false};
boolean oldswitch_State[] = {false,false,false};
 
void setup()  
{
   for (byte pin = 0; pin < 3; pin++) 
   {
      // Настройка портов кнопок на вход:
      pinMode(SWITCH_PIN[pin], INPUT); 
      // Настройка выводов портов светодиодов на выход
      // и перевод их в лог. 0:
      pinMode(LED_PIN[pin], OUTPUT);  digitalWrite(LED_PIN[pin],LOW);
   }
 
   // Открытие соединения терминала для отладки:
   Serial.begin(9600);
   Serial.print("Sketch:   ");   Serial.println(__FILE__);
   Serial.print("Uploaded: ");   Serial.println(__DATE__);
   Serial.println(" ");
 
   // Открытие программного последовательного соединения
   // с модулем Bluetooth HM-10:
   BTserial.begin(9600); 
   Serial.println("AltSoftSerial started at 9600"); 
 
   newData = false;
}
 
void loop()  
{
   for (byte switchNum = 1; switchNum < 4; switchNum++) 
   {
      checkSwitch(switchNum);
   }
   // Проверка, была ли принята какая-нибудь новая команда:
   recvWithStartEndMarkers();
   // Если мы приняли новую команду, то выполним
   // соответствующие действия:
   if (newData) { processCommand(); }
}
 
/****************************************
* Функция checkSwitch()
* проверяет состояние кнопок и включает или выключает
* светодиоды в зависимости от состояния кнопки.
*
* Используемые глобальные переменные:
*      switch_State[]
*      LED_State[]
*      oldswitch_State[]
*
* Устанавливает:
*      switch_State[]
*      LED_State[]
*      oldswitch_State[]
*/
void checkSwitch( byte pos)
{
   // Номер позиции pos = 1,2,3. Индексы в массиве должны
   // быть 0,1,2, поэтому преобразуем позиции в индекс
   // вычитанием единицы:
   pos = pos-1;
 
   // Очень простое подавление дребезга контактов:
   boolean state1 = digitalRead(SWITCH_PIN[pos]); delay(1);
   boolean state2 = digitalRead(SWITCH_PIN[pos]); delay(1);
   boolean state3 = digitalRead(SWITCH_PIN[pos]); delay(1);
   if ((state1 == state2) && (state1==state3))
   { 
      switch_State[pos] = state1;  
      if ( (switch_State[pos] == HIGH) && (oldswitch_State[pos] == LOW) )
      {
         // Переключение состояние в противоположное значение:
         LED_State[pos] = ! LED_State[pos];
 
         // Вместо того, чтобы создать длинный список команд,
         // команда создается на лету во временной переменной TMPcmd.
         // Значения в ней заменяются в зависимости от того,
         // какая кнопка была нажата.
         char TMPcmd[8] = "[L,1,0]";
         // pos+1 должны иметь значения 1,2 или 3. Здесь происходит
         // преобразование этой позиции в ASCII-код путем добавления
         // значения 48:
         TMPcmd[3] = pos+1+48;
         // LED_State должно быть 0 или 1:
         TMPcmd[5] = LED_State[pos]+48;
         BTserial.print(TMPcmd);
 
         digitalWrite(LED_PIN[pos],LED_State[pos]);
         Serial.println(TMPcmd);
      }
      oldswitch_State[pos] = switch_State[pos];
   }
}
 
/*****************************************
* Функция processCommand
* парсит данные команды, находящиеся в массиве receivedChars[].
* Массив receivedChars[] не проверяется на наличие ошибок.
*
* Используемые глобальные переменные:
*       receivedChars[]
*       newData
*
* Устанавливает:
*       receivedChars[]
*       newData
*/
void processCommand()
{
   Serial.print("receivedChars = ");   Serial.println(receivedChars);
 
   if (receivedChars[0] == 'L')  // У нас поступила команда управления LED?
   {
      // Мы знаем, что у команды управления LED длина фиксирована ("L10"),
      // и значение в позиции 1 это номер светодиода, а значение в позиции
      // 2 это значение, обозначающее состояние светодиода (on/off). 
      // В этом месте используется простое преобразование ASCII-кода
      // в номер светодиода и в двоичное значение состояния светодиода
      // путем вычитания 48 (значение 48 это ASCII-код символа "0"):
      byte LEDnum = receivedChars[1] - 48;
      boolean LEDstatus = receivedChars[2] - 48;
 
      digitalWrite(LED_PIN[LEDnum-1],LEDstatus);
      LED_State[LEDnum-1] = LEDstatus;
   }
 
   receivedChars[0] = '\0';
   newData = false;
}
 
// Функция recvWithStartEndMarkers от Robin2 с форума Arduino,
// см. http://forum.arduino.cc/index.php?topic=288234.0
/*****************************************
* Функция recvWithStartEndMarkers
* читает последовательные данные и возвращает содержимое
* между маркерами начала и конца команды.
*
* Используемые глобальные переменные:
*       receivedChars[]
*       newData
*
* Устанавливает:
*       newData
*       receivedChars
*/
void recvWithStartEndMarkers()
{
   static boolean recvInProgress = false;
   static byte ndx = 0;
   char startMarker = '[';
   char endMarker = ']';
   char rc;
 
   if (BTserial.available() > 0) 
   {
      rc = BTserial.read();
      if (recvInProgress == true) 
      {
         if (rc != endMarker) 
         {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx > maxDataLength) { ndx = maxDataLength; }
         }
         else 
         {
            receivedChars[ndx] = '\0'; // завершение строки
            recvInProgress = false;
            ndx = 0;
            newData = true;
         }
      }
      else if (rc == startMarker) { recvInProgress = true; }
   }
}

Обновление приложения Android. Сохраните текущий проект в файл ARD_HM10_AI2_3LEDs_3Switches_08.aia.

Вся сложная работа уже была выполнена раньше. В этой части мы просто дублируем органы управления в приложении и расширяем списки проверки if/then в процедуре COMMAND_LED.

Сначала добавим две кнопки управления LED. Между кнопками также добавлены пустые текстовые метки с пробелами для их выравнивания.

Параметры выравнивающих текстовых меток:

Height – Fill parent (заполнить все родительское пространство)
Width – 5 pixels (ширина 5 точек)
HasMargins – unchecked (границ метки нет)

ARD HM 10 AI2 05 12

Над кнопками добавлены цифровые метки для их идентификации. Свойство HasMargins оставлено не установленным, чтобы размеры меток совпадали. У меток также есть метки интервалов, чтобы отделить их друг от друга так же, как и кнопки.

ARD HM 10 AI2 05 11

И конечно, для новых кнопок у нас будут новые блоки. Процедура BTN_LED01.Click была дублирована два раза и имена были изменены на BTN_LED02 и BTN_LED03, и обновлена посылаемая команда. Вы можете сделать то же самое с процедурами LED_BUTTON_ON и LED_BUTTON_OFF, скопируйте их и измените имена.

ARD HM 10 AI2 05 15

ARD HM 10 AI2 05 16

Клики на кнопках обрабатываются, теперь нужно позаботиться о том, что приложение может принимать дополнительные команды.

Поскольку мы принимаем только команды LED, то нам не нужно менять процедуру processCommand, надо обновить только процедуру COMMAND_LED.

Теперь у нас есть команды также и для LED #2 и LED #3. Все что нужно сделать - скопировать оригинальные блоки и обновить проверяемые значения для того, чтобы знать, для какого светодиода нужно вызвать процедуру включения/выключения.

Процедуру COMMAND_LED можно было бы сделать немного меньше. Например, мы знаем, что независимо от того, какой светодиод используется, код выключения/включения должен быть 0 или 1. Мы можем проверить это в начале вместо того, что делать это в трех разных местах. Другой вариант - поместить элементы кнопок в список, и затем использовать номер светодиода как индекс в этом списке. Это будет работать примерно так же, как и скетч Arduino. Здесь для упрощения оставлено 3 блока кода.

ARD HM 10 AI2 05 17

Если Вы читали предыдущие примеры, то должны помнить, что команда LED разбивается на список, и первый элемент в списке это "L" для LED, второй элемент это номер LED (от 1 до 3), и третий элемент это код выключения/включения (0 или 1).

Первый элемент проверяется в процедуре processCommand, и если в нем обнаружен код "L", то список передается в процедуру COMMAND_LED.

ARD HM 10 AI2 05 20

В процедуре COMMAND_LED проверяется второй элемент списка, где находится номер LED:

ARD HM 10 AI2 05 21

Если номер светодиода 1, 2 или 3, то проверяется третий элемент списка, где находится код выключения/включения. Если приложение обнаружит "0" или "1", то вызывается процедура изменение кнопки в состояние OFF или процедура изменения кнопки в состояние ON.

ARD HM 10 AI2 05 22

Вот так будут выглядеть полученные блоки приложения:

ARD HM10 AI2 08 blocks

В статье [2] рассматривается добавление слайдера, управляющего яркостью светодиода.

[Ссылки]

1. Arduino, HM-10 and App Inventor 2 site:martyncurrey.com.
2. Arduino, HM-10 and App Inventor 2: Adding a slider site:martyncurrey.com.
3. Make IBeacon With HM10/HM11 site:instructables.com.
4. Модули HM-10 Bluetooth 4 BLE.
5. Tutorials for App Inventor site:appinventor.mit.edu.
6. Make apps for the Internet of Things with MIT App Inventor! site:appinventor.mit.edu.
7. BluetoothLE site:iot.appinventor.mit.edu.
8. AltSoftSerial Library site:pjrc.com.
9. Topic: Serial Input Basics site:forum.arduino.cc.
10. Turning a LED on and off with an Arduino, Bluetooth and Android. Part III 3 LEDs and 3 Switches site:martyncurrey.com.
11. 181221BLE-Bluetooth-HM-10-with-AI2.zip - скетчи, проекты AI2, документация.

 

Комментарии  

 
0 #1 Alexey 10.05.2019 14:21
Возможно ли сделать так, чтобы не Android устройство подключалось к HM-10, а HM-10 в роли мастера подключался к Android устройству? Мне удавалось подключить HM-10 друг к другу, подключаются без проблем. Однако подключить его к Android устройству не удавалось (при сканировании Android устройство даже не находится).
Цитировать
 

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


Защитный код
Обновить

Top of Page