RTOS & BLE: nimble_port_run() is blocking; how to advertise constantly with own concurrent functionality?
Posted: 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.
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.
- #include <stdio.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 "sdkconfig.h"
- #include "esp_random.h"
- int NUM_FOR_BLE = 0;
- char *TAG = "BLE-Server";
- uint8_t ble_addr_type;
- void ble_app_advertise(void);
- // Read data from ESP32 defined as server
- static int device_read(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
- {
- char data_str[10];
- sprintf(data_str, "val: %d", NUM_FOR_BLE);
- os_mbuf_append(ctxt->om, data_str, strlen(data_str));
- return 0;
- }
- // 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
- },
- {0}
- }
- },
- {0}
- };
- // BLE event handling
- static int ble_gap_event(struct ble_gap_event *event, void *arg)
- {
- switch (event->type)
- {
- // Advertise if connected
- case BLE_GAP_EVENT_CONNECT:
- ESP_LOGI("GAP", "BLE GAP EVENT CONNECT %s", event->connect.status == 0 ? "OK!" : "FAILED!");
- if (event->connect.status != 0)
- {
- ble_app_advertise();
- }
- break;
- // Advertise again after completion of the event
- case BLE_GAP_EVENT_DISCONNECT:
- ESP_LOGI("GAP", "BLE GAP EVENT DISCONNECTED");
- break;
- case BLE_GAP_EVENT_ADV_COMPLETE:
- ESP_LOGI("GAP", "BLE GAP EVENT");
- 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;
- 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; // connectable or non-connectable
- adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable
- 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); // Determines the best address type automatically
- ble_app_advertise(); // Define the BLE connection
- }
- // The infinite task
- void host_task(void *param)
- {
- nimble_port_run(); // This function will return only when nimble_port_stop() is executed
- }
- TaskHandle_t numGenHandler = NULL;
- void numberGenerator(void* params);
- void numberGenerator(void* params)
- {
- while (true) {
- vTaskDelay(portTICK_PERIOD_MS * 2000);
- uint32_t rNum = esp_random();
- NUM_FOR_BLE = (rNum % 10) + 1;
- fprintf(stdout, "num: %d\n", NUM_FOR_BLE);
- }
- }
- void app_main()
- {
- nvs_flash_init(); // Initialize NVS flash using
- nimble_port_init(); // Initialize the host stack
- ble_svc_gap_device_name_set("BLETest"); // Initialize NimBLE configuration - server name
- ble_svc_gap_init(); // Initialize NimBLE configuration - gap service
- ble_svc_gatt_init(); // Initialize NimBLE configuration - gatt service
- ble_gatts_count_cfg(gatt_svcs); // Initialize NimBLE configuration - config gatt services
- ble_gatts_add_svcs(gatt_svcs); // Initialize NimBLE configuration - queues gatt services.
- ble_hs_cfg.sync_cb = ble_app_on_sync; // Initialize application
- nimble_port_freertos_init(host_task); // Run the thread
- xTaskCreate(&numberGenerator, "main Task", 4096, NULL, 2, &numGenHandler);
- }