From ba0506caf70400c1c4fcad154c355ea055ae5d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D1=8F=D1=87=D0=B5=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A8?= =?UTF-8?q?=D1=82=D0=B5=D0=B9=D0=B1=D0=B5=D0=B7=D0=B0=D0=BD=D0=B4=D1=82?= Date: Fri, 5 Dec 2025 15:22:19 +0300 Subject: [PATCH] =?UTF-8?q?=D0=95=D1=89=D1=91=20=D0=BD=D0=B5=D0=BC=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B5=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- M3KTE_TERM/debugterminaldialog.cpp | 83 +++++++++++++++ M3KTE_TERM/lineringer.cpp | 39 ++++++- M3KTE_TERM/m3kte.cpp | 15 --- M3KTE_TERM/multiplesettings.cpp | 39 +++++++ M3KTE_TERM/parameterbox.cpp | 25 +++++ M3KTE_TERM/parameterdevice.cpp | 136 ++++++++++++++++++++---- M3KTE_TERM/parameterworkspace.cpp | 164 +++++++++++++++++++++-------- M3KTE_TERM/scanboard.cpp | 28 ++++- M3KTE_TERM/settingsdialog.cpp | 40 ++++++- M3KTE_TERM/writeregistermodel.cpp | 150 +++++++++++++++++++++----- 10 files changed, 610 insertions(+), 109 deletions(-) diff --git a/M3KTE_TERM/debugterminaldialog.cpp b/M3KTE_TERM/debugterminaldialog.cpp index bf5aab9..ab23249 100644 --- a/M3KTE_TERM/debugterminaldialog.cpp +++ b/M3KTE_TERM/debugterminaldialog.cpp @@ -43,6 +43,14 @@ void DebugTerminalDialog::setMainTerm(M3KTE* term) mainTerm = term; } +/** + * @brief Устанавливает указатель на объект QModbusClient для отладочного терминала. + * + * Этот метод присваивает указатель на Modbus-устройство и, при наличии графического диалога + * для отображения данных АЦП, передает ему тот же объект для синхронизации. + * + * @param device Указатель на объект QModbusClient, представляющий Modbus-устройство. + */ void DebugTerminalDialog::setModbusDevice(QModbusClient *device) { m_modbusDevice = device; @@ -50,6 +58,15 @@ void DebugTerminalDialog::setModbusDevice(QModbusClient *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); @@ -58,6 +75,15 @@ void DebugTerminalDialog::setDebugTerminalCoil(int enable) writeCoil(3, COIL_DEBUG_MODE, enable); } +/** + * @brief Обрабатывает событие отображения окна диалога. + * + * Этот метод вызывается при показе окна и сначала вызывает базовую реализацию showEvent. + * Затем происходит сброс всех настроек или состояний через resetAll(), а после этого + * устанавливается значение 1 для коили, отвечающих за режим отладки, с помощью setDebugTerminalCoil(1). + * + * @param event Указатель на объект QShowEvent, содержащий информацию о событии отображения. + */ void DebugTerminalDialog::showEvent(QShowEvent *event) { QDialog::showEvent(event); @@ -66,6 +92,15 @@ void DebugTerminalDialog::showEvent(QShowEvent *event) setDebugTerminalCoil(1); } +/** + * @brief Обрабатывает событие закрытия диалогового окна. + * + * Перед закрытием окна вызывается метод setDebugTerminalCoil(0), + * который отключает режим отладки, устанавливая значение коил в 0. + * Затем вызывается базовая обработка closeEvent. + * + * @param event Указатель на объект QCloseEvent, содержащий информацию о событии закрытия. + */ void DebugTerminalDialog::closeEvent(QCloseEvent *event) { // При закрытии окна записываем в коил 555 значение "0" @@ -84,6 +119,15 @@ void DebugTerminalDialog::updateConnectionStatus(int boardID, bool connected) // Реализация по необходимости } +/** + * @brief Обработка клика по кнопкам в диалоговом окне. + * + * В зависимости от роли нажатой кнопки, выполняются соответствующие действия: + * - Если роль Reset, вызывается resetAll(), сбрасывающий настройки. + * - Если роль Accept, вызывается accept(), подтверждающий изменения. + * + * @param button Указатель на нажатую кнопку. + */ void DebugTerminalDialog::on_buttonBox_clicked(QAbstractButton *button) { switch (ui->buttonBox->buttonRole(button)) { @@ -489,6 +533,16 @@ 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) { // Удаляем старый диалог и создаем новый @@ -507,12 +561,31 @@ void DebugTerminalDialog::openAdc(int boardID, int teNumber) 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; @@ -534,6 +607,16 @@ 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) { // Получаем групбокс для указанной платы diff --git a/M3KTE_TERM/lineringer.cpp b/M3KTE_TERM/lineringer.cpp index 35d2b73..0d24150 100644 --- a/M3KTE_TERM/lineringer.cpp +++ b/M3KTE_TERM/lineringer.cpp @@ -45,14 +45,41 @@ LineRinger::~LineRinger() 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" << "Примечание"; + 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) @@ -210,6 +237,16 @@ LineRinger::callStatus LineRinger::lineCall() return callStatus::NOERROR; } +/** + * @brief Обработчик нажатия кнопки "Опросить" для устройства LineRinger. + * + * Этот слот очищает текущий список устройств, обновляет интерфейс, а затем, + * в случае автоматического определения скорости передачи (isAutoBaud), + * последовательно пытается подключиться к устройствам с различными скоростями. + * В процессе поиска отображается прогресс-бар, который можно отменить. + * Если обнаружен вызываемый ответ или возникает ошибка, происходит завершение поиска. + * В случае ручного режима поиска, выполняется однократная попытка сканирования. + */ void LineRinger::on_ringButton_clicked() { ui->deviceOnlineView->clear(); diff --git a/M3KTE_TERM/m3kte.cpp b/M3KTE_TERM/m3kte.cpp index 776dd81..233da88 100644 --- a/M3KTE_TERM/m3kte.cpp +++ b/M3KTE_TERM/m3kte.cpp @@ -979,10 +979,8 @@ bool M3KTE::event(QEvent *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()) { @@ -1286,7 +1284,6 @@ void M3KTE::onParityUpdate() { // Останавливаем сканирование плат stopScanBoard(); - // Настройка регистра для установки режима четности QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 174, 1); switch(m_deviceSettingsDialog->currentParity()) { @@ -1294,16 +1291,13 @@ void M3KTE::onParityUpdate() case 1: unit.setValue(0, 0x0400); break; // Четный case 2: unit.setValue(0, 0x0600); break; // Нечетный } - // Переменные для подсчёта прогресса auto totalActiveBoards = QSharedPointer::create(0); auto confirmedBoards = QSharedPointer::create(0); auto pendingBoards = QSharedPointer>::create(); - // Сохраняем текущий и новый параметры четности для отката при ошибках auto oldParity = m_settingsDialog->curParity(); auto newParity = m_deviceSettingsDialog->currentParity(); - /** * Лямбда-функция, вызываемая по завершении всех запросов. * Обрабатывает успешное изменение или ошибку. @@ -1318,7 +1312,6 @@ void M3KTE::onParityUpdate() modbusDevice->disconnectDevice(); modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, newParity); modbusDevice->connectDevice(); - // Обработка ошибок и успеха при смене паритета auto errorHandler = [this, oldParity]() { disconnect(this, &M3KTE::errorAtCheckBoards, this, nullptr); @@ -1328,34 +1321,28 @@ void M3KTE::onParityUpdate() 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]() { @@ -1369,11 +1356,9 @@ void M3KTE::onParityUpdate() 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(); diff --git a/M3KTE_TERM/multiplesettings.cpp b/M3KTE_TERM/multiplesettings.cpp index c467464..477c33d 100644 --- a/M3KTE_TERM/multiplesettings.cpp +++ b/M3KTE_TERM/multiplesettings.cpp @@ -16,6 +16,15 @@ MultipleSettings::~MultipleSettings() delete ui; } +/** + * @brief Обработка нажатий кнопок в диалоговом окне настроек. + * + * В зависимости от нажатой кнопки выполняются разные действия: + * - Если нажата кнопка "Записать", сохраняются введенные параметры и вызывается сигнал write(). + * - Если нажата кнопка "Записать и установить", сохраняются параметры и вызывается сигнал writeAndSend(). + * + * @param button Указатель на нажатую кнопку. + */ void MultipleSettings::on_buttonBox_clicked(QAbstractButton *button) { if(button == ui->buttonBox->button(QDialogButtonBox::Ok)) { @@ -35,6 +44,15 @@ void MultipleSettings::on_buttonBox_clicked(QAbstractButton *button) } } +/** + * @brief Обработка изменения выбранного типа регистра. + * + * В зависимости от выбранного типа регистра (index) и текущей выбранной платы (ui->boardBox), + * устанавливается диапазон допустимых значений для поля адреса (ui->adrBox). + * Также значение адреса сбрасывается на начальное. + * + * @param index Индекс выбранного типа регистра. + */ void MultipleSettings::on_regTypeBox_currentIndexChanged(int index) { short maxRange = 0; @@ -58,12 +76,33 @@ void MultipleSettings::on_regTypeBox_currentIndexChanged(int index) } } +/** + * @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))) diff --git a/M3KTE_TERM/parameterbox.cpp b/M3KTE_TERM/parameterbox.cpp index 5e79ef2..01e22c8 100644 --- a/M3KTE_TERM/parameterbox.cpp +++ b/M3KTE_TERM/parameterbox.cpp @@ -25,6 +25,13 @@ ParameterBox::~ParameterBox() delete ui; } +/** + * @brief Обработчик нажатия кнопки отправки (sendButton). + * + * При нажатии проверяется, заполнено ли выбранное значение в valueBox. + * Если значение пустое, выполнение прерывается. + * В противном случае генерируется сигнал writeParameter с текущим адресом и выбранным значением (в шестнадцатеричном формате). + */ void ParameterBox::on_sendButton_clicked() { if(ui->valueBox->currentText().isEmpty()) @@ -32,11 +39,29 @@ void ParameterBox::on_sendButton_clicked() 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) { diff --git a/M3KTE_TERM/parameterdevice.cpp b/M3KTE_TERM/parameterdevice.cpp index 0dcfc82..ddb6bbc 100644 --- a/M3KTE_TERM/parameterdevice.cpp +++ b/M3KTE_TERM/parameterdevice.cpp @@ -20,28 +20,56 @@ 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) + // Очистка старых параметров + for (ParameterBox* box : parameterBoxes) + if (box) box->deleteLater(); parameterBoxes.clear(); quint16 strAdr; int cnt = 0; + // Установка стартового адреса strAdr = ui->adrSpin->value(); - switch(mode) { + // В зависимости от режима задаем диапазон чтения + switch (mode) { case fullRequest: strAdr = 0x80; cnt = 128; @@ -50,51 +78,72 @@ void ParameterDevice::on_checkButton_clicked() cnt = ui->countSpin->value(); break; } + // Настройка таймера для измерения времени QElapsedTimer timer; + // Создаем диалог прогресса QProgressDialog progressDialog("Обработка...", "Отмена", 0, cnt, this); progressDialog.setWindowModality(Qt::WindowModal); - progressDialog.setMinimumDuration(0); // показывать сразу + progressDialog.setMinimumDuration(0); // показывать сразу же + // Создаем цикл событий для ожидания завершения QEventLoop loop; + // Соединяем сигнал завершения передачи с выходом из цикла connect(this, &ParameterDevice::transmitEnd, &loop, &QEventLoop::quit); + // Запускаем таймер timer.start(); - for(int i = 0; i < cnt; i ++) { - // Обновляем прогресс + // Цикл чтения данных + for (int i = 0; i < cnt; i++) { + // Обновление прогресс-бара progressDialog.setValue(i); - if(progressDialog.wasCanceled()) - break; // пользователь отменил + if (progressDialog.wasCanceled()) + break; // пользователь отменил выполнение + // Формируем запрос QByteArray data = QByteArray::fromHex("0E04"); - data.append(strAdr+i); + data.append(strAdr + i); QModbusRequest request(QModbusRequest::EncapsulatedInterfaceTransport, data); - emit read(request, strAdr+i); + // Отправка запроса + emit read(request, strAdr + i); + // Ожидание завершения передачи loop.exec(); - if(errorAtTransmit) { + // Проверка на ошибку + 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 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); @@ -102,43 +151,84 @@ void ParameterDevice::setError(QString error) 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) { - // Создайте новый лейаут, если его нет + // Если лейаут отсутствует, создаем новый и устанавливаем его + if (!pblayout) { pblayout = new QVBoxLayout(ui->parameterBoxHubContents); ui->parameterBoxHubContents->setLayout(pblayout); } + // Очистка текущих элементов из лейаута QLayoutItem *item; - while((item = pblayout->takeAt(0)) != nullptr){} - for(int i = 0; i < parameterBoxes.count(); i++) + 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(); diff --git a/M3KTE_TERM/parameterworkspace.cpp b/M3KTE_TERM/parameterworkspace.cpp index 0fcbb1d..864c011 100644 --- a/M3KTE_TERM/parameterworkspace.cpp +++ b/M3KTE_TERM/parameterworkspace.cpp @@ -13,96 +13,178 @@ ParameterWorkspace::~ParameterWorkspace() delete ui; } +/** + * @brief Устанавливает количество устройств и обновляет интерфейс. + * + * Очистка текущего списка устройств и удаления соответствующих вкладок, + * затем создание новых устройств и добавление их в интерфейс. + * + * @param count Новое количество устройств. + */ void ParameterWorkspace::setDeviceCount(int count) { - for(const auto& device : deviceList) { - device.tab->deleteLater(); - device.device->deleteLater(); + // Удаление и очистка текущих устройств + for (const auto& device : deviceList) { + device.tab->deleteLater(); ///< Удаление вкладки устройства + device.device->deleteLater(); ///< Удаление объекта устройства } - deviceList.clear(); - for(int i = 0; i < ui->tabWidget->count(); i++) + deviceList.clear(); ///< Очистка списка устройств + // Удаление всех вкладок из таб-виджета + for (int i = 0; i < ui->tabWidget->count(); ++i) { ui->tabWidget->removeTab(i); - for(int i = 0; i < count; i++) { - device* _device = new device(); - deviceList.append(*_device); - deviceList[i].device = new ParameterDevice(); - int newtab = ui->tabWidget->addTab(deviceList[i].device, tr("Device №%1").arg(i+1)); + } + // Создание новых устройств + 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); - }); + [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); - }); + 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); - }); + 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) + // Проверка правильности состояния соединения + 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()) + // Отправка запроса + if (auto *reply = modbusDevice->sendWriteRequest(unit, adr)) { + // Обработка завершения запроса + if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, [reply]() { - if(reply->error() != QModbusDevice::NoError) - {/*ERROR*/} - reply->deleteLater(); + // Проверка ошибок выполнения + if (reply->error() != QModbusDevice::NoError) { + // Обработка ошибок (можно добавить лог или сообщение) + } + reply->deleteLater(); // Очистка объекта }); - else + } 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) + // Проверка правильности состояния соединения + 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()) + // Отправка запроса + if (auto *reply = modbusDevice->sendWriteRequest(unit, adr)) { + // Обработка завершения запроса + if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, [reply]() { - if(reply->error() != QModbusDevice::NoError) - {/*ERROR*/} - reply->deleteLater(); + // Проверка наличия ошибок + if (reply->error() != QModbusDevice::NoError) { + // Можно добавить лог или обработку ошибок + } + reply->deleteLater(); // Очистка объекта }); - else + } 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) + // Проверка состояния соединения + 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) { + // Отправка сырого запроса к устройству + 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) { - QString result = QString(resp.data().remove(0, MODBUS_REQUEST_PROTOCOL_INFO_LENGTH)); + // Проверка размера данных + 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(); + qDebug() << "Получен ответ с ошибкой:" << reply->errorString(); } - reply->deleteLater(); + reply->deleteLater(); // Очистка объекта }); } - } else + } 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; diff --git a/M3KTE_TERM/scanboard.cpp b/M3KTE_TERM/scanboard.cpp index b67ec22..7282a5b 100644 --- a/M3KTE_TERM/scanboard.cpp +++ b/M3KTE_TERM/scanboard.cpp @@ -13,26 +13,52 @@ 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 = (Qt::CheckState)arg1; + checkState = static_cast(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); diff --git a/M3KTE_TERM/settingsdialog.cpp b/M3KTE_TERM/settingsdialog.cpp index e58e066..1d0a836 100644 --- a/M3KTE_TERM/settingsdialog.cpp +++ b/M3KTE_TERM/settingsdialog.cpp @@ -36,40 +36,74 @@ 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); - if(parity > 0) + // Если 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(port.portName() + ": " + port.manufacturer()), QVariant(port.portName())); + for (const auto& port : listPorts) { + // Добавляем элемент с именем порта и производителем + ui->comBox->addItem(QString("%1: %2").arg(port.portName(), port.manufacturer()), QVariant(port.portName())); + } } diff --git a/M3KTE_TERM/writeregistermodel.cpp b/M3KTE_TERM/writeregistermodel.cpp index aaf057c..40bd331 100644 --- a/M3KTE_TERM/writeregistermodel.cpp +++ b/M3KTE_TERM/writeregistermodel.cpp @@ -19,31 +19,62 @@ 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) + // Проверка корректности индекса + 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) + // Обработка столбца с номером + 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))+1); - if(index.column() == CoilsColumn && role == Qt::CheckStateRole) // coils + // Обработка столбца с именем + 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) // holding registers - return QString("%1 В").arg(QString::number((double)((double)m_holdingRegisters.at(index.row()) / (double)1000), 'f', 3)); - if(index.column() == CurrentUColumn && role == Qt::DisplayRole) - return QString("%1 В").arg(QString::number((double)((double)m_currentU.at(index.row()) / (double)1000), 'f', 3)); + // Обработка значения в регистре (в Вольтах) + 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) + if (role != Qt::DisplayRole) return QVariant(); - if(orientation == Qt::Horizontal) { - switch(section) { + if (orientation == Qt::Horizontal) { + switch (section) { case NumColumn: return QStringLiteral("#Reg"); case NameColumn: @@ -61,67 +92,136 @@ QVariant WriteRegisterModel::headerData(int section, Qt::Orientation orientation 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) + // Проверяем валидность индекса и границы + 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) { // coils - auto s = static_cast(value.toUInt()); - s == Qt::Checked ? m_coils.setBit(index.row()) : m_coils.clearBit(index.row()); + // Обработка для чекбоксов + if (index.column() == CoilsColumn && role == Qt::CheckStateRole) { + auto state = static_cast(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) { // holding registers + // Обработка для регистров + if (index.column() == HoldingColumn && role == Qt::EditRole) { bool result = false; - quint16 newValue = value.toString().toUShort(&result, 10); - if(result) + // Преобразование QVariant в строку, затем в число + QString valueStr = value.toString(); + quint16 newValue = valueStr.toUShort(&result, 10); + if (result) { m_holdingRegisters[index.row()] = newValue; - emit dataChanged(index, index); + 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) + // Проверка валидности индекса + 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))) + // Отключение элементов вне диапазона адресов + if ((index.row() < m_address) || (index.row() >= (m_address + m_number))) flags &= ~Qt::ItemIsEnabled; - if(index.column() == CoilsColumn) // coils + // Установка флага чекбокса для столбца Coils + if (index.column() == CoilsColumn) return flags | Qt::ItemIsUserCheckable; - if(index.column() == HoldingColumn) // holding registers + // Установка флага редактируемости для столбца 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; - if(isHR) + + // Если isHR активен, обновляем также и по смещенному индексу + if (isHR) m_currentU[index + m_number] = _tmpU; + return true; }