简体中文
简体中文
English
联系我们
注册
登录
layout空间导航

MCU SDK 移植(HomeKit)

更新时间:2022-02-16 03:30:31下载pdf

HomeKit MCU SDK 是根据涂鸦 IoT 平台定义的产品功能自动生成的 MCU 代码,能够协助您快速完成 MCU 程序的开发。本文为您介绍移植 HomeKit MCU SDK 的流程以及注意事项。

简介

HomeKit MCU SDK 是根据涂鸦开发平台定义的产品功能,自动生成的 MCU 代码。为了减少您使用涂鸦 Wi-Fi HomeKit 通用串口协议的对接成本,HomeKit MCU SDK 已搭建通讯及协议解析架构。将HomeKit MCU SDK 添加至工程并配置相关信息后,既可以快速的完成 MCU 程序开发。

限制条件

涂鸦 SDK 包对 MCU 的要求如下。资源不足的用户,可自行对接串口协议,SDK 包中的函数可以作为使用参考。

  • 内存:4KB
  • RAM:DP 数据长度有关,约为 100B (OTA 功能需大于 260B)。
  • 函数嵌套级数: 9 级

文件结构

文件 说明
mcu_api.c
包含可供调用的函数。
mcu_api.h mcu_api.c中的函数声明。
protocol.c 包含协议数据体的内容处理函数。您可以根据项目需求,在相应函数内添加代码,获取 Wi-Fi 模组向 MCU 发送的数据。
protocol.h protocol.h 包含以下信息:
  • MCU 要发送至 Wi-Fi 模组初始化所需的参数。
  • SDK 裁剪所定义的宏。用户可根据需要的功能打开相应的宏定义。
  • protocol.c中的函数声明。
system.c 串口通讯协议解析的具体实现。
system.h system.h 包含以下信息:
  • 协议命令字的定义。
  • 部分全局变量的定义。
  • system.c中的函数声明。
wifi.h 包含 Wi-Fi 相关宏定义。
homekit.c 包含HomeKit相关配置和数据处理。需要用户根据实际应用做适当修改。
homekit.h homekit.h 包含以下信息:
  • HomeKit服务和特性数据 结构定义。
  • HomeKit服务和特性的字符串标识定义。
  • homekit.c中的函数声明。
tuya_type.h 包含了数据类型的定义,数据类型定义默认单片机为32位,若用户使用的单片机是其他类型,可在此头文件中自行调整数据类型。

移植流程

  1. 编写 MCU 基础程序,移植 SDK 文件。
  2. 确认 protocol.h 宏定义。
  3. 移植 protocol.c 文件及函数调用。
  4. homekit.c 文件中配置 HomeKit 服务和特性。
  5. 添加 DP 上报下发函数调用。
  6. 添加配网功能及指示灯函数。
  7. 添加产测功能。

编写 MCU 基础程序和移植 SDK 文件

  1. 在原项目工程中,完成 MCU 相关外设初始化,包括:串口、外部中断(按键)、定时器(指示灯闪烁)等。

  2. 将 MCU SDK 文件夹中的 .c.h 文件添加至相应头文件引用路径。

    MCU SDK 移植(HomeKit)
    MCU SDK 移植(HomeKit)

确认 protocol.h 宏定义

确认产品信息

  1. 定义 PID。PRODUCT_KEY 为产品 PID 宏定义。PID 即产品 ID,为每个产品的唯一标识,可在 涂鸦 IoT 平台 的产品详情页面获取。

    #define PRODUCT_KEY "ax23rawjo4np****"
    

    如果 PRODUCT_KEY 和产品 PID 不一致,请在 IoT 平台的 硬件开发>下载资料 下载最新 SDK 开发包后重试。

  2. 定义版本号。MCU_VER 为软件版本,默认为 1.0.0 。若 MCU 需要 OTA 功能,需要添加新的 MCU 版本号。

    #define MCU_VER "1.0.0"
    
  3. 选择 HomeKit 产品品类。需要和在苹果平台创建的品类一致。例如,在苹果平台创建的品类是空气净化器,此处便选择空气净化器品类。

    /* HomeKit产品品类选择。 服务配置选择和特性配置选择请在homekit.c中进行 */
    //#define HOMEKIT_PRODUCT_TYPE    3             //风扇
    //#define HOMEKIT_PRODUCT_TYPE    4             //车库门开启器
    //#define HOMEKIT_PRODUCT_TYPE    5             //照明
    //#define HOMEKIT_PRODUCT_TYPE    9             //恒温器
    //#define HOMEKIT_PRODUCT_TYPE    12            //门
    //#define HOMEKIT_PRODUCT_TYPE    13            //窗户
    //#define HOMEKIT_PRODUCT_TYPE    14            //窗帘
    #define HOMEKIT_PRODUCT_TYPE    19            //空气净化器
    //#define HOMEKIT_PRODUCT_TYPE    20            //加热器冷却器(取暖器,cooler)
    //#define HOMEKIT_PRODUCT_TYPE    22            //加湿器
    //#define HOMEKIT_PRODUCT_TYPE    23            //除湿器
    //#define HOMEKIT_PRODUCT_TYPE    28            //洒水器
    //#define HOMEKIT_PRODUCT_TYPE    29            //水龙头
    //#define HOMEKIT_PRODUCT_TYPE    30            //淋浴系统
    
  4. 选择是否进行特性有效值配置。

    /* 是否需要配置特性有效值 */
    //#define HOMEKIT_CHARACTER_VALID_CFG_ENABLE
    

    特性有效值配置命令在 char_valid_value_cfg 函数中配置。

    /**
     * @brief  特性有效值配置
     * @param  Null
     * @return Null
     * @note   
     */
    static void char_valid_value_cfg(void)
    {
        //#error "请自行完成特性有效值配置代码,完成后请删除该行"
        static u8 cfg_count = 0;
        u16 send_len = 0;
        char strbuff[80] = {0};
        u8 strlen = 0;
        
        switch(cfg_count++) {
            case 0:
                //请在此处配置有效值配置字符串
                //例如:strlen = sprintf(strbuff, "{\"service_serial\":7,\"char_str\":\"A9\",\"val_type\":0,\"valid_val\":[0,2]}");
            break;
            
            //如果需要配置多个,请添加case
    //        case 1:
    //            //请在此处配置有效值配置字符串
    //        break;
            
            default:
                //有效值配置完成,结束服务配置
                send_len = set_wifi_uart_byte(send_len, HK_SUB_CMD_CFG_QUERY);
                wifi_uart_write_frame(HOMEKIT_FUN_CMD, MCU_TX_VER, send_len);
                cfg_count = 0;
                return;
            break;
        }
        
        send_len = set_wifi_uart_byte(send_len, HK_SUB_CMD_CHARACTER_VALID_CFG);
        send_len = set_wifi_uart_buffer(send_len, strbuff, strlen);
        wifi_uart_write_frame(HOMEKIT_FUN_CMD, MCU_TX_VER, send_len);
    }
    

设置 OTA 升级(可选)

  1. 开启 OTA 升级功能。如果需要支持 OTA 固件升级,请定义 SUPPORT_MCU_FIRM_UPDATE,默认关闭。

    #define         SUPPORT_MCU_FIRM_UPDATE
    
  2. 定义单次发送固件包的大小。

    #ifdef SUPPORT_MCU_FIRM_UPDATE
    #define PACKAGE_SIZE                     0        //单个固件包大小为 256 字节
    //#define PACKAGE_SIZE                   1        //单个固件包大小为 512 字节
    //#define PACKAGE_SIZE                   2        //单个固件包大小为 1024 字节
    #endif
    

    OTA 升级流程参见 OTA 升级说明-平台配置

  3. MCU固件备份区配置。

    /*  MCU固件区类型  */
    #define MCU_FIRMWARE_BACKUP_AREA_TYPE   0       //MCU是双固件区(默认)
    //#define MCU_FIRMWARE_BACKUP_AREA_TYPE   1       //MCU是单固件区
    

    如果选用MCU单固件区方式,OTA数据包处理逻辑会有所不同,详细信息见:OTA 升级指导

    • OTA 通常要对 flash 进行读写操作。每次写入 flash 数据后,及时进行数据读取并和写入的数据进行校验,确保写入数据的正确性。
    • 添加 OTA 超时检测,防止 OTA 失败时 MCU 一致处于数据接收状态。
    • 每个 OTA 数据包都有包偏移的信息,利用包偏移检测 OTA 数据包是否存在重包或漏包。

定义收发缓存(可选)

  • 串口接收缓存:大小受到串口数据处理被调用的频率影响。如果 MCU 对串口数据的处理较快,串口接收缓存大小可适当减小。

  • 串口发送缓存:大小要大于数据最长的 DP 数据长度。

  • 串口数据处理缓存:大小需要大于数据最长的 DP 数据长度,还要根据是否需要 OTA 功能和是否需要天气服务、天气服务类型数量和天数来调整大小,需要大于最大数据量的大小。

    /******************************************************************************
                             3:定义收发缓存:
                        如当前使用MCU的RAM不够,可修改为24
    ******************************************************************************/
    #ifndef SUPPORT_MCU_FIRM_UPDATE
    #define WIFI_UART_RECV_BUF_LMT          16              //串口数据接收缓存区大小,如MCU的RAM不够,可根据MCU实际情况适当缩小
    #define WIFI_DATA_PROCESS_LMT           24              //串口数据处理缓存区大小,根据用户DP数据大小量定,建议大于24
    #else
    #define WIFI_UART_RECV_BUF_LMT          128             //串口数据接收缓存区大小,如MCU的RAM不够,可缩小
    
    //请在此处选择合适的串口数据处理缓存大小(根据上面MCU固件升级包选择的大小和是否开启天气服务来选择开启多大的缓存)
    #define WIFI_DATA_PROCESS_LMT           300             //串口数据处理缓存大小,如需MCU固件升级,若单包大小选择256,则缓存必须大于260,若开启天气服务,则需要更大
    //#define WIFI_DATA_PROCESS_LMT           600             //串口数据处理缓存大小,如需MCU固件升级,若单包大小选择512,则缓存必须大于520,若开启天气服务,则需要更大
    //#define WIFI_DATA_PROCESS_LMT           1200            //串口数据处理缓存大小,如需MCU固件升级,若单包大小选择1024,则缓存必须大于1030,若开启天气服务,则需要更大
    #endif
    #define WIFIR_UART_SEND_BUF_LMT         60              //根据用户DP数据大小量定,必须大于51
    

定义模组工作方式(必选)

  • 如果配网按键和 LED 接在 MCU 端,即选择模组和 MCU 配合处理工作模式(常用),保持 WIFI_CONTROL_SELF_MODE 宏定义处于被注释状态。

    //#define         WIFI_CONTROL_SELF_MODE                       //使用wifi自处理请开启该宏,并指定tuya-WiFi指示灯、HomeKit-WiFi指示灯、按键的模组IO。若使用MCU与模块配合处理,请关闭该宏
    
  • 如果配网指示灯和按键是接在 Wi-Fi 模组上的,即选择模组自处理工作模式,开启 WIFI_CONTROL_SELF_MODE 宏定义,然后根据实际的硬件连接,将指示灯和按键所连接的 GPIO 脚位填入下面两个宏定义。

    #ifdef          WIFI_CONTROL_SELF_MODE                      //模块自处理
      #define     TY_STATE_LED            0                     //wifi模块tuya网络状态指示,请根据实际使用的模块IO管脚设置
      #define     HK_STATE_LED            4                     //wifi模块HomeKit网络状态指示,请根据实际使用的模块IO管脚设置
      #define     WF_RESERT_KEY           5                     //wifi模块重置按键,请根据实际使用的模块IO管脚设置
    #endif
    

开启 Wi-Fi 产测功能(可选)

Wi-Fi 产测功能默认开启。为保证最终量产效率及品质,建议开启该功能。

#define         WIFI_TEST_ENABLE                //开启 Wi-Fi 产测功能(扫描指定路由)

详细产测流程,可查看 生产测试说明

其他功能(可选)

其他功能可以通过完善相应函数实现。

移植 protocol.c 文件及函数调用

  1. wifi.h 文件保存至存放 Wi-Fi 相关文件的文件夹中,例如 main.c文件夹。

  2. 在 MCU 串口及其他外设初始化后调用 mcu_api.c 文件中的 wifi_protocol_init() 函数。

  3. 将 MCU 串口单字节发送函数填入 protocol.c 文件中 uart_transmit_output 函数内,并删除 #error。示例如下:

    /**
     * @brief  串口发送数据
     * @param[in] {value} 串口要发送的1字节数据
     * @return Null
     */
    void uart_transmit_output(u8 value)
    {
        //#error "请将MCU串口发送函数填入该函数,并删除该行"
        UART3_SendByte(value);
    }
    
  4. 在串口接收中断服务函数里面调用 mcu_api.c 文件内的 uart_receive_input 函数,并将接收到的字符作为参数传入。示例如下:

    void UART3_SendByte(unsigned char data)
    {
        while((USART3->SR&UART_FLAG_TXE)!=UART_FLAG_TXE);
        USART3->DR = data;
    }
    
  5. 单片机进入 while(1) 循环后调用 mcu_api.c 文件内的 wifi_uart_service() 函数。main.c 中示例代码结构如下:

    #include "wifi.h"
    ...
    void main(void)
    {   
        wifi_protocol_init();
        ...    
        while(1) {
            wifi_uart_service();
            ...    
        }    
        ...
    }
    

    MCU 必须在 while(1) 中直接调用 mcu_api.c 内的 wifi_uart_service() 函数。程序正常初始化完成后,建议不进行关中断,如必须关中断,关中断时间必须短。关中断会引起串口数据包丢失,请勿在中断内调用上报函数。

调用 DP 数据上报和下发函数调用

DP 类型主要有 6 种:

  • bool 型:
    通常为开关类的 DP,例如开关、ECO 和屏显。
  • enum 型:
    通常用作那些有多种状态的 DP,例如工作模式、风速和风摆位置。
  • value 型:
    通常用作数值类型的 DP,例如设定温度值、当前温度值和电量。
  • fault 型:
    通常用于故障的上报,数据常用 bitmap 格式显示。
  • string 型:
    通常用作字符串类型的 DP。需要以字符串形式传输的 DP 可以使用此类型。部分不便于使用 bool、enum、value 或 fault 类型的 DP,也可用此类型。
  • raw 型:
    通常用作需要透传但对数据格式无要求(明文或者加密)的数据。发送端和接收端对数据的格式、组包和解析方式需要统一。

如果某些 DP 状态需要同步到HomeKit,请调用 homekit_character_upload 函数。

所有 DP 数据上报

在模组重启或者重新配网后,Wi-Fi 模组主动下发状态查询指令,此时需要 MCU 上报设备所有 DP 状态给 Wi-Fi 模组进行同步。

  1. 打开 protocol.c 找到函数 all_data_update(void)

  2. 把所有需要上报的 DP 初值填入相应上报函数,为面板提供开机显示初值。

    用户请勿随意调用 all_data_update() 函数,该函数会在特定时间主动调用。

    /**
     * @brief  系统所有dp点信息上传,实现APP和muc数据同步
     * @param  Null
     * @return Null
     * @note   此函数SDK内部需调用,MCU必须实现该函数内数据上报功能,包括只上报和可上报可下发型数据
     */
    void all_data_update(void)
    {
        //#error "请在此处理可下发可上报数据及只上报数据示例,处理完成后删除该行"
        
        //此代码为平台自动生成,请按照实际数据修改每个可下发可上报函数和只上报函数
        mcu_dp_bool_update(DPID_SWITCH,当前开关); //BOOL型数据上报;
        mcu_dp_enum_update(DPID_MODE,当前模式); //枚举型数据上报;
        mcu_dp_fault_update(DPID_FAULT,当前故障告警); //故障型数据上报;
        mcu_dp_value_update(DPID_PM25,当前PM2.5); //VALUE型数据上报;
        mcu_dp_value_update(DPID_PM10,当前PM10); //VALUE型数据上报;
        mcu_dp_enum_update(DPID_TIMER,当前定时); //枚举型数据上报;
        mcu_dp_value_update(DPID_FILTER_PERCENT,当前滤芯剩余百分比); //VALUE型数据上报;
        mcu_dp_value_update(DPID_FILTER_USEH,当前滤芯使用时间); //VALUE型数据上报;
        mcu_dp_bool_update(DPID_PHYSICAL_LOCKED,当前儿童锁); //BOOL型数据上报;
        
    #ifdef HOMEKIT_PRODUCT_TYPE
        homekit_character_upload_all();
    #endif
    }
    

单个 DP 数据上报

在单个 DP 状态发生变化时,MCU 需要主动上报,App 更新显示内容。上报格式为 mcu_dp_xxxx_updata(DPID_X,n),其中DPID_X 为状态改变的 DP。all_data_update() 内的函数,均可单独调用。例如:

mcu_dp_bool_update(DPID_SWITCH,1); //BOOL型数据上报
mcu_dp_value_update(DPID_PM25,15); //VALUE型数据上报
mcu_dp_enum_update(DPID_MODE,2); //枚举型数据上报
mcu_dp_fault_update(DPID_FAULT,0x01); //故障型数据上报
mcu_dp_string_update(DPID_DAY,"1234",4); //STRING 型数据上报

DP 数据下发处理函数

protocol.c 文件中,每个可下发的 DP,都有一个单独下发数据处理函数。格式为 dp_download_xxx_handle()xxx 为可下发 DP。函数解析功能点之后,MCU 需在相应位置完成逻辑控制。
以接收到开关 DP 数据为例:

/*****************************************************************************
函数名称 : dp_download_switch_handle
功能描述 : 针对DPID_SWITCH的处理函数
输入参数 : value:数据源数据
        : length:数据长度
返回参数 : 成功返回:SUCCESS/失败返回:ERROR
使用说明 : 可下发可上报类型,需要在处理完数据后上报处理结果至app
*****************************************************************************/
static u8 dp_download_switch_handle(const u8 value[], u16 length)
{
    //示例:当前DP类型为BOOL
    u8 ret;
    //0:关/1:开
    u8 switch_1;
    
    switch_1 = mcu_get_dp_download_bool(value,length);
    if(switch_1 == 0) {
        //开关关
    }else {
        //开关开
    }
  
    //处理完DP数据后应有反馈
    ret = mcu_dp_bool_update(DPID_SWITCH,switch_1);
    if(ret == SUCCESS)
        return SUCCESS;
    else
        return ERROR;
}

当设备状态在非 App 控制下发生变化时,MCU 中需要调用 mcu_dp_bool_update(DPID_SWITCH_1,switch_1);上传功能点(开关)状态实时状态,形成反馈。您也可以根据实际需要选择上报时机。

添加配网功能及指示灯函数

MCU 与模组配合处理模式工作模式需要设置配网功能及指示灯函数。

移植协议成功后,如果需要设备配网,还需要添加配网指令以及指示灯功能。配合处理模式的 MCU、配网触发方式和指示方式,可以根据实际情况自行决定,通常为按键触发和 LED 指示。

配网指令

配网指令(mcu_reset_wifi())通常在按键触发配网后,在按键处理函数中调用。

mcu_reset_wifi():调用后复位 Wi-Fi 模组,复位后之前的配网信息全部清除。

配网指示

通常在 while(1) 调用 get_wifi_tuya_state()get_wifi_homekit_state() 函数获取 Wi-Fi 状态。根据 Wi-Fi 状态,写入相应闪灯的模式。

涂鸦网络状态:

设备联网状态 描述 状态值
状态1 处于配网状态 间隔250ms亮灭
状态2 模组已配置但未未连上路由器 常灭
状态3 连上路由器但未连上云端 常灭
状态4 连接到云端 常亮

HomeKit 网络状态:

设备联网状态 描述 状态值
状态1 待绑定或绑定中 间隔250ms亮灭
状态2 未连接 常灭
状态3 已连接 常亮

调用函数 get_wifi_tuya_state()get_wifi_homekit_state() 获取连接状态,函数架构如下:

void main(void){    
    ...    
    while(1)    
    {        
        ...        
        switch(get_wifi_tuya_state()) {
            case CONFIG_STATE:  //0x00
                if(Timer3_Value % 2 == 0)   //LED快闪
                {
                    HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
                }
                else
                {
                    HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
                }
            break;
            case WIFI_NOT_CONNECTED:  //0x02
                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET); //LEDϨ熄灭
            break;
            case WIFI_CONNECTED:  //0x03
                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);//LED常亮
            break;
            case WIFI_CONN_CLOUD:  //0x04
                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET); //LED常亮
            break;
            default:
                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
            break;
        }
        
        switch(get_wifi_homekit_state()) {
            case CONFIG_STATE:  //0x00
                if(Timer3_Value % 2 == 0)   //LED快闪
                {
                    HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET);
                }
                else
                {
                    HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET);
                }
            break;
            case WIFI_NOT_CONNECTED:  //0x02
                HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET); //LEDϨ熄灭
            break;
            case WIFI_CONNECTED:  //0x03
                HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET);//LED常亮
            break;

            default:
                HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET);
            break;
        }
    }
}

添加产测功能

扫描指定路由。具体流程查看:生产测试说明

移植 homekit.c 文件及 HomeKit 服务和特性配置

  1. 配置服务。

    用户可以根据自己的需要配置服务,需要用哪个就打开哪个宏。例如选择一个风扇服务。

    /******************************************************************************
                        1.HomeKit服务配置选择
                   用户可根据产品功能需求打开对应宏
    ******************************************************************************/
    #ifdef HOMEKIT_PRODUCT_TYPE
    #define HOMEKIT_SERV_FAN_V2                                         //风扇
    //#define HOMEKIT_SERV_GARAGE_DOOR_OPENER                             //车库门开启器
    //#define HOMEKIT_SERV_LIGHTBULB                                      //灯泡
    //#define HOMEKIT_SERV_THERMOSTAT                                     //恒温器
    //#define HOMEKIT_SERV_DOOR                                           //门
    //#define HOMEKIT_SERV_WINDOW                                         //窗户
    //#define HOMEKIT_SERV_WINDOW_COVERING                                //窗帘
    //#define HOMEKIT_SERV_AIR_PURIFIER                                   //空气净化器
    //#define HOMEKIT_SERV_FILTER_MAINTENANCE                             //过滤器维护
    //#define HOMEKIT_SERV_AIR_QUALITY_SENSOR                             //空气质量传感器
    //#define HOMEKIT_SERV_SLAT                                           //板条
    //#define HOMEKIT_SERV_HEATER_COOLER                                  //加热器冷却器
    //#define HOMEKIT_SERV_HUMIDIFIER_DEHUMIDIFIER                        //加湿器除湿机
    //#define HOMEKIT_SERV_IRRIGATION_SYSTEM                              //灌溉系统
    //#define HOMEKIT_SERV_VALVE                                          //阀门
    //#define HOMEKIT_SERV_FAUCET                                         //水龙头
    #endif
    
  2. 配置可选特性。

    选择服务后,需要对每个服务选择可选特性。例如选择当前风扇状态、目标风扇状态、转动方向、转速。

    /* FAN_V2 风扇服务的可选特性选择 */
    #ifdef HOMEKIT_SERV_FAN_V2//
    #define FAN_V2_CHAR_NAME                                    //名称
    #define FAN_V2_CHAR_CURRENT_FAN_STATE                       //当前风扇状态
    #define FAN_V2_CHAR_TARGET_FAN_STATE                        //目标风扇状态
    #define FAN_V2_CHAR_ROTATION_DIRECTION                      //转动方向
    #define FAN_V2_CHAR_ROTATION_SPEED                          //转速
    //#define FAN_V2_CHAR_SWING_MODE                              //摇摆模式
    //#define FAN_V2_CHAR_LOCK_PHYSICAL_CONTROLS                  //锁定物理控制(童锁)
    #endif
    
  3. 配置特性的初值。

    可选特性配置完成之后,需要配置特性点的初值,MCCU SDK默认填的是取值范围的最小值,用户可自行修改。

    HOMEKIT_CHARACTER_S fan_v2_character[] = {
        {fan_v2_char_uuid_active_str,                           TRUE,   TRUE,   HAP_DATA_TYPE_UINT,      .hap_val.u = 0 /* 在此处可以给特性赋初值 */ },
    #ifdef FAN_V2_CHAR_NAME
        {fan_v2_char_uuid_name_str,                             FALSE,  FALSE,  HAP_DATA_TYPE_STRING,    .hap_val.s =   /* 在此处可以给特性赋初值 */ },
    #endif
    #ifdef FAN_V2_CHAR_CURRENT_FAN_STATE
        {fan_v2_char_uuid_current_fan_state_str,                FALSE,  FALSE,  HAP_DATA_TYPE_UINT,      .hap_val.u = 0 /* 在此处可以给特性赋初值 */ },
    #endif
    #ifdef FAN_V2_CHAR_TARGET_FAN_STATE
        {fan_v2_char_uuid_target_fan_state_str,                 FALSE,  FALSE,  HAP_DATA_TYPE_UINT,      .hap_val.u = 0 /* 在此处可以给特性赋初值 */ },
    #endif
    #ifdef FAN_V2_CHAR_ROTATION_DIRECTION
        {fan_v2_char_uuid_rotation_direction_str,               FALSE,  FALSE,  HAP_DATA_TYPE_INT,       .hap_val.i = 0 /* 在此处可以给特性赋初值 */ },
    #endif
    #ifdef FAN_V2_CHAR_ROTATION_SPEED
        {fan_v2_char_uuid_rotation_speed_str,                   FALSE,  FALSE,  HAP_DATA_TYPE_FLOAT,     .hap_val.f = 0 /* 在此处可以给特性赋初值 */ },
    #endif
    #ifdef FAN_V2_CHAR_SWING_MODE
        {fan_v2_char_uuid_swing_mode_str,                       FALSE,  FALSE,  HAP_DATA_TYPE_UINT,      .hap_val.u = 0 /* 在此处可以给特性赋初值 */ },
    #endif
    #ifdef FAN_V2_CHAR_LOCK_PHYSICAL_CONTROLS
        {fan_v2_char_uuid_lock_physical_controls_str,           FALSE,  FALSE,  HAP_DATA_TYPE_UINT,      .hap_val.u = 0 /* 在此处可以给特性赋初值 */ },
    #endif
    };
    
  4. 服务序号配置。

    如果配置了多个服务,需要把每个服务配置成不同的服务序号,用来区分不同的服务。这样做的原因是为了能够识别那些需要配置多个相同服务的产品。例如:灌溉器。如果需要配置多个相同的服务,可根据现有代码格式自行添加。

    #ifdef HOMEKIT_SERV_FAN_V2
        {
            0, //需要用户自行修改服务序号,且不能与其他相同
            hap_serv_uuid_fan_v2_str,
            FALSE,
            CNTSOF(fan_v2_character),
            fan_v2_character,
        },
    #endif
    
  5. 处理HomeKit特性命令下发。

    MCU收到命令下发时,需要及时处理并回复结果,请在 homekit_character_ctrl 函数中进行补充。如果特性点中有需要同步给涂鸦 App 的,还需要增加涂鸦 DP 的上报函数,可调用 mcu_dp_bool_updatemcu_dp_value_updatemcu_dp_enum_update 等函数将状态同步到涂鸦。

    /**
     * @brief  HomeKit特性命令下发
     * @param[in] {serv_serial} 服务序号
     * @param[in] {p_char_str} 特性字符串标识
     * @param[in] {char_data_type} 特性数据类型
     * @param[in] {char_val_len} 特性数据长度
     * @param[in] {p_char_val} 特性数据
     * @return SUCCESS/ERROR
     * @note   
     */
    u8 homekit_character_ctrl(u8 serv_serial, i8 p_char_str[], u8 char_data_type, u16 char_val_len, HAP_VALUE_T *p_char_val)
    {
        //#error "请自行完成 homekit 命令下发处理代码,并删除该行"
        
        u8 serv_i = 0, char_j = 0;
        
        for(serv_i = 0; serv_i < CNTSOF(homekit_service); serv_i++) {
            if(serv_serial == homekit_service[serv_i].serial) {
                for(char_j = 0; char_j < homekit_service[serv_i].character_amount; char_j++) {
                    if(0 == my_strcmp(p_char_str, homekit_service[serv_i].p_character_arr[char_j].p_character_str)) {
                        if(char_data_type == homekit_service[serv_i].p_character_arr[char_j].hap_val_type) {
    
                            //请在此添加特性下发命令处理代码
                            //homekit_service[serv_i].p_service_str:服务
                            //homekit_service[serv_i].p_character_arr[char_j].p_character_str:特性
                            //char_data_type:数据类型
                            //char_val_len:数据长度
                            //p_char_val:数据
                            
                            
                            if(0 == my_strcmp(homekit_service[serv_i].p_service_str, HAP_SERV_UUID_AIR_PURIFIER) && 0 == my_strcmp(homekit_service[serv_i].p_character_arr[char_j].p_character_str, HAP_CHAR_UUID_ACTIVE))
                            {
                                homekit_character_upload(serv_serial, HAP_CHAR_UUID_CURRENT_AIR_PURIFIER_STATE, char_data_type, char_val_len, p_char_val);
                            }
                            
                            
                            //可在此添加状态上报。例如:
                            homekit_character_upload(serv_serial, homekit_service[serv_i].p_character_arr[char_j].p_character_str, char_data_type, char_val_len, p_char_val);
                            return SUCCESS;
                        }
                    }
                }
                return ERROR;
            }
        }
        return ERROR;
    }
    

与 Wi-Fi 模组数据通信

请在初始化完成后再进行数据交互,避免不可预测的问题。

帧格式

帧格式参考 帧格式说明

初始化通信

在 MCU 和模组上电后,需要进行初始化配置。初始化通信包括但不限于以下内容:

  • 需要验证 MCU 与模组是否能正常工作
  • 验证通信连接是否正常
  • 获取模组激活所需要数据
  • 获取模组工作方式

心跳包

心跳包是 MCU 和模组之间定时通信的数据,可作为验证 MCU 或者模组是否正常工作的依据。建议将 MCU 和模组上电后的第一次通信的帧作为心跳包。只有在心跳包发送和回复都正常的情况下,才可以进行通信。

查询产品信息

心跳包正常交互后,模组需要查询 MCU 固件以下信息。

  • PID :产品 ID,用于产品激活。
  • 版本号:MCU 版本号,用于 OTA 升级完成后,验证是否升级成功。
  • 配网方式:模组的配网方式。

设定模组的工作方式

用来设置模组工作方式,选择 MCU 与模组配合处理方式或者模组自处理方式。可根据 Wi-Fi 网络状态指示灯和模组重置按键的硬件连接位置选择。

Wi-Fi 模组初始化数据交互流程

MCU SDK 移植(HomeKit)

DP 上报和网络状态的关系

涂鸦 App 和设备之间能够实现 DP 下发和上报,有两种情况:

  • Wi-Fi 模组的网络状态为状态 5:设备连上路由和涂鸦 IoT。此时 DP 可以正常上报。
  • Wi-Fi 模组的网络状态为状态 4:设备已连上路由器,但未连接到云端。手机可以连接到路由,此时 DP 可以在局域网进行控制。DP 也可以正常上报到 App。

OTA 数据包交互

MCU 与 Wi-Fi 模组在进行 OTA 数据交互时,数据包交互的数据量比较多,会出现一些异常情况。本章节介绍 Wi-Fi 模组的处理逻辑,您可以根据。

  • OTA 启动
    Wi-Fi 模组在检测到有 MCU OTA 时,会发送启动升级包。

    • 如果 5s 内没有收到 MCU 的回复,会进行重发。若重发 3 次之后 MCU 依旧没有回复,就认为 OTA 失败。
    • 如果固件包总字节数超出了 MCU 的处理范围,MCU 可以不做回复,让 Wi-Fi 模组自行退出 OTA。此时需检查在涂鸦 IoT 平台上传的文件是否正确。
  • OTA 数据传输
    Wi-Fi 模组在发送一包 OTA 数据之后,如果 5s 内没有收到 MCU 的回复,会进行重发。若重发 3 次之后 MCU 依旧没有回复,就认为 OTA 失败。

    • 如果 MCU 检测到 OTA 数据有误,可以不做回复,让 Wi-Fi 模组进行重发。此时需要注意包偏移是否正确,防止重发或漏发。
    • 如果 MCU 对 OTA 数据的处理速度太慢,会导致模组频繁重发数据,此时需要注意包偏移是否正确,防止重发或漏发。如果出现这种情况,建议调小固件包传输的大小。
  • OTA 传输完成

    支持以下两种方式判断 OTA 传输是否完成。

    • 帧长度为 0x0004,即 OTA 数据包长度为 0 。
    • 包偏移等于 Wi-Fi 模组发送升级启动帧时的固件包总长度。

    建议同时使用以上两种方式检测 OTA 传输情况。

MCU 对接认证测试

在开发MCU时,不止要实现与模组的串口对接协议通信,还要注意苹果的认证要求。此章节对部分苹果认证测试用例MCU如何配合做出说明,认证测试具体要求请访问苹果官网查看。

  • TCF039:此用例是进行测试配网二维码的。

    配网码是根据苹果平台申请的PPID以及其他的校验信息而生成的,所以,MCU的品类配置需要和苹果平台上的配置的品类一致,否则此用例会失败。例如,在苹果平台创建的品类是风扇,那么MCU配置中,品类就要配置为风扇。下图为MCU SDK中 protocol.h 文件中的配置示例。

    MCU SDK 移植(HomeKit)

  • TCI014:此用例主要进行特性点的上报以及下发。

    测试工具会对部分用例进行特性点的查询、下发、等待上报,需要执行哪些额被测特性的操作类型有关,如果是只读的,会有查询、等待上报,如果是可读可写的,查询、下发、等待上报都会有。

    如果是只读,测试工具会读取当前状态,然后弹出一个窗口提示,此时需要设备根据窗口提示内容主动上报一个不一样的值,上报成功之后就会通过,如果上报的值与测试工具一开始读到的值一样,就没有反应。也就是说,如果之前的状态值是0,就要上报1或者其他值,如果之前的状态值是1,就要上报0或者其他值。

    如果是读写,测试工具会读取当前状态,然后根据当前状态下发一个不一样的值,下发之后再一次读取。例如设置时间(D3)的类型是读写,按照这个逻辑,初始状态是0,测试工具读到之后会下发1,然后设备回复1,完成之后,这个特性的状态缓存记录就是1了。然后就会进行和上述1一样的测试步骤。此时如果还是上报1,就和一开始读到的值是一样的了,就没反应,上报0反而是可以通过的。

    为方便某些特性点的状态更改,建议MCU上报状态时,涂鸦和HomeKit都要上报。

  • TCH096:此用例要求设备有配件提示功能。

    根据用例要求,设备需要在测试工具发出配件提示指令后,在5s内有提示信息(例如:指示灯闪烁几次,或者蜂鸣器响几声)。

    用MCU通用对接方案,模组有自处理和配合处理两种工作模式。如果使用模组自处理,此逻辑模组已做成HomeKit状态指示灯闪烁3次,随后回复常亮状态。如果使用配合处理,模组会给MCU发送HomeKit状态为03的网络状态通知命令,MCU在收到此命令后5s内需作出提示逻辑,此逻辑需要用户的MCU自行实现,并且为防止此提示命令会影响到用户网络指示逻辑,在5s左右之后模组会再发送一个HomeKit状态为02的命令,详细说明可查看协议文档报告设备联网状态章节。

    MCU SDK 移植(HomeKit)

  • TCS003:此用例是特性点压测。此用例需要压测某个特性点。如果使用的 不是TYWE3SE 模组,需要在配置可选特性时将名称(name)特性添加上,否则在测此用例时将会找不到可以压测的特性点。

  • TCH091:此用例根据特性有效值和数据范围进行校验。根据此用例的说明,如果某特性的有效值为0、1、2、3,则写入4。如果有效值为1、3、4,则写入2,若写入失败,则测试通过,所以,当某个特性默认取值范围内的值不使用,MCU务必要发送特性有效值配置命令。

  • TCV004:此用例需要设置阀门名称,使用阀门(Valve)服务需要关心此说明。由于目前不支持配置阀门的名称(Name)特性,所以为了让名称显示正常,需要配置服务标签索引(Service Label Index)特性,取值范围是1~255。如果有配置多个阀门服务,则需要每个服务标签索引的值不一样,以作相同服务的区分。

    例如:配置了3个阀门服务,并且都配置了服务标签索引特性,第一个服务的服务标签索引特性的值配置为1,第二个服务的服务标签索引特性的值配置为2,第三个服务的服务标签索引特性的值配置为3,那么设备绑定到HomeKit之后,在家庭APP上显示的三个阀门服务的名称分别是valve 1、valve 2、valve 3 。