Files
mbIoUniversal/Src/mainwindow.cpp
2025-07-04 15:03:55 +07:00

378 lines
14 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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);
}
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); // Устанавливаем состояние кнопки
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();
}
}