网关设备端开发指南

更新时间:2018-06-06 14:18:26

术语约定

  • SDK:Software Development Kit(软件开发工具包),本文所述的SDK均指网关SDK;
    网关设备:也叫主设备,是指可以挂载子设备的直连设备,网关具有子设备管理模块,维持子设备的拓扑关系,并且可以将拓扑关系同步到云端;

  • 子设备:不能直接连接到云端,只能通过挂接到网关上,通过网关间接连云的设备;

  • 设备三要素:在网关SDK中,将设备的能力分成三种,分别是属性、事件和服务,这三种能力简称设备的三要素;

  • 设备ID:设备句柄,在网关SDK中用于标识一个具体的设备;

  • 设备标识三元组:设备标识三元组指设备的productKey、deviceName和deviceSecret,用于在阿里IoT云中唯一标识设备;

  • 动态注册:子设备无需烧录一机一密(三元组),只需要烧录productKey,然后基于子设备原厂的唯一标识作为三元组中的deviceName,例如MAC地址,再使用网关SDK到云端动态注册获取deviceSecret。这样,子设备就可以动态获取三元组与云端通信,并可以在云端管理子设备。

  • 静态注册:为了进一步保证子设备的安全性,厂商可以预先在平台注册设备的节点类型,并通过云端控制台或者调用云端API的方式获取三元组,然后将三元组烧录在子设备上,子设备传递已经烧录的三元组给网关进而与云端通信;

  • TSL:Thing Specification Language,基于JSON格式,用于描述设备所具备的功能和能力,详细说明请参阅《物的模型TSL》

开发流程

网关设备端开发主要包括以下步骤:

  1. 移植网关SDK的硬件抽象层(HAL)接口;
  2. 调用初始化接口对网关SDK进行初始化;
  3. 调用SDK接口创建网关设备,注册网关相关的回调函数并启动网关服务;
  4. 调用SDK接口创建子设备,注册子设备相关的回调函数;

HAL接口移植

网关SDK通过实现一个HAL层将操作系统和硬件平台相关的代码进行隔离,从而确保SDK的实现与操作系统和硬件平台无关,方便网关SDK移植到不同的操作系统和硬件平台。HAL层接口的移植和实现可以参考《使用未适配IoT SDK的平台开发智能灯》

网关SDK初始化

网关SDK的初始化主要包括以下步骤:

  1. 获取默认参数;
  2. 修改默认参数;
  3. 设置事件回调;
  4. 调用linkkit_gateway_init()对网关SDK进行初始化。

获取默认参数

为了简化网关SDK默认参数的初始化,确保用户无需重新编译代码,只需要替换网关SDK生成的动态库即可完成设备的升级,网关SDK并没有将用于保存SDK初始化参数的linkkit_params_t结构体暴露在头文件中,该结构体实例需要通过以下接口获取:

linkkit_params_t *linkkit_gateway_get_default_params(void);

注意:linkkit_gateway_get_default_params()返回的默认参数用户无需释放。

修改默认参数

网关SDK默认参数的修改需要通过以下接口进行修改:

int linkkit_gateway_set_option(linkkit_params_t *params, int option, void *value, int value_len);

其中params为linkkit_gateway_get_default_params()返回的默认参数实例,option为需要修改的参数项目,value为option对应的值,value_len为value的字节长度。可选的option如下表所示:

option
值类型
默认值
取值范围
说明
LINKKIT_OPT_MAX_MSG_SIZE
int
20480
512 - 51200
设置最大消息长度
LINKKIT_OPT_MAX_MSG_QUEUE_SIZE
int
16
2 - 32
设置消息队列长度
LINKKIT_OPT_THREAD_POOL_SIZE
int
4
1 - 16
用于处理消息的线程池线程数,用户注册的回调会在线程池中调用
LINKKIT_OPT_THREAD_STACK_SIZE
int
8192
1024-8388608
线程池的线程栈栈大小
LINKKIT_OPT_LOG_LEVEL
int
4
0-5
日志打印等级

在实际使用过程中,用户可以根据自身业务的特点和硬件的性能选择合适的参数。

设置事件回调

网关SDK通过注册事件回调的方式通知用户SDK内部状态的变化,可以通过以下接口设置网关SDK的事件处理回调:

int linkkit_gateway_set_event_callback(linkkit_params_t *params, int (*event_cb)(linkkit_event_t *ev, void *ctx), void *ctx);

其中params为linkkit_gateway_get_default_params()返回的默认参数实例,event_cb为需要注册的事件处理回调,ctx为用户私有数据,内部在调用event_cb时会将ctx作为event_cb的第二个参数传递给event_cb。
网关SDK使用linkkit_event_t结构体封装了事件及事件对应的数据,linkkit_event_t的声明如下所示:

typedef struct {
    int event_type; /* see LINKKIT_EVENT_XXX for more details */

    union {
        struct {
            char *productKey;
            char *deviceName;
        } subdev_deleted;

        struct {
            char *productKey;
            int   timeoutSec;
        } subdev_permited;

        struct {
            char *subdevList;
        } subdev_setup;
    } event_data;
} linkkit_event_t;

其中event_type为事件类型,event_data为事件对应的数据。event_data为union类型,网关SDK内部会根据不同的事件类型给event_data对应的成员赋值,例如,当事件类型为LINKKIT_EVENT_SUBDEV_DELETED时,用户应该使用event_datasubdev_deleted成员结构体。

网关SDK支持event_type如下表所示:

事件类型
枚举值
event_data
说明
LINKKIT_EVENT_CLOUD_DISCONNECTED
0
设备断开阿里IoT云
LINKKIT_EVENT_CLOUD_CONNECTED
1
设备连接上阿里IoT云。在连上IoT云时,应该post设备的所有属性,确保云端的影子设备与设备端同步
LINKKIT_EVENT_SUBDEV_DELETED
2
subdev_deleted
云端删除子设备,删除的子设备由event_data的subdev_deleted指定
LINKKIT_EVENT_SUBDEV_PERMITED
3
subdev_permited
允许zigbee子设备接入网关。允许接入网关的子设备由event_data的subdev_permited指定,如果product Key为NULL,则允许所有子设备接入,如果productKey不为NULL,则只允许productKey为对应值的子设备接入。timeoutSec为允许子设备接入的时间窗口。如果timeoutSec为0,则禁止子设备接入。
LINKKIT_EVENT_SUBDEV_SETUP
4
subdev_setup
ZigBee网关置换专用,用户在APP端触发网关置换请求,云端通过属性设置接口设置ZigBee网络参数到新的网关,再从云端同步被置换网关子设备信息到新的网关,subdevList json字符串格式:[{"productKey":"","deviceName":""},...]

事件回调的处理流程示例代码如下所示:

static int event_cb(linkkit_event_t *ev, void *ctx)
{
    switch (ev->event_type) {
    case LINKKIT_EVENT_CLOUD_CONNECTED:
        {
            printf("cloud connected\n");
            /* TODO: 在此处处理成功连接阿里IoT云的消息 */
        }
        break;
    case LINKKIT_EVENT_CLOUD_DISCONNECTED:
        {
            printf("cloud disconnected\n");
            /* TODO: 在此处处理断开阿里IoT云的消息 */
        }
        break;
    case LINKKIT_EVENT_SUBDEV_DELETED:
        {
            char *productKey = ev->event_data.subdev_deleted.productKey;
            char *deviceName = ev->event_data.subdev_deleted.deviceName;
            printf("delete subdev %s<%s>\n", productKey, deviceName);
            /* TODO: 在此处删除productKey和deviceName指定的子设备 */
        }
        break;
    case LINKKIT_EVENT_SUBDEV_PERMITED:
        {
            char *productKey = ev->event_data.subdev_permited.productKey;
            int   timeoutSec = ev->event_data.subdev_permited.timeoutSec;
            printf("permit subdev %s in %d seconds\n", productKey, timeoutSec);
            /* TODO: 在此处调用Zigbee网关的配网接口,使网关进入配网状态 */
        }
        break;
    case LINKKIT_EVENT_SUBDEV_SETUP:
        {
            char *subdevs = ev->event_data.subdev_setup.subdevList;
            printf("setup subdevs: %s\n", subdevs);
            /* TODO: 在此处调用Zigbee网关子设备pk、dn配置接口,将ZigBee设备迁移到当前网关上 */
        }
        break;
    }

    return 0;
}

注意linkkit_gateway_set_event_callback()需要在调用linkkit_gateway_init()前设置好事件回调,否则事件回调将无法生效。

调用linkkit_gateway_init()进行网关SDK初始化

用户在设置好初始化参数后,可以将初始化参数作为linkkit_gateway_init()的输入参数,通过调用linkkit_gateway_init()对SDK进行实际的初始化。linkkit_gateway_init()的声明如下:

int linkkit_gateway_init(linkkit_params_t *initParams)

注意:网关SDK中的接口除了linkkit_gateway_get_default_params()linkkit_gateway_set_event_callback()linkkit_gateway_set_option()需要在linkkit_gateway_init()前调用外,其他的接口需要在linkkit_gateway_init()调用成功后才能调用。

示例代码

网关SDK的初始化示例代码如下所示:

linkkit_params_t *initParams = linkkit_gateway_get_default_params();
if (!initParams)
    return -1;

int maxMsgSize = 40 * 1024;
linkkit_gateway_set_option(initParams, LINKKIT_OPT_MAX_MSG_SIZE, &maxMsgSize, sizeof(int));

int maxMsgQueueSize = 8;
linkkit_gateway_set_option(initParams, LINKKIT_OPT_MAX_MSG_QUEUE_SIZE, &maxMsgQueueSize, sizeof(int));

int threadPoolSize = 4;
linkkit_gateway_set_option(initParams, LINKKIT_OPT_THREAD_POOL_SIZE, &threadPoolSize, sizeof(int));

int threadStackSize = 8 * 1024;
linkkit_gateway_set_option(initParams, LINKKIT_OPT_THREAD_STACK_SIZE, &threadStackSize, sizeof(int));

linkkit_gateway_set_event_callback(initParams, event_cb, &user_data);

if (linkkit_gateway_init(initParams) < 0) {
    DPRINT("linkkit_gateway_init failed\n");
    return -1;
}

在上述SDK的初始化示例代码中,将最大消息大小设置为40KB,将消息队列最大长度设置为8,将线程池的最大线程数设置为4,将线程池的线程栈大小设置为8KB。注册的事件回调为event_cb,event_cb的实现请参考本文设置事件回调一节的示例代码。

网关设备创建和销毁

网关设备的创建

用户通过调用linkkit_gateway_start()创建网关设备和对网关设备进行初始化,linkkit_gateway_start()的声明如下所示:

int linkkit_gateway_start(linkkit_cbs_t *cbs, void *ctx);

在调用linkkit_gateway_start()的时候需要传入linkkit_cbs_t定义的一组用于操作网关设备的三要素的函数指针。linkkit_cbs_t的声明如下所示:

typedef struct {
    int (*get_property)(char *in, char *out, int out_len, void *ctx);
    int (*set_property)(char *in, void *ctx);
    int (*call_service)(char *identifier, char *in, char *out, int out_len, void *ctx);
} linkkit_cbs_t;

其中,云端获取设备的属性时会调用get_property指定的函数,云端设置设备的属性时会调用set_property指定的函数,云端调用设备的服务时会调用call_service指定的函数。

linkkit_gateway_start()调用成功会返回网关设备的设备ID,用户可以通过设备ID对设备进行管理。用户可以通过设备ID对网关设备进行以下操作:

  1. linkkit_gateway_trigger_event_json_sync:以同步的方式上报网关设备相关的事件;
  2. linkkit_gateway_post_property_json_sync:上报网关设备相关的属性变更;
  3. linkkit_gateway_post_extinfos:上报网关设备相关的扩展信息;
  4. linkkit_gateway_delete_extinfos:删除网关设备的扩展信息;
  5. linkkit_gateway_get_devinfo:获取网关设备的设备信息和当前状态;
  6. linkkit_gateway_stop:停止和销毁网关设备;

网关设备的销毁

用户可以通过以下接口销毁网关设备:

int linkkit_gateway_stop(int devid);

其中devid为网关设备的设备ID,该设备ID由调用linkkit_gateway_start()时返回。

注意:调用linkkit_gateway_stop()前需要销毁和解绑所有的子设备。

示例代码

网关的创建和初始化的示例代码如下所示:

static int gateway_get_property(char *in, char *out, int out_len, void *ctx)
{
    /*
     * 1.解析in,获取属性列表;
     * 2.根据属性列表获取属性列表中指定属性的值;
     * 3.创建属性-值列表并填充到out指针指定的buffer中;
     */
    return 0;
}

static int gateway_set_property(char *in, void *ctx)
{
    /*
     * 1.解析in,获取属性-值列表;
     * 2.根据属性-值列表设置网关对应的属性的值
     * 3.调用linkkit_gateway_post_property_json_sync()接口将修改后的属性-值
     */
    return 0;
}

static int gateway_call_service(char *identifier, char *in, char *out, int out_len, void *ctx){
{
    /*
     * 1.解析in,获取服务的输入参数;
     * 2.根据identifier识别云端调用的具体服务;
     * 3.调用服务;
     * 4.将服务调用的结果填充到out指针指定的buffer中;
     */
    return 0;
}

static linkkit_cbs_t alinkcbs = {
    .get_property = gateway_get_property,
    .set_property = gateway_set_property,
    .call_service = gateway_call_service,
};

int gateway_devid = linkkit_gateway_start(&alinkcbs, &user_data);

子设备的创建和管理

子设备的初始化

子设备的初始化流程如下所示:

  1. 子设备初次入网,需要绑定子设备到网关,创建网关和子设备的拓扑关系;
  2. 创建子设备,为子设备分配相关的资源,并添加到网关的子设备列表中。

子设备绑定到网关

网关设备和子设备在云端是单独创建产品,两者之间并没有联系,只有通过绑定操作,才能在网关设备和子设备间建立拓扑关系。用户通过以下接口将子设备绑定到网关,建立拓扑关系:

int linkkit_gateway_subdev_register(char *productKey, char *deviceName, char *deviceSecret);

其中productKey、deviceName和deviceSecret为子设备的设备标识三元组。linkkit_gateway_subdev_register()支持子设备的动态注册绑定和静态注册绑定,当使用动态注册绑定时,deviceSecret为NULL,由云端自行分配deviceSecret。

注意:设备只需要在第一次连接到网关时执行子设备绑定网关的操作,同时需要确保网关已经在线连接到阿里IoT云,因为设备初次绑定,需要验证子设备的合法性,确保子设备已经经过认证。子设备不能同时绑定到两个或两个以上的网关,子设备在绑定到另外一个网关时需要解除与原网关的绑定关系

子设备解除与网关的绑定

子设备离网且需要解除子设备与网关的绑定关系时,可以调用以下接口解除子设备和网关的绑定关系,从而允许子设备绑定到其他网关:

int linkkit_gateway_subdev_unregister(char *productKey, char *deviceName);

注意:该调用linkkit_gateway_subdev_unregister()前需要销毁productKey和deviceName对应的子设备。

创建子设备

用户通过以下接口创建子设备:

int linkkit_gateway_subdev_create(char *productKey, char *deviceName, linkkit_cbs_t *cbs, void *ctx);

子设备创建时需要提供子设备的productKey、deviceName和用于操作设备的回调函数。

在设备在创建的过程中也需要注册与网关设备类似的linkkit_cbs_t,用于传入操作子设备的三要素的函数指针。linkkit_gateway_subdev_create()在调用成功后返回子设备的设备ID,通过设备ID,用户可以调用以下接口对子设备进行操作和管理:

  1. linkkit_gateway_trigger_event_json_sync:上报网关设备相关的事件;
  2. linkkit_gateway_post_property_json_sync:上报网关设备相关的属性变更;
  3. linkkit_gateway_post_extinfos:上报网关设备相关的扩展信息;
  4. linkkit_gateway_delete_extinfos:删除网关设备的扩展信息;
  5. linkkit_gateway_get_devinfo:获取网关设备的设备信息和当前状态;
  6. linkkit_gateway_subdev_destroy:销毁子设备,释放子设备相关的资源;
  7. linkkit_gateway_subdev_login:子设备登录,云端显示对应的子设备在线;
  8. linkkit_gateway_subdev_logout:子设备登出,云端显示对应的子设备离线;

销毁子设备

可以调用以下接口销毁由linkkit_gateway_subdev_create()创建的子设备:

int linkkit_gateway_subdev_destroy(int devid);

该接口会释放子设备关联的各种连接资源和内存资源。

注意linkkit_gateway_subdev_destroy()只会销毁devid对应的子设备,不会解除子设备与网关的绑定关系,如果在销毁子设备后需要解除子设备与网关的绑定关系,可以调用linkkit_gateway_subdev_unregister()。如果子设备只是关机或重启等情况下临时断网,应该调用linkkit_gateway_subdev_logout()

子设备登入和登出

子设备登入

子设备创建后,子设备并不会处于在线状态,需要调用子设备的登入(login)接口,子设备才会处于在线状态,此时云端控制台也才会显示子设备处于在线状态。子设备登入接口声明如下:

int linkkit_gateway_subdev_login(int devid);

调用子设备login接口后,子设备会处于在线状态,云端控制态也会显示该子设备处于在线状态。子设备在线后可以调用linkkit_gateway_trigger_event_json_sync()linkkit_gateway_post_property_json_sync()等接口上报事件和属性,进行设备管理。

子设备登出

子设备在掉线后,需要调用子设备的登出接口,以提示云端控制台设备处于离线状态。子设备的登出接口声明如下:

int linkkit_gateway_subdev_logout(int devid);

调用子设备登出接口后,子设备会处于离线状态,云端控制态也会显示该子设备处于离线状态。

示例代码

static int light_get_property(char *in, char *out, int out_len, void *ctx)
{
    /*
     * 1.解析in,获取属性列表;
     * 2.根据属性列表获取属性列表中指定属性的值;
     * 3.创建属性-值列表并填充到out指针指定的buffer中;
     */
    return 0;
}

static int light_set_property(char *in, void *ctx)
{
    /*
     * 1.解析in,获取属性-值列表;
     * 2.根据属性-值列表设置网关对应的属性的值
     * 3.调用linkkit_gateway_post_property_json_sync()接口将修改后的属性-值
     */
    return 0;
}

static int light_call_service(char *identifier, char *in, char *out, int out_len, void *ctx)
{
    /*
     * 1.解析in,获取服务的输入参数;
     * 2.根据identifier识别云端调用的具体服务;
     * 3.调用服务;
     * 4.将服务调用的结果填充到out指针指定的buffer中;
     */
    return 0;
}

static linkkit_cbs_t light_cbs = {
    .get_property = light_get_property,
    .set_property = light_set_property,
    .call_service = light_call_service,
};

if (linkkit_gateway_subdev_register(PRODUCTKEY, DEVICENAME, DEVICESECRET) < 0)
    return -1;

int devid = linkkit_gateway_subdev_create(PRODUCTKEY, DEVICENAME, NULL, 0, &light_cbs, light);
if (devid < 0)
    return -1;

if (linkkit_gateway_subdev_login(light->devid) < 0)
    return -1;

事件上报

网关SDK除了支持云端调用设备的服务,也支持设备主动上报事件和属性变更。通过事件上报和属性变更上报,云端可以及时获取设备状态的变化,进而进行相应的处理。

事件的构成主要有两部分:

  1. 用于标识事件类型的identifier;
  2. 事件对应的的事件参数。

网关SDK事件上报的接口声明如下:

int linkkit_gateway_trigger_event_json_sync(int devid, char *identifier, char *events, int timeout_ms)

其中,devid为网关或子设备的设备ID,identifier为事件名,events为事件的具体参数,使用JSON格式的字符串表示,timeout_ms为事件上报的超时时间。当timeout_ms=0时,不等待云端确认。

事件上报的示例代码如下所示:

linkkit_gateway_trigger_event_json_sync(devid, "Error", "{\"ErrorCode\": 0}", 10000);

上述示例代码中事件的identifier为"Error",事件体为{"ErrorCode":0},超时时间为10秒。

属性上报

当设备的属性发生变化时,可以调用以下接口上报变更后的属性:

int linkkit_gateway_post_property_json_sync(int devid, char *props, int timeout_ms);

其中,devid为网关或子设备的设备ID,props为属性的名值对列表,使用JSON格式的字符串表示,timeout_ms为属性上报的超时时间。当timeout_ms=0时,不等待云端确认。

属性上报的示例代码如下所示:

linkkit_gateway_post_property_json_sync(devid, "{\"Temperature\": 35.8, \"Color\": \"Green\"}", 10000);

上述示例代码中,上报的属性有两个,分别是Temperature,对应的值为浮点数35.8,另外一个是Color,对应的值为字符串Green。属性上报的超时时间为10秒。

扩展信息上报

网关SDK支持扩展信息上报。扩展信息是用户自定义的键值对,用户可以通过云端控制台创建扩展信息,扩展信息的创建无需审核。在云端控制台创建好之后,用户可以调用以下接口上报扩展信息:

int linkkit_gateway_post_extinfos(int devid, linkkit_extinfo_t *extinfos, int nb_extinfos, int timeout_ms);

linkkit_gateway_post_extinfos()支持同时上报多个扩展信息键值对,键值对使用linkkit_extinfo_t表示,键值对的数目由nb_extinfos确定,timeout_ms为扩展信息上报的超时时间,以毫秒为单位。

linkkit_extinfo_t用于表示扩展信息的键值对,linkkit_extinfo_t的声明如下所示:

typedef struct {
    char *attrKey;    /* the key of extend info.   */
    char *attrValue;  /* the value of extend info. */
} linkkit_extinfo_t;

其中attrKey为扩展信息的键,attrValue为扩展信息的值。

扩展信息上报的示例代码如下所示:

linkkit_extinfo_t extinfos[3];
extinfos[0].attrKey = "Red";
extinfos[0].attrValue = "128";
extinfos[1].attrKey = "Green";
extinfos[1].attrValue = "64";
extinfos[2].attrKey = "Blue";
extinfos[2].attrValue = "96";

linkkit_gateway_post_extinfos(devid, extinfos, 3, 10000);

在上述示例代码中,创建了三个扩展信息键值对,并通过linkkit_gateway_post_extinfos()一次上报到云端,并设置超时时间为10秒,如果10秒内不能成功将扩展信息上报到云端,linkkit_gateway_post_extinfos()将返回-1。

获取设备列表

网关SDK内部维护着用户已经注册的设备列表,可以通过以下接口获取这个设备列表:

int linkkit_get_num_devices(void);
int linkkit_gateway_get_devinfos(linkkit_devinfo_t *devinfos, int nb_devinfos);

其中linkkit_get_num_devices()用于获取已经注册到SDK中的总设备个数,linkkit_gateway_get_devinfos()用户获取设备信息列表。linkkit_gateway_get_devinfos()包含两个参数,其中devinfos为linkkit_devinfo_t数组,用于保存获取到的设备信息列表,nb_devinfos代表devinfos数组的长度,linkkit_gateway_get_devinfos()返回值为实际返回的设备信息个数。

以下代码演示了如何获取并打印所有设备(包括网关)的设备信息:

int nb_devices = linkkit_get_num_devices();
if (nb_devices == 0)
    return 0;

linkkit_devinfo_t *devinfos = malloc(sizeof(linkkit_devinfo_t) * nb_devices);
if (!devinfos)
    return -1;

int total = linkkit_gateway_get_devinfos(devinfos, nb_devices);

int i;
for (i = 0; i < total; i++) {
    printf("device %s<%s>\n", devinfos[i].deviceName, devinfos[i].productKey);
}

free(devinfos);

网关SDK申请

请将网关SDK申请发送至邮箱:alibaba_iot@service.aliyun.com,邮件标题“【Living Link】网关SDK申请”,同时请在邮件中注明以下信息:

  1. 硬件平台关键参数,包括使用的CPU型号、CPU使用大端还是小端、RAM大小、Flash大小等;
  2. 使用的操作系统及版本信息;
  3. 网关使用的场景,接入的子设备数量;
  4. 网关和子设备间使用何种物理连接;

收到邮件后我们会在1-2个工作日内给您反馈。

results matching ""

    No results matching ""