#include #include #include #include #include #include #include #include #include #include "mainwindow.h" #include ColoredSquare::ColoredSquare(QWidget *parent) : QWidget(parent), m_color(Qt::gray) { } void ColoredSquare::setColor(const QColor& color) { m_color = color; update(); } void ColoredSquare::paintEvent(QPaintEvent *) { QPainter painter(this); painter.fillRect(rect(), m_color); } MainWindows::MainWindows(QWidget *parent) : QWidget(parent), m_modbusClient(nullptr), m_connected(false), m_requestCounter(0), m_responseCounter(0) { // Устанавливаем кодировку для всего приложения QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); // Устанавливаем шрифт с поддержкой кириллицы QFont font("Arial", 10); // или можно использовать "MS Shell Dlg 2" this->setFont(font); loadConfiguration(); createUIElements(); initModbusConnection(); } void MainWindows::loadConfiguration() { const QString filePath = QCoreApplication::applicationDirPath() + "/config.ini"; QSettings settings(filePath, QSettings::IniFormat); // Устанавливаем кодировку для чтения файла конфигурации settings.setIniCodec("UTF-8"); settings.beginGroup("Connection"); m_ipAddress = settings.value("ip", "127.0.0.1").toString(); m_port = settings.value("port", 502).toInt(); m_responseTimeout = settings.value("responseTimeout", 1000).toInt(); m_connectTimeout = settings.value("connectTimeout", 3000).toInt(); m_pollInterval = settings.value("timeBetweenPolls", 100).toInt(); settings.endGroup(); qDebug() << "ip: " << m_ipAddress << "port: " << m_port; qDebug() << "responseTimeout: " << m_responseTimeout; qDebug() << "connectTimeout: " << m_connectTimeout; qDebug() << "poolInterval: " << m_pollInterval; // Read registers configuration settings.beginGroup("Registers"); m_buttonCount = settings.value("buttonCount", 0).toInt(); m_indicatorCount = settings.value("indicatorCount", 0).toInt(); m_buttonRegisters.clear(); m_indicatorRegisters.clear(); m_buttonBits.clear(); // Load button registers and bits for(int i = 1; i <= m_buttonCount; ++i) { QString buttonRegKey = QString("button_reg%1").arg(i); QString buttonBitKey = QString("button_bit%1").arg(i); int buttonReg = settings.value(buttonRegKey, 0).toInt(); int buttonBit = settings.value(buttonBitKey, 0).toInt(); if(buttonReg > 0 && buttonBit >= 0 && buttonBit < 16) { m_buttonRegisters.append(buttonReg); m_buttonBits.append(buttonBit); } } // Load indicator registers for(int i = 1; i <= m_indicatorCount; ++i) { QString indicatorKey = QString("indicator_reg%1").arg(i); int indicatorReg = settings.value(indicatorKey, 0).toInt(); if (indicatorReg > 0) { m_indicatorRegisters.append(indicatorReg); } } settings.endGroup(); // Load button labels settings.beginGroup("Registers"); for(int i = 1; i <= m_buttonCount; ++i) { QString labelKey = QString("button_label%1").arg(i); QString label = settings.value(labelKey).toString(); if(label.isEmpty()) { label = QString("Button %1").arg(i); } m_buttonLabels.append(label); } settings.endGroup(); } void MainWindows::initModbusConnection() { delete m_modbusClient; m_modbusClient = new QModbusTcpClient(this); // Set timeouts m_modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_ipAddress); m_modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port); m_modbusClient->setTimeout(m_responseTimeout); m_modbusClient->setNumberOfRetries(1); connect(m_modbusClient, &QModbusDevice::stateChanged, this, &MainWindows::onStateChanged); // Initialize poll timer if (!m_pollTimer) { m_pollTimer = new QTimer(this); } m_pollTimer->setInterval(m_pollInterval); connect(m_pollTimer, &QTimer::timeout, this, &MainWindows::onPollTimer); } void MainWindows::onStateChanged(QModbusDevice::State state) { if (state == QModbusDevice::ConnectedState) { m_connected = true; m_connectButton->setText("Disconnect"); m_requestCounter = 0; m_responseCounter = 0; updateStatusBar(); readRegisters(); m_pollTimer->start(); // Start polling when connected } else if (state == QModbusDevice::UnconnectedState) { m_connected = false; m_connectButton->setText("Connect"); m_pollTimer->stop(); // Stop polling when disconnected updateStatusBar(); } } void MainWindows::onConnectButtonClicked() { if (!m_connected) { m_disconnectRequested = false; if (!m_modbusClient->connectDevice()) { QMessageBox::critical(this, tr("Connection Error"), tr("Could not connect to the Modbus device!")); } // Start connection timeout timer QTimer::singleShot(m_connectTimeout, this, [this]() { if (!m_connected && !m_disconnectRequested) { m_modbusClient->disconnectDevice(); QMessageBox::warning(this, tr("Connection Timeout"), tr("Connection attempt timed out!")); } }); } else { m_disconnectRequested = true; m_modbusClient->disconnectDevice(); } } void MainWindows::onPollTimer() { if (m_connected) { readRegisters(); } } void MainWindows::readRegisters() { if (!m_connected) return; // Увеличиваем счетчик запросов m_requestCounter += m_indicatorCount + m_buttonCount; updateStatusBar(); // Read indicator registers for (int i = 0; i < m_indicatorCount; ++i) { if (i < m_indicatorRegisters.size()) { QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, m_indicatorRegisters[i], 1); if (auto *reply = m_modbusClient->sendReadRequest(readUnit, 1)) { if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, &MainWindows::onReadReady); } else { delete reply; } } } } // Read button registers for (int i = 0; i < m_buttonCount; ++i) { if (i < m_buttonRegisters.size()) { QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, m_buttonRegisters[i], 1); if (auto *reply = m_modbusClient->sendReadRequest(readUnit, 1)) { if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, &MainWindows::onReadReady); } else { delete reply; } } } } } void MainWindows::onReadReady() { auto* reply = qobject_cast(sender()); if (!reply) return; std::unique_ptr replyPtr(reply); // Автоматическое освобождение памяти if (replyPtr->error() == QModbusDevice::NoError) { m_responseCounter++; updateStatusBar(); const QModbusDataUnit unit = replyPtr->result(); int address = unit.startAddress(); int value = unit.value(0); // Handle indicators for (int i = 0; i < m_indicatorRegisters.size(); ++i) { if (address == m_indicatorRegisters[i]) { static const QColor colors[] = {Qt::gray, Qt::black, Qt::green}; static const QString states[] = {"?", "Закрыт", "Открыт"}; m_colorIndices[i] = value % 3; m_squares[i]->setColor(colors[m_colorIndices[i]]); if (auto* item = m_table->item(i, 3)) { item->setText(states[m_colorIndices[i]]); } break; } } // Handle buttons for (int i = 0; i < m_buttonRegisters.size(); ++i) { if (address == m_buttonRegisters[i]) { m_buttonValues[i] = value; bool bitState = (value >> m_buttonBits[i]) & 1; m_buttons[i]->setChecked(bitState); // Устанавливаем состояние кнопки qDebug() << "mButtonRegisters[" << i << "] = " << m_buttonValues[i]; bool hasIndicator = false; if (m_indicatorRegisters.size() <= i) { hasIndicator = true; } m_buttons[i]->setText(hasIndicator ? QString("%1").arg(bitState ? "Выключить" : "Включить") : QString("%1").arg(bitState ? "Закрыть" : "Открыть")); } } } } void MainWindows::createUIElements() { auto* mainLayout = new QVBoxLayout(this); // Добавляем статусную строку m_statusLabel = new QLabel(this); m_statusLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); updateStatusBar(); // Add connect button at the top m_connectButton = new QPushButton("Connect", this); connect(m_connectButton, &QPushButton::clicked, this, &MainWindows::onConnectButtonClicked); mainLayout->addWidget(m_connectButton); // Create table with maximum of buttons or indicators int rowCount = qMax(m_buttonCount, m_indicatorCount); m_table = new QTableWidget(rowCount, 4, this); // Changed from 3 to 4 columns // Setup table properties m_table->verticalHeader()->setVisible(false); m_table->horizontalHeader()->setVisible(false); m_table->setShowGrid(false); m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_table->setSelectionMode(QAbstractItemView::NoSelection); // Устанавливаем шрифт для таблицы QFont tableFont("Arial", 10); m_table->setFont(tableFont); // Create UI elements for(int i = 0; i < rowCount; ++i) { // Add label in first column QString label; if (i < m_buttonCount) { label = m_buttonLabels[i]; } m_table->setItem(i, 0, new QTableWidgetItem(label)); if (i < m_buttonCount) { auto* container = new QWidget(); auto* containerLayout = new QHBoxLayout(container); auto* button = new QPushButton(QString("Register %1 Bit %2").arg(m_buttonRegisters[i]).arg(m_buttonBits[i]), this); button->setFont(tableFont); button->setCheckable(true); button->setMaximumHeight(button->height() - 5); containerLayout->addWidget(button); containerLayout->setAlignment(Qt::AlignCenter); containerLayout->setContentsMargins(0, 0, 0, 0); m_buttons.append(button); m_buttonValues.append(0); m_table->setCellWidget(i, 1, container); connect(button, &QPushButton::clicked, this, [this, i]() { QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, m_buttonRegisters[i], 1); int currentValue = m_buttonValues[i]; int newValue = currentValue ^ (1 << m_buttonBits[i]); m_buttonValues[i] = newValue; writeUnit.setValue(0, newValue); if (auto* reply = m_modbusClient->sendWriteRequest(writeUnit, 1)) { if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); } else { delete reply; } } }); } if (i < m_indicatorCount) { auto* container = new QWidget(); auto* containerLayout = new QHBoxLayout(container); auto* square = new ColoredSquare(container); square->setFixedSize(m_table->rowHeight(i) - 4, m_table->rowHeight(i) - 4); // Немного уменьшаем размер containerLayout->addWidget(square); containerLayout->setAlignment(Qt::AlignCenter); containerLayout->setContentsMargins(0, 0, 0, 0); m_squares.append(square); m_colorIndices.append(0); m_table->setCellWidget(i, 2, container); // Add status text cell const auto statusItem = new QTableWidgetItem("?"); statusItem->setTextAlignment(Qt::AlignCenter); m_table->setItem(i, 3, statusItem); } } // Adjust column sizes m_table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); m_table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); m_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); m_table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); m_table->setColumnWidth(0, 180); // Label column m_table->setColumnWidth(1, 170); // Button column m_table->setColumnWidth(2, 50); // Square column m_table->setColumnWidth(3, 100); // Status text column mainLayout->addWidget(m_table); mainLayout->addWidget(m_statusLabel); mainLayout->setContentsMargins(0, 0, 0, 0); // Обновляем расчет размера окна с учетом статусной строки int totalWidth = m_table->horizontalHeader()->length() + 4; int totalHeight = m_table->verticalHeader()->length() + m_connectButton->height() + m_statusLabel->sizeHint().height() + 20; setFixedSize(totalWidth, totalHeight); } void MainWindows::updateStatusBar() const { QString status = QString("IP: %1 Port: %2 | Requests: %3 Responses: %4") .arg(m_ipAddress) .arg(m_port) .arg(m_requestCounter) .arg(m_responseCounter); m_statusLabel->setText(status); } MainWindows::~MainWindows() { if (m_pollTimer) { m_pollTimer->stop(); } if (m_modbusClient && m_modbusClient->state() != QModbusDevice::UnconnectedState) { m_modbusClient->disconnectDevice(); } }