一、概述
本文档用于指导北向应用开发者,通过物联云平台API接口,获取设备实时数据、设备告警数据、设备事件和服务调用实现业务应用接入开发。
应用接入主要分为设备实时数据查询、设备实时控制、设备告警数据接收。物联云平台通过提供Https Rest API和DDQ SDK用于设备数据查询、设备实时控制和设备实时数据推送、设备告警数据接收。开发者可根据本文档说明基于物联云平台进行北向应用对接。
二、数据流转过程示意图
数据流转过程说明:
1、设备上报数据到IoT平台;
2、数据通过规则引擎,存储在数据库;
3、数据通过DDQ 流转到企业的应用服务器;详见订阅设备数据(DDQ)章节
4、企业的应用系统调用IoT平台API,下发设备控制或配置指令;详见设备物模型接口章节;
三、API接口调用规范
3.1 API说明
物联云平台接口访问域名为api2.xlink.cn(如果是私有云,则需单独提供私有云访问地址)。
API网关是物联网平台面向开发者接口定义转发的服务,提供接口为Restful风格的HTTP接口。协议版本为HTTPS 1.1,HTTP 1.1(HTTPS暂不需要使用证书双向认证)。
物联云平台API接口为REST风格的HTTP接口,其资源访问路径结构为 协议://域名/API路径。
HTTP Request以及Response都采用JSON格式字符串作为消息实体。
注意:文件上传或其他流式数据传输另作讨论。
采用HTTP协议的GET、POST、PUT、DELETE Method作为资源操作动作。操作原则为:
- GET:进行数据获取;
- POST:数据添加。或者是复杂的数据查询,需要以Request Body作为查询参数的;
- PUT:数据修改;
- DELETE:数据删除。
3.2 请求鉴权
3.2.1 鉴权说明
HTTP请求API必须带上鉴权信息,平台通过鉴权信息来决定该请求能访问API范围以及数据范围。
鉴权信息放入HTTP请求的Header中,Header字段为:“Access-Token”。
3.2.2 权限类型
HTTP请求API必须带上鉴权信息,平台通过鉴权信息来决定该请求能访问API范围以及数据范围。
鉴权机制有两种,以具体的API以及业务为准
鉴权方式 | 适用范围 | 说明 |
---|---|---|
AccessToken机制 | Web/App端管理平台API | 鉴权信息存入HTTP Header字段为:“Access-Token” |
Signature机制 | SaaS服务云云对接 | 鉴权信息存入HTTP Header字段为:“Signature” |
3.2.3 鉴权信息类型
- 注意:Access-Token为API网关北向应用调用时所需要传递的参数,但是SaaS应用本身设计API接口时,无需加入Access-Token字段。
- 鉴权信息优先以AccessToken为准,即是在请求头发现Access-Token标识会进入Access-Token的鉴权逻辑,仅在请求头未发现Access-Token标识且发现Signature标识才会进入Signature鉴权逻辑。
- 针对AccessToken鉴权机制和Signature机制鉴权机制,请求所需传的鉴权信息主要分为一下三种类型,不同的类型所能访问的权限结构不同,具体类型说明如下:
序号 | 类型 | 鉴权机制 | 获取方式 | 权限概述 |
---|---|---|---|---|
1 | Corp | 见企业登录验证 | AccessToken机制 | 企业B端用户权限,主要用于管理台访问平台数据,包括不限于创建获取设备数据等 |
2 | User | 见用户登录验证 | AccessToken机制 | 企业C端用户权限,主要用于C端APP访问平台数据,包括不限于订阅设备、获取个人信息等 |
3 | App | 见应用网关登录验证 | Signature机制 | 应用网关权限,主要用于平台对接时,访问平台数据,可限制具体接口,包括不限于用户管理类数据、产品设备管理类数据等 |
3.3 请求头
- 根据不同的鉴权机制所需传输的请求头有所不同
3.3.1 AccessToken机制
字段 | 说明 | 是否必须 |
---|---|---|
Group | 前端API标识 | 是 |
Stage | 前端API所属环境, RELEASE:正式环境 TEST:测试环境 PRE_RELEASE:预发布环境 | 是 |
Content-Type | application/json | 是 |
Access-Token | 调用凭证,一般为用户登录后平台下发的用户Token | 否,以具体接口为准 |
Request-Base | API网关解析Token后加入到Header中的基础字段 | 否,请求通过API网关后自动加入 |
3.3.2 Signature机制
字段 | 说明 | 必填 |
---|---|---|
Group | 前端API标识 | true |
Stage | 前端API所属环境 RELEASE:正式环境 TEST:测试环境 PRE_RELEASE:预发布环境 |
true |
Content-Type | application/json | true |
Signature | 签名 | true |
App-ID | 应用标识 | true |
Timestamp | 时间戳 | true |
Request-ID | 请求标识 | true |
Content-MD5 | 请求内容MD5散列值 | false 当请求为POST和PUT请求时必传 |
3.5 通用返回结果
{
"code": 4081001,
"status": 408,
"msg": "Request Timeout",
"data": {
"name":"xlink"
}
}
字段 | 类型 | 描述 |
---|---|---|
code | Integer | 业务错误码,业务错误码由各个接口来具体定义。 |
status | Integer | 通用HTTP状态码,200表示执行成功。 其他表示错误或其他返回,看附录的HTTP状态码定义。 |
msg | String | 错误的详细信息描述。 |
data | Object | 具体的接口响应数据。 无实际响应数据时,该字段为空。 其他数据以具体接口为准。 |
3.6 错误码定义
3.6.1 HTTP状态码
值 | 描述 |
---|---|
200 | 请求成功 |
302 | 重定向跳转 |
400 | 请求字段不合法 |
401 | 缺少调用凭证 |
403 | 授权不通过 |
404 | 资源不存在 |
408 | 请求超时 |
405 | 方法不支持 |
502 | 响应超时 |
503 | 参数解析错误 |
504 | 服务器异常 |
3.6.2 业务错误码(示例)
- 业务错误码由各个接口来具体定义,以下为通用示例:
值 | 描述 |
---|---|
4001002 | 参数不能为空 |
4011001 | 请求头缺少Access-Token |
4031003 | 无效Access-Token |
4041001 | 接口找不到 |
4051001 | Http方法不允许 |
5021001 | 网关异常 |
5031002 | 服务降级 |
5041001 | 访问超时 |
四、 订阅设备数据(DDQ)
DDQ(Data Dispath Queue)数据调度队列是物联网平台面向外部数据调度分发的服务,是物联云平台通过队列的方式对外数据输出的方式,外部系统通过DDQ能以数据流的方式接收到物联网数据。
目前DDQ在传输协议上,采用http2协议。物联云平台提供了DDQ
SDK便于应用开发者进行开发和接入,能够实时接收物联网平台设备数据,数据包含设备在线状态变化数据、设备属性变化数据、设备事件上报数据、设备告警数据。
4.1 DDQ物模型数据格式说明
通过DDQ SDK,
可以接收设备在线状态变化数据、物模型属性变化数据、设备事件上报数据,设备告警数据。
下列说明具体的事件主题和数据格式。
4.1.1 设备在线状态变化
当设备在线状态发生变化时,如设备从在线变化为离线,DDQ
SDK会接收到如下格式的消息。
{
"topic": "/device-biz/state/product",
"message_id": "消息ID",
"payload": {
"device_id": "设备ID",
"is_online": "是否在线",
"product_id": "产品ID",
"corp_id": "企业ID",
"create_time": "时间,yyyy-MM-dd'T'HH:mm:ss.SS'Z'",
"project_id": "设备所属的项目ID"
}
}
字段说明
字段 | 必填 | 类型 | 备注 |
---|---|---|---|
product_id | True | String | 产品ID |
device_id | True | Int | 设备ID |
corp_id | False | Stirng | 企业ID |
create_time | False | String | 创建时间 |
project_id | False | String | 所属项目ID |
4.1.2 设备属性上报
当设备通过物模型属性上报数据时,DDQ SDK会接收到如下的消息内容。
{
"topic": "thing/event/{product_id}/device_attribute_sync_event",
"message_id": "消息ID",
"payload": {
"version": 1,
"time": "触发时间",
"type": "tml",
"event": "device_attribute_sync_event",
"thing_id": "设备ID",
"corp_id": "企业ID",
"product_id": "产品ID",
"attributes": [
{
"field": "mode",
"value": 1
}
]
}
}
字段说明:
字段 | 必填 | 类型 | 备注 |
---|---|---|---|
version | True | Int | 消息版本号 |
time | True | Long | 触发时间,毫秒时间戳 |
type | True | String | 消息类型,固定为:tml |
event | True | String | 固定为:device_attribute_sync_event |
thing_id | True | Int | 设备ID |
corp_id | False | String | 企业ID |
product_id | False | String | 产品ID |
thing_model_group | False | String | 品类分组 |
thing_model_category | False | String | 品类分类 |
thing_model_type | False | String | 品类类型 |
attributes | True | List<Object> | 物模型属性集合 |
attributes.field | True | String | 属性字段名 |
attributes.value | True | String | 物模型属性值 |
4.1.3 设备事件上报
当设备通过物模型上报事件时,DDQ SDK会接收到如下的消息内容。
{
"topic": "thing/event/{product_id}/event_report",
"message_id": "消息ID",
"payload": {
"version": 1,
"time": "触发时间",
"type": "tml",
"event": "....",
"thing_id": "设备ID",
"corp_id": "企业ID",
"product_id": "产品ID",
"events": [
{
"field": "mode",
"value": 1
}
]
}
}
字段 | 必填 | 类型 | 备注 |
---|---|---|---|
version | True | Int | 消息版本号 |
time | True | Long | 触发时间,毫秒时间戳 |
type | True | String | 消息类型,固定为:tml |
event | True | String | 事件名 |
thing_id | True | Int | 设备ID |
corp_id | False | String | 企业ID |
product_id | False | String | 产品ID |
thing_model_group | False | String | 品类分组 |
thing_model_category | False | String | 品类分类 |
thing_model_type | False | String | 品类类型 |
events | True | List<Object> | 事件信息集合 |
events.field | True | String | 事件字段名 |
events.value | True | String | 事件字段值 |
4.1.4 设备告警事件
当设备状态或者设备上报的物模型属性、事件满足管理台或者用户配置的告警条件时, DDQ SDK会接收到如下的消息内容。 {
"topic": "/device/alert/product/{product_id}",
"message_id": "消息ID",
"payload": {
"product_id": "产品ID",
"corp_id": "企业ID",
"device": {
"id": "设备ID",
"mac": "设备MAC"
},
"alert": {
"exception": "当前是否异常,否则为恢复",
"time": "异常/恢复时间, yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"rules": [
{
"id": "异常规则ID",
"status": " 异常状态,1报警2恢复3已手动处理",
"notify": "异常规则通知类型,1通知2告警",
"current": "当前值",
"count": "本次累计异常次数",
"new_time": "本次异常触发时间,yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"start_time": "本地异常开始时间,yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"recover_time": "本此异常恢复时间,yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"processed_time": "本此异常处理时间,yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"exception_describe": "异常描述",
"notification_content": "本次异常报警内容"
}
]
}
}
}
字段 | 必填 | 类型 | 备注 |
---|---|---|---|
product_id | True | String | 产品ID |
device.id | True | Int | 设备ID |
device.mac | False | Stirng | 设备MAC |
corp_id | True | String | 企业ID |
alert.exception | True | Boolean | 是否异常 |
alert.time | True | String | 异常/恢复时间 |
alert.rules | False | List<Object> | 相关规则列表 |
alert.rules.id | False | String | 规则ID |
alert.rules.status | False | Int | 异常状态 |
alert.rules.notify | False | Int | 通知类型 |
alert.rules.current | False | String | 当前置 |
alert.rules.count | False | Int | 累计异常次数 |
alert.rules.new_time | False | String | 异常触发最新时间 |
alert.rules.start_time | False | String | 异常触发开始时间 |
alert.rules.recover_time | False | String | 异常恢复时间 |
alert.rules.processed_time | False | String | 异常处理时间 |
alert.rules.exception_describe | False | String | 异常描述 |
alert.rules.notification_content | False | String | 异常内存 |
4.2 DDQ SDK关键参数说明
DDQ SDK初始化完成,发起连接时,需要提供用于连接和认证的必要参数,具体说明如下:
参数 | 参数说明 | 备注 |
---|---|---|
host | DDQ服务器访问地址(IP或域名) | 公有云为h2-ddq.xlink.cn |
port | DDQ服务访问端口 | 公有云为:80 |
authUrl | 客户端认证URL,客户端通过该url向DDQ服务器进行AppId、Token,成为有效客户端 | 公有云为: https://api2.xlink.cn |
appId | 应用ID | 通过管理台,应用中心->应用详情中查看 |
accessToken | 通过应用管理的AppId和AppSecret换取的AccessToken | 接口见下面获取AccessToken凭证说明 |
4.3 获取AccessToken凭证
AccessToken是用于DDQ
SDK连接服务端时的凭证。本接口可以根据应用中心创建的AppId和AppSecre换取AccessToken。接口说明如下:
请求
POST /v2/plugin/app_auth
Header
Content-Type:application/json
Content
{
"app_id": "应用网关标识",
"app_secret": "应用网关密钥"
}
响应
HTTP/\`\`1.1\` \`200\` \`OK
Content
{
"access_token": "平台授予应用网关token",
"refresh_token": "平台授予应用网关刷新token",
"expire_in": "平台授予应用网关token过期时间"
}
4.4 DDQ SDK使用示例代码
4.4.1 DDQ SDK初始化
private static void connect() throws Exception {
// 创建http2客户端,构造参数1为心跳间隔,参数2为判断超时时间,单位皆为毫秒
Http2Client http2Client = new Http2Client(1000, 15000);
//建立远程连接
Connection connection = http2Client.connect(socketAddress).get();
//设置接收数据回调
connection.setDefaultStreamListener(new Http2DataReceiver());
connection.setConnectionListener(new Http2ConnectionListener());
....
....
}
4.4.2 发起连接认证
private static void connect() throws Exception {
//建立连接
....
....
//客户端认证
Http2Headers headers = new DefaultHttp2Headers();
headers.path(authUrl).scheme("http").method("POST").authority("xlink.cn");
//构建request data
JSONObject body = new JSONObject();
body.put("app_id", appId);
body.put("access_token", accessToken);
body.put("auth_type", "accesskey");
byte[] data = body.toJSONString().getBytes();
//发起认证请求
connection.writeHeaders(headers, false, new Http2StreamListener() {
@Override
public void onDataRead(Connection connection, Http2Stream streamData, byte[]
data, boolean endOfStream) {
String dataString = new String(data);
JSONObject json = JSONObject.parseObject(dataString);
log.info("info {} ", json.toJSONString());
}
@Override
public void onHeadersRead(Connection connection, Http2Stream stream,
Http2Headers headers, boolean endOfStream) {
log.info("info {} ", connection.getStatus());
}
@Override
public void onStreamError(Connection connection, Http2Stream stream, IOException
e) {
log.info("info {} ", connection.getStatus());
}
}).get().writeData(data, true).get();
....
....
}
4.4.3 订阅数据
private static void connect() throws Exception{
//建立连接
....
....
//客户端认证
....
....
//订阅消息
Http2Headers headersSub = new DefaultHttp2Headers();
JSONObject subData = new JSONObject();
subData.put("topic", topic);
headersSub.path(subscribeUrl).scheme("http").method("POST").authority("xlink.cn");
//发起订阅
connection.writeHeaders(headersSub, false, new Http2StreamListener() {
@Override
public void onDataRead(Connection connection, Http2Stream stream, byte[] data,
boolean endOfStream) {
String dataString = new String(data);
JSONObject json = JSONObject.parseObject(dataString);
log.info("info {} ", json.toJSONString());
}
@Override
public void onHeadersRead(Connection connection, Http2Stream stream,
Http2Headers headers, boolean endOfStream) {
log.info("info {} ", connection.getStatus());
}
@Override
public void onStreamError(Connection connection, Http2Stream stream, IOException
e) {
log.info("info {} ", connection.getStatus());
}
}).get().writeData(subData.toJSONString().getBytes(), true).get();
}
4.4.4 接收数据回调
/**
* 接收数据处理
*/
public static class Http2DataReceiver extends AbstractHttp2StreamDataReceiver {
@Override
public void onDataRead(Connection connection, Http2Stream stream, StreamData
streamData) {
Http2Response response = new Http2Response(streamData.getHeaders(),
streamData.readAllData());
//接收到的数据
String content = new String(response.getContent());
log.info("receiver data {}", content);
JSONObject json = JSONObject.parseObject(content);
//...
String topic = json.getString("topic");
String msgId = json.getString("msgId");
JSONObject payload = json.getJSONObject("payload");
}
@Override
public void onStreamError(Connection connection, Http2Stream stream, IOException
e) {
}
}
4.4.5 连接状态监听
/**
* 连接状态监听
*/
public static class Http2ConnectionListener implements ConnectionListener {
@Override
public void onSettingReceive(Connection connection, Http2Settings settings) {
}
@Override
public void onStatusChange(ConnectionStatus status, Connection connection) {
//连接断开
if (status == ConnectionStatus.CLOSED){
log.error("http/2 connection disconnected. ");
//当前连接断开时,进行重连
connection.close();
//开启重连线程
new ReconnectThread().start();
}
}
}
4.4.6 当连接断开时,定时重连
/**
* 重连线程
*/
private static class ReconnectThread extends Thread{
@Override
public void run() {
for(;;){
try {
//每5秒进行重连
Thread.sleep(5000L);
connect();
//没有异常说明连接成功,跳出定时重连
break;
}catch (Exception e){
log.error("", e);
}
}
}
}