29 Commits

Author SHA1 Message Date
Вячеслав Штейбезандт
ba0506caf7 Ещё немного комментариев 2025-12-05 15:22:19 +03:00
Вячеслав Штейбезандт
f10f3c8927 Оптимизация кода, начало работы с комментариями 2025-12-01 17:23:24 +03:00
Вячеслав Штейбезандт
3f1cd53e19 Добавлен модуль для работы с (0x2B / 0x0E) Read Device Identification 2025-10-21 17:23:23 +03:00
Вячеслав Штейбезандт
e402334153 Алексей: сделал определение плат по char, а не hex
т.е. не 0x1-0x4, а "1"-"4" (0x31-0x34)
2025-10-16 17:32:52 +03:00
Вячеслав Штейбезандт
9071eb4322 Обновление списка ошибок 2025-10-16 09:56:36 +03:00
Вячеслав Штейбезандт
227deaf686 Исправление ошибки диапазона для записи уставок на четвёртой плате. 2025-10-16 09:23:18 +03:00
Вячеслав Штейбезандт
4ec595d92e Таймаут во время поиска плат снижен до 50 мс. 2025-10-16 09:20:54 +03:00
Вячеслав Штейбезандт
a9f3d13b5f Обновление отображения значений регистров с HEX на значения порогов.
Для быстроты ввода дробной части новые значения записывать с учётом последующего деления на 1000. (Ввод 500, Запись 0.500)
2025-10-16 09:11:58 +03:00
Вячеслав Штейбезандт
5ae694d254 Обновление алгоритма для корректного опроса четвёртой платы. 2025-10-16 08:41:49 +03:00
Вячеслав Штейбезандт
c2a32e3ff5 Добавлено отслеживание ошибок на платах в режиме отладки. 2025-10-16 08:39:30 +03:00
Вячеслав Штейбезандт
1e3ecd0834 график ацп выведен в отдельное меню и отвязан от конкретной борды 2025-10-10 18:30:40 +03:00
Вячеслав Штейбезандт
5b3faa798b фиксы графика и статической компиляции 2025-10-09 18:30:27 +03:00
Вячеслав Штейбезандт
93b4a24b8b добавлена вклада для отладочных функций
note: крашится в static компиле
2025-10-09 12:05:20 +03:00
Вячеслав Штейбезандт
4ae15d8c01 добавлен локальный статус для каждого мзктэ 2025-10-08 18:47:23 +03:00
Вячеслав Штейбезандт
190f3337ed убрана остановка опроса при ошибке платы 2025-10-08 11:31:13 +03:00
Вячеслав Штейбезандт
726d8d24ef новый алг иправлен
+ исправлен нахождение плат с костылем
+ исправлен вылет приложения при опросе 4 платы (из-за того, что в ней 65, а не 85 регистров) сделал чтобы везде было 85, просто отображатся будет 65
2025-10-08 11:11:12 +03:00
Вячеслав Штейбезандт
c9b44e5dab Изменение обработки ошибок и вывода записи в лог 2025-09-22 12:40:36 +03:00
Вячеслав Штейбезандт
14d7907d92 Отмена отслеживания папок Debug и Release 2025-09-15 13:10:16 +03:00
Вячеслав Штейбезандт
b4803e19e2 Алгоритм изменения скорости обмена данными с платами. Пока без вывода ошибок в лог. 2025-09-15 13:07:14 +03:00
Вячеслав Штейбезандт
d9f58e72e4 Обновление алгоритма смены скорости обмена - промежуточный коммит 2025-09-10 16:42:25 +03:00
Вячеслав Штейбезандт
a4cdcb7091 Снапшот перед обновлением алгоритма смены скоростей на платах 2025-09-09 16:48:21 +03:00
Вячеслав Штейбезандт
7dc314d266 Fix & update
> Исправлено окно настроек (Период опроса плат и раздел сетевого адреса)
> Исправлено значение Odd Parity при передаче
> Добавлена возможность остановить опрос платы

Добавлен модуль LineRinger для опроса Modbus устройств.
2025-03-12 11:05:14 +03:00
Вячеслав Штейбезандт
568ec9b5d3 Third checkpoint 2025-01-23 17:17:45 +03:00
Вячеслав Штейбезандт
009ac176f9 Second checkpoint 2025-01-23 17:10:34 +03:00
Вячеслав Штейбезандт
1b7388821e Checkpoint to git test 2025-01-23 17:05:06 +03:00
Вячеслав Штейбезандт
fc01a6c06d Pre-release.
Added:
1) Added check for transition to new speed and parity.
2) Added the ability to stop scanning before polling all addresses.
3) A window has been added that allows you to set the value of multiple registers at once. Supports both simple saving on the device and the saved-sent mode.
4) Added check for parity and speed changes.
5) Added output of current voltage to the table.
6) Now, when you click on an indicator in the table, the corresponding element will be highlighted.

Minor updates:
1) Now the device settings take into account the number of boards found during scanning.
2) When you close the main window, the device turns off.
2024-12-18 14:58:33 +03:00
Вячеслав Штейбезандт
49083ca06d Hot Fix 2.
>Некорректная индикация напряжения ТЭ была устранена.
>Исправлена ошибка с некорректным определением модели регистров.
>Исправлено некорректное визуальное отображение статуса работы МЗКТЕ

Примечание:
Необходимо отвязать индикацию ТЭ от панели управления, передав уставки и коилы на внутренние таблицы
2024-12-11 16:55:48 +03:00
Вячеслав Штейбезандт
d7063703b6 Hot Fix 1.
>Отображение кнопок статуса МЗКТЭ было изменено.
>Операция записи раньше использовала ИД вместо адреса - исправлено.
>Стартовый адрес опроса плат смещён с нуля до первого.
>При поиске плат в сети использовался адрес по умолчанию вместо итератора - исправлено.
>При поиске плат в сети добавлена проверка на наличие ошибок при приёме.
>Проверка на поведение терминала при отсутствии плат в сети - пройдена.
>Изменено поведение progressbar при поиске плат и опросе их текущих настроек.
>Добавлено сообщение об ошибке во время опроса плат об их текущих настройках.
>Добавлено сообщение об ошибке при некорректном ответе во время опроса текущего напряжения.
2024-12-11 10:57:49 +03:00
Вячеслав Штейбезандт
5b2a64a39b Программа нуждается в тестировании.
Реализовано и проверено:
1) Подключение устройства
2) Запись и чтение Coil и Holding регистров
3) Индикация работы МЗКТЭ и напряжения на ТЭ

Реализовано и нуждается в проверке:
1) Поиск плат и установление их адресов в сети
2) Сканирование текущих настроек платы при подключении
3) Опрос текущих значений напряжений ТЭ по таймеру
4) Изменение скорости обмена, сетевого адреса плат и настройка контроля четности.

#3
2024-12-10 17:19:23 +03:00
50 changed files with 23560 additions and 89 deletions

3
.gitignore vendored
View File

@@ -54,3 +54,6 @@ compile_commands.json
*_qmlcache.qrc
/Debug
/Release

21
Debug/.qmake.stash Normal file
View File

@@ -0,0 +1,21 @@
QMAKE_CXX.QT_COMPILER_STDCXX = 201402L
QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 7
QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 3
QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0
QMAKE_CXX.COMPILER_MACROS = \
QT_COMPILER_STDCXX \
QMAKE_GCC_MAJOR_VERSION \
QMAKE_GCC_MINOR_VERSION \
QMAKE_GCC_PATCH_VERSION
QMAKE_CXX.INCDIRS = \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include/c++ \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include/c++/i686-w64-mingw32 \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include/c++/backward \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include-fixed \
C:/Qt/Qt5.14.2/Tools/mingw730_32/i686-w64-mingw32/include
QMAKE_CXX.LIBDIRS = \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0 \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc \
C:/Qt/Qt5.14.2/Tools/mingw730_32/i686-w64-mingw32/lib \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib

View File

@@ -1,31 +0,0 @@
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
m3kte.cpp
HEADERS += \
m3kte.h
FORMS += \
m3kte.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

77
M3KTE_TERM/M3KTE_TERM.pro Normal file
View File

@@ -0,0 +1,77 @@
QT += core gui
QT += widgets serialport
QT += serialbus widgets
requires(qtConfig(combobox))
QT += serialport
qtConfig(modbus-serialport): QT += serialport
QT += charts
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
#TEMPLATE = lib
#DEFINES += M3KTE_LIBRARY
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
adcgraphdialog.cpp \
debugterminaldialog.cpp \
devicesettingsdialog.cpp \
lineringer.cpp \
main.cpp \
m3kte.cpp \
multiplesettings.cpp \
parameterbox.cpp \
parameterdevice.cpp \
parameterworkspace.cpp \
scanboard.cpp \
settingsdialog.cpp \
writeregistermodel.cpp
HEADERS += \
adcgraphdialog.h \
debugterminaldialog.h \
devicesettingsdialog.h \
lineringer.h \
m3kte.h \
multiplesettings.h \
parameterbox.h \
parameterdevice.h \
parameterworkspace.h \
scanboard.h \
settingsdialog.h \
writeregistermodel.h
FORMS += \
adcgraphdialog.ui \
debugTerminalDialog.ui \
devicesettingsdialog.ui \
lineringer.ui \
m3kte.ui \
multiplesettings.ui \
parameterbox.ui \
parameterdevice.ui \
parameterworkspace.ui \
scanboard.ui \
settingsdialog.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
#unix {
# target.path = /usr/lib
#}
#!isEmpty(target.path): INSTALLS += target

View File

@@ -0,0 +1,470 @@
#include "adcgraphdialog.h"
#include "ui_adcgraphdialog.h"
#include <QModbusReply>
#include <QDebug>
#include <QMessageBox>
#include <QValueAxis>
#include <QVBoxLayout> // Добавить этот include
#include <QChartView> // Добавить этот include
// Адреса регистров для АЦП
#define REG_ADC_ZERO 555
#define REG_ADC_ONE_VOLT 556
#define REG_STABLE_START 557
#define REG_STABLE_END 558
#define REG_TE_NUMBER 564
#define REG_ADC_BUFFER_START 571
#define REG_ADC_BUFFER_END 1070
AdcGraphDialog::AdcGraphDialog(QModbusClient *modbusDevice, QWidget *parent) :
QDialog(parent),
ui(new Ui::AdcGraphDialog),
m_modbusDevice(modbusDevice),
m_updateTimer(new QTimer(this)),
m_boardId(-1),
m_boardAddress(-1),
m_teNumber(-1),
m_adcZero(0),
m_adcOneVolt(4096),
m_stableStartLine(new QLineSeries()),
m_stableEndLine(new QLineSeries()),
m_stableStartIndex(-1),
m_stableEndIndex(-1),
m_series(new QLineSeries()),
m_chart(new QChart()),
m_startAddress(0),
m_registerCount(100)
{
ui->setupUi(this);
// Настройка основного графика
m_series->setName("АЦП данные");
m_chart->addSeries(m_series);
// Настройка линий стабильного участка
m_stableStartLine->setName("Начало стаб. участка");
m_stableStartLine->setColor(Qt::red);
m_stableStartLine->setPen(QPen(Qt::red, 2, Qt::DashLine));
m_chart->addSeries(m_stableStartLine);
m_stableEndLine->setName("Конец стаб. участка");
m_stableEndLine->setColor(Qt::green);
m_stableEndLine->setPen(QPen(Qt::green, 2, Qt::DashLine));
m_chart->addSeries(m_stableEndLine);
m_chart->setTitle("График АЦП");
m_chart->legend()->setVisible(true);
m_chart->legend()->setAlignment(Qt::AlignTop);
m_axisX = new QValueAxis();
m_axisX->setTitleText("Отсчеты АЦП");
m_axisX->setRange(0, m_registerCount);
m_chart->addAxis(m_axisX, Qt::AlignBottom);
m_series->attachAxis(m_axisX);
m_stableStartLine->attachAxis(m_axisX);
m_stableEndLine->attachAxis(m_axisX);
m_axisY = new QValueAxis();
m_axisY->setTitleText("Напряжение, В");
m_axisY->setRange(-1.3, 1.3);
m_chart->addAxis(m_axisY, Qt::AlignLeft);
m_series->attachAxis(m_axisY);
m_stableStartLine->attachAxis(m_axisY);
m_stableEndLine->attachAxis(m_axisY);
QChartView *chartView = new QChartView(m_chart);
chartView->setRenderHint(QPainter::Antialiasing);
QVBoxLayout *layout = new QVBoxLayout(ui->plotWidget);
layout->addWidget(chartView);
// Подключаем сигналы элементов управления диапазоном
connect(ui->registerCountSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &AdcGraphDialog::on_registerCountChanged);
connect(ui->rangeApplyButton, &QPushButton::clicked,
this, &AdcGraphDialog::on_rangeApplyClicked);
connect(ui->teNumberSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &AdcGraphDialog::on_teNumberChanged);
connect(m_updateTimer, &QTimer::timeout, this, &AdcGraphDialog::onUpdateTimer);
connect(ui->closeBtn, &QPushButton::clicked, this, &AdcGraphDialog::on_closeBtn_clicked);
}
AdcGraphDialog::~AdcGraphDialog()
{
stopGraph();
delete ui;
}
void AdcGraphDialog::setModbusDevice(QModbusClient *device)
{
m_modbusDevice = device;
}
void AdcGraphDialog::on_teNumberChanged(int value)
{
if(m_teNumber != value)
setTENumber(m_boardId, value);
}
void AdcGraphDialog::setTENumber(int boardID, int teNumber)
{
if(m_boardAddress == -1)
return;
m_teNumber = teNumber;
m_boardId = boardID;
m_boardAddress = m_boardId + 1;
// Обновляем заголовок окна
setWindowTitle(QString("График АЦП - Плата %1, ТЭ %2 (адр %3-%4)")
.arg(m_boardId + 1)
.arg(m_teNumber)
.arg(m_startAddress)
.arg(m_startAddress + m_registerCount - 1));
// Записываем новый номер ТЭ в устройство
if(m_modbusDevice) {
QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, REG_TE_NUMBER, 1);
unit.setValue(0, teNumber);
if(auto *reply = m_modbusDevice->sendWriteRequest(unit, m_boardAddress)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, [this, reply, teNumber]() {
if(reply->error() == QModbusDevice::NoError) {
qDebug() << "TE number changed to:" << teNumber;
// Можно обновить данные сразу после смены ТЭ
readAdcDataAndIndices();
} else {
qDebug() << "Error writing TE number:" << reply->errorString();
}
reply->deleteLater();
});
else
reply->deleteLater();
}
}
ui->teNumberSpinBox->setValue(teNumber);
}
void AdcGraphDialog::readCalibrationValues()
{
if(!m_modbusDevice || m_boardAddress == -1)
return;
// Читаем калибровочные значения (регистры 555 и 556)
QModbusDataUnit unit(QModbusDataUnit::InputRegisters, REG_ADC_ZERO, 2);
if(auto *reply = m_modbusDevice->sendReadRequest(unit, m_boardAddress)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit result = reply->result();
if(result.valueCount() >= 2) {
m_adcZero = result.value(0);
m_adcOneVolt = result.value(1);
}
}
reply->deleteLater();
});
else
reply->deleteLater();
}
}
void AdcGraphDialog::readStableIndices()
{
if(!m_modbusDevice || m_boardAddress == -1)
return;
// Читаем индексы стабильного участка (регистры 557 и 558)
QModbusDataUnit unit(QModbusDataUnit::InputRegisters, REG_STABLE_START, 2);
if(auto *reply = m_modbusDevice->sendReadRequest(unit, m_boardAddress)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, &AdcGraphDialog::onStableIndicesReady);
else
reply->deleteLater();
}
}
void AdcGraphDialog::onStableIndicesReady()
{
auto *reply = qobject_cast<QModbusReply*>(sender());
if(!reply)
return;
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit result = reply->result();
if(result.valueCount() >= 2) {
m_stableStartIndex = result.value(0);
m_stableEndIndex = result.value(1);
updateStableLines();
// Обновляем статистику с новыми индексами
updateStatisticsWithStableInfo();
}
}
reply->deleteLater();
}
void AdcGraphDialog::updateStatistics()
{
if(m_series->count() > 0) {
double min = 1000, max = -1000, sum = 0;
for(const QPointF &point : m_series->points()) {
double y = point.y();
if(y < min)
min = y;
if(y > max)
max = y;
sum += y;
}
double avg = sum / m_series->count();
ui->minLabel->setText(QString::number(min, 'f', 3) + " В");
ui->maxLabel->setText(QString::number(max, 'f', 3) + " В");
ui->avgLabel->setText(QString::number(avg, 'f', 3) + " В");
// Обновляем информацию о стабильном участке
updateStatisticsWithStableInfo();
}
}
void AdcGraphDialog::updateStatisticsWithStableInfo()
{
if(m_series->count() > 0)
// Используем абсолютные индексы в формате "начальный-конечный"
ui->samplesLabel->setText(
QString("%1 отсч. (адр %2-%3) [стаб: %4-%5]")
.arg(m_series->count())
.arg(m_startAddress)
.arg(m_startAddress + m_registerCount - 1)
.arg(m_stableStartIndex) // Абсолютный начальный индекс
.arg(m_stableEndIndex) // Абсолютный конечный индекс
);
}
void AdcGraphDialog::updateStableLines()
{
// Очищаем предыдущие линии
m_stableStartLine->clear();
m_stableEndLine->clear();
// Получаем текущий диапазон оси Y
double yMin = m_axisY->min();
double yMax = m_axisY->max();
// Добавляем вертикальную линию для начала стабильного участка
// Учитываем текущий диапазон отображения
if(m_stableStartIndex >= m_startAddress && m_stableStartIndex < m_startAddress + m_registerCount) {
int relativeStartIndex = m_stableStartIndex - m_startAddress;
m_stableStartLine->append(relativeStartIndex, yMin);
m_stableStartLine->append(relativeStartIndex, yMax);
}
// Добавляем вертикальную линию для конца стабильного участка
if(m_stableEndIndex >= m_startAddress && m_stableEndIndex < m_startAddress + m_registerCount) {
int relativeEndIndex = m_stableEndIndex - m_startAddress;
m_stableEndLine->append(relativeEndIndex, yMin);
m_stableEndLine->append(relativeEndIndex, yMax);
}
}
void AdcGraphDialog::updateGraphRange()
{
// Обновляем диапазон оси X
m_axisX->setRange(0, m_registerCount);
m_axisX->setTitleText(QString("Отсчеты АЦП (%1-%2)").arg(m_startAddress).arg(m_startAddress + m_registerCount - 1));
// Сбрасываем ось Y к разумному диапазону по умолчанию
m_axisY->setRange(-1.2, 1.2);
// Обновляем линии стабильного участка с учетом нового диапазона
updateStableLines();
}
void AdcGraphDialog::readAdcDataAndIndices()
{
if(!m_modbusDevice || m_boardAddress == -1)
return;
// Создаем один запрос для данных АЦП + индексов стабильности
// Адреса: данные АЦП (571+), индексы (557-558)
int start = m_startAddress + REG_ADC_BUFFER_START;
int count = m_registerCount;
// Читаем индексы стабильности (557-558) и данные АЦП одним запросом
QModbusDataUnit unit(QModbusDataUnit::InputRegisters, REG_STABLE_START,
count + (start - REG_STABLE_START));
if(auto *reply = m_modbusDevice->sendReadRequest(unit, m_boardAddress)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, &AdcGraphDialog::onCombinedDataReady);
else
reply->deleteLater();
}
}
void AdcGraphDialog::onCombinedDataReady()
{
auto *reply = qobject_cast<QModbusReply*>(sender());
if(!reply)
return;
if((m_adcZero == 0) || (m_adcOneVolt == 0)) {
readCalibrationValues();
return;
}
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit result = reply->result();
// Обрабатываем индексы стабильности (первые 2 регистра)
if(result.valueCount() >= 2) {
m_stableStartIndex = result.value(0);
m_stableEndIndex = result.value(1);
}
// Обрабатываем данные АЦП (остальные регистры)
uint adcDataStartIndex = (m_startAddress + REG_ADC_BUFFER_START) - REG_STABLE_START;
// Очищаем предыдущие данные
m_series->clear();
// Добавляем новые точки и определяем диапазон
double minVoltage = 1000, maxVoltage = -1000;
for(int i = 0; i < m_registerCount; ++i) {
if(adcDataStartIndex + i < result.valueCount()) {
double voltage = convertAdcToVoltage(result.value(adcDataStartIndex + i));
m_series->append(i, voltage);
// Обновляем мин/макс для автоматического масштабирования
if(voltage < minVoltage)
minVoltage = voltage;
if(voltage > maxVoltage)
maxVoltage = voltage;
}
}
// Обновляем график и статистику
updateYAxisRange(minVoltage, maxVoltage);
updateStableLines();
updateStatistics();
}
reply->deleteLater();
}
void AdcGraphDialog::readAdcBuffer()
{
if(!m_modbusDevice || m_boardAddress == -1)
return;
// Читаем выбранный диапазон буфера АЦП
QModbusDataUnit unit(QModbusDataUnit::InputRegisters, m_startAddress+REG_ADC_BUFFER_START, m_registerCount);
if(auto *reply = m_modbusDevice->sendReadRequest(unit, m_boardAddress)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, &AdcGraphDialog::onReadReady);
else
reply->deleteLater();
}
}
void AdcGraphDialog::startGraph(int boardId, int teNumber)
{
m_boardId = boardId;
m_teNumber = teNumber;
m_boardAddress = boardId + 1;
// Устанавливаем начальное значение в спинбокс
ui->teNumberSpinBox->setValue(teNumber);
setWindowTitle(QString("График АЦП - Плата %1, ТЭ %2 (адр %3-%4)")
.arg(boardId + 1)
.arg(teNumber)
.arg(m_startAddress)
.arg(m_startAddress + m_registerCount - 1));
// Очищаем предыдущие данные
m_series->clear();
m_stableStartLine->clear();
m_stableEndLine->clear();
// Обновляем диапазон графика
updateGraphRange();
readCalibrationValues();
// Записываем начальный номер ТЭ
setTENumber(m_boardAddress, teNumber);
m_updateTimer->start(m_timeout);
}
void AdcGraphDialog::setTimeout(int timeout)
{
m_timeout = timeout;
}
void AdcGraphDialog::stopGraph()
{
m_updateTimer->stop();
m_boardId = -1;
m_boardAddress = -1;
// Отменяем все pending запросы Modbus
if(m_modbusDevice)
m_modbusDevice->disconnect(this); // Отключаем все сигналы
}
void AdcGraphDialog::onUpdateTimer()
{
if(m_boardAddress == -1)
return;
// Читаем и буфер АЦП, и индексы стабильности каждый период
readAdcDataAndIndices();
}
void AdcGraphDialog::onReadReady()
{
auto *reply = qobject_cast<QModbusReply*>(sender());
if(!reply)
return;
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit result = reply->result();
// Очищаем предыдущие данные
m_series->clear();
// Добавляем новые точки и определяем диапазон
double minVoltage = 1000, maxVoltage = -1000;
for(uint i = 0; i < result.valueCount(); ++i) {
double voltage = convertAdcToVoltage(result.value(i));
m_series->append(i, voltage);
// Обновляем мин/макс для автоматического масштабирования
if(voltage < minVoltage)
minVoltage = voltage;
if(voltage > maxVoltage)
maxVoltage = voltage;
}
// Автоматически настраиваем диапазон оси Y
updateYAxisRange(minVoltage, maxVoltage);
// Обновляем линии стабильного участка
updateStableLines();
// Обновляем статистику
if(m_series->count() > 0) {
double min = 1000, max = -1000, sum = 0;
for(const QPointF &point : m_series->points()) {
double y = point.y();
if(y < min)
min = y;
if(y > max)
max = y;
sum += y;
}
double avg = sum / m_series->count();
ui->minLabel->setText(QString::number(min, 'f', 3) + " В");
ui->maxLabel->setText(QString::number(max, 'f', 3) + " В");
ui->avgLabel->setText(QString::number(avg, 'f', 3) + " В");
// Обновляем информацию о стабильном участке
updateStatisticsWithStableInfo();
}
}
reply->deleteLater();
}
void AdcGraphDialog::updateYAxisRange(double minVoltage, double maxVoltage)
{
// Добавляем запас 10% к диапазону
double range = maxVoltage - minVoltage;
double margin = range * 0.1;
double yMin = minVoltage - margin;
double yMax = maxVoltage + margin;
// Если диапазон слишком маленький или слишком большой, устанавливаем разумные пределы
yMin = -1.5;
yMax = 1.5;
// Ограничиваем разумными пределами
yMin = qMax(yMin, -5.0); // Не ниже -5В
yMax = qMin(yMax, 5.0); // Не выше 5В
// Устанавливаем новый диапазон
m_axisY->setRange(yMin, yMax);
// Обновляем линии стабильного участка с новым диапазоном
updateStableLines();
}
double AdcGraphDialog::convertAdcToVoltage(quint16 adcValue)
{
if(m_adcOneVolt == m_adcZero)
return 0;
return (adcValue - m_adcZero) * 1.1 / (m_adcOneVolt - m_adcZero);
}
void AdcGraphDialog::on_registerCountChanged(int value)
{
m_registerCount = value;
}
void AdcGraphDialog::on_rangeApplyClicked()
{
updateGraphRange();
// Немедленно обновляем данные
if(m_boardAddress != -1)
readAdcBuffer();
}
void AdcGraphDialog::on_closeBtn_clicked()
{
stopGraph();
reject(); // Или accept() в зависимости от логики
}

View File

@@ -0,0 +1,86 @@
#ifndef ADCGRAPHDIALOG_H
#define ADCGRAPHDIALOG_H
#include <QDialog>
#include <QTimer>
#include <QVector>
#include <QtCharts>
#include <QModbusClient>
QT_CHARTS_USE_NAMESPACE
// Forward declaration
class QModbusClient;
namespace Ui {
class AdcGraphDialog;
}
class AdcGraphDialog : public QDialog
{
Q_OBJECT
public:
explicit AdcGraphDialog(QModbusClient *modbusDevice, QWidget *parent = nullptr);
~AdcGraphDialog();
void setTENumber(int boardID, int teNumber);
void setModbusDevice(QModbusClient *device);
void startGraph(int boardId, int teNumber);
void stopGraph();
void setTimeout(int timeout);
void readyClose();
signals:
void dialogClosed();
protected:
void closeEvent(QCloseEvent *event) override {
stopGraph();
event->accept();
emit dialogClosed();
QDialog::closeEvent(event);
}
private slots:
void on_teNumberChanged(int value);
void onUpdateTimer();
void onReadReady();
void onStableIndicesReady();
void on_closeBtn_clicked();
void on_registerCountChanged(int value);
void on_rangeApplyClicked();
private:
Ui::AdcGraphDialog *ui;
QModbusClient *m_modbusDevice;
QTimer *m_updateTimer;
int m_boardId;
int m_boardAddress;
int m_teNumber;
int m_timeout;
// Калибровочные значения
double m_adcZero;
double m_adcOneVolt;
// Для отображения стабильного участка
QLineSeries *m_stableStartLine;
QLineSeries *m_stableEndLine;
int m_stableStartIndex;
int m_stableEndIndex;
// Данные графика
QLineSeries *m_series;
QValueAxis *m_axisX;
QValueAxis *m_axisY;
QChart *m_chart; // Добавить указатель на chart
// Управление диапазоном регистров
int m_startAddress;
int m_registerCount;
void updateStatistics();
void readAdcDataAndIndices();
void onCombinedDataReady();
void updateStatisticsWithStableInfo();
void updateYAxisRange(double minVoltage, double maxVoltage);
void setupRangeControls();
void updateGraphRange();
void readStableIndices();
void updateStableLines();
void readCalibrationValues();
void readAdcBuffer();
double convertAdcToVoltage(quint16 adcValue);
};
#endif // ADCGRAPHDIALOG_H

View File

@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AdcGraphDialog</class>
<widget class="QDialog" name="AdcGraphDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>График АЦП</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="rangeLayout">
<item>
<widget class="QLabel" name="registerCountLabel">
<property name="text">
<string>Количество:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="registerCountSpinBox">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="rangeApplyButton">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Номер ТЭ</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="teNumberSpinBox">
<property name="maximum">
<number>85</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="plotWidget" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Мин:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="minLabel">
<property name="text">
<string>0.000 В</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Макс:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="maxLabel">
<property name="text">
<string>0.000 В</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Средн:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="avgLabel">
<property name="text">
<string>0.000 В</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Сэмплов:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="samplesLabel">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="closeBtn">
<property name="text">
<string>Закрыть</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,796 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DebugTerminalDialog</class>
<widget class="QDialog" name="DebugTerminalDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1188</width>
<height>578</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0" colspan="4">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QGroupBox" name="DbgPlate_4">
<property name="title">
<string>Плата 3</string>
</property>
<layout class="QGridLayout" name="gridLayout_25">
<item row="3" column="0">
<widget class="QGroupBox" name="leds_3">
<property name="title">
<string>Тест светодиодов</string>
</property>
<layout class="QGridLayout" name="gridLayout_13">
<item row="0" column="0">
<widget class="QCheckBox" name="ledWorkTestChkBox_3">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="ledWarnTestChkBox_3">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="ledErrTestChkBox_3">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="ledConnectTestChkBox_3">
<property name="text">
<string>Связь</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ledVH1TestChkBox_3">
<property name="text">
<string>LED VH1 (Зеленый)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="ledVH2TestChkBox_3">
<property name="text">
<string>LED VH2 (Зеленый)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="ledVH3TestChkBox_3">
<property name="text">
<string>LED VH3 (Красный)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enableLedTestChkBox_3">
<property name="text">
<string>Тест ламп и дискретных сигналов</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Вызов функций</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<item row="2" column="0">
<widget class="QCheckBox" name="pollTECallChkBox_3">
<property name="text">
<string>Опрос ТЭ</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="continiusCallChkBox_3">
<property name="text">
<string>Непрерывный вызов калибр. и опроса</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="calibrateCallChkBox_3">
<property name="text">
<string>Калибровка</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="getHardfaultCallChkBox_3">
<property name="text">
<string>Генерация HardFault</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="resetDefaultCallChkBox_3">
<property name="text">
<string>Настройки по умолчанию</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="resetKeyCallChkBox_3">
<property name="text">
<string>Сбросить все ключи</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="discs_3">
<property name="title">
<string>Тест дискретных сигналов</string>
</property>
<layout class="QGridLayout" name="gridLayout_12">
<item row="2" column="0">
<widget class="QCheckBox" name="discErrTestChkBox_3">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="discErr5VsciTestChkBox_3">
<property name="text">
<string>Ошибка 5 Vsci</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="discWarnTestChkBox_3">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="discWorkTestChkBox_3">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="discErr5TestChkBox_3">
<property name="text">
<string>Ошибка 5 В</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="discErr24TestChkBox_3">
<property name="text">
<string>Ошибка 24 В</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="discErr5VATestChkBox_3">
<property name="text">
<string>Ошибка 5 VA</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="DbgPlate_3">
<property name="title">
<string>Плата 2</string>
</property>
<layout class="QGridLayout" name="gridLayout_24">
<item row="3" column="0">
<widget class="QGroupBox" name="leds_2">
<property name="title">
<string>Тест светодиодов</string>
</property>
<layout class="QGridLayout" name="gridLayout_21">
<item row="0" column="0">
<widget class="QCheckBox" name="ledWorkTestChkBox_2">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="ledWarnTestChkBox_2">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="ledErrTestChkBox_2">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="ledConnectTestChkBox_2">
<property name="text">
<string>Связь</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ledVH1TestChkBox_2">
<property name="text">
<string>LED VH1 (Зеленый)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="ledVH2TestChkBox_2">
<property name="text">
<string>LED VH2 (Зеленый)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="ledVH3TestChkBox_2">
<property name="text">
<string>LED VH3 (Красный)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enableLedTestChkBox_2">
<property name="text">
<string>Тест ламп и дискретных сигналов</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_21">
<property name="title">
<string>Вызов функций</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<item row="2" column="0">
<widget class="QCheckBox" name="calibrateCallChkBox_2">
<property name="text">
<string>Калибровка</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="resetDefaultCallChkBox_2">
<property name="text">
<string>Настройки по умолчанию</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="resetKeyCallChkBox_2">
<property name="text">
<string>Сбросить все ключи</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="pollTECallChkBox_2">
<property name="text">
<string>Опрос ТЭ</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="getHardfaultCallChkBox_2">
<property name="text">
<string>Генерация HardFault</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="continiusCallChkBox_2">
<property name="text">
<string>Непрерывный вызов калибр. и опроса</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="discs_2">
<property name="title">
<string>Тест дискретных сигналов</string>
</property>
<layout class="QGridLayout" name="gridLayout_20">
<item row="2" column="1">
<widget class="QCheckBox" name="discErr5VsciTestChkBox_2">
<property name="text">
<string>Ошибка 5 Vsci</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="discErr24TestChkBox_2">
<property name="text">
<string>Ошибка 24 В</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="discWarnTestChkBox_2">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="discWorkTestChkBox_2">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="discErr5TestChkBox_2">
<property name="text">
<string>Ошибка 5 В</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="discErrTestChkBox_2">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="discErr5VATestChkBox_2">
<property name="text">
<string>Ошибка 5 VA</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="DbgPlate_1">
<property name="title">
<string>Плата 1</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Вызов функций</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0">
<widget class="QCheckBox" name="pollTECallChkBox_1">
<property name="text">
<string>Опрос ТЭ</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="continiusCallChkBox_1">
<property name="text">
<string>Непрерывный вызов калибр. и опроса</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="calibrateCallChkBox_1">
<property name="text">
<string>Калибровка</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="getHardfaultCallChkBox_1">
<property name="text">
<string>Генерация HardFault</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="resetDefaultCallChkBox_1">
<property name="text">
<string>Настройки по умолчанию</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="resetKeyCallChkBox_1">
<property name="text">
<string>Сбросить все ключи</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="leds_1">
<property name="title">
<string>Тест светодиодов</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QCheckBox" name="ledWorkTestChkBox_1">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="ledWarnTestChkBox_1">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="ledErrTestChkBox_1">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="ledConnectTestChkBox_1">
<property name="text">
<string>Связь</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ledVH1TestChkBox_1">
<property name="text">
<string>LED VH1 (Зеленый)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="ledVH2TestChkBox_1">
<property name="text">
<string>LED VH2 (Зеленый)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="ledVH3TestChkBox_1">
<property name="text">
<string>LED VH3 (Красный)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="discs_1">
<property name="title">
<string>Тест дискретных сигналов</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="1">
<widget class="QCheckBox" name="discErr5VsciTestChkBox_1">
<property name="text">
<string>Ошибка 5 Vsci</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="discErr24TestChkBox_1">
<property name="text">
<string>Ошибка 24 В</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="discWarnTestChkBox_1">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="discErr5TestChkBox_1">
<property name="text">
<string>Ошибка 5 В</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="discWorkTestChkBox_1">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="discErrTestChkBox_1">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="discErr5VATestChkBox_1">
<property name="text">
<string>Ошибка 5 VA</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enableLedTestChkBox_1">
<property name="text">
<string>Тест ламп и дискретных сигналов</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="DbgPlate_2">
<property name="title">
<string>Плата 4</string>
</property>
<layout class="QGridLayout" name="gridLayout_23">
<item row="2" column="0">
<widget class="QGroupBox" name="discs_4">
<property name="title">
<string>Тест дискретных сигналов</string>
</property>
<layout class="QGridLayout" name="gridLayout_16">
<item row="1" column="0">
<widget class="QCheckBox" name="discWarnTestChkBox_4">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="discErrTestChkBox_4">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="discErr5VsciTestChkBox_4">
<property name="text">
<string>Ошибка 5 Vsci</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="discWorkTestChkBox_4">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="discErr5TestChkBox_4">
<property name="text">
<string>Ошибка 5 В</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="discErr24TestChkBox_4">
<property name="text">
<string>Ошибка 24 В</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="discErr5VATestChkBox_4">
<property name="text">
<string>Ошибка 5 VA</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="leds_4">
<property name="title">
<string>Тест светодиодов</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<item row="0" column="0">
<widget class="QCheckBox" name="ledWorkTestChkBox_4">
<property name="text">
<string>Работа</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="ledWarnTestChkBox_4">
<property name="text">
<string>Предупреждение</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="ledErrTestChkBox_4">
<property name="text">
<string>Авария</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="ledConnectTestChkBox_4">
<property name="text">
<string>Связь</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ledVH1TestChkBox_4">
<property name="text">
<string>LED VH1 (Зеленый)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="ledVH2TestChkBox_4">
<property name="text">
<string>LED VH2 (Зеленый)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="ledVH3TestChkBox_4">
<property name="text">
<string>LED VH3 (Красный)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enableLedTestChkBox_4">
<property name="text">
<string>Тест ламп и дискретных сигналов</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Вызов функций</string>
</property>
<layout class="QGridLayout" name="gridLayout_15">
<item row="2" column="0">
<widget class="QCheckBox" name="pollTECallChkBox_4">
<property name="text">
<string>Опрос ТЭ</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="continiusCallChkBox_4">
<property name="text">
<string>Непрерывный вызов калибр. и опроса</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="calibrateCallChkBox_4">
<property name="text">
<string>Калибровка</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="getHardfaultCallChkBox_4">
<property name="text">
<string>Генерация HardFault</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="resetDefaultCallChkBox_4">
<property name="text">
<string>Настройки по умолчанию</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="resetKeyCallChkBox_4">
<property name="text">
<string>Сбросить все ключи</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DebugTerminalDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DebugTerminalDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,790 @@
#include "debugterminaldialog.h"
#include "ui_debugTerminalDialog.h"
#include "adcgraphdialog.h"
#include <QDebug>
#include <QShowEvent>
#include <QCloseEvent>
#include <QAbstractButton>
DebugTerminalDialog::DebugTerminalDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::DebugTerminalDialog),
m_adcGraphDialog(nullptr),
m_modbusDevice(nullptr)
{
ui->setupUi(this);
boards[0].error24V = ui->discErr24TestChkBox_1;
boards[0].error5V = ui->discErr5TestChkBox_1;
boards[0].error5VSCI = ui->discErr5VsciTestChkBox_1;
boards[0].error5VA = ui->discErr5VATestChkBox_1;
boards[1].error24V = ui->discErr24TestChkBox_2;
boards[1].error5V = ui->discErr5TestChkBox_2;
boards[1].error5VSCI = ui->discErr5VsciTestChkBox_2;
boards[1].error5VA = ui->discErr5VATestChkBox_2;
boards[2].error24V = ui->discErr24TestChkBox_3;
boards[2].error5V = ui->discErr5TestChkBox_3;
boards[2].error5VSCI = ui->discErr5VsciTestChkBox_3;
boards[2].error5VA = ui->discErr5VATestChkBox_3;
boards[3].error24V = ui->discErr24TestChkBox_4;
boards[3].error5V = ui->discErr5TestChkBox_4;
boards[3].error5VSCI = ui->discErr5VsciTestChkBox_4;
boards[3].error5VA = ui->discErr5VATestChkBox_4;
initializeConnections();
// Создаем AdcGraphDialog с nullptr
m_adcGraphDialog = new AdcGraphDialog(nullptr, this);
}
DebugTerminalDialog::~DebugTerminalDialog()
{
delete ui;
}
void DebugTerminalDialog::setMainTerm(M3KTE* term)
{
mainTerm = term;
}
/**
* @brief Устанавливает указатель на объект QModbusClient для отладочного терминала.
*
* Этот метод присваивает указатель на Modbus-устройство и, при наличии графического диалога
* для отображения данных АЦП, передает ему тот же объект для синхронизации.
*
* @param device Указатель на объект QModbusClient, представляющий Modbus-устройство.
*/
void DebugTerminalDialog::setModbusDevice(QModbusClient *device)
{
m_modbusDevice = device;
if(m_adcGraphDialog && device)
m_adcGraphDialog->setModbusDevice(device);
}
/**
* @brief Устанавливает режим отладки, активируя или деактивируя коили.
*
* Этот метод вызывает функцию writeCoil для нескольких адресов (0, 1, 2, 3),
* устанавливая значение enable для каждого из них. Предполагается, что эти коили
* управляют режимом отладки устройства или системы.
*
* @param enable Целое значение (например, 0 или 1), указывающее, включать или выключать режим отладки.
*/
void DebugTerminalDialog::setDebugTerminalCoil(int enable)
{
writeCoil(0, COIL_DEBUG_MODE, enable);
writeCoil(1, COIL_DEBUG_MODE, enable);
writeCoil(2, COIL_DEBUG_MODE, enable);
writeCoil(3, COIL_DEBUG_MODE, enable);
}
/**
* @brief Обрабатывает событие отображения окна диалога.
*
* Этот метод вызывается при показе окна и сначала вызывает базовую реализацию showEvent.
* Затем происходит сброс всех настроек или состояний через resetAll(), а после этого
* устанавливается значение 1 для коили, отвечающих за режим отладки, с помощью setDebugTerminalCoil(1).
*
* @param event Указатель на объект QShowEvent, содержащий информацию о событии отображения.
*/
void DebugTerminalDialog::showEvent(QShowEvent *event)
{
QDialog::showEvent(event);
// При открытии окна записываем в коил 555 значение "1"
resetAll();
setDebugTerminalCoil(1);
}
/**
* @brief Обрабатывает событие закрытия диалогового окна.
*
* Перед закрытием окна вызывается метод setDebugTerminalCoil(0),
* который отключает режим отладки, устанавливая значение коил в 0.
* Затем вызывается базовая обработка closeEvent.
*
* @param event Указатель на объект QCloseEvent, содержащий информацию о событии закрытия.
*/
void DebugTerminalDialog::closeEvent(QCloseEvent *event)
{
// При закрытии окна записываем в коил 555 значение "0"
setDebugTerminalCoil(0);
QDialog::closeEvent(event);
}
void DebugTerminalDialog::initializeConnections(){}
void DebugTerminalDialog::updateConnectionStatus(int boardID, bool connected)
{
// Обновляем визуальное отображение статуса соединения
// Можно изменить цвет рамки или добавить индикатор
Q_UNUSED(boardID);
Q_UNUSED(connected);
// Реализация по необходимости
}
/**
* @brief Обработка клика по кнопкам в диалоговом окне.
*
* В зависимости от роли нажатой кнопки, выполняются соответствующие действия:
* - Если роль Reset, вызывается resetAll(), сбрасывающий настройки.
* - Если роль Accept, вызывается accept(), подтверждающий изменения.
*
* @param button Указатель на нажатую кнопку.
*/
void DebugTerminalDialog::on_buttonBox_clicked(QAbstractButton *button)
{
switch (ui->buttonBox->buttonRole(button)) {
case QDialogButtonBox::ResetRole:
resetAll();
break;
case QDialogButtonBox::AcceptRole:
accept();
break;
default:
break;
}
}
// Реализация слотов для вызова функций платы 1
void DebugTerminalDialog::on_continiusCallChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_CONTINUOUS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_calibrateCallChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_CALIBRATE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_pollTECallChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_POLL_TE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetKeyCallChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_RESET_KEYS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetDefaultCallChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_RESET_DEFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_getHardfaultCallChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_HARDFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для теста дискретных сигналов платы 1
void DebugTerminalDialog::on_enableLedTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_TEST_ENABLE, state == Qt::Checked ? 1 : 0);
ui->leds_1->setEnabled(state);
ui->discs_1->setEnabled(state);
}
void DebugTerminalDialog::on_discWorkTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_DISC_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discWarnTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_DISC_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErrTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_DISC_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErr24TestChkBox_1_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5TestChkBox_1_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VsciTestChkBox_1_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VATestChkBox_1_stateChanged(int state){Q_UNUSED(state)}
// Реализация слотов для теста светодиодов платы 1
void DebugTerminalDialog::on_ledWorkTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledWarnTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledErrTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledConnectTestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_CONNECT_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH1TestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_VH1_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH2TestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_VH2_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH3TestChkBox_1_stateChanged(int state)
{
writeCoil(0, COIL_LED_VH3_TEST, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для вызова функций платы 2
void DebugTerminalDialog::on_continiusCallChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_CONTINUOUS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_calibrateCallChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_CALIBRATE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_pollTECallChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_POLL_TE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetKeyCallChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_RESET_KEYS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetDefaultCallChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_RESET_DEFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_getHardfaultCallChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_HARDFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для теста дискретных сигналов платы 2
void DebugTerminalDialog::on_enableLedTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_TEST_ENABLE, state == Qt::Checked ? 1 : 0);
ui->leds_2->setEnabled(state);
ui->discs_2->setEnabled(state);
}
void DebugTerminalDialog::on_discWorkTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_DISC_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discWarnTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_DISC_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErrTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_DISC_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErr24TestChkBox_2_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5TestChkBox_2_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VsciTestChkBox_2_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VATestChkBox_2_stateChanged(int state){Q_UNUSED(state)}
// Реализация слотов для теста светодиодов платы 2
void DebugTerminalDialog::on_ledWorkTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledWarnTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledErrTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledConnectTestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_CONNECT_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH1TestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_VH1_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH2TestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_VH2_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH3TestChkBox_2_stateChanged(int state)
{
writeCoil(1, COIL_LED_VH3_TEST, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для вызова функций платы 3
void DebugTerminalDialog::on_continiusCallChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_CONTINUOUS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_calibrateCallChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_CALIBRATE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_pollTECallChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_POLL_TE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetKeyCallChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_RESET_KEYS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetDefaultCallChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_RESET_DEFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_getHardfaultCallChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_HARDFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для теста дискретных сигналов платы 3
void DebugTerminalDialog::on_enableLedTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_TEST_ENABLE, state == Qt::Checked ? 1 : 0);
ui->leds_3->setEnabled(state);
ui->discs_3->setEnabled(state);
}
void DebugTerminalDialog::on_discWorkTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_DISC_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discWarnTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_DISC_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErrTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_DISC_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErr24TestChkBox_3_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5TestChkBox_3_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VsciTestChkBox_3_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VATestChkBox_3_stateChanged(int state){Q_UNUSED(state)}
// Реализация слотов для теста светодиодов платы 3
void DebugTerminalDialog::on_ledWorkTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledWarnTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledErrTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledConnectTestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_CONNECT_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH1TestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_VH1_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH2TestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_VH2_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH3TestChkBox_3_stateChanged(int state)
{
writeCoil(2, COIL_LED_VH3_TEST, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для вызова функций платы 4
void DebugTerminalDialog::on_continiusCallChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_CONTINUOUS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_calibrateCallChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_CALIBRATE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_pollTECallChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_POLL_TE_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetKeyCallChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_RESET_KEYS_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_resetDefaultCallChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_RESET_DEFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_getHardfaultCallChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_HARDFAULT_CALL, state == Qt::Checked ? 1 : 0);
}
// Реализация слотов для теста дискретных сигналов платы 4
void DebugTerminalDialog::on_enableLedTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_TEST_ENABLE, state == Qt::Checked ? 1 : 0);
ui->leds_4->setEnabled(state);
ui->discs_4->setEnabled(state);
}
void DebugTerminalDialog::on_discWorkTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_DISC_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discWarnTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_DISC_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErrTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_DISC_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_discErr24TestChkBox_4_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5TestChkBox_4_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VsciTestChkBox_4_stateChanged(int state){Q_UNUSED(state)}
void DebugTerminalDialog::on_discErr5VATestChkBox_4_stateChanged(int state){Q_UNUSED(state)}
// Реализация слотов для теста светодиодов платы 4
void DebugTerminalDialog::on_ledWorkTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_WORK_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledWarnTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_WARN_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledErrTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_ERR_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledConnectTestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_CONNECT_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH1TestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_VH1_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH2TestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_VH2_TEST, state == Qt::Checked ? 1 : 0);
}
void DebugTerminalDialog::on_ledVH3TestChkBox_4_stateChanged(int state)
{
writeCoil(3, COIL_LED_VH3_TEST, state == Qt::Checked ? 1 : 0);
}
/**
* @brief Открывает окно графика для отображения данных АЦП с указанной платы и канала.
*
* Этот метод уничтожает предыдущий экземпляр диалога AdcGraphDialog, если он есть,
* создает новый, связывает сигнал закрытия с установкой режима отладки в 0,
* задает интервал обновления графика, запускает отображение данных и показывает окно.
*
* @param boardID Идентификатор платы.
* @param teNumber Номер канала (или технологической единицы).
*/
void DebugTerminalDialog::openAdc(int boardID, int teNumber)
{
// Удаляем старый диалог и создаем новый
if(m_adcGraphDialog) {
m_adcGraphDialog->deleteLater();
m_adcGraphDialog = nullptr;
}
m_adcGraphDialog = new AdcGraphDialog(m_modbusDevice, this);
connect(m_adcGraphDialog, &AdcGraphDialog::dialogClosed, this, [this]() {
setDebugTerminalCoil(0);
});
setGraphUpdateInterval(1000);
m_adcGraphDialog->startGraph(boardID, teNumber);
m_adcGraphDialog->show();
m_adcGraphDialog->raise();
m_adcGraphDialog->activateWindow();
}
/**
* @brief Устанавливает интервал обновления графика в миллисекундах.
*
* Этот метод задает период времени (в миллисекундах), с которым график в диалоге
* AdcGraphDialog обновляется. Если диалог открыт, вызывает его метод setTimeout.
*
* @param milliseconds Интервал обновления графика в миллисекундах.
*/
void DebugTerminalDialog::setGraphUpdateInterval(int milliseconds)
{
if(m_adcGraphDialog)
m_adcGraphDialog->setTimeout(milliseconds);
}
/**
* @brief Записывает значение коила для указанной платы.
*
* Этот метод выбирает группу элементов в интерфейсе в зависимости от идентификатора платы (boardID),
* проверяет, что выбранная плата активна (enabled), и затем сообщает о смене значения коила через сигнал.
* Перед этим выводит диагностическое сообщение в лог.
*
* @param boardID Идентификатор платы (0-3).
* @param coil Номер коила, который необходимо изменить.
* @param value Новое значение для коила.
*/
void DebugTerminalDialog::writeCoil(int boardID, int coil, int value)
{
QGroupBox* boardGroup = nullptr;
switch(boardID) {
case 0: boardGroup = ui->DbgPlate_1; break; // Плата 1
case 1: boardGroup = ui->DbgPlate_2; break; // Плата 2
case 2: boardGroup = ui->DbgPlate_3; break; // Плата 3
case 3: boardGroup = ui->DbgPlate_4; break; // Плата 4
default: return;
}
if(!boardGroup->isEnabled())
return;
qDebug() << "Writing board" << boardID << "coil:" << coil << "value:" << value;
emit coilValueChanged(boardID, coil, value);
}
void DebugTerminalDialog::writeTENumber(int boardId, int teNumber)
{
m_adcGraphDialog->setTENumber(boardId, teNumber);
}
/**
* @brief Устанавливает доступность (активность) для выбранной платы.
*
* Этот метод выбирает соответствующий QGroupBox для указанной платы (boardID),
* включает или отключает его в зависимости от булевого флага active.
* Также при деактивации меняет цвет текста на серый для визуального представления.
*
* @param boardID Идентификатор платы (0-3).
* @param active Булевое значение, определяющее активность (true — активировать, false — деактивировать).
*/
void DebugTerminalDialog::setBoardActive(int boardID, bool active)
{
// Получаем групбокс для указанной платы
QGroupBox* boardGroup = nullptr;
switch(boardID) {
case 0: boardGroup = ui->DbgPlate_1; break; // Плата 1
case 1: boardGroup = ui->DbgPlate_2; break; // Плата 2
case 2: boardGroup = ui->DbgPlate_3; break; // Плата 3
case 3: boardGroup = ui->DbgPlate_4; break; // Плата 4
default: return;
}
if(boardGroup) {
boardGroup->setEnabled(active);
// Можно добавить визуальное отличие неактивных плат
if(!active)
boardGroup->setStyleSheet("QGroupBox { color: gray; }");
else
boardGroup->setStyleSheet(""); // Сброс стиля
}
}
void DebugTerminalDialog::updateBoardStates(bool activeBoards[4])
{
for(int i = 0; i < 4; i++)
setBoardActive(i, activeBoards[i]);
}
void DebugTerminalDialog::resetAll()
{
// Сброс всех чекбоксов вызова функций
ui->continiusCallChkBox_1->setChecked(false);
ui->calibrateCallChkBox_1->setChecked(false);
ui->pollTECallChkBox_1->setChecked(false);
ui->resetKeyCallChkBox_1->setChecked(false);
ui->resetDefaultCallChkBox_1->setChecked(false);
ui->getHardfaultCallChkBox_1->setChecked(false);
ui->enableLedTestChkBox_1->setChecked(false);
ui->leds_1->setEnabled(false);
ui->discs_1->setEnabled(false);
ui->discWorkTestChkBox_1->setChecked(false);
// Сброс всех чекбоксов теста дискретных сигналов
ui->discWorkTestChkBox_1->setChecked(false);
ui->discWarnTestChkBox_1->setChecked(false);
ui->discErrTestChkBox_1->setChecked(false);
// Сброс всех чекбоксов теста светодиодов
ui->ledWorkTestChkBox_1->setChecked(false);
ui->ledWarnTestChkBox_1->setChecked(false);
ui->ledErrTestChkBox_1->setChecked(false);
ui->ledConnectTestChkBox_1->setChecked(false);
ui->ledVH1TestChkBox_1->setChecked(false);
ui->ledVH2TestChkBox_1->setChecked(false);
ui->ledVH3TestChkBox_1->setChecked(false);
// Сброс всех чекбоксов вызова функций
ui->continiusCallChkBox_2->setChecked(false);
ui->calibrateCallChkBox_2->setChecked(false);
ui->pollTECallChkBox_2->setChecked(false);
ui->resetKeyCallChkBox_2->setChecked(false);
ui->resetDefaultCallChkBox_2->setChecked(false);
ui->getHardfaultCallChkBox_2->setChecked(false);
ui->enableLedTestChkBox_2->setChecked(false);
ui->leds_2->setEnabled(false);
ui->discs_2->setEnabled(false);
ui->discWorkTestChkBox_2->setChecked(false);
// Сброс всех чекбоксов теста дискретных сигналов
ui->discWorkTestChkBox_2->setChecked(false);
ui->discWarnTestChkBox_2->setChecked(false);
ui->discErrTestChkBox_2->setChecked(false);
// Сброс всех чекбоксов теста светодиодов
ui->ledWorkTestChkBox_2->setChecked(false);
ui->ledWarnTestChkBox_2->setChecked(false);
ui->ledErrTestChkBox_2->setChecked(false);
ui->ledConnectTestChkBox_2->setChecked(false);
ui->ledVH1TestChkBox_2->setChecked(false);
ui->ledVH2TestChkBox_2->setChecked(false);
ui->ledVH3TestChkBox_2->setChecked(false);
// Сброс всех чекбоксов вызова функций
ui->continiusCallChkBox_3->setChecked(false);
ui->calibrateCallChkBox_3->setChecked(false);
ui->pollTECallChkBox_3->setChecked(false);
ui->resetKeyCallChkBox_3->setChecked(false);
ui->resetDefaultCallChkBox_3->setChecked(false);
ui->getHardfaultCallChkBox_3->setChecked(false);
ui->enableLedTestChkBox_3->setChecked(false);
ui->leds_3->setEnabled(false);
ui->discs_3->setEnabled(false);
ui->discWorkTestChkBox_3->setChecked(false);
// Сброс всех чекбоксов теста дискретных сигналов
ui->discWorkTestChkBox_3->setChecked(false);
ui->discWarnTestChkBox_3->setChecked(false);
ui->discErrTestChkBox_3->setChecked(false);
// Сброс всех чекбоксов теста светодиодов
ui->ledWorkTestChkBox_3->setChecked(false);
ui->ledWarnTestChkBox_3->setChecked(false);
ui->ledErrTestChkBox_3->setChecked(false);
ui->ledConnectTestChkBox_3->setChecked(false);
ui->ledVH1TestChkBox_3->setChecked(false);
ui->ledVH2TestChkBox_3->setChecked(false);
ui->ledVH3TestChkBox_3->setChecked(false);
// Сброс всех чекбоксов вызова функций
ui->continiusCallChkBox_4->setChecked(false);
ui->calibrateCallChkBox_4->setChecked(false);
ui->pollTECallChkBox_4->setChecked(false);
ui->resetKeyCallChkBox_4->setChecked(false);
ui->resetDefaultCallChkBox_4->setChecked(false);
ui->getHardfaultCallChkBox_4->setChecked(false);
ui->enableLedTestChkBox_4->setChecked(false);
ui->leds_4->setEnabled(false);
ui->discs_4->setEnabled(false);
ui->discWorkTestChkBox_4->setChecked(false);
// Сброс всех чекбоксов теста дискретных сигналов
ui->discWorkTestChkBox_4->setChecked(false);
ui->discWarnTestChkBox_4->setChecked(false);
ui->discErrTestChkBox_4->setChecked(false);
// Сброс всех чекбоксов теста светодиодов
ui->ledWorkTestChkBox_4->setChecked(false);
ui->ledWarnTestChkBox_4->setChecked(false);
ui->ledErrTestChkBox_4->setChecked(false);
ui->ledConnectTestChkBox_4->setChecked(false);
ui->ledVH1TestChkBox_4->setChecked(false);
ui->ledVH2TestChkBox_4->setChecked(false);
ui->ledVH3TestChkBox_4->setChecked(false);
}
void DebugTerminalDialog::boardDebugReading(int boardID)
{
if(mainTerm == nullptr)
return;
if(!boards[boardID].isActive)
return;
if(!this->isVisible())
return;
QModbusReply *_24V = mainTerm->readSingleCoil(boardID, 603);
if(_24V != nullptr)
connect(_24V, &QModbusReply::finished, this, [this, boardID, _24V]() {
if(_24V->error() == QModbusDevice::NoError)
boards[boardID].error24V->setChecked(_24V->result().value(0));
_24V->deleteLater();
});
QModbusReply *_5V = mainTerm->readSingleCoil(boardID, 604);
if(_5V != nullptr)
connect(_5V, &QModbusReply::finished, this, [this, boardID, _5V]() {
if(_5V->error() == QModbusDevice::NoError)
boards[boardID].error5V->setChecked(_5V->result().value(0));
_5V->deleteLater();
});
QModbusReply *_5VSCI = mainTerm->readSingleCoil(boardID, 605);
if(_5VSCI != nullptr)
connect(_5VSCI, &QModbusReply::finished, this, [this, boardID, _5VSCI]() {
if(_5VSCI->error() == QModbusDevice::NoError)
boards[boardID].error5VSCI->setChecked(_5VSCI->result().value(0));
_5VSCI->deleteLater();
});
QModbusReply *_5VA = mainTerm->readSingleCoil(boardID, 606);
if(_5VA != nullptr)
connect(_5VA, &QModbusReply::finished, this, [this, boardID, _5VA]() {
if(_5VA->error() == QModbusDevice::NoError)
boards[boardID].error5VA->setChecked(_5VA->result().value(0));
_5VA->deleteLater();
});
}
void DebugTerminalDialog::setScanBoardActive(bool flag, int boardID)
{
boards[boardID].isActive = flag;
}
void DebugTerminalDialog::offAllBoard()
{
for(int i = 0; i < 4; i++)
boards[i].isActive = false;
}

View File

@@ -0,0 +1,193 @@
#ifndef DEBUGTERMINALDIALOG_H
#define DEBUGTERMINALDIALOG_H
#include <QDialog>
#include <QModbusClient>
#include <QAbstractButton>
#include <QCheckBox>
#include <m3kte.h>
// Forward declarations вместо include
class M3KTE;
class AdcGraphDialog;
// Дефайны для адресов коилов
#define COIL_DEBUG_MODE 555
#define COIL_CONTINUOUS_CALL 571
#define COIL_CALIBRATE_CALL 572
#define COIL_POLL_TE_CALL 573
#define COIL_RESET_KEYS_CALL 574
#define COIL_RESET_DEFAULT_CALL 576
#define COIL_HARDFAULT_CALL 586
#define COIL_LED_TEST_ENABLE 587
// Тест дискретных сигналов
#define COIL_DISC_WORK_TEST 588
#define COIL_DISC_WARN_TEST 589
#define COIL_DISC_ERR_TEST 590
// Тест светодиодов
#define COIL_LED_CONNECT_TEST 591
#define COIL_LED_WORK_TEST 592
#define COIL_LED_WARN_TEST 593
#define COIL_LED_ERR_TEST 594
#define COIL_LED_VH1_TEST 595
#define COIL_LED_VH2_TEST 596
#define COIL_LED_VH3_TEST 597
// Адреса для чтения состояний ошибок питания
#define COIL_READ_ERR_24V 600 // Чтение ошибки 24В
#define COIL_READ_ERR_5V 601 // Чтение ошибки 5В
#define COIL_READ_ERR_5VSCI 602 // Чтение ошибки 5Vsci
#define COIL_READ_ERR_5VA 603 // Чтение ошибки 5VA
#define REGISTER_TE_NUMB 564
namespace Ui {
class DebugTerminalDialog;
}
class DebugTerminalDialog : public QDialog
{
Q_OBJECT
public:
explicit DebugTerminalDialog(QWidget *parent = nullptr);
~DebugTerminalDialog();
AdcGraphDialog *m_adcGraphDialog;
void setDebugTerminalCoil(int enable);
void updateConnectionStatus(int boardId, bool connected);
void setBoardActive(int boardId, bool active);
void updateBoardStates(bool activeBoards[4]);
void setModbusDevice(QModbusClient *device);
void setGraphUpdateInterval(int milliseconds);
void writeTENumber(int boardId, int teNumber);
void openAdc(int boardID, int teNumber);
void setMainTerm(M3KTE* term);
public slots:
void boardDebugReading(int boardID);
void setScanBoardActive(bool flag, int boardID);
void offAllBoard();
protected:
void showEvent(QShowEvent *event) override;
void closeEvent(QCloseEvent *event) override;
private slots:
void on_buttonBox_clicked(QAbstractButton *button);
// Плата 1
void on_continiusCallChkBox_1_stateChanged(int state);
void on_calibrateCallChkBox_1_stateChanged(int state);
void on_pollTECallChkBox_1_stateChanged(int state);
void on_resetKeyCallChkBox_1_stateChanged(int state);
void on_resetDefaultCallChkBox_1_stateChanged(int state);
void on_getHardfaultCallChkBox_1_stateChanged(int state);
void on_enableLedTestChkBox_1_stateChanged(int state);
void on_discWorkTestChkBox_1_stateChanged(int state);
void on_discWarnTestChkBox_1_stateChanged(int state);
void on_discErrTestChkBox_1_stateChanged(int state);
void on_discErr24TestChkBox_1_stateChanged(int state);
void on_discErr5TestChkBox_1_stateChanged(int state);
void on_discErr5VsciTestChkBox_1_stateChanged(int state);
void on_discErr5VATestChkBox_1_stateChanged(int state);
void on_ledWorkTestChkBox_1_stateChanged(int state);
void on_ledWarnTestChkBox_1_stateChanged(int state);
void on_ledErrTestChkBox_1_stateChanged(int state);
void on_ledConnectTestChkBox_1_stateChanged(int state);
void on_ledVH1TestChkBox_1_stateChanged(int state);
void on_ledVH2TestChkBox_1_stateChanged(int state);
void on_ledVH3TestChkBox_1_stateChanged(int state);
// Плата 2
void on_continiusCallChkBox_2_stateChanged(int state);
void on_calibrateCallChkBox_2_stateChanged(int state);
void on_pollTECallChkBox_2_stateChanged(int state);
void on_resetKeyCallChkBox_2_stateChanged(int state);
void on_resetDefaultCallChkBox_2_stateChanged(int state);
void on_getHardfaultCallChkBox_2_stateChanged(int state);
void on_enableLedTestChkBox_2_stateChanged(int state);
void on_discWorkTestChkBox_2_stateChanged(int state);
void on_discWarnTestChkBox_2_stateChanged(int state);
void on_discErrTestChkBox_2_stateChanged(int state);
void on_discErr24TestChkBox_2_stateChanged(int state);
void on_discErr5TestChkBox_2_stateChanged(int state);
void on_discErr5VsciTestChkBox_2_stateChanged(int state);
void on_discErr5VATestChkBox_2_stateChanged(int state);
void on_ledWorkTestChkBox_2_stateChanged(int state);
void on_ledWarnTestChkBox_2_stateChanged(int state);
void on_ledErrTestChkBox_2_stateChanged(int state);
void on_ledConnectTestChkBox_2_stateChanged(int state);
void on_ledVH1TestChkBox_2_stateChanged(int state);
void on_ledVH2TestChkBox_2_stateChanged(int state);
void on_ledVH3TestChkBox_2_stateChanged(int state);
// Плата 3
void on_continiusCallChkBox_3_stateChanged(int state);
void on_calibrateCallChkBox_3_stateChanged(int state);
void on_pollTECallChkBox_3_stateChanged(int state);
void on_resetKeyCallChkBox_3_stateChanged(int state);
void on_resetDefaultCallChkBox_3_stateChanged(int state);
void on_getHardfaultCallChkBox_3_stateChanged(int state);
void on_enableLedTestChkBox_3_stateChanged(int state);
void on_discWorkTestChkBox_3_stateChanged(int state);
void on_discWarnTestChkBox_3_stateChanged(int state);
void on_discErrTestChkBox_3_stateChanged(int state);
void on_discErr24TestChkBox_3_stateChanged(int state);
void on_discErr5TestChkBox_3_stateChanged(int state);
void on_discErr5VsciTestChkBox_3_stateChanged(int state);
void on_discErr5VATestChkBox_3_stateChanged(int state);
void on_ledWorkTestChkBox_3_stateChanged(int state);
void on_ledWarnTestChkBox_3_stateChanged(int state);
void on_ledErrTestChkBox_3_stateChanged(int state);
void on_ledConnectTestChkBox_3_stateChanged(int state);
void on_ledVH1TestChkBox_3_stateChanged(int state);
void on_ledVH2TestChkBox_3_stateChanged(int state);
void on_ledVH3TestChkBox_3_stateChanged(int state);
// Плата 4
void on_continiusCallChkBox_4_stateChanged(int state);
void on_calibrateCallChkBox_4_stateChanged(int state);
void on_pollTECallChkBox_4_stateChanged(int state);
void on_resetKeyCallChkBox_4_stateChanged(int state);
void on_resetDefaultCallChkBox_4_stateChanged(int state);
void on_getHardfaultCallChkBox_4_stateChanged(int state);
void on_enableLedTestChkBox_4_stateChanged(int state);
void on_discWorkTestChkBox_4_stateChanged(int state);
void on_discWarnTestChkBox_4_stateChanged(int state);
void on_discErrTestChkBox_4_stateChanged(int state);
void on_discErr24TestChkBox_4_stateChanged(int state);
void on_discErr5TestChkBox_4_stateChanged(int state);
void on_discErr5VsciTestChkBox_4_stateChanged(int state);
void on_discErr5VATestChkBox_4_stateChanged(int state);
void on_ledWorkTestChkBox_4_stateChanged(int state);
void on_ledWarnTestChkBox_4_stateChanged(int state);
void on_ledErrTestChkBox_4_stateChanged(int state);
void on_ledConnectTestChkBox_4_stateChanged(int state);
void on_ledVH1TestChkBox_4_stateChanged(int state);
void on_ledVH2TestChkBox_4_stateChanged(int state);
void on_ledVH3TestChkBox_4_stateChanged(int state);
signals:
void coilValueChanged(int boardID, int coil, int value);
void writeRegister(int boardID, int reg, int value);
void readCoil(int boardID, int coil, QModbusReply *reply);
private:
Ui::DebugTerminalDialog *ui;
QModbusClient *m_modbusDevice; // Храним указатель здесь
M3KTE* mainTerm = nullptr;
// Карты для хранения состояний
QMap<int, bool> m_functionCalls; // boardId -> coil -> state
QMap<int, bool> m_discreteTests; // boardId -> coil -> state
QMap<int, bool> m_ledTests; // boardId -> coil -> state
// Номера ТЭ для каждой платы
int m_teNumbers[4] = {0, 0, 0, 0};
struct boardErrorLinks{
bool isActive = false;
QCheckBox* error24V = nullptr;
QCheckBox* error5V = nullptr;
QCheckBox* error5VSCI = nullptr;
QCheckBox* error5VA = nullptr;
};
boardErrorLinks boards[4];
void initializeConnections();
void writeCoil(int boardID, int coil, int value);
//void readCoil(int coil);
void resetAll();
};
#endif // DEBUGTERMINALDIALOG_H

View File

@@ -0,0 +1,150 @@
#include "devicesettingsdialog.h"
#include "ui_devicesettingsdialog.h"
#include <QDebug>
#include <QProcess>
#include <QCoreApplication>
DeviceSettingsDialog::DeviceSettingsDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::DeviceSettingsDialog)
{
ui->setupUi(this);
on_buttonApplyChangeTimer_clicked();
_m_timer[0] = ui->spinTimerBoard_1;
_m_timer[1] = ui->spinTimerBoard_2;
_m_timer[2] = ui->spinTimerBoard_3;
_m_timer[3] = ui->spinTimerBoard_4;
_currentSpeed = ui->speedBox->currentText().toUInt();
_currentParity = ui->parityBox->currentIndex();
for(int i = 0; i < 4; i++)
_currentAdrs[i] = i+1;
}
DeviceSettingsDialog::~DeviceSettingsDialog()
{
delete ui;
}
void DeviceSettingsDialog::on_buttonApplyChangeTimer_clicked()
{
_currentBoardTimers[0] = ui->spinTimerBoard_1->value();
_currentBoardTimers[1] = ui->spinTimerBoard_2->value();
_currentBoardTimers[2] = ui->spinTimerBoard_3->value();
_currentBoardTimers[3] = ui->spinTimerBoard_4->value();
}
void DeviceSettingsDialog::on_buttonApplyChangeSpeed_clicked()
{
_currentSpeed = ui->speedBox->currentIndex();
emit speedChanged();
}
void DeviceSettingsDialog::on_buttonApplyChangeParity_clicked()
{
_currentParity = ui->parityBox->currentIndex();
emit parityChanged();
}
void DeviceSettingsDialog::on_buttonApplyChangeAdr_clicked()
{
BoardIdHasBeenChanged* _boardIdHasBeenChanged = new BoardIdHasBeenChanged(ui->idComboBox->currentText().toUInt(), ui->adrSpinBox->value());
QCoreApplication::postEvent(parent(), _boardIdHasBeenChanged);
close();
}
unsigned DeviceSettingsDialog::currentBoardTimer(unsigned short _ID)
{
return _currentBoardTimers[_ID];
}
unsigned DeviceSettingsDialog::currentSpeed()
{
return _currentSpeed;
}
unsigned short DeviceSettingsDialog::currentParity()
{
return _currentParity;
}
void DeviceSettingsDialog::updateSettingsAfterConnection(unsigned tmp_speed, unsigned tmp_parity, unsigned *tmp_adr, bool *ActiveDevices)
{
ui->speedBox->setCurrentText(QString::number(_currentSpeed=tmp_speed, 10));
if(tmp_parity > 0)
tmp_parity--;
ui->parityBox->setCurrentIndex(_currentParity = tmp_parity);
for(int i = 0; i < 4; i++) {
if(ActiveDevices[i]) {
_m_timer[i]->setEnabled(true);
ui->idComboBox->addItem(QString::number(i));
_currentAdrs[i] = tmp_adr[i];
} else
_m_timer[i]->setEnabled(false);
}
on_idComboBox_currentIndexChanged(ui->idComboBox->currentIndex());
}
void DeviceSettingsDialog::on_idComboBox_currentIndexChanged(int index)
{
ui->adrSpinBox->setValue(_currentAdrs[index]);
}
void DeviceSettingsDialog::on_buttonBox_clicked(QAbstractButton *button)
{
switch (ui->buttonBox->buttonRole(button)) {
case QDialogButtonBox::ResetRole:
ui->spinTimerBoard_1->setValue(1000);
ui->spinTimerBoard_2->setValue(1000);
ui->spinTimerBoard_3->setValue(1000);
ui->spinTimerBoard_4->setValue(1000);
on_buttonApplyChangeTimer_clicked();
ui->speedBox->setCurrentText("31250");
_currentSpeed = ui->speedBox->currentText().toUInt();
ui->parityBox->setCurrentIndex(0);
_currentParity = ui->parityBox->currentIndex();
for(int i = 0; i < 4; i++)
_currentAdrs[i] = i + 1;
ui->adrSpinBox->setValue(_currentAdrs[ui->idComboBox->currentIndex()]);
break;
case QDialogButtonBox::AcceptRole:
close();
break;
default:
break;
}
}
void DeviceSettingsDialog::initPollForBoard(unsigned boardID, unsigned boardAdr)
{
ui->idPollComboBox->addItem(QString("Плата №%1 (ID %2)").arg(boardID + 1).arg(boardAdr), QVariant(boardID));
}
void DeviceSettingsDialog::updatePollStatus(unsigned boardID, bool status)
{
_currentPollStatus[boardID] = status;
}
void DeviceSettingsDialog::on_buttonApplyChangePoll_clicked()
{
sendPollCommand(ui->idPollComboBox->currentData().toUInt(), (bool)ui->pollStatusBox->currentIndex());
}
void DeviceSettingsDialog::sendPollCommand(unsigned boardID, bool status)
{
updatePollStatus(boardID, status);
pollStatusChange* _pollStatusChanged = new pollStatusChange(boardID, status);
QCoreApplication::postEvent(parent(), _pollStatusChanged);
}
void DeviceSettingsDialog::on_idPollComboBox_currentIndexChanged(int index)
{
Q_UNUSED(index);
ui->pollStatusBox->setCurrentIndex(_currentPollStatus[ui->idPollComboBox->currentData().toUInt()]);
}
void DeviceSettingsDialog::onDisconnect()
{
ui->idComboBox->clear();
ui->idPollComboBox->clear();
}

View File

@@ -0,0 +1,78 @@
#ifndef DEVICESETTINGSDIALOG_H
#define DEVICESETTINGSDIALOG_H
#include <QDialog>
#include <QEvent>
#include <QAbstractButton>
#include <QDialogButtonBox>
#include <QSpinBox>
class BoardIdHasBeenChanged : public QEvent
{
public:
BoardIdHasBeenChanged(const short num, const short newId) : QEvent(QEvent::User) {_BoardNum = num; _BoardNewID = newId;}
~BoardIdHasBeenChanged() {}
short BoardNum() const {return _BoardNum;}
short BoardNewID() const {return _BoardNewID;}
private:
short _BoardNum;
short _BoardNewID;
};
class pollStatusChange : public QEvent
{
public:
pollStatusChange(unsigned BoardID, bool Stat) : QEvent((QEvent::Type)1001) {_BoardID = BoardID; _Status = Stat;}
~pollStatusChange() {}
unsigned BoardID() const {return _BoardID;}
bool Status() const {return _Status;}
private:
unsigned _BoardID;
bool _Status;
};
namespace Ui {
class DeviceSettingsDialog;
}
class DeviceSettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit DeviceSettingsDialog(QWidget *parent = nullptr);
~DeviceSettingsDialog();
unsigned currentBoardTimer(unsigned short _ID);
unsigned currentSpeed();
unsigned short currentParity();
void updateSettingsAfterConnection(unsigned tmp_speed, unsigned tmp_parity, unsigned *tmp_adr, bool *ActiveDevices);
void sendPollCommand(unsigned boardID, bool status);
void updatePollStatus(unsigned boardID, bool status);
void initPollForBoard(unsigned boardID, unsigned boardAdr);
void onDisconnect();
signals:
void parityChanged();
void speedChanged();
void firstBoardAdrHasBeenChanged();
void secondBoardAdrHasBeenChanged();
void thirdBoardAdrHasBeenChanged();
void fourthBoardAdrHasBeenChanged();
private slots:
void on_buttonApplyChangeTimer_clicked();
void on_buttonApplyChangeSpeed_clicked();
void on_buttonApplyChangeParity_clicked();
void on_buttonApplyChangeAdr_clicked();
void on_idComboBox_currentIndexChanged(int index);
void on_buttonBox_clicked(QAbstractButton *button);
void on_buttonApplyChangePoll_clicked();
void on_idPollComboBox_currentIndexChanged(int index);
private:
QSpinBox *_m_timer[4];
unsigned _currentBoardTimers[4];
unsigned _currentSpeed;
unsigned short _currentParity;
unsigned _currentAdrs[4];
bool _currentPollStatus[4];
Ui::DeviceSettingsDialog *ui;
};
#endif // DEVICESETTINGSDIALOG_H

View File

@@ -0,0 +1,339 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeviceSettingsDialog</class>
<widget class="QDialog" name="DeviceSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>278</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Период опроса плат</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="boardLabel_5">
<property name="text">
<string>Плата №3</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="spinTimerBoard_3">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string> мс</string>
</property>
<property name="maximum">
<number>1999999999</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="boardLabel_1">
<property name="text">
<string>Плата №1</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="buttonApplyChangeTimer">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="spinTimerBoard_2">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string> мс</string>
</property>
<property name="maximum">
<number>1999999999</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="boardLabel_2">
<property name="text">
<string>Плата №2</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinTimerBoard_1">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string> мс</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1999999999</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="boardLabel_4">
<property name="text">
<string>Плата №4</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="spinTimerBoard_4">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string> мс</string>
</property>
<property name="maximum">
<number>1999999999</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Контроль четности</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
<widget class="QPushButton" name="buttonApplyChangeParity">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="parityBox">
<item>
<property name="text">
<string>No</string>
</property>
</item>
<item>
<property name="text">
<string>Even</string>
</property>
</item>
<item>
<property name="text">
<string>Odd</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Сетевой адрес МЗКТЭ</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QComboBox" name="idComboBox">
<property name="whatsThis">
<string>Номер платы</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="adrSpinBox">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>247</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="buttonApplyChangeAdr">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Скорость обмена</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QPushButton" name="buttonApplyChangeSpeed">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="speedBox">
<property name="currentIndex">
<number>3</number>
</property>
<item>
<property name="text">
<string>9600</string>
</property>
</item>
<item>
<property name="text">
<string>14400</string>
</property>
</item>
<item>
<property name="text">
<string>19200</string>
</property>
</item>
<item>
<property name="text">
<string>31250</string>
</property>
</item>
<item>
<property name="text">
<string>38400</string>
</property>
</item>
<item>
<property name="text">
<string>56000</string>
</property>
</item>
<item>
<property name="text">
<string>57600</string>
</property>
</item>
<item>
<property name="text">
<string>115200</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item row="5" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Опрос ТЭ</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QComboBox" name="idPollComboBox"/>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="pollStatusBox">
<item>
<property name="text">
<string>Выкл</string>
</property>
</item>
<item>
<property name="text">
<string>Вкл</string>
</property>
</item>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="buttonApplyChangePoll">
<property name="text">
<string>Применить</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DeviceSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DeviceSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

308
M3KTE_TERM/lineringer.cpp Normal file
View File

@@ -0,0 +1,308 @@
#include "lineringer.h"
#include "ui_lineringer.h"
LineRinger::LineRinger(QWidget *parent) :
QWidget(parent),
ui(new Ui::LineRinger)
{
ui->setupUi(this);
const auto listPorts = QSerialPortInfo::availablePorts();
for(const auto& port: listPorts)
ui->comBox->addItem(QString(port.portName() + ": " + port.manufacturer()), QVariant(port.portName()));
//чётность
{
ui->parityControlBox->addItem("No", QVariant(QSerialPort::NoParity));
ui->parityControlBox->addItem("Even", QVariant(QSerialPort::EvenParity));
ui->parityControlBox->addItem("Odd", QVariant(QSerialPort::OddParity));
ui->parityControlBox->addItem("Space", QVariant(QSerialPort::SpaceParity));
ui->parityControlBox->addItem("Mark", QVariant(QSerialPort::MarkParity));
}
//данные
{
ui->dataBox->addItem("Data5", QVariant(QSerialPort::Data5));
ui->dataBox->addItem("Data6", QVariant(QSerialPort::Data6));
ui->dataBox->addItem("Data7", QVariant(QSerialPort::Data7));
ui->dataBox->addItem("Data8", QVariant(QSerialPort::Data8));
ui->dataBox->setCurrentIndex(3);
}
//стопбиты
{
ui->stopBox->addItem("One", QVariant(QSerialPort::OneStop));
ui->stopBox->addItem("OneAndHalf", QVariant(QSerialPort::OneAndHalfStop));
ui->stopBox->addItem("Two", QVariant(QSerialPort::TwoStop));
}
ui->deviceOnlineView->horizontalHeader()->setVisible(true);
syncColumnHeaders();
ui->deviceOnlineView->setColumnHidden(1, true);
ui->ringButton->setEnabled(false);
modbusDevice = new QModbusRtuSerialMaster(this);
}
LineRinger::~LineRinger()
{
if(modbusDevice->state() == QModbusDevice::ConnectedState)
on_connectButton_clicked();
delete ui;
}
/**
* @brief Обновляет заголовки столбцов таблицы устройств.
*
* Этот метод устанавливает список заголовков для таблицы отображения устройств
* в интерфейсе и затем автоматически изменяет ширину колонок под содержимое.
*/
void LineRinger::syncColumnHeaders()
{
// Создаём список заголовков колонок
QStringList headers;
headers << "ID"
<< "BaudRate"
<< "Vendor Name"
<< "Product Code"
<< "Major Minor Revision"
<< "Vendor Url"
<< "Product Name"
<< "Model Name"
<< "User Application Name"
<< "Примечание";
// Устанавливаем заголовки для таблицы
ui->deviceOnlineView->setHorizontalHeaderLabels(headers);
// Автоматически подгоняем ширину колонок под содержимое
ui->deviceOnlineView->resizeColumnsToContents();
}
/**
* @brief Обработчик нажатия кнопки "Подключить/Отключить".
*
* Этот слот управляет подключением или отключением модбус-устройства.
* При отсутствии подключения он выполняет настройку параметров соединения,
* инициирует подключение и обновляет интерфейс в зависимости от результата.
* Если устройство уже подключено, происходит отключение и обновление интерфейса.
*/
void LineRinger::on_connectButton_clicked()
{
if(!modbusDevice)
return;
if(modbusDevice->state() != QModbusDevice::ConnectedState) {
modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
ui->comBox->currentData().toString());
#if QT_CONFIG(modbus_serialport)
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
ui->parityControlBox->currentData().toInt());
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
ui->baudRateBox->currentText().toInt(nullptr, 10));
modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
ui->dataBox->currentData().toInt());
modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
ui->stopBox->currentData().toInt());
#endif
modbusDevice->setTimeout(50);
modbusDevice->setNumberOfRetries(0);
if(!modbusDevice->connectDevice())
QMessageBox::warning(this, "Ошибка", "Произошла ошибка при попытке подключения.");
else {
ui->connectButton->setText(tr("Отключить"));
ui->ringButton->setEnabled(true);
currentBaudRate = ui->baudRateBox->currentText().toUInt(nullptr, 10);
}
} else {
modbusDevice->disconnectDevice();
ui->connectButton->setText(tr("Подключить"));
ui->ringButton->setEnabled(false);
}
}
LineRinger::callStatus LineRinger::lineCall()
{
QModbusRequest readDeviceBasicIdentification(QModbusRequest::EncapsulatedInterfaceTransport, QByteArray::fromHex("0E0100"));
QModbusRequest readDeviceRegularIdentification(QModbusRequest::EncapsulatedInterfaceTransport, QByteArray::fromHex("0E0200"));
bool isRun = false;
bool *tmp_isRun = &isRun;
uint tmp_adr = 1;
auto bar = new QProgressDialog(this);
connect(bar, &QProgressDialog::canceled, this, [tmp_isRun]() {
*tmp_isRun = true;
});
connect(this, &LineRinger::stopLineCall, this, [tmp_isRun]() {
*tmp_isRun = true;
});
bar->setLabelText(tr("Поиск устройств... Текущий адрес: %1").arg(tmp_adr));
bar->setCancelButton(nullptr);
bar->setRange(1, 247);
bar->setMinimumDuration(100);
for(tmp_adr = 1; tmp_adr < 248; tmp_adr++) {
bar->setValue(tmp_adr);
bar->setLabelText(tr("Поиск устройств... Текущий адрес: %1/247").arg(tmp_adr));
auto *reply = modbusDevice->sendRawRequest(readDeviceBasicIdentification, tmp_adr);
//Запрос типа устройства.
if(reply == nullptr) {
QMessageBox::warning(this, "Ошибка при сканировании.", QString("%1").arg(modbusDevice->errorString()));
bar->close();
bar->deleteLater();
return callStatus::ERROR;
}
while(!reply->isFinished()) {
if(isRun) {
bar->close();
bar->deleteLater();
return callStatus::INTERRUPT;
}
QCoreApplication::processEvents();
}
if(isRun) {
bar->close();
bar->deleteLater();
return callStatus::INTERRUPT;
} else if(!isRun) {
//Нужна проверка типа устройства
if(reply->error() != QModbusDevice::TimeoutError) {
deviceOnLine currentDevice;
currentDevice.adr = tmp_adr;
currentDevice.baudRate = currentBaudRate;
bool regularReplyError = false;
QString regularReplyErrorString;
if(reply->error()==QModbusDevice::NoError) {
QModbusResponse resp = reply->rawResult();
uint8_t numOfObject = resp.data().at(5);
QByteArray result = resp.data().remove(0, 6);
for(int tmp_obj = 0; tmp_obj < numOfObject; tmp_obj++) {
uint8_t objectID = result.at(0);
uint8_t lengthOfObject = result.at(1);
if(lengthOfObject > 0)
currentDevice.fields[objectID].clear();
for(int i = 0; i < lengthOfObject; i++)
currentDevice.fields[objectID] += QString(result.at(2 + i));
result.remove(0, lengthOfObject + 2);
}
auto *regularReply = modbusDevice->sendRawRequest(readDeviceRegularIdentification, tmp_adr);
if(regularReply == nullptr) {
QMessageBox::warning(this, "Ошибка при сканировании.",
QString("%1: %2").arg(modbusDevice->error()).arg(modbusDevice->errorString()));
bar->close();
bar->deleteLater();
return callStatus::ERROR;
}
while(!regularReply->isFinished()) {
if(isRun) {
bar->close();
bar->deleteLater();
return callStatus::INTERRUPT;
}
QCoreApplication::processEvents();
}
if(isRun) {
bar->close();
bar->deleteLater();
return callStatus::INTERRUPT;
} else if(!isRun) {
if(regularReply->error()!=QModbusDevice::NoError) {
regularReplyError = true;
regularReplyErrorString = QString("%1: %2").arg(regularReply->error()).arg(regularReply->errorString());
}
QModbusResponse regularResp = regularReply->rawResult();
uint8_t numOfRegularObject = regularResp.data().at(5);
QByteArray regularResult = regularResp.data().remove(0, 6);
for(int tmp_obj = 0; tmp_obj < numOfRegularObject; tmp_obj++) {
uint8_t objectID = regularResult.at(0);
if(objectID > 0x06)
continue;
uint8_t lengthOfObject = regularResult.at(1);
if(lengthOfObject > 0)
currentDevice.fields[objectID].clear();
for(int i = 0; i < lengthOfObject; i++)
currentDevice.fields[objectID] += QString(regularResult.at(2 + i));
regularResult.remove(0, lengthOfObject + 2);
}
}
}
if(!isRun) {
devicesList.append(currentDevice);
unsigned newRow = ui->deviceOnlineView->rowCount();
ui->deviceOnlineView->insertRow(newRow);
ui->deviceOnlineView->setItem(newRow, 0, new QTableWidgetItem(QString::number(currentDevice.adr)));
ui->deviceOnlineView->setItem(newRow, 1, new QTableWidgetItem(QString::number(currentDevice.baudRate)));
for(int i = 0; i < 7; i++)
ui->deviceOnlineView->setItem(newRow, i + 2, new QTableWidgetItem(currentDevice.fields[i]));
if(reply->error()!=QModbusDevice::NoError)
ui->deviceOnlineView->setItem(newRow, 9, new QTableWidgetItem(QString("%1: %2").arg(reply->error()).arg(reply->errorString())));
else if(regularReplyError)
ui->deviceOnlineView->setItem(newRow, 9, new QTableWidgetItem(regularReplyErrorString));
ui->deviceOnlineView->resizeColumnsToContents();
syncColumnHeaders();
}
}
}
}
return callStatus::NOERROR;
}
/**
* @brief Обработчик нажатия кнопки "Опросить" для устройства LineRinger.
*
* Этот слот очищает текущий список устройств, обновляет интерфейс, а затем,
* в случае автоматического определения скорости передачи (isAutoBaud),
* последовательно пытается подключиться к устройствам с различными скоростями.
* В процессе поиска отображается прогресс-бар, который можно отменить.
* Если обнаружен вызываемый ответ или возникает ошибка, происходит завершение поиска.
* В случае ручного режима поиска, выполняется однократная попытка сканирования.
*/
void LineRinger::on_ringButton_clicked()
{
ui->deviceOnlineView->clear();
devicesList.clear();
syncColumnHeaders();
while(ui->deviceOnlineView->rowCount() != 0)
ui->deviceOnlineView->removeRow(ui->deviceOnlineView->rowCount() - 1);
if(isAutoBaud) {
auto bar = new QProgressDialog(this);
bar->setLabelText(tr("Поиск устройств... Текущая скорость: %1").arg(currentBaudRate));
bar->setRange(0, ui->baudRateBox->count());
bar->setAutoClose(true);
bar->setMinimumDuration(0);
bar->setCancelButton(nullptr);
connect(bar, &QProgressDialog::canceled, this, [this]() {
emit stopLineCall();
});
bar->setValue(0);
ui->deviceOnlineView->setColumnHidden(1, false);
modbusDevice->disconnectDevice();
for(int i = 0; i < ui->baudRateBox->count(); i++) {
bar->setValue(i + 1);
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
ui->baudRateBox->itemText(i).toInt(nullptr, 10));
if(!modbusDevice->connectDevice()) {
QMessageBox::warning(this, "Ошибка", "Произошла ошибка при попытке подключения.");
on_connectButton_clicked();
break;
}
currentBaudRate = ui->baudRateBox->itemText(i).toUInt(nullptr, 10);
bar->setLabelText(tr("Поиск устройств... Текущая скорость: %1").arg(currentBaudRate));
if(lineCall() == callStatus::INTERRUPT) {
QMessageBox::warning(this, "Уведомление",
QString("Досрочное завершение опроса. Найдено %1 устройств.").arg(devicesList.count()));
modbusDevice->disconnectDevice();
on_connectButton_clicked();
bar->close();
bar->deleteLater();
ui->timer->setTime(QTime::currentTime());
ui->timer->setDate(QDate::currentDate());
return;
}
modbusDevice->disconnectDevice();
}
on_connectButton_clicked();
} else {
ui->deviceOnlineView->setColumnHidden(1, true);
if(lineCall() == callStatus::INTERRUPT)
QMessageBox::warning(this, "Уведомление",
QString("Досрочное завершение опроса. Найдено %1 устройств.").arg(devicesList.count()));
}
ui->timer->setTime(QTime::currentTime());
ui->timer->setDate(QDate::currentDate());
}
void LineRinger::on_checkAutoBaud_stateChanged(int arg1)
{
isAutoBaud = (bool)arg1;
}

48
M3KTE_TERM/lineringer.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef LINERINGER_H
#define LINERINGER_H
#include <QWidget>
#include <QModbusTcpClient>
#include <QModbusRtuSerialMaster>
#include <QSerialPortInfo>
#include <QSerialPort>
#include <QMessageBox>
#include <QProgressDialog>
namespace Ui {
class LineRinger;
}
class LineRinger : public QWidget
{
Q_OBJECT
public:
enum callStatus{
NOERROR = 0,
ERROR = 1,
INTERRUPT = 2
};
explicit LineRinger(QWidget *parent = nullptr);
~LineRinger();
callStatus lineCall();
signals:
void stopLineCall();
private slots:
void on_connectButton_clicked();
void on_ringButton_clicked();
void on_checkAutoBaud_stateChanged(int arg1);
private:
Ui::LineRinger *ui;
void syncColumnHeaders();
struct deviceOnLine{
uint8_t adr;
unsigned baudRate;
QString fields[7] = {"Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined", "Undefined"};
};
QVector<deviceOnLine>devicesList;
bool isAutoBaud = false;
unsigned currentBaudRate;
QModbusClient *modbusDevice = nullptr;
};
#endif // LINERINGER_H

303
M3KTE_TERM/lineringer.ui Normal file
View File

@@ -0,0 +1,303 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LineRinger</class>
<widget class="QWidget" name="LineRinger">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>939</width>
<height>522</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2">
<widget class="QGroupBox" name="gridGroupBox_2">
<layout class="QGridLayout" name="cmdLayout">
<item row="4" column="1">
<widget class="QLabel" name="labelTimer">
<property name="text">
<string>Последний опрос:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="ringButton">
<property name="text">
<string>Опросить</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="checkAutoBaud">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Любая скорость</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QDateTimeEdit" name="timer">
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="keyboardTracking">
<bool>true</bool>
</property>
<property name="currentSection">
<enum>QDateTimeEdit::DaySection</enum>
</property>
<property name="displayFormat">
<string>dd.MM.yyyy HH:mm:ss</string>
</property>
<property name="calendarPopup">
<bool>false</bool>
</property>
<property name="currentSectionIndex">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="controlGroup">
<layout class="QGridLayout" name="controlLaylout">
<item row="0" column="5">
<widget class="QLabel" name="labelStopBit">
<property name="text">
<string>Стоп-биты</string>
</property>
</widget>
</item>
<item row="1" column="8">
<widget class="QPushButton" name="connectButton">
<property name="text">
<string>Подключить</string>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QLabel" name="labelEvenControl">
<property name="text">
<string>Чётность</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QComboBox" name="stopBox"/>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="comBox">
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>21</width>
<height>21</height>
</size>
</property>
<property name="text">
<string>Поиск</string>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QComboBox" name="parityControlBox"/>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="baudRateBox">
<property name="editable">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>9600</string>
</property>
</item>
<item>
<property name="text">
<string>14400</string>
</property>
</item>
<item>
<property name="text">
<string>19200</string>
</property>
</item>
<item>
<property name="text">
<string>31250</string>
</property>
</item>
<item>
<property name="text">
<string>38400</string>
</property>
</item>
<item>
<property name="text">
<string>56000</string>
</property>
</item>
<item>
<property name="text">
<string>57600</string>
</property>
</item>
<item>
<property name="text">
<string>115200</string>
</property>
</item>
</widget>
</item>
<item row="1" column="4">
<widget class="QComboBox" name="dataBox"/>
</item>
<item row="0" column="3">
<widget class="QLabel" name="labelBaud">
<property name="text">
<string>Скорость</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="labelCom">
<property name="text">
<string>Порт</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="labelDataBit">
<property name="text">
<string>Биты данных</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QTableWidget" name="deviceOnlineView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="columnCount">
<number>10</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
<column>
<property name="text">
<string>BaudRate</string>
</property>
</column>
<column>
<property name="text">
<string>Vendor Name</string>
</property>
</column>
<column>
<property name="text">
<string>Product Code</string>
</property>
</column>
<column>
<property name="text">
<string>Major Minor Revision</string>
</property>
</column>
<column>
<property name="text">
<string>Vendor Url</string>
</property>
</column>
<column>
<property name="text">
<string>Product Name</string>
</property>
</column>
<column>
<property name="text">
<string>Model Name</string>
</property>
</column>
<column>
<property name="text">
<string>User Application Name</string>
</property>
</column>
<column>
<property name="text">
<string>Примечание</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

2252
M3KTE_TERM/m3kte.cpp Normal file
View File

@@ -0,0 +1,2252 @@
#include "m3kte.h"
#include "ui_m3kte.h"
#include "settingsdialog.h"
#include "writeregistermodel.h"
#include <QModbusTcpClient>
#include <QModbusRtuSerialMaster>
#include <QStandardItemModel>
#include <QStatusBar>
#include <QUrl>
#include <QScrollBar>
#include <QTableWidget>
#include <QPointer>
#include <QDebug>
#include <QProcess>
#include <QCoreApplication>
#include <QThread>
QWidget* init(QWidget *parent)
{
return new M3KTE(parent);
}
//1024 768
//Ширина колбы - уже
M3KTE::M3KTE(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::M3KTE)
{
ui->setupUi(this);
//Массив указателей на индикаторы напряжения топливных элементов
{
int i = 0;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_1;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_2;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_3;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_4;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_5;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_6;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_7;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_8;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_9;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_10;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_11;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_12;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_13;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_14;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_15;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_16;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_17;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_18;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_19;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_20;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_21;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_22;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_23;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_24;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_25;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_26;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_27;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_28;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_29;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_30;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_31;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_32;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_33;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_34;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_35;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_36;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_37;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_38;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_39;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_40;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_41;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_42;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_43;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_44;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_45;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_46;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_47;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_48;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_49;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_50;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_51;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_52;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_53;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_54;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_55;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_56;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_57;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_58;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_59;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_60;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_61;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_62;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_63;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_64;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_65;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_66;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_67;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_68;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_69;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_70;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_71;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_72;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_73;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_74;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_75;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_76;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_77;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_78;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_79;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_80;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_81;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_82;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_83;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_84;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_85;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_86;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_87;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_88;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_89;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_90;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_91;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_92;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_93;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_94;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_95;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_96;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_97;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_98;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_99;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_100;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_101;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_102;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_103;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_104;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_105;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_106;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_107;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_108;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_109;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_110;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_111;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_112;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_113;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_114;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_115;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_116;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_117;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_118;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_119;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_120;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_121;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_122;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_123;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_124;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_125;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_126;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_127;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_128;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_129;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_130;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_131;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_132;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_133;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_134;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_135;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_136;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_137;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_138;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_139;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_140;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_141;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_142;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_143;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_144;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_145;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_146;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_147;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_148;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_149;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_150;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_151;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_152;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_153;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_154;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_155;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_156;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_157;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_158;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_159;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_160;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_161;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_162;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_163;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_164;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_165;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_166;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_167;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_168;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_169;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_170;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_171;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_172;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_173;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_174;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_175;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_176;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_177;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_178;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_179;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_180;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_181;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_182;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_183;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_184;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_185;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_186;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_187;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_188;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_189;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_190;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_191;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_192;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_193;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_194;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_195;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_196;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_197;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_198;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_199;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_200;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_201;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_202;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_203;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_204;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_205;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_206;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_207;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_208;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_209;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_210;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_211;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_212;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_213;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_214;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_215;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_216;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_217;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_218;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_219;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_220;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_221;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_222;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_223;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_224;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_225;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_226;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_227;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_228;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_229;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_230;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_231;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_232;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_233;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_234;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_235;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_236;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_237;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_238;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_239;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_240;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_241;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_242;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_243;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_244;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_245;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_246;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_247;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_248;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_249;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_250;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_251;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_252;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_253;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_254;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_255;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_256;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_257;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_258;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_259;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_260;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_261;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_262;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_263;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_264;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_265;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_266;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_267;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_268;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_269;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_270;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_271;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_272;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_273;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_274;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_275;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_276;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_277;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_278;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_279;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_280;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_281;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_282;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_283;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_284;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_285;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_286;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_287;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_288;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_289;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_290;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_291;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_292;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_293;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_294;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_295;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_296;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_297;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_298;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_299;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_300;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_301;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_302;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_303;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_304;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_305;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_306;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_307;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_308;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_309;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_310;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_311;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_312;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_313;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_314;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_315;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_316;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_317;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_318;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_319;
m_ProgressBar[i++] = ui->FuelCellVoltageBar_320;
}
m_settingsDialog = new SettingsDialog(this);
ui->writeTable->addItem(tr("Exceptions"), QModbusDataUnit::Coils);
ui->writeTable->addItem(tr("Warnings"), QModbusDataUnit::HoldingRegisters);
ui->writeTable->addItem(tr("Accidents"), QModbusDataUnit::HoldingRegisters);
for(int i = 0; i < 4; i++) {
Boards[i].ModbusModelCoil = new WriteRegisterModel(this, 85 - (i / 3 * 20), false);
Boards[i].ModbusModelCoil->setStartAddress(0);
Boards[i].ModbusModelCoil->setNumberOfValues(QString::number(85 - (i / 3 * 20)));
Boards[i].ModbusModelHoldingReg = new WriteRegisterModel(this, (85 - (i / 3 * 20)) * 2, true);
Boards[i].ModbusModelHoldingReg->setStartAddress(0);
Boards[i].ModbusModelHoldingReg->setNumberOfValues(QString::number(85 - (i / 3 * 20)));
}
m_deviceSettingsDialog = new DeviceSettingsDialog(this);
m_debugTerminalDialog = new DebugTerminalDialog(this);
m_debugTerminalDialog->setMainTerm(this);
m_regMultipleSettings = new MultipleSettings(this);
modbusDevice = new QModbusRtuSerialMaster(this);
m_parameterWorkspace = new ParameterWorkspace();
m_parameterWorkspace->setModbusClient(modbusDevice);
m_debugTerminalDialog->setModbusDevice(modbusDevice);
connect(this, &M3KTE::boardReading, m_debugTerminalDialog, &DebugTerminalDialog::boardDebugReading);
m_lineRinger = new LineRinger();
Boards[0].boardScanners = new QTimer();
Boards[1].boardScanners = new QTimer();
Boards[2].boardScanners = new QTimer();
Boards[3].boardScanners = new QTimer();
Boards[0].boardScanners->setSingleShot(true);
Boards[1].boardScanners->setSingleShot(true);
Boards[2].boardScanners->setSingleShot(true);
Boards[3].boardScanners->setSingleShot(true);
connect(Boards[0].boardScanners, &QTimer::timeout, this, [this]() {boardScan(0);});
connect(Boards[1].boardScanners, &QTimer::timeout, this, [this]() {boardScan(1);});
connect(Boards[2].boardScanners, &QTimer::timeout, this, [this]() {boardScan(2);});
connect(Boards[3].boardScanners, &QTimer::timeout, this, [this]() {boardScan(3);});
{
Boards[0].adr = 1;
Boards[1].adr = 2;
Boards[2].adr = 3;
Boards[3].adr = 4;
}
bool activeBoards[4] = {false, false, false, false};
m_debugTerminalDialog->updateBoardStates(activeBoards);
ui->M3kteRegSettings->setEnabled(false);
ui->BSM_Warning->setEnabled(false);
ui->BSM_Accident->setEnabled(false);
ui->BSM_WorkInProgress->setEnabled(false);
initActions();
ui->BST_On->setCheckable(false);
ui->BST_Off->setChecked(true);
ui->boardSelectBox->setCurrentIndex(0);
ui->writeTable->setCurrentIndex(0);
changeTable(0, 0);
{
Boards_Fields[0] = ui->FCBoardBox;
Boards_Fields[1] = ui->FCBoardBox_2;
Boards_Fields[2] = ui->FCBoardBox_3;
Boards_Fields[3] = ui->FCBoardBox_4;
}
{
Boards[0].timerStatus = ui->timeStatus_1;
Boards[1].timerStatus = ui->timeStatus_2;
Boards[2].timerStatus = ui->timeStatus_3;
Boards[3].timerStatus = ui->timeStatus_4;
}
{
Boards[0].timerData = ui->timeData_1;
Boards[1].timerData = ui->timeData_2;
Boards[2].timerData = ui->timeData_3;
Boards[3].timerData = ui->timeData_4;
}
{
Boards[0].localError = ui->localErrorEdit_1;
Boards[1].localError = ui->localErrorEdit_2;
Boards[2].localError = ui->localErrorEdit_3;
Boards[3].localError = ui->localErrorEdit_4;
}
{
Boards[0].localState[LOCAL_STATE_POLL] = ui->localPollChkBox_1;
Boards[1].localState[LOCAL_STATE_POLL] = ui->localPollChkBox_2;
Boards[2].localState[LOCAL_STATE_POLL] = ui->localPollChkBox_3;
Boards[3].localState[LOCAL_STATE_POLL] = ui->localPollChkBox_4;
Boards[0].localState[LOCAL_STATE_WARN] = ui->localWarnChkBox_1;
Boards[1].localState[LOCAL_STATE_WARN] = ui->localWarnChkBox_2;
Boards[2].localState[LOCAL_STATE_WARN] = ui->localWarnChkBox_3;
Boards[3].localState[LOCAL_STATE_WARN] = ui->localWarnChkBox_4;
Boards[0].localState[LOCAL_STATE_ERR] = ui->localErrChkBox_1;
Boards[1].localState[LOCAL_STATE_ERR] = ui->localErrChkBox_2;
Boards[2].localState[LOCAL_STATE_ERR] = ui->localErrChkBox_3;
Boards[3].localState[LOCAL_STATE_ERR] = ui->localErrChkBox_4;
}
{ // не кликабельные чекбоксы и радиобоксы
for(int i = 0; i < 4; i++) {
connect(Boards[i].localState[LOCAL_STATE_POLL], &QCheckBox::clicked,
this, [i, this](bool checked) {m_deviceSettingsDialog->sendPollCommand(i, checked);});
Boards[i].localState[LOCAL_STATE_WARN]->setAttribute(Qt::WA_TransparentForMouseEvents, true);
Boards[i].localState[LOCAL_STATE_ERR]->setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
ui->BSM_Warning->setAttribute(Qt::WA_TransparentForMouseEvents, true);
ui->BSM_Accident->setAttribute(Qt::WA_TransparentForMouseEvents, true);
ui->BSM_WorkInProgress->setAttribute(Qt::WA_TransparentForMouseEvents, true);
ui->BST_On->setAttribute(Qt::WA_TransparentForMouseEvents, true);
ui->BST_Off->setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
for(int i = 0; i < 4; i++) {
statusM3KTE.Warnings[i] = false;
statusM3KTE.Accidents[i] = false;
Boards_Fields[i]->setEnabled(false);
Boards[i].timerData->setText(" ");
Boards[i].timerStatus->setText(" ");;
Boards[i].localError->setText(" ");;
}
for(int i = 0; i < 5; i++)
ui->writeValueTable->resizeColumnToContents(i);
QBrush tb(Qt::transparent); // Transparent brush, solid pattern
for(int i = 0; i < 320; i++) {
m_ProgressBar[i]->setTextVisible(true);
m_ProgressBar[i]->setMinimumSize(25, 25);
m_ProgressBar[i]->setMaximumSize(25, 25);
m_ProgressBar[i]->resize(25, 25);
m_ProgressBar[i]->setAlignment(Qt::AlignCenter);
m_ProgressBar[i]->setFormat(QString("%1").arg((i % 85 + 1)));
m_ProgressBar[i]->setValue(3);
QString style_fc_off = "QProgressBar {border: 2px solid black; font: bold 10px} QProgressBar::chunk {background: hsva(" + QString::number(30) + ", 30, 30, 30%);} ";
m_ProgressBar[i]->setStyleSheet(style_fc_off);
ThePhantomMenace[i] = new QPushButton(m_ProgressBar[i]);
ThePhantomMenace[i]->setFlat(true);
ThePhantomMenace[i]->setPalette(QPalette(tb, tb, tb, tb, tb, tb, tb, tb, tb));
connect(ThePhantomMenace[i], &QPushButton::clicked, this, [this, i]() {
selectPositionOnTree(i);
m_debugTerminalDialog->writeTENumber(i / 85, i - (i / 85) * 85 + 1);
});
}
connect(m_deviceSettingsDialog, &DeviceSettingsDialog::parityChanged, this, &M3KTE::onParityUpdate);
connect(m_deviceSettingsDialog, &DeviceSettingsDialog::speedChanged, this, &M3KTE::onSpeedUpdate);
loggerTable = new QTableWidget(ui->loggerWidget);
ui->loggerWidget->layout()->addWidget(loggerTable);
loggerTable->setColumnCount(5);
QStringList headers;
headers << "Время" << "Плата" << "Тип ошибки" << "Счётчик" << "Примечание";
loggerTable->setHorizontalHeaderLabels(headers);
loggerTable->setSortingEnabled(true);
loggerTable->setAutoScroll(true);
}
M3KTE::~M3KTE()
{
if(modbusDevice->state() == QModbusDevice::ConnectedState)
onConnectClicked();
delete ui;
}
/**
* @brief Инициализация действий и сигналов интерфейса пользователя.
*
* Этот метод настраивает начальные состояния элементов интерфейса и соединяет
* сигналы пользовательских действий с соответствующими слотами для обработки.
*/
void M3KTE::initActions()
{
ui->ConnectionMenuConnect->setEnabled(true);
ui->ConnectionMenuDisconnect->setEnabled(false);
ui->ConnectionMenuSettings->setEnabled(true);
connect(ui->ConnectionMenuConnect, &QAction::triggered,
this, &M3KTE::onConnectClicked);
connect(ui->ConnectionMenuDisconnect, &QAction::triggered,
this, &M3KTE::onConnectClicked);
connect(ui->readButton, &QPushButton::clicked,
this, &M3KTE::onReadButtonClicked);
connect(ui->writeButton, &QPushButton::clicked,
this, &M3KTE::onWriteButtonClicked);
connect(ui->clearLoggerBtn, &QPushButton::clicked,
this, &M3KTE::clearLogger);
connect(ui->boardSelectBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &M3KTE::onSelectedBoardChanged);
connect(ui->writeTable, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &M3KTE::onWriteTableChanged);
connect(ui->DebugTerm, &QAction::triggered, m_debugTerminalDialog, &QWidget::show);
connect(ui->OpenADCBuff, &QAction::triggered, this, [this]() {
m_debugTerminalDialog->setDebugTerminalCoil(1);
m_debugTerminalDialog->openAdc(0, 1);
});
connect(m_debugTerminalDialog, &DebugTerminalDialog::coilValueChanged,
this, &M3KTE::writeSingleCoil);
connect(m_debugTerminalDialog, &DebugTerminalDialog::writeRegister,
this, &M3KTE::writeSingleRegister);
connect(ui->LineCall, &QAction::triggered, m_lineRinger, &QWidget::show);
connect(ui->M3kteRegSettings, &QAction::triggered, m_regMultipleSettings, &QDialog::show);
connect(m_regMultipleSettings, &MultipleSettings::write, this, &M3KTE::slotmultipleRegWrite);
connect(m_regMultipleSettings, &MultipleSettings::writeAndSend, this, &M3KTE::slotmultipleRegWriteAndSend);
connect(ui->ConnectionMenuSettings, &QAction::triggered, m_settingsDialog, &QDialog::show);
connect(ui->M3kteMenuSettings, &QAction::triggered, m_deviceSettingsDialog, &QDialog::show);
connect(ui->ParameterScan, &QAction::triggered, m_parameterWorkspace, &QWidget::show);
}
/**
* @brief Записывает сообщение об ошибке в таблицу логирования.
*
* Этот метод добавляет новую строку в таблицу loggerTable с информацией об ошибке,
* включая время, место возникновения, описание ошибки, счётчик ошибок и дополнительную информацию.
*
* @param errorPlace Строка, указывающая место возникновения ошибки.
* @param errorString Строка с описанием самой ошибки.
* @param errorCount Общее число ошибок этого типа (или другое релевантное значение).
* @param description Дополнительное описание ошибки.
*/
void M3KTE::logError(const QString &errorPlace, const QString &errorString, unsigned errorCount, const QString &description)
{
unsigned newRow = loggerTable->rowCount();
loggerTable->insertRow(newRow);
loggerTable->setItem(newRow, 0, new QTableWidgetItem(QTime::currentTime().toString("HH:mm:ss")));
loggerTable->setItem(newRow, 1, new QTableWidgetItem(errorPlace));
loggerTable->setItem(newRow, 2, new QTableWidgetItem(errorString));
loggerTable->setItem(newRow, 3, new QTableWidgetItem(QString::number(errorCount)));
loggerTable->setItem(newRow, 4, new QTableWidgetItem(description));
loggerTable->resizeColumnsToContents();
if(!loggerTable->verticalScrollBar()->isSliderDown())
loggerTable->verticalScrollBar()->setSliderPosition(loggerTable->verticalScrollBar()->maximum());
}
/**
* @brief Очищает содержимое таблицы логирования.
*
* Этот метод удаляет все строки из таблицы loggerTable. Перед удалением
* обновление пользовательского интерфейса временно отключается для повышения производительности.
* Если таблица не инициализирована, метод просто завершает работу.
*/
void M3KTE::clearLogger()
{
// Проверяем, что таблица инициализирована
if(!loggerTable)
return;
// Отключаем обновление UI для повышения производительности
loggerTable->setUpdatesEnabled(false);
// Очищаем все строки таблицы
loggerTable->setRowCount(0);
// Включаем обновление UI
loggerTable->setUpdatesEnabled(true);
}
/**
* @brief Обработчик нажатия кнопки "Подключиться".
*
* Эта функция устанавливает соединение с устройством Modbus, если оно не подключено,
* или разрывает соединение, если уже подключено.
* В процессе подключения происходит настройка параметров соединения (порт, параллельность,
* скорость передачи и др.), а также опрос устройств для определения их состояния.
* В случае успешного подключения включаются соответствующие элементы интерфейса и
* происходит подготовка данных для дальнейшей работы.
* В случае разрыва соединения происходит сброс настроек интерфейса и отключение устройств.
*
* Основные действия:
* - Проверка актуальности объекта modbusDevice.
* - Настройка параметров соединения и попытка подключения.
* - В случае успеха — обновление интерфейса и активация элементов.
* - В случае неудачи — отображение сообщения об ошибке.
* - При уже активном соединении — отключение устройства, сброс данных и интерфейса.
*/
void M3KTE::onConnectClicked()
{
if(!modbusDevice)
return;
statusBar()->clearMessage();
if(modbusDevice->state() != QModbusDevice::ConnectedState) {
modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
m_settingsDialog->settings().portName);
#if QT_CONFIG(modbus_serialport)
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
m_settingsDialog->settings().parity);
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
m_settingsDialog->settings().baud);
modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
m_settingsDialog->settings().dataBits);
modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
m_settingsDialog->settings().stopBits);
#endif
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries);
if(!modbusDevice->connectDevice())
statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000);
else {
ui->ConnectionMenuConnect->setEnabled(false);
ui->ConnectionMenuDisconnect->setEnabled(true);
//Опрос устройст
if(pingNetworkDevices()) {
unsigned tmp_adr[4];
bool ActiveDevices[4];
for(int i = 0; i < 4; i++) {
tmp_adr[i] = Boards[i].adr;
ActiveDevices[i] = Boards[i].isActive;
}
ui->M3kteRegSettings->setEnabled(true);
m_deviceSettingsDialog->updateSettingsAfterConnection(m_settingsDialog->settings().baud,
m_settingsDialog->settings().parity,
tmp_adr,
ActiveDevices);
m_debugTerminalDialog->updateBoardStates(ActiveDevices);
ui->boardSelectBox->setCurrentIndex(0);
ui->writeTable->setCurrentIndex(0);
changeTable(0, 0);
ui->M3kteMenuSettings->setEnabled(true);
ui->M3kteRegSettings->setEnabled(true);
ui->BST_Off->setCheckable(false);
ui->BST_Off->setChecked(false);
ui->BST_On->setCheckable(true);
ui->BST_On->setChecked(true);
ui->BSM_Warning->setEnabled(true);
ui->BSM_Accident->setEnabled(true);
ui->BSM_WorkInProgress->setEnabled(true);
ui->BSM_WorkInProgress->setChecked(true);
ui->ParameterScan->setEnabled(true);
}
}
} else {
QString style_fc_off = "QProgressBar {border: 2px solid black; font: bold 10px} QProgressBar::chunk {background: hsva(" + QString::number(30) + ", 30, 30, 30%);} ";
for(int i = 0; i < 4; i++) {
Boards[i].boardScanners->stop();
Boards_Fields[i]->setEnabled(false);
Boards[i].isActive=false;
for(int j = 0; j < (85 - (i / 3 * 20)); j++) {
Boards[i].coil[j] = false;
Boards[i].ModbusModelCoil->m_coils[j] = 0;
Boards[i].ModbusModelHoldingReg->m_currentU[j] = 0;
Boards[i].ModbusModelHoldingReg->m_holdingRegisters[j] = 0;
Boards[i].ModbusModelHoldingReg->m_holdingRegisters[j + (85 - (i / 3 * 20))] = 0;
}
Boards_Fields[i]->setTitle(QString("Плата №%1").arg(i + 1));
Boards[i].timerData->setText(" ");;
Boards[i].timerStatus->setText(" ");;
Boards[i].localError->setText(" ");;
}
for(int i = 0; i < 320; i++) {
m_ProgressBar[i]->setStatusTip(QString("П%1 ТЭ%2: Топливный Элемент не учитывается.").arg(QString::number(i / 85 + 1), QString::number(i % 85)));
m_ProgressBar[i]->setWhatsThis(QString("П%1 ТЭ%2: Топливный Элемент не учитывается.").arg(QString::number(i / 85 + 1), QString::number(i % 85)));
m_ProgressBar[i]->setValue(3);
m_ProgressBar[i]->setStyleSheet(style_fc_off);
}
modbusDevice->disconnectDevice();
ui->M3kteMenuSettings->setEnabled(false);
ui->M3kteRegSettings->setEnabled(false);
ui->ConnectionMenuConnect->setEnabled(true);
ui->ConnectionMenuDisconnect->setEnabled(false);
ui->BST_Off->setCheckable(true);
ui->BST_Off->setChecked(true);
ui->BST_On->setCheckable(false);
ui->BST_On->setChecked(false);
ui->BSM_Warning->setChecked(false);
ui->BSM_Accident->setChecked(false);
ui->BSM_WorkInProgress->setChecked(false);
ui->BSM_Warning->setEnabled(false);
ui->BSM_Accident->setEnabled(false);
ui->BSM_WorkInProgress->setEnabled(false);
ui->M3kteRegSettings->setEnabled(false);
ui->ParameterScan->setEnabled(false);
m_deviceSettingsDialog->onDisconnect();
m_debugTerminalDialog->offAllBoard();
}
}
/**
* @brief Обработчик нажатия кнопки чтения.
*
* Этот слот вызывается при нажатии кнопки для чтения данных по протоколу Modbus.
* В случае успешного создания запроса, подключает обработчик завершения запроса.
* Если произошла ошибка при отправке запроса, выводит сообщение об ошибке в статус-бар.
*/
void M3KTE::onReadButtonClicked()
{
if(!modbusDevice)
return;
statusBar()->clearMessage();
if(auto *reply = modbusDevice->sendReadRequest(readRequest(), Boards[ui->boardSelectBox->currentIndex()].adr)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, &M3KTE::onReadReady);
else
delete reply; // broadcast replies return immediately
} else
statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
}
/**
* @brief Обрабатывает завершение ответа на запрос чтения Modbus.
*
* Этот слот вызывается, когда ответ на запрос Modbus готов. Он получает указатель
* на объект QModbusReply, обрабатывает успешный ответ, обновляет состояние устройств
* и модели данных, а также выводит сообщения об ошибках при необходимости.
*/
void M3KTE::onReadReady()
{
auto reply = qobject_cast<QModbusReply *>(sender());
if(!reply)
return;
int Adr = 255;
for(int i = 0; i < 4; i++)
if(Boards[i].isActive && Boards[i].adr == reply->serverAddress()) {
Adr = i;
break;
}
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
for(int i = 0, total = int(unit.valueCount()); i < total; ++i)
if(unit.registerType() == QModbusDataUnit::Coils) {
Boards[Adr].coil[i + unit.startAddress()] = unit.value(i);
if(unit.value(i) == 1)
Boards[Adr].ModbusModelCoil->setData(ui->writeValueTable->model()->index(i + unit.startAddress(), 2), Qt::Checked, Qt::CheckStateRole);
else
Boards[Adr].ModbusModelCoil->setData(ui->writeValueTable->model()->index(i + unit.startAddress(), 2), Qt::Unchecked, Qt::CheckStateRole);
} else if(unit.registerType() == QModbusDataUnit::HoldingRegisters) {
Boards[Adr].HR[i + unit.startAddress()] = unit.value(i);
Boards[Adr].ModbusModelHoldingReg->setData(ui->writeValueTable->model()->index(i + unit.startAddress(), 3), QString::number(unit.value(i), 10), Qt::EditRole);
}
switch(unit.registerType()) {
case QModbusDataUnit::Coils:
Boards[Adr].ModbusModelCoil->dataChanged(ui->writeValueTable->model()->index(unit.startAddress(), 2),
ui->writeValueTable->model()->index(unit.startAddress() + unit.valueCount() -1, 2));
break;
case QModbusDataUnit::HoldingRegisters:
Boards[Adr].ModbusModelHoldingReg->dataChanged(ui->writeValueTable->model()->index(unit.startAddress(), 3),
ui->writeValueTable->model()->index(unit.startAddress() + unit.valueCount() -1, 3));
break;
default:
break;
}
} else if(reply->error() == QModbusDevice::ProtocolError) {
statusBar()->showMessage(tr("Read response error: %1 (Mobus exception: 0x%2)").
arg(reply->errorString()).
arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(Adr + 1).arg(Boards[Adr].adr),
reply->errorString(), ++Boards[Adr].error_RX,
QString::number(reply->rawResult().exceptionCode()));
} else {
statusBar()->showMessage(tr("Read response error: %1 (code: 0x%2)").
arg(reply->errorString()).
arg(reply->error(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(Adr + 1).arg(Boards[Adr].adr),
reply->errorString(), ++Boards[Adr].error_RX,
QString::number(reply->error(), 16));
}
reply->deleteLater();
}
/**
* @brief Обрабатывает событие нажатия кнопки записи.
*
* Этот слот собирает данные из моделей, формирует запрос на запись по протоколу Modbus
* и отправляет его на устройство. Обрабатывает завершение запроса и отображает
* сообщения об ошибках при необходимости.
*/
void M3KTE::onWriteButtonClicked()
{
if(!modbusDevice)
return;
statusBar()->clearMessage();
QModbusDataUnit writeUnit = writeRequest();
QModbusDataUnit::RegisterType table = writeUnit.registerType();
for(int i = 0, total = int(writeUnit.valueCount()); i < total; ++i) {
if(table == QModbusDataUnit::Coils) {
Boards[ui->boardSelectBox->currentIndex()].coil[i + writeUnit.startAddress()] =
Boards[ui->boardSelectBox->currentIndex()].ModbusModelCoil->m_coils[i + writeUnit.startAddress()];
writeUnit.setValue(i, Boards[ui->boardSelectBox->currentIndex()].ModbusModelCoil->m_coils[i + writeUnit.startAddress()]);
} else {
Boards[ui->boardSelectBox->currentIndex()].HR[i + writeUnit.startAddress()] =
Boards[ui->boardSelectBox->currentIndex()].ModbusModelHoldingReg->m_holdingRegisters[i + writeUnit.startAddress()];
writeUnit.setValue(i, Boards[ui->boardSelectBox->currentIndex()].ModbusModelHoldingReg->m_holdingRegisters[i + writeUnit.startAddress()]);
}
}
if(auto *reply = modbusDevice->sendWriteRequest(writeUnit, Boards[ui->boardSelectBox->currentIndex()].adr)) {
if(!reply->isFinished()) {
unsigned tmp_id = ui->boardSelectBox->currentIndex();
connect(reply, &QModbusReply::finished, this, [this, reply, tmp_id]() {
if(reply->error() == QModbusDevice::ProtocolError) {
statusBar()->showMessage(tr("Write response error: %1 (Mobus exception: 0x%2)")
.arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(tmp_id + 1).arg(Boards[tmp_id].adr),
reply->errorString(), ++Boards[tmp_id].error_TX,
QString::number(reply->rawResult().exceptionCode(), 16));
} else if(reply->error() != QModbusDevice::NoError) {
statusBar()->showMessage(tr("Write response error: %1 (code: 0x%2)").
arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(tmp_id + 1).arg(Boards[tmp_id].adr),
reply->errorString(), ++Boards[tmp_id].error_TX,
QString::number(reply->error()));
}
reply->deleteLater();
});
} else
// broadcast replies return immediately
reply->deleteLater();
} else {
statusBar()->showMessage(tr("Write error: ") + modbusDevice->errorString(), 5000);
logError(tr("Терминал"), modbusDevice->errorString(), 0, "");
}
}
/**
* @brief Обрабатывает изменение выбранной платы.
*
* Этот метод вызывается при изменении выбора платы пользователем.
* Он вызывает changeTable с текущими индексами платы и таблицы.
*
* @param index Индекс выбранной платы.
*/
void M3KTE::onSelectedBoardChanged(int index)
{
changeTable(index, ui->writeTable->currentIndex());
}
/**
* @brief Обрабатывает изменение выбранной таблицы.
*
* Этот метод вызывается при переключении таблицы пользователем.
* Он вызывает changeTable с текущими индексами выбранной платы и таблицы.
*
* @param index Индекс выбранной таблицы.
*/
void M3KTE::onWriteTableChanged(int index)
{
changeTable(ui->boardSelectBox->currentIndex(), index);
}
/**
* @brief Частично меняет отображаемую таблицу в зависимости от типа.
*
* В зависимости от типа таблицы (например, "Реле" или "Регистры") устанавливает
* соответствующую модель данных, скрывает или показывает строки и колонки,
* а также подгоняет ширину колонок.
*
* @param board Индекс выбранной платы.
* @param tabletype Тип таблицы (0 — Coil, 1 или другой — Holding Registers).
*/
void M3KTE::changeTable(int board, int tabletype)
{
if(tabletype == 0) {
ui->writeValueTable->setModel(Boards[board].ModbusModelCoil);
int i = 0;
for(; i < Boards[board].ModbusModelHoldingReg->rowCount(); i++)
ui->writeValueTable->setRowHidden(i, ui->writeValueTable->model()->index(i, 0).parent(), false);
ui->writeValueTable->hideColumn(3);
ui->writeValueTable->showColumn(2);
} else {
ui->writeValueTable->setModel(Boards[board].ModbusModelHoldingReg);
if(tabletype == 1) {
Boards[board].ModbusModelHoldingReg->setStartAddress(0);
int i = 0;
for(; i < Boards[board].ModbusModelHoldingReg->rowCount() / 2; i++)
ui->writeValueTable->setRowHidden(i, ui->writeValueTable->model()->index(i, 0).parent(), false);
for(; i < Boards[board].ModbusModelHoldingReg->rowCount(); i++)
ui->writeValueTable->setRowHidden(i, ui->writeValueTable->model()->index(i, 0).parent(), true);
} else {
Boards[board].ModbusModelHoldingReg->setStartAddress(Boards[board].ModbusModelHoldingReg->rowCount() / 2);
int i = 0;
for(; i < Boards[board].ModbusModelHoldingReg->rowCount() / 2; i++)
ui->writeValueTable->setRowHidden(i, ui->writeValueTable->model()->index(i, 0).parent(), true);
for(; i < Boards[board].ModbusModelHoldingReg->rowCount(); i++)
ui->writeValueTable->setRowHidden(i, ui->writeValueTable->model()->index(i, 0).parent(), false);
}
ui->writeValueTable->hideColumn(2);
ui->writeValueTable->showColumn(3);
}
for(int i = 0; i < 5; i++)
ui->writeValueTable->resizeColumnToContents(i);
}
/**
* @brief Создает и возвращает объект запроса чтения для Modbus.
*
* Этот метод формирует объект QModbusDataUnit в соответствии с текущими настройками интерфейса:
* - определяет тип данных (коэли или регистры) в зависимости от выбранной таблицы;
* - вычисляет стартовый адрес на основе текущего индекса таблицы;
* - определяет число записей для чтения, ограниченное диапазоном доступных данных.
*
* @return Объект QModbusDataUnit, готовый для отправки запроса чтения.
*/
QModbusDataUnit M3KTE::readRequest() const
{
const auto table =
static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
int startAddress = 85 * (ui->writeTable->currentIndex() / 2);
Q_ASSERT(startAddress >= 0 && startAddress < 340);
quint16 numberOfEntries = qMin((ushort)(85 - (ui->boardSelectBox->currentIndex() / 3 * 20)), quint16(340 - startAddress));
return QModbusDataUnit(table, startAddress, numberOfEntries);
}
/**
* @brief Создает и возвращает объект запроса записи для Modbus.
*
* Этот метод формирует объект QModbusDataUnit для отправки данных на устройство,
* основываясь на текущих настройках интерфейса:
* - определяет тип данных (например, кони или регистры) в зависимости от выбранной таблицы;
* - вычисляет стартовый адрес для записи, исходя из текущего индекса таблицы;
* - задает количество записей для записи, учитывая ограничение диапазона данных.
*
* @return Объект QModbusDataUnit, подготовленный для отправки запроса записи.
*/
QModbusDataUnit M3KTE::writeRequest() const
{
const auto table =
static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
int startAddress = 85 * (ui->writeTable->currentIndex() / 2);
Q_ASSERT(startAddress >= 0 && startAddress < 340);
quint16 numberOfEntries = qMin((ushort)(85 - (ui->boardSelectBox->currentIndex() / 3 * 20)), quint16(340 - startAddress));
return QModbusDataUnit(table, startAddress, numberOfEntries);
}
/**
* @brief Обрабатывает пользовательские события, связанные с изменением ID платы и её статуса.
*
* Метод переопределяет обработку событий QWidget. В зависимости от типа события:
* - При `QEvent::User` выполняется изменение ID платы через Modbus и последующая проверка.
* - При типе 1001 (кастомный тип) происходит обновление состояния платы.
*
* Логируются ошибки при неудачных операциях, осуществляется управление асинхронными запросами Modbus.
*
* @param event Указатель на событие
* @return true, если событие обработано; иначе вызывает базовый обработчик
*/
bool M3KTE::event(QEvent *event)
{
if(event->type() == QEvent::User) {
// Обработка пользовательского события изменения ID платы
auto _event = static_cast<BoardIdHasBeenChanged*>(event);
// Создаем запрос на запись нового ID в регистр
auto *_unit = new QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 172, 1);
_unit->setValue(0, _event->BoardNewID());
// Обновляем локально временный адрес платы
Boards[_event->BoardNum()]._tmp_adr = _event->BoardNewID();
// Отправка запроса на изменение адреса через Modbus
if(auto *reply = modbusDevice->sendWriteRequest(*_unit, Boards[_event->BoardNum()].adr)) {
if(!reply->isFinished()) {
// Обработка завершения ответа через сигнал finished
connect(reply, &QModbusReply::finished, this, [reply, this, _event, _unit]() {
if(reply->error() == QModbusDevice::TimeoutError) {
// В случае тайм-аута, отправляем запрос на чтение адреса
if(auto *subreply = modbusDevice->sendReadRequest(*_unit, Boards[_event->BoardNum()]._tmp_adr)) {
if(!subreply->isFinished()) {
connect(subreply, &QModbusReply::finished, this, [subreply, this, _event]() {
checkAdrChange(subreply, _event->BoardNum());
});
} else {
// Обработка ошибки чтения адреса
logError(tr("Плата %1 (ID %2)").arg(_event->BoardNum() + 1).arg(Boards[_event->BoardNum()].adr),
subreply->errorString(), ++Boards[_event->BoardNum()].error_adr_change,
"Не удалось изменить адрес устройства. [1]");
reply->deleteLater();
delete subreply;
}
} else {
// Ошибка при создании запроса на чтение
logError(tr("Плата %1 (ID %2)").arg(_event->BoardNum() + 1).arg(Boards[_event->BoardNum()].adr),
modbusDevice->errorString(), ++Boards[_event->BoardNum()].error_adr_change,
"Не удалось изменить адрес устройства. [2]");
reply->deleteLater();
}
} else {
// Ошибка при ответе на запись
logError(tr("Плата %1 (ID %2)").arg(_event->BoardNum() + 1).arg(Boards[_event->BoardNum()].adr),
reply->errorString(), ++Boards[_event->BoardNum()].error_adr_change,
"Не удалось изменить адрес устройства. [3]");
reply->deleteLater();
}
});
} else {
// Запрос завершился сразу — ошибка
logError(tr("Плата %1 (ID %2)").arg(_event->BoardNum() + 1).arg(Boards[_event->BoardNum()].adr),
reply->errorString(), ++Boards[_event->BoardNum()].error_adr_change,
"Не удалось изменить адрес устройства. [4]");
reply->deleteLater();
delete reply;
}
} else {
// Не удалось отправить запрос
logError(tr("Плата %1 (ID %2)").arg(_event->BoardNum() + 1).arg(Boards[_event->BoardNum()].adr),
modbusDevice->errorString(), ++Boards[_event->BoardNum()].error_adr_change,
"Не удалось изменить адрес устройства. [5]");
reply->deleteLater();
}
m_deviceSettingsDialog->show();
return true;
} else if(event->type() == (QEvent::Type)1001) {
// Обработка смены статуса плат
auto _event = static_cast<pollStatusChange*>(event);
auto *_unit = new QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 170, 1);
_unit->setValue(0, _event->Status());
// Отправка команды на изменение статуса
if(auto *reply = modbusDevice->sendWriteRequest(*_unit, Boards[_event->BoardID()].adr)) {
if(!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [reply, this, _event]() {
if(reply->error() != QModbusDevice::NoError) {
// Логирование ошибок
logError(tr("Плата %1 (ID %2)").arg(_event->BoardID() + 1).arg(Boards[_event->BoardID()].adr),
reply->errorString(), ++Boards[_event->BoardID()].error_cmd_change, "");
reply->deleteLater();
}
});
}
} else {
// Не удалось отправить команду
logError(tr("Плата %1 (ID %2)").arg(_event->BoardID() + 1).arg(Boards[_event->BoardID()].adr),
modbusDevice->errorString(), ++Boards[_event->BoardID()].error_cmd_change, "");
reply->deleteLater();
}
return true;
}
// В случаене распознанного события вызываем дефолтный обработчик
return QWidget::event(event);
}
/**
* @brief Проверяет результат изменения адреса устройства после Modbus-запроса.
*
* Анализирует ответ `reply`, обновляет текущий адрес в структуре `Boards`
* при успешной проверке. В случае ошибки логирует сообщение и увеличивает счетчик ошибок.
*
* @param reply Указатель на объект QModbusReply, содержащий ответ на запрос изменения адреса.
* @param boardNum Индекс устройства в массиве `Boards`.
*/
void M3KTE::checkAdrChange(QModbusReply *reply, unsigned boardNum)
{
if(!reply) {
logError(tr("Плата %1 (ID %2)").arg(boardNum + 1).arg(Boards[boardNum].adr),
modbusDevice->errorString(), ++Boards[boardNum].error_adr_change,
"Не удалось проверить изменение адреса устройства.");
reply->deleteLater();
return;
}
if(reply->error() == QModbusDevice::NoError) {
//OK
Boards[boardNum].adr = Boards[boardNum]._tmp_adr;
reply->deleteLater();
} else {
logError(tr("Плата %1 (ID %2)").arg(boardNum + 1).arg(Boards[boardNum].adr),
reply->errorString(), ++Boards[boardNum].error_adr_change,
"Ошибка при подтверждении изменения адреса устройства.");
reply->deleteLater();
}
}
/**
* @brief Проверяет состояние всех активных плат через Modbus.
*
* Этот метод инициирует последовательные асинхронные чтения входных регистров (адрес 85)
* для всех активных плат, используя `QSharedPointer` для безопасного управления счётчиками и наборами.
* После завершения всех запросов генерируются сигналы:
* - `successAtCheckBoards()`, если все платы успешно подтвердили состояние,
* - `errorAtCheckBoards()`, если произошла ошибка или не все платы подтвердили.
*
* В случае неуспешной отправки запроса по плате, вызывается сигнал `errorAtCheckBoards()`,
* и выполнение метода завершается.
*
* Если активных плат не обнаружено, также генерируется сигнал `errorAtCheckBoards()`.
*/
void M3KTE::checkBoards()
{
QModbusDataUnit unitCheck(QModbusDataUnit::InputRegisters, 85, 1);
// Используем shared pointers вместо ссылок на стековые переменные
auto totalActiveBoards = QSharedPointer<int>::create(0);
auto confirmedBoards = QSharedPointer<int>::create(0);
auto pendingBoards = QSharedPointer<QSet<int>>::create();
for(int i = 0; i < 4; ++i) {
if(!Boards[i].isActive)
continue;
(*totalActiveBoards)++;
int slaveAddress = Boards[i].adr;
QModbusReply *reply = modbusDevice->sendReadRequest(unitCheck, slaveAddress);
if(!reply) {
emit errorAtCheckBoards();
return;
}
pendingBoards->insert(slaveAddress);
connect(reply, &QModbusReply::finished, this,
[this, i, reply, slaveAddress, totalActiveBoards, confirmedBoards, pendingBoards]() {
if(reply->error() == QModbusDevice::NoError)
(*confirmedBoards)++;
else
logError(tr("Плата %1 (ID %2)").arg(i + 1).arg(slaveAddress),
reply->errorString(), ++Boards[i].error_baud_change,
"Ошибка при подтверждении изменения скорости обмена.");
pendingBoards->remove(slaveAddress);
reply->deleteLater();
if(pendingBoards->isEmpty()) {
if(*confirmedBoards != *totalActiveBoards)
emit errorAtCheckBoards();
else
emit successAtCheckBoards();
}
});
}
// Если нет ни одной активной платы
if(*totalActiveBoards == 0)
emit errorAtCheckBoards();
}
/**
* @brief Обновляет скорость обмена данными и проверяет ее подтверждение для устройств.
*
* Этот метод выполняет изменение скорости обмена, отправляя команды через Modbus,
* и после подтверждения повторно подключается с новой скоростью или восстанавливает предыдущие настройки при ошибках.
*
* Основные шаги:
* 1. Останавливает сканирование плат.
* 2. Устанавливает таймаут для модбас-устройства.
* 3. Определяет новую скорость исходя из текущих настроек.
* 4. Проверяет допустимость выбранной скорости.
* 5. Отправляет команды записи скорости на все активные платы.
* 6. После подтверждения всех плат — обновляет настройки соединения и перезапускает сканирование.
* 7. В случае ошибок — восстанавливает предыдущую скорость и перезапускает скан.
*
* Для синхронизации используем `QSharedPointer` для счетчиков активных и подтвержденных плат,
* а для списков активных плат — `QSharedPointer<QSet<int>>`.
*
* Лямбда `processResult` вызывается после обработки всех плат, чтобы завершить или повторить операцию в случае ошибок.
*/
void M3KTE::onSpeedUpdate()
{
stopScanBoard();
modbusDevice->setTimeout(500);
unsigned tmp_speed = 0;
switch(m_deviceSettingsDialog->currentSpeed()) {
case 0: tmp_speed = 9600; break;
case 1: tmp_speed = 14400; break;
case 2: tmp_speed = 19200; break;
case 3: tmp_speed = 31250; break;
case 4: tmp_speed = 38400; break;
case 5: tmp_speed = 56000; break;
case 6: tmp_speed = 57600; break;
case 7: tmp_speed = 115200; break;
}
if(tmp_speed == 0) {
logError(tr("Программная ошибка"), "Неожиданное значение скорости", 0,
"Ошибка при изменении скорости обмена.");
beginScanBoards();
return;
}
// Используем shared pointers вместо ссылок на стековые переменные
auto totalActiveBoards = QSharedPointer<int>::create(0);
auto confirmedBoards = QSharedPointer<int>::create(0);
auto pendingBoards = QSharedPointer<QSet<int>>::create();
auto newSpeed = tmp_speed; // копируем для захвата
QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 173, 1);
unit.setValue(0, m_deviceSettingsDialog->currentSpeed());
// Лямбда для обработки результата
auto processResult = [this, totalActiveBoards, confirmedBoards, newSpeed]() {
if(*confirmedBoards != *totalActiveBoards) {
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
revertToOldSpeedAndRestart();
beginScanBoards();
} else {
modbusDevice->disconnectDevice();
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, newSpeed);
modbusDevice->connectDevice();
// Используем QPointer для безопасного доступа к this
QPointer<M3KTE> safeThis(this);
connect(this, &M3KTE::errorAtCheckBoards, this, [safeThis]() {
if(!safeThis)
return;
safeThis->disconnect(safeThis, &M3KTE::errorAtCheckBoards, safeThis, nullptr);
safeThis->modbusDevice->setTimeout(safeThis->m_settingsDialog->settings().responseTime);
safeThis->revertToOldSpeedAndRestart();
safeThis->beginScanBoards();
});
connect(this, &M3KTE::successAtCheckBoards, this, [safeThis]() {
if(!safeThis)
return;
safeThis->disconnect(safeThis, &M3KTE::successAtCheckBoards, safeThis, nullptr);
safeThis->m_settingsDialog->UpdateBaud(safeThis->m_deviceSettingsDialog->currentSpeed());
safeThis->modbusDevice->setTimeout(safeThis->m_settingsDialog->settings().responseTime);
safeThis->beginScanBoards();
});
checkBoards();
}
};
for(int i = 0; i < 4; i++) {
if(!Boards[i].isActive)
continue;
int slaveAdress = Boards[i].adr;
auto *reply = modbusDevice->sendWriteRequest(unit, slaveAdress);
if(reply) {
(*totalActiveBoards)++;
pendingBoards->insert(slaveAdress);
// Захватываем shared pointers по значению - это безопасно
connect(reply, &QModbusReply::finished, this,
[this, i, reply, slaveAdress, totalActiveBoards, confirmedBoards, pendingBoards, processResult]() {
if(reply->error() == QModbusDevice::TimeoutError)
(*confirmedBoards)++;
else if(reply->error() == QModbusDevice::NoError)
logError(tr("Плата %1 (ID %2)").arg(i + 1).arg(slaveAdress),
tr("Неожиданный ответ."), ++Boards[i].error_baud_change,
"Ошибка при изменении скорости обмена.");
else
logError(tr("Плата %1 (ID %2)").arg(i + 1).arg(slaveAdress),
reply->errorString(), ++Boards[i].error_baud_change,
"Ошибка при изменении скорости обмена.");
pendingBoards->remove(slaveAdress);
reply->deleteLater();
if(pendingBoards->isEmpty())
processResult();
});
}
}
}
/**
* @brief Восстанавливает предыдущую скорость обмена и перезапускает соединение.
*
* Этот метод отключает текущий модбас-устройник, устанавливает скорость соединения
* в значение, сохраненное в настройках (`curBaud()`), и затем переподключается.
* Используется для возврата к исходной скорости обмена при ошибках или отмене операции.
*/
void M3KTE::revertToOldSpeedAndRestart()
{
modbusDevice->disconnectDevice();
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
m_settingsDialog->curBaud());
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
modbusDevice->connectDevice();
}
/**
* @brief Обрабатывает изменение параметра четности (parity) для устройств.
*
* Отправляет запросы на изменение параметра четности на активных платах,
* затем обрабатывает результаты, при необходимости откатывая изменения.
*/
void M3KTE::onParityUpdate()
{
// Останавливаем сканирование плат
stopScanBoard();
// Настройка регистра для установки режима четности
QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 174, 1);
switch(m_deviceSettingsDialog->currentParity()) {
case 0: unit.setValue(0, 0x0000); break; // Нет контроля
case 1: unit.setValue(0, 0x0400); break; // Четный
case 2: unit.setValue(0, 0x0600); break; // Нечетный
}
// Переменные для подсчёта прогресса
auto totalActiveBoards = QSharedPointer<int>::create(0);
auto confirmedBoards = QSharedPointer<int>::create(0);
auto pendingBoards = QSharedPointer<QSet<int>>::create();
// Сохраняем текущий и новый параметры четности для отката при ошибках
auto oldParity = m_settingsDialog->curParity();
auto newParity = m_deviceSettingsDialog->currentParity();
/**
* Лямбда-функция, вызываемая по завершении всех запросов.
* Обрабатывает успешное изменение или ошибку.
*/
auto processResult = [this, totalActiveBoards, confirmedBoards, oldParity, newParity]() {
if(*confirmedBoards != *totalActiveBoards) {
// Не все подтвердили — повторно запускаем скан
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
beginScanBoards();
} else {
// Все подтвердили — меняем параметры соединения
modbusDevice->disconnectDevice();
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, newParity);
modbusDevice->connectDevice();
// Обработка ошибок и успеха при смене паритета
auto errorHandler = [this, oldParity]() {
disconnect(this, &M3KTE::errorAtCheckBoards, this, nullptr);
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
modbusDevice->disconnectDevice();
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, oldParity);
modbusDevice->connectDevice();
beginScanBoards();
};
auto successHandler = [this, newParity]() {
disconnect(this, &M3KTE::successAtCheckBoards, this, nullptr);
m_settingsDialog->UpdateParity(newParity);
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
beginScanBoards();
};
// Подписка на события успеха и ошибки
connect(this, &M3KTE::errorAtCheckBoards, this, errorHandler);
connect(this, &M3KTE::successAtCheckBoards, this, successHandler);
// Запускаем повторную проверку плат
checkBoards();
}
};
// Отправляем запросы на каждую активную плату
for(int i = 0; i < 4; i++) {
if(!Boards[i].isActive)
continue;
int slaveAddress = Boards[i].adr;
auto *reply = modbusDevice->sendWriteRequest(unit, slaveAddress);
if(reply) {
(*totalActiveBoards)++;
pendingBoards->insert(slaveAddress);
// Обработка завершения ответа
connect(reply, &QModbusReply::finished, this,
[this, i, reply, slaveAddress, totalActiveBoards, confirmedBoards, pendingBoards, processResult]() {
if(reply->error() == QModbusDevice::TimeoutError)
(*confirmedBoards)++;
else if(reply->error() == QModbusDevice::NoError)
logError(tr("Плата %1 (ID %2)").arg(i + 1).arg(slaveAddress),
tr("Неожиданный ответ."), ++Boards[i].error_baud_change,
"Ошибка при изменении чётности.");
else
logError(tr("Плата %1 (ID %2)").arg(i + 1).arg(slaveAddress),
reply->errorString(), ++Boards[i].error_baud_change,
"Ошибка при изменении чётности.");
// Удаляем складной ответ
pendingBoards->remove(slaveAddress);
reply->deleteLater();
// Когда все ответы получены, обрабатываем результат
if(pendingBoards->isEmpty())
processResult();
});
}
}
}
/**
* @brief Функция реакция на ошибку при инициализации устройств.
*
* Инициирует отключение, закрывает прогресс-бар, и настраивает параметры
* таймаута и количества повторных попыток.
*
* @param bar Указатель на прогресс-бар для отображения процесса.
* @return false, так как функция, в текущем виде, всегда возвращает false.
*/
bool M3KTE::deadPing(QProgressDialog *bar)
{
onConnectClicked();
bar->close();
bar->deleteLater();
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries);
return false;
}
/**
* @brief Выполняет сканирование сети устройств (плат) по Modbus-адресам.
*
* Эта функция последовательно посылает запросы на определения типа устройства и идентификатора платы
* по адресу от 1 до 246. Если устройство отвечает ожидаемым ответом, оно считается найденным и
* добавляется в список активных плат. После сканирования отображается окно с результатами, и при необходимости
* выполняется считывание текущих настроек каждой активной платы.
*
* Основные шаги:
* - Открытие прогресс-диалога и настройка обработки отмены.
* - Посылка запросов для определения типа и ID платы.
* - Обработка ответов, обновление информации о платах.
* - Взаимодействие с пользователем: сообщение о завершении сканирования и опрос настроек.
* - Обновление интерфейса и состояния устройств.
*
* @return true, если сканирование прошло успешно и завершено; false — при ошибках или отмене.
*/
bool M3KTE::pingNetworkDevices()
{
CurrentConnectedDevice = 0;
int tmp_adr = 1;
bool isRun = false;
bool *tmp_isRun = &isRun;
auto bar = new QProgressDialog(this);
connect(bar, &QProgressDialog::canceled, this, [tmp_isRun]() {
*tmp_isRun = true;
});
bar->setLabelText(tr("Поиск плат... Текущий адрес: %1").arg(tmp_adr));
bar->setCancelButton(nullptr);
bar->setRange(0, 4);
bar->setMinimumDuration(100);
bar->setValue(CurrentConnectedDevice);
modbusDevice->setNumberOfRetries(0);
QModbusRequest requestOfDeviceType(QModbusRequest::EncapsulatedInterfaceTransport, QByteArray::fromHex("0E0404"));
QModbusRequest requestOfBoardID(QModbusRequest::EncapsulatedInterfaceTransport, QByteArray::fromHex("0E0401"));
modbusDevice->setTimeout(50);
for(CurrentConnectedDevice = 0; CurrentConnectedDevice < 4;) {
auto *reply = modbusDevice->sendRawRequest(requestOfDeviceType, tmp_adr);
//Запрос типа устройства.
if(reply == nullptr)
return deadPing(bar);
while(!reply->isFinished()) {
if(isRun && CurrentConnectedDevice < 1)
return deadPing(bar);
else if(isRun)
break;
QCoreApplication::processEvents();
}
if(isRun && CurrentConnectedDevice < 1)
return deadPing(bar);
else if(isRun)
break;
else if(!isRun) {
//Нужна проверка типа устройства
if(reply->error() == QModbusDevice::NoError) {
QModbusResponse resp = reply->rawResult();
QString result = QString(resp.data().remove(0, MODBUS_REQUEST_PROTOCOL_INFO_LENGTH));
if(result == QString("KTE")) {
auto *subreply = modbusDevice->sendRawRequest(requestOfBoardID, tmp_adr);
while(!subreply->isFinished()) {
if(isRun && CurrentConnectedDevice < 1)
return deadPing(bar);
else if(isRun)
break;
QCoreApplication::processEvents();
}
int plata_ind = 0;
if(subreply->rawResult().data().size() >= MODBUS_REQUEST_PROTOCOL_INFO_LENGTH) // ответ принят
plata_ind = subreply->rawResult().data().at(MODBUS_REQUEST_PROTOCOL_INFO_LENGTH) - 0x30; // парс ответа
if(plata_ind < 1 || plata_ind > 4) {
QMessageBox::warning(this, "Ошибка при сканировании сети.",
QString("Не удалось получить порядковый номер платы по адресу %1").arg(tmp_adr));
return deadPing(bar);
}
int board_ind = plata_ind - 1;
if(isRun && CurrentConnectedDevice < 1)
return deadPing(bar);
else if(isRun)
break;
else {
statusBar()->showMessage(tr("Плата %1 найдена по адресу %2.").arg(board_ind).arg(tmp_adr),
m_settingsDialog->settings().responseTime);
if(Boards[board_ind].isActive) {
QMessageBox::warning(this, "Ошибка при сканировании сети.",
QString("Платы по адресам %1 и %2 имеют одинаковый порядковый номер %3").arg(Boards[board_ind].adr).arg(tmp_adr).arg(plata_ind));
return deadPing(bar);
}
CurrentConnectedDevice++;
Boards[board_ind].adr = Boards[board_ind]._tmp_adr = tmp_adr;
Boards[board_ind].isActive = true;
bar->setValue(CurrentConnectedDevice);
}
}
}
}
tmp_adr++;
bar->setLabelText(tr("Поиск плат... Текущий адрес: %1").arg(tmp_adr));
if(tmp_adr >= 247 && (CurrentConnectedDevice < 1)) {
//ERROR
//OUT OF RANGE
QMessageBox::warning(this, "Ошибка при сканировании сети.",
QString("Выход за пределы допустимых адресов. Найдено %1 плат.").arg(CurrentConnectedDevice));
bar->setValue(4);
return deadPing(bar);
} else if(tmp_adr >= 247)
break;
}
isRun = false;
// Создаем QMessageBox
QMessageBox msgBox;
msgBox.setWindowTitle("Сканирование сети завершено.");
msgBox.setText(QString("Найдено плат: %1 из 4.").arg(CurrentConnectedDevice));
// Добавляем две кнопки
QPushButton *requestSettingsBtn = msgBox.addButton("Запросить настройки", QMessageBox::AcceptRole);
QPushButton *cancelBtn = msgBox.addButton("Прервать подключение", QMessageBox::RejectRole);
// Выполняем модальное окно и ожидаем ответ
msgBox.exec();
// Обработка нажатия
if(msgBox.clickedButton() == cancelBtn)
return deadPing(bar);
modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
bar->setLabelText(tr("Считывание текущих настроек..."));
bar->setRange(0, CurrentConnectedDevice * 3);
for(int i = 0; i < 4; i++) {
if(Boards[i].isActive) {
QModbusDataUnit* _unit_settings[3];
QModbusDataUnit cCoils(QModbusDataUnit::Coils, 0, 85 - (i / 3 * 20));
QModbusDataUnit wHR(QModbusDataUnit::HoldingRegisters, 0, 85 - (i / 3 * 20));
QModbusDataUnit aHR(QModbusDataUnit::HoldingRegisters, 85, 85 - (i / 3 * 20));
_unit_settings[0] = &cCoils;
_unit_settings[1] = &wHR;
_unit_settings[2] = &aHR;
Boards_Fields[i]->setEnabled(true);
for(int j = 0; j < 3; j++) {
bar->setValue(i * 3 + j);
if(isRun)
return deadPing(bar);
auto *reply = modbusDevice->sendReadRequest(*_unit_settings[j], Boards[i].adr);
if(!reply)
return deadPing(bar);
while(!reply->isFinished()) {
if(isRun) {
QMessageBox::warning(this, "Ошибка при получении текущих настроек.",
QString("Прерывание по запросу пользователя."));
return deadPing(bar);
}
QCoreApplication::processEvents();
}
if(reply->error()==QModbusDevice::NoError)
applySettingsFromScan(reply);
else {
QMessageBox::warning(this, "Ошибка при получении текущих настроек.",
QString("Таймаут при опросе устройства %1 по адресу %2").arg(i + 1).arg(Boards[i].adr));
bar->setValue(CurrentConnectedDevice * 3);
return deadPing(bar);
}
}
Boards_Fields[i]->setTitle(QString("Плата №%1 (ID %2)").arg(i + 1).arg(Boards[i].adr));
}
}
int totalBoardsActive = 0;
for(int i = 0; i < 4; i++)
if(Boards[i].isActive)
totalBoardsActive++;
m_parameterWorkspace->setDeviceCount(totalBoardsActive);
modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries);
beginScanBoards();
bar->deleteLater();
return true;
}
/**
* @brief Запускает процесс сканирования активных плат в системе.
*
* Эта функция перебирает все возможные платы (от 0 до 3), проверяет их активность,
* и для каждой активной платы инициализирует её сканирование:
* - Обновляет состояние интерфейса для отображения активных плат.
* - Инициализирует опрос настроек устройства по адресу.
* - Запускает процедуру сканирования платы.
* - Обновляет рабочее пространство параметров с информацией об активной плате.
*
* После завершения процедуры все активные платы будут обработаны, и пользователь увидит актуальное состояние.
*/
void M3KTE::beginScanBoards()
{
int totalBoardsActive = 0;
for(int i = 0; i < 4; i++)
if(Boards[i].isActive) {
m_debugTerminalDialog->setScanBoardActive(true, i);
m_deviceSettingsDialog->initPollForBoard(i, Boards[i].adr);
boardScan(i);
m_parameterWorkspace->updateDevice(totalBoardsActive++, Boards[i].isActive, Boards[i].adr);
}
return;
}
/**
* @brief Выполняет сканирование данных платы по Modbus.
*
* Эта функция инициирует последовательные запросы к плате с идентификатором `boardID` для получения
* статуса и полного набора данных через Modbus. Обрабатывает завершения запросов асинхронно, используя сигналы и слоты,
* измеряет время отклика, логирует ошибки и запусквает следующий цикл сканирования.
*
* Основные шаги:
* - Проверяет наличие активного Modbus-устройства.
* - Генерирует запрос чтения регистра статуса (регистры 85).
* - Обрабатывает ответ по таймеру; при успешном ответе:
* - извлекает статус регистра;
* - инициирует запрос полного набора данных (с учётом идентификатора платы);
* - обрабатывает ответ данных, вызывает функцию отображения результата,
* и перезапускает таймер следующего сканирования.
* - В случае ошибок или завершения запросов мгновенно, логирует ошибку и перезапускает цикл.
*
* @param boardID Идентификатор платы (индекс в массиве).
*/
void M3KTE::boardScan(unsigned boardID)
{
if(!modbusDevice)
return;
emit boardReading(boardID);
statusBar()->clearMessage();
QModbusDataUnit statusUnit(QModbusDataUnit::InputRegisters, 85, 1);
if(auto *reply = modbusDevice->sendReadRequest(statusUnit, Boards[boardID].adr)) {
Boards[boardID].timerToStatusResponse.start();
if(!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, boardID, reply]() {
if(!Boards[boardID].isActive)
return;
// Обработка ответа статуса
Boards[boardID].timerStatus->setText(QString("Status: %1 ms").arg(Boards[boardID].timerToStatusResponse.elapsed()));
if(reply->error() == QModbusDevice::NoError) {
statusreg StatusReg;
StatusReg.AllReg = reply->result().value(0);
// Запрос полных данных
QModbusDataUnit dataUnit(QModbusDataUnit::InputRegisters, 0, 85 - (boardID / 3 * 20));
if(auto *dataReply = modbusDevice->sendReadRequest(dataUnit, Boards[boardID].adr)) {
Boards[boardID].timerToDataResponse.start();
if(!dataReply->isFinished()) {
connect(dataReply, &QModbusReply::finished, this, [this, boardID, dataReply, StatusReg]() {
if(!Boards[boardID].isActive)
return;
Boards[boardID].timerData->setText(QString("Data: %1 ms").arg(Boards[boardID].timerToDataResponse.elapsed()));
displayResultOfScan(dataReply, boardID, StatusReg.AllReg);
dataReply->deleteLater();
unsigned timerInterval = m_deviceSettingsDialog->currentBoardTimer(boardID);
Boards[boardID].boardScanners->start(timerInterval);
});
} else {
// Мгновенно завершенный запрос данных
Boards[boardID].timerToDataResponse.elapsed();
// shouldLog для dataReply должен определяться здесь, но используем тот же принцип
if(Boards[boardID].isActive && !(reply->error() == QModbusDevice::ReplyAbortedError))
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
modbusDevice->errorString(), ++Boards[boardID].error_TX, "");
dataReply->deleteLater();
unsigned timerInterval = m_deviceSettingsDialog->currentBoardTimer(boardID);
Boards[boardID].boardScanners->start(timerInterval);
}
} else {
// Ошибка отправки запроса данных
if(Boards[boardID].isActive && !(reply->error() == QModbusDevice::ReplyAbortedError))
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
modbusDevice->errorString(), ++Boards[boardID].error_TX, "");
unsigned timerInterval = m_deviceSettingsDialog->currentBoardTimer(boardID);
Boards[boardID].boardScanners->start(timerInterval);
}
} else {
// Ошибка в ответе статуса
if(Boards[boardID].isActive && !(reply->error() == QModbusDevice::ReplyAbortedError))
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
reply->errorString(), ++Boards[boardID].error_TX, "");
unsigned timerInterval = m_deviceSettingsDialog->currentBoardTimer(boardID);
Boards[boardID].boardScanners->start(timerInterval);
}
reply->deleteLater();
});
} else {
// Мгновенно завершенный запрос статуса
Boards[boardID].timerToStatusResponse.elapsed();
// Для мгновенно завершенных запросов определяем shouldLog здесь
if(Boards[boardID].isActive && !(reply->error() == QModbusDevice::ReplyAbortedError))
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
modbusDevice->errorString(), ++Boards[boardID].error_TX, "");
reply->deleteLater();
unsigned timerInterval = m_deviceSettingsDialog->currentBoardTimer(boardID);
Boards[boardID].boardScanners->start(timerInterval);
}
} else
// Ошибка отправки запроса статуса
if(Boards[boardID].isActive)
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
modbusDevice->errorString(), ++Boards[boardID].error_TX, "");
}
/**
* @brief Обрабатывает и отображает результат сканирования Modbus-ответа.
*
* Эта функция анализирует ответ, обновляет состояние элементов пользовательского интерфейса
* и регистров, а также регистрирует ошибки и предупреждения.
*
* @param reply Указатель на объект QModbusReply, содержащий ответ.
* @param boardID Индекс платы, которую обрабатываем.
* @param status Статус регистра, полученный из устройства.
*/
void M3KTE::displayResultOfScan(QModbusReply *reply, int boardID, int status)
{
if(!reply)
return;
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
bool W_Flag = false;
bool A_Flag = false;
if(unit.startAddress() != 0 || unit.valueCount() != (unsigned)(85 - (boardID / 3 * 20))) {
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
"Ошибка при приёме.", ++Boards[boardID].error_RX,
tr("Принятый ответ: Стартовый адрес %1, Количество элементов %2").arg(unit.startAddress()).arg(unit.valueCount()));
reply->deleteLater();
return;
}
statusreg StatusReg;
StatusReg.AllReg = status;
// Обрабатываем статус MZKT
QString statusText;
int state = StatusReg.ParsingReg.mzkte_status;
int numb_err = StatusReg.ParsingReg.mzkte_error;
if(state == 0)
statusText = "Ok";
else if(state == 1)
statusText = "Non-Critical";
else if(state == 2)
statusText = "Critical";
if(state) {
switch (numb_err) {
case 1: statusText += QString(" (Err 5 VD)"); break;
case 2: statusText += QString(" (Err 5 VA)"); break;
case 3: statusText += QString(" (Err 5 Vsci)"); break;
case 4: statusText += QString(" (Err 24 V)"); break;
case 5: statusText += QString(" (Hardfault)"); break;
case 6: statusText += QString(" (Empty Settings)"); break;
case 7: statusText += QString(" (ADC Error)"); break;
case 8: statusText += QString(" (Program Err 4)"); break;
case 9: statusText += QString(" (Program Err 5)"); break;
case 10: statusText += QString(" (EEPROM Error)"); break;
case 11: statusText += QString(" (Unstable discharge)"); break;
case 12: statusText += QString(" (RS/UART Errors)"); break;
default: statusText += QString(" (Program Err %1)").arg(numb_err - 4);
}
}
Boards[boardID].localError->setText(statusText);
Boards[boardID].localState[LOCAL_STATE_POLL]->setChecked(StatusReg.ParsingReg.poll_allowed);
Boards[boardID].localState[LOCAL_STATE_WARN]->setChecked(StatusReg.ParsingReg.warning);
Boards[boardID].localState[LOCAL_STATE_ERR]->setChecked(StatusReg.ParsingReg.accident);
QString W_Adr;
QString A_Adr;
int total_qnt;
if(boardID == 3)
total_qnt = 65;
else
total_qnt = 85;
for(int i = unit.startAddress(), total = total_qnt; i < total; ++i) {
if(Boards[boardID].coil[i]==true) {
int j = 0;
if(Boards[boardID].HR[i + 85] > unit.value(i)) {
j = 1;
if(j != m_ProgressBar[i + boardID * 85]->value()) {
A_Adr += tr("ТЭ%1 ").arg(i);
Boards[boardID].error_A++;
}
m_ProgressBar[i + boardID * 85]->setStatusTip(QString("П%1 ТЭ%2: Аварийный уровень напряжения.").arg(QString::number(boardID + 1), QString::number(i % 85)));
m_ProgressBar[i + boardID * 85]->setWhatsThis(QString("П%1 ТЭ%2: Аварийный уровень напряжения.").arg(QString::number(boardID + 1), QString::number(i % 85)));
A_Flag = true;
} else if(Boards[boardID].HR[i] > unit.value(i)) {
j = 2;
if(j != m_ProgressBar[i + boardID * 85]->value()) {
W_Adr += tr("ТЭ%1 ").arg(i);
Boards[boardID].error_W++;
}
m_ProgressBar[i + boardID * 85]->setStatusTip(QString("П%1 ТЭ%2: Предупредительный уровень напряжения.").arg(QString::number(boardID + 1), QString::number(i % 85)));
m_ProgressBar[i + boardID * 85]->setWhatsThis(QString("П%1 ТЭ%2: Предупредительный уровень напряжения.").arg(QString::number(boardID + 1), QString::number(i % 85)));
W_Flag = true;
} else {
j = 3;
m_ProgressBar[i + boardID * 85]->setStatusTip(QString("П%1 ТЭ%2: Уровень напряжения в норме.").arg(QString::number(boardID + 1), QString::number(i % 85)));
m_ProgressBar[i + boardID * 85]->setWhatsThis(QString("П%1 ТЭ%2: Уровень напряжения в норме.").arg(QString::number(boardID + 1), QString::number(i % 85)));
}
m_ProgressBar[i + boardID * 85]->setValue(j);
QString style_fc = "QProgressBar {border: 2px solid black; font: bold 10px} QProgressBar::chunk {background: hsva(" + QString::number(j * 50 - 50) + ", 255, 255, 100%);} ";
m_ProgressBar[i + boardID * 85]->setStyleSheet(style_fc);
} else {
m_ProgressBar[i + boardID * 85]->setValue(3);
m_ProgressBar[i + boardID * 85]->setStatusTip(QString("П%1 ТЭ%2: Топливный Элемент не учитывается.").arg(QString::number(boardID + 1), QString::number(i % 85)));
m_ProgressBar[i + boardID * 85]->setWhatsThis(QString("П%1 ТЭ%2: Топливный Элемент не учитывается.").arg(QString::number(boardID + 1), QString::number(i % 85)));
QString style_fc_off = "QProgressBar {border: 2px solid black; font: bold 10px} QProgressBar::chunk {background: hsva(" + QString::number(30) + ", 30, 30, 30%);} ";
m_ProgressBar[i + boardID * 85]->setStyleSheet(style_fc_off);
}
Boards[boardID].ModbusModelCoil->set_currentU(unit.value(i), i);
Boards[boardID].ModbusModelHoldingReg->set_currentU(unit.value(i), i);
}
Boards[boardID].ModbusModelCoil->dataChanged(ui->writeValueTable->model()->index(unit.startAddress(), 2),
ui->writeValueTable->model()->index(unit.startAddress() + total_qnt -1, 2));
Boards[boardID].ModbusModelHoldingReg->dataChanged(ui->writeValueTable->model()->index(unit.startAddress(), 3),
ui->writeValueTable->model()->index(unit.startAddress() + total_qnt -1, 3));
if(A_Flag && !A_Adr.isEmpty()) {
statusM3KTE.Accidents[boardID] = true;
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
"Авария", Boards[boardID].error_A, A_Adr);
} else
statusM3KTE.Accidents[boardID] = false;
if(W_Flag && !W_Adr.isEmpty()) {
statusM3KTE.Warnings[boardID] = true;
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
"Предупреждение", Boards[boardID].error_W, W_Adr);
} else
statusM3KTE.Warnings[boardID] = false;
ui->BSM_Warning->setChecked(false);
ui->BSM_Accident->setChecked(false);
ui->BSM_WorkInProgress->setChecked(true);
for(int i = 0; i < 4; i++) {
if(statusM3KTE.Accidents[i]) {
ui->BSM_WorkInProgress->setChecked(false);
ui->BSM_Warning->setChecked(false);
ui->BSM_Accident->setChecked(true);
break;
}
if(statusM3KTE.Warnings[i]) {
ui->BSM_WorkInProgress->setChecked(false);
ui->BSM_Accident->setChecked(false);
ui->BSM_Warning->setChecked(true);
}
}
} else if(reply->error() == QModbusDevice::ProtocolError) {
statusBar()->showMessage(tr("Read response error: %1 (Mobus exception: 0x%2)").
arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
reply->errorString(), ++Boards[boardID].error_RX,
QString("Mobus exception: 0x%1").arg(reply->rawResult().exceptionCode(), -1, 16));
} else {
statusBar()->showMessage(tr("Read response error: %1 (code: 0x%2)").
arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
reply->errorString(), ++Boards[boardID].error_RX,
QString::number(reply->error(), 16));
}
reply->deleteLater();
}
/**
* @brief Обрабатывает ответ Modbus и применяет настройки к платам.
*
* Эта функция проверяет ошибку ответа и, при успешном ответе,
* обновляет данные в моделях и элементах интерфейса для соответствующей платы.
* В случае ошибок отображает сообщения и регистрирует ошибки.
*
* @param reply Указатель на объект QModbusReply с ответом от устройства.
*/
void M3KTE::applySettingsFromScan(QModbusReply *reply)
{
int Adr = 255;
for(int i = 0; i < 4; i++)
if(Boards[i].adr==reply->serverAddress() &&Boards[i].isActive) {
Adr = i;
break;
}
if(reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
for(int i = 0, total = int(unit.valueCount()); i < (total); ++i) {
if(unit.registerType() == QModbusDataUnit::Coils) {
Boards[Adr].coil[i + unit.startAddress()] = unit.value(i);
if(unit.value(i) == 1)
Boards[Adr].ModbusModelCoil->setData(ui->writeValueTable->model()->index(i + unit.startAddress(), 2), Qt::Checked, Qt::CheckStateRole);
else
Boards[Adr].ModbusModelCoil->setData(ui->writeValueTable->model()->index(i + unit.startAddress(), 2), Qt::Unchecked, Qt::CheckStateRole);
} else if(unit.registerType() == QModbusDataUnit::HoldingRegisters) {
Boards[Adr].HR[i + unit.startAddress()] = unit.value(i);
Boards[Adr].ModbusModelHoldingReg->setData(Boards[Adr].ModbusModelHoldingReg->index(i + unit.startAddress(), 3), QString::number(unit.value(i), 10), Qt::EditRole);
}
}
} else if(reply->error() == QModbusDevice::ProtocolError) {
statusBar()->showMessage(tr("Read response error: %1 (Mobus exception: 0x%2)").
arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(Adr + 1).arg(Boards[Adr].adr),
reply->errorString(), ++Boards[Adr].error_RX,
QString("Mobus exception: 0x%1").arg(reply->rawResult().exceptionCode(), -1, 16));
} else {
statusBar()->showMessage(tr("Read response error: %1 (code: 0x%2)").
arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(Adr + 1).arg(Boards[Adr].adr),
reply->errorString(), ++Boards[Adr].error_RX,
QString::number(reply->error(), 16));
}
reply->deleteLater();
}
/**
* @brief Слот для обработки команды записи нескольких регистров.
*
* Проверяет наличие активного соединения с Modbus-устройством и
* вызывает функцию `multipleRegWrite()` для выполнения записи.
*/
void M3KTE::slotmultipleRegWrite()
{
if(!modbusDevice)
return;
multipleRegWrite();
}
/**
* @brief Слот для записи нескольких регистров и отправки данных.
*
* Проверяет наличие активного соединения с Modbus-устройством и,
* если соединение установлено, вызывает функции `multipleRegWrite()` для записи
* и `multipleRegSend()` для отправки данных.
*/
void M3KTE::slotmultipleRegWriteAndSend()
{
if(!modbusDevice)
return;
multipleRegWrite();
multipleRegSend();
}
/**
* @brief Отправляет команду записи нескольких регистров или катушек на устройство по Modbus.
*
* Функция создает объект QModbusDataUnit с типом регистров или катушек, в зависимости от настроек.
* Заполняет его значениями и обновляет модели данных внутреннего устройства.
* Затем инициирует асинхронную отправку запроса через `modbusDevice->sendWriteRequest()`.
* Обрабатывает результат асинхронного ответа и выводит сообщения об ошибках.
*/
void M3KTE::multipleRegSend()
{
QModbusDataUnit *unit_tx = nullptr;
if(m_regMultipleSettings->getTypeReg())
unit_tx = new QModbusDataUnit(QModbusDataUnit::HoldingRegisters, m_regMultipleSettings->getStartAdr(), m_regMultipleSettings->getCountReg());
else
unit_tx = new QModbusDataUnit(QModbusDataUnit::Coils, m_regMultipleSettings->getStartAdr(), m_regMultipleSettings->getCountReg());
for(unsigned i = 0; i < m_regMultipleSettings->getCountReg(); i++) {
unit_tx->setValue(i, m_regMultipleSettings->getNewValue());
if(m_regMultipleSettings->getTypeReg())
Boards[m_regMultipleSettings->getBoardId()].HR[i+m_regMultipleSettings->getStartAdr()] = m_regMultipleSettings->getNewValue();
else
Boards[m_regMultipleSettings->getBoardId()].coil[i+m_regMultipleSettings->getStartAdr()] = m_regMultipleSettings->getNewValue();
}
if(auto *reply = modbusDevice->sendWriteRequest(*unit_tx, Boards[m_regMultipleSettings->getBoardId()].adr)) {
unsigned Adr = m_regMultipleSettings->getBoardId();
if(!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, reply, Adr]() {
if(reply->error() == QModbusDevice::ProtocolError) {
statusBar()->showMessage(tr("Write response error: %1 (Mobus exception: 0x%2)")
.arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(Adr + 1).arg(Boards[Adr].adr),
reply->errorString(), ++Boards[Adr].error_TX,
QString("Mobus exception: 0x%1").arg(reply->rawResult().exceptionCode(), -1, 16));
} else if(reply->error() != QModbusDevice::NoError) {
statusBar()->showMessage(tr("Write response error: %1 (code: 0x%2)").
arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
logError(tr("Плата %1 (ID %2)").arg(Adr + 1).arg(Boards[Adr].adr),
reply->errorString(), ++Boards[Adr].error_TX,
QString::number(reply->error(), 16));
}
reply->deleteLater();
});
} else
// broadcast replies return immediately
reply->deleteLater();
} else
statusBar()->showMessage(tr("Write error: ") + modbusDevice->errorString(), 5000);
}
/**
* @brief Обновляет внутренние модели данных для нескольких регистров или катушек.
*
* В цикле по количеству регистров или катушек, указанных в настройках, обновляет
* значения внутренних моделей `ModbusModelHoldingReg` или `ModbusModelCoil` для соответствующих
* позиций, основываясь на типе регистров. Значения устанавливаются в новое заданное значение.
*/
void M3KTE::multipleRegWrite()
{
for(unsigned i = 0; i < m_regMultipleSettings->getCountReg(); i++) {
if(m_regMultipleSettings->getTypeReg())
Boards[m_regMultipleSettings->getBoardId()].ModbusModelHoldingReg->m_holdingRegisters[i + m_regMultipleSettings->getStartAdr()] = m_regMultipleSettings->getNewValue();
else
Boards[m_regMultipleSettings->getBoardId()].ModbusModelCoil->m_coils[i + m_regMultipleSettings->getStartAdr()] = (bool)m_regMultipleSettings->getNewValue();
}
}
/**
* @brief Записывает значение в один котл на конкретной плате по Modbus.
*
* Проверяет наличие активного соединения и активности платы.
* Создает объект `QModbusDataUnit` для одного катушечного адреса, устанавливает значение.
* Выполняет отправку записи и обрабатывает результат асинхронно.
*
* @param boardId Идентификатор платы.
* @param coilAddress Адрес котлы, в которую нужно записать.
* @param value Значение для записи (true — включено, false — выключено).
*/
void M3KTE::writeSingleCoil(int boardId, int coilAddress, bool value)
{
if(!modbusDevice || !Boards[boardId].isActive)
return;
QModbusDataUnit unit(QModbusDataUnit::Coils, coilAddress, 1);
unit.setValue(0, value ? 1 : 0);
if(auto *reply = modbusDevice->sendWriteRequest(unit, Boards[boardId].adr)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, [this, boardId, reply]() {
if(reply->error() != QModbusDevice::NoError)
logError(tr("Плата %1 (ID %2)").arg(boardId + 1).arg(Boards[boardId].adr),
reply->errorString(), ++Boards[boardId].error_TX,
"Single coil write");
reply->deleteLater();
});
else
reply->deleteLater();
}
}
/**
* @brief Записывает значение в один регистр (holding register) на указанной плате по Modbus.
*
* Проверяет, инициализировано ли соединение и активна ли плата.
* Создает объект `QModbusDataUnit` для одного регистра по указанному адресу и присваивает ему значение.
* Выполняет асинхронную отправку записи и обрабатывает ответ.
*
* @param boardID Идентификатор платы.
* @param regAddress Адрес регистра, в который нужно записать значение.
* @param value Значение для записи в регистр.
*/
void M3KTE::writeSingleRegister(int boardID, int regAddress, quint16 value)
{
if(!modbusDevice || !Boards[boardID].isActive)
return;
QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, regAddress, 1);
unit.setValue(0, value);
if(auto *reply = modbusDevice->sendWriteRequest(unit, Boards[boardID].adr)) {
if(!reply->isFinished())
connect(reply, &QModbusReply::finished, this, [this, boardID, reply]() {
if(reply->error() != QModbusDevice::NoError)
logError(tr("Плата %1 (ID %2)").arg(boardID + 1).arg(Boards[boardID].adr),
reply->errorString(), ++Boards[boardID].error_TX,
"Single register write");
reply->deleteLater();
});
else
reply->deleteLater();
}
}
/**
* @brief Читает состояние одного по цепочке (coil) на указанной плате по Modbus.
*
* Проверяет, инициализирован ли модуль и активна ли плата.
* Создает объект `QModbusDataUnit` для одного котла по адресу и отправляет запрос на чтение.
* Возвращает указатель на объект `QModbusReply`, который можно использовать для обработки результата асинхронно.
*
* @param boardID Идентификатор платы.
* @param coilAddress Адрес котлы (coil), состояние которой нужно прочитать.
* @return Указатель на `QModbusReply`, или `nullptr`, если модуль не инициализирован или плата не активна.
*/
QModbusReply* M3KTE::readSingleCoil(int boardID, int coilAddress)
{
if(!modbusDevice || !Boards[boardID].isActive)
return nullptr;
QModbusDataUnit unit(QModbusDataUnit::Coils, coilAddress, 1);
auto *reply = modbusDevice->sendReadRequest(unit, Boards[boardID].adr);
return reply;
}
/**
* @brief Выбирает позицию в дереве и таблице UI по заданному индексу.
*
* Расчет осуществляется на основе индекса, деля его на блоки, соответствующие определенным
* диапазонам элементов в интерфейсе (например, 85 элементов на плату). Затем обновляется
* выделение и прокрутка таблицы до выбранной позиции.
*
* @param index Индекс элемента, который следует выделить и прокрутить.
*/
void M3KTE::selectPositionOnTree(unsigned int index)
{
ui->boardSelectBox->setCurrentIndex(index / 85);
int maxReg = 85 - (ui->boardSelectBox->currentIndex() / 3 * 20);
QModelIndex selected = ui->writeValueTable->model()->index(index % maxReg + maxReg*(ui->writeTable->currentIndex() / 2), 0);
ui->writeValueTable->selectionModel()->select(selected, QItemSelectionModel::ClearAndSelect |QItemSelectionModel::Rows);
ui->writeValueTable->scrollTo(selected);
}
/**
* @brief Выполняет автоматический сканирование скорости передачи данных для устройств по Modbus.
*
* Эта функция перебирает стандартные скорости передачи данных (9600, 14400, 19200 и др.)
* и пытается обнаружить устройства, отвечающие на запросы по каждой скорости.
* В процессе происходит подключение к порту, отправка специальных запросов, проверка типа устройства
* и последующая синхронизация устройств, если такие были найдены.
*
* В случае успешного обнаружения устройств, результат выводится и отображается, также выполняется
* настройка устройств на выбранную скорость.
*
* Выполняет последовательные соединения, отправку запросов, обработку ответов, а также мониторинг
* пользовательского прерывания операции через диалоговое окно прогресса.
*
* @return true, если сканирование завершено успешно и все устройства синхронизированы, иначе false.
*/
bool M3KTE::autoBaudRateScan()
{
unsigned countOfDeviceOnLine = 0;
QString resultOfScan;
QVector<unsigned> KTE[8];
modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
m_settingsDialog->settings().portName);
#if QT_CONFIG(modbus_serialport)
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
m_settingsDialog->settings().parity);
modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
m_settingsDialog->settings().dataBits);
modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
m_settingsDialog->settings().stopBits);
#endif
modbusDevice->setTimeout(50);
modbusDevice->setNumberOfRetries(0);
uint m_baud[] = {9600, 14400, 19200, 31250, 38400, 56000, 57600, 115200};
bool isRun = false;
bool *tmp_isRun = &isRun;
auto bar = new QProgressDialog(this);
connect(bar, &QProgressDialog::canceled, this, [tmp_isRun]() {
*tmp_isRun = true;
});
bar->setCancelButton(nullptr);
bar->setRange(0, 8);
bar->setMinimumDuration(100);
bar->setValue(0);
QModbusRequest requestOfDeviceType(QModbusRequest::EncapsulatedInterfaceTransport, QByteArray::fromHex("0E0404"));
for(int i = 0; i < 8; i++) {
bar->setValue(i);
bar->setLabelText(tr("Поиск плат... Текущая скорость: %1").arg(m_baud[i]));
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baud[i]);
if(!modbusDevice->connectDevice())
statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000);
for(int tmp_adr = 1; tmp_adr < 248; tmp_adr++) {
auto *reply = modbusDevice->sendRawRequest(requestOfDeviceType, tmp_adr);
//Запрос типа устройства.
if(reply == nullptr) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
}
while(!reply->isFinished()) {
if(isRun) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
}
QCoreApplication::processEvents();
}
if(isRun) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
} else if(!isRun)
//Нужна проверка типа устройства
if(reply->error()==QModbusDevice::NoError) {
QModbusResponse resp = reply->rawResult();
QString result = QString(resp.data().remove(0, MODBUS_REQUEST_PROTOCOL_INFO_LENGTH));
if(result == QString("KTE")) {
countOfDeviceOnLine++;
KTE[i].append(tmp_adr);
}
}
}
if(countOfDeviceOnLine > 0)
resultOfScan += QString("%1 плат M3KTE работают на скорости %2.\n").arg(countOfDeviceOnLine).arg(m_baud[i]);
countOfDeviceOnLine = 0;
modbusDevice->disconnectDevice();
}
if(m_scanBoard->exec() == QDialog::Accepted) {
if(m_scanBoard->getCheckState() == Qt::Checked) {
for(int i = 0; i < 8; i++) {
for(int j = 0; j < KTE[i].size(); j++) {
for(int l = i; l < 8; l++) {
if(KTE[l].indexOf(KTE[i].at(j)) == -1) {
QMessageBox::warning(this, "Error",
QString("Несколько устройств по адресу %1, работающих на скоростях %2 и %3.")
.arg(KTE[i].at(j)).arg(m_baud[i]).arg(m_baud[l]));
return false;
}
}
}
}
QModbusDataUnit* _unit = new QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 173, 1);
_unit->setValue(0, m_scanBoard->getBaud());
for(int i = 0; i < 8; i++) {
bar->setValue(i);
bar->setLabelText(tr("Синхронизация плат на скорости %1").arg(m_baud[i]));
for(int j = 0; j < KTE[i].size(); j++) {
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baud[i]);
if(!modbusDevice->connectDevice())
statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000);
auto *reply = modbusDevice->sendWriteRequest(*_unit, KTE[i].at(j));
if(!reply) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
}
while(!reply->isFinished()) {
if(isRun) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
}
QCoreApplication::processEvents();
}
if(reply->error() == QModbusDevice::TimeoutError) {
modbusDevice->disconnectDevice();
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
m_settingsDialog->UpdateBaud(m_scanBoard->getBaud()));
modbusDevice->connectDevice();
QModbusDataUnit* _unit = new QModbusDataUnit(QModbusDataUnit::InputRegisters, 85, 1);
if(auto *subreply = modbusDevice->sendReadRequest(*_unit, Boards[i].adr)) {
while(!subreply->isFinished()) {
if(isRun) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
}
QCoreApplication::processEvents();
}
if(subreply->error() != QModbusDevice::NoError) {
onConnectClicked();
bar->close();
bar->deleteLater();
return false;
}
}
} else if(reply->error() != QModbusDevice::NoError)
return false;
modbusDevice->disconnectDevice();
}
}
}
return true;
}
else
return false;
}
/**
* @brief Останавливает процесс сканирования на всех платах.
*
* Эта функция перебирает все четыре устройства типа Boards и вызывает
* метод `stop()` для объекта `boardScanners`, который управляет запуском
* и остановкой процесса сканирования. После вызова все текущие сканирования
* на платах будут прекращены.
*/
void M3KTE::stopScanBoard()
{
for(int i = 0; i < 4; i++)
Boards[i].boardScanners->stop();
return;
}

160
M3KTE_TERM/m3kte.h Normal file
View File

@@ -0,0 +1,160 @@
#ifndef M3KTE_H
#define M3KTE_H
#include <QMainWindow>
#include <QModbusDataUnit>
#include <qprogressbar.h>
#include <QtSerialBus/QModbusDataUnit>
#include "writeregistermodel.h"
#include "debugterminaldialog.h"
#include "devicesettingsdialog.h"
#include "multiplesettings.h"
#include "scanboard.h"
#include "lineringer.h"
#include "parameterworkspace.h"
#include <QModbusTcpClient>
#include <QModbusRtuSerialMaster>
#include <QHBoxLayout>
#include <QTimer>
#include <QMessageBox>
#include <QProgressDialog>
#include <QErrorMessage>
#include <QPushButton>
#include <QGroupBox>
#include <QTableWidget>
#include <QTime>
#include <QElapsedTimer>
#include <QtSerialBus/qtserialbusglobal.h>
#if QT_CONFIG(modbus_serialport)
#include <QSerialPort>
#endif
#define MODBUS_REQUEST_PROTOCOL_INFO_LENGTH 8
extern "C" __declspec(dllexport) QWidget* init(QWidget *parent);
QT_BEGIN_NAMESPACE
namespace Ui { class M3KTE; class SettingsDialog; class DebugTerminalDialog;}
QT_END_NAMESPACE
#define LOCAL_STATE_POLL 0
#define LOCAL_STATE_WARN 1
#define LOCAL_STATE_ERR 2
class SettingsDialog;
class DebugTerminalDialog;
class WriteRegisterModel;
class M3KTE : public QMainWindow
{
Q_OBJECT
private:
void initActions();
QModbusDataUnit readRequest() const;
QModbusDataUnit writeRequest() const;
void changeTable(int board, int tabletype);
bool event(QEvent* event);
bool deadPing(QProgressDialog *bar);
bool pingNetworkDevices();
void beginScanBoards();
void stopScanBoard();
void displayResultOfScan(QModbusReply *reply, int boardID, int status);
void applySettingsFromScan(QModbusReply *reply);
void multipleRegWrite();
void multipleRegSend();
bool autoBaudRateScan();
void selectPositionOnTree(unsigned index);
signals:
void successAtCheckBoards();
void errorAtCheckBoards();
void boardReading(int boardID);
private slots:
void clearLogger();
void logError(const QString &errorPlace, const QString &errorString, unsigned errorCount, const QString &description);
void slotmultipleRegWrite();
void slotmultipleRegWriteAndSend();
void onConnectClicked();
void onReadButtonClicked();
void onReadReady();
void checkAdrChange(QModbusReply *reply, unsigned boardNum);
void onWriteButtonClicked();
void onSelectedBoardChanged(int index);
void onWriteTableChanged(int index);
void checkBoards();
void onSpeedUpdate();
void revertToOldSpeedAndRestart();
void onParityUpdate();
void boardScan(unsigned boardID);
public:
M3KTE(QWidget *parent = nullptr);
~M3KTE();
QModbusClient* getModbusDevice() const { return modbusDevice; }
QModbusReply* readSingleCoil(int boardID, int coilAddress);
void writeSingleCoil(int boardId, int coilAddress, bool value);
void writeSingleRegister(int boardId, int regAddress, quint16 value);
private:
Ui::M3KTE *ui;
QTableWidget *loggerTable = nullptr;
int CurrentConnectedDevice = 0;
QProgressBar *m_ProgressBar[320];
QPushButton *ThePhantomMenace[320];
QModbusReply *lastRequest = nullptr;
QModbusClient *modbusDevice = nullptr;
DeviceSettingsDialog *m_deviceSettingsDialog = nullptr;
DebugTerminalDialog *m_debugTerminalDialog = nullptr;
SettingsDialog *m_settingsDialog = nullptr;
MultipleSettings *m_regMultipleSettings = nullptr;
ParameterWorkspace *m_parameterWorkspace = nullptr;
ScanBoard *m_scanBoard = nullptr;
LineRinger *m_lineRinger = nullptr;
QGroupBox *Boards_Fields[4];
struct StatusM3KTE{
bool Warnings[4];
bool Accidents[4];
}statusM3KTE;
unsigned error_terminal;
struct BoardModbusRegisters {
bool isActive = false;
bool pollIsActive = true;
int adr;
int _tmp_adr;
bool coil[85];
unsigned HR[170];
unsigned error_W = 0;
unsigned error_A = 0;
unsigned error_modbus = 0;
unsigned error_baud_change = 0;
unsigned error_parity_change = 0;
unsigned error_RX = 0;
unsigned error_TX = 0;
unsigned error_adr_change = 0;
unsigned error_cmd_change = 0;
QLabel *timerData = nullptr;
QLabel *timerStatus = nullptr;
QLabel *localError = nullptr;
QCheckBox *localState[3] = {nullptr, nullptr, nullptr};
WriteRegisterModel *ModbusModelCoil;
WriteRegisterModel *ModbusModelHoldingReg;
QTimer *boardScanners;
bool isScan = false;
QElapsedTimer timerToStatusResponse;
QElapsedTimer timerToDataResponse;
}Boards[4];
union statusreg {
struct parsingFields {
unsigned accident:1;
unsigned warning:1;
unsigned poll_allowed:1;
unsigned mzkte_status:2;
unsigned reserv:3;
unsigned mzkte_error:8;
}ParsingReg;
unsigned AllReg:16;
};
};
#endif // M3KTE_H

14730
M3KTE_TERM/m3kte.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
int main(int argc, char *argv[])
{
qputenv("QT_FATAL_WARNINGS", "0"); // отключает падение от ASSERT
QApplication a(argc, argv);
M3KTE w;
w.show();

View File

@@ -0,0 +1,110 @@
#include "multiplesettings.h"
#include "ui_multiplesettings.h"
MultipleSettings::MultipleSettings(QWidget *parent) :
QDialog(parent),
ui(new Ui::MultipleSettings)
{
ui->setupUi(this);
ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Записать");
ui->buttonBox->button(QDialogButtonBox::SaveAll)->setText("Записать и установить");
selectedBoard = 0;
}
MultipleSettings::~MultipleSettings()
{
delete ui;
}
/**
* @brief Обработка нажатий кнопок в диалоговом окне настроек.
*
* В зависимости от нажатой кнопки выполняются разные действия:
* - Если нажата кнопка "Записать", сохраняются введенные параметры и вызывается сигнал write().
* - Если нажата кнопка "Записать и установить", сохраняются параметры и вызывается сигнал writeAndSend().
*
* @param button Указатель на нажатую кнопку.
*/
void MultipleSettings::on_buttonBox_clicked(QAbstractButton *button)
{
if(button == ui->buttonBox->button(QDialogButtonBox::Ok)) {
newValue = ui->regValueLine->text().toInt(nullptr, 16);
typeReg = ui->regTypeBox->currentIndex();
startAdr = ui->adrBox->value();
countReg = ui->countBox->value();
boardId = ui->boardBox->currentIndex();
emit write();
} else if(button == ui->buttonBox->button(QDialogButtonBox::SaveAll)) {
newValue = ui->regValueLine->text().toInt(nullptr, 16);
typeReg = ui->regTypeBox->currentIndex();
startAdr = ui->adrBox->value();
countReg = ui->countBox->value();
boardId = ui->boardBox->currentIndex();
emit writeAndSend();
}
}
/**
* @brief Обработка изменения выбранного типа регистра.
*
* В зависимости от выбранного типа регистра (index) и текущей выбранной платы (ui->boardBox),
* устанавливается диапазон допустимых значений для поля адреса (ui->adrBox).
* Также значение адреса сбрасывается на начальное.
*
* @param index Индекс выбранного типа регистра.
*/
void MultipleSettings::on_regTypeBox_currentIndexChanged(int index)
{
short maxRange = 0;
switch(ui->boardBox->currentIndex()) {
case 3:
maxRange = 64;
break;
default:
maxRange = 84;
}
switch(index) {
case 0:
case 1:
ui->adrBox->setRange(0, maxRange);
ui->adrBox->setValue(0);
break;
case 2:
ui->adrBox->setRange(85, 85 + maxRange);
ui->adrBox->setValue(85);
break;
}
}
/**
* @brief Обработка изменения выбранной платы в интерфейсе.
*
* Этот слот сохраняет выбранный индекс платы в переменную selectedBoard
* и обновляет диапазон адресов в зависимости от текущего типа регистра,
* вызывая обработчик on_regTypeBox_currentIndexChanged.
*
* @param index Индекс выбранной платы.
*/
void MultipleSettings::on_boardBox_currentIndexChanged(int index)
{
selectedBoard = index;
on_regTypeBox_currentIndexChanged(ui->regTypeBox->currentIndex());
}
/**
* @brief Обработка изменения значения адреса (adrBox).
*
* При изменении значения адреса (arg1) обновляется максимальный допустимый диапазон
* для количества регистров (countBox). Максимальное значение зависит от текущего
* выбранного номера платы, типа регистра и текущего адреса.
*
* Формула для расчета диапазона:
* max = (85 - (20 * (boardIndex / 3))) * (1 + (regTypeIndex / 2)) - arg1
*
* @param arg1 Новое значение адреса.
*/
void MultipleSettings::on_adrBox_valueChanged(int arg1)
{
ui->countBox->setRange(1, ((85 - (20 * (ui->boardBox->currentIndex() / 3)))
* (1 + (ui->regTypeBox->currentIndex() / 2)) - arg1));
}

View File

@@ -0,0 +1,40 @@
#ifndef MULTIPLESETTINGS_H
#define MULTIPLESETTINGS_H
#include <QDialog>
#include <QPushButton>
namespace Ui {
class MultipleSettings;
}
class MultipleSettings : public QDialog
{
Q_OBJECT
public:
explicit MultipleSettings(QWidget *parent = nullptr);
~MultipleSettings();
quint16 getNewValue() {return newValue;}
unsigned getStartAdr() {return startAdr;}
unsigned getCountReg() {return countReg;}
short getTypeReg() {return typeReg;}
short getBoardId() {return boardId;}
signals:
void write();
void writeAndSend();
private slots:
void on_buttonBox_clicked(QAbstractButton *button);
void on_regTypeBox_currentIndexChanged(int index);
void on_boardBox_currentIndexChanged(int index);
void on_adrBox_valueChanged(int arg1);
private:
Ui::MultipleSettings *ui;
quint16 newValue = 0;
unsigned startAdr;
unsigned countReg;
short typeReg;
short boardId;
short selectedBoard;
};
#endif // MULTIPLESETTINGS_H

View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MultipleSettings</class>
<widget class="QDialog" name="MultipleSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>381</width>
<height>128</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Уставка</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="4">
<widget class="QLabel" name="countLabel">
<property name="text">
<string>Кол-во</string>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QLabel" name="adrLabel">
<property name="text">
<string>Стартовый</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="boardLabel">
<property name="text">
<string>Плата</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="adrBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximum">
<number>84</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="boardBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
</widget>
</item>
<item row="1" column="4">
<widget class="QSpinBox" name="countBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>85</number>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QLineEdit" name="regValueLine"/>
</item>
<item row="0" column="5">
<widget class="QLabel" name="regValueLabel">
<property name="text">
<string>Значение (HEX)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="regTypeBox">
<item>
<property name="text">
<string>Coil</string>
</property>
</item>
<item>
<property name="text">
<string>Warning</string>
</property>
</item>
<item>
<property name="text">
<string>Accident</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="regTypeLabel">
<property name="text">
<string>Тип Регистра</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::SaveAll</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MultipleSettings</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MultipleSettings</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,78 @@
#include "parameterbox.h"
#include "ui_parameterbox.h"
ParameterBox::ParameterBox(QWidget *parent, pboxMode Mode, quint16 objectID) :
QWidget(parent),
ui(new Ui::ParameterBox)
{
ui->setupUi(this);
boxMode = Mode;
ID = objectID;
switch(boxMode) {
case Info:
ui->adrLine->setHidden(true);
ui->valueBox->setHidden(true);
ui->sendButton->setHidden(true);
break;
case MTemplate:
break;
}
ui->objectIdLabel->setText("0x" + QString::number(ID, 16));
}
ParameterBox::~ParameterBox()
{
delete ui;
}
/**
* @brief Обработчик нажатия кнопки отправки (sendButton).
*
* При нажатии проверяется, заполнено ли выбранное значение в valueBox.
* Если значение пустое, выполнение прерывается.
* В противном случае генерируется сигнал writeParameter с текущим адресом и выбранным значением (в шестнадцатеричном формате).
*/
void ParameterBox::on_sendButton_clicked()
{
if(ui->valueBox->currentText().isEmpty())
return;
emit writeParameter(ui->adrLine->text().toInt(), ui->valueBox->currentText().toInt(nullptr, 16));
}
/**
* @brief Установка текста параметра.
*
* Этот метод устанавливает переданный текст (data) в поле для имени параметра (nameLine).
*
* @param data Текст для отображения в поле имени.
*/
void ParameterBox::setData(QString data)
{
ui->nameLine->setText(data);
}
/**
* @brief Установка данных в параметры в зависимости от режима работы.
*
* В зависимости от текущего режима (boxMode) заполняются соответствующие элементы интерфейса:
* - При режиме Info заполняется только поле имени.
* - При режиме MTemplate заполняются поля имени, адреса и список значений.
*
* @param name Имя параметра.
* @param adr Адрес параметра.
* @param values Список значений для выбора.
*/
void ParameterBox::setData(QString name, QString adr, QStringList values)
{
switch(boxMode) {
case Info:
ui->nameLine->setText(name);
break;
case MTemplate:
ui->nameLine->setText(name);
ui->adrLine->setText(adr);
ui->valueBox->clear();
ui->valueBox->addItems(values);
break;
}
}

39
M3KTE_TERM/parameterbox.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef PARAMETERBOX_H
#define PARAMETERBOX_H
#include <QWidget>
namespace Ui {
class ParameterBox;
}
class ParameterBox : public QWidget
{
Q_OBJECT
public:
enum pboxMode{
Info = 0,
MTemplate = 1
};
enum objectType{
Coil = 0,
HR = 1
};
explicit ParameterBox(QWidget *parent = nullptr, pboxMode Mode = Info, quint16 objectID = 0x80);
~ParameterBox();
void setData(QString data);
void setData(QString name, QString adr, QStringList values);
quint16 getID(){return ID;}
objectType getType(){return type;}
signals:
void writeParameter(int adr, quint16 value);
private slots:
void on_sendButton_clicked();
private:
quint16 ID;
pboxMode boxMode;
objectType type = HR;
Ui::ParameterBox *ui;
};
#endif // PARAMETERBOX_H

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ParameterBox</class>
<widget class="QWidget" name="ParameterBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>43</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QLineEdit" name="adrLine">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="sendButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>SEND</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameLine">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="valueBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="objectIdLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,236 @@
#include "parameterdevice.h"
#include "ui_parameterdevice.h"
void sortParameterBoxesByID(QVector<ParameterBox*>& parameterBoxes) {
std::sort(parameterBoxes.begin(), parameterBoxes.end(),
[](ParameterBox* a, ParameterBox* b) {
return a->getID() < b->getID();
});
}
ParameterDevice::ParameterDevice(QWidget *parent) :
QWidget(parent),
ui(new Ui::ParameterDevice)
{
ui->setupUi(this);
}
ParameterDevice::~ParameterDevice()
{
delete ui;
}
/**
* @brief Обработка выбора режима "Выборочный" (selectiveRadio).
*
* Включает доступность элементов управления для выбора выборочного режима
* и устанавливает режим работы на `selectiveRequest`.
*/
void ParameterDevice::on_selectiveRadio_clicked()
{
// Включение элементов для выбора выборочного режима
ui->selectiveBox->setEnabled(true);
// Установка текущего режима
mode = selectiveRequest;
}
/**
* @brief Обработка выбора режима "Полный" (fullRadio).
*
* Выключает элементы управления для выборочного режима,
* а также устанавливает режим работы на `fullRequest`.
*/
void ParameterDevice::on_fullRadio_clicked()
{
// Отключение элементов для выборочного режима
ui->selectiveBox->setEnabled(false);
// Установка текущего режима
mode = fullRequest;
}
/**
* @brief Обработка нажатия на кнопку checkButton.
*
* Выполняет чтение данных по определенной логике (полный или выборочный запрос),
* отображает прогресс-бар, измеряет общее время выполнения и обновляет UI.
*
* @note В процессе выполнения очищает текущие параметры, инициирует последовательное чтение данных,
* отображает прогресс, а по завершении показывает затраченное время.
*/
void ParameterDevice::on_checkButton_clicked()
{
// Очистка старых параметров
for (ParameterBox* box : parameterBoxes)
if (box)
box->deleteLater();
parameterBoxes.clear();
quint16 strAdr;
int cnt = 0;
// Установка стартового адреса
strAdr = ui->adrSpin->value();
// В зависимости от режима задаем диапазон чтения
switch (mode) {
case fullRequest:
strAdr = 0x80;
cnt = 128;
break;
case selectiveRequest:
cnt = ui->countSpin->value();
break;
}
// Настройка таймера для измерения времени
QElapsedTimer timer;
// Создаем диалог прогресса
QProgressDialog progressDialog("Обработка...", "Отмена", 0, cnt, this);
progressDialog.setWindowModality(Qt::WindowModal);
progressDialog.setMinimumDuration(0); // показывать сразу же
// Создаем цикл событий для ожидания завершения
QEventLoop loop;
// Соединяем сигнал завершения передачи с выходом из цикла
connect(this, &ParameterDevice::transmitEnd, &loop, &QEventLoop::quit);
// Запускаем таймер
timer.start();
// Цикл чтения данных
for (int i = 0; i < cnt; i++) {
// Обновление прогресс-бара
progressDialog.setValue(i);
if (progressDialog.wasCanceled())
break; // пользователь отменил выполнение
// Формируем запрос
QByteArray data = QByteArray::fromHex("0E04");
data.append(strAdr + i);
QModbusRequest request(QModbusRequest::EncapsulatedInterfaceTransport, data);
// Отправка запроса
emit read(request, strAdr + i);
// Ожидание завершения передачи
loop.exec();
// Проверка на ошибку
if (errorAtTransmit) {
errorAtTransmit = false;
break;
}
}
// Расчет затраченного времени
qint64 elapsedNanoSeconds = timer.nsecsElapsed();
// Переводим в минуты, секунды, миллисекунды, микросекунды
qint64 totalMicroseconds = elapsedNanoSeconds / 1000;
qint64 totalMilliseconds = totalMicroseconds / 1000;
qint64 totalSeconds = totalMilliseconds / 1000;
qint64 totalMinutes = totalSeconds / 60;
qint64 remainingMicroseconds = totalMicroseconds % 1000;
qint64 remainingMilliseconds = totalMilliseconds % 1000;
qint64 remainingSeconds = totalSeconds % 60;
// Формируем строку для отображения времени
QString timeString = QString("%1 мин %2 сек %3 мс %4 мкс")
.arg(totalMinutes)
.arg(remainingSeconds)
.arg(remainingMilliseconds)
.arg(remainingMicroseconds);
// Выводим сообщение с временем выполнения
QMessageBox::information(this, "Общее время выполнения", timeString);
// Устанавливаем прогресс в конец
progressDialog.setValue(cnt);
// Сортируем параметры по ID и обновляем интерфейс
sortParameterBoxesByID(parameterBoxes);
sortScrollArea();
}
/**
* @brief Обработка ошибки при передаче данных.
*
* Показывает информационное сообщение с текстом ошибки,
* устанавливает флаг errorAtTransmit в true,
* а затем сигнализирует о завершении передачи.
*
* @param error Текст ошибки, который будет отображен пользователю.
*/
void ParameterDevice::setError(QString error)
{
QMessageBox::information(nullptr, "Получен ответ", error);
errorAtTransmit = true;
emit transmitEnd();
}
/**
* @brief Обработчик изменения значения спинбокса адреса (adrSpin).
*
* При изменении значения аргумента (arg1) обновляет диапазон допустимых
* значений для другого спинбокса (countSpin), чтобы сумма не превышала 256.
*
* Устанавливает диапазон: от 1 до (256 - текущего значения adrSpin).
*
* @param arg1 Новое значение адреса.
*/
void ParameterDevice::on_adrSpin_valueChanged(int arg1)
{
ui->countSpin->setRange(1, 256 - arg1);
}
/**
* @brief Установка ответа и создание нового параметра.
*
* Этот метод создает новый объект ParameterBox, устанавливает его данные,
* и в зависимости от типа параметра (Coil или HR) устанавливает соответствующие соединения.
* После этого добавляет его в список и сигнализирует о завершении передачи.
*
* @param reply Текст ответа, который отображается в параметре.
* @param objectID Идентификатор объекта, связанного с этим параметром.
*/
void ParameterDevice::setAnswer(QString reply, quint16 objectID)
{
// Создаем новый объект ParameterBox в режиме Info
ParameterBox *newbox = new ParameterBox(nullptr, ParameterBox::Info, objectID);
// Устанавливаем данные ответа в созданный параметр
newbox->setData(reply);
// В зависимости от типа параметра устанавливаем соединения
switch(newbox->getType()) {
case ParameterBox::Coil:
// Для типа Coil связываем сигнал writeParameter с выводом writeSingleCoil
connect(newbox, &ParameterBox::writeParameter, this, [this](int adr, quint16 value){
emit writeSingleCoil(adr, (bool)value);
});
break;
case ParameterBox::HR:
// Для типа HR связываем сигнал writeParameter с выводом writeSingleRegister
connect(newbox, &ParameterBox::writeParameter, this, [this](int adr, quint16 value){
emit writeSingleRegister(adr, value);
});
break;
}
// Добавляем созданный параметр в список
parameterBoxes.append(newbox);
// Посылаем сигнал о завершении передачи
emit transmitEnd();
}
/**
* @brief Перестроение (сортировка) содержимого внутри scrollArea.
*
* Этот метод очищает текущий лейаут внутри UI элемента parameterBoxHubContents,
* затем добавляет все параметры из списка parameterBoxes обратно в лейаут.
* После этого обновляются и перерасчитываются размеры элементов интерфейса.
*/
void ParameterDevice::sortScrollArea()
{
// Получение текущего лейаута внутри parameterBoxHubContents
QLayout* pblayout = ui->parameterBoxHubContents->layout();
// Если лейаут отсутствует, создаем новый и устанавливаем его
if (!pblayout) {
pblayout = new QVBoxLayout(ui->parameterBoxHubContents);
ui->parameterBoxHubContents->setLayout(pblayout);
}
// Очистка текущих элементов из лейаута
QLayoutItem *item;
while ((item = pblayout->takeAt(0)) != nullptr) {
delete item;
}
// Добавление всех параметров из списка parameterBoxes обратно в лейаут
for (int i = 0; i < parameterBoxes.count(); i++) {
pblayout->addWidget(parameterBoxes.at(i));
}
// Обновление размеров и отображения интерфейса
ui->parameterBoxHubContents->update();
ui->parameterBoxHubContents->adjustSize();
ui->scrollArea->updateGeometry();
ui->scrollArea->viewport()->update();
}

View File

@@ -0,0 +1,54 @@
#ifndef ParameterDevice_H
#define ParameterDevice_H
#include <QWidget>
#include <QModbusRequest>
#include "parameterbox.h"
#include <algorithm>
#include <QLayout>
#include <QTimer>
#include <QMessageBox>
#include <QProgressDialog>
#include <QElapsedTimer>
void sortParameterBoxesByID(QVector<ParameterBox*>& parameterBoxes);
namespace Ui {
class ParameterDevice;
}
class ParameterDevice : public QWidget
{
Q_OBJECT
public:
enum requestMode{
fullRequest = 0,
selectiveRequest = 1
};
void setAnswer(QString reply, quint16 objectID);
void setError(QString error);
void setAdr(int _adr){adr = _adr;}
int getAdr(){return adr;}
bool errorAtTransmit = false;
explicit ParameterDevice(QWidget *parent = nullptr);
~ParameterDevice();
signals:
void read(QModbusRequest request, quint16 objectID);
void writeSingleCoil(int coilAddress, bool value);
void writeSingleRegister(int registerAddress, quint16 value);
void transmitEnd();
private slots:
void on_selectiveRadio_clicked();
void on_fullRadio_clicked();
void on_checkButton_clicked();
void on_adrSpin_valueChanged(int arg1);
private:
void sortScrollArea();
private:
int adr;
QVector<ParameterBox*>parameterBoxes;
requestMode mode = fullRequest;
Ui::ParameterDevice *ui;
};
#endif // ParameterDevice_H

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ParameterDevice</class>
<widget class="QWidget" name="ParameterDevice">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>687</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Extended Objects</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1">
<widget class="QGroupBox" name="selectiveBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string/>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QSpinBox" name="adrSpin">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="prefix">
<string>0x</string>
</property>
<property name="minimum">
<number>128</number>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="displayIntegerBase">
<number>16</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Start Adr:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Count:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="countSpin">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="selectiveRadio">
<property name="text">
<string>Selective Request</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="checkButton">
<property name="text">
<string>Check</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="fullRadio">
<property name="text">
<string>Full Request</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="parameterBoxHubContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>380</width>
<height>450</height>
</rect>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,193 @@
#include "parameterworkspace.h"
#include "ui_parameterworkspace.h"
ParameterWorkspace::ParameterWorkspace(QWidget *parent) :
QWidget(parent),
ui(new Ui::ParameterWorkspace)
{
ui->setupUi(this);
}
ParameterWorkspace::~ParameterWorkspace()
{
delete ui;
}
/**
* @brief Устанавливает количество устройств и обновляет интерфейс.
*
* Очистка текущего списка устройств и удаления соответствующих вкладок,
* затем создание новых устройств и добавление их в интерфейс.
*
* @param count Новое количество устройств.
*/
void ParameterWorkspace::setDeviceCount(int count)
{
// Удаление и очистка текущих устройств
for (const auto& device : deviceList) {
device.tab->deleteLater(); ///< Удаление вкладки устройства
device.device->deleteLater(); ///< Удаление объекта устройства
}
deviceList.clear(); ///< Очистка списка устройств
// Удаление всех вкладок из таб-виджета
for (int i = 0; i < ui->tabWidget->count(); ++i) {
ui->tabWidget->removeTab(i);
}
// Создание новых устройств
for (int i = 0; i < count; ++i) {
auto _device = new device(); ///< Создаваемое устройство
deviceList.append(*_device); ///< Добавление в список
deviceList[i].device = new ParameterDevice(); ///< Создание объекта ParameterDevice
// Добавление вкладки для устройства
int newtab = ui->tabWidget->addTab(deviceList[i].device, tr("Device №%1").arg(i + 1));
deviceList[i].tab = ui->tabWidget->widget(newtab);
// Подключение сигналов от устройства к слотам
connect(deviceList[i].device, &ParameterDevice::read, this,
[this, i](QModbusRequest request, quint16 objectID) {
readDeviceIdentification(deviceList[i].device, request, deviceList[i].adr, objectID);
});
connect(deviceList[i].device, &ParameterDevice::writeSingleCoil, this,
[this, i](int coilAddress, bool value){
writeSingleCoil(deviceList[i].adr, coilAddress, value);
});
connect(deviceList[i].device, &ParameterDevice::writeSingleRegister, this,
[this, i](int registerAddress, quint16 value){
writeSingleRegister(deviceList[i].adr, registerAddress, value);
});
}
}
/**
* @brief Отправляет команду на изменение состояния одиночного котла по Modbus.
*
* Проверяет состояние соединения, создает запрос и отправляет его.
* Обрабатывает завершение запроса, проверяя наличие ошибок.
*
* @param adr Адрес устройства по Modbus.
* @param coilAddress Адрес катушки (котла).
* @param value Значение для установки (true — включено, false — выключено).
*/
void ParameterWorkspace::writeSingleCoil(int adr, int coilAddress, bool value)
{
// Проверка правильности состояния соединения
if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState)
return;
// Создание пакета данных для записи
QModbusDataUnit unit(QModbusDataUnit::Coils, coilAddress, 1);
unit.setValue(0, value ? 1 : 0);
// Отправка запроса
if (auto *reply = modbusDevice->sendWriteRequest(unit, adr)) {
// Обработка завершения запроса
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [reply]() {
// Проверка ошибок выполнения
if (reply->error() != QModbusDevice::NoError) {
// Обработка ошибок (можно добавить лог или сообщение)
}
reply->deleteLater(); // Очистка объекта
});
} else {
// Если ответ уже готов, очищаем его сразу
reply->deleteLater();
}
}
}
/**
* @brief Отправляет команду на запись одного регистрового значения по Modbus.
*
* Проверяет состояние соединения, создает запрос и отправляет его.
* Обрабатывает завершение запроса и возможные ошибки.
*
* @param adr Адрес устройства по Modbus.
* @param regAddress Адрес регистрового аргумента.
* @param value Значение для записи.
*/
void ParameterWorkspace::writeSingleRegister(int adr, int regAddress, quint16 value)
{
// Проверка правильности состояния соединения
if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState)
return;
// Создание пакета данных для записи
QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, regAddress, 1);
unit.setValue(0, value);
// Отправка запроса
if (auto *reply = modbusDevice->sendWriteRequest(unit, adr)) {
// Обработка завершения запроса
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [reply]() {
// Проверка наличия ошибок
if (reply->error() != QModbusDevice::NoError) {
// Можно добавить лог или обработку ошибок
}
reply->deleteLater(); // Очистка объекта
});
} else {
// Если ответ уже готов, очищаем его сразу
reply->deleteLater();
}
}
}
/**
* @brief Читает идентификационную информацию устройства по Modbus.
*
* Отправляет необработанный запрос к устройству и обрабатывает ответ.
* В случае успешного ответа извлекает данные и вызывает метод `setAnswer`.
* В случае ошибки вызывает `setError` и выводит сообщение в лог.
*
* @param device Указатель на устройство, которому предназначен ответ.
* @param request Объект запроса Modbus.
* @param adr Адрес устройства по Modbus.
* @param objectID Идентификатор объекта для обработки.
*/
void ParameterWorkspace::readDeviceIdentification(ParameterDevice *device, QModbusRequest request, int adr, quint16 objectID)
{
// Проверка состояния соединения
if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState)
return;
// Отправка сырого запроса к устройству
if (auto *reply = modbusDevice->sendRawRequest(request, adr)) {
// Обработка ответа, если он не завершен
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [device, reply, objectID]() {
// Проверка ошибок
if (reply->error() == QModbusDevice::NoError) {
QModbusResponse resp = reply->rawResult();
// Проверка размера данных
if (resp.data().size() >= MODBUS_REQUEST_PROTOCOL_INFO_LENGTH) {
// Удаление протокольной части данных
QByteArray data = resp.data();
data.remove(0, MODBUS_REQUEST_PROTOCOL_INFO_LENGTH);
QString result = QString::fromUtf8(data); // или другой метод преобразования, в зависимости от формата
device->setAnswer(result, objectID);
}
} else {
// Обработка ошибок
device->setError(reply->errorString());
qDebug() << "Получен ответ с ошибкой:" << reply->errorString();
}
reply->deleteLater(); // Очистка объекта
});
}
} else {
// Ошибка при отправке запроса
device->setError("Unknow error");
}
}
/**
* @brief Обновляет параметры устройства по идентификатору.
*
* Обновляет статус активности, адрес и устанавливает новый адрес в объекте устройства.
*
* @param ID Индекс или уникальный идентификатор устройства в списке.
* @param status Новый статус активности (true/false).
* @param adr Новый адрес устройства.
*/
void ParameterWorkspace::updateDevice(int ID, bool status, int adr)
{
deviceList[ID].isActive = status;
deviceList[ID].adr = adr;
deviceList[ID].device->setAdr(adr);
}

View File

@@ -0,0 +1,50 @@
#ifndef PARAMETERWORKSPACE_H
#define PARAMETERWORKSPACE_H
#include <QWidget>
#include <QDebug>
#include <QModbusRequest>
#include <QModbusDataUnit>
#include <QModbusTcpClient>
#include <QModbusRtuSerialMaster>
#include <QtSerialBus/qtserialbusglobal.h>
#if QT_CONFIG(modbus_serialport)
#include <QSerialPort>
#endif
#include "parameterdevice.h"
#define MODBUS_REQUEST_PROTOCOL_INFO_LENGTH 8
namespace Ui {
class ParameterWorkspace;
}
class ParameterWorkspace : public QWidget
{
Q_OBJECT
public:
explicit ParameterWorkspace(QWidget *parent = nullptr);
~ParameterWorkspace();
public:
void setModbusClient(QModbusClient *device){modbusDevice = device;}
void setDeviceCount(int count);
void updateDevice(int deviceID, bool status, int adr);
private slots:
void readDeviceIdentification(ParameterDevice *board, QModbusRequest request, int adr, quint16 objectID);
void writeSingleCoil(int adr, int coilAddress, bool value);
void writeSingleRegister(int adr, int registerAddress, quint16 value);
private:
struct device{
bool isActive = false;
int adr = 1;
ParameterDevice *device = nullptr;
QWidget *tab = nullptr;
};
QVector<device> deviceList;
QModbusClient *modbusDevice = nullptr;
Ui::ParameterWorkspace *ui;
};
#endif // PARAMETERWORKSPACE_H

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ParameterWorkspace</class>
<widget class="QWidget" name="ParameterWorkspace">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>610</width>
<height>646</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

65
M3KTE_TERM/scanboard.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "scanboard.h"
#include "ui_scanboard.h"
ScanBoard::ScanBoard(QWidget *parent) :
QDialog(parent),
ui(new Ui::ScanBoard)
{
ui->setupUi(this);
}
ScanBoard::~ScanBoard()
{
delete ui;
}
/**
* @brief Получает текущий состояние чекбокса.
* @return Текущий Qt::CheckState.
*/
Qt::CheckState ScanBoard::getCheckState()
{
return checkState;
}
/**
* @brief Обработчик изменения состояния чекбокса "Apply to All".
* @param arg1 Новое состояние чекбокса (целое число, преобразуемое в Qt::CheckState).
*
* Этот слот вызывается при изменении состояния чекбокса и сохраняет новое значение.
*/
void ScanBoard::on_applyToAllBox_stateChanged(int arg1)
{
checkState = static_cast<Qt::CheckState>(arg1);
}
/**
* @brief Выводит результат сканирования в лог.
* @param resultOfScan Строка с результатом сканирования.
*
* Добавляет результат сканирования в лог-виджет ui->logger.
*/
void ScanBoard::showMeTheTruth(QString resultOfScan)
{
ui->logger->append(resultOfScan);
}
/**
* @brief Получает текущий baud-скорость.
* @return Значение скорости в виде quint16.
*/
quint16 ScanBoard::getBaud()
{
return baud;
}
/**
* @brief Обработчик изменения текста в поле выбора скорости baud.
* @param arg1 Новое значение текса в comboBox.
*
* Парсит текст в целое число и сохраняет в переменную baud.
*/
void ScanBoard::on_baudRateBox_currentTextChanged(const QString &arg1)
{
baud = arg1.toInt(nullptr, 10);
}

29
M3KTE_TERM/scanboard.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef SCANBOARD_H
#define SCANBOARD_H
#include <QDialog>
namespace Ui {
class ScanBoard;
}
class ScanBoard : public QDialog
{
Q_OBJECT
public:
explicit ScanBoard(QWidget *parent = nullptr);
~ScanBoard();
Qt::CheckState getCheckState();
void showMeTheTruth(QString resultOfScan);
quint16 getBaud();
private slots:
void on_applyToAllBox_stateChanged(int arg1);
void on_baudRateBox_currentTextChanged(const QString &arg1);
private:
Qt::CheckState checkState;
quint16 baud;
Ui::ScanBoard *ui;
};
#endif // SCANBOARD_H

81
M3KTE_TERM/scanboard.ui Normal file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScanBoard</class>
<widget class="QDialog" name="ScanBoard">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>337</width>
<height>145</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QTextEdit" name="logger"/>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QComboBox" name="baudRateBox"/>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="applyToAllBox">
<property name="text">
<string>Применить ко всем обнаруженным платам</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ScanBoard</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ScanBoard</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,109 @@
#include "settingsdialog.h"
#include "ui_settingsdialog.h"
SettingsDialog::SettingsDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog)
{
ui->setupUi(this);
ui->parityCombo->setCurrentIndex(0);
#if QT_CONFIG(modbus_serialport)
ui->baudCombo->setCurrentText(QString::number(m_settings.baud));
ui->dataBitsCombo->setCurrentText(QString::number(m_settings.dataBits));
ui->stopBitsCombo->setCurrentText(QString::number(m_settings.stopBits));
on_updateComBox_clicked();
#endif
ui->timeoutSpinner->setValue(m_settings.responseTime);
ui->retriesSpinner->setValue(m_settings.numberOfRetries);
connect(ui->AcceptOrRejectButtonBox, &QDialogButtonBox::accepted, [this]() {
#if QT_CONFIG(modbus_serialport)
m_settings.portName = ui->comBox->currentData().toString();
m_settings.parity = ui->parityCombo->currentIndex();
if(m_settings.parity > 0)
m_settings.parity++;
m_settings.baud = ui->baudCombo->currentText().toInt();
m_settings.dataBits = ui->dataBitsCombo->currentText().toInt();
m_settings.stopBits = ui->stopBitsCombo->currentText().toInt();
#endif
m_settings.responseTime = ui->timeoutSpinner->value();
m_settings.numberOfRetries = ui->retriesSpinner->value();
hide();
});
}
SettingsDialog::~SettingsDialog()
{
delete ui;
}
/**
* @brief Возвращает текущие настройки.
* @return Объект настроек типа Settings.
*/
SettingsDialog::Settings SettingsDialog::settings() const
{
return m_settings;
}
/**
* @brief Обновляет индекс baud-скорости в UI и сохраняет значение в настройки.
* @param baud Индекс выбранной скорости.
* @return Новое значение baud, сохранённое в настройках.
*/
int SettingsDialog::UpdateBaud(int baud)
{
// Устанавливаем текущий индекс в comboBox
ui->baudCombo->setCurrentIndex(baud);
// Обновляем настройки и возвращаем новую скорость как число
return (m_settings.baud = ui->baudCombo->currentText().toInt());
}
/**
* @brief Обновляет индекс паритета в UI и сохраняет значение в настройки.
* @param parity Индекс выбранного паритета.
* @return Новое значение паритета, сохранённое в настройках.
*/
int SettingsDialog::UpdateParity(int parity)
{
// Устанавливаем текущий индекс
ui->parityCombo->setCurrentIndex(parity);
// Если parity > 0, увеличиваем его значение перед сохранением
if (parity > 0)
return (m_settings.parity = ++parity);
else
return (m_settings.parity = parity);
}
/**
* @brief Получает текущий индекс выбранной baud-скорости.
* @return Индекс выбранного элемента в comboBox.
*/
int SettingsDialog::curBaud()
{
return ui->baudCombo->currentIndex();
}
/**
* @brief Получает текущий индекс выбранного паритета.
* @return Индекс выбранного элемента в comboBox.
*/
int SettingsDialog::curParity()
{
return ui->parityCombo->currentIndex();
}
/**
* @brief Обновляет список доступных COM-портов.
*
* Этот метод вызывается при нажатии кнопки, обновляя список портов в UI.
* Он очищает текущий список и заполняет его всеми доступными портами.
*/
void SettingsDialog::on_updateComBox_clicked()
{
ui->comBox->clear();
const auto listPorts = QSerialPortInfo::availablePorts();
for (const auto& port : listPorts) {
// Добавляем элемент с именем порта и производителем
ui->comBox->addItem(QString("%1: %2").arg(port.portName(), port.manufacturer()), QVariant(port.portName()));
}
}

View File

@@ -0,0 +1,44 @@
#ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#include <QDialog>
#include <QtSerialBus/QModbusDataUnit>
#include <QtSerialBus/qtserialbusglobal.h>
#if QT_CONFIG(modbus_serialport)
#include <QSerialPort>
#include <QSerialPortInfo>
#endif
namespace Ui {
class SettingsDialog;
}
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
struct Settings {
QString portName;
int parity = QSerialPort::NoParity;
int baud = 115200;
int dataBits = QSerialPort::Data8;
int stopBits = QSerialPort::OneStop;
int responseTime = 1000;
int numberOfRetries = 0;
};
explicit SettingsDialog(QWidget *parent = nullptr);
~SettingsDialog();
Settings settings() const;
int UpdateBaud(int baud);
int UpdateParity(int parity);
int curBaud();
int curParity();
private slots:
void on_updateComBox_clicked();
private:
Settings m_settings;
Ui::SettingsDialog *ui;
};
#endif // SETTINGSDIALOG_H

View File

@@ -0,0 +1,315 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>311</width>
<height>288</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1" colspan="2">
<widget class="QSpinBox" name="timeoutSpinner">
<property name="suffix">
<string> мс</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>200</number>
</property>
</widget>
</item>
<item row="2" column="1" rowspan="2" colspan="2">
<widget class="QSpinBox" name="retriesSpinner">
<property name="enabled">
<bool>true</bool>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="text">
<string>Тайм-аут ответа:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>127</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Serial Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="parityLabel">
<property name="text">
<string>Чётность</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="baudCombo">
<property name="currentIndex">
<number>7</number>
</property>
<item>
<property name="text">
<string>9600</string>
</property>
</item>
<item>
<property name="text">
<string>14400</string>
</property>
</item>
<item>
<property name="text">
<string>19200</string>
</property>
</item>
<item>
<property name="text">
<string>31250</string>
</property>
</item>
<item>
<property name="text">
<string>38400</string>
</property>
</item>
<item>
<property name="text">
<string>56000</string>
</property>
</item>
<item>
<property name="text">
<string>57600</string>
</property>
</item>
<item>
<property name="text">
<string>115200</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="dataBitsLabel">
<property name="text">
<string>Биты данных</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="stopBitsCombo">
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>3</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="baudLabel">
<property name="text">
<string>Скорость</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="parityCombo">
<item>
<property name="text">
<string>No</string>
</property>
</item>
<item>
<property name="text">
<string>Even</string>
</property>
</item>
<item>
<property name="text">
<string>Odd</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="dataBitsCombo">
<property name="currentIndex">
<number>3</number>
</property>
<item>
<property name="text">
<string>5</string>
</property>
</item>
<item>
<property name="text">
<string>6</string>
</property>
</item>
<item>
<property name="text">
<string>7</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="portLabel">
<property name="text">
<string>Порт</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="stopBitsLabel">
<property name="text">
<string>Стоп-биты</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QComboBox" name="comBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="updateComBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>45</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Поиск</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" rowspan="2">
<widget class="QLabel" name="retriesLabel">
<property name="text">
<string>Количество повторов:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDialogButtonBox" name="AcceptOrRejectButtonBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>AcceptOrRejectButtonBox</sender>
<signal>accepted()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>AcceptOrRejectButtonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,227 @@
#include "writeregistermodel.h"
enum { NumColumn = 0, NameColumn = 1, CoilsColumn = 2, HoldingColumn = 3, ColumnCount = 5, CurrentUColumn = 4};
WriteRegisterModel::WriteRegisterModel(QObject *parent, int _tmpRC, bool _isHR)
: QAbstractTableModel(parent),
m_coils(RowCount=_tmpRC, false), m_holdingRegisters(RowCount=_tmpRC, 0u), m_currentU(RowCount=_tmpRC)
{
isHR=_isHR;
}
int WriteRegisterModel::rowCount(const QModelIndex &/*parent*/) const
{
return RowCount;
}
int WriteRegisterModel::columnCount(const QModelIndex &/*parent*/) const
{
return ColumnCount;
}
/**
* @brief Возвращает данные модели для отображения в представлении.
*
* В зависимости от роли и столбца возвращает соответствующее значение:
* - номер строки;
* - имя;
* - состояние чекбокса;
* - значение в Вольтах для регистров и текущего напряжения.
*
* @param index Индекс элемента модели.
* @param role Роль запрашиваемых данных.
* @return Значение данных в виде QVariant.
*/
QVariant WriteRegisterModel::data(const QModelIndex &index, int role) const
{
// Проверка корректности индекса
if (!index.isValid() || index.row() >= RowCount || index.column() >= ColumnCount)
return QVariant();
// Проверка соответствия размеров
Q_ASSERT(m_coils.count() == RowCount);
Q_ASSERT(m_holdingRegisters.count() == RowCount);
// Обработка столбца с номером
if (index.column() == NumColumn && role == Qt::DisplayRole)
return QString::number(index.row());
// Обработка столбца с именем
if (index.column() == NameColumn && role == Qt::DisplayRole)
return QString("ТЭ%1").arg(index.row() % (RowCount / (1 + isHR)));
// Обработка чекбокса
if (index.column() == CoilsColumn && role == Qt::CheckStateRole)
return m_coils.at(index.row()) ? Qt::Checked : Qt::Unchecked;
// Обработка значения в регистре (в Вольтах)
if (index.column() == HoldingColumn && role == Qt::DisplayRole)
return QString("%1 В").arg(QString::number((double)m_holdingRegisters.at(index.row()) / 1000.0, 'f', 3));
// Обработка текущего напряжения (в Вольтах)
if (index.column() == CurrentUColumn && role == Qt::DisplayRole)
return QString("%1 В").arg(QString::number((double)m_currentU.at(index.row()) / 1000.0, 'f', 3));
// Возврат пустого QVariant по умолчанию
return QVariant();
}
/**
* @brief Возвращает заголовки столбцов для отображения в представлении.
*
* Обеспечивает отображение названий для горизонтальных заголовков.
*
* @param section Индекс столбца.
* @param orientation Ориентация (горизонтальная или вертикальная).
* @param role Роль данных, обычно Qt::DisplayRole.
* @return Заголовок в виде QVariant или пустой QVariant при несоответствии.
*/
QVariant WriteRegisterModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case NumColumn:
return QStringLiteral("#Reg");
case NameColumn:
return QStringLiteral("Fuel Cell");
case CoilsColumn:
return QStringLiteral("Coils");
case HoldingColumn:
return QStringLiteral("Holding Registers");
case CurrentUColumn:
return QStringLiteral("Current U");
default:
break;
}
}
return QVariant();
}
/**
* @brief Устанавливает данные модели по индексу.
*
* Обработка для чекбоксов (CoilsColumn) и регистров (HoldingColumn).
*
* @param index Индекс элемента.
* @param value Новое значение.
* @param role Роль, определяющая тип данных (например, Qt::CheckStateRole, Qt::EditRole).
* @return true, если данные успешно установлены; иначе false.
*/
bool WriteRegisterModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
// Проверяем валидность индекса и границы
if (!index.isValid() || index.row() >= RowCount || index.column() >= ColumnCount)
return false;
// Проверка размеров массивов
Q_ASSERT(m_coils.count() == RowCount);
Q_ASSERT(m_holdingRegisters.count() == RowCount);
// Обработка для чекбоксов
if (index.column() == CoilsColumn && role == Qt::CheckStateRole) {
auto state = static_cast<Qt::CheckState>(value.toUInt());
if (state == Qt::Checked)
m_coils.setBit(index.row());
else
m_coils.clearBit(index.row());
emit dataChanged(index, index);
return true;
}
// Обработка для регистров
if (index.column() == HoldingColumn && role == Qt::EditRole) {
bool result = false;
// Преобразование QVariant в строку, затем в число
QString valueStr = value.toString();
quint16 newValue = valueStr.toUShort(&result, 10);
if (result) {
m_holdingRegisters[index.row()] = newValue;
emit dataChanged(index, index);
}
return result;
}
// Если не обработано, вернуть false
return false;
}
/**
* @brief Устанавливает флаги для элементов модели, определяя их поведение.
*
* @param index Индекс элемента модели.
* @return Флаги, определяющие свойства элемента.
*/
Qt::ItemFlags WriteRegisterModel::flags(const QModelIndex &index) const
{
// Проверка валидности индекса
if (!index.isValid() || index.row() >= RowCount || index.column() >= ColumnCount)
return QAbstractTableModel::flags(index);
// Получение базовых флагов
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
// Отключение элементов вне диапазона адресов
if ((index.row() < m_address) || (index.row() >= (m_address + m_number)))
flags &= ~Qt::ItemIsEnabled;
// Установка флага чекбокса для столбца Coils
if (index.column() == CoilsColumn)
return flags | Qt::ItemIsUserCheckable;
// Установка флага редактируемости для столбца HoldingRegisters
if (index.column() == HoldingColumn)
return flags | Qt::ItemIsEditable;
// Возврат текущих флагов по умолчанию
return flags;
}
/**
* @brief Устанавливает начальный адрес для модели.
*
* @param address Начальный адрес.
*/
void WriteRegisterModel::setStartAddress(int address)
{
m_address = address;
}
/**
* @brief Устанавливает число значений (величин) на основе строки.
*
* @param number Строковое представление числа.
*/
void WriteRegisterModel::setNumberOfValues(const QString &number)
{
m_number = number.toInt();
}
/**
* @brief Получает состояние coil по индексу.
*
* @param index Индекс элемента.
* @return true, если coil активен; иначе false.
*/
bool WriteRegisterModel::get_coil(const QModelIndex &index)
{
return m_coils.at(index.row());
}
/**
* @brief Получает значение holding register по индексу.
*
* @param index Индекс элемента.
* @return Значение регистров.
*/
uint WriteRegisterModel::get_holreg(const QModelIndex &index)
{
return m_holdingRegisters.at(index.row());
}
/**
* @brief Устанавливает текущие значения U для указанных индексов.
*
* Изначально обновляет значение в массиве m_currentU по указанному индексу.
* Если isHR установлено в true, также обновляет значение по индексу, смещённому на m_number.
*
* @param _tmpU Новое значение U.
* @param index Индекс элемента.
* @return Всегда возвращает true.
*/
bool WriteRegisterModel::set_currentU(unsigned _tmpU, unsigned index)
{
// Установка значения для текущего индекса
m_currentU[index] = _tmpU;
// Если isHR активен, обновляем также и по смещенному индексу
if (isHR)
m_currentU[index + m_number] = _tmpU;
return true;
}

View File

@@ -0,0 +1,36 @@
#ifndef WRITEREGISTERMODEL_H
#define WRITEREGISTERMODEL_H
#include <QAbstractItemModel>
#include <QBitArray>
#include <QObject>
class WriteRegisterModel : public QAbstractTableModel
{
private:
bool isHR;
int RowCount;
public:
WriteRegisterModel(QObject *parent = nullptr, int _tmpRC = 85, bool _isHR = false);
bool get_coil(const QModelIndex &index);
uint get_holreg(const QModelIndex &index);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool set_currentU(unsigned _tmpU, unsigned index);
public slots:
void setStartAddress(int address);
void setNumberOfValues(const QString &number);
signals:
void updateViewport();
public:
int m_number = 0;
int m_address = 0;
QBitArray m_coils;
QVector<quint16> m_holdingRegisters;
QVector<quint16> m_currentU;
};
#endif // WRITEREGISTERMODEL_H

BIN
MZKT_Test_Terminal.exe Normal file

Binary file not shown.

21
Release/.qmake.stash Normal file
View File

@@ -0,0 +1,21 @@
QMAKE_CXX.QT_COMPILER_STDCXX = 201402L
QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 7
QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 3
QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0
QMAKE_CXX.COMPILER_MACROS = \
QT_COMPILER_STDCXX \
QMAKE_GCC_MAJOR_VERSION \
QMAKE_GCC_MINOR_VERSION \
QMAKE_GCC_PATCH_VERSION
QMAKE_CXX.INCDIRS = \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include/c++ \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include/c++/i686-w64-mingw32 \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include/c++/backward \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0/include-fixed \
C:/Qt/Qt5.14.2/Tools/mingw730_32/i686-w64-mingw32/include
QMAKE_CXX.LIBDIRS = \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc/i686-w64-mingw32/7.3.0 \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib/gcc \
C:/Qt/Qt5.14.2/Tools/mingw730_32/i686-w64-mingw32/lib \
C:/Qt/Qt5.14.2/Tools/mingw730_32/lib

Binary file not shown.

View File

@@ -1,15 +0,0 @@
#include "m3kte.h"
#include "ui_m3kte.h"
M3KTE::M3KTE(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::M3KTE)
{
ui->setupUi(this);
}
M3KTE::~M3KTE()
{
delete ui;
}

21
m3kte.h
View File

@@ -1,21 +0,0 @@
#ifndef M3KTE_H
#define M3KTE_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class M3KTE; }
QT_END_NAMESPACE
class M3KTE : public QMainWindow
{
Q_OBJECT
public:
M3KTE(QWidget *parent = nullptr);
~M3KTE();
private:
Ui::M3KTE *ui;
};
#endif // M3KTE_H

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>M3KTE</class>
<widget class="QMainWindow" name="M3KTE">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>M3KTE</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar"/>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

0
test.bmp Normal file
View File

373
ТЗ/modbus_data.h Normal file
View File

@@ -0,0 +1,373 @@
/**
**************************************************************************
* @file modbus_data.h
* @brief Заголовочный файл с описанием даты MODBUS.
* @details Данный файл необходимо подключается в rs_message.h. После rs_message.h
* подключается к основному проекту.
*
* @defgroup MODBUS_DATA
* @ingroup MODBUS
* @brief Modbus data description
*
*************************************************************************/
#include "stdint.h"
//--------------DEFINES FOR REGISTERS---------------
// DEFINES FOR ARRAYS
/**
* @addtogroup MODBUS_DATA_RERISTERS_DEFINES
* @ingroup MODBUS_DATA
* @brief Defines for registers
Структура дефайна адресов
@verbatim
Для массивов регистров:
R_<NAME_ARRAY>_ADDR - модбас адресс первого регистра в массиве
R_<NAME_ARRAY>_QNT - количество регистров в массиве
@endverbatim
* @{
*/
/**
* @brief Скорость обмена
*/
typedef enum //MB_SpeedTypeDef
{
MB_9600bs = 0x0000, ///< Скорость 9600 б/с
MB_14400bs = 0x0001, ///< Скорость 14400 б/с
MB_19200bs = 0x0002, ///< Скорость 19200 б/с
MB_31250bs = 0x0003, ///< Скорость 31250 б/с
MB_38400bs = 0x0004, ///< Скорость 38400 б/с
MB_56000bs = 0x0005, ///< Скорость 56000 б/с
MB_57600bs = 0x0006, ///< Скорость 57600 б/с
MB_115200bs = 0x0007, ///< Скорость 115200 б/с
mb16bit = 0x1000, ///< костыль чтобы enum был 16-битный... или забить на enum, пока не определился
}MB_SpeedTypeDef;
/**
* @brief Контроль четности
*/
typedef enum //MB_ParityCtrlTypeDef
{
NoParityCtrl = 0x0000, ///< контроля нет
EvenParityCtrl = 0x0400, ///< четный контроль
OddParityCtrl = 0x0600, ///< нечетый контроль
}MB_ParityCtrlTypeDef;
/**
* @brief Регистры хранения для уставок параметров МКЗТЭ
*/
typedef struct //MB_DataHoldRegsTypeDef
{
uint16_t Warning_Setpoints[85]; /*!< @brief Адреса 0-84: Уставки «Предупреждение»
@details Задает пороговое напряжение, при достижении которого будет сформирован сигнал «Предупреждение».
Максимальное значение (записываемое в регистр) - 1100 (1,1 В *1000) */
uint16_t Errors_Setpoints[85]; /*!< @brief Адреса 85-169: Уставки «Авария»
@details Задает пороговое напряжение, при достижении которого будет сформирован сигнал «Авария».
Максимальное значение (записываемое в регистр) - 1100 (1,1 В *1000) */
uint16_t Commands_Mode; /*!< @brief Адрес 170: Уставка «Команды»
@details Принимаемые значения:
- 0x0000 стандартная работа
- 0x0001 запрет опроса ТЭ (активен только обмен с ЛСУ ЭС, ТЭ не контролируются) */
uint16_t reserved; ///< Адрес 171 зарезервирован
uint16_t MZKTE_Network_Adress; /*!< @brief Адрес 172: Уставка «Сетевой адрес МЗКТЭ»
@details При удачной записи этого регистра ответный фрейм не отправляется */
MB_SpeedTypeDef Modbus_Speed:16; /*!< @brief Адрес 173: Уставка «Скорость обмена»
@details Принимаемые значения описаны в @ref MB_SpeedTypeDef.
При удачной записи этого регистра ответный фрейм не отправляется */
MB_ParityCtrlTypeDef Parity_Control:16; /*!< @brief Адрес 174: Уставка «Команды»
@details Принимаемые значения описаны в @ref MB_ParityCtrlTypeDef.
При удачной записи этого регистра ответный фрейм не отправляется */
}MB_DataHoldRegsTypeDef;
/**
* @brief Номер неисправности МЗКТЭ
*/
typedef enum //MB_MZKTEErrorsTypeDef
{
No_Err = 0x00, ///< Неисправность отсутствует
Err_Digit_5V_Power = 0x01, ///< Неисправность цифрового источника питания +5 В
Err_Analog_15V_5V_3V_Power = 0x02, ///< Неисправность аналогового источника питания (±15 В/+5 В/+3,3 В)
Err_SCI_5V_Power = 0x03, ///< Неисправность источника питания последовательных интерфейсов микроконтроллера +5 В
Err_24V_Power = 0x04, ///< Неисправность источника питания +24 В
Program_Err_1 = 0x05, ///< Программная ошибка 1
Program_Err_2 = 0x06, ///< Программная ошибка 2
Program_Err_3 = 0x07, ///< Программная ошибка 3
Program_Err_4 = 0x08, ///< Программная ошибка 4
Program_Err_5 = 0x09, ///< Программная ошибка 5
Program_Err_6 = 0x0A, ///< Программная ошибка 6
Program_Err_7 = 0x0B, ///< Программная ошибка 7
Program_Err_8 = 0x0C, ///< Программная ошибка 8
}MB_MZKTEErrorsTypeDef;
/**
* @brief Сборный параметр
* @details Информация о состоянии МЗКТЭ
*/
typedef union //MB_MZKTEStatusTypeDef
{
uint16_t all; ///< Доступ к регистру целиком
struct
{
unsigned TE_ErrActive:1; /*!< @brief Бит [0]: Авария на ТЭ
@details Состояния:
- 0 - напряжения на всех ТЭ выше аварийных порогов, задаваемых уставками «Авария»
- 1 - напряжение на одном или нескольких ТЭ достигло или ниже аварийного порога, задаваемого уставкой «Авария» */
unsigned TE_WarnActive:1; /*!< @brief Бит [1]: Предупреждения на ТЭ
@details Состояния:
- 0 - напряжения на всех ТЭ выше предупредительных порогов, задаваемых уставкой «Предупреждение»
- 1 - напряжение на одном или нескольких ТЭ достигло или ниже предупредительного порога, задаваемого уставкой «Предупреждение» */
unsigned Opros_TE:1; /*!< @brief Бит [2]: Разрешение опроса ТЭ
@details Состояния:
- 0 опрос ТЭ разрешен
- 1 опрос ТЭ запрещен */
unsigned MZKTE_ErrStatus:2; /*!< @brief Биты [4:3]: Состояние МЗКТЭ
@details Состояния:
- 0 - МЗКТЭ функционирует нормально. Идет опрос ТЭ.
- 1 - неисправность МЗКТЭ, при которой МЗКТЭ может выполнять свои основные функции
(некоторые программные ошибки из @ref MB_MZKTEErrorsTypeDef)
- 2 - Неисправность МЗКТЭ, при которой выполнение основных функций не представляется возможным
(ошибки 1-3 и некоторые программные ошибки из @ref MB_MZKTEErrorsTypeDef) */
unsigned reserved:3; ///< Биты [7:5] зарезервированны
MB_MZKTEErrorsTypeDef MZKTE_Error:8; /*!< @brief Биты [15:8]: Номер неисправности МЗКТЭ
@details Номера неисправностей описаны в @ref MB_MZKTEErrorsTypeDef */
}param; ///< Доступ к регистру по параметрам
}MB_MZKTEStatusTypeDef;
/**
* @brief Входные регистры для контроля состояния МЗКТЭ
*/
typedef struct //MB_DataInRegsTypeDef
{
uint16_t U_TE[85]; /*!< @brief Адреса 0-84: Текущие значения напряжения ТЭ
@details Значения передаются умноженными на 1000 (т.е. если Uтэ = 0,625 В, то будет передано число 625) */
MB_MZKTEStatusTypeDef Status; /*!< @brief Адрес 85: Сборный параметр
@details Информация о состоянии МЗКТЭ @ref MB_MZKTEStatusTypeDef */
}MB_DataInRegsTypeDef;
// DEFINES FOR REGISTERS ARRAYS
#define R_INREG_ADDR 0
#define R_INREG_QNT 86
#define R_HOLDREG_ADDR 0
#define R_HOLDREG_QNT 175
// DEFINES FOR REGISTERS LOCAL ADDRESSES
//#define R_SET_ERROR(_te_num_) 0
/** MODBUS_DATA_RERISTERS_DEFINES
* @}
*/
//----------------DEFINES FOR COILS-----------------
/**
* @addtogroup MODBUS_DATA_COILS_DEFINES
* @ingroup MODBUS_DATA
* @brief Defines for coils
@verbatim
Структура дефайна
Для массивов коилов:
C_<NAME_ARRAY>_ADDR - модбас адресс первого коила в массиве
C_<NAME_ARRAY>_QNT - количество коилов в массиве (минимум 16)
@endverbatim
* @{
*/
/**
* @brief Флаги исключения ТЭ из алгоритма формирования сигналов «Предупреждение» и «Авария»
* @details В случае установки нулевого значения конкретной ячейки значение напряжения ТЭ,
* связанного с этой ячейкой, не будет учитываться при формировании сигналов «Предупреждение» и «Авария»
*/
typedef struct //MB_DataCoilsTypeDef
{
union
{
struct
{
uint64_t TE0_63; ///< Ячейки ТЭ №0-63
uint32_t TE64_84:21; ///< Ячейки ТЭ №64-84
}all;
struct
{
unsigned TE0_Exclude:1;
unsigned TE1_Exclude:1;
unsigned TE2_Exclude:1;
unsigned TE3_Exclude:1;
unsigned TE4_Exclude:1;
unsigned TE5_Exclude:1;
unsigned TE6_Exclude:1;
unsigned TE7_Exclude:1;
unsigned TE8_Exclude:1;
unsigned TE9_Exclude:1;
unsigned TE10_Exclude:1;
unsigned TE11_Exclude:1;
unsigned TE12_Exclude:1;
unsigned TE13_Exclude:1;
unsigned TE14_Exclude:1;
unsigned TE15_Exclude:1;
unsigned TE16_Exclude:1;
unsigned TE17_Exclude:1;
unsigned TE18_Exclude:1;
unsigned TE19_Exclude:1;
unsigned TE20_Exclude:1;
unsigned TE21_Exclude:1;
unsigned TE22_Exclude:1;
unsigned TE23_Exclude:1;
unsigned TE24_Exclude:1;
unsigned TE25_Exclude:1;
unsigned TE26_Exclude:1;
unsigned TE27_Exclude:1;
unsigned TE28_Exclude:1;
unsigned TE29_Exclude:1;
unsigned TE30_Exclude:1;
unsigned TE31_Exclude:1;
unsigned TE32_Exclude:1;
unsigned TE33_Exclude:1;
unsigned TE34_Exclude:1;
unsigned TE35_Exclude:1;
unsigned TE36_Exclude:1;
unsigned TE37_Exclude:1;
unsigned TE38_Exclude:1;
unsigned TE39_Exclude:1;
unsigned TE40_Exclude:1;
unsigned TE41_Exclude:1;
unsigned TE42_Exclude:1;
unsigned TE43_Exclude:1;
unsigned TE44_Exclude:1;
unsigned TE45_Exclude:1;
unsigned TE46_Exclude:1;
unsigned TE47_Exclude:1;
unsigned TE48_Exclude:1;
unsigned TE49_Exclude:1;
unsigned TE50_Exclude:1;
unsigned TE51_Exclude:1;
unsigned TE52_Exclude:1;
unsigned TE53_Exclude:1;
unsigned TE54_Exclude:1;
unsigned TE55_Exclude:1;
unsigned TE56_Exclude:1;
unsigned TE57_Exclude:1;
unsigned TE58_Exclude:1;
unsigned TE59_Exclude:1;
unsigned TE60_Exclude:1;
unsigned TE61_Exclude:1;
unsigned TE62_Exclude:1;
unsigned TE63_Exclude:1;
unsigned TE64_Exclude:1;
unsigned TE65_Exclude:1;
unsigned TE66_Exclude:1;
unsigned TE67_Exclude:1;
unsigned TE68_Exclude:1;
unsigned TE69_Exclude:1;
unsigned TE70_Exclude:1;
unsigned TE71_Exclude:1;
unsigned TE72_Exclude:1;
unsigned TE73_Exclude:1;
unsigned TE74_Exclude:1;
unsigned TE75_Exclude:1;
unsigned TE76_Exclude:1;
unsigned TE77_Exclude:1;
unsigned TE78_Exclude:1;
unsigned TE79_Exclude:1;
unsigned TE80_Exclude:1;
unsigned TE81_Exclude:1;
unsigned TE82_Exclude:1;
unsigned TE83_Exclude:1;
unsigned TE84_Exclude:1;
}bit; ///< Биты для доступа к каждой ячейке ТЭ
}Exclude; ///< Юнион для исключения ТЭ
}MB_DataCoilsTypeDef;
// DEFINES FOR COIL ARRAYS
#define C_TE_EXCLUDE_ADDR 0
#define C_TE_EXCLUDE_QNT 84
/** MODBUS_DATA_COILS_DEFINES
* @}
*/
//-----------MODBUS DEVICE DATA SETTING-------------
// MODBUS DATA STRUCTTURE
/**
* @brief Структура со всеми регистрами и коилами модбас
* @ingroup MODBUS_DATA
*/
typedef struct // mzkt modbus data
{
MB_DataInRegsTypeDef InRegs; ///< Modbus input registers @ref MB_DataInRegsTypeDef
MB_DataCoilsTypeDef Coils; ///< Modbus coils @ref MB_DataCoilsTypeDef
MB_DataHoldRegsTypeDef HoldRegs; ///< Modbus holding registers @ref MB_DataHoldRegsTypeDef
}MB_DataStructureTypeDef;
extern MB_DataStructureTypeDef MB_DATA;
/////////////////////////////////////////////////////////////
///////////////////////TEMP/OUTDATE/OTHER////////////////////
//typedef enum //MB_MZKTECommandsTypeDef
//{
// StandartMode = 0x00, ///< Стандартная работа
// Opros_TE_Disable = 0x01, ///< Запрет опроса ТЭ (активен только обмен с ЛСУ ЭС, ТЭ не контролируются)
//}MB_MZKTECommandsTypeDef;
///**
// * @brief Состояние МЗКТЭ
// */
//typedef enum //MB_MZKTEErrStatusTypeDef
//{
// MZKTE_OK = 0x0, ///< МЗКТЭ функционирует нормально. Идет опрос ТЭ.
// NonCritical_Err = 0x1, ///< Неисправность МЗКТЭ, при которой МЗКТЭ может выполнять свои основные функции (некоторые программные ошибки из @ref MB_MZKTEErrorsTypeDef).
// Critical_Err = 0x2, ///< Неисправность МЗКТЭ, при которой выполнение основных функций не представляется возможным (ошибки 1-3 и некоторые программные ошибки из @ref MB_MZKTEErrorsTypeDef)
//
//}MB_MZKTEErrStatusTypeDef;
//typedef enum
//{
// TE_No_Err = 0x0, ///< Напряжения на всех ТЭ выше аварийных порогов, задаваемых уставками «Авария»
// TE_Err = 0x1, ///< Напряжение на одном или нескольких ТЭ достигло или ниже аварийного порога, задаваемого уставкой «Авария»
//}MB_TEErrActiveTypeDef;
//typedef enum
//{
// TE_No_Warn = 0x0, ///< Напряжения на всех ТЭ выше предупредительных порогов, задаваемых уставкой «Предупреждение»
// TE_Warn = 0x1, ///< Напряжение на одном или нескольких ТЭ достигло или ниже предупредительного порога, задаваемого уставкой «Предупреждение»
//}MB_TEWarnActiveTypeDef;
//typedef enum
//{
// OprosTE_Enable = 0x0, ///< Опрос ТЭ разрешен
// OprosTE_Disable = 0x1, ///< Опрос ТЭ запрещен (см. регистр хранения 170)
//}MB_OprosTETypeDef;

37
ТЗ/неофтз.txt Normal file
View File

@@ -0,0 +1,37 @@
По модбасу: нужна терминалка для общения с МЗКТЭ. Вот краткое ТЗ
Команды:
0x01 Read Coils
0x03 Read Holding Registers
0x04 Read Input Registers
0x05 Write Single Coil
0x06 Write Single Register
0x0F Write Multiple Coils
0x10 Write Multiple Registers
Т.е. обращение будет к следующим типам данных:
- Входные регистры (MB_DataInRegsTypeDef),
- Регистры хранения (MB_DataHoldRegsTypeDef),
- Коилы (MB_DataCoilsTypeDef).
Там некоторые регистры еще парсяться, поэтому скидываю файл с структурой данных, который я сделал. В скобках указал имя typedef соответствующего типа данных в файле. Можешь его использовать, заодно мб придумаешь че можно улучшить.
По программе:
Там идет управление и контроль ТЭ (топливные элементы), которых всего 85 штук. С них снимается задаются две уставки: предупреждение и авария. Коилами можно исключать ТЭ из работы.
Также есть пару управляющих настроек и статус-регистр.
Соответственно можно сделать два окна:
- одно небольшое - для статуса МЗКТЭ
- второе большое - для ТЭ. Можно сделать 4 вкладки, в каждой 85 ячеек:
- Напряжение на ТЭ,
- Уставки "Предупреждение",
- Уставки "Авария",
- Исключения ТЭ
И можно сделать одно открываемое окно для настроек. Типа по кнопке "Настройки МЗКТЭ". Это для регистров хранения 170-174. И если что их запись должна быть только по команде 0x06 (сингл). Остальные уставки поддерживают запись 0x10 (мультипл).
Статус МКЗТЭ и Напряжения на ТЭ только считывается (команда 0x04)
Уставки "Предупреждение" и "Авария" можно считать и записать всеми способами (0x03, 0x06, 0x10)
Настройки МЗКТЭ можно считывать и записать только по одному регистру (0x03, 0x06)
Исключения ТЭ можно считывать и записывать всеми способами (0x01, 0x05, 0x0F)

BIN
ТЗ/прил.MODBUS.pdf Normal file

Binary file not shown.