网关设备端开发指南
更新时间:2018-05-28 17:34:48
术语约定
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》。
开发流程
网关设备端开发主要包括以下步骤:
- 移植网关SDK的硬件抽象层(HAL)接口;
- 调用初始化接口对网关SDK进行初始化;
- 调用SDK接口创建网关设备,注册网关相关的回调函数并启动网关服务;
- 调用SDK接口创建子设备,注册子设备相关的回调函数;
HAL接口移植
网关SDK通过实现一个HAL层将操作系统和硬件平台相关的代码进行隔离,从而确保SDK的实现与操作系统和硬件平台无关,方便网关SDK移植到不同的操作系统和硬件平台。HAL层接口的移植和实现可以参考《使用未适配IoT SDK的平台开发智能灯》。
网关SDK初始化
网关SDK的初始化主要包括以下步骤:
- 获取默认参数;
- 修改默认参数;
- 设置事件回调;
- 调用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_data
的subdev_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);
int (*down_rawdata)(const void *in, int in_len, void *out, int out_len, void *ctx);
int (*post_rawdata_reply)(const void *data, int len, void *ctx);
} linkkit_cbs_t;
其中,云端获取设备的属性时会调用get_property
指定的函数,云端设置设备的属性时会调用set_property
指定的函数,云端调用设备的服务时会调用call_service
指定的函数。
linkkit_gateway_start()
调用成功会返回网关设备的设备ID,用户可以通过设备ID对设备进行管理。用户可以通过设备ID对网关设备进行以下操作:
linkkit_gateway_trigger_event_json_sync
:以同步的方式上报网关设备相关的事件;linkkit_gateway_post_property_json_sync
:上报网关设备相关的属性变更;linkkit_gateway_post_extinfos
:上报网关设备相关的扩展信息;linkkit_gateway_delete_extinfos
:删除网关设备的扩展信息;linkkit_gateway_get_devinfo
:获取网关设备的设备信息和当前状态;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);
子设备的创建和管理
子设备的初始化
子设备的初始化流程如下所示:
- 子设备初次入网,需要绑定子设备到网关,创建网关和子设备的拓扑关系;
- 创建子设备,为子设备分配相关的资源,并添加到网关的子设备列表中。
子设备绑定到网关
网关设备和子设备在云端是单独创建产品,两者之间并没有联系,只有通过绑定操作,才能在网关设备和子设备间建立拓扑关系。用户通过以下接口将子设备绑定到网关,建立拓扑关系:
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,用户可以调用以下接口对子设备进行操作和管理:
linkkit_gateway_trigger_event_json_sync
:上报网关设备相关的事件;linkkit_gateway_post_property_json_sync
:上报网关设备相关的属性变更;linkkit_gateway_post_extinfos
:上报网关设备相关的扩展信息;linkkit_gateway_delete_extinfos
:删除网关设备的扩展信息;linkkit_gateway_get_devinfo
:获取网关设备的设备信息和当前状态;linkkit_gateway_subdev_destroy
:销毁子设备,释放子设备相关的资源;linkkit_gateway_subdev_login
:子设备登录,云端显示对应的子设备在线;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除了支持云端调用设备的服务,也支持设备主动上报事件和属性变更。通过事件上报和属性变更上报,云端可以及时获取设备状态的变化,进而进行相应的处理。
事件的构成主要有两部分:
- 用于标识事件类型的identifier;
- 事件对应的的事件参数。
网关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。申请网关SDK,需提交以下信息:
- 硬件平台关键参数,包括使用的CPU类型、RAM大小、Flash大小等;
- 使用的操作系统类型;
- 使用的交叉编译工具链;