RTOS & BLE: nimble_port_run() is blocking; how to advertise constantly with own concurrent functionality?

CascadingStatic
Posts: 5
Joined: Sat Feb 17, 2024 6:13 pm

RTOS & BLE: nimble_port_run() is blocking; how to advertise constantly with own concurrent functionality?

Postby CascadingStatic » Sat Jul 06, 2024 1:05 am

Hello all.
I'm a bit new to BLE/real-world RTOS functionality and would really appreciate your help.

MY PROBLEM: nimble_port_run(), which enables BLE functionality and makes my ESP BLE server accessible, blocks until nimble_port_stop() is called elsewhere. I need to be able to constantly advertise using my S3 as a BLE server, while also updating the value that a client will read in their scanner app through other functionality. If the value my custom functionality computes is above a given threshold, I need to update the value advertised by the BLE server. Here, I have simulated this with a basic "random number generator task".

Below is a basic starting point, reproducible example of my current code for an S3. Menuconfig->BLE in the bluetooth menu, flash at 4MB are necessary. I have the CPU freq set to 240MHz.

If you scroll near the bottom, you'll see the app_main and the two tasks.
I do not know if it is good practice to just turn it on and off constantly, or whether there is a good way to multithread it or something. I have:
- Tried using semaphores (doesn't solve the blocking problem)
- Tried changing task priorities by digging into the task created by nimble_port_freertos_init()
- Tried switching the nimble port on and off (not init/deinit; just stop/run). But this really is not viable for my project; I need my other sensor peripherals working constantly, and this also ticking away constantly.

Is there something I'm missing about how BLE or RTOS works (I'm sure there is!). Thanks for patience and any time at all spent reading.
  1. #include <stdio.h>
  2. #include "freertos/FreeRTOS.h"
  3. #include "freertos/task.h"
  4. #include "freertos/event_groups.h"
  5. #include "esp_event.h"
  6. #include "nvs_flash.h"
  7. #include "esp_log.h"
  8. #include "esp_nimble_hci.h"
  9. #include "nimble/nimble_port.h"
  10. #include "nimble/nimble_port_freertos.h"
  11. #include "host/ble_hs.h"
  12. #include "services/gap/ble_svc_gap.h"
  13. #include "services/gatt/ble_svc_gatt.h"
  14. #include "sdkconfig.h"
  15.  
  16. #include "esp_random.h"
  17.  
  18.  
  19. int NUM_FOR_BLE = 0;
  20.  
  21. char *TAG = "BLE-Server";
  22. uint8_t ble_addr_type;
  23. void ble_app_advertise(void);
  24.  
  25.  
  26. // Read data from ESP32 defined as server
  27. static int device_read(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
  28. {  
  29.     char data_str[10];
  30.     sprintf(data_str, "val: %d", NUM_FOR_BLE);
  31.     os_mbuf_append(ctxt->om, data_str, strlen(data_str));
  32.     return 0;
  33. }
  34.  
  35. // Array of pointers to other service definitions
  36. // UUID - Universal Unique Identifier
  37. static const struct ble_gatt_svc_def gatt_svcs[] = {
  38.     {.type = BLE_GATT_SVC_TYPE_PRIMARY,
  39.      .uuid = BLE_UUID16_DECLARE(0x180),                 // Define UUID for device type
  40.      .characteristics = (struct ble_gatt_chr_def[]) {
  41.             {
  42.                 .uuid = BLE_UUID16_DECLARE(0xFEF4),           // Define UUID for reading
  43.                 .flags = BLE_GATT_CHR_F_READ,
  44.                 .access_cb = device_read
  45.             },
  46.             {0}
  47.         }
  48.     },
  49.     {0}
  50. };
  51.  
  52. // BLE event handling
  53. static int ble_gap_event(struct ble_gap_event *event, void *arg)
  54. {
  55.     switch (event->type)
  56.     {
  57.     // Advertise if connected
  58.     case BLE_GAP_EVENT_CONNECT:
  59.         ESP_LOGI("GAP", "BLE GAP EVENT CONNECT %s", event->connect.status == 0 ? "OK!" : "FAILED!");
  60.         if (event->connect.status != 0)
  61.         {
  62.             ble_app_advertise();
  63.         }
  64.         break;
  65.     // Advertise again after completion of the event
  66.     case BLE_GAP_EVENT_DISCONNECT:
  67.         ESP_LOGI("GAP", "BLE GAP EVENT DISCONNECTED");
  68.         break;
  69.     case BLE_GAP_EVENT_ADV_COMPLETE:
  70.         ESP_LOGI("GAP", "BLE GAP EVENT");
  71.         ble_app_advertise();
  72.         break;
  73.     default:
  74.         break;
  75.     }
  76.     return 0;
  77. }
  78.  
  79. // Define the BLE connection
  80. void ble_app_advertise(void)
  81. {
  82.     // GAP - device name definition
  83.     struct ble_hs_adv_fields fields;
  84.     const char *device_name;
  85.     memset(&fields, 0, sizeof(fields));
  86.     device_name = ble_svc_gap_device_name(); // Read the BLE device name
  87.     fields.name = (uint8_t *)device_name;
  88.     fields.name_len = strlen(device_name);
  89.     fields.name_is_complete = 1;
  90.     ble_gap_adv_set_fields(&fields);
  91.  
  92.     // GAP - device connectivity definition
  93.     struct ble_gap_adv_params adv_params;
  94.     memset(&adv_params, 0, sizeof(adv_params));
  95.     adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // connectable or non-connectable
  96.     adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable
  97.     ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL);
  98. }
  99.  
  100. // The application
  101. void ble_app_on_sync(void)
  102. {
  103.     ble_hs_id_infer_auto(0, &ble_addr_type); // Determines the best address type automatically
  104.     ble_app_advertise();                     // Define the BLE connection
  105. }
  106.  
  107. // The infinite task
  108. void host_task(void *param)
  109. {
  110.     nimble_port_run(); // This function will return only when nimble_port_stop() is executed
  111. }
  112.  
  113.  
  114. TaskHandle_t numGenHandler = NULL;
  115. void numberGenerator(void* params);
  116.  
  117. void numberGenerator(void* params)
  118. {  
  119.     while (true) {
  120.         vTaskDelay(portTICK_PERIOD_MS * 2000);
  121.         uint32_t rNum = esp_random();
  122.         NUM_FOR_BLE = (rNum % 10) + 1;
  123.         fprintf(stdout, "num: %d\n", NUM_FOR_BLE);
  124.     }
  125. }
  126.  
  127.  
  128. void app_main()
  129. {
  130.     nvs_flash_init();                          // Initialize NVS flash using
  131.     nimble_port_init();                        // Initialize the host stack
  132.     ble_svc_gap_device_name_set("BLETest");    // Initialize NimBLE configuration - server name
  133.     ble_svc_gap_init();                        // Initialize NimBLE configuration - gap service
  134.     ble_svc_gatt_init();                       // Initialize NimBLE configuration - gatt service
  135.     ble_gatts_count_cfg(gatt_svcs);            // Initialize NimBLE configuration - config gatt services
  136.     ble_gatts_add_svcs(gatt_svcs);             // Initialize NimBLE configuration - queues gatt services.
  137.     ble_hs_cfg.sync_cb = ble_app_on_sync;      // Initialize application
  138.     nimble_port_freertos_init(host_task);      // Run the thread
  139.  
  140.     xTaskCreate(&numberGenerator, "main Task", 4096, NULL, 2, &numGenHandler);
  141. }

MicroController
Posts: 1702
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: RTOS & BLE: nimble_port_run() is blocking; how to advertise constantly with own concurrent functionality?

Postby MicroController » Sat Jul 06, 2024 5:04 pm

CascadingStatic wrote:
Sat Jul 06, 2024 1:05 am
MY PROBLEM: nimble_port_run(), which enables BLE functionality and makes my ESP BLE server accessible, blocks until nimble_port_stop() is called elsewhere.
I'm not sure I get what your problem actually is.
Yes, nimble_port_run() loops processing BLE events until NimBLE is stopped. That's why nimble_port_freertos_init(...) internally creates a new task dedicated to running this loop.
When started, advertising also runs "in the background", concurrently to your application's other tasks, until it times out or is stopped.
If you want to change the contents of the advertisement message, you'll have to stop advertising, reconfigure the advertisement, and start advertising again.
When a BLE client is connected and tries to read an attribute you delared, the callback function you defined (device_read() in your case) is called by the BLE stack to provide the data to return to the client.
If the server wishes to notify a client, e.g. because an attribute's value has changed, it can proactively send a notification or indication to the client, provided the client has previously 'subscribed' to/enabled the notification by writing to the corresponding CCCD attribute.

To share data between tasks, e.g. the numberGenerator and the BLE host task, there are a couple of possible ways. For single values of 32 bits or less, declaring a volatile variable can be sufficient (like 'volatile int NUM_FOR_BLE;').

CascadingStatic
Posts: 5
Joined: Sat Feb 17, 2024 6:13 pm

Re: RTOS & BLE: nimble_port_run() is blocking; how to advertise constantly with own concurrent functionality?

Postby CascadingStatic » Sat Jul 06, 2024 6:09 pm

MicroController wrote:
Sat Jul 06, 2024 5:04 pm
I'm not sure I get what your problem actually is.
Yes, nimble_port_run() loops processing BLE events until NimBLE is stopped. That's why nimble_port_freertos_init(...) internally creates a new task dedicated to running this loop.
When started, advertising also runs "in the background", concurrently to your application's other tasks, until it times out or is stopped.
If you want to change the contents of the advertisement message, you'll have to stop advertising, reconfigure the advertisement, and start advertising again.
When a BLE client is connected and tries to read an attribute you delared, the callback function you defined (device_read() in your case) is called by the BLE stack to provide the data to return to the client.
If the server wishes to notify a client, e.g. because an attribute's value has changed, it can proactively send a notification or indication to the client, provided the client has previously 'subscribed' to/enabled the notification by writing to the corresponding CCCD attribute.

To share data between tasks, e.g. the numberGenerator and the BLE host task, there are a couple of possible ways. For single values of 32 bits or less, declaring a volatile variable can be sufficient (like 'volatile int NUM_FOR_BLE;').
Hi, I'm really sorry -- I wrote that at ~0300 and thought I had included: currently, the BLE task seems to be completely blocking my own generator task; that fprintf only outputs to the serial console, and likewise the NUM_FOR_BLE is only changed, if I call a read or a write service from my client (I cut this out from the example code to keep it smaller -- it follows the same standard GATT/callback format -- code included below *** should it be relevant). The number generator task doesn't actually run at all.

This is program-breaking for me as I need to continually update the global integer with the value returned from an environmental monitoring peripheral -- the device_read() pulls on that when writing to the mbuf. My understanding is/was thatif the client is connected to and requests a read service several times, that global NUM_FOR_BLE's value and its changed should be passed over to the BLE client app, no?

Your suggesiton to use the 'notify' service with a volatile attribute, and let it continually message out without needing to an acknowledgement back from client, and without need of me repeatedly making requests to the 'read' service, like you say, seems like the better choice either way!


Here's the code *** if relevant/helpful for context

Code: Select all

// Array of pointers to other service definitions
// UUID - Universal Unique Identifier
static const struct ble_gatt_svc_def gatt_svcs[] = {
    {.type = BLE_GATT_SVC_TYPE_PRIMARY,
     .uuid = BLE_UUID16_DECLARE(0x180),                 // Define UUID for device type
     .characteristics = (struct ble_gatt_chr_def[]) {
            {
                .uuid = BLE_UUID16_DECLARE(0xFEF4),           // Define UUID for reading
                .flags = BLE_GATT_CHR_F_READ,
                .access_cb = device_read
            },
            {
                .uuid = BLE_UUID16_DECLARE(0xDEAD),           // Define UUID for writing
                .flags = BLE_GATT_CHR_F_WRITE,
                .access_cb = device_write
            },
            {
                0
            }
        }
    },
    {0}
};

static int device_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    char * data = (char *)ctxt->om->om_data;
    printf("Data from the client: %.*s\n", ctxt->om->om_len, ctxt->om->om_data);   
    return 0;
}


MicroController
Posts: 1702
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: RTOS & BLE: nimble_port_run() is blocking; how to advertise constantly with own concurrent functionality?

Postby MicroController » Sat Jul 06, 2024 7:57 pm

I think I see the issue now:

Code: Select all

vTaskDelay(portTICK_PERIOD_MS * 2000);
in numberGenerator() should be

Code: Select all

vTaskDelay(2000 / portTICK_PERIOD_MS);
to get the 2000ms delay I believe you want..

Who is online

Users browsing this forum: No registered users and 95 guests