avatar

松果工作室

欢迎光临

  • 首页
  • ESP
  • LVGL
  • CubeMX
  • freeRTOS
  • 快速笔记
  • 个人收藏
  • 我的服务
  • 考察日志
Home nimble
文章

nimble

Posted 2026-06-14 Updated 2026-06- 14
By YCP
97~125 min read

下面用你这份 wls_gatts_nimble.c(乐鑫 bleprph + 自定义 GATT)当主线,把 BLE 和代码里涉及的大部分知识串起来。按「先懂概念 → 再看代码怎么走」读即可。

一、BLE 是什么:和你的代码对应关系

1.1 两种角色

角色英文你的设备
从机 / 外设Peripheral✅ CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y
主机 / 中心Central❌ 未开(手机当 Central)

ESP32 广播、等手机连、提供 GATT 服务 → 典型 Peripheral。

1.2 协议栈分层(简化)

手机 App
   ↕  GATT(读/写/通知)
   ↕  ATT(属性表 handle)
   ↕  L2CAP / SMP(安全,你关了)
   ↕  Link Layer(连接、广播)
   ↕  Controller(芯片蓝牙射频)

代码里你主要打交道的是:

  • GAP:广播、连接、断开 → bleprph_gap_event、bleprph_advertise
  • GATT Server:服务/特征/读写 → gatt_svr_svcs、gatt_svc_access

二、NimBLE 在 ESP-IDF 里怎么跑

2.1 两个世界

部分做什么代码
Controller射频、链路层nimble_port_init() 里一起起来
HostGAP/GATT/ATT 逻辑bleprph_host_task → nimble_port_run()

Host 在 单独 FreeRTOS 任务里跑,所以 BLE 是 回调驱动,不是你在 main 里写 while 读数据。

2.2 唯一入口:wls_gatts_nimble_init()

void wls_gatts_nimble_init(void) {
    nimble_port_init();
    ble_hs_cfg.sync_cb = bleprph_on_sync;
    ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
    ...
    ble_gatts_count_cfg(gatt_svr_svcs);
    ble_gatts_add_svcs(gatt_svr_svcs);
    ble_svc_gap_device_name_set("ESP666");
    ble_store_config_init();
    nimble_port_freertos_init(bleprph_host_task);
}

记住顺序:

  1. 初始化栈
  2. 配回调
  3. 注册 GATT 表
  4. 设设备名
  5. 启动 Host 任务

广播不在 init 里开,要等 sync_cb。


三、GAP:广播与连接

3.1 广播(Advertising)

目的:让手机 扫到你,知道「有个叫 ESP666 的设备可以连」。

bleprph_advertise() 干两件事:

① 广播包里放什么(ble_hs_adv_fields)

    fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
    fields.tx_pwr_lvl_is_present = 1;
    fields.name = ble_svc_gap_device_name();  // "ESP666"
    fields.mfg_data = (uint8_t[]) {0x01, 0x02};  // 厂商自定义 2 字节
字段含义
DISC_GEN通用可发现
BREDR_UNSUP我只支持 BLE,不是经典蓝牙
name扫描列表里显示的名字
mfg_data厂商数据,自定义协议常用

② 怎么播(ble_gap_adv_params)

    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;  // 可连接、非定向
    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
    ble_gap_adv_start(..., bleprph_gap_event, NULL);

ble_gap_adv_start 最后一个参数:之后所有 GAP 事件进 bleprph_gap_event。

3.2 何时开始广播:bleprph_on_sync

Controller 和 Host 同步完成后才安全使用蓝牙:

static void bleprph_on_sync(void) {
    ble_hs_id_infer_auto(0, &own_addr_type);  // 公网 MAC 还是随机地址
    ble_hs_id_copy_addr(...);                   // 打印本机 BLE 地址
    bleprph_advertise();                        // 开始广播
}

3.3 连接生命周期:bleprph_gap_event

这是 GAP 状态机,只需先掌握 3 个事件:

bleprph_advertise()
       ↓
  [广播中]
       ↓
BLE_GAP_EVENT_CONNECT ──成功──→ [已连接,手机可 GATT 读写]
       │ fail
       └──→ 再 advertise

BLE_GAP_EVENT_DISCONNECT → 再 advertise
        case BLE_GAP_EVENT_CONNECT:
            if (event->connect.status != 0) {
                bleprph_advertise();  // 连接失败,继续播
            }
        case BLE_GAP_EVENT_DISCONNECT:
            bleprph_advertise();      // 断开,继续播

其它 case 先当「扩展知识」:

事件作用
CONN_UPDATE连接参数更新(间隔、latency)
MTU单次 GATT 最大 payload 变大(你 sdkconfig MTU=256)
SUBSCRIBE手机订阅/取消 Notify
NOTIFY_TXNotify 是否发成功
ENC_CHANGE加密状态变化(你未开安全)
PASSKEY_ACTION配对 PIN(你 SECURITY_ENABLE=n,一般不会进)

3.4 连接描述符 ble_gap_conn_desc

bleprph_print_conn_desc 打印:handle、本机/对端地址、连接间隔、是否加密/绑定等。
conn_handle:后面 Notify、GATT 操作都要带它标识「哪条连接」。


四、GATT:服务、特征、描述符

4.1 层级(必背)

Service(服务)       UUID: gatt_svr_svc_uuid
  └── Characteristic(特征)
        ├── Value(值)     1 字节: gatt_svr_chr_val  ← 主数据
        └── Descriptor(描述符) gatt_svr_dsc_uuid     ← 附加属性
  • Service:逻辑分组(「配网服务」「电池服务」),不能被手机直接 Write。
  • Characteristic Value:主数据(SSID、传感器值、命令)。
  • Descriptor:元数据/控制;最常见 CCCD(0x2902) 用来 开/关 Notify。

4.2 UUID

static const ble_uuid128_t gatt_svr_svc_uuid = BLE_UUID128_INIT(...);
static const ble_uuid128_t gatt_svr_chr_uuid = BLE_UUID128_INIT(...);
static const ble_uuid128_t gatt_svr_dsc_uuid = BLE_UUID128_INIT(...);
类型长度例子
16-bit 标准 UUID2 字节0x180F 电池服务
128-bit 自定义16 字节你代码里全是 128-bit

蓝牙规定:自定义 UUID 常写成 0000xxxx-0000-1000-8000-00805F9B34FB 形式;乐鑫示例用全自定义 128-bit。

4.3 特征「属性 flags」

        .flags = BLE_GATT_CHR_F_READ |
                 BLE_GATT_CHR_F_WRITE |
                 BLE_GATT_CHR_F_NOTIFY |
                 BLE_GATT_CHR_F_INDICATE,
Flag手机能做什么
READ读特征值
WRITE写特征值
NOTIFY订阅后,设备 主动推 数据(无确认)
INDICATE同 Notify,但手机要 确认 收到

你工程 未开加密(CONFIG_EXAMPLE_ENCRYPTION=0),所以没有 READ_ENC / WRITE_ENC。

4.4 Handle(句柄)

注册服务时 gatt_svr_register_cb 会打印:

    case BLE_GATT_REGISTER_OP_CHR:
        ... val_handle=...

ATT 协议里每个属性有数字 handle。
gatt_svr_chr_val_handle 保存 特征值的 handle,读写分支里用来判断「是不是这个特征」:

            if (attr_handle == gatt_svr_chr_val_handle) {

五、GATT 注册流程(静态服务表)

NimBLE 用 C 结构体数组 描述 GATT,不是运行时 JSON:

static const struct ble_gatt_svc_def gatt_svr_svcs[] = { ... {0}, {0} };

注册两步:

    ble_gatts_count_cfg(gatt_svr_svcs);   // 先数需要多少资源
    ble_gatts_add_svcs(gatt_svr_svcs);    // 再真正加入协议栈

注册过程中触发 gatt_svr_register_cb,只打 log,方便 debug handle/UUID。


六、读写:access_cb 与 gatt_svc_access

6.1 谁有回调?

  • Characteristic → .access_cb = gatt_svc_access
  • Descriptor → 同上(示例里自定义描述符也要读)

Service 没有 access_cb。

6.2 四种操作 ctxt->op

    switch (ctxt->op) {
        case BLE_GATT_ACCESS_OP_READ_CHR:   // 读特征值
        case BLE_GATT_ACCESS_OP_WRITE_CHR:  // 写特征值
        case BLE_GATT_ACCESS_OP_READ_DSC:   // 读描述符
        case BLE_GATT_ACCESS_OP_WRITE_DSC:  // 写描述符(示例未实现)

手机每一次 Read/Write ATT 请求 → 进一次 gatt_svc_access。

6.3 读特征:往 mbuf 里塞数据

                rc = os_mbuf_append(ctxt->om,
                                    &gatt_svr_chr_val,
                                    sizeof(gatt_svr_chr_val));
  • os_mbuf:NimBLE 的链式 buffer,响应用 ctxt->om 装数据回给手机。

6.4 写特征:gatt_svr_write

                rc = gatt_svr_write(ctxt->om, 1, 1, &gatt_svr_chr_val, NULL);
                ble_gatts_chr_updated(attr_handle);
  1. gatt_svr_write:从 ctxt->om 拷到 gatt_svr_chr_val,并校验长度必须是 1 字节
  2. ble_gatts_chr_updated:若手机订阅了 Notify/Indicate,推送给订阅者

6.5 Notify 完整链路

手机写 CCCD(订阅)     → GAP: BLE_GAP_EVENT_SUBSCRIBE
手机写特征值            → gatt_svc_access WRITE
你调用 ble_gatts_chr_updated → 栈发 Notify
                      → GAP: BLE_GAP_EVENT_NOTIFY_TX

产品里要主动发数据,常用 ble_gatts_notify_custom(conn_handle, attr_handle, om),不一定每次都要 chr_updated。


七、安全(代码里有,你工程里关着)

头文件里把示例开关都关掉了:

#define CONFIG_EXAMPLE_BONDING              0
#define CONFIG_EXAMPLE_ENCRYPTION           0
...

sdkconfig:CONFIG_BT_NIMBLE_SECURITY_ENABLE=n

所以 PASSKEY_ACTION 大段代码不会跑,但值得知道概念:

概念含义
配对 Pairing第一次连接建立密钥
绑定 Bonding密钥存 NVS,下次自动连
SMPSecurity Manager,管配对
MITM / SC防中间人 / 安全连接(LE SC)
ble_store_config_init() + ble_store_util_status_rr:为 绑定存密钥 准备;你未开 bonding,主要是模板保留。

八、地址类型(代码里涉及)

    rc = ble_hs_id_infer_auto(0, &own_addr_type);
类型说明
Public固定厂商 MAC
Random随机地址(隐私)

你 CONFIG_EXAMPLE_RANDOM_ADDR=0 → 用 public。手机扫描到的 MAC 即 print_addr_4 打印的值(小端显示)。


九、内存与配置(和 RAM 有关)

sdkconfig 里和本文件相关的:

配置你的值含义
MAX_CONNECTIONS1同时只连 1 个手机
ATT_PREFERRED_MTU256单次 GATT 最多传 ~253 字节有效载荷
ACL_SIZE255链路层单包大小
HOST_TASK_STACK4096Host 任务栈

MTU 协商成功后,Write 可以一次传 比 1 字节多得多 的数据(配网 JSON 要利用 MTU,buffer 要开大)。


十、整段故事:从开机到手机写 1 字节

product_init()
  → wls_gatts_nimble_init()
      → 注册 GATT 服务表
      → 启动 bleprph_host_task

Host 任务:
  → bleprph_on_sync()
      → bleprph_advertise()          // 开始广播 "ESP666"

手机扫描 → 连接:
  → BLE_GAP_EVENT_CONNECT

手机 GATT Discover → 找到你的 Service/Characteristic

手机 Read 特征:
  → gatt_svc_access(READ_CHR)
  → 返回 gatt_svr_chr_val

手机 Write 0x42:
  → gatt_svc_access(WRITE_CHR)
  → gatt_svr_write → gatt_svr_chr_val = 0x42
  → ble_gatts_chr_updated → Notify 给已订阅的手机

手机断开:
  → BLE_GAP_EVENT_DISCONNECT
  → bleprph_advertise()            // 继续可被搜到

十一、做产品时怎么「删繁就简」

保留可删/忽略
wls_gatts_nimble_init 主线Extended Adv、EATT、Power Control
bleprph_on_sync + bleprph_advertisePASSKEY_ACTION 整段
bleprph_gap_event 的 CONNECT/DISCONNECT自定义 Descriptor 0x2222
gatt_svr_svcs + gatt_svc_accessbleprph_print_conn_desc 可精简
WRITE 里接你的业务(wls_event_post)1 字节 demo 变量

配网典型 GATT 设计:

  • 1 个 Service
  • 1 个 Write 特征(手机发 WiFi 配置)
  • 1 个 Notify 特征(设备回状态)
  • 不需要自定义 Descriptor

十二、和 WiFi/MQTT 的关系

当前 BLE 没有 wls_event_post,只改 gatt_svr_chr_val。
要做 BLE 配网:在 WRITE_CHR 里解析 JSON/二进制 → post 到 wls_event_handler → 调 start_sta() 等。

BLE 与 WiFi 共存(你已在用):注意 RAM 和射频 coexist,与 GATT 代码逻辑无直接冲突。


十三、自测清单(检验是否真懂)

  1. Peripheral 和 Central 谁是你、谁是手机?
  2. 广播在哪个回调里才开始?
  3. Service / Characteristic / Descriptor 各干什么?
  4. access_cb 在 READ 和 WRITE 时分别要做什么?
  5. gatt_svr_write 解决什么问题?
  6. Notify 需要手机先做什么?(订阅 CCCD)
  7. 断开连接后为什么要再 bleprph_advertise()?

最后一句话: 这份代码 = GAP 管「能不能连」+ GATT 管「连上后读写什么」;NimBLE 用 init 注册表 + sync 开广播 + gap_event 管连接 + gatt_svc_access 管数据。把这条链吃透,再读文件里 #if CONFIG_EXAMPLE_* 分支就不会迷路。

#include "wls_gatts_nimble.h"

#include "esp_log.h"
#include "nvs_flash.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"

static const char *TAG = "wls_gatts_nimble";

extern void ble_store_config_init(void);

static int bleprph_gap_event(struct ble_gap_event *event, void *arg);

static int gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);

void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);

#if CONFIG_EXAMPLE_EXTENDED_ADV
static uint8_t ext_adv_pattern_1[] = {
    0x02, 0x01, 0x06,
    0x03, 0x03, 0xab, 0xcd,
    0x03, 0x03, 0x18, 0x11,
    0x11, 0X09, 'n', 'i', 'm', 'b', 'l', 'e', '-', 'b', 'l', 'e', 'p', 'r', 'p', 'h', '-', 'e',
};
#endif

#if CONFIG_EXAMPLE_RANDOM_ADDR
static uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM;
#else
static uint8_t own_addr_type;
#endif

#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0
static uint16_t cids[MYNEWT_VAL(BLE_EATT_CHAN_NUM)];
static uint16_t bearers;
#endif

void print_addr_4(const void *addr) {
    const uint8_t *u8p;
    u8p = addr;
    ESP_LOGI(TAG, "%02x:%02x:%02x:%02x:%02x:%02x",
             u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
}

// 打印连接设备的信息
static void bleprph_print_conn_desc(struct ble_gap_conn_desc *desc) {
    ESP_LOGI(TAG, "handle=%d our_ota_addr_type=%d our_ota_addr=",
             desc->conn_handle, desc->our_ota_addr.type);
    print_addr_4(desc->our_ota_addr.val);
    ESP_LOGI(TAG, " our_id_addr_type=%d our_id_addr=",
             desc->our_id_addr.type);
    print_addr_4(desc->our_id_addr.val);
    ESP_LOGI(TAG, " peer_ota_addr_type=%d peer_ota_addr=",
             desc->peer_ota_addr.type);
    print_addr_4(desc->peer_ota_addr.val);
    ESP_LOGI(TAG, " peer_id_addr_type=%d peer_id_addr=",
             desc->peer_id_addr.type);
    print_addr_4(desc->peer_id_addr.val);
    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);
}

#if CONFIG_EXAMPLE_EXTENDED_ADV
/**
 * Enables advertising with the following parameters:
 *     o General discoverable mode.
 *     o Undirected connectable mode.
 */
static void
ext_bleprph_advertise(void)
{
    struct ble_gap_ext_adv_params params;
    struct os_mbuf *data;
    uint8_t instance = 0;
    int rc;

    /* First check if any instance is already active */
    if(ble_gap_ext_adv_active(instance)) {
        return;
    }

    /* use defaults for non-set params */
    memset (&params, 0, sizeof(params));

    /* enable connectable advertising */
    params.connectable = 1;

    /* advertise using random addr */
    params.own_addr_type = BLE_OWN_ADDR_PUBLIC;

    params.primary_phy = BLE_HCI_LE_PHY_1M;
    params.secondary_phy = BLE_HCI_LE_PHY_2M;
    //params.tx_power = 127;
    params.sid = 1;

    params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
    params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;

    /* configure instance 0 */
    rc = ble_gap_ext_adv_configure(instance, &params, NULL,
                                   bleprph_gap_event, NULL);
    assert (rc == 0);

    /* in this case only scan response is allowed */

    /* get mbuf for scan rsp data */
    data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
    assert(data);

    /* fill mbuf with scan rsp data */
    rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
    assert(rc == 0);

    rc = ble_gap_ext_adv_set_data(instance, data);
    assert (rc == 0);

    /* start advertising */
    rc = ble_gap_ext_adv_start(instance, 0, 0);
    assert (rc == 0);
}
#else
// 打开蓝牙广播的函数,在配置完蓝牙打开,在断开蓝牙后打开,等等
static void bleprph_advertise(void) {
    struct ble_gap_adv_params adv_params;
    struct ble_hs_adv_fields fields;
    const char *name;
    int rc;

    // 以下为广播参数设置
    memset(&fields, 0, sizeof fields);

    // 设置为可发现/BLE ONLY
    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;

    // 广播包添加设备名称
    name = ble_svc_gap_device_name();
    fields.name = (uint8_t *) name;
    fields.name_len = strlen(name);
    fields.name_is_complete = 1;

    // 用户自定义数据
    fields.mfg_data = (uint8_t[]) {0x01, 0x02};
    fields.mfg_data_len = 2;

    rc = ble_gap_adv_set_fields(&fields);
    if (rc != 0) {
        ESP_LOGI(TAG, "TAG setting advertisement data; rc=%d\n", rc);
        return;
    }

    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(own_addr_type, NULL, BLE_HS_FOREVER,
                           &adv_params, bleprph_gap_event, NULL);
    if (rc != 0) {
        ESP_LOGI(TAG, "TAG enabling advertisement; rc=%d\n", rc);
        return;
    }
}
#endif
#if MYNEWT_VAL(BLE_POWER_CONTROL)
static void bleprph_power_control(uint16_t conn_handle)
{
    int rc;

    rc = ble_gap_read_remote_transmit_power_level(conn_handle, 0x01 );  // Attempting on LE 1M phy
    assert (rc == 0);

    rc = ble_gap_set_transmit_power_reporting_enable(conn_handle, 0x1, 0x1);
    assert (rc == 0);
}
#endif

// 必选 gap 广播断联连接事件
static int bleprph_gap_event(struct ble_gap_event *event, void *arg) {
    struct ble_gap_conn_desc desc;
    int rc;

    switch (event->type) {
        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);
            if (event->connect.status == 0) {
                rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
                assert(rc == 0);
                bleprph_print_conn_desc(&desc);
#if CONFIG_EXAMPLE_BONDING
                ble_gap_security_initiate(event->connect.conn_handle);
#endif
            }
            ESP_LOGI(TAG, "\n");

            if (event->connect.status != 0) {
                /* Connection failed; resume advertising. */
#if CONFIG_EXAMPLE_EXTENDED_ADV
                ext_bleprph_advertise();
#else
                bleprph_advertise();
#endif
            }

#if MYNEWT_VAL(BLE_POWER_CONTROL)
            bleprph_power_control(event->connect.conn_handle);
#endif
            return 0;

        case BLE_GAP_EVENT_DISCONNECT:
            ESP_LOGI(TAG, "disconnect; reason=%d ", event->disconnect.reason);
            bleprph_print_conn_desc(&event->disconnect.conn);
            ESP_LOGI(TAG, "\n");

#if CONFIG_EXAMPLE_EXTENDED_ADV
            ext_bleprph_advertise();
#else
            bleprph_advertise();
#endif
            return 0;

        case BLE_GAP_EVENT_CONN_UPDATE:
            ESP_LOGI(TAG, "connection updated; status=%d ",
                     event->conn_update.status);
            rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
            assert(rc == 0);
            bleprph_print_conn_desc(&desc);
            ESP_LOGI(TAG, "\n");
            return 0;

        case BLE_GAP_EVENT_ADV_COMPLETE:
            ESP_LOGI(TAG, "advertise complete; reason=%d",
                     event->adv_complete.reason);
#if CONFIG_EXAMPLE_EXTENDED_ADV
            ext_bleprph_advertise();
#else
            bleprph_advertise();
#endif
            return 0;

        case BLE_GAP_EVENT_ENC_CHANGE:
            ESP_LOGI(TAG, "encryption change event; status=%d ",
                     event->enc_change.status);
            rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
            assert(rc == 0);
            bleprph_print_conn_desc(&desc);
            ESP_LOGI(TAG, "\n");
            return 0;

        case BLE_GAP_EVENT_NOTIFY_TX:
            ESP_LOGI(TAG, "notify_tx 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 0;

        case BLE_GAP_EVENT_SUBSCRIBE:
            ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d "
                          "reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
                     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);
            return 0;

        case BLE_GAP_EVENT_MTU:
            ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
                     event->mtu.conn_handle,
                     event->mtu.channel_id,
                     event->mtu.value);
            return 0;

        case BLE_GAP_EVENT_REPEAT_PAIRING:
            rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
            assert(rc == 0);
            ble_store_util_delete_peer(&desc.peer_id_addr);
            return BLE_GAP_REPEAT_PAIRING_RETRY;

        case BLE_GAP_EVENT_PASSKEY_ACTION:
            ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started");
            struct ble_sm_io pkey = {0};
            int key = 0;

            // NimBLE 的安全管理(SM, Security Manager)定义了几种配对方法,这里都有处理
            if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
                // 用于 设备显示一个6位数 PIN,对方设备(通常是手机)需要输入这个 PIN。
                pkey.action = event->passkey.params.action;
                pkey.passkey = 123456; // This is the passkey to be entered on peer
                ESP_LOGI(TAG, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
                rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
                ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc);
            } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
                // 在 BLE_SM_IOACT_NUMCMP 模式下,ESP32 上显示的 PIN 是 由 NimBLE 协议栈随机生成的,并且和对端设备显示的 PIN 一致。
                ESP_LOGI(TAG, "Passkey on device's display: %" PRIu32, event->passkey.params.numcmp);
                ESP_LOGI(TAG, "Accept or reject the passkey through console in this format -> key Y or key N");
                pkey.action = event->passkey.params.action;
                // 这里简单地接受所有 PIN,实际应用中应该根据用户输入判断是否接受
                //if (scli_receive_key(&key)) {
                //    pkey.numcmp_accept = key;
                //} else {BLE_SM_IOACT_NUMCMP
                //    pkey.numcmp_accept = 0;
                //    ESP_LOGE(TAG, "Timeout! Rejecting the key");
                //}
                pkey.numcmp_accept = 1;
                rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
                ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc);
            } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
                //带外认证,通常通过 NFC、二维码或其他方式提前共享一个秘钥(OOB data)。
                //代码里 pkey.oob 填了一个全 0 的数组,实际开发时要换成双方约定好的 OOB 值。
                //即:使用外部信道交换秘钥,不靠蓝牙广播。
                static uint8_t tem_oob[16] = {0};
                pkey.action = event->passkey.params.action;
                for (int i = 0; i < 16; i++) {
                    pkey.oob[i] = tem_oob[i];
                }
                rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
                ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc);
            } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
                // 对方设备显示 PIN,本机(ESP32)需要输入这个 PIN,这里简单的固定为 123456
                ESP_LOGI(TAG, "Enter the passkey through console in this format-> key 123456");
                pkey.action = event->passkey.params.action;
//                if (scli_receive_key(&key)) {
//                    pkey.passkey = key;
//                } else {
//                    pkey.passkey = 0;
//                    ESP_LOGE(TAG, "Timeout! Passing 0 as the key");
//                }
                pkey.passkey = 123456;
                rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
                ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc);
            }
            return 0;

        case BLE_GAP_EVENT_AUTHORIZE:
            ESP_LOGI(TAG, "authorize event: conn_handle=%d attr_handle=%d is_read=%d",
                     event->authorize.conn_handle,
                     event->authorize.attr_handle,
                     event->authorize.is_read);

            /* The default behaviour for the event is to reject authorize request */
            event->authorize.out_response = BLE_GAP_AUTHORIZE_REJECT;
            return 0;

#if MYNEWT_VAL(BLE_POWER_CONTROL)
            case BLE_GAP_EVENT_TRANSMIT_POWER:
                ESP_LOGI(TAG, "Transmit power event : status=%d conn_handle=%d reason=%d "
                                   "phy=%d power_level=%x power_level_flag=%d delta=%d",
                             event->transmit_power.status,
                             event->transmit_power.conn_handle,
                             event->transmit_power.reason,
                             event->transmit_power.phy,
                             event->transmit_power.transmit_power_level,
                             event->transmit_power.transmit_power_level_flag,
                             event->transmit_power.delta);
                return 0;

            case BLE_GAP_EVENT_PATHLOSS_THRESHOLD:
                ESP_LOGI(TAG, "Pathloss threshold event : conn_handle=%d current path loss=%d "
                                   "zone_entered =%d",
                             event->pathloss_threshold.conn_handle,
                             event->pathloss_threshold.current_path_loss,
                             event->pathloss_threshold.zone_entered);
                return 0;
#endif

#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0
            case BLE_GAP_EVENT_EATT:
                ESP_LOGI(TAG, "EATT %s : conn_handle=%d cid=%d",
                        event->eatt.status ? "disconnected" : "connected",
                        event->eatt.conn_handle,
                        event->eatt.cid);
            if (event->eatt.status) {
                /* Abort if disconnected */
                return 0;
            }
            cids[bearers] = event->eatt.cid;
            bearers += 1;
            if (bearers != MYNEWT_VAL(BLE_EATT_CHAN_NUM)) {
                /* Wait until all EATT bearers are connected before proceeding */
                return 0;
            }
            /* Set the default bearer to use for further procedures */
            rc = ble_att_set_default_bearer_using_cid(event->eatt.conn_handle, cids[0]);
            if (rc != 0) {
                ESP_LOGI(TAG, "Cannot set default EATT bearer, rc = %d\n", rc);
                return rc;
            }

            return 0;
#endif

#if MYNEWT_VAL(BLE_CONN_SUBRATING)
            case BLE_GAP_EVENT_SUBRATE_CHANGE:
                ESP_LOGI(TAG, "Subrate change event : conn_handle=%d status=%d factor=%d",
                            event->subrate_change.conn_handle,
                            event->subrate_change.status,
                            event->subrate_change.subrate_factor);
                return 0;
#endif
    }

    return 0;
}

// 可选
static void bleprph_on_reset(int reason) {
    ESP_LOGI(TAG, "Resetting state; reason=%d\n", reason);
}

#if CONFIG_EXAMPLE_RANDOM_ADDR
static void
ble_app_set_addr(void)
{
    ble_addr_t addr;
    int rc;

    /* generate new non-resolvable private address */
    rc = ble_hs_id_gen_rnd(0, &addr);
    assert(rc == 0);

    /* set generated address */
    rc = ble_hs_id_set_rnd(addr.val);

    assert(rc == 0);
}
#endif

// 必选
static void bleprph_on_sync(void) {
    int rc;

#if CONFIG_EXAMPLE_RANDOM_ADDR
    /* Generate a non-resolvable private address. */
    ble_app_set_addr();
#endif

#if CONFIG_EXAMPLE_RANDOM_ADDR
    rc = ble_hs_util_ensure_addr(1);
#else
    rc = ble_hs_util_ensure_addr(0);
#endif
    assert(rc == 0);

    rc = ble_hs_id_infer_auto(0, &own_addr_type);
    if (rc != 0) {
        ESP_LOGI(TAG, "TAG determining address type; rc=%d\n", rc);
        return;
    }

    uint8_t addr_val[6] = {0};
    rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);

    ESP_LOGI(TAG, "Device Address: ");
    print_addr_4(addr_val);
    ESP_LOGI(TAG, "\n");
#if CONFIG_EXAMPLE_EXTENDED_ADV
    ext_bleprph_advertise();
#else
    bleprph_advertise();
#endif
}

// 必选
void bleprph_host_task(void *param) {
    ESP_LOGI(TAG, "BLE Host Task Started");
    nimble_port_run();
    nimble_port_freertos_deinit();
}

/*************************************** GATT 服务 START ***************************************/
// GATT 服务
static const ble_uuid128_t gatt_svr_svc_uuid =
        BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
                         0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);

// 对于 GATT 服务,定义一个特征
static uint8_t gatt_svr_chr_val;
static uint16_t gatt_svr_chr_val_handle;
static const ble_uuid128_t gatt_svr_chr_uuid =
        BLE_UUID128_INIT(0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
                         0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11);

// 对于 GATT 特征,定义一个自定义描述符
static uint8_t gatt_svr_dsc_val;
static const ble_uuid128_t gatt_svr_dsc_uuid =
        BLE_UUID128_INIT(0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
                         0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22);

//Device
// └── Service(服务,例如「配网服务」)
//       └── Characteristic(特征,例如「WiFi 配置」)
//             ├── Value(特征值)     ← 手机 Read/Write 的主数据
//             └── Descriptor(s)(描述符)← 附加属性,各自有独立 handle(CCCD)
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
        //service
        {
                .type = BLE_GATT_SVC_TYPE_PRIMARY,
                .uuid = &gatt_svr_svc_uuid.u,
                .characteristics = (struct ble_gatt_chr_def[])
                        {

                                {
                                        .uuid = &gatt_svr_chr_uuid.u,
                                        .access_cb = gatt_svc_access,
#if CONFIG_EXAMPLE_ENCRYPTION
                                        .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE |
                                        BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC |
                                        BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
#else
                                        .flags = BLE_GATT_CHR_F_READ |
                                                 BLE_GATT_CHR_F_WRITE |
                                                 BLE_GATT_CHR_F_NOTIFY |
                                                 BLE_GATT_CHR_F_INDICATE,
#endif
                                        .val_handle = &gatt_svr_chr_val_handle,
                                        .descriptors = (struct ble_gatt_dsc_def[])
                                                {
                                                        //characteristic configuration descriptor
                                                        {
                                                                .uuid = &gatt_svr_dsc_uuid.u,
#if CONFIG_EXAMPLE_ENCRYPTION
                                                                .att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_ENC,
#else
                                                                .att_flags = BLE_ATT_F_READ,
#endif
                                                                .access_cb = gatt_svc_access,
                                                        },
                                                        {0}
                                                },
                                },
                                {0}
                        },
        },
        {0},
};

// 必须 获取 gatt write 的数据
static int gatt_svr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, void *dst, uint16_t *len) {
    uint16_t om_len;
    int rc;

    om_len = OS_MBUF_PKTLEN(om);
    if (om_len < min_len || om_len > max_len) {
        return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
    }

    rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
    if (rc != 0) {
        return BLE_ATT_ERR_UNLIKELY;
    }

    return 0;
}

// 必须 处理 gatt read/write 请求
static int gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
    const ble_uuid_t *uuid;
    int rc;

    switch (ctxt->op) {
        case BLE_GATT_ACCESS_OP_READ_CHR:
            if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
                ESP_LOGI(TAG, "Characteristic read; conn_handle=%d attr_handle=%d\n",
                         conn_handle, attr_handle);
            } else {
                ESP_LOGI(TAG, "Characteristic read by NimBLE stack; attr_handle=%d\n",
                         attr_handle);
            }
            uuid = ctxt->chr->uuid;
            if (attr_handle == gatt_svr_chr_val_handle) {
                rc = os_mbuf_append(ctxt->om,
                                    &gatt_svr_chr_val,
                                    sizeof(gatt_svr_chr_val));
                return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
            }
            goto unknown;

        case BLE_GATT_ACCESS_OP_WRITE_CHR:
            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);
            }
            uuid = ctxt->chr->uuid;
            if (attr_handle == gatt_svr_chr_val_handle) {
                rc = gatt_svr_write(ctxt->om,
                                    sizeof(gatt_svr_chr_val),
                                    sizeof(gatt_svr_chr_val),
                                    &gatt_svr_chr_val, NULL);
                ble_gatts_chr_updated(attr_handle);
                ESP_LOGI(TAG, "Notification/Indication scheduled for "
                              "all subscribed peers.\n");
                return rc;
            }
            goto unknown;

        case BLE_GATT_ACCESS_OP_READ_DSC:
            if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
                ESP_LOGI(TAG, "Descriptor read; conn_handle=%d attr_handle=%d\n",
                         conn_handle, attr_handle);
            } else {
                ESP_LOGI(TAG, "Descriptor read by NimBLE stack; attr_handle=%d\n",
                         attr_handle);
            }
            uuid = ctxt->dsc->uuid;
            if (ble_uuid_cmp(uuid, &gatt_svr_dsc_uuid.u) == 0) {
                gatt_svr_dsc_val = 0x99;
                rc = os_mbuf_append(ctxt->om,
                                    &gatt_svr_dsc_val,
                                    sizeof(gatt_svr_chr_val));
                return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
            }
            goto unknown;

        case BLE_GATT_ACCESS_OP_WRITE_DSC:
            goto unknown;

        default:
            goto unknown;
    }

    unknown:
    assert(0);
    return BLE_ATT_ERR_UNLIKELY;
}

// 可选
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) {
    char buf[BLE_UUID_STR_LEN];

    switch (ctxt->op) {
        case BLE_GATT_REGISTER_OP_SVC:
            ESP_LOGI(TAG, "registered service %s with handle=%d\n",
                     ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
                     ctxt->svc.handle);
            break;

        case BLE_GATT_REGISTER_OP_CHR:
            ESP_LOGI(TAG, "registering characteristic %s with "
                          "def_handle=%d val_handle=%d\n",
                     ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
                     ctxt->chr.def_handle,
                     ctxt->chr.val_handle);
            break;

        case BLE_GATT_REGISTER_OP_DSC:
            ESP_LOGI(TAG, "registering descriptor %s with handle=%d\n",
                     ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
                     ctxt->dsc.handle);
            break;

        default:
            assert(0);
            break;
    }
}

void wls_gatts_nimble_init(void) {
    static bool init = false;
    if (init == true) {
        return;
    }
    init = true;
    esp_err_t ret = nimble_port_init();                     // IDF接口,初始化 NimBLE 主机栈和控制器
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to init nimble %d ", ret);
        return;
    }

    ble_hs_cfg.reset_cb = bleprph_on_reset;                 // 回调需要自定义,可选,当蓝牙出现错误时的回调
    ble_hs_cfg.sync_cb = bleprph_on_sync;                   // 回调需要自定义,必选,当蓝牙底层和上层都初始化完成时打开蓝牙广播(bleprph_on_sync->bleprph_advertise->bleprph_advertise->bleprph_gap_event)
    ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;    // 回调需要自定义,可选,在注册GATT回调时打印注册信息
    ble_hs_cfg.store_status_cb = ble_store_util_status_rr;  // 回调源于库函数,可选,在发生储存错误时会触发
    ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE;

    // 1. 可选,绑定功能
#if CONFIG_EXAMPLE_BONDING
    ble_hs_cfg.sm_bonding = 1;
    /* Enable the appropriate bit masks to make sure the keys
     * that are needed are exchanged
     */
    ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
    ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
#endif
    // 2. 可选,增加中间人攻击的安全机制
#if CONFIG_EXAMPLE_MITM
    ble_hs_cfg.sm_mitm = 1;
#endif
    // 3. 可选,使用安全连接
#if CONFIG_EXAMPLE_USE_SC
    ble_hs_cfg.sm_sc = 1;
#else
    ble_hs_cfg.sm_sc = 0;
#endif
    // 4. 可选,解析动态地址,仅使用绑定功能时需要(由于手机随机蓝牙地址,易使ESP32重复绑定)
#if CONFIG_EXAMPLE_RESOLVE_PEER_ADDR
    /* Stores the IRK */
    ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
    ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
#endif

    int rc = 0;
    // 5. 加入 GATT 服务
    // 5.1 初始化 GAP
    ble_svc_gap_init();
    // 5.2 初始化 GATT
    ble_svc_gatt_init();
    // 5.3 初始化 ANS 服务
    //ble_svc_ans_init();
    // 5.4 先统计服务数量
    rc = ble_gatts_count_cfg(gatt_svr_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Failed to count svcs %d ", rc);
        return;
    }
    // 5.5 再加入 GATT 服务
    rc = ble_gatts_add_svcs(gatt_svr_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Failed to add svcs %d ", rc);
        return;
    }

    // 6. 设备名称
    rc = ble_svc_gap_device_name_set("ESP666");
    if (rc != 0) {
        ESP_LOGE(TAG, "Failed to set device name %d ", rc);
        return;
    }

    // 7. 初始化 NimBLE 存储配置 API,是设备绑定、加密连接、地址解析等核心安全功能的基础保障 API
    ble_store_config_init();

    // 8. 创建 NimBLE 主机任务(Host Task)API
    nimble_port_freertos_init(bleprph_host_task);

#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0
    bearers = 0;
    for (int i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_NUM); i++) {
        cids[i] = 0;
    }
#endif
}
``
ESP
License:  CC BY 4.0
Share

Further Reading

OLDER

NEWER

ESP32c3 内存占用测试报告

Recently Updated

  • nimble
  • ESP32c3 内存占用测试报告
  • ESP Event
  • HTTP 快速刷新
  • [adb] 读取屏幕内容与点击,用于测试

Trending Tags

LVGL WCH Linux Elec ThatProject freeRTOS STM ESP Flutter Others

Contents

©2026 松果工作室. Some rights reserved.

Using the Halo theme Chirpy