From 4116ef780e28ebb678fee190e871d58e2637c999 Mon Sep 17 00:00:00 2001 From: Sergio Semedi Date: Mon, 1 Dec 2025 00:42:14 +0100 Subject: [PATCH] init commit from esp-idf ble example --- CMakeLists.txt | 8 + README.md | 2 +- main/CMakeLists.txt | 5 + main/Kconfig.projbuild | 42 ++++++ main/idf_component.yml | 2 + main/include/common.h | 32 ++++ main/include/gap.h | 18 +++ main/include/gatt_svc.h | 18 +++ main/include/heart_rate.h | 15 ++ main/include/led.h | 19 +++ main/main.c | 128 ++++++++++++++++ main/src/gap.c | 300 +++++++++++++++++++++++++++++++++++++ main/src/gatt_svc.c | 264 ++++++++++++++++++++++++++++++++ main/src/heart_rate_mock.c | 11 ++ main/src/led.c | 79 ++++++++++ sdkconfig.defaults | 6 + sdkconfig.defaults.esp32 | 1 + 17 files changed, 949 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 main/CMakeLists.txt create mode 100644 main/Kconfig.projbuild create mode 100644 main/idf_component.yml create mode 100644 main/include/common.h create mode 100644 main/include/gap.h create mode 100644 main/include/gatt_svc.h create mode 100644 main/include/heart_rate.h create mode 100644 main/include/led.h create mode 100644 main/main.c create mode 100644 main/src/gap.c create mode 100644 main/src/gatt_svc.c create mode 100644 main/src/heart_rate_mock.c create mode 100644 main/src/led.c create mode 100644 sdkconfig.defaults create mode 100644 sdkconfig.defaults.esp32 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..69f6c1b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(nimble_gatt_server) diff --git a/README.md b/README.md index d2ab98a..00d191b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# nimble_gatt_temp_server +# ESP32 nimble_gatt_temp_server diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..7b976bf --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE srcs "main.c" "src/*.c") + +idf_component_register(SRCS "${srcs}" + PRIV_REQUIRES bt nvs_flash esp_driver_gpio + INCLUDE_DIRS "./include") diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..0035dc3 --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,42 @@ +menu "Example Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + choice BLINK_LED + prompt "Blink LED type" + default BLINK_LED_GPIO + help + Select the LED type. A normal level controlled LED or an addressable LED strip. + The default selection is based on the Espressif DevKit boards. + You can change the default selection according to your board. + + config BLINK_LED_GPIO + bool "GPIO" + config BLINK_LED_STRIP + bool "LED strip" + endchoice + + choice BLINK_LED_STRIP_BACKEND + depends on BLINK_LED_STRIP + prompt "LED strip backend peripheral" + default BLINK_LED_STRIP_BACKEND_RMT if SOC_RMT_SUPPORTED + default BLINK_LED_STRIP_BACKEND_SPI + help + Select the backend peripheral to drive the LED strip. + + config BLINK_LED_STRIP_BACKEND_RMT + depends on SOC_RMT_SUPPORTED + bool "RMT" + config BLINK_LED_STRIP_BACKEND_SPI + bool "SPI" + endchoice + + config BLINK_GPIO + int "Blink GPIO number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 8 + help + GPIO number (IOxx) to blink on and off the LED. + Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink. + +endmenu diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..8723a2e --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + espressif/led_strip: "^2.4.1" diff --git a/main/include/common.h b/main/include/common.h new file mode 100644 index 0000000..928a3df --- /dev/null +++ b/main/include/common.h @@ -0,0 +1,32 @@ +#ifndef COMMON_H +#define COMMON_H + +/* Includes */ +/* STD APIs */ +#include +#include +#include +#include + +/* ESP APIs */ +#include "esp_log.h" +#include "nvs_flash.h" +#include "sdkconfig.h" + +/* FreeRTOS APIs */ +#include +#include + +/* NimBLE stack APIs */ +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "host/util/util.h" +#include "nimble/ble.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" + +/* Defines */ +#define TAG "NimBLE_GATT_Server" +#define DEVICE_NAME "NimBLE_GATT" + +#endif // COMMON_H diff --git a/main/include/gap.h b/main/include/gap.h new file mode 100644 index 0000000..4f32782 --- /dev/null +++ b/main/include/gap.h @@ -0,0 +1,18 @@ +#ifndef GAP_SVC_H +#define GAP_SVC_H + +/* Includes */ +/* NimBLE GAP APIs */ +#include "host/ble_gap.h" +#include "services/gap/ble_svc_gap.h" + +/* Defines */ +#define BLE_GAP_APPEARANCE_GENERIC_TAG 0x0200 +#define BLE_GAP_URI_PREFIX_HTTPS 0x17 +#define BLE_GAP_LE_ROLE_PERIPHERAL 0x00 + +/* Public function declarations */ +void adv_init(void); +int gap_init(void); + +#endif // GAP_SVC_H diff --git a/main/include/gatt_svc.h b/main/include/gatt_svc.h new file mode 100644 index 0000000..46c877e --- /dev/null +++ b/main/include/gatt_svc.h @@ -0,0 +1,18 @@ +#ifndef GATT_SVR_H +#define GATT_SVR_H + +/* Includes */ +/* NimBLE GATT APIs */ +#include "host/ble_gatt.h" +#include "services/gatt/ble_svc_gatt.h" + +/* NimBLE GAP APIs */ +#include "host/ble_gap.h" + +/* Public function declarations */ +void send_heart_rate_indication(void); +void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg); +void gatt_svr_subscribe_cb(struct ble_gap_event *event); +int gatt_svc_init(void); + +#endif // GATT_SVR_H diff --git a/main/include/heart_rate.h b/main/include/heart_rate.h new file mode 100644 index 0000000..56fd21c --- /dev/null +++ b/main/include/heart_rate.h @@ -0,0 +1,15 @@ +#ifndef HEART_RATE_H +#define HEART_RATE_H + +/* Includes */ +/* ESP APIs */ +#include "esp_random.h" + +/* Defines */ +#define HEART_RATE_TASK_PERIOD (1000 / portTICK_PERIOD_MS) + +/* Public function declarations */ +uint8_t get_heart_rate(void); +void update_heart_rate(void); + +#endif // HEART_RATE_H diff --git a/main/include/led.h b/main/include/led.h new file mode 100644 index 0000000..d646c67 --- /dev/null +++ b/main/include/led.h @@ -0,0 +1,19 @@ +#ifndef LED_H +#define LED_H + +/* Includes */ +/* ESP APIs */ +#include "driver/gpio.h" +#include "led_strip.h" +#include "sdkconfig.h" + +/* Defines */ +#define BLINK_GPIO CONFIG_BLINK_GPIO + +/* Public function declarations */ +uint8_t get_led_state(void); +void led_on(void); +void led_off(void); +void led_init(void); + +#endif // LED_H diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..373ba4f --- /dev/null +++ b/main/main.c @@ -0,0 +1,128 @@ +/* Includes */ +#include "common.h" +#include "gap.h" +#include "gatt_svc.h" +#include "heart_rate.h" +#include "led.h" + +/* Library function declarations */ +void ble_store_config_init(void); + +/* Private function declarations */ +static void on_stack_reset(int reason); +static void on_stack_sync(void); +static void nimble_host_config_init(void); +static void nimble_host_task(void *param); + +/* Private functions */ +/* + * Stack event callback functions + * - on_stack_reset is called when host resets BLE stack due to errors + * - on_stack_sync is called when host has synced with controller + */ +static void on_stack_reset(int reason) { + /* On reset, print reset reason to console */ + ESP_LOGI(TAG, "nimble stack reset, reset reason: %d", reason); +} + +static void on_stack_sync(void) { + /* On stack sync, do advertising initialization */ + adv_init(); +} + +static void nimble_host_config_init(void) { + /* Set host callbacks */ + ble_hs_cfg.reset_cb = on_stack_reset; + ble_hs_cfg.sync_cb = on_stack_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + /* Store host configuration */ + ble_store_config_init(); +} + +static void nimble_host_task(void *param) { + /* Task entry log */ + ESP_LOGI(TAG, "nimble host task has been started!"); + + /* This function won't return until nimble_port_stop() is executed */ + nimble_port_run(); + + /* Clean up at exit */ + vTaskDelete(NULL); +} + +static void heart_rate_task(void *param) { + /* Task entry log */ + ESP_LOGI(TAG, "heart rate task has been started!"); + + /* Loop forever */ + while (1) { + /* Update heart rate value every 1 second */ + update_heart_rate(); + ESP_LOGI(TAG, "heart rate updated to %d", get_heart_rate()); + + /* Send heart rate indication if enabled */ + send_heart_rate_indication(); + + /* Sleep */ + vTaskDelay(HEART_RATE_TASK_PERIOD); + } + + /* Clean up at exit */ + vTaskDelete(NULL); +} + +void app_main(void) { + /* Local variables */ + int rc; + esp_err_t ret; + + /* LED initialization */ + led_init(); + + /* + * NVS flash initialization + * Dependency of BLE stack to store configurations + */ + 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(); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to initialize nvs flash, error code: %d ", ret); + return; + } + + /* NimBLE stack initialization */ + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to initialize nimble stack, error code: %d ", + ret); + return; + } + + /* GAP service initialization */ + rc = gap_init(); + if (rc != 0) { + ESP_LOGE(TAG, "failed to initialize GAP service, error code: %d", rc); + return; + } + + /* GATT server initialization */ + rc = gatt_svc_init(); + if (rc != 0) { + ESP_LOGE(TAG, "failed to initialize GATT server, error code: %d", rc); + return; + } + + /* NimBLE host configuration initialization */ + nimble_host_config_init(); + + /* Start NimBLE host task thread and return */ + xTaskCreate(nimble_host_task, "NimBLE Host", 4*1024, NULL, 5, NULL); + xTaskCreate(heart_rate_task, "Heart Rate", 4*1024, NULL, 5, NULL); + return; +} diff --git a/main/src/gap.c b/main/src/gap.c new file mode 100644 index 0000000..ca26bbe --- /dev/null +++ b/main/src/gap.c @@ -0,0 +1,300 @@ +#include "gap.h" +#include "common.h" +#include "gatt_svc.h" + +/* Private function declarations */ +inline static void format_addr(char *addr_str, uint8_t addr[]); +static void print_conn_desc(struct ble_gap_conn_desc *desc); +static void start_advertising(void); +static int gap_event_handler(struct ble_gap_event *event, void *arg); + +/* Private variables */ +static uint8_t own_addr_type; +static uint8_t addr_val[6] = {0}; +static uint8_t esp_uri[] = {BLE_GAP_URI_PREFIX_HTTPS, '/', '/', 'e', 's', 'p', 'r', 'e', 's', 's', 'i', 'f', '.', 'c', 'o', 'm'}; + +/* Private functions */ +inline static void format_addr(char *addr_str, uint8_t addr[]) { + sprintf(addr_str, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], + addr[2], addr[3], addr[4], addr[5]); +} + +static void print_conn_desc(struct ble_gap_conn_desc *desc) { + /* Local variables */ + char addr_str[18] = {0}; + + /* Connection handle */ + ESP_LOGI(TAG, "connection handle: %d", desc->conn_handle); + + /* Local ID address */ + format_addr(addr_str, desc->our_id_addr.val); + ESP_LOGI(TAG, "device id address: type=%d, value=%s", + desc->our_id_addr.type, addr_str); + + /* Peer ID address */ + format_addr(addr_str, desc->peer_id_addr.val); + ESP_LOGI(TAG, "peer id address: type=%d, value=%s", desc->peer_id_addr.type, + addr_str); + + /* Connection info */ + ESP_LOGI(TAG, + "conn_itvl=%d, conn_latency=%d, supervision_timeout=%d, " + "encrypted=%d, authenticated=%d, bonded=%d\n", + desc->conn_itvl, desc->conn_latency, desc->supervision_timeout, + desc->sec_state.encrypted, desc->sec_state.authenticated, + desc->sec_state.bonded); +} + +static void start_advertising(void) { + /* Local variables */ + int rc = 0; + const char *name; + struct ble_hs_adv_fields adv_fields = {0}; + struct ble_hs_adv_fields rsp_fields = {0}; + struct ble_gap_adv_params adv_params = {0}; + + /* Set advertising flags */ + adv_fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + + /* Set device name */ + name = ble_svc_gap_device_name(); + adv_fields.name = (uint8_t *)name; + adv_fields.name_len = strlen(name); + adv_fields.name_is_complete = 1; + + /* Set device tx power */ + adv_fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + adv_fields.tx_pwr_lvl_is_present = 1; + + /* Set device appearance */ + adv_fields.appearance = BLE_GAP_APPEARANCE_GENERIC_TAG; + adv_fields.appearance_is_present = 1; + + /* Set device LE role */ + adv_fields.le_role = BLE_GAP_LE_ROLE_PERIPHERAL; + adv_fields.le_role_is_present = 1; + + /* Set advertiement fields */ + rc = ble_gap_adv_set_fields(&adv_fields); + if (rc != 0) { + ESP_LOGE(TAG, "failed to set advertising data, error code: %d", rc); + return; + } + + /* Set device address */ + rsp_fields.device_addr = addr_val; + rsp_fields.device_addr_type = own_addr_type; + rsp_fields.device_addr_is_present = 1; + + /* Set URI */ + rsp_fields.uri = esp_uri; + rsp_fields.uri_len = sizeof(esp_uri); + + /* Set advertising interval */ + rsp_fields.adv_itvl = BLE_GAP_ADV_ITVL_MS(500); + rsp_fields.adv_itvl_is_present = 1; + + /* Set scan response fields */ + rc = ble_gap_adv_rsp_set_fields(&rsp_fields); + if (rc != 0) { + ESP_LOGE(TAG, "failed to set scan response data, error code: %d", rc); + return; + } + + /* Set non-connetable and general discoverable mode to be a beacon */ + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + + /* Set advertising interval */ + adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(500); + adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(510); + + /* Start advertising */ + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, + gap_event_handler, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "failed to start advertising, error code: %d", rc); + return; + } + ESP_LOGI(TAG, "advertising started!"); +} + +/* + * NimBLE applies an event-driven model to keep GAP service going + * gap_event_handler is a callback function registered when calling + * ble_gap_adv_start API and called when a GAP event arrives + */ +static int gap_event_handler(struct ble_gap_event *event, void *arg) { + /* Local variables */ + int rc = 0; + struct ble_gap_conn_desc desc; + + /* Handle different GAP event */ + switch (event->type) { + + /* Connect event */ + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGI(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + + /* Connection succeeded */ + if (event->connect.status == 0) { + /* Check connection handle */ + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + if (rc != 0) { + ESP_LOGE(TAG, + "failed to find connection by handle, error code: %d", + rc); + return rc; + } + + /* Print connection descriptor */ + print_conn_desc(&desc); + + /* Try to update connection parameters */ + struct ble_gap_upd_params params = {.itvl_min = desc.conn_itvl, + .itvl_max = desc.conn_itvl, + .latency = 3, + .supervision_timeout = + desc.supervision_timeout}; + rc = ble_gap_update_params(event->connect.conn_handle, ¶ms); + if (rc != 0) { + ESP_LOGE( + TAG, + "failed to update connection parameters, error code: %d", + rc); + return rc; + } + } + /* Connection failed, restart advertising */ + else { + start_advertising(); + } + return rc; + + /* Disconnect event */ + case BLE_GAP_EVENT_DISCONNECT: + /* A connection was terminated, print connection descriptor */ + ESP_LOGI(TAG, "disconnected from peer; reason=%d", + event->disconnect.reason); + + /* Restart advertising */ + start_advertising(); + return rc; + + /* Connection parameters update event */ + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + ESP_LOGI(TAG, "connection updated; status=%d", + event->conn_update.status); + + /* Print connection descriptor */ + rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); + if (rc != 0) { + ESP_LOGE(TAG, "failed to find connection by handle, error code: %d", + rc); + return rc; + } + print_conn_desc(&desc); + return rc; + + /* Advertising complete event */ + case BLE_GAP_EVENT_ADV_COMPLETE: + /* Advertising completed, restart advertising */ + ESP_LOGI(TAG, "advertise complete; reason=%d", + event->adv_complete.reason); + start_advertising(); + return rc; + + /* Notification sent event */ + case BLE_GAP_EVENT_NOTIFY_TX: + if ((event->notify_tx.status != 0) && + (event->notify_tx.status != BLE_HS_EDONE)) { + /* Print notification info on error */ + ESP_LOGI(TAG, + "notify event; conn_handle=%d attr_handle=%d " + "status=%d is_indication=%d", + event->notify_tx.conn_handle, event->notify_tx.attr_handle, + event->notify_tx.status, event->notify_tx.indication); + } + return rc; + + /* Subscribe event */ + case BLE_GAP_EVENT_SUBSCRIBE: + /* Print subscription info to log */ + ESP_LOGI(TAG, + "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d", + event->subscribe.conn_handle, event->subscribe.attr_handle, + event->subscribe.reason, event->subscribe.prev_notify, + event->subscribe.cur_notify, event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + + /* GATT subscribe event callback */ + gatt_svr_subscribe_cb(event); + return rc; + + /* MTU update event */ + case BLE_GAP_EVENT_MTU: + /* Print MTU update info to log */ + ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d", + event->mtu.conn_handle, event->mtu.channel_id, + event->mtu.value); + return rc; + } + + return rc; +} + + +/* Public functions */ +void adv_init(void) { + /* Local variables */ + int rc = 0; + char addr_str[18] = {0}; + + /* Make sure we have proper BT identity address set (random preferred) */ + rc = ble_hs_util_ensure_addr(0); + if (rc != 0) { + ESP_LOGE(TAG, "device does not have any available bt address!"); + return; + } + + /* Figure out BT address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "failed to infer address type, error code: %d", rc); + return; + } + + /* Printing ADDR */ + rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "failed to copy device address, error code: %d", rc); + return; + } + format_addr(addr_str, addr_val); + ESP_LOGI(TAG, "device address: %s", addr_str); + + /* Start advertising. */ + start_advertising(); +} + +int gap_init(void) { + /* Local variables */ + int rc = 0; + + /* Call NimBLE GAP initialization API */ + ble_svc_gap_init(); + + /* Set GAP device name */ + rc = ble_svc_gap_device_name_set(DEVICE_NAME); + if (rc != 0) { + ESP_LOGE(TAG, "failed to set device name to %s, error code: %d", + DEVICE_NAME, rc); + return rc; + } + return rc; +} diff --git a/main/src/gatt_svc.c b/main/src/gatt_svc.c new file mode 100644 index 0000000..b89431a --- /dev/null +++ b/main/src/gatt_svc.c @@ -0,0 +1,264 @@ +/* Includes */ +#include "gatt_svc.h" +#include "common.h" +#include "heart_rate.h" +#include "led.h" + +/* Private function declarations */ +static int heart_rate_chr_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); +static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +/* Private variables */ +/* Heart rate service */ +static const ble_uuid16_t heart_rate_svc_uuid = BLE_UUID16_INIT(0x180D); + +static uint8_t heart_rate_chr_val[2] = {0}; +static uint16_t heart_rate_chr_val_handle; +static const ble_uuid16_t heart_rate_chr_uuid = BLE_UUID16_INIT(0x2A37); + +static uint16_t heart_rate_chr_conn_handle = 0; +static bool heart_rate_chr_conn_handle_inited = false; +static bool heart_rate_ind_status = false; + +/* Automation IO service */ +static const ble_uuid16_t auto_io_svc_uuid = BLE_UUID16_INIT(0x1815); +static uint16_t led_chr_val_handle; +static const ble_uuid128_t led_chr_uuid = + BLE_UUID128_INIT(0x23, 0xd1, 0xbc, 0xea, 0x5f, 0x78, 0x23, 0x15, 0xde, 0xef, + 0x12, 0x12, 0x25, 0x15, 0x00, 0x00); + +/* GATT services table */ +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + /* Heart rate service */ + {.type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &heart_rate_svc_uuid.u, + .characteristics = + (struct ble_gatt_chr_def[]){ + {/* Heart rate characteristic */ + .uuid = &heart_rate_chr_uuid.u, + .access_cb = heart_rate_chr_access, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_INDICATE, + .val_handle = &heart_rate_chr_val_handle}, + { + 0, /* No more characteristics in this service. */ + }}}, + + /* Automation IO service */ + { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &auto_io_svc_uuid.u, + .characteristics = + (struct ble_gatt_chr_def[]){/* LED characteristic */ + {.uuid = &led_chr_uuid.u, + .access_cb = led_chr_access, + .flags = BLE_GATT_CHR_F_WRITE, + .val_handle = &led_chr_val_handle}, + {0}}, + }, + + { + 0, /* No more services. */ + }, +}; + +/* Private functions */ +static int heart_rate_chr_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) { + /* Local variables */ + int rc; + + /* Handle access events */ + /* Note: Heart rate characteristic is read only */ + switch (ctxt->op) { + + /* Read characteristic event */ + case BLE_GATT_ACCESS_OP_READ_CHR: + /* Verify connection handle */ + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + ESP_LOGI(TAG, "characteristic read; conn_handle=%d attr_handle=%d", + conn_handle, attr_handle); + } else { + ESP_LOGI(TAG, "characteristic read by nimble stack; attr_handle=%d", + attr_handle); + } + + /* Verify attribute handle */ + if (attr_handle == heart_rate_chr_val_handle) { + /* Update access buffer value */ + heart_rate_chr_val[1] = get_heart_rate(); + rc = os_mbuf_append(ctxt->om, &heart_rate_chr_val, + sizeof(heart_rate_chr_val)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + goto error; + + /* Unknown event */ + default: + goto error; + } + +error: + ESP_LOGE( + TAG, + "unexpected access operation to heart rate characteristic, opcode: %d", + ctxt->op); + return BLE_ATT_ERR_UNLIKELY; +} + +static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) { + /* Local variables */ + int rc; + + /* Handle access events */ + /* Note: LED characteristic is write only */ + switch (ctxt->op) { + + /* Write characteristic event */ + case BLE_GATT_ACCESS_OP_WRITE_CHR: + /* Verify connection handle */ + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + ESP_LOGI(TAG, "characteristic write; conn_handle=%d attr_handle=%d", + conn_handle, attr_handle); + } else { + ESP_LOGI(TAG, + "characteristic write by nimble stack; attr_handle=%d", + attr_handle); + } + + /* Verify attribute handle */ + if (attr_handle == led_chr_val_handle) { + /* Verify access buffer length */ + if (ctxt->om->om_len == 1) { + /* Turn the LED on or off according to the operation bit */ + if (ctxt->om->om_data[0]) { + led_on(); + ESP_LOGI(TAG, "led turned on!"); + } else { + led_off(); + ESP_LOGI(TAG, "led turned off!"); + } + } else { + goto error; + } + return rc; + } + goto error; + + /* Unknown event */ + default: + goto error; + } + +error: + ESP_LOGE(TAG, + "unexpected access operation to led characteristic, opcode: %d", + ctxt->op); + return BLE_ATT_ERR_UNLIKELY; +} + +/* Public functions */ +void send_heart_rate_indication(void) { + if (heart_rate_ind_status && heart_rate_chr_conn_handle_inited) { + ble_gatts_indicate(heart_rate_chr_conn_handle, + heart_rate_chr_val_handle); + ESP_LOGI(TAG, "heart rate indication sent!"); + } +} + +/* + * Handle GATT attribute register events + * - Service register event + * - Characteristic register event + * - Descriptor register event + */ +void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { + /* Local variables */ + char buf[BLE_UUID_STR_LEN]; + + /* Handle GATT attributes register events */ + switch (ctxt->op) { + + /* Service register event */ + case BLE_GATT_REGISTER_OP_SVC: + ESP_LOGD(TAG, "registered service %s with handle=%d", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + break; + + /* Characteristic register event */ + case BLE_GATT_REGISTER_OP_CHR: + ESP_LOGD(TAG, + "registering characteristic %s with " + "def_handle=%d val_handle=%d", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, ctxt->chr.val_handle); + break; + + /* Descriptor register event */ + case BLE_GATT_REGISTER_OP_DSC: + ESP_LOGD(TAG, "registering descriptor %s with handle=%d", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + break; + + /* Unknown event */ + default: + assert(0); + break; + } +} + +/* + * GATT server subscribe event callback + * 1. Update heart rate subscription status + */ + +void gatt_svr_subscribe_cb(struct ble_gap_event *event) { + /* Check connection handle */ + if (event->subscribe.conn_handle != BLE_HS_CONN_HANDLE_NONE) { + ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d", + event->subscribe.conn_handle, event->subscribe.attr_handle); + } else { + ESP_LOGI(TAG, "subscribe by nimble stack; attr_handle=%d", + event->subscribe.attr_handle); + } + + /* Check attribute handle */ + if (event->subscribe.attr_handle == heart_rate_chr_val_handle) { + /* Update heart rate subscription status */ + heart_rate_chr_conn_handle = event->subscribe.conn_handle; + heart_rate_chr_conn_handle_inited = true; + heart_rate_ind_status = event->subscribe.cur_indicate; + } +} + +/* + * GATT server initialization + * 1. Initialize GATT service + * 2. Update NimBLE host GATT services counter + * 3. Add GATT services to server + */ +int gatt_svc_init(void) { + /* Local variables */ + int rc; + + /* 1. GATT service initialization */ + ble_svc_gatt_init(); + + /* 2. Update GATT services counter */ + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + /* 3. Add GATT services */ + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + return 0; +} diff --git a/main/src/heart_rate_mock.c b/main/src/heart_rate_mock.c new file mode 100644 index 0000000..424d84f --- /dev/null +++ b/main/src/heart_rate_mock.c @@ -0,0 +1,11 @@ +/* Includes */ +#include "common.h" +#include "heart_rate.h" + +/* Private variables */ +static uint8_t heart_rate; + +/* Public functions */ +uint8_t get_heart_rate(void) { return heart_rate; } + +void update_heart_rate(void) { heart_rate = 60 + (uint8_t)(esp_random() % 21); } diff --git a/main/src/led.c b/main/src/led.c new file mode 100644 index 0000000..5275740 --- /dev/null +++ b/main/src/led.c @@ -0,0 +1,79 @@ +/* Includes */ +#include "led.h" +#include "common.h" + +/* Private variables */ +static uint8_t led_state; + +#ifdef CONFIG_BLINK_LED_STRIP +static led_strip_handle_t led_strip; +#endif + +/* Public functions */ +uint8_t get_led_state(void) { return led_state; } + +#ifdef CONFIG_BLINK_LED_STRIP + +void led_on(void) { + /* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */ + led_strip_set_pixel(led_strip, 0, 16, 16, 16); + + /* Refresh the strip to send data */ + led_strip_refresh(led_strip); + + /* Update LED state */ + led_state = true; +} + +void led_off(void) { + /* Set all LED off to clear all pixels */ + led_strip_clear(led_strip); + + /* Update LED state */ + led_state = false; +} + +void led_init(void) { + ESP_LOGI(TAG, "example configured to blink addressable led!"); + /* LED strip initialization with the GPIO and pixels number*/ + led_strip_config_t strip_config = { + .strip_gpio_num = CONFIG_BLINK_GPIO, + .max_leds = 1, // at least one LED on board + }; +#if CONFIG_BLINK_LED_STRIP_BACKEND_RMT + led_strip_rmt_config_t rmt_config = { + .resolution_hz = 10 * 1000 * 1000, // 10MHz + .flags.with_dma = false, + }; + ESP_ERROR_CHECK( + led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); +#elif CONFIG_BLINK_LED_STRIP_BACKEND_SPI + led_strip_spi_config_t spi_config = { + .spi_bus = SPI2_HOST, + .flags.with_dma = true, + }; + ESP_ERROR_CHECK( + led_strip_new_spi_device(&strip_config, &spi_config, &led_strip)); +#else +#error "unsupported LED strip backend" +#endif + /* Set all LED off to clear all pixels */ + led_off(); +} + +#elif CONFIG_BLINK_LED_GPIO + +void led_on(void) { gpio_set_level(CONFIG_BLINK_GPIO, true); } + +void led_off(void) { gpio_set_level(CONFIG_BLINK_GPIO, false); } + +void led_init(void) { + ESP_LOGI(TAG, "example configured to blink gpio led!"); + gpio_reset_pin(CONFIG_BLINK_GPIO); + /* Set the GPIO as a push/pull output */ + gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); +} + +#else +#error "unsupported LED type" +#endif diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..551f506 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=n + +CONFIG_BLINK_LED_GPIO=y +CONFIG_BLINK_GPIO=8 diff --git a/sdkconfig.defaults.esp32 b/sdkconfig.defaults.esp32 new file mode 100644 index 0000000..263ec93 --- /dev/null +++ b/sdkconfig.defaults.esp32 @@ -0,0 +1 @@ +CONFIG_BLINK_GPIO=5