Zephyr 中 bt_gatt_indicate_params 数据结构

目录

概述

1 bt_gatt_indicate_params 数据结构介绍

1.1 结构定义

1.2 成员详解

1.2.1 func - 指示完成回调

1.2.2 destroy - 析构函数(可选)

1.2.3 attr - 目标特征属性

1.2.4 data - 数据指针

1.2.5 len - 数据长度

1.2.6 _req - 内部请求结构

2  使用流程

2.1  准备指示参数

2.2 发送指示

2.3 处理回调

3 高级用法

3.1 带重试机制的指示

3.2 多连接指示广播

3.3  指示超时处理

3.4 生命周期管理

3.4.1 内存分配策略

3.4.2 资源清理方法

4 错误处理

4.1 常见错误代码

4.2 健壮的错误处理


概述

bt_gatt_indicate_params 是 Zephyr 蓝牙协议栈中用于配置 GATT 指示操作的核心数据结构。指示(Indication)是一种需要客户端确认的可靠数据传输机制,适用于关键数据更新和控制操作。该数据结构是 Zephyr 蓝牙协议栈中实现可靠数据传输的关键数据结构。通过合理管理其生命周期、正确处理回调函数并优化资源使用,可以构建高效可靠的蓝牙应用系统,特别适用于需要确认的关键操作和数据传输场景。

1 bt_gatt_indicate_params 数据结构介绍

1.1 结构定义

struct bt_gatt_indicate_params {
    // 指示操作完成时的回调函数
    void (*func)(struct bt_conn *conn,
                 struct bt_gatt_indicate_params *params,
                 uint8_t err);
    
    // 指示参数析构函数(可选)
    void (*destroy)(struct bt_gatt_indicate_params *params);
    
    // 指向要指示的特征属性的指针
    const struct bt_gatt_attr *attr;
    
    // 要发送的数据指针
    const void *data;
    
    // 数据长度
    uint16_t len;
    
    // 内部使用字段(协议栈管理)
    struct bt_att_req _req;
};

1.2 成员详解

1.2.1 func - 指示完成回调

void (*func)(struct bt_conn *conn,
             struct bt_gatt_indicate_params *params,
             uint8_t err);
  • 功能:当指示被确认或失败时调用的回调函数

  • 参数

    • conn:目标连接句柄(发送指示的连接)

    • params:指向当前指示参数结构的指针

    • err:错误代码(0 表示成功)

典型实现

static void indicate_cb(struct bt_conn *conn,
                       struct bt_gatt_indicate_params *params,
                       uint8_t err)
{
    if (err) {
        printk("Indication failed: 0x%02x\n", err);
    } else {
        printk("Indication confirmed\n");
    }
    
    // 释放资源
    k_free(params);
}

1.2.2 destroy - 析构函数(可选)

void (*destroy)(struct bt_gatt_indicate_params *params);
  • 功能:指示完成后用于清理资源的回调

  • 使用场景

    • 释放动态分配的内存

    • 重置状态标志

示例

static void params_destroy(struct bt_gatt_indicate_params *params)
{
    struct custom_context *ctx = CONTAINER_OF(params, struct custom_context, params);
    ctx->busy = false;
}

1.2.3 attr - 目标特征属性

const struct bt_gatt_attr *attr;
  • 功能:指向要指示的特征值属性

  • 获取方式

    // 在服务定义中获取属性指针
    BT_GATT_SERVICE_DEFINE(my_service,
        BT_GATT_PRIMARY_SERVICE(...),
        BT_GATT_CHARACTERISTIC(...) // 索引0: 服务声明, 索引1: 特征声明, 索引2: 特征值
    );
    
    // 使用索引获取特征值属性
    const struct bt_gatt_attr *char_value_attr = &my_service.attrs[2];

    1.2.4 data - 数据指针

const void *data;
  • 功能:指向要发送的数据缓冲区的指针

  • 注意事项

    • 数据在发送期间必须保持有效

    • 设置为 NULL 将使用特征当前值

  • 典型用法

    uint8_t alert_data = ALERT_HIGH;
    params.data = &alert_data;

    1.2.5 len - 数据长度

uint16_t len;
  • 功能:要发送的数据长度(字节)

  • 注意事项

    • 设置为 0 且 data 为 NULL 时将使用特征值长度

    • 不能超过 MTU 限制(bt_gatt_get_mtu(conn) - 3

1.2.6 _req - 内部请求结构

struct bt_att_req _req;
  • 功能:协议栈内部使用的请求管理结构

  • 开发者注意事项

    • 不要直接访问或修改此字段

    • 由协议栈在指示生命周期内管理

2  使用流程

2.1  准备指示参数

// 定义自定义上下文结构
struct indicate_context {
    struct bt_gatt_indicate_params params;
    uint8_t data[20];
    uint32_t timestamp;
};

// 创建指示参数
struct indicate_context *create_indication(struct bt_conn *conn, 
                                          const void *data, uint16_t len)
{
    // 分配内存
    struct indicate_context *ctx = k_malloc(sizeof(*ctx));
    if (!ctx) return NULL;
    
    // 设置参数
    ctx->params.func = indicate_callback;
    ctx->params.destroy = NULL; // 在回调中手动释放
    ctx->params.attr = get_target_characteristic_attr();
    ctx->params.data = data ? data : ctx->data;
    ctx->params.len = len;
    
    // 保存上下文数据
    if (data && len > 0) {
        memcpy(ctx->data, data, MIN(len, sizeof(ctx->data)));
    }
    ctx->timestamp = k_uptime_get_32();
    
    return ctx;
}

2.2 发送指示

int send_critical_indication(struct bt_conn *conn, 
                            const void *data, uint16_t len)
{
    // 创建参数
    struct indicate_context *ctx = create_indication(conn, data, len);
    if (!ctx) return -ENOMEM;
    
    // 发送指示
    int err = bt_gatt_indicate(conn, &ctx->params);
    if (err) {
        printk("Indicate failed: %d\n", err);
        k_free(ctx);
        return err;
    }
    
    return 0;
}

2.3 处理回调

static void indicate_callback(struct bt_conn *conn,
                             struct bt_gatt_indicate_params *params,
                             uint8_t err)
{
    // 获取上下文
    struct indicate_context *ctx = 
        CONTAINER_OF(params, struct indicate_context, params);
    
    // 处理结果
    if (err) {
        printk("Indication failed after %d ms (err 0x%02x)\n", 
               k_uptime_get_32() - ctx->timestamp, err);
    } else {
        printk("Indication confirmed in %d ms\n", 
               k_uptime_get_32() - ctx->timestamp);
    }
    
    // 清理资源
    k_free(ctx);
}

3 高级用法

3.1 带重试机制的指示

#define MAX_RETRIES 3

struct retry_context {
    struct bt_gatt_indicate_params params;
    uint8_t data[32];
    uint8_t retry_count;
};

static void retry_indicate(struct bt_conn *conn, 
                          struct retry_context *ctx)
{
    int err = bt_gatt_indicate(conn, &ctx->params);
    if (err) {
        printk("Retry failed: %d\n", err);
        k_free(ctx);
    }
}

static void retry_callback(struct bt_conn *conn,
                          struct bt_gatt_indicate_params *params,
                          uint8_t err)
{
    struct retry_context *ctx = 
        CONTAINER_OF(params, struct retry_context, params);
    
    if (err && ctx->retry_count < MAX_RETRIES) {
        ctx->retry_count++;
        printk("Retrying indication (%d/%d)\n", 
               ctx->retry_count, MAX_RETRIES);
        k_work_submit(&retry_work, K_MSEC(100));
    } else {
        printk("Indication %s after %d attempts\n", 
               err ? "failed" : "succeeded", ctx->retry_count + 1);
        k_free(ctx);
    }
}

3.2 多连接指示广播

void broadcast_indication_to_all(const void *data, uint16_t len)
{
    for (int i = 0; i < ARRAY_SIZE(connected_clients); i++) {
        if (connected_clients[i].conn && 
            connected_clients[i].indicate_enabled) {
            
            struct indicate_context *ctx = create_indication(
                connected_clients[i].conn, data, len);
            
            if (!ctx) continue;
            
            if (bt_gatt_indicate(connected_clients[i].conn, &ctx->params)) {
                k_free(ctx);
            }
        }
    }
}

3.3  指示超时处理

static void indication_timeout(struct k_work *work)
{
    struct indicate_context *ctx = 
        CONTAINER_OF(work, struct indicate_context, timeout_work);
    
    if (ctx->pending) {
        ctx->params.func(ctx->conn, &ctx->params, BT_ATT_ERR_UNLIKELY);
    }
}

void send_indication_with_timeout(struct bt_conn *conn, 
                                  const void *data, uint16_t len,
                                  uint32_t timeout_ms)
{
    struct indicate_context *ctx = create_indication(conn, data, len);
    ctx->pending = true;
    
    // 设置超时工作
    k_work_init_delayable(&ctx->timeout_work, indication_timeout);
    k_work_schedule(&ctx->timeout_work, K_MSEC(timeout_ms));
    
    if (bt_gatt_indicate(conn, &ctx->params)) {
        k_work_cancel_delayable(&ctx->timeout_work);
        k_free(ctx);
    }
}

static void indicate_callback(/*...*/)
{
    struct indicate_context *ctx = /*...*/;
    ctx->pending = false;
    k_work_cancel_delayable(&ctx->timeout_work);
    // ...其他处理...
}

3.4 生命周期管理

3.4.1 内存分配策略

策略适用场景示例
静态分配单连接、低频指示static struct bt_gatt_indicate_params params;
动态分配多连接、可变数据k_malloc(sizeof(struct indicate_context));
内存池高频指示k_mem_slab_alloc(&indicate_slab, &ctx, K_NO_WAIT);

3.4.2 资源清理方法

// 方法1:在回调中直接释放
static void callback_direct_free(struct bt_conn *conn,
                                struct bt_gatt_indicate_params *params,
                                uint8_t err)
{
    k_free(params);
}

// 方法2:使用析构函数
static void params_destroy(struct bt_gatt_indicate_params *params)
{
    struct custom_ctx *ctx = CONTAINER_OF(params, struct custom_ctx, params);
    ctx->in_use = false;
}

// 方法3:内存池释放
static void mempool_callback(struct bt_conn *conn,
                            struct bt_gatt_indicate_params *params,
                            uint8_t err)
{
    k_mem_slab_free(&indicate_slab, (void **)&params);
}

4 错误处理

4.1 常见错误代码

错误代码说明
BT_ATT_ERR_INSUFFICIENT_RESOURCES0x11资源不足
BT_ATT_ERR_UNLIKELY0x0E一般错误
BT_ATT_ERR_WRITE_NOT_PERMITTED0x03写权限不足
BT_ATT_ERR_INVALID_OFFSET0x07无效偏移量

4.2 健壮的错误处理

static void robust_indicate_callback(struct bt_conn *conn,
                                    struct bt_gatt_indicate_params *params,
                                    uint8_t err)
{
    struct indicate_context *ctx = /*...*/;
    
    switch (err) {
    case BT_ATT_ERR_INSUFFICIENT_RESOURCES:
        printk("Error: Insufficient resources\n");
        schedule_retry(ctx);
        break;
        
    case BT_ATT_ERR_UNLIKELY:
        printk("Error: General failure\n");
        log_error(ctx);
        k_free(ctx);
        break;
        
    case 0: // 成功
        handle_success(ctx);
        k_free(ctx);
        break;
        
    default:
        printk("Unhandled error: 0x%02x\n", err);
        k_free(ctx);
        break;
    }
}

### Zephyr RTOS 中 `include_directories` 的 CMake 使用方法 在 Zephyr RTOS 项目中,CMake 是主要构建工具之一。为了管理头文件路径,在应用程序或模块的 CMakeLists.txt 文件中可以使用 `target_include_directories()` 或者传统的 `include_directories()` 函数来指定编译器应查找头文件的位置[^1]。 #### 基本语法 对于较新的推荐做法: ```cmake target_include_directories(app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc) ``` 这行命令会向目标 `app` 添加一个公共包含目录 `${CMAKE_CURRENT_SOURCE_DIR}/inc`。这里的关键字 `PUBLIC` 表明此接口对其他依赖该目标的对象可见;如果只想让这些包含路径仅限于当前目标,则可替换为 `PRIVATE` 关键字[^2]。 而传统方式如下所示: ```cmake include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc) ``` 这种方式同样指定了源码根目录下的 `inc` 子目录作为额外的头文件搜索位置。不过需要注意的是,这种方法不如前者灵活,并且不支持作用域控制(即无法区分 `PUBLIC`, `INTERFACE`, 和 `PRIVATE`)[^3]。 #### 实际案例展示 假设有一个简单的 Blink LED 应用程序结构如下: ``` blink/ ├── src/ │ └── main.c └── inc/ └── led.h ``` 此时可以在顶层 CMakeLists.txt 文件里这样写入以确保能正确找到自定义头文件: ```cmake # 推荐的方式 target_include_directories(app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc) # 或者旧版兼容模式下 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc) ``` 通过上述配置之后,在编写代码时就可以直接引用位于 `./inc/led.h` 下的内容而不必担心找不到对应的声明了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值