QT串口温湿度控制系统

QT串口温湿度控制系统

基于QT的串口温湿度控制系统,包含实时数据显示、历史曲线、阈值报警和设备控制功能。

系统架构

+-----------------------+
|     用户界面层         |
| - 主控制窗口          |
| - 数据可视化          |
| - 参数设置            |
+-----------------------+
           |
           v
+-----------------------+
|     业务逻辑层         |
| - 串口通信管理        |
| - 数据处理与分析      |
| - 报警管理            |
| - 设备控制逻辑        |
+-----------------------+
           |
           v
+-----------------------+
|     硬件抽象层         |
| - 串口通信接口        |
| - 传感器数据解析      |
| - 控制命令生成        |
+-----------------------+

完整代码实现

主窗口类 (mainwindow.h)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QChartView>
#include <QLineSeries>
#include <QValueAxis>
#include <QTimer>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QSystemTrayIcon>
#include <QCloseEvent>

QT_CHARTS_USE_NAMESPACE

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

protected:
    void closeEvent(QCloseEvent *event) override;

private slots:
    void on_btnConnect_clicked();
    void on_btnDisconnect_clicked();
    void on_btnRefreshPorts_clicked();
    void on_btnSend_clicked();
    void on_btnClearLog_clicked();
    void on_btnStartLogging_clicked();
    void on_btnStopLogging_clicked();
    void on_btnSetThresholds_clicked();
    void on_chkAutoControl_toggled(bool checked);
    void on_chkSoundAlarm_toggled(bool checked);
    void on_chkEmailAlert_toggled(bool checked);
    
    void readSerialData();
    void handleSerialError(QSerialPort::SerialPortError error);
    void updateConnectionStatus();
    void updateDataDisplay();
    void checkThresholds();
    void controlDevice(const QString &device, bool state);
    void logDataToFile();
    void updateCharts();
    void showAboutDialog();
    void toggleWindowVisibility();
    void showNotification(const QString &title, const QString &message);

private:
    Ui::MainWindow *ui;
    
    // 串口相关
    QSerialPort *serialPort;
    QTimer *dataTimer;
    QTimer *chartUpdateTimer;
    QTimer *loggingTimer;
    
    // 数据存储
    QList<QPointF> tempData;
    QList<QPointF> humiData;
    QList<QPointF> timePoints;
    int timeCounter;
    
    // 系统状态
    bool isConnected;
    bool isLogging;
    bool autoControlEnabled;
    bool soundAlarmEnabled;
    bool emailAlertEnabled;
    
    // 配置参数
    double tempThresholdHigh;
    double tempThresholdLow;
    double humiThresholdHigh;
    double humiThresholdLow;
    QString logFilePath;
    
    // 图表
    QChart *tempChart;
    QChart *humiChart;
    QLineSeries *tempSeries;
    QLineSeries *humiSeries;
    QValueAxis *axisX;
    QValueAxis *axisYTemp;
    QValueAxis *axisYHumi;
    
    // 系统托盘
    QSystemTrayIcon *trayIcon;
    QMenu *trayMenu;
    
    // 方法
    void initUI();
    void initCharts();
    void initSerialPort();
    void initSystemTray();
    void loadSettings();
    void saveSettings();
    void updateDeviceStatus();
    void sendCommand(const QString &cmd);
    void parseData(const QString &data);
    void addLogEntry(const QString &entry);
    void showAlarm(const QString &message);
    void sendEmailAlert(const QString &message);
};

#endif // MAINWINDOW_H

主窗口实现 (mainwindow.cpp)

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QSerialPortInfo>
#include <QDebug>
#include <QDateTime>
#include <QSettings>
#include <QDesktopServices>
#include <QUrl>
#include <QStyle>
#include <QIcon>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QCloseEvent>
#include <QMessageBox>
#include <QFileDialog>
#include <QStandardPaths>
#include <QSoundEffect>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    serialPort(nullptr),
    isConnected(false),
    isLogging(false),
    autoControlEnabled(false),
    soundAlarmEnabled(true),
    emailAlertEnabled(false),
    timeCounter(0),
    tempThresholdHigh(30.0),
    tempThresholdLow(10.0),
    humiThresholdHigh(70.0),
    humiThresholdLow(30.0)
{
    ui->setupUi(this);
    
    // 初始化UI
    initUI();
    
    // 初始化图表
    initCharts();
    
    // 初始化串口
    initSerialPort();
    
    // 初始化系统托盘
    initSystemTray();
    
    // 加载设置
    loadSettings();
    
    // 设置定时器
    dataTimer = new QTimer(this);
    connect(dataTimer, &QTimer::timeout, this, &MainWindow::updateDataDisplay);
    dataTimer->start(1000); // 每秒更新一次显示
    
    chartUpdateTimer = new QTimer(this);
    connect(chartUpdateTimer, &QTimer::timeout, this, &MainWindow::updateCharts);
    chartUpdateTimer->start(5000); // 每5秒更新一次图表
    
    loggingTimer = new QTimer(this);
    connect(loggingTimer, &QTimer::timeout, this, &MainWindow::logDataToFile);
    
    // 设置初始状态
    ui->btnDisconnect->setEnabled(false);
    ui->btnSend->setEnabled(false);
    ui->actionConnect->setEnabled(true);
    ui->actionDisconnect->setEnabled(false);
    
    // 设置窗口标题
    setWindowTitle("温湿度监控系统 v1.0");
    
    // 设置样式
    qApp->setStyle("Fusion");
    QPalette palette;
    palette.setColor(QPalette::Window, QColor(53,53,53));
    palette.setColor(QPalette::WindowText, Qt::white);
    palette.setColor(QPalette::Base, QColor(25,25,25));
    palette.setColor(QPalette::AlternateBase, QColor(53,53,53));
    palette.setColor(QPalette::ToolTipBase, Qt::white);
    palette.setColor(QPalette::ToolTipText, Qt::white);
    palette.setColor(QPalette::Text, Qt::white);
    palette.setColor(QPalette::Button, QColor(53,53,53));
    palette.setColor(QPalette::ButtonText, Qt::white);
    palette.setColor(QPalette::BrightText, Qt::red);
    palette.setColor(QPalette::Highlight, QColor(142,45,197).lighter());
    palette.setColor(QPalette::HighlightedText, Qt::black);
    qApp->setPalette(palette);
}

MainWindow::~MainWindow()
{
    if (serialPort && serialPort->isOpen()) {
        serialPort->close();
    }
    
    saveSettings();
    
    delete ui;
}

void MainWindow::initUI()
{
    // 设置表格列宽
    ui->tableData->setColumnWidth(0, 120);
    ui->tableData->setColumnWidth(1, 100);
    ui->tableData->setColumnWidth(2, 100);
    ui->tableData->setColumnWidth(3, 100);
    
    // 设置状态栏
    ui->statusBar->addPermanentWidget(ui->lblStatus);
    ui->lblStatus->setText("未连接");
    
    // 设置按钮图标
    ui->btnConnect->setIcon(style()->standardIcon(QStyle::SP_DialogOkButton));
    ui->btnDisconnect->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
    ui->btnRefreshPorts->setIcon(style()->standardIcon(QStyle::SP_BrowserReload));
    ui->btnSend->setIcon(style()->standardIcon(QStyle::SP_ArrowRight));
    ui->btnClearLog->setIcon(style()->standardIcon(QStyle::SP_DialogResetButton));
    ui->btnStartLogging->setIcon(style()->standardIcon(QStyle::SP_DriveFDIcon));
    ui->btnStopLogging->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
    
    // 设置复选框状态
    ui->chkAutoControl->setChecked(autoControlEnabled);
    ui->chkSoundAlarm->setChecked(soundAlarmEnabled);
    ui->chkEmailAlert->setChecked(emailAlertEnabled);
    
    // 设置阈值输入框
    ui->spinTempHigh->setValue(tempThresholdHigh);
    ui->spinTempLow->setValue(tempThresholdLow);
    ui->spinHumiHigh->setValue(humiThresholdHigh);
    ui->spinHumiLow->setValue(humiThresholdLow);
    
    // 初始化表格
    QStringList headers;
    headers << "时间" << "温度(℃)" << "湿度(%)" << "状态";
    ui->tableData->setHorizontalHeaderLabels(headers);
    ui->tableData->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    
    // 初始化日志文本框
    ui->txtLog->setReadOnly(true);
    ui->txtLog->setStyleSheet("background-color: #1e1e1e; color: #dcdcdc; font-family: Consolas;");
    
    // 添加菜单动作
    QAction *aboutAction = new QAction("关于", this);
    connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutDialog);
    ui->menuHelp->addAction(aboutAction);
}

void MainWindow::initCharts()
{
    // 创建温度图表
    tempSeries = new QLineSeries();
    tempSeries->setName("温度(℃)");
    tempSeries->setColor(Qt::red);
    
    tempChart = new QChart();
    tempChart->addSeries(tempSeries);
    tempChart->setTitle("温度变化曲线");
    tempChart->legend()->hide();
    tempChart->setTheme(QChart::ChartThemeDark);
    
    axisX = new QValueAxis;
    axisX->setRange(0, 60); // 显示最近60个点
    axisX->setLabelFormat("%d");
    axisX->setTitleText("时间(分钟)");
    
    axisYTemp = new QValueAxis;
    axisYTemp->setRange(0, 50);
    axisYTemp->setLabelFormat("%.1f");
    axisYTemp->setTitleText("温度(℃)");
    
    tempChart->addAxis(axisX, Qt::AlignBottom);
    tempChart->addAxis(axisYTemp, Qt::AlignLeft);
    tempSeries->attachAxis(axisX);
    tempSeries->attachAxis(axisYTemp);
    
    ui->chartTemp->setChart(tempChart);
    ui->chartTemp->setRenderHint(QPainter::Antialiasing);
    
    // 创建湿度图表
    humiSeries = new QLineSeries();
    humiSeries->setName("湿度(%)");
    humiSeries->setColor(Qt::blue);
    
    humiChart = new QChart();
    humiChart->addSeries(humiSeries);
    humiChart->setTitle("湿度变化曲线");
    humiChart->legend()->hide();
    humiChart->setTheme(QChart::ChartThemeDark);
    
    axisYHumi = new QValueAxis;
    axisYHumi->setRange(0, 100);
    axisYHumi->setLabelFormat("%.1f");
    axisYHumi->setTitleText("湿度(%)");
    
    humiChart->addAxis(axisX, Qt::AlignBottom);
    humiChart->addAxis(axisYHumi, Qt::AlignLeft);
    humiSeries->attachAxis(axisX);
    humiSeries->attachAxis(axisYHumi);
    
    ui->chartHumi->setChart(humiChart);
    ui->chartHumi->setRenderHint(QPainter::Antialiasing);
}

void MainWindow::initSerialPort()
{
    serialPort = new QSerialPort(this);
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::readSerialData);
    connect(serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleSerialError);
    
    // 刷新串口列表
    on_btnRefreshPorts_clicked();
}

void MainWindow::initSystemTray()
{
    trayIcon = new QSystemTrayIcon(this);
    trayIcon->setIcon(QIcon(":/icons/temperature.png"));
    trayIcon->setToolTip("温湿度监控系统");
    
    trayMenu = new QMenu(this);
    QAction *showAction = trayMenu->addAction("显示窗口");
    connect(showAction, &QAction::triggered, this, &MainWindow::toggleWindowVisibility);
    
    QAction *connectAction = trayMenu->addAction("连接");
    connect(connectAction, &QAction::triggered, this, &MainWindow::on_btnConnect_clicked);
    
    QAction *disconnectAction = trayMenu->addAction("断开连接");
    connect(disconnectAction, &QAction::triggered, this, &MainWindow::on_btnDisconnect_clicked);
    
    trayMenu->addSeparator();
    
    QAction *exitAction = trayMenu->addAction("退出");
    connect(exitAction, &QAction::triggered, qApp, &QApplication::quit);
    
    trayIcon->setContextMenu(trayMenu);
    trayIcon->show();
    
    connect(trayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
        if (reason == QSystemTrayIcon::DoubleClick) {
            toggleWindowVisibility();
        }
    });
}

void MainWindow::loadSettings()
{
    QSettings settings("ThermoMonitor", "SerialMonitor");
    
    // 串口设置
    ui->cmbPort->setCurrentText(settings.value("port", "").toString());
    ui->cmbBaud->setCurrentText(settings.value("baud", "9600").toString());
    ui->cmbDataBits->setCurrentText(settings.value("databits", "8").toString());
    ui->cmbParity->setCurrentText(settings.value("parity", "无").toString());
    ui->cmbStopBits->setCurrentText(settings.value("stopbits", "1").toString());
    
    // 阈值设置
    tempThresholdHigh = settings.value("tempHigh", 30.0).toDouble();
    tempThresholdLow = settings.value("tempLow", 10.0).toDouble();
    humiThresholdHigh = settings.value("humiHigh", 70.0).toDouble();
    humiThresholdLow = settings.value("humiLow", 30.0).toDouble();
    
    // 其他选项
    autoControlEnabled = settings.value("autoControl", false).toBool();
    soundAlarmEnabled = settings.value("soundAlarm", true).toBool();
    emailAlertEnabled = settings.value("emailAlert", false).toBool();
    logFilePath = settings.value("logFilePath", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/thermo_log.csv").toString();
    
    ui->spinTempHigh->setValue(tempThresholdHigh);
    ui->spinTempLow->setValue(tempThresholdLow);
    ui->spinHumiHigh->setValue(humiThresholdHigh);
    ui->spinHumiLow->setValue(humiThresholdLow);
}

void MainWindow::saveSettings()
{
    QSettings settings("ThermoMonitor", "SerialMonitor");
    
    // 串口设置
    settings.setValue("port", ui->cmbPort->currentText());
    settings.setValue("baud", ui->cmbBaud->currentText());
    settings.setValue("databits", ui->cmbDataBits->currentText());
    settings.setValue("parity", ui->cmbParity->currentText());
    settings.setValue("stopbits", ui->cmbStopBits->currentText());
    
    // 阈值设置
    settings.setValue("tempHigh", tempThresholdHigh);
    settings.setValue("tempLow", tempThresholdLow);
    settings.setValue("humiHigh", humiThresholdHigh);
    settings.setValue("humiLow", humiThresholdLow);
    
    // 其他选项
    settings.setValue("autoControl", autoControlEnabled);
    settings.setValue("soundAlarm", soundAlarmEnabled);
    settings.setValue("emailAlert", emailAlertEnabled);
    settings.setValue("logFilePath", logFilePath);
}

void MainWindow::on_btnConnect_clicked()
{
    if (ui->cmbPort->currentText().isEmpty()) {
        QMessageBox::warning(this, "连接错误", "请选择串口");
        return;
    }
    
    // 配置串口
    serialPort->setPortName(ui->cmbPort->currentText());
    serialPort->setBaudRate(ui->cmbBaud->currentText().toInt());
    
    // 设置数据位
    if (ui->cmbDataBits->currentText() == "5") serialPort->setDataBits(QSerialPort::Data5);
    else if (ui->cmbDataBits->currentText() == "6") serialPort->setDataBits(QSerialPort::Data6);
    else if (ui->cmbDataBits->currentText() == "7") serialPort->setDataBits(QSerialPort::Data7);
    else serialPort->setDataBits(QSerialPort::Data8);
    
    // 设置校验位
    if (ui->cmbParity->currentText() == "偶校验") serialPort->setParity(QSerialPort::EvenParity);
    else if (ui->cmbParity->currentText() == "奇校验") serialPort->setParity(QSerialPort::OddParity);
    else if (ui->cmbParity->currentText() == "空格校验") serialPort->setParity(QSerialPort::SpaceParity);
    else serialPort->setParity(QSerialPort::NoParity);
    
    // 设置停止位
    if (ui->cmbStopBits->currentText() == "1.5") serialPort->setStopBits(QSerialPort::OneAndHalfStop);
    else if (ui->cmbStopBits->currentText() == "2") serialPort->setStopBits(QSerialPort::TwoStop);
    else serialPort->setStopBits(QSerialPort::OneStop);
    
    // 打开串口
    if (serialPort->open(QIODevice::ReadWrite)) {
        isConnected = true;
        ui->btnConnect->setEnabled(false);
        ui->btnDisconnect->setEnabled(true);
        ui->btnSend->setEnabled(true);
        ui->actionConnect->setEnabled(false);
        ui->actionDisconnect->setEnabled(true);
        
        ui->lblStatus->setText("已连接: " + ui->cmbPort->currentText());
        ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + "串口连接成功");
        
        // 发送初始化命令
        sendCommand("INIT");
    } else {
        QMessageBox::critical(this, "连接错误", "无法打开串口: " + serialPort->errorString());
        ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + "串口连接失败: " + serialPort->errorString());
    }
}

void MainWindow::on_btnDisconnect_clicked()
{
    if (serialPort && serialPort->isOpen()) {
        serialPort->close();
        isConnected = false;
        ui->btnConnect->setEnabled(true);
        ui->btnDisconnect->setEnabled(false);
        ui->btnSend->setEnabled(false);
        ui->actionConnect->setEnabled(true);
        ui->actionDisconnect->setEnabled(false);
        
        ui->lblStatus->setText("未连接");
        ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + "串口已断开");
    }
}

void MainWindow::on_btnRefreshPorts_clicked()
{
    ui->cmbPort->clear();
    const auto ports = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &port : ports) {
        ui->cmbPort->addItem(port.portName());
    }
}

void MainWindow::on_btnSend_clicked()
{
    if (!isConnected || !serialPort->isOpen()) {
        QMessageBox::warning(this, "发送错误", "串口未连接");
        return;
    }
    
    QString command = ui->txtCommand->text().trimmed();
    if (command.isEmpty()) {
        QMessageBox::warning(this, "发送错误", "命令不能为空");
        return;
    }
    
    sendCommand(command);
    ui->txtCommand->clear();
}

void MainWindow::sendCommand(const QString &cmd)
{
    if (serialPort && serialPort->isOpen()) {
        serialPort->write((cmd + "\r\n").toUtf8());
        ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + "发送: " + cmd);
    }
}

void MainWindow::readSerialData()
{
    if (!serialPort) return;
    
    QByteArray data = serialPort->readAll();
    while (serialPort->waitForReadyRead(10)) {
        data += serialPort->readAll();
    }
    
    QString receivedData = QString::fromUtf8(data).trimmed();
    if (receivedData.isEmpty()) return;
    
    ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + "接收: " + receivedData);
    
    // 解析数据
    parseData(receivedData);
}

void MainWindow::parseData(const QString &data)
{
    // 示例数据格式: "TEMP:25.5,HUMI:60.0,STATUS:NORMAL"
    QStringList parts = data.split(',');
    double temp = 0.0;
    double humi = 0.0;
    QString status = "未知";
    
    for (const QString &part : parts) {
        if (part.startsWith("TEMP:")) {
            temp = part.mid(5).toDouble();
        } else if (part.startsWith("HUMI:")) {
            humi = part.mid(5).toDouble();
        } else if (part.startsWith("STATUS:")) {
            status = part.mid(7);
        }
    }
    
    // 更新UI显示
    ui->lcdTemp->display(QString::number(temp, 'f', 1));
    ui->lcdHumi->display(QString::number(humi, 'f', 1));
    ui->lblStatusText->setText(status);
    
    // 更新状态指示灯
    if (status == "NORMAL") {
        ui->lblTempStatus->setPixmap(QPixmap(":/icons/green_led.png"));
        ui->lblHumiStatus->setPixmap(QPixmap(":/icons/green_led.png"));
    } else if (status == "WARNING") {
        ui->lblTempStatus->setPixmap(QPixmap(":/icons/yellow_led.png"));
        ui->lblHumiStatus->setPixmap(QPixmap(":/icons/yellow_led.png"));
    } else if (status == "ALARM") {
        ui->lblTempStatus->setPixmap(QPixmap(":/icons/red_led.png"));
        ui->lblHumiStatus->setPixmap(QPixmap(":/icons/red_led.png"));
    }
    
    // 添加到表格
    int row = ui->tableData->rowCount();
    ui->tableData->insertRow(row);
    ui->tableData->setItem(row, 0, new QTableWidgetItem(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")));
    ui->tableData->setItem(row, 1, new QTableWidgetItem(QString::number(temp, 'f', 1)));
    ui->tableData->setItem(row, 2, new QTableWidgetItem(QString::number(humi, 'f', 1)));
    ui->tableData->setItem(row, 3, new QTableWidgetItem(status));
    
    // 滚动到最后一行
    ui->tableData->scrollToBottom();
    
    // 添加到数据序列
    tempData.append(QPointF(timeCounter, temp));
    humiData.append(QPointF(timeCounter, humi));
    timePoints.append(QPointF(timeCounter, 0));
    timeCounter++;
    
    // 限制数据点数量
    if (tempData.size() > 100) {
        tempData.removeFirst();
        humiData.removeFirst();
        timePoints.removeFirst();
    }
    
    // 检查阈值
    checkThresholds();
    
    // 自动控制
    if (autoControlEnabled) {
        if (temp > tempThresholdHigh) {
            controlDevice("COOLER", true);
            controlDevice("HEATER", false);
        } else if (temp < tempThresholdLow) {
            controlDevice("HEATER", true);
            controlDevice("COOLER", false);
        } else {
            controlDevice("COOLER", false);
            controlDevice("HEATER", false);
        }
        
        if (humi > humiThresholdHigh) {
            controlDevice("HUMIDIFIER", false);
            controlDevice("DEHUMIDIFIER", true);
        } else if (humi < humiThresholdLow) {
            controlDevice("HUMIDIFIER", true);
            controlDevice("DEHUMIDIFIER", false);
        } else {
            controlDevice("HUMIDIFIER", false);
            controlDevice("DEHUMIDIFIER", false);
        }
    }
}

void MainWindow::checkThresholds()
{
    double temp = ui->lcdTemp->value();
    double humi = ui->lcdHumi->value();
    QString alarmMsg;
    
    if (temp > tempThresholdHigh) {
        alarmMsg = QString("温度过高: %1℃ > %2℃").arg(temp).arg(tempThresholdHigh);
        ui->lblTempStatus->setPixmap(QPixmap(":/icons/red_led.png"));
        if (soundAlarmEnabled) {
            QSoundEffect::play(":sounds/alarm.wav");
        }
        if (emailAlertEnabled) {
            sendEmailAlert(alarmMsg);
        }
    } else if (temp < tempThresholdLow) {
        alarmMsg = QString("温度过低: %1℃ < %2℃").arg(temp).arg(tempThresholdLow);
        ui->lblTempStatus->setPixmap(QPixmap(":/icons/blue_led.png"));
        if (soundAlarmEnabled) {
            QSoundEffect::play(":sounds/alarm.wav");
        }
        if (emailAlertEnabled) {
            sendEmailAlert(alarmMsg);
        }
    }
    
    if (humi > humiThresholdHigh) {
        alarmMsg = QString("湿度过高: %1%% > %2%%").arg(humi).arg(humiThresholdHigh);
        ui->lblHumiStatus->setPixmap(QPixmap(":/icons/red_led.png"));
        if (soundAlarmEnabled) {
            QSoundEffect::play(":sounds/alarm.wav");
        }
        if (emailAlertEnabled) {
            sendEmailAlert(alarmMsg);
        }
    } else if (humi < humiThresholdLow) {
        alarmMsg = QString("湿度过低: %1%% < %2%%").arg(humi).arg(humiThresholdLow);
        ui->lblHumiStatus->setPixmap(QPixmap(":/icons/blue_led.png"));
        if (soundAlarmEnabled) {
            QSoundEffect::play(":sounds/alarm.wav");
        }
        if (emailAlertEnabled) {
            sendEmailAlert(alarmMsg);
        }
    }
    
    if (!alarmMsg.isEmpty()) {
        showAlarm(alarmMsg);
        addLogEntry(alarmMsg);
    }
}

void MainWindow::controlDevice(const QString &device, bool state)
{
    QString cmd = device.toUpper() + ":" + (state ? "ON" : "OFF");
    sendCommand(cmd);
    
    // 更新设备状态显示
    if (device == "HEATER") {
        ui->lblHeaterStatus->setText(state ? "开启" : "关闭");
        ui->lblHeaterStatus->setStyleSheet(state ? "color: red;" : "color: green;");
    } else if (device == "COOLER") {
        ui->lblCoolerStatus->setText(state ? "开启" : "关闭");
        ui->lblCoolerStatus->setStyleSheet(state ? "color: red;" : "color: green;");
    } else if (device == "HUMIDIFIER") {
        ui->lblHumidifierStatus->setText(state ? "开启" : "关闭");
        ui->lblHumidifierStatus->setStyleSheet(state ? "color: red;" : "color: green;");
    } else if (device == "DEHUMIDIFIER") {
        ui->lblDehumidifierStatus->setText(state ? "开启" : "关闭");
        ui->lblDehumidifierStatus->setStyleSheet(state ? "color: red;" : "color: green;");
    }
}

void MainWindow::on_btnSetThresholds_clicked()
{
    tempThresholdHigh = ui->spinTempHigh->value();
    tempThresholdLow = ui->spinTempLow->value();
    humiThresholdHigh = ui->spinHumiHigh->value();
    humiThresholdLow = ui->spinHumiLow->value();
    
    saveSettings();
    
    QMessageBox::information(this, "设置成功", "阈值已更新");
    addLogEntry("阈值更新: 温度(" + QString::number(tempThresholdLow) + "~" + 
                QString::number(tempThresholdHigh) + "℃), 湿度(" + 
                QString::number(humiThresholdLow) + "~" + 
                QString::number(humiThresholdHigh) + "%)");
}

void MainWindow::on_chkAutoControl_toggled(bool checked)
{
    autoControlEnabled = checked;
    saveSettings();
    addLogEntry(QString("自动控制已%1").arg(checked ? "启用" : "禁用"));
}

void MainWindow::on_chkSoundAlarm_toggled(bool checked)
{
    soundAlarmEnabled = checked;
    saveSettings();
    addLogEntry(QString("声音报警已%1").arg(checked ? "启用" : "禁用"));
}

void MainWindow::on_chkEmailAlert_toggled(bool checked)
{
    emailAlertEnabled = checked;
    saveSettings();
    addLogEntry(QString("邮件报警已%1").arg(checked ? "启用" : "禁用"));
}

void MainWindow::on_btnStartLogging_clicked()
{
    if (isLogging) return;
    
    logFilePath = QFileDialog::getSaveFileName(this, "选择日志文件", 
                                              logFilePath, 
                                              "CSV文件 (*.csv)");
    if (logFilePath.isEmpty()) return;
    
    // 创建文件并写入标题行
    QFile file(logFilePath);
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream stream(&file);
        stream << "时间,温度(℃),湿度(%)\n";
        file.close();
    }
    
    isLogging = true;
    loggingTimer->start(60000); // 每分钟记录一次
    ui->btnStartLogging->setEnabled(false);
    ui->btnStopLogging->setEnabled(true);
    
    addLogEntry("开始记录日志到: " + logFilePath);
}

void MainWindow::on_btnStopLogging_clicked()
{
    if (!isLogging) return;
    
    isLogging = false;
    loggingTimer->stop();
    ui->btnStartLogging->setEnabled(true);
    ui->btnStopLogging->setEnabled(false);
    
    addLogEntry("停止记录日志");
}

void MainWindow::logDataToFile()
{
    if (!isLogging || logFilePath.isEmpty()) return;
    
    QFile file(logFilePath);
    if (file.open(QIODevice::Append | QIODevice::Text)) {
        QTextStream stream(&file);
        stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << ","
               << ui->lcdTemp->value() << ","
               << ui->lcdHumi->value() << "\n";
        file.close();
    }
}

void MainWindow::updateDataDisplay()
{
    // 更新设备状态显示
    updateDeviceStatus();
}

void MainWindow::updateDeviceStatus()
{
    // 在实际应用中,这里应该从设备获取状态
    // 这里简化处理,直接显示当前状态
}

void MainWindow::updateCharts()
{
    // 更新温度图表
    tempSeries->replace(tempData);
    axisX->setRange(timeCounter - 60, timeCounter);
    axisYTemp->setRange(0, qMax(50.0, *std::max_element(tempData.constBegin(), tempData.constEnd(), 
        [](const QPointF &a, const QPointF &b) { return a.y() < b.y(); }) + 5.0));
    
    // 更新湿度图表
    humiSeries->replace(humiData);
    axisYHumi->setRange(0, 100);
}

void MainWindow::handleSerialError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::NoError) return;
    
    QString errorMsg = "串口错误: ";
    switch (error) {
    case QSerialPort::DeviceNotFoundError:
        errorMsg += "设备未找到";
        break;
    case QSerialPort::PermissionError:
        errorMsg += "权限不足";
        break;
    case QSerialPort::OpenError:
        errorMsg += "设备忙或无法打开";
        break;
    case QSerialPort::NotOpenError:
        errorMsg += "设备未打开";
        break;
    case QSerialPort::WriteError:
        errorMsg += "写入错误";
        break;
    case QSerialPort::ReadError:
        errorMsg += "读取错误";
        break;
    case QSerialPort::ResourceError:
        errorMsg += "资源不可用";
        break;
    case QSerialPort::UnsupportedOperationError:
        errorMsg += "不支持的操作";
        break;
    case QSerialPort::TimeoutError:
        errorMsg += "操作超时";
        break;
    default:
        errorMsg += "未知错误";
        break;
    }
    
    ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + errorMsg);
    showNotification("串口错误", errorMsg);
    
    // 如果是严重错误,断开连接
    if (error != QSerialPort::NoError && error != QSerialPort::TimeoutError) {
        on_btnDisconnect_clicked();
    }
}

void MainWindow::on_btnClearLog_clicked()
{
    ui->txtLog->clear();
}

void MainWindow::addLogEntry(const QString &entry)
{
    ui->txtLog->append(QDateTime::currentDateTime().toString("[hh:mm:ss] ") + entry);
}

void MainWindow::showAlarm(const QString &message)
{
    trayIcon->showMessage("温湿度报警", message, QSystemTrayIcon::Critical, 5000);
    ui->statusBar->showMessage(message, 5000);
}

void MainWindow::showNotification(const QString &title, const QString &message)
{
    trayIcon->showMessage(title, message, QSystemTrayIcon::Information, 3000);
}

void MainWindow::sendEmailAlert(const QString &message)
{
    // 在实际应用中,这里应该实现邮件发送功能
    // 这里简化处理,只记录日志
    addLogEntry("邮件报警: " + message);
}

void MainWindow::toggleWindowVisibility()
{
    if (isVisible()) {
        hide();
    } else {
        showNormal();
        activateWindow();
    }
}

void MainWindow::showAboutDialog()
{
    QMessageBox::about(this, "关于温湿度监控系统",
                     "<h3>温湿度监控系统 v1.0</h3>"
                     "<p>基于QT的串口温湿度监控系统</p>"
                     "<p>功能:</p>"
                     "<ul>"
                     "<li>串口通信与数据采集</li>"
                     "<li>实时数据显示与曲线绘制</li>"
                     "<li>阈值报警与自动控制</li>"
                     "<li>数据记录与日志管理</li>"
                     "</ul>"
                     "<p>© 2023 工业自动化实验室</p>");
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (trayIcon->isVisible()) {
        hide();
        event->ignore();
    } else {
        event->accept();
    }
}

主函数 (main.cpp)

#include "mainwindow.h"
#include <QApplication>
#include <QIcon>
#include <QTranslator>
#include <QLocale>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    // 设置应用程序信息
    QApplication::setApplicationName("温湿度监控系统");
    QApplication::setApplicationVersion("1.0");
    QApplication::setOrganizationName("工业自动化实验室");
    
    // 设置应用程序图标
    QApplication::setWindowIcon(QIcon(":/icons/temperature.png"));
    
    // 加载翻译文件(如果需要国际化)
    QTranslator translator;
    const QString locale = QLocale::system().name();
    if (translator.load(QString("thermo_") + locale, ":/translations")) {
        QApplication::installTranslator(&translator);
    }
    
    MainWindow w;
    w.show();
    
    return a.exec();
}

UI文件 (mainwindow.ui)

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1200</width>
    <height>800</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>温湿度监控系统</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <widget class="QGroupBox" name="groupBoxConnection">
      <property name="title">
       <string>串口连接</string>
      </property>
      <layout class="QGridLayout" name="gridLayout_2">
       <item row="0" column="0">
        <widget class="QLabel" name="label">
         <property name="text">
          <string>串口号:</string>
         </property>
        </widget>
       </item>
       <item row="0" column="1">
        <widget class="QComboBox" name="cmbPort"/>
       </item>
       <item row="0" column="2">
        <widget class="QPushButton" name="btnRefreshPorts">
         <property name="text">
          <string>刷新</string>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QLabel" name="label_2">
         <property name="text">
          <string>波特率:</string>
         </property>
        </widget>
       </item>
       <item row="1" column="1">
        <widget class="QComboBox" name="cmbBaud">
         <item>
          <property name="text">
           <string>9600</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>19200</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>38400</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>57600</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>115200</string>
          </property>
         </item>
        </widget>
       </item>
       <item row="2" column="0">
        <widget class="QLabel" name="label_3">
         <property name="text">
          <string>数据位:</string>
         </property>
        </widget>
       </item>
       <item row="2" column="1">
        <widget class="QComboBox" name="cmbDataBits">
         <item>
          <property name="text">
           <string>8</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>7</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>6</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>5</string>
          </property>
         </item>
        </widget>
       </item>
       <item row="3" column="0">
        <widget class="QLabel" name="label_4">
         <property name="text">
          <string>校验位:</string>
         </property>
        </widget>
       </item>
       <item row="3" column="1">
        <widget class="QComboBox" name="cmbParity">
         <item>
          <property name="text">
           <string>无</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>奇校验</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>偶校验</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>空格校验</string>
          </property>
         </item>
        </widget>
       </item>
       <item row="4" column="0">
        <widget class="QLabel" name="label_5">
         <property name="text">
          <string>停止位:</string>
         </property>
        </widget>
       </item>
       <item row="4" column="1">
        <widget class="QComboBox" name="cmbStopBits">
         <item>
          <property name="text">
           <string>1</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>1.5</string>
          </property>
         </item>
         <item>
          <property name="text">
           <string>2</string>
          </property>
         </item>
        </widget>
       </item>
       <item row="5" column="0" colspan="3">
        <layout class="QHBoxLayout" name="horizontalLayout">
         <item>
          <widget class="QPushButton" name="btnConnect">
           <property name="text">
            <string>连接</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="btnDisconnect">
           <property name="text">
            <string>断开连接</string>
           </property>
          </widget>
         </item>
        </layout>
       </item>
      </layout>
     </widget>
    </item>
    <item row="0" column="1">
     <widget class="QGroupBox" name="groupBoxData">
      <property name="title">
       <string>实时数据</string>
      </property>
      <layout class="QGridLayout" name="gridLayout_3">
       <item row="0" column="0">
        <widget class="QLabel" name="label_6">
         <property name="text">
          <string>温度:</string>
         </property>
        </widget>
       </item>
       <item row="0" column="1">
        <widget class="QLCDNumber" name="lcdTemp">
         <property name="digitCount">
          <number>5</number>
         </property>
         <property name="segmentStyle">
          <enum>QLCDNumber::Filled</enum>
         </property>
         <property name="smallDecimalPoint">
          <bool>true</bool>
         </property>
         <property name="value" stdset="0">
          <double>0.000000000000000</double>
         </property>
        </widget>
       </item>
       <item row="0" column="2">
        <widget class="QLabel" name="lblTempStatus">
         <property name="pixmap">
          <pixmap resource="resources.qrc">:/icons/gray_led.png</pixmap>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QLabel" name="label_7">
         <property name="text">
          <string>湿度:</string>
         </property>
        </widget>
       </item>
       <item row="1" column="1">
        <widget class="QLCDNumber" name="lcdHumi">
         <property name="digitCount">
          <number>5</number>
         </property>
         <property name="segmentStyle">
          <enum>QLCDNumber::Filled</enum>
         </property>
         <property name="smallDecimalPoint">
          <bool>true</bool>
         </property>
         <property name="value" stdset="0">
          <double>0.000000000000000</double>
         </property>
        </widget>
       </item>
       <item row="1" column="2">
        <widget class="QLabel" name="lblHumiStatus">
         <property name="pixmap">
          <pixmap resource="resources.qrc">:/icons/gray_led.png</pixmap>
         </property>
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item row="2" column="0">
        <widget class="QLabel" name="label_8">
         <property name="text">
          <string>状态:</string>
         </property>
        </widget>
       </item>
       <item row="2" column="1">
        <widget class="QLabel" name="lblStatusText">
         <property name="text">
          <string>未连接</string>
         </property>
        </widget>
       </item>
       <item row="3" column="0" colspan="3">
        <widget class="QLabel" name="labelDeviceStatus">
         <property name="text">
          <string>设备状态:</string>
         </property>
        </widget>
       </item>
       <item row="4" column="0">
        <widget class="QLabel" name="label_9">
         <property name="text">
          <string>加热器:</string>
         </property>
        </widget>
       </item>
       <item row="4" column="1">
        <widget class="QLabel" name="lblHeaterStatus">
         <property name="text">
          <string>关闭</string>
         </property>
         <property name="styleSheet">
          <string notr="true">color: green;</string>
         </property>
        </widget>
       </item>
       <item row="5" column="0">
        <widget class="QLabel" name="label_10">
         <property name="text">
          <string>冷却器:</string>
         </property>
        </widget>
       </item>
       <item row="5" column="1">
        <widget class="QLabel" name="lblCoolerStatus">
         <property name="text">
          <string>关闭</string>
         </property>
         <property name="styleSheet">
          <string notr="true">color: green;</string>
         </property>
        </widget>
       </item>
       <item row="6" column="0">
        <widget class="QLabel" name="label_11">
         <property name="text">
          <string>加湿器:</string>
         </property>
        </widget>
       </item>
       <item row="6" column="1">
        <widget class="QLabel" name="lblHumidifierStatus">
         <property name="text">
          <string>关闭</string>
         </property>
         <property name="styleSheet">
          <string notr="true">color: green;</string>
         </property>
        </widget>
       </item>
       <item row="7" column="0">
        <widget class="QLabel" name="label_12">
         <property name="text">
          <string>除湿器:</string>
         </property>
        </widget>
       </item>
       <item row="7" column="1">
        <widget class="QLabel" name="lblDehumidifierStatus">
         <property name="text">
          <string>关闭</string>
         </property>
         <property name="styleSheet">
          <string notr="true">color: green;</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item row="1" column="0" colspan="2">
     <widget class="QTabWidget" name="tabWidget">
      <widget class="QWidget" name="tabDashboard">
       <attribute name="title">
        <string>仪表盘</string>
       </attribute>
       <layout class="QGridLayout" name="gridLayout_4">
        <item row="0" column="0">
         <widget class="QChartView" name="chartTemp"/>
        </item>
        <item row="0" column="1">
         <widget class="QChartView" name="chartHumi"/>
        </item>
        <item row="1" column="0" colspan="2">
         <widget class="QTableWidget" name="tableData">
          <column>
           <property name="text">
            <string>时间</string>
           </property>
          </column>
          <column>
           <property name="text">
            <string>温度(℃)</string>
           </property>
          </column>
          <column>
           <property name="text">
            <string>湿度(%)</string>
           </property>
          </column>
          <column>
           <property name="text">
            <string>状态</string>
           </property>
          </column>
         </widget>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="tabControl">
       <attribute name="title">
        <string>控制</string>
       </attribute>
       <layout class="QGridLayout" name="gridLayout_5">
        <item row="0" column="0">
         <widget class="QGroupBox" name="groupBoxThresholds">
          <property name="title">
           <string>阈值设置</string>
          </property>
          <layout class="QGridLayout" name="gridLayout_6">
           <item row="0" column="0">
            <widget class="QLabel" name="label_13">
             <property name="text">
              <string>温度上限(℃):</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1">
            <widget class="QDoubleSpinBox" name="spinTempHigh">
             <property name="decimals">
              <number>1</number>
             </property>
             <property name="minimum">
              <double>-50.000000000000000</double>
             </property>
             <property name="maximum">
              <double>100.000000000000000</double>
             </property>
             <property name="singleStep">
              <double>0.500000000000000</double>
             </property>
             <property name="value" stdset="0">
              <double>30.000000000000000</double>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="label_14">
             <property name="text">
              <string>温度下限(℃):</string>
             </property>
            </widget>
           </item>
           <item row="1" column="1">
            <widget class="QDoubleSpinBox" name="spinTempLow">
             <property name="decimals">
              <number>1</number>
             </property>
             <property name="minimum">
              <double>-50.000000000000000</double>
             </property>
             <property name="maximum">
              <double>100.000000000000000</double>
             </property>
             <property name="singleStep">
              <double>0.500000000000000</double>
             </property>
             <property name="value" stdset="0">
              <double>10.000000000000000</double>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QLabel" name="label_15">
             <property name="text">
              <string>湿度上限(%):</string>
             </property>
            </widget>
           </item>
           <item row="2" column="1">
            <widget class="QDoubleSpinBox" name="spinHumiHigh">
             <property name="decimals">
              <number>1</number>
             </property>
             <property name="minimum">
              <double>0.000000000000000</double>
             </property>
             <property name="maximum">
              <double>100.000000000000000</double>
             </property>
             <property name="singleStep">
              <double>1.000000000000000</double>
             </property>
             <property name="value" stdset="0">
              <double>70.000000000000000</double>
             </property>
            </widget>
           </item>
           <item row="3" column="0">
            <widget class="QLabel" name="label_16">
             <property name="text">
              <string>湿度下限(%):</string>
             </property>
            </widget>
           </item>
           <item row="3" column="1">
            <widget class="QDoubleSpinBox" name="spinHumiLow">
             <property name="decimals">
              <number>1</number>
             </property>
             <property name="minimum">
              <double>0.000000000000000</double>
             </property>
             <property name="maximum">
              <double>100.000000000000000</double>
             </property>
             <property name="singleStep">
              <double>1.000000000000000</double>
             </property>
             <property name="value" stdset="0">
              <double>30.000000000000000</double>
             </property>
            </widget>
           </item>
           <item row="4" column="0" colspan="2">
            <widget class="QPushButton" name="btnSetThresholds">
             <property name="text">
              <string>应用阈值</string>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
        <item row="0" column="1">
         <widget class="QGroupBox" name="groupBoxAutomation">
          <property name="title">
           <string>自动控制</string>
          </property>
          <layout class="QGridLayout" name="gridLayout_7">
           <item row="0" column="0">
            <widget class="QCheckBox" name="chkAutoControl">
             <property name="text">
              <string>启用自动控制</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QCheckBox" name="chkSoundAlarm">
             <property name="text">
              <string>声音报警</string>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QCheckBox" name="chkEmailAlert">
             <property name="text">
              <string>邮件报警</string>
             </property>
            </widget>
           </item>
           <item row="3" column="0">
            <widget class="QLabel" name="label_17">
             <property name="text">
              <string>手动控制:</string>
             </property>
            </widget>
           </item>
           <item row="4" column="0">
            <widget class="QPushButton" name="btnHeaterOn">
             <property name="text">
              <string>加热器开</string>
             </property>
            </widget>
           </item>
           <item row="5" column="0">
            <widget class="QPushButton" name="btnHeaterOff">
             <property name="text">
              <string>加热器关</string>
             </property>
            </widget>
           </item>
           <item row="6" column="0">
            <widget class="QPushButton" name="btnCoolerOn">
             <property name="text">
              <string>冷却器开</string>
             </property>
            </widget>
           </item>
           <item row="7" column="0">
            <widget class="QPushButton" name="btnCoolerOff">
             <property name="text">
              <string>冷却器关</string>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="tabLog">
       <attribute name="title">
        <string>日志</string>
       </attribute>
       <layout class="QGridLayout" name="gridLayout_8">
        <item row="0" column="0">
         <widget class="QTextEdit" name="txtLog"/>
        </item>
        <item row="1" column="0">
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
           <widget class="QPushButton" name="btnClearLog">
            <property name="text">
             <string>清空日志</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QPushButton" name="btnStartLogging">
            <property name="text">
             <string>开始记录</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QPushButton" name="btnStopLogging">
            <property name="text">
             <string>停止记录</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
      <widget class="QWidget" name="tabCommands">
       <attribute name="title">
        <string>命令</string>
       </attribute>
       <layout class="QGridLayout" name="gridLayout_9">
        <item row="0" column="0">
         <widget class="QLabel" name="label_18">
          <property name="text">
           <string>发送命令:</string>
          </property>
         </widget>
        </item>
        <item row="0" column="1">
         <widget class="QLineEdit" name="txtCommand"/>
        </item>
        <item row="0" column="2">
         <widget class="QPushButton" name="btnSend">
          <property name="text">
           <string>发送</string>
          </property>
         </widget>
        </item>
        <item row="1" column="0" colspan="3">
         <widget class="QTextEdit" name="txtCommandLog"/>
        </item>
       </layout>
      </widget>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QStatusBar" name="statusBar">
   <widget class="QLabel" name="lblStatus"/>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1200</width>
     <height>26</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFile">
    <property name="title">
     <string>文件</string>
    </property>
    <addaction name="actionConnect"/>
    <addaction name="actionDisconnect"/>
    <addaction name="separator"/>
    <addaction name="actionExit"/>
   </widget>
   <widget class="QMenu" name="menuHelp">
    <property name="title">
     <string>帮助</string>
    </property>
   </widget>
   <addaction name="menuFile"/>
   <addaction name="menuHelp"/>
  </widget>
  <action name="actionConnect">
   <property name="text">
    <string>连接</string>
   </property>
  </action>
  <action name="actionDisconnect">
   <property name="text">
    <string>断开连接</string>
   </property>
  </action>
  <action name="actionExit">
   <property name="text">
    <string>退出</string>
   </property>
  </action>
 </widget>
 <resources>
  <include location="resources.qrc"/>
 </resources>
 <connections/>
</ui>

系统功能说明

1. 串口通信管理

2. 数据显示与可视化

3. 阈值报警系统

4. 设备控制功能

5. 数据记录与分析

6. 系统管理

参考代码 QT 串口温湿度控制系统 www.youwenfan.com/contentcsw/122853.html

系统特点

  1. 现代化UI设计

    • 深色主题界面
    • 响应式布局
    • 直观的数据可视化
    • 自定义控件样式
  2. 强大的串口通信

    • 支持多种串口参数配置
    • 自动错误检测与恢复
    • 实时通信日志
    • 命令发送与响应显示
  3. 智能报警系统

    • 多级报警机制(警告/报警)
    • 多种报警方式(声音/视觉/通知)
    • 报警历史记录
    • 报警阈值灵活配置
  4. 完善的数据管理

    • 实时数据存储
    • 历史数据查询
    • 数据导出功能
    • 数据可视化分析
  5. 可靠的系统架构

    • 模块化设计
    • 异常处理机制
    • 配置文件保存
    • 系统日志记录

部署与使用

系统要求

安装步骤

  1. 安装Qt开发环境
  2. 克隆项目仓库
  3. 配置串口参数
  4. 编译并运行程序

使用流程

  1. 连接串口设备
  2. 配置串口参数(端口、波特率等)
  3. 点击"连接"按钮
  4. 设置报警阈值
  5. 启用自动控制或手动控制设备
  6. 查看实时数据和曲线
  7. 需要时记录数据到文件

扩展功能建议

  1. 云平台集成

    void uploadToCloud(const QString &data) {
        QNetworkAccessManager *manager = new QNetworkAccessManager(this);
        QNetworkRequest request(QUrl("https://api.example.com/data"));
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    
        QJsonObject json;
        json["temperature"] = ui->lcdTemp->value();
        json["humidity"] = ui->lcdHumi->value();
        json["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
    
        QNetworkReply *reply = manager->post(request, QJsonDocument(json).toJson());
        connect(reply, &QNetworkReply::finished, this, [reply]() {
            reply->deleteLater();
        });
    }
    
  2. 数据库存储

    #include <QSqlDatabase>
    #include <QSqlQuery>
    
    void initDatabase() {
        QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
        db.setDatabaseName("thermo_data.db");
        if (!db.open()) {
            qDebug() << "无法打开数据库";
            return;
        }
    
        QSqlQuery query;
        query.exec("CREATE TABLE IF NOT EXISTS readings ("
                   "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                   "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,"
                   "temperature REAL,"
                   "humidity REAL,"
                   "status TEXT)");
    }
    
    void saveToDatabase(double temp, double humi, const QString &status) {
        QSqlQuery query;
        query.prepare("INSERT INTO readings (temperature, humidity, status) "
                      "VALUES (:temp, :humi, :status)");
        query.bindValue(":temp", temp);
        query.bindValue(":humi", humi);
        query.bindValue(":status", status);
        query.exec();
    }
    
  3. WebSocket实时推送

    #include <QWebSocketServer>
    #include <QWebSocket>
    
    void initWebSocketServer() {
        QWebSocketServer *server = new QWebSocketServer("ThermoMonitor", QWebSocketServer::NonSecureMode, this);
        if (server->listen(QHostAddress::Any, 8080)) {
            connect(server, &QWebSocketServer::newConnection, this, [server, this]() {
                QWebSocket *client = server->nextPendingConnection();
                clients << client;
    
                connect(client, &QWebSocket::disconnected, this, [client, this]() {
                    clients.removeAll(client);
                    client->deleteLater();
                });
            });
        }
    }
    
    void broadcastData(double temp, double humi) {
        QJsonObject json;
        json["temperature"] = temp;
        json["humidity"] = humi;
        json["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
    
        QJsonDocument doc(json);
        QString message = doc.toJson(QJsonDocument::Compact);
    
        for (QWebSocket *client : clients) {
            client->sendTextMessage(message);
        }
    }
    
  4. 移动端适配

    #ifdef Q_OS_ANDROID
    void requestPermissions() {
        QtAndroid::requestPermissionsSync({"android.permission.INTERNET",
                                           "android.permission.ACCESS_FINE_LOCATION"});
    }
    
    void setupMobileUI() {
        // 适配移动端的UI布局
        ui->centralwidget->setStyleSheet("background-color: #f0f0f0;");
        ui->tabWidget->setTabPosition(QTabWidget::South);
    }
    #endif
    
  5. AI预测分析

    #include <tensorflow/lite/interpreter.h>
    #include <tensorflow/lite/model.h>
    
    void predictTrend() {
        // 加载TensorFlow Lite模型
        std::unique_ptr<tflite::FlatBufferModel> model = 
            tflite::FlatBufferModel::BuildFromFile("trend_predict.tflite");
    
        // 创建解释器
        tflite::ops::builtin::BuiltinOpResolver resolver;
        std::unique_ptr<tflite::Interpreter> interpreter;
        tflite::InterpreterBuilder(*model, resolver)(&interpreter);
    
        // 设置输入数据(最近10个数据点)
        float input[10] = {...};
        memcpy(interpreter->typed_input_tensor<float>(0), input, sizeof(input));
    
        // 运行推理
        interpreter->Invoke();
    
        // 获取预测结果
        float *output = interpreter->typed_output_tensor<float>(0);
        float predictedTemp = output[0];
        float predictedHumi = output[1];
    
        // 显示预测结果
        ui->lblPrediction->setText(QString("预测: %1℃, %2%").arg(predictedTemp).arg(predictedHumi));
    }
    

常见问题解决

  1. 串口无法打开

    • 检查串口是否被其他程序占用
    • 确认串口名称和参数是否正确
    • 尝试以管理员权限运行程序
    • 检查USB转串口驱动是否安装
  2. 数据显示异常

    • 检查传感器数据格式
    • 验证串口通信协议
    • 查看通信日志定位问题
    • 尝试不同的波特率
  3. 报警不触发

    • 确认阈值设置是否正确
    • 检查报警开关是否启用
    • 验证声音设备是否正常
    • 查看系统日志是否有错误
  4. 数据记录失败

    • 检查文件路径权限
    • 确认磁盘空间充足
    • 验证CSV文件格式
    • 尝试手动指定文件路径
  5. 界面卡顿

    • 减少图表更新频率
    • 关闭不必要的日志显示
    • 优化数据处理算法
    • 升级硬件配置

专注于matlab/simulink,电子电路,编程