0

I'm working on this project where I'm using two ESP32-C3 microcontrollers. I'm trying to use BLE ( NimBLE stack specifically ) to connect them to share some data. I never worked with BLE or anything similar before so this was quite challenging. I got the two ESP32s to connect but I still can't share any data because I get some errors when doing service discovery on the client-side. Note: I downloaded some phone app called nRF Connect that enables me to connect to the server ESP32 and share data. I can successfully share data using that phone app, meaning, my server side ESP32' code is kind of "OK". The problem should be in the client-side code, regarding those service discovery errors.

I'll provide the code for both server and client side, but don't judge me, it's not really clean and I had ChatGPT help me a lot understanding these things, so the issue might even be my misunderstanding of something. Also, I took a lot of inspiration from other people' code examples, specifically SIMS-IOT guy from YouTube.

SERVER-SIDE

// NimBLE Server

#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "host/ble_gatt.h"
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "sdkconfig.h"

#define MY_SERVICE_UUID         BLE_UUID16_DECLARE(0x1405)
#define MY_READ_CHR_UUID        BLE_UUID16_DECLARE(0x0508)
#define MY_WRITE_CHR_UUID       BLE_UUID16_DECLARE(0x0512)

char *TAG = "BLE-Server";
uint8_t ble_addr_type;
void ble_app_advertise(void);
static uint16_t timestamp_value = 69;

static int timestamp_read(uint16_t con_handle, uint16_t attr_handle, 
    struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    os_mbuf_append(ctxt->om, &timestamp_value, sizeof(uint16_t));
    return 0;
}

static int timestamp_write(uint16_t conn_handle, uint16_t attr_handle,
                                  struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    printf("Data from the client: %.*s\n", ctxt->om->om_len, ctxt->om->om_data);
    if (ctxt->om->om_len == sizeof(uint16_t)) 
    {
        memcpy(&timestamp_value, ctxt->om->om_data, sizeof(uint16_t));
        printf("%u\n", timestamp_value);
    }
    return 0;
}

static const struct ble_gatt_svc_def gatt_svcs[] = {
    {.type = BLE_GATT_SVC_TYPE_PRIMARY,
     .uuid = MY_SERVICE_UUID,                       // UUID for service
     .characteristics = (struct ble_gatt_chr_def[]){
         {.uuid = MY_READ_CHR_UUID,                 // UUID for reading
          .flags = BLE_GATT_CHR_F_READ,
          .access_cb = timestamp_read},
         {.uuid = MY_WRITE_CHR_UUID,                // UUID for writing
          .flags = BLE_GATT_CHR_F_WRITE,
          .access_cb = timestamp_write},
         {0}}},
    {0}};

// BLE event handling
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
    switch (event->type)
    {
    case BLE_GAP_EVENT_CONNECT:
        ESP_LOGI("GAP", "BLE GAP EVENT CONNECT %s", 
        event->connect.status == 0 ? "OK!" : "FAILED!");
        if (event->connect.status == 0) {
                printf("Connection established with client\n");
            } else {
                printf("Connection failed with status: %d\n", event->connect.status);
                ble_app_advertise();
            }
        break;
    case BLE_GAP_EVENT_ADV_COMPLETE:
        ESP_LOGI("GAP", "BLE GAP EVENT ADVERTISEMENT COMPLETE");
        ble_app_advertise();
        break;
    case BLE_GAP_EVENT_DISCONNECT:
        ESP_LOGI("GAP", "BLE DEVICE DISCONNECTED");
        ble_app_advertise();
        break;
    default:
        break;
    }
    return 0;
}

// Define the BLE connection
void ble_app_advertise(void)
{
    // GAP - device name definition
    struct ble_hs_adv_fields fields;
    const char *device_name;
    memset(&fields, 0, sizeof(fields));
    device_name = ble_svc_gap_device_name(); // Read the BLE device name
    fields.name = (uint8_t *)device_name;
    fields.name_len = strlen(device_name);
    fields.name_is_complete = 1;

    // Include the list of service UUIDs in the advertisement
    fields.uuids16 = MY_SERVICE_UUID;
    fields.num_uuids16 = 1;

    ble_gap_adv_set_fields(&fields);

    // GAP - device connectivity definition
    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);
}

// The application
void ble_app_on_sync(void)
{
    ble_hs_id_infer_auto(0, &ble_addr_type);
    ble_app_advertise();                     // Define the BLE connection
}

// The infinite task
void host_task(void *param)
{
    nimble_port_run();
}

void app_main()
{
    nvs_flash_init();                          // 1 - Initialize NVS flash using
    esp_nimble_hci_init();                     // 2 - Initialize ESP controller
    nimble_port_init();                        // 3 - Initialize the host stack
    ble_svc_gap_device_name_set("BLE-Server"); // 4 - Initialize NimBLE configuration - server name
    ble_svc_gap_init();                        // 4 - Initialize NimBLE configuration - gap service
    ble_svc_gatt_init();                       // 4 - Initialize NimBLE configuration - gatt service
    ble_gatts_count_cfg(gatt_svcs);            // 4 - Initialize NimBLE configuration - config gatt services
    ble_gatts_add_svcs(gatt_svcs);             // 4 - Initialize NimBLE configuration - queues gatt services.
    ble_hs_cfg.sync_cb = ble_app_on_sync;      // 5 - Initialize application
    nimble_port_freertos_init(host_task);      // 6 - Run the thread
}

CLIENT-SIDE

// NimBLE Client

#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "host/ble_gatt.h"
#include "sdkconfig.h"

#define MY_SERVICE_UUID         BLE_UUID16_DECLARE(0x1405)
#define MY_READ_CHR_UUID        BLE_UUID16_DECLARE(0x0508)
#define MY_WRITE_CHR_UUID       BLE_UUID16_DECLARE(0x0512)

uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE;
uint16_t attr_handle = 0;
uint16_t read_handle = 0;
uint16_t write_handle = 0;
bool writable_chr_discovered = false;
static bool service_discovery_in_progress = false;


struct ble_hs_adv_fields fields;


char *TAG = "BLE Client Scan";
uint8_t ble_addr_type;
void ble_app_scan(void);
static uint16_t timestamp_value = 0;

static int on_read(uint16_t conn_handle, int status, struct ble_gatt_attr *attr, void *arg)
{
    if (status == 0 && attr != NULL)
    {
        if (attr->om->om_len == sizeof(uint16_t))
        {
            memcpy(&timestamp_value, attr->om->om_data, sizeof(uint16_t));
            printf("Received timestamp value from the server: %u\n", timestamp_value);
        }
    }
    else
    {
        printf("Read operation failed with status: %d\n", status);
    }

    os_mbuf_free_chain(attr->om);

    return 0;
}

// Characteristic discovery callback
static int on_disc_chr(uint16_t conn_handle, const struct ble_gatt_error *error,
                       const struct ble_gatt_chr *chr, void *arg)
{
    int rc = 0;
    ESP_LOGI("GAP", "1\n");
    if (error == NULL && chr != NULL)
    {
        if (ble_uuid_cmp(&chr->uuid, MY_READ_CHR_UUID) == 0)
        {
            read_handle = chr->val_handle;
        } else if (ble_uuid_cmp(&chr->uuid, MY_WRITE_CHR_UUID) == 0)
        {
            write_handle = chr->val_handle;
            printf("Found writable characteristic. Handle: %d\n", write_handle);
            writable_chr_discovered = true;
            ESP_LOGI("GAP", "2\n");
        }
    }
    return 0;
}

// Service discovery callback
static int on_disc_svc(uint16_t conn_handle, const struct ble_gatt_error *error,
                       const struct ble_gatt_svc *service, void *arg)
{
    ESP_LOGI("GAP", "3\n");
    if (error == NULL)
    {
        if (service != NULL)
        {
            int rc = ble_gattc_disc_all_chrs(conn_handle, service->start_handle, service->end_handle, on_disc_chr, NULL);
            if (rc != 0)
            {
                printf("Failed to discover characteristics for service (error: %d)\n", rc);
                ESP_LOGI("GAP", "4\n");
            }
            else
            {
                printf("Discovered a service!\n");
                ESP_LOGI("GAP", "5\n");
                ble_gap_disc_cancel();
            }
        }
    }
    else
    {
        printf("Service discovery error: %d\n", error->status);
    }
    service_discovery_in_progress = false;

    return 0;
}

void ble_app_write_timestamp(uint16_t conn_handle, uint16_t write_handle, uint16_t timestamp_value)
{
    struct os_mbuf *om;
    om = ble_hs_mbuf_from_flat(&timestamp_value, sizeof(timestamp_value));
    ESP_LOGI("GAP", "6\n");
    if (om != NULL)
    {
        int rc = ble_gattc_write_flat(conn_handle, write_handle, om->om_data, om->om_len, NULL, NULL);
        if (rc != 0)
        {
            printf("Error writing timestamp value: %d\n", rc);
            ESP_LOGI("GAP", "7\n");
        }
        os_mbuf_free_chain(om);
    }
}

// BLE connection event handling callback
static int ble_gap_connect_cb(struct ble_gap_event *event, void *arg)
{
    int rc = 0;
    switch (event->type) {
        case BLE_GAP_EVENT_CONNECT:
            ESP_LOGI("GAP", "GAP EVENT CONNECTED");
            conn_handle = event->connect.conn_handle;
            printf("Connected with %.*s\n", fields.name_len, fields.name);

            vTaskDelay(pdMS_TO_TICKS(1000));

            if (!service_discovery_in_progress) {
                service_discovery_in_progress = true;
                rc = ble_gattc_disc_all_svcs(event->connect.conn_handle, on_disc_svc, NULL);
                if (rc != 0) {
                    printf("Failed to initiate service discovery (error: %d)\n", rc);
                    service_discovery_in_progress = false;
                }
            } else {
                printf("Service discovery already in progress, skipping.\n");
            }
            break;
        case BLE_GAP_EVENT_DISCONNECT:
            ESP_LOGI("GAP", "GAP EVENT DISCONNECTED");
            conn_handle = BLE_HS_CONN_HANDLE_NONE;
            writable_chr_discovered = false;
            service_discovery_in_progress = false;
            break;
        default:
            break;
    }
    return 0;
}


// BLE event handling
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
    struct ble_gap_conn_params conn_params;
    memset(&conn_params, 0, sizeof(conn_params));
    int rc = 0;

    switch (event->type)
    {
    case BLE_GAP_EVENT_DISC:
        ESP_LOGI("GAP", "GAP EVENT DISCOVERY");
        ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data);
        if(fields.name_len > 0)
        {
            printf("Discovered %.*s\n", fields.name_len, fields.name);
        }
        ble_gap_disc_cancel();

        conn_params.scan_itvl = BLE_GAP_SCAN_FAST_INTERVAL_MAX;
        conn_params.scan_window = BLE_GAP_SCAN_FAST_WINDOW;
        conn_params.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN;
        conn_params.itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX;
        conn_params.latency = BLE_GAP_INITIAL_CONN_LATENCY;
        conn_params.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT;
        conn_params.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN;
        conn_params.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN;


        rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &event->disc.addr, 1000, &conn_params, ble_gap_connect_cb, NULL);
        vTaskDelay(pdMS_TO_TICKS(400));
        if (rc != 0)
        {
            printf("Failed to initiate connection (error: %d)\n", rc);
        }
        break;

    default:
        break;

    }
    return 0;
}

void ble_app_scan(void)
{
    struct ble_gap_disc_params disc_params;

    printf("Start scanning ...\n");

    disc_params.filter_duplicates = 1;
    disc_params.passive = 0;
    disc_params.itvl = 128;
    disc_params.window = 0;
    disc_params.filter_policy = 0;
    disc_params.limited = 0;

    ble_gap_disc(ble_addr_type, BLE_HS_FOREVER, &disc_params, ble_gap_event, NULL);
}

// The application
void ble_app_on_sync(void)
{
    ble_hs_id_infer_auto(0, &ble_addr_type);
    vTaskDelay(pdMS_TO_TICKS(1000));
    ble_app_scan();                          
}

// The infinite task
void host_task(void *param)
{
    nimble_port_run();
}

void app_main()
{
    nvs_flash_init();                               // 1 - Initialize NVS flash using
    esp_nimble_hci_init();                          // 2 - Initialize ESP controller
    nimble_port_init();                             // 3 - Initialize the controller stack
    esp_log_level_set("*", ESP_LOG_INFO);           // Initialize Logging System with log level INFO
    ble_svc_gap_device_name_set("BLE-Client");      // 4 - Set device name characteristic
    ble_hs_cfg.sync_cb = ble_app_on_sync;           // 4 - Set application
    ble_svc_gap_init();                             // 5 - Initialize GAP service
    nimble_port_freertos_init(host_task);           // 6 - Set infinite task
}

So, I'm trying to send a simple timestamp value from the client to the server, and I was planning on doing the same thing in reverse. For testing purposes, I used a simple uint16_t integer as the "timestamp" value. I wanted to send that value using the ble_app_write_timestamp() function I implemented. I didn't call it in the provided code because I was trying to figure out some errors that occurred while doing service discovery. I put some log statements in my code to help me figure out where are things going wrong, and this is the output I get from the client:

Start scanning ...
I (1367) NimBLE: GAP procedure initiated: discovery;
I (1367) NimBLE: own_addr_type=0 filter_policy=0 passive=0 limited=0 filter_duplicates=1  
I (1367) NimBLE: duration=forever
I (1367) NimBLE: 

I (1407) GAP: GAP EVENT DISCOVERY
Discovered BLE-Server
I (1407) NimBLE: GAP procedure initiated: connect; 
I (1407) NimBLE: peer_addr_type=0 peer_addr=
I (1407) NimBLE: 58:cf:79:0c:84:b2
I (1417) NimBLE:  scan_itvl=96 scan_window=48 itvl_min=24 itvl_max=40 latency=0 supervision_timeout=256 min_ce_len=0 max_ce_len=0 own_addr_type=0
I (1427) NimBLE:

I (1837) GAP: GAP EVENT CONNECTED
Connected with BLE-Server
I (2837) NimBLE: GATT procedure initiated: discover all services

I (2917) GAP: 3

Service discovery error: 0
I (2917) GAP: 3

Service discovery error: 0
I (2917) GAP: 3

Service discovery error: 0
I (2927) GAP: 3

Service discovery error: 14

And this is the output from the server:

I (367) NimBLE: GAP procedure initiated: stop advertising.

I (367) NimBLE: GAP procedure initiated: advertise;
I (377) NimBLE: disc_mode=2
I (377) NimBLE:  adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
I (387) NimBLE:

I (6117) GAP: BLE GAP EVENT CONNECT OK!
Connection established with client

I didn't copy all of the output because there's a lot of initialization stuff that's hopefully not relevant. Anyway, my question is, if you can help me figure out why can't I discover the server services on client-side?

BTW, I'm using ESP-IDF enviroment in VS Code, and I'm programming this in C of course.

user58697
  • 7,808
  • 1
  • 14
  • 28
f0dor
  • 15
  • 4

0 Answers0