Здесь приведен перевод руководства [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
Характеристика FFE1 активна по умолчанию. FFE2 не активна, и должна быть включена перед использованием. Обратите внимание, что характеристика FFE2 доступна только на запись. Это означает, что Вы можете использовать её для отправки данных в HM-10, но не можете использовать для отправки данных в обратном направлении, из HM-10. В последующих примерах характеристика и идентификатором UUID FFE1 используется для чтения и записи данных.
Из-за того, что слой UART HM-10 ограничен в использовании пользовательской характеристикой, в примерах будет использоваться одна характеристика для передачи всех данных. Это означает, что HM-10 на самом деле не использует возможности BLE так, как предусмотрено стандартом BLE. Если делать то же самое на другом чипе или модуле, который позволяет больше возможностей по управлению протоколом BLE (наподобие модулей Nordic BLE или Arduino 101), то можно было бы создать отдельные характеристики для разных устройств; например, светодиоды (LED) имели бы свою характеристику, кнопки управления свою, и так далее. Можно было бы даже создать отдельную характеристику для каждого светодиода или кнопки. Если Вы хотите управлять только двумя лампочками, было бы удобно применить для них отдельные характеристики. Если нужно управлять большим количеством лампочек, то создание отдельной характеристики для каждой лампочки было бы слишком трудоемким.
Среда 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): включать его и выключать.
Схема. Соединения очень простые, система состоит из платы 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 для вывода отладочных сообщений.
Скетч 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.
voidloop()
{
// Чтение данных из модуля 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, будет отображаться в окне терминала.
Приложение 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].
Откройте App Inventer, откройте aia-файл BaseConnect, и сохраните его как ARD_HM10_AI2_Single_LED_01.aia. Модуль HM-10 доступен для подключения на устройстве Android, если использовать приложение MIT AI2 Companion. Кликните Scan, и увидите, какие устройства можно найти в сети Bluetooth BLE.
На этом скриншоте видны разные устройства BLE, включая HM-10 (первый в списке). К сожалению, текст очень мал и неудобен для чтения.
В Дизайнере (Designer) выберите ListBLE, и поменяйте размер текста TextSize на 48. Теперь можно прочитать имена найденных устройств BLE.
Приложение BaseConnect очень простое, и в секции Blocks мы можем увидеть, что оно состоит 7 блоков или процедур. То, что делает каждая процедура, должно быть самодокументированным и понятным.
Добавление кнопок управления LED. Следующий шаг - добавление органов управления светодиодом, чтобы можно было его включать и выключать. Вместе с кнопками автор использовал текстовые метки из пробелов для организации интервалов между кнопками. Кнопки расположены горизонтально (Horizontal Arrangement).
Нужно переименовать элементы:
После того, как Вы добавили элементы управления, сохраните проект в файл 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" подойдет любой из этих вариантов. Автор использовал строки, потому что при таком варианте в будущем можно было бы применить более сложные управляющие коды без внесения лишних изменений.
Блоки BLE Write обычно ожидают данные, переданные в списке. К счастью, если Вы используете простые данные (plain data), AI2 будет их прозрачно преобразовывать в нужный тип.
Чтобы можно было записать данные, нам нужно знать service UUID и characteristic UUID.
Функция WriteStrings слепая, она просто отправляет данные, и у неё нет никакого понятия о том, принимает ли их HM-10, или нет. В расширении BLE мы можем проверить завершение передачи путем использования блока WriteStringsWithResponse.
Это инструктирует модуль HM-10 отправить обратно подтверждение. AI2 получает это подтверждение, и это вызывает срабатывание события в AI2, что мы можем проверить. Пока нам это не нужно, будем использовать только функцию WriteStrings.
В редакторе блоков (Blocks editor), добавьте несколько глобальных переменных, чтобы в них держать номера UUID и добавьте блоки события клика на кнопках управления LED:
Теперь Вы должны реализовать действия при нажатии кнопки 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):
Теперь нам нужно поменять, что будет происходить по клику на кнопке Scan. Когда на этой кнопке осуществлен клик, нам сначала надо проверить, разрешен ли Bluetooth, если разрешен, то можно сканировать. Если нет, то нам нужно выдать сообщение об ошибке. Проверка, разрешен ли Bluetooth, делается с помощью блока BluetoothClient.Enabled.
В редакторе блоков добавьте проверку Bluetooth в процедуру ButtonScan.Click. Одновременно добавьте блок else к блоку if/then, и вызовите оповещатель. Вызов оповещателя отобразит сообщение об ошибке, если Bluetooth на устройстве Android не разрешен.
Теперь если мы попробуем запустить сканирование перед включением Bluetooth, то получим предупреждение:
С кнопками управления LED нам нужно проверить, активно ли подключение, перед попыткой отправки кодов управления "0" и "1". Это можно сделать в блоке BLE isDeviceConnected.
Теперь если кликнуть на кнопку ON или OFF, когда HM-10 не подключен, то ничего не произойдет. Вы могли бы, если бы захотели, отобразить сообщение об ошибке. Но скорее всего иметь слишком много подобных сообщений не очень хорошо, поэтому лучше ничего не делать. Как вариант, можно сделать эти кнопки не активными до тех пор, пока не будет активизировано подключение.
Запустите приложение еще раз, теперь оно будет выглядеть лучше. Когда Bluetooth не разрешен, мы получим отличное сообщение, говорящее нам, что нужно его включить. Когда мы кликнем на кнопки управления LED без наличия соединения, больше не будем получать системное сообщение об ошибке. При клике на другие кнопки с выключенным Bluetooth мы получим больше системных сообщений об ошибках.
Сейчас у нас есть одна кнопка для запуска сканирования, и еще одна для остановки сканирования. Их можно объединить одной переключающей кнопкой (toggle button), где будет меняться надпись в зависимости от текущего состояния, SCAN и STOP SCAN. Приложение запустится с надписью на кнопке SCAN, и если на неё кликнуть, то при включенном Bluetooth запустится сканирование, и на кнопке поменяется надпись на STOP SCAN. Это означает, что пользователь может нажать только на эту же единственную кнопку с надписью STOP SCAN, если он до этого нажал на кнопку SCAN. Этот метод также означает, что текст на кнопке можно использовать как некий флаг состояния. То же самое можно проделать с кнопками Connect и Disconnect, заменив их на одну кнопку.
В Дизайнере поменяйте текст на кнопке Scan, и удалите кнопку Stop Scan. В редакторе блоков удалите функцию обработки события ButtonStopScan.Click, она больше не нужна.
Процедуру события ButtonScan.Click нужно расширить следующим образом:
Теперь при клике функция сначала проверяет текст на кнопке, если этот текст "SCAN", то мы знаем, что нужно запустить сканирование. Если текст не "SCAN", то этот текст должен быть "STOP SCAN", и нужно установить сканирование.
То же самое проделайте с кнопками Connect и Disconnect. Объедините две кнопки в одну переключающую, которая обеспечит такое поведение, что пользователь может выполнить отключение только в том случае, если перед этим он активизировал подключение. Однако это не становит пользователя от нажатия на "Connect", если до этого он не выбрал устройство, с которым он будет осуществлять соединение. Мы разберемся с этим после комбинирования кнопок.
В функции события ButtonConnect.Click добавьте проверку текста на кнопке, и переместите инструкции подключения:
В редакторе блоков (Blocks editor) удалите функцию обработки события ButtonDisconnect.Click, и затем удалите кнопку ButtonDisconnect в Дизайнере. Поменяйте текст на кнопке ButtonConnect на "CONNECT".
Если Вы попробуете запустить это приложение, то увидите, что подключение и отключение работает, но все еще будет появляться ошибка, если Вы попробуете подключиться перед сканированием и выбором устройства для подключения. Мы это попробуем исправить позже.
У элемента AI2 ListView есть несколько свойств, которые мы можем использовать, если выбран один из элементов списка: List.Selection и List.SelectionIndex. Перед тем, как сделан выбор в списке, значение List.SelectionIndex равно 0 и List.Selection равно null или "". Используя SelectionIndex, мы можем легко увидеть, что если SelectionIndex больше 0, то пользователь что-то выбрал в списке.
Давайте попробуем, наш алгоритм должен работать. Как минимум это сработает, когда Вы попробуете подключиться первый раз. Это произойдет, если Вы выполните сканирование, подключение, затем отключение, и затем снова подключение без сканирования. Приложение попробует подключиться снова к тому же самому устройству. Приложение помнит, какой элемент в списке выбран, и использует его снова. Вы можете либо все оставить как есть, как стандартное поведение, или можете выбрать сбрасывать индекс списка, когда было сделано соединение.
В настоящий момент когда пользователь кликает на кнопку CONNECT/DISCONNECT, на кнопке будет соответствующим образом меняться текст. Он поменяется даже в том случае, когда есть проблема с соединением, и оно по какой-то причине не произошло. Чтобы исправить это, переместите инструкции изменения текста в функции BluetoothLE1.Connected и BluetoothLE1.Disonnected. Это должно выглядеть примерно так:
В функции обработки события ButtonConect.Click сейчас есть инструкция для установки текста LabelStatus в "Status: Connecting", что можно также при желании сделать и с кнопкой CONNECT/DISCONNECT.
После всех изменений приложение будет выглядеть чище и проще:
Вот так выглядят все блоки:
Сохраните текущий проект в 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 и пустые метки, которые использовались ранее для интервалов между кнопками:
После добавления оператора if/then для проверки текста на кнопке и перемещения блоков мы получим следующий код:
Это отлично работает, кнопка ON/OFF полностью управляет светодиодом LED. Однако есть еще несколько вещей, которые хорошо бы исправить.
1. Когда приложение запускается, то на кнопке присутствует текст "ON", хотя LED по умолчанию выключен после подачи питания на плату Arduino. 2. Цвет у кнопки не меняется. 3. Нам нужно в двух местах кода проверять наличие соединения.
Поменяйте в Дизайнере текст кнопки на на "OFF", и текст фона кнопки (background colour) на красный (red). Теперь при старте приложения по умолчанию на кнопке будет текст OFF, и кнопка будет красная.
Чтобы поменять цвет кнопки, мы будем использовать её свойство BackgroundColor. Цвета выбираются в меню Color.
Примечание: AI2 дает больше возможностей для выбора цвета, но в этом примере используются базовые цвета, которые можно выбрать в меню.
Ниже показан код с командами смены цвета. Кнопка теперь вместе с текстом будет менять также и свой цвет.
Вместо того, чтобы делать проверку соединения после проверки текста на кнопке, мы можем сначала проверять соединение. В таком случае нам понадобится делать проверку соединения только 1 раз. Для этой цели просто переместите соответствующий оператор if/then выше проверки текста на кнопке.
После сделанных изменений поведение приложения не поменяется, но код станет немного лучше.
Есть еще кое-что, на что следует обратить внимание. Когда Вы кликаете на кнопку SCAN, приложение продолжает сканировать, пока Вы не кликните на кнопку STOP SCAN. Когда Вы осуществили соединение, приложение все еще продолжает сканирование, хотя это уже не нужно. Чтобы исправить это, сделаем так, что когда мы кликаем на кнопку CONNECT, сканирование будет останавливаться.
Посмотрите на функцию кнопки SCAN:
Если в момент нажатия на кнопку CONNECT на кнопке ButtonScan на кнопке ButtonScan присутствует текст "SCAN", то он должен быть заменен на "STOP SCAN", и сканирование должно быть остановлено.
Мы могли бы просто скопировать эти операторы из обработчика кнопки сканирования, однако есть способ получше: следует переместить эти операторы в отдельную процедуру, и делать её вызов.
Схватите блок создания новой процедуры, и переместите туда блоки остановки сканирования. Переименуйте эту процедуру в StopScan.
После того, как новая процедура создана, она будет добавлена в меню процедур:
Теперь у нас есть процедурный блок StopScan, который можно вызвать. Перетащите его в процедуру ButtonScan.Click.
Чтобы остановить сканирование, когда осуществляется клик на кнопке CONNECT, добавьте вызов процедуры StopScan в процедуру ButtonConnect.Click в секцию CONNECT после проверки индекса ListBLE.SecitionIndex.
Последнее, что следуют сделать, это добавить отображаемое имя приложения (title). В Дизайнере добавьте 3 метки в самой верхней части экрана.
Переименуйте их:
Отредактируйте свойства меток пробелов (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
Получится следующая форма приложения:
Теперь у нас есть приложение, с помощью которого можно управлять по радио светодиодом, подключенным к плате 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.
Скетч Arduino. Код проверки состояния кнопки оформлен в виде отдельной функции checkSwitch(), которая вызывается в главном цикле программы (loop). Функция checkSwitch() проверяет, нажата ли кнопка, и если нажатие определено, то она меняет состояние LED, и одновременно отправляет соответствующий код управления в приложение Android через UART-соединение с модулем HM-10.
Код, который проверяет принятые данные, остался таким же, как раньше, просто был перенесен в отдельную функцию checkRecievedData(). Использование отдельных функций подобного рода делает основной код программы красивым и ясным для понимания. Это также дает возможность повторного использования кода (что может сэкономить память программ) и снижает вероятность ошибок.
voidloop()
{
checkSwitch();
checkRecievedData();
}voidcheckSwitch()
{
// Функция переключения по нажатию кнопки, с простым подавлением// дребезга контактов.
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;
}
}
voidcheckRecievedData()
{
// Чтение из модуля 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:
Сам по себе блок .StringsReceived использовать нельзя, он полагается на событие приема данных (data received event), которое создается блоком .RegisterForStrings:
Обратите внимание, что это в свою очередь полагается на характеристику, у которой есть свойство оповещения (notify property). Среда AI2 неявно говорит модулю BLE включить оповещения, и затем использует эти оповещения для генерации событий приема данных.
Когда мы завершаем работу с соединением, то хорошей практикой будет снять регистрацию или отменить событие приема данных, что реализуется с помощью блока .UnregisterForValues.
Мы не можем зарегистрировать событие до запуска соединения, поэтому хорошее место для блока .RegisterForStrings - тело процедуры BluetoothLE1.Connected. Затем после того, как соединение установится, сразу вызывается блок .RegisterForStrings.
Блок .UnregisterForValues должен быть вызван перед вызовом отключения. Если Вы попытаетесь вызвать .UnregisterForValues после разрыва соединения, то получите ошибку.
В результате получится следующий код:
У нового приложения есть 2 возможных способа поменять вид своей кнопки управления LED:
1 – клик на эту кнопку, 2 – через обработку поступающих кодов управления.
Это означает, что у нас есть 2 части приложения, которые меняют текст на кнопке, одна часть меняет текст на "ON", и вторая на "OFF". Вместо того, чтобы дублировать одинаковый код, были сделаны отдельные процедуры для изменения свойств кнопки LED:
Теперь для изменения кнопки мы просто вызываем LED_BUTTON_ON или LED_BUTTON_OFF в процедуре события BTN_LED.Click:
Осталось проверять приходящие данные и обновлять кнопку управления LED. Как уже упоминалось ранее, блок .StringsReceived вызывается, когда были приняты новые данные.
Это дает нам список вместо простых данных, так что придется обрабатывать этот список. Сначала делается двойная проверка, что у нас действительно есть список, с помощью блока "is a list?" (это список?).
Затем делается цикл по всем элементам списка в блоке "for each item in list" (для каждого элемента в списке). В списке находятся значения stringValues.
Примечание: у нас должен быть только 1 символ в принятых данных, так что в списке должен быть только 1 элемент, и Вам может показаться, что не нужно делать цикл по всем элементам списка. Для этого простого примера возможно так и есть (и здесь мы так и делаем), но хорошей практикой было бы просматривать весь список, потому что в будущем может появиться приложение, которое очень быстро получает много данных. Кроме того, что может произойти, если кто-то будет нажимать кнопку на Arduino очень быстро?
Теперь нам нужно добавить проверку данных на "1" или "0", и если такие данные пришли, то соответственно менять в приложении вид кнопки управления LED.
Обратите внимание, что здесь у нас 2 оператора if вместо одного оператора if/then. Один проверяет данные на "0", и второй на "1". Такой способ проверки данных отбрасывает все не корректные входные данные.
На этом все, у нас получилось два способа управления:
Блоки приложения:
Теперь Вы можете использовать этот метод для дополнительных односимвольных кодов управления:
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.
Скетч 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, имеют немного другой формат по сравнению с кодами, которые он получает.
voidloop()
{
// Опрос кнопки:
checkSwitch();
// Проверка, принята ли новая команда:
recvWithStartEndMarkers();
// Если у нас есть новая команда, то выполним её обработку:if (newCommand) { processCommand(); }
}
/******************************************
* Функция checkSwitch()
* проверяет состояние кнопки и включает и выключает
Обновление приложения Android. Перед тем, как продолжить, сохраните проект под новым именем ARD_HM10_AI2_Single_LED_06.aia.
Чтобы увидеть, как работает скетч Arduino, в процедуре BTN_LED.Click поменяйте коды, которые посылает приложения, на "[L10]" и "[L11]".
Если Вы запустите приложение, и попробуете его в работе, то увидите, что оно может управлять светодиодом, но не меняет вид своей кнопки управления светодиодом. Причина в том, что Arduino посылает теперь новые коды управления, и приложение ничего пока про это не знает. Давайте это исправим.
В предыдущей версии приложения оно обрабатывало только одиночные символы, и поэтому их прием и проверки были довольно просты. Теперь у нас в команде больше одного символа, и нужен, как и в скетче Arduino, какой-то способ получить все данные команды и гарантировать, что получена команда целиком.
В скетче Arduino переменная receivedChars использовалась для сохранения принятых данных. В приложении Android мы будем использовать нечто подобное, глобальную переменную с именем BT_receivedData_Buffer.
Когда мы будем получать новые данные, то будем добавлять их в этот буфер. В процедуре BluetoothLE1.StringsReceived удалите блоки, которые проверяют появление символов "1" и "0", и замените их на блок:
В результате получится следующее:
Теперь новые принятые данные добавляются в буфер вместо немедленной обработки. Следовательно, что нам нужно потом что-то делать с этим буфером.
Чтобы обработать буфер, создайте (перетаскиванием из меню) новую процедуру и переименуйте её в PROCESS_BUFFER. В этом месте может быть хорошей идеей подумать о том, что у нас есть и что с этим делать.
У нас есть переменная, в которой может находиться или не находиться код команды. Мы знаем что полная команда находится между маркерами начала и конца, так что с помощью этой информации мы можем определить, получена ли команда полностью. Если в буфере содержится маркер начала "[" и также в нем есть маркер конца "]", и маркер начала находится перед маркером конца, то это хороший шанс, что в буфере находится вся команда целиком. Тогда мы можем взять из буфера эту команду и разобрать, что она означает. Если она означает какое-то действие, то это действие должно быть выполнено.
Вот код блока процедуры целиком, после этого разберем, как он работает:
Сначала создаются локальные (временные) переменные:
Как только эти локальные переменные созданы, присваиваются значения переменным startMarkerPos и endMarkerPos. Если символы маркера не найдены, то startMarkerPos и/или endMarkerPos будут равны 0.
Далее проверяется позиция маркеров. Если маркеры найдены, то их позиции будут > 0, следующая проверка подтверждает, что маркер начала находится перед маркером конца.
После успеха этих проверок реальная команда копируется в локальную переменную command.
И затем данные команды (включая маркеры начала и конца) удаляются из буфера приема.
В этом месте переменная command хранит себе команду, которая была отправлена кодом Arduino в процедуре processCommand. Аналогичная процедура здесь пока не создана.
И наконец, здесь реализована проверка, есть ли в буфере еще маркеры начала и конца, и если есть, то весь процесс повторяется.
Все это содержится в цикле while.
Это удобный способ зацикливания, когда есть больше одного условия для выхода из цикла. Автор использовал переменную Done, которая устанавливается в false в начале, и по завершении она устанавливается в true, чтобы выйти из цикла. В результате получается "while not done" ("пока не будет готово"). Этот код проще читать, и поэтому такая организация цикла имеет смысл.
Затем нам нужно вызывать процедуру PROCESS_BUFFER из функции BluetoothLE1.StringsReceived.
Все это будет хорошо работать, пока маркер начала будет находиться перед маркером конца. Если по какой-то причине сначала в буфере окажется маркер конца, то приложение ничего не обработает. Оно найдет оба маркера, но начальный маркер начала не будет первым, и произойдет выход из цикла. В следующей проверке буфера будет получен точно такой же результат.
Чтобы исключить такую ситуацию, была добавлена функция обрезки до маркера начала TrimToStartMarker. Она обрезает буфер таким образом, что первым символом будет начальный маркер.
Вызов TrimToStartMarker был помещен в начало процедуры PROCESS_BUFFER.
Все, что осталось сделать, проверить принятые коды и если они корректные, менять внешний вид кнопки. Это делается в процедуре PROCESS_COMMAND.
Вот так выглядят получившиеся блоки:
В четвертом примере мы рассмотрим обработку кодов управления, что должно быть очень простым. В примере 5 добавим больше светодиодов и кнопок. В примере 6 попробуем работу слайдеров, чтобы управлять яркостью светодиода.
[Пример 4: обработка кодов]
В этой секции мы подготовимся к добавлению дополнительных управляемых устройств, например чтобы можно было управлять большим количеством светодиодов.
В настоящий момент команда управления обрабатывается как один элемент, например "L10" и "L11". Здесь мы разобьем команды на отдельные символы, и обработаем каждый символ отдельно:
L для светодиода (LED) 1 для номера светодиода 1/0 чтобы включить или выключить светодиод.
Если Вы захотите переключать 2 или 3 светодиода, то это можно легко реализовать такими дополнительными проверками.
Скетч Arduino. Здесь изменилась только функция processCommand().
voidprocessCommand()
{
// Команда для управления 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.
Измененный блок целиком:
Вот что здесь изменено: сначала команда разбивается на список, при разбиении запятая используется в качестве разделителя. В каждом элементе списка окажется один символ команды.
Затем первый элемент списка сравнивается с "L":
Если это действительно "L", то вызывается процедура COMMAND_LED:
COMMAND_LED это новая процедура, она обрабатывает только команды LED. Сначала она проверяет номер LED (второй элемент массива):
После чего она меняет вид кнопки управления LED в зависимости от того, какой символ поступил в третьем элементе списка:
Также здесь была добавлена обработка ошибок. С помощью двух операторов if и оператора else перехватываются некорректные значения. Здесь для упрощения просто вставлен блок комментария, который может быть заменен выводом всплывающего окна сообщения об ошибке.
Вот так выглядят все блоки программы:
В следующем примере будет показано, как скетч и приложение Android можно легко доработать для управления несколькими устройствами.
[Пример 5: 3 светодиода и 3 кнопки]
Добавим больше светодиодов и кнопок, и расширим команды управления, чтобы этим можно было управлять.
Схема. Дополнительные кнопки были подключены к выводам портов D3 и D4. Светодиоды были добавлены на порты D6 и D7.
Скетч Arduino. Здесь не будет описан процесс создания скетча, код создавался по тем же принципам, как и в предыдущих примерах. Подробно об этом можно узнать в статье [10].
Однако здесь все-таки нужно сделать несколько замечаний. Создается необходимая команда для отправки в приложение Android. Для этого используется временная переменная команды TMPcmd, и значения заменяются перед отправкой команды.
Для принимаемых команд предполагается, что принятые значения корректны и находятся в нужном диапазоне, поэтому напрямую используются значения из команды без дополнительных проверок. Порты светодиодов сохранены в массиве, поэтому можно использовать номер светодиода из команды в качестве индекса в этом массиве, и никаких проверок номера светодиода не делается.
voidsetup()
{
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;
}
voidloop()
{
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[]
*/
voidcheckSwitch( 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
*/
voidprocessCommand()
{
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
* читает последовательные данные и возвращает содержимое
Обновление приложения Android. Сохраните текущий проект в файл ARD_HM10_AI2_3LEDs_3Switches_08.aia.
Вся сложная работа уже была выполнена раньше. В этой части мы просто дублируем органы управления в приложении и расширяем списки проверки if/then в процедуре COMMAND_LED.
Сначала добавим две кнопки управления LED. Между кнопками также добавлены пустые текстовые метки с пробелами для их выравнивания.
Над кнопками добавлены цифровые метки для их идентификации. Свойство HasMargins оставлено не установленным, чтобы размеры меток совпадали. У меток также есть метки интервалов, чтобы отделить их друг от друга так же, как и кнопки.
И конечно, для новых кнопок у нас будут новые блоки. Процедура BTN_LED01.Click была дублирована два раза и имена были изменены на BTN_LED02 и BTN_LED03, и обновлена посылаемая команда. Вы можете сделать то же самое с процедурами LED_BUTTON_ON и LED_BUTTON_OFF, скопируйте их и измените имена.
Клики на кнопках обрабатываются, теперь нужно позаботиться о том, что приложение может принимать дополнительные команды.
Поскольку мы принимаем только команды LED, то нам не нужно менять процедуру processCommand, надо обновить только процедуру COMMAND_LED.
Теперь у нас есть команды также и для LED #2 и LED #3. Все что нужно сделать - скопировать оригинальные блоки и обновить проверяемые значения для того, чтобы знать, для какого светодиода нужно вызвать процедуру включения/выключения.
Процедуру COMMAND_LED можно было бы сделать немного меньше. Например, мы знаем, что независимо от того, какой светодиод используется, код выключения/включения должен быть 0 или 1. Мы можем проверить это в начале вместо того, что делать это в трех разных местах. Другой вариант - поместить элементы кнопок в список, и затем использовать номер светодиода как индекс в этом списке. Это будет работать примерно так же, как и скетч Arduino. Здесь для упрощения оставлено 3 блока кода.
Если Вы читали предыдущие примеры, то должны помнить, что команда LED разбивается на список, и первый элемент в списке это "L" для LED, второй элемент это номер LED (от 1 до 3), и третий элемент это код выключения/включения (0 или 1).
Первый элемент проверяется в процедуре processCommand, и если в нем обнаружен код "L", то список передается в процедуру COMMAND_LED.
В процедуре COMMAND_LED проверяется второй элемент списка, где находится номер LED:
Если номер светодиода 1, 2 или 3, то проверяется третий элемент списка, где находится код выключения/включения. Если приложение обнаружит "0" или "1", то вызывается процедура изменение кнопки в состояние OFF или процедура изменения кнопки в состояние ON.
Вот так будут выглядеть полученные блоки приложения:
В статье [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, документация.
Возможно ли сделать так, чтобы не Android устройство подключалось к HM-10, а HM-10 в роли мастера подключался к Android устройству? Мне удавалось подключить HM-10 друг к другу, подключаются без проблем. Однако подключить его к Android устройству не удавалось (при сканировании Android устройство даже не находится).
Комментарии
RSS лента комментариев этой записи