1 华为OceanConnect云平台配置
这里只涉及华为云平台部署以及北向开发。上面链接介绍了如何在旧版的华为云平台进行部署,也有专门的博客介绍新版的华为云平台如何部署。
2 基于QT接入华为OceanConnect云平台
上位机软件与提供的的平台profile是对应的,如果profile的关键参数不一致,需要同步修改软件代码!
2.1 软件介绍
OceanConnect云平台搭建好之后,可以使用模拟器进行发送数据的测试。但是我们必须通过登陆华为OceanConnect云后台,才看到数据,这样其实是不太友好的,因此采用QT平台结合华为发布的API文档来开发一个简单的PC软件。
本次用QT实现与华为OceanConnect云平台(以下简称华为云)的对接,主要包括
1、用户根据appid password登陆
2、自动查询当前应用下所有设备
3、定时向华为云查询最新数据
4、用户可以根据时间段查询云端的历史数据
2.2 效果预览
3 登陆云平台预备知识
待解决的问题
问题:用户联网登录
1.提供登录界面(客户端),输入用户名和密码;
2.将用户名和密码发送给服务器端,并进行相关查询,如果用户合法,返回用户信息;
3.对返回的用户信息进行解析。
假设:
已经有了服务器和存储着用户名和密码数据库。服务器当接收到客服端的请求的时候,先在数据库中进行查询操作,然后返回用户信息。所以要实现的只是客户端请求信息的配置和对返回信息的解析。
涉及的知识点
网络请求的基础知识
Json
需要解决的技术问题:
在qt中如何设置网络请求
在qt中如何生成和解析Json数据
3.1 Qt中与网络请求相关类介绍
QNetworkAccessManager
Allows the application to send network requests and receive replies
QNetworkRequest
Holds a request to be sent with QNetworkAccessManager
QNetworkReply
Contains the data and headers for a request sent with QNetworkAccessManager
QNetworkRequest设置网络请求的各个参数
QNetworkReply用于接收服务器返回的数据
QNetworkAccessManager充当着一个中介的作用,设置post、get等方式将request发出去,然后监测服务器是否返回数据(存在QNetworkReply中),并将返回得来的reply委托给一个slot处理。
当进行网络请求的时候,要做的事情:
3.2 QJson的生成与解析
JSON简介
JSON:JavaScript 对象表示法(JavaScript Object Notation)。
JSON 是存储和交换文本信息的语法。类似 XML。
JSON 比 xml 更小、更快,更易解析。
{
"employees": [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName":"Carter" }
]
}
employee 对象是包含 3 个员工记录(对象)的数组
什么是 JSON ?
* JSON 使用 JavaScript 语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。
JSON 语法规则
JSON 语法是 JavaScript 对象表示法语法的子集。
JSON 名称/值对
JSON 数据的书写格式是:名称/值对。
名称/值对包括字段名称(在双引号中),后面写一个冒号,然后是值:
"firstName" : "John"
这很容易理解,等价于这条 JavaScript 语句:
firstName = "John"
JSON 值
JSON 值可以是:
JSON 对象
JSON 对象在花括号中书写:
对象可以包含多个名称/值对:
{ "firstName":"John" , "lastName":"Doe" }
这一点也容易理解,与这条 JavaScript 语句等价:
firstName = "John"
lastName = "Doe"
JSON 数组
JSON 数组在方括号中书写:
数组可包含多个对象:
{
"employees": [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}
对象 "employees" 是包含三个对象的数组。每个对象代表一条关于某人(有姓和名)的记录。
JSON 使用 JavaScript 语法
因为 JSON 使用 JavaScript 语法,所以无需额外的软件就能处理 JavaScript 中的 JSON。
通过 JavaScript,您可以创建一个对象数组,并像这样进行赋值:
例子
var employees = [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName": "Carter" }
];
可以像这样访问 JavaScript 对象数组中的第一项:
employees[0].lastName;
返回的内容是:
Gates
可以像这样修改数据:
employees[0].lastName = "Jobs";
JSON 文件
无论是设置request所需的参数信息,还是返回来的reply,其数据均采用QJson的形式表示,所以要实现网络请求,QJson的生成和解析重要。
Qt中与JSON相关类介绍
QJsonDocument 类提供了读写JSON文档的方式, JSON文档 可以从它的基于文本的表示 使用QJsonDocument::fromJson()转换为QJsonDocument,用.toJSON()将其转换回文字。
QJsonArray封装了JSON中的数组。
QJsonObject封装了JSON中的对象。
QJsonValue封装了JSON中的值。
QJsonParseError 用于报告JSON解析中的错误类型。
基于QJsonObject的Json生成与解析
1.生成json
QJsonObject json;
json.insert("name", QString("Qt"));
json.insert("version", 5);
json.insert("windows", true);
QJsonDocument document;
document.setObject(json);
QByteArray byte_array = document.toJson(QJsonDocument::Compact);
QString json_str(byte_array);
结果
json_str为: {"name": "Qt","version": 5,"windows": true}
2.解析Json
QJsonParseError json_error;
QJsonDocument parse_doucment = QJsonDocument::fromJson(byte_array, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
if(parse_doucment.isObject())
{
QJsonObject obj = parse_doucment.object();
if(obj.contains("name"))
{
QJsonValue name_value = obj.take("name");
if(name_value.isString())
{
QString name = name_value.toString();
}
}
if(obj.contains("version"))
{
QJsonValue version_value = obj.take("version");
if(version_value.isDouble())
{
int version = version_value.toVariant().toInt();
}
}
if(obj.contains("windows"))
{
QJsonValue version_value = obj.take("windows");
if(version_value.isBool())
{
bool flag = version_value.toBool();
}
}
}
}
结果:
name:Qt
version:5
windows:true
基于QJsonArray的的Json生成与解析
1.生成json
QJsonArray json;
json.insert(0, QString("Qt"));
json.insert(1, QString("version"));
json.insert(2, true);
QJsonDocument document;
document.setArray(json);
QByteArray byte_array = document.toJson(QJsonDocument::Compact);
QString json_str(byte_array);
结果
json_str:["Qt","version",true]
2.解析json
QJsonParseError json_error;
QJsonDocument parse_doucment = QJsonDocument::fromJson(byte_array, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
if(parse_doucment.isArray())
{
QJsonArray array = parse_doucment.array();
int size = array.size();
for(int i=0; i
结果
数组不同下标对应的值
0:Qt
1:version
2:true
4.login.h / login.cpp / login.ui
login.ui
四个标签,四个文本框,两个按钮
login.h
#ifndef LOGIN_H
#define LOGIN_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Ui {
class login;
}
class login : public QDialog
{
Q_OBJECT
public:
explicit login(QWidget *parent = nullptr);
~login();
private slots:
//登陆
void on_pushButton_login_clicked();
//载入
void on_pushButton_clicked();
//当鉴权成功后会发送给登陆一个信号,然后再登陆中,绑定此信号到onSignLogin槽函数
void onSignLogin(int codeNum);
private:
Ui::login *ui;
};
#endif // LOGIN_H
login.cpp
#include "login.h"
#include "ui_login.h"
#include "datacomponent.h"
extern DataComponent g_DC;
login::login(QWidget *parent) :
QDialog(parent),
ui(new Ui::login)
{
ui->setupUi(this);
connect(&g_DC, SIGNAL(signMsgToLogin(int)), this, SLOT(onSignLogin(int)));
}
login::~login()
{
delete ui;
}
//载入文件
void login::on_pushButton_clicked()
{
QFile file;
QByteArray configData;//用于保存json文档
QString path = "./Config.json";
file.setFileName(path);
file.open(QIODevice::ReadOnly);
configData = file.readAll();
//解析json
QJsonParseError json_error;
QJsonDocument jsonDoc(QJsonDocument::fromJson(configData, &json_error));
if(json_error.error != QJsonParseError::NoError)
{
qDebug() << "json data is error!";
return;
}
QJsonObject rootObj = jsonDoc.object();
if(rootObj.contains("IP") && rootObj.contains("Port") && rootObj.contains("AppId") && rootObj.contains("Password"))
{
ui->lineEdit_IP->setText(rootObj.value("IP").toString());
ui->lineEdit_Port->setText(rootObj.value("Port").toString());
ui->lineEdit_AppId->setText(rootObj.value("AppId").toString());
ui->lineEdit_Password->setText(rootObj.value("Password").toString());
}
else
{
QMessageBox::information(nullptr, "Title", "输入数据错误", QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
}
}
//登陆 完成网络请求的初始化和鉴权工作
void login::on_pushButton_login_clicked()
{
UConfig cfg;
cfg.ip = ui->lineEdit_IP->text();
cfg.port = ui->lineEdit_Port->text();
cfg.appid = ui->lineEdit_AppId->text();
cfg.password = ui->lineEdit_Password->text();
g_DC.init(cfg);
g_DC.auth();
}
//隐藏模态对话框并将结果代码设置为Accepted
void login::onSignLogin(int codeNum)
{
if(SIGN_CODE_AUTH_SUCCESS == codeNum)
{
qDebug() << "认证成功";
accept();//用户操作成功
}
else
{
qDebug() << "认证失败";
}
}
5.datacomponent.h / datacomponent.cpp
UConfig 登陆相关的结构体
typedef struct config_s
{
QString ip;
QString port;
QString appid;
QString password;
}UConfig;
UAuthConfig 鉴权相关结构体
保存鉴权相关返回值的结构体
应用安全接入
Application携带在IoT Platform产生的AppId ID和密钥,调用鉴权接口,获取鉴权token
Auth(鉴权)
接口功能
实现第三方系统在访问开放API之前的认证
调用方法
post
接口路径
https://server:port/iocm/app/sec/v1.1.0/login
注意事项:
鉴权接口是调用其他API的前提,北向接口除了鉴权接口(Auth),其他接口调用都需要在request header
中携带参数app_key和Authorization:Bearer {accessToken}。
app_key为参数中的appId, Authorization中{accessToken}的值即为调用Auth接口获取到的accessToken。
如果多次获取令牌,则之前的令牌失效, 最后一次获取的令牌才有效。请勿并发获取令牌
当用户登录的时候,生成access_token和refresh_token,并返回给APP。
当access_token失效时,APP使用refresh_token来请求刷新token。
如果refresh_token过期,需要用户重新登录,。也就是说,用户每一次登陆的时候refresh_token都会重新更改,
accessToken;鉴权参数,用于其他接口的Authorization头域中
refreshToken;鉴权参数,用来刷新accessToken
expiresIn; 平台生成并返回accessToken的有效时间,单位秒
typedef struct authConfig_s
{
QString accessToken;
QString refreshToken;
int expiresIn;
}UAuthConfig;
SIGN_TYPE 枚举,信号标志
表征信号此时处于什么状态
typedef enum SIGN_TYPE_E
{
SIGN_TYPE_INIT,
SIGN_TYPE_AUTH,
SIGN_TYPE_GETDEVLIST,
SIGN_TYPE_GETFASTDATA,
SIGN_TYPE_GETDATA_BYDATE
}SIGN_TYPE;
DataComponent类
QNetworkRequest 类保存要与 QNetworkAccessManager 一起发送的请求。QNetworkRequest 是网络访问API的一部分,它是一个类,包含通过网络发送请求所需的信息。它包含一个URL和一些可用于修改请求的辅助信息。
QNetworkAccessManager类允许应用程序发送网络请求和接收回复。网络访问API是围绕一个 QNetworkAccessManager 对象构建的,它保存了它发送的请求的通用配置和设置。它包含代理和缓存配置,以及与这些问题相关的信号,以及可用于监视网络操作进度的回复信号。一个 QNetworkAccessManager 实例应该足够用于整个Qt应用程序。由于QNetworkAccessManager基于QObject,因此只能从它所属的线程使用它。一旦创建了 QNetworkAccessManager 对象,应用程序就可以使用它通过网络发送请求。提供了一组接受请求和可选数据的标准函数,每个函数返回一个QNetworkReply对象。返回的对象用于获取响应相应请求返回的任何数据。
QNetworkReply 类包含与使用 QNetworkAccessManager 发布的请求相关的数据和元数据。
与 QNetworkRequest 一样,它包含一个URL和头(以解析和原始形式),一些关于回复状态和回复本身内容的信息。
QNetworkreply 是一个顺序访问 QIODevice,这意味着一旦从对象中读取数据,设备就不再保留它。因此,如果需要,应用程序有责任保留这些数据。每当从网络接收到更多数据并进行处理时,就会发出readyread()信号。
class DataComponent : public QObject
{
Q_OBJECT
public:
explicit DataComponent(QObject *parent = nullptr);
QNetworkRequest DC_request;
QNetworkReply *DC_pReply;
QNetworkAccessManager *DC_pManager;
UConfig DC_ucfg; //登陆相关的结构体
SIGN_TYPE DC_signType;//信号标志
UAuthConfig DC_authCfg;//保存鉴权相关返回值的结构体
//设备相关
QJsonArray DC_devRootArray; //设备数组
QJsonObject DC_currentDev; //当前选中的设备
//数据
QJsonObject DC_resultFastData; //最近的一条数据
QJsonArray DC_resultDataArray; //查找出来的结果
void init(UConfig cfg);
void auth();
void getDeviceList();
void getDeviceDataByCount(QString deviceId, int count);
void getDeviceDataByDate(QString deviceId, QString startDate, QString endDate);
public slots:
void onFinished();
void onError(QNetworkReply::NetworkError errorCode);
signals:
void signMsgToLogin(int codeNum);
void signMsgToMain(int codeNum);
};
DataComponent构造函数
DataComponent::DataComponent(QObject *parent) : QObject(parent)
{
DC_ucfg.ip = "";
DC_ucfg.port = "";
DC_ucfg.appid = "";
DC_ucfg.password = "";
DC_signType = SIGN_TYPE_INIT;
DC_authCfg.accessToken = "";
DC_authCfg.refreshToken = "";
DC_authCfg.expiresIn = 0;
}
IoT平台北向应用证书使用指南
为了保障在业务中鉴权、密码、业务数据等敏感信息的传输安全。IoT平台提供HTTPS接口,与北向Application(以下简称NA)对接。
公钥和私钥
公钥和私钥可理解为两个有关系的数字密码。经公钥加密的数据只可以被私钥解密。经私钥加密的数据也只可以被公钥解密。通常情况下公钥是对外公开的,私钥是保密的。
例:A与B通信,通信内容不想被他人知道,可以采用如下方式进行通信
数字证书
一个数字证书包含的内容有:
正式的数字证书的颁发者必须是权威机构,权威机构的证书(包含权威机构的公钥的数字证书,也就是权威机构的CA证书)是公开的,所以只要拿到权威机构的CA证书,我们就能对其签发数字证书的签名进行解密,解密成功证明该数字证书是权威机构所签发的。
于是,A与B之间的通信就变成这样:
HTTPs认证过程
http (超文本传输协议)协议被用于在客户端和服务端之间传递信息,http协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了客户端和服务端之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息。
为了解决http协议的这一缺陷,需要使用另一种协议: https(安全套接字层超文本传输协议),https在http的基础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证双方的身份,验证通过后为客户端和服务端之间的通信进行加密。
https双向认证简单过程:
说明:
下图就是双向认证的过程,去掉红色框中的步骤即为单向认证。
了解了以上知识,我们可以总结出:
应用对接平台
应用对接平台有两种情况:
应用调用平台接口
平台是服务端,应用是客户端。应用要完成的工作有:
QSslConfiguration 类
QSslConfiguration 类保存 SSL 连接的配置和状态
Qt网络类使用QSslConfiguration来 传达 有关开放SSL连接的信息,并允许应用程序控制该连接的某些功能。
QSslConfiguration目前支持的设置是:
这些设置仅在连接握手期间应用。 建立连接后设置它们无效。
QSslConfiguration对象中的状态无法更改
QSslConfiguration可以与QSslSocket和网络访问API一起使用。
QSslCertificate 类
QSslCertificate存储X509证书,通常用于验证身份和存储有关本地主机,远程连接的对等方或受信任的第三方证书颁发机构的信息。 有许多方法可以构建QSslCertificate。最常见的方法是调用QSslSocket :: peerCertificate(),它返回一个QSslCertificate对象,或QSslSocket :: peerCertificateChain(),它返回它们的列表。您还可以从DER(二进制)或PEM(Base64)编码的包中加载证书,通常存储为一个或多个本地文件,或者存储在Qt资源中。
QFile file;
QString path = "./certificate/";
file.setFileName( path + "cert.pem");
file.open(QIODevice::ReadOnly);
QSslCertificate local(&file, QSsl::Pem);
m_sslConfig.setLocalCertificate( local );
file.close();
certificate文件夹下有三个文件:cert.pem、key.pem、truststore.pem,需要把certificate这个文件夹放在运行程序exe的同级目录下。将SSL握手期间要提供给对等方的证书设置为证书。建立连接后设置证书无效。证书是SSL过程中使用的标识手段。 远程端使用本地证书来验证本地用户对其证书颁发机构列表的身份。
QSslKey类
QSslKey提供了一个用于管理密钥的简单API。QSslKey类为私钥和公钥提供接口,将连接的私钥设置为key。 私钥和本地证书由必须向SSL对等方证明其身份的客户端和服务器使用。通过使用指定的算法和编码格式从设备读取和解码数据来构造QSslKey。 type指定密钥是公共密钥还是私有密钥。 如果密钥已加密,则使用passPhrase对其进行解密。 构造完成后,使用isNull()检查设备是否提供了有效密钥。
QSslKey(QIODevice *device, QSsl::KeyAlgorithm algorithm, QSsl::EncodingFormat encoding = QSsl::Pem, QSsl::KeyType type = QSsl::PrivateKey, const QByteArray &passPhrase = QByteArray())
file.setFileName( path + "key.pem");
file.open(QIODevice::ReadOnly);
QSslKey key(&file, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray("YeNiu1234"));
m_sslConfig.setPrivateKey( key );
file.close();
搜索并解析设备中以指定格式编码的所有证书,并将其返回到证书列表中。将此套接字的CA证书数据库设置为证书。必须在SSL握手之前设置证书数据库。 在握手阶段,套接字使用CA证书数据库来验证对等方的证书。将此网络请求的SSL配置设置为m_sslConfig。适用的设置包括私钥,本地证书,SSL协议(SSLv2,SSLv3,适用的TLSv1.0),CA证书以及允许SSL后端使用的密码。
void DataComponent::init(UConfig cfg)
{
DC_pManager = new QNetworkAccessManager();
QSslConfiguration m_sslConfig = DC_request.sslConfiguration();//返回此网络请求的SSL配置。
m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);//QSslSocket不会从对等方请求证书。
m_sslConfig.setProtocol(QSsl::TlsV1SslV3);//将此配置的 协议 设置为QSsl::TlsV1SslV3协议。
//客户端证书
QFile file;
QString path = "./certificate/";
file.setFileName( path + "cert.pem");
file.open(QIODevice::ReadOnly);
QSslCertificate local(&file, QSsl::Pem);
m_sslConfig.setLocalCertificate( local );
file.close();
//私钥
file.setFileName( path + "key.pem");
file.open(QIODevice::ReadOnly);
QSslKey key(&file, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray("YeNiu1234"));
m_sslConfig.setPrivateKey( key );
file.close();
//信任库
file.setFileName( path + "truststore.pem");
file.open(QIODevice::ReadOnly);
QList list = QSslCertificate::fromDevice(&file, QSsl::Pem);
//搜索并解析设备中以指定格式编码的所有证书,并将其返回到证书列表中。
file.close();
m_sslConfig.setCaCertificates(list);
//将此套接字的CA证书数据库设置为证书。
//必须在SSL握手之前设置证书数据库。 在握手阶段,套接字使用CA证书数据库来验证对等方的证书。
DC_request.setSslConfiguration(m_sslConfig);
//将此网络请求的SSL配置设置为m_sslConfig。
//适用的设置包括私钥,本地证书,SSL协议(SSLv2,SSLv3,适用的TLSv1.0),CA证书以及允许SSL后端使用的密码。
DC_ucfg.ip = cfg.ip;
DC_ucfg.port = cfg.port;
DC_ucfg.appid = cfg.appid;
DC_ucfg.password = cfg.password;
}
Auth(鉴权) 详情见华为IoT平台北向API参考
接口功能
实现第三方系统在访问开放API之前的认证
调用方法
post
接口路径
https://server:port/iocm/app/sec/v1.1.0/login
QUrl类为处理URL提供了方便的接口。它可以解码和构造编码和未编码形式的URL
content-type请求头
http请求头有四种类型,分别是通用头部,请求头部,响应头部以及内容头部。首先,content-type是属于 内容头部,是用来向 接收端 解释传递的该内容主体的,content-type的取值是告诉服务端,你传递过去的内容是啥,你应该准备好如何接收,application/x-www-form-urlencoded 这个类型是ajax默认的 content-type类型,这时前端可以以对象方式直接给后端,或者以json方式传给后端。 当action为post时候,浏览器把form数据封装到http body中,然后发送到server。
将HTTP POST请求发送到请求指定的目标,并返回打开以供读取的新QNetworkReply对象,该对象将包含服务器发送的回复。数据设备的内容将上传到服务器。数据必须打开才能读取,并且必须保持有效,直到为此回复发出finished()信号。
GET - 从指定的资源请求数据。
请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:
POST - 向指定的资源提交要被处理的数据
请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的
connect(DC_pReply, SIGNAL(finished()), this, SLOT(onFinished()));
回复处理完毕后会发出此信号。 发出此信号后,将不再更新回复的数据或元数据。注意:请勿删除连接到此信号的插槽中的对象。 使用deleteLater()。 您甚至可以在收到finished()信号之前使用isFinished()检查QNetworkReply是否已完成。
connect(DC_pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
当回复检测到处理中的错误时,发出该信号。 完成信号可能会跟随,表明连接已结束。code参数包含检测到的错误代码。 调 用errorString()以获取错误条件的文本表示。注意:请勿删除连接到此信号的插槽中的对象。 使用deleteLater()。
//鉴权 实现第三方系统在访问开放API之前的认证 POST
void DataComponent::auth()
{
QString url = "";
QString form = "";
QByteArray bytePost;
url = QString("https://%1:%2/iocm/app/sec/v1.1.0/login").arg(DC_ucfg.ip).arg(DC_ucfg.port);
DC_request.setUrl(QUrl(url)); //设置此网络请求引用的URL为url。
DC_request.setRawHeader("content-type","application/x-www-form-urlencoded");//将标头headerName设置为值headerValue。
form = QString("appId=%1&secret=%2").arg(DC_ucfg.appid).arg(DC_ucfg.password);
bytePost.append(form);
DC_signType = SIGN_TYPE_AUTH;
DC_pReply = DC_pManager->post(DC_request, bytePost);
connect(DC_pReply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(DC_pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
}
按条件批量查询设备信息列表
接口功能
按条件批量查询设备信息列表,如查询指定网关下的所有设备信息列表。
调用方法
GET
接口路径
https://server:port/iocm/app/dm/v1.3.0/devices?
appId={appId}&gatewayId={gatewayId}&nodeType={nodeType}&deviceType={deviceType}&protocolType={proto
colType}&pageNo={pageNo}&pageSize={pageSize}&startTime={startTime}&endTime={endTime}&status={status
}&sort={sort}
注意事项
携带头域信息
app_key:{appId}
Authorization:Bearer {accessToken}
Content-Type:application/json
当action为get时候,浏览器用x-www-form-urlencoded的编码方式,把form数据转换成一个字串 (name1=value1&name2=value2),然后把这个字串append到url后面,用?分割,加载这个新的url。
可选参数
pageNo=0&pageSize=5 查询的数据为最新的5条数据。
网络操作发送与接收是异步的,当https接收完成或者异常等事件,会调用注册的对应槽函数来处理。信号与槽的连接
当HTTPS正常收发完成后,会在onFinished()函数里面进行出处理,这里就可以对接收的数据进行解析,获取到我们想要的数据信息。
//获取设备列表 get
void DataComponent::getDeviceList()
{
QString url = "";
QString form = "";
QByteArray bytePost;
url = QString("https://%1:%2/iocm/app/dm/v1.3.0/devices?pageNo=0&pageSize=5").arg(DC_ucfg.ip).arg(DC_ucfg.port);// pageNo=0&pageSize=5 查询的数据为最新的5条数据。
QNetworkRequest DC_request;
DC_request.setUrl(QUrl(url));
DC_request.setRawHeader("content-type","application/x-www-form-urlencoded");
DC_request.setRawHeader("app_key",QString(DC_ucfg.appid).toLatin1());
DC_request.setRawHeader("Authorization",QString("Bearer " + QString(DC_authCfg.accessToken)).toLatin1());
DC_request.setRawHeader("content-type",QString("application/json").toLatin1());
DC_signType = SIGN_TYPE_GETDEVLIST;
DC_pReply = DC_pManager->get(DC_request);
connect(DC_pReply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(DC_pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
}
获取设备数据
根据设备id查询数据往前查询count条 get
查询设备历史数据
接口功能
Application查询设备历史数据。
调用方法
GET
接口路径
https://server:port/iocm/app/data/v1.1.0/deviceDataHistory?deviceId={deviceId}&
gatewayId={gatewayId}&serviceId ={serviceId}&pageNo={pageNo}&pageSize
={pageSize}&startTime={startTime}&endTime={endTime}&property={property}&appId={appId}
注意事项
携带头域信息
app_key:{appId}
Authorization:Bearer {accessToken}
Content-Type:application/json
deviceId 设备唯一标识, 1-64个字节
gatewayId 网关的设备唯一标识。
//获取设备数据 根据设备id查询数据往前查询count条 get
void DataComponent::getDeviceDataByCount(QString deviceId, int count)
{
QString url = "";
QString form = "";
QByteArray bytePost;
url = QString("https://%1:%2/iocm/app/data/v1.1.0/deviceDataHistory?"
"deviceId=%3&"
"gatewayId=%4&"
"pageNo=%5&"
"pageSize=%6").arg(DC_ucfg.ip).arg(DC_ucfg.port).arg(deviceId).arg(deviceId).arg(0).arg(count);
QNetworkRequest DC_request;
DC_request.setUrl(QUrl(url));
DC_request.setRawHeader("content-type","application/x-www-form-urlencoded");
DC_request.setRawHeader("app_key",QString(DC_ucfg.appid).toLatin1());
DC_request.setRawHeader("Authorization",QString("Bearer " + QString(DC_authCfg.accessToken)).toLatin1());
DC_request.setRawHeader("content-type",QString("application/json").toLatin1());
DC_signType = SIGN_TYPE_GETFASTDATA;
DC_pReply = DC_pManager->get(DC_request);
connect(DC_pReply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(DC_pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
}
根据设备id+上报时间查询数据--最多500条 get
//根据设备id+上报时间查询数据--最多500条 get
void DataComponent::getDeviceDataByDate(QString deviceId, QString startDate, QString endDate)
{
QString url = "";
QString form = "";
QByteArray bytePost;
url = QString("https://%1:%2/iocm/app/data/v1.1.0/deviceDataHistory?"
"deviceId=%3&"
"gatewayId=%4&"
"startTime=%5&"
"endTime=%6&"
"pageNo=0&pageSize=500").arg(DC_ucfg.ip).arg(DC_ucfg.port).arg(deviceId).arg(deviceId).arg(startDate).arg(endDate);
QNetworkRequest DC_request;
DC_request.setUrl(QUrl(url));
DC_request.setRawHeader("content-type","application/x-www-form-urlencoded");
DC_request.setRawHeader("app_key",QString(DC_ucfg.appid).toLatin1());
DC_request.setRawHeader("Authorization",QString("Bearer " + QString(DC_authCfg.accessToken)).toLatin1());
DC_request.setRawHeader("content-type",QString("application/json").toLatin1());
DC_signType = SIGN_TYPE_GETDATA_BYDATE;
DC_pReply = DC_pManager->get(DC_request);
connect(DC_pReply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(DC_pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
}
当https接收发生异常,会调用注册的对应槽函数来处理。
void DataComponent::onError(QNetworkReply::NetworkError errorCode)
{
qDebug() << "请求异常";
QNetworkReply * pReplay = qobject_cast(sender());
//输出错误及错误信息
qDebug() << errorCode;
qDebug() << pReplay->errorString();
}
当https接收完成,会调用注册的对应槽函数来处理。
void auth();
void getDeviceList();
void getDeviceDataByCount(QString deviceId, int count);
void getDeviceDataByDate(QString deviceId, QString startDate, QString endDate);
connect(DC_pReply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(DC_pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
DC_signType == SIGN_TYPE_AUTH
Method:POST
request: (非JSON格式)
https://server:port/iocm/app/sec/v1.1.0/login
Content-Type:application/x-www-form-urlencoded
appId={appId}&secret={secret}
response:
Status Code: 200 OK
Content-Type: application/json
{
"scope":"default",
"tokenType":"bearer",
"expiresIn":"*******",
"accessToken":"*******",
"refreshToken":"*******"
}
DC_signType == SIGN_TYPE_GETDEVLIST
Method:
GET
request:
https://server:port/iocm/app/dm/v1.3.0/devices?
appId={appId}&gatewayId={gatewayId}&nodeType={nodeType}&deviceType={deviceType}&protocolType={proto
colType}&pageNo={pageNo}&pageSize={pageSize}&startTime={startTime}&endTime={endTime}&status={status
}&sort={sort}
app_key:{appId}
Authorization:Bearer {accessToken}
Content-Type:application/json
response:
Status Code: 200 OK
Content-Type: application/json
{
"totalCount":1,
"pageNo":0,
"pageSize":10,
"devices":[
{
"deviceId":"*************************",
"gatewayId":"*************************",
"nodeType":"GATEWAY",
"createTime":"20170426T022604Z",
"lastModifiedTime":"20170426T022604Z",
"deviceInfo":{
"nodeId":null,
"name":null,
"description":null,
"manufacturerId":null,
"manufacturerName":null,
"mac":null,
"location":null,
"deviceType":null,
"model":null,
"swVersion":null,
"fwVersion":null,
"hwVersion":null,
"protocolType":null,
"bridgeId":null,
"status":"OFFLINE",
"statusDetail":"NOT_ACTIVE",
"mute":null,
"supportedSecurity":null,
"isSecurity":null,
"signalStrength":null,
"sigVersion":null,
"serialNumber":null,
"batteryLevel":null
},
"services":[
{
"serviceId": "****",
"serviceType": "****",
"data": {
"p1": "****",
"p2": "****"
},
"eventTime": "20180125T141525Z",
"serviceInfo": null
}
],
"connectionInfo":{
"protocolType":null
},
"location":null,
"devGroupIds":[]
}
]
}
DC_signType == SIGN_TYPE_GETFASTDATA
Method:
GET
request:
https://server:port/iocm/app/data/v1.1.0/deviceDataHistory?deviceId={deviceId}&
gatewayId={gatewayId}&serviceId ={serviceId}&pageNo={pageNo}&pageSize
={pageSize}&startTime={startTime}&endTime={endTime}&property={property}&appId={appId}
app_key:{appId}
Authorization:Bearer {accessToken}
Content-Type:application/json
response:
Status Code: 200 OK
Content-Type:application/json
{
"totalCount": 2,
"pageNo": 0,
"pageSize": 10,
"deviceDataHistoryDTOs": [
{
"deviceId": "0d34bb9d-73e1-4780-a12f-9243345a4567",
"gatewayId": "f6ee5aff-53f1-4459-be2f-4fc05ccd7004",
"appId": "c8855f40-d7a3-4d51-8b46-9f7747839ee2",
"serviceId": "Battery",
"data": {
"batteryLevel": 100
},
"timestamp": "20160708T025828Z"
},
{
"deviceId": "0d34bb9d-73e1-4780-a12f-9243345a4567",
"gatewayId": "f6ee5aff-53f1-4459-be2f-4fc05ccd7004",
"appId": "c8855f40-d7a3-4d51-8b46-9f7747839ee2",
"serviceId": "Temperature",
"data": {
"temperature": "33.5"
},
"timestamp": "20160708T025827Z"
}
]
}
void DataComponent::onFinished()
{
qDebug() << "onFinished" << DC_signType;
if(DC_signType == SIGN_TYPE_AUTH)
{
QNetworkReply *pReplay = qobject_cast(sender());
QByteArray replyContent = pReplay->readAll();
QJsonParseError json_error;
QJsonDocument jsonDoc(QJsonDocument::fromJson(replyContent, &json_error));
if(json_error.error != QJsonParseError::NoError)
{
qDebug() << "SIGN_TYPE_AUTH json data is error!";
return;
}
QJsonObject rootObj = jsonDoc.object();
if(rootObj.contains("accessToken") && rootObj.contains("tokenType")
&& rootObj.contains("refreshToken") && rootObj.contains("expiresIn"))
{
DC_authCfg.accessToken = rootObj.value("accessToken").toString();
DC_authCfg.refreshToken = rootObj.value("refreshToken").toString();
DC_authCfg.expiresIn = rootObj.value("expiresIn").toInt();
//向login发出信号
emit signMsgToLogin(SIGN_CODE_AUTH_SUCCESS);
}
else
{
QMessageBox::information(nullptr, "Title", "输入数据错误", QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes);
emit signMsgToLogin(SIGN_CODE_AUTH_FAIL);
}
}
else if(DC_signType == SIGN_TYPE_GETDEVLIST)
{
QNetworkReply *pReplay = qobject_cast(sender());
QByteArray replyContent = pReplay->readAll();
QJsonObject devRootObj;
QJsonParseError json_error;
QJsonDocument jsonDoc(QJsonDocument::fromJson(replyContent, &json_error));
if(json_error.error != QJsonParseError::NoError)
{
qDebug() << "SIGN_TYPE_GETDEVLIST json data is error!";
return;
}
devRootObj = jsonDoc.object();
if(devRootObj.contains("devices") && devRootObj.value("devices").isArray())
{
DC_devRootArray = devRootObj.value("devices").toArray();
//通知主界面处理
emit signMsgToMain(SIGN_CODE_GETDEVLIST_SUCCESS);
}
}
else if(DC_signType == SIGN_TYPE_GETFASTDATA)
{
QNetworkReply *pReplay = qobject_cast(sender());
QByteArray replyContent = pReplay->readAll();
QJsonObject devRootObj;
QJsonParseError json_error;
QJsonDocument jsonDoc(QJsonDocument::fromJson(replyContent, &json_error));
if(json_error.error != QJsonParseError::NoError)
{
qDebug() << "SIGN_TYPE_GETFASTDATA json data is error!";
return;
}
devRootObj = jsonDoc.object();
if(devRootObj.contains("deviceDataHistoryDTOs")
&& devRootObj.value("deviceDataHistoryDTOs").isArray()
&& devRootObj.value("deviceDataHistoryDTOs").toArray().size())
{
DC_resultFastData = devRootObj.value("deviceDataHistoryDTOs").toArray().at(0).toObject();
emit signMsgToMain(SIGN_CODE_GETFASTDATA_SUCCESS);
}
qDebug() << replyContent;
}
else if(DC_signType == SIGN_TYPE_GETDATA_BYDATE)
{
QNetworkReply *pReplay = qobject_cast(sender());
QByteArray replyContent = pReplay->readAll();
QJsonObject devRootObj;
QJsonParseError json_error;
QJsonDocument jsonDoc(QJsonDocument::fromJson(replyContent, &json_error));
if(json_error.error != QJsonParseError::NoError)
{
qDebug() << "SIGN_TYPE_GETDATA_BYDATE json data is error!";
return;
}
devRootObj = jsonDoc.object();
if(devRootObj.contains("deviceDataHistoryDTOs")
&& devRootObj.value("deviceDataHistoryDTOs").isArray()
&& devRootObj.value("deviceDataHistoryDTOs").toArray().size())
{
DC_resultDataArray = devRootObj.value("deviceDataHistoryDTOs").toArray();
emit signMsgToMain(SIGN_CODE_GETDATA_BYDATE_SUCCESS);
}
qDebug() << replyContent;
}
}
6.mainwindow.h / mainwindow.cpp / mainwindow.ui
mainwindow.ui
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
QTimer *updateTimer;
private slots:
//ui生成的槽函数
//获取数据
void on_pushButton_4_clicked();
//查询设备
void on_pushButton_findDev_clicked();
//自定义的槽函数
void onSignDataComponent(int codeNum);
//查询设备下标改变
void on_comboBox_Dev_currentIndexChanged(const QString &arg1);
void updateTimeOut();
//更新周期下表改变
void on_comboBox_updataCycle_currentIndexChanged(const QString &arg1);
//获取数据 500
void on_pushButton_5_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "datacomponent.h"
DataComponent g_DC;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置窗口大小,不允许用户最大化
setFixedSize(this->width(), this->height());
ui->dateEdit_start->setCalendarPopup(true);
//此属性保存当前日历弹出显示模式。单击箭头按钮后将显示日历弹出窗口。 仅当存在有效的日期显示格式时,此属性才有效
ui->dateEdit_end->setCalendarPopup(true);
QDate date;
date = QDate::currentDate();
ui->dateEdit_end->setDate(date);
date = date.addDays(-3);//往回退3天
ui->dateEdit_start->setDate(date);
updateTimer = nullptr;
ui->comboBox_updataCycle->addItem(QString::fromLocal8Bit("设定"));
ui->comboBox_updataCycle->addItem("5");
ui->comboBox_updataCycle->addItem("10");
ui->comboBox_updataCycle->addItem("30");
ui->comboBox_updataCycle->addItem("60");
//单条数据更新周期
updateTimer = new QTimer(this);
connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateTimeOut()));
connect(&g_DC, SIGNAL(signMsgToMain(int)), this, SLOT(onSignDataComponent(int)));
ui->comboBox_updataCycle->setCurrentIndex(2);
}
MainWindow::~MainWindow()
{
delete ui;
}
//通过QNetworkAccessManager API接口获取到数据,我们需要对其进行处理。
//所以在datacomponent类中的onFinished()函数的最后发送一个自定义的信号到主界面线程中
void MainWindow::onSignDataComponent(int codeNum)
{
if( codeNum == SIGN_CODE_GETDEVLIST_SUCCESS )
{
//获取设备列表
qDebug() << "SIGN_CODE_GETDEVLIST_SUCCESS";
ui->comboBox_Dev->clear();
for(int i=0; icomboBox_Dev->addItem(devName);
}
}
else if( codeNum == SIGN_CODE_GETFASTDATA_SUCCESS )
{
//获取第一条数据
if(g_DC.DC_resultFastData.isEmpty())
{
QMessageBox::about(nullptr, "Error", "数据接受异常");
}
QJsonObject dataObj = g_DC.DC_resultFastData.value("data").toObject();
ui->lineEdit_Temp->setText(dataObj.value("Temperature").toString());
ui->lineEdit_Hum->setText(dataObj.value("Humidity").toString());
ui->lineEdit_ill->setText(dataObj.value("illumination").toString());
ui->lineEdit_time->setText(g_DC.DC_resultFastData.value("timestamp").toString());
QString current_date = QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");
ui->lineEdit_timeReq->setText(current_date);
}
else if( codeNum == SIGN_CODE_GETDATA_BYDATE_SUCCESS)
{
if(g_DC.DC_resultDataArray.isEmpty())
{
QMessageBox::about(nullptr, "Error", "数据接受异常");
}
QJsonArray dataArray = g_DC.DC_resultDataArray;
QJsonObject dataObj;
QString dataLine = "";
dataLine = " ";
dataLine += QString("温度").leftJustified(18, ' ');//返回一个大小为18的字符串不够填充空格
dataLine += QString("湿度").leftJustified(18, ' ');//返回一个大小为18的字符串不够填充空格
dataLine += QString("光照").leftJustified(18, ' ');//返回一个大小为18的字符串不够填充空格
dataLine += QString("上报时间");
dataLine += QString("\r\n");
ui->textEdit->setText("");
ui->textEdit->insertPlainText(dataLine);
for(int j=0; jtextEdit->insertPlainText(dataLine);
}
}
}
//获取数据
void MainWindow::on_pushButton_4_clicked()
{
QString devId = "";
if(g_DC.DC_currentDev.isEmpty())
{
QMessageBox::about(nullptr, "Error", "请先选择需要查询的设备");
return;
}
devId = g_DC.DC_currentDev.value("deviceId").toString();
g_DC.getDeviceDataByCount(devId,1);
}
//查询设备
void MainWindow::on_pushButton_findDev_clicked()
{
g_DC.getDeviceList();
}
//查询设备下标改变
void MainWindow::on_comboBox_Dev_currentIndexChanged(const QString &arg1)
{
QString devName = arg1;
QString devId = "";
if(g_DC.DC_devRootArray.size() == 0)
{
QMessageBox::about(nullptr, "Error", "设备检测异常");
}
for(int i=0; icomboBox_updataCycle->currentText();
updateTimer->start(timeCycle.toInt()*1000);
}
}
}
//更新周期下表改变
void MainWindow::on_comboBox_updataCycle_currentIndexChanged(const QString &arg1)
{
(void)arg1;
qDebug() << "on_comboBox_updataCycle_currentIndexChanged";
if(updateTimer != nullptr && !g_DC.DC_currentDev.isEmpty())
{
QString timeCycle = ui->comboBox_updataCycle->currentText();
updateTimer->start(timeCycle.toInt()*1000);
}
}
void MainWindow::updateTimeOut()
{
qDebug() << "updateTimeOut";
QString devId = "";
if(g_DC.DC_currentDev.isEmpty())
{
QMessageBox::about(nullptr, "Erro", "请先选择需要查询的设备");
return;
}
devId = g_DC.DC_currentDev.value("deviceId").toString();
g_DC.getDeviceDataByCount(devId, 1);
}
//查询数据 500
void MainWindow::on_pushButton_5_clicked()
{
qDebug() << "on_pushButton_5_clicked_500";
QString devId = "";
QString startDate = "";
QString endDate = "";
if(g_DC.DC_currentDev.isEmpty())
{
QMessageBox::about(nullptr, "Error", "请先选择需要查询的设备");
return;
}
devId = g_DC.DC_currentDev.value("deviceId").toString();
startDate = ui->dateEdit_start->date().toString("yyyyMMddT000000Z");
endDate = ui->dateEdit_end->date().toString("yyyyMMddT235959Z");
g_DC.getDeviceDataByDate(devId, startDate, endDate);
}
mainwindow主要是信号与槽
提醒一点如下:
datacomponent类中 getDeviceList 会发出 信号 finished 等待槽函数 onFinished 处理。
onFinished中会向主线程发送信号 emit signMsgToMain(SIGN_CODE_GETDEVLIST_SUCCESS)
mainwindow.cpp构造函数中
connect(&g_DC, SIGNAL(signMsgToMain(int)), this, SLOT(onSignDataComponent(int)));
onSignDataComponent会对数据进行显示处理
完整工程链接