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

嵌入式项目:基于QT与Hi3861的物联网智能大棚集成控制系统

关键词:MQTT、物联网、QT、网络连接、远程控制

一、系统概述

本系统是一套完整的智能大棚监控解决方案,由两部分构成:

  1. 基于Hi3861的嵌入式硬件系统(负责环境数据采集和设备控制)
  2. 基于Qt开发的跨平台控制软件(提供可视化界面和远程控制)

该系统实现对大棚环境的全方位监控:

  • ​环境监测系统​​:实时检测温度、湿度、光照强度、CO₂浓度
  • ​安防系统​​:人体检测报警(PIR传感器)
  • ​执行设备​​:LED补光灯控制、通风风扇控制
  • ​双模控制​​:手动控制/自动智能控制模式
  • ​远程监控​​:实时数据面板 + 三态环境状态指示
  • ​异常处理​​:临界值提醒与自动执行保护策略

二、Qt控制界面设计亮点

1. 布局结构(用户登录界面和系统控制界面)

登录界面

2. 动态数据可视化功能

​环境数据卡片设计:​

QWidget* Widget::createDataCard(const QString& title, const QString& unit, const QColor& color)
{// 创建带有彩色边框的数据卡片// 包含标题、实时数值、单位和趋势指示器
}

​趋势变化可视化:​

由于博主的QT版本过低,部分模块及代码无法兼容,所以无法通过绘制温湿度、光照强度的折线图观察环境数据的具体趋势,采用箭头指示​的方式作为系统数据趋势的指示器,当检测到此次数据较上一次采集数据高时箭头指向↑,以此类推。

// 更新趋势指示器(上升↑、下降↓、稳定→)
if (value > prevValue) {trendLabel->setText("↑");trendLabel->setStyleSheet("color: red;");
} else if (value < prevValue) {trendLabel->setText("↓");trendLabel->setStyleSheet("color: blue;");
} else {trendLabel->setText("→");
}
​​​3. 三态预警系统​

我们创新性地采用三色指示灯直观展示环境状态(正常/临界/异常),避免单一阈值判断的局限性:

void Widget::updateStatusIndicator(QWidget* indicator, double value, double min, double max) {QLabel *light = indicator->findChild<QLabel*>("statusLight");if (!light) return;// 正常范围:显示绿色if (value >= min && value <= max) {light->setStyleSheet("border-radius: 15px; background-color: #4CAF50;");} // 临界范围:显示黄色(预留±5缓冲区间)else if ((value >= min-5 && value < min) || (value > max && value <= max+5)) {light->setStyleSheet("border-radius: 15px; background-color: #FFC107;");} // 危险范围:显示红色else {light->setStyleSheet("border-radius: 15px; background-color: #F44336;");}
}

应用场景示例

  • 温度预警:20-28℃正常(绿)|15-19℃或29-33℃警告(黄)|<15℃或>33℃危险(红)
  • 该设计解决了传统二元警报频繁误报问题,大幅提升监控准确性
​4. 人体检测可视化​

采用动态指示灯+状态文字双重提示:

// 提取人体监测数据bool humanDetected = jsonObj.contains("HumanSensor") ? jsonObj["HumanSensor"].toBool() : false;// 更新人体监测指示灯if (humanDetected) {humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: green;");humanMonitorStatus->setText("有人");} else {humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: red;");humanMonitorStatus->setText("无人");}

安防联动设计

  • 检测到人员时触发警报(可以尝试添加人脸识别,制作检测抓拍记录功能
  • 夜间模式下自动开启补光灯辅助监控

​​​三、ThingsCloud云平台

​1.设备详情

新建设备智能大棚,添加大棚灯、风机、温湿度、人体监测等部分,需要注意的是由QT控制的部分(灯、风机、警报)需要将属性设置为云端下发属性,QT接收的温湿度、光照强度、CO2等设置为设备上报属性

2.设备看板

3.手机端云平台

在ThingsCloud云平台创建自己的UI界面,注意添加对应组件的属性,保存后在扫描用户应用中的微信小程序二维码,登录的手机端云平台界面就是电脑端创建的UI界面,该界面与电脑端界面操作同步。

4.MQTT通信效果

我们在MQTTX创建设备连接,连接云平台设备,在发送如下字符(设备上报)后发现看板组件确实安装发送的温湿度数据配置,同时在看板中打开灯,云端下发了如下字符,证明MQTT通信成功,可以连接。

四、双模式控制机制

​1. 手动模式

用户可直接点击控件按钮,系统实时反馈设备状态:

void Widget::toggleLight1() {if (!autoMode) { // 仅在手动模式响应light1On = !light1On;light1Button->setText(light1On ? "开" : "关");sendData(light1On ? "{\"light0\":true}" : "{\"light0\":false}");}
}

点击按键后,QT根据按键向开发板发送固定字符,开发板通过接收的字符进行判断,控制部分外设进行特定功能。

​2. 自动模式

动态响应环境变化,用户在自动模式下无法直接操作按键,由系统自行控制,同时系统日志显示切换到自动模式。

void Widget::parseEnvironmentData(const QByteArray &data) {if (autoMode) {// 温度>26°C开启风扇,<25°C关闭(滞回控制防抖动)if (temp > 26.0 && !fanOn) sendControlCommand(FAN_ON); else if (temp <= 25.0 && fanOn) sendControlCommand(FAN_OFF);// 光照<80Lux开灯,>90Lux关灯if (lightIntensity < 80 && !light1On) sendControlCommand(LIGHT_ON);else if (lightIntensity >= 90 && light1On) sendControlCommand(LIGHT_OFF);// 人员检测联动报警if (humanDetected) sendControlCommand(ALARM_ON);else if (alarmOn) sendControlCommand(ALARM_OFF);}
}

策略优势

  • 滞回区间设计:防止温度波动导致风扇频繁启停
  • 分级响应:不同设备采用独立阈值策略

​五、通信与算法优化实践​

​1. 高效UDP通信协议​

初始化udp套接字,以及开发板IP地址。注:确保电脑和开发板要连在同一WiFi热点下,通过串口查找开发板设备IP地址,在QT代码中设为连接目标,确保端口号与开发板程序设定的端口号一致(此处设置为9000,设备IP地址为192.168.34.8).​

// 初始化UDPudpSocket = new QUdpSocket(this);if (!udpSocket->bind(QHostAddress::Any, 9000)) {QMessageBox::critical(this, "初始化错误", "UDP套接字绑定失败,请检查端口是否被占用");} else {connect(udpSocket, SIGNAL(readyRead()), this, SLOT(recvData()));}
}
​2. 核心算法优化实例​

解析JSON数据:温湿度,光照,CO2,人体监测状态,判断是否为自动模式,根据解析的数据进行面板显示和状态指示灯显示。​

void Widget::parseEnvironmentData(const QByteArray &data)
{QJsonParseError jsonError;QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);if (jsonError.error == QJsonParseError::NoError && jsonDoc.isObject()) {QJsonObject jsonObj = jsonDoc.object();// 提取温度数据(使用0作为默认值)double temperature = jsonObj.contains("Temp") ? jsonObj["Temp"].toDouble() : 0.0;// 更新温度数据卡片if (tempCard) {QLabel *tempValueLabel = tempCard->findChild<QLabel*>("valueLabel");if (tempValueLabel) {tempValueLabel->setText(QString::number(temperature, 'f', 1));// 更新趋势指示器QLabel *tempTrendLabel = tempCard->findChild<QLabel*>("trendLabel");if (tempTrendLabel) {if (temperature > prevTemp) {tempTrendLabel->setText("↑");tempTrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (temperature < prevTemp) {tempTrendLabel->setText("↓");tempTrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {tempTrendLabel->setText("→");tempTrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevTemp = temperature;}}}// 提取湿度数据double humidity = jsonObj.contains("Humi") ? jsonObj["Humi"].toDouble() : 0.0;if (humCard) {QLabel *humValueLabel = humCard->findChild<QLabel*>("valueLabel");if (humValueLabel) {humValueLabel->setText(QString::number(humidity, 'f', 0));// 更新趋势指示器QLabel *humTrendLabel = humCard->findChild<QLabel*>("trendLabel");if (humTrendLabel) {if (humidity > prevHum) {humTrendLabel->setText("↑");humTrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (humidity < prevHum) {humTrendLabel->setText("↓");humTrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {humTrendLabel->setText("→");humTrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevHum = humidity;}}}// 提取CO₂数据int co2 = jsonObj.contains("CO2") ? jsonObj["CO2"].toInt() : 0;if (co2Card) {QLabel *co2ValueLabel = co2Card->findChild<QLabel*>("valueLabel");if (co2ValueLabel) {co2ValueLabel->setText(QString::number(co2));// 更新趋势指示器QLabel *co2TrendLabel = co2Card->findChild<QLabel*>("trendLabel");if (co2TrendLabel) {if (co2 > prevCO2) {co2TrendLabel->setText("↑");co2TrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (co2 < prevCO2) {co2TrendLabel->setText("↓");co2TrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {co2TrendLabel->setText("→");co2TrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevCO2 = co2;}}}// 提取光照强度数据int lightIntensity = jsonObj.contains("lumen") ? jsonObj["lumen"].toInt() : 0;if (lightCard) {QLabel *lightValueLabel = lightCard->findChild<QLabel*>("valueLabel");if (lightValueLabel) {lightValueLabel->setText(QString::number(lightIntensity));// 更新趋势指示器QLabel *lightTrendLabel = lightCard->findChild<QLabel*>("trendLabel");if (lightTrendLabel) {if (lightIntensity > prevLight) {lightTrendLabel->setText("↑");lightTrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (lightIntensity < prevLight) {lightTrendLabel->setText("↓");lightTrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {lightTrendLabel->setText("→");lightTrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevLight = lightIntensity;}}}// 更新状态指示器updateStatusIndicator(tempStatus, temperature, 20, 28);updateStatusIndicator(humStatus, humidity, 40, 60);updateStatusIndicator(co2Status, co2, 0, 800);updateStatusIndicator(lightStatus, lightIntensity, 80, 100);// 提取人体监测数据bool humanDetected = jsonObj.contains("HumanSensor") ? jsonObj["HumanSensor"].toBool() : false;// 更新人体监测指示灯if (humanDetected) {humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: green;");humanMonitorStatus->setText("有人");} else {humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: red;");humanMonitorStatus->setText("无人");}// 更新温度数据用于存储if (!temperatureData.isEmpty()) {temperatureData.pop_front();temperatureData.append(temperature);}// 如果是自动模式,执行自动控制逻辑if (autoMode) {// 温度高于26度时开启风扇if (temperature > 26.0 && !fanOn) {sendData("{\"Fan0\":true}");fanOn = true;fanButton->setText("开");fanButton->setChecked(true);}// 温度低于25度时关闭风扇else if (temperature <= 25.0 && fanOn) {sendData("{\"Fan0\":false}");fanOn = false;fanButton->setText("关");fanButton->setChecked(false);}// 检测到有人时开启报警if (humanDetected && !alarmOn) {sendData("{\"Alarm\":true}");alarmOn = true;alarmButton->setText("开");alarmButton->setChecked(true);}// 无人时关闭报警else if (!humanDetected && alarmOn) {sendData("{\"Alarm\":false}");alarmOn = false;alarmButton->setText("关");alarmButton->setChecked(false);}// 光照强度小于100时开启灯1if (lightIntensity < 80 && !light1On) {sendData("{\"light0\":true}");light1On = true;light1Button->setText("开");light1Button->setChecked(true);}// 光照强度大于150时关闭灯1else if (lightIntensity >= 90 && light1On) {sendData("{\"light0\":false}");light1On = false;light1Button->setText("关");light1Button->setChecked(false);}}}
}

​六、代码管理

1. QT登录界面

​实现两种登录模式(用户名密码登录、其他账号登录),这里博主为了省事直接将其他方式写为点击后弹出窗口显示系统维护。。。具体实现方法和用户名密码登录大体一致,感兴趣的朋友可以自行尝试其他方法登录。

#include "loginwidget.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPainter>
#include <QPaintEvent>
#include <QStyleOption>
#include <QMessageBox>
#include <QTimer>
#include <QRadialGradient>
#include "widget.h" // 控制界面头文件
#include <QGroupBox>LoginWidget::LoginWidget(QWidget *parent) : QWidget(parent)
{setWindowTitle("智能大棚控制系统");setFixedSize(1000, 700);setupUI();// 填充示例用户名usernameEdit->setText("");passwordEdit->setText(""); // 实际使用时应该为空
}void LoginWidget::setupUI()
{// 主布局QHBoxLayout *mainLayout = new QHBoxLayout(this);mainLayout->setContentsMargins(0, 0, 0, 0);mainLayout->setSpacing(0);// 左侧装饰区(占位60%宽度)QWidget *leftDecoration = new QWidget(this);leftDecoration->setStyleSheet("background-color: #0f1120;");mainLayout->addWidget(leftDecoration, 6);// 替换左侧装饰区创建代码//leftDecoration->setStyleSheet("background-color: #0f1120;"); // 保留这个以防图片加载失败QVBoxLayout *leftLayout = new QVBoxLayout(leftDecoration);leftLayout->setContentsMargins(0, 0, 0, 0);// 创建图片标签QLabel *bgLabel = new QLabel(leftDecoration);bgLabel->setScaledContents(true); // 设置图片缩放以适应标签leftLayout->addWidget(bgLabel);bgLabel->setPixmap(QPixmap(":/2025.png"));// 右侧登录表单区(占位40%宽度)QWidget *rightForm = new QWidget(this);rightForm->setStyleSheet("background-color: #f9fafb;");QVBoxLayout *formLayout = new QVBoxLayout(rightForm);formLayout->setContentsMargins(70, 100, 70, 100);formLayout->setSpacing(0);mainLayout->addWidget(rightForm, 4);// 标题区域QVBoxLayout *titleLayout = new QVBoxLayout;titleLayout->setSpacing(5);// 欢迎语QLabel *welcomeLabel = new QLabel("你好,xxx");welcomeLabel->setStyleSheet("font-size: 32px; font-weight: bold; color: #2c3e50;");titleLayout->addWidget(welcomeLabel);// 英文标题QLabel *subtitle = new QLabel("Welcome, Wang");subtitle->setStyleSheet("font-size: 20px; color: #7b8a9b; line-height: 1.5;");titleLayout->addWidget(subtitle);titleLayout->addSpacing(10);formLayout->addLayout(titleLayout);// 表单区域QGroupBox *formGroup = new QGroupBox;formGroup->setStyleSheet("QGroupBox { border: none; }");QVBoxLayout *inputLayout = new QVBoxLayout(formGroup);inputLayout->setSpacing(15);// 用户名输入QLabel *usernameLabel = new QLabel("用户名");usernameLabel->setStyleSheet("font-size: 16px; color: #2c3e50;");inputLayout->addWidget(usernameLabel);usernameEdit = new QLineEdit;usernameEdit->setStyleSheet("QLineEdit {""   border: 1px solid #dfe6f0;""   border-radius: 6px;""   padding: 12px;""   font-size: 20px;""   background: white;""}""QLineEdit:focus { border: 1px solid #2196f3; }");inputLayout->addWidget(usernameEdit);// 密码输入QLabel *passwordLabel = new QLabel("密码");passwordLabel->setStyleSheet("font-size: 16px; color: #2c3e50;");inputLayout->addWidget(passwordLabel);passwordEdit = new QLineEdit;passwordEdit->setEchoMode(QLineEdit::Password);passwordEdit->setStyleSheet("QLineEdit {""   border: 1px solid #dfe6f0;""   border-radius: 6px;""   padding: 12px;""   font-size: 14px;""   background: white;""}""QLineEdit:focus { border: 1px solid #2196f3; }");inputLayout->addWidget(passwordEdit);// 密码管理区域QHBoxLayout *passwordOptions = new QHBoxLayout;QCheckBox *rememberCheckbox = new QCheckBox("记住密码");rememberCheckbox->setStyleSheet("color: #7b8a9b;");passwordOptions->addWidget(rememberCheckbox);passwordOptions->addStretch();QLabel *forgotPassword = new QLabel("忘记密码?");forgotPassword->setStyleSheet("font-size: 14px; color: #2196f3; text-decoration: underline;");passwordOptions->addWidget(forgotPassword);inputLayout->addLayout(passwordOptions);inputLayout->addSpacing(15);formLayout->addWidget(formGroup);// 登录按钮loginButton = new QPushButton("登录");loginButton->setFixedHeight(48);loginButton->setStyleSheet("QPushButton {""   background-color: #2196f3;""   color: white;""   border-radius: 6px;""   font-size: 15px;""   font-weight: bold;""}""QPushButton:hover { background-color: #1e88e5; }""QPushButton:pressed { background-color: #1976d2; }");formLayout->addWidget(loginButton);// 第三方登录区域 - 替换为单一按钮QLabel *otherMethods = new QLabel("其他登录方式");otherMethods->setAlignment(Qt::AlignCenter);otherMethods->setStyleSheet("font-size: 14px; color: #7b8a9b; margin: 30px 0 15px 0;");formLayout->addWidget(otherMethods);otherMethods->setAlignment(Qt::AlignCenter);otherMethods->setStyleSheet("font-size: 14px; color: #7b8a9b; margin: 30px 0 15px 0;");formLayout->addWidget(otherMethods);// 创建成都理工大学登录按钮QPushButton *cdutLoginButton = new QPushButton("使用xxxx账号登录");cdutLoginButton->setFixedHeight(48);cdutLoginButton->setStyleSheet("QPushButton {""   background-color: #2196f3;""   color: white;""   border: 1px solid #1d5ab4;""   border-radius: 6px;""   font-size: 14px;""   font-weight: bold;""}""QPushButton:hover {""   background-color: #e3f0ff;""}""QPushButton:pressed {""   background-color: #c7e1ff;""}");formLayout->addWidget(cdutLoginButton);// 连接登录按钮信号// 连接新按钮的信号connect(cdutLoginButton, SIGNAL(clicked()), this, SLOT(onCdutLoginClicked()));connect(loginButton, SIGNAL(clicked()), this, SLOT(onLoginClicked()));
}void LoginWidget::onCdutLoginClicked()
{QMessageBox::information(this, "系统维护", "xxxx统一认证服务正在维护中,请稍后再试");
}void LoginWidget::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);
}void LoginWidget::onLoginClicked()
{QString username = usernameEdit->text();QString password = passwordEdit->text();// 禁用登录按钮防止重复点击loginButton->setEnabled(false);loginButton->setText("登录中...");// 添加简单的登录动画效果QTimer::singleShot(1000, this, [=]() {// 检查用户名和密码是否都是"1"if (username == "1" && password == "1") {// 登录成功openControlWidget();this->hide(); // 隐藏登录窗口} else {// 登录失败QMessageBox::warning(this, "登录失败", "用户名或密码错误");// 重置登录按钮状态loginButton->setEnabled(true);loginButton->setText("登录");}});
}void LoginWidget::openControlWidget()
{// 创建控制界面Widget *controlWidget = new Widget();controlWidget->show();// 当控制界面关闭时,重新显示登录窗口connect(controlWidget, &Widget::destroyed, this, [=]() {// 重新启用登录按钮(为下次登录准备)loginButton->setEnabled(true);loginButton->setText("登录");// 重新显示登录窗口this->show();});
}
2. QT用户控制界面
#include "widget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QSplitter>
#include <QTimer>
#include <cmath>
#include <QButtonGroup>
#include <QMessageBox>
#include <QDateTime> // 添加这行Widget::Widget(QWidget *parent) :QWidget(parent),light1On(false), light2On(false), fanOn(false), alarmOn(false),light1Button(nullptr), light2Button(nullptr), fanButton(nullptr), alarmButton(nullptr),udpInput(nullptr), udpTextEdit(nullptr),targetIp("192.168.34.8"),modeGroup(nullptr), manualButton(nullptr), autoButton(nullptr),autoMode(false), // 默认手动模式// 初始化卡片指针tempCard(nullptr), humCard(nullptr), co2Card(nullptr), lightCard(nullptr),// 初始化状态指示器指针tempStatus(nullptr), humStatus(nullptr), co2Status(nullptr), lightStatus(nullptr),// 初始化上一次的值prevTemp(0.0), prevHum(0.0), prevCO2(0), prevLight(0),// 人体监测相关humanMonitorLabel(nullptr), humanMonitorStatus(nullptr)
{setWindowTitle("智能大棚控制系统");resize(1200, 800);setStyleSheet("background-color: #f0f8ff;");// 初始化温度数据为0for(int i = 0; i < 60; i++) {temperatureData.append(0.0);}// 主布局QSplitter *mainSplitter = new QSplitter(Qt::Horizontal, this);// 左侧控制面板mainSplitter->addWidget(createLeftPanel());// 右侧面板(上下分割)QSplitter *rightSplitter = new QSplitter(Qt::Vertical);rightSplitter->addWidget(createRightTopPanel());rightSplitter->addWidget(createRightBottomPanel());mainSplitter->addWidget(rightSplitter);mainSplitter->setStretchFactor(0, 2);  // 左侧占40%mainSplitter->setStretchFactor(1, 3);  // 右侧占60%QHBoxLayout *mainLayout = new QHBoxLayout(this);mainLayout->setContentsMargins(10, 10, 10, 10);mainLayout->addWidget(mainSplitter);// 初始化UDPudpSocket = new QUdpSocket(this);if (!udpSocket->bind(QHostAddress::Any, 9000)) {QMessageBox::critical(this, "初始化错误", "UDP套接字绑定失败,请检查端口是否被占用");} else {connect(udpSocket, SIGNAL(readyRead()), this, SLOT(recvData()));}
}Widget::~Widget()
{if (udpSocket) {udpSocket->close();delete udpSocket;}
}// 创建数据卡片
QWidget* Widget::createDataCard(const QString& title, const QString& unit, const QColor& color)
{QWidget *card = new QWidget;card->setObjectName("dataCard");card->setStyleSheet("QWidget#dataCard {""background-color: white;""border-radius: 10px;""border: 2px solid " + color.name() + ";""}");// 添加阴影效果QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect;shadow->setBlurRadius(10);shadow->setColor(QColor(0, 0, 0, 70));shadow->setOffset(0, 2);card->setGraphicsEffect(shadow);QVBoxLayout *layout = new QVBoxLayout(card);layout->setAlignment(Qt::AlignCenter);layout->setSpacing(10);// 标题QLabel *titleLabel = new QLabel(title);titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: " + color.name() + ";");titleLabel->setAlignment(Qt::AlignCenter);layout->addWidget(titleLabel);// 值QLabel *valueLabel = new QLabel("0");valueLabel->setObjectName("valueLabel");valueLabel->setStyleSheet("font-size: 32px; font-weight: bold;");valueLabel->setAlignment(Qt::AlignCenter);layout->addWidget(valueLabel);// 单位QLabel *unitLabel = new QLabel(unit);unitLabel->setStyleSheet("font-size: 16px;");unitLabel->setAlignment(Qt::AlignCenter);layout->addWidget(unitLabel);// 趋势指示器(箭头)QLabel *trendLabel = new QLabel("→");trendLabel->setObjectName("trendLabel");trendLabel->setStyleSheet("font-size: 24px;");trendLabel->setAlignment(Qt::AlignCenter);layout->addWidget(trendLabel);return card;
}// 创建状态指示器
QWidget* Widget::createStatusIndicator(const QString& name, const QString& color)
{QWidget *indicator = new QWidget;indicator->setObjectName("statusIndicator");indicator->setStyleSheet("QWidget#statusIndicator {""background-color: white;""border: 1px solid " + color + ";""border-radius: 10px;""}");// 添加阴影效果QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect;shadow->setBlurRadius(8);shadow->setColor(QColor(0, 0, 0, 50));shadow->setOffset(0, 2);indicator->setGraphicsEffect(shadow);QVBoxLayout *layout = new QVBoxLayout(indicator);layout->setAlignment(Qt::AlignCenter);layout->setSpacing(10);// 状态灯QLabel *light = new QLabel;light->setObjectName("statusLight");light->setFixedSize(30, 30);light->setStyleSheet("border-radius: 15px; background-color: gray;");layout->addWidget(light, 0, Qt::AlignHCenter);// 名称标签QLabel *nameLabel = new QLabel(name);nameLabel->setStyleSheet("font-size: 16px; color: " + color + "; font-weight: bold;");nameLabel->setAlignment(Qt::AlignCenter);layout->addWidget(nameLabel);return indicator;
}void Widget::recvData()
{while (udpSocket && udpSocket->hasPendingDatagrams()) {// 接收数据报QByteArray datagram;datagram.resize(udpSocket->pendingDatagramSize());QHostAddress sender;quint16 senderPort;udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);// 记录接收信息QString message = QString("[%1] From %2:%3 - %4").arg(QDateTime::currentDateTime().toString("hh:mm:ss")).arg(sender.toString()).arg(senderPort).arg(QString::fromUtf8(datagram));// 在UI中显示if (udpTextEdit) {udpTextEdit->append(message);}// 解析环境数据parseEnvironmentData(datagram);}
}void Widget::parseEnvironmentData(const QByteArray &data)
{QJsonParseError jsonError;QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);if (jsonError.error == QJsonParseError::NoError && jsonDoc.isObject()) {QJsonObject jsonObj = jsonDoc.object();// 提取温度数据(使用0作为默认值)double temperature = jsonObj.contains("Temp") ? jsonObj["Temp"].toDouble() : 0.0;// 更新温度数据卡片if (tempCard) {QLabel *tempValueLabel = tempCard->findChild<QLabel*>("valueLabel");if (tempValueLabel) {tempValueLabel->setText(QString::number(temperature, 'f', 1));// 更新趋势指示器QLabel *tempTrendLabel = tempCard->findChild<QLabel*>("trendLabel");if (tempTrendLabel) {if (temperature > prevTemp) {tempTrendLabel->setText("↑");tempTrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (temperature < prevTemp) {tempTrendLabel->setText("↓");tempTrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {tempTrendLabel->setText("→");tempTrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevTemp = temperature;}}}// 提取湿度数据double humidity = jsonObj.contains("Humi") ? jsonObj["Humi"].toDouble() : 0.0;if (humCard) {QLabel *humValueLabel = humCard->findChild<QLabel*>("valueLabel");if (humValueLabel) {humValueLabel->setText(QString::number(humidity, 'f', 0));// 更新趋势指示器QLabel *humTrendLabel = humCard->findChild<QLabel*>("trendLabel");if (humTrendLabel) {if (humidity > prevHum) {humTrendLabel->setText("↑");humTrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (humidity < prevHum) {humTrendLabel->setText("↓");humTrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {humTrendLabel->setText("→");humTrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevHum = humidity;}}}// 提取CO₂数据int co2 = jsonObj.contains("CO2") ? jsonObj["CO2"].toInt() : 0;if (co2Card) {QLabel *co2ValueLabel = co2Card->findChild<QLabel*>("valueLabel");if (co2ValueLabel) {co2ValueLabel->setText(QString::number(co2));// 更新趋势指示器QLabel *co2TrendLabel = co2Card->findChild<QLabel*>("trendLabel");if (co2TrendLabel) {if (co2 > prevCO2) {co2TrendLabel->setText("↑");co2TrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (co2 < prevCO2) {co2TrendLabel->setText("↓");co2TrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {co2TrendLabel->setText("→");co2TrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevCO2 = co2;}}}// 提取光照强度数据int lightIntensity = jsonObj.contains("lumen") ? jsonObj["lumen"].toInt() : 0;if (lightCard) {QLabel *lightValueLabel = lightCard->findChild<QLabel*>("valueLabel");if (lightValueLabel) {lightValueLabel->setText(QString::number(lightIntensity));// 更新趋势指示器QLabel *lightTrendLabel = lightCard->findChild<QLabel*>("trendLabel");if (lightTrendLabel) {if (lightIntensity > prevLight) {lightTrendLabel->setText("↑");lightTrendLabel->setStyleSheet("color: red; font-size: 24px;");} else if (lightIntensity < prevLight) {lightTrendLabel->setText("↓");lightTrendLabel->setStyleSheet("color: blue; font-size: 24px;");} else {lightTrendLabel->setText("→");lightTrendLabel->setStyleSheet("color: gray; font-size: 24px;");}prevLight = lightIntensity;}}}// 更新状态指示器updateStatusIndicator(tempStatus, temperature, 20, 28);updateStatusIndicator(humStatus, humidity, 40, 60);updateStatusIndicator(co2Status, co2, 0, 800);updateStatusIndicator(lightStatus, lightIntensity, 80, 100);// 提取人体监测数据bool humanDetected = jsonObj.contains("HumanSensor") ? jsonObj["HumanSensor"].toBool() : false;// 更新人体监测指示灯if (humanDetected) {humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: green;");humanMonitorStatus->setText("有人");} else {humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: red;");humanMonitorStatus->setText("无人");}// 更新温度数据用于存储if (!temperatureData.isEmpty()) {temperatureData.pop_front();temperatureData.append(temperature);}// 如果是自动模式,执行自动控制逻辑if (autoMode) {// 温度高于26度时开启风扇if (temperature > 26.0 && !fanOn) {sendData("{\"Fan0\":true}");fanOn = true;fanButton->setText("开");fanButton->setChecked(true);}// 温度低于25度时关闭风扇else if (temperature <= 25.0 && fanOn) {sendData("{\"Fan0\":false}");fanOn = false;fanButton->setText("关");fanButton->setChecked(false);}// 检测到有人时开启报警if (humanDetected && !alarmOn) {sendData("{\"Alarm\":true}");alarmOn = true;alarmButton->setText("开");alarmButton->setChecked(true);}// 无人时关闭报警else if (!humanDetected && alarmOn) {sendData("{\"Alarm\":false}");alarmOn = false;alarmButton->setText("关");alarmButton->setChecked(false);}// 光照强度小于100时开启灯1if (lightIntensity < 80 && !light1On) {sendData("{\"light0\":true}");light1On = true;light1Button->setText("开");light1Button->setChecked(true);}// 光照强度大于150时关闭灯1else if (lightIntensity >= 90 && light1On) {sendData("{\"light0\":false}");light1On = false;light1Button->setText("关");light1Button->setChecked(false);}}}
}void Widget::updateStatusIndicator(QWidget* indicator, double value, double min, double max)
{QLabel *light = indicator ? indicator->findChild<QLabel*>("statusLight") : nullptr;if (!light) return;// 定义警告范围(临界范围)的边界double warnLow = min - 5;  // 低于正常范围5个单位视为临界double warnHigh = max + 5; // 高于正常范围5个单位视为临界// 确定颜色(红-黄-绿)if (value >= min && value <= max) {// 在正常范围内,显示绿色light->setStyleSheet("border-radius: 15px; background-color: green;");} else if ((value >= warnLow && value < min) || (value > max && value <= warnHigh)) {// 在临界范围内,显示黄色light->setStyleSheet("border-radius: 15px; background-color: yellow;");} else {// 超出临界范围,显示红色light->setStyleSheet("border-radius: 15px; background-color: red;");}
}void Widget::sendData(const QString& data)
{if (udpSocket) {QByteArray byteArray = data.toUtf8();udpSocket->writeDatagram(byteArray, QHostAddress(targetIp), 9000);}
}void Widget::sendData(const char* data)
{if (udpSocket) {QByteArray byteArray(data);udpSocket->writeDatagram(byteArray, QHostAddress(targetIp), 9000);}
}void Widget::sendManualData()
{if (udpInput && !udpInput->text().isEmpty()) {sendData(udpInput->text());udpInput->clear();}
}void Widget::toggleLight1()
{if (!light1Button || !udpSocket) return;// 手动模式下才响应按钮点击if (!autoMode) {light1On = !light1On;// 更新按钮显示状态light1Button->setText(light1On ? "开" : "关");light1Button->setChecked(light1On);// 发送控制命令sendData(light1On ? "{\"light0\":true}" : "{\"light0\":false}");QMessageBox::information(this, "灯1控制", light1On ? "灯1已开启" : "灯1已关闭");}
}void Widget::toggleLight2()
{if (!light2Button || !udpSocket) return;// 手动模式下才响应按钮点击if (!autoMode) {light2On = !light2On;// 更新按钮显示状态light2Button->setText(light2On ? "开" : "关");light2Button->setChecked(light2On);sendData(light2On ? "{\"light1\":true}" : "{\"light1\":false}");QMessageBox::information(this, "灯2控制", light2On ? "灯2已开启" : "灯2已关闭");}
}void Widget::toggleFan()
{if (!fanButton || !udpSocket) return;// 手动模式下才响应按钮点击if (!autoMode) {fanOn = !fanOn;// 更新按钮显示状态fanButton->setText(fanOn ? "开" : "关");fanButton->setChecked(fanOn);sendData(fanOn ? "{\"Fan0\":true}" : "{\"Fan0\":false}");QMessageBox::information(this, "风扇控制", fanOn ? "风扇已启用" : "风扇已禁用");}
}void Widget::toggleAlarm()
{if (!alarmButton || !udpSocket) return;// 手动模式下才响应按钮点击if (!autoMode) {alarmOn = !alarmOn;// 更新按钮显示状态alarmButton->setText(alarmOn ? "开" : "关");alarmButton->setChecked(alarmOn);// 发送控制命令sendData(alarmOn ? "{\"Alarm\":true}" : "{\"Alarm\":false}");QMessageBox::information(this, "报警控制", alarmOn ? "报警已启用" : "报警已禁用");}
}// 切换到手动模式
void Widget::setManualMode()
{autoMode = false;manualButton->setStyleSheet("background-color: #42a5f5; color: white; border-radius: 5px; padding: 8px;");autoButton->setStyleSheet("background-color: #e0e0e0; color: #757575; border-radius: 5px; padding: 8px;");// 启用手动控制按钮light1Button->setEnabled(true);light2Button->setEnabled(true);fanButton->setEnabled(true);alarmButton->setEnabled(true);
}// 切换到自动模式
void Widget::setAutoMode()
{autoMode = true;manualButton->setStyleSheet("background-color: #e0e0e0; color: #757575; border-radius: 5px; padding: 8px;");autoButton->setStyleSheet("background-color: #42a5f5; color: white; border-radius: 5px; padding: 8px;");// 禁用手动控制按钮light1Button->setEnabled(false);light2Button->setEnabled(false);fanButton->setEnabled(false);alarmButton->setEnabled(false);// 在状态栏显示切换到自动模式udpTextEdit->append(QString("[%1] 系统切换到自动模式").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
}QWidget* Widget::createLeftPanel()
{QWidget *panel = new QWidget;panel->setStyleSheet("background-color: white; border-radius: 8px;");QVBoxLayout *layout = new QVBoxLayout(panel);layout->setContentsMargins(15, 15, 15, 15);layout->setSpacing(15);// 标题区域QLabel *title = new QLabel("设备控制");title->setAlignment(Qt::AlignCenter);title->setStyleSheet("font-size: 22px; font-weight: bold; color: #42a5f5; margin-bottom: 15px;");layout->addWidget(title);// 添加手动/自动模式切换按钮QWidget *modeWidget = new QWidget;QHBoxLayout *modeLayout = new QHBoxLayout(modeWidget);modeLayout->setContentsMargins(0, 0, 0, 0);modeGroup = new QButtonGroup(this);manualButton = new QPushButton("手动模式");manualButton->setCheckable(true);manualButton->setChecked(true); // 默认手动模式manualButton->setStyleSheet("background-color: #42a5f5; color: white; border-radius: 5px; padding: 8px;");connect(manualButton, SIGNAL(clicked()), this, SLOT(setManualMode()));autoButton = new QPushButton("自动模式");autoButton->setCheckable(true);autoButton->setStyleSheet("background-color: #e0e0e0; color: #757575; border-radius: 5px; padding: 8px;");connect(autoButton, SIGNAL(clicked()), this, SLOT(setAutoMode()));modeGroup->addButton(manualButton);modeGroup->addButton(autoButton);modeLayout->addWidget(manualButton);modeLayout->addWidget(autoButton);layout->addWidget(modeWidget);// ====== LED控制部分 ======QGroupBox *ledGroup = new QGroupBox("LED控制");ledGroup->setStyleSheet("QGroupBox { border: 1px solid #a3d1f5; border-radius: 8px; padding: 10px; }""QGroupBox::title { color: #42a5f5; font-weight: bold; font-size: 16px; }");QVBoxLayout *ledLayout = new QVBoxLayout(ledGroup);// LED开关选项QWidget *ledControl = new QWidget;QHBoxLayout *ledControlLayout = new QHBoxLayout(ledControl);ledControlLayout->setContentsMargins(0, 0, 0, 0);QLabel *ledSwitchLabel = new QLabel("灯1开关");ledSwitchLabel->setStyleSheet("font-size: 30px;");// LED开关按钮light1Button = new QPushButton("关");light1Button->setCheckable(true);light1Button->setStyleSheet("QPushButton {""   background-color: #42a5f5;""   color: white;""   border-radius: 5px;""   padding: 6px;""   font-size: 30px;""   min-width: 50px;""}""QPushButton:checked { background-color: #66bb6a; }");connect(light1Button, SIGNAL(clicked()), this, SLOT(toggleLight1()));ledControlLayout->addWidget(ledSwitchLabel);ledControlLayout->addStretch();ledControlLayout->addWidget(light1Button);ledLayout->addWidget(ledControl);// 灯2开关QWidget *led2Control = new QWidget;QHBoxLayout *led2ControlLayout = new QHBoxLayout(led2Control);led2ControlLayout->setContentsMargins(0, 0, 0, 0);QLabel *led2SwitchLabel = new QLabel("灯2开关");led2SwitchLabel->setStyleSheet("font-size: 30px;");light2Button = new QPushButton("关");light2Button->setCheckable(true);light2Button->setStyleSheet("QPushButton {""   background-color: #42a5f5;""   color: white;""   border-radius: 5px;""   padding: 6px;""   font-size: 30px;""   min-width: 50px;""}""QPushButton:checked { background-color: #66bb6a; }");connect(light2Button, SIGNAL(clicked()), this, SLOT(toggleLight2()));led2ControlLayout->addWidget(led2SwitchLabel);led2ControlLayout->addStretch();led2ControlLayout->addWidget(light2Button);ledLayout->addWidget(led2Control);layout->addWidget(ledGroup);// ====== 风扇控制 ======QGroupBox *fanGroup = new QGroupBox("风扇控制");fanGroup->setStyleSheet("QGroupBox { border: 1px solid #a3d1f5; border-radius: 8px; padding: 10px; }""QGroupBox::title { color: #42a5f5; font-weight: bold; font-size: 16px; }");QVBoxLayout *fanLayout = new QVBoxLayout(fanGroup);// 风扇开关QWidget *fanControl = new QWidget;QHBoxLayout *fanControlLayout = new QHBoxLayout(fanControl);fanControlLayout->setContentsMargins(0, 0, 0, 0);QLabel *fanSwitchLabel = new QLabel("风扇开关");fanSwitchLabel->setStyleSheet("font-size: 30px;");fanButton = new QPushButton("关");fanButton->setCheckable(true);fanButton->setStyleSheet("QPushButton {""   background-color: #42a5f5;""   color: white;""   border-radius: 5px;""   padding: 6px;""   font-size: 30px;""   min-width: 50px;""}""QPushButton:checked { background-color: #66bb6a; }");connect(fanButton, SIGNAL(clicked()), this, SLOT(toggleFan()));fanControlLayout->addWidget(fanSwitchLabel);fanControlLayout->addStretch();fanControlLayout->addWidget(fanButton);fanLayout->addWidget(fanControl);layout->addWidget(fanGroup);// ====== 报警控制 ======QGroupBox *alarmGroup = new QGroupBox("报警控制");alarmGroup->setStyleSheet("QGroupBox { border: 1px solid #a3d1f5; border-radius: 8px; padding: 10px; }""QGroupBox::title { color: #42a5f5; font-weight: bold; font-size: 16px; }");QVBoxLayout *alarmLayout = new QVBoxLayout(alarmGroup);QWidget *alarmControl = new QWidget;QHBoxLayout *alarmControlLayout = new QHBoxLayout(alarmControl);alarmControlLayout->setContentsMargins(0, 0, 0, 0);QLabel *alarmSwitchLabel = new QLabel("报警开关");alarmSwitchLabel->setStyleSheet("font-size: 30px;");alarmButton = new QPushButton("关");alarmButton->setCheckable(true);alarmButton->setStyleSheet("QPushButton {""   background-color: #42a5f5;""   color: white;""   border-radius: 5px;""   padding: 6px;""   font-size: 30px;""   min-width: 50px;""}""QPushButton:checked { background-color: #66bb6a; }");connect(alarmButton, SIGNAL(clicked()), this, SLOT(toggleAlarm()));alarmControlLayout->addWidget(alarmSwitchLabel);alarmControlLayout->addStretch();alarmControlLayout->addWidget(alarmButton);alarmLayout->addWidget(alarmControl);layout->addWidget(alarmGroup);// ====== UDP手动发送区域 ======QGroupBox *udpGroup = new QGroupBox("UDP手动发送");udpGroup->setStyleSheet("QGroupBox { border: 1px solid #a3d1f5; border-radius: 8px; padding: 10px; }""QGroupBox::title { color: #42a5f5; font-weight: bold; font-size: 16px; }");QVBoxLayout *udpLayout = new QVBoxLayout(udpGroup);QHBoxLayout *inputLayout = new QHBoxLayout;udpInput = new QLineEdit;udpInput->setPlaceholderText("输入要发送的数据");udpInput->setStyleSheet("font-size: 20px; padding: 8px;");QPushButton *sendButton = new QPushButton("发送");sendButton->setStyleSheet("QPushButton {""   background-color: #42a5f5;""   color: white;""   border-radius: 5px;""   padding: 8px;""   font-size: 20px;""}""QPushButton:hover { background-color: #1e88e5; }");connect(sendButton, SIGNAL(clicked()), this, SLOT(sendManualData()));inputLayout->addWidget(udpInput);inputLayout->addWidget(sendButton);udpLayout->addLayout(inputLayout);// UDP接收数据显示udpTextEdit = new QTextEdit;udpTextEdit->setReadOnly(true);udpTextEdit->setStyleSheet("font-size: 14px; background-color: #f8f8f8; border: 1px solid #ddd;");udpLayout->addWidget(udpTextEdit);layout->addWidget(udpGroup);layout->addStretch();return panel;
}QWidget* Widget::createRightTopPanel()
{// 环境监测面板QWidget *panel = new QWidget;panel->setStyleSheet("background-color: #f0f8ff; border-radius: 8px; padding: 10px;");QGridLayout *layout = new QGridLayout(panel);layout->setContentsMargins(10, 10, 10, 10);layout->setHorizontalSpacing(15);layout->setVerticalSpacing(15);// 数据卡片tempCard = createDataCard("温度", "°C", "#ff7043"); // 橙色layout->addWidget(tempCard, 0, 0);humCard = createDataCard("湿度", "%", "#42a5f5");   // 蓝色layout->addWidget(humCard, 0, 1);co2Card = createDataCard("CO₂", "ppm", "#66bb6a"); // 绿色layout->addWidget(co2Card, 0, 2);lightCard = createDataCard("光照", "lx", "#ffd700"); // 金色layout->addWidget(lightCard, 1, 0);// 人体监测部分QWidget *humanMonitorWidget = new QWidget;humanMonitorWidget->setObjectName("humanMonitorContainer");humanMonitorWidget->setStyleSheet("QWidget#humanMonitorContainer {""background-color: white;""border: 2px solid #9c27b0;""border-radius: 10px;""}");QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect;shadow->setBlurRadius(10);shadow->setColor(QColor(0, 0, 0, 70));shadow->setOffset(0, 2);humanMonitorWidget->setGraphicsEffect(shadow);QVBoxLayout *humanLayout = new QVBoxLayout(humanMonitorWidget);humanLayout->setAlignment(Qt::AlignCenter);humanLayout->setSpacing(8);// 指示灯humanMonitorLabel = new QLabel();humanMonitorLabel->setFixedSize(30, 30);humanMonitorLabel->setStyleSheet("border-radius: 15px; background-color: gray;");humanLayout->addWidget(humanMonitorLabel, 0, Qt::AlignHCenter);// 标题QLabel *humanTitle = new QLabel("人体监测");humanTitle->setStyleSheet("font-size: 18px; font-weight: bold; color: #9c27b0;");humanTitle->setAlignment(Qt::AlignCenter);humanLayout->addWidget(humanTitle);// 状态humanMonitorStatus = new QLabel("无人");humanMonitorStatus->setObjectName("statusText");humanMonitorStatus->setStyleSheet("font-size: 16px; font-weight: bold;");humanMonitorStatus->setAlignment(Qt::AlignCenter);humanLayout->addWidget(humanMonitorStatus);// 添加人体监测到网格(横跨两列)layout->addWidget(humanMonitorWidget, 1, 1, 1, 2);return panel;
}QWidget* Widget::createRightBottomPanel()
{// 状态监测面板QWidget *panel = new QWidget;panel->setStyleSheet("background-color: #f0f8ff; border-radius: 8px; padding: 15px;");QVBoxLayout *mainLayout = new QVBoxLayout(panel);mainLayout->setContentsMargins(10, 10, 10, 10);// 标题QLabel *title = new QLabel("环境状态监测");title->setAlignment(Qt::AlignCenter);title->setStyleSheet("font-size: 22px; font-weight: bold; color: #3a4a6b;");mainLayout->addWidget(title);// 状态灯网格布局QGridLayout *gridLayout = new QGridLayout;gridLayout->setHorizontalSpacing(20);gridLayout->setVerticalSpacing(20);gridLayout->setContentsMargins(20, 20, 20, 20);// 创建状态指示器tempStatus = createStatusIndicator("温度", "#ff7043"); // 橙色gridLayout->addWidget(tempStatus, 0, 0);humStatus = createStatusIndicator("湿度", "#42a5f5");   // 蓝色gridLayout->addWidget(humStatus, 0, 1);co2Status = createStatusIndicator("CO₂", "#66bb6a");   // 绿色gridLayout->addWidget(co2Status, 0, 2);lightStatus = createStatusIndicator("光照", "#ffd700"); // 金色gridLayout->addWidget(lightStatus, 1, 1);// 添加状态灯到主布局QWidget *statusContainer = new QWidget;statusContainer->setLayout(gridLayout);mainLayout->addWidget(statusContainer);// 添加图例说明QWidget *legendWidget = new QWidget;QHBoxLayout *legendLayout = new QHBoxLayout(legendWidget);legendLayout->setSpacing(15);QLabel *greenLegend = new QLabel;greenLegend->setFixedSize(20, 20);greenLegend->setStyleSheet("background-color: green; border-radius: 10px;");legendLayout->addWidget(greenLegend);legendLayout->addWidget(new QLabel("正常"));QLabel *yellowLegend = new QLabel;yellowLegend->setFixedSize(20, 20);yellowLegend->setStyleSheet("background-color: yellow; border-radius: 10px;");legendLayout->addWidget(yellowLegend);legendLayout->addWidget(new QLabel("临界"));QLabel *redLegend = new QLabel;redLegend->setFixedSize(20, 20);redLegend->setStyleSheet("background-color: red; border-radius: 10px;");legendLayout->addWidget(redLegend);legendLayout->addWidget(new QLabel("异常"));legendLayout->addStretch();mainLayout->addWidget(legendWidget, 0, Qt::AlignCenter);return panel;
}void Widget::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);  // 不使用event参数// 在这里添加绘制代码QWidget::paintEvent(event);
}
3. HI3861主程序(部分)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "hi_io.h"      // 海思 Pegasus SDK:IoT硬件设备操作接口-IO
#include "hi_time.h"
#include "hi_watchdog.h"
#include "hi_gpio.h"
// 海思 Pegasus SDK:IoT硬件设备操作接口-ADC
// Analog-to-digital conversion (ADC)
// 提供8个ADC通道,通道7为参考电压,不能adc转换
#include "hi_adc.h"
#include "hi_watchdog.h"
// 定义一个宏,用于标识ADC6通道,GPIO13管脚复用
#define LIGHT_SENSOR_CHAN_NAME HI_ADC_CHANNEL_3
#define LuMen_SENSOR_CHAN_NAME HI_ADC_CHANNEL_6#include "m_wifi.h"
#include "m_mqtt.h"#include "lwip/netifapi.h"
#include "lwip/sockets.h"
#include "lwip/api_shell.h"#include "m_key.h"
#include "m_led.h"
#include "m_dht11.h"
#include "oled_ssd1306.h"   // OLED显示屏简化版驱动接口文件
#include "oled_fonts.h"//控制任务
#define WIFI_SSID "iQOO11"
#define WIFI_PAWD "88888888"#define SERVER_IP_ADDR "82.157.254.205"    //broker.emqx.io //54.244.173.190
#define SERVER_IP_PORT 1883
#define MQTT_TOPIC_SUB "attributes/push"
#define MQTT_TOPIC_PUB "attributes"
#define TASK_INIT_TIME 2 // s
#define MQTT_RECV_TASK_TIME (200 * 1000) // us#define MQTT_USERNAME  "tpqacbx5s052il1p"
#define MQTT_PASSWORD  "kqy3EoVZUZ"#define BEEP_PIN         HI_IO_NAME_GPIO_10 // for hispark_pegasus
#define BEEP_FUN_GPIO    HI_IO_FUNC_GPIO_10_GPIO
#define HUMANSENSOR_PIN  	   HI_IO_NAME_GPIO_14
#define HUMANSENSOR_FUN_GPIO  HI_IO_FUNC_GPIO_14_GPIO
#define DUOJI_PIN  	   HI_IO_NAME_GPIO_2
#define DUOJI_FUN_GPIO  HI_IO_FUNC_GPIO_2_GPIO
static volatile  hi_gpio_value g_duojiPinValue = 0;//-------------------------------------------------------------------------------------
int result;  
uint8_t temp;  	    
uint8_t humi;
uint8_t co2;
uint8_t Lum;
uint8_t renne;
char recvBuf[512];
int human_flag = 0;
hi_gpio_value human_sensor_val=HI_GPIO_VALUE0;int socket_fd = 0 ;           // 套接字文件描述符
int result;                  // 函数返回值
char sendBuf[512];           // 发送缓冲区
struct sockaddr_in client_addr;
socklen_t addr_length; osThreadId_t mqtt_send_task_id;   // mqtt订阅数据任务
osThreadId_t mqtt_recv_task_id;   // mqtt发布数据任务//人传感器初始话
void human_sensor_init(void)
{hi_gpio_init();//初始化gpio管脚hi_io_set_pull(HUMANSENSOR_PIN,HI_IO_PULL_DOWN);//设置管脚下拉hi_io_set_func(HUMANSENSOR_PIN,HUMANSENSOR_FUN_GPIO);//设置复用功能为通用输入输出功能hi_gpio_set_dir(HUMANSENSOR_PIN,HI_GPIO_DIR_IN);//设置管脚为输出
}void udp_judge()
{if(strcmp(recvBuf,"{\"light0\":true}")==0)//大棚灯0 开{printf("--- 大棚灯0 开 ---\r\n");led_ctl(LED_BLUE_GPIO, HI_GPIO_VALUE1);}else if(strcmp(recvBuf,"{\"light0\":false}")==0)//大棚灯0 关{printf("--- 大棚灯0 关 ---\r\n");led_ctl(LED_BLUE_GPIO, HI_GPIO_VALUE0);}if(strcmp(recvBuf,"{\"light1\":true}")==0)//大棚灯1 开{printf("--- 大棚灯1 开 ---\r\n");led_ctl(LED_GREEN_GPIO, HI_GPIO_VALUE1);}else if(strcmp(recvBuf,"{\"light1\":false}")==0)//大棚灯1 关{printf("--- 大棚灯1 关 ---\r\n");led_ctl(LED_GREEN_GPIO, HI_GPIO_VALUE0);}if(strcmp(recvBuf,"{\"Alarm\":true}")==0)//报警 开{printf("--- 报警 开 ---\r\n");led_ctl(LED_RED_GPIO, HI_GPIO_VALUE1);led_ctl(BEEP_PIN, HI_GPIO_VALUE0);//开蜂鸣器}else if(strcmp(recvBuf,"{\"Alarm\":false}")==0)//报警 关{printf("--- 报警 关 ---\r\n");led_ctl(LED_RED_GPIO, HI_GPIO_VALUE0);led_ctl(BEEP_PIN, HI_GPIO_VALUE1);//关蜂鸣器}if(strcmp(recvBuf,"{\"Fan0\":true}")==0)//风扇 开{printf("--- 风扇 开 ---\r\n");g_duojiPinValue=1;}else if(strcmp(recvBuf,"{\"Fan0\":false}")==0)//风扇 关{printf("--- 风扇 关 ---\r\n");g_duojiPinValue=0;}
}
//订阅的回调函数
int8_t mqtt_sub_payload_callback(unsigned char *topic, char *payload)
{printf("[info] topic:[%s]    recv<== **%s**\r\n", topic, payload);//判断订阅收到的消息内容/** 蓝色灯代表 大棚灯0 ,绿色灯代表大棚灯1,红色灯代表报警*/if(strcmp(payload,"{\"light0\":true}")==0)//大棚灯0 开{printf("--- 大棚灯0 开 ---\r\n");led_ctl(LED_BLUE_GPIO, HI_GPIO_VALUE1);}else if(strcmp(payload,"{\"light0\":false}")==0)//大棚灯0 关{printf("--- 大棚灯0 关 ---\r\n");led_ctl(LED_BLUE_GPIO, HI_GPIO_VALUE0);}if(strcmp(payload,"{\"light1\":true}")==0)//大棚灯1 开{printf("--- 大棚灯1 开 ---\r\n");led_ctl(LED_GREEN_GPIO, HI_GPIO_VALUE1);}else if(strcmp(payload,"{\"light1\":false}")==0)//大棚灯1 关{printf("--- 大棚灯1 关 ---\r\n");led_ctl(LED_GREEN_GPIO, HI_GPIO_VALUE0);}if(strcmp(payload,"{\"Alarm\":true}")==0)//报警 开{printf("--- 报警 开 ---\r\n");led_ctl(LED_RED_GPIO, HI_GPIO_VALUE1);led_ctl(BEEP_PIN, HI_GPIO_VALUE0);//开蜂鸣器}else if(strcmp(payload,"{\"Alarm\":false}")==0)//报警 关{printf("--- 报警 关 ---\r\n");led_ctl(LED_RED_GPIO, HI_GPIO_VALUE0);led_ctl(BEEP_PIN, HI_GPIO_VALUE1);//关蜂鸣器}if(strcmp(payload,"{\"Fan0\":true}")==0)//风扇 开{printf("--- 风扇 开 ---\r\n");g_duojiPinValue=1;}else if(strcmp(payload,"{\"Fan0\":false}")==0)//风扇 关{printf("--- 风扇 关 ---\r\n");g_duojiPinValue=0;}}//接收任务
void mqtt_recv_task(void)
{while (1) {MQTTClient_sub();usleep(MQTT_RECV_TASK_TIME);}
}//任务1---------------------------------------------------------------------------
void mqtt_send_task(void)
{uint8_t res=0;// 连接Wifiif (WiFi_connectHotspots(WIFI_SSID, WIFI_PAWD) != WIFI_SUCCESS) {printf("[error] WiFi_connectHotspots\r\n");}// 连接MQTT服务器if (MQTTClient_connectServer(SERVER_IP_ADDR, SERVER_IP_PORT) != 0) {printf("[error] MQTTClient_connectServer\r\n");} else {printf("[success] MQTTClient_connectServer\r\n");}sleep(TASK_INIT_TIME);// 初始化MQTT客户端if (MQTTClient_init("mqtt_client_123", MQTT_USERNAME, MQTT_PASSWORD) != 0) {printf("[error] MQTTClient_init\r\n");} else {printf("[success] MQTTClient_init\r\n");}sleep(TASK_INIT_TIME);// 订阅Topic(增加重试)
int retry = 0;
while (MQTTClient_subscribe(MQTT_TOPIC_SUB) != 0 && retry < 3) {printf("[retry %d] MQTTClient_subscribe...\n", retry+1);sleep(1);retry++;
}
if (retry >= 3) {printf("[error] MQTT订阅最终失败!\n");
} else {printf("[success] MQTT订阅成功!\n");
}sleep(TASK_INIT_TIME); osThreadAttr_t options;options.name = "mqtt_recv_task";options.attr_bits = 0;options.cb_mem = NULL;options.cb_size = 0;options.stack_mem = NULL;options.stack_size = 1024*5;options.priority = osPriorityNormal;mqtt_recv_task_id = osThreadNew((osThreadFunc_t)mqtt_recv_task, NULL, &options);if (mqtt_recv_task_id != NULL) {printf("ID = %d, Create mqtt_recv_task_id is OK!\r\n", mqtt_recv_task_id);}while (1) {//MQTTClient_pub(MQTT_TOPIC_PUB, "hello world!!!", strlen("hello world!!!"));// global_temp=temp;// global_humi=humi;DHT11_Read_Data(&temp,&humi);printf("温度=%d°C  湿度=%d%%RH\r\n",temp,humi);sleep(TASK_INIT_TIME);}
}
//任务2-------------------------------------------------------------------------------
static void AdcTask(void *arg)
{// 工作循环,每隔100ms获取一次ADC6通道的值while (1) {// 保存ADC6通道的值unsigned short data = 0;// 获取ADC6通道的值// 读数速率较慢,请避免在中断中使用if (hi_adc_read(LIGHT_SENSOR_CHAN_NAME, &data, HI_ADC_EQU_MODEL_4, HI_ADC_CUR_BAIS_DEFAULT, 0)== HI_ERR_SUCCESS) {// LIGHT_SENSOR_CHAN_NAME表示ADC4通道// HI_ADC_EQU_MODEL_4表示采样数据平均滤波处理,平均次数4次// HI_ADC_CUR_BAIS_DEFAULT表示模数转换采用默认电压基准// 0表示从配置采样到启动采样的延时时间计数为0// 返回值HI_ERR_SUCCESS表示成功// 打印ADC6通道的值//printf("ADC_VALUE = %d\n", (unsigned int)data);// 打印测量电压的值//printf("电压 = %.2f V\n", data*1.8*4/4096);}co2=(unsigned int)data;// 等待100msosDelay(10);hi_watchdog_feed();}
}
//任务3
void *HUMANSENSORTASK(void *arg)
{human_sensor_init();//hi_watchdog_enable();while(1){hi_gpio_get_input_val(HUMANSENSOR_PIN, &human_sensor_val);//让LED3_PIN输出高电平,led3亮if(human_sensor_val == HI_GPIO_VALUE1){osDelay(20);hi_gpio_get_input_val(HUMANSENSOR_PIN,&human_sensor_val);if(human_sensor_val == HI_GPIO_VALUE1){//printf("检测到有人\n");}}if(human_flag != human_sensor_val){human_flag = human_sensor_val;if(human_flag == 0){renne=0;printf("检测到无人了!!!\n");}else{renne=1;printf("检测到有人了!!!\n");}}osDelay(100);//hi_watchdog_feed();}
}
//任务4
void duoji_init(void)
{hi_gpio_init();//初始化gpio管脚hi_io_set_func(DUOJI_PIN,DUOJI_FUN_GPIO);//设置DUOJI_PIN 复用功能为通用输入输出功能hi_gpio_set_dir(DUOJI_PIN,HI_GPIO_DIR_OUT);//设置DUOJI_PIN管脚为输出
}
void *DUOJITASK(void *arg)
{int count = 0;uint16_t m_time = 1000;uint16_t l_time = 1500;uint16_t s_time = 4000;duoji_init();while(1)// 实现舵机旋转{count++;if(count == 1000){count = 0;usleep(100);}if(g_duojiPinValue == HI_GPIO_VALUE1){hi_gpio_set_ouput_val(DUOJI_PIN,HI_GPIO_VALUE1);//让DUOJI_PIN输出高电平hi_udelay(m_time);//延时1mshi_gpio_set_ouput_val(DUOJI_PIN,HI_GPIO_VALUE0);//让DUOJI_PIN输出低电平hi_udelay(20000-m_time);//延时1ms}else if(g_duojiPinValue = HI_GPIO_VALUE0){//hi_gpio_set_ouput_val(DUOJI_PIN,HI_GPIO_VALUE1);//让DUOJI_PIN输出高电平//hi_udelay(l_time);//延时1.5ms//hi_gpio_set_ouput_val(DUOJI_PIN,HI_GPIO_VALUE0);//让DUOJI_PIN输出低电平//hi_udelay(20000-l_time);//延时1.5mshi_gpio_set_ouput_val(DUOJI_PIN,HI_GPIO_VALUE1);//让DUOJI_PIN输出高电平hi_udelay(s_time);//延时3mshi_gpio_set_ouput_val(DUOJI_PIN,HI_GPIO_VALUE0);//让DUOJI_PIN输出低电平hi_udelay(20000-s_time);//延时3ms}}return NULL;
}//任务五
void thread_func_udp(void *arg)
{// int socket_fd=0;                  // 函数返回值// int result;                  // 函数返回值char ip_buf[16]="";          // IP地址存储缓冲区(IPv4最大15字符+结束符)hi_watchdog_enable();WiFi_connectHotspots(WIFI_SSID,WIFI_PAWD);socket_fd = socket(AF_INET, SOCK_DGRAM, 0);//AF_INET 是ipv4协议家族,SOCK_DGRAM 代表的是udp协议,0,协议栈自动识别协议类型。if((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){printf("creat socket failed!\r\n");return ;}struct sockaddr_in my_addr;my_addr.sin_family = AF_INET;         // IPv4协议族my_addr.sin_port = htons(9000);  // 端口号(主机转网络字节序)my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 绑定所有本地接口result = bind(socket_fd, (struct sockaddr*)&my_addr, sizeof(my_addr));if(result != 0){perror("bind");return 0;}//struct sockaddr_in client_addr;addr_length=sizeof(client_addr);while(1){//printf("%d2",socket_fd);memset(recvBuf, 0, sizeof(recvBuf));  // 清空接收缓冲区初始化地址结构体长度/* 接收数据(阻塞式) */result = recvfrom(socket_fd, recvBuf, sizeof(recvBuf)-1, 0,(struct sockaddr*)&client_addr, &addr_length);memset(ip_buf, 0, sizeof(ip_buf));inet_ntop(AF_INET, &(client_addr.sin_addr), ip_buf, INET_ADDRSTRLEN);printf("收到来自ip地址为:%s的数据,", ip_buf);printf("收到数据字节数:%d, recvBuf:%s\r\n", result, recvBuf);// if (result > 0)// {// printf("收到数据字节数:%d, recvBuf:%s\r\n", result, recvBuf);// 构造包含温湿度的响应数据// char response[256] = {0};// snprintf(response, sizeof(response), // "{\"cmd\":\"%s\",\"temp\":%d,\"humi\":%d}", // recvBuf, global_temp, global_humi);// 发送数据到客户端 [修正] 移除多余的length)// result = sendto(socket_fd, response, strlen(response), 0, // (struct sockaddr *)&client_addr, addr_length);// if (result > 0)// {// [修正] 输出正确的发送内容// printf("发送成功字节数:%d, sendData:%s\r\n", result, response);// }//[优化] 只在接收到数据时处理命令// }udp_judge();/* 解析客户端地址信息 */osDelay(100);hi_watchdog_feed();  }
}//任务六
static void lightAdcTask(void *arg)
{while (1) {unsigned short lumen = 0;//光照强度	读ADC4通道if (hi_adc_read(LuMen_SENSOR_CHAN_NAME, &lumen, HI_ADC_EQU_MODEL_4, HI_ADC_CUR_BAIS_DEFAULT, 0)== HI_ERR_SUCCESS) {// LIGHT_SENSOR_CHAN_NAME表示ADC4通道// HI_ADC_EQU_MODEL_4表示采样数据平均滤波处理,平均次数4次// HI_ADC_CUR_BAIS_DEFAULT表示模数转换采用默认电压基准// 0表示从配置采样到启动采样的延时时间计数为0// 返回值HI_ERR_SUCCESS表示成功// 打印ADC6通道的值// printf("ADC_VALUE = %d\n", (unsigned int)lumen);// 打印测量电压的值// printf("电压 = %.2f V\n", lumen*1.8*4/4096);}float adc;adc=(float)lumen*(3.3/4096);   Lum=3100-adc*1000;//printf("lumen = %d ", (unsigned int)lumen);//printf("adc = %f ", adc);//printf("LUM = %d ", Lum);// 等待100msosDelay(10);hi_watchdog_feed();}}//任务7
void Lcd_Task(void *arg)
{OledInit();OledFillScreen(0x00);//OledShowString(0,0,"a",FONT8x16);
while(1){char Temp[10];// char Humi[10];// char LUM[10];// char CO2[10];snprintf(Temp, sizeof(Temp), "Temp: %d C", temp);// snprintf(Humi, sizeof(Humi), "Humi: %d %%", humi);// snprintf(LUM, sizeof(LUM), "Light: %d lux", Lum);// snprintf(CO2, sizeof(CO2), "CO2: %d ppm", co2);OledFillScreen(0x00);OledShowString(0, 0, "Temp", FONT8x16);OledShowChar(2, 2, Temp, FONT8x16);	// OledShowChinese2(29 + 0, 3, 2); // OledShowChinese2(29 + 18, 3, 1); // OledShowChar(29 + 36, 3, Humi, FONT6x8);	// OledShowChinese2(29 + 0, 5, 3); // OledShowChinese2(29 + 18, 5, 4); // OledShowChar(29 + 36, 5, LUM, FONT6x8);	// OledShowChinese2(29 + 0, 7, 5); // OledShowChinese2(29 + 18, 7, 6); // OledShowChinese2(29 + 36, 7, 7); // OledShowChinese2(29 + 54, 7, 8); // OledShowChinese2(29 + 72, 7, 9); // OledShowChinese2(29 + 90, 7, 1); // OledShowChar(29 + 108, 7, CO2, FONT6x8);	}}//-----------------------------------------------------------------------------
//任务创建1 mqtt的发送
void wifi_mqtt_task_create(void)
{osThreadAttr_t taskOptions;p_MQTTClient_sub_callback = &mqtt_sub_payload_callback;taskOptions.name = "mqttTask";       // 任务的名字taskOptions.attr_bits = 0;               // 属性位taskOptions.cb_mem = NULL;               // 堆空间地址taskOptions.cb_size = 0;                 // 堆空间大小taskOptions.stack_mem = NULL;            // 栈空间地址taskOptions.stack_size = 1024*5;           // 栈空间大小 单位:字节taskOptions.priority = osPriorityNormal; // 任务的优先级mqtt_send_task_id = osThreadNew((osThreadFunc_t)mqtt_send_task, NULL, &taskOptions); // 创建任务if (mqtt_send_task_id != NULL){printf("ID = %d, mqtt_send_task_id Create OK!\n", mqtt_send_task_id);}
}
//任务创建2 adc
void AdcDemo(void)
{// 定义线程属性osThreadAttr_t attr;attr.name = "AdcTask";attr.attr_bits = 0U;attr.cb_mem = NULL;attr.cb_size = 0U;attr.stack_mem = NULL;attr.stack_size = 4096;attr.priority = osPriorityNormal;// 创建线程if (osThreadNew(AdcTask, NULL, &attr) == NULL) {printf("[AdcDemo] Falied to create ADCLightTask!\n");}
}
//任务创建3
static void human_sensor_demo(void)
{//定义线程属性osThreadAttr_t attrr;attrr.name = "HUMANSENSORTASK";attrr.stack_size = 10*1024;attrr.priority = osPriorityNormal;if (osThreadNew((osThreadFunc_t)HUMANSENSORTASK, NULL, &attrr) == NULL) {printf("[ThreadTestTask] Falied to create Task01!\n");}
}
//任务创建4
static void duoji_demo(void)
{//定义线程属性osThreadAttr_t attrrr;attrrr.name = "DUOJITASK";attrrr.stack_size = 10*1024;attrrr.priority = osPriorityNormal;if (osThreadNew((osThreadFunc_t)DUOJITASK, NULL, &attrrr) == NULL) {printf("[ThreadTestTask] Falied to create Task!\n");}
}//任务创建5
static void UDPTask(void)
{//定义线程属性osThreadAttr_t attrrrr;attrrrr.name = "thread_func_udp";attrrrr.stack_size = 10*1024;attrrrr.priority = osPriorityNormal;if (osThreadNew((osThreadFunc_t)thread_func_udp, NULL, &attrrrr) == NULL) {printf("[ThreadTestTask] Falied to create Task01!\n");}
}//任务创建6
static void lightTask(void)
{//定义线程属性osThreadAttr_t attrrrrr;attrrrrr.name = "lightAdcTask";attrrrrr.stack_size = 10*1024;attrrrrr.priority = osPriorityNormal;if (osThreadNew((osThreadFunc_t)lightAdcTask, NULL, &attrrrrr) == NULL) {printf("[ThreadTestTask] Falied to create Task01!\n");}
}//任务创建7
static void LCDTask(void)
{//定义线程属性osThreadAttr_t attrrrrrr;attrrrrrr.name = "Lcd_Task";attrrrrrr.stack_size = 10*1024;attrrrrrr.priority = osPriorityNormal;if (osThreadNew((osThreadFunc_t)Lcd_Task, NULL, &attrrrrrr) == NULL) {printf("[ThreadTestTask] Falied to create Task01!\n");}
}/*** @description: 初始化并创建任务* @param {*}* @return {*}*/
static void template_demo(void)
{human_sensor_init();key_init();led_init();printf("WIFI MQTT实验\r\n");wifi_mqtt_task_create();//任务1创建AdcDemo();//任务2创建human_sensor_demo();//任务3创建duoji_demo();//任务4创建UDPTask();lightTask();LCDTask();
}
SYS_RUN(template_demo);

​七、应用价值与创新突破​

1. 技术创新突破点​
  • ​双模无缝切换​​:手动操作可临时覆盖自动策略,满足紧急干预需求
  • ​设备联动引擎​​:实现"光照不足→自动补光+温度调节"的复合响应
  • ​设备联动​​:采用QT控制开发板时云平台图标随QT同步变化(前提是在云平台将设备组件属性改为共享模式)​
  • ​QT界面:采用分块布局,左侧为控制面板,右侧为监测面板,布局合理

  • 数据分析:舍弃折线图形式,采用对比数据显示箭头表示数据变化趋势、三态指示灯表示数据异常情况

​八、写在最后

1. 注意事项

a. 编写主程序时需要注意变量的宏定义和引用问题,例如将UDP上传放在按键中断中,在主程序的任务中接收温湿度,需要确定变量类型。

b. 值得注意的是,在使用云平台控制开发板时我们打开MQTTX软件观察,发现MQTTX反复断开后重连,起初我们认为是网络连接不稳定,后来排除后发现云平台每次只能和MQTTX或者开发板其一通信,两者同时使用会产生冲突。

​在整个项目中出现了各个部分的bug包括主程序中线程间的占用问题、网络连接问题、QT的UDP通信问题,甚至在MQTT无法连接时进行各种排查,发现是博主的队友云平台账号被封号。。。。但是最后的完成度还是非常高,有兴趣的朋友可以尝试使用其他外设,添加语音、摄像头、人脸识别,甚至接入deepseek,都是可以尝试的方向。

相关文章:

  • pandas---使用教程
  • docker小白自存-windows系统通过docker安装n8n-nodes-puppeteer
  • 基于GPS-RTK的履带吊车跑偏检测技术方案
  • Python网络自动化API接口统一库之napalm使用详解
  • Python打卡:Day38
  • 利用云雾自动化在智能无人水面航行器中实现自主碰撞检测和分类
  • redis配置文件-redis.conf
  • 【Docker】解决:构建(docker build)或重新运行容器时,丢失apt-get update问题
  • 【Docker基础】Docker容器管理:docker ps及其参数详解
  • HexHub开发运维利器Database, Docker, SSH, SFTP
  • 数据库外连接详解:方式、差异与关键注意事项
  • 基于fpga的串口控制的音乐播放器
  • Franka 机器人在配置空间距离场实验中的突破性应用:从算法优化到动态场景适配
  • Stable Diffusion 3终极提示词库:2000个工业设计场景生成公式(2025企业级实战指南)
  • html css js网页制作成品——HTML+CSS湘菜网页设计(4页)附源码
  • Ehcache、Caffeine、Spring Cache、Redis、J2Cache、Memcached 和 Guava Cache 的主要区别
  • 自动化保护 AWS ECS Fargate 服务:使用 Prisma Cloud 实现容器安全
  • 无人机防护装置技术解析
  • 5,FreeRTOS中断管理
  • 本地部署 WordPress 博客完整指南(基于 XAMPP)