Initial commit: add Qt environment monitoring client

This commit is contained in:
yzfh
2026-10-19 16:03:19 +08:00
commit a0dac24c97
8 changed files with 1949 additions and 0 deletions
+429
View File
@@ -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));
}
}
}