在前几节课中已经介绍了如何在 涂鸦 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 功能:

  1. 在 tuya-ble-sdk-development-course-demo 中下载本课程配套 Demo;(app_demo)
  2. 参考 3.1.2 文件结构 将仓库中 app_demo 目录下 tuya_ble_sdk_demo.h (包含了 PID 和固件版本信息等,请保留您自己的配置) 的所有文件复制到您的工程中,并在工程配置中添加 头文件路径
  3. 根据您使用的 授权方案 确认是否需要修改初始化参数 use_ext_license_keydevice_id_len
  4. 根据您的电路设计确认是否需要修改相关引脚编号,可参考 3.1.3 引脚分配;
  5. 编译代码,并使用 Telink BDT 将生成的固件烧录到您的开发板中;
  6. 使用 智能生活 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 的初始化,封装应用层的事件调度 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 操作不同)

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 中新建 srcinclude 两个文件夹,并将 SDK 自带的示例文件 tuya_ble_sdk_demo.c 等按类型移入;其次,分别在 srcinclude 中新建文件 tuya_ble_sensor_rht_demo.ctuya_ble_sensor_rht_demo.h,用于编写温湿度传感应用的示例代码;最后,在 components 中新建 ty_sht3x 文件夹,并在文件夹中新建 ty_sht3x.cty_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.应用开发相关推荐

  1. 涂鸦 Wi-Fi SDK开发系列教程——1. SoC开发环境搭建

    SoC开发环境搭建 本章节主要介绍如何搭建涂鸦 Wi-Fi 和 Wi-Fi&Bluetooth LE 系列模组二次开发的编译环境.在 Windows 上使用 VirtualBox 虚拟机软件安 ...

  2. 涂鸦蓝牙SDK开发系列教程——1.快速入门

    本系列课程将介绍如何使用 涂鸦蓝牙模组及其 SDK 进行产品开发,帮助开发者更快掌握 涂鸦蓝牙模组.涂鸦蓝牙 SDK.涂鸦 IoT 平台.涂鸦三明治开发套件.涂鸦云模组烧录授权平台 等开发工具的使用方 ...

  3. 涂鸦蓝牙SDK开发系列教程——6.固件升级

    涂鸦 IoT 平台提供 固件 OTA 升级 功能,以满足客户在产品发布后,仍可对已出货设备进行固件版本升级的需求,本节课将介绍不同开发方式下固件 OTA 升级的方法.涂鸦方案的 OTA 流程与交互协议 ...

  4. android app初始化sdk,Android SDK使用系列教程——2.SDK初始化和常用类介绍

    本帖最后由 碎羽 于 2015-6-18 11:36 编辑 上次讲到SDK的下载和导入,这次来讲讲SDK的初始化和常用类的介绍. 一.初始化SDK 初始化SDK,首先要获得对应设备的AppID.App ...

  5. C#微信公众号开发系列教程三(消息体签名及加解密)

    http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...

  6. 微信小程序开发系列教程三:微信小程序的调试方法

    微信小程序开发系列教程 微信小程序开发系列一:微信小程序的申请和开发环境的搭建 微信小程序开发系列二:微信小程序的视图设计 这个教程的前两篇文章,介绍了如何用下图所示的微信开发者工具自动生成一个Hel ...

  7. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...

  8. C#微信公众号开发系列教程二(新手接入指南)

    此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可直接跳过,也欢迎大神吐槽. 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教 ...

  9. HTML5游戏开发系列教程5(译)

    原文地址:http://www.script-tutorials.com/html5-game-development-lesson-5/ 最终我决定准备下一篇游戏开发系列的文章,我们将继续使用can ...

最新文章

  1. Delegate,Action,Func,匿名方法,匿名委托,事件 (转载)
  2. 苹果应用ipa图片提取
  3. MySql根据经纬度查询任意距离范围内数据
  4. tensorRT 使用过程中的Bug记录
  5. c语言试卷大全,C语言试题大全
  6. 在什么情况下,刘强东会丧失京东的控制权?
  7. VUE 解决:Method “xxx“ has already been defined as a data property.
  8. java split 路径,JAVA通过文件路径分隔符分割文件路径
  9. 机器学习与计算机视觉(slam技术)
  10. 1971年中国的预警机就上天了
  11. Visio 2003软件安装教程
  12. 如何在工具类中注入Service
  13. 小米8装magisk
  14. 案例分析:股票涨跌预测
  15. 6 个常用的 API 接口在线管理平台
  16. win10计算机恢复到一天前,Win10恢复电脑(刷新电脑)的方法步骤图文详解
  17. RS485通信接口设计方案
  18. 诚之和:李子柒向资本发起“反击战”
  19. mysql 查询练习——计算总订单价格大于100的订单号和总订单价格
  20. canvas制作画板

热门文章

  1. C语言基础知识入门和C语言入门基础知识大全
  2. ubuntu22 使用todesk被远程控制时显示黑屏或者白屏
  3. 如何到官网下载jqury
  4. android manifest相关属性
  5. SQL Server 还原数据库
  6. 窗口看门狗和独立看门狗区别
  7. Cisco Packet Tracer 思科模拟器SSH配置
  8. (20200921 Solved)ConnectionResetError: [Errno 104] Connection reset by peer
  9. WeRateDog---分析推特数据
  10. 计算机导航种植牙的优势,计算机导航微创种植牙修复