377 lines
14 KiB
C++
377 lines
14 KiB
C++
#include <QTableWidget>
|
||
#include <QHeaderView>
|
||
#include <QTextCodec>
|
||
#include <QVBoxLayout>
|
||
#include <QThread>
|
||
#include <QPainter>
|
||
#include <QSettings>
|
||
#include "mainwindow.h"
|
||
#include <QCoreApplication>
|
||
#include <QModbusTcpClient>
|
||
#include <QModbusDataUnit>
|
||
#include <QUrl>
|
||
#include <QMessageBox>
|
||
#include <QDebug>
|
||
|
||
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() {
|
||
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, &QModbusClient::stateChanged,
|
||
this, &MainWindows::onStateChanged);
|
||
|
||
// Initialize poll timer
|
||
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) {
|
||
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_modbusClient->disconnectDevice();
|
||
QMessageBox::warning(this, tr("Connection Timeout"),
|
||
tr("Connection attempt timed out!"));
|
||
}
|
||
});
|
||
} else {
|
||
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;
|
||
|
||
if (reply->error() == QModbusDevice::NoError) {
|
||
m_responseCounter++;
|
||
updateStatusBar();
|
||
const QModbusDataUnit unit = reply->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 ? "Закрыть" : "Открыть"));
|
||
}
|
||
}
|
||
}
|
||
reply->deleteLater();
|
||
}
|
||
|
||
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* button = new QPushButton(QString("Register %1 Bit %2").arg(m_buttonRegisters[i]).arg(m_buttonBits[i]), this);
|
||
button->setFont(tableFont);
|
||
button->setCheckable(true); // Делаем кнопку переключаемой
|
||
m_buttons.append(button);
|
||
m_buttonValues.append(0);
|
||
m_table->setCellWidget(i, 1, button);
|
||
connect(button, &QPushButton::clicked, [this, i]() {
|
||
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, m_buttonRegisters[i], 1);
|
||
int currentValue = m_buttonValues[i];
|
||
int newValue = currentValue ^ (1 << m_buttonBits[i]); // Toggle specific bit
|
||
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* square = new ColoredSquare(this);
|
||
square->setFixedSize(m_table->rowHeight(i), m_table->rowHeight(i));
|
||
m_squares.append(square);
|
||
m_colorIndices.append(0);
|
||
m_table->setCellWidget(i, 2, square);
|
||
|
||
// 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() + 20;
|
||
int totalHeight = m_table->verticalHeader()->length() +
|
||
m_connectButton->height() +
|
||
m_statusLabel->sizeHint().height() + 20;
|
||
setFixedSize(totalWidth, totalHeight);
|
||
}
|
||
|
||
void MainWindows::updateStatusBar() {
|
||
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();
|
||
}
|
||
}
|