前言

        因为项目需要,计划写一个可以自定义量程、自己选择设备、选择通道号、选择传感器电压范围和测量范围等数据的上位机软件,用于对不同型号的传感器数据进行分析处理。

        最初设计草稿:

最初的demo_1效果展示:

        由上图效果可见,主要演示的是可以配置的选项和效果,以及根据量程范围变化以后实际测量的模拟量数据的变化情况。

优化阶段1效果:添加保存文件功能

代码:

咱们一共需要这么几个类:全局信号类、上位机界面类、弹出对话框类、文件处理类。

源码会分享出来供大家参考和自己修改,如果不在后面会放在文章最后。

全局信号类:

#ifndef GLOBALSIGNAL_H
#define GLOBALSIGNAL_H

#include <QObject>

class GlobalSignal : public QObject
{
    Q_OBJECT
public:
    //获取单实例
    static GlobalSignal* getInstance();
    //释放单实例
    static void deleteInstance();

signals:
    void getDataByChannel(const QString& DevName, int ChannelNum, double currVolt);
    void getDataByDevNumChannel(const int DevNum, int ChannelNum, double currVolt);
    void getValue(int value);

private:
    //单实例对象指针
    static GlobalSignal *globalSignal;

    //构造、析构、拷贝构造、赋值构造私有
    GlobalSignal(){};
    ~GlobalSignal(){};
    GlobalSignal(const GlobalSignal &){};
    const GlobalSignal &operator=(const GlobalSignal &){};

};

#endif // GLOBALSIGNAL_H

// 使用方式:
// 1. 引入头文件,在私有对象中添加 GlobalSignal *glbSignal;
//
// 2. 构造函数中创建:glbSignal = GlobalSignal::getInstance();
//
// 3. 触发方式:
// 	emit glbSignal->getValue(num);
// 4. 接收方式:
//  connect(glbSignal,&GlobalSignal::getValue,this,[=](int value){
//          QString msg = QString("接收到数据:nn:%1").arg(value);
//          ui->textBrowser->append(msg);
//  });


#include "globalSignal.h"

//全局信号转发单例类: 饿汉式
GlobalSignal* GlobalSignal::globalSignal = new GlobalSignal();

GlobalSignal *GlobalSignal::getInstance()
{
    return globalSignal;
}

void GlobalSignal::deleteInstance()
{
    if(globalSignal)
    {
        delete globalSignal;
        globalSignal = nullptr;
    }
}

数据结构体

#ifndef ASKDIALOGSTRUCT_H
#define ASKDIALOGSTRUCT_H

#include <QString>
#include <QList>
#include <QVector>

// 询问对话框的结构体,用于存储界面中的信息
typedef struct AskDialogContent
{
    QString name;               // 名称
    QString unit;               // 单位

    int     devNum = 0;         // 设备号
    QString devDes;             // 设备描述

    int     channelNum = 0;     // 通道号
    QString channelDes;         // 通道描述

    int     voltRangeNum = 0;   // 电压范围编号
    QString voltRangeDes;       // 电压范围描述

    double  min = 0.0;          // 可测物理量范围最小值
    double  max = 100.0;        // 可测物理量范围最大值

}*pAskDialogContent;

typedef struct RecvDataListStruct
{
    int count = 0;

    // 序号
    QList<int> NumList;

    // 时间戳
    QList<qint64> TimeStampList;

    // 数据
    QList<double> DataList;

    QList<int>&  getNumList() {
        return NumList;
    };
    QList<double>& getDataList(){
        return DataList;
    };
    QList<qint64>& getTimeStampList(){
        return TimeStampList;
    }

}*pRecvDataList;

#endif // ASKDIALOGSTRUCT_H

传感器自定义量程界面类:

界面:

头文件:

#ifndef SENSORADAPTIVEWIDGET_H
#define SENSORADAPTIVEWIDGET_H

#include <QWidget>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QPushButton>
#include <QStringList>
#include <QTableView>
#include <QStandardItem>
#include <QDateTime>
#include <QHBoxLayout>
#include <QModelIndex>
#include <QTextBrowser>
#include <QVector>
#include <QThread>
#include <QDebug>
#include <QtCore>
#include <QtConcurrent/QtConcurrent>

#include "globalSignal.h"
#include "askContentDialog.h"
#include "askDialogStruct.h"
#include "fileHandleWorker.h"

QT_BEGIN_NAMESPACE
namespace Ui { class SensorAdaptiveWidget; }
QT_END_NAMESPACE

class SensorAdaptiveWidget : public QWidget
{
    Q_OBJECT

public:
    SensorAdaptiveWidget(QWidget *parent = nullptr);
    ~SensorAdaptiveWidget();
    void initTable();
    void initUI();

public slots:
    // void parseDataByDevNumChannel(const int& DevNum, int ChannelNum, double currVolt);
    void test();

private:
    Ui::SensorAdaptiveWidget *ui;
    GlobalSignal *glbSignal;

    QStandardItemModel *m_pStandardItemModel;
    QItemSelectionModel *m_pItemSelectModel;

    int  m_itemIndex = 0;
    bool flag = true;

    QThread *fileHandleThread;
    FileHandleWorker fileHandleWorker;
    int  m_fileNum = 0; // 按一下添加就加1,删除不会减去
};
#endif // SENSORADAPTIVEWIDGET_H

cpp文件

#include "sensorAdaptiveWidget.h"
#include "ui_sensorAdaptiveWidget.h"

SensorAdaptiveWidget::SensorAdaptiveWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::SensorAdaptiveWidget)
{
    ui->setupUi(this);
    glbSignal = GlobalSignal::getInstance();

    fileHandleThread = new QThread;
    fileHandleThread->start();
    fileHandleWorker.moveToThread(fileHandleThread);

    // connect(glbSignal,&GlobalSignal::getDataByChannel,this,&SensorAdaptiveWidget::parseDataByChannel);

    initTable();
    initUI();
    QtConcurrent::run([=](){ test();}); // 测试用的多线程。
}

SensorAdaptiveWidget::~SensorAdaptiveWidget()
{
    flag = false;
    fileHandleThread->quit();
    delete ui;
}

void SensorAdaptiveWidget::initTable()
{
    m_pStandardItemModel = new QStandardItemModel(this);
    m_pItemSelectModel   = new QItemSelectionModel(m_pStandardItemModel);
    ui->tableView->setModel(m_pStandardItemModel);
    ui->tableView->setSelectionModel(m_pItemSelectModel);
    ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
    ui->tableView->setMinimumSize(50, 25);
    // ui->tableView->verticalHeader()->hide();
    // ui->tableView->horizontalHeader()->show();

    QStringList columnTitles;  // 名字,数值,单位,按键
    columnTitles << "名字" << "数据" << "单位" << "删除" << "详情" << "导出文件";
    m_pStandardItemModel->setHorizontalHeaderLabels(columnTitles);
    // connect(m_pStandardItemModel, &QStandardItemModel::dataChanged, this, &SensorAdaptiveWidget::onTableDataChanged);

}

void SensorAdaptiveWidget::initUI()
{
    // 添加采集项的按钮事件:
    connect(ui->pBtn_addItem,&QPushButton::clicked,this,[=](){
        // #弹出对话框,填写信息
        AskContentDialog askDialog(this);

        if(askDialog.exec() == QDialog::Accepted)
        {
            // qDebug()<<"ok";

            // #获取对话框信息
            AskDialogContent content = askDialog.getContent();

            // 准备存储:
            RecvDataListStruct* recvDataStruct = new RecvDataListStruct;
            fileHandleWorker.getRecvDataListVector()->append(recvDataStruct);
            m_fileNum++;

            // 调试:
            // qDebug()<<content.name;
            // qDebug()<<content.unit;
            // qDebug()<<content.devNum;
            // qDebug()<<content.devDes;
            // qDebug()<<content.channelNum;
            // qDebug()<<content.channelDes;
            // qDebug()<<content.min;
            // qDebug()<<content.max;
            // qDebug()<<"------------------";

            // #将tableview增加一行
            int currCount = m_pStandardItemModel->rowCount();
            m_pStandardItemModel->setItem(currCount,0,new QStandardItem(content.name)); //名称
            m_pStandardItemModel->setItem(currCount,1,new QStandardItem("0.0"));        //数值
            m_pStandardItemModel->setItem(currCount,2,new QStandardItem(content.unit)); //单位
            QPersistentModelIndex theIndex(m_pStandardItemModel->index(currCount, 0));

            // 绑定数据变动信号:接收到指定设备和通道的信号数据
            connect(glbSignal,&GlobalSignal::getDataByDevNumChannel,this, [=](const int DevNum, int ChannelNum, double currVolt){
                if(DevNum==content.devNum && ChannelNum==content.channelNum){
                    // qDebug()<<QString("get signal : DevNum:%1, ChannelNum:%2,  currVolt:%3").arg(DevNum).arg(ChannelNum).arg(currVolt);
                    // qDebug()<<QString("currContent: devNum:%1, channelNum:%2").arg(content.devNum).arg(content.channelNum);

                    // 换算公式:
                    double voltRangeMax = 0.0;
                    switch (content.voltRangeNum) {
                        case 0:
                            voltRangeMax = 2.5;
                            break;
                        case 1:
                            voltRangeMax = 5;
                            break;
                        case 2:
                            voltRangeMax = 10;
                            break;
                        case 3:
                            voltRangeMax = 20;
                            break;
                    default:
                        voltRangeMax = 10;
                        break;
                    }
                    //当前实际值 = (当前电压 / 电压量程差)* (传感器量程差) + 传感器量程下限
                    double currValue = (currVolt/voltRangeMax)*(content.max - content.min)+content.min;
                    QStandardItem *item = new QStandardItem(QString::number(currValue,'f',2));
                    // 原始值:
                    // QStandardItem *item = new QStandardItem(QString::number(currVolt));

                    m_pStandardItemModel->setItem(theIndex.row(),1, item);

                    // 存储数据,写数据:
                    QDateTime currDateTime = QDateTime::currentDateTime();
                    qint64 currTimeStamp = currDateTime.currentSecsSinceEpoch();
                    int temp_count = recvDataStruct->count;
                    recvDataStruct->getNumList().append(temp_count);
                    recvDataStruct->getDataList().append(QString::number(currValue, 'f', 2).toDouble());
                    recvDataStruct->getTimeStampList().append(currTimeStamp);
                    recvDataStruct->count++;
                }
            });

            // #填到tableview中
            QPushButton *button1 = new QPushButton("删除");
            button1->setProperty("id", m_itemIndex);
            QPushButton *button2 = new QPushButton("详情");
            button2->setProperty("id", m_itemIndex);
            QPushButton *button3 = new QPushButton("导出");
            button3->setProperty("id", m_itemIndex);
            m_itemIndex++;

            // 绑定按钮事件:
            connect(button1, &QPushButton::clicked, [=](){
                m_pStandardItemModel->removeRow(theIndex.row());
                //disconnect();
            }); // 删除按钮

            connect(button2, &QPushButton::clicked, this, [=](){
                AskContentDialog desDialog;
                desDialog.loadContentToShow(content);
                desDialog.show();
                desDialog.exec();
            }); // 详情按钮

            connect(button3, &QPushButton::clicked, this, [=](){
                QMetaObject::invokeMethod(&fileHandleWorker,"writeCsv",Qt::DirectConnection, Q_ARG(QString, QString("D:/%1_dev%2_ch%3.csv").arg(content.name).arg(content.devNum).arg(currCount)),Q_ARG(int, currCount));
            }); // 导出按钮

            ui->tableView->setIndexWidget(m_pStandardItemModel->index(theIndex.row(),3),button1);
            ui->tableView->setIndexWidget(m_pStandardItemModel->index(theIndex.row(),4),button2);
            ui->tableView->setIndexWidget(m_pStandardItemModel->index(theIndex.row(),5),button3);
        }

    });
}

void SensorAdaptiveWidget::test()
{
    int num = 6000;
    while (flag && num--) {
        emit glbSignal->getDataByDevNumChannel(0,num % 16,1.0*(qrand()%10000)/1000.0);
        QThread::msleep(20);
    }
}

弹出对话框类

界面:

头文件:

#ifndef ASKCONTENTDIALOG_H
#define ASKCONTENTDIALOG_H

#include <QDialog>
#include <QString>
#include "askDialogStruct.h"

namespace Ui {
class AskContentDialog;
}

class AskContentDialog : public QDialog
{
    Q_OBJECT

public:
    explicit AskContentDialog(QWidget *parent = nullptr);
    ~AskContentDialog();
    AskDialogContent getContent();    // 获取数据结构体
    void loadContentToShow(AskDialogContent content);

private:
    Ui::AskContentDialog *ui;
};

#endif // ASKCONTENTDIALOG_H

cpp文件:

#include "askContentDialog.h"
#include "ui_askContentDialog.h"

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

    // 设置对话框标题
    setWindowTitle("新采集项对话框");
}

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

AskDialogContent AskContentDialog::getContent()
{
    AskDialogContent content;

    content.name = ui->lineEdit_name->text();
    content.unit = ui->lineEdit_unit->text();
    content.devNum = ui->comboBox_dev->currentIndex();
    content.devDes = ui->comboBox_dev->currentText();
    content.channelNum = ui->comboBox_channelNum->currentIndex();
    content.channelDes = ui->comboBox_channelNum->currentText();
    content.voltRangeNum = ui->comboBox_VoltRange->currentIndex();
    content.voltRangeDes = ui->comboBox_VoltRange->currentText();
    content.min = ui->doubleSpinBox_min->value();
    content.max = ui->doubleSpinBox_max->value();

    return content;
}

void AskContentDialog::loadContentToShow(AskDialogContent content)
{
    ui->label->setText("配置查看");
    ui->buttonBox->setVisible(false);
    ui->lineEdit_name->setText(content.name);
    ui->lineEdit_unit->setText(content.unit);
    ui->comboBox_dev->setCurrentIndex(content.devNum);
    ui->comboBox_channelNum->setCurrentIndex(content.channelNum);
    ui->comboBox_VoltRange->setCurrentIndex(content.voltRangeNum);
    ui->doubleSpinBox_min->setValue(content.min);
    ui->doubleSpinBox_max->setValue(content.max);
}

源码:

QT自定义量程上位机官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐