在前几节课中已经介绍了如何在 涂鸦 IoT 平台 上创建智能产品以及如何搭建涂鸦蓝牙模组的开发环境,在此基础上,本节课将继续以 BTU 模组为例,介绍如何使用 涂鸦蓝牙模组及其 SDK 开发一款温湿度传感器产品。
前期准备
产品创建
首先,要在 涂鸦 IoT 平台 上创建好一个 温湿度传感器 产品,具体方法请参考 产品创建。
环境搭建
本 Demo 的开发环境如下:
- 涂鸦三明治 BLE SoC 主控板 (BTU)
- 涂鸦三明治 温湿度传感器 功能版 (SHT30-DIS)
- USB 转串口工具、串口调试助手
- 生产解决方案 (涂鸦云模组烧录授权平台)
- Telink 烧录器、Telink IDE、Telink BDT
- PC (Windows 10)
- SDK (tuya-ble-sdk-demo-project-tlsr8253)
- 智能手机、智能生活 APP
如果您使用 BTU 模组或同芯片平台模组进行开发,在开发前需准备好上述开发环境 (主控板和功能板可选);如果您使用其他芯片平台的涂鸦蓝牙模组,请参考 环境搭建 和芯片厂家官网资料进行环境搭建。
烧录授权
在开发前先使用 涂鸦云模组烧录授权平台 对模组进行烧录授权,请按照 烧录授权 中的介绍完成初版固件的配置与烧录,并通过 智能生活 APP 检验设备是否能正常配网。对模组进行授权之后,在调试阶段我们就可以直接使用 Telink BDT 进行固件烧录。
快速上手
如果您准备好了以上开发环境,那么可以按以下步骤快速体验 Demo 功能:
- 在 tuya-ble-sdk-development-course-demo 中下载本课程配套 Demo;(
app_demo ) - 参考文件结构 将仓库中
app_demo 目录下 除 tuya_ble_sdk_demo.h 外 (包含了 PID 和固件版本信息等,请保留您自己的配置) 的所有文件复制到您的工程中,并在工程配置中添加 头文件路径; - 根据您使用的 授权方案 确认是否需要修改初始化参数
use_ext_license_key 和 device_id_len ; - 根据您的电路设计确认是否需要修改相关引脚编号,可参考引脚分配 ;
- 编译代码,并使用 Telink BDT 将生成的固件烧录到您的开发板中;
- 使用 智能生活 APP 扫描设备进行功能体验,可参考功能演示 。
如果您使用的是其他芯片平台的涂鸦蓝牙模组,则不需要复制 Demo 中 board 目录下的文件 (但需要确认 SDK 中 board 目录下的 API 接口函数 是否已实现相关功能),并使用芯片平台对应的烧录软件进行固件烧录。
SDK 介绍
文件结构
下面以 TLSR825x 平台的 SDK 为例介绍 SDK 的文件结构。
├── ble_sdk_multimode
| ├── telink_sdk /* 原厂 SDK */
| ├── tuya_ble_sdk_demo /* 涂鸦蓝牙 SDK 及其 Demo,适配了 TLSR825x 平台 */
| | ├── app /* 应用代码,提供了一些 Demo 文件,可直接在此基础上进行扩展 */
| | ├── board /* 芯片平台关联代码,包括相关外设的硬件抽象和为 tuya_ble_sdk 提供的 port 接口及应用层配置文件 */
| | ├── components /* 组件代码,包括一些通用组件,用户自封装的组件也可放置于该目录下 */
| | ├── doc /* 文档资料 */
| | ├── tools /* 工具文件 */
| | └── tuya_ble_sdk /* 涂鸦蓝牙 SDK 代码,主要实现了涂鸦智能设备和智能生活 APP 之间的通信协议,以及事件调度机制 */
| |
| └── .cproject 及其他 /* 工程文件 */
|
├── CHANGELOG.md /* 更新日志 */
├── README.md /* Readme 文件英文版 */
└── README_zh-CN /* Readme 文件中文版 */
-
tuya_ble_sdk_demo tuya_ble_sdk_demo.c :实现 tuya_ble_sdk 的初始化,封装应用层的事件调度 API tuya_ble_sdk_test.c :实现 tuya_ble_sdk 测试用的串口指令,仅供测试,与应用无关 tuya_ble_bulk_data_demo.c :大数据通道例程 tuya_ble_product_test_demo.c :整机产测例程 -
component external\easylogger :调试信息打印接口组件 -
board TLSR825X\ty_board_tlsr825x :相关外设硬件抽象 TLSR825X\tuya_ble_port :专为 tuya_ble_sdk 提供的 port 接口和应用层配置文件 TLSR825X\ota :实现各自平台的 OTA(数据传输协议一致,Flash 操作不同)
应用入口
-
tuya_ble_sdk_demo_init :各芯片平台的涂鸦蓝牙模组 SDK 中都提供了 SDK 开发的 Demo 文件,可以直接在 tuya_ble_sdk_demo.c 代码的基础上进行开发;tuya_ble_sdk_demo_init 为 SDK Demo 初始化函数,在此函数的最后加上您的应用初始化代码即可。 void tuya_ble_sdk_demo_init(void)
{
if (tuya_ble_device_param.use_ext_license_key) {
memcpy(tuya_ble_device_param.device_id, device_id_test, DEVICE_ID_LEN);
memcpy(tuya_ble_device_param.auth_key, auth_key_test, AUTH_KEY_LEN);
memcpy(tuya_ble_device_param.mac_addr_string, TY_DEVICE_MAC, MAC_STRING_LEN);
}
memcpy(tuya_ble_device_param.product_id, TY_DEVICE_PID, tuya_ble_device_param.product_id_len);
memcpy(tuya_ble_device_param.adv_local_name, TY_DEVICE_NAME, tuya_ble_device_param.adv_local_name_len);
tuya_ble_sdk_init(&tuya_ble_device_param);
tuya_ble_callback_queue_register(tuya_ble_sdk_callback);
tuya_ble_ota_init();
tuya_ble_disconnect_and_reset_timer_init();
tuya_ble_update_conn_param_timer_init();
tuya_ble_app_init();
}
-
tuya_ble_main_tasks_exec :在不使用 RTOS 的芯片平台架构下,该函数作为涂鸦蓝牙 SDK 的事件主调度器循环执行,位于 tuya_ble_main.c 中;如果有需要循环处理的任务可放置于该函数中,但更建议使用创建定时器的方式来执行,定时器的使用方法可参考 软件定时器?。 void tuya_ble_main_tasks_exec(void)
{
tuya_ble_app_loop();
tuya_sched_execute();
}
常用 API
1. 日志打印
API 列表
函数名称 | 功能描述 |
---|
TUYA_APP_LOG_ERROR | 打印错误信息 | TUYA_APP_LOG_WARNING | 打印警告信息 | TUYA_APP_LOG_INFO | 打印通知信息 | TUYA_APP_LOG_DEBUG | 打印调试信息 |
API 说明
接口文件:tuya_ble_log.h
#define TUYA_APP_LOG_ENABLE 1
#define TY_LOG_ENABLE 1
#defien TUYA_APP_LOG_LEVEL TUYA_APP_LOG_LEVEL_DEBUG
void TUYA_APP_LOG_xxxx(const char *format, …)
应用示例
TUYA_APP_LOG_xxxx("tuya_ble_sdk_init succeeded.");
TUYA_APP_LOG_xxxx("tuya_ble_sdk_init failed, error code: %d.", res);
TUYA_APP_LOG_xxxx("receive data: %x.", data);
2. 软件定时器
API 列表
函数名称 | 功能描述 |
---|
tuya_ble_timer_create | 创建一个定时器 | tuya_ble_timer_start | 启动指定定时器 | tuya_ble_timer_stop | 停止指定定时器 | tuya_ble_timer_restart | 重启指定定时器 | tuya_ble_timer_delete | 删除指定定时器 |
API 说明
接口文件:tuya_ble_port.h
tuya_ble_status_t tuya_ble_timer_create(void** p_timer_id,
uint32_t timeout_value_ms,
tuya_ble_timer_mode mode,
tuya_ble_timer_handler_t timeout_handler);
tuya_ble_status_t tuya_ble_timer_start(void* timer_id);
tuya_ble_status_t tuya_ble_timer_stop(void* timer_id);
tuya_ble_status_t tuya_ble_timer_restart(void* timer_id, uint32_t timeout_value_ms);
tuya_ble_status_t tuya_ble_timer_delete(void* timer_id);
数据类型
typedef enum {
TUYA_BLE_TIMER_SINGLE_SHOT,
TUYA_BLE_TIMER_REPEATED,
} tuya_ble_timer_mode;
typedef void (*tuya_ble_timer_handler_t)(void*);
应用示例
功能概述:上电后,指示灯点亮 2s 后熄灭,按键按下时指示灯点亮,5s 后再次熄灭。
#include "tuya_ble_port.h"
#include "ty_pin.h"
#define KEY_PIN GPIO_PA0
#define LED_PIN GPIO_PD7
#define KEY_SCAN_TIME_MS 10
#define LED_TURN_TIME_MS_1 2000
#define LED_TURN_TIME_MS_2 5000
uint8_t delete_flag = 0;
tuya_ble_timer_t key_timer;
tuya_ble_timer_t led_timer;
void key_timer_cb(void)
{
ty_pin_level_t pin_level;
ty_pin_get(KEY_PIN, &pin_level);
if (TY_PIN_LOW == pin_level) {
ty_pin_set(LED_PIN, TY_PIN_HIGH);
tuya_ble_timer_restart(led_timer, LED_TURN_TIME_MS_2);
tuya_ble_timer_stop(key_timer);
delete_flag = 1;
}
}
void led_timer_cb(void)
{
ty_pin_set(LED_PIN, TY_PIN_LOW);
if (delete_flag) {
tuya_ble_timer_delete(key_timer);
} else {
tuya_ble_timer_start(key_timer);
}
}
void app_init(void)
{
ty_pin_init(KEY_PIN, TY_PIN_MODE_IN_PU);
ty_pin_init(LED_PIN, TY_PIN_MODE_OUT_PP_LOW);
ty_pin_set(LED_PIN, TY_PIN_HIGH);
tuya_ble_timer_create(&key_timer, KEY_SCAN_TIME_MS, TUYA_BLE_TIMER_REPEATED, (tuya_ble_timer_handler_t)key_timer_cb);
tuya_ble_timer_create(&led_timer, LED_TURN_TIME_MS_1, TUYA_BLE_TIMER_SINGLE_SHOT, (tuya_ble_timer_handler_t)led_timer_cb);
tuya_ble_timer_start(led_timer);
}
3. DP上报
API 列表
函数名称 | 功能描述 |
---|
tuya_ble_dp_data_send | 发送 DP 数据 |
API 说明
接口文件:tuya_ble_api.h
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);
数据类型
typedef enum {
DP_SEND_TYPE_ACTIVE = 0,
DP_SEND_TYPE_PASSIVE,
} tuya_ble_dp_data_send_type_t;
typedef enum {
DP_SEND_FOR_CLOUD_PANEL = 0,
DP_SEND_FOR_CLOUD,
DP_SEND_FOR_PANEL,
DP_SEND_FOR_NONE,
} tuya_ble_dp_data_send_mode_t;
typedef enum {
DP_SEND_WITH_RESPONSE = 0,
DP_SEND_WITHOUT_RESPONSE,
} tuya_ble_dp_data_send_ack_t;
DP 说明
涂鸦 IoT 平台是以 DP 模型管理数据的。任何设备产生的数据都需要抽象为 DP 模型形式,一个 DP 模型由四部分组成:DP ID、DP 数据类型、DP 数据长度、DP 数据。更多详情,请参考 自定义功能。
涂鸦蓝牙模组 SDK 的 DP 模型管理协议如下:
字段 | 长度 (byte) | 说明 |
---|
dp_id | 1 | DP ID | dp_type | 1 | DP 数据类型 | dp_len | 2 | DP 数据长度 | dp_data | dp_len | DP 数据 |
DP 数据类型及其数据长度范围规定如下:
dp_type | 标识符 | 取值 | dp_len |
---|
透传 (Raw) | DT_RAW | 0 | 1~255 | 布尔 (Bool) | DT_BOOL | 1 | 1 | 数值 (Value) | DT_VALUE | 2 | 4 | 字符串 (String) | DT_STRING | 3 | 0~255 | 枚举 (Enum) | DT_ENUM | 4 | 1 |
每个 DP 数据的最大长度在涂鸦 IoT 平台定义时指定。dp_type = 0 或 3 时, dp_len 数值自定义,但必须小于在涂鸦 IoT 平台定义时的最大长度。
tuya_ble_dp_data_send 的参数 p_dp_data 指向的数据必须以下表格式组装上报:
DP | 第 1 个 DP 点数据 | 第 2 个 DP 点数据 | ... |
---|
Byte | 0 | 1 | 2~3 | 4~n-1 | n | n+1 | n+2~n+3 | n+4~m-1 | ... | 字段 | dp1_id | dp1_type | dp1_len | dp1_data | dp2_id | dp2_type | dp2_len | dp2_data | ... |
注:n-1 = (4 + dp1_len) - 1;m-1 = n + (4 + dp2_len) - 1。
一次可发送多个 DP 数据,只要总长度不超过限制即可,最大长度为 TUYA_BLE_SEND_MAX_DATA_LEN-7 ,其中 TUYA_BLE_SEND_MAX_DATA_LEN 可配置。
应用示例
功能概述:(温度更新时)上报温度数据,(蓝牙连接时)上报所有数据。
#define DP_ID_TEMP_CURRENT 1
#define DP_ID_TEMP_ALARM 14
#define DT_RAW 0
#define DT_BOOL 1
#define DT_VALUE 2
#define DT_STRING 3
#define DT_ENUM 4
#define DP_DATA_INDEX_OFFSET_ID 0
#define DP_DATA_INDEX_OFFSET_TYPE 1
#define DP_DATA_INDEX_OFFSET_LEN_H 2
#define DP_DATA_INDEX_OFFSET_LEN_L 3
#define DP_DATA_INDEX_OFFSET_DATA 4
static int32_t sg_cur_temp = 0, sg_prv_temp = 0;
static uint8_t sg_temp_alarm = 0;
static uint8_t sg_repo_array[255+4];
static uint32_t sg_sn = 0;
static void __report_one_dp_data(const uint8_t dp_id, const uint8_t dp_type, const uint16_t dp_len, const uint8_t *dp_data)
{
uint16_t i;
sg_repo_array[DP_DATA_INDEX_OFFSET_ID] = dp_id;
sg_repo_array[DP_DATA_INDEX_OFFSET_TYPE] = dp_type;
sg_repo_array[DP_DATA_INDEX_OFFSET_LEN_H] = (uint8_t)(dp_len >> 8);
sg_repo_array[DP_DATA_INDEX_OFFSET_LEN_L] = (uint8_t)dp_len;
for (i = 0; i < dp_len; i++) {
sg_repo_array[DP_DATA_INDEX_OFFSET_DATA + i] = *(dp_data + (dp_len-i-1));
}
tuya_ble_dp_data_send(sg_sn++, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITHOUT_RESPONSE, sg_repo_array, dp_len + DP_DATA_INDEX_OFFSET_DATA);
}
void app_repo_dp_temp(void)
{
if (sg_cur_temp != sg_prv_temp)) {
__report_one_dp_data(DP_ID_TEMP_CURRENT, DT_VALUE, 4, (uint8_t *)&sg_cur_temp);
sg_prv_temp = sg_cur_temp;
}
}
static uint8_t __add_one_dp_data(const uint8_t dp_id, const uint8_t dp_type, const uint16_t dp_len, const uint8_t *dp_data, uint8_t *addr)
{
uint16_t i;
*(addr + DP_DATA_INDEX_OFFSET_ID) = dp_id;
*(addr + DP_DATA_INDEX_OFFSET_TYPE) = dp_type;
*(addr + DP_DATA_INDEX_OFFSET_LEN_H) = (UCHAR_T)(dp_len >> 8);
*(addr + DP_DATA_INDEX_OFFSET_LEN_L) = (UCHAR_T)dp_len;
for (i = 0; i < dp_len; i++) {
*(addr + DP_DATA_INDEX_OFFSET_DATA + i) = *(dp_data + (dp_len-i-1));
}
return (dp_len + DP_DATA_INDEX_OFFSET_DATA);
}
void app_repo_dp_all(void)
{
uint32_t total_len = 0;
total_len += __add_one_dp_data(DP_ID_TEMP_CURRENT, DT_VALUE, 4, &sg_cur_temp, sg_repo_array);
total_len += __add_one_dp_data(DP_ID_TEMP_ALARM, DT_ENUM, 1, &sg_temp_alarm, sg_repo_array+total_len);
tuya_ble_dp_data_send(sg_sn++, DP_SEND_TYPE_ACTIVE, DP_SEND_FOR_CLOUD_PANEL, DP_SEND_WITHOUT_RESPONSE, sg_repo_array, total_len);
}
4. 设备状态
API 列表
函数名称 | 功能描述 |
---|
tuya_ble_connect_status_get | 查询蓝牙连接状态 | tuya_ble_device_unbind | 设备端主动解绑,不会清除设备虚拟 ID | tuya_ble_device_factory_reset | 设备重置,并清除设备虚拟 ID |
注:设备虚拟 ID 管理着云端历史数据。
API 说明
接口文件:tuya_ble_api.h
tuya_ble_connect_status_t tuya_ble_connect_status_get(void);
tuya_ble_status_t tuya_ble_device_unbind(void);
tuya_ble_status_t tuya_ble_device_factory_reset(void);
数据类型
typedef enum {
UNBONDING_UNCONN = 0,
UNBONDING_CONN,
BONDING_UNCONN,
BONDING_CONN,
BONDING_UNAUTH_CONN,
UNBONDING_UNAUTH_CONN,
UNKNOW_STATUS
} tuya_ble_connect_status_t;
蓝牙网络状态的含义说明如下:
蓝牙网络状态 | 说明 |
---|
未绑定未连接 | 表示设备当前既未注册到涂鸦云,也没有处于蓝牙连接状态。若当前设备还处于 蓝牙广播 状态,则设备处于 可配网 状态。 | 未绑定已连接 | 表示未绑定的设备处于蓝牙连接状态。 | 已绑定未连接 | 通常也叫 设备离线,表示设备与 APP 账号建立了绑定关系,但链路层未连接,不处于安全通讯状态。 | 已绑定已连接 | 通常也叫 设备上线 或 设备在线,表示蓝牙设备通过 涂鸦蓝牙通讯协议 与 APP 建立的安全通讯状态。 | 已绑定已连接未鉴权 | 这个状态是配对或重连中的一个中间状态,通常表示已绑定的设备刚刚建立蓝牙连接。 | 未绑定已连接未鉴权 | 与未绑定未连接的区别是该状态表示已处于蓝牙连接状态,暂时不可被发现。 |
应用示例
功能概述:应用程序申请重新配网时,查询蓝牙连接状态,如果已绑定则调用 API 进行主动解绑;应用程序申请设备重置时,调用 API 进行设备重置。
void app_unbind(void)
{
tuya_ble_connect_status_t ble_conn_sta = tuya_ble_connect_status_get();
if ((ble_conn_sta == BONDING_UNCONN) ||
(ble_conn_sta == BONDING_CONN) ||
(ble_conn_sta == BONDING_UNAUTH_CONN)) {
tuya_ble_device_unbind();
}
}
void app_reset(void)
{
tuya_ble_device_factory_reset();
}
5. Callback
在 tuya_ble_sdk_demo.c 中,您可以看到 tuya_ble_sdk_callback 函数。该函数为初始化时注册的消息回调函数,用于涂鸦蓝牙 SDK(TUYA BLE SDK)向设备应用程序发送消息 (状态、数据等)。使用时在对应的 case 下添加要处理的内容即可。
常用的处理事件如下表所示,更多关于 Callback 的信息可参考 API 说明 - Callback。
Event | 说明 |
---|
TUYA_BLE_CB_EVT_CONNECTE_STATUS | 蓝牙 SDK 每次状态的改变都会发送该事件给设备应用程序 | TUYA_BLE_CB_EVT_DP_DATA_RECEIVED | 蓝牙 SDK 收到的手机 App 发送的 DP 数据 | TUYA_BLE_CB_EVT_DP_QUERY | 蓝牙 SDK 收到的手机 App 发送的要查询的 DP ID 数组 | TUYA_BLE_CB_EVT_OTA_DATA | 蓝牙 SDK 收到的手机 App 发送的 OTA 固件数据 | TUYA_BLE_CB_EVT_TIME_STAMP | 蓝牙 SDK 收到的手机 App 发送的字符串格式的时间戳 | TUYA_BLE_CB_EVT_TIME_NORMAL | 蓝牙 SDK 收到的手机 App 发送的常规格式的时间 | TUYA_BLE_CB_EVT_UNBOUND | 蓝牙 SDK 收到的手机 App 发送的解绑指令 | TUYA_BLE_CB_EVT_ANOMALY_UNBOUND | 蓝牙 SDK 收到的手机 App 发送的异常解绑指令 |
|