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

news2024/11/15 16:45:17

智能家居入门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 byte
		if ( 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());

    //CONNECT
    if(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 byte
		if ( DataLen > 0 )
			encodedByte = encodedByte | 128;
		mqtt_txbuf[mqtt_txlen++] = encodedByte;
	}while ( DataLen > 0 );	
	
	//可变报头
    mqtt_txbuf[mqtt_txlen++] = 0;			//消息标识符 MSB
    mqtt_txbuf[mqtt_txlen++] = 0x0A;        //消息标识符 LSB
	//有效载荷
    mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主题长度 MSB
    mqtt_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 byte
		if ( DataLen > 0 )
			encodedByte = encodedByte | 128;
		mqtt_txbuf[mqtt_txlen++] = encodedByte;
	}while ( DataLen > 0 );	
	
    mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主题长度MSB
    mqtt_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转换为QString
        QString 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转换为QJsonDocument
    QJsonDocument 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对象添加到rootObject
    rootObject["params"] = paramsObject;

    return rootObject;
}

//解析服务器下发指令并动作,函数
void MainWindow::parseJsonAndAction(const QByteArray& jsonData) {
    // 将QByteArray转换为QJsonDocument
    QJsonDocument 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_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    QString serverIp = "183.230.40.96"; // 替换为服务器IP
    quint16 serverPort = 1883; // 替换为服务器端口
    QString clientId = "dht11"; // 替换为设备名称
    QString username = "bs2u21MIHC"; // 替换为产品ID
    QString password = "version=2018-10-31&res=products%2Fbs2u21MIHC%2Fdevices%2Fdht11&et=2810295937232&method=md5&sign=dkKx5uuWp0sMqet7BJGa2w%3D%3D"; // 替换为token
    QString 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,
            })
        }
    });
  },

   //下发指令:led
   onledsChange(event){
    const that = this
    console.log(event.detail.value);
    const sw = event.detail.value
    that.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 = this
      setInterval(function(){
        that.getinfo()
      },5000)
    }
  })

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


五、板端运行

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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 热题100-24

回文链表 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff…

四层负载企业实战

通过四层负载LVSkeepalived将请求转发到nginx代理服务器。通过代理服务器访问后端真实应用服务。 拓补图&#xff1a; 准备环境6台机器&#xff1b; lvskeepalived---两台 nginx代理 ---两台 tomcat----一台 php ----一台 所有机器关闭防火墙和selinux 所有机器解析如下…

APP安全检测报告内容详解

一、APP安全检测的必要性 APP安全检测是确保移动应用安全的重要一环。在开发过程中&#xff0c;由于技术、人员和管理等方面的原因&#xff0c;APP可能存在各种安全漏洞和风险。这些漏洞一旦被黑客利用&#xff0c;可能导致用户数据泄露、恶意代码注入、应用被篡改等严重后果。…

Windows下使用QT5.14.2编译MySQL8.0对应的最新64位驱动程序步骤

不得不说mysql更新的速度是真的快&#xff0c;最近一两年都更新了好几个的小版本了。如果安装了高版本的 mysql软件&#xff0c;低版本的驱动就不支持了&#xff0c;因此需要重新使用QT来编译对应的mysql驱动。具体办法如下&#xff1a; 1、官网下载最新的mysql8.0安装包。下载…

day04--js的综合案例

1.1 商品全选 需求&#xff1a;商品全选 1. 全选 &#xff1a;点击全选按钮,所有复选框都被选中 2. 全不选 &#xff1a;点击全不选按钮,所有复选框都被取消选中 3. 反选 &#xff1a; 点击反选按钮,所有复选框状态取反 <!DOCTYPE html> <html lang"en">…

智慧校园云平台电子班牌系统源码,智慧教育一体化云解决方案

智慧校园云平台电子班牌系统&#xff0c;利用先进的云计算技术&#xff0c;将教育信息化资源和教学管理系统进行有效整合&#xff0c;实现生态基础数据共享、应用生态统一管理&#xff0c;为智慧教育建设的统一性&#xff0c;稳定性&#xff0c;可扩展性&#xff0c;互通性提供…

利用PDAL库实现Las文件向PLY文件的转换

1、官网的下载链接 PDAL&#xff08;Point Data Abstraction Library&#xff09;是一个开源的C库&#xff0c;专门用于处理三维点云数据。它提供了数据读取、转换、过滤、统计分析等多种功能&#xff0c;支持多种数据格式&#xff0c;如LAS、LiDAR、ASCII等。在IT行业中&…

Java常用集合(List、Map)类型相关问题整理

一、背景 针对Java基础集合的部分&#xff0c;对一些常见的问题进行整理&#xff0c;方便后续能够随时复习 二、问题与回答 &#xff08;1&#xff09;Java集合类ArrayList初始化时数组的默认长度是多少&#xff1f; 答&#xff1a;在new ArrayList() 这段代码执行完后&a…

类Unix环境在Windows上的演进史

自从以Unix为基础的操作系统被发明以来&#xff0c;尝试在Windows环境中模仿Unix操作的工具就一直存在。这种需求源于许多原因&#xff0c;包括Unix对脚本和命令行工具的强大支持&#xff0c;以及Unix和Linux系统在科学、工程和其他技术领域的广泛使用。下面就让我们一起探讨下…

input[type=checkbox]勾选框自定义样式

效果图&#xff1a; <template> <input class"rule-checkbox" type"checkbox" checked v-model"isChecked" /> </template><script setup lang"ts"> import { ref } from vue; const isChecked ref(); </…

应急响应-DDOS-技术指南

初步预判 通常&#xff0c;可从以下几方面判断服务器/主机是否遭受DDoS攻击查看防火墙、流量监控设备、网络设备等是否出现安全告警或大量异常数据包。如图所示&#xff0c;通过流量对比&#xff0c;发现在异常时间段存在大量UDP数据包&#xff0c;并且与业务无关。 通过安全设…

猫毛还是满天飞?宠物空气净化器是个好帮手

家里养了几只可爱的小猫咪&#xff0c;每天都想它们贴贴&#xff0c;有时候看到它们这么可爱的待在家里&#xff0c;都不想出门上班了。每天睁眼是它们&#xff0c;闭眼前也是它们&#xff0c;只要我待在家里&#xff0c;它们就和我一起挪动&#xff0c;好像身边多了几只可爱的…

qt-12工具盒(ToolBox)

工具盒--ToolBox drawer.hdrawer.cppmain.cpp运行图 drawer.h #ifndef DRAWER_H #define DRAWER_H #include <QWidget> #include <QToolBox> #include <QToolButton> #include <QGroupBox> #include <QVBoxLayout>class Drawer : public QToolB…

机械学习—零基础学习日志(如何理解线性代数5)

零基础为了学人工智能&#xff0c;正在快乐学习&#xff0c;每天都长脑子 特征向量和特征值 我们知道&#xff0c;线性映射&#xff0c;也就是矩阵&#xff0c;其实就是平面的一种变换。 但是在矩阵变换过程中&#xff0c;我们会发现有一个方向上&#xff0c;变化方向会与x的…

Python 函数式编程 内置高阶函数及周边【进阶篇 3】推荐

前面我们已经总结并实践了用python获取到了数据。也介绍了python中http网络请求的几种方式&#xff0c;正在学习python开发语言或者对python3知识点生疏需要回顾的请点这里 &#xff0c;本章主要总结了函数式编程及特点 和 python中内置的高阶函数及周边知识&#xff0c;方便自…

Scout Suite:开源云安全审计工具

Scout Suite 是一个开源、多云安全审计工具&#xff0c;旨在评估云环境的安全态势。 Scout Suite 利用云供应商提供的 API 来收集和整理配置数据&#xff0c;从而更轻松地识别潜在风险。 Scout Suite 无需手动筛选云 Web 控制台上的大量页面&#xff0c;而是会自动生成全面清…

ObjectUtils.nullSafeEquals你真的用对了吗?

目录 引言排查思考 引言 在写代码时&#xff0c;我们通常喜欢使用org.springframework.util.ObjectUtils#nullSafeEquals来比较两个对象是否相等&#xff0c;从而避免使用equals方法在对象为空时导致空指针异常。 最近在写代码时&#xff0c;我试图使用stream流的filter&#…

1.Linux_常识

UNIX、Linux、GNU 1、UNIX UNIX是一个分时操作系统&#xff0c;特点是多用户、多任务 实时操作系统&#xff1a;来了请求就去解决请求 分时操作系统&#xff1a;来了请求先存着&#xff0c;通过调度轮到执行时执行 2、Linux Linux是一个操作系统内核 发行版本&#xff1…

Linux - 常用基础指令和命令

文章目录 1、ifconfig指令2、ssh指令3、ls指令4、pwd命令5、cd 指令6、stat命令7、 touch指令8、mkdir指令9、rmdir指令10、rm指令11、man指令12、cp指令13、mv指令14、cat指令15、more指令16、less指令17、head指令18、tail指令19、时间相关的指令20、cal指令21、find指令22、…

指向派生类的基类指针、强转为 void* 再转为基类指针、此时调用虚函数会发生什么?

指向派生类的基类指针、强转为 void* 再转为基类指针、此时调用虚函数会发生什么&#xff1f; 1、无论指针类型怎么转&#xff0c;类对象内存没有发生任何变化&#xff0c;还是vfptr指向虚函数表&#xff0c;下面是成员变量&#xff0c;这在编译阶段就已经确定好了&#xff1b…