【嵌入式linux开发】智能家居入门5(QT、微信小程序、HTTP协议、ONENET云平台、旭日x3派)

智能家居入门5(QT、微信小程序、HTTP协议、ONENET云平台、旭日x3派)

前言

前四篇智能家居相关文章都是使用STM32作为主控,本篇使用旭日x3派作为主控,这是一款嵌入式linux开发板,运行linux操作系统。本项目在开发板端运行QT,使用http协议与onenet云平台通信,微信小程序作为移动端,可以接收到下位机上传至服务器的数据,也可以下发指令到服务器,开发板端不断获取指令,进而执行指令。

界面设置是在QTcreator中进行,相较于代码设计来说更简单,然后是在电脑虚拟机的ubuntu中进行设计和测试,成功之后直接将项目移植到板端即可,这里是因为开发板能直接运行qt,所以可以直接移植项目,如果板端不能直接运行qt的话,就参考正点原子的交叉编译,最好是使用他官方的开发板。

本文使用的是onenet平台的多协议接入,目前对于新用户已经没有这个功能了,然后老用户之前没创建产品的话,现在也创建不了了,所以本篇文章仅给使用多协议接入的小伙伴参考,对于最新的上传数据方法我也已经完成,但是获取数据和转发数据还没有实现,有兴趣的可以留言。


由于本文的目的主要是在linux下实现双向通信和QT练习,所以没有真的连接温湿度传感器等外设。最终现象如下视频所示:

本文中开发板端称为下位机,微信小程序称为上位机,ONENET云平台称为服务器。


环境:

ubuntu20.04

QT5

旭日x3派

微信开发者工具


一、QT界面设计

虚拟机端的QT5安装,直接网上搜,有很多,一般用命令行安装比较快。

这里直接使用视频的方式来简单展示搭建的过程,主要包括:添加资源文件qrc,添加qss样式表,界面设计等。

视频中用到的qss样式表代码参考如下,都给出了注释很好理解:
①QRadiobutton

复制代码
QRadioButton::indicator{     //设置点击区域的长和宽
	width:45px;
	height:30px;
}
QRadioButton::indicator:unchecked{     //设置未点击时的显示图像
    image: url(:/images/switch_off.png);
}
QRadioButton::indicator:checked{    //设置点击时的显示图像
    image: url(:/images/switch_on.png);
}

②QWidget

复制代码
QWidget#widget {              // #特指某个对象,从右上角的类和对象中可以看当前选中的是哪个对象。
    background-color:rgb(150, 150, 150);        //背景颜色
    background-image: url(:/images/light.png);    //背景图片
    background-repeat: no-repeat;    //表示不要应用到子对象上,不设置的话开关里面也会添加一张背景图片
    background-position: top left;   //显示到左上角
	border-radius: 18px;    //边框圆角
}

视频中用到的图像,可以到阿里巴巴矢量图标库中找到,下载png图像即可,然后最好将图像大小重新设置成60*60,不然就有问题:

ps:这里最后设计成自己喜欢的样子就可以了,当然可以不参考我的设计,如果只对通信感兴趣的直接往后看就好了,本文的重点也是在双向通信上。

二、云平台产品创建与连接

1、云平台产品创建

前言中已经提到了多协议接入的问题,这里不再赘述,使用最新物联网组件的小伙伴可以评论区讨论,我看了官方文档的http接入,只看到了数据上传,没有看到获取指令或者数据的部分,是不是新版的onenet中http不支持双向了呢?MQTT协议好像还是和之前差不多的,只是不再是以前的三元组了。

2、云平台连接测试

这部分可以直接参考正点原子的视频,写一个tcp客户端,就可以正常连接云平台了,注意连接的ONENET云平台的IP地址是:183.230.40.33,端口号是:80。

建议直接跟着视频写一下:TCP客户端程序编写,主要是防止后续代码看不懂。

三、下位机端QT代码总览:

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 <QTcpSocket>
#include <QJsonObject>
#include <QByteArray>
#include <QJsonDocument>
#include "QHostAddress"
#include <QJsonArray>
#include <QJsonValue>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    bool deng_flag = 0;
    int last_mainkongtiaoValue;

    const char *str[4] = {"POST /devices/1188390993/datapoints HTTP/1.1",
                    "api-key:=4T1J3khTpmZO99YYNDHvM5EZiI=",
                    "Host:api.heclouds.com",
                    ""};
    QString getbtnRequest = "GET /devices/1188390993/datastreams/ HTTP/1.1\r\napi-key:=4T1J3khTpmZO99YYNDHvM5EZiI=\r\nHost:api.heclouds.com\r\n\r\n" ;

private:
    Ui::MainWindow *ui;

    QDateTimeEdit *dateTimeEdit;
    QTimer *timer;

    QTcpSocket *tcpsocket;

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 receiveMessage();//tcp相关
    void mStateChange(QAbstractSocket::SocketState);
    void on_pushButton_clicked();
    void on_pushButton_2_clicked();

};
#endif // MAINWINDOW_H

mainwindow.cpp:

复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //tcp客户端通过http连接云服务器
    this->setWindowTitle("智能家居");
    tcpsocket = new QTcpSocket(this);
    connect(tcpsocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
    connect(tcpsocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this, SLOT(mStateChange(QAbstractSocket::SocketState)));
    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(50,400, 200, 40);
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &MainWindow::updateDateTimeEdit);
    timer->start(1000);

    //下面是按钮对应的信号与槽连接
    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)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::receiveMessage()
{
    QString response = tcpsocket->readAll();
    qDebug() << "接收到服务器下发消息" << endl;

    // 提取 JSON 部分
    int jsonStartIndex = response.indexOf('{');
    QString jsonString = response.mid(jsonStartIndex);

    // 解析 JSON
    QJsonDocument jsonResponse = QJsonDocument::fromJson(jsonString.toUtf8());
    QJsonObject jsonObject = jsonResponse.object();

    QJsonArray dataArray = jsonObject["data"].toArray();
    for (const QJsonValue &value : dataArray)
    {
        QJsonObject obj = value.toObject();
        if (obj["id"].toString() == "led_ctl")
        {
            int ledCtlValue = obj["current_value"].toInt();
            //qDebug() << "LED Control Value:" << ledCtlValue;
            // 可以在这里处理 ledCtlValue,如更新 UI 显示
            if(ledCtlValue == 0)
            {
                ui->textBrowser->append("手机控制关闭客厅灯");
                ui->radioButton->setText("客厅灯|离线");
                ui->radioButton->setChecked(0);
            }
            else if(ledCtlValue == 1)
            {
                ui->textBrowser->append("手机控制打开客厅灯");
                ui->radioButton->setText("客厅灯|在线");
                ui->radioButton->setChecked(1);
            }
        }
        else if (obj["id"].toString() == "mainroom_kongtiao")
        {
            int mainkongtiaoValue = obj["current_value"].toInt();
                bool ischecked = ui->radioButton_3->isChecked();
                if((ischecked == 1) && (mainkongtiaoValue ==0))
                {
                    ui->textBrowser->append("手机控制关闭主卧空调");
                    ui->radioButton_3->setText("空调|离线");
                    ui->radioButton_3->setChecked(0);
                }
                else if((ischecked == 0) && (mainkongtiaoValue ==1))
                {
                    ui->textBrowser->append("手机控制打开主卧空调");
                    ui->radioButton_3->setText("空调|在线");
                    ui->radioButton_3->setChecked(1);
                }
        }
    }
}

void MainWindow::mStateChange(QAbstractSocket::SocketState state)
{
    switch (state) {
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("因某种原因与服务端连接断开,尝试重连");
        qDebug() << "因某种原因与服务端连接断开,尝试重连" << endl;
        tcpsocket->connectToHost(QHostAddress("183.230.40.33"),80);
        ui->pushButton->setEnabled(true);
        ui->pushButton_2->setEnabled(false);
        break;
    case QAbstractSocket::ConnectedState:
        qDebug() << "已连接服务器" << endl;
        ui->textBrowser->append("已连接服务端");
        ui->pushButton->setEnabled(false);
        ui->pushButton_2->setEnabled(true);
        break;
    default:
        break;
    }
}

//开启关闭客厅灯
void MainWindow::onRadioButtonToggled(bool checked)
{
    if (checked) {
        ui->radioButton->setText("客厅灯|在线");
        //deng_flag = 1;
        qDebug() << "客厅灯打开" << endl;

    } else {
        ui->radioButton->setText("客厅灯|离线");
        //deng_flag = 0;
        qDebug() << "客厅灯关闭" << endl;
    }
}

//场景选择:居家
void MainWindow::onRadioButton2Toggled(bool checked)
{
    if (checked) {
        qDebug() << "居家模式已打开" << endl;
    } else {
        qDebug() << "居家模式已关闭" << endl;
    }
}

//开启关闭主卧空调
void MainWindow::onRadioButton3Toggled(bool checked)
{
    if (checked) {
        ui->radioButton_3->setText("空调|在线");
        qDebug() << "主卧空调打开" << endl;
    } else {
        ui->radioButton_3->setText("空调|离线");
        qDebug() << "主卧空调关闭" << endl;
    }
}

//场景选择:暂时出门
void MainWindow::onRadioButton4Toggled(bool checked)
{
    if (checked) {
        //ui->radioButton_3->setForegroundRole();
        qDebug() << "暂时出门模式已打开" << endl;
    } else {
        //ui->radioButton_3->setText("空调|离线");
        qDebug() << "暂时出门模式已关闭" << endl;
    }
}


//场景选择:出远门
void MainWindow::onRadioButton5Toggled(bool checked)
{
    if (checked) {
        qDebug() << "出远门模式已打开" << endl;
    } else {
        qDebug() << "出远门模式已关闭" << endl;
    }
}

void MainWindow::on_pushButton_clicked()
{
    tcpsocket->connectToHost(QHostAddress("183.230.40.33"),80);
}

void MainWindow::on_pushButton_2_clicked()
{
    tcpsocket->disconnectFromHost();
}

//定时器刷新时间、上传数据至服务器、刷新本地显示数据
void MainWindow::updateDateTimeEdit() {
    static int flag = 0;

    dateTimeEdit->setDateTime(QDateTime::currentDateTime());

    //请求设备数据,这里会读回来所有的数据,比如温适度和数据流模板
    QByteArray getbtnRequestBytes = getbtnRequest.toUtf8();
    if(tcpsocket->state() == QAbstractSocket::ConnectedState) {
        tcpsocket->write(getbtnRequestBytes);
        //qDebug() << "请求已发送" << endl;;
    } else {
        ui->textBrowser->append("请先连接服务端");
        //qDebug() << "请先连接服务器" << endl;;
    }


    if(flag == 10)
   {
        ui->label_6->setText("14度");
        ui->label_8->setText("70");
        ui->label_10->setText("9.7 ppm");

        QJsonObject postData;
        postData["temp"] = 14;
        postData["humi"] = 70;

        QJsonDocument jsonDoc(postData);
        QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);

        // 计算请求体的长度
        int contentLength = jsonData.size();

        // 构建HTTP请求
        QString httpRequestString =
            "POST /devices/1188390993/datapoints?type=3 HTTP/1.1\r\n"
            "api-key:=4T1J3khTpmZO99YYNDHvM5EZiI=\r\n"  // 请注意api-key的值需要是您的实际API密钥
            "Host: api.heclouds.com\r\n"
            "Content-Length: " + QString::number(contentLength) + "\r\n"  // 设置正确的内容长度
            "\r\n"  // 请求头和请求体之间的空行
            + QString(jsonData)  // 添加JSON请求体
        ;
        QByteArray httpRequest(httpRequestString.toUtf8());

        if(tcpsocket->state() == QAbstractSocket::ConnectedState) {
            tcpsocket->write(httpRequest);
            qDebug() << "请求已发送" << endl;
        } else {
            ui->textBrowser->append("请先连接服务端");
            //qDebug() << "请先连接服务器" << endl;;
        }
        flag = 0;
    }
    flag++;
}

只要简单学过QT,上述的代码都能看懂,并不是直接复制就能用,如果自己画的ui界面和我的不一样,那就要改代码,有兴趣的可以私聊拿原项目。

四、微信小程序端代码总览

有用的目录格式如下:

各部分的代码如下,注意只测试了温度和湿度,其他的想加很简单(微信小程序直接使用的b站up的代码:彼岸有光我们有船):
index.js:

复制代码
Page({
    data: {
     temp:0
    },
    // 事件处理函数
    getinfo(){
      var that = this
      wx.request({
      url: "https://api.heclouds.com/devices/1188390993/datapoints",   
      //将请求行中的数字换成自己的设备ID
      header: {
    
        "api-key": "=4T1J3khTpmZO99YYNDHvM5EZiI="
      },
      method: "GET",
      success: function (e) {
        console.log("获取成功",e)
        that.setData({
          temp:e.data.data.datastreams[0].datapoints[0].value,
          humi:e.data.data.datastreams[2].datapoints[0].value,
        })
        console.log("temp==",that.data.temp),
        console.log("humi==",that.data.humi)
      }
     });
    },


kai:function(){
  let data={
  "datastreams": [  
  {"id": "led_ctl","datapoints":[{"value": 1}]},
  //led是数据流的名称,value是要传上去的数值
      ]	
  }
 //按钮发送命令控制硬件
  wx.request({
    url:'https://api.heclouds.com/devices/1188390993/datapoints',
    header: {
      'content-type': 'application/json',
      'api-key':'=4T1J3khTpmZO99YYNDHvM5EZiI='
    },
    method: 'POST',
    data: JSON.stringify(data),//data数据转换成JSON格式
    success(res){
      console.log("成功",res.data)
    },
    fail(res){
      console.log("失败",res)
    }
  })
},

guan:function(){
  let data={
  "datastreams": [  
  {"id": "led_ctl","datapoints":[{"value": 0}]},
  //led是数据流的名称,value是要传上去的数值
      ]	
  }
 //按钮发送命令控制硬件
  wx.request({
    url:'https://api.heclouds.com/devices/1188390993/datapoints',
    header: {
      'content-type': 'application/json',
      'api-key':'=4T1J3khTpmZO99YYNDHvM5EZiI='
    },
    method: 'POST',
    data: JSON.stringify(data),//data数据转换成JSON格式
    success(res){
      console.log("成功",res.data)
    },
    fail(res){
      console.log("失败",res)
    }
  })
},

kaikong:function(){
  let data={
  "datastreams": [  
  {"id": "mainroom_kongtiao","datapoints":[{"value": 1}]},
  //led是数据流的名称,value是要传上去的数值
      ]	
  }
 //按钮发送命令控制硬件
  wx.request({
    url:'https://api.heclouds.com/devices/1188390993/datapoints',
    header: {
      'content-type': 'application/json',
      'api-key':'=4T1J3khTpmZO99YYNDHvM5EZiI='
    },
    method: 'POST',
    data: JSON.stringify(data),//data数据转换成JSON格式
    success(res){
      console.log("成功",res.data)
    },
    fail(res){
      console.log("失败",res)
    }
  })
},

guankong:function(){
  let data={
  "datastreams": [  
  {"id": "mainroom_kongtiao","datapoints":[{"value": 0}]},
  //led是数据流的名称,value是要传上去的数值
      ]	
  }
 //按钮发送命令控制硬件
  wx.request({
    url:'https://api.heclouds.com/devices/1188390993/datapoints',
    header: {
      'content-type': 'application/json',
      'api-key':'=4T1J3khTpmZO99YYNDHvM5EZiI='
    },
    method: 'POST',
    data: JSON.stringify(data),//data数据转换成JSON格式
    success(res){
      console.log("成功",res.data)
    },
    fail(res){
      console.log("失败",res)
    }
  })
},


    onLoad() {
      var that = this
      setInterval(function(){
        that.getinfo()
      },5000)
    }
  })

index.wxml:

复制代码
<view class="userinfo">
 <image class="img" src="../images/温度 .png"></image>
 <text>温度:{{temp}}℃</text> 
</view>

<view class="userinfo">
 <image class="img" src="../images/湿度.png"></image>
 <text>湿度:{{humi}}%</text> 
</view>

<view class="userinfo">
 <image class="img" src="../images/甲烷.png"></image>
 <text>天然气:{{gas_ch4}}PPM</text> 
</view>

<view class="userinfo">
 <image class="img" src="../images/可燃气体.png"></image>
 <text>可燃气体:{{ranqi}}PPM</text> 
</view>

<button type="primary" style="margin-top: 20px;" bindtap="kai">开灯</button>
<button type="warn" bindtap="guan">关灯</button>

<button type="primary" style="margin-top: 20px;" bindtap="kaikong">开主卧空调</button>
<button type="warn" bindtap="guankong">关主卧空调</button>

index.wxss:

复制代码
/**index.wxss**/
.userinfo {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: rgb(141, 10, 10);
  font-size: 15px;
}

.img {
  width: 100rpx;
  height: 100rpx;
}

.usermotto {
  margin-top: 200px;
}

index.json:

复制代码
{
  "usingComponents": {}
}

也是非常简单,比起qt还要更简单,简单看看就能看懂。愉快的测试即可。

五、板端测试

虚拟机端代码跑通之后其实就可以到板子上跑了。直接远程登录开发板,然后安装QT5:

复制代码
sudo apt-get install qt5-default qt5-qmake qtcreator

随后将项目拷贝至任意目录下,打开qtcreator,然后打开项目,即可运行。

相关推荐
路溪非溪6 分钟前
关于Linux内核中头文件问题相关总结
linux
lxmyzzs1 小时前
pyqt5无法显示opencv绘制文本和掩码信息
python·qt·opencv
大橘1 小时前
【qml-4】qml与c++交互(类型多例)
qt·qml
Lovyk2 小时前
Linux 正则表达式
linux·运维
Fireworkitte3 小时前
Ubuntu、CentOS、AlmaLinux 9.5的 rc.local实现 开机启动
linux·ubuntu·centos
sword devil9004 小时前
ubuntu常见问题汇总
linux·ubuntu
ac.char4 小时前
在CentOS系统中查询已删除但仍占用磁盘空间的文件
linux·运维·centos
淮北也生橘126 小时前
Linux的ALSA音频框架学习笔记
linux·笔记·学习
华强笔记9 小时前
Linux内存管理系统性总结
linux·运维·网络
Forward♞9 小时前
Qt——文件操作
开发语言·c++·qt