配网方式-蓝牙

更新时间:2024-06-25 06:16:42下载pdf

本文介绍 SDK 中的 蓝牙配网 功能。首先,将阐述其工作原理,随后详细说明如何实施蓝牙配网,以便实现将网关设备接入涂鸦开发者平台的目标。

概述

功能描述

蓝牙配网是指网关设备与手机 App 通过蓝牙通道传输路由器 SSID、密码以及激活 Token,网关设备接收这些信息后,使用相应的 SSID 和密码连接路由器,并利用激活 Token 绑定到涂鸦开发者平台。

SDK 已内置配网流程的业务逻辑,通过定义一套 TuyaOS Kernel Layer(简称 TKL)接口来屏蔽硬件与系统的差异,您只需适配 TKL 接口即可。

工作原理

蓝牙配网的工作原理如下:

  1. 设备进入配网状态后发送蓝牙广播。
  2. App 扫描到广播后,通过蓝牙主动连接设备。
  3. App 把路由器 SSID、密码以及激活 Token 加密后发给设备。
  4. 设备解析这些信息,然后切换至 Station 模式连接路由器,联网后完成激活流程。

蓝牙配网过程中,设备与 App 传输的数据均由 SDK 处理,您无需关心数据内容,只负责接收和发送即可。

蓝牙配网的流程图如下图所示:

SDK开发者手机 App云端初始化蓝牙协议栈设置 GAP 事件回调设置 GATT 事件回调添加 GATT 服务和属性设置广播和扫描响应数据蓝牙广播扫描请求扫描响应建立蓝牙连接推送蓝牙连接事件Write Request推送 Write Request 事件Value Notification发送 Value Notification 消息loop[配网数据未传输完成]Wi-Fi 切换到 Station 模式连接路由器推送 Wi-Fi 连接事件激活返回设备 schemaSDK开发者手机 App云端

开发指导

使用方法

  • tkl_ble_stack_init

    OPERATE_RET tkl_ble_stack_init(UCHAR_T role);
    

    初始化 SDK 时调用此接口。您可以在接口中初始化蓝牙协议栈,可以空实现。

  • tkl_ble_gap_callback_register

    OPERATE_RET tkl_ble_gap_callback_register(CONST TKL_BLE_GAP_EVT_FUNC_CB gap_evt);
    

    注册蓝牙 Generic Access Profile(GAP)事件回调接口,您应将 gap_evt 函数指针保存至全局变量。当监听到 Central 建立或断开连接时,通过此函数指针,使用 TKL_BLE_GAP_EVT_CONNECTTKL_BLE_GAP_EVT_DISCONNECT 通知 SDK。

  • tkl_ble_gatt_callback_register

    OPERATE_RET tkl_ble_gatt_callback_register(CONST TKL_BLE_GATT_EVT_FUNC_CB gatt_evt);
    

    注册蓝牙 Generic Attribute Profile(GATT)事件回调接口,您应将 gatt_evt 函数指针保存至全局变量。当接收到 GATT 数据时,通过此函数指针,使用 TKL_BLE_GATT_EVT_WRITE_REQ 事件将数据传递给 SDK 进行处理。

  • tkl_ble_gap_adv_rsp_data_set

    OPERATE_RET tkl_ble_gap_adv_rsp_data_set(TKL_BLE_DATA_T CONST *p_adv, TKL_BLE_DATA_T CONST *p_scan_rsp)
    

    设置广播和扫描响应数据接口。

  • tkl_ble_gap_adv_start

    OPERATE_RET tkl_ble_gap_adv_start(TKL_BLE_GAP_ADV_PARAMS_T CONST *p_adv_params);
    

    开启广播接口,广播的数据采用 tkl_ble_gap_adv_rsp_data_set 接口提供的数据。

  • tkl_ble_gap_adv_stop

    OPERATE_RET tkl_ble_gap_adv_stop(VOID);
    

    关闭广播接口。

  • tkl_ble_gatts_value_notify

    OPERATE_RET tkl_ble_gatts_value_notify(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length);
    

    发送 Characteristic 值变化通知接口。SDK 通过蓝牙通道给 App 发送数据时调用此接口。

  • tkl_ble_gatts_service_add

    OPERATE_RET tkl_ble_gatts_service_add(TKL_BLE_GATTS_PARAMS_T *p_service);
    

    添加 GATT Service 接口。

    Characteristic Handle 需要自行处理,涉及以下两处:

    • 对于 tkl_ble_gatts_service_add 接口中的 p_service 参数,需要把 Characteristic Handle 赋值给 p_service 里的 p_service[i].p_char[j].handle,其中 i 表示 Service 的索引,j 表示 Characteristic 的索引。
    • 对于 TKL_BLE_GATT_EVT_WRITE_REQ 事件,需要把 Characteristic Handle 赋值给 event.gatt_event.write_report.char_handle

    如果蓝牙的实现不关心 Characteristic Handle,可以直接把 Characteristic 的 UUID 赋值给 Handle。更多信息,参考使用示例的代码。

数据格式

蓝牙广播和扫描响应的数据格式:

配网方式-蓝牙

蓝牙广播和扫描响应数据遵循蓝牙规范,SDK 已经把广播数据和扫描响应数据封装成 AD Structure 格式,您无需关注数据内容,直接透传即可。

App 和网关设备建立蓝牙连接后,采用蓝牙 The Attribute Protocol(ATT)协议来传输数据,发送数据和接收数据均由 SDK 处理,您只需适配 TKL 接口即可。

Service 和 Characteristic 定义如下:

Service UUID: 0x1910

Characteristic UUID 属性 说明
Write Characteristic 0x2B11 Write Without Response App 向蓝牙低功耗设备发送数据
Notify Characteristic 0x2B10 Notify 蓝牙低功耗设备向 App 发送通知信息

使用示例

以下示例展示了使用如何使用 BlueZ 来实现蓝牙配网功能。代码仅提供实现思路,实际生产代码应考虑内存管理和性能

tkl_bluetooth.c

#include "tkl_bluetooth.h"
#include "tuya_cloud_types.h"
#include "uni_log.h"
#include "mem_pool.h"
#include "uni_queue.h"

#include "tuya_bluez_api.h"

#define BLE_CONN_HANDLE 0x0001

typedef struct {
    UINT16_T    uuid;
    USHORT_T    length;
    UCHAR_T     data[0];
} BLE_CACHE_DATA_T;

STATIC TKL_BLE_GAP_EVT_FUNC_CB  __gap_evt_cb  = NULL;
STATIC TKL_BLE_GATT_EVT_FUNC_CB __gatt_evt_cb = NULL;

STATIC BOOL_T g_connected = FALSE;
STATIC P_QUEUE_CLASS g_cache_queue = NULL;

STATIC VOID __gatt_write_request_event_cb(UINT16_T uuid, UINT8_T *data, UINT8_T len)
{
    INT_T i = 0;
    BLE_CACHE_DATA_T *cache_data = NULL;

    PR_DEBUG("recv write request event, uuid: 0x%04x", uuid);
    for (i = 0; i < len; i++) {
        PR_DEBUG_RAW(" %02x", data[i]);
    }
    PR_DEBUG_RAW("\r\n");

    /**
     * FIXME: BlueZ uses D-BUS for inter-process communication. In our
     *        testing, we faced a problem where data was received before
     *        the connection event was detected. To address this, we are
     *        introducing a caching mechanism.
     */
    if (!g_connected) {
        cache_data = (BLE_CACHE_DATA_T *)Malloc(SIZEOF(BLE_CACHE_DATA_T) + len);
        if (!cache_data) {
            PR_ERR("Malloc err");
            return;
        }
        cache_data->uuid = uuid;
        cache_data->length = len;
        memcpy(cache_data->data, data, len);
        InQueue(g_cache_queue, (unsigned char *)&cache_data, 1);
        return;
    }

    TKL_BLE_GATT_PARAMS_EVT_T event;
    memset(&event, 0, SIZEOF(TKL_BLE_GATT_PARAMS_EVT_T));

    event.result = 0;
    event.type = TKL_BLE_GATT_EVT_WRITE_REQ;
    event.conn_handle = BLE_CONN_HANDLE;
    event.gatt_event.write_report.char_handle = uuid;
    event.gatt_event.write_report.report.p_data = data;
    event.gatt_event.write_report.report.length = len;

    if (__gatt_evt_cb)
        __gatt_evt_cb(&event);
}

STATIC VOID __gap_connect_event_cb(INT_T status)
{
    PR_DEBUG("recv connect event, status: %d", status);

    g_connected = status;

    TKL_BLE_GAP_PARAMS_EVT_T event;
    memset(&event, 0, SIZEOF(TKL_BLE_GAP_PARAMS_EVT_T));

    event.result = 0;
    if (status) {
        event.type = TKL_BLE_GAP_EVT_CONNECT;
    } else {
        event.type = TKL_BLE_GAP_EVT_DISCONNECT;
    }
    event.conn_handle = BLE_CONN_HANDLE;
    event.gap_event.connect.role = TKL_BLE_ROLE_SERVER;

    if (__gap_evt_cb) {
        __gap_evt_cb(&event);
    }

    /**
     * FIXME: BlueZ uses D-BUS for inter-process communication. In our
     *        testing, we faced a problem where data was received before
     *        the connection event was detected. To address this, we are
     *        introducing a caching mechanism.
     */
    while (GetCurQueNum(g_cache_queue)) {
        BLE_CACHE_DATA_T *cache_data = NULL;
        if (!OutQueue(g_cache_queue, (unsigned char *)&cache_data, 1)) {
            break;
        }
        __gatt_write_request_event_cb(cache_data->uuid, cache_data->data, cache_data->length);
        Free(cache_data);
    }
}

OPERATE_RET tkl_ble_stack_init(UCHAR_T role)
{
    PR_INFO("tkl_ble_stack_init");

    g_cache_queue = CreateQueueObj(32, SIZEOF(BLE_CACHE_DATA_T));
    if (!g_cache_queue) {
        PR_ERR("CreateQueueObj error");
        return OPRT_COM_ERROR;
    }

    tuya_bluez_init();
    tuya_bluez_le_register_connect_event(__gap_connect_event_cb);
    tuya_bluez_le_register_write_req_event(__gatt_write_request_event_cb);

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_callback_register(CONST TKL_BLE_GAP_EVT_FUNC_CB gap_evt)
{
    PR_INFO("tkl_ble_gap_callback_register");

    __gap_evt_cb = gap_evt;

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gatt_callback_register(CONST TKL_BLE_GATT_EVT_FUNC_CB gatt_evt)
{
    PR_INFO("tkl_ble_gatt_callback_register");

    __gatt_evt_cb = gatt_evt;

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_adv_start(TKL_BLE_GAP_ADV_PARAMS_T CONST *p_adv_params)
{
    PR_INFO("tkl_ble_gap_adv_start");

    le_set_adv_params_t adv_param = {
        .advtype = p_adv_params->adv_type,
        .min_interval = p_adv_params->adv_interval_min,
        .max_interval = p_adv_params->adv_interval_max,
    };
    tuya_bluez_le_set_adv_params(&adv_param);

    tuya_bluez_le_set_adv_enable(1);

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_adv_stop(VOID)
{
    tuya_bluez_le_set_adv_enable(0);

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_adv_rsp_data_set(TKL_BLE_DATA_T CONST *p_adv, TKL_BLE_DATA_T CONST *p_scan_rsp)
{
    PR_INFO("tkl_ble_gap_adv_rsp_data_set");

    tuya_bluez_le_set_adv_data(p_adv->p_data, p_adv->length);
    tuya_bluez_le_set_scan_rsp_data(p_scan_rsp->p_data, p_scan_rsp->length);

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_adv_rsp_data_update(TKL_BLE_DATA_T CONST *p_adv, TKL_BLE_DATA_T CONST *p_scan_rsp)
{
    PR_INFO("tkl_ble_gap_adv_rsp_data_update");

    return tkl_ble_gap_adv_rsp_data_set(p_adv, p_scan_rsp);
}

OPERATE_RET tkl_ble_gatts_service_add(TKL_BLE_GATTS_PARAMS_T *p_service)
{
    INT_T i = 0, j = 0;
    UINT8_T svc_num = 0;
    le_gatt_service_t *gatt_svc = NULL;

    PR_INFO("tkl_ble_gatts_service_add");

    svc_num = p_service->svc_num;

    gatt_svc = (le_gatt_service_t *)Malloc(svc_num * SIZEOF(le_gatt_service_t));
    if (!gatt_svc) {
        return OPRT_MALLOC_FAILED;
    }
    memset(gatt_svc, 0, svc_num * SIZEOF(le_gatt_service_t));

    for (i = 0; i < svc_num; i++) {
        /* Service Information */
        TKL_BLE_SERVICE_PARAMS_T *p_service_param = &p_service->p_service[i];

        gatt_svc[i].uuid = p_service_param->svc_uuid.uuid.uuid16;
        gatt_svc[i].type = p_service_param->type;
        gatt_svc[i].chr_num = p_service_param->char_num;

        /* Characteristic information*/
        UINT8_T chr_num = p_service_param->char_num;
        le_gatt_characteristic_t *gatt_chr = (le_gatt_characteristic_t *)Malloc(chr_num * SIZEOF(le_gatt_characteristic_t));
        if (!gatt_chr) {
            Free(gatt_svc);
            return OPRT_MALLOC_FAILED;
        }
        memset(gatt_chr, 0, chr_num * SIZEOF(le_gatt_characteristic_t));

        for (j = 0; j < gatt_svc[i].chr_num; j++) {
            gatt_chr[j].uuid = p_service_param->p_char[j].char_uuid.uuid.uuid16;
            gatt_chr[j].property = p_service_param->p_char[j].property;

            /**
             * FIXME: BlueZ does not focus on the handle, so in this
             *        case, we're using the uuid as
             *        the handle value to differentiate the specific
             *        characteristic.
             */
            p_service_param->p_char[j].handle = gatt_chr[j].uuid;
        }

        gatt_svc[i].chr = gatt_chr;
    }

    tuya_bluez_le_add_gatt_service(gatt_svc, svc_num);

    for (i = 0; i < svc_num; i++) {
        if (gatt_svc[i].chr) {
            Free(gatt_svc[i].chr);
        }
    }
    Free(gatt_svc);

    return OPRT_OK;
}

OPERATE_RET tkl_ble_gatts_value_notify(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length)
{
    PR_INFO("tkl_ble_gatts_value_notify, chr_handle: 0x%04x", char_handle);

    tuya_bluez_le_gatts_value_notify(char_handle, p_data, length);

    return OPRT_OK;
}

OPERATE_RET tkl_ble_stack_deinit(UCHAR_T role)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_stack_gatt_link(USHORT_T *p_link)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_addr_set(TKL_BLE_GAP_ADDR_T CONST *p_peer_addr)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_address_get(TKL_BLE_GAP_ADDR_T *p_peer_addr)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_scan_start(TKL_BLE_GAP_SCAN_PARAMS_T CONST *p_scan_params)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_scan_stop(VOID)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_connect(TKL_BLE_GAP_ADDR_T CONST *p_peer_addr, TKL_BLE_GAP_SCAN_PARAMS_T CONST *p_scan_params, TKL_BLE_GAP_CONN_PARAMS_T CONST *p_conn_params)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_disconnect(USHORT_T conn_handle, UCHAR_T hci_reason)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_conn_param_update(USHORT_T conn_handle, TKL_BLE_GAP_CONN_PARAMS_T CONST *p_conn_params)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_tx_power_set(UCHAR_T role, INT_T tx_power)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_rssi_get(USHORT_T conn_handle)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gap_name_set(CHAR_T *p_name)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gatts_value_set(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gatts_value_get(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gatts_value_indicate(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gatts_exchange_mtu_reply(USHORT_T conn_handle, USHORT_T server_rx_mtu)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_all_service_discovery(USHORT_T conn_handle)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_all_char_discovery(USHORT_T conn_handle, USHORT_T start_handle, USHORT_T end_handle)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_char_desc_discovery(USHORT_T conn_handle, USHORT_T start_handle, USHORT_T end_handle)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_write_without_rsp(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_write(USHORT_T conn_handle, USHORT_T char_handle, UCHAR_T *p_data, USHORT_T length)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_read(USHORT_T conn_handle, USHORT_T char_handle)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_gattc_exchange_mtu_request(USHORT_T conn_handle, USHORT_T client_rx_mtu)
{
    return OPRT_OK;
}

OPERATE_RET tkl_ble_vendor_command_control(USHORT_T opcode, VOID_T *user_data, USHORT_T data_len)
{
    return OPRT_NOT_SUPPORTED;
}

tuya_hci.c

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

#include "uni_log.h"

#include "tuya_hci.h"

int tuya_hci_le_set_adv_params(uint16_t min_interval, uint16_t max_interval, uint8_t  advtype)
{
    int device = 0;
    uint8_t status = 0;

    device = hci_open_dev(hci_get_route(NULL));
    if (device < 0) {
        return LE_OPEN_ERROR;
    }

    le_set_advertising_parameters_cp adv_params_cp;
    memset(&adv_params_cp, 0, sizeof(adv_params_cp));
    adv_params_cp.advtype = advtype;
    adv_params_cp.min_interval = htobs(min_interval);
    adv_params_cp.max_interval = htobs(max_interval);
    adv_params_cp.chan_map = 7;

    struct hci_request req;
    memset(&req, 0, sizeof(req));
    req.ogf = OGF_LE_CTL;
    req.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
    req.cparam = &adv_params_cp;
    req.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
    req.rparam = &status;
    req.rlen = 1;

    if (hci_send_req(device, &req, 1000) != 0) {
        hci_close_dev(device);
        return LE_READ_ERROR;
    }

    PR_DEBUG("Set Advertisement Parameters, status 0x%02x", status);

    hci_close_dev(device);

    return LE_SUCCESS;
}

int tuya_hci_le_set_adv_enable(bool enable)
{
    int device = 0;
    uint8_t status = 0;

    device = hci_open_dev(hci_get_route(NULL));
    if (device < 0) {
        return LE_OPEN_ERROR;
    }

    le_set_advertise_enable_cp advertise_cp;
    memset(&advertise_cp, 0, sizeof(advertise_cp));
    advertise_cp.enable = enable;

    struct hci_request req;
    memset(&req, 0, sizeof(req));
    req.ogf = OGF_LE_CTL;
    req.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
    req.cparam = &advertise_cp;
    req.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
    req.rparam = &status;
    req.rlen = 1;

    if (hci_send_req(device, &req, 1000) != 0) {
        hci_close_dev(device);
        return LE_READ_ERROR;
    }

    PR_DEBUG("Set Advertisement %s, status 0x%02x", enable ? "Enable" : "Disable", status);

    hci_close_dev(device);

    return LE_SUCCESS;
}

int tuya_hci_le_set_adv_data(uint8_t *data, uint8_t len)
{
    int device = 0;
    uint8_t status = 0;

    if ((data == NULL) || (len == 0)) {
        return LE_INVALID_PARAM;
    }

    device = hci_open_dev(hci_get_route(NULL));
    if (device < 0) {
        return LE_OPEN_ERROR;
    }

    le_set_advertising_data_cp adv_data_cp;
    memset(&adv_data_cp, 0, sizeof(adv_data_cp));
    memcpy(adv_data_cp.data, data, len);
    adv_data_cp.length = len;

    struct hci_request req;
    memset(&req, 0, sizeof(req));
    req.ogf = OGF_LE_CTL;
    req.ocf = OCF_LE_SET_ADVERTISING_DATA;
    req.cparam = &adv_data_cp;
    req.clen = LE_SET_ADVERTISING_DATA_CP_SIZE;
    req.rparam = &status;
    req.rlen = 1;

    if (hci_send_req(device, &req, 1000) != 0) {
        hci_close_dev(device);
        return LE_READ_ERROR;
    }

    PR_DEBUG("Set Advertisement Data, status 0x%02x", status);

    hci_close_dev(device);

    return LE_SUCCESS;
}

int tuya_hci_le_set_scan_rsp_data(uint8_t *data, uint8_t len)
{
    int device = 0;
    uint8_t status = 0;

    if ((data == NULL) || (len == 0)) {
        return LE_INVALID_PARAM;
    }

    device = hci_open_dev(hci_get_route(NULL));
    if (device < 0) {
        return LE_OPEN_ERROR;
    }

    le_set_scan_response_data_cp scan_rsp_data_cp;
    memset(&scan_rsp_data_cp, 0, sizeof(scan_rsp_data_cp));
    memcpy(scan_rsp_data_cp.data, data, len);
    scan_rsp_data_cp.length = len;

    struct hci_request req;
    memset(&req, 0, sizeof(req));
    req.ogf = OGF_LE_CTL;
    req.ocf = OCF_LE_SET_SCAN_RESPONSE_DATA;
    req.cparam = &scan_rsp_data_cp;
    req.clen = LE_SET_SCAN_RESPONSE_DATA_CP_SIZE;
    req.rparam = &status;
    req.rlen = 1;

    if (hci_send_req(device, &req, 1000) != 0) {
        hci_close_dev(device);
        return LE_READ_ERROR;
    }

    PR_DEBUG("Set Scan Response Data, status 0x%02x", status);

    hci_close_dev(device);

    return LE_SUCCESS;
}

tuya_gatt.c

/**
 * Copy from gatt-service.c
 */
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <glib.h>
#include <dbus/dbus.h>

#include "uni_log.h"

#include "gdbus/gdbus.h"
#include "tuya_gatt.h"

#define ERROR_INTERFACE        "org.bluez.Error"

#define DEVICE_INFACE          "org.bluez.Device1"
#define GATT_MGR_IFACE         "org.bluez.GattManager1"
#define GATT_SERVICE_IFACE     "org.bluez.GattService1"
#define GATT_CHR_IFACE         "org.bluez.GattCharacteristic1"
#define GATT_DESCRIPTOR_IFACE  "org.bluez.GattDescriptor1"

#define PATH_PREFIX    "/com/tuya"

struct characteristic
{
    char *service;
    char *uuid;
    char *path;
    uint8_t *value;
    int vlen;
    uint8_t props;
};

struct descriptor
{
    struct characteristic *chr;
    char *uuid;
    char *path;
    uint8_t *value;
    int vlen;
    uint8_t props;
};

static DBusConnection *connection = NULL;
static GDBusClient *client = NULL;
static GSList *chr_list;

static void (*__gatt_connect_event)(int status) = NULL;
static void (*__gatt_write_request_event)(uint16_t uuid, uint8_t *data, uint8_t len) = NULL;

static gboolean desc_get_uuid(const GDBusPropertyTable *property,
                              DBusMessageIter *iter, void *user_data)
{
    struct descriptor *desc = user_data;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &desc->uuid);

    return TRUE;
}

static gboolean desc_get_characteristic(const GDBusPropertyTable *property,
                                        DBusMessageIter *iter, void *user_data)
{
    struct descriptor *desc = user_data;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
                                   &desc->chr->path);

    return TRUE;
}

static bool desc_read(struct descriptor *desc, DBusMessageIter *iter)
{
    DBusMessageIter array;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
                                     DBUS_TYPE_BYTE_AS_STRING, &array);

    if (desc->vlen && desc->value)
        dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
                                             &desc->value, desc->vlen);

    dbus_message_iter_close_container(iter, &array);

    return true;
}

static gboolean desc_get_value(const GDBusPropertyTable *property,
                               DBusMessageIter *iter, void *user_data)
{
    struct descriptor *desc = user_data;

    PR_DEBUG("Descriptor(%s): Get(\"Value\")", desc->uuid);

    return desc_read(desc, iter);
}

static void desc_write(struct descriptor *desc, const uint8_t *value, int len)
{
    g_free(desc->value);
    desc->value = g_memdup(value, len);
    desc->vlen = len;

    g_dbus_emit_property_changed(connection, desc->path,
                                 GATT_DESCRIPTOR_IFACE, "Value");
}

static int parse_value(DBusMessageIter *iter, const uint8_t **value, int *len)
{
    DBusMessageIter array;

    if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
        return -EINVAL;

    dbus_message_iter_recurse(iter, &array);
    dbus_message_iter_get_fixed_array(&array, value, len);

    return 0;
}

static void desc_set_value(const GDBusPropertyTable *property,
                           DBusMessageIter *iter,
                           GDBusPendingPropertySet id, void *user_data)
{
    struct descriptor *desc = user_data;
    const uint8_t *value;
    int len;

    PR_DEBUG("Descriptor(%s): Set(\"Value\", ...)", desc->uuid);

    if (parse_value(iter, &value, &len))
    {
        PR_ERR("Invalid value for Set('Value'...)");
        g_dbus_pending_property_error(id,
                                      ERROR_INTERFACE ".InvalidArguments",
                                      "Invalid arguments in method call");
        return;
    }

    desc_write(desc, value, len);

    g_dbus_pending_property_success(id);
}

static gboolean desc_get_props(const GDBusPropertyTable *property,
                               DBusMessageIter *iter, void *data)
{
    struct descriptor *desc = data;
    DBusMessageIter array;
    int i;
    char *prop = NULL;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
                                     DBUS_TYPE_STRING_AS_STRING, &array);

    if (desc->props & LE_GATT_CHR_PROP_WRITE_NO_RSP) {
        prop = "write-without-response";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (desc->props & LE_GATT_CHR_PROP_WRITE) {
        prop = "write";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (desc->props & LE_GATT_CHR_PROP_NOTIFY) {
        prop = "notify";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (desc->props & LE_GATT_CHR_PROP_INDICATE) {
        prop = "indicate";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (desc->props & LE_GATT_CHR_PROP_READ) {
        prop = "read";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }

    dbus_message_iter_close_container(iter, &array);

    return TRUE;
}

static const GDBusPropertyTable desc_properties[] = {
    {"UUID", "s", desc_get_uuid},
    {"Characteristic", "o", desc_get_characteristic},
    {"Value", "ay", desc_get_value, desc_set_value, NULL},
    {"Flags", "as", desc_get_props, NULL, NULL},
    {}};

static gboolean chr_get_uuid(const GDBusPropertyTable *property,
                             DBusMessageIter *iter, void *user_data)
{
    struct characteristic *chr = user_data;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &chr->uuid);

    return TRUE;
}

static gboolean chr_get_service(const GDBusPropertyTable *property,
                                DBusMessageIter *iter, void *user_data)
{
    struct characteristic *chr = user_data;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
                                   &chr->service);

    return TRUE;
}

static bool chr_read(struct characteristic *chr, DBusMessageIter *iter)
{
    DBusMessageIter array;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
                                     DBUS_TYPE_BYTE_AS_STRING, &array);

    dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
                                         &chr->value, chr->vlen);

    dbus_message_iter_close_container(iter, &array);

    return true;
}

static gboolean chr_get_value(const GDBusPropertyTable *property,
                              DBusMessageIter *iter, void *user_data)
{
    struct characteristic *chr = user_data;

    PR_DEBUG("Characteristic(%s): Get(\"Value\")", chr->uuid);

    return chr_read(chr, iter);
}

static gboolean chr_get_props(const GDBusPropertyTable *property,
                              DBusMessageIter *iter, void *data)
{
    struct characteristic *chr = data;
    DBusMessageIter array;
    int i;
    char *prop = NULL;

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
                                     DBUS_TYPE_STRING_AS_STRING, &array);

    if (chr->props & LE_GATT_CHR_PROP_WRITE_NO_RSP) {
        prop = "write-without-response";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (chr->props & LE_GATT_CHR_PROP_WRITE) {
        prop = "write";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (chr->props & LE_GATT_CHR_PROP_NOTIFY) {
        prop = "notify";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (chr->props & LE_GATT_CHR_PROP_INDICATE) {
        prop = "indicate";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }
    if (chr->props & LE_GATT_CHR_PROP_READ) {
        prop = "read";
        dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &prop);
    }

    dbus_message_iter_close_container(iter, &array);

    return TRUE;
}

static void chr_write(struct characteristic *chr, const uint8_t *value, int len)
{
    g_free(chr->value);
    chr->value = g_memdup(value, len);
    chr->vlen = len;

    g_dbus_emit_property_changed(connection, chr->path, GATT_CHR_IFACE,
                                 "Value");
}

static void chr_set_value(const GDBusPropertyTable *property,
                          DBusMessageIter *iter,
                          GDBusPendingPropertySet id, void *user_data)
{
    struct characteristic *chr = user_data;
    const uint8_t *value;
    int len;

    PR_DEBUG("Characteristic(%s): Set('Value', ...)", chr->uuid);

    if (!parse_value(iter, &value, &len))
    {
        PR_ERR("Invalid value for Set('Value'...)");
        g_dbus_pending_property_error(id,
                                      ERROR_INTERFACE ".InvalidArguments",
                                      "Invalid arguments in method call");
        return;
    }

    chr_write(chr, value, len);

    g_dbus_pending_property_success(id);
}

static const GDBusPropertyTable chr_properties[] = {
    {"UUID", "s", chr_get_uuid},
    {"Service", "o", chr_get_service},
    {"Value", "ay", chr_get_value, chr_set_value, NULL},
    {"Flags", "as", chr_get_props, NULL, NULL},
    {}};

static gboolean service_get_primary(const GDBusPropertyTable *property,
                                    DBusMessageIter *iter, void *user_data)
{
    dbus_bool_t primary = TRUE;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &primary);

    return TRUE;
}

static gboolean service_get_uuid(const GDBusPropertyTable *property,
                                 DBusMessageIter *iter, void *user_data)
{
    const char *uuid = user_data;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid);

    return TRUE;
}

static gboolean service_get_includes(const GDBusPropertyTable *property,
                                     DBusMessageIter *iter, void *user_data)
{
#if 0
    const char *uuid = user_data;
    char service_path[100] = {
        0,
    };
    DBusMessageIter array;
    char *p = NULL;

    snprintf(service_path, 100, "/service3");
    printf("Get Includes: %s\n", uuid);

    p = service_path;

    printf("Includes path: %s\n", p);

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
                                     DBUS_TYPE_OBJECT_PATH_AS_STRING, &array);

    dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH,
                                   &p);

    snprintf(service_path, 100, "/service2");
    p = service_path;
    printf("Get Includes: %s\n", p);

    dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH,
                                   &p);
    dbus_message_iter_close_container(iter, &array);
#endif

    return TRUE;
}

static gboolean service_exist_includes(const GDBusPropertyTable *property,
                                       void *user_data)
{
    return FALSE;
}

static const GDBusPropertyTable service_properties[] = {
    {"Primary", "b", service_get_primary},
    {"UUID", "s", service_get_uuid},
    {"Includes", "ao", service_get_includes, NULL,
     service_exist_includes},
    {}};

static void chr_iface_destroy(gpointer user_data)
{
    struct characteristic *chr = user_data;

    g_free(chr->uuid);
    g_free(chr->service);
    g_free(chr->value);
    g_free(chr->path);
    g_free(chr);
}

static void desc_iface_destroy(gpointer user_data)
{
    struct descriptor *desc = user_data;

    g_free(desc->uuid);
    g_free(desc->value);
    g_free(desc->path);
    g_free(desc);
}

static int parse_options(DBusMessageIter *iter, const char **device)
{
    DBusMessageIter dict;

    if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
        return -EINVAL;

    dbus_message_iter_recurse(iter, &dict);

    while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY)
    {
        const char *key;
        DBusMessageIter value, entry;
        int var;

        dbus_message_iter_recurse(&dict, &entry);
        dbus_message_iter_get_basic(&entry, &key);

        dbus_message_iter_next(&entry);
        dbus_message_iter_recurse(&entry, &value);

        var = dbus_message_iter_get_arg_type(&value);
        if (strcasecmp(key, "device") == 0)
        {
            if (var != DBUS_TYPE_OBJECT_PATH)
                return -EINVAL;
            dbus_message_iter_get_basic(&value, device);
            PR_DEBUG("Device: %s", *device);
        }

        dbus_message_iter_next(&dict);
    }

    return 0;
}

static DBusMessage *chr_read_value(DBusConnection *conn, DBusMessage *msg,
                                   void *user_data)
{
    struct characteristic *chr = user_data;
    DBusMessage *reply;
    DBusMessageIter iter;
    const char *device;

    if (!dbus_message_iter_init(msg, &iter))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    if (parse_options(&iter, &device))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    reply = dbus_message_new_method_return(msg);
    if (!reply)
        return g_dbus_create_error(msg, DBUS_ERROR_NO_MEMORY,
                                   "No Memory");

    dbus_message_iter_init_append(reply, &iter);

    chr_read(chr, &iter);

    return reply;
}

static DBusMessage *chr_write_value(DBusConnection *conn, DBusMessage *msg,
                                    void *user_data)
{
    struct characteristic *chr = user_data;
    DBusMessageIter iter;
    const uint8_t *value;
    int len;
    const char *device;

    dbus_message_iter_init(msg, &iter);

    if (parse_value(&iter, &value, &len))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    if (parse_options(&iter, &device))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    chr_write(chr, value, len);

    if (__gatt_write_request_event) {
        __gatt_write_request_event((uint16_t)strtol(chr->uuid, NULL, 16), (uint8_t *)value, len);
    }

    return dbus_message_new_method_return(msg);
}

static DBusMessage *chr_start_notify(DBusConnection *conn, DBusMessage *msg,
                                     void *user_data)
{
    return g_dbus_create_error(msg, DBUS_ERROR_NOT_SUPPORTED,
                               "Not Supported");
}

static DBusMessage *chr_stop_notify(DBusConnection *conn, DBusMessage *msg,
                                    void *user_data)
{
    return g_dbus_create_error(msg, DBUS_ERROR_NOT_SUPPORTED,
                               "Not Supported");
}

static const GDBusMethodTable chr_methods[] = {
    {GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({"options", "a{sv}"}),
                        GDBUS_ARGS({"value", "ay"}),
                        chr_read_value)},
    {GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({"value", "ay"}, {"options", "a{sv}"}),
                        NULL, chr_write_value)},
    {GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, chr_start_notify)},
    {GDBUS_METHOD("StopNotify", NULL, NULL, chr_stop_notify)},
    {}};

static DBusMessage *desc_read_value(DBusConnection *conn, DBusMessage *msg,
                                    void *user_data)
{
    struct descriptor *desc = user_data;
    DBusMessage *reply;
    DBusMessageIter iter;
    const char *device;

    if (!dbus_message_iter_init(msg, &iter))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    if (parse_options(&iter, &device))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    reply = dbus_message_new_method_return(msg);
    if (!reply)
        return g_dbus_create_error(msg, DBUS_ERROR_NO_MEMORY,
                                   "No Memory");

    dbus_message_iter_init_append(reply, &iter);

    desc_read(desc, &iter);

    return reply;
}

static DBusMessage *desc_write_value(DBusConnection *conn, DBusMessage *msg,
                                     void *user_data)
{
    struct descriptor *desc = user_data;
    DBusMessageIter iter;
    const char *device;
    const uint8_t *value;
    int len;

    if (!dbus_message_iter_init(msg, &iter))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    if (parse_value(&iter, &value, &len))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    if (parse_options(&iter, &device))
        return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS,
                                   "Invalid arguments");

    desc_write(desc, value, len);

    return dbus_message_new_method_return(msg);
}

static const GDBusMethodTable desc_methods[] = {
    {GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({"options", "a{sv}"}),
                        GDBUS_ARGS({"value", "ay"}),
                        desc_read_value)},
    {GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({"value", "ay"}, {"options", "a{sv}"}),
                        NULL, desc_write_value)},
    {}};

static void register_app_reply(DBusMessage *reply, void *user_data)
{
    DBusError derr;

    dbus_error_init(&derr);
    dbus_set_error_from_message(&derr, reply);

    if (dbus_error_is_set(&derr))
        PR_DEBUG("RegisterApplication: %s", derr.message);
    else
        PR_DEBUG("RegisterApplication: OK");

    dbus_error_free(&derr);
}

static void register_app_setup(DBusMessageIter *iter, void *user_data)
{
    const char *path = "/";
    DBusMessageIter dict;

    dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);

    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);

    /* TODO: Add options dictionary */

    dbus_message_iter_close_container(iter, &dict);
}

static void register_app(GDBusProxy *proxy)
{
    if (!g_dbus_proxy_method_call(proxy, "RegisterApplication",
                                  register_app_setup, register_app_reply,
                                  NULL, NULL))
    {
        PR_ERR("Unable to call RegisterApplication");
        return;
    }
}

static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
{
    const char *iface;

    iface = g_dbus_proxy_get_interface(proxy);

    if (g_strcmp0(iface, GATT_MGR_IFACE))
        return;

    register_app(proxy);
}

static void property_changed_cb(GDBusProxy *proxy, const char *name,
                    DBusMessageIter *iter, void *user_data)
{
    dbus_bool_t conn_status = FALSE;
    const char *interface = g_dbus_proxy_get_interface(proxy);
    const char *path = g_dbus_proxy_get_path(proxy);

    PR_DEBUG("property_changed, path: %s, iface: %s, name: %s", path, interface, name);

    if (!g_strcmp0(interface, DEVICE_INFACE)) {
        if (!g_strcmp0(name, "ServicesResolved")) {
            dbus_message_iter_get_basic(iter, &conn_status);
            if (__gatt_connect_event) {
                __gatt_connect_event(conn_status);
            }
        }
    }
}

int tuya_gatt_init(void)
{
    connection = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);

    if (!g_dbus_attach_object_manager(connection)) {
        PR_ERR("g_dbus_attach_object_manager error");
        return LE_COM_ERROR;
    }

    client = g_dbus_client_new(connection, "org.bluez", "/");

    g_dbus_client_set_proxy_handlers(client, proxy_added_cb, NULL, property_changed_cb,
                                     NULL);

    return LE_SUCCESS;
}

int tuya_gatt_register_service(uint16_t uuid)
{
    if (!connection) {
        PR_WARN("Connection not initialized");
        return LE_COM_ERROR;
    }

    char path[64] = {0};
    char *uuid_str = NULL;

    uuid_str = g_strdup_printf("%04x", uuid);
    g_snprintf(path, sizeof(path), "%s/service%s", PATH_PREFIX, uuid_str);
    if (!g_dbus_register_interface(connection, path, GATT_SERVICE_IFACE,
                                   NULL, NULL, service_properties,
                                   uuid_str, g_free))
    {
        PR_ERR("Couldn't register service interface");
        g_free(uuid_str);
        return LE_COM_ERROR;
    }

    return LE_SUCCESS;
}

int tuya_gatt_register_characteristic(uint16_t svc_uuid, uint16_t chr_uuid, uint8_t props,
                                        uint16_t desc_uuid, uint8_t desc_props)
{
    if (!connection) {
        PR_WARN("Connection not initialized");
        return LE_COM_ERROR;
    }

    struct characteristic *chr = NULL;
    struct descriptor *desc = NULL;

    char chr_path[128] = {0};
    char svc_path[128] = {0};
    char desc_path[128] = {0};

    char svc_uuid_str[32] = {0};
    char chr_uuid_str[32] = {0};
    char desc_uuid_str[32] = {0};

    g_snprintf(svc_uuid_str, sizeof(svc_uuid_str), "%04x", svc_uuid);
    g_snprintf(chr_uuid_str, sizeof(chr_uuid_str), "%04x", chr_uuid);

    g_snprintf(svc_path, sizeof(svc_path), "%s/service%s", PATH_PREFIX, svc_uuid_str);
    g_snprintf(chr_path, sizeof(chr_path), "%s/service%s/characteristic%s", PATH_PREFIX, svc_uuid_str, chr_uuid_str);

    chr = g_new0(struct characteristic, 1);
    chr->uuid = g_strdup(chr_uuid_str);
    chr->props = props;
    chr->service = g_strdup(svc_path);
    chr->path = g_strdup(chr_path);

    if (!g_dbus_register_interface(connection, chr->path, GATT_CHR_IFACE,
                                   chr_methods, NULL, chr_properties,
                                   chr, chr_iface_destroy))
    {
        PR_ERR("Couldn't register characteristic interface");
        chr_iface_destroy(chr);
        return LE_COM_ERROR;
    }

    chr_list = g_slist_append(chr_list, chr);

    if (!desc_uuid)
        return LE_SUCCESS;

    g_snprintf(desc_uuid_str, sizeof(desc_uuid_str), "%04x", desc_uuid);
    g_snprintf(desc_path, sizeof(desc_path), "%s/service%s/characteristic%s/descriptor%s", PATH_PREFIX, svc_uuid_str, chr_uuid_str, desc_uuid_str);

    desc = g_new0(struct descriptor, 1);
    desc->uuid = g_strdup(desc_uuid_str);
    desc->chr = chr;
    desc->props = desc_props;
    desc->path = g_strdup(desc_path);

    if (!g_dbus_register_interface(connection, desc->path,
                                   GATT_DESCRIPTOR_IFACE,
                                   desc_methods, NULL, desc_properties,
                                   desc, desc_iface_destroy))
    {
        PR_ERR("Couldn't register descriptor interface");
        g_dbus_unregister_interface(connection, chr->path,
                                    GATT_CHR_IFACE);

        desc_iface_destroy(desc);
        return LE_COM_ERROR;
    }

    return LE_SUCCESS;
}

int tuya_gatt_server_send_characteristic_notification(uint16_t uuid, uint8_t *data, uint8_t len)
{
    struct characteristic *chr = NULL;
    GSList *c = chr_list;

    while (c != NULL) {
        chr = (struct characteristic *)c->data;
        if (uuid == strtol(chr->uuid, NULL, 16)) {
            break;
        }
        c = c->next;
    }

    if (c) {
        chr_write(chr, data, len);
    }

    return LE_SUCCESS;
}

void tuya_gatt_register_connect_event(void(*cb)(int status))
{
    __gatt_connect_event = cb;
}

void tuya_gatt_register_write_req_event(void(*cb)(uint16_t uuid, uint8_t *data, uint8_t len))
{
    __gatt_write_request_event = cb;
}

tuya_bluez_api.c

#include <glib.h>
#include <pthread.h>

#include "uni_log.h"

#include "tuya_bluez_api.h"
#include "tuya_hci.h"
#include "tuya_gatt.h"

static int g_bluez_inited = FALSE;
static GMainLoop *main_loop;

static void *__loop_run(void *arg)
{
    g_main_loop_run((GMainLoop *)arg);

    return NULL;
}

int tuya_bluez_init(void)
{
    int ret = 0;
    pthread_t tid;

    if (g_bluez_inited) {
        PR_WARN("tuya bluez had been initialized");
        return LE_SUCCESS;
    }

    main_loop = g_main_loop_new(NULL, FALSE);

    ret = tuya_gatt_init();
    if (ret != 0) {
        PR_ERR("tuya_gatt_init error");
        return ret;
    }

    pthread_create(&tid, NULL, __loop_run, main_loop);

    g_bluez_inited = TRUE;

    return LE_SUCCESS;
}

int tuya_bluez_deinit(void)
{
    return 0;
}

int tuya_bluez_le_set_adv_params(le_set_adv_params_t *params)
{
    if (params == NULL) {
        return LE_INVALID_PARAM;
    }

    return tuya_hci_le_set_adv_params(params->min_interval, params->max_interval, params->advtype);
}

int tuya_bluez_le_set_adv_enable(bool enable)
{
    return tuya_hci_le_set_adv_enable(enable);
}

int tuya_bluez_le_set_adv_data(uint8_t *data, uint8_t len)
{
    if ((data == NULL) || (len == 0)) {
        return LE_INVALID_PARAM;
    }

    return tuya_hci_le_set_adv_data(data, len);
}

int tuya_bluez_le_set_scan_rsp_data(uint8_t *data, uint8_t len)
{
    if ((data == NULL) || (len == 0)) {
        return LE_INVALID_PARAM;
    }

    return tuya_hci_le_set_scan_rsp_data(data, len);
}

int tuya_bluez_le_add_gatt_service(le_gatt_service_t *service, uint8_t service_num)
{
    PR_DEBUG("register gatt service, num: %d", service_num);

    if ((service == NULL) || (service_num == 0)) {
        return LE_INVALID_PARAM;
    }

    int i = 0, j = 0;
    uint16_t svc_uuid = 0x0000;
    le_gatt_characteristic_t *p_chr = NULL;

    for (i = 0; i < service_num; i++) {
        svc_uuid = service[i].uuid;
        if (tuya_gatt_register_service(svc_uuid) != LE_SUCCESS) {
            PR_ERR("tuya_gatt_register_service error");
            continue;
        }
        p_chr = service[i].chr;
        for (j = 0; j < service[i].chr_num; j++) {
            if (tuya_gatt_register_characteristic(svc_uuid, p_chr[j].uuid, p_chr[j].property, 0, 0) != LE_SUCCESS) {
                PR_ERR("tuya_gatt_register_characteristic error");
                break;
            }
        }
    }

    return LE_SUCCESS;
}

int tuya_bluez_le_gatts_value_notify(uint16_t uuid, uint8_t *value, uint16_t len)
{
    return tuya_gatt_server_send_characteristic_notification(uuid, value, len);
}

void tuya_bluez_le_register_connect_event(void(*cb)(int status))
{
    tuya_gatt_register_connect_event(cb);
}

/**
 * @brief Register write request event callback
 */
void tuya_bluez_le_register_write_req_event(void(*cb)(uint16_t uuid, uint8_t *data, uint8_t len))
{
    tuya_gatt_register_write_req_event(cb);
}

注意事项

  • TKL 接口不允许阻塞,耗时的任务建议做异步处理。
  • 蓝牙配网除了要适配 tkl_bluetooth.h 接口,还需要适配 tkl_wifi.h 接口。关于 tkl_wifi.h 接口适配,参考 热点配网