Передача данных от ESP32 по Bluetooth LE к Android

от автора

Идея приложения состоит в демонстрации IoT — интеграция различных устройств, и передача данных по разным протоколам в Edge или Cloud. Допустим, наш автономный механизм работает без подключения к интернету, а нам необходимо сделать замеры поведения движений во времени. Мы подключаемся с помощью смартфона по Bluetooth LE к контроллеру механизма и в течении определенного времени делаем запись. При этом наш смартфон успешно подключается к облачному MQTT-брокеру и передает данные в IoT платформу. Платформа производит аналитику и предоставляет нам результат. А мы в это время на основании полученных данных можем внести требуемые значения характеристик механизма в контроллер по BLE.

В статье Machine learning на ESP32 мы начали разработку проекта распознавания жестов для ESP32. В данной статье продолжим реализацию подключение и отправку данных по BLE и MQTT с помощью Android-устройства. Хотя ESP 32 может напрямую подключаться к Wi-Fi и MQTT, как, например, показано в статье Платформа с web-камерой на ESP32, мы все же реализуем передачу данных по BLE, руководствуясь выше изложенными соображениями.

Содержание

  • Краткое описание Bluetooth LE

  • Bluetooth LE на чипах Espressif

  • Реализация обмена данными Bluetooth LE на ESP32

  • Реализация проекта на Android

  • Заключение

  • Используемые источники

Краткое описание Bluetooth LE

Bluetooth — это стандарт беспроводной связи, работающий в диапазоне частот 2,4 ГГц. В наши дни, если устройство поддерживает Bluetooth, почти всегда это означает поддержку Bluetooth Low Energy (BLE). BLE значительно отличается от оригинального стандарта Bluetooth, который теперь называется Classic Bluetooth.

Classic Bluetooth и Bluetooth Low Energy — это фактически разные протоколы. Classic Bluetooth представляет собой беспроводной аналог традиционного последовательного соединения. Он ориентирован на высокую скорость передачи данных, что делает его подходящим для таких задач, как печать документов, передача файлов и потоковая передача аудио.

Однако Classic Bluetooth плохо подходит для устройств с низким энергопотреблением, например, работающих от батареек. На практике многие чипсеты поддерживают как Classic Bluetooth, так и Bluetooth Low Energy, особенно в ноутбуках и смартфонах.

На Хабре ранее уже писали статью Bluetooth Low Energy: подробный гайд для начинающих, где можно прочитать про архитектурные уровни протокола. А также BLE под микроскопом. А в этой статье можно посмотреть пример реализации SmartPulse устройства на ESP32-C3Fx4 Mini. C руководством Bluetooth LE можно ознакомится на официальном сайте.
В данном материале я кратко изложу основы, с которыми познакомился в процессе изучения BLE. Контент, скорее всего, во многом уже описан в других статьях, но возможно некоторые моменты в коротком структурированном виде помогут читателю быстрее ознакомится с концепцией BLE.

Архитектура

Спецификация Bluetooth Core содержит более 3200 страниц. И это только базовая спецификация; для BLE существует множество дополнительных документов. Хотя BLE имеет многоуровневую архитектуру, многие конечные приложения используют только верхние уровни, что упрощает разработку.

BLE состоит из трёх основных блоков: Controller, Host и Application.

Controller Отвечает за низкоуровневые функции: физический уровень (PHY), канальный уровень (LL) и режим прямого тестирования (DTM). Именно здесь происходит передача радиосигналов Bluetooth на частоте около 2,4 ГГц. Контроллер взаимодействует с хостом через интерфейс Host Controller Interface (HCI).

Host — это уровень, с которым работают конечные пользователи и разработчики. Он включает в себя несколько протоколов:

  • L2CAP (Logical Link Control and Adaptation Protocol) — управление каналами и сигнальными командами;

  • SMP (Security Manager Protocol) — обработка защищённых соединений (аутентификация и шифрование);

  • ATT (Attribute Protocol) — определяет формат представления данных;

  • GATT (Generic Attribute Profile) — реализует обнаружение сервисов и работу с их характеристиками;

  • GAP (Generic Access Profile) — отвечает за обнаружение устройств, соединение, спаривание и привязку.

Хост взаимодействует с контроллером через HCI, а приложения работают с хостом через API операционной системы.

Application Реализует прикладные сервисы и профили.

  • Characteristic — определённый тип данных (например, уровень тревоги);

  • Service — набор характеристик (например, сервис потери связи);

  • Profile — набор сервисов, обеспечивающих взаимодействие между устройствами (например, профиль Proximity с ролями Proximity Monitor и Proximity Reporter).

BLE-архитектура поддерживает разные аппаратные реализации:

  • Одночиповая (SoC) — контроллер, хост и приложение работают на одном чипе (часто используется в датчиках BLE).

  • Двухчиповая (HCI) — контроллер и хост находятся на отдельных чипах и общаются через HCI (используется в ноутбуках, Raspberry Pi и смартфонах).

  • Двухчиповая (с чипом подключения) — приложение работает на одном чипе, а контроллер и хост на другом (например, BLE-модули для расширения существующих устройств).

Взаимодействие с BLE-устройствами

Bluetooth Low Energy поддерживает два режима связи: без соединения и с соединением.

Без соединения — advertisement-пакеты

BLE-устройство просто транслирует информацию в advertisement-пакете, которую могут принимать другие устройства.

Примеры:

  • Маяки (Proximity Beacons) — транслируют уникальные идентификаторы (например, Apple iBeacon).

  • Датчики — передают данные (температуру, влажность).

  • Смартфоны — например, COVID-19 Exposure Notifications передавали анонимные идентификаторы для отслеживания контактов.

С соединением

В этом режиме одно устройство (клиент) сканирует advertisement-пакеты BLE-устройств (серверов) и подключается к ним. После подключения клиент может считывать данные сервера или отправлять команды.

Примеры:

  • Фитнес-трекеры — смартфон получает данные о сердечном ритме и заряде батареи.

  • Датчики — некоторые BLE-датчики позволяют считывать данные только после установки соединения.

  • Оповещатели близости (Proximity Reporters) — издают сигнал при разрыве соединения с основным устройством.

Передача данных с помощью advertisement-пакетов BLE

Большинство типов advertisement (рекламных) пакетов передаются в режиме широковещательной трансляции (broadcast), что означает, что любое BLE-устройство в зоне действия может их принять. Именно поэтому рекламные пакеты играют значительную роль в процессе обнаружения устройств. Однако помимо этой задачи они также позволяют удобно передавать данные нескольким получателям одновременно.

Роли устройств

Generic Access Profile (GAP) определяет четыре роли:

  • Broadcaster (Транслятор)
    Передает нерегистрируемые рекламные пакеты, доступные для всех устройств в радиусе действия. К нему нельзя подключиться.

  • Observer (Наблюдатель)
    Сканирует пространство в поисках нерегистрируемых рекламных пакетов.

  • Peripheral (Периферийное устройство)
    Передает подключаемые рекламные пакеты, позволяя другим устройствам установить соединение. После установления соединения периферийное устройство называется slave на уровне канального слоя.

  • Central (Центральное устройство)
    Инициирует соединение с периферийным устройством. После установления соединения центральное устройство называется master на уровне канального слоя.

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

Advertisement-пакеты

BLE использует 40 каналов шириной 2 МГц в диапазоне 2,4 ГГц (ISM-диапазон). Из них три канала предназначены для рекламирования:

  • Канал 37 (2402 МГц),

  • Канал 38 (2426 МГц),

  • Канал 39 (2480 МГц).

Остальные 37 каналов (от 0 до 36) используются для передачи данных при установленных соединениях. Эти три рекламных канала расположены по всему спектру BLE: один в начале, один в конце и один между каналами 10 и 11. Такое распределение помогает снизить помехи от других беспроводных устройств.

BLE также использует частотное прыгание (frequency hopping), что уменьшает вероятность помех. Например, устройство передает рекламные пакеты поочередно через три канала: 37 → 38 → 39 → 37 → 38 → 39, и так далее.

В BLE используются следующие типы рекламных пакетов:

Тип

Назначение

ADV_IND

Общий подключаемый рекламный пакет

ADV_DIRECT_IND

Направленный подключаемый рекламный пакет

ADV_NONCONN_IND

Общий неподключаемый рекламный пакет

ADV_SCAN_IND

Общий сканируемый рекламный пакет

SCAN_REQ

Запрос активного сканирования

SCAN_RSP

Ответ на активное сканирование

CONNECT_REQ

Запрос на установление соединения

В документации Espressif Device Discovery можно детальнее ознакомится с конфигурированием Advertising для ESP32.

Соединения и сервисы

В Generic Access Profile (GAP) определены две роли:

  • Peripheral
    Отправляет advertising packets, сообщая другим устройствам о возможности соединения. После установления соединения периферийное устройство также называется ведомым (slave) на уровне канального соединения (link layer).

  • Central
    Инициирует соединение с периферийным устройством. После установления соединения центральное устройство также называется ведущим (master) на уровне канального соединения.

Одно central-устройство (например, телефон или компьютер) может подключаться к нескольким периферийным устройствам.

Передача данных в BLE возможна в двух режимах:

  • broadcasting — однонаправленный метод связи, при котором передатчик отправляет данные сразу нескольким наблюдателям.

  • connections — двусторонний метод связи, где одно центральное устройство подключается к одному периферийному устройству.

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

Помимо ролей GAP, при работе с соединениями важны также роли Generic Attribute Profile (GATT):

  • Client — отправляет запросы серверу и получает ответы.

  • Server — получает запросы от клиента и отправляет ответы.

Роли GAP и GATT независимы друг от друга. Например, если ваш телефон подключен к смарт-часам, то:

  • Телефон — это central-устройство (GAP), а часы — peripheral.

  • Однако GATT-роли зависят от того, кто запрашивает данные:

    • Когда телефон запрашивает данные о частоте сердцебиения, он является GATT-клиентом, а часы — GATT-сервером.

    • Когда часы запрашивают текущее время у телефона, они становятся GATT-клиентом, а телефон — GATT-сервером.

Атрибуты и характеристики

GATT-сервер можно рассматривать как базу данных с определенной структурой. В BLE данные организованы согласно Attribute Protocol (ATT), где каждый элемент информации представлен атрибутом.

Атрибут имеет следующие характеристики:

  • Дескриптор (Handle)
    Уникальный 16-битный адрес атрибута. Он не изменяется во время соединения, а при установке связи (bonding) между устройствами может сохраняться даже между сессиями.

  • Тип (Type)
    Определяется UUID (16-битный, 32-битный или 128-битный). Bluetooth SIG назначает стандартные короткие UUID для оптимизации места в рекламных пакетах.

  • Разрешения (Permissions)
    Определяют, можно ли читать/записывать атрибут, какой уровень безопасности требуется, и нужна ли авторизация.

  • Значение (Value)
    Само значение атрибута (максимальная длина — 512 байт). Оно интерпретируется в зависимости от типа атрибута.

Все атрибуты в GATT принадлежат к одному из трех типов:

  1. Сервисы (Services)

  2. Характеристики (Characteristics)

  3. Дескрипторы (Descriptors)

Сервисы

Сервис объединяет несколько взаимосвязанных характеристик. Атрибут, объявляющий сервис, называется Service Declaration и содержит UUID сервиса.

Дополнительно в GATT существует включенные сервисы (Included Services), которые позволяют вложить один сервис в другой, избегая дублирования данных. Основной тип сервиса называется Primary Service, а вложенный — Secondary Service.
Некоторые сервисы допускают несколько экземпляров в одном GATT-сервере.

Характеристики

Характеристики содержат основные данные в GATT-сервере. Каждая характеристика включает как минимум два атрибута:

  • Characteristic Declaration
    Описывает свойства характеристики (чтение, запись, уведомления и т. д.), а также UUID характеристики.

  • Characteristic Value
    Хранит само значение характеристики. Тип данных определяется UUID характеристики.

Безопасность BLE

В блоке Host находится компонент Security Manager Protocol (SMP) на том же уровне, что и Attribute Protocol. Основная задача Security Manager Protocol — генерировать и обмениваться правильными ключами шифрования между BLE-устройствами, чтобы обеспечить безопасную связь.

Однако безопасность реализована не только в компоненте Security Manager Protocol, но и в других уровнях стека протоколов Bluetooth. Например, периферийное устройство GAP определяет разрешения атрибутов в своей таблице атрибутов. Эти разрешения указывают, какие механизмы безопасности необходимы для чтения или записи атрибутов.

Для понимания безопасности в BLE важно разобраться в двух типах процедур: pairing и bonding.

Сопряжение (Pairing). Если два устройства ещё не обменивались защищёнными данными, но хотят это сделать, им необходимо пройти процедуру сопряжения. Этот процесс включает:

  • аутентификацию — проверку того, что устройства обладают общим секретом (например, PIN-кодом),

  • шифрование передаваемых данных,

  • обмен ключами шифрования.

Связывание (Bonding). Если устройства уже прошли сопряжение и сохранили обменянные ключи в локальной базе безопасности, они считаются связанными. Это позволяет ускорить установку безопасного соединения в будущем. Если ключи сохранены только на одном устройстве, повторное соединение не удастся, и устройства придётся сопрягать заново.

Для детального изучения BLE security можно прочитать соответствующие разделы в указанных источниках.

Bluetooth LE на чипах Espressif

Здесь я привел краткий перевод из документации Espressif Bluetooth LE

ESP32 поддерживает два режима: Bluetooth 4.2 и Bluetooth 5.0.

ESP32 Bluetooth LE Stack Architecture

ESP32 Bluetooth LE Stack Architecture

ESP Bluetooth Controller

На нижнем уровне находится ESP Bluetooth Controller, который охватывает различные модули, такие как PHY, Baseband, Link Controller, Link Manager, Device Manager и HCI. Он отвечает за аппаратное управление интерфейсом и связью. Предоставляет функции в виде библиотек и доступен через API. Этот уровень напрямую взаимодействует с аппаратными и низкоуровневыми протоколами Bluetooth.

Хосты

Существует два хоста, ESP-Bluedroid и ESP-NimBLE. Основное различие между ними заключается в следующем:

  • ESP-NimBLE требует меньше размера памяти кучи и флэш-памяти.

  • ESP-Bluedroid поддерживает как классический Bluetooth, так и Bluetooth LE, в то время как ESP-NimBLE поддерживает только Bluetooth LE.

ESP-Bluedroid

ESP-Bluedroid — это модифицированная версия стека Android Bluetooth. Он состоит из двух уровней: верхнего уровня Bluetooth (BTU) и уровня контроллера транспорта Bluetooth (BTC). Уровень BTU отвечает за обработку протоколов Bluetooth нижнего уровня, таких как L2CAP, GATT/ATT, SMP, GAP и другие профили. ESP-Bluedroid для ESP32 поддерживает Classic Bluetooth и Bluetooth LE.

ESP-NimBLE

ESP-NimBLE — это хост-стек, построенный поверх NimBLE, разработанного Apache Mynewt. Хост-стек NimBLE портирован для чипов ESP32 и FreeRTOS.
ESP-NimBLE поддерживает только Bluetooth LE. Классический Bluetooth не поддерживается.

Профили

Выше хост-стеков находятся реализации профилей Espressif и некоторые общие профили. В зависимости от конфигурации эти профили могут работать на ESP-Bluedroid или ESP-NimBLE.

ESP-BLE-MESH

ESP-BLE-MESH построенная поверх стека Zephyr Bluetooth Mesh, её реализация поддерживает подготовку устройств и управление узлами. Она также поддерживает такие функции узлов, как Proxy, Relay, Low power и Friend.

BluFi

BluFi для ESP32 — это функция конфигурации сети Wi-Fi через канал Bluetooth. Она обеспечивает безопасный протокол для передачи конфигурации Wi-Fi и учетных данных. ESP32 может затем подключиться к точке доступа или установить softAP.

Приложения

На самом верхнем уровне находятся приложения. Вы можете создавать собственные приложения поверх стеков ESP-Bluedroid и ESP-NimBLE, используя предоставленные API и профили.

Реализация обмена данными Bluetooth LE на ESP32

Перед реализацией BLE на ESP32 по рекомендациям из комментариев из статьи про ML на ESP32 и согласно практическому смыслу я добавил в модель жест распознавания фиксированного (неподвижного) положения сенсора — Fixed.
Для этого записал данные сенсора в неподвижном положении в файл fixed.csv. В листинге Jupyter Notebook добавил в словарь этот жест

gestures = {"circle": 0, "cross": 1, "pad": 2, "fixed": 3} 

А выходной слой Softmax расширил до 4 согласно количеству жестов.

keras.layers.Dense(4, activation='softmax') 

Выполнил обучение и конвертацию модели.

В качестве базового примера для реализации BLE можно взять проект NimBLE_GATT_Server
В демо-проекте распознавания жестов исходных код для BLE состоит из заголовка и файла с реализацией: ble_provider.h, ble_provider.c

Инициализация

Первым шагом делаем инициализацию BLE. В функции void ble_init() выполняются вызовы всех остальных API инициализации.

  • Инициализация NVS Flash — nvs_flash_init. ESP32 использует NVS Flash для хранения необходимой конфигурации, поэтому перед инициализацией стека Bluetooth мы должны вызвать nvs_flash_init для инициализации NVS Flash.

  • Инициализация контроллера ESP Bluetooth (уровень связи) и транспортного уровня VHCI между хостом NimBLE и контроллером ESP Bluetooth esp_nimble_hci_and_controller_init

  • Далее необходимо вызвать nimble_port_init для инициализации стека хоста NimBLE.

  • Указываем имя gap-устройства для обнаружения по имени ble_svc_gap_device_name_set(«BLE-Server»)

  • Вызываем функцию ble_svc_gap_init для инициализации GAP-сервиса

  • Вызываем функцию ble_svc_gatt_init для инициализации GATT-сервиса

Cервис и характеристики

Точные UUID для типов и значений можно найти в документе 16-битных UUID на странице назначенных номеров Bluetooth SIG. Чаще всего в примерах статей используются сервис и характеристики Heart Rate. В нашем же примере мы используем не стандартные сервисы и характеристики, поэтому сгенерируем для них UUID, например здесь. Для коммерческого использования такой вариант не подходит, но для целей изучения вполне сгодится. И того, нам нужны UUID:

  • для кастомного сервиса BLE_GATT_SVC_TYPE_PRIMARY, объединяющего все характеристики;

  • для нотификаций BLE_GATT_CHR_F_NOTIFY массива значений ускорений сенсора `compressed_data[375];

  • для чтения распознанного жеста из контроллера BLE_GATT_CHR_F_READ

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

Пример конфигурации массива структур данных ble_gatt_svc_def gatt_svcs[]

const struct ble_gatt_svc_def gatt_svcs[] = {     {.type = BLE_GATT_SVC_TYPE_PRIMARY,      .uuid = BLE_UUID128_DECLARE(0x2c9fdd5d, 0x990c, 0x4801, 0x80c6, 0xac7eff021a8f), // Define UUID for device type      .characteristics = (struct ble_gatt_chr_def[]){          {.uuid = BLE_UUID128_DECLARE(0xba851a41, 0x1fd6, 0x4501, 0x8ad0, 0xf715adf43e4e), // Rate Measurement characteristic           .flags = BLE_GATT_CHR_F_NOTIFY,           .access_cb = rate_notify, // Callback for notifications           .val_handle = &rate_handle},          {.uuid = BLE_UUID128_DECLARE(0xe24d9433, 0x9255, 0x4880, 0xb56e, 0xc09529224418), // Define UUID for reading           .flags = BLE_GATT_CHR_F_READ,           .access_cb = device_read},          {.uuid = BLE_UUID128_DECLARE(0xca561598, 0xf8ba, 0x41a4, 0xbcc1, 0x17c269355adc), // Define UUID for writing           .flags = BLE_GATT_CHR_F_WRITE,           .access_cb = device_write},          {0}}},     {0}}; 
  • Нотификация
    Назначается с помощью флага BLE_GATT_CHR_F_NOTIFY;
    в качестве буфера данных используется массив char stored_data[MAX_DATA_LEN], где MAX_DATA_LEN = 375 — количество измеренных ускорений сенсора за 2.5 с; rate_notify — коллбэк-функция, записывающая буфер в контекст os_mbuf_append(ctxt->om, compressed_data, BLE_SAMPLE_SIZE).

  • ble_gatt_svc_def конфигурация
    Созданную структуру передаем в качестве аргумента в функции ble_gatts_count_cfg и ble_gatts_add_svcs для добавления сервисов и данных характеристик, определенных в таблице сервисов gatt_svr_svcs, на сервер GATT, и добавление счетчиков в соотв. поля, как указано в документации

  • Поток хоста
    Запускаем поток для стека хоста с помощью nimble_port_freertos_init, doc

  • Чтение
    device_read — коллбэк-функция чтения массива ускорений, данные копируются в контекст os_mbuf_append(ctxt->om, stored_data, strlen(stored_data))

  • Запись
    device_write — коллбэк-функция записи величины свечения светодиода.
    считываем данные с контекста memcpy(&duty, ctxt->om->om_data, len);
    устанавливаем свечение светодиода pwm_set_duty(duty);

LEDC — регулировка свечения светодиода

Для демонстрации записи значения характеристики BLE используется LED Control PWM (или PDF) — LEDC, с помощью чего устанавливается уровень свечения светодиода. В рамках данной статьи LEDC не описывается. Для LEDC в проекте используется файлы pwm.h, pwm.c, в которых расположены функции инициализации, запуска, останова PWM, а также функция задания заполнения PWM (duty) для регулировки свечения светодиода.

Т.к. регулировка свечения светодиода выполняется с помощью BLE, то инициализация LEDC производится вызовом функции pwm_init в ble_init.

Светодиод подключен к GPIO 19 выводу микроконтроллера

Светодиод подключен к GPIO 19 выводу микроконтроллера

Advertisement-пакеты

В функции ble_app_advertise производим doc:

  • Настройку структуры полей рекламных пакетов struct ble_hs_adv_fields fields, таких как имя устройства и его дина fields.name, fields.name_len;

  • Установку структуры полей с помощью функции ble_gap_adv_set_fields(&fields);

  • Установку gap-параметров рекламных пакетов с помощью структуры struct ble_gap_adv_params adv_params и функции, где указываем режим обнаружения в качестве маяка:

 adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;  adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; 
  • Создаем функцию-обработчик событий BLE ble_gap_event, где указываем поведение логики при наступлении различных событий.

  • Выполняем запуск рекламирования с помощью функции ble_gap_adv_start.

void ble_app_advertise(void) {     struct ble_hs_adv_fields fields;     const char *device_name;     memset(&fields, 0, sizeof(fields));     device_name = ble_svc_gap_device_name();     fields.name = (uint8_t *)device_name;     fields.name_len = strlen(device_name);     fields.name_is_complete = 1;     ble_gap_adv_set_fields(&fields);      struct ble_gap_adv_params adv_params;     memset(&adv_params, 0, sizeof(adv_params));     adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;     adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;     ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL); } 

Получение данных для BLE

ble_provider#ble_loop — функция-задача для получения массива ускорений сенсора из очереди sensorQueue. Как описано ранее в статье, мы инициализируем очередь для записи данных. Задача считывания данных gesture#gesture_predict имеет более высокий приоритет, и записывает измеренные ускорения сенсора и результат вычисления вероятностей жестов в очередь sensorQueue.

memcpy(&model_input->data.f[SAMPLE_SIZE * 3], output->data.f, FEATURES * sizeof(float));      portBASE_TYPE xStatus = xQueueSend(sensorQueue, &model_input->data.f, 0);     if( xStatus != pdPASS ) {         ESP_LOGE(TAG, "Could not send sensor data to the queue.\r\n");     } 

Задача ble_provider#ble_loop блокируется при чтении очереди до тех пор пока, структура model_input->data.f не будет заполнена и передана в очередь.

MTU — максимальная единица передачи, она определяет, какое максимальное количество байтов может быть передано между клиентом и сервером BLE. В ESP32 — это параметр BT_NIMBLE_ATT_PREFERRED_MTU, по умолчанию он равен 23 байта, что не достаточно для передачи 375 величин ускорений. Согласно doc, значение MTU может быть увеличено до 512 байтов. Поэтому откроем редактор конфигурации SDK configuration editor (meuconfig), в строке поиска вводим MTU, находим опцию Preferred MTU size in octets и увеличиваем значение, например 500 байт.
Значения ускорений из очереди данных приходят в float формате, каждый размер float в байтах равен 4, т.е. 375 * 4 = 1500, что значит, что мы не сможем передать за один раз весь массив ускорений во float формате. Варианты решения могут быть различные, например, может разработать формат телеграммы с заголовком и контрольной суммой и на стороне клиента получать телеграммы, сравнивать их последовательность и контрольную сумму, затем склеивать полный набор. Но мы пойдем более простым и наглядным образом.

Применим квантизацию, т.е. жертвуя некоторой точностью, преобразовываем float в int8_t, т.е. в байт.
Сделаем преобразование по такой формуле

y = int8(\frac{(x + a) \cdot b}{r} - s),

где

  • x — Исходное значение (элемент массива data[i]);

  • y — Преобразованное значение;

  • a — Сдвиг. На основе экспериментальных данных мы знаем, что диапазон считываемых ускорений лежит в пределах от -2 до 2 (-1.5 до 1.5), добавление 2 к каждому значению переносит их в диапазон от 0 до 4, что удобно для масштабирования.

  • b — максимальное значение диапазона после масштабирования (в данном случае b=255)

  • r — исходный диапазон данных (в данном случае r=4)

  • s — сдвиг в центр диапазона для 8-битных значений (в данном случае s=128)

  • s — сдвиг в центр диапазона для 8-битных значений (в данном случае s=128)

 for (int i = 0; i < BLE_SAMPLE_SIZE; i++)             {                 //Quantization of the data to 8 bits                 compressed_data[i] = (int8_t)((data[i] + 2) * 255.0 / 4 - 128.0);             } 

После преобразования мы имеем массив uint8_t compressed_data[375] байтов, соотв. нашим ускорениям.

После получения и преобразования данных вызываем функцию-триггер отправки сообщений ble_gatts_chr_updated(rate_handle).

Исходный код ESP32 проекта находится в этом репозитории.

Реализация проекта на Android

Ранее уже писали статьи на Хабре о том, как использовать Tensorflow lite на мобильном устройстве и создать проект в Android Studio с нуля. Я опишу лишь некоторые шаги, для своего демо-проекта. А в этой серии статей можно ознакомится с тем, каким образом работать с BLE для Android на Java, в результате имеется библиотека blessed-android, которая упрощает работу со стеком BLE. Для данного демо-проекта была выбрана реализация blessed-kotlin.

Дисклеймер, проект выполнен не по канонам мобильной разработки. Многие вещи были упущены ради простоты. Например, все файлы в одном пакете, использование Jetpack Compose, дизайн и т.д.

Шаги по настройки проекта

Первым шагом необходимо создать новый проект с одним Activity в Android Studio. В этом проекте используется targetSdk = 34 (Android 14), minSdk = 31 (Android 12)

Подключаем необходимые зависимости в файл build.gradle.kts:

  • com.github.weliem:blessed-kotlin:3.0.8 — для удобной работы с BLE

  • org.tensorflow:tensorflow-lite:2.17.0 — Tensorflow lite модель

  • com.hivemq:hivemq-mqtt-client:1.3.5 — MQTT-обработка

Устанавливаем разрешения для работы с BLE и MQTT в манифест-файле AndroidManifest.xml

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.INTERNET"/>

Для правильной настройки работы с библиотекой hivemq-mqtt можно пользоваться hivemq-mqtt-client руководством.

Добавляем исключения в packaging options

    packaging {         resources {             excludes += listOf("META-INF/INDEX.LIST", "META-INF/io.netty.versions.properties")         }     } 

Добавляем следующие строки в файл правил proguard-rules.pro

-keepclassmembernames class io.netty.** { *; } -keepclassmembers class org.jctools.** { *; } 

Исходный код проекта в этом репозитории. Структура проекта выглядит так

  • Gesture — константы типов жестов;

  • MainActivity — логика основного экрана приложения;

  • MqttHelper — класс для подключения к MQTT-брокеру, отправки и приема сообщений;

  • MqttPayload — data-класс, формирующий json-строку MQTT-сообщения;

  • TFLiteModel — класс обработки Tensorflow lite модели.

Функции приложения

Проявив все свои «дизайнерские способности», я сделал такой вот интерфейс для основного экрана.

Приложение имеет следующий функционал:

  • автоматическое подключение к test.mosquitto.org:1883 MQTT-брокеру во время запуска (в проекте можно задать любой хост и порт брокера);

  • отправка дынных в MQTT-брокер версии 3;

  • подключение к серверу BLE ESP32 с помощью кнопки Connect;

  • отключение от сервера BLE ESP32 с помощью кнопки Disconnect, очистка данных с экрана;

  • автоматическая подписка на нотификации BLE после подключения:

  • обработка gesture_model.tflite моделью массива 375 ускорений, полученных из ESP32, результат обработки — названиt жеста на первой строке, и значения вероятностей на второй строке;

  • запрос распознанного жеста с ESP32 для сравнения с помощью кнопки Read;

  • установка величины свечения светодиода, заданной в % в текстовом поле с помощью кнопки Write;

Подключение Tensorflow-модели

В статье Machine learning на ESP32 был описан процесс создания модели распознавания жестов в Tensorflow на Python и конвертации ее в gesture_model.tflite файл. Располагаем файл модели в директории assets.

Исходный код класса модели расположен здесь — TFLiteModel Работа модели аналогична тому, как это было описано для микроконтроллера:

  • загружаем файл модели gesture_model.tflite и инициализируем интерпретатор interpreter;

  • создаем входной и выходной буферы: inputBuffer, outputBuffer

  • выполняем инференс (запуск) модели interpreter.run(inputBuffer, outputBuffer)

  • получаем результат из выходного буфера и производим дополнительные действия, если необходимо, например округление;

Краткое описание логики

Основная логика приложения расположена в классе MainActivity. Опишу некоторые методы.

  • onCreate

    • инициализируется основной экран приложения

    • инициализация компонентов экрана (кнопки, поля)

    • инициализация обработчиков кнопок

    • инициализация Tensorflow-модели

    • инициализация MQTT-обработчика

  • onDestroy

    • дисконнект MQTT-обработчика

  • startScan — подключение к ESP32 BLE-серверу

  • peripheralCallback — коллбэк обработки нотификаций BLE

    • Получаем массив из 375 байтов value: ByteArray

    • Делаем обратное преобразование в FloatArray(375)

fun dequantize(quantized: ByteArray): FloatArray {         val decompressedData = FloatArray(375)         for (i in quantized.indices) {             decompressedData[i] = ((quantized[i].toInt() + 128).toFloat() * 4 / 255.0f) - 2         }         return decompressedData     } 
  • обрабатываем данные с помощью модели tfliteModel.gestureResult(output)

  • выводим результат на экран

  • отсылаем результат в JSON-формате, содержащем время нотификации (timestamp) и распознанный жест в MQTT-брокер mqttHelper.publish

Перед запуском Android-приложения подаем пинание на ESP32. После этого можно открыть приложение и нажать кнопку Connect. Если сенсор находится в неподвижном положении, мы увидим Fixed жест в верхнем поле, а ниже — величины всех вероятностей жестов. Если выполним один из ранее описанных жестов: окружность, пересечение или перемещение, то увидим соотв. значение жеста в верхней строке. Если хотим сравнить значение жеста и вероятности, вычисленной на ESP 32, то можем нажать кнопку Read, и в строке выше увидим данные.

В строку ввода значения свечения светодиода можно ввести величину от 0 до 100. Яркость светодиода измениться соответственно.

Описание MQTT можно прочитать во многих статьях, например, Подробное объяснение MQTT или видео.
Приложение автоматически подключается к MQTT-брокеру. test.mosquitto.org:1883, и после подключения к BLE, автоматически отсылает данные. Для просмотра MQTT payload на компьютере можно использовать MQTT Explorer. В строке фильтра вводим BLE/gesture

MQTT Explorer

MQTT Explorer

Заключение

Bluetooth Low Energy (BLE) — один из самых доступных стандартов беспроводной связи. Хотя BLE — относительно сложная технология с исчерпывающей спецификацией, начать с основ достаточно легко, основываясь на доступных примерах и статьях. В данном материале мы реализовали логику подключения и передачи массива ускорений сенсора и распознаний жестов от микроконтроллера ESP32 к Android-устройству. BLE подходит для обмена объема данных не превышающих лимитов MTU. Для оптимизации пересылки сообщений можно использовать квантование — преобразование данных в байты фиксированного размера.

Используемые источники


ссылка на оригинал статьи https://habr.com/ru/articles/892418/