SDK 移植

更新时间:2022-11-24 09:20:02下载pdf

当您已经获取到对应的蓝牙 SDK,且将涂鸦蓝牙 SDK 导入进您的应用工程后,即可按照下述步骤,完成 SDK 的移植操作。

请确保蓝牙 SDK 内的 Lib 库是与该芯片平台和编译环境相匹配,否则可能会导致设备无法接入涂鸦体系。更多详情请参考 硬件支持

Config 配置项设置

您必须新建一个 Config 的头文件。例如在工程基础上新建一个名为 custom_tuya_ble_config.h 的头文件,并在 tuya_ble_config.h 头文件中引用新建的 custom_tuya_ble_config.h 配置文件。

然后您可以根据产品实际需求在 custom_tuya_ble_config.h 文件内,对 SDK 的配置项进行设置。配置项详细内容,请参考 Config

此处介绍需要您重点关注的配置项。

  • CUSTOMIZED_TUYA_BLE_CONFIG_FILE

    该配置文件可用来自定义自配置文件名。您需要将创建好的文件名赋值给 CUSTOMIZED_TUYA_BLE_CONFIG_FILE,并添加到整个工程的宏定义中。示例如下:

    CUSTOMIZED_TUYA_BLE_CONFIG_FILE =  <custom_tuya_ble_config.h>
    
  • TUYA_BLE_PORT_PLATFORM_HEADER_FILE

    芯片平台移植代码头文件名定义。创建完 port 文件后,在配置文件中添加相应的 Port 文件定义。示例如下:

    #define TUYA_BLE_PORT_PLATFORM_HEADER_FILE       "tuya_ble_port_nRF52832.h"
    
  • TUYA_BLE_APP_VERSION_STRING

    基于涂鸦蓝牙 SDK 开发的应用固件版本号。示例如下:

    #define TUYA_BLE_APP_VERSION_STRING         "1.0"   //定义固件版本为1.0
    

    每次固件升级需要修改新固件此处的固件版本号。

  • TUYA_BLE_APP_BUILD_FIRMNAME_STRING

    基于涂鸦蓝牙 SDK 开发的应用固件名称,示例如下:

    #define TUYA_BLE_APP_BUILD_FIRMNAME_STRING   "tuya_ble_sdk_demo_nRF52832"
    
  • TUYA_BLE_USE_OS

    固件是否使用 OS。示例如下:

    #define TUYA_BLE_USE_OS 0      //不使用 OS
    
  • TUYA_BLE_DEVICE_COMMUNICATION_ABILITY

    设备能力配置。示例如下:

    #define TUYA_BLE_DEVICE_COMMUNICATION_ABILITY
    (TUYA_BLE_DEVICE_COMMUNICATION_ABILITY_BLE|TUYA_BLE_DEVICE_COMMUNICATION_ABILITY_REGISTER_FROM_BLE)
    //配置能力为 单点蓝牙设备
    
  • TUYA_BLE_DATA_MTU_MAX

    最大传输单元(MTU,MAXIMUM TRANSMISSION UNIT)指在一个协议数据单元(PDU,Protocol Data Unit)能够传输的最大数据量。

    • 蓝牙 4.0 定义了 ATT(Attribute Protocol) 的默认 MTU 为 23 个字节, 除去 ATT 的 opcode 1 个字节以及 ATT 的 handle 2 个字节之后,剩下的 20 个字节便是留给 GATT(Generic Attribute Profile)的,这就是单包数据最大长度为 20 字节的原因。

    • 蓝牙 4.2 开始支持 MTU 交换,MTU 交换是为了在主从双方设置一个 PDU 中能够交换的最大数据量,通过 MTU 的交换和双方确认。主从双方约定每次数据传输时不超过这个最大数据单元。MTU 交换通常发生在主从双方建立连接关系后,如下图所示:

      SDK 移植

      通过 MTU 交换,主从双方选择一个较低的值作为本次连接 MTU。

    手机端蓝牙 MTU 交换在蓝牙建立连接后会自动进行,无需上层应用干预,但是上层应用需要拿到交换的结果 ATT MTU 值,然后通过获取设备信息指令将(ATT MTU - 3)发送给设备,之后的通信就采用该值作为每次写操作的最大值。

    由于设备端定义的 ATT MTU 当前不能超过 247。因此可以将它进行如下设置:

    #define TUYA_BLE_DATA_MTU_MAX         244     //设置 ATT MTU = 247,单包数据长度为 247-3=244
    
  • TUYA_NV_ERASE_MIN_SIZE

    为涂鸦蓝牙 SDK 分配的 NV(Flash)最小擦除单位大小。示例如下:

    #define TUYA_NV_ERASE_MIN_SIZE 4096   //设置擦除单位为 4096 字节:
    
  • TUYA_NV_WRITE_GRAN

    为涂鸦蓝牙 SDK 分配的 NV(Flash)写入粒度大小。示例如下:

    #define TUYA_NV_WRITE_GRAN 4    //配置以 4 字节单位写入
    
  • TUYA_NV_START_ADDR

    为涂鸦蓝牙 SDK 分配的 NV(Flash)起始地址。示例如下:

    #define TUYA_NV_START_ADDR 0x1000
    
  • TUYA_NV_AREA_SIZE

    为涂鸦蓝牙 SDK 分配的 NV(Flash)大小。示例如下:

    #define TUYA_NV_AREA_SIZE      (4*TUYA_NV_ERASE_MIN_SIZE)
    

    取值必须是 TUYA_NV_ERASE_MIN_SIZE 的倍数。

  • TUYA_BLE_LOG_ENABLE

    设置是否打开 SDK 日志。

    建议开发调试阶段,将此设置为1,打开 SDK 日志。稳定后的 release 版代码,可关闭。

  • TUYA_BLE_LOG_COLORS_ENABLE

    设置是否开启 SDK 日志多颜色显示。

  • TUYA_BLE_LOG_LEVEL

    设置 SDK 日志的显示等级。

  • TUYA_APP_LOG_ENABLE

    设置是否开启 App 日志。

    建议开发调试阶段,将此设置为1,打开 SDK 日志。稳定后的 release 版代码,可关闭。

  • TUYA_APP_LOG_COLORS_ENABLE

    设置是否开启 App 日志多颜色显示。

  • TUYA_APP_LOG_LEVEL

    设置 App 日志的显示等级。

Port 层芯片平台移植

Port 文件内的函数为涂鸦蓝牙 SDK 所需要的抽象接口,需要用户根据使用的芯片平台移植实现。

  1. 新建芯片平台 Port 文件,例如命名为 tuya_ble_port_nrf52832.htuya_ble_port_nrf52832.c,将其放入工程中。
  2. 在新建的 port 文件中按照配置实现 tuya_ble_port.h 中所列的接口。
  3. 编写完 Port 文件后,在 Config 配置文件中添加相应的 Port 文件名定义。

更多详情,请参考 Port

您并不需要实现全部的接口,根据情况按需实现即可。例如若不使用 RTOS,就不需要实现 OS 相关接口。如果使用涂鸦蓝牙 SDK 的内部内存管理模块,那么不需要实现内存分配和释放接口。

以下函数为 Port 文件下需要重点实现的函数。

蓝牙 Service 初始化

涂鸦蓝牙 SDK 不提供初始化 Service 相关接口,设备应用程序需要在初始化 SDK 前,实现按下述定义的 Sevice Characteristics。当然,设备应用程序除了定义涂鸦蓝牙 SDK 所需的 Service 外,也可以定义其他 Service。蓝牙广播数据的初始化内容按下方描述实现,否则 SDK 将不能正常工作。

Service 特性

Service UUID 特性 UUID 属性 安全权限 特性值的最大长度
FD50 注1 00000001-0000-1001-8001-00805F9B07D0 Write without response ATT MTU - 3
00000002-0000-1001-8001-00805F9B07D0 Notify ATT MTU - 3
00000003-0000-1001-8001-00805F9B07D0 Read 注2 512
  • FD50:为涂鸦在蓝牙技术联盟注册的 16 位通用唯一识别码

  • Read:该属性只有在使能 LINK 层加密时才需要(即 TUYA_BLE_LINK_LAYER_ENCRYPTION_SUPPORT_ENABLE 配置为 1 时),否则不需要定义。

蓝牙广播包数据结构

涂鸦蓝牙 SDK 在初始化完成后会自动更新蓝牙广播内容,但是设备应用程序在初始化蓝牙广播时需要将广播内容初始化为如下所示的数据。

SDK 移植

涂鸦蓝牙 ADV 数据

广播数据段描述 类型 说明
Flags 0x01 长度:0x02
类型:0x01
数据:0x06
Service UUID 0x02 长度:0x03
类型:0x02
数据:UUID=0xFD50
Service Data 0x16 长度:0x17
类型:0x16
数据:
UUID: 0xFD50
DATA:20 个字节的 0x00
请注意,蓝牙 SDK 在初始化后会自行变更广播参数,在这之前,您需要用户按此格式要求完成广播初始化。

涂鸦蓝牙 Scan Response 数据

广播数据段描述 类型 说明
Manufacturer data 0xFF 长度:0x17
类型:0xFF
数据:
COMPANY ID: 0x07D0
DATA:20 个字节的 0x00
请注意,蓝牙 SDK 在初始化后会自行变更扫描响应包参数,在这之前,您需要按此格式要求完成广播初始化。
Custom Complete Local Name 0x09 长度:Length(最大6个字节)
类型:0x09
数据:Name: (Length - 1)个字节长度的自定义名称

GATT 数据接收

在蓝牙底层接收回调函数下,调用 tuya_ble_gatt_receive_data 函数,该函数用于将蓝牙底层收到的 GATT 数据发送给涂鸦蓝牙 SDK。涂鸦蓝牙 SDK 会对接收到的加密数据进行解析,并返回给应用程序。

必须在涂鸦 Write Characteristic UUID 00000001-0000-1001-8001-00805F9B07D0 的写回调函数中调用。

基于 Nordic nRF52832 示例代码参考:

/**@brief Function for handling the data from the Nordic UART Service.
*
* @details This function will process the data received from the Nordic UART
BLE Service and send
*     it to the UART module.
*
* @param[in] p_evt    Nordic UART Service event.
*/
/**@snippet [Handling the data received over BLE] */
static void nus_commdata_handler(ble_nus_evt_t * p_evt)
{
  if (p_evt->type == BLE_NUS_EVT_RX_DATA)
 {
    tuya_ble_gatt_receive_data((uint8_t*)(p_evt->params.rx_data.p_data),p_evt->params.rx_data.length);
   //将蓝牙底层收到的 GATT 数据发送给涂鸦蓝牙 SDK 解析。
   
 }
}

消息事件主调度器

Main process function 为 SDK 的主引擎,设备应用程序需要一直调用。

在非 OS 架构下,蓝牙 SDK 的消息事件主调度器,应用程序必须在主循环 LOOP 中调用 tuya_ble_main_tasks_exec

基于 Nordic nRF52832 示例代码参考:

static void idle_state_handle(void) //在主循环 LOOP 中调用
{
  ret_code_t err_code;
  err_code = nrf_ble_lesc_request_handler();
  APP_ERROR_CHECK(err_code);
  tuya_ble_main_tasks_exec(); // 消息事件主调度器
  if ((NRF_LOG_PROCESS() == false)&&(tuya_ble_sleep_allowed_check()))
 {
    nrf_pwr_mgmt_run();
 }
}

如果使用 OS,SDK 会基于 Port 层提供的 OS 相关 API 自动创建一个任务用于执行 Main process function

蓝牙断连回调函数

应用程序需要在芯片平台 SDK 的蓝牙连接、断开回调处,调用涂鸦蓝牙 SDK 的 tuya_ble_connected_handler()tuya_ble_disconnected_handler() 这2个函数,SDK 是根据此函数的执行来管理 SDK 内部蓝牙连接状态的。

基于 Nordic nRF52832 示例代码参考:

static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
  uint32_t err_code;
  switch (p_ble_evt->header.evt_id)
 {
  case BLE_GAP_EVT_CONNECTED:
    NRF_LOG_INFO("Connected");
    tuya_ble_connected_handler();  //此处调用 tuya_ble_connected_handler()
    err_code = bsp_indication_set(BSP_INDICATE_CONNECTED);
    APP_ERROR_CHECK(err_code);
    m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
    err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
    APP_ERROR_CHECK(err_code);
    break;
  case BLE_GAP_EVT_DISCONNECTED:   
    NRF_LOG_INFO("Disconnected"); 
    tuya_ble_disconnected_handler(); //此处调用 tuya_ble_disconnected_handler()
    tuya_ota_init_disconnect();
    // LED indication will be changed when advertising starts.
    m_conn_handle = BLE_CONN_HANDLE_INVALID;
    break;
     ....
  default:
    // No implementation needed.
    break;
 }
}

蓝牙 SDK 初始化函数

tuya_ble_sdk_init(tuya_ble_device_param_t * param_data)

函数实现参考如下

static tuya_ble_device_param_t tuya_ble_device_param = {
    .use_ext_license_key = 1, //1-调试阶段设置为1,表示使用以下应用程序传入的 license;固件release后,需要将此设置为 0 ,SDK 将不会使用传入的license,而是通过涂鸦生产烧录软件进行授权。
    .device_id_len       = 16, //如果 use_ext_license_key 为 0,License 通过涂鸦产测工具授权获得。此时,不需要传入 license,将 device_id_len 赋值为 0 即可。
    .p_type              = TUYA_BLE_PRODUCT_ID_TYPE_PID,
    .product_id_len      = 8,
    .adv_local_name_len  = 4,
    .firmware_version    = 0x00000201,  //固件版本 2.1
    .hardware_version    = 0x00000100,  //硬件版本 1.0
    .device_id           = "tuyaf3752699xxxx"  //license 中的参数,可通过以下渠道获取~
    .auth_key            = "4gBM3DK6SRmRn9LTLbyOUAz3bHMGxxxx" ////license中的参数,可通过以下渠道获取~
    .mac_addr_string     = "DC234D12XXXX"  ////license 中的参数,可通过以下渠道获取~
    .product_id          = "suq5jmo5" //填入涂鸦 IoT 平台新建产品时自动生成的 PID
    .adv_local_name      = "demo"  //应用程序自定义蓝牙广播名字的变量
};


void tuya_ble_sdk_demo_init(void)  //封装初始化函数,应用程序必须调用此函数完成初始化
{
    if(device_param.use_ext_license_key==1)
    {
        device_param.device_id_len = 16;  //若使用调试传入license方式,device_id_len=16,否则=0
    }  
    tuya_ble_sdk_init(&tuya_ble_device_param); //蓝牙 SDK初始化函数,否则 SDK不能运行。
    TUYA_APP_LOG_HEXDUMP_INFO("auth key", tuya_ble_current_para.auth_settings.auth_key, AUTH_KEY_LEN);   //打印 auth_key
    TUYA_APP_LOG_HEXDUMP_INFO("device id", tuya_ble_current_para.auth_settings.device_id, DEVICE_ID_LEN);  //打印 device_id

    tuya_ble_callback_queue_register(tuya_ble_sdk_callback);//注册事件回调函数

}
  • 开发调试阶段,常使用 授权码清单 给调试设备授权,获取方式请参考 Bluetooth LE 系列授权

  • 固件稳定后的生产阶段,需要使用工装和 涂鸦云模组烧录授权平台 授权生产设备,请参考 Bluetooth LE 系列授权

注册消息队列

SDK 通过消息(Message)或者设备应用程序注册的回调(Callback)函数向设备应用程序发送数据(状态、数据等)。

如果设备使用 OS,API 的调用将采用基于消息的异步机制。初始化 SDK 时,SDK 将会根据 tuya_ble_config.h 文件的相关配置自动创建一个任务用于处理 SDK 的核心逻辑,同时自动创建一个消息队列用于接收 API 的执行请求,API 的执行结果也将会以消息的方式通知给设备的应用程序,所以应用程序需要创建一个消息队列并在调用 tuya_ble_sdk_init() 后调用 tuya_ble_callback_queue_register() 将消息队列注册至 SDK 中。回调函数为:

tuya_ble_status_t tuya_ble_callback_queue_register(void *cb_queue)

os_msg_queue_create(&tuya_custom_queue_handle,MAX_NUMBER_OF_TUYA_CUSTOM_MESSAGE, sizeof(tuya_ble_cb_evt_param_t));
tuya_ble_callback_queue_register(tuya_custom_queue_handle);  //注册回调函数

void tuya_custom_queue_handle(void *p_param) //应用层可以在这里接收到SDK返回的任何事件,并补充设备逻辑
{
  tuya_ble_cb_evt_param_t event;
  while (true)
 {
    if (os_msg_recv(tuya_custom_queue_handle, &event, 0xFFFFFFFF) == true)
   {
      switch (event.evt)
     {
      case TUYA_BLE_CB_EVT_CONNECTE_STATUS:
        break;
      case TUYA_BLE_CB_EVT_DP_WRITE:      
        break;
      case TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE:
        break;
      case TUYA_BLE_CB_EVT_DP_DATA_WTTH_TIME_REPORT_RESPONSE:
        break;
      case TUYA_BLE_CB_EVT_UNBOUND:
        break;
      case TUYA_BLE_CB_EVT_ANOMALY_UNBOUND:
        break;
      case TUYA_BLE_CB_EVT_DEVICE_RESET:
        break;
      case TUYA_BLE_CB_EVT_DP_QUERY:
        break;
      case TUYA_BLE_CB_EVT_OTA_DATA:
        break;
      case TUYA_BLE_CB_EVT_NETWORK_INFO:
        break;
      case TUYA_BLE_CB_EVT_WIFI_SSID:
        break;
      case TUYA_BLE_CB_EVT_TIME_STAMP:
        break;
      case TUYA_BLE_CB_EVT_TIME_NORMAL:
        break;
      case TUYA_BLE_CB_EVT_DATA_PASSTHROUGH:
        break;
      default:
        break;
     }     
      tuya_ble_event_response(&event);//RTOS架构下,应用代码处理完蓝牙SDK发送来的消息后,必须调用此函数给与蓝牙SDK反馈。
   }
 }
}

若使用非 OS 架构,则需要使用使用原型 2 注册回调函数:

tuya_ble_status_t tuya_ble_callback_queue_register(tuya_ble_callback_t cb)

tuya_ble_callback_queue_register(tuya_ble_sdk_callback);    //注册回调函数

static void tuya_ble_sdk_callback(tuya_ble_cb_evt_param_t* event) //应用层可以在这里接收到 SDK 返回的任何事件,并补充设备逻辑
{
      switch (event->evt)
     {
      case TUYA_BLE_CB_EVT_CONNECTE_STATUS:
        break;
      case TUYA_BLE_CB_EVT_DP_WRITE:      
        break;
      case TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE:
        break;
      case TUYA_BLE_CB_EVT_DP_DATA_WTTH_TIME_REPORT_RESPONSE:
        break;
      case TUYA_BLE_CB_EVT_UNBOUND:
        break;
      case TUYA_BLE_CB_EVT_ANOMALY_UNBOUND:
        break;
      case TUYA_BLE_CB_EVT_DEVICE_RESET:
        break;
      case TUYA_BLE_CB_EVT_DP_QUERY:
        break;
      case TUYA_BLE_CB_EVT_OTA_DATA:
        break;
      case TUYA_BLE_CB_EVT_NETWORK_INFO:
        break;
      case TUYA_BLE_CB_EVT_WIFI_SSID:
        break;
      case TUYA_BLE_CB_EVT_TIME_STAMP:
        break;
      case TUYA_BLE_CB_EVT_TIME_NORMAL:
        break;
      case TUYA_BLE_CB_EVT_DATA_PASSTHROUGH:
        break;
      default:
        break;

}

蓝牙配网

若完成以上内容,基本完成移植相关内容。部分内容需要基于工程单独编译调试。将固件烧录进芯片内,运行固件程序。

若移植完全正确,此时的 SDK 日志打印信息如下,日志显示 PID、device_uuid、auth_key、MAC 均已配置,且 bood_flag 为0,表示此时设备处于未绑定状态。以 JLinkRTTViewer 打印为例:

SDK 移植

  1. 使用蓝牙广播抓包应用,例如 nRF Connect,扫描广播包,可以看到此处有个名为 demo 的蓝牙设备。

    SDK 移植
  2. 点开该设备,了解广播数据,TYPE=16 处,数据为 0X50FD41...

    0X50FD41... 中,0x41 表示设备处于未绑定状态,若设备处于绑定状态,该标志位为 0X49,对应广播数据为 0X50FD49...

    SDK 移植
  3. 打开手机 APP,搜索蓝牙设备。

    SDK 移植
  4. 点击下一步,绑定设备,此时可看到绑定成功。

    SDK 移植
  5. 查看设备日志信息,如下所示:

    SDK 移植

下一步

完成了 SDK 移植后,您可以进入应用程序开发,请查看对应的开发指南。