当前位置: 首页 > news >正文

【嵌入式linux开发】智能家居入门6:最新ONENET,物联网开放平台(QT、微信小程序、MQTT协议、ONENET云平台、旭日x3派)

智能家居入门6:最新ONENET物联网开放平台

    • 前言
    • 最终现象
    • 一、ONENET云平台创建产品与设备
    • 二、使用MQTT客户端软件测试
      • 2.1 测试前的准备
      • 2.2 测试
    • 三、LInux QT代码
    • 四、onenet 云端API介绍及微信小程序代码
      • 4.1 onenet API介绍
      • 4.2 微信小程序代码
    • 五、板端运行

前言

ONENET云平台更新之后,已经将以前的多协议接入这部分融合进了物联网开放平台,以前在多协议接入里创建的产品和设备,虽然还可以用,但实际上已经过时了,因为新用户根本没有多协议接入这个选项。我的这系列前几篇文章有的也已经失去了实际意义,但是使用mqtt服务器的这两篇(入门三,入门四)仍然有效!因为它们使用mqtt服务器和onenet没关系。本篇文章给想接入最新版onenet的小伙伴提供参考。

须知:新版的http协议接入已经跟之前的完全不一样了,看了官方文档和亲身实践,http协议只能上传数据至服务器,已经不能从服务器获取数据了,所以对于需要双向控制的智能家居而言已经没有啥价值了,本文使用MQTT协议接入,响应快、可靠度高,程序的健壮性也已经测试,在板端跑了几天也没有出问题!
本文下位机(旭日x3派运行QT)与服务器的通信使用mqtt协议,而上位机(微信小程序)与服务器的通信使用的是标准http方法!这是因为官方文档中,服务器的API接口就是使用标准HTTP方法实现资源CURD操作,这个在本文第四节有介绍,如果有小伙伴在微信小程序端也使用mqtt协议与服务器通信的话,可以互相交流一下。

***最重要的参考就是官方文档:https://open.iot.10086.cn/doc/v5/fuse/detail/1464
QT端的mqtt报文构建直接使用DS小龙哥的代码
微信小程序在之前的基础上改改即可


最终现象

linux QT智能家居演示


一、ONENET云平台创建产品与设备

这部分直接以视频的形式给出:

需要注意的点:
①数据协议:
这里选择的是OneJson,当然也可以选择数据流,但是它们对应的发送数据和接收数据的topic是不一样的,所以如果想省事直接使用本文代码,那就选择OneJson。
在这里插入图片描述

②新版三元组:
按照视频创建完毕后,进入设备管理的详情中,可以看到后续会用到的三个参数:
在这里插入图片描述


二、使用MQTT客户端软件测试

2.1 测试前的准备

①token:
产品与设备创建完成之后,按照文档指示,需要计算token,在连接时会用到:
在这里插入图片描述
这里的clienid和username就是前面截图中包含的两个参数:设备名称、产品ID,这里的password就是使用官方软件计算生成的token。
接下来视频演示如何计算token:

重点如下:
在这里插入图片描述
纠错:上图中的时间过小,在后面多加随便一个数字既可:2810295937232。
②OneJson数据协议对应的发布、订阅topic:
文档中有明确给出OneJson数据协议(物模型)的发布和订阅topic,如果数据协议选择数据流的小伙伴,在文档的这个界面往后翻翻就可以看到对应的。
在这里插入图片描述
在连上服务器之后,对这两个topic操作就可以上传和接收数据啦!

2.2 测试

下载客户端软件,提取码:q1a1,这是DS小龙哥开源的,填入自己的信息:
在这里插入图片描述
①数据上传测试:
点击登录即可连接上服务器,然后点击发布主题就可以把数据发布到特定的物模型中,可以在onenet云平台中观察是否成功:
在这里插入图片描述
②数据下发测试:
onenet中进入设备调试界面,将fan_ctl设置为true,然后点击属性期望值设置。客户端软件同样连接服务器之后,点击订阅主题,观察是否收到服务器下发的消息:
在这里插入图片描述

在这里插入图片描述
这样双向测试就算完成了,接下来就是下位机与上位机的代码简介。

三、LInux QT代码

既然上面的MQTT客户端软件已经可以实现连接服务器、发布话题、订阅话题了,那说明在自己的界面中编写代码是可行的。如果不知道如何设计界面可以参考本系列第五篇文章。
项目的目录结构如下:
在这里插入图片描述
其中mqtt这两个文件是DS小龙哥开源的,具体哪篇文章我也找不见了,我加了一个信号来解析接收到的服务器下发的json数据。这个mqtt代码的好处就是短小精悍,完全够用,避免了使用官方库麻烦的缺点,当然喜欢使用官方qmqtt库的小伙伴可以参考网上的教程进行编译和移植,然后自己编写相关逻辑。
由于找不到原文章,这里就给出原博主的代码:
mqtt.cpp

#include "mqtt.h"//连接成功服务器回应 20 02 00 00
//客户端主动断开连接 e0 00
const quint8 parket_connetAck[] = {0x20,0x02,0x00,0x00};
const quint8 parket_disconnet[] = {0xe0,0x00};
const quint8 parket_heart[] = {0xc0,0x00};
const quint8 parket_heart_reply[] = {0xc0,0x00};
const quint8 parket_subAck[] = {0x90,0x03};MQTT_WorkClass::~MQTT_WorkClass()
{qDebug()<<"析构函数---TCP";
}void MQTT_WorkClass::run()
{qDebug()<<"执行:run";if(timer){delete  timer;timer=nullptr;}timer = new QTimer(this);connect(timer, SIGNAL(timeout()), this, SLOT(EndEvenLoop()));socket_type=0;//连接到服务器ConnectMqttServer(m_ip,m_port);//开始事件循环StartEvenLoop();//初始化mqtt协议MQTT_Init();//连接mqtt协议if(MQTT_Connect(m_MQTT_ClientID.toUtf8().data(),m_MQTT_UserName.toUtf8().data(),m_MQTT_PassWord.toUtf8().data())){LogSend("MQTT服务器登录失败.\n");}else{LogSend("MQTT服务器登录成功.\n");}
}void MQTT_WorkClass::MQTT_Init(void)
{//缓冲区赋值mqtt_rxbuf = _mqtt_rxbuf;mqtt_rxlen = sizeof(_mqtt_rxbuf);mqtt_txbuf = _mqtt_txbuf;mqtt_txlen = sizeof(_mqtt_txbuf);memset(mqtt_rxbuf,0,mqtt_rxlen);memset(mqtt_txbuf,0,mqtt_txlen);
}/*
函数功能: 登录服务器
函数返回值: 0表示成功 1表示失败
*/
quint8 MQTT_WorkClass::MQTT_Connect(char *ClientID,char *Username,char *Password)
{quint8 i,j;int ClientIDLen = strlen(ClientID);int UsernameLen = strlen(Username);int PasswordLen = strlen(Password);int DataLen;mqtt_txlen=0;//可变报头+Payload  每个字段包含两个字节的长度标识DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);//固定报头//控制报文类型mqtt_txbuf[mqtt_txlen++] = 0x10;		//MQTT Message Type CONNECT//剩余长度(不包括固定头部)do{quint8 encodedByte = DataLen % 128;DataLen = DataLen / 128;// if there are more data to encode, set the top bit of this byteif ( DataLen > 0 )encodedByte = encodedByte | 128;mqtt_txbuf[mqtt_txlen++] = encodedByte;}while ( DataLen > 0 );//可变报头//协议名mqtt_txbuf[mqtt_txlen++] = 0;        	// Protocol Name Length MSB    mqtt_txbuf[mqtt_txlen++] = 4;           // Protocol Name Length LSB    mqtt_txbuf[mqtt_txlen++] = 'M';        	// ASCII Code for M    mqtt_txbuf[mqtt_txlen++] = 'Q';        	// ASCII Code for Q    mqtt_txbuf[mqtt_txlen++] = 'T';        	// ASCII Code for T    mqtt_txbuf[mqtt_txlen++] = 'T';        	// ASCII Code for T    //协议级别mqtt_txbuf[mqtt_txlen++] = 4;        		// MQTT Protocol version = 4   对于 3.1.1 版协议,协议级别字段的值是 4(0x04)   //连接标志mqtt_txbuf[mqtt_txlen++] = 0xc2;        	// conn flags mqtt_txbuf[mqtt_txlen++] = 0;        		// Keep-alive Time Length MSB    mqtt_txbuf[mqtt_txlen++] = 100;        	// Keep-alive Time Length LSB  100S心跳包    保活时间mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB    mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB  	memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen);mqtt_txlen += ClientIDLen;if(UsernameLen > 0){   mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen);		//username length MSB    mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen);    	//username length LSB    memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen);mqtt_txlen += UsernameLen;}if(PasswordLen > 0){    mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen);		//password length MSB    mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen);    	//password length LSB  memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen);mqtt_txlen += PasswordLen; }    //清空数据memset(mqtt_rxbuf,0,mqtt_rxlen);ReadData.clear();MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);//开始事件循环StartEvenLoop();if(ReadData.length()==0){//开始事件循环StartEvenLoop();}memcpy((char *)mqtt_rxbuf,ReadData.data(),ReadData.length());//CONNECTif(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //连接成功{return 0;//连接成功}return 1;
}/*
函数功能: MQTT订阅/取消订阅数据打包函数
函数参数:topic       主题   qos         消息等级 0:最多分发一次  1: 至少分发一次  2: 仅分发一次whether     订阅/取消订阅请求包 (1表示订阅,0表示取消订阅)
返回值: 0表示成功 1表示失败
*/
quint8 MQTT_WorkClass::MQTT_SubscribeTopic(char *topic,quint8 qos,quint8 whether)
{    quint8 i,j;mqtt_txlen=0;int topiclen = strlen(topic);int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度//固定报头//控制报文类型if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息类型和标志订阅else	mqtt_txbuf[mqtt_txlen++] = 0xA2;    //取消订阅//剩余长度do{quint8 encodedByte = DataLen % 128;DataLen = DataLen / 128;// if there are more data to encode, set the top bit of this byteif ( DataLen > 0 )encodedByte = encodedByte | 128;mqtt_txbuf[mqtt_txlen++] = encodedByte;}while ( DataLen > 0 );	//可变报头mqtt_txbuf[mqtt_txlen++] = 0;			//消息标识符 MSBmqtt_txbuf[mqtt_txlen++] = 0x0A;        //消息标识符 LSB//有效载荷mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主题长度 MSBmqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主题长度 LSB   memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);mqtt_txlen += topiclen;if(whether){mqtt_txbuf[mqtt_txlen++] = qos;//QoS级别}ReadData.clear();MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);//开始事件循环StartEvenLoop();if(ReadData.length()==0){//开始事件循环StartEvenLoop();}memcpy((char *)mqtt_rxbuf,ReadData.data(),ReadData.length());if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //订阅成功{return 0;//订阅成功}return 1; //失败
}//MQTT发布数据打包函数
//topic   主题 
//message 消息
//qos     消息等级 
quint8 MQTT_WorkClass::MQTT_PublishData(char *topic, char *message, quint8 qos)
{  int topicLength = strlen(topic);    int messageLength = strlen(message);     static quint16 id=0;int DataLen;mqtt_txlen=0;//有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度//QOS为0时没有标识符//数据长度             主题名   报文标识符   有效载荷if(qos)	DataLen = (2+topicLength) + 2 + messageLength;       else	DataLen = (2+topicLength) + messageLength;   //固定报头//控制报文类型mqtt_txbuf[mqtt_txlen++] = 0x30;    // MQTT Message Type PUBLISH  //剩余长度do{quint8 encodedByte = DataLen % 128;DataLen = DataLen / 128;// if there are more data to encode, set the top bit of this byteif ( DataLen > 0 )encodedByte = encodedByte | 128;mqtt_txbuf[mqtt_txlen++] = encodedByte;}while ( DataLen > 0 );	mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主题长度MSBmqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主题长度LSB memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷贝主题mqtt_txlen += topicLength;//报文标识符if(qos){mqtt_txbuf[mqtt_txlen++] = BYTE1(id);mqtt_txbuf[mqtt_txlen++] = BYTE0(id);id++;}memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength);mqtt_txlen += messageLength;ReadData.clear();MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);//开始事件循环StartEvenLoop();return mqtt_txlen;
}void MQTT_WorkClass::MQTT_SentHeart(void)
{MQTT_SendBuf((quint8 *)parket_heart,sizeof(parket_heart));
}void MQTT_WorkClass::MQTT_Disconnect(void)
{MQTT_SendBuf((quint8 *)parket_disconnet,sizeof(parket_disconnet));
}void MQTT_WorkClass::MQTT_SendBuf(quint8 *buf,quint16 len)
{if(socket_type){
//        qDebug()<<"len:"<<len;
//        for(int i=0;i<len;i++)
//        {
//            qDebug("%#x ",buf[i]);
//        }LocalTcpClientSocket->write((const char *)buf,len);}
}	//客户端模式:创建客户端
void MQTT_WorkClass::ConnectMqttServer(QString ip,quint16 port)
{if(LocalTcpClientSocket){LocalTcpClientSocket->close();delete  LocalTcpClientSocket;LocalTcpClientSocket=nullptr;}/*1. 创建本地客户端TCP套接字*/LocalTcpClientSocket = new QTcpSocket;/*2. 设置服务器IP地址*/QHostAddress FarServerAddr(ip);/*3. 连接客户端的信号槽*/connect(LocalTcpClientSocket,SIGNAL(connected()),this,SLOT(LocalTcpClientConnectedSlot()));connect(LocalTcpClientSocket,SIGNAL(disconnected()),this,SLOT(LocalTcpClientDisconnectedSlot()));connect(LocalTcpClientSocket,SIGNAL(readyRead()),this,SLOT(LocalTcpClientReadDtatSlot()));connect(LocalTcpClientSocket,SIGNAL(bytesWritten(qint64)),this,SLOT(LocalTcpClientBytesWrittenSlot(qint64)));/*4. 尝试连接服务器主机*/LocalTcpClientSocket->connectToHost(FarServerAddr,port);
}void MQTT_WorkClass::Set_MQTT_Addr(QString ip,quint16 port,QString MQTT_ClientID,QString MQTT_UserName,QString MQTT_PassWord)
{m_ip=ip;m_port=port;m_MQTT_ClientID=MQTT_ClientID;m_MQTT_UserName=MQTT_UserName;m_MQTT_PassWord=MQTT_PassWord;
}//客户端模式:响应连接上服务器之后的操作
void MQTT_WorkClass::LocalTcpClientConnectedSlot()
{socket_type=1;//通知外部emit MQTT_ConnectState(socket_type);//结束事件循环EndEvenLoop();
}//客户端模式:断开服务器
void MQTT_WorkClass::LocalTcpClientDisconnectedSlot()
{socket_type=0;//通知外部emit MQTT_ConnectState(socket_type);
}//客户端模式:读取服务器发过来的数据
void MQTT_WorkClass::LocalTcpClientReadDtatSlot()
{ReadData=LocalTcpClientSocket->readAll();//qDebug()<<"读取服务器发过来的数据:"<<ReadData.length();//qDebug()<<"读取服务器发过来的数据:"<<ReadData;//这里发布一个信号,这样在外面就可以连接槽函数,解析数据做出动作emit ReceiveWechatData(ReadData);EndEvenLoop(); //退出事件循环
}//客户端模式:数据发送成功
void MQTT_WorkClass::LocalTcpClientBytesWrittenSlot(qint64 byte)
{LogSend(QString("数据发送成功:%1\n").arg(byte));EndEvenLoop(); //退出事件循环
}//订阅主题
void MQTT_WorkClass::slot_SubscribeTopic(QString topic)
{if(MQTT_SubscribeTopic(topic.toUtf8().data(),0,1)){LogSend(QString("主题订阅失败.\n"));}else{LogSend(QString("主题订阅成功.\n"));}
}//发布消息
void MQTT_WorkClass::slot_PublishData(QString topic,QString message)
{MQTT_PublishData(topic.toUtf8().data(),message.toUtf8().data(),0);
}void MQTT_WorkClass::EndEvenLoop()
{//停止定时器timer->stop();//先退出事件循环loop.exit();//qDebug()<<"退出事件循环";
}//开始事件循环
void MQTT_WorkClass::StartEvenLoop()
{//qDebug()<<"开始事件循环";timer->start(5000);loop.exec();
}//断开连接
void MQTT_WorkClass::slot_tcp_close()
{if(socket_type){timer->stop();loop.exit();LocalTcpClientSocket->close();}
}

mqtt.h

#ifndef XL_MQTT_H
#define XL_MQTT_H
extern "C"
{
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
}
#include <iostream>
#include <QWidget>
#include <QTcpServer>
#include <QHostInfo>  //获取计算机网络信息
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
#include <QScrollBar>#define BYTE0(dwTemp)       (*(char *)(&dwTemp))
#define BYTE1(dwTemp)       (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp)       (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp)       (*((char *)(&dwTemp) + 3))typedef enum
{//名字 	    值 			报文流动方向 	描述M_RESERVED1	=0	,	//	禁止	保留M_CONNECT		,	//	客户端到服务端	客户端请求连接服务端M_CONNACK		,	//	服务端到客户端	连接报文确认M_PUBLISH		,	//	两个方向都允许	发布消息M_PUBACK		,	//	两个方向都允许	QoS 1消息发布收到确认M_PUBREC		,	//	两个方向都允许	发布收到(保证交付第一步)M_PUBREL		,	//	两个方向都允许	发布释放(保证交付第二步)M_PUBCOMP		,	//	两个方向都允许	QoS 2消息发布完成(保证交互第三步)M_SUBSCRIBE		,	//	客户端到服务端	客户端订阅请求M_SUBACK		,	//	服务端到客户端	订阅请求报文确认M_UNSUBSCRIBE	,	//	客户端到服务端	客户端取消订阅请求M_UNSUBACK		,	//	服务端到客户端	取消订阅报文确认M_PINGREQ		,	//	客户端到服务端	心跳请求M_PINGRESP		,	//	服务端到客户端	心跳响应M_DISCONNECT	,	//	客户端到服务端	客户端断开连接M_RESERVED2		,	//	禁止	保留
}_typdef_mqtt_message;class MQTT_WorkClass:public QObject
{Q_OBJECT
public:QTimer *timer=nullptr;MQTT_WorkClass(QObject* parent=nullptr):QObject(parent){}~MQTT_WorkClass();//用户名初始化void OneNet_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret);//MQTT协议相关函数声明quint8 MQTT_PublishData(char *topic, char *message, quint8 qos);quint8 MQTT_SubscribeTopic(char *topic,quint8 qos,quint8 whether);void MQTT_Init(void);quint8 MQTT_Connect(char *ClientID,char *Username,char *Password);void MQTT_SentHeart(void);void MQTT_Disconnect(void);void MQTT_SendBuf(quint8 *buf,quint16 len);void ConnectMqttServer(QString ip,quint16 port);void Set_MQTT_Addr(QString ip,quint16 port,QString MQTT_ClientID,QString MQTT_UserName,QString MQTT_PassWord);void StartEvenLoop();
public slots:void EndEvenLoop();void run();void LocalTcpClientConnectedSlot();void LocalTcpClientDisconnectedSlot();void LocalTcpClientReadDtatSlot();void LocalTcpClientBytesWrittenSlot(qint64 byte);//订阅主题void slot_SubscribeTopic(QString topic);//发布消息void slot_PublishData(QString topic,QString message);//断开连接void slot_tcp_close();
signals:void LogSend(QString text);void MQTT_ConnectState(bool state);void ReceiveWechatData(const QByteArray& data);
private:quint8 *mqtt_rxbuf;quint8 *mqtt_txbuf;quint16 mqtt_rxlen;quint16 mqtt_txlen;quint8 _mqtt_txbuf[256];//发送数据缓存区quint8 _mqtt_rxbuf[256];//接收数据缓存区QTcpSocket *LocalTcpClientSocket=nullptr;QString m_ip;quint16 m_port;bool socket_type=0;  //这是网络的状态: 1表示已经连接 0表示未连接QString m_MQTT_ClientID;QString m_MQTT_UserName;QString m_MQTT_PassWord;QEventLoop loop;QByteArray  ReadData;
};
#endif

接下来给出代码中比较重要的部分,比如连接服务器、发布数据、订阅话题并解析:
mainwindow.cpp(注意这里不全,给大家一个参考,重要的代码全给出了)

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);this->setWindowTitle("智能家居——MQTT");ui->radioButton_2->setChecked(1);/**** 连接服务器 ****/mqttClient = new MQTT_WorkClass();// 设置MQTT服务器的地址和其他参数mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);// 连接到MQTT服务器mqttClient->run();/**** 订阅主题 ****/mqttClient->slot_SubscribeTopic(topicToSubscribe);/**** 两个pushbutton的信号与槽连接 ****/connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(on_pushButton_clicked()));connect(ui->pushButton_2,SIGNAL(clicked()),this,SLOT(on_pushButton_2_clicked()));/**** 定时器(刷新日期时间、上传数据)与日期时间 ****/dateTimeEdit = new QDateTimeEdit(QDateTime::currentDateTime(),this);dateTimeEdit->setGeometry(10,400, 200, 40);timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &MainWindow::updateDateTimeEdit);timer->start(1000);/**** 五个radiobutton的信号与槽连接 ****/connect(ui->radioButton,SIGNAL(toggled(bool)), this, SLOT(onRadioButtonToggled(bool)));connect(ui->radioButton_2,SIGNAL(toggled(bool)), this, SLOT(onRadioButton2Toggled(bool)));connect(ui->radioButton_3,SIGNAL(toggled(bool)), this, SLOT(onRadioButton3Toggled(bool)));connect(ui->radioButton_4,SIGNAL(toggled(bool)), this, SLOT(onRadioButton4Toggled(bool)));connect(ui->radioButton_5,SIGNAL(toggled(bool)), this, SLOT(onRadioButton5Toggled(bool)));//断线重连槽函数connect(mqttClient,SIGNAL(MQTT_ConnectState(bool)),this,SLOT(reconnect(bool)));//接收到服务器下发数据的重写槽函数connect(mqttClient,SIGNAL(ReceiveWechatData(QByteArray)),this,SLOT(receiveWechatData(QByteArray)));
}//断线重连槽函数
void MainWindow::reconnect(bool)
{mqttClient = new MQTT_WorkClass();// 设置MQTT服务器的地址和其他参数mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);// 连接到MQTT服务器mqttClient->run();// 订阅主题mqttClient->slot_SubscribeTopic(topicToSubscribe);
}//定时器,槽函数
void MainWindow::updateDateTimeEdit() {static int send_flag = 0;dateTimeEdit->setDateTime(QDateTime::currentDateTime());//每隔8s上传一次数据if(send_flag  == 8){/**** 发布消息到主题 ****/QMap<QString, double> dataMap;dataMap["temp"] = 40.5; // 温度dataMap["humi"] = 70;   //湿度dataMap["ch4"] = 12;   //甲烷// 调用函数创建JSON对象QJsonObject rootObject = createJsonObj(dataMap);//将QJsonObject转换为QStringQString jsonString = convertObjectToJsonString(rootObject);mqttClient->slot_PublishData(topicToPublish, jsonString);send_flag = 0;qDebug() << "数据上传成功" << endl;//本地刷新显示QString temp_show = QString::number(dataMap["temp"]) + "℃";QString humi_show = QString::number(dataMap["hdmi"]) + "%rh";QString ch4_show = QString::number(dataMap["ch4"]) + "ppm";ui->label_6->setText(temp_show);ui->label_8->setText(humi_show);ui->label_10->setText(ch4_show);}send_flag++;
}//解析服务器下发数据,槽函数
void MainWindow::receiveWechatData(QByteArray data)
{//qDebug()<<"读取服务器发过来的数据:"<< data;// 找到JSON对象的起始位置(第一个'{'符号)int startPos = data.indexOf('{');if (startPos != -1) {// 删除起始位置之前的所有字符data = data.mid(startPos);} else {qDebug() << "未找到JSON对象的起始位置";//return -1; // 如果找不到起始位置,返回错误代码}parseJsonAndAction(data);
}//将QJsonObject转换为QString,函数
QString MainWindow::convertObjectToJsonString(const QJsonObject& object) {// 将QJsonObject转换为QJsonDocumentQJsonDocument jsonDoc(object);// 将QJsonDocument转换为QString,使用Compact模式return jsonDoc.toJson(QJsonDocument::Compact);
}//组织上传数据,函数
QJsonObject MainWindow::createJsonObj(const QMap<QString, double>& dataMap) {QJsonObject rootObject;// 设置id字段rootObject["id"] = "123";// 创建params对象QJsonObject paramsObject;// 遍历数据映射,添加每个数据字段for (auto it = dataMap.constBegin(); it != dataMap.constEnd(); ++it) {QJsonObject valueObj;valueObj["value"] = it.value(); // 设置数值paramsObject[it.key()] = valueObj; // 添加到params对象}// 将params对象添加到rootObjectrootObject["params"] = paramsObject;return rootObject;
}//解析服务器下发指令并动作,函数
void MainWindow::parseJsonAndAction(const QByteArray& jsonData) {// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);if (jsonDoc.isNull()) {qDebug() << "JSON解析失败";return;}// 确保这是一个对象if (!jsonDoc.isObject()) {qDebug() << "JSON不是一个对象";return;}// 获取JSON对象QJsonObject jsonObject = jsonDoc.object();// 进入params对象if (jsonObject.contains("params")){QJsonObject paramsObject = jsonObject["params"].toObject();QDateTime currentDateTime = QDateTime::currentDateTime();// 格式化日期时间为字符串,例如:"2024-08-16 18:32:10"QString dateTimeStr = currentDateTime.toString("yyyy-MM-dd HH:mm:ss");// 提取led_ctl的值if (paramsObject.contains("led_ctl")){bool ledCtlValue = paramsObject["led_ctl"].toBool();qDebug() << "led_ctl的值:" << ledCtlValue;if(ledCtlValue == 0){QString message = dateTimeStr + " - 手机控制关闭客厅灯";ui->textBrowser->append(message);ui->radioButton->setText("客厅灯|离线");ui->radioButton->setChecked(0);}else if(ledCtlValue == 1){QString message = dateTimeStr + " - 手机控制打开客厅灯";ui->textBrowser->append(message);ui->radioButton->setText("客厅灯|在线");ui->radioButton->setChecked(1);}} else{qDebug() << "params中不包含led_ctl";}if (paramsObject.contains("fan_ctl")){bool fanCtlValue = paramsObject["fan_ctl"].toBool();qDebug() << "fan_ctl的值:" << fanCtlValue;if(fanCtlValue ==0){QString message = dateTimeStr + " - 手机控制关闭空调";ui->textBrowser->append(message);ui->radioButton_3->setText("空调|离线");ui->radioButton_3->setChecked(0);}else if(fanCtlValue == 1){QString message = dateTimeStr + " - 手机控制打开空调";ui->textBrowser->append(message);ui->radioButton_3->setText("空调|在线");ui->radioButton_3->setChecked(1);}} else{qDebug() << "params中不包含fan_ctl";}if (paramsObject.contains("curtain")){int curtainCtlValue = paramsObject["curtain"].toInt();qDebug() << "curtain的值:" << curtainCtlValue;if(curtainCtlValue >=0){QString message = dateTimeStr + " - 手机控制窗帘";ui->textBrowser->append(message);QString cl = "窗帘打开程度:" + QString::number(curtainCtlValue);ui->label_14->setText(cl);qDebug() << "窗帘打开程度:" << curtainCtlValue << endl;}} else{qDebug() << "params中不包含curtain";}}else{qDebug() << "JSON对象中不包含params";}
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QRadioButton>
#include <QDebug>
#include <QTimer>
//日期时间
#include <QDateTimeEdit>
#include <QTimeEdit>
#include <QDateEdit>
//tcp客户端通过http协议连接云服务器
#include <QJsonObject>
#include <QByteArray>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonValue>#include <MQTT/mqtt.h>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();QString serverIp = "183.230.40.96"; // 替换为服务器IPquint16 serverPort = 1883; // 替换为服务器端口QString clientId = "dht11"; // 替换为设备名称QString username = "bs2u21MIHC"; // 替换为产品IDQString password = "version=2018-10-31&res=products%2Fbs2u21MIHC%2Fdevices%2Fdht11&et=2810295937232&method=md5&sign=dkKx5uuWp0sMqet7BJGa2w%3D%3D"; // 替换为tokenQString topicToSubscribe = "$sys/bs2u21MIHC/dht11/thing/property/set"; // 替换为要订阅的主题QString topicToPublish = "$sys/bs2u21MIHC/dht11/thing/property/post"; // 替换为要发布消息的主题//三个处理函数QString convertObjectToJsonString(const QJsonObject& object);QJsonObject createJsonObj(const QMap<QString, double>& dataMap);void parseJsonAndAction(const QByteArray& jsonData);private:Ui::MainWindow *ui;QDateTimeEdit *dateTimeEdit;QTimer *timer;MQTT_WorkClass *mqttClient;private slots:void onRadioButtonToggled(bool checked);//五个radiobutton,控制模式和显示状态void onRadioButton2Toggled(bool checked);void onRadioButton3Toggled(bool checked);void onRadioButton4Toggled(bool checked);void onRadioButton5Toggled(bool checked);void updateDateTimeEdit();//刷新日期时间,上传数据至云服务器void on_pushButton_clicked();//连接服务器void on_pushButton_2_clicked();//断线重连void reconnect(bool);//接收服务器下发的数据void receiveWechatData(QByteArray);};
#endif // MAINWINDOW_H

代码中注释很全,集成到自己的项目中是很容易的,主要是ui界面设计不是纯代码所以即使我给出了完整代码也没用,很多东西对不上,花点时间集成mqtt相关代码到自己的项目里就好啦!这部分完成之后可以先进行测试,看看是否可以将数据上传至服务器,并且能否订阅到服务器下发的数据,测试方法与第二节一样。


四、onenet 云端API介绍及微信小程序代码

4.1 onenet API介绍

通过阅读onenet物联网开放平台的文档,发现OneNET API提供产品、设备、服务等云端API,使用标准HTTP方法实现资源CURD操作,其中URL中的中文参数使用UTF-8编码。具体的大家可以去看文档,写的很清楚,所以微信小程序使用这种方式接入服务器。
在这里插入图片描述
从文档中的接口列表可以看到物模型使用的接口:
在这里插入图片描述
具体的使用方式如下:
①设备属性最新数据查询(微信小程序从服务器获取数据)
在这里插入图片描述
文档往下翻可以看到使用示例,微信小程序中参考这种格式即可:
在这里插入图片描述
②设备属性期望设置(微信小程序发布数据到服务器):
在这里插入图片描述
文档往下翻可以看到使用示例,微信小程序中参考这种格式即可:
在这里插入图片描述
有了上面这两个示例,就可以在微信小程序中进行代码的编写了!

4.2 微信小程序代码

index.js

Page({data: {temp:0,humi:0,gas_ch4:0,fumes:0,leds:false,fen:false,water:false},/* 获取OneNET云平台设备数据 */getinfo() {/* 发起 HTTPS 网络请求 获取设备参数 */wx.request({/* 接口地址、产品ID、设备名 */url: "https://iot-api.heclouds.com/thingmodel/query-device-property?product_id=466832eFSH&device_name=dht11",header: {/* token */"authorization": "version=2018-10-31&res=products%2F466832eFSH%2Fdevices%2Fdht11&et=281029593723&method=md5&sign"},method: "GET",   /* HTTP 请求方法:获取 */success: res => {console.log("获取成功", res)this.setData({humi: res.data.data[4].value,temp: res.data.data[6].value,gas_ch4: res.data.data[0].value,fumes: res.data.data[3].value,})}});},//下发指令:ledonledsChange(event){const that = thisconsole.log(event.detail.value);const sw = event.detail.valuethat.setData({leds:sw})if(sw){wx.request({url: 'https://iot-api.heclouds.com/thingmodel/set-device-property',method: 'POST',  /* HTTP 请求方法:上传 */header: {/* token */"authorization": "version=2018-10-31&res=products%2F466832eFSH%2Fdevices%2Fdht11&et=281029593723&method=md5&sign"},data: {"product_id": "466832eFSH","device_name": "dht11","params": {"led_ctl": true   /* 控制板端LED */}},success(res){console.log("成功",res.data)},fail(res){console.log("失败",res)}})}else{wx.request({url: 'https://iot-api.heclouds.com/thingmodel/set-device-desired-property',method: 'POST',header: {/* 用户鉴权信息 */"authorization": "version=2018-10-31&res=products%2F466832eFSH%2Fdevices%2Fdht11&et=281029593723&method=md5&sign"},data: {"product_id": "466832eFSH","device_name": "dht11","params": {"led_ctl": false   /* 控制板端LED */}},success(res){console.log("成功",res.data)},fail(res){console.log("失败",res)}})}},onLoad() {var that = thissetInterval(function(){that.getinfo()},5000)}})

上面的代码也不是完整的,有了思路和格式就是非常大的帮助,大家可以参照这样的发布与请求数据的格式将代码集成到自己的项目中,进行自己的界面设计,注意替换产品ID、设备名称、鉴权信息(token)等。


五、板端运行

这里也和本系列的第五篇文章一样,这个开发板能直接运行QT,所以不需要交叉编译,只需要在虚拟机中测试完成之后,把项目复制到板端就可以运行!对于其它开发板的小伙伴,就参考正点原子的交叉编译吧。


本文到这就结束了,智能家居系列应该就更到这了,说不定等下次onenet更新之后,我又会再更,由于网上现在对新版onenet的接入说明还是很少的,本篇文章提供了完整的思路的请求格式,打通了下位机与微信小程序,还是比较有参考价值的,最最最重要的是自己要去看官方的文档,配合博主的介绍,很容易就能看懂,接下来就是代码实现,多看看就可以看懂。
对于不想使用linux开发板的小伙伴当然也可以使用stm32配合esp8266实现一样的功能,这方面很早就有up实现了,也是新版onenet,不过实现方式大部分不体现在代码端了,是将固件烧入esp8266,这样直接使用内置的AT指令,提供参数就可以直接连接服务器了,不像以前还需要mqtt的库,现在也不需要啦,大家加油!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Linux环境下运行介绍
  • 51单片机学习
  • 第八季完美童模全球总冠军·韩嘉潞 破浪扬帆写就传奇
  • INSTALLMENT MORTGAGE
  • Mac升级系统文件都丢了怎么办?Mac更新后资料找不到了怎么恢复
  • 【ARM 芯片 安全与攻击 6 -- ARM 中的 PAC 是什么?】
  • 2024年电赛H题全开源
  • 深度学习(YOLO、DETR) 十折交叉验证
  • 一个简单的Rtmp推流客户端(QT录音,OpenCV摄像,FFmpeg编码推流)
  • 模糊Smith智能控制方法的研究 及其单片机实现
  • 打造聊天流式回复效果:Spring Boot+WebSocket + JS实战
  • Python办公自动化:初识 `openpyxl`
  • Ubuntu离线安装库并解决依赖关系
  • 发信息(c语言)
  • golang提案,内置 Go 错误检查函数
  • angular学习第一篇-----环境搭建
  • chrome扩展demo1-小时钟
  • Javascript基础之Array数组API
  • jQuery(一)
  • Node 版本管理
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 阿里云购买磁盘后挂载
  • 简单易用的leetcode开发测试工具(npm)
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 我的zsh配置, 2019最新方案
  • 字符串匹配基础上
  • 最近的计划
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • (1)常见O(n^2)排序算法解析
  • (2)MFC+openGL单文档框架glFrame
  • (SpringBoot)第二章:Spring创建和使用
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (十七)Flink 容错机制
  • (转)树状数组
  • (转载)Linux网络编程入门
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .describe() python_Python-Win32com-Excel
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • [ C++ ] STL---string类的模拟实现
  • [ Linux 长征路第五篇 ] make/Makefile Linux项目自动化创建工具
  • [ web基础篇 ] Burp Suite 爆破 Basic 认证密码
  • []串口通信 零星笔记
  • [000-002-01].数据库调优相关学习
  • [51nod1610]路径计数