#include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include // 添加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 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)); } } }