Bluetooth dan ESP32 – GATT Server

Jika kita ingin berbagi informasi sensor data dengan client applications, beacon tidak bisa digunakan.

Pada contoh project ini, kita akan membuat Bluetooth temperature sensor, dimana kita bisa connect dan menerima informasi temperature menggunakan mobile application, seperti nRF
Connect.

Fitur yang menarik adalah aplikasi sensor dapat melakukan push data ke client application ketika informasi baru sudah tersedia.

Persiapan Project

Hardware yang digunakan adalah DHT11 yang terhubung ke GPIO17.

Buat Project baru, lalu edit platformion.ini, (sesuaikan path esp-idf-lib pada project Anda).

[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
framework = espidf

monitor_speed = 115200
lib_extra_dirs = ../esp-idf-lib/components

Tambahkan dua files, src/app.h dan src/app.c, file ini merupakan abstraksi BLE-related implementation details agar main application clear dan mudah dipahami.

File app.c

#include "app.h"
#include <string.h>

static uint8_t adv_service_uuid128[32] = {
    0xfb,
    0x34,
    0x9b,
    0x5f,
    0x80,
    0x00,
    0x00,
    0x80,
    0x00,
    0x10,
    0x00,
    0x00,
    0xFF,
    0x00,
    0x00,
    0x00,
};

esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = false,
    .min_interval = 0x0006,
    .max_interval = 0x0010,
    .appearance = 0x00,
    .manufacturer_len = 0,
    .p_manufacturer_data = NULL,
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = sizeof(adv_service_uuid128),
    .p_service_uuid = adv_service_uuid128,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,
    .adv_int_max = 0x40,
    .adv_type = ADV_TYPE_IND,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .channel_map = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

service_info_t service_def;

void init_service_def(void)
{
    service_def.service_id.is_primary = true;
    service_def.service_id.id.inst_id = 0x00;
    service_def.service_id.id.uuid.len = ESP_UUID_LEN_16;
    service_def.service_id.id.uuid.uuid.uuid16 = GATT_SERVICE_UUID;

    service_def.char_uuid.len = ESP_UUID_LEN_16;
    service_def.char_uuid.uuid.uuid16 = GATT_CHARACTERISTIC_UUID;

    service_def.descr_uuid.len = ESP_UUID_LEN_16;
    service_def.descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;

    service_def.gatts_if = 0;
}

void update_conn_params(esp_bd_addr_t remote_bda)
{
    esp_ble_conn_update_params_t conn_params = {0};
    memcpy(conn_params.bda, remote_bda, sizeof(esp_bd_addr_t));
    conn_params.latency = 0;
    conn_params.max_int = 0x20;
    conn_params.min_int = 0x10;
    conn_params.timeout = 400;
    esp_ble_gap_update_conn_params(&conn_params);
}

File app.h

#ifndef gattex_app_h_
#define gattex_app_h_

#include <stdint.h>
#include <stddef.h>
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"

#define GATT_SERVICE_UUID 0x00FF
#define GATT_CHARACTERISTIC_UUID 0xFF01
#define GATT_HANDLE_COUNT 4

typedef struct
{
    uint16_t service_handle;
    esp_gatt_srvc_id_t service_id;
    uint16_t char_handle;
    esp_bt_uuid_t char_uuid;
    uint16_t descr_handle;
    esp_bt_uuid_t descr_uuid;
    esp_gatt_if_t gatts_if;
    uint16_t client_write_conn;

} service_info_t;

extern esp_ble_adv_data_t adv_data;
extern esp_ble_adv_params_t adv_params;
extern service_info_t service_def;

void init_service_def(void);
void update_conn_params(esp_bd_addr_t remote_bda);

#endif

Berikutnya konfigurasi ESP-IDF untuk enable Bluedroid dengan menjalankan PIO CLI.

(penv)$ pio run -t menuconfig

Code

Buka file src/main.c, tambahkan code dibawah:

//import library dan variable
#include <string.h>
#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 "esp_bt.h"

#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"

#include "sdkconfig.h"
#include "app.h"
#include "dht.h"

#define TAG "app"
#define SENSOR_NAME "ESP32-DHT11"
#define DHT11_PIN 17

static int16_t temp, hum;

static esp_attr_value_t sensor_data = {
    .attr_max_len = (uint16_t)sizeof(temp),
    .attr_len = (uint16_t)sizeof(temp),
    .attr_value = (uint8_t *)(&temp),
};


//gap handler
static void gap_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event)
    {
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        esp_ble_gap_start_advertising(&adv_params);
        break;
    default:
        break;
    }
}

//gatt handler
static void gatt_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    switch (event)
    {
    case ESP_GATTS_REG_EVT:
        esp_ble_gatts_create_service(gatts_if, &service_def.service_id, GATT_HANDLE_COUNT);
        break;
    case ESP_GATTS_CREATE_EVT:
        service_def.service_handle = param->create.service_handle;

        esp_ble_gatts_start_service(service_def.service_handle);
        esp_ble_gatts_add_char(service_def.service_handle,
                               &service_def.char_uuid,
                               ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
                               ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY,
                               &sensor_data, NULL);
        break;
    case ESP_GATTS_ADD_CHAR_EVT:
    {
        service_def.char_handle = param->add_char.attr_handle;
        esp_ble_gatts_add_char_descr(service_def.service_handle, &service_def.descr_uuid,
                                     ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
        break;
    }
    case ESP_GATTS_ADD_CHAR_DESCR_EVT:
        service_def.descr_handle = param->add_char_descr.attr_handle;
        esp_ble_gap_config_adv_data(&adv_data);
        break;

    case ESP_GATTS_CONNECT_EVT:
    {
        update_conn_params(param->connect.remote_bda);
        service_def.gatts_if = gatts_if;
        service_def.client_write_conn = param->write.conn_id;
        break;
    }

    case ESP_GATTS_READ_EVT:
    {
        esp_gatt_rsp_t rsp;
        memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
        rsp.attr_value.handle = param->read.handle;
        rsp.attr_value.len = sensor_data.attr_len;
        memcpy(rsp.attr_value.value, sensor_data.attr_value, sensor_data.attr_len);
        esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
                                    ESP_GATT_OK, &rsp);
        break;
    }
    case ESP_GATTS_WRITE_EVT:
    {
        if (service_def.descr_handle == param->write.handle)
        {
            uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];
            if (descr_value != 0x0000)
            {
                ESP_LOGI(TAG, "notify enable");
                esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, service_def.char_handle,
                                            sensor_data.attr_len, sensor_data.attr_value, false);
            }
            else
            {
                ESP_LOGI(TAG, "notify disable");
            }
            esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
                                        param->write.trans_id, ESP_GATT_OK, NULL);
        }
        else
        {
            esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
                                        param->write.trans_id, ESP_GATT_WRITE_NOT_PERMIT, NULL);
        }
        break;
    }

    case ESP_GATTS_DISCONNECT_EVT:
        service_def.gatts_if = 0;
        esp_ble_gap_start_advertising(&adv_params);
        break;

    default:
        break;
    }
}

//read temp
static void read_temp_task(void *arg)
{
    while (1)
    {
        vTaskDelay(2000 / portTICK_PERIOD_MS);
        if (dht_read_data(DHT_TYPE_DHT11, (gpio_num_t)DHT11_PIN, &hum, &temp) == ESP_OK)
        {
            temp /= 10;
            ESP_LOGI(TAG, "temp: %d", temp);
            if (service_def.gatts_if > 0)
            {
                esp_ble_gatts_send_indicate(service_def.gatts_if, service_def.client_write_conn,
                                            service_def.char_handle,
                                            sensor_data.attr_len, sensor_data.attr_value, false);
            }
        }
        else
        {
            ESP_LOGE(TAG, "DHT11 read failed");
        }
    }
}


//main function
void app_main(void)
{
    init_service_def();

    esp_err_t 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());
        ESP_ERROR_CHECK(nvs_flash_init());
    }
    esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);
    esp_bt_controller_enable(ESP_BT_MODE_BLE);
    esp_bluedroid_init();
    esp_bluedroid_enable();
    esp_ble_gap_set_device_name(SENSOR_NAME);

    esp_ble_gap_register_callback(gap_handler);
    esp_ble_gatts_register_callback(gatt_handler);
    esp_ble_gatts_app_register(0);

    xTaskCreate(read_temp_task, "temp", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL);
}

Penjelasan Code

Bagian Import dan Variable

Pertama include header files dan application macros. Salah satu dari macro akan mendefinisikan nama dari Bluetooth sensor.

Varaible temp dan hum are untuk menyimpan DHT11 readings. sensor_data adalah cara kita pass pembacaan temperature ke BLE API.

Bagian Main Function

Fungsi init_service_def adalah fungsi dari app.c untuk inisialisasi BLE-related global variables.

Kemudian kita inisialisasi nvs partition dari flash dan enable BT controller dalam BLE mode operation. Kita juga inisialisasi Bluedroid, sebagai BLE host.

Registrasikan callback functions gap_handler dan gatt_handler untuk handle GAP dan GATT events. BLE application diregeistrasikan dengan fungsi calling esp_ble_gatts_app_register, yang akan mentrigger event pada GATT layer dari BLE. Kemudian kita jalankan FreeRTOS task untuk membaca DHT11 secara periodik.

Bagian Gatt handler

Fungsi gatt_handler cukup panjang, oleh karena itu kita akan bahas GATT events handled satu persatu.

Event ESP_GATTS_REG_EVT, terjadi setlah BLE application diregistrasikan di app_main. Ketika event terhadi, kita buat BLE service agar dapat diliihat oleh BLE clients. service_def adalah variable yang dideklarasikan di app.h. Akan menyimpan informasi mengenai BLE service yang kita buat.

ESP_GATTS_CREATE_EVT terjadi setalah service dibuat pada event diatas. Kita start service disini dan menambahkan characteristic untuk serve sensor_data. Kita konfigurasi characteristic sebagai read dan notify.

Read berarti, client dapat membaca value dan notify berarti client dapat mengkonfigurasi characteristic untuk mengirim perubahan value tanpa diperlukan explicit read request. Ketika notify bit diset oleh client, semua informasi perubahan temperature akan dikirim otomatis.

Ketika characteristic ditambahkan, event ESP_GATTS_ADD_CHAR_EVT akan terjadi. Disini kita tambahkan characteristic descriptor untuk characteristic yang telah ditambahkan pada langkah diatas.

Descriptor menyediakan interface untuk client untuk enable atau disable notifications. Ketika client menulis value 0x00 ke descriptor, characteristic’s notify bit di reset. Untuk non-zero values, notify bit di set.

Mennambahkan characteristic descriptor akan menyebabkan event ESP_GATTS_ADD_CHAR_DESCR_EVT.

Ini adalah langkah terakhir dalam setup BLE service.

Setelah service tersedia, kita panggil esp_ble_gap_config_adv_data, untuk configures advertisement data dan triggers event pada GAP layer.

ESP_GATTS_CONNECT_EVT adalah event pertama ketika client terhubung. Kita update connection parameters dengan perintah update_conn_params, yang diimplementasikan pada app.c. Kemudian kita simpan communication handles pada service_def yang akan digunakan ketika akan mengirim BLE messages.

Event ESP_GATTS_READ_EVT terjadi ketika ada read request. Ketika ada read request dari client, kita reply dengan current temperature value dengan memanggil esp_ble_gatts_send_response.

Untuk write request, event ESP_GATTS_WRITE_EVT terjadi. Ketika ada write request dari client, pertama kita periksa, jika untuk descriptor dan value adalah non-zero, kita kembalikan current
value dari sensor_data dengan memanggil esp_ble_gatts_send_indicate. Fungsi ini akan mengirim data jika notify bit dari characteristic diset.

Terakhir, event ESP_GATTS_DISCONNECT_EVT yang terjadi ketika client disconnects. Kita akan mulai advertising untuk menunggu koneksi lainnya.

Terdapat beberapa GATT event yang tidak kita handle pada contoh project (karena tidak diperlukan). Untuk dokumentasi lengkap lihat di https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/esp_gatts.html

Bagian gap_handler

FUngsi gap_handler akan memerikasa event ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT untuk memulai advertising.

Bagian read_temp_task

Fungsi dipanggil dari app_main sebagai FreeRTOS task. Disini kita akan update global temp variable dan memanggil esp_ble_gatts_send_indicate untuk memberitahu perubahan ke client.
Jika notify bit dari of the characteristic di set, fungsi ini akan mengirim BLE message dengan temperature value baru.

Compile, Upload dan Testing

Setelah kita flash devkit, buka aplikas nRF.

Dibawah Services tab, kita dapat lihat service dengan UUID : 00FF. Dibawahnya terdapat characteristic list.

Tidak memiliki nama; hanya dilist sebagai unknow (karena berupa custom definition)

Seperti yang kita lihat pada gambar dibawa, Characteristic properties adalah Read dan Notify seperti yang kita konfigurasi dalam application. Characteristic descriptor ditampilkan dalam Client Characteristic Configuration, yang memiliki UUID : 2902.

Untuk enable atau disable notification, kita dapat gunakan up arrow dari descriptor (lihat gambar dibawah) untuk mamasukan value atau kita tap pada down-arrows button (lihat gambar dibawah) dari characteristic.

up arrow descriptor
down-arrows button pada characteristic

Sampai disini kita sudah berhasil menggunakan GATT server. Pada modul selanjutnya kita akan bahas BLE Mesh Network.

Sharing is caring:

Leave a Comment