1.4.4.2 嵌入式开发指南

优质
小牛编辑
121浏览
2023-12-01

名词解释

名词解释
SDK软件开发工具包,HeyThings SDK包含了HeyThings协议的实现,客户在SDK的基础上,可快速完成产品接入
MCU微控制单元,嵌入式设备的核心控制芯片,这里指客户已有产品中的核心控制芯片
schema产品功能抽象服务定义,产品抽象为具备多个服务,每个服务包含支持的属性、方法、事件
user_helper应用层辅助代码,实现基于HeyThings协议的具体产品模型的封装和解析,代理应用层与SDK交互
user_code应用层产品功能代码,实现产品的实际业务操作和状态改变,通过user_helper与SDK交互

1 概述

​平台将提供WiFi、BLE、MESH等多种接入的方式,目前主要开放的接入方式为WiFi和BLE两种。WiFi适合较大带宽、低延迟、交流供电的场景,如台灯、插座、空调等大小家电。BLE相对来说适合低功耗、低成本的产品,像电池供电的传感器、穿戴式设备。

本章主要说明如何在IoT WiFi模组中集成HeyThings SDK进行产品开发,实现设备注册、控制、状态上报、OTA升级等业务功能。

2 开发模式

2.1 模组SDK模式

HeyThings平台提供IoT模组的SDK,客户把SDK和产品功能软件一起集成到产品上。

应用场景:使用一块IoT控制芯片实现产品功能,节省芯片成本;产品自身功能不复杂,容易移植到IoT模组上。

单模组芯片

2.2 模组SDK + MCU 模式

HeyThings平台提供集成了SDK的IoT模组,客户在模组上添加串口协议,与自己产品的独立MCU通信。

应用场景: 客户有自己的MCU,产品自身的主要业务已在MCU侧实现,客户不修改MCU侧的产品实现。

模组+MCU

2.3 移植SDK到其他模组

应用场景:客户有自己的模组,且MCU资源丰富,希望将HeyThings SDK移植到自己的模组上运行。

该模式下的移植将主要由客户自己完成,HeyThings平台提供协助。移植完成后的使用将与上述的模组SDK模式一致。

3 模组SDK模式

该模式指使用单芯片模组完成整个产品的核心控制功能,且HeyThings SDK与用户设备控制功能都运行在同一IoT模组上。

开发步骤:

  1. 定义设备schema(设备所支持的服务)
  2. 申请及配置设备证书、产品信息
  3. 完成应用层需实现的接口(设备控制回调、设备属性状态变化设置等)
  4. 调用HeyThings SDK循环

HeyThings SDK的主要运行流程如下:

@startuml
participant "app/clouds" as app
participant sdk
participant user_helper
participant user_code

group iot_sdk_start
group sdk 初始化
    sdk->sdk           : 加载设备基本信息
    sdk->sdk           : 加载设备证书
    sdk->sdk           : sdk内部资源初始化
    sdk->user_code     : 初始化完成回调\nhandle->init_complete_cb()
end group

    sdk->app           : connect, login ...
    sdk->user_code     : 状态通知 handle->event_handle()

group 设备控制、状态上报
    app->sdk           : 控制命令
    sdk->user_helper : 属性设置控制类回调:handle->cmd_set_handle()\n 数组类属性操作回调:cmd_array_xxx_handle()
    user_helper -> user_code : user_handle_dev_xx() \n业务层填充设备具体响应操作
    user_code -> user_helper: serviec_xxx_property_xxx_changed()\n业务层完成操作后回复属性改变
    user_helper->sdk        : 设备状态变化通知sdk\n iot_dev_property_changed_string()
    sdk -> app              : 状态上报
end group

group 辅助函数
    user_code -> sdk : 获取系统时间 sdk_time_get_date(time_date_t * now)
end group

end group
@enduml

业务应用层需要实现的函数,都封装在oc_handler_t结构中。

//需用户层实现的函数
typedef struct {
    char *app_version;

    //user handle 'method:set' command.
    //return 0 on success, -1 on error
    int (*cmd_set_handle)(int siid, int n_iid, unsigned int *iids, int len, unsigned char *changes);

    //if user care about some get cmd of service. set the ssid to siid_monitor array.
    //siid_monitor_cnt indicate the count of the service user want to monitor.
    int siid_monitor[ MAX_MONITOR_SERVICE ];

    int siid_monitor_cnt;

    //get cmd callback, if user care about some get cmd of service.
    int (*cmd_get_cmd_cb)(int siid, int n_iid, unsigned int *iids);

    //user handle 'method:arrayAdd' command.
    //return the id the of the item added, return < 0 if error occurs.
    int (*cmd_array_add_handle)(int siid, int iid, int len, unsigned char *content);

    //user handle 'method:arrayDel' command.
    //return 0 on success, -1 on error
    int (*cmd_array_del_handle)(int siid, int iid, int n_ids, int *ids);

    //user handle 'method:arrayUpdate' command.
    //return 0 on success, -1 on error
    int (*cmd_array_update_handle)(int siid, int iid, int id, int len, unsigned char *changes);

    //sdk notify state changes to app layer.
    void (*event_handle)(user_event_t evt);

    //sdk notify user to allow device provision.
    void (*dev_provision_confirm)(void);

    //called when init complete.
    void (*init_complete_cb)(void);

    //user handle 'method:actioncall' command.
    int (*cmd_action_call)(int siid, unsigned int iid, int len, unsigned char *data);
} oc_handler_t;

/**
 * @brief : start sdk, function not return to the caller
 * @param user_handler : callback function provided by user.
 * @param odos         : whether it is one device one secert device
 * @param secert_mode  : whether added device by PIN
 * @param p_dev_info   : device base info.
 * @return int         : -1 on error.
 */
int iot_sdk_start(oc_handler_t *user_handler, unsigned int odos,
                  DEV_SECERT_MODE_T secert_mode, iot_dev_info_t *p_dev_info);

/**
 * @brief       : if device property value changes, it should call this function to notify sdk
 * @param siid  : service instance id.
 * @param iid   : property instance id.
 * @param value : property value serialized.
 * @param len   : length the value data.
 * @return int  : 0 on success, -1 on error.
 */
int iot_dev_property_changed_string(int siid, int iid, unsigned char *value, int len);

/**
 * @brief       : register property to sdk.
 * @param siid  : service instance id
 * @param iid   : property instance id
 * @param value : property value in binary format
 * @param len   : value length
 */
void iot_dev_property_register(int siid, int iid, unsigned char *value, int len);

3.1 设置产品硬件相关信息

对具体某一类设备,业务开发者通过硬编码的方式填充iot_dev_info_t结构,作为初始化函数的参数传递信息到SDK。

typedef struct
{
    unsigned char  cid[DEV_CID_BYTES + 1];            //产品category id  
    unsigned char  pid[DEV_PID_BYTES + 1];            //产品product id

    unsigned char  brand[DEV_BRAND_BYTES + 1];        //品牌名
    unsigned char  manufacture[DEV_MANUF_BYTES + 1];  //厂商名
    unsigned char  model[DEV_MODEL_BYTES + 1];        //型号

    unsigned char  connect_type;                      //接入类型:wifi,zigbee,bt
    unsigned char  hw_version[DEV_HWVER_LEN + 1];     //硬件版本
} iot_dev_info_t;

3.2 设置设备证书等出厂信息

HeyThings SDK定义了出厂信息的格式和存储区域,并提供了工具生成信息烧录文件,开发者需要按照指定位置烧录文件。

3.3 SDK消息循环

/**
 * @brief : start sdk, function not return to the caller
 * @param user_handler : callback function provided by user.
 * @param odos         : whether it is one device one secert device
 * @param secert_mode  : whether added device by PIN
 * @param p_dev_info   : device base info.
 * @return int         : -1 on error.
 */
int iot_sdk_start(oc_handler_t *user_handler, unsigned int odos,
                  DEV_SECERT_MODE_T secert_mode, iot_dev_info_t *p_dev_info);

该函数完成SDK的初始化、各功能实现(配网、云端和app端消息处理等)。调用该函数后,该函数将循环运行,不返回。

典型调用如下:

int user_sdk_main_thread(void)
{
    int ret;

    ret = iot_sdk_start(&handler);
    if( ret < 0) {
        return -1;
    }

    return 0;
}

3.4 设备控制

云端或APP控制设备时,SDK接收控制命令,然后通过回调函数通知用户层程序完成设备的实际控制。

int (*cmd_set_handle)(int siid, int n_iid, unsigned int *iids, int len, unsigned char *changes);

由于控制端与设备之间的模型交互为protobuf格式,SDK将protobuf序列化之后的数据传递给应用层,应用层需要通过设备模型定义才能理解含义。 为简化开发者的应用层开发,SDK所提供的demo中已实现设备抽象层代码、服务代码自动生成等,开发者只需完成实际设备控制函数。

set命令在sdk中的定义:

struct  _Iot__CmdSetPropertiesReq
{
  ProtobufCMessage base;
  uint64_t did;    //子设备时使用
  uint32_t siid;   //服务id
  /*
   * property id list.
   */
  size_t n_iid;   //iid 数量
  uint32_t *iid;  //iid 列表
  /*
   * packed service(properties) value
   */
  ProtobufCBinaryData changes;  // 服务、属性值
};

以灯操作为例:


    SDK -> user_helper : cmd_set_user_cb(int siid, int n_iid, \nunsigned int *iids, int len, \nunsigned char *changes)  通用代码

    user_helper -> user_code : cmd_set_handle( iid, service_object )  通用代码

    user_code -> user_code :  switch(iid) : \ncase 1:  user_handle_dev_on_off(x)\n case 2: user_handle_dev_bright(x)\n case xxx :...\n(开发者填充,实际控制设备)

3.5 注册设备属性

注册设备属性到SDK,根据属性类型,调用相应的注册接口。属性以key-value形式表示。

用户定义设备支持的服务后,该部分代码可由工具自动生成。

//示例:
//服务id定义,来源于开放平台
#define SIID_LIGHT       512

//属性定义
struct iid_name ServiceLight[] = {
    {.iid = 1, .field_name = "switch_" },
    {.iid = 2, .field_name = "brightness" },
    {.iid = 3, .field_name = "colortemperature" },
    {.iid = 4, .field_name = "colorrgb" },
    {.iid = 5, .field_name = "mode" },
};

//注册各属性
int service_light_init()
{
    iot_dev_property_register( SIID_LIGHT, iid, buf, buf_len );
}

3.6 属性上报

设备属性变化后,将新的值设置到SDK中,由SDK完成上报。

//灯服务示例:
void serviec_light_property_switch__changed(int switch_);
void serviec_light_property_brightness_changed(uint32_t brightness);

设备状态变化(如用户按键开关灯):

user_code -> user_helper: 用户按照属性调用\nserviec_light_property_xxx_changed(property_value)

user_helper -> sdk: iot_dev_property_changed_string(xx)

sdk -> sdk : 存储、发布状态

3.7 SDK内部状态通知

用户程序可通过sdk的状态通知,获取sdk的状态变化。如:根据wifi模块的状态,指示灯进行对应模式的闪烁。

typedef enum
{
    E_LAN_CONNECTED,        //已连接到路由器,还未连接到云端
    E_CLOUD_CONNECTED,      //已连接到云端

    E_LAN_DISCONNECTED,     //和路由器断开连接
    E_CLOUD_DISCONNECTED,   //和云端断开连接

    E_DEV_ADDING,           //正处于配网过程中
    E_DEV_ADDING_SUCCESS,   //已配网,还未连接到路由器
    E_DEV_ADDING_FAIL,      //配网超时

    E_DEV_DELETED,          //device deleted by app or cloud.
}user_event_t;

void demo_user_event_handle(user_event_t evt);

3.8 OTA升级接口

OTA升级是定义为设备的一个抽象服务,与其他应用服务类似,需要由应用层完成其服务模型解析,以获取到固件的URL和文件签名数据,并完成固件烧写和进度上报的动作。

3.9 应用层demo代码

详细代码内容见demo源码文件。

app_src/
    ├── protobuf-c                  //protobuf的C语言实现库
    │   ├── protobuf-c.c
    │   └── protobuf-c.h
    ├── app_main.c                  //应用层入口主程序文件
    ├── app_main.h
    ├── app_version.h               //应用层软件版本号定义
    ├── debug_cli.c                 //终端调试命令任务
    ├── debug_cli.h
    ├── dev_abstract.c              //设备抽模型接收回调,SDK命令处理入口
    ├── dev_abstract.h
    ├── devInfo.pb-c.c              //设备基础信息服务protobuf定义
    ├── devInfo.pb-c.h
    ├── devInfo.proto
    ├── light.pb-c.c                //智能灯控制服务protobuf定义
    ├── light.pb-c.h
    ├── light.proto
    ├── light_service.c             //灯控制服务的模型封装和解析,即user helper
    ├── light_service.h
    ├── light_service_devinfo.c     //灯基础信息服务的模型封装和解析
    ├── light_service_devinfo.h
    ├── light_service_upgrade.c     //设备OTA升级服务的模型封装和解析
    ├── light_service_upgrade.h
    ├── ota_task.c                  //应用层实现的ota升级处理,平台相关
    ├── softwareUpdate.pb-c.c       //设备OTA升级服务protobuf定义
    ├── softwareUpdate.pb-c.h
    └── softwareUpdate.proto

4 IoT模组 + MCU

客户的设备控制功能运行在客户已有MCU上,通过串口与IoT模组通讯,实现云端接入和app控制。 如果客户已有私有的串口通信协议,需在模块上完成串口协议的开发,完成与MCU的通信。 如果客户还未开发串口通信协议,可使用IoT模组定义的通用串口协议(TODO)

串口协议需支持设备控制、状态获取、OTA升级等功能。