Initial commit: add Qt environment monitoring client
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
# Build directories and generated Makefiles
|
||||
/build*/
|
||||
/debug/
|
||||
/release/
|
||||
/Makefile*
|
||||
.qmake.stash
|
||||
.qmake.cache
|
||||
|
||||
# Qt and compiler generated files
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
*.o
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ilk
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.a
|
||||
*.lib
|
||||
/qt_client
|
||||
/qt_client_compat
|
||||
|
||||
# Local Qt Creator settings
|
||||
*.pro.user*
|
||||
*.qws
|
||||
/.qtcreator/
|
||||
@@ -0,0 +1,311 @@
|
||||
# Qt温湿度监控系统 - 面试技术要点
|
||||
|
||||
## 🎯 项目概述 / Project Overview
|
||||
|
||||
**项目类型**: 跨平台桌面应用程序 | **技术栈**: Qt5.12+, C++11, TCP/IP, JSON
|
||||
**开发周期**: 2-3周 | **代码量**: ~430行核心代码
|
||||
**核心价值**: 实时监控 + 设备控制 + 网络通信 + 跨平台兼容
|
||||
|
||||
## 🚀 技术亮点 / Technical Highlights
|
||||
|
||||
### 1. 架构设计亮点 / Architecture Design Highlights
|
||||
|
||||
#### 事件驱动架构
|
||||
```cpp
|
||||
// 基于Qt信号槽机制的完全异步设计
|
||||
connect(m_socket, &QTcpSocket::connected, this, &MainWindow::onConnected);
|
||||
connect(m_pollTimer, &QTimer::timeout, this, &MainWindow::pollData);
|
||||
```
|
||||
- **优势**: 非阻塞I/O,响应速度快,资源利用率高
|
||||
- **应用场景**: 网络通信、定时任务、UI交互
|
||||
|
||||
#### 双定时器协调机制
|
||||
```cpp
|
||||
// 互斥式定时器管理 - 避免资源冲突
|
||||
void MainWindow::onConnected() {
|
||||
m_pollTimer->start(); // 数据轮询启动
|
||||
m_reconnectTimer->stop(); // 重连机制停止
|
||||
}
|
||||
```
|
||||
- **��计模式**: 状态机模式
|
||||
- **技术难点**: 定时器状态协调,防止竞态条件
|
||||
|
||||
### 2. 网络通信技术 / Network Communication Technology
|
||||
|
||||
#### 智能数据解析
|
||||
```cpp
|
||||
// 分层数据处理 - 兼容多种数据格式
|
||||
void MainWindow::onReadyRead() {
|
||||
// 层1: 按行处理标准JSON
|
||||
while (m_socket->canReadLine()) {
|
||||
QByteArray line = m_socket->readLine().trimmed();
|
||||
parseJsonMessage(line);
|
||||
}
|
||||
|
||||
// 层2: 处理无换行符的批量数据
|
||||
if (m_socket->bytesAvailable() > 0) {
|
||||
QByteArray raw = m_socket->readAll();
|
||||
processBulkData(raw);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**技术难点**:
|
||||
- TCP粘包处理
|
||||
- 不完整数据恢复
|
||||
- 多种消息格式兼容
|
||||
|
||||
#### 消息类型分发系统
|
||||
```cpp
|
||||
// 策略模式处理不同消息类型
|
||||
if (obj.contains("type")) {
|
||||
QString type = obj.value("type").toString();
|
||||
if (type == "devices_list") handleDeviceList(obj);
|
||||
else if (type == "device_status") handleDeviceStatus(obj);
|
||||
} else {
|
||||
handleSensorData(obj); // 向后兼容
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据结构与算法 / Data Structures & Algorithms
|
||||
|
||||
#### 设备自动发现算法
|
||||
```cpp
|
||||
// O(n)复杂度的设备查找和自动添加
|
||||
bool deviceExists = false;
|
||||
for (int i = 0; i < m_deviceCombo->count(); ++i) {
|
||||
if (m_deviceCombo->itemData(i).toString() == deviceId) {
|
||||
deviceExists = true;
|
||||
break; // 早期退出优化
|
||||
}
|
||||
}
|
||||
```
|
||||
- **算法复杂度**: O(n)时间,O(1)空间
|
||||
- **优化策略**: 早期退出,直接字符串比较
|
||||
|
||||
#### 智能缓存机制
|
||||
```cpp
|
||||
// QMap实现的设备数据缓存
|
||||
QMap<QString, QString> m_deviceData; // device_id -> JSON字符串
|
||||
|
||||
// 压缩存储 + 按需解析
|
||||
m_deviceData[deviceId] = doc.toJson(QJsonDocument::Compact);
|
||||
```
|
||||
- **内存优化**: JSON紧凑格式,延迟解析
|
||||
- **查询效率**: O(log n)查找复杂度
|
||||
|
||||
### 4. 跨平台兼容性技术 / Cross-Platform Compatibility
|
||||
|
||||
#### GLIBC兼容性处理
|
||||
```cpp
|
||||
// 多层兼容性保障
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
||||
# if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 31)
|
||||
# define GLIBC_MAX_VERSION "2.31"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// 静态链接减少依赖
|
||||
QMAKE_CXXFLAGS += -static-libgcc -static-libstdc++
|
||||
QMAKE_CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0
|
||||
```
|
||||
|
||||
**兼容性挑战**:
|
||||
- 不同Linux发行版的GLIBC版本差异
|
||||
- Qt版本兼容性(支持5.12+)
|
||||
- 编译器差异处理
|
||||
|
||||
#### Qt5兼容性设计
|
||||
```cpp
|
||||
// 使用Qt5兼容的API
|
||||
connect(m_deviceCombo, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(onDeviceSelectionChanged()));
|
||||
// 而非Qt5.15+的函数指针语法
|
||||
```
|
||||
|
||||
## 💡 面试重点问题 / Key Interview Questions
|
||||
|
||||
### 1. 设计决策类问题
|
||||
|
||||
**Q: 为什么选择Qt而不是其他GUI框架?**
|
||||
**A**:
|
||||
- **跨平台一致性**: 一次编写,多平台运行
|
||||
- **成熟生态**: 丰富的网络、JSON、定时器模块
|
||||
- **信号槽机制**: 简化异步编程
|
||||
- **商业项目**: 适合企业级应用开发
|
||||
|
||||
**Q: 为什么使用TCP而不是UDP或HTTP?**
|
||||
**A**:
|
||||
- **可靠性**: TCP保证数据完整性和顺序
|
||||
- **实时性**: 相比HTTP减少握手开销
|
||||
- **连接管理**: 支持长连接,状态同步
|
||||
- **防火墙友好**: 标准端口,易部署
|
||||
|
||||
### 2. 技术实现类问题
|
||||
|
||||
**Q: 如何处理TCP粘包问题?**
|
||||
```cpp
|
||||
// 按行分割 + JSON验证
|
||||
while (m_socket->canReadLine()) {
|
||||
QByteArray line = m_socket->readLine().trimmed();
|
||||
if (line.isEmpty()) continue; // 跳过空行
|
||||
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(line, &err);
|
||||
if (err.error == QJsonParseError::NoError) {
|
||||
processValidMessage(doc.object());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Q: 如何实现断线自动重连?**
|
||||
```cpp
|
||||
// 状态驱动的重连机制
|
||||
void MainWindow::onDisconnected() {
|
||||
m_pollTimer->stop();
|
||||
if (!m_reconnectTimer->isActive()) { // 防重复启动
|
||||
m_reconnectTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
// 定时器触发重连
|
||||
void MainWindow::onReconnectTimeout() {
|
||||
if (m_socket->state() == QAbstractSocket::UnconnectedState) {
|
||||
m_socket->connectToHost(m_ipEdit->text(), m_port);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 优化与性能类问题
|
||||
|
||||
**Q: 如何优化内存使用?**
|
||||
```cpp
|
||||
// 1. Qt对象树自动管理
|
||||
m_pollTimer = new QTimer(this); // 父对象管理生命周期
|
||||
|
||||
// 2. 数据压缩存储
|
||||
m_deviceData[deviceId] = doc.toJson(QJsonDocument::Compact);
|
||||
|
||||
// 3. 按需解析
|
||||
if (m_deviceData.contains(deviceId)) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
|
||||
// 只在需要时解析
|
||||
}
|
||||
```
|
||||
|
||||
**Q: 如何提升用户体验?**
|
||||
- **即时反馈**: 连接状态实时显示
|
||||
- **智能缓存**: 设备切换时显示历史数据
|
||||
- **容错处理**: JSON解析失败不影响其他功能
|
||||
- **自动发现**: 新设备自动添加到列表
|
||||
|
||||
### 4. 问题解决类问题
|
||||
|
||||
**Q: 遇到过的技术难点及解决方案**
|
||||
|
||||
**难点1: GLIBC兼容性问题**
|
||||
```bash
|
||||
# 错误: version 'GLIBC_2.32' not found
|
||||
# 解决: 静态链接 + 版本限制
|
||||
QMAKE_CXXFLAGS += -static-libgcc -static-libstdc++
|
||||
QMAKE_CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0
|
||||
```
|
||||
|
||||
**难点2: JSON数据格式兼容**
|
||||
```cpp
|
||||
// 解决方案: 分层处理 + 向后兼容
|
||||
if (obj.contains("type")) {
|
||||
// 新格式: 有type字段
|
||||
processTypedMessage(obj);
|
||||
} else {
|
||||
// 旧格式: 传感器数据
|
||||
processLegacyData(obj);
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 项目扩展点 / Project Extensions
|
||||
|
||||
### 1. 功能扩展建议
|
||||
- **历史数据图表**: 集成Qt Charts模块
|
||||
- **数据导出**: 支持CSV/Excel格式导出
|
||||
- **用户认证**: 添加登录和权限管理
|
||||
- **多语言**: 国际化支持
|
||||
|
||||
### 2. 技术升级方向
|
||||
- **Qt6迁移**: 利用最新Qt6特性
|
||||
- **异步网络**: 升级到QNetworkAccessManager
|
||||
- **数据加密**: 添加TLS/SSL支持
|
||||
- **云同步**: 数据云端备份
|
||||
|
||||
## 📊 项目指标 / Project Metrics
|
||||
|
||||
### 代码质量指标
|
||||
- **圈复杂度**: 平均3-5(单个函数)
|
||||
- **代码重复率**: <5%
|
||||
- **注释覆盖率**: 15%(核心逻辑)
|
||||
- **测试覆盖**: 可扩展单元测试
|
||||
|
||||
### 性能指标
|
||||
- **启动时间**: <2秒(冷启动)
|
||||
- **内存占用**: ~20MB(运行时)
|
||||
- **网络延迟**: <100ms(局域网)
|
||||
- **UI响应**: <50ms(用户操作)
|
||||
|
||||
## 🔧 技术栈深度 / Technology Stack Deep Dive
|
||||
|
||||
### Qt框架掌握程度
|
||||
- **核心模块**: QtCore, QtGui, QtWidgets, QtNetwork
|
||||
- **设计模式**: 信号槽、对象树、事件循环
|
||||
- **内存管理**: RAII、父子对象关系
|
||||
- **多线程**: QTimer、QThread(了解)
|
||||
|
||||
### C++语言特性
|
||||
- **C++11标准**: auto、Lambda、智能指针
|
||||
- **STL容器**: QMap、QList、QString
|
||||
- **异常处理**: Qt的异常安全机制
|
||||
- **模板编程**: 信号槽的模板实现
|
||||
|
||||
### 网络编程
|
||||
- **TCP/IP协议**: 连接管理、数据传输
|
||||
- **Socket编程**: QTcpSocket使用
|
||||
- **协议设计**: JSON消息格式
|
||||
- **并发处理**: 异步I/O模型
|
||||
|
||||
## 💼 面试演示建议 / Interview Demo Suggestions
|
||||
|
||||
### 1. 核心功能演示顺序
|
||||
1. **连接建立**: 展示网络连接过程
|
||||
2. **设备发现**: 自动添加新设备
|
||||
3. **数据监控**: 实时数据显示
|
||||
4. **设备控制**: 远程控制功能
|
||||
5. **断线重连**: 网络异常处理
|
||||
|
||||
### 2. 技术要点强调
|
||||
- **架构设计**: 事件驱动、模块化
|
||||
- **代码质量**: 错误处理、边界检查
|
||||
- **性能优化**: 缓存机制、资源管理
|
||||
- **用户体验**: 界面响应、状态反馈
|
||||
|
||||
### 3. 扩展思考讨论
|
||||
- **技术选型理由**
|
||||
- **架构改进建议**
|
||||
- **性能优化方案**
|
||||
- **新技术集成**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结 / Summary
|
||||
|
||||
这个项目展示了从需求分析到技术实现的完整工程能力,涵盖了GUI开发、网络编程、数据处理、跨平台兼容等多个技术领域。项目规模适中,技术深度足够,非常适合作为技术面试的案例展示。
|
||||
|
||||
**核心竞争力**:
|
||||
- 扎实的C++和Qt框架基础
|
||||
- 网络编程和异步处理经验
|
||||
- 跨平台开发和兼容性处理能力
|
||||
- 系统设计和问题解决能力
|
||||
|
||||
**面试价值**:
|
||||
- 展示实际项目经验
|
||||
- 体现代码质量和工程思维
|
||||
- 体现学习能力和技术热情
|
||||
- 提供深入技术讨论的基础
|
||||
@@ -0,0 +1,978 @@
|
||||
# Qt前端客户端 / Qt Frontend Client
|
||||
|
||||
一个基于Qt框架开发的跨平台温湿度监控系统客户端应用程序,提供直观的图形用户界面用于实时监控和控制多个STM32设备。
|
||||
|
||||
A cross-platform temperature and humidity monitoring system client application developed with Qt framework, providing an intuitive graphical user interface for real-time monitoring and control of multiple STM32 devices.
|
||||
|
||||
## 📋 功能概览 / Features Overview
|
||||
|
||||
### 核心功能 / Core Features
|
||||
- **实时数据监控** - 显示温度、湿度、蜂鸣器和LED状态
|
||||
- **多设备管理** - 支持同时监控多个STM32设备
|
||||
- **设备控制** - 远程控制设备的蜂鸣器和LED
|
||||
- **自动重连** - 网络断开时自动重新连接
|
||||
- **可配置刷新** - 自定义数据刷新间隔
|
||||
- **跨平台支持** - 支持Linux、Windows和WSL
|
||||
|
||||
### 界面组件 / Interface Components
|
||||
- **连接配置** - 服务器地址和端口设置
|
||||
- **设备选择** - 下拉菜单选择当前监控设备
|
||||
- **数据显示** - 实时显示传感器数据
|
||||
- **控制面板** - 复选框控制设备输出
|
||||
- **状态指示** - 连接状态和设备状态显示
|
||||
|
||||
## 🛠️ 技术特性 / Technical Features
|
||||
|
||||
### 兼容性设计 / Compatibility Design
|
||||
- **Qt版本** - 兼容Qt 5.12及以上版本
|
||||
- **C++标准** - 使用C++11标准
|
||||
- **GLIBC兼容** - 支持GLIBC 2.31及更新版本
|
||||
- **静态链接** - 减少运行时依赖
|
||||
- **网络模块** - 基于QTcpSocket的TCP通信
|
||||
|
||||
### 架构特点 / Architecture Features
|
||||
- **事件驱动** - 基于Qt信号槽机制
|
||||
- **异步通信** - 非阻塞网络I/O
|
||||
- **定时器管理** - 自动数据轮询和重连机制
|
||||
- **JSON解析** - 结构化数据处理
|
||||
- **内存管理** - Qt对象树自动内存管理
|
||||
|
||||
## 🚀 快速开始 / Quick Start
|
||||
|
||||
### 环境要求 / Prerequisites
|
||||
|
||||
#### 系统要求 / System Requirements
|
||||
- **操作系统**: Linux 18.04+, Windows 10+, WSL2
|
||||
- **处理器**: x86_64, ARM64
|
||||
- **内存**: 最少512MB RAM
|
||||
- **磁盘空间**: ~50MB
|
||||
|
||||
#### 软件依赖 / Software Dependencies
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install -y qt5-default qtbase5-dev qtbase5-dev-tools
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo yum install -y qt5-qtbase-devel qt5-qttools-devel
|
||||
|
||||
# Windows
|
||||
# 下载并安装Qt 5.12+ from https://www.qt.io/download
|
||||
```
|
||||
|
||||
### 编译安装 / Build & Install
|
||||
|
||||
#### 方法一:使用qmake(推荐)
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd qt_client
|
||||
|
||||
# 生成Makefile
|
||||
qmake qt_client.pro
|
||||
|
||||
# 编译(使用所有CPU核心)
|
||||
make -j$(nproc)
|
||||
|
||||
# 运行程序
|
||||
./qt_client
|
||||
```
|
||||
|
||||
#### 方法二:使用CMake(高级用户)
|
||||
```bash
|
||||
# 如果有CMakeLists.txt(需要创建)
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
./qt_client
|
||||
```
|
||||
|
||||
#### Windows编译
|
||||
```bash
|
||||
# 使用Qt命令提示符
|
||||
cd qt_client
|
||||
qmake qt_client.pro
|
||||
nmake
|
||||
qt_client.exe
|
||||
```
|
||||
|
||||
## 📖 使用指南 / User Guide
|
||||
|
||||
### 启动和连接 / Startup & Connection
|
||||
|
||||
1. **启动应用程序**
|
||||
```bash
|
||||
./qt_client
|
||||
```
|
||||
|
||||
2. **配置服务器连接**
|
||||
- 服务器地址:输入后端服务器IP地址(如:127.0.0.1)
|
||||
- 端口:输入服务器端口(默认:5000)
|
||||
- 点击"连接"按钮
|
||||
|
||||
3. **选择监控设备**
|
||||
- 点击设备下拉菜单
|
||||
- 选择要监控的设备ID
|
||||
- 系统将自动开始显示该设备数据
|
||||
|
||||
### 界面操作 / Interface Operations
|
||||
|
||||
#### 连接管理 / Connection Management
|
||||
- **连接按钮**:建立/断开与服务器的连接
|
||||
- **重连间隔**:设置断线重连的时间间隔(秒)
|
||||
- **数据刷新间隔**:设置数据轮询频率(毫秒)
|
||||
|
||||
#### 设备监控 / Device Monitoring
|
||||
- **设备列表**:显示所有已连接的设备
|
||||
- **刷新按钮**:手动刷新设备列表
|
||||
- **当前设备**:显示正在监控的设备ID
|
||||
|
||||
#### 数据显示 / Data Display
|
||||
- **温度**:显示当前温度值(°C)
|
||||
- **湿度**:显示当前湿度值(%)
|
||||
- **蜂鸣器状态**:显示蜂鸣器开关状态
|
||||
- **LED状态**:显示三个LED的开关状态
|
||||
|
||||
#### 设备控制 / Device Control
|
||||
- **蜂鸣器控制**:勾选复选框开启蜂鸣器
|
||||
- **LED控制**:勾选对应LED的复选框控制开关
|
||||
- **发送控制**:点击"发送控制"应用设置
|
||||
|
||||
### 高级功能 / Advanced Features
|
||||
|
||||
#### 自动重连 / Auto Reconnection
|
||||
- 网络断开时程序会自动尝试重连
|
||||
- 重连间隔可在界面中配置
|
||||
- 支持手动禁用自动重连
|
||||
|
||||
#### 数据轮询 / Data Polling
|
||||
- 定时向服务器请求最新数据
|
||||
- 轮询间隔可自定义(100ms-60s)
|
||||
- 支持暂停/恢复数据轮询
|
||||
|
||||
## 🔧 配置选项 / Configuration Options
|
||||
|
||||
### 项目配置 / Project Configuration
|
||||
|
||||
#### qt_client.pro 主要配置项
|
||||
```pro
|
||||
# Qt模块配置
|
||||
QT += core widgets network
|
||||
|
||||
# C++标准
|
||||
CONFIG += c++11
|
||||
|
||||
# 目标名称
|
||||
TARGET = qt_client
|
||||
TEMPLATE = app
|
||||
|
||||
# 兼容性定义
|
||||
DEFINES += _GLIBCXX_USE_CXX11_ABI=0
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050C00
|
||||
```
|
||||
|
||||
### 运行时配置 / Runtime Configuration
|
||||
|
||||
#### 网络配置 / Network Configuration
|
||||
- **超时时间**:连接超时5秒
|
||||
- **缓冲区大小**:接收缓冲区8KB
|
||||
- **协议**:TCP/IP协议
|
||||
- **数据格式**:JSON文本格式
|
||||
|
||||
#### 界面配置 / Interface Configuration
|
||||
- **窗口大小**:800x600像素(可调整)
|
||||
- **字体**:系统默认字体
|
||||
- **主题**:系统原生主题
|
||||
- **语言**:中文/英文(基于系统语言)
|
||||
|
||||
## 📊 数据协议 / Data Protocol
|
||||
|
||||
### 客户端发送 / Client to Server
|
||||
|
||||
#### 轮询请求 / Poll Request
|
||||
```json
|
||||
{
|
||||
"type": "poll",
|
||||
"device_id": "dev001"
|
||||
}
|
||||
```
|
||||
|
||||
#### 控制命令 / Control Command
|
||||
```json
|
||||
{
|
||||
"type": "control",
|
||||
"device_id": "dev001",
|
||||
"buzzer": 1,
|
||||
"led0": 0,
|
||||
"led1": 1,
|
||||
"led2": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 服务器响应 / Server to Client
|
||||
|
||||
#### 传感器数据 / Sensor Data
|
||||
```json
|
||||
{
|
||||
"device_id": "dev001",
|
||||
"temp": 25.5,
|
||||
"humidity": 60.0,
|
||||
"buzzer": 0,
|
||||
"led0": 1,
|
||||
"led1": 0,
|
||||
"led2": 0,
|
||||
"timestamp": "2025-11-03 10:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### 设备列表 / Device List
|
||||
```json
|
||||
{
|
||||
"type": "device_list",
|
||||
"devices": ["dev001", "dev002", "dev003"]
|
||||
}
|
||||
```
|
||||
|
||||
## 🐛 故障排除 / Troubleshooting
|
||||
|
||||
### 常见问题 / Common Issues
|
||||
|
||||
#### 编译问题 / Build Issues
|
||||
1. **找不到qmake**
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install -y qt5-qmake
|
||||
|
||||
# 检查Qt安装
|
||||
which qmake
|
||||
qmake -version
|
||||
```
|
||||
|
||||
2. **链接错误**
|
||||
```bash
|
||||
# 清理重新编译
|
||||
make clean
|
||||
qmake qt_client.pro
|
||||
make
|
||||
```
|
||||
|
||||
3. **GLIBC版本不兼容**
|
||||
```bash
|
||||
# 检查GLIBC版本
|
||||
ldd --version
|
||||
|
||||
# 使用兼容模式运行
|
||||
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu
|
||||
./qt_client
|
||||
```
|
||||
|
||||
#### 运行时问题 / Runtime Issues
|
||||
|
||||
1. **无法连接服务器**
|
||||
- 检查服务器是否运行:`netstat -tlnp | grep 5000`
|
||||
- 验证IP地址和端口
|
||||
- 检查防火墙设置
|
||||
|
||||
2. **设备列表为空**
|
||||
- 确认STM32设备已连接到服务器
|
||||
- 检查设备网络连接
|
||||
- 查看服务器日志
|
||||
|
||||
3. **数据不更新**
|
||||
- 检查轮询间隔设置
|
||||
- 确认设备在线状态
|
||||
- 重启应用程序
|
||||
|
||||
### 调试模式 / Debug Mode
|
||||
|
||||
#### 启用调试输出 / Enable Debug Output
|
||||
```bash
|
||||
# 编译时添加调试标志
|
||||
qmake CONFIG+=debug qt_client.pro
|
||||
make
|
||||
|
||||
# 使用调试器运行
|
||||
gdb ./qt_client
|
||||
```
|
||||
|
||||
#### 日志信息 / Log Information
|
||||
- 网络连接状态
|
||||
- 数据接收/发送记录
|
||||
- 错误信息和异常
|
||||
- 设备状态变化
|
||||
|
||||
## 🔬 核心逻辑实现详解 / Core Logic Implementation Details
|
||||
|
||||
### 网络通信架构 / Network Communication Architecture
|
||||
|
||||
#### TCP连接管理 / TCP Connection Management
|
||||
程序采用Qt的QTcpSocket进行TCP通信,支持断线自动重连:
|
||||
|
||||
```cpp
|
||||
// 连接状态机 (mainwindow.cpp:149-162)
|
||||
void MainWindow::connectToServer() {
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
// 已连接状态 -> 断开连接
|
||||
m_socket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
// 未连接状态 -> 尝试连接
|
||||
QString ip = m_ipEdit->text().trimmed();
|
||||
quint16 port = m_portEdit->text().toUShort(&ok);
|
||||
m_socket->connectToHost(ip, port);
|
||||
}
|
||||
```
|
||||
|
||||
**连接生命周期管理**:
|
||||
1. **连接建立** (mainwindow.cpp:164-172): 启动轮询定时器,停止重连定时器
|
||||
2. **连接断开** (mainwindow.cpp:174-179): 停止轮询定时器,启动重连定时器
|
||||
3. **自动重连** (mainwindow.cpp:321-331): 定时器触发时检查连接状态,自动重连
|
||||
|
||||
#### 数据接收处理逻辑 / Data Reception Processing Logic
|
||||
实现智能数据解析,支持多种数据格式:
|
||||
|
||||
```cpp
|
||||
// 分层数据处理 (mainwindow.cpp:181-306)
|
||||
void MainWindow::onReadyRead() {
|
||||
// 第一层:按行处理数据
|
||||
while (m_socket->canReadLine()) {
|
||||
QByteArray line = m_socket->readLine().trimmed();
|
||||
// JSON解析和验证
|
||||
QJsonDocument doc = QJsonDocument::fromJson(line, &err);
|
||||
// 消息类型分发处理
|
||||
}
|
||||
|
||||
// 第二层:处理无换行符的批量数据
|
||||
if (m_socket->bytesAvailable() > 0) {
|
||||
QByteArray raw = m_socket->readAll();
|
||||
// 分割并处理多行JSON数据
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**消息类型识别和处理**:
|
||||
- **设备列表消息** (`type: "devices_list"`): 动态更新设备下拉框
|
||||
- **设备状态消息** (`type: "device_status"`): 更新设备在线状态
|
||||
- **传感器数据**: 自动识别并添加新设备,更新显示
|
||||
- **控制状态响应**: 更新设备控制界面的状态
|
||||
|
||||
### 定时器管理系统 / Timer Management System
|
||||
|
||||
#### 双定时器架构 / Dual-Timer Architecture
|
||||
程序使用两个独立的定时器实现不同功能:
|
||||
|
||||
```cpp
|
||||
// 轮询定时器 - 数据采集 (mainwindow.cpp:125-129)
|
||||
m_pollTimer = new QTimer(this);
|
||||
m_pollTimer->setInterval(m_pollSpin->value()); // 默认100ms
|
||||
connect(m_pollTimer, SIGNAL(timeout()), this, SLOT(pollData()));
|
||||
|
||||
// 重连定时器 - 连接管理 (mainwindow.cpp:132-136)
|
||||
m_reconnectTimer = new QTimer(this);
|
||||
m_reconnectTimer->setInterval(m_reconnectSpin->value() * 1000); // 默认5秒
|
||||
m_reconnectTimer->setSingleShot(false); // 重复触发
|
||||
```
|
||||
|
||||
**定时器协调机制**:
|
||||
- **连接状态**: 轮询定时器启动,重连定时器停止
|
||||
- **断开状态**: 轮询定时器停止,重连定时器启动
|
||||
- **动态调整**: 用户可实时调整定时器间隔
|
||||
|
||||
### 设备管理系统 / Device Management System
|
||||
|
||||
#### 动态设备发现 / Dynamic Device Discovery
|
||||
实现设备的自动发现和状态管理:
|
||||
|
||||
```cpp
|
||||
// 设备自动添加逻辑 (mainwindow.cpp:226-244)
|
||||
QString deviceId = obj.value("device_id").toString();
|
||||
if (!deviceId.isEmpty()) {
|
||||
// 检查设备是否已存在于下拉框
|
||||
bool deviceExists = false;
|
||||
for (int i = 0; i < m_deviceCombo->count(); ++i) {
|
||||
if (m_deviceCombo->itemData(i).toString() == deviceId) {
|
||||
deviceExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动添加新发现的设备
|
||||
if (!deviceExists) {
|
||||
QString displayText = QString("%1 (在线)").arg(deviceId);
|
||||
m_deviceCombo->addItem(displayText, deviceId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 设备数据缓存 / Device Data Caching
|
||||
使用QMap实现设备数据的本地缓存:
|
||||
|
||||
```cpp
|
||||
// 设备数据存储 (mainwindow.h:79)
|
||||
QMap<QString, QString> m_deviceData; // device_id -> latest data
|
||||
|
||||
// 数据更新逻辑 (mainwindow.cpp:246-249)
|
||||
m_deviceData[deviceId] = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
|
||||
```
|
||||
|
||||
### 用户界面逻辑 / User Interface Logic
|
||||
|
||||
#### 信号槽连接模式 / Signal-Slot Connection Pattern
|
||||
采用Qt5兼容的信号槽连接方式:
|
||||
|
||||
```cpp
|
||||
// 兼容Qt 5.12的连接方式 (mainwindow.cpp:116-139)
|
||||
connect(m_connectBtn, SIGNAL(clicked()), this, SLOT(connectToServer()));
|
||||
connect(m_socket, SIGNAL(connected()), this, SLOT(onConnected()));
|
||||
connect(m_deviceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onDeviceSelectionChanged()));
|
||||
```
|
||||
|
||||
#### 设备切换逻辑 / Device Switching Logic
|
||||
设备选择切换时的完整处理流程:
|
||||
|
||||
```cpp
|
||||
// 设备选择处理 (mainwindow.cpp:333-368)
|
||||
void MainWindow::onDeviceSelectionChanged() {
|
||||
// 1. 更新当前设备ID
|
||||
m_currentDeviceId = m_deviceCombo->currentData().toString();
|
||||
|
||||
// 2. 加载该设备的缓存数据
|
||||
if (m_deviceData.contains(m_currentDeviceId)) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8(), &err);
|
||||
updateDisplayFromJson(doc.object());
|
||||
}
|
||||
|
||||
// 3. 请求设备的最新控制状态
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
QString request = QString("GET control %1\n").arg(m_currentDeviceId);
|
||||
m_socket->write(request.toUtf8());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 控制命令系统 / Control Command System
|
||||
|
||||
#### 命令构建和发送 / Command Construction and Sending
|
||||
控制命令的JSON构建和发送流程:
|
||||
|
||||
```cpp
|
||||
// 控制命令发送 (mainwindow.cpp:370-393)
|
||||
void MainWindow::sendControlCommand() {
|
||||
QJsonObject obj;
|
||||
obj["device_id"] = m_currentDeviceId;
|
||||
obj["buzzer"] = m_buzzerChk->isChecked() ? 1 : 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
obj[QString("led%1").arg(i)] = m_ledChk[i]->isChecked() ? 1 : 0;
|
||||
}
|
||||
|
||||
QJsonDocument doc(obj);
|
||||
QByteArray ba = doc.toJson(QJsonDocument::Compact) + '\n';
|
||||
m_socket->write(ba);
|
||||
m_socket->flush();
|
||||
}
|
||||
```
|
||||
|
||||
#### 本地状态同步 / Local State Synchronization
|
||||
发送命令后立即更新本地显示状态:
|
||||
|
||||
```cpp
|
||||
// 本地显示状态更新 (mainwindow.cpp:388-392)
|
||||
QJsonObject displayObj;
|
||||
displayObj["buzzer"] = m_buzzerChk->isChecked() ? 1 : 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
displayObj[QString("led%1").arg(i)] = m_ledChk[i]->isChecked() ? 1 : 0;
|
||||
}
|
||||
updateDisplayFromJson(displayObj);
|
||||
```
|
||||
|
||||
### 兼容性设计模式 / Compatibility Design Patterns
|
||||
|
||||
#### GLIBC兼容性处理 / GLIBC Compatibility Handling
|
||||
多层兼容性保障确保在不同系统上运行:
|
||||
|
||||
```cpp
|
||||
// GLIBC版本限制 (main.cpp:6-11, mainwindow.h:9-14)
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
||||
# if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 31)
|
||||
# define GLIBC_MAX_VERSION "2.31"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// 编译器标志设置 (qt_client.pro:21-25)
|
||||
QMAKE_CXXFLAGS += -static-libgcc -static-libstdc++
|
||||
QMAKE_CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0
|
||||
QMAKE_CXXFLAGS += -std=c++11
|
||||
```
|
||||
|
||||
#### Qt版本兼容 / Qt Version Compatibility
|
||||
使用Qt 5.12兼容的API和语法:
|
||||
|
||||
```cpp
|
||||
// 使用QString字面量而非QLatin1String (mainwindow.cpp:379)
|
||||
obj["device_id"] = m_currentDeviceId; // 兼容Qt 5.12
|
||||
|
||||
// 使用SIGNAL/SLOT宏而非函数指针 (mainwindow.cpp:122)
|
||||
connect(m_deviceCombo, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(onDeviceSelectionChanged()));
|
||||
```
|
||||
|
||||
### 错误处理机制 / Error Handling Mechanism
|
||||
|
||||
#### JSON解析容错 / JSON Parsing Fault Tolerance
|
||||
多层JSON解析错误处理:
|
||||
|
||||
```cpp
|
||||
// 分层JSON验证 (mainwindow.cpp:189-195, 277-282)
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(line, &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
qWarning() << "JSON parse error:" << err.errorString() << "line:" << line;
|
||||
continue; // 跳过错误数据,继续处理
|
||||
}
|
||||
if (!doc.isObject()) continue; // 验证数据类型
|
||||
```
|
||||
|
||||
#### 网络异常处理 / Network Exception Handling
|
||||
网络通信中的异常情况处理:
|
||||
|
||||
```cpp
|
||||
// 连接状态检查 (mainwindow.cpp:310-311)
|
||||
if (m_socket->state() != QAbstractSocket::ConnectedState) return;
|
||||
|
||||
// 端口解析容错 (mainwindow.cpp:157-159)
|
||||
quint16 port = m_portEdit->text().toUShort(&ok);
|
||||
if (!ok) port = 5001; // 默认端口
|
||||
```
|
||||
|
||||
## 🔮 扩展开发 / Extended Development
|
||||
|
||||
### 添加新功能 / Adding New Features
|
||||
|
||||
#### 1. 添加新的传感器支持
|
||||
```cpp
|
||||
// 在mainwindow.h中添加
|
||||
QLabel *m_pressureLabel; // 气压显示
|
||||
|
||||
// 在updateDisplayFromJson中处理
|
||||
if (obj.contains("pressure")) {
|
||||
m_pressureLabel->setText(QString::number(obj["pressure"].toDouble(), 'f', 2));
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 添加历史数据图表
|
||||
```pro
|
||||
# 在.pro文件中添加
|
||||
QT += charts
|
||||
```
|
||||
|
||||
#### 3. 添加数据导出功能
|
||||
```cpp
|
||||
// 导出CSV格式
|
||||
void exportToCSV(const QString &filename);
|
||||
```
|
||||
|
||||
### 界面定制 / Interface Customization
|
||||
|
||||
#### 主题支持 / Theme Support
|
||||
```cpp
|
||||
// 应用暗色主题
|
||||
QApplication::setStyle("Fusion");
|
||||
QPalette darkPalette;
|
||||
// 配置暗色调色板
|
||||
qApp->setPalette(darkPalette);
|
||||
```
|
||||
|
||||
#### 多语言支持 / Multi-language Support
|
||||
```cpp
|
||||
// 加载翻译文件
|
||||
QTranslator translator;
|
||||
translator.load("qt_client_zh_CN.qm");
|
||||
qApp->installTranslator(&translator);
|
||||
```
|
||||
|
||||
## 🔄 程序执行流程 / Program Execution Flow
|
||||
|
||||
### 应用程序启动流程 / Application Startup Flow
|
||||
```cpp
|
||||
// main.cpp 执行路径 (main.cpp:16-22)
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication a(argc, argv); // 1. 创建Qt应用程序实例
|
||||
MainWindow w; // 2. 创建主窗口实例
|
||||
w.show(); // 3. 显示主窗口
|
||||
return a.exec(); // 4. 进入Qt事件循环
|
||||
}
|
||||
```
|
||||
|
||||
### 主窗口初始化流程 / Main Window Initialization Flow
|
||||
```cpp
|
||||
// MainWindow构造函数执行路径 (mainwindow.cpp:35-143)
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
// 阶段1: UI组件创建和布局设置 (35-114)
|
||||
QWidget *central = new QWidget(this);
|
||||
// 创建输入框、按钮、标签等UI控件
|
||||
// 设置水平和垂直布局
|
||||
|
||||
// 阶段2: 信号槽连接 (116-139)
|
||||
connect(m_connectBtn, SIGNAL(clicked()), this, SLOT(connectToServer()));
|
||||
connect(m_socket, SIGNAL(connected()), this, SLOT(onConnected()));
|
||||
// 连接所有UI事件和网络事件
|
||||
|
||||
// 阶段3: 定时器初始化和配置 (125-136)
|
||||
m_pollTimer = new QTimer(this);
|
||||
m_reconnectTimer = new QTimer(this);
|
||||
// 配置定时器间隔和触发方式
|
||||
|
||||
// 阶段4: 窗口属性设置 (141-142)
|
||||
setWindowTitle(tr("温湿度监控"));
|
||||
resize(480, 200);
|
||||
}
|
||||
```
|
||||
|
||||
### 事件驱动处理流程 / Event-Driven Processing Flow
|
||||
|
||||
#### 网络事件处理链 / Network Event Processing Chain
|
||||
```
|
||||
用户点击"连接"按钮
|
||||
↓
|
||||
connectToServer() 被调用
|
||||
↓
|
||||
m_socket->connectToHost(ip, port)
|
||||
↓
|
||||
[网络连接建立]
|
||||
↓
|
||||
onConnected() 信号触发
|
||||
↓
|
||||
启动轮询定时器 + 停止重连定时器 + 自动刷新设备列表
|
||||
↓
|
||||
服务器发送数据
|
||||
↓
|
||||
onReadyRead() 信号触发
|
||||
↓
|
||||
JSON解析 + 消息类型分发 + UI更新
|
||||
```
|
||||
|
||||
#### 用户交互处理链 / User Interaction Processing Chain
|
||||
```
|
||||
用户选择设备
|
||||
↓
|
||||
onDeviceSelectionChanged() 被调用
|
||||
↓
|
||||
更新当前设备ID + 加载缓存数据 + 请求控制状态
|
||||
↓
|
||||
用户调整控制选项
|
||||
↓
|
||||
sendControlCommand() 被调用
|
||||
↓
|
||||
构建JSON命令 + 发送到服务器 + 同步本地显示
|
||||
```
|
||||
|
||||
## 📁 项目结构 / Project Structure
|
||||
|
||||
```
|
||||
qt_client/ # Qt客户端根目录
|
||||
├── main.cpp # 程序入口点
|
||||
├── mainwindow.h # 主窗口类声明
|
||||
├── mainwindow.cpp # 主窗口类实现
|
||||
├── qt_client.pro # qmake项目文件
|
||||
├── qt_client_compat.pro # 兼容性项目文件
|
||||
├── qt_client # 编译后的可执行文件
|
||||
├── README.md # 本文档
|
||||
└── resources/ # 资源文件(如果存在)
|
||||
├── icons/ # 图标文件
|
||||
├── images/ # 图片文件
|
||||
└── translations/ # 翻译文件
|
||||
```
|
||||
|
||||
### 文件说明 / File Descriptions
|
||||
|
||||
| 文件 | 描述 | 主要功能 |
|
||||
|------|------|----------|
|
||||
| `main.cpp` | 程序入口 | GLIBC兼容性处理,Qt应用初始化,主窗口创建 |
|
||||
| `mainwindow.h` | 主窗口头文件 | 类声明,UI组件定义,信号槽声明,成员变量定义 |
|
||||
| `mainwindow.cpp` | 主窗口实现 | UI布局,网络通信,数据处理,事件处理,兼容性处理 |
|
||||
| `qt_client.pro` | 项目配置 | Qt模块配置,编译标志,GLIBC兼容性设置,依赖管理 |
|
||||
|
||||
## 🧠 数据结构与算法 / Data Structures & Algorithms
|
||||
|
||||
### 核心数据结构 / Core Data Structures
|
||||
|
||||
#### 设备数据管理结构 / Device Data Management Structure
|
||||
```cpp
|
||||
// 设备数据缓存映射 (mainwindow.h:79)
|
||||
QMap<QString, QString> m_deviceData; // device_id -> JSON数据字符串
|
||||
|
||||
// 设备选择组合框数据结构 (mainwindow.cpp:203-211)
|
||||
m_deviceCombo->addItem(displayText, deviceId); // 显示文本 + 设备ID作为用户数据
|
||||
```
|
||||
|
||||
**数据结构特点**:
|
||||
- **键值映射**: 使用QString作为键,避免内存拷贝
|
||||
- **JSON缓存**: 原始JSON字符串缓存,减少重复解析开销
|
||||
- **双重存储**: ComboBox存储显示文本和设备ID,便于UI展示和数据查询
|
||||
|
||||
#### 网络消息处理结构 / Network Message Processing Structure
|
||||
```cpp
|
||||
// 分层消息处理 (mainwindow.cpp:181-306)
|
||||
// 第一层:行式数据处理
|
||||
while (m_socket->canReadLine()) {
|
||||
QByteArray line = m_socket->readLine().trimmed();
|
||||
// JSON解析和消息类型识别
|
||||
}
|
||||
|
||||
// 第二层:批量数据处理
|
||||
if (m_socket->bytesAvailable() > 0) {
|
||||
QByteArray raw = m_socket->readAll();
|
||||
QList<QByteArray> parts = raw.split('\n');
|
||||
// 逐个处理每个JSON消息
|
||||
}
|
||||
```
|
||||
|
||||
### 关键算法实现 / Key Algorithm Implementations
|
||||
|
||||
#### 设备自动发现算法 / Device Auto-Discovery Algorithm
|
||||
```cpp
|
||||
// 设备存在性检查算法 (mainwindow.cpp:229-243)
|
||||
bool deviceExists = false;
|
||||
for (int i = 0; i < m_deviceCombo->count(); ++i) {
|
||||
if (m_deviceCombo->itemData(i).toString() == deviceId) {
|
||||
deviceExists = true;
|
||||
break; // 早期退出优化
|
||||
}
|
||||
}
|
||||
|
||||
// 设备自动添加算法
|
||||
if (!deviceExists) {
|
||||
QString displayText = QString("%1 (在线)").arg(deviceId);
|
||||
m_deviceCombo->addItem(displayText, deviceId);
|
||||
}
|
||||
```
|
||||
|
||||
**算法复杂度**: O(n) 时间复杂度,其中n为设备数量
|
||||
**优化策略**:
|
||||
- 早期退出减少不必要的遍历
|
||||
- 设备ID直接比较,避免字符串解析
|
||||
|
||||
#### JSON消息解析算法 / JSON Message Parsing Algorithm
|
||||
```cpp
|
||||
// 容错JSON解析算法 (mainwindow.cpp:188-195)
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(line, &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
qWarning() << "JSON parse error:" << err.errorString();
|
||||
continue; // 跳过错误数据,继续处理
|
||||
}
|
||||
|
||||
// 消息类型分发算法 (mainwindow.cpp:198-224)
|
||||
if (obj.contains("type")) {
|
||||
QString type = obj.value("type").toString();
|
||||
if (type == "devices_list") {
|
||||
// 处理设备列表消息
|
||||
} else if (type == "device_status") {
|
||||
// 处理设备状态消息
|
||||
}
|
||||
} else {
|
||||
// 处理传感器数据消息
|
||||
}
|
||||
```
|
||||
|
||||
**算法特点**:
|
||||
- **错误隔离**: 单个JSON解析失败不影响其他消息处理
|
||||
- **类型分发**: 基于消息类型的策略模式处理
|
||||
- **向后兼容**: 支持无type字段的遗留消息格式
|
||||
|
||||
#### 定时器协调算法 / Timer Coordination Algorithm
|
||||
```cpp
|
||||
// 连接状态驱动的定时器管理 (mainwindow.cpp:164-179)
|
||||
void MainWindow::onConnected() {
|
||||
m_connectBtn->setText(tr("断开"));
|
||||
m_pollTimer->start(); // 启动数据轮询
|
||||
m_reconnectTimer->stop(); // 停止自动重连
|
||||
}
|
||||
|
||||
void MainWindow::onDisconnected() {
|
||||
m_connectBtn->setText(tr("连接"));
|
||||
m_pollTimer->stop(); // 停止数据轮询
|
||||
if (!m_reconnectTimer->isActive()) {
|
||||
m_reconnectTimer->start(); // 启动自动重连
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**协调机制**:
|
||||
- **互斥控制**: 轮询定时器和重连定时器永远不会同时运行
|
||||
- **状态驱动**: 定时器状态完全由网络连接状态决定
|
||||
- **防重复**: 检查定时��状态,避免重复启动
|
||||
|
||||
### 内存管理策略 / Memory Management Strategy
|
||||
|
||||
#### Qt对象树管理 / Qt Object Tree Management
|
||||
```cpp
|
||||
// 自动内存管理 (mainwindow.cpp:36)
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), m_socket(new QTcpSocket(this)) {
|
||||
// 所有UI组件以this为父对象,自动管理生命周期
|
||||
m_pollTimer = new QTimer(this);
|
||||
m_reconnectTimer = new QTimer(this);
|
||||
}
|
||||
```
|
||||
|
||||
**内存管理特点**:
|
||||
- **父子关系**: 所有UI组件和定时器以主窗口为父对象
|
||||
- **自动清理**: 主窗口销毁时自动清理所有子对象
|
||||
- **无需手动delete**: Qt对象树自动管理内存分配和释放
|
||||
|
||||
#### 数据缓存优化 / Data Caching Optimization
|
||||
```cpp
|
||||
// JSON数据压缩缓存 (mainwindow.cpp:248)
|
||||
m_deviceData[deviceId] = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
// 按需加载策略 (mainwindow.cpp:342-350)
|
||||
if (m_deviceData.contains(m_currentDeviceId)) {
|
||||
QString data = m_deviceData[m_currentDeviceId];
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8(), &err);
|
||||
// 只在需要时才解析JSON数据
|
||||
}
|
||||
```
|
||||
|
||||
**优化策略**:
|
||||
- **压缩存储**: 使用Compact格式减少内存占用
|
||||
- **延迟解析**: 只在显示需要时才解析JSON
|
||||
- **LRU潜力**: QMap天然支持最近最少使用访问模式
|
||||
|
||||
## 📚 API参考 / API Reference
|
||||
|
||||
### 主要类 / Main Classes
|
||||
|
||||
#### MainWindow类
|
||||
```cpp
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
void connectToServer(); // 连接服务器
|
||||
void onConnected(); // 连接成功
|
||||
void onDisconnected(); // 连接断开
|
||||
void onReadyRead(); // 数据接收
|
||||
void pollData(); // 轮询数据
|
||||
void sendControlCommand(); // 发送控制命令
|
||||
|
||||
private:
|
||||
void updateDisplayFromJson(const QJsonObject &obj);
|
||||
|
||||
// UI组件
|
||||
QLineEdit *m_ipEdit; // IP地址输入
|
||||
QLineEdit *m_portEdit; // 端口输入
|
||||
QComboBox *m_deviceCombo; // 设备选择
|
||||
QLabel *m_tempLabel; // 温度显示
|
||||
QLabel *m_humLabel; // 湿度显示
|
||||
QTcpSocket *m_socket; // TCP套接字
|
||||
QTimer *m_pollTimer; // 轮询定时器
|
||||
};
|
||||
```
|
||||
|
||||
### 信号槽连接 / Signal-Slot Connections
|
||||
```cpp
|
||||
// 网络连接
|
||||
connect(m_socket, &QTcpSocket::connected, this, &MainWindow::onConnected);
|
||||
connect(m_socket, &QTcpSocket::disconnected, this, &MainWindow::onDisconnected);
|
||||
connect(m_socket, &QTcpSocket::readyRead, this, &MainWindow::onReadyRead);
|
||||
|
||||
// UI交互
|
||||
connect(m_connectBtn, &QPushButton::clicked, this, &MainWindow::connectToServer);
|
||||
connect(m_sendControlBtn, &QPushButton::clicked, this, &MainWindow::sendControlCommand);
|
||||
|
||||
// 定时器
|
||||
connect(m_pollTimer, &QTimer::timeout, this, &MainWindow::pollData);
|
||||
```
|
||||
|
||||
## 📈 性能优化 / Performance Optimization
|
||||
|
||||
### 内存优化 / Memory Optimization
|
||||
- 使用Qt对象树自动管理内存
|
||||
- 避免内存泄漏,及时释放资源
|
||||
- 合理设置缓冲区大小
|
||||
|
||||
### 网络优化 / Network Optimization
|
||||
- 使用TCP_NODELAY减少延迟
|
||||
- 批量处理网络数据
|
||||
- 实现数据压缩传输
|
||||
|
||||
### 渲染优化 / Rendering Optimization
|
||||
- 减少不必要的UI更新
|
||||
- 使用双缓冲减少闪烁
|
||||
- 优化重绘频率
|
||||
|
||||
## 🔒 安全考虑 / Security Considerations
|
||||
|
||||
### 网络安全 / Network Security
|
||||
- 验证服务器身份证书
|
||||
- 加密敏感数据传输
|
||||
- 实现访问控制机制
|
||||
|
||||
### 数据安全 / Data Security
|
||||
- 本地数据加密存储
|
||||
- 定期清理临时文件
|
||||
- 记录操作审计日志
|
||||
|
||||
## 📄 许可证 / License
|
||||
|
||||
本Qt客户端项目遵循MIT许可证:
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Temperature & Humidity Monitoring System
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
```
|
||||
|
||||
## 🤝 贡献指南 / Contributing Guide
|
||||
|
||||
### 开发流程 / Development Workflow
|
||||
1. Fork项目仓库
|
||||
2. 创建功能分支:`git checkout -b feature/new-feature`
|
||||
3. 提交更改:`git commit -am 'Add new feature'`
|
||||
4. 推送分支:`git push origin feature/new-feature`
|
||||
5. 创建Pull Request
|
||||
|
||||
### 代码规范 / Code Style
|
||||
- 遵循Qt官方编码规范
|
||||
- 使用有意义的变量和函数名
|
||||
- 添加适当的注释和文档
|
||||
- 保持代码简洁和可读性
|
||||
|
||||
## 📞 技术支持 / Technical Support
|
||||
|
||||
### 联系方式 / Contact Information
|
||||
- 📧 **邮箱**: [your-email@example.com]
|
||||
- 🐛 **问题反馈**: [GitHub Issues Page]
|
||||
- 📖 **项目文档**: [Project Wiki]
|
||||
- 💬 **技术讨论**: [Discussion Forum]
|
||||
|
||||
### 版本历史 / Version History
|
||||
|
||||
| 版本 | 日期 | 更新内容 |
|
||||
|------|------|----------|
|
||||
| v1.0.0 | 2025-11-03 | 初始版本发布 |
|
||||
| v1.1.0 | 计划中 | 添加历史数据图表 |
|
||||
| v1.2.0 | 计划中 | 支持数据导出功能 |
|
||||
| v2.0.0 | 计划中 | 全新UI设计和主题支持 |
|
||||
|
||||
---
|
||||
|
||||
**最后更新 / Last Updated**: 2025-11-03
|
||||
|
||||
**文档版本 / Documentation Version**: v1.0.0
|
||||
@@ -0,0 +1,22 @@
|
||||
// 添加GLIBC兼容性支持
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
// 限制GLIBC版本兼容性到2.31
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
||||
# if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 31)
|
||||
# define GLIBC_MAX_VERSION "2.31"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
#include "mainwindow.h"
|
||||
#include <QtWidgets>
|
||||
#include <QTcpSocket>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QTimer>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QMap>
|
||||
#include <QHeaderView>
|
||||
|
||||
// 添加GLIBC兼容性支持
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__GLIBC__) && (__GLIBC__ >= 2)
|
||||
# if defined(_GNU_SOURCE) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))
|
||||
# define USE_GNU 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// 修复glibc 2.31兼容性问题
|
||||
#ifndef GLIBC_MAX_VERSION
|
||||
#define GLIBC_MAX_VERSION "2.31"
|
||||
#endif
|
||||
|
||||
// 确保定时器相关功能与旧版GLIBC兼容
|
||||
#if defined(__GLIBC__) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 31))
|
||||
// 提供向后兼容的实现
|
||||
#endif
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), m_socket(new QTcpSocket(this))
|
||||
{
|
||||
QWidget *central = new QWidget(this);
|
||||
setCentralWidget(central);
|
||||
|
||||
QLabel *lblIp = new QLabel(tr("服务器 IP:"));
|
||||
m_ipEdit = new QLineEdit("159.195.9.238");
|
||||
QLabel *lblPort = new QLabel(tr("端口:"));
|
||||
m_portEdit = new QLineEdit("5000");
|
||||
m_connectBtn = new QPushButton(tr("连接"));
|
||||
|
||||
// 设备选择和刷新
|
||||
QLabel *lblDevice = new QLabel(tr("设备:"));
|
||||
m_deviceCombo = new QComboBox;
|
||||
m_deviceCombo->setMinimumWidth(120);
|
||||
m_refreshBtn = new QPushButton(tr("刷新"));
|
||||
m_currentDeviceLabel = new QLabel(tr("当前设备: 未选择"));
|
||||
|
||||
m_tempLabel = new QLabel(tr("温度: --"));
|
||||
m_humLabel = new QLabel(tr("湿度: --"));
|
||||
m_buzzerLabel = new QLabel(tr("蜂鸣器: --"));
|
||||
|
||||
// 创建3个LED状态标签
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_ledLabels[i] = new QLabel(tr("小灯%1: --").arg(i));
|
||||
}
|
||||
|
||||
QHBoxLayout *topLayout = new QHBoxLayout;
|
||||
topLayout->addWidget(lblIp);
|
||||
topLayout->addWidget(m_ipEdit);
|
||||
topLayout->addWidget(lblPort);
|
||||
topLayout->addWidget(m_portEdit);
|
||||
topLayout->addWidget(m_connectBtn);
|
||||
|
||||
// 设备选择行
|
||||
QHBoxLayout *deviceLayout = new QHBoxLayout;
|
||||
deviceLayout->addWidget(lblDevice);
|
||||
deviceLayout->addWidget(m_deviceCombo);
|
||||
deviceLayout->addWidget(m_refreshBtn);
|
||||
deviceLayout->addWidget(m_currentDeviceLabel);
|
||||
|
||||
|
||||
// 读取与重连设置以及测试发送按钮
|
||||
QLabel *lblPoll = new QLabel(tr("读取间隔(ms):"));
|
||||
m_pollSpin = new QSpinBox;
|
||||
m_pollSpin->setRange(50, 10000);
|
||||
m_pollSpin->setValue(100);
|
||||
QLabel *lblReconnect = new QLabel(tr("重连间隔(s):"));
|
||||
m_reconnectSpin = new QSpinBox;
|
||||
m_reconnectSpin->setRange(1, 60);
|
||||
m_reconnectSpin->setValue(5);
|
||||
topLayout->addWidget(lblPoll);
|
||||
topLayout->addWidget(m_pollSpin);
|
||||
topLayout->addWidget(lblReconnect);
|
||||
topLayout->addWidget(m_reconnectSpin);
|
||||
|
||||
// 控制面板:蜂鸣器 + 3个 LED
|
||||
m_buzzerChk = new QCheckBox(tr("蜂鸣器"));
|
||||
m_buzzerChk->setChecked(false);
|
||||
topLayout->addWidget(m_buzzerChk);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_ledChk[i] = new QCheckBox(tr("小灯%1").arg(i));
|
||||
m_ledChk[i]->setChecked(false);
|
||||
topLayout->addWidget(m_ledChk[i]);
|
||||
}
|
||||
m_sendControlBtn = new QPushButton(tr("发送控制"));
|
||||
topLayout->addWidget(m_sendControlBtn);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(topLayout);
|
||||
mainLayout->addLayout(deviceLayout);
|
||||
mainLayout->addWidget(m_tempLabel);
|
||||
mainLayout->addWidget(m_humLabel);
|
||||
mainLayout->addWidget(m_buzzerLabel);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
mainLayout->addWidget(m_ledLabels[i]);
|
||||
}
|
||||
|
||||
central->setLayout(mainLayout);
|
||||
|
||||
connect(m_connectBtn, SIGNAL(clicked()), this, SLOT(connectToServer()));
|
||||
connect(m_socket, SIGNAL(connected()), this, SLOT(onConnected()));
|
||||
connect(m_socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
|
||||
connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
|
||||
connect(m_refreshBtn, SIGNAL(clicked()), this, SLOT(refreshDevicesList()));
|
||||
// 使用SIGNAL/SLOT宏以确保Qt 5.12兼容性
|
||||
connect(m_deviceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onDeviceSelectionChanged()));
|
||||
|
||||
// poll timer
|
||||
m_pollTimer = new QTimer(this);
|
||||
m_pollTimer->setInterval(m_pollSpin->value());
|
||||
connect(m_pollTimer, SIGNAL(timeout()), this, SLOT(pollData()));
|
||||
// 使用SIGNAL/SLOT宏以确保Qt 5.12兼容性
|
||||
connect(m_pollSpin, SIGNAL(valueChanged(int)), m_pollTimer, SLOT(setInterval(int)));
|
||||
|
||||
// reconnect timer
|
||||
m_reconnectTimer = new QTimer(this);
|
||||
m_reconnectTimer->setInterval(m_reconnectSpin->value() * 1000);
|
||||
m_reconnectTimer->setSingleShot(false);
|
||||
connect(m_reconnectSpin, SIGNAL(valueChanged(int)), this, SLOT(onReconnectIntervalChanged(int)));
|
||||
connect(m_reconnectTimer, SIGNAL(timeout()), this, SLOT(onReconnectTimeout()));
|
||||
|
||||
// control send
|
||||
connect(m_sendControlBtn, SIGNAL(clicked()), this, SLOT(sendControlCommand()));
|
||||
|
||||
setWindowTitle(tr("温湿度监控"));
|
||||
resize(480, 200);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
}
|
||||
|
||||
void MainWindow::connectToServer()
|
||||
{
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
m_socket->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
|
||||
QString ip = m_ipEdit->text().trimmed();
|
||||
bool ok;
|
||||
quint16 port = m_portEdit->text().toUShort(&ok);
|
||||
if (!ok) port = 5001; // 默认端口
|
||||
m_socket->connectToHost(ip, port);
|
||||
m_connectBtn->setText(tr("连接中..."));
|
||||
}
|
||||
|
||||
void MainWindow::onConnected()
|
||||
{
|
||||
m_connectBtn->setText(tr("断开"));
|
||||
m_pollTimer->start();
|
||||
m_reconnectTimer->stop();
|
||||
|
||||
// Auto-refresh device list after connection
|
||||
QTimer::singleShot(500, this, SLOT(refreshDevicesList()));
|
||||
}
|
||||
|
||||
void MainWindow::onDisconnected()
|
||||
{
|
||||
m_connectBtn->setText(tr("连接"));
|
||||
m_pollTimer->stop();
|
||||
if (!m_reconnectTimer->isActive()) m_reconnectTimer->start();
|
||||
}
|
||||
|
||||
void MainWindow::onReadyRead()
|
||||
{
|
||||
// read available data; server sends line-delimited JSON
|
||||
// Handle both line-delimited and bulk data (in case newline missing)
|
||||
while (m_socket->canReadLine()) {
|
||||
QByteArray line = m_socket->readLine().trimmed();
|
||||
qDebug() << "onReadyRead (line):" << line;
|
||||
if (line.isEmpty()) continue;
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(line, &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
qWarning() << "JSON parse error:" << err.errorString() << "line:" << line;
|
||||
continue;
|
||||
}
|
||||
if (!doc.isObject()) continue;
|
||||
QJsonObject obj = doc.object();
|
||||
|
||||
// Handle different message types
|
||||
if (obj.contains("type")) {
|
||||
QString type = obj.value("type").toString();
|
||||
if (type == "devices_list") {
|
||||
// Update device combo box
|
||||
QJsonArray devices = obj.value("devices").toArray();
|
||||
m_deviceCombo->clear();
|
||||
for (const QJsonValue &device : devices) {
|
||||
QJsonObject deviceObj = device.toObject();
|
||||
QString deviceId = deviceObj.value("device_id").toString();
|
||||
QString status = deviceObj.value("status").toString();
|
||||
QString displayText = QString("%1 (%2)").arg(deviceId, status);
|
||||
m_deviceCombo->addItem(displayText, deviceId);
|
||||
}
|
||||
qDebug() << "Updated devices list with" << devices.size() << "devices";
|
||||
} else if (type == "device_status") {
|
||||
// Update device status in combo box
|
||||
QString deviceId = obj.value("device_id").toString();
|
||||
QString status = obj.value("status").toString();
|
||||
for (int i = 0; i < m_deviceCombo->count(); ++i) {
|
||||
if (m_deviceCombo->itemData(i).toString() == deviceId) {
|
||||
QString displayText = QString("%1 (%2)").arg(deviceId, status);
|
||||
m_deviceCombo->setItemText(i, displayText);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular sensor data with device_id - auto-add to device list if not present
|
||||
QString deviceId = obj.value("device_id").toString();
|
||||
if (!deviceId.isEmpty()) {
|
||||
// Check if device is already in combo box
|
||||
bool deviceExists = false;
|
||||
for (int i = 0; i < m_deviceCombo->count(); ++i) {
|
||||
if (m_deviceCombo->itemData(i).toString() == deviceId) {
|
||||
deviceExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If device not in combo box, add it
|
||||
if (!deviceExists) {
|
||||
QString displayText = QString("%1 (在线)").arg(deviceId);
|
||||
m_deviceCombo->addItem(displayText, deviceId);
|
||||
qDebug() << "Auto-added new device to list:" << deviceId;
|
||||
}
|
||||
}
|
||||
// Regular sensor data - deviceId already declared above
|
||||
if (!deviceId.isEmpty()) {
|
||||
// Store the data for this device
|
||||
m_deviceData[deviceId] = QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
// Update display only if this is the currently selected device
|
||||
if (deviceId == m_currentDeviceId) {
|
||||
updateDisplayFromJson(obj);
|
||||
|
||||
// If we were waiting for this device, update the status
|
||||
if (m_currentDeviceLabel->text().contains("等待设备上线")) {
|
||||
m_currentDeviceLabel->setText(tr("当前设备: %1").arg(deviceId));
|
||||
qDebug() << "Device" << deviceId << "is now online";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy message without device_id, display anyway
|
||||
updateDisplayFromJson(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there is remaining data without a trailing newline, try to parse it too
|
||||
if (m_socket->bytesAvailable() > 0) {
|
||||
QByteArray raw = m_socket->readAll();
|
||||
raw = raw.trimmed();
|
||||
if (!raw.isEmpty()) {
|
||||
qDebug() << "onReadyRead (raw):" << raw;
|
||||
// split by newline just in case multiple lines present
|
||||
QList<QByteArray> parts = raw.split('\n');
|
||||
for (QByteArray part : parts) {
|
||||
part = part.trimmed();
|
||||
if (part.isEmpty()) continue;
|
||||
QJsonParseError err2;
|
||||
QJsonDocument doc2 = QJsonDocument::fromJson(part, &err2);
|
||||
if (err2.error != QJsonParseError::NoError) {
|
||||
qWarning() << "JSON parse error (raw):" << err2.errorString() << "part:" << part;
|
||||
continue;
|
||||
}
|
||||
if (!doc2.isObject()) continue;
|
||||
QJsonObject obj = doc2.object();
|
||||
// Handle control state responses
|
||||
QString respDeviceId = obj.value("device_id").toString();
|
||||
if (!respDeviceId.isEmpty() && respDeviceId == m_currentDeviceId) {
|
||||
// This is a control state response for current device
|
||||
updateDisplayFromJson(obj);
|
||||
qDebug() << "Updated control state for device:" << respDeviceId;
|
||||
} else if (obj.contains("temp")) {
|
||||
double t = obj.value("temp").toDouble();
|
||||
m_tempLabel->setText(tr("温度: %1 °C").arg(t));
|
||||
}
|
||||
if (obj.contains("humidity")) {
|
||||
double h = obj.value("humidity").toDouble();
|
||||
m_humLabel->setText(tr("湿度: %1 %").arg(h));
|
||||
}
|
||||
if (obj.contains("buzzer")) {
|
||||
int b = obj.value("buzzer").toInt();
|
||||
m_buzzerLabel->setText(b ? tr("蜂鸣器: ON") : tr("蜂鸣器: OFF"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::pollData()
|
||||
{
|
||||
if (m_socket->state() != QAbstractSocket::ConnectedState) return;
|
||||
if (m_socket->bytesAvailable() > 0) {
|
||||
onReadyRead();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onReconnectIntervalChanged(int value)
|
||||
{
|
||||
m_reconnectTimer->setInterval(value * 1000);
|
||||
}
|
||||
|
||||
void MainWindow::onReconnectTimeout()
|
||||
{
|
||||
if (m_socket->state() == QAbstractSocket::UnconnectedState) {
|
||||
QString ip = m_ipEdit->text().trimmed();
|
||||
bool ok;
|
||||
quint16 port = m_portEdit->text().toUShort(&ok);
|
||||
if (!ok) port = 5001; // 默认端口
|
||||
qDebug() << "Reconnect timer: trying to connect to" << ip << port;
|
||||
m_socket->connectToHost(ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onDeviceSelectionChanged()
|
||||
{
|
||||
if (m_deviceCombo->count() == 0) return;
|
||||
|
||||
m_currentDeviceId = m_deviceCombo->currentData().toString();
|
||||
if (!m_currentDeviceId.isEmpty()) {
|
||||
m_currentDeviceLabel->setText(tr("当前设备: %1").arg(m_currentDeviceId));
|
||||
qDebug() << "Selected device:" << m_currentDeviceId;
|
||||
|
||||
// Load the latest sensor data for this device if available
|
||||
if (m_deviceData.contains(m_currentDeviceId)) {
|
||||
QString data = m_deviceData[m_currentDeviceId];
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8(), &err);
|
||||
if (err.error == QJsonParseError::NoError && doc.isObject()) {
|
||||
updateDisplayFromJson(doc.object());
|
||||
}
|
||||
} else {
|
||||
// No data available for this device yet, reset display
|
||||
m_tempLabel->setText(tr("温度: --"));
|
||||
m_humLabel->setText(tr("湿度: --"));
|
||||
m_buzzerLabel->setText(tr("蜂鸣器: --"));
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_ledLabels[i]->setText(tr("小灯%1: --").arg(i));
|
||||
}
|
||||
}
|
||||
|
||||
// Request control state for selected device
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
QString request = QString("GET control %1\n").arg(m_currentDeviceId);
|
||||
m_socket->write(request.toUtf8());
|
||||
m_socket->flush();
|
||||
qDebug() << "Requested control state for device:" << m_currentDeviceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::sendControlCommand()
|
||||
{
|
||||
if (m_socket->state() != QAbstractSocket::ConnectedState) return;
|
||||
if (m_currentDeviceId.isEmpty()) {
|
||||
qDebug() << "No device selected for control";
|
||||
return;
|
||||
}
|
||||
QJsonObject obj;
|
||||
// use a simple QString literal for compatibility with Qt 5.12
|
||||
obj["device_id"] = m_currentDeviceId;
|
||||
obj["buzzer"] = m_buzzerChk->isChecked() ? 1 : 0;
|
||||
for (int i = 0; i < 3; ++i) obj[QString("led%1").arg(i)] = m_ledChk[i]->isChecked() ? 1 : 0;
|
||||
QJsonDocument doc(obj);
|
||||
QByteArray ba = doc.toJson(QJsonDocument::Compact) + '\n';
|
||||
m_socket->write(ba);
|
||||
m_socket->flush();
|
||||
qDebug() << "Sent control command to device:" << m_currentDeviceId;
|
||||
|
||||
// Also update local display to show expected state
|
||||
QJsonObject displayObj;
|
||||
displayObj["buzzer"] = m_buzzerChk->isChecked() ? 1 : 0;
|
||||
for (int i = 0; i < 3; ++i) displayObj[QString("led%1").arg(i)] = m_ledChk[i]->isChecked() ? 1 : 0;
|
||||
updateDisplayFromJson(displayObj);
|
||||
}
|
||||
|
||||
void MainWindow::refreshDevicesList()
|
||||
{
|
||||
if (m_socket->state() != QAbstractSocket::ConnectedState) return;
|
||||
|
||||
// Send GET devices request
|
||||
QByteArray request = "GET devices\n";
|
||||
m_socket->write(request);
|
||||
m_socket->flush();
|
||||
qDebug() << "Sent devices list request";
|
||||
}
|
||||
|
||||
void MainWindow::updateDisplayFromJson(const QJsonObject &obj)
|
||||
{
|
||||
if (obj.contains("temp")) {
|
||||
double t = obj.value("temp").toDouble();
|
||||
m_tempLabel->setText(tr("Temp: %1 °C").arg(t));
|
||||
}
|
||||
if (obj.contains("humidity")) {
|
||||
double h = obj.value("humidity").toDouble();
|
||||
m_humLabel->setText(tr("Humidity: %1 %").arg(h));
|
||||
}
|
||||
if (obj.contains("buzzer")) {
|
||||
int b = obj.value("buzzer").toInt();
|
||||
m_buzzerLabel->setText(b ? tr("Buzzer: ON") : tr("Buzzer: OFF"));
|
||||
}
|
||||
|
||||
// Update all 3 LEDs
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
QString ledKey = QString("led%1").arg(i);
|
||||
if (obj.contains(ledKey)) {
|
||||
int l = obj.value(ledKey).toInt();
|
||||
m_ledLabels[i]->setText(l ? tr("小灯%1: ON").arg(i) : tr("小灯%1: OFF").arg(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
// 添加GLIBC兼容性支持
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
// 限制GLIBC版本兼容性到2.31
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
||||
# if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 31)
|
||||
# define GLIBC_MAX_VERSION "2.31"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QMap>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QLineEdit;
|
||||
class QTcpSocket;
|
||||
class QTimer;
|
||||
class QSpinBox;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
void connectToServer();
|
||||
void onConnected();
|
||||
void onDisconnected();
|
||||
void onReadyRead();
|
||||
void pollData();
|
||||
void onReconnectTimeout();
|
||||
void onReconnectIntervalChanged(int value);
|
||||
void onDeviceSelectionChanged();
|
||||
void refreshDevicesList();
|
||||
void sendControlCommand();
|
||||
|
||||
private:
|
||||
void updateDisplayFromJson(const QJsonObject &obj);
|
||||
|
||||
private:
|
||||
QLineEdit *m_ipEdit;
|
||||
QLineEdit *m_portEdit;
|
||||
QPushButton *m_connectBtn;
|
||||
|
||||
// 设备列表和选择
|
||||
QComboBox *m_deviceCombo;
|
||||
QPushButton *m_refreshBtn;
|
||||
QLabel *m_currentDeviceLabel;
|
||||
|
||||
QLabel *m_tempLabel;
|
||||
QLabel *m_humLabel;
|
||||
QLabel *m_buzzerLabel;
|
||||
QLabel *m_ledLabels[3];
|
||||
|
||||
QTcpSocket *m_socket;
|
||||
QTimer *m_pollTimer;
|
||||
QTimer *m_reconnectTimer;
|
||||
QSpinBox *m_pollSpin;
|
||||
QSpinBox *m_reconnectSpin;
|
||||
// 控制面板
|
||||
QCheckBox *m_buzzerChk;
|
||||
QCheckBox *m_ledChk[3];
|
||||
QPushButton *m_sendControlBtn;
|
||||
|
||||
// 设备数据管理
|
||||
QString m_currentDeviceId;
|
||||
QMap<QString, QString> m_deviceData; // device_id -> latest data
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
@@ -0,0 +1,48 @@
|
||||
QT += core widgets network
|
||||
CONFIG += c++11
|
||||
CONFIG += no_plugin_manifest
|
||||
|
||||
TARGET = qt_client
|
||||
TEMPLATE = app
|
||||
|
||||
# 设置最低Qt版本兼容性 (支持Qt 5.12)
|
||||
QT_MIN_VERSION = 5.12
|
||||
|
||||
# 添加GLIBC兼容性定义
|
||||
DEFINES += _GLIBCXX_USE_CXX11_ABI=0
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050C00
|
||||
|
||||
# 确保与GLIBC向后兼容 (支持GLIBC 2.31)
|
||||
QMAKE_LFLAGS += -Wl,--no-as-needed
|
||||
QMAKE_LFLAGS += -Wl,--hash-style=gnu
|
||||
QMAKE_LFLAGS += -Wl,--as-needed
|
||||
|
||||
# 静态链接标准库以减少GLIBC依赖
|
||||
QMAKE_CXXFLAGS += -static-libgcc -static-libstdc++
|
||||
QMAKE_CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0
|
||||
|
||||
# 使用更老的C++特性以确保兼容性
|
||||
QMAKE_CXXFLAGS += -std=c++11
|
||||
|
||||
# 添加符号版本控制以限制GLIBC版本依赖
|
||||
unix:!macx {
|
||||
# 确保动态链接兼容性
|
||||
QMAKE_LFLAGS += -Wl,--enable-new-dtags
|
||||
QMAKE_LFLAGS += -Wl,-rpath,'$$[QT_INSTALL_LIBS]'
|
||||
|
||||
# 限制GLIBC版本需求
|
||||
QMAKE_CXXFLAGS += -D_GNU_SOURCE
|
||||
QMAKE_CXXFLAGS += -fvisibility=hidden
|
||||
QMAKE_CXXFLAGS += -fvisibility-inlines-hidden
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h
|
||||
|
||||
# 安装目标配置
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/mainwindows/application
|
||||
INSTALLS += target
|
||||
@@ -0,0 +1,49 @@
|
||||
QT += core widgets network
|
||||
CONFIG += c++11
|
||||
CONFIG += no_plugin_manifest
|
||||
|
||||
TARGET = qt_client_compat
|
||||
TEMPLATE = app
|
||||
|
||||
# 设置最低Qt版本兼容性 (支持Qt 5.12)
|
||||
QT_MIN_VERSION = 5.12
|
||||
|
||||
# 添加GLIBC兼容性定义
|
||||
DEFINES += _GLIBCXX_USE_CXX11_ABI=0
|
||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050C00
|
||||
|
||||
# 强制兼容性设置
|
||||
QMAKE_CXXFLAGS += -U_GNU_SOURCE
|
||||
QMAKE_CXXFLAGS += -D_POSIX_C_SOURCE=200809L
|
||||
QMAKE_CXXFLAGS += -D_XOPEN_SOURCE=700
|
||||
|
||||
# 静态链接标准库以减少GLIBC依赖
|
||||
QMAKE_CXXFLAGS += -static-libgcc -static-libstdc++
|
||||
QMAKE_CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0
|
||||
|
||||
# 使用更老的C++特性以确保兼容性
|
||||
QMAKE_CXXFLAGS += -std=c++11
|
||||
|
||||
# 移除所有可能导致新GLIBC版本依赖的标志
|
||||
unix:!macx {
|
||||
# 最小化GLIBC版本需求
|
||||
QMAKE_CXXFLAGS += -fno-stack-protector
|
||||
QMAKE_CXXFLAGS += -U_FORTIFY_SOURCE
|
||||
QMAKE_CXXFLAGS += -D_FORTIFY_SOURCE=0
|
||||
|
||||
# 链接器设置以减少GLIBC依赖
|
||||
QMAKE_LFLAGS -= -Wl,--as-needed
|
||||
QMAKE_LFLAGS += -Wl,--no-as-needed
|
||||
QMAKE_LFLAGS += -Wl,--hash-style=both
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h
|
||||
|
||||
# 安装目标配置
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/mainwindows/application
|
||||
INSTALLS += target
|
||||
Reference in New Issue
Block a user