Программирование ARM ESP-IDF: создание сервера BLE на основе таблицы GATT Fri, April 19 2024  

Поделиться

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

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

ESP-IDF: создание сервера BLE на основе таблицы GATT Печать
Добавил(а) microsin   

В этом документе представлено описание кода примера GATT Server Service Table для ESP32. Пример реализует сервер GATT Bluetooth Low Energy (BLE), используя табличную структуру данных для определения служб сервера и характеристик, таких как показаны на картинке ниже. Таким образом, демонстрируется практический метод определения функционала сервера в одном месте вместо того, чтобы добавлять сервисы и характеристики по одному.

В примере реализован профиль устройства анализа сердцебиения (Heart Rate Profile, HRP), как это определено традиционными стандартами (Traditional Profile Specifications).

Примечание: незнакомые термины и сокращения см. в словарике [5].

Heart Rate Service

Начнем с того, что рассмотрим подключаемые заголовки в основном файле gatts_table_creat_demo.c примера:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "bt.h"
#include "bta_api.h"
 
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_main.h"
#include “gatts_table_creat_demo.h"

Эти подключения требуются для запуска FreeRTOS и нижележащих системных компонентов, включая функционал логгинга и библиотеки для сохранения данных в энергонезависимой памяти flash. Нам более интересны bt.h, esp_bt_main.h, esp_gap_ble_api.h и esp_gatts_api.h, которые предоставляют BLE API, необходимое для реализации этого примера.

bt.h: реализует контроллер BT и процедуры конфигурации VHCI на стороне хоста.
esp_bt_main.h: реализует инициализацию и разрешение стека Bluedroid.
esp_gap_ble_api.h: реализует конфигурацию GAP, такую как параметры advertising-а и соединения.
esp_gatts_api.h: реализует конфигурацию GATT Server, такую как создание сервисов и характеристик.

[Таблица служб (Service Table)]

Заголовочный файл gatts_table_creat_demo.h содержит перечисление создаваемых служб и характеристик:

enum
{
   HRS_IDX_SVC,
 
   HRS_IDX_HR_MEAS_CHAR,
   HRS_IDX_HR_MEAS_VAL,
   HRS_IDX_HR_MEAS_NTF_CFG,
 
   HRS_IDX_BOBY_SENSOR_LOC_CHAR,
   HRS_IDX_BOBY_SENSOR_LOC_VAL,
 
   HRS_IDX_HR_CTNL_PT_CHAR,
   HRS_IDX_HR_CTNL_PT_VAL,
 
   HRS_IDX_NB,
};

Элементы в этом перечислении установлены в том же порядке, что и атрибуты Heart Rate Profile, начинаясь со службы, за которой идут характеристики службы. Дополнительно характеристика Heart Rate Measurement содержит дескриптор Client Characteristic Configuration (CCC), который является дополнительным атрибутом, описывающим, включена ли у характеристики функция оповещений. Индекс перечисления может использоваться для идентификации каждого элемента позже, когда создается реальная таблица атрибутов. В общем элементы описаны следующим образом:

HRS_IDX_SVC: индекс Heart Rate Service.
HRS_IDX_HR_MEAS_CHAR: индекс характеристики Heart Rate Measurement.
HRS_IDX_HR_MEAS_VAL: индекс значения характеристики Heart Rate Measurement.
HRS_IDX_HR_MEAS_NTF_CFG: индекс конфигурации оповещений Heart Rate Measurement (CCC).
HRS_IDX_BOBY_SENSOR_LOC_CHAR: индекс характеристики Heart Rate Body Sensor Location.
HRS_IDX_BOBY_SENSOR_LOC_VAL: индекс значения характеристики Heart Rate Body Sensor Location.
HRS_IDX_HR_CTNL_PT_CHAR: индекс характеристики Heart Rate Control Point.
HRS_IDX_HR_CTNL_PT_VAL: индекс значения характеристики Heart Rate Control Point.
HRS_IDX_NB: количество элементов в таблице.

[Основное приложение]

Точка входа в основное приложения находится в функции app_main (основная функция приложения):

void app_main()
{
   esp_err_t ret;
 
   // Инициализация NVS:
   ret = nvs_flash_init();
   if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
   {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
   }
   ESP_ERROR_CHECK( ret );
 
   esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
   ret = esp_bt_controller_init(&bt_cfg);
   if (ret)
   {
      ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
      return;
   }
 
   ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
   if (ret)
   {
      ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
      return;
   }
 
   ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__);
   ret = esp_bluedroid_init();
   if (ret)
   {
      ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed\n", __func__);
      return;
   }
 
   ret = esp_bluedroid_enable();
   if (ret)
   {
      ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed\n", __func__);
      return;
   }
 
   esp_ble_gatts_register_callback(gatts_event_handler);
   esp_ble_gap_register_callback(gap_event_handler);
   esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
   return;
}

Основная функция приложения начинается с инициализации библиотеки энергонезависимого хранилища (non-volatile storage, NVS), чтобы иметь возможность сохранять параметры в память flash [4].

ret = nvs_flash_init();

[Инициализация BT-контроллера и стека]

Основная функция приложения также инициализирует контроллер BT, сначала создавая структуру конфигурации esp_bt_controller_config_t с настройками по умолчанию, которые генерируются макросом BT_CONTROLLER_INIT_CONFIG_DEFAULT(). Контроллер BT реализует Host Controller Interface (HCI) на стороне контроллер, слой связи (Link Layer, LL) и слой физики (Physical Layer, PHY). Контроллер BT не видим для приложений пользователя, и он отвечает за работу нижних слоев протокола и стека BLE. Конфигурация контроллера включает настройки размера стека BT-контроллера, приоритет и скорость (HCI baud rate). Созданными настройками функция esp_bt_controller_init() инициализирует и разрешает контроллер BT:

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);

Затем контроллер разрешается в режиме BLE.

ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);

Примечание: если надо поддерживать двойной режим (классический BT и BLE) то в качестве параметра надо передать ESP_BT_MODE_BTDM.

Поддерживается 4 режима Bluetooth:

ESP_BT_MODE_IDLE: Bluetooth не запущен.
ESP_BT_MODE_BLE: режим BLE.
ESP_BT_MODE_CLASSIC_BT: режим BT Classic.
ESP_BT_MODE_BTDM: двойной режим (BLE + BT Classic).

После инициализации контроллера BT выполняется инициализация и разрешение стека Bluedroid, которая включает общие определения и API-функции как для BT Classic, так и для BLE:

ret = esp_bluedroid_init();
ret = esp_bluedroid_enable();

В этом месте стек Bluetooth запускается, однако функционал приложения пока еще не определен. Функционал определяется реакцией на такие события, как установка соединения с клиентом, попытки чтения или записи параметров устройством клиента. Существуют 2 главных менеджера событий - обработчики GAP и GATT. Приложению надо зарегистрировать callback-функцию для каждого обработчика, чтобы приложение знало, какие функции предназначены для обработки событий GAP и GATT:

esp_ble_gatts_register_callback(gatts_event_handler);
esp_ble_gap_register_callback(gap_event_handler);

Функции gatts_event_handler() и gap_event_handler() обрабатывают все события, которые проталкиваются в приложение из стека BLE.

[Профили приложения]

В примере реализован профиль приложения (Application Profile) для Heart Rate Service. Профиль приложения это способ группировки функционала, который разработан для использования клиентским устройством BLE, например программой, которая запущена на смартфоне. Таким методом на одном сервере может быть реализованы разные типы профилей. Назначаемый пользователем Application Profile ID используется для идентификации каждого профиля и для регистрации профиля в стеке. В этом примере ID = 0x55.

Примечание: в терминологии Bluetooth клиент это главное устройство (или Central), а сервер это подчиненное устройство (или Peripheral). Сервер периодически выдает пакеты оповещения (advertising) и прослушивает эфир в ожидании подключения со стороны клиента. Таким образом, клиент управляет процессом соединения и передачей данных.

#define HEART_PROFILE_NUM      1
#define HEART_PROFILE_APP_IDX  0
#define ESP_HEART_RATE_APP_ID  0x55

Профили сохранены в массиве heart_rate_profile_tab. Поскольку в этом примере профиль только один, в массиве сохранен только один элемент по индексу 0, как это определено в HEART_PROFILE_APP_IDX. Дополнительно инициализируется callback-функция обработчика событий профиля. Каждое приложение на сервере GATT использует отдельный интерфейс, представленный параметром gatts_if. Для инициализации этот параметр установлен в ESP_GATT_IF_NONE, а позже, когда приложение зарегистрировано, параметр gatts_if обновляется соответствующим интерфейсом, генерируемым стеком.

/* Один профиль GATT, один app_id и один gatts_if. Этот массив будет хранить
   gatts_if, возвращенный событием ESP_GATTS_REG_EVT */
static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] =
{
   [HEART_PROFILE_APP_IDX] =
   {
      .gatts_cb = gatts_profile_event_handler,
      .gatts_if = ESP_GATT_IF_NONE,
   },
};

Регистрация приложения происходит внутри app_main с использованием функции esp_ble_gatts_app_register():

esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);

[Установка параметров GAP]

Событие регистрации приложение это первое, что срабатывает во время жизни программы. Пример использует это событие, чтобы при регистрации в обработчике события профиля конфигурировать параметры оповещения (advertising). Для этого вызываются функции:

esp_ble_gap_set_device_name(): используется для установки оповещаемого имени устройства.
esp_ble_gap_config_adv_data(): используется для конфигурации стандартных данных оповещения.

Для конфигурации стандартных параметров оповещения используется функция esp_ble_gap_config_adv_data(), которая принимает указатель на структуру esp_ble_adv_data_t. В ней находятся данные оповещения (advertising data):

typedef struct
{
   bool set_scan_rsp;    /*!< Устанавливать эти данные в качестве scan response, или нет */
   bool include_name;    /*!< Включать имя устройства в advertising data, или нет */
   bool include_txpower; /*!< Advertising data включают TX power */
   int min_interval;     /*!< Advertising data показывают минимальный предпочтительный
                              для slave интервал соединения */
   int max_interval;     /*!< Advertising data показывают максимальный предпочтительный
                              для slave интервал соединения */
   int appearance;       /*!< Как устройство видится снаружи */
   uint16_t manufacturer_len; /*!< Длина данных производителя */
   uint8_t *p_manufacturer_data; /*!< Указатель на данные производителя */
   uint16_t service_data_len;    /*!< Длина данных службы */
   uint8_t *p_service_data;      /*!< Указатель на данные службы */
   uint16_t service_uuid_len;    /*!< Длина UUID службы */
   uint8_t *p_service_uuid;      /*!< Указатель на массив UUID */
   uint8_t flag;         /*!< Advertising-флаг режима распознавания (discovery mode),
                              подробнее см. BLE_ADV_DATA_FLAG */
} esp_ble_adv_data_t;

В этом примере структура esp_ble_adv_data_t инициализируется следующим образом:

static esp_ble_adv_data_t heart_rate_adv_config =
{
   .set_scan_rsp = false,
   .include_name = true,
   .include_txpower = true,
   .min_interval = 0x0006,
   .max_interval = 0x0010,
   .appearance = 0x00,
   .manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
   .p_manufacturer_data =  NULL, //&test_manufacturer[0],
   .service_data_len = 0,
   .p_service_data = NULL,
   .service_uuid_len = sizeof(heart_rate_service_uuid),
   .p_service_uuid = heart_rate_service_uuid,
   .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

Минимальный и максимальный предпочтительные интервалы соединения для slave устанавливаются в единицах 1.25 мс. В этом примере минимальный предпочтительный интервал соединения slave определен как 0x0006 * 1.25 = 7.5 мс, и максимальный предпочтительный интервал инициализирован как 0x0010 * 1.25 = 20 мс.

Полезная нагрузка пакетов оповещения (advertising payload) может быть до 31 байт данных. Может быть так, что некоторые параметры превысят 31-байтный предел пакета оповещения, что заставляет стек обрезать сообщение и отбросить некоторые параметры. Чтобы решить эту проблему, обычно более длинные параметры сохраняют в scan response, который может быть сконфигурирован той же функцией esp_ble_gap_config_adv_data() и дополнительной структурой esp_ble_adv_data_t с параметром .set_scan_rsp, установленным в true. И наконец, для установки имени устройства используется функция esp_ble_gap_set_device_name(). Регистрация обработчика события выполнятся следующим образом:

static void gatts_profile_event_handler(esp_gatts_cb_event_t event, 
                                        esp_gatt_if_t gatts_if,
                                        esp_ble_gatts_cb_param_t *param)
{
   ESP_LOGE(GATTS_TABLE_TAG, "event = %x\n",event);
   switch (event)
   {
   case ESP_GATTS_REG_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
      esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
      ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
      esp_ble_gap_config_adv_data(&heart_rate_adv_config);
      ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
   ...

[GAP Event Handler]

Как только данные оповещения были установлены, сработает событие ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT, и оно будет обработано GAP event handler. Кроме того, сработает событие ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT, если был установлен scan response. Как только конфигурация данных оповещения и scan response установлены, обработчик может использовать любое из этих событий для старта оповещения (advertising), что осуществляется с помощью функции esp_ble_gap_start_advertising():

static void gap_event_handler(esp_gap_ble_cb_event_t event,
                              esp_ble_gap_cb_param_t *param)
{   
   ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event);
   
   switch (event)
   {
   case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
      esp_ble_gap_start_advertising(&heart_rate_adv_params);
      break;
   case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
      // Событие запуска оповещения покажет, был ли запуск успешным:
      if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
      {
         ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed\n");
      }
      break;
   default:
      break;
   }
}

Функция для запуска оповещения принимает структуру типа esp_ble_adv_params_t с требуемыми advertising-параметрами.

/// Параметры адвертайзинга
typedef struct
{
   uint16_t adv_int_min; /*!< Минимальный advertising-интервал для undirected
                              и low duty cycle directed advertising.
                              Диапазон: 0x0020 .. 0x4000
                              По умолчанию N = 0x0800 (1.28 сек)
                              Time = N * 0.625 мс
                              Диапазон: 20 мс .. 10.24 сек */
   uint16_t adv_int_max; /*!< Максимальный advertising-интервал для undirected
                              и low duty cycle directed advertising.
                              Диапазон: 0x0020 .. 0x4000
                              По умолчанию: N = 0x0800 (1.28 сек)
                              Time = N * 0.625 мс
                              Диапазон: 20 мс .. 10.24 сек */
   esp_ble_adv_type_t adv_type;            /*!< Тип оповещения */
   esp_ble_addr_type_t own_addr_type;      /*!< Тип адреса владельца устройства */
   esp_bd_addr_t peer_addr;                /*!< Адрес пира */
   esp_ble_addr_type_t peer_addr_type;     /*!< Тип адреса пира */
   esp_ble_adv_channel_t channel_map;      /*!< Карта каналов оповещения */
   esp_ble_adv_filter_t adv_filter_policy; /*!< Политика фильтрации оповещения */
} esp_ble_adv_params_t;

Обратите внимание, что esp_ble_gap_config_adv_data() конфигурирует данные, которые посылаются в оповещении клиенту, и эта функция принимает структуру esp_ble_adv_data_t, в то время как esp_ble_gap_start_advertising() действительно запускает advertising, и принимает структуру esp_ble_adv_params_t. Данные advertising это информация, которую види потенциальный клиент, в тов время как параметры адвертайзинга это конфигурации, которая нужна для работы стеку BLE.

Для этого примера параметры адвертайзинга инициализированы так:

static esp_ble_adv_params_t heart_rate_adv_params =
{
   .adv_int_min        = 0x20,
   .adv_int_max        = 0x40,
   .adv_type           = ADV_TYPE_IND,
   .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
   //.peer_addr            =
   //.peer_addr_type       =
   .channel_map        = ADV_CHNL_ALL,
   .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

Эти параметры конфигурируют advertising-интервал между 20 мс и 40 мс. Оповещение типа ADV_IND, которое является обычным, не направленным на определенное устройство Central, и оно показывает, что сервер может принять соединение. Тип адреса публичный, используются все каналы, и разрешено как сканирование, так и запросы соединения от любого устройства Central.

Если процедура запуска advertising завершена, то генерируется событие ESP_GAP_BLE_ADV_START_COMPLETE_EVT, которое в этом примере используется для проверки статуса адвертайзинга, и если что-то пошло не так, выводится сообщение об ошибке.

   ...
   case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
      // Событие запуска оповещения покажет, был ли запуск успешным:
      if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
      {
         ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed\n");
      }
      break;
   ...

[GATT Event Handlers]

Когда Application Profile зарегистрирован, сработает событие ESP_GATTS_REG_EVT. Параметры этого события:

esp_gatt_status_t status;    /*!< Статус операции */
uint16_t app_id;             /*!< ID приложения, которое попало на вход
                                  API-функции регистрации */

В дополнение к предыдущим параметрам событие также содержит интерфейс GATT, назначенный стеком BLE. Событие захватывается обработчиком gatts_event_handler(), который сохраняет генерируемый интерфейс в таблицу профилей, и затем перенаправляет событие в обработчик события соответствующего профиля.

static void gatts_event_handler(esp_gatts_cb_event_t event,
                                esp_gatt_if_t gatts_if,
                                esp_ble_gatts_cb_param_t *param)
{
   ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if);
 
   /* Если это событие регистрации, то сохранение gatts_if
      для каждого профиля. */
   if (event == ESP_GATTS_REG_EVT)
   {
      if (param->reg.status == ESP_GATT_OK)
      {
         heart_rate_profile_tab[HEART_PROFILE_APP_IDX].gatts_if = gatts_if;
      }
      else
      {
         ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",
                  param->reg.app_id,
                  param->reg.status);
         return;
      }
   }
 
   do
   {
      int idx;
      for (idx = 0; idx < HEART_PROFILE_NUM; idx++)
      {
         if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, не указывается
                                                определенный gatt_if, требуется
                                                вызвать callback-функцию каждого
                                                из профилей */
             gatts_if == heart_rate_profile_tab[idx].gatts_if)
         {
            if (heart_rate_profile_tab[idx].gatts_cb)
            {
               heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
            }
         }
      }
   } while (0);
}

[Создание служб и характеристик с помощью таблицы атрибутов]

Событие регистрации используется для создания таблицы атрибутов профиля путем вызова функции esp_ble_gatts_create_attr_tab(). Эта функция принимает аргумент типа esp_gatts_attr_db_t, который соответствует таблице поиска (lookup table), заполненной значениями перечисления, определенного в заголовочном файле.

У структуры esp_gatts_attr_db_t два поля:

esp_attr_control_t attr_control; /*!< Тип управления атрибутом */
esp_attr_desc_t    att_desc;     /*!< Тип атрибута */

Здесь attr_control это параметр автоответа, который может быть установлен в ESP_GATT_AUTO_RSP, чтобы позволить стеку BLE позаботиться о сообщениях ответа, когда поступает событие чтение или записи. Другая опция ESP_GATT_RSP_BY_APP, которая позволяет ответить вручную, используя функцию esp_ble_gatts_send_response().

Поле att_desc это описание атрибута, которое составлено из:

uint16_t uuid_length;  /*!< Длина UUID */
uint8_t  *uuid_p;      /*!< Значение UUID */
uint16_t perm;         /*!< Разрешения на доступ к атрибуту */
uint16_t max_length;   /*!< Максимальная длина элемента */
uint16_t length;       /*!< Текущая длина элемента */
uint8_t  *value;       /*!< Массив значения элемента */

Например, первый элемент таблицы в этом примере это атрибут службы (service attribute):

[HRS_IDX_SVC] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&primary_service_uuid,
         ESP_GATT_PERM_READ,
         sizeof(uint16_t),
         sizeof(heart_rate_svc),
         (uint8_t *)&heart_rate_svc
      }
   },

Значения инициализации следующие:

[HRS_IDX_SVC]: Именованный или обозначенный инициализатор в таблице enum.
ESP_GATT_AUTO_RSP: конфигурация автоответа, чтобы стек отвечал сам.
ESP_UUID_LEN_16: длина UUID установлена в 16 бит.
(uint8_t *)&primary_service_uuid: UUID для идентификации службы как основной, primary (0x2800).
ESP_GATT_PERM_READ: разрешено читать службу (Read Permission).
sizeof(uint16_t): максимальная длина UUID службы (16 бит).
sizeof(heart_rate_svc): текущая длина службы установлена в размер переменной heart_rate_svc, что составляет 16 бит.
(uint8_t *)&heart_rate_svc: значение атрибута службы установлено в heart_rate_svc, где содержится стандартный идентификатор службы анализа сердцебиения, Heart Rate Service UUID (0x180D).

Остальные атрибуты инициализируются таким же образом. У некоторых атрибутов также есть свойство NOTIFY, которое установлено через &char_prop_notify. Полная структура таблицы инициализируется следующим образом:

/// Полное описание базы данных HRS - используется для добавления
/// в базу данных стека:
static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
{
   // Декларация Heart Rate Service
   [HRS_IDX_SVC] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&primary_service_uuid,
         ESP_GATT_PERM_READ,
         sizeof(uint16_t),
         sizeof(heart_rate_svc),
         (uint8_t *)&heart_rate_svc
      }
   },
 
   // Декларация характеристики Heart Rate Measurement
   [HRS_IDX_HR_MEAS_CHAR] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&character_declaration_uuid,
         ESP_GATT_PERM_READ,
         CHAR_DECLARATION_SIZE,
         CHAR_DECLARATION_SIZE,
         (uint8_t *)&char_prop_notify
         }
      },
 
   // Декларация значения характеристики Heart Rate Measurement
   [HRS_IDX_HR_MEAS_VAL] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&heart_rate_meas_uuid,
         ESP_GATT_PERM_READ,
         HRPS_HT_MEAS_MAX_LEN,
         0,
         NULL
      }
   },
 
   // Характеристика Heart Rate Measurement - дескриптор конфигурации
   // характеристики клиента
   [HRS_IDX_HR_MEAS_NTF_CFG] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&character_client_config_uuid,
         ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
         sizeof(uint16_t),
         sizeof(heart_measurement_ccc),
         (uint8_t *)heart_measurement_ccc
      }
   },
 
   // Декларация характеристик Body Sensor Location
   [HRS_IDX_BOBY_SENSOR_LOC_CHAR] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&character_declaration_uuid,
         ESP_GATT_PERM_READ,
         CHAR_DECLARATION_SIZE,
         CHAR_DECLARATION_SIZE,
         (uint8_t *)&char_prop_read
      }
   },
 
   // Значение характеристики Body Sensor Location
   [HRS_IDX_BOBY_SENSOR_LOC_VAL] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&body_sensor_location_uuid,
         ESP_GATT_PERM_READ,
         sizeof(uint8_t),
         sizeof(body_sensor_loc_val),
         (uint8_t *)body_sensor_loc_val
      }
   },
 
   // Декларация характеристики Heart Rate Control Point
   [HRS_IDX_HR_CTNL_PT_CHAR] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&character_declaration_uuid,
         ESP_GATT_PERM_READ,
         CHAR_DECLARATION_SIZE,
         CHAR_DECLARATION_SIZE,
         (uint8_t *)&char_prop_read_write
      }
   },
 
   // Значение характеристики Heart Rate Control
   [HRS_IDX_HR_CTNL_PT_VAL] =
   {
      {ESP_GATT_AUTO_RSP},
      {
         ESP_UUID_LEN_16,
         (uint8_t *)&heart_rate_ctrl_point,
         ESP_GATT_PERM_WRITE|ESP_GATT_PERM_READ,
         sizeof(uint8_t),
         sizeof(heart_ctrl_point),
         (uint8_t *)heart_ctrl_point
      }
   },
};

[Запуск службы]

Когда таблица атрибутов создана, сработает событие ESP_GATTS_CREAT_ATTR_TAB_EVT. У этого события параметры следующие:

esp_gatt_status_t status; /*!< Статус операции */
esp_bt_uuid_t svc_uuid;   /*!< Тип UUID службы */
uint16_t num_handle;      /*!< Номер handle атрибута для добавления в базу данных GATTS */
uint16_t *handles;        /*!< Количество handle */

Пример использует это событие для печати информации и для проверки, что размер созданной таблицы равен количеству элементов в перечислении HRS_IDX_NB. Если таблица создана корректно, то handle атрибутов копируются в таблицу heart_rate_handle_table, и служба запускается с помощью функции esp_ble_gatts_start_service():

   case ESP_GATTS_CREAT_ATTR_TAB_EVT:
   {
      ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",
               param->add_attr_tab.num_handle);
      if (param->add_attr_tab.status != ESP_GATT_OK)
      {
         ESP_LOGE(GATTS_TABLE_TAG,
                  "Create attribute table failed, error code=0x%x",
                  param->add_attr_tab.status);
      }
      else if (param->add_attr_tab.num_handle != HRS_IDX_NB)
      {
         ESP_LOGE(GATTS_TABLE_TAG,
                  "Create attribute table abnormally, num_handle (%d) \
doesn't equal to HRS_IDX_NB(%d)",
                  param->add_attr_tab.num_handle,
                  HRS_IDX_NB);
      }
      else
      {
         memcpy(heart_rate_handle_table,
                param->add_attr_tab.handles,
                sizeof(heart_rate_handle_table));
         esp_ble_gatts_start_service(heart_rate_handle_table[HRS_IDX_SVC]);
      }
      break;

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

И наконец, heart_rate_handle_table содержит Application Profile в форме структуры с информацией по параметрам атрибутов, а также по интерфейсу GATT, ID соединения, разрешениям и ID приложения. Структура профиля показана следующим образом, обратите внимание, что этом примере используются не все поля:

struct gatts_profile_inst
{
   esp_gatts_cb_t gatts_cb;
   uint16_t gatts_if;
   uint16_t app_id;
   uint16_t conn_id;
   uint16_t service_handle;
   esp_gatt_srvc_id_t service_id;
   uint16_t char_handle;
   esp_bt_uuid_t char_uuid;
   esp_gatt_perm_t perm;
   esp_gatt_char_prop_t property;
   uint16_t descr_handle;
   esp_bt_uuid_t descr_uuid;
};

[Ссылки]

1. ESP-IDF GATT Server Service Table Example Walkthrough site:github.com.
2. ESP-IDF GATT Server Example Walkthrough site:github.com.
3. ESP-IDF Controller && VHCI site:espressif.com.
4. ESP32: библиотека энергонезависимого хранилища данных.
5. Bluetooth: аббревиатуры и термины.

 

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


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

Top of Page