# BLE CSC Example Walkthrough ## Introduction This Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) profile is designed to provide real-time measurements of a cyclist's speed and cadence. It allows cycling-related devices, such as bike sensors or fitness trackers, to communicate this data wirelessly to a receiving device, such as a smartphone or cycling computer. ## Includes This example is located in the examples folder of the ESP-IDF under the [blecsc/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are: These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“blecsc_sens.h”` which expose the BLE APIs required to implement this example. * `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack. * `nimble_port_freertos.h`: Initializes and enables nimble host task. * `ble_hs`.h`: Defines the functionalities to handle the host event * `ble_svc_gap`.h`: Defines the macros for device name, and device appearance and declares the function to set them. * `blecsc_sens.h`: This file defines the Cycling Speed and Cadence configuration, Device Information configuration, CSC Measurement flags, CSC feature flags, Sensor location enum CSC simulation configuration, etc. ## Main Entry Point ```c int app_main(void) { int rc; /* Initialize NVS — it is used to store PHY calibration data */ 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()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); ret = nimble_port_init(); if (ret != ESP_OK) { ESP_LOGE(tag, "Failed to init nimble %d ", ret); return -1; } /* Initialize the NimBLE host configuration */ ble_hs_cfg.sync_cb = blecsc_on_sync; /* Initialize measurement and notification timer */ ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(), blecsc_measurement, NULL); rc = ble_npl_callout_reset(&blecsc_measure_timer, portTICK_PERIOD_MS * 100); assert(rc == 0); rc = gatt_svr_init(&csc_measurement_state); assert(rc == 0); /* Set the default device name */ rc = ble_svc_gap_device_name_set(device_name); assert(rc == 0); nimble_port_freertos_init(blecsc_host_task); return 0; } ``` The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations. ```c 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()); ret = nvs_flash_init(); } ESP_ERROR_CHECK( ret ); ``` ## BT Controller and Stack Initialization The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions: ```c esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&config_opts); ``` Next, the controller is enabled in BLE Mode. ```c ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); ``` The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode. There are four Bluetooth modes supported: 1. `ESP_BT_MODE_IDLE`: Bluetooth not running 2. `ESP_BT_MODE_BLE`: BLE mode 3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode 4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`: ```c esp_err_t esp_nimble_init(void) { #if !SOC_ESP_NIMBLE_CONTROLLER /* Initialize the function pointers for OS porting */ npl_freertos_funcs_init(); npl_freertos_mempool_init(); if(esp_nimble_hci_init() != ESP_OK) { ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n"); return ESP_FAIL; } /* Initialize default event queue */ ble_npl_eventq_init(&g_eventq_dflt); os_msys_init(); void ble_store_ram_init(void); /* XXX Need to have a template for store */ ble_store_ram_init(); #endif /* Initialize the host */ ble_hs_init(); return ESP_OK; } ``` The host is configured by setting up the callback that gets executed upon Stack-sync. ```c ble_hs_cfg.sync_cb = blecsc_on_sync; ``` ## Callout init and callout reset ```c ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(), blecsc_measurement, NULL); rc = ble_npl_callout_reset(&blecsc_measure_timer, portTICK_PERIOD_MS * 100); assert(rc == 0); ``` It initializes a timer for measuring and sending notifications. Let's break down the code step by step: 1. `ble_npl_callout_init(&blecsc_measure_timer, nimble_port_get_dflt_eventq(), blecsc_measurement, NULL);` - This line initializes a timer named `blecsc_measure_timer` using a function or library called `ble_npl_callout_init`. - `&blecsc_measure_timer` is a pointer to the timer structure that will be initialized. - `nimble_port_get_dflt_eventq()` is a function call that retrieves the default event queue for a NimBLE (a Bluetooth stack) port. This event queue is likely used for scheduling events and tasks related to Bluetooth communication. - `blecsc_measurement` is a callback that will be called when the timer expires. It is explained in following parts. - `NULL` is passed as the last argument, which typically represents user-specific data or context to be passed to the callback function. In this case, it's not used, so it's set to `NULL`. 2. `rc = ble_npl_callout_reset(&blecsc_measure_timer, portTICK_PERIOD_MS * 100);` - This line resets the `blecsc_measure_timer` timer with a new timeout value. - `&blecsc_measure_timer` is again a pointer to the timer structure to be reset. - `portTICK_PERIOD_MS * 100` sets the timer's new expiration time. It appears to be based on the `portTICK_PERIOD_MS` value multiplied by 100, suggesting a timeout of 100 milliseconds. - The return value of `ble_npl_callout_reset` is stored in `rc`, indicating the result of the timer reset operation. In summary, this code initializes a timer named `blecsc_measure_timer`, associates it with a specific event queue, and sets it to trigger the `blecsc_measurement` function after a delay of 100 milliseconds. If the timer reset operation is successful (indicated by `rc == 0`), the program continues; otherwise, it will halt if there's an error. This timer is used for periodic measurement and notification tasks in a BLECSC application. ## GATT SERVER INIT ```c rc = gatt_svr_init(); assert(rc == 0); ``` The gatt_svr_init function is called during the initialization phase of a BLE application to set up the GATT server and define the services and characteristics it supports. ## SETTING DEVICE NAME The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecsc_sensor' is passed as the default device name to this function. ```c rc = ble_svc_gap_device_name_set(device_name); ``` ## THREAD MODEL The main function creates a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`. ```c nimble_port_freertos_init(blecsc_host_task); ``` `esp_nimble_enable()` creates a task where the nimble host will run. Nimble stack runs in a separate thread with its own context to handle async operations from the controller and post HCI commands to the controller. # BLE CSC ADVERTISEMENT 1. CSC advertisement is used by many applications to start advertising from applications. The `blecsc` example provides this feature of advertisement. It uses the following function to start an advertisement. 2. `blecsc_advertise` start by creating the instances of structures `ble_gap_adv_params` and `ble_hs_adv_fields`. 3. Advertising parameters such as connecting modes, discoverable modes, advertising intervals, channel map advertising filter policy, and high duty cycle for directed advertising are defined in these structures. 4. `ble_hs_adv_fields` provides a structure to store advertisement data in a Bluetooth Low Energy (BLE) application. It contains various data members, such as flags (indicating advertisement type and other general information), advertising transmit power, device name, and 16-bit service UUIDs (such as alert notifications), etc. Below is the implementation to start the advertisement. ## Configuration of Advertising data(`ble_hs_adv_fields fields`) ### Setting advertising flags and transmitting power level ```c memset(&fields, 0, sizeof fields); fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; fields.tx_pwr_lvl_is_present = 1; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; ``` All data members of `fields` are initialized to 0 before setting up using the `memset()` function. Discoverability for upcoming advertisements is set to general. Advertisement is set to BLE supported only. Assigning the `tx_pwr_lvl` field to `BLE_HS_ADV_TX_PWR_LVL_AUTO` prompts the stack to automatically fill in the transmit power level field. ### Setting up the device name and appearance ```c fields.name = (uint8_t *)device_name; fields.name_len = strlen(device_name); fields.name_is_complete = 1; fields.appearance = ble_svc_gap_device_appearance(); fields.appearance_is_present = 1; ``` The app main function calls the API `ble_svc_gap_device_name_set("blecsc_sensor")` which takes the user-defined device name as a parameter. This API sets the device name to the characters array `ble_svc_gap_name` defined in the file [ble_svc_gap.c](../../../../../components/bt/host/nimble/nimble/nimble/host/services/gap/src/ble_svc_gap.c). The `ble_svc_gap_device_name()` API returns the same above-mentioned char array which was set from the main function. `ble_gap_adv_set_fields()` sets the following advertising data. This API internally depends on other APIs to set the data.APIs can be checked here [ble_hs_adv.c](../../../../../components/bt/host/nimble/nimble/nimble/host/src/ble_hs_adv.c) 1. Adv flags 2. 16-bit service class UUIDS 3. 32-bit service class UUIDS 4. 128-bit service class UUIDS 5. Local Name 6. Tx Power Level 7. Slave connection interval range 8. Service data - 16-bit UUID 9. Public target address 10. Appearance 11. Advertising interval 12. Service data - 32-bit UUID 13. Service data - 128-bit UUID 14. URI 15. Manufacturer-specific data ## Start Advertisement ```c 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; rc = ble_gap_adv_start(blecsc_addr_type, NULL, BLE_HS_FOREVER, &adv_params, blecsc_gap_event, NULL); if (rc != 0) { MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); return; } ``` Advertisement parameters are initialized to 0 using `memset`. Enables advertising with general discoverable mode and undirected connectable mode. The `ble_gap_adv_start()` function configures and starts the advertising procedure. The description of the `ble_gap_adv_start()` method is as follows: ```c Return Value : 0 on success, error code on failure ``` ```c Parameters 1. blecsc_addr_type:The type of address the stack should use for itself. Valid values are: - BLE_OWN_ADDR_PUBLIC - BLE_OWN_ADDR_RANDOM - BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT - BLE_OWN_ADDR_RPA_RANDOM_DEFAULT 2. direct_addr:The peer’s address for directed advertising. This parameter shall be non-NULL if directed advertising is being used. In this case, we have set it to NULL. 3. duration_ms: The duration of the advertisement procedure. On expiration, the procedure ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported. Units are milliseconds. Specify BLE_HS_FOREVER for no expiration. 4. adv_params: Additional arguments specifying the particulars of the advertising procedure. 5. cb: The callback to associate with this advertising procedure. If advertising ends, the event is reported through this callback. If advertising results in a connection, the connection inherits this callback as its event-reporting mechanism. The Callback, in this case, is `blecsc_gap_event`. 6. cb_arg: The optional argument to pass to the callback function. ``` ### BLECSC Simulate speed and cadance value. This function, named `blecsc_simulate_speed_and_cadence`, is related to simulating speed and cadence data in the context of cycling. It is used in a Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) application. Following is the break down what this function is doing step by step: 1. **Function Description:** This function updates simulated measurements for cycling speed and cadence. It calculates the time periods for wheel and crank revolutions based on the simulated speed (in kilometers per hour, kph) and cadence (in revolutions per minute, RPM). The time periods are expressed in 1/1024th of a second units. 2. **Constants:** There are some constants defined, such as `CSC_SIM_SPEED_KPH_MAX`, `CSC_SIM_SPEED_KPH_MIN`, `CSC_SIM_CRANK_RPM_MAX`, and `CSC_SIM_CRANK_RPM_MIN`, which set the maximum and minimum values for simulated speed and cadence. 3. **Simulated Speed and Cadence Updates:** The function increments the simulated cycling speed (`csc_sim_speed_kph`) and cadence (`csc_sim_crank_rpm`) by one. If these values reach their maximum limits, they are reset to their minimum values. 4. **Calculating Wheel and Crank Revolution Periods:** - **Wheel Revolution Period (`wheel_rev_period`):** If the simulated speed is greater than zero, it calculates the time it takes for one wheel revolution based on the circumference of the wheel and the simulated speed. It increments the cumulative wheel revolution counter and updates the last wheel event time by adding the wheel revolution period. - **Crank Revolution Period (`crank_rev_period`):** If the simulated cadence is greater than zero, it calculates the time it takes for one crank revolution based on the cadence. It increments the cumulative crank revolution counter and updates the last crank event time by adding the crank revolution period. Overall, this function is part of a simulated cycling environment, used to generate and update simulated speed and cadence measurements based on predefined parameters and equations. These simulated values can be used for testing and development of a cycling-related application, over a BLE connection. ### Mathematical Calculations ```c 60 * 1024 crank_dt = -------------- cadence[RPM] circumference[mm] * 1024 * 60 * 60 wheel_dt = ------------------------------------- 10^6 * speed [kph] ``` ### BLECSC Measurement This function appears to be part of a program that's related to Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) measurements. It's called `blecsc_measurement`, and it seems to be responsible for running the CSC measurement simulation and notifying the simulated measurements to a client. Let's break down what this function is doing point by point: 1. **Function Description:** This function is responsible for triggering the CSC measurement simulation and notifying the simulated measurements to a client over a BLE connection. 2. **`ble_npl_callout_reset` Call:** - `ble_npl_callout_reset` is a function call used to schedule an event to run periodically. In this case, it schedules the `blecsc_measure_timer` event to run every 10 milliseconds (`portTICK_PERIOD_MS * 10`). - The return code is stored in the `rc` variable, and an `assert` statement is used to check if the return code is zero (indicating successful scheduling). 3. **`blecsc_simulate_speed_and_cadence` Call:** - This function is called to simulate speed and cadence measurements. It updates the simulated values, as explained in the previous response. 4. **Notification to the Client:** - There is a condition check `if (notify_state)` which seems to determine whether to notify the client about the simulated measurements. - If `notify_state` is true (non-zero), it proceeds to notify the client about the CSC measurement. It likely uses the function `gatt_svr_chr_notify_csc_measurement` to send this notification. - The return code of the notification function call is stored in the `rc` variable. In summary, this function is part of a BLE CSC application and serves as a periodic task that simulates CSC measurements, schedules the next simulation, and notifies the client about these measurements if the `notify_state` condition is met. It includes error checking to ensure the success of scheduling and notification operations. ### BLE CSC GapEvent This function, `blecsc_gap_event`, appears to handle various events related to the Bluetooth Low Energy (BLE) connection in a Cycling Speed and Cadence (CSC) application. It contains a `switch` statement that handles different event types and takes appropriate actions for each event. Let's go through each case in this switch statement point by point: 1. **BLE_GAP_EVENT_CONNECT:** - This case handles events related to the establishment of a new connection or the failure of a connection attempt. - It logs whether the connection was successfully established or if it failed along with the status code. - If the connection attempt fails (`event->connect.status != 0`), it resumes advertising and sets the connection handle (`conn_handle`) to 0. - If the connection was successfully established, it sets `conn_handle` to the value of `event->connect.conn_handle`. 2. **BLE_GAP_EVENT_DISCONNECT:** - This case handles events related to a disconnection. It logs the reason for the disconnection. - It sets `conn_handle` to 0 and resumes advertising after a disconnection. 3. **BLE_GAP_EVENT_ADV_COMPLETE:** - This case handles events indicating that advertising has been completed. - It logs that the advertising is complete. 4. **BLE_GAP_EVENT_SUBSCRIBE:** - This case handles events related to subscriptions by a client to characteristics. It includes information about which attribute was subscribed and whether notifications or indications are enabled. - It logs the attribute handle and the current state of notification/indication for the subscribed attribute. - If the subscribed attribute is the CSC measurement attribute, it updates the `notify_state`. - If the subscribed attribute is the CSC control point attribute, it updates the `indicate_state` and sets it using the `gatt_svr_set_cp_indicate` function. 5. **BLE_GAP_EVENT_MTU:** - This case handles events related to the Maximum Transmission Unit (MTU) update. The MTU represents the maximum data packet size that can be sent over the connection. - It logs the connection handle and the updated MTU value. In summary, this function is a critical part of managing the BLE connection in a CSC application. It handles connection and disconnection events, tracks subscription states for specific attributes (such as CSC measurements and control points), and logs relevant information for debugging and monitoring purposes. Additionally, it resumes advertising when connections are terminated or when a connection attempt fails ## Conclusion - This tutorial explains the CSC measurement values i.e how it is calculated and communicated over ble protocol - It also commented on ble_csc advertising which is similar to peripheral advertisement . - Various gap events related to this csc service is explained here.