Pair Over Bluetooth

Last Updated on : 2024-02-18 03:28:23download

This topic describes how pairing over Bluetooth works and how to implement this feature to connect a gateway to the Tuya IoT Development Platform.

Overview

Features

The mobile app sends the Wi-Fi credentials (SSID and password) and activation token to the gateway over Bluetooth. The gateway uses the Wi-Fi credentials to connect to the Wi-Fi network, while using the activation token to bind with the Tuya IoT Development Platform.

The SDK comes with the implementation of the pairing logic. It defines a set of TuyaOS Kernel Layer (TKL) APIs that abstract hardware and system differences, so you only need to adapt the TKL APIs.

How it works

How pairing over Bluetooth works:

  1. After entering pairing mode, the device sends Bluetooth advertising packets.
  2. Upon receiving the advertising packet, the mobile app will send a connection request to the target device.
  3. The mobile app sends the device the encrypted Wi-Fi credentials and token.
  4. The gateway parses the received information, switches to station mode, connects to the Wi-Fi network, and completes activation.

The SDK manages the data content exchanged between the device and mobile app during the pairing process. Your application only needs to handle data reception and transmission.

The following figure shows the process of pairing over Bluetooth.

SDKDeveloperMobile AppCloudInitialize the Bluetoothstack.Set the GAP event callback.Set the GATT eventcallback.Add GATT services andcharacteristics.Set the advertising andscan response data.Bluetooth advertising.Send a scan request.Send a scan response.Establish a Bluetoothconnection.Push a Bluetoothconnection event.Send a write request.Push a write request event.Value notification.Send a value notification.loop[The pairing datatransmission is notfinished.]Switch Wi-Fi to stationmode.Connect to the router'sWi-Fi network.Push Wi-Fi connectionevent.Activation.Return the device's schema.SDKDeveloperMobile AppCloud

Development guide

How to use

  • tkl_ble_stack_init

    OPERATE_RET tkl_ble_stack_init(UCHAR_T role);
    

    During SDK initialization, you can call this API to initialize the Bluetooth stack. An empty implementation is possible for this API.

  • tkl_ble_gap_callback_register

    OPERATE_RET tkl_ble_gap_callback_register(CONST TKL_BLE_GAP_EVT_FUNC_CB gap_evt);
    

    Register Bluetooth Generic Access Profile (GAP) event callbacks. Save the pointer to the gap_evt to the global variable. With this function pointer, notify the SDK of connection to or disconnection from the central device through TKL_BLE_GAP_EVT_CONNECT or TKL_BLE_GAP_EVT_DISCONNECT.

  • tkl_ble_gatt_callback_register

    OPERATE_RET tkl_ble_gatt_callback_register(CONST TKL_BLE_GATT_EVT_FUNC_CB gatt_evt);
    

    Register Bluetooth Generic Attribute Profile (GATT) event callbacks. Save the pointer to the gatt_evt to the global variable. With this function pointer, send the received GATT data to the SDK for processing through TKL_BLE_GATT_EVT_WRITE_REQ.

  • 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)
    

    Set advertising and scan response.

  • tkl_ble_gap_adv_start

    OPERATE_RET tkl_ble_gap_adv_start(TKL_BLE_GAP_ADV_PARAMS_T CONST *p_adv_params);
    

    Start advertising, with the data from tkl_ble_gap_adv_rsp_data_set.

  • tkl_ble_gap_adv_stop

    OPERATE_RET tkl_ble_gap_adv_stop(VOID);
    

    Stop advertising.

  • 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);
    

    Send notifications of changes in characteristics. The SDK calls this API to send data to the mobile app over Bluetooth.

  • tkl_ble_gatts_service_add

    OPERATE_RET tkl_ble_gatts_service_add(TKL_BLE_GATTS_PARAMS_T *p_service);
    

    Add a GATT service.

Data format

Bluetooth advertising and scan response data format:

Pair Over Bluetooth

Bluetooth advertising and scan response comply with Bluetooth specifications. The SDK packages the data into AD Structures, which can be passed through without processing.

With a Bluetooth connection, the mobile app and the gateway transmit data over the Attribute Protocol (ATT). The SDK manages data reception and transmission. You only need to adapt the TKL APIs.

Service and characteristic are defined as follows:

Service UUID: 0x1910

Characteristic UUID Property Description
Write 0x2B11 Write Without Response The mobile app sends data to the Bluetooth LE device.
Notify 0x2B10 Notify The Bluetooth LE device sends a Notify message to the mobile app.

Example

The following example shows how to use BlueZ to implement pairing over Bluetooth. The example code is for reference only. Consider memory management and performance when implementing your program.

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);
}

Things to note

  • Do not block TKL APIs. It is recommended to process time-consuming tasks asynchronously.
  • Adapt both the tkl_bluetooth.h and tkl_wifi.h APIs. For more information about tkl_wifi.h, see Pair Over Wireless Connection.