一、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;

ProfileSvcCopyToAdapterSvcble_gatt_mgt.cBleGattProfileSvcInit 中被调用,遍历通过 BleAddGattSvc 注册到链表的所有Profile服务,逐一转换为适配层格式。

2.3 OHOS C API结构

最底层是OHOS C API结构 BleGattServiceBleGattAttr,定义在 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动态注册的核心——回调函数直接嵌入属性项中,注册时随属性一起传递给协议栈。

AdapterServiceCopyToOhosGattAttriotc_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包含 INDICATENOTIFY 位时,函数自动插入一个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.cBleServiceInit 中,按固定顺序初始化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包含 INDICATENOTIFY 位时,注册函数自动插入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;

适配层用 OhosStatusToAdapterStatusOHOS_BT_STATUS_SUCCESS 映射为 IOTC_ADPT_BLE_STATUS_SUCCESS,在 IotcBleStartGattsService 中检查返回值,非SUCCESS则返回 IOTC_ERROR


Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐