393 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <QTableWidget>
 | ||
| #include <QHeaderView>
 | ||
| #include <QTextCodec>
 | ||
| #include <QVBoxLayout>
 | ||
| #include <QThread>
 | ||
| #include <QPainter>
 | ||
| #include <QSettings>
 | ||
| #include <QMessageBox>
 | ||
| #include <QDebug>
 | ||
| #include "mainwindow.h"
 | ||
| #include <QCoreApplication>
 | ||
| 
 | ||
| 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);
 | ||
|     }
 | ||
| 
 | ||
|     // Load indicator labels
 | ||
|     for(int i = 1; i <= m_indicatorCount; ++i) {
 | ||
|         QString labelKey = QString("indicator_label%1").arg(i);
 | ||
|         QString label = settings.value(labelKey).toString();
 | ||
|         if(label.isEmpty()) {
 | ||
|             label = QString("Indicator %1").arg(i);
 | ||
|         }
 | ||
|         m_indicatorLabels.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<QModbusReply*>(sender());
 | ||
|     if (!reply) return;
 | ||
| 
 | ||
|     std::unique_ptr<QModbusReply> 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); // Устанавливаем состояние кнопки
 | ||
|                 bool hasIndicator = false;
 | ||
|                 // Проверяем, есть ли индикатор для этой кнопки
 | ||
|                 for (int j = 0; j < m_indicatorRegisters.size(); ++j) {
 | ||
|                     if (m_indicatorLabels[j].contains(m_buttonLabels[i], Qt::CaseInsensitive)) {
 | ||
|                         hasIndicator = true;
 | ||
|                         break;
 | ||
|                     }
 | ||
|                 }
 | ||
|                 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];
 | ||
|         } else if (i < m_indicatorCount) {
 | ||
|             label = m_indicatorLabels[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, 100); // 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();
 | ||
|     }
 | ||
| }
 |