Nordic添加蓝牙服务

简介

本文介绍如何给nrf52840添加自定义的服务

环境

1、zephyr v3.5.99

2、nrf connect sdk v2.6.1

具体操作

1、通过添加宏定义的方式添加服务及内部特征。

//自定义蓝牙 GATT(Generic Attribute Profile)服务
BT_GATT_SERVICE_DEFINE(
	// 这个宏定义了一个 GATT 服务,并将其命名为 custom_service。
	// custom_service 是这个服务的标识符,将用于引用和操作该服务。
    custom_service, 

	// 这个宏定义了一个主服务(Primary Service)。
	// PRIMARY_SERVICE_UUID 是服务的 UUID,用于唯一标识该服务。
    BT_GATT_PRIMARY_SERVICE(PRIMARY_SERVICE_UUID),

	// 这个宏定义了一个特征(Characteristic)。
	// INDICATE_CHAR_UUID 是特征的 UUID。
	// BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE 定义了这个特征的属性(可以读取和指示)。
	// BT_GATT_PERM_READ 定义了读权限。
	// indicate_char_callback 是读操作的回调函数。
	// NULL 表示写操作没有回调函数。
	// &indicate_flag 是特征的初始值或状态指针。
    BT_GATT_CHARACTERISTIC(
        INDICATE_CHAR_UUID, 
        BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE, 
        BT_GATT_PERM_READ, 
        indicate_char_callback, 
        NULL, 
        &indicate_flag
    ),

	// 这个宏定义了一个客户端特征配置(Client Characteristic Configuration, CCC)。
	// indicate_char_ccc_cfg_changed 是 CCC 配置变化的回调函数。
	// BT_GATT_PERM_READ | BT_GATT_PERM_WRITE 设置了 CCC 的读写权限。
    BT_GATT_CCC(
        indicate_char_ccc_cfg_changed, 
        BT_GATT_PERM_READ | BT_GATT_PERM_WRITE
    ),

	// WRITE_CHAR_UUID 是特征的 UUID。
	// BT_GATT_CHRC_WRITE 定义了这个特征的属性(可以写入)。
	// BT_GATT_PERM_WRITE 定义了写权限。
	// NULL 表示没有读操作的回调函数。
	// write_char_callback 是写操作的回调函数。
	// NULL 表示没有初始值或状态指针。
    BT_GATT_CHARACTERISTIC(
        WRITE_CHAR_UUID, 
        BT_GATT_CHRC_WRITE, 
        BT_GATT_PERM_WRITE, 
        NULL, 
        write_char_callback, 
        NULL
    ),

	// NOTIFY_CHAR_UUID: 这是通知特征(Notify Characteristic)的 UUID。
	// BT_GATT_CHRC_NOTIFY: 这个属性表明该特征支持通知(Notify)。通知是指当特征的值发生变化时,服务器可以主动将新的值发送给已订阅该特征的客户端。
	// BT_GATT_PERM_NONE: 这里没有设置任何权限,因为通知特征通常不需要直接的读写权限。
	// NULL: 没有读操作的回调函数,因为该特征不支持读取。
	// NULL: 没有写操作的回调函数,因为该特征不支持写入。
	// NULL: 没有初始值或状态指针,因为该特征不需要初始化值。
    BT_GATT_CHARACTERISTIC(
        NOTIFY_CHAR_UUID, 
        BT_GATT_CHRC_NOTIFY, 
        BT_GATT_PERM_NONE, 
        NULL, 
        NULL, 
        NULL
    ),

	// notify_char_ccc_cfg_changed: 这是一个客户端特征配置(CCC)变化的回调函数。它将在客户端启用或禁用通知时被调用。
	// BT_GATT_PERM_READ | BT_GATT_PERM_WRITE: 设置了 CCC 的读写权限。客户端可以读取和写入这个配置,以启用或禁用通知。
    BT_GATT_CCC(
        notify_char_ccc_cfg_changed, 
        BT_GATT_PERM_READ | BT_GATT_PERM_WRITE
    )
);

2、配置回调函数 

这些回调函数与上述代码对应

/**
 * @description: 处理特征读取操作的回调函数
 * @param: conn 蓝牙连接句柄
 *         attr GATT 属性结构
 *         buf 缓冲区,用于存储读取的数据
 *         len 缓冲区的长度
 *         offset 数据偏移量
 * @return: ssize_t 读取的数据长度
 */
static ssize_t indicate_char_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
                                      uint16_t len, uint16_t offset)
{
    // 获取存储在属性中的用户数据
    const char *value = attr->user_data;

    // 打印读取操作的相关信息
    printk("Attribute read, handle: %u, conn: %p", attr->handle, (void *)conn);

    // 使用 bt_gatt_attr_read 函数读取属性值并返回读取的数据长度
    return bt_gatt_attr_read(conn, attr, buf, len, offset, value, sizeof(*value));
}

/**
 * @description: 处理客户端特征配置(CCC)变化的回调函数
 * @param: attr GATT 属性结构
 *         value 新的 CCC 配置值
 * @return: 无
 */
static void indicate_char_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
    // 根据新的配置值设置指示功能的启用状态
    indicate_enabled = (value == BT_GATT_CCC_INDICATE);
}

/**
 * @description: 处理指示完成的回调函数
 * @param: conn 蓝牙连接句柄
 *         params 指示参数结构
 *         err 指示操作的错误代码
 * @return: 无
 */
static void indicate_cb(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err)
{
    // 打印指示操作的结果
    printk("Indication %s\n", err != 0U ? "fail" : "success");
}

/**
 * @description: 处理特征写入操作的回调函数
 * @param: conn 蓝牙连接句柄
 *         attr GATT 属性结构
 *         buf 缓冲区,包含写入的数据
 *         len 缓冲区的长度
 *         offset 数据偏移量
 *         flags 标志,指示写入操作的属性
 * @return: ssize_t 写入的数据长度
 */
static ssize_t write_char_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf,
                                   uint16_t len, uint16_t offset, uint8_t flags)
{
    // 打印写入操作的相关信息
    printk("Attribute(write_char) write, handle: %u, conn: %p, len:%d\n", attr->handle, (void *)conn, len);

    // 检查数据偏移量是否正确
    if (offset != 0) {
        printk("Write led: Incorrect data offset\n");
        return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
    }

    // 将写入的数据转换为字符串格式
    char *value = (uint8_t *)buf;
    printk("Write data: %.*s\n", len, value);

    // 返回写入的数据长度
    return len;
}

/**
 * @description: 处理客户端特征配置(CCC)变化的回调函数
 * @param: attr GATT 属性结构
 *         value 新的 CCC 配置值
 * @return: 无
 */
static void notify_char_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
    // 根据新的配置值设置通知功能的启用状态
    notify_enabled = (value == BT_GATT_CCC_NOTIFY);
}

添加常见蓝牙服务


static ssize_t manu_name_read_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
                                      uint16_t len, uint16_t offset)
{
    // 获取存储在属性中的用户数据
    const char *value = attr->user_data;
	uint16_t data_len = strlen(value);

    // 打印读取操作的相关信息
    printk("Attribute(manu_name_read) read, handle: %u, conn: %p, data_len:%d, data:%.*s", attr->handle, (void *)conn, data_len, data_len, attr->user_data);

    // 使用 bt_gatt_attr_read 函数读取属性值并返回读取的数据长度
    return bt_gatt_attr_read(conn, attr, buf, len, offset, value, data_len);
}

static ssize_t version_read_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
                                      uint16_t len, uint16_t offset)
{
    // 获取存储在属性中的用户数据
    const char *value = attr->user_data;
	uint16_t data_len = strlen(value);

    // 打印读取操作的相关信息
    printk("Attribute(version_read) read, handle: %u, conn: %p, data_len:%d, data:%.*s", attr->handle, (void *)conn, data_len, data_len, attr->user_data);

    // 使用 bt_gatt_attr_read 函数读取属性值并返回读取的数据长度
    return bt_gatt_attr_read(conn, attr, buf, len, offset, value, data_len);
}

//设备信息服务(Device Information Service)UUID: 0x180A
BT_GATT_SERVICE_DEFINE(
 	dis_svc, 
	BT_GATT_PRIMARY_SERVICE(BT_UUID_DIS),
	BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MANUFACTURER_NAME,
			       BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
			       manu_name_read_callback, NULL, "Zephyr Manufacturer"),
	BT_GATT_CHARACTERISTIC(BT_UUID_DIS_FIRMWARE_REVISION,
			       BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
			       version_read_callback, NULL, "1.0.1"),
);

如何发送数据

示例代码

int indicate_send(char* value, int len)
{
	if(!indicate_enabled)
	{
		return EACCES;
	}

	indicate_param.attr = &custom_service.attrs[2];
	indicate_param.func = indicate_cb;
	indicate_param.data = value;
	indicate_param.len = len;
	indicate_param.destroy = NULL;

	return bt_gatt_indicate(NULL, &indicate_param);
}

int notify_send(char* value, int len)
{
	if(!notify_enabled)
	{
		return EACCES;
	}

	return bt_gatt_notify(NULL, &custom_service.attrs[7], value, len);
}

解释

在 Zephyr 的 Bluetooth GATT(Generic Attribute Profile)中,一个特征(Characteristic)通常需要两个 BT_GATT_ATTRIBUTE 来定义。这两个 BT_GATT_ATTRIBUTE 分别承担不同的职责,以便完整地描述和操作特征。

#define BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, _read, _write, _user_data) \
	BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, BT_GATT_PERM_READ,                 \
			  bt_gatt_attr_read_chrc, NULL,                         \
			  ((struct bt_gatt_chrc[]) {                            \
				BT_GATT_CHRC_INIT(_uuid, 0U, _props),           \
						   })),                         \
	BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data)
第一个 BT_GATT_ATTRIBUTE

这个 BT_GATT_ATTRIBUTE 定义了 GATT 特征声明(Characteristic Declaration),它包含了以下信息:

  • UUIDBT_UUID_GATT_CHRC,这是一个固定的 UUID,用于标识这是一个特征声明。
  • 权限BT_GATT_PERM_READ,表示这个特征声明是可读的。
  • 读取回调bt_gatt_attr_read_chrc,这是一个读取回调函数,用于处理特征声明的读取请求。
  • 用户数据((struct bt_gatt_chrc[]) { BT_GATT_CHRC_INIT(_uuid, 0U, _props), }),这是一个包含特征属性(如 UUID 和属性标志)的结构体数组。
第二个 BT_GATT_ATTRIBUTE

这个 BT_GATT_ATTRIBUTE 定义了实际的特征值(Characteristic Value),它包含了以下信息:

  • UUID:特征的 UUID(由 _uuid 参数指定)。
  • 权限:特征值的权限(由 _perm 参数指定),如读、写、通知等。
  • 读取回调:特征值的读取回调函数(由 _read 参数指定),用于处理读取请求。
  • 写入回调:特征值的写入回调函数(由 _write 参数指定),用于处理写入请求。
  • 用户数据:与特征值相关的用户数据(由 _user_data 参数指定)。
为什么需要两个 BT_GATT_ATTRIBUTE
  1. 特征声明:第一个 BT_GATT_ATTRIBUTE 定义了特征声明,它告诉客户端这是一个特征,并描述了特征的属性(如读、写、通知等)。这使客户端知道如何与特征交互。
  2. 特征值:第二个 BT_GATT_ATTRIBUTE 定义了实际的特征值,它包含了特征的数据和操作回调。客户端可以通过特征声明了解到特征值的位置,然后对其进行读写操作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值