Initial commit: add Qt environment monitoring client
This commit is contained in:
+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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user