01 概述
NB-IoT
NB-IoT(Narrow Band Internet of Things)是一种基于蜂窝的窄带物联网技术,支持低功耗设备在低功耗广域物联(LPWAN)进行蜂窝数据连接。
LWM2M协议
LwM2M的全称是Lightweight Machine-To-Machine,是由OMA(Open Mobile Alliance)组织制定的轻量化协议,主要面向基于蜂窝的窄带物联网(NB-IoT)场景下物联网应用。
- 基本架构和接口
1.1 逻辑实体
- LWM2M Server(接入机):平台服务器接口;
- LWM2M client(客户端):负责执行服务器 的命令和上报执行结果;
- Bootstrap server(引导机):负责 配置 LWM2M 客户端。
1.2 逻辑接口 - Bootstrap(引导接口):向LwM2M客户端提供注册到LwM2M服务器的必要信息;
- Client Registration(客户端注册接口):使LwM2M客户端与LwM2M服务器互联,将LwM2M客户端的相关信息存储在LwM2M服务器上;
- Device Management and Service Enablement(设备管理与服务实现接口): 主控方为LwM2M服务器,服务器向客户端发送指令,客户端对指令做出回应并将回应消息发送给服务器;
- Information Reporting(信息上报接口):允许LwM2M服务器端向客户端订阅资源信息,客户端接收订阅后按照约定的模式向服务器端报告自己的资源变化情况。
- 特点
- 基于REST架构;
- 消息传递是通过CoAP协议来实现的;
- 定义了一个紧凑高效又不乏扩展性的数据模型。
- 对象和资源
LwM2M协议定义了一个以资源(Resource)为基本单位的模型,每个资源都可以携带数值,可以指向地址,以表示LwM2M客户端中每一项可用的信息。而资源都存在于对象实例中(Object Instance),即对象(Object)的实例化。
LwM2M协议预定义了8种对象(Object)来满足基本的需求,分别是:
Object | Object ID |
---|---|
Security(安全对象) | 0 |
Server(服务器对象) | 1 |
Access Control(访问控制对象) | 2 |
Device(设备对象) | 3 |
Connectivity Monitoring(连通性监控对象) | 4 |
Firmware(固件对象) | 5 |
Location(位置对象) | 6 |
Connectivity Statistics(连通性统计对象) | 7 |
在客户端向平台注册的阶段,LwM2M客户端把携带了资源信息的对象实例传递给LwM2M服务器,以通知服务器自身设备所具备的能力。
OneNET平台
OneNET是中国移动的IoT物联网平台,提供多种协议接入和应用。其中移动 OneNET 平台的NB-IoT就是采用基于上述 NB-IOT 的 LWM2M 协议和 CoAP 协议实现 UE(User Equipment) 与平台的通信,其中 LWM2M 协议为应用层协议,CoAP 协议为传输层协议。
- OneNET LwM2M对象和资源
OneNET针对LwM2M协议提供丰富的对象(object)和属性(resource),对象和属性的定义基于OMA规范做了部分修改,具体可以查看OneNET LwM2M对象和属性。
-
Digital Input(数字输入对象)
object:3200
resource:id name type 5500 Digital Input State boolean 5501 Digital Input Counter integer 5502 Digital Input Polarity boolean 5503 Digital Input Debounce integer 5504 Digital Input Edge Selection integer 5750 Application Type string 5751 Sensor Type string -
Analog Input(模拟输入对象)
object:3202
resource:id name type 5600 Analog Input Current Value float 5601 Min Measured Value float 5602 Max Measured Value float 5603 Min Range Value float 5604 Max Range Value float 5750 Application Type string 5751 Sensor Type string
以上是列举的部分对象和资源,操作时需要注意资源的读写属性。
2. 设备接入流程
2.1 准备工作
- 物联网SIM卡
如果我们选择的物联网卡运营商为中国移动,那么就可以和OneNET平台无缝对接,如果是联通或电信,则需要开通相应限制。 - 实现了OneNET对应接口固件的MCU
- 移植好OneNET平台SDK的NB通信模组
2.2 设备接入步骤 - OneNET平台创建产品和设备
- 设备软硬件初始化
- 创建设备和资源
- 注册到OneNET平台
- 平台完成订阅和发现设备资源
02 实现
硬件设计
- BC26模块设计
这个大家可以参考官方的《硬件设计手册》,下面列举一般的应用电路。
- 供电电路
在靠近VBAT输入端增加一个TVS 管以提高模块的浪涌电压承受能力。
- 串口通信电路
因为模块的串口电压域为1.8V,所以应用到非1.8V的系统,需要进行电平转换,可以用专门的电平转换芯片,也可以自己搭转换电路。
- 开机电路
- 复位电路
- 网络状态指示电路
- USIM卡电路
在外部USIM 卡座的引脚增加TVS 管,确保有良好的ESD防护性能。
- 天线电路
默认情况下,C1、C2 不贴,只在R1 贴0 欧姆电阻。
2. 整体硬件设计
供电后,接入MCU的UART和POWER_EN、PWRKEY、RESET、PSM EINT的控制信号即可对模块进行操控。
固件设计
BC26模块配置
- AT指令收发
/** 根据AT指令手册枚举指令表*/
typedef enum
{
/** product info*/
AT_CMD,
ATI_CMD,
AT_CGMI_CMD,
AT_CGMM_CMD,
AT_CGMR_CMD,
AT_CGSN_Q_CMD,
AT_CCLK_Q_CMD,
/** ...*/
/** oneNET*/
AT_MIPLCONFIG_CMD,
AT_MIPLCREATE_CMD,
AT_MIPLDELETE_CMD,
AT_MIPLVER_CMD,
AT_MIPLADDOBJ_CMD,
AT_MIPLDELOBJ_CMD,
AT_MIPLRD_CMD,
AT_MIPLOPEN_CMD,
AT_MIPLCLOSE_CMD,
AT_MIPLDISCOVERRSP_CMD,
AT_MIPLOBSERVERSP_CMD,
AT_MIPLREADRSP_CMD,
AT_MIPLREAD_CMD,
AT_MIPLWRITERSP_CMD,
AT_MIPLWRITE_CMD,
AT_MIPLEXECUTERSP_CMD,
AT_MIPLPAPAMETERRSP_CMD,
AT_MIPLNOTIFY_CMD,
AT_MIPLUPDATE_CMD,
} AT_CMT_T;
/** AT指令发送*/
/*!
* @brief Send AT command
*
* @param cmdType
*
* @param cmdBuffer
*
* @retval error
*
*/
int BC26_ATCmdSend(uint8_t cmdType, AT_CMD_BUF_T cmdBuffer)
{
char cmdStr[100];
char lineEnd[] = "\r\n";
switch(cmdType)
{
case AT_CMD:
sprintf(cmdStr, "AT%s", lineEnd);
break;
case ATI_CMD:
sprintf(cmdStr, "ATI%s", lineEnd);
break;
case AT_CIMI_CMD:
sprintf(cmdStr, "AT+CIMI%s", lineEnd);
break;
case AT_CGSN_Q_CMD:
sprintf(cmdStr, "AT+CGSN=%d%s", cmdBuffer.Int[0], lineEnd);
break;
/** ...*/
case AT_MIPLWRITERSP_CMD:
sprintf(cmdStr, "AT+MIPLWRITERSP=%d,%d,%d%s",cmdBuffer.Int[0],cmdBuffer.Int[1],cmdBuffer.Int[2],lineEnd);
printf(">> %s\r\n",cmdStr);
break;
default:
return 1;
}
LPUART1_SendStr(cmdStr);
return 0;
}
/** AT指令接收*/
/*!
* @brief Get AT ack
*
* @param cmdType
*
* @param timeout
*
* @param waitTime
*
* @retval None
*
*/
void BC26_ATAckGet(uint8_t cmdType, uint16_t timeout, uint16_t waitTime)
{
char *atAckStr;
char atAckStr1[100];
char atAckStr2[20];
int atAckNum[11];
float atAckFltNum[10];
uint8_t timeoutFlag = 0;
switch(cmdType)
{
case AT_CMD:
Delay_ms(waitTime);
while(timeout)
{
atAckStr = strstr((const char*)rxBufLPUART1, (const char*)"OK");
if(atAckStr == NULL)
{
Delay_ms(1);
timeout--;
if(timeout == 0)
{
timeoutFlag = 1;
}
}
else
{
printf("<< %s",atAckStr);
break;
}
}
break;
case ATI_CMD:
Delay_ms(waitTime);
while(timeout)
{
atAckStr = strstr((const char*)rxBufLPUART1, (const char*)"Quectel_Ltd");
sscanf(atAckStr, "%*[^\n]\n%[^\r]%*[^:]:%[^\r]", atAckStr1,atAckStr2);
if(atAckStr == NULL)
{
Delay_ms(1);
timeout--;
if(timeout == 0)
{
timeoutFlag = 1;
}
}
else
{
printf("<< ET:%s\r\n",atAckStr1);
printf("<< Rev:%s\r\n",atAckStr2);
break;
}
}
break;
/** ...*/
case AT_MIPLWRITERSP_CMD:
Delay_ms(waitTime);
while(timeout)
{
atAckStr = strstr((const char*)rxBufLPUART1, (const char*)"OK");
if(atAckStr == NULL)
{
Delay_ms(1);
timeout--;
if(timeout == 0)
{
timeoutFlag = 1;
}
}
else
{
printf("<< %s",atAckStr);
break;
}
}
break;
default:
break;
}
if(timeoutFlag)
{
g_errorFlag = ERR_TIMEOUT;
}
memset(rxBufLPUART1,0,LPUART1_REV_LEN);
rxStaLPUART1.USART_RX_STA_B.VALID_LENGHT = 0;
timeoutFlag = 0;
}
- BC26模块初始化
/*!
* @brief Init BC26 module
*
* @param None
*
* @retval error
*
*/
int BC26_Init(void)
{
AT_CMD_BUF_T cmdBuffer;
/** 自动匹配波特率*/
printf("Auto match boudrate\r\n");
Delay_ms(200);
cmdBuffer.Int[0] = 0;
BC26_ATCmdSend(AT_CMD,cmdBuffer);
BC26_ATAckGet(AT_CMD,100,100);
BC26_ATCmdSend(AT_CMD,cmdBuffer);
BC26_ATAckGet(AT_CMD,100,100);
BC26_ATCmdSend(AT_CMD,cmdBuffer);
BC26_ATAckGet(AT_CMD,100,100);
if(BC26_ErrorHandler())
{
return 1;
}
BC26_ATCmdSend(ATI_CMD,cmdBuffer);
BC26_ATAckGet(ATI_CMD,500,500);
BC26_ATCmdSend(AT_CIMI_CMD,cmdBuffer);
BC26_ATAckGet(AT_CIMI_CMD,200,500);
cmdBuffer.Int[0] = CGSN_IMEI;
BC26_ATCmdSend(AT_CGSN_Q_CMD,cmdBuffer);
BC26_ATAckGet(AT_CGSN_Q_CMD,200,500);
BC26_ATCmdSend(AT_CGATT_CMD,cmdBuffer);
BC26_ATAckGet(AT_CGATT_CMD,200,500);
BC26_ATCmdSend(AT_QBAND_CMD,cmdBuffer);
BC26_ATAckGet(AT_QBAND_CMD,200,500);
BC26_ATCmdSend(AT_CSQ_CMD,cmdBuffer);
BC26_ATAckGet(AT_CSQ_CMD,200,500);
BC26_ATCmdSend(AT_CEREG_Q_CMD,cmdBuffer);
BC26_ATAckGet(AT_CEREG_Q_CMD,200,500);
cmdBuffer.Int[0] = 1;
BC26_ATCmdSend(AT_CGPADDR_S_CMD,cmdBuffer);
BC26_ATAckGet(AT_CGPADDR_S_CMD,200,500);
if(BC26_ErrorHandler())
{
return 1;
}
return 0;
}
OneNET平台接口设计
OneNET平台支持LWM2M和IPSO定义的资源模型,用户需根据传感器类型从这两种标准中选择适合的资源模型。资源模型为Object / Instance / Resource三层结构。
- Object(对象):表示某类传感器类型。
- Instance(实例):同一类传感器的数量。
- Resource(属性):传感器某些特性描述。
设备在应用程序中创建完设备(dev)后,还需要创建object以及对应的instance和resource。
- 创建对象和资源
可以将原本的AI、DI对象的资源项用于自身项目的资源。
/*!
* @brief oneNET平台对象属性和燃气表数据的对照表
* 对象 属性名 属性类型 操作类型 燃气表数据
* AI(3202) AICV(5600) float R standardConditionFlow
* AI(3202) MinMV(5601) float R NONE
* AI(3202) MamMV(5602) float R NONE
* AI(3202) MinRV(5603) float R NONE
* AI(3202) MaxRV(5604) float R NONE
* AI(3202) AT(5750) string W/R gasUnitPrice
* AI(3202) ST(5751) string R NONE
*
* DI(3200) State(5500) bool R NONE
* DI(3200) Counter(5501) int R gasMeterStatus
* DI(3200) Polarity(5502) bool W/R airValCTRL
* DI(3200) Debounce(5503) int W/R gasSerialNum
* DI(3200) ES(5504) int W/R gasProductID
* DI(3200) AT(5750) string W/R gasMoney
* DI(3200) ST(5751) string R NONE
*/
- 平台注册操作
以下是设备注册到注销注册的通信过程。
AT+MIPLCREATE
+MIPLCREATE: 0 //成功创建通信套件实例。
OK
AT+MIPLADDOBJ=0,3200,"1",1,7,0 //添加LwM2M 对象。
OK //成功添加对象,且将注册ID 为0 的实例。
AT+MIPLOPEN=0,100 //向OneNET 发送注册请求。
OK
+MIPLEVENT: 0,1 //开始连接到Bootstrap 服务器。
+MIPLEVENT: 0,2 //成功连接到Bootstrap 服务器。
+MIPLEVENT: 0,4 //成功连接到OneNET 平台。
+MIPLEVENT: 0,6 //成功注册到OneNET 平台。
+MIPLOBSERVE: 0,69234,1,3200,0,-1 //接收到订阅请求(3311/0)。
AT+MIPLOBSERVERSP=0,69234,1 //响应订阅请求,其结果码为1。
OK
+MIPLDISCOVER: 0,26384,3200 //接收到发现资源请求。
AT+MIPLDISCOVERRSP=0,26384,1,34,"5500;5501;5502;5503;5504;5750;5751" //使用资源ID 列表响应发现资源请求。
OK
AT+MIPLDELOBJ=0,3200 //删除LwM2M 对象。
OK
AT+MIPLCLOSE=0 //向OneNET 平台发送注销请求。
OK
+MIPLEVENT: 0,15 //成功注销。
AT+MIPLDELETE=0
OK //成功删除通信套件实例。
- 数据上报
注意MsgID号要和注册时下发的一致。
AT+MIPLNOTIFY=0,69234,3200,0,5501,3,4,29416122,0,0 //上报资源数据。
OK
- 读资源响应
+MIPLREAD: 0,3123,3200,0,5501 //接收到读取资源请求。
AT+MIPLREADRSP=0,3123,1,3299,0,5501,3,4,29416122,0,0 /响应读取请求。
OK //成功发送数据29416122到应用服务器。
- 写资源响应
+MIPLWRITE: 0,38017,3200,0,5750,2,5,3132333435,0,0 //接收到写入资源请求,写入12345。
AT+MIPLWRITERSP=0,38017,2 //响应写入请求,其结果码为2。
OK
OneNET平台配置
平台端创建的设备是真实设备在平台端的映射,用户可以通过平台端的“虚拟设备”对真实设备进行信息查询、命令下发、数据流管理、添加触发器等操作。
- 创建产品
进入OneNET平台控制台后,点击”添加产品”按钮。在弹出页面中按照提示填写产品的基本信息,进行产品创建。
联网方式:NB-IoT;
设备接入协议选择:LWM2M;
操作系统选择:无;
网络运营商选择:移动。
这里以用移动物联网sim卡为例,最后点击确定即可完成产品的创建。
- 添加设备
完成产品的创建后,点击“设备列表”,然后选择“添加设备”。填入IMEI、IMSI等信息,并开启“自动订阅”。如果开启自动订阅功能,平台会在设备注册成功后,下发指令获取设备的资源信息。
-
设备硬件初始化
设备在上电以后,需要完成设备的软硬件初始化。硬件初始化包括各个硬件模块的上电、处理器的寄存器初始化、工作模式初始化等等。软件初始化包括基础通信套件初始化、应用接口初始化、应用程序初始化等等。 -
设备对象和资源的创建
如上述创建对象和资源所述。 -
设备注册到平台
设备的基础通信套件完成初始化以及设备资源创建完成后,可向OneNET平台上报注册请求的注册码,服务器在收到登录请求的注册码后,会验证注册码中的参数,返回注册结果。若设备注册成功,则产品的“在线状态”会变为“在线”标识。
- 平台订阅
开启“自动订阅”功能后,设备注册到平台后,OneNET平台会向设备下发Observer消息。设备收到这条消息后,需要相应响应平台。
- 发现设备资源
平台侧默认不开启自动发现设备资源,需要用户手动“更新实例”。点击更新后,平台和向设备下发Discover消息。设备收到这条消息后,需要相应响应平台。
若要平台自动发现资源,则需要在“设备列表”中开启“自动发现资源”选项。
下图是已经更新好资源的设备。
03 应用
小程序应用设计
平台数据推送
平台以HTTPS POST请求形式向第三方应用平台注册地址推送数据,推送数据相关信息以JSON串的形式置于HTTPS请求中的body部分。
- 消息
消息有两种消息类型,分别为数据点消息和设备上下线消息。而数据点消息又分为单条数据点消息和数据点消息批量形式,数据点消息批量形式中的不同数据点是以不同时间戳区分的。
//数据点消息
{
"errno": 0,
"data": {
"update_at": "2022-01-27 12:35:43",
"id": "3200_0_5504",
"create_time": "2022-01-17 14:11:24",
"current_value": 102304069
},
"error": "succ"
}
//数据流消息
{
"errno":0,
"data":{
"cursor":"25971_564280_1448961152173",
"count":5,
"datastreams":[
{
"datapoints":[
{
"at":"2022-03-01 17:10:24.981",
"value":"35"
},
{
"at":"2022-03-01 17:10:53.406",
"value":"38"
},
...
],
"id":"3200_0_5501"
},
...
]
},
"error":"succ"
}
- 消息相关字段说明
相关字段可以查询OneNET平台文档,这里不赘述。
API接口
这里只列举读写资源相关的API,详细的API介绍,大家可以查看官方文档。
- 平台相关API
-
查询设备信息
项目 描述 HTTP方法 GET URL https://api.heclouds.com/devices/<device_id> HTTP头部 api-key:xxxx-ffff-zzzzz,必须为可查看该设备的Key
Content-Type:application/json请求返回 {
“errno”: 0,
“data”: {
“area”: “3”,
“private”: false,
“create_time”: “2022-01-09 17:40:53”,
“act_time”: “2022-01-09 17:42:52”,
“obsv”: true,
“auth_info”: {
“863409058100099”: “460044813808442”
},
“last_ct”: “2022-01-10 21:57:58”,
“imsi”: “460044813808442”,
“title”: “GeehyGasMeter02”,
“tags”: [],
“manufacturer”: “移远通信”,
“obsv_st”: false,
“protocol”: “LWM2M”,
“rg_id”: “863409058100099”,
“imsi_old”: [
“460044813808442”
],
“online”: false,
“location”: {
“lat”: 0,
“lon”: 0
},
“id”: “878229973”,
“datastreams”: [
{
“create_time”: “2022-01-09 17:43:40”,
“uuid”: “5cad4abc-c25a-46bd-b0e7-7f0fe7c843dd”,
“id”: “3303_0_5700”
},
{
“create_time”: “2022-01-09 20:21:40”,
“uuid”: “b8a891af-c714-415e-ac83-0d8b83c06f82”,
“id”: “3200_0_5501”
},
{
“create_time”: “2022-01-09 20:23:11”,
“uuid”: “4d2d2f9a-07fb-4555-b609-ef04258e3396”,
“id”: “3200_0_5500”
},
{
“create_time”: “2022-01-09 20:45:05”,
“uuid”: “c26488b6-c394-4507-8fb9-23e207c7f94b”,
“id”: “3202_0_5600”
},
{
“create_time”: “2022-01-09 20:45:14”,
“uuid”: “85d877f4-40b0-48a4-9bb2-77f40d6016ad”,
“id”: “3202_0_5601”
},
{
“create_time”: “2022-01-09 20:45:18”,
“uuid”: “b419c823-d7ad-4426-9b43-19eebf45dcd8”,
“id”: “3202_0_5602”
},
{
“create_time”: “2022-01-09 20:45:21”,
“uuid”: “0f0785c8-658c-4d58-90b4-7806606e1b41”,
“id”: “3202_0_5603”
},
{
“create_time”: “2022-01-09 20:45:23”,
“uuid”: “28c67b22-f680-48e6-b6b3-0f570f48fa69”,
“id”: “3202_0_5604”
}
],
“desc”: “”
},
“error”: “succ”
} -
读数据点
项目 描述 HTTP方法 GET URL https://api.heclouds.com/devices/<device_id>/datastreams/ HTTP头部 api-key:xxxx-ffff-zzzzz //必须为MasterKey HTTP参数 “resource_id”:3200_0_5504, //资源ID号,必填 HTTP响应响应消息内容 {
“errno”: 0,
“data”: {
“update_at”: “2022-01-27 12:35:43”,
“id”: “3200_0_5504”,
“create_time”: “2022-01-17 14:11:24”,
“current_value”: 102304069
},
“error”: “succ”
}说明 1、响应消息中errno表示错误码,error表示错误原因。 -
写数据点
项目 描述 HTTP方法 POST URL https://api.heclouds.com/devices/<device_id>/datapoints HTTP头部 api-key:xxxx-ffff-zzzzz //必须为MasterKey HTTP参数 {
“datastreams”: [{
“id”: “3200_0_5502”,
“datapoints”: [{
“at”: “2022-01-28T00:32:43”,
“value”: 1
}
]
}
]
}HTTP内容 {
“datastreams”: [{
“id”: “3200_0_5502”,
“datapoints”: [{
“at”: “2022-01-28T00:32:43”,
“value”: 1
}
]
}
]
}HTTP响应响应消息内容 {
“errno”: 0,
“error”: “succ”
}说明 1、响应消息中errno表示错误码,error表示错误原因。
- 设备相关API
-
即时命令 - 读设备数据点
项目 描述 HTTP方法 GET URL https://api.heclouds.com/nbiot HTTP头部 api-key:xxxx-ffff-zzzzz //必须为MasterKey HTTP参数 “imei”:121, // nbiot设备的身份码,必填
"obj_id":1212, //设备的object ID对应到平台模型中为数据流id,必填
"obj_inst_id": 1212, // nbiot设备object下具体一个instance的id ,对应到平台模型中数据点key值的一部分,选填
"res_id": 2122 ,// nbiot设备的资源id,选填HTTP响应响应消息内容 {
“errno”: 0,
“error”: “succ”,
“data”: [{
“obj_inst_id”:123,
“res”:[
{
“res_id”:1234,
“val”: Object //可为boolean、string、long、double类型数据
},
………
]
},
………
]
}说明 1、响应消息中errno表示错误码,error表示错误原因。 -
即时命令 - 写设备数据点
项目 描述 HTTP方法 POST URL https://api.heclouds.com/nbiot HTTP头部 api-key:xxxx-ffff-zzzzz //必须为MasterKey HTTP参数 “imei”:121, // nbiot设备的身份码,必填
"obj_id":1212, //设备的object ID对应到平台模型中为数据流id,必填
"obj_inst_id": 1212, // nbiot设备object下具体一个instance的id ,对应到平台模型中数据点key值的一部分,选填
"mode": 1HTTP内容 {
“data”:[
{
“res_id”:5502,
“val”:true
}
]
}HTTP响应响应消息内容 {
“errno”: 0,
“error”: “succ”
}说明 1、响应消息中errno表示错误码,error表示错误原因。 -
缓存命令 - 读设备数据点
项目 描述 HTTP方法 GET URL http://api.heclouds.com /nbiot/offline HTTP头部 api-key:xxxx-ffff-zzzzz //必须为MasterKey HTTP参数 “imei”:121, // nbiot设备的身份码,必填
"obj_id":1212, //设备的object ID对应到平台模型中为数据流id,必填
"obj_inst_id": 1212, // nbiot设备object下具体一个instance的id ,对应到平台模型中数据点key值的一部分,选填
"res_id": 2122 ,// nbiot设备的资源id,选填
“valid_time”: “2022-03-01T17:30:00”,//命令开始生效时间,必填且大于当前时间
“expired_time”: “2022-03-02T17:30:00”,//命令过期时间,必填且大于valid_time
“retry”:3 // [3-10]之间,表示失败重试次数(等待下一次设备update或者上线),必填HTTP响应响应消息内容 {
“errno”: 0,
“error”: “succ”,
“data”: {
“uuid”: “f195c6d1-7035-5747-a4ce-b6f6b09a2d73”
}
}说明 1、响应消息中errno表示错误码,error表示错误原因。 -
缓存命令 - 写设备数据点
项目 描述 HTTP方法 POST URL https://api.heclouds.com /nbiot/offline HTTP头部 api-key:xxxx-ffff-zzzzz,//必须为MasterKey
Content-Type:application/jsonHTTP参数 “imei”:121, // nbiot设备的身份码,必填
"obj_id":1212, //设备的object id , 对应到平台模型中为数据流id,必填
"obj_inst_id": 1212, // nbiot设备object下具体一个instance的id ,对应到平台模型
中数据点key值的一部分,必填
"mode": 1
“valid_time”: “2022-03-01T17:30:00”, //命令开始生效时间,必填大于当前时间
“expired_time”: “2022-03-02T17:30:00”,//命令过期时间,必填且大于valid_time
“retry”:3 //[3-10]之间,表示失败重试次数(等待下一次设备update或者上线),必填HTTP内容 {
“data”: [{
“res_id”:123,
“type”:1, //可选,目前只支持为1,此时数据为十六进制字符串
"val":Object //可为boolean、string、long、double
},
{…},
…
]
}HTTP响应响应消息内容 {
“errno”: 0,
“error”: “succ”,
“data”: {
“uuid”: “432a877d-53ab-5dbb-8c51-4853f65d94fb”
}
}说明 1、响应消息中errno表示错误码,error表示错误原因。
API测试工具
在开始小程序应用设计之前,我们需要用一些API测试工具来验证相关API接口是否正确。一般我们会用到“Fiddler”或者“ApiPost”这两款软件。
-
ApiPost使用方法
以读取平台数据点为例。新建API接口,选择HTTP方法为GET,填入读取平台数据点的URL。然后在HTTP头部填入api-key,最后点击”发送“按钮,即可获得平台的实时响应。
- Fiddler使用方法
以读取平台数据点为例。在“组合器”选项卡选择HTTP方法为GET,填入读取平台数据点的URL。然后在HTTP头部填入api-key,最后点击”执行“按钮。
等待一会即可在左侧的“查看器”看到响应。双击响应之后,可以查看JSON串等详细信息。
小程序的UI设计
这里只列举部分UI,以气度数为例。
- 在.js页面文件中定义并初始化数据变量。
Page({
/**
* 页面的初始数据
*/
data: {
device_info:{
flowQty:0,
serialNum:0,
productID:0,
stdFlow:0.0,
gasMoney:0.0,
gasUnitPrice:0.0,
gasMeterStatus:0,
gasOnlineStatus:0,
airValSta:false,
//气度数
stdFlowArr: [0,0,0,0,0,0,0,0],
deviceName:"str",
apiKey:"str",
deviceID:0,
deviceIMEI:0,
deviceIMSI:0,
deviceConnectStatus:false
},
},
}
- 设计好UI并将页面数据绑定。
<view class="bg-white margin-top">
<view class="flex flex-wrap solid-bottom padding-top padding-bottom align-center">
<view class="basis-xs padding-left">
<text class="text-cut">气度数:</text>
</view>
<view class="basis-lo justify-start text-center">
<view class="grid col-10 align-center">
<view class="bg-green-gs number-gs-f">
<text class="text-white">{{gas_meter_info.stdFlowArr[0]}}</text>
</view>
<view class="bg-green-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[1]}}</text>
</view>
<view class="bg-green-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[2]}}</text>
</view>
<view class="bg-green-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[3]}}</text>
</view>
<view class="bg-green-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[4]}}</text>
</view>
<view class="bg-green-gs number-gs">
<text class="text-white">.</text>
</view>
<view class="bg-orange-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[5]}}</text>
</view>
<view class="bg-orange-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[6]}}</text>
</view>
<view class="bg-orange-gs number-gs">
<text class="text-white">{{gas_meter_info.stdFlowArr[7]}}</text>
</view>
<view class="padding-left">
<text>m³</text>
</view>
</view>
</view>
</view>
</view>
小程序的API请求实现
- 基础信息定义
定义平台API和设备名称、接口等信息。
//onenet device info
var Test01 = {
deviceName:"Test 01",
apiKey:"XD=MV2btSD8Es022II=2hSKgM4=",
deviceID:897824175,
deviceIMEI:0,
deviceIMSI:0,
deviceConnectStatus:false,
}
const Host = "api.heclouds.com"
//get/post datapoints
const datapointsInfoURL = "https://api.heclouds.com/devices/"
const deviceInfoURL = "https://api.heclouds.com/devices/"
//get/post device resource cmd
const devResourceURL = "https://api.heclouds.com/nbiot?imei=" //在线
const devResOfflineURL = "http://api.heclouds.com/nbiot/offline?imei=" //离线
const stdFlowID = "3202_0_5600" //标况量
const moneyID = "3200_0_5750" //余额
- 获取设备信息
getDeviceInfo:function(e) {
var that = this
wx.request({
url: deviceInfoURL + Test01.deviceID,
header: {
"api-key": Test01.apiKey
},
data: {
},
method:"GET",
success(res) {
console.log("**********getDeviceInfo start***********")
console.log(res)
Test01.deviceIMEI = res.data.data.rg_id
Test01.deviceIMSI = res.data.data.imsi
if (res.data.data.online) {
Test01.deviceConnectStatus = true
that.setData({
"device_info[1].devOnlineStatus":1
})
console.log("online:%s",that.data.onlineStatus[that.data.device_info[1].gasOnlineStatus])
} else {
Test01.deviceConnectStatus = false
that.setData({
"device_info[1].gasOnlineStatus":0
})
console.log("offline:%s",that.data.onlineStatus[that.data.device_info[1].gasOnlineStatus])
}
that.data.device_info[1].deviceName = Test01.deviceName
that.data.device_info[1].apiKey = Test01.apiKey
that.data.device_info[1].deviceID = Test01.deviceID
that.data.device_info[1].deviceIMEI = Test01.deviceIMEI
that.data.device_info[1].deviceIMSI = Test01.deviceIMSI
that.data.device_info[1].deviceConnectStatus = Test01.deviceConnectStatus
},
fail(res) {
console.log("getDeviceInfo 请求失败")
app.showToast("请求数据失败")
Test01.deviceConnectStatus = false
that.data.device_info[1].deviceConnectStatus = false
},
complete() {
console.log("**********getDeviceInfo end***********")
}
})
}
- 读写平台数据
- 读取平台数据点
getPlatfromDatapoint:function(apiKey, deviceID, objID, objInsID, resID) {
var that = this
var getUrl = datapointsInfoURL + deviceID + "/datastreams/" + objID +'_' + objInsID + '_' + resID
return new Promise(function (resolve, reject) {
wx.request({
url: getUrl,
header: {
"Host":Host,
"api-key":apiKey
},
data: {
},
method:'GET',
success(res) {
//通过resolve返回请求的数据
resolve(res)
},
fail(res) {
console.log("getPlatfromDatapoint 请求失败")
},
complete() {
}
})
})
}
- 写平台数据点
//获取json时间戳
formatTimeStamp:function() {
var timeStamp = new Date()
var year = timeStamp.getFullYear()
var month = (timeStamp.getMonth() + 1).toString()
var day = timeStamp.getDate().toString()
var hour = timeStamp.getHours().toString()
var minute = timeStamp.getMinutes().toString()
var second = timeStamp.getSeconds().toString()
month = month.length < 2 ? '0' + month : month
day = day.length < 2 ? '0' + day : day
hour = hour.length < 2 ? '0' + hour : hour
minute = minute.length < 2 ? '0' + minute : minute
second = second.length < 2 ? '0' + second : second
timeStampJSON = year + '-' + month + '-' + day + 'T' + hour + ':' + minute + ':' + second
}
//写平台数据点
setPlatfromDatapoint:function(apiKey, deviceID, objID, objInsID, resID, value) {
var that = this
var datastreamsID = objID + '_' + objInsID + '_' + resID
var postUrl = datapointsInfoURL + deviceID + "/datapoints"
that.formatTimeStamp()
let data ={
"datastreams": [{
"id": datastreamsID,
"datapoints": [{
"at": timeStampJSON,
"value": value
}
]
}
]
}
return new Promise(function (resolve, reject) {
wx.request({
url: postUrl,
method:'POST',
header: {
"Host":Host,
"api-key":apiKey,
"content-type": 'application/json',
},
data: JSON.stringify(data),
success(res) {
//通过resolve返回请求的数据
resolve(res)
},
fail(res) {
console.log("setPlatfromDatapoint 请求失败")
},
complete() {
}
})
})
}
- 读写设备数据(缓存命令)
在设备离线状态下,我们可以调用缓存命令对设备进行操作。其原理是用户设置命令开始生效时间和命令过期时间,在此时间段内,OneNET平台对缓存命令进行缓存,当终端设备上线时,平台下发缓存的命令到终端设备。如果超过此有效时间段,平台将清处缓存命令缓存。
和“即时”读写设备的区别在于API中去掉缓存命令读写设备数据的offline及expired_time字段。大家感兴趣,可以查阅官方API手册实现。
- 离线获取设备数据点
//合成命令过期时间
expiredTimeStamp:function() {
var timeStamp = new Date()
var year = timeStamp.getFullYear()
var month = (timeStamp.getMonth() + 1).toString()
var day = (timeStamp.getDate() + 1).toString()
var hour = timeStamp.getHours().toString()
var minute = timeStamp.getMinutes().toString()
var second = timeStamp.getSeconds().toString()
month = month.length < 2 ? '0' + month : month
day = day.length < 2 ? '0' + day : day
hour = hour.length < 2 ? '0' + hour : hour
minute = minute.length < 2 ? '0' + minute : minute
second = second.length < 2 ? '0' + second : second
expiredTimeStamp = year + '-' + month + '-' + day + 'T' + hour + ':' + minute + ':' + second
},
//离线获取设备数据点
getDeviceDatapointOffline:function(apiKey, deviceID, objID, objInsID, resID) {
var that = this
var retry = 3
that.expiredTimeStamp()
var getUrl = devResOfflineURL + that.data.gas_meter_info.deviceIMEI + "&obj_id=" + objID + "&obj_inst_id=" + objInsID + "&res_id=" + resID + '&expired_time=' + expiredTimeStamp + '&retry' + retry
return new Promise(function (resolve, reject) {
wx.request({
url: getUrl,
header: {
"Host":Host,
"api-key":apiKey,
},
data: {
},
method:'GET',
success(res) {
resolve(res)
},
fail(res) {
console.log("getDeviceDatapointOffline 请求失败")
},
complete() {
}
})
})
},
- 离线写设备数据点
//离线写设备数据点
setDeviceDatapointOffline:function(apiKey, deviceID, objID, objInsID, resID, value) {
var that = this
var retry = 3
var mode = 2
let data ={
"data":[
{
"res_id":resID,
"val":value
}
]
}
that.expiredTimeStamp()
var postUrl = devResOfflineURL + that.data.gas_meter_info.deviceIMEI + "&obj_id=" + objID + "&obj_inst_id=" + objInsID + '&expired_time=' + expiredTimeStamp + '&retry' + retry + '&mode=' + mode
return new Promise(function (resolve, reject) {
wx.request({
url: postUrl,
method:'POST',
header: {
"Host":Host,
"api-key":apiKey,
"content-type": 'application/json',
},
data: JSON.stringify(data),
success(res) {
resolve(res)
},
fail(res) {
console.log("setDeviceDatapointOffline 请求失败")
},
complete() {
}
})
})
}