基础功能

更新时间:2021-09-23 02:50:10下载pdf

蓝牙基础功能是指 MCU 对接蓝牙通用方案,完成设备智能化必须的协议功能,包括初始化流程、蓝牙绑定连接操作和数据下发上报功能等。

  • 如果设备 MCU 硬件资源有限,不使用涂鸦提供的 MCU SDK 开发,您也可以选择自行对接蓝牙通用串口协议。需要注意的是,您需要完成基础功能,设备才能正常工作。
  • 本文展示的 功能协议示例 均由模组调试助手展示。推荐您使用 涂鸦模组调试助手 协助调试,可快速了解协议实现流程。

模组初始化

模组初始化是指模组接收到 MCU 发送的配置信息完成初始化配置的过程。

功能概述

模组在与 MCU 建立通信后首先进行的便是初始化流程,该流程包括建立心跳连接、产品信息上报、模组工作方式配置、报告模组绑定连接状态和状态同步查询等协议指令。只有先完成初始化流程,模组才可以响应 MCU 进行绑定连接、数据处理及拓展功能的实现。

模组上电会不断发送心跳包,MCU 正确回复心跳响应帧后,模组将启动下图中的初始化流程。

基础功能

MCU 可以通过 0xE90xE8 其中一个命令字上报版本号,或者两个命令字都上报。区别是:

  • 0xE9 命令字为 MCU 主动上报调用。

    0xE9 命令字为 MCU 可选功能,当模组上电复位后,MCU 可通过 0xE9 命令字主动上报 MCU 版本信息。

  • 0xE8 命令字为模组下发查询 MCU 版本号,MCU 被动上报调用。

    MCU 回复 0x01 命令字后,若 MCU 此前未通过 0xE9 主动上报版本信息,模组会下发 0xE8 命令字查询 MCU 版本号,此后 MCU 通过 0xE8 命令字上报当前 MCU 版本信息。

模组与手机 App 或蓝牙网关连接成功后,会通过 0xE1 命令字自动下发一次实时时间,MCU 根据产品功能定义选择处理实时时间并上报 0xE1 命令字。

波特率自适应流程

  1. 模组读取 Flash 保存的波特率纪录数据。

  2. 模组根据能否读取到波特率数据进入对应流程:

    • 读取到波特率数据:使用该波特率配置,间隔 3 秒发送 1 次心跳包给 MCU。若 MCU 没回复,模组则重新进入扫描模式。
    • 未读取到波特率数据:模组进入扫描模式,即在 9600115200 两种波特率中切换,每 3 秒切换一次波特率发送 1 次心跳包。如果能收到指定的回复帧,则代表识别成功。随后模组将使用该配置初始化串口并将组合参数保存至 Flash。

模组最新固件中添加了波特率检测自适应的功能,可自适应判断 9600115200 波特率。因此,在初次初始化过程前,模组会经过波特率检测,收到部分乱码或初次启动时间延迟属于正常现象。

使用场景

模组重启上电或者重置后会启动初始化流程,与 MCU 进行正确的指令交互完成初始化。有下面两种情况:

  • 如果模组未进行设备蓝牙绑定,启动初始化流程后,完成产品信息及模组工作模式配置,模组报告当前工作状态后,等待您对模组进行绑定操作。

  • 如果模组已完成设备蓝牙绑定,启动初始化流程后,完成产品信息及模组工作模式配置后,模组重新与手机或网关建立蓝牙连接,并将模组的工作状态报告给 MCU,同时模组会查询设备状态。MCU 收到查询指令后应上报当前设备所有 DP 状态,用于建立连接后 App 同步设备状态。

指令列表

初始化流程涉及多个协议指令:

  • 心跳检测指令:命令字 0x00
  • 获取 MCU 信息指令:命令字 0x01
  • 请求模组工作模式指令:命令字 0x02
  • 发送模组工作状态指令:命令字 0x03
  • 状态查询指令:命令字 0x08(前置条件:模组与手机 App 或网关建立了蓝牙连接)
  • 模组查询 MCU 版本号指令:命令字 0xE8
  • MCU 主动发送当前版本号指令:命令字 0xE9
  • 模组下发实时时间指令:命令字 0xE1(前置条件:模组与手机 App 或网关建立了蓝牙连接)

心跳检测指令

指令作用:模组与 MCU 通过心跳包相互检测对方是否正常工作。

应用场景:模组上电后,以 3 秒的间隔定期发送心跳,收到 MCU 的心跳包回应后,模组认为 MCU 正常工作,随后继续下发 获取 MCU 信息命令 0x01 (见下文)。MCU 也可依据此心跳定期检测模组是否正常工作,若模组无心跳下发,则 MCU 可通过模组提供的硬件复位引脚复位模组。

  • 在低功耗模式下无心跳,正常功耗模式下以 10s 的间隔定期发送心跳。
  • Telink 模组在获得 MCU 信息(PID)后也不再发送心跳包,因此也无法检测 MCU 是否发生重启。

示例:

  • 模组发送:55 AA 00 00 00 00 FF
  • MCU回复:55 AA 00 00 00 01 00 00(MCU 上电后第一次回复)
  • MCU回复:55 AA 00 00 00 01 01 01(除第一次回复外)

指令详细介绍,请参考 蓝牙通用串口协议-心跳包

获取 MCU 信息指令

指令作用:模组向 MCU 查询获取 PID 及 MCU 固件版本号。

应用场景:收到心跳包回复后,模组会发送查询产品信息指令,MCU 收到后需回复产品信息,包含 PID、MCU 固件版本。

若产品信息回复错误,模组会一直下发查询指令,此时需要检查上报产品信息的内容和格式是否正确,模组目前不对 MCU 固件版本号做解析,MCU 固件版本号需要通过协议指令 0xE80xE9 上报。

示例:

  • 模组发送:55 AA 00 01 00 00 00

  • MCU 返回:55 AA 00 01 00 0D 6F 30 79 74 64 7A 66 64 31 2E 30 2E 30 2E

字段 说明 字节数 字符 十六进制的 ASCII 码
p 产品 PID 8 o0ytdzfd 6F 30 79 74 64 7A 66 64
v MCU 版本 5 1.0.0 31 2E 30 2E 30

指令详细介绍,请参考 蓝牙通用串口协议-获取 MCU 信息,包含传感类固件根据可选 TLD 信息配置模组固件功能。

请求模组工作模式指令

指令作用:模组发送请求模组工作模式配置指令,MCU 回复模组工作模式。

应用场景:收到产品信息后,模组会发送查询 MCU 设定模组工作模式的 0x02 命令字,MCU 收到后需回复模组工作模式,模组工作模式主要针对如何重置模组以及如何指示模组的工作状态,主要分两种情况:

  • MCU 与模组配合处理模式:

    • 重置模组:MCU 通过串口发送重置指令通知重置模组。

    • 支持模组工作状态:模组通过串口通知 MCU 当前模组的工作状态,然后 MCU 提供显示蓝牙模组当前的工作状态。

  • 模组自处理模式:目前不支持,仅支持 MCU 与模组配合处理模式

指令详细介绍,请参考 蓝牙通用串口协议-请求模组工作模式

发送模组工作状态指令

指令作用:模组主动下发当前模组状态到 MCU。

应用场景

  • 当模组收到 MCU 请求模组工作模式指令回复后,会向 MCU 下发模组工作状态
  • 当模组检测到模组状态变化时,主动下发当前模组状态到 MCU。
  • 模组重启时模组也会主动下发当前模组状态到 MCU。

工作状态说明:

State 工作状态说明 健康或共享类通用固件注意事项
0x00
0x01
  • 未绑定
  • 绑定未连接
健康或共享类产品对 绑定 这个概念进行了模糊处理,未绑定和绑定未连接在健康或共享类都认为是可配网状态
0x02 绑定已连接 绑定已连接在健康或共享类认为是已配网已连接状态

指令详细介绍,请参考 蓝牙通用串口协议-发送模组工作状态

状态查询指令

指令作用:模组发送该指令到 MCU 查询所有类型的 DP 状态,当 MCU 收到该指令时,需通过状态上报指令 0x07上报 DP 状态。

模组发送查询状态指令的时机有两个:

  • 模组绑定已连接状态下,检查到 MCU 发生重启(通过心跳回复判断),查询发送。

    Telink 通用固件不支持检测 MCU 重启。

  • 模组蓝牙离线再上线(由绑定未连接状态变化成绑定已连接状态) 的情况,查询发送。

指令详细介绍,请参考 蓝牙通用串口协议-状态查询

查询 MCU 版本号指令

指令作用:模组向 MCU 查询获取 MCU 固件版本号及 MCU 硬件版本号。

应用场景:模组在每次发送获取 MCU 信息时,同时发送查询 MCU 版本号指令,MCU 收到后需上报 MCU 当前固件版本号和当前硬件版本号。

指令详细介绍,请参考 蓝牙通用串口协议-查询 MCU 版本号

MCU 主动发送当前版本号指令

指令作用:MCU 主动发送当前 MCU 固件版本号到模组。

应用场景:为了确保模组及时获取到 MCU 固件版本号,MCU 需要在每次启动时主动发送一次当前版本号到模组,如果没有收到模组响应,需再次发送。

示例:

  • MCU发送:55 AA 00 E9 00 06 01 00 00 01 00 00 F0Soft_Ver:1.0.0、Hard_ver:1.0.0)

  • 模组回复:55 AA 00 E9 00 01 00 E9(返回值:00 ,即代表 MCU 主动上报版本号成功)

指令详细介绍,请参考 蓝牙通用串口协议-MCU主动发送当前版本号

获取实时时间指令

指令作用:MCU 选择时间类型后发送该指令向模组获取实时时间。

应用场景

  • 设备蓝牙连接后,蓝牙模组会通过该指令主动下发一次云端时间给 MCU(发送的时间类型是 0x01)。

  • MCU 主动发送该指令向模组获取当前时间。

时间类型说明:

Time_Type 说明
0x00 格式 0:获取 7 字节时间时间类型,和 2 字节时区信息
计年方式:2018+year
0x01 格式 1:获取 13 字节毫秒(ms)级 Unix 时间,和 2 字节时区信息
0x02 格式 2:获取 7 字节时间时间类型,和 2 字节时区信息
计年方式:2000+year

指令详细介绍,请参考 蓝牙通用串口协议-获取实时时间

功能协议示例

  • 心跳检测

    基础功能

  • 获取 MCU 信息

    基础功能

  • 请求模组工作模式

    基础功能

  • 发送模组工作状态

    基础功能

  • 状态查询

    基础功能

  • 查询 MCU 版本号

    基础功能

  • MCU 主动发送当前版本号

    基础功能

  • 获取实时时间

    基础功能

MCU SDK示例

MCU SDK入口函数说明

MCU SDK 入口函数 bt_uart_service() 定义在 SDK 的 mcu_api.c 文件中,作用是接收蓝牙模组下发的串口数据,并对蓝牙模组下发的串口数据进行处理。

应用步骤

  1. bt_uart_service() 函数判断 MCU 接收的模组下发串口数据格式是否正确,并检验串口数据校验和。

  2. 调用 data_handle() 函数,对蓝牙模组下发的串口数据进行相应的处理。

    MCU 应用程序需要在 main 函数的循环体中调用 bt_uart_service() 函数,调用该函数不要加任何条件判断。

    /*****************************************************************************
    Function name: bt_uart_service
    Function description: bt serial port processing service
    Input parameters: none
    Return parameter: none
    Instructions: call this function in the while loop of the MCU main function
    *************************************************************************
    void bt_uart_service(void)
    {
    static unsigned short rx_in = 0;
    unsigned short offset = 0;
    unsigned short rx_value_len = 0;             //Data frame length
    
    if((rx_in < sizeof(bt_uart_rx_buf)) && get_queue_total_data() > 0)
    {
    	if(mcu_common_uart_data_unpack(Queue_Read_Byte()))
    	{
    		if((bt_uart_rx_buf[0]==0x55)&&(bt_uart_rx_buf[1]==0xAA)&&(bt_uart_rx_buf[2]==0x00))
    		{
    			data_handle(0);
    			rx_value_len = bt_uart_rx_buf[LENGTH_HIGH] * 0x100 + bt_uart_rx_buf[LENGTH_LOW] + PROTOCOL_HEAD;
    		}
    		else if((bt_uart_rx_buf[0]==0x66)&&(bt_uart_rx_buf[1]==0xAA)&&(bt_uart_rx_buf[2]==0x00))
    		{
    			rx_value_len = bt_uart_rx_buf[LENGTH_HIGH] * 0x100 + bt_uart_rx_buf[LENGTH_LOW];
    			factory_test_handler(bt_uart_rx_buf[FRAME_TYPE],&bt_uart_rx_buf[DATA_START],rx_value_len);
    		}
    		my_memset(bt_uart_rx_buf_temp,0,3);
    		my_memset(bt_uart_rx_buf,0,sizeof(bt_uart_rx_buf));
    		UART_RX_Count = 0;
    		current_uart_rev_state_type = MCU_UART_REV_STATE_FOUND_NULL;
    		uart_data_len = 0;
    	}
    }
    
    }
    	}
    

心跳包检测函数说明

心跳包检测函数 heat_beat_check() 定义在 SDK 的 system.c 文件中,作用是回复模组发送的心跳包。

应用步骤

  1. 串口数据处理函数 data_handle() 接收到心跳指令。

  2. MCU SDK 调用 heat_beat_check() 回复。第一次回复的心跳包为 0x00,第二次及以后回复的心跳包为 0x01

  3. MCU SDK 函数已实现该功能。

    /*****************************************************************************
    Function name: data_handle
    Function description: data frame processing
    Input parameter:
    	Offset: Data start bit
    Return parameter: none
    *****************************************************************************/
    void data_handle(unsigned short offset)
    {
    	......
    	case HEAT_BEAT_CMD:                                     //心跳检测指令
    			heat_beat_check();                              //心跳回复函数
    		break;
    		......
    }
    
    /*****************************************************************************
    Function name: heat_beat_check
    Function description: Heartbeat packet detection
    Input parameters: none
    Return parameter: none
    *****************************************************************************/
    static void heat_beat_check(void)
    {
    unsigned char length = 0;
    static unsigned char mcu_reset_state = FALSE;
    
    if(FALSE == mcu_reset_state)
    {
    	length = set_bt_uart_byte(length,FALSE);
    	mcu_reset_state = TRUE;
    }
    else
    {
    	length = set_bt_uart_byte(length,TRUE);
    }
    
    bt_uart_write_frame(HEAT_BEAT_CMD, length);
    }
    

    data_handle() 为串口数据处理函数,函数定义在 system.c 文件中,作用是接收模组发送的协议指令数据,并做处理,bt_uart_write_frame() 为串口发送函数,函数定义在 system.c 文件中,作用是往模组串口发送指令数据。

MCU 信息回复函数说明

MCU 信息回复函数 product_info_update() 定义在 SDK 的 system.c 文件中,作用是 MCU 返回产品信息。

应用步骤

  1. 串口数据处理函数 data_handle() 接收到查询产品信息指令。

  2. 将 PID 和 MCU 固件版本号写入宏定义。

  3. 调用 product_info_update() 上报产品信息。

  4. MCU SDK 函数已实现该功能。

    PID、MCU 版本号信息是通过打开或修改宏定义确认。

    #define PRODUCT_KEY "o0ytdzfd"    // IoT 平台创建产品后生成的产品唯一标识
    #define MCU_VER "1.0.0"
    
    void data_handle(unsigned short offset)
    {
    	......
    	case PRODUCT_INFO_CMD:                                  //查询产品信息指令
    			product_info_update();
    		break;
    		......
    }
    
    /*****************************************************************************
    Function name: product_info_update
    Function description: upload product information
    Input parameters: none
    Return parameter: none
    *****************************************************************************/
    static void product_info_update(void)
    {
    unsigned char length = 0;
    
    length = set_bt_uart_buffer(length,(unsigned char *)PRODUCT_KEY,my_strlen((unsigned char *)PRODUCT_KEY));
    length = set_bt_uart_buffer(length,(unsigned char *)MCU_VER,my_strlen((unsigned char *)MCU_VER));
    
    bt_uart_write_frame(PRODUCT_INFO_CMD, length);
    }
    

配置模组工作模式函数说明

配置模组工作模式函数 get_mcu_bt_mode(void) 定义在 SDK 的 system.c 文件中,作用是发送模组工作模式配置到模组。

应用步骤

  1. 串口数据处理函数 data_handle() 接收到查询模组工作模式指令。

  2. 调用 get_mcu_bt_mode() 回复上报。

  3. MCU SDK 函数已实现该功能。

    通过屏蔽或打开 BT_CONTROL_SELF_MODE 的宏定义,可以配置模组工作方式为配合处理模式或模组自处理模式,如果是自处理模式,还需要配置重置按键和状态指示 I/O 口,目前只支持模组与 MCU 配合处理。

    //#define         BT_CONTROL_SELF_MODE                       / / bt self-processing button and LED indicator; if it is MCU external button / LED indicator, please turn off the macro
    #ifdef          BT_CONTROL_SELF_MODE                      // Module self-processing
    #define     BT_STATE_KEY            14                    //Bt module status indication button, please set according to the actual GPIO pin
    #define     BT_RESERT_KEY           0                     //Bt module reset button, please set according to the actual GPIO pin
    #endif
    
    void data_handle(unsigned short offset)
    {
    	......
    	case WORK_MODE_CMD:                  //Query module working mode set by MCU
    	get_mcu_bt_mode();
    	break;
    	......
    }
    
    /*****************************************************************************
    Function name: get_mcu_bt_mode
    Function description: query the working mode of MCU and bt
    Input parameters: none
    Return parameter: none
    *****************************************************************************/
    static void get_mcu_bt_mode(void)
    {
    unsigned char length = 0;
    
    #ifdef BT_CONTROL_SELF_MODE                                   //Module self-processing
    length = set_bt_uart_byte(length, BT_STATE_KEY);
    length = set_bt_uart_byte(length, BT_RESERT_KEY);
    #else
    //No need to process data
    #endif
    bt_uart_write_frame(WORK_MODE_CMD, length);
    }
    

发送模组工作状态指令处理函数说明

MCU 接收到发送模组工作状态指令后的 SDK 处理在函数 data_handle() 中。

应用步骤

  1. 模组工作模式选择 MCU 与模组配合处理时,串口数据处理函数 data_handle() 接收模组下发的网络状态赋值给变量 bt_work_state

  2. 若当前模组状态是未绑定 0x00 或已绑定未连接 0x01,MCU 调用 mcu_ota_init_disconnect() 配置 MCU OTA 初始化。

  3. MCU SDK 函数已实现该功能。

    void data_handle(unsigned short offset)
    {
    	......
    	#ifndef BT_CONTROL_SELF_MODE
    	case BT_STATE_CMD:                                  //bt work state
    	bt_work_state = bt_uart_rx_buf[offset + DATA_START];
    	if(bt_work_state==0x01||bt_work_state==0x00)
    	{
    		mcu_ota_init_disconnect();
    	}
    	bt_uart_write_frame(BT_STATE_CMD,0);
    	break;
    	......
    }
    

上报所有 DP 状态函数说明

上报所有 DP 状态函数 all_data_update(void) 定义在 protocol.c 文件中,作用是上报所有 DP 状态。

应用步骤

  1. 在串口数据处理函数 data_handle() 中,收到模组发来的设备状态查询。

  2. MCU 需要调用 all_data_update() 函数上报所有 DP 状态,用于面板同步设备状态。

    all_data_update() 内调用的分别是不同数据类型的 DP 上报函数,如 mcu_dp_bool_update() 函数就是上报 bool 型的数据,形参是上报的 dpid 和 DP 数据。以此类推,mcu_dp_value_update() 函数是数值型 DP 上报函数,mcu_dp_enum_update() 是枚举型 DP 上报函数。

    该部分的函数功能需要 MCU 应用程序自行补充该函数功能。

    void data_handle(unsigned short offset)
    {
    	......
    	case STATE_QUERY_CMD:                                 //Status query
    			all_data_update();
    		break;
    		......
    }
    
    /*****************************************************************************
    Function name: all_data_update
    Function description: upload all dp information of the system to achieve data synchronization between App and muc
    Input parameters: none
    Return parameter: none
    Instructions for use: this function needs to be called internally in SDK
    		MCU must implement the data reporting function within this function, including reporting only and downloadable hairstyle data.
    *****************************************************************************/
    void all_data_update(void)
    {
    #error "Please process all DP data here and delete the row when the processing is complete"
    // This code is automatically generated for the platform. Please modify each function according to the actual data
    	mcu_dp_value_update(DPID_TARGET_WATER,当前目标水量); //VALUE型数据上报;
    	mcu_dp_value_update(DPID_WATER_TEMP,当前水杯温度); //VALUE型数据上报;
    	mcu_dp_value_update(DPID_WATER_INTAKE,当前当前饮水量); //VALUE型数据上报;
    	mcu_dp_value_update(DPID_WATER_TIMES,当前饮水次数); //VALUE型数据上报;
    	mcu_dp_value_update(DPID_BATTERY_PERCENTAGE,当前电池电量); //VALUE型数据上报;
    	mcu_dp_value_update(DPID_WATER_REMIND,当前喝水提醒); //VALUE型数据上报;
    	mcu_dp_fault_update(DPID_FAULT,当前故障告警); //故障型数据上报;
    }
    

MCU 主动发送当前版本号函数说明

MCU 主动发送当前版本号函数 bt_send_mcu_ver(void) 定义在 SDK 的 system.c 文件中,作用是 MCU 上电后主动调用发送当前 MCU 固件版本号到模组。

应用步骤

  1. 打开 TUYA_BCI_UART_COMMON_MCU_SEND_VERSION 宏定义,并将 MCU 的软件和硬件版本号写入宏定义,启用 MCU 主动发送当前版本号指令 0xE9 功能。

  2. MCU 上电后,自行调用该函数发送当前 MCU 固件版本号到模组。

    模组接收到后在 data_handle() 函数中处理,目前 MCU SDK 中没有补充该部分,需要MCU 应用程序自行补上。

    #define MCU_APP_VER_NUM      0x010000					//User's software version, used for MCU firmware upgrade, MCU upgrade version needs to be modified eg.1.0.0
    #define MCU_HARD_VER_NUM     0x010000					//The hardware version of the user is currently of no practical use
    
    #ifdef TUYA_BCI_UART_COMMON_MCU_SEND_VERSION
    /*****************************************************************************
    Function name: bt_send_mcu_ver
    Function description: send the MCU version number to the module actively, mainly in order that the module can obtain the MCU version information in a more timely manner.
    
    Return parameter: none
    Instructions: MCU can be called once after initialization of serial port.
    *****************************************************************************/
    void bt_send_mcu_ver(void)
    {
    	unsigned short length = 0;
    	length = set_bt_uart_buffer(length,(unsigned char *)MCU_APP_VER_NUM,3);
    	length = set_bt_uart_buffer(length,(unsigned char *)MCU_HARD_VER_NUM,3);
    	bt_uart_write_frame(TUYA_BCI_UART_COMMON_MCU_SEND_VERSION,length);
    }
    #endif
    

获取实时时间函数说明

获取实时时间函数 bt_send_time_sync_req() 定义在 protocol.c 文件中,作用是 MCU 发送该指令到模组获取实时时间。

应用步骤

  1. MCU 自行调用 bt_send_time_sync_req() 向模组获取实时时间。

  2. 在串口数据处理函数 data_handle() 中,收到模组发来数据并做判断,若数据正确则进行接收。

  3. 实时时间处理函数 bt_time_sync_result() 收到模组发来的时间数据后,可自行决定是否需对时间数据进行处理。

    该函数代码逻辑需要 MCU 应用程序自行完成。

    /*****************************************************************************
    Function name: bt_send_time_sync_req
    Function description: Send time synchronization request to module
    Input parameter: sync_time_type
    0x00- Gets 7 bytes of time time type +2
    Byte time zone information
    0x01- Gets 13 bytes of MS level Unix time
    + 2-byte time zone information
    0x02-get 7 byte time type + 2 Byte time zone information
    Return parameter: none
    *****************************************************************************/
    void bt_send_time_sync_req(unsigned char sync_time_type)
    {
    	unsigned short length = 0;
    	length = set_bt_uart_byte(length,sync_time_type);
    	bt_uart_write_frame(TUYA_BCI_UART_COMMON_SEND_TIME_SYNC_TYPE,length);
    }
    
    void data_handle(unsigned short offset)
    {
    	......
    		case TUYA_BCI_UART_COMMON_SEND_TIME_SYNC_TYPE:
    		ret = bt_uart_rx_buf[offset + DATA_START];
    		if(ret==0)//Get time succeeded
    		{
    			if(bt_uart_rx_buf[offset + DATA_START+1]==0x00)//Time format 0 :Get 7 bytes of time and time type + 2 bytes of time zone information
    			{
    				bt_time.nYear = bt_uart_rx_buf[offset + DATA_START+2] + 2018;
    				bt_time.nMonth = bt_uart_rx_buf[offset + DATA_START+3];
    				bt_time.nDay = bt_uart_rx_buf[offset + DATA_START+4];
    				bt_time.nHour = bt_uart_rx_buf[offset + DATA_START+5];
    				bt_time.nMin = bt_uart_rx_buf[offset + DATA_START+6];
    				bt_time.nSec = bt_uart_rx_buf[offset + DATA_START+7];
    				bt_time.DayIndex = bt_uart_rx_buf[offset + DATA_START+8];
    				time_zone_100 = ((unsigned short)bt_uart_rx_buf[offset + DATA_START+9]<<8)+bt_uart_rx_buf[offset + DATA_START+10];
    			}
    			else if(bt_uart_rx_buf[offset + DATA_START+1]==0x01)//Time format 1: Get 13 bytes of ms-level unix time + 2 bytes of time zone information
    			{
    				my_memcpy(current_timems_string,&bt_uart_rx_buf[offset + DATA_START+2],13);
    				time_stamp_ms = my_atoll(current_timems_string);
    				time_zone_100 = ((unsigned short)bt_uart_rx_buf[offset + DATA_START+15]<8)+bt_uart_rx_buf[offset + DATA_START+16];
    			}
    			else if(bt_uart_rx_buf[offset + DATA_START+1]==0x02)//Time format 2: Get 7 bytes of time and time type + 2 bytes of time zone information
    			{
    				bt_time.nYear = bt_uart_rx_buf[offset + DATA_START+2] + 2000;
    				bt_time.nMonth = bt_uart_rx_buf[offset + DATA_START+3];
    				bt_time.nDay = bt_uart_rx_buf[offset + DATA_START+4];
    				bt_time.nHour = bt_uart_rx_buf[offset + DATA_START+5];
    				bt_time.nMin = bt_uart_rx_buf[offset + DATA_START+6];
    				bt_time.nSec = bt_uart_rx_buf[offset + DATA_START+7];
    				bt_time.DayIndex = bt_uart_rx_buf[offset + DATA_START+8];
    				time_zone_100 = ((unsigned short)bt_uart_rx_buf[offset + DATA_START+9]<<8)+bt_uart_rx_buf[offset + DATA_START+10];
    			}
    			bt_time_sync_result(0,bt_uart_rx_buf[offset + DATA_START+1],bt_time,time_zone_100,time_stamp_ms);
    		}
    		else//Failed to get time
    		{
    			bt_time_sync_result(1,bt_uart_rx_buf[offset + DATA_START+1],bt_time,time_zone_100,time_stamp_ms);
    		}
    		break;
    		......
    }
    
    /*****************************************************************************
    Function name: bt_time_sync_result
    Function description: send the result of time synchronization to the module
    Input parameters: result synchronization result 0 successful, other failed
    		sync_time_type :time format
    		Bt_time: Custom time (valid if time format 0 or 1)
    		Time_zone_100: time zone
    		Time_stamp_ms: timestamp (valid if it is in time format 1)
    Return parameter: none
    Instructions: MCU needs to improve the function on its own.
    *****************************************************************************/
    void bt_time_sync_result(unsigned char result,unsigned char sync_time_type,bt_time_struct_data_t bt_time,unsigned short time_zone_100,long long time_stamp_ms)
    {
    	#error "Please improve the function by yourself and delete the line after completion"
    	if(result == 0x00)
    	{
    		// synchronization time is successful
    		if(sync_time_type==0x00||sync_time_type==0x02)
    		{
    			// populate the data of custom time format in bt_time into the MCU clock system
    			//time_zone_100
    		}
    		else if(sync_time_type==0x01)
    		{
    			// populate the timestamp in time_stamp_ms into the MCU clock system
    			//time_zone_100
    		}
    	}
    	else
    	{
    		// synchronization time failed
    	}
    }
    

设备配网

配网功能是指通过 MCU 发送重置配网指令使模组进入配网状态,并使用客户端 App(如涂鸦智能)或者蓝牙网关与设备交互完成配网操作连接到 涂鸦 IoT 平台

功能概述

本文介绍如何让模组进入配网状态。使用涂鸦智能 App 配网操作参考 App 使用指南

蓝牙配网原理:模组广播产品信息,客户端 App 或蓝牙网关收到后与设备去建立蓝牙连接,进行设备信息同步,如果信息同步一致,则完成配对。蓝牙配网的特点是发现快,可靠性强,配网操作简单,蓝牙模组只要进入配网状态,会一直发送蓝牙广播,等待客户App进行配网或连接。

使用场景

当需要对设备进行配网操作,必须先让模组进入未绑定状态,才可以使用 App 进行配网绑定。

模组工作状态介绍详见 蓝牙单点串口协议

常见的使用场景

MCU 发送重置指令给到蓝牙模组,模组进入配网状态。一般的方式是在设备上做配网按键,需要配网时操作配网按键触发 MCU 发送重置指令给蓝牙模组,使模组进入配网状态。

如在涂鸦 IoT 平台修改过产品的 DP 功能或其他功能,需要将设备移除重新绑定连接才可生效。

若在设备完成绑定状态下使用本地重置,重置成功时 App 的设备图标不会被删除,只会显示蓝牙断开,该现象是因为蓝牙设备并不是直连涂鸦 IoT 平台,本地重置无法告知 App,所以本地重置后 App 仍显示设备,此时模组重置并重新绑定,原来的设备图标就会删除。

指令列表

重置模组操作是指 MCU 给模组发送重置指令让设备进入配网状态,重置指令有如下:

  • 重置模组指令:命令字 0x04
  • 新重置模组指令:命令字 0x05
  • 模组解绑指令:命令字 0x09

以上指令都具有重置模组的作用,但各有差异,可根据应用场景使用一个或多个指令。

重置蓝牙指令

指令作用:断开模组蓝牙连接,解除蓝牙绑定关系,清除模组离线缓存信息,清除虚拟 ID 重启。清除虚拟 ID 后,您的数据也会被清除掉。

应用场景:MCU 给模组发送 0x04 命令字,模组进入未绑定状态,可进行配网绑定。

由于 Telink 平台存在许多用户使用该接口作为解绑接口来使用,为了不影响这部分场景的功能,涂鸦针对 Telink 平台的重置不会清除虚拟 ID。Telink 平台模组重置并清除缓存信息可参考命令 0x05

指令详细介绍,请参考 蓝牙通用串口协议- 重置模组

新重置蓝牙

指令作用:断开模组蓝牙连接,解除蓝牙绑定关系,清除模组离线缓存信息,清除虚拟 ID 重启。清除虚拟 ID 后,您的数据也会被清除掉。该指令的作用与 0x04 命令字完全一致,主要差异是解决 Telink 平台不清除虚拟 ID 的问题和平台前后版本的兼容性问题。

应用场景:MCU 给模组发送 0x05 命令字,模组进入未绑定状态,可进行配网绑定。

目前支持该协议指令的固件仅有 Telink 825x 通用固件 v7.22+ 版本。

指令详细介绍,请参考 蓝牙通用串口协议- 新重置模组

模组解绑

指令作用:仅解除和手机或蓝牙网关的绑定关系,并且断开蓝牙连接,不会清除数据和虚拟 ID。若手机或蓝牙网关与模组重新建立连接,之前设置的用户数据仍保留。

应用场景:MCU 给模组发送 0x09 命令字,模组进入未绑定状态,可进行配网绑定。

健康或共享类通用固件不支持该指令。

指令详细介绍,请参考 蓝牙通用串口协议- 模组解绑

功能协议示例

  • 重置蓝牙

    基础功能

  • 新重置蓝牙

    基础功能

  • 模组解绑

    基础功能

MCU SDK示例

重置模组函数说明

重置模组函数 mcu_reset_bt() 定义在 mcu_api.c 文件中,作用是发送重置配网指令到模组。

应用步骤

在串口数据处理函数 data_handle() 中,若接收到模组回复重置指令将重置标志 reset_bt_flag 置有效,通过 mcu_get_reset_bt_flag() 函数返回 reset_bt_flag,判断重置模组是否成功。

MCU 主动调用该函数,自定义在何时启动配网。

/*****************************************************************************
Function name: mcu_reset_bt
Function description: MCU actively resets bt working mode
Input parameters: none
Return parameter: none
Instructions for use: 1:MCU is called actively, and the mcu_get_reset_bt_flag () function is used to obtain whether the reset bt is successful or not.
		   2: if it is in module self-processing mode, MCU does not need to call this function
*****************************************************************************/
void mcu_reset_bt(void)
{
  reset_bt_flag = RESET_BT_ERROR;

  bt_uart_write_frame(BT_RESET_CMD, 0);
}
void data_handle(unsigned short offset)
{
	......
    case BT_RESET_CMD:                         //Reset BT (BT returns success)
    reset_bt_flag = RESET_BT_SUCCESS;
    	break;
        ......
}
/*****************************************************************************
Function name: mcu_get_reset_bt_flag
Function description: MCU acquires reset bt success flag
Input parameters: none
Return parameter: RESET_BT_ERROR:failed/RESET_BT_SUCCESS:success
Instructions for use: 1:MCU call this function after calling mcu_reset_bt () actively to get the reset state
		   2: if it is in module self-processing mode, MCU does not need to call this function
*****************************************************************************/
unsigned char mcu_get_reset_bt_flag(void)
{
  return reset_bt_flag;
}

模组解绑函数说明

模组解绑函数 bt_unbound_req() 定义在 protocol.c 文件中,作用是发送模组解绑指令到模组。

使用场景

模组解绑函数主要用在模组连接后,MCU 需要解绑 App 或蓝牙网关时主动调用模组解绑功能。MCU 硬件设计可添加按键解绑功能,此后在 MCU 应用程序中检测该按键是否按下,检测到按键按下后调用 bt_unbound_req() 解绑函数,蓝牙模组收到解绑命令后将处于未绑定状态。

应用步骤

  1. 打开 TUYA_BCI_UART_COMMON_UNBOUND_REQ 宏定义,启用模组解绑功能。

  2. MCU 上电后,自行调用该函数发送解绑指令 0x09 到模组。

  3. 模组接收到后,在 data_handle() 函数中处理。目前 MCU SDK 中没有补充该部分,需要 MCU 应用程序自行补上。

    MCU 应用程序需要自行调用该函数实现解绑功能。

    #ifdef TUYA_BCI_UART_COMMON_UNBOUND_REQ
    /*****************************************************************************
    Function name: bt_unbound_req
    Function description: Send the unbind request to the module, and the module will unbind the Bluetooth connection after receiving the instruction
    Input parameters: None
    Return parameter: none
    Instructions: The MCU calls for active untying
    *****************************************************************************/
    void bt_unbound_req(void)
    {
    	bt_uart_write_frame(TUYA_BCI_UART_COMMON_UNBOUND_REQ,0);
    }
    #endif
    

数据传输

功能概述

实现设备远程控制最直接相关的功能就是下发上报,模组下发控制指令,设备执行并上报自身状态。下面介绍了协议中与数据传输相关的指令。

在涂鸦 IoT 平台修改 DP 后,需要先将设备移除后,重新连接绑定才能生效,否则上报调试过程可能报错。

上报原则

  • 除回复 0x08 查询状态指令需要上报所有 DP 状态外,其他时刻建议只有 DP 状态变化时才上报。

  • 不建议高频上报,会对模组及云端产生未知影响,可能造成设备离线或者数据丢失等问题,建议上报频率低于每秒/帧。

使用场景

智能化设备的远程控制链路分为下发和上报,下面分别介绍:

  • 下发链路:来自面板的控制指令通过 App 发送到模组,模组通过串口协议指令发送给 MCU。

  • 上报链路:MCU 通过串口协议指令把设备状态发送给模组,模组上报 App,App 再给到面板显示当前状态,并同步发送到涂鸦 IoT 平台。

    基础功能

上面介绍的数据传输链路是手机与设备通过蓝牙连接通信,模组完成配网绑定操作才能进行该操作。除了手机与设备直接蓝牙通信外,设备与手机 App 之间还可以通过蓝牙网关实现远距离通信:

  • 蓝牙网关通信说明:蓝牙设备在蓝牙网关下完成配网绑定,加入网关后,即使手机与蓝牙设备不处于蓝牙连接状态,手机的控制指令可以通过涂鸦 IoT 平台发送到蓝牙网关,再由网关转发给设备,实现设备控制。同理设备上报可由蓝牙网关转发到涂鸦 IoT 平台,手机 App 再从涂鸦 IoT 平台拉取当前设备状态给到面板做显示。
  • 蓝牙连接通信说明:在设备完成配网绑定后,手机与设备处于蓝牙连接范围,此时 App 可以与设备通过蓝牙完成数据传输。

指令列表

MCU 与模组之间的数据传输是通过下发和上报指令完成的,相关指令如下:

  • 命令下发指令:命令字 0x06
  • 状态上报指令:命令字 0x07
  • 记录型数据上报指令:命令字 0xE0

命令下发指令

设备与手机 App 处于绑定连接的情况下,App 可以实现对设备的远程控制指令下发,模组把来自 App 的控制指令,用串口协议数据格式发送给 MCU,MCU 收到控制指令后需要执行下列操作:

  1. 调用 0x07 指令回复下发指令,如设置喝水提醒时间为 60 min:

    • 模组发送:55 AA 00 06 00 08 06 02 00 04 00 00 00 3C 55

    • MCU 回复:55 AA 00 07 00 08 06 02 00 04 00 00 00 3C 56

    • 模组回复:55 AA 00 07 00 01 00 07

      MCU 需回复相同的DP控制数据。

  2. MCU 执行控制指令内容,如收到蓝牙水杯喝水提醒时间设置指令,MCU 应调整提醒时间。

指令详细介绍,请参考 蓝牙通用串口协议-命令下发

异步上报指令

应用场景

  • 回复 0x08 查询指令,需要使用 0x07 上报指令上报设备所有 DP 状态。
  • 回复 0x06 下发指令,应用介绍见下发指令 0x06 介绍。
  • 设备状态变化时,需主动调用 0x07 上报变化后的 DP 状态。

注意事项

  • 数据长度说明:指 DP 数据的长度,不是指整帧数据长度。数据长度根据 DP 数据类型和 DP 数量确定,数据内容不满数据长度的,前边补 0 即可。数据类型介绍见串口协议-DP格式说明

  • Bitmap 型数据:支持多故障同时上报。每一个 bit 位可代表一个警告,置 1 表示发生故障,置 0 表示无故障。可以为 1、2 或 4 字节,大于1 字节时为大端模式。例如 MCU 发送:55 aa 03 07 00 06 0d 05 00 02 00 09 2C(同时上报故障 bit0 和故障 bit3 )。如果需要配置告警推送及故障提示文案,请参考 推送设备消息

  • String 型数据:字符串的含义与显示需与面板配套,自定义的可与面板沟通。字符串信息需要转换成十六进制的 ASCII 码上报。例如 MCU 发送:55 AA 03 07 00 08 6E 03 00 04 74 65 73 74 46(上报数据 74 65 73 74 对应字符 test

  • 组合上报:组合上报是指多个 DP 数据格式组合上报,减少数据帧交互,例如当前温度,模式放在同一帧上报,MCU 发送:55 aa 03 07 00 00 03 02 00 04 00 00 00 19 04 04 00 01 00 {校验和}(当前温度 25 度,模式选择为 smart)

指令详细介绍,请参考 蓝牙通用串口协议-状态上报

记录型数据上报指令

指令作用:主要用于重要数据的上报,如果上报时模组处于未连接状态,模组会将数据存储在模组 flash 中,待模组上线后再上报至 App,如果模组处于连接状态,会在最后一条记录型数据上报成功(连续上报的最后一条)后,释放该记录型缓存数据,将该记录型数据保存至 App。

应用场景:对于重要数据的上报,尤其是记录型数据,建议使用该指令上报,无论蓝牙设备是连接还是未连接状态。

模组存储DP数据与芯片平台有关,具体关系如下:

模组芯片平台 DP 数据
nrf52832/BK3431q 最多可存储 80 条,每条数据最长 200 字节
bk3432 最多存储 32 条,每条数据最长 32 字节

时钟精度说明:蓝牙模组内部时钟精度有限,24 小时误差小于 1 分钟,但每次重连会重新校准时钟。如果您对精度要求高,建议使用 MCU 自带时间上报。

指令详细介绍,请参考 蓝牙通用串口协议-记录型数据上报

功能协议示例

  • 下发功能:红色框标出的控制指令下发,下发后需对应上报回复。

    基础功能

  • 上报功能:上下图中绿色框标出的为上报指令,分别有回复下发指令上报、DP 状态变化主动上报和回复 08 查询上报。

    基础功能

    基础功能

MCU SDK示例

命令下发处理函数

命令下发处理函数 data_point_handle() 定义在 system.c 文件中,作用是处理模组下发的 DP 控制指令。

应用步骤

  1. 在串口接收数据处理函数 data_handle() 中,收到模组发来的 DP 控制指令。

  2. 调用 data_point_handle() 函数接收下发帧的数据单元,检测数据格式是否正确。

  3. data_point_handle() 函数调用 DP 下发处理函数 dp_download_handle() 进行处理,dp_download_handle() 会针对 DP ID 调用相应的处理函数执行控制指令,并把变化后的状态上报。

    MCU 应用程序需自行实现该功能。

    void data_handle(unsigned short offset)
    {
    	......
    	case DATA_QUERT_CMD:                                  //dp data handled
    	total_len = bt_uart_rx_buf[offset + LENGTH_HIGH] * 0x100;
    	total_len += bt_uart_rx_buf[offset + LENGTH_LOW];
    
    	for(i = 0;i < total_len;)
    	{
    	dp_len = bt_uart_rx_buf[offset + DATA_START + i + 2] * 0x100;
    	dp_len += bt_uart_rx_buf[offset + DATA_START + i + 3];
    	//
    	ret = data_point_handle((unsigned char *)bt_uart_rx_buf + offset + DATA_START + i);
    	if(SUCCESS == ret)
    	{
    		//Success tips
    	}
    	else
    	{
    		//Error message
    	}
    	i += (dp_len + 4);
    	}
    	break;
    	......
    }
    
    /*****************************************************************************
    Function name: data_point_handle
    Function description: send data processing
    Input parameter:
    	value: the pointer of the data source issued
    Return parameter:
    	ret: return data processing result
    *****************************************************************************/
    static unsigned char data_point_handle(const unsigned char value[])
    {
    unsigned char dp_id,index;
    unsigned char dp_type;
    unsigned char ret;
    unsigned short dp_len;
    
    dp_id = value[0];
    dp_type = value[1];
    dp_len = value[2] * 0x100;
    dp_len += value[3];
    index = get_dowmload_dpid_index(dp_id);
    if(dp_type != download_cmd[index].dp_type)
    {
    	//Error message
    	return FALSE;
    }
    else
    {
    	ret = dp_download_handle(dp_id,value + 4,dp_len);
    }
    
    return ret;
    }
    

状态上报函数说明

异步上报函数根据 DP 数据类型分为:

  • mcu_dp_raw_update()
  • mcu_dp_bool_update()
  • mcu_dp_value_update()
  • mcu_dp_string_update()
  • mcu_dp_enum_update()
  • mcu_dp_fault_update()

这些函数定义在 mcu_api.c 文件中,作用是上报 DP 状态。如 DP1 数据类型是 bool 型,上报调用 mcu_dp_bool_update()

MCU 应用程序需自行实现该功能。

/*****************************************************************************
Function name: mcu_dp_bool_update
Function description: Bool dp data upload
Input parameter: dpid:id number
           value:
Return parameter: none
*****************************************************************************/
unsigned char mcu_dp_bool_update(unsigned char dpid,unsigned char value)
{
  unsigned short length = 0;

  if(stop_update_flag == ENABLE)
    return SUCCESS;
  length = set_bt_uart_byte(length,dpid);
  length = set_bt_uart_byte(length,DP_TYPE_BOOL);
  //
  length = set_bt_uart_byte(length,0);
  length = set_bt_uart_byte(length,1);
  //
  if(value == FALSE)
  {
    length = set_bt_uart_byte(length,FALSE);
  }
  else
  {
    length = set_bt_uart_byte(length,1);
  }
  bt_uart_write_frame(STATE_UPLOAD_CMD,length);
  return SUCCESS;
}

记录型数据上报函数说明

记录型数据上报函数 bt_send_recordable_dp_data() 定义在 protocol.c 文件中,作用是完成记录型数据上报。

应用步骤

  1. 打开 TUYA_BCI_UART_COMMON_SEND_STORAGE_TYPE 宏定义,启动上报记录数据功能。

  2. MCU自行调用 bt_send_recordable_dp_data() 函数上报记录型数据,该函数功能代码需您自行完成。

  3. 在串口接收数据处理函数 data_handle() 中,收到模组上报记录型数据应答,并调用上报记录数据结果处理函数 bt_send_recordable_dp_data_result 做下一步判断处理,该函数需要您自行完成。

    MCU 应用程序需自行实现该功能。

    /*****************************************************************************
    Function name: bt_send_recordable_DP_data
    Function description: report the recorded data
    Input parameters: Type-1: Bluetooth module built-in time report -2: original data only report, no time -3: MCU built-in time report
    		Dpid: former datapoint serial number
    		Dptype: Corresponds to a datapoint specific data type on the open platform
    		value:
    		len:
    Return parameter: none
    Instructions: the MCU needs to improve the function itself
    	It is recommended to use the cache queue. All data to be sent to the module should be put into the MCU cache queue, and the next data should be reported after one has been reported successfully. The recorded data should ensure that each data has been reported successfully
    *****************************************************************************/
    void bt_send_recordable_dp_data(unsigned char snedType,unsigned char dpid,unsigned char dpType, unsigned char value[],unsigned short len)
    {
    	#error "Please improve the function by yourself and delete the line after completion"
    	if(snedType==0x01)//Format 1, Bluetooth module self-report time
    	{
    
    	}
    	else if(snedType==0x02)//Format 2, report only the original data, no time (Note: Telink docking platform does not support this format)
    	{
    
    	}
    	else if(snedType==0x03)//Format 3, MCU own time report
    	{
    
    	}
    }
    
    void data_handle(unsigned short offset)
    {
    	......
    	#ifdef TUYA_BCI_UART_COMMON_SEND_STORAGE_TYPE
    	case TUYA_BCI_UART_COMMON_SEND_STORAGE_TYPE:
    		bt_send_recordable_dp_data_result(bt_uart_rx_buf[offset + DATA_START]);
    		break;
    	#endif
    	......
    }
    
    /*****************************************************************************
    Function name: bt_send_recordable_dp_data_result
    Function description: report the recorded data
    Input parameter :result: 0 storage success, 1 storage failure
    Return parameter: none
    Instructions: the MCU needs to improve the function itself
    *****************************************************************************/
    void bt_send_recordable_dp_data_result(unsigned char result)
    {
    	#error "Please improve the function by yourself and delete the line after completion"
    }
    

下一步

实现了蓝牙设备的 基础功能 后,您可以继续实现 拓展功能