BLE GATT服务注册详解——IOTC SDK动态属性表注册流程
一、IOTC GATT注册的分层架构
IOTC SDK的GATT服务注册遵循OHOS标准蓝牙接口,采用动态属性表注册模型。整个注册流程从应用层Profile定义出发,经过三层结构转换,最终通过OHOS C API BleGattsStartServiceEx 将属性列表提交给底层协议栈。
注册流程经过六层调用:
ble_svc.c: BleServiceInit()
└─ ble_svc.c: BleGattInit()
├─ ble_gatt_data_svc.c: BleGattDataSvcInit()
│ └─ BleAddGattSvc(&g_bleDataSvc) ← 第1步: Profile注册到链表
└─ ble_gatt_mgt.c: BleGattMgtInit()
├─ BleGattProfileSvcInit() ← 第2步: Profile转适配层格式
└─ IotcBleStartGattsService() ← 第3步: 适配层入口
└─ iotc_ble.c: AdapterServiceCopyToOhosGattAttr() ← 第4步: 构造BleGattAttr[]
└─ ohos_bt_gatt_server.c: BleGattsStartServiceEx() ← 第5步: OHOS C API
├─ BleGattsRegister() → gatts_register_server()
├─ BleGattsAddService() → gatts_add_service()
├─ BleGattsAddCharacteristic() → gatts_add_characteristic_sync()
├─ BleGattsAddDescriptor() → gatts_add_descriptor_sync()
│ └─ 自动添加CCCD(0x2902)
└─ BleGattsStartService() → gatts_start_service() ← 第6步: 激活
二、三层结构转换链
IOTC SDK的GATT注册从应用层Profile定义开始,经过三层结构转换最终到达OHOS C API。理解这三层结构是掌握注册流程的前提。
IotcBleGattProfileSvc (应用层)
↓ ProfileSvcCopyToAdapterSvc()
IotcAdptBleGattService (适配层)
↓ AdapterServiceCopyToOhosGattAttr()
BleGattService + BleGattAttr[] (OHOS C API)
2.1 应用层Profile定义
最上层是应用层Profile结构,定义在 iotc_ble_def.h 中。ble_gatt_data_svc.c 中的 g_bleDataSvc 是一个典型示例:
/* ble_gatt_data_svc.c — 静态Profile定义 */
#define DATA_SVC_UUID "15F1E600A27743FCA484DD39EF8A9100"
#define DATA_SVC_READ_UUID "15F1E601A27743FCA484DD39EF8A9100"
#define DATA_SVC_WRITE_UUID "15F1E602A27743FCA484DD39EF8A9100"
static IotcBleGattProfileChar g_dataChar[] = {
{
.uuid = DATA_SVC_WRITE_UUID,
.permission = IOTC_BLE_GATT_PERMISSION_READ_ENCRYPTED | IOTC_BLE_GATT_PERMISSION_WRITE_ENCRYPTED,
.property = IOTC_BLE_GATT_CHARACTER_PROPERTY_BIT_WRITE,
.readFunc = NULL,
.writeFunc = BleDataCharWrite,
.indicateFunc = NULL,
.desc = NULL,
.descNum = 0,
},
{
.uuid = DATA_SVC_READ_UUID,
.permission = IOTC_BLE_GATT_PERMISSION_READ_ENCRYPTED | IOTC_BLE_GATT_PERMISSION_WRITE_ENCRYPTED,
.property = IOTC_BLE_GATT_CHARACTER_PROPERTY_BIT_READ | IOTC_BLE_GATT_CHARACTER_PROPERTY_BIT_INDICATE,
.readFunc = BleDataCharRead,
.writeFunc = NULL,
.indicateFunc = NULL,
.desc = NULL,
.descNum = 0,
}
};
static const IotcBleGattProfileSvc g_bleDataSvc = {
.uuid = DATA_SVC_UUID,
.character = g_dataChar,
.charNum = (sizeof(g_dataChar) / sizeof(IotcBleGattProfileChar)),
};
这个Profile定义了两个特征值:RX(Write属性,接收App下发数据)和TX(Read+Indicate属性,向App上报数据)。UUID使用16字节128-bit格式,以十六进制字符串形式声明。每个特征值绑定了独立的回调函数指针——writeFunc 处理写入请求,readFunc 处理读取请求。
2.2 适配层中间结构
中间层是适配层结构 IotcAdptBleGattService,定义在 adapter/include/iotc_ble.h 中:
typedef struct {
const char *uuid;
IotcAdptBleGattsChar *character;
uint32_t charNum;
int32_t svcHandle;
int32_t serverId;
} IotcAdptBleGattService;
ProfileSvcCopyToAdapterSvc 在 ble_gatt_mgt.c 的 BleGattProfileSvcInit 中被调用,遍历通过 BleAddGattSvc 注册到链表的所有Profile服务,逐一转换为适配层格式。
2.3 OHOS C API结构
最底层是OHOS C API结构 BleGattService 和 BleGattAttr,定义在 ohos_bt_gatt_server.h 中:
typedef struct {
BleAttribType attrType; /* SERVICE / CHAR / CHAR_VALUE / CHAR_CLIENT_CONFIG / CHAR_USER_DESCR */
unsigned int permission; /* GattAttributePermission */
UuidType uuidType; /* 16/32/128 bit */
unsigned char uuid[OHOS_BLE_UUID_MAX_LEN]; /* 16字节 */
unsigned char *value;
unsigned char valLen;
unsigned char properties; /* GattCharacteristicProperty */
BleGattOperateFunc func; /* {read, write, indicate} 回调 */
} BleGattAttr;
typedef struct {
unsigned int attrNum;
BleGattAttr *attrList;
} BleGattService;
BleGattAttr 中的 BleGattOperateFunc 是一个函数指针联合体,携带read/write/indicate三个回调。这正是IOTC SDK动态注册的核心——回调函数直接嵌入属性项中,注册时随属性一起传递给协议栈。
AdapterServiceCopyToOhosGattAttr 在 iotc_ble.c 中实现,负责将适配层结构展开为扁平的 BleGattAttr 数组。属性数量由 GetSvcAttrNum 计算:
/* iotc_ble.c — 计算属性数量 */
static uint32_t GetSvcAttrNum(IotcAdptBleGattService *svc)
{
uint32_t attrCnt = 1; /* 服务声明本身占1项 */
attrCnt += svc->charNum;
for (uint32_t i = 0; i < svc->charNum; i++) {
attrCnt += svc->character[i].descNum; /* 每个特征值的描述符 */
}
return attrCnt;
}
对于 g_bleDataSvc 这个包含2个特征值、0个描述符的服务,attrNum = 1 + 2 + 0 = 3。属性列表的排列顺序为:[服务声明] [特征值0] [特征值1]。
UUID从字符串到字节数组的转换由 UtilsUnhexifyR 完成,它将十六进制字符串反转解为字节序列填入 BleGattAttr.uuid。
三、服务链表预注册机制
IOTC SDK采用链表预注册模式。BleAddGattSvc 将Profile服务添加到全局链表,不立即触发注册:
/* ble_gatt_mgt.c — 添加服务到链表 */
int32_t BleAddGattSvc(const IotcBleGattProfileSvc *svc)
{
BleGattSvcList *newNode = (BleGattSvcList *)IotcMalloc(sizeof(BleGattSvcList));
(void)memset_s(newNode, sizeof(BleGattSvcList), 0, sizeof(BleGattSvcList));
newNode->svc = svc;
(void)UtilsGlobalMutexLock();
LIST_INSERT_BEFORE(&newNode->list, GetBleGattSvcListHead());
g_gattSvcListNum++;
UtilsGlobalMutexUnlock();
return IOTC_OK;
}
所有服务(数据服务、配网服务、认证服务、SPEKE服务等)在初始化阶段各自调用 BleAddGattSvc 注册到链表,之后由 BleGattMgtInit 统一处理。这种设计允许各功能模块独立注册服务,无需集中维护一个巨大的属性表。
BleGattProfileSvcInit 遍历链表,将每个 IotcBleGattProfileSvc 转换为 IotcAdptBleGattService:
/* ble_gatt_mgt.c — Profile服务初始化 */
static int32_t BleGattProfileSvcInit(void)
{
uint32_t svcNum = GetBleGattSvcListNum();
IotcAdptBleGattService *svc = (IotcAdptBleGattService *)IotcCalloc(svcNum, sizeof(IotcAdptBleGattService));
LIST_FOR_EACH_ITEM(item, GetBleGattSvcListHead()) {
BleGattSvcList *node = CONTAINER_OF(item, BleGattSvcList, list);
int32_t ret = ProfileSvcCopyToAdapterSvc(node->svc, &svc[index]);
/* 复制uuid、character数组、回调函数指针等 */
}
g_bleGattApp.svc = svc;
g_bleGattApp.svcNum = svcNum;
return IOTC_OK;
}
四、适配层注册入口
转换完成后,IotcBleStartGattsService 遍历所有服务,逐一构造 BleGattAttr 数组并调用 BleGattsStartServiceEx:
/* iotc_ble.c — 适配层注册入口 */
int32_t IotcBleStartGattsService(IotcAdptBleGattService *svc, uint32_t svcNum)
{
for (uint8_t i = 0; i < svcNum; i++) {
uint8_t attrNum = GetSvcAttrNum(svc + i);
BleGattAttr attrList[attrNum]; /* VLA: 栈上分配 */
(void)memset_s(&attrList, attrNum * sizeof(BleGattAttr), 0, attrNum * sizeof(BleGattAttr));
/* 适配层结构 → OHOS C API结构 */
if (AdapterServiceCopyToOhosGattAttr(svc + i, attrList, attrNum) != IOTC_OK) {
return IOTC_ERROR;
}
BleGattService srvcInfo = {0};
srvcInfo.attrNum = attrNum;
srvcInfo.attrList = attrList;
int32_t ret = BleGattsStartServiceEx(&svc[i].svcHandle, &srvcInfo);
if (ret != OHOS_BT_STATUS_SUCCESS) { return IOTC_ERROR; }
RefreshHandle(svc + i); /* 根据返回的svcHandle刷新特征值handle */
}
return IOTC_OK;
}
RefreshHandle 在注册成功后将 svcHandle 加上各特征值的相对偏移,得到绝对handle值,保存到适配层结构中供后续Indication发送使用。
五、BleGattsStartServiceEx注册实现
BleGattsStartServiceEx 是OHOS蓝牙接口中的一次性服务注册函数,IOTC SDK通过它将构造好的属性列表提交给底层协议栈。在hi3863适配实现中(ohos_bt_gatt_server.c),这个函数的完整逻辑清晰展示了动态注册的全过程:
/* ohos_bt_gatt_server.c — BleGattsStartServiceEx */
int BleGattsStartServiceEx(int *srvcHandle, BleGattService *srvcInfo)
{
errcode_t ret = ERRCODE_BT_SUCCESS;
BtUuid btUuid;
/* 步骤1: 从属性列表首项提取服务UUID并注册GATT应用 */
if (srvcInfo->attrNum > 1) {
BleGattAttr *attr = &(srvcInfo->attrList[0]);
ret = convertBtUuid(&btUuid, attr->uuid);
if (ret < 0) { return ret; }
}
ret = BleGattsRegister(btUuid); /* 获取server_id */
if (ret != ERRCODE_BT_SUCCESS) { return ret; }
/* 步骤2: 遍历属性列表,逐项添加到GATT服务器 */
for (unsigned int i = 0; i < srvcInfo->attrNum; i++) {
BleGattAttr *attr = &(srvcInfo->attrList[i]);
ret = convertBtUuid(&btUuid, attr->uuid);
switch (attr->attrType) {
case OHOS_BLE_ATTRIB_TYPE_SERVICE:
ret = BleGattsAddService(g_server_id, btUuid, 1, srvcInfo->attrNum);
break;
case OHOS_BLE_ATTRIB_TYPE_CHAR:
ret = BleGattsAddCharacteristic(g_server_id, g_srvc_handle, btUuid,
attr->properties, attr->permission);
/* 保存write/read回调到全局变量 */
break;
case OHOS_BLE_ATTRIB_TYPE_CHAR_USER_DESCR:
ret = BleGattsAddDescriptor(g_server_id, g_srvc_handle, btUuid, attr->permission);
break;
}
/* 步骤3: 为INDICATE/NOTIFY属性自动添加CCCD描述符 */
if ((attr->properties & OHOS_GATT_CHARACTER_PROPERTY_BIT_INDICATE) ||
(attr->properties & OHOS_GATT_CHARACTER_PROPERTY_BIT_NOTIFY)) {
BtUuid bt_uuid_des = {0};
bt_uuid_des.uuid = g_hilink_cccd_uuid; /* {0x29, 0x02} */
bt_uuid_des.uuidLen = sizeof(g_hilink_cccd_uuid);
ret = BleGattsAddDescriptor(g_server_id, g_srvc_handle, bt_uuid_des,
OHOS_GATT_PERMISSION_READ | OHOS_GATT_PERMISSION_WRITE);
}
}
/* 步骤4: 启动服务 */
ret = BleGattsStartService(g_server_id, g_srvc_handle);
return ret;
}
注册过程分四个阶段。首先从属性列表首项提取服务UUID,调用 BleGattsRegister 获取 g_server_id——这是GATT服务器实例标识,后续所有操作都基于它。BleGattsRegister 内部调用底层SDK的 gatts_register_server 完成注册,并通过回调通知上层。
然后遍历属性列表,根据 attrType 分发到不同的添加函数。遇到 OHOS_BLE_ATTRIB_TYPE_SERVICE 时调用 BleGattsAddService 创建服务容器;遇到 OHOS_BLE_ATTRIB_TYPE_CHAR 时调用 BleGattsAddCharacteristic 添加特征值,同时将属性中携带的回调函数指针保存到全局变量供后续使用。
关键细节在于CCCD的自动添加。当检测到特征值的properties包含 INDICATE 或 NOTIFY 位时,函数自动插入一个UUID为 0x2902 的CCCD描述符,权限设为 READ|WRITE。这意味着应用层在定义Profile时无需显式声明CCCD——协议栈会根据特征值属性自动补全。对于 g_bleDataSvc 的TX特征值(具有INDICATE属性),注册后实际属性表为4项:服务声明、RX特征值、TX特征值、TX的CCCD。
最后调用 BleGattsStartService 激活服务,服务handle通过 g_srvc_handle 返回给调用方。
BleGattsRegister 的配套实现调用底层SDK注册:
int BleGattsRegister(BtUuid appUuid)
{
bt_uuid_t uuid = {0};
uuid.uuid_len = appUuid.uuidLen;
(void)memcpy_s(uuid.uuid, appUuid.uuidLen, (uint8_t *)appUuid.uuid, appUuid.uuidLen);
uint8_t ret = gatts_register_server(&uuid, &g_server_id);
if (g_bleGattsCb->registerServerCb) {
g_bleGattsCb->registerServerCb(0, g_server_id, &appUuid);
}
return ret;
}
六、GATT回调注册机制
在OHOS标准接口层,BleGattsRegisterCallbacks 注册一个 BtGattServerCallbacks 结构体,包含多个异步回调:
/* ohos_bt_gatt_server.c — 回调注册 */
static gatts_callbacks_t g_gattCallback = {
.add_service_cb = add_service_callback,
.add_characteristic_cb = add_characteristic_callback,
.add_descriptor_cb = add_descriptor_callback,
.start_service_cb = start_service_callback,
.stop_service_cb = stop_service_callback,
.delete_service_cb = delete_service_callback,
.read_request_cb = read_request_callback,
.write_request_cb = write_request_callback,
.mtu_changed_cb = mtu_changed_callback,
};
int BleGattsRegisterCallbacks(BtGattServerCallbacks *func)
{
g_bleGattsCb = func;
return gatts_register_callbacks(&g_gattCallback);
}
IOTC适配层在 IotcBleRegisterGattCb 中注册这些回调,将OHOS异步事件转换为IOTC的同步处理模型。write_request_cb 收到写入请求后,通过 g_bleGattsCb 回调通知IOTC核心层,核心层再根据服务handle找到对应的write回调执行业务逻辑。
/* iotc_ble.c — 回调注册 */
int32_t IotcBleRegisterGattCb(const IotcAdptBleGattCallback callback)
{
g_gattEventHandler = callback;
(void)memset_s(&g_regGattAppResult, sizeof(g_regGattAppResult), 0, sizeof(g_regGattAppResult));
int32_t ret = BleGattsRegisterCallbacks(&g_bleGattsCb); /* 注册GATT Server回调 */
ret = BleGattRegisterCallbacks(&g_bleGattCb); /* 注册GAP回调 */
return IOTC_OK;
}
IOTC SDK的回调直接嵌入 BleGattAttr.func 中,无需UUID分发。在 AdapterServiceCopyToOhosGattAttr 转换时,Profile中的 writeFunc/readFunc 直接复制到属性项的 func 字段。当GATT请求到达时,协议栈直接调用属性项中携带的回调函数,无需switch-case区分。每个特征值拥有独立的回调函数,代码结构清晰。
七、服务初始化编排
整个GATT服务注册的入口在 ble_svc.c 的 BleServiceInit 中,按固定顺序初始化BLE协议栈各子系统:
/* ble_svc.c — BleServiceInit */
static int32_t BleServiceInit(BleSvcCtx *ctx)
{
int32_t ret = BleStackInit(); /* 1. 初始化BLE协议栈 */
ret = BleScheduleEventInit(); /* 2. 初始化事件调度 */
ret = BleGattInit(); /* 3. 初始化GATT(数据服务+管理) */
ret = BleAdvSvcInit(ctx); /* 4. 初始化广播 */
ret = BleProfileInit(); /* 5. 初始化Profile(注册链路层服务) */
ret = BleSessInit(); /* 6. 初始化会话管理 */
ret = BleSvcReportInit(); /* 7. 初始化上报 */
ret = BleSvcNetCfgInit(); /* 8. 初始化配网 */
return IOTC_OK;
}
static int32_t BleGattInit(void)
{
int32_t ret = BleGattDataSvcInit(); /* 注册数据GATT服务(静态属性表) */
ret = BleGattMgtInit(); /* 初始化GATT管理(触发服务注册) */
return IOTC_OK;
}
BleGattDataSvcInit 注册数据服务的Profile到链表,BleGattMgtInit 统一将链表中所有Profile转换为适配层格式并调用 IotcBleStartGattsService 完成注册。Profile服务通过 LinkLayerServiceRegister 注册的链路层服务包括 netcfg、device_info、auth_setup、speke、create_session 等,每个服务在初始化时各自调用 BleAddGattSvc 添加到链表。
八、UUID体系与编码
IOTC SDK统一使用128-bit UUID,以十六进制字符串形式声明:
#define DATA_SVC_UUID "15F1E600A27743FCA484DD39EF8A9100"
#define DATA_SVC_READ_UUID "15F1E601A27743FCA484DD39EF8A9100"
#define DATA_SVC_WRITE_UUID "15F1E602A27743FCA484DD39EF8A9100"
在 AdapterServiceCopyToOhosGattAttr 中由 UtilsUnhexifyR 将十六进制字符串转换为字节数组,填入 BleGattAttr.uuid。CH585数据服务的UUID 15f1e600-a277-43fc-a484-dd39ef8a9100 是IOTC SDK为统一互联协议保留的固定UUID,TX和RX特征值分别在末两位递增(601/602)。
CCCD的UUID为BLE标准UUID 0x2902,在 BleGattsStartServiceEx 中自动添加,无需应用层声明。当特征值的properties包含 INDICATE 或 NOTIFY 位时,注册函数自动插入CCCD描述符。
九、OHOS BT状态码
注册流程中所有函数的返回值使用OHOS标准状态码,定义在 ohos_bt_def.h 中:
typedef enum {
OHOS_BT_STATUS_SUCCESS = 0x00,
OHOS_BT_STATUS_FAIL,
OHOS_BT_STATUS_NOT_READY,
OHOS_BT_STATUS_NOMEM,
OHOS_BT_STATUS_BUSY,
OHOS_BT_STATUS_DONE,
OHOS_BT_STATUS_UNSUPPORTED,
OHOS_BT_STATUS_PARM_INVALID,
OHOS_BT_STATUS_UNHANDLED,
OHOS_BT_STATUS_AUTH_FAILURE,
OHOS_BT_STATUS_RMT_DEV_DOWN,
OHOS_BT_STATUS_AUTH_REJECTED,
OHOS_BT_STATUS_DUPLICATED_ADDR
} BtStatus;
适配层用 OhosStatusToAdapterStatus 将 OHOS_BT_STATUS_SUCCESS 映射为 IOTC_ADPT_BLE_STATUS_SUCCESS,在 IotcBleStartGattsService 中检查返回值,非SUCCESS则返回 IOTC_ERROR。
更多推荐
所有评论(0)