涂鸦协议接入
本文档介绍 AC63_BT_SDK 中涂鸦(Tuya)蓝牙协议栈的接入方式,涵盖 SDK 目录结构、初始化流程、DP 数据收发、事件回调、JL 平台移植层、OTA、产测与 UART 通用处理等完整实现机制,帮助开发者将涂鸦智能生态(涂鸦智能 App、云平台)接入基于杰理 AC63 系列芯片的蓝牙产品。
Purpose and Scope
本页面向需要将产品接入涂鸦智能生态的开发者,完整说明仓库中 apps/common/third_party_profile/tuya_protocol/ 目录所承载的涂鸦 BLE 协议栈如何与杰理蓝牙协议栈(BLE GATT、UART、Flash、OS)协同工作,以及应用层(Demo、产测、OTA、UART 透传)如何基于 SDK 实现设备功能。
本页不包含以下内容,它们属于其他目录项:
- 涂鸦 Mesh 灯控模型(
apps/mesh/examples/TUYA_light.c、SIG_MESH_TUYA_LIGHT)请参见 Mesh 相关页面。 - 腾讯、阿里(天猫精灵)等第三方协议接入请参见各自的 third_party_profile 页面。
- 通用 BLE 协议栈、GATT 服务、广播配置等基础能力请参见蓝牙协议栈相关页面。
概述
涂鸦协议接入的本质是:在杰理 AC63 平台(作为 BLE 从机)上运行涂鸦官方 BLE SDK,通过 SDK 提供的设备参数(PID、auth key、device id、MAC)完成与涂鸦云的认证与绑定,通过 DP 点(Data Point) 机制与涂鸦智能 App 双向同步设备状态,并借助事件回调、OTA、产测等扩展能力形成完整的产品闭环。
仓库中的涂鸦代码按三层组织:
| 层级 | 目录 | 职责 |
|---|---|---|
| 应用层 | app/demo、app/product_test、app/uart_common | 设备业务逻辑:DP 点解析、LED 控制、OTA、产测指令、UART 透传 |
| SDK 层 | sdk/include(及未列出的 sdk 源文件) | 涂鸦官方协议栈:DP 编解码、加密(AES/MD5)、绑定、事件队列、GATT 发送队列 |
| 移植层 | port/ | 把涂鸦 SDK 抽象接口(广播、扫描应答、时间、Flash、日志、随机数、加密等)映射到 JL 平台 |
SDK 对外暴露的入口 API 集中在 sdk/include/tuya_ble_api.h,应用只需按序调用 tuya_ble_sdk_init() → tuya_ble_callback_queue_register(),并在 BLE 收包处调用 tuya_ble_gatt_receive_data(),即可完成协议栈的启动与数据通路建立。涂鸦 SDK 与 JL 平台之间通过 port/tuya_ble_port_JL.c 等移植文件适配,保证 SDK 无平台依赖。
架构
flowchart TD
subgraph sg_App["应用层 (apps/common/third_party_profile/tuya_protocol/app)"]
Demo["tuya_ble_app_demo<br/>DP 解析 / LED 控制 / 回调注册"]
OTA["tuya_ota<br/>OTA 数据处理"]
ProdTest["tuya_ble_app_production_test<br/>产测指令"]
UartHandler["tuya_ble_app_uart_common_handler<br/>UART 通用处理"]
end
subgraph sg_Sdk["涂鸦 SDK 层 (sdk)"]
Api["tuya_ble_api.h<br/>对外 API 入口"]
DataHandler["tuya_ble_data_handler<br/>DP 编解码"]
Event["tuya_ble_event<br/>事件与回调队列"]
Bulk["tuya_ble_bulk_data<br/>批量数据传输"]
GattQ["tuya_ble_gatt_send_queue<br/>GATT 发送队列"]
Feature["tuya_ble_feature_weather<br/>天气特性"]
end
subgraph sg_Port["移植层 (port)"]
PortJL["tuya_ble_port_JL.c<br/>广播/扫描应答/时间/加密"]
PortPeriph["tuya_ble_port_peripheral<br/>外设接口"]
Stdlib["tuya_ble_stdlib.h<br/>标准库映射"]
end
subgraph sg_Platform["JL 平台"]
BLE["杰理 BLE 协议栈<br/>GATT / 广播"]
UART["UART 外设"]
Flash["Flash 存储"]
OS["OS 任务/信号量"]
Crypto["AES / MD5 硬件加速"]
end
Demo --> Api
OTA --> Api
ProdTest --> Api
UartHandler --> Api
Api --> DataHandler
Api --> Event
Api --> GattQ
Api --> Bulk
Api --> Feature
DataHandler --> PortJL
Event --> PortJL
PortJL --> BLE
PortJL --> UART
PortJL --> Flash
PortJL --> OS
PortJL --> Crypto
PortPeriph --> BLE
Stdlib --> OS
架构说明:
- 应用层是开发者主要改动的部分:
tuya_ble_app_demo.c演示了完整的接入范式——构造tuya_ble_device_param_t、调用tuya_ble_sdk_init()、注册回调、在回调里处理 DP 数据并控制 GPIO。tuya_ota.c、tuya_ble_app_production_test.c、tuya_ble_app_uart_common_handler.c则是可选能力,分别处理固件升级、产线测试、WIFI+BLE 组合设备的 UART 转发。 - SDK 层以
tuya_ble_api.h为唯一对外入口,内部的数据处理、事件分发、GATT 发送队列、批量数据、天气特性等模块对应用透明。协议版本由宏TUYA_BLE_PROTOCOL_VERSION_HIGN区分(仓库 Demo 支持 0x03/0x04 两套 DP 上报 API)。 - 移植层把 SDK 的弱符号(
__TUYA_BLE_WEAK)实现覆盖为 JL 平台实现。例如广播数据更新时,根据CONFIG_BT_GATT_COMMON_ENABLE选择走 GATT 公共服务还是传统ble_op_*接口,并通过注册的回调钩子注入广播内容。 - JL 平台提供 BLE 协议栈、UART、Flash、OS 与 AES/MD5 加密能力,是涂鸦 SDK 的运行时底座。
apps/spp_and_le/app_main.c在TUYA_DEMO_EN下创建user_deal线程承载涂鸦任务调度,CONFIG_APP_TUYA将应用动作绑定为ACTION_TUYA。
SDK 初始化流程
涂鸦 BLE 协议栈的启动遵循"先初始化 SDK,再注册回调"的固定顺序。Demo 中的 tuya_ble_app_init() 是标准范式:
void tuya_ble_app_init(void)
{
device_param.device_id_len = 16; //If use the license stored by the SDK,initialized to 0, Otherwise 16 or 20.
int ret = 0;
device_param.use_ext_license_key = 1;
if (device_param.device_id_len == 16) {
memcpy(device_param.auth_key, (void *)auth_key_test, AUTH_KEY_LEN);
memcpy(device_param.device_id, (void *)device_id_test, DEVICE_ID_LEN);
memcpy(device_param.mac_addr.addr, mac_test, 6);
device_param.mac_addr.addr_type = TUYA_BLE_ADDRESS_TYPE_RANDOM;
}
device_param.p_type = TUYA_BLE_PRODUCT_ID_TYPE_PID;
device_param.product_id_len = 8;
memcpy(device_param.product_id, APP_PRODUCT_ID, 8);
device_param.firmware_version = TY_APP_VER_NUM;
device_param.hardware_version = TY_HARD_VER_NUM;
printf("HJY:12345\n");
tuya_ble_sdk_init(&device_param);
ret = tuya_ble_callback_queue_register(tuya_cb_handler);
y_printf("tuya_ble_callback_queue_register,ret=%d\n", ret);
//tuya_ota_init();
//TUYA_APP_LOG_INFO("demo project version : "TUYA_BLE_DEMO_VERSION_STR);
TUYA_APP_LOG_INFO("app version : "TY_APP_VER_STR);
}
Source: tuya_ble_app_demo.c
关键点与设计意图:
device_id_len与use_ext_license_key共同决定授权信息的来源。Demo 将device_id_len置为 16 并开启外部授权(use_ext_license_key=1),此时必须手动填入 16 字节 device id、32 字节 auth key 与 6 字节 MAC;若置 0,则由 SDK 内部存储的 license 提供,App 侧无需关心密钥细节。涂鸦云端绑定正是基于这套三元组完成设备身份认证。p_type = TUYA_BLE_PRODUCT_ID_TYPE_PID表示使用产品 PID(8 字节)而非二维码/其他类型标识产品;APP_PRODUCT_ID宏来自tuya_ble_app_demo.h(如'y','j','f','s','5','0','6','f'),需替换为涂鸦 IoT 平台为产品分配的 PID。tuya_ble_sdk_init()必须在所有平台初始化完成后调用(API 注释明确要求 "after all platform init complete"),因为 SDK 内部会立即申请任务、初始化定时器与加密模块,这些依赖 JL 的 OS 与硬件驱动就绪。- 回调注册紧随 init:
tuya_ble_callback_queue_register(tuya_cb_handler)把应用处理函数挂入 SDK 的事件队列,此后所有上行业务事件(连接状态、DP 下发、OTA 数据、解绑等)都会以队列方式异步进入tuya_cb_handler。
初始化时序
sequenceDiagram
participant App as 应用层 (tuya_ble_app_init)
participant Sdk as 涂鸦 SDK (tuya_ble_api)
participant Port as 移植层 (tuya_ble_port_JL)
participant Jl as JL 平台 (BLE/OS/Crypto)
App->>App: 填充 device_param<br/>(PID/auth_key/device_id/MAC/版本)
App->>Sdk: tuya_ble_sdk_init(&device_param)
Sdk->>Port: 平台初始化回调 (task/定时器/随机数/加密)
Port->>Jl: os_task / timer / aes / md5
Jl-->>Port: 资源就绪
Sdk->>Sdk: 解析授权信息、初始化内部状态机
App->>Sdk: tuya_ble_callback_queue_register(tuya_cb_handler)
Sdk-->>App: 注册成功 (ret)
Note over Sdk,Jl: BLE 连接事件到达时
Jl->>Port: GATT 连接回调
Port->>Sdk: tuya_ble_connected_handler()
Sdk-->>App: 事件入队 TUYA_BLE_CB_EVT_CONNECTE_STATUS
初始化完成后,SDK 进入等待广播/连接状态;每次 BLE 连接、断开、链路加密成功,JL 协议栈侧必须把事件透传给 SDK(tuya_ble_connected_handler() / tuya_ble_disconnected_handler() / tuya_ble_link_encrypted_handler()),否则 SDK 的绑定与加密状态机不会推进。
数据通路:GATT 收包入口
涂鸦 SDK 本身不直接操作 BLE 控制器,而是要求平台在收到对端数据时"喂"给 SDK。tuya_ble_api.h 定义了收包入口:
/**
* @brief Function for transmit ble data from peer devices to tuya sdk.
*
* @note This function must be called from where the ble data is received.
*.
* */
tuya_ble_status_t tuya_ble_gatt_receive_data(uint8_t *p_data, uint16_t len);
/**
* @brief Function for transmit uart data to tuya sdk.
*
* @note This function must be called from where the uart data is received.
*.
* */
tuya_ble_status_t tuya_ble_common_uart_receive_data(uint8_t *p_data, uint16_t len);
Source: tuya_ble_api.h
设计意图:SDK 采用"外部分发、内部解析"的架构——平台只负责把原始字节送入 SDK,协议帧校验、加密解密、DP 编解码全部在 SDK 内部完成。这既保证了 SDK 可移植性(不绑定任何厂商的 GATT 实现),也让平台侧接入成本降到最低:只需在 GATT 写回调/通知回调里调用一次 tuya_ble_gatt_receive_data()。同理,WIFI+BLE 组合设备通过 tuya_ble_common_uart_receive_data() 把 MCU 侧 UART 数据送入 SDK;若应用自己有完整的帧解析逻辑,也可直接调用 tuya_ble_common_uart_send_full_instruction_received() 提交完整指令(含 0x55/0x66 0xaa 帧头与校验和)。
事件回调机制与 DP 数据处理
事件回调总入口
涂鸦 SDK 通过统一回调 tuya_cb_handler(tuya_ble_cb_evt_param_t *event) 向上层分发所有业务事件。Demo 中的处理分支覆盖了典型产品需要的全部事件类型:
static void tuya_cb_handler(tuya_ble_cb_evt_param_t *event)
{
int16_t result = 0;
printf("tuya cb_handler event->evt=0x%x\n", event->evt);
switch (event->evt) {
case TUYA_BLE_CB_EVT_CONNECTE_STATUS:
TUYA_APP_LOG_INFO("received tuya ble conncet status update event,current connect status = %d", event->connect_status);
break;
case TUYA_BLE_CB_EVT_DP_DATA_RECEIVED:
tuya_data_parse(event);
break;
case TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE:
TUYA_APP_LOG_INFO("received dp data report response result code =%d", event->dp_response_data.status);
break;
...
case TUYA_BLE_CB_EVT_DP_QUERY:
TUYA_APP_LOG_INFO("received TUYA_BLE_CB_EVT_DP_QUERY event");
if (dp_data_len > 0) {
#if (TUYA_BLE_PROTOCOL_VERSION_HIGN == 0x03)
tuya_ble_dp_data_report(dp_data_array, dp_data_len);
#endif
#if (TUYA_BLE_PROTOCOL_VERSION_HIGN == 0x04)
tuya_ble_dp_data_send(sn, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITH_RESPONSE, dp_data_array, dp_data_len);
#endif
}
break;
case TUYA_BLE_CB_EVT_OTA_DATA:
tuya_ota_proc(event->ota_data.type, event->ota_data.p_data, event->ota_data.data_len);
break;
case TUYA_BLE_CB_EVT_NETWORK_INFO:
TUYA_APP_LOG_INFO("received net info : %s", event->network_data.p_data);
tuya_ble_net_config_response(result);
break;
case TUYA_BLE_CB_EVT_TIME_STAMP:
TUYA_APP_LOG_INFO("received unix timestamp : %s ,time_zone : %d", event->timestamp_data.timestamp_string, event->timestamp_data.time_zone);
tuya_data_init();
break;
case TUYA_BLE_CB_EVT_DATA_PASSTHROUGH:
TUYA_APP_LOG_HEXDUMP_DEBUG("received ble passthrough data :", event->ble_passthrough_data.p_data, event->ble_passthrough_data.data_len);
tuya_ble_data_passthrough(event->ble_passthrough_data.p_data, event->ble_passthrough_data.data_len);
break;
default:
TUYA_APP_LOG_WARNING("app_tuya_cb_queue msg: unknown event type 0x%04x", event->evt);
break;
}
tuya_ble_inter_event_response(event);
}
Source: tuya_ble_app_demo.c
设计要点:
- 回调末尾必须调用
tuya_ble_inter_event_response(event)释放事件资源,否则 SDK 内部事件缓冲会泄漏,最终导致收不到新事件——这是接入时最容易遗漏的一行。 TUYA_BLE_CB_EVT_DP_QUERY(App 主动查询 DP 状态)要求设备缓存最近一次上报的 DP 数据(Demo 用dp_data_array/dp_data_len静态缓存),并在收到查询时重发,保证 App 打开即见最新状态。TUYA_BLE_CB_EVT_TIME_STAMP到达时调用tuya_data_init()主动上报一次 DP,利用 App 校准后的时间戳同步设备状态,典型场景是设备上电后首次配对。- 协议版本宏
TUYA_BLE_PROTOCOL_VERSION_HIGN决定使用哪套发送 API:v3 用tuya_ble_dp_data_report(),v4 用tuya_ble_dp_data_send()(携带 sn、发送类型、模式、ack 参数)。两套 API 在tuya_ble_api.h中以#if分别编译,仓库 Demo 对两版均做了兼容。
DP 上报(设备 → App)
tuya_data_init() 演示了构造并上报一个 DP 点的完整过程:
void tuya_data_init()
{
struct {
uint8_t id;
uint8_t type;
uint16_t len;
uint8_t data;
} p_dp_data;
p_dp_data.id = 1;
p_dp_data.type = 1;
p_dp_data.len = 0x0100;
p_dp_data.data = tuya_led_state;
#if (TUYA_BLE_PROTOCOL_VERSION_HIGN == 0x03)
tuya_ble_dp_data_report(&p_dp_data, 5); //1
#endif
#if (TUYA_BLE_PROTOCOL_VERSION_HIGN == 0x04)
tuya_ble_dp_data_send(sn, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITH_RESPONSE, &p_dp_data, 5);
#endif
}
Source: tuya_ble_app_demo.c
DP 帧布局(Demo 结构体)为:id(DP 点编号)+ type(数据类型,1 表示 bool)+ len(长度字段,小端,Demo 中 0x0100 表示长度 1)+ data(数据负载)。SDK 会将其封装为涂鸦协议帧并通过 GATT 发送队列发出,发送结果以 TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE 事件回传 status。
DP 下发(App → 设备)
tuya_data_parse() 演示了解析 App 下发的 DP 数据并执行动作:
void tuya_data_parse(tuya_ble_cb_evt_param_t *event)
{
uint8_t *buf = event->dp_received_data.p_data;
uint32_t sn = event->dp_received_data.sn;
put_buf(buf, event->dp_received_data.data_len);
struct {
uint8_t id;
uint8_t type;
uint16_t len;
uint8_t data;
} p_dp_data;
p_dp_data.id = buf[0];
p_dp_data.type = buf[1];
p_dp_data.len = 0x0100;
p_dp_data.data = buf[4];
switch (buf[0]) {
case 1:
printf("tuya switch control, onoff set to: %d\n", p_dp_data.data);
tuya_led_state = p_dp_data.data;
gpio_direction_output(LED_PIN, tuya_led_state);
break;
default:
printf("unknow control msg len = %d, data:", buf[2]);
break;
}
#if (TUYA_BLE_PROTOCOL_VERSION_HIGN == 0x03)
tuya_ble_dp_data_report(&p_dp_data, 5); //1
#endif
#if (TUYA_BLE_PROTOCOL_VERSION_HIGN == 0x04)
tuya_ble_dp_data_send(sn, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITH_RESPONSE, &p_dp_data, 5);
#endif
}
Source: tuya_ble_app_demo.c
设计意图:收到 App 控制指令后先执行实际动作(Demo 中通过 gpio_direction_output(LED_PIN, ...) 控制 IO_PORTA_01 上的 LED),再把同一 DP 数据原样回传,形成"执行 + 回执"闭环。这样 App 端能立即拿到设备确认后的状态,即使设备本地状态与 App 期望不一致(例如开关联动、异常保护导致拒绝执行),回执也能如实反映真实状态。实际产品中应把 switch(buf[0]) 扩展为产品定义的每个 DP 点 id,并在 default 分支做异常处理。
端到端 DP 数据流
sequenceDiagram
participant App as 涂鸦智能 App
participant JlBle as JL BLE 协议栈
participant Port as 移植层/收包入口
participant Sdk as 涂鸦 SDK
participant Cb as 应用回调 tuya_cb_handler
participant Dev as 设备 GPIO/业务
App->>JlBle: GATT Write (DP 控制帧)
JlBle->>Port: 平台收包
Port->>Sdk: tuya_ble_gatt_receive_data(p_data, len)
Sdk->>Sdk: 帧校验/解密/DP 解析
Sdk->>Cb: TUYA_BLE_CB_EVT_DP_DATA_RECEIVED 入队
Cb->>Dev: tuya_data_parse() → GPIO 控制
Dev-->>Cb: 执行结果
Cb->>Sdk: tuya_ble_dp_data_send / report (回执)
Sdk->>JlBle: GATT 发送队列 → 通知
JlBle-->>App: Notification (DP 回执)
自定义事件
SDK 还提供应用自定义事件通道,用于在 SDK 任务上下文之外传递业务数据:
void custom_evt_1_send_test(uint8_t data)
{
tuya_ble_custom_evt_t event;
for (uint8_t i = 0; i < 50; i++) {
custom_data.data[i] = data;
}
event.evt_id = APP_CUSTOM_EVENT_1;
event.custom_event_handler = (void *)custom_data_process;
event.data = &custom_data;
tuya_ble_custom_event_send(&event);
}
Source: tuya_ble_app_demo.c
tuya_ble_custom_event_send() 把 {evt_id, handler, data} 打包投递到 SDK 事件队列,由注册的 custom_data_process() 在 SDK 上下文异步消费(Demo 中演示了 5 个自定义事件 id 的开关分支)。这为"非 BLE 中断上下文触发的业务上报"提供了线程安全入口,避免应用直接跨任务操作 SDK 内部数据结构。
JL 平台移植层(port)
涂鸦 SDK 通过弱符号(__TUYA_BLE_WEAK)定义平台抽象接口,JL 移植层在 port/tuya_ble_port_JL.c 中强制覆盖(#undef __TUYA_BLE_WEAK)为真实实现。以广播数据更新为例:
__TUYA_BLE_WEAK tuya_ble_status_t tuya_ble_gap_advertising_adv_data_update(uint8_t const *p_ad_data, uint8_t ad_len)
{
#if CONFIG_BT_GATT_COMMON_ENABLE
ble_gatt_server_adv_enable(0);
if (app_set_adv_data) {
app_set_adv_data((void *)p_ad_data, (int)ad_len);
}
ble_gatt_server_adv_enable(1);
#else
ble_op_adv_enable(0);
ble_op_set_adv_data((int)ad_len, (void *)p_ad_data);
tuya_set_adv_enable();
#endif
return TUYA_BLE_SUCCESS;
}
Source: tuya_ble_port_JL.c
移植要点与设计意图:
- 广播更新采用"先停后启"策略:先
ble_gatt_server_adv_enable(0)关闭广播,通过tuya_app_set_adv_data_register()注册的钩子注入新广播内容,再恢复广播。这是因为 JL BLE 控制器要求广播参数变更在广播关闭状态下进行,避免协议栈内部状态冲突。 - 双路径适配:
CONFIG_BT_GATT_COMMON_ENABLE为 1 时使用 GATT 公共服务(ble_gatt_server_adv_enable+ 应用回调),否则使用传统ble_op_adv_enable/ble_op_set_adv_data接口。这让同一套移植代码兼容不同 SDK 配置的工程。 - 注册钩子:
tuya_app_set_adv_data_register()/tuya_app_set_rsp_data_register()由应用在启动早期调用,把广播/扫描应答内容设置函数注入移植层;扫描应答更新tuya_ble_gap_advertising_scan_rsp_data_update()采用完全相同的模式。 - 移植层还覆盖了任务创建(
struct ble_task_param保存涂鸦创建的任务信息,供调试与任务间同步)、信号量、时间戳、随机数、AES/MD5 加密、Flash 存储等接口。SDK 的加密安全通信(绑定密钥协商、数据加密)全部依赖这些底层能力。
OTA 与产测扩展
OTA(tuya_ota.c)
OTA 由回调事件驱动:TUYA_BLE_CB_EVT_OTA_DATA 触发 tuya_ota_proc(type, p_data, data_len) 处理分片数据,最终将完整固件交给 JL 的升级模块(移植层包含 update.h / update_loader_download.h 依赖)。OTA 数据流与 DP 数据流共用 GATT 通道,SDK 负责分片与校验,应用只需在回调中转发给升级模块。Demo 中 tuya_ble_app_init() 里 //tuya_ota_init(); 被注释,说明 OTA 模块按需使能。
产测(tuya_ble_app_production_test.c)
产测模块响应涂鸦产测指令,典型的产测交互包括:设备授权信息写入(通过 tuya_ble_device_update_product_id() / tuya_ble_device_update_login_key() / tuya_ble_device_update_bound_state() 等 API 动态更新),以及产测指令的异步应答(tuya_ble_api.h 在 240 行之后提供 tuya_ble_production_test_asynchronous_response() 类接口)。产测场景下 device_id_len 通常置 0,由产测工具通过指令写入授权信息,而非编译期硬编码。
UART 通用处理(tuya_ble_app_uart_common_handler.c)
针对 WIFI+BLE 组合设备(如涂鸦 WIFI 模组 + JL BLE 模组),该模块把 UART 上收到的 MCU 指令转发给 SDK(tuya_ble_common_uart_receive_data()),并处理 SDK 回给 MCU 的数据。这样 BLE 端与 WIFI 端通过涂鸦协议在 UART 上桥接,实现"App 连 BLE 控 WIFI 设备"的组合产品形态。
配置选项
| 宏/配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TUYA_DEMO_EN | bool | 工程相关 | 使能涂鸦 Demo:在 spp_and_le/app_main.c 中创建 user_deal 线程承载涂鸦任务调度 |
CONFIG_APP_TUYA | bool | 关闭 | 将应用动作绑定为 ACTION_TUYA(任务名 "tuya") |
TUYA_BLE_PROTOCOL_VERSION_HIGN | int | 0x03/0x04 | 涂鸦协议主版本,决定 DP 发送 API 族(v3 report 系 / v4 send 系) |
TUYA_BLE_USE_OS | bool | 1 | 是否使用 RTOS;为 0 时应用主循环需调用 tuya_ble_main_tasks_exec() 驱动 SDK |
TUYA_BLE_LINK_LAYER_ENCRYPTION_SUPPORT_ENABLE | bool | 依赖协议版本 | 使能链路层加密状态上报(tuya_ble_link_encrypted_handler()) |
TUYA_BLE_BEACON_KEY_ENABLE | bool | v4 可选 | 使能 beacon key 更新(tuya_ble_device_update_beacon_key()) |
CONFIG_BT_GATT_COMMON_ENABLE | bool | 工程相关 | 移植层广播更新走 GATT 公共服务或传统 ble_op_* 路径 |
device_param.device_id_len | uint8 | 0/16/20 | 0 表示授权信息由 SDK license 提供;16/20 表示外部传入 device id |
device_param.use_ext_license_key | uint8 | 1(Demo) | 是否使用外部授权密钥(auth key) |
device_param.p_type | enum | TUYA_BLE_PRODUCT_ID_TYPE_PID | 产品标识类型(PID 等) |
APP_PRODUCT_ID | char[8] | 涂鸦平台分配的 PID | 产品 ID,需替换为实际值 |
TUYA_BLE_ADDRESS_TYPE_RANDOM | enum | — | 设备广播地址类型(Demo 用随机地址) |
API 参考(tuya_ble_api.h 核心接口)
tuya_ble_status_t tuya_ble_sdk_init(tuya_ble_device_param_t *param_data)
初始化涂鸦 BLE SDK,必须在所有平台初始化完成后调用一次。
参数:
param_data(tuya_ble_device_param_t *):设备参数(device id、auth key、MAC、PID、版本等)
返回: tuya_ble_status_t,TUYA_BLE_SUCCESS 表示成功。
tuya_ble_status_t tuya_ble_callback_queue_register(tuya_ble_cb_handler_t handler)
注册业务事件回调(Demo 中为 tuya_cb_handler),事件以队列方式异步派发。
tuya_ble_status_t tuya_ble_gatt_receive_data(uint8_t *p_data, uint16_t len)
在 BLE 收包处调用,把对端原始数据送入 SDK。返回: TUYA_BLE_SUCCESS 表示已接收。
tuya_ble_status_t tuya_ble_dp_data_report(uint8_t *p_data, uint32_t len)(协议 v3)
上报 DP 数据(主动上报或应答 DP 查询)。
tuya_ble_status_t tuya_ble_dp_data_send(uint32_t sn, tuya_ble_dp_data_send_type_t type, tuya_ble_dp_data_send_mode_t mode, tuya_ble_dp_data_send_ack_t ack, uint8_t *p_dp_data, uint32_t dp_data_len)(协议 v4)
带发送属性的 DP 上报。type:DP_SEND_TYPE_ACTIVE(主动)/ DP_SEND_TYPE_PASSIVE(应答查询);mode:目标(云面板/App 等);ack:是否需要响应。v4 要求每个 DP 的长度字段 L 必须为 2 字节。
tuya_ble_status_t tuya_ble_dp_data_with_time_send(...)(协议 v4)
带时间戳的 DP 上报。time_type 支持 DP_TIME_TYPE_MS_STRING(13 字节毫秒字符串)与 DP_TIME_TYPE_UNIX_TIMESTAMP(4 字节 unix 时间戳)。
void tuya_ble_connected_handler(void) / void tuya_ble_disconnected_handler(void)
连接/断开事件透传,平台 BLE 回调中必须调用,驱动 SDK 内部状态机。
void tuya_ble_link_encrypted_handler(void)(需使能链路层加密)
链路层加密成功后调用,通知 SDK 进入加密通信状态。
tuya_ble_status_t tuya_ble_data_passthrough(uint8_t *p_data, uint32_t len)
数据透传:把 SDK 收到的透传数据转发给 App(对应 TUYA_BLE_CB_EVT_DATA_PASSTHROUGH 的回传路径)。
tuya_ble_status_t tuya_ble_device_update_product_id(...) / tuya_ble_device_update_login_key(...) / tuya_ble_device_update_bound_state(uint8_t state)
动态更新设备授权信息(产测/授权流程使用),信息变更后必须立即调用。
失败模式、边界情况与并发
失败模式
| 失败场景 | 表现 | 处理方式(依据源码) |
|---|---|---|
| 授权信息错误(device id / auth key / PID 不匹配) | App 无法发现/绑定设备 | 核对 device_param 三要素与涂鸦平台一致;产测流程用 tuya_ble_device_update_* 系列 API 重新写入 |
| 事件资源未释放 | SDK 事件队列耗尽,收不到后续事件 | 回调末尾必须调用 tuya_ble_inter_event_response(event) |
| DP 上报未带响应 | App 侧状态不同步 | 使用 DP_SEND_WITH_RESPONSE,并在 TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE 中检查 status |
| OTA 数据分片丢失 | 升级失败 | 依赖 SDK 分片校验与 JL 升级模块;失败后需重新发起升级 |
| 广播更新未关闭广播 | 广播内容不生效或协议栈冲突 | 移植层采用"先 adv_enable(0) 再更新再 adv_enable(1)"的原子序列 |
| 回调事件为未知类型 | 日志打印 unknown event type 0x%04x | SDK 升级后需同步扩展应用回调分支 |
边界情况
- 首次上电/未绑定:设备应正常广播并可被 App 发现;Demo 在收到
TUYA_BLE_CB_EVT_TIME_STAMP(App 连接后下发时间)时调用tuya_data_init()主动上报一次 DP,确保 App 端状态初始化。 - App 查询 DP(
TUYA_BLE_CB_EVT_DP_QUERY):设备必须缓存最近一次上报数据(Demo 的dp_data_array/dp_data_len),否则查询时无数据可回。 - 解绑/复位:
TUYA_BLE_CB_EVT_UNBOUND、TUYA_BLE_CB_EVT_ANOMALY_UNBOUND、TUYA_BLE_CB_EVT_DEVICE_RESET三个事件分别对应正常解绑、异常解绑与设备复位,产品应在此清除本地绑定状态。 - 协议版本差异:Demo 对 v3/v4 两套 DP API 做了
#if兼容,切换协议版本时只需改宏,业务逻辑(tuya_data_parse的 DP 布局)不变。
并发与任务模型
涂鸦 SDK 的事件回调运行在 SDK 内部任务上下文(JL 侧由 tuya_ble_port_JL.c 创建,apps/spp_and_le/app_main.c 在 TUYA_DEMO_EN 下额外创建 user_deal 线程承载涂鸦任务调度),而 GPIO 控制、UART 收包发生在其他上下文。因此:
- 回调内不要执行耗时操作(如长 Flash 写入、大块 memcpy),应把工作投递到应用自己的任务;
tuya_ble_custom_event_send()提供了跨任务投递的安全通道。 tuya_ble_gatt_receive_data()/tuya_ble_common_uart_receive_data()从 BLE/UART 中断或回调上下文调用,SDK 内部负责入队与加锁,应用侧无需重复加锁。- 广播更新接口内部使用
ble_gatt_server_adv_enable(0/1)成对调用,天然互斥;多个任务同时触发广播更新时仍建议统一入口串行化,避免广播反复抖动。
性能与运维考虑
- GATT 发送队列(
sdk/include/tuya_ble_gatt_send_queue.h)是 DP 上报的缓冲层:高频 DP 上报会被排队,避免单次通知超长导致丢包;发送结果通过报告响应事件回传。产品应控制 DP 上报频率,长数据使用批量数据模块(tuya_ble_bulk_data.h)。 - 日志:Demo 大量使用
TUYA_APP_LOG_*/printf,量产固件建议关闭或降级日志等级,避免 UART 日志与业务 UART 抢占带宽。 - 升级可靠性:OTA 走 BLE 通道,建议在连接稳定、信号好的环境下升级,必要时结合链路层加密(
TUYA_BLE_LINK_LAYER_ENCRYPTION_SUPPORT_ENABLE)防止固件被窃取。 - 调试建议:
tuya_ble_port_JL.c中struct ble_task_param保留了涂鸦创建的任务名与信号量,可在异常时打印任务状态辅助定位死锁/阻塞问题。
扩展点
- DP 点扩展:在
tuya_data_parse()的switch (buf[0])中按产品定义扩展 DP id;上报侧用tuya_data_init()同款结构体构造多 DP 帧(数组 + 总长度)。 - 新回调事件:SDK 事件号在
tuya_ble_event.h中定义,应用在tuya_cb_handler的switch中添加分支即可,无需改动 SDK。 - 新平台移植:参考
port/tuya_ble_port_JL.c覆盖__TUYA_BLE_WEAK接口(广播、扫描应答、任务、信号量、时间、随机数、Flash、AES/MD5),并保持tuya_ble_gatt_receive_data()收包入口不变。 - 功能特性开关:
tuya_ble_feature_weather.h(天气特性)、tuya_ble_bulk_data.h(批量数据)、tuya_ble_data_handler.h(DP 编解码)均为可选的 SDK 能力,按产品形态使能。 - Mesh 形态:如需 Mesh 组网灯控,见
apps/mesh/examples/TUYA_light.c(SIG_MESH_TUYA_LIGHT=8,TUYA_CATEGORY=0x1011,产品 PID "yjfs506f"),与本文的 BLE 单点接入是两套独立协议栈。
测试覆盖
仓库未提供针对涂鸦协议栈的独立单元测试工程;接入验证依赖以下路径:
- Demo 编译验证:
spp_and_le工程CONFIG_APP_TUYA/TUYA_DEMO_EN使能后编译,验证任务创建与回调注册(tuya_ble_callback_queue_register返回值检查)。 - 真机联调:涂鸦智能 App 配网 → 绑定 → DP 下发(App 控制 LED)→ DP 上报(设备状态回显)→ 解绑 → 重新绑定,覆盖
tuya_cb_handler全部事件分支。 - 产测流程:
tuya_ble_app_production_test.c配合涂鸦产测工具验证授权写入与指令应答。 - Mesh 例程:
apps/mesh/examples/TUYA_light.c在CONFIG_MESH_MODEL == SIG_MESH_TUYA_LIGHT下编译,验证涂鸦 Mesh 灯模型。
相关链接
- tuya_ble_api.h(SDK 对外 API)
- tuya_ble_app_demo.c(接入示例)
- tuya_ble_app_demo.h(PID/版本宏)
- tuya_ble_port_JL.c(JL 平台移植层)
- tuya_ota.c(OTA 处理)
- tuya_ble_app_production_test.c(产测)
- tuya_ble_app_uart_common_handler.c(UART 通用处理)
- app_main.c(应用集成/任务创建)
- TUYA_light.c(涂鸦 Mesh 灯控例程)
- 涂鸦 Mesh 模型:见 Mesh 开发相关页面;其他第三方协议(腾讯、阿里 HiLink)见 third_party_profile 对应页面。