Arduino, HM-10 и App Inventor 2

Это руководство представляет собой полное введение в использование модуля HM-10 Bluetooth Low Energy с App Inventor 2. Материал выходит за рамки базовых начальных уроков и рассматривает практические шаблоны реализации.

«Хотя я использую Arduino, принципы будут одинаковыми для любого другого микропроцессора или для использования HM-10 самостоятельно.»

Arduino HM-10 App Inventor 2 демонстрация

BLE (Bluetooth Low Energy)

BLE принципиально отличается от классического Bluetooth. Вместо поддержания постоянных соединений BLE использует нечастые небольшие пакеты данных для работы с низким энергопотреблением. Он не был разработан для постоянных соединений или передачи больших объёмов данных.

Существуют два режима связи:

  • Broadcaster + Observer (Вещатель + Наблюдатель): вещатель отправляет периодические рекламные пакеты, не зная, слушает ли кто-то

  • Central + Peripheral (Центральный + Периферийный): центральное устройство инициирует соединения с периферийными устройствами, аналогично классическому Bluetooth

Архитектура BLE основана на сервисах и характеристиках. Сервисы группируют связанные характеристики, а характеристики содержат фактические значения данных. Каждый имеет уникальный идентификатор UUID.

HM-10

HM-10 — это недорогой последовательный BLE-модуль от Jinan Huamao с UART-слоем, упрощающим интеграцию с Arduino. Однако это удобство ограничивает доступ к функциональности BLE.

Пользовательский сервис и характеристики HM-10 по умолчанию:

ПОЛЬЗОВАТЕЛЬСКИЙ СЕРВИС

  • UUID: 0000FFE0-0000-1000-8000-00805F9B34FB

ПОЛЬЗОВАТЕЛЬСКИЕ ХАРАКТЕРИСТИКИ

  • UUID: 0000FFE1-0000-1000-8000-00805F9B34FB (ЧТЕНИЕ/ЗАПИСЬ/УВЕДОМЛЕНИЕ)

  • UUID: 0000FFE2-0000-1000-8000-00805F9B34FB (только ЗАПИСЬ)

Характеристика FFE1 активна по умолчанию; FFE2 требует активации перед использованием.

App Inventor 2

App Inventor 2 использует визуальное программирование Blockly вместо текстового кода, что делает разработку Android-приложений более доступной. Новое BLE-расширение в настоящее время находится в бета-версии, но обеспечивает стабильную работу для большинства приложений.

«Новое BLE-расширение является частью AI2 IOT, и подробности об этом можно найти на сайте AI2 IOT.»

Шаблон BaseConnect предоставляет основу для сканирования и подключения к BLE-устройствам, хотя перед использованием рекомендуется обновить BLE-расширение.

BaseConnect — начальный экран

BaseConnect — сканирование

BaseConnect — список устройств

BaseConnect — подключение

BaseConnect — подключено

BaseConnect — экран 6

BaseConnect — экран 7

Пример 1: Включение и выключение светодиода (базовый)

Схема подключения

Схема подключения:

  • Arduino D8 (AltSoftSerial RX) к HM-10 TX

  • Arduino D9 (AltSoftSerial TX) через делитель напряжения к HM-10 RX

  • Arduino D2 через резистор 330 Ом к светодиоду

Делитель напряжения (1 КОм и 2 КОм) понижает 5 В до 3.3 В, защищая вывод RX HM-10.

Схема подключения

Макетная плата

Скетч Arduino — Часть 1

//  Arduino, HM-10, App Inventor 2
//
//  Example Project Part 1: Turn an LED on and off basic
//  By Martyn Currey. www.martyncurrey.com

#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()
{
    if (ASSserial.available())
    {
        c = ASSserial.read();
        Serial.println(c);

        // ASCII code for 0 is dec 48
        // ASCII code for 1 is dec 49
        if ( c== 48) { digitalWrite(LEDPin, LOW); }
        if ( c== 49) { digitalWrite(LEDPin, HIGH); }
    }
}

Скетч отслеживает последовательный ввод на наличие символов «0» (выключить светодиод) или «1» (включить светодиод). Любые другие символы игнорируются.

Монитор последовательного порта

Разработка Android-приложения

Начиная с шаблона BaseConnect, улучшения включают:

Улучшения интерфейса:

  • Увеличен размер текста ListView до 48 пикселей для удобства чтения

  • Элементы переименованы для ясности

  • Добавлены кнопки управления светодиодом

Дизайнер App Inventor — начало

Дизайнер — настройка

Компоненты приложения

Свойства компонентов

Настройка ListView

Управление Bluetooth:

  • Проверка включённости Bluetooth перед сканированием

  • Отображение уведомления, если Bluetooth отключён

  • Проверка подключения устройства перед отправкой команд

Добавление компонента Bluetooth

Добавление Notifier

Структура кода:

Добавлены глобальные переменные для UUID:

  • Сервис: 0000FFE0-0000-1000-8000-00805F9B34FB

  • Характеристика: 0000FFE1-0000-1000-8000-00805F9B34FB

Блоки — глобальные переменные

Блоки — UUID

Реализация кнопки-переключателя:

  • Сканирование/остановка сканирования объединены в одну кнопку

  • Подключение/отключение объединены в одну кнопку

  • Текст кнопки указывает текущее состояние

Блоки сканирования

Приложение — экран 1

Приложение — экран 2

Приложение — экран 3

Усовершенствованный поток управления

Блоки управления кнопкой сканирования

Блоки управления подключением

Блоки обработки подключения

Блоки обработки ошибок

Блоки управления светодиодом

Блоки отправки данных

Приложение — управление светодиодом

Улучшенное приложение обеспечивает:

  • Корректную обработку ошибок при отключённом Bluetooth

  • Предотвращение ошибок при нажатии кнопок без правильного состояния

  • Удобную обратную связь через изменение текста кнопок

Пример 2: Двустороннее управление светодиодом

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

Добавление к схеме

К предыдущей схеме добавляется:

  • Arduino D3 с подтягивающим резистором 10 КОм к кнопке

Схема с кнопкой

Макетная плата с кнопкой

Скетч Arduino — Часть 2

//  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

#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;

               if ( LED_State == HIGH)
               {
                    digitalWrite(LEDPin,HIGH);
                    ASSserial.print("1" );
                    Serial.println("Sent - 1");
               }
               else
               {
                   digitalWrite(LEDPin,LOW);
                   ASSserial.print("0");
                   Serial.println("Sent - 0");
               }
          }
          oldswitch_State = switch_State;
      }
}

void checkRecievedData()
{
    if (ASSserial.available())
    {
        c = ASSserial.read();
        Serial.println(c);

        if ( c== 48) { digitalWrite(LEDPin, LOW);     LED_State = LOW;  }
        if ( c== 49) { digitalWrite(LEDPin, HIGH);    LED_State = HIGH; }
    }
}

Скетч теперь включает:

  • Устранение дребезга кнопки через тройное считывание

  • Функцию переключения для физической кнопки

  • Передачу состояния в приложение при нажатии кнопки

Обновления приложения для двунаправленного управления

Блоки регистрации для получения строк

Блоки отмены регистрации

Регистрация для приёма строк:

  • Добавление вызова RegisterForStrings в событии BluetoothLE1.Connected

  • Отмена регистрации перед отключением через UnregisterForValues

Обработчик StringsReceived

Обработчик приёма данных:

  • Обработка входящих строк в событии StringsReceived

  • Проверка, что данные являются списком, перед обработкой

  • Цикл по элементам списка (обычно один символ)

Процедуры обновления кнопки — часть 1

Процедуры обновления кнопки — часть 2

Процедуры обновления кнопки:

Создание отдельных процедур для изменения состояния:

  • LED_BUTTON_ON: устанавливает текст кнопки «ON», фон зелёный

  • LED_BUTTON_OFF: устанавливает текст кнопки «OFF», фон красный

Логика управления

Обработка полученных данных

Блоки — обновление кнопки LED ON

Блоки — обновление кнопки LED OFF

Блоки — полная логика

Приложение — двусторонний контроль

Двунаправленная система теперь обеспечивает:

  • Кнопка приложения отражает фактическое состояние светодиода

  • Управление физической кнопкой видно в интерфейсе приложения

  • Оба метода управления обновляют состояние приложения

Все блоки — итоговый вид

Пример 3: Сложные коды управления

В этом разделе простые односимвольные коды заменяются структурированными многосимвольными командами.

Новый формат кодов управления

Вместо «0» и «1» используется:

  • L10 — L для LED, 1 для светодиода №1, 0 для выключения

  • L11 — L для LED, 1 для светодиода №1, 1 для включения

Формат инкапсуляции:

  • Arduino получает: [L10] и [L11]

  • Arduino отправляет: [L,1,0] и [L,1,1]

Маркеры начала/конца ([]) гарантируют полный приём сообщения, несмотря на возможную фрагментацию данных при передаче.

Формат сложных кодов

Пример команд

Скетч Arduino — Часть 3

//  Arduino, HM-10, App Inventor 2
//
//  Example Project Part 3: Complex control codes
//  By Martyn Currey. www.martyncurrey.com

#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();  }
}

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;
      }
}

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;
}

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; }
     }
}

Функция recvWithStartEndMarkers() (адаптирована из поста Robin2 на форуме Arduino) захватывает полные сообщения между маркерами.

Обновления приложения для сложных кодов

Блоки — отправка сложных кодов

Изменения управления кнопками:

Замена простых символьных кодов на форматированные команды: кнопка OFF отправляет [L10], кнопка ON отправляет [L11].

Блоки — кнопки LED

Управление буфером:

Создание глобальной переменной BT_receivedData_Buffer для накопления входящих данных.

Блоки — обработка буфера

Блоки — процедура обработки буфера

Блоки — TrimToStartMarker

Блоки — processCommand

Блоки — обработка команд

Процедура TrimToStartMarker:

Удаляет любые символы перед первым «[» для поддержания целостности буфера при поступлении неполных сообщений.

Блоки — полная процедура обработки

Блоки — дополнительная обработка

Блоки — извлечение команды

Блоки — итоговый вид

Блоки — завершающий блок

Дизайнер — итоговый вид приложения

Блоки — итоговый вид всех блоков

Пример 4: Изменение обработки кодов

Этот раздел расширяет обработку команд, делая код более масштабируемым.

Пример 4 — код

Пример 4 — блоки

Пример 4 — дополнительные блоки

Пример 4 — обработчик команд

Пример 4 — улучшенная обработка

Пример 4 — блоки управления

Пример 4 — итоговые блоки

Пример 4 — последовательный монитор

Пример 5: 3 светодиода и 3 переключателя

Пример 5 — скетч

Пример 5 — блоки

Пример 5 — схема

Пример 5 — макетная плата

Пример 5 — монитор

Пример 5 — приложение

Пример 5 — итоговые блоки

Пример 5 — итоговый интерфейс приложения

Файлы для скачивания

Важно

BLE-расширение, включённое в примеры, устарело и больше не функционирует полностью. Скачайте последнюю версию BLE-расширения с http://iot.appinventor.mit.edu/assets/resources/edu.mit.appinventor.ble-20181124.aix и загрузите его для замены старой версии.

Примечание

Оригинальная статья: Arduino, HM-10 and App Inventor 2 (Martyn Currey)