应用开发

更新时间:2023-01-29 07:32:16下载pdf

在前几节课中,我们已经介绍了如何在 涂鸦 IoT 开发平台 上创建智能产品以及如何搭建涂鸦蓝牙模组的开发环境。在此基础上,本节课将继续以 BTU 模组为例,介绍如何使用 涂鸦蓝牙模组及其 SDK 开发一款温湿度传感器产品。

前期准备

产品创建

首先,在 涂鸦 IoT 开发平台 上创建一个 温湿度传感器 产品。具体方法,请参考 产品创建

环境搭建

本 Demo 的开发环境如下:

  • 涂鸦三明治蓝牙 LE 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 功能:

  1. tuya-ble-sdk-development-course-demo 中下载本课程配套 Demo,即 app_demo
  2. 参考本章 Demo 介绍中的 文件结构 将仓库中 app_demo 目录下 tuya_ble_sdk_demo.h (包含了 PID 和固件版本信息等,请保留您自己的配置) 的所有文件复制到您的工程中,并在工程配置中添加 头文件路径
  3. 根据您使用的 授权方案,确认是否需要修改初始化参数 use_ext_license_keydevice_id_len
  4. 根据您的电路设计,确认是否需要修改相关引脚编号。可参考本章的 引脚分配
  5. 编译代码,并使用 Telink BDT 将生成的固件烧录到您的开发板中。
  6. 使用 智能生活 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)
    {
        /* 根据是否使用 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 中。如果有需要循环处理的任务可放置于该函数中,但更建议使用创建定时器的方式来执行,定时器的使用方法可参考下文常用 API 的 软件定时器 介绍。

    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	/* 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. 软件定时器

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);
}

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);
}

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();
}

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 介绍

Demo 设计

功能定义

本 Demo 的基本功能设定如下:

功能 说明
数据采集 每秒采集一次温湿度数据。温湿度数据变化时,上报数据给手机 App,更新设备面板显示。
阈值报警 可通过手机 App 设置温度上下限和湿度上下限。当温湿度超过设定阈值时,可上报对应的报警信息至手机 App。
配网指示
  • 设备未被绑定,指示灯闪烁。
  • 设备被绑定时,指示灯熄灭。
  • 设备被解绑后,指示灯闪烁。

根据功能设定,我们先在产品开发的 功能定义 页面添加相关的功能。上述功能都可以在 标准功能 中找到,添加后再对属性进行编辑即可,可参考下图配置:

应用开发

如果您在开发过程中修改了 DP 定义,或者遇到设备面板显示异常的情况,建议 重新绑定 设备。

文件结构

涂鸦蓝牙 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
  1. app 中新建 srcinclude 两个文件夹,并将 SDK 自带的示例文件 tuya_ble_sdk_demo.c 等按类型移入。
  2. 分别在 srcinclude 中新建文件 tuya_ble_sensor_rht_demo.ctuya_ble_sensor_rht_demo.h,用于编写温湿度传感应用的示例代码。
  3. 最后,在 components 中新建 ty_sht3x 文件夹,并在文件夹中新建 ty_sht3x.cty_sht3x.h,用于编写 SHT3x 的驱动组件代码。

工程配置

按照上述文件结构进行设定后,新增了一些头文件目录,因此需要配置工程中的头文件包含路径,否则会导致代码编译失败。可参考下图进行操作,修改后确认代码能否编译通过。

应用开发

应用开发

引脚分配

序号 符号 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

传感简介

本 Demo 使用 涂鸦三明治温湿度传感功能板 来采集温湿度数据。功能板包含一颗 SENSIRION 温湿度传感器 SHT30-DIS,通信采用 I2C 协议,其从机地址由 ADDR 引脚 (Pin2) 控制:ADDR 接 GND 时,设备地址为 0x44。ADDR 接 VDD 时,设备地址为 0x45

官网资料下载:

软件流程

应用开发

代码说明

初始化函数

tuya_ble_sdk_demo_init

位于:tuya_ble_sdk_demo.c

在上文 应用入口 中已经提到,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;
#else
    cpu_set_gpio_wakeup (WAKEUP_MODULE_GPIO, Level_Low, 1);
    GPIO_WAKEUP_MODULE_HIGH;
#endif
    return 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();
#endif
    return 0;
}

超时处理函数

__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.");
    }
}

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);
}

Callback 函数

tuya_ble_sdk_callback

位于:tuya_ble_sdk_demo.c

Callback 函数已经在上文的 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.");   /* 打印通知信息:指示灯开始闪烁 */
}

传感驱动函数

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 */
}

功能演示

设备配网

将开发完成的程序烧录至开发板中,复位芯片,即可看到指示灯闪烁,表示设备正在等待用户绑定。

应用开发

通过智能生活 APP 添加设备,设备成功绑定后,指示灯熄灭。

应用开发

如果之前设备已被绑定,那么指示灯不会闪烁,打开 APP 后设备会自动连接。可按下图在 APP 上将设备解绑之后,即可看到指示灯闪烁的效果。

应用开发

数据更新

设备连接 App 后,可以看到温湿度数据实时更新。

应用开发

阈值报警

在 App 端设置温湿度报警阈值。

应用开发

可通过一些人为手段使环境温湿度超过设定阈值,此时 App 端即可收到设备上报的报警信息。

应用开发