nimble
下面用你这份 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() 里一起起来 |
| Host | GAP/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);
}
记住顺序:
- 初始化栈
- 配回调
- 注册 GATT 表
- 设设备名
- 启动 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_TX | Notify 是否发成功 |
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 标准 UUID | 2 字节 | 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);
gatt_svr_write:从ctxt->om拷到gatt_svr_chr_val,并校验长度必须是 1 字节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,下次自动连 |
| SMP | Security 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_CONNECTIONS | 1 | 同时只连 1 个手机 |
ATT_PREFERRED_MTU | 256 | 单次 GATT 最多传 ~253 字节有效载荷 |
ACL_SIZE | 255 | 链路层单包大小 |
HOST_TASK_STACK | 4096 | Host 任务栈 |
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_advertise | PASSKEY_ACTION 整段 |
bleprph_gap_event 的 CONNECT/DISCONNECT | 自定义 Descriptor 0x2222 |
gatt_svr_svcs + gatt_svc_access | bleprph_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 代码逻辑无直接冲突。
十三、自测清单(检验是否真懂)
- Peripheral 和 Central 谁是你、谁是手机?
- 广播在哪个回调里才开始?
- Service / Characteristic / Descriptor 各干什么?
access_cb在 READ 和 WRITE 时分别要做什么?gatt_svr_write解决什么问题?- Notify 需要手机先做什么?(订阅 CCCD)
- 断开连接后为什么要再
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 (¶ms, 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, ¶ms, 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
}
``