涂鸦蓝牙SDK开发系列教程——5.应用开发
在前几节课中已经介绍了如何在 涂鸦 IoT 平台 上创建智能产品以及如何搭建涂鸦蓝牙模组的开发环境,在此基础上,本节课将继续以 BTU 模组为例,介绍如何使用 涂鸦蓝牙模组及其 SDK 开发一款温湿度传感器产品。
一. 前期准备
1.1 产品创建
首先,要在 涂鸦 IoT 平台 上创建好一个 温湿度传感器 产品,具体方法请参考 第 2 课:产品创建。
1.2 环境搭建
本 Demo 的开发环境如下:
- 涂鸦三明治 BLE SoC 主控板 (BTU)
- 涂鸦三明治 温湿度传感器 功能版 (SHT30-DIS)
- USB 转串口工具、串口调试助手
- 生产解决方案 (涂鸦云模组烧录授权平台)
- Telink 烧录器、Telink IDE、Telink BDT
- PC (Windows 10)
- SDK (tuya-ble-sdk-demo-project-tlsr8253)
- 智能手机、智能生活 APP
如果您使用 BTU 模组或同芯片平台模组进行开发,在开发前需准备好上述开发环境 (主控板和功能板可选);如果您使用其他芯片平台的涂鸦蓝牙模组,请参考 第 3 课:环境搭建 和芯片厂家官网资料进行环境搭建。
1.3 烧录授权
在开发前先使用 涂鸦云模组烧录授权平台 对模组进行烧录授权,请按照 第 4 课:烧录授权 中的介绍完成初版固件的配置与烧录,并通过 智能生活 APP 检验设备是否能正常配网。对模组进行授权之后,在调试阶段我们就可以直接使用 Telink BDT 进行固件烧录。
1.4 快速上手
如果您准备好了以上开发环境,那么可以按以下步骤快速体验 Demo 功能:
- 在 tuya-ble-sdk-development-course-demo 中下载本课程配套 Demo;(
app_demo
) - 参考 3.1.2 文件结构 将仓库中
app_demo
目录下 除tuya_ble_sdk_demo.h
外 (包含了 PID 和固件版本信息等,请保留您自己的配置) 的所有文件复制到您的工程中,并在工程配置中添加 头文件路径; - 根据您使用的 授权方案 确认是否需要修改初始化参数
use_ext_license_key
和device_id_len
; - 根据您的电路设计确认是否需要修改相关引脚编号,可参考 3.1.3 引脚分配;
- 编译代码,并使用 Telink BDT 将生成的固件烧录到您的开发板中;
- 使用 智能生活 APP 扫描设备进行功能体验,可参考 3.3 功能演示。
如果您使用的是其他芯片平台的涂鸦蓝牙模组,则不需要复制 Demo 中 board 目录下的文件 (但需要确认 SDK 中 board 目录下的 API 接口函数 是否已实现相关功能),并使用芯片平台对应的烧录软件进行固件烧录。
二. SDK 介绍
2.1 文件结构
下面以 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 的初始化,封装应用层的事件调度 APItuya_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 操作不同)
2.2 应用入口
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) {/* 根据是否使用 tuya_ble_sdk_demo.h 中的授权码配置相关参数 */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);}/* 配置 PID 和设备名称 */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 初始化 */tuya_ble_sdk_init(&tuya_ble_device_param);/* 注册 tuya_ble_sdk 回调函数 */tuya_ble_callback_queue_register(tuya_ble_sdk_callback);/* OTA 初始化、定时器初始化 */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
中;如果有需要循环处理的任务可放置于该函数中,但更建议使用创建定时器的方式来执行,定时器的使用方法可参考 2.3.2 软件定时器。void tuya_ble_main_tasks_exec(void) {/* 智能产品应用程序主循环 */tuya_ble_app_loop();/* 主调度器 */tuya_sched_execute(); }
2.3 常用 API
2.3.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 /* 0-关闭,1-开启 (custom_tuya_ble_config.h) */
#define TY_LOG_ENABLE 1 /* 0-关闭,1-开启 (board.h) */
/* 设置日志等级 */
#defien TUYA_APP_LOG_LEVEL TUYA_APP_LOG_LEVEL_DEBUG/*** @brief 打印日志 (xxxx 可替换为 ERROR 或 WARNING 或 INFO 或 DEBUG)* @param[in] format: 格式控制符* @param[in] …: 可变参数* @return 无*/
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.3.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
/*** @brief 创建一个定时器* @param[in] p_timer_id: 定时器ID* @param[in] timeout_value_ms: 超时时间(ms)* @param[in] mode:工作模式* @param[in] timeout_handler: 超时处理函数* @return 操作结果*/
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);/*** @brief 启动指定定时器* @param[in] timer_id: 定时器ID* @return 操作结果*/
tuya_ble_status_t tuya_ble_timer_start(void* timer_id);/*** @brief 停止指定定时器* @param[in] timer_id: 定时器ID* @return 操作结果*/
tuya_ble_status_t tuya_ble_timer_stop(void* timer_id);/*** @brief 重启指定定时器* @param[in] timer_id: 定时器ID* @return 操作结果*/
tuya_ble_status_t tuya_ble_timer_restart(void* timer_id, uint32_t timeout_value_ms);/*** @brief 删除指定定时器* @param[in] timer_id: 定时器ID* @return 操作结果*/
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 /* KEY */
#define LED_PIN GPIO_PD7 /* LED */
/* 定时时间 */
#define KEY_SCAN_TIME_MS 10 /* 10ms */
#define LED_TURN_TIME_MS_1 2000 /* 2s */
#define LED_TURN_TIME_MS_2 5000 /* 5s *//* 定时器可删除标志 */
uint8_t delete_flag = 0;
/* 定时器 ID */
tuya_ble_timer_t key_timer;
tuya_ble_timer_t led_timer;/*** @brief key_timer超时处理函数* @param 无* @return 无*/
void key_timer_cb(void)
{/* 读取按键引脚的电平 */ty_pin_level_t pin_level;ty_pin_get(KEY_PIN, &pin_level);if (TY_PIN_LOW == pin_level) {/* 点亮 LED, 重启led_timer, 定时5s */ty_pin_set(LED_PIN, TY_PIN_HIGH);tuya_ble_timer_restart(led_timer, LED_TURN_TIME_MS_2);/* 停止定时器key_timer, 标记key_timer可删除 */tuya_ble_timer_stop(key_timer);delete_flag = 1;}
}/*** @brief led_timer超时处理函数* @param 无* @return 无*/
void led_timer_cb(void)
{/* 熄灭LED */ty_pin_set(LED_PIN, TY_PIN_LOW);if (delete_flag) {/* 删除定时器key_timer */tuya_ble_timer_delete(key_timer);} else {/* 启动定时器key_timer */tuya_ble_timer_start(key_timer);}
}/*** @brief 初始化函数* @param 无* @return 无*/
void app_init(void)
{/* 初始化按键和指示灯引脚,点亮 LED */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);/* 创建一个定时器: key_timer, 定时10ms, 重复触发, 注册超时处理函数key_timer_cb */tuya_ble_timer_create(&key_timer, KEY_SCAN_TIME_MS, TUYA_BLE_TIMER_REPEATED, (tuya_ble_timer_handler_t)key_timer_cb);/* 创建一个定时器并启动: led_timer, 定时2s, 单次触发, 注册超时处理函数led_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);
}
2.3.3 DP上报
API 列表
函数名称 | 功能描述 |
---|---|
tuya_ble_dp_data_send | 发送 DP 数据 |
API 说明
接口文件:tuya_ble_api.h
/*** @brief 发送DP数据* @param[in] sn: 发送序号,由应用程序自行定义管理的序号,每发送一次递增1* @param[in] type: 发送类型* @param[in] mode: 发送模式* @param[in] ack: 是否需要应答标志* @param[in] p_dp_data: DP数据* @param[in] dp_data_len: 数据总长度,最大不能超过TUYA_BLE_SEND_MAX_DATA_LEN-7* @return 操作结果*/
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, /* 设备响应APP的DP数据查询指令 */
} tuya_ble_dp_data_send_type_t;/* 发送模式 */
typedef enum {DP_SEND_FOR_CLOUD_PANEL = 0, /* APP将接收到的数据上传到云端,同时发送到面板显示 */DP_SEND_FOR_CLOUD, /* APP仅将接收到的数据上传到云端 */DP_SEND_FOR_PANEL, /* APP仅将接收到的数据发送到面板显示 */DP_SEND_FOR_NONE, /* 既不上传到云端,也不发送到面板显示 */
} tuya_ble_dp_data_send_mode_t;/* 响应模式 */
typedef enum {DP_SEND_WITH_RESPONSE = 0, /* 需要APP响应 */DP_SEND_WITHOUT_RESPONSE, /* 不需要APP响应 */
} 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
可配置。
应用示例
功能概述:(温度更新时)上报温度数据,(蓝牙连接时)上报所有数据。
/* DP ID */
#define DP_ID_TEMP_CURRENT 1 /* 温度 */
#define DP_ID_TEMP_ALARM 14 /* 温度报警 *//* DP数据类型(可以直接使用tuya_ble_mutli_tsf_protocol.h中的定义) */
#define DT_RAW 0 /* 透传 */
#define DT_BOOL 1 /* 布尔 */
#define DT_VALUE 2 /* 数值 */
#define DT_STRING 3 /* 字符串 */
#define DT_ENUM 4 /* 枚举 *//* DP模型字段偏移量 */
#define DP_DATA_INDEX_OFFSET_ID 0 /* dp_id */
#define DP_DATA_INDEX_OFFSET_TYPE 1 /* dp_type */
#define DP_DATA_INDEX_OFFSET_LEN_H 2 /* dp_len */
#define DP_DATA_INDEX_OFFSET_LEN_L 3 /* dp_len */
#define DP_DATA_INDEX_OFFSET_DATA 4 /* dp_data *//* DP数据变量 */
static int32_t sg_cur_temp = 0, sg_prv_temp = 0;
static uint8_t sg_temp_alarm = 0;/* DP组装上报数组 */
static uint8_t sg_repo_array[255+4];/* 发送序号 */
static uint32_t sg_sn = 0;/*** @brief 上报一个DP数据* @param[in] dp_id: DP ID* @param[in] dp_type: DP数据类型* @param[in] dp_len: DP数据长度* @param[in] dp_data: DP数据* @return 无*/
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;/* 将DP数据存入数组 */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));}/* 调用API发送DP数据 */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);
}/*** @brief 上报温度数据* @param 无* @return 无*/
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;}
}/*** @brief 添加一个DP数据* @param[in] dp_id: DP ID* @param[in] dp_type: DP数据类型* @param[in] dp_len: DP数据长度* @param[in] dp_data: DP数据* @param[in] addr: DP存储起始地址* @return 已存储数据总长度*/
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);
}/*** @brief 上报所有数据* @param 无* @return 无*/
void app_repo_dp_all(void)
{uint32_t total_len = 0;/* 添加所有数据到DP组装上报数组 */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);/* 调用API发送DP数据 */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);
}
2.3.4 设备状态
API 列表
函数名称 | 功能描述 |
---|---|
tuya_ble_connect_status_get | 查询蓝牙连接状态 |
tuya_ble_device_unbind | 设备端主动解绑,不会清除设备虚拟 ID |
tuya_ble_device_factory_reset | 设备重置,并清除设备虚拟 ID |
注:设备虚拟 ID 管理着云端历史数据。
API 说明
接口文件:tuya_ble_api.h
/*** @brief 查询蓝牙连接状态* @param 无* @return 蓝牙连接状态*/
tuya_ble_connect_status_t tuya_ble_connect_status_get(void);/*** @brief 设备端主动解绑* @param 无* @return 操作结果*/
tuya_ble_status_t tuya_ble_device_unbind(void);/*** @brief 重置设备* @param 无* @return 操作结果*/
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 进行设备重置。
/*** @brief 设备解绑* @param 无* @return 无*/
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();}
}/*** @brief 设备重置* @param 无* @return 无*/
void app_reset(void)
{tuya_ble_device_factory_reset();
}
2.3.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 发送的异常解绑指令 |
三. Demo 介绍
3.1 Demo 设计
3.1.1 功能定义
本 Demo 的基本功能设定如下:
功能 | 说明 |
---|---|
数据采集 |
每秒采集一次温湿度数据; 温湿度数据变化时,上报数据给手机 APP,更新设备面板显示。 |
阈值报警 |
可通过手机 APP 设置温度上下限和湿度上下限; 当温湿度超过设定阈值时,可上报对应的报警信息至手机 APP。 |
配网指示 |
- 设备未被绑定,指示灯闪烁; - 设备被绑定时,指示灯熄灭; - 设备被解绑后,指示灯闪烁。 |
根据功能设定,我们先在产品开发的【功能定义】页面添加相关的功能,上述功能都可以在【标准功能】中找到,添加后再对属性进行编辑即可,可参考下图配置:
注:如果您在开发过程中修改了 DP 定义,或者遇到设备面板显示异常的情况,建议 重新绑定 设备。
3.1.2 文件结构
涂鸦蓝牙 SDK 已经提供了基本的文件目录框架,我们一般将应用代码存放于 tuya_ble_sdk_demo\app
中,组件代码存放于 tuya_ble_sdk_demo\components
中。
本 Demo 的文件目录设定如下:
tuya_ble_sdk_demo
├── app /* 应用 */
| ├── include /* 头文件目录,文件名同src */
| └── src /* 源文件目录 */
| ├── tuya_ble_bulk_data_demo.h /* 大数据通道例程 */
| ├── tuya_ble_product_test_demo.h /* 整机产测例程 */
| ├── tuya_ble_sdk_demo.h /* 实现tuya_ble_sdk的初始化,应用程序入口 */
| ├── tuya_ble_sdk_test.h /* 实现tuya_ble_sdk测试的串口指令 */
| └── tuya_ble_sensor_rht_demo.h /* 温湿度传感应用的示例程序 */
├── board
├── components /* 组件 */
| ├── external
| ├── ty_oled
| └── ty_sht3x /* SHT3x驱动组件 */
| ├── ty_sht3x.c
| └── ty_sht3x.h
├── doc
├── tools
└── tuya_ble_sdk
说明:首先,在 app
中新建 src
和 include
两个文件夹,并将 SDK 自带的示例文件 tuya_ble_sdk_demo.c
等按类型移入;其次,分别在 src
和 include
中新建文件 tuya_ble_sensor_rht_demo.c
和 tuya_ble_sensor_rht_demo.h
,用于编写温湿度传感应用的示例代码;最后,在 components
中新建 ty_sht3x
文件夹,并在文件夹中新建 ty_sht3x.c
和 ty_sht3x.h
,用于编写 SHT3x 的驱动组件代码。
工程配置
按照上述文件结构进行设定后,新增了一些头文件目录,因此需要配置工程中的头文件包含路径,否则会导致代码编译失败。可参考下图进行操作,修改后确认代码能否编译通过。
3.1.3 引脚分配
序号 | 符号 | I/O 类型 | 用途 | 备注 |
---|---|---|---|---|
1 | D3 | I/O | - | - |
2 | D7 | I/O | LED 控制 | 主控板的指示灯,高电平点亮 |
3 | C0 | I/O | I2C 通信 - SDA | 对应温湿度传感功能板的 SDA |
4 | SWS | I/O | SWS | 用于 Telink 烧录 |
5 | B6 | I | - | - |
6 | A0 | I/O | - | - |
7 | A1 | I/O | - | - |
8 | C2 | I/O | - | - |
9 | C3 | I/O | LOG 打印 | 用于串口调试 |
10 | D2 | I/O | - | - |
11 | B4 | I/O | - | - |
12 | B5 | I/O | - | - |
13 | GND | P | GND | 接地 |
14 | VCC | P | VCC | 接 3.3V |
15 | B1 | I/O | 串口通信 - TX | 用于串口烧录 |
16 | B7 | I/O | 串口通信 - RX | 用于串口烧录 |
17 | C4 | I/O | - | - |
18 | RST | I/O | RST | 主控板的复位键 |
19 | C1 | I/O | I2C 通信 - SCL | 对应温湿度传感功能板的 SCL |
20 | D4 | I/O | - | - |
21 | NC | I/O | - | - |
上述引脚在代码中的修改位置如下,如果还需要修改引脚模式,可通过以下宏名找到相关配置函数进行修改:
/* tuya_ble_sensor_rht_demo.c */
#define LED_PIN GPIO_PD7/* ty_i2c_tlsr825x.c */
#define I2C_PIN_SDA GPIO_PC0
#define I2C_PIN_SCL GPIO_PC1/* ty_uart_tlsr825x.c */
#define TLSR_UART_GPIO_TX UART_TX_PB1
#define TLSR_UART_GPIO_RX UART_RX_PB7/* telink_sdk\vendor\8258_module\app_config.h */
#define DEBUG_INFO_TX_PIN GPIO_PC3
#define PC3_FUNC AS_GPIO
#define PC3_INPUT_ENABLE 0
#define PC3_OUTPUT_ENABLE 1
#define PC3_DATA_OUT 1
3.1.4 传感简介
本 Demo 使用 涂鸦三明治温湿度传感功能板 来采集温湿度数据。功能板包含一颗 SENSIRION 温湿度传感器 SHT30-DIS,通信采用 I2C 协议,其从机地址由 ADDR 引脚 (Pin2) 控制:ADDR 接 GND 时,设备地址为 0x44
;ADDR 接 VDD 时,设备地址为 0x45
。
官网资料下载:
- 数据手册:Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf
- 示例代码:Sensirion_Humidity_Sensors_Software_SHT3x_Sample_Code.zip
软件流程
3.2 代码说明
3.2.1 初始化函数
tuya_ble_sdk_demo_init
位于:tuya_ble_sdk_demo.c
在 2.2 应用入口 中已经提到,tuya_ble_sdk_demo_init
为应用初始化的入口函数,因此温湿度传感的初始化函数 tuya_ble_sensor_rht_init
放在函数末尾即可。
/*** @brief 应用初始化入口函数* @param 无* @return 无*/
void tuya_ble_sdk_demo_init(void)
{/* ... *//* 温湿度传感示例程序初始化 */tuya_ble_sensor_rht_init();
}
tuya_ble_sensor_rht_init
位于:tuya_ble_sensor_rht_demo.c
/* 数据比例,1表示10的1次方 */
#define TEMP_SCALE 1 /* 温度数据:原始数据的10倍 */
#define HUMI_SCALE 1 /* 湿度数据:原始数据的10倍 *//* 数据采集周期 */
#define SHT3X_PERI_TIME_MS 1000 /* 1s / 1Hz *//* 温湿度数据类型 */
typedef struct {int32_t temp; /* 温度值 */int32_t humi; /* 湿度值 */uint8_t temp_alarm; /* 温度报警值 */uint8_t humi_alarm; /* 湿度报警值 */
} RHT_DP_DATA_T;/* 温湿度数据 */
static RHT_DP_DATA_T sg_rht_data = {.temp = 0, /* 温度,初值:0 */.humi = 0, /* 湿度,初值:0 */.temp_alarm = ALARM_CANCEL, /* 温度报警,初值:报警解除 */.humi_alarm = ALARM_CANCEL /* 湿度报警,初值:报警解除 */
};/* 温湿度采集定时器 */
static tuya_ble_timer_t sg_rht_daq_timer;/*** @brief 温湿度传感应用初始化* @param 无* @return 无*/
void tuya_ble_sensor_rht_init(void)
{/* 打印通知信息:温湿度传感示例程序启动 */TUYA_APP_LOG_INFO("Sensor RH-T demo start.");/* 联网处理初始化 */__net_proc_init();/* SHT3x 驱动初始化,ADDR引脚接地 */ty_sht3x_init(false);/* 测量一次数据,读到的温湿度数据存储到sg_rht_data,数值均扩大10倍 */if (ty_sht3x_measure_single_shot(&sg_rht_data.temp, &sg_rht_data.humi, TEMP_SCALE, HUMI_SCALE)) {/* 打印调试信息:按整型打印温湿度数据 */TUYA_APP_LOG_DEBUG("Temperature: %d, Humidity: %d", sg_rht_data.temp, sg_rht_data.humi);} else {/* 打印错误信息:ty_sht3x_measure_single_shot执行失败 */TUYA_APP_LOG_ERROR("ty_sht3x_measure_single_shot failed.");}/* 启动周期性测量,高重复性,采样频率1Hz */ty_sht3x_start_periodic_measure(REPEATAB_HIGH, FREQ_1HZ);/* 创建并启动定时器sg_rht_daq_timer,定时1s,重复触发,注册处理函数__rht_daq_timer_handler */tuya_ble_timer_create(&sg_rht_daq_timer, SHT3X_PERI_TIME_MS, TUYA_BLE_TIMER_REPEATED, (tuya_ble_timer_handler_t)__rht_daq_timer_handler);tuya_ble_timer_start(sg_rht_daq_timer);
}
__net_proc_init
位于:tuya_ble_sensor_rht_demo.c
/* 引脚定义 */
#define LED_PIN GPIO_PD7 /* LED引脚 */
/* 闪烁间隔 */
#define LED_TIMER_VAL_MS 300 /* 300ms *//* LED闪烁控制定时器 */
static tuya_ble_timer_t sg_led_flash_timer;/* LED状态标志 */
static uint8_t sg_led_status = 0;/*** @brief 联网处理初始化* @param 无* @return 无*/
static void __net_proc_init(void)
{/* 初始化LED引脚:PD7,推挽输出,初期低电平 */ty_pin_init(LED_PIN, TY_PIN_MODE_OUT_PP_LOW);/* 创建定时器sg_led_flash_timer,定时300ms,重复触发,注册超时处理函数__led_flash_timer_cb */tuya_ble_timer_create(&sg_led_flash_timer, LED_TIMER_VAL_MS, TUYA_BLE_TIMER_REPEATED, (tuya_ble_timer_handler_t)__led_flash_timer_cb);/* 获取蓝牙网络状态并打印 */tuya_ble_connect_status_t ble_conn_sta = tuya_ble_connect_status_get();TUYA_APP_LOG_DEBUG("BLE connect status: %d.", ble_conn_sta);/* 根据蓝牙网络状态控制指示灯 */if ((ble_conn_sta == BONDING_UNCONN) ||(ble_conn_sta == BONDING_CONN) ||(ble_conn_sta == BONDING_UNAUTH_CONN)) {/* 如果蓝牙网络状态为已绑定,指示灯保持熄灭 */TUYA_APP_LOG_INFO("LED keep off."); /* 打印信息:指示灯保持熄灭 */} else {/* 如果蓝牙网络状态为未绑定,指示灯开始闪烁 */sg_led_status = 1; /* 更新LED状态标志 */ty_pin_set(LED_PIN, TY_PIN_HIGH); /* LED引脚输出高电平 */tuya_ble_timer_start(sg_led_flash_timer); /* 启动定时器sg_led_flash_timer */TUYA_APP_LOG_INFO("LED start falshing."); /* 打印信息:指示灯开始闪烁 */}
}
ty_pin_init
位于:ty_pin_tlsr825x.c
(适用于 TLSR825x 芯片平台的 GPIO 驱动文件)
这部分代码需要您自行补充,可参考本 Demo 进行移植;或者您可以直接调用原厂接口来驱动引脚。
/*** @brief 引脚初始化* @param[in] pin: 引脚编号(参数类型需要根据芯片平台修改)* @param[in] mode: 引脚模式* @return none*/
uint32_t ty_pin_init(uint16_t pin, ty_pin_mode_t mode)
{/* 设置引脚功能 */gpio_set_func(pin, AS_GPIO);/* 设置引脚输出方向 */if ((mode & TY_PIN_INOUT_MASK) <= TY_PIN_IN_IRQ) {gpio_set_input_en(pin, 1);gpio_set_output_en(pin, 0);} else {gpio_set_input_en(pin, 0);gpio_set_output_en(pin, 1);}/* 设置引脚模式和初期电平 */switch (mode) {case TY_PIN_MODE_IN_PU:gpio_setup_up_down_resistor(pin, PM_PIN_PULLUP_10K);break;case TY_PIN_MODE_IN_PD:gpio_setup_up_down_resistor(pin, PM_PIN_PULLDOWN_100K);break;case TY_PIN_MODE_IN_FL:gpio_setup_up_down_resistor(pin, PM_PIN_UP_DOWN_FLOAT);break;case TY_PIN_MODE_OUT_PP_LOW:gpio_write(pin, 0);break;case TY_PIN_MODE_OUT_PP_HIGH:gpio_write(pin, 1);break;default:break;}/* 设置唤醒引脚 */
#if (GPIO_WAKEUP_MODULE_POLARITY == 1)cpu_set_gpio_wakeup (WAKEUP_MODULE_GPIO, Level_High, 1);GPIO_WAKEUP_MODULE_LOW;
#elsecpu_set_gpio_wakeup (WAKEUP_MODULE_GPIO, Level_Low, 1);GPIO_WAKEUP_MODULE_HIGH;
#endifreturn 0;
}
ty_sht3x_init
位于:ty_sht3x.c
/* I2C设备地址 */
#define SHT3X_DEV_ADDR_A 0x44 /* ADDR引脚(pin2)连接逻辑低电平 */
#define SHT3X_DEV_ADDR_B 0x45 /* ADDR引脚(pin2)连接逻辑高电平 *//* I2C设备地址变量,默认ADDR接地 */
uint8_t g_dev_addr = SHT3X_DEV_ADDR_A;/*** @brief SHT3x驱动初始化* @param[in] addr_pin_high: ADDR引脚是否接高电平* @return 无*/
void ty_sht3x_init(bool addr_pin_high)
{/* I2C初始化 */ty_i2c_init();/* 设置从机地址 */if (addr_pin_high) {g_dev_addr = SHT3X_DEV_ADDR_B;}
}
ty_i2c_init
位于:ty_i2c_tlsr825x.c
(适用于 TLSR825x 芯片平台的 I2C 驱动文件)
由于选择使用软件 I2C 驱动,还需要将 ty_i2c.h
中的 USE_SOFT_I2C
的值修改为 1。
/* I2C引脚定义,根据硬件设计修改引脚编号 */
#define I2C_PIN_SDA GPIO_PC0
#define I2C_PIN_SCL GPIO_PC1/*** @brief I2C引脚初始化* @param 无* @return 无*/
void i2c_soft_gpio_init(void)
{gpio_set_func(I2C_PIN_SDA, AS_GPIO);gpio_set_func(I2C_PIN_SCL, AS_GPIO);gpio_set_output_en(I2C_PIN_SDA, 1);gpio_set_input_en(I2C_PIN_SDA, 0);gpio_set_output_en(I2C_PIN_SCL, 1);gpio_set_input_en(I2C_PIN_SCL, 0);
}/*** @brief I2C初始化* @param 无* @return 操作结果*/
uint32_t ty_i2c_init(void)
{#if (USE_SOFT_I2C == 0)/* ... */
#else/* I2C引脚初始化 */i2c_soft_gpio_init();
#endifreturn 0;
}
3.2.2 超时处理函数
__led_flash_timer_cb
位于:tuya_ble_sensor_rht_demo.c
/*** @brief 指示灯闪烁控制超时处理函数* @param 无* @return 无*/
static void __led_flash_timer_cb(void)
{/* 翻转LED状态 */sg_led_status = !sg_led_status;/* 根据LED状态控制引脚输出电平 */if (sg_led_status) {ty_pin_set(LED_PIN, TY_PIN_HIGH);} else {ty_pin_set(LED_PIN, TY_PIN_LOW);}
}
__rht_daq_timer_handler
位于:tuya_ble_sensor_rht_demo.c
/* 报警代码 */
#define ALARM_LOWER 0 /* 低于阈值报警 */
#define ALARM_UPPER 1 /* 高于阈值报警 */
#define ALARM_CANCEL 2 /* 报警解除 *//* 报警阈值数据类型 */
typedef struct {int32_t temp_max; /* 温度上限 */int32_t temp_min; /* 温度下限 */int32_t humi_max; /* 湿度上限 */int32_t humi_min; /* 湿度下限 */
} ALARM_THR_T;/* 报警阈值 */
static ALARM_THR_T sg_alarm_thr = {.temp_max = 400, /* 温度上限,初值:40℃ */.temp_min = 0, /* 温度下限,初值:0℃ */.humi_max = 700, /* 湿度上限,初值:70% */.humi_min = 300 /* 湿度下限,初值:30% */
};/*** @brief 检查温度是否超过报警阈值* @param[in] cur_temp: 当前温度* @return 报警代码*/
static uint8_t __check_temp_val(int32_t cur_temp)
{uint8_t res;if (cur_temp < sg_alarm_thr.temp_min) {res = ALARM_LOWER;} else if (cur_temp > sg_alarm_thr.temp_max) {res = ALARM_UPPER;} else {res = ALARM_CANCEL;}return res;
}/*** @brief 检查湿度是否超过报警阈值* @param[in] cur_humi: 当前湿度* @return 报警代码*/
static uint8_t __check_humi_val(int32_t cur_humi)
{uint8_t res;if (cur_humi < sg_alarm_thr.humi_min) {res = ALARM_LOWER;} else if (cur_humi > sg_alarm_thr.humi_max) {res = ALARM_UPPER;} else {res = ALARM_CANCEL;}return res;
}/*** @brief 温湿度数据采集超时处理函数* @param 无* @return 无*/
static void __rht_daq_timer_handler(void)
{int32_t temp, humi;/* 读取温湿度数据,存储到temp和humi,数值均扩大10倍 */if (ty_sht3x_read_data(&temp, &humi, TEMP_SCALE, HUMI_SCALE)) {/* 读到数据则打印 */TUYA_APP_LOG_DEBUG("Temperature: %d, Humidity: %d", temp, humi);/* 数据转存至sg_rht_data */sg_rht_data.temp = temp;sg_rht_data.humi = humi;/* 检查数据是否超过报警阈值 */sg_rht_data.temp_alarm = __check_temp_val(temp);sg_rht_data.humi_alarm = __check_humi_val(humi);/* 如果蓝牙已连接则上报数据 */if (BONDING_CONN == tuya_ble_connect_status_get()) {__repo_dp_data_all();}} else {/* 打印错误信息:ty_sht3x_read_data执行失败 */TUYA_APP_LOG_ERROR("ty_sht3x_read_data failed.");}
}
3.2.3 DP上报函数
__repo_dp_data_all
位于:tuya_ble_sensor_rht_demo.c
/* DP ID (涂鸦IoT平台上定义的DP点的ID) */
#define DP_ID_TEMP_CURRENT 1
#define DP_ID_HUMIDITY_VALUE 2
#define DP_ID_TEMP_ALARM 14
#define DP_ID_HUM_ALARM 15/*** @brief 上报所有需要更新的DP数据* @param 无* @return 无*/
static void __repo_dp_data_all(void)
{/* 定义前回数据存储变量 */static RHT_DP_DATA_T s_prv_val = {.temp = 0,.humi = 0,.temp_alarm = ALARM_CANCEL,.humi_alarm = ALARM_CANCEL};/* 上报温度数据 */if (sg_rht_data.temp != s_prv_val.temp) {__report_one_dp_data(DP_ID_TEMP_CURRENT, DT_VALUE, 4, (uint8_t *)&sg_rht_data.temp);s_prv_val.temp = sg_rht_data.temp;}/* 上报湿度数据 */if (sg_rht_data.humi != s_prv_val.humi) {__report_one_dp_data(DP_ID_HUMIDITY_VALUE, DT_VALUE, 4, (uint8_t *)&sg_rht_data.humi);s_prv_val.humi = sg_rht_data.humi;}/* 上报温度报警信息 */if (sg_rht_data.temp_alarm != s_prv_val.temp_alarm) {__report_one_dp_data(DP_ID_TEMP_ALARM, DT_ENUM, 1, (uint8_t *)&sg_rht_data.temp_alarm);s_prv_val.temp_alarm = sg_rht_data.temp_alarm;}/* 上报湿度报警信息 */if (sg_rht_data.humi_alarm != s_prv_val.humi_alarm) {__report_one_dp_data(DP_ID_HUM_ALARM, DT_ENUM, 1, (uint8_t *)&sg_rht_data.humi_alarm);s_prv_val.humi_alarm = sg_rht_data.humi_alarm;}
}
__report_one_dp_data
位于:tuya_ble_sensor_rht_demo.c
/* DP模型字段偏移量 */
#define DP_DATA_INDEX_OFFSET_ID 0 /* dp_id */
#define DP_DATA_INDEX_OFFSET_TYPE 1 /* dp_type */
#define DP_DATA_INDEX_OFFSET_LEN_H 2 /* dp_len高字节 */
#define DP_DATA_INDEX_OFFSET_LEN_L 3 /* dp_len低字节 */
#define DP_DATA_INDEX_OFFSET_DATA 4 /* dp_data *//* 上报数组的大小 */
#define REPO_ARRAY_SIZE 8/* 上报数据存储数组 */
static uint8_t sg_repo_array[REPO_ARRAY_SIZE];/* 发送序号,初值:0 */
static uint32_t sg_sn = 0;/*** @brief 上报一个DP数据* @param[in] dp_id: DP ID* @param[in] dp_type: DP数据类型* @param[in] dp_len: DP数据长度* @param[in] dp_data: DP数据* @return 无*/
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;/* 将DP数据存入数组 */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));}/* 调用API发送DP数据 */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);
}
3.2.4 Callback函数
tuya_ble_sdk_callback
位于:tuya_ble_sdk_demo.c
Callback 函数已经在 2.3.5 Callback 中提到,本 DEMO 中主要关注以下几个事件:
/*** @brief tuya_ble_sdk callback* @param[in] event: 事件(消息)* @return 无*/
static void tuya_ble_sdk_callback(tuya_ble_cb_evt_param_t* event)
{switch (event->evt) {/* 蓝牙连接状态变更 */case TUYA_BLE_CB_EVT_CONNECTE_STATUS: {/* 当前蓝牙连接状态变更为“已绑定已连接”时 */if (event->connect_status == BONDING_CONN) {TUYA_APP_LOG_INFO("bonding and connecting");tuya_ble_update_conn_param_timer_start();/* 蓝牙连接处理 */tuya_net_proc_ble_conn();}} break;/* 接收到手机APP发送的DP数据 */case TUYA_BLE_CB_EVT_DP_DATA_RECEIVED: {/* 接收DP数据的处理,传入DP数据和DP数据总长度 */tuya_net_proc_dp_recv(event->dp_received_data.p_data, event->dp_received_data.data_len);} break;/* 接收到手机APP发送的解绑指令 */case TUYA_BLE_CB_EVT_UNBOUND: {/* 蓝牙解绑处理 */tuya_net_proc_ble_unbound();TUYA_APP_LOG_INFO("TUYA_BLE_CB_EVT_UNBOUND");} break;/* 接收到手机APP发送的异常解绑指令 */case TUYA_BLE_CB_EVT_ANOMALY_UNBOUND: {/* 蓝牙解绑处理 */tuya_net_proc_ble_unbound();TUYA_APP_LOG_INFO("TUYA_BLE_CB_EVT_ANOMALY_UNBOUND");} break;/* 默认 */default: {TUYA_APP_LOG_INFO("tuya_ble_sdk_callback unknown event type 0x%04x", event->evt);} break;}
}
tuya_net_proc_ble_conn
位于:tuya_ble_sensor_rht_demo.c
/*** @brief 蓝牙连接处理函数* @param 无* @return 无*/
void tuya_net_proc_ble_conn(void)
{sg_led_status = 0; /* 更新LED状态标志 */ty_pin_set(LED_PIN, TY_PIN_LOW); /* LED引脚输出低电平 */tuya_ble_timer_stop(sg_led_flash_timer);/* 停止定时器sg_led_flash_timer */TUYA_APP_LOG_INFO("LED stop falshing.");/* 打印通知信息:指示灯停止闪烁 */
}
tuya_net_proc_dp_recv
位于:tuya_ble_sensor_rht_demo.c
/* DP ID (涂鸦IoT平台上定义的DP点的ID) */
#define DP_ID_MAXTEMP_SET 10
#define DP_ID_MINITEMP_SET 11
#define DP_ID_MAXHUM_SET 12
#define DP_ID_MINIHUM_SET 13/*** @brief DP数据接收处理函数* @param[in] dp_data: DP数据* @param[in] dp_len: DP数据总长度* @return 无*/
void tuya_net_proc_dp_recv(uint8_t *dp_data, uint16_t dp_len)
{int32_t val = 0;/* 判断DP数据长度是否为4 (Value类型为4bytes) */if (dp_len - 4 == 4) {/* 拼接数据并打印调试信息 */val = dp_data[4] << 24 | dp_data[5] << 16 | dp_data[6] << 8 | dp_data[7];TUYA_APP_LOG_DEBUG("val: %x", val);}/* 根据DP_ID进行处理 */switch (dp_data[0]) {/* 温度上限:存储数据并上报,打印通知信息 */case DP_ID_MAXTEMP_SET:sg_alarm_thr.temp_max = val;__report_one_dp_data(DP_ID_MAXTEMP_SET, DT_VALUE, 4, (uint8_t *)&val);TUYA_APP_LOG_INFO("Set the maximum temperature to %d.", sg_alarm_thr.temp_max);break;/* 温度下限:存储数据并上报,打印通知信息 */case DP_ID_MINITEMP_SET:sg_alarm_thr.temp_min = val;__report_one_dp_data(DP_ID_MINITEMP_SET, DT_VALUE, 4, (uint8_t *)&val);TUYA_APP_LOG_INFO("Set the minimum temperature to %d.", sg_alarm_thr.temp_min);break;/* 湿度上限:存储数据并上报,打印通知信息 */case DP_ID_MAXHUM_SET:sg_alarm_thr.humi_max = val;__report_one_dp_data(DP_ID_MAXHUM_SET, DT_VALUE, 4, (uint8_t *)&val);TUYA_APP_LOG_INFO("Set the maximum humidity to %d.", sg_alarm_thr.humi_max);break;/* 温度下限:存储数据并上报,打印通知信息 */case DP_ID_MINIHUM_SET:sg_alarm_thr.humi_min = val;__report_one_dp_data(DP_ID_MINIHUM_SET, DT_VALUE, 4, (uint8_t *)&val);TUYA_APP_LOG_INFO("Set the minimum humidity to %d.", sg_alarm_thr.humi_min);break;/* 默认 */default:break;}
}
tuya_net_proc_ble_unbound
位于:tuya_ble_sensor_rht_demo.c
/*** @brief 蓝牙解绑处理函数* @param 无* @return 无*/
void tuya_net_proc_ble_unbound(void)
{sg_led_status = 1; /* 更新LED状态标志 */ty_pin_set(LED_PIN, TY_PIN_HIGH); /* LED引脚输出高电平 */tuya_ble_timer_start(sg_led_flash_timer); /* 启动定时器sg_led_flash_timer */TUYA_APP_LOG_INFO("LED start falshing."); /* 打印通知信息:指示灯开始闪烁 */
}
3.2.5 传感驱动函数
ty_sht3x_measure_single_shot
位于:ty_sht3x.c
#define SHT3X_CMD_MEAS_CLOCKSTR_H 0x2C06 /* 单次测量: 时钟拉伸, 高重复性 *//*** @brief 测量一次数据* @param[out] temp: 温度值* @param[out] humi: 湿度值* @param[in] temp_scale: 温度倍数* @param[in] humi_scale: 湿度倍数* @return 操作结果*/
uint8_t ty_sht3x_measure_single_shot(int32_t *temp, int32_t *humi, uint8_t temp_scale, uint8_t humi_scale)
{/* 写命令:单次测量,带停止信号 */__sht3x_write_cmd(SHT3X_CMD_MEAS_CLOCKSTR_H, 1);/* 延时20ms */i2c_delay(20000);/* 读数据并返回操作结果 */return __sht3x_read_data(temp, humi, temp_scale, humi_scale);
}
ty_sht3x_start_periodic_measure
位于:ty_sht3x.c
#define SHT3X_CMD_MEAS_PERI_05_H 0x2032 /* 每秒测量0.5次(0.5Hz), 高重复性 */
#define SHT3X_CMD_MEAS_PERI_05_M 0x2024 /* 每秒测量0.5次(0.5Hz), 中重复性 */
#define SHT3X_CMD_MEAS_PERI_05_L 0x202F /* 每秒测量0.5次(0.5Hz), 低重复性 */
#define SHT3X_CMD_MEAS_PERI_1_H 0x2130 /* 每秒测量1次(1Hz), 高重复性 */
#define SHT3X_CMD_MEAS_PERI_1_M 0x2126 /* 每秒测量1次(1Hz), 中重复性 */
#define SHT3X_CMD_MEAS_PERI_1_L 0x212D /* 每秒测量1次(1Hz), 低重复性 */
#define SHT3X_CMD_MEAS_PERI_2_H 0x2236 /* 每秒测量2次(2Hz), 高重复性 */
#define SHT3X_CMD_MEAS_PERI_2_M 0x2220 /* 每秒测量2次(2Hz), 中重复性 */
#define SHT3X_CMD_MEAS_PERI_2_L 0x222B /* 每秒测量2次(2Hz), 低重复性 */
#define SHT3X_CMD_MEAS_PERI_4_H 0x2334 /* 每秒测量4次(4Hz), 高重复性 */
#define SHT3X_CMD_MEAS_PERI_4_M 0x2322 /* 每秒测量4次(4Hz), 中重复性 */
#define SHT3X_CMD_MEAS_PERI_4_L 0x2329 /* 每秒测量4次(4Hz), 低重复性 */
#define SHT3X_CMD_MEAS_PERI_10_H 0x2737 /* 每秒测量10次(10Hz), 高重复性 */
#define SHT3X_CMD_MEAS_PERI_10_M 0x2721 /* 每秒测量10次(10Hz), 中重复性 */
#define SHT3X_CMD_MEAS_PERI_10_L 0x272A /* 每秒测量10次(10Hz), 低重复性 *//*** @brief 启动周期性测量* @param[in] rept: 重复性* @param[in] freq: 采样频率* @return 无*/
void ty_sht3x_start_periodic_measure(SHT3X_REPT_E rept, SHT3X_FREQ_E freq)
{switch (rept) {/* 高重复性 */case REPEATAB_HIGH:switch (freq) {case FREQ_HZ5: /* 0.5Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_05_H, 0);break;case FREQ_1HZ: /* 1Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_1_H, 0);break;case FREQ_2HZ: /* 2Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_2_H, 0);break;case FREQ_4HZ: /* 4Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_4_H, 0);break;case FREQ_10HZ: /* 10Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_10_H, 0);break;default:break;}break;/* 中重复性 */case REPEATAB_MEDIUM:switch (freq) {case FREQ_HZ5: /* 0.5Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_05_M, 0);break;case FREQ_1HZ: /* 1Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_1_M, 0);break;case FREQ_2HZ: /* 2Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_2_M, 0);break;case FREQ_4HZ: /* 4Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_4_M, 0);break;case FREQ_10HZ: /* 10Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_10_M, 0);break;default:break;}break;/* 低重复性 */case REPEATAB_LOW:switch (freq) {case FREQ_HZ5: /* 0.5Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_05_L, 0);break;case FREQ_1HZ: /* 1Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_1_L, 0);break;case FREQ_2HZ: /* 2Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_2_L, 0);break;case FREQ_4HZ: /* 4Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_4_L, 0);break;case FREQ_10HZ: /* 10Hz */__sht3x_write_cmd(SHT3X_CMD_MEAS_PERI_10_L, 0);break;default:break;}break;default:break;}
}
ty_sht3x_read_data
位于:ty_sht3x.c
#define SHT3X_CMD_FETCH_DATA 0xE000 /* 读取数据(周期性测量模式) *//*** @brief 从SHT3x读数据* @param[out] temp: 温度值* @param[out] humi: 湿度值* @param[in] temp_scale: 温度倍数* @param[in] humi_scale: 湿度倍数* @return 操作结果*/
uint8_t ty_sht3x_read_data(int32_t *temp, int32_t *humi, uint8_t temp_scale, uint8_t humi_scale)
{/* 写命令:读取数据,不带停止信号 */__sht3x_write_cmd(SHT3X_CMD_FETCH_DATA, 0);/* 读数据并返回操作结果 */return __sht3x_read_data(temp, humi, temp_scale, humi_scale);
}
__sht3x_write_cmd
位于:ty_sht3x.c
/* I2C写命令位 */
#define I2C_CMD_BIT_WRITE 0/*** @brief 写命令到SHT3x* @param[in] cmd: 命令字* @param[in] stop: 是否需要发送停止信号* @return 无*/
static void __sht3x_write_cmd(uint16_t cmd, bool stop)
{/* 拆分命令字 */uint8_t cmd_bytes[2];cmd_bytes[0] = (uint8_t)(cmd >> 8);cmd_bytes[1] = (uint8_t)(cmd & 0x00FF);/* I2C发送 */i2c_start();i2c_send_bytes((g_dev_addr << 1) | I2C_CMD_BIT_WRITE, cmd_bytes, 2);if (stop) {i2c_stop();}
}
__sht3x_read_data
位于:ty_sht3x.c
/* I2C读命令位 */
#define I2C_CMD_BIT_READ 1/*** @brief 读取多个字节* @param[out] buffer: 数据缓存* @param[in] len: 数据长度* @return 无*/
static void __sht3x_read_bytes(uint8_t *buffer, uint8_t len)
{i2c_start();i2c_rcv_bytes((g_dev_addr << 1) | I2C_CMD_BIT_READ, buffer, len);i2c_stop();
}/*** @brief 计算温度* @param[in] raw_data: 原始数据* @param[in] scale: 倍数* @return 温度值,单位℃*/
static int32_t __sht3x_calc_temp(int16_t raw_data, uint8_t scale)
{int32_t gain = 1;while(scale--) {gain *= 10;}return (gain * 175 * (int32_t)raw_data / 65535 - gain * 45);
}/*** @brief 计算湿度* @param[in] raw_data: 原始数据* @param[in] scale: 倍数* @return 温度值,单位RT%*/
static int32_t __sht3x_calc_humi(uint16_t raw_data, uint8_t scale)
{int32_t gain = 1;while(scale--) {gain *= 10;}return (gain * 100 * (int32_t)raw_data / 65535);
}/*** @brief 读取数据* @param[out] temp: 温度值* @param[out] humi: 湿度值* @param[in] temp_scale: 温度倍数* @param[in] humi_scale: 湿度倍数* @return 操作结果*/
uint8_t __sht3x_read_data(int32_t *temp, int32_t *humi, uint8_t temp_scale, uint8_t humi_scale)
{uint8_t buf[6];/* 读取6个字节数据:温度(2 bytes),湿度(2 bytes),校验码(2 bytes) */__sht3x_read_bytes(buf, 6);/* 检查CRC8校验码 */if ((!__sht3x_check_crc(buf, 2, buf[2])) ||(!__sht3x_check_crc(buf+3, 2, buf[5]))) {return 0; /* 校验失败,返回0 */}/* 根据倍数要求计算输出的数据 */*temp = __sht3x_calc_temp(((int16_t)buf[0] << 8) | buf[1], temp_scale);*humi = __sht3x_calc_humi(((int16_t)buf[3] << 8) | buf[4], humi_scale);return 1; /* 校验成功,返回1 */
}
3.3 功能演示
3.3.1 设备配网
将开发完成的程序烧录至开发板中,复位芯片,即可看到指示灯闪烁,表示设备正在等待用户绑定。
通过智能生活 APP 添加设备,设备成功绑定后,指示灯熄灭。
如果之前设备已被绑定,那么指示灯不会闪烁,打开 APP 后设备会自动连接。可按下图在 APP 上将设备解绑之后,即可看到指示灯闪烁的效果。
3.3.2 数据更新
设备连接 APP 后,可以看到温湿度数据实时更新。
3.2.3 阈值报警
在 APP 端设置温湿度报警阈值。
可通过一些人为手段使环境温湿度超过设定阈值,此时 APP 端即可收到设备上报的报警信息。
上一篇: 第 4 课:烧录授权
下一篇: 第 6 课:固件升级
涂鸦蓝牙SDK开发系列教程——5.应用开发相关推荐
- 涂鸦 Wi-Fi SDK开发系列教程——1. SoC开发环境搭建
SoC开发环境搭建 本章节主要介绍如何搭建涂鸦 Wi-Fi 和 Wi-Fi&Bluetooth LE 系列模组二次开发的编译环境.在 Windows 上使用 VirtualBox 虚拟机软件安 ...
- 涂鸦蓝牙SDK开发系列教程——1.快速入门
本系列课程将介绍如何使用 涂鸦蓝牙模组及其 SDK 进行产品开发,帮助开发者更快掌握 涂鸦蓝牙模组.涂鸦蓝牙 SDK.涂鸦 IoT 平台.涂鸦三明治开发套件.涂鸦云模组烧录授权平台 等开发工具的使用方 ...
- 涂鸦蓝牙SDK开发系列教程——6.固件升级
涂鸦 IoT 平台提供 固件 OTA 升级 功能,以满足客户在产品发布后,仍可对已出货设备进行固件版本升级的需求,本节课将介绍不同开发方式下固件 OTA 升级的方法.涂鸦方案的 OTA 流程与交互协议 ...
- android app初始化sdk,Android SDK使用系列教程——2.SDK初始化和常用类介绍
本帖最后由 碎羽 于 2015-6-18 11:36 编辑 上次讲到SDK的下载和导入,这次来讲讲SDK的初始化和常用类的介绍. 一.初始化SDK 初始化SDK,首先要获得对应设备的AppID.App ...
- C#微信公众号开发系列教程三(消息体签名及加解密)
http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...
- 微信小程序开发系列教程三:微信小程序的调试方法
微信小程序开发系列教程 微信小程序开发系列一:微信小程序的申请和开发环境的搭建 微信小程序开发系列二:微信小程序的视图设计 这个教程的前两篇文章,介绍了如何用下图所示的微信开发者工具自动生成一个Hel ...
- C#微信公众号开发系列教程五(接收事件推送与消息排重)
C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...
- C#微信公众号开发系列教程二(新手接入指南)
此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可直接跳过,也欢迎大神吐槽. 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教 ...
- HTML5游戏开发系列教程5(译)
原文地址:http://www.script-tutorials.com/html5-game-development-lesson-5/ 最终我决定准备下一篇游戏开发系列的文章,我们将继续使用can ...
最新文章
- Delegate,Action,Func,匿名方法,匿名委托,事件 (转载)
- 苹果应用ipa图片提取
- MySql根据经纬度查询任意距离范围内数据
- tensorRT 使用过程中的Bug记录
- c语言试卷大全,C语言试题大全
- 在什么情况下,刘强东会丧失京东的控制权?
- VUE 解决:Method “xxx“ has already been defined as a data property.
- java split 路径,JAVA通过文件路径分隔符分割文件路径
- 机器学习与计算机视觉(slam技术)
- 1971年中国的预警机就上天了
- Visio 2003软件安装教程
- 如何在工具类中注入Service
- 小米8装magisk
- 案例分析:股票涨跌预测
- 6 个常用的 API 接口在线管理平台
- win10计算机恢复到一天前,Win10恢复电脑(刷新电脑)的方法步骤图文详解
- RS485通信接口设计方案
- 诚之和:李子柒向资本发起“反击战”
- mysql 查询练习——计算总订单价格大于100的订单号和总订单价格
- canvas制作画板