From fe4d22caadd893b338d3b3794d8fe54c3f803e9d Mon Sep 17 00:00:00 2001 From: EugeniusMiroshnichenko Date: Wed, 29 Apr 2026 13:09:47 +0500 Subject: [PATCH 01/58] =?UTF-8?q?=D0=A2=D0=97=20=D0=BD=D0=B0=20=D0=B1?= =?UTF-8?q?=D1=8D=D0=BA=D0=B5=D0=BD=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tz.md | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 Tz.md diff --git a/Tz.md b/Tz.md new file mode 100644 index 0000000..fe9e6a9 --- /dev/null +++ b/Tz.md @@ -0,0 +1,221 @@ +# ТЕХНИЧЕСКОЕ ЗАДАНИЕ + +## 1. Причина разработки + +Разработка системы обусловлена необходимостью автоматизации процесса обкатки дизельных двигателей, включая: +- управление режимами испытаний; +- контроль критических параметров; +- снижение влияния человеческого фактора; +- повышение наглядности и безопасности процесса. + +Система реализуется в учебных целях с использованием моделируемых датчиков и исполнительных механизмов. + +## 2. Описание функций + +Система должна обеспечивать выполнение следующих функций: + +- управление процессом обкатки (запуск, остановка, выбор этапа, автоматический и ручной переход); +- настройка параметров испытания (длительности, обороты, нагрузка, критические пороги); +- управление исполнительными механизмами (электродвигатель в режиме прокрутки/торможения, дроссельная заслонка); +- циклический сбор данных с модели датчиков по протоколу Modbus TCP; +- визуализация текущих параметров и графиков в реальном времени; +- аварийная защита (контроль критических значений, автоматический останов); +- регистрация и хранение всех измерений и событий в базе данных; +- формирование отчёта по результатам испытания; +- предоставление HTTP API и WebSocket для взаимодействия с пользовательским интерфейсом. + +## 3. Детальное описание функций + +### 3.1. Управление процессом обкатки + +Бэкенд реализует конечный автомат со следующими состояниями: + +- `IDLE` – ожидание команды. +- `COLD_CRANKING` – холодная обкатка (притирка), двигатель прокручивается внешним электродвигателем, дизель не запущен. +- `START_AND_WARMUP` – запуск и прогрев, подача топлива, управление дроссельной заслонкой, электродвигатель отключён. +- `HOT_NO_LOAD` – горячая обкатка без нагрузки, двигатель работает, нагрузка отсутствует. +- `HOT_WITH_LOAD` – горячая обкатка под нагрузкой, электродвигатель переведён в режим электромагнитного тормоза. +- `COMPLETED` – испытание успешно завершено. +- `ABORTED` – аварийная остановка. + +Переходы: +- команда `start` (с настройками) переводит `IDLE` в `COLD_CRANKING`; +- команда `nextStage` – ручной переход к следующему этапу; +- автоматический переход по истечении заданной длительности этапа (если настроено); +- команда `abort` или срабатывание аварийной защиты – немедленный переход в `ABORTED` из любого активного состояния; +- после последнего этапа – в `COMPLETED`. + +Каждый переход логируется в журнале событий. + +### 3.2. Сбор и обработка данных + +Обмен данными с моделью объекта осуществляется по протоколу **Modbus TCP** через выделенный класс-клиент. +Период опроса: **20 мс** (50 измерений в секунду). +В каждом цикле опроса считываются следующие параметры (регистры): + +- частота вращения (об/мин); +- крутящий момент (Н·м); +- температура дизеля (°C); +- температура электродвигателя (°C); +- температура тормозного резистора (°C); +- давление дизеля (кПа). + +Дополнительно снимаются текущие значения управляющих сигналов: +- положение дроссельной заслонки (%); +- тормозной момент (Н·м). + +Собранные данные (структура `SensorSnapshot`) передаются в две потокобезопасные очереди: +1. для сохранения в базу данных (поток `Logger`); +2. для рассылки подключённым WebSocket-клиентам (поток веб-сервера). + +### 3.3. Управление исполнительными механизмами + +На основе состояния этапа и текущих измерений бэкенд формирует управляющие сигналы и записывает их в модель через Modbus: + +- **Холодная обкатка:** включается режим прокрутки, задаётся частота вращения электродвигателя (низкие обороты), дроссель закрыт, тормозной момент нулевой. +- **Запуск и прогрев:** электродвигатель отключается, дроссель устанавливается в положение, заданное оператором; частота вращения определяется моделью дизеля. +- **Горячая без нагрузки:** управление дросселем по профилю, электродвигатель отключён, нагрузка отсутствует. +- **Горячая под нагрузкой:** электродвигатель переводится в режим тормоза, задаётся тормозной момент из настроек, дроссель регулируется по программе. + +### 3.4. Аварийная защита + +На каждом такте опроса после получения измерений выполняется проверка критических границ, заданных оператором перед стартом: + +- превышение критической частоты вращения (отдельные пороги для притирки и обкаточного режима); +- превышение критической температуры электродвигателя (особенно важно на малых оборотах притирки); +- превышение критической температуры дизеля; +- температура тормозного резистора выше допустимой; +- выход давления дизеля за пределы [минимальное, максимальное]. + +При нарушении любого из условий: +1. формируется событие аварии с указанием причины; +2. немедленно выставляется сигнал на останов (аварийный останов); +3. конечный автомат переводится в состояние `ABORTED`; +4. пользователю отправляется уведомление через WebSocket; +5. событие записывается в базу данных. + +### 3.5. Хранение данных + +Используется **SQLite** (файл `diag_data.db`). Схема базы данных: + +- **runs** – информация об испытаниях (id, время старта/окончания, статус, полные настройки в JSON). +- **stages** – записи этапов (id, run_id, номер этапа, время начала/окончания). +- **sensor_data** – временные ряды показаний (run_id, stage_num, ts_ms, все измеренные и управляющие параметры). Индекс по `(run_id, stage_num, ts_ms)`. +- **events** – журнал событий (run_id, ts_ms, тип: stage_change, abort, warning, info, сообщение). + +Поток-логгер накапливает пачку измерений и вставляет их в БД одной транзакцией раз в 1 секунду (или по заполнению буфера). Это обеспечивает целостность и высокую скорость. + +Хранение не менее 1000 часов испытаний с периодом 20 мс (~180 млн строк) работоспособно при правильных индексах, но для учебного проекта объём не является проблемой. + +### 3.6. Формирование отчёта + +По завершении испытания (штатном или аварийном) бэкенд автоматически формирует отчёт либо по запросу `GET /api/report/{id}`. + +Состав отчёта (JSON): + +- дата и время испытания; +- режим обкатки (пройденные этапы); +- все заданные оператором параметры (длительности, обороты, нагрузка, критические пороги); +- фактически достигнутые параметры по этапам (мин/макс/среднее); +- ссылки на выборки данных для построения графиков; +- полный журнал событий; +- итог: "успешно", "аварийно остановлен (причина)". + +Фронтенд, получив этот JSON, рендерит страницу с графиками и таблицами. + +### 3.7. HTTP API и WebSocket + +Бэкенд предоставляет интерфейс для фронтенда через **cpp-httplib**: + +| Метод | Путь | Описание | +|-------|------|----------| +| POST | /api/start | Запуск испытания (передаются настройки JSON) | +| POST | /api/stop | Аварийная остановка | +| POST | /api/next | Принудительный переход к следующему этапу | +| GET | /api/status | Текущее состояние, последние показания датчиков | +| GET | /api/data?run=N&stage=S&from=T1&to=T2 | Выборка SensorSnapshot для графика | +| GET | /api/report/N | Полный отчёт по испытанию | +| GET | /api/runs | Список проведённых испытаний | + +WebSocket-эндпоинт: `/ws/live`. +При подключении клиент начинает получать JSON-пакеты с полями `SensorSnapshot` при каждом новом измерении (каждые 20 мс). + +### 3.8. Конфигурация + +Настройки (порт, период опроса, путь к БД и т.д.) вынесены в файл `config.json`, загружаемый при старте. + +## 4. Экранные формы + +### 4.1. Главный экран +- Кнопки «Старт», «Стоп», «Следующий этап». +- Индикатор текущего этапа и состояния системы. +- Таблица с текущими значениями всех измеряемых параметров. +- Область графиков (частота вращения, момент, температуры, давление) в реальном времени. + +### 4.2. Экран настройки +- Поля ввода длительности этапов. +- Задание целевой частоты вращения для холодной обкатки. +- Задание положения дросселя для каждого этапа. +- Уровень нагрузки (тормозной момент) для горячей обкатки под нагрузкой. +- Критические значения: + - макс. частота вращения (притирка / обкатка); + - макс. температура электродвигателя; + - макс. температура дизеля; + - макс. температура тормозного резистора; + - мин. / макс. давление дизеля. + +### 4.3. Экран отчётов +- Список проведённых испытаний с датами и статусом. +- Кнопка «Просмотреть отчёт». +- Графики выбранных параметров по этапам. +- Журнал событий испытания. +- Итоговое заключение. + +## 5. Сценарий использования + +1. Оператор открывает главный экран. +2. При необходимости переходит на экран настройки и вводит параметры испытания (или использует предустановленные). +3. Возвращается на главный экран и нажимает «Старт». +4. Система переходит в этап **Холодная обкатка**: + - включается электродвигатель в режиме прокрутки; + - на графиках отображаются текущие обороты, момент, температуры. +5. По истечении заданного времени или по нажатию «Следующий этап» система переходит в **Запуск и прогрев**: + - электродвигатель отключается; + - подаётся топливо, регулируется дроссель; + - контролируется прогрев дизеля. +6. Далее аналогично выполняются этапы **Горячая без нагрузки** и **Горячая под нагрузкой** (включается электромагнитный тормоз). +7. При любом нарушении критического порога: + - появляется аварийное сообщение; + - процесс немедленно останавливается; + - запись об аварии попадает в отчёт. +8. После завершения последнего этапа (или аварии) открывается экран отчёта, где можно просмотреть все параметры, графики и итог. + +## 6. Базовые сценарии тестирования + +### Тест 1: Запуск процесса +- Действие: нажать кнопку «Старт». +- Ожидание: система переходит в состояние `COLD_CRANKING`, начинают обновляться показания датчиков на экране, запускается WebSocket-поток. + +### Тест 2: Автоматический переход этапов +- Действие: задать длительность первого этапа 5 секунд, запустить, не нажимать «Следующий этап». +- Ожидание: через 5 секунд система автоматически переходит в `START_AND_WARMUP`, смена этапа отражается в интерфейсе и журнале событий. + +### Тест 3: Ручной переход +- Действие: во время этапа `COLD_CRANKING` нажать «Следующий этап». +- Ожидание: этап немедленно завершается, система переходит к следующему. + +### Тест 4: Аварийный останов по перегреву электродвигателя +- Действие: в настройках установить критическую температуру ЭД = 40 °C, запустить холодную обкатку и дождаться, пока модель выдаст температуру выше порога. +- Ожидание: процесс останавливается, появляется аварийное уведомление "Перегрев электродвигателя", система переходит в `ABORTED`, в БД фиксируется событие аварии. + +### Тест 5: Контроль давления +- Действие: смоделировать падение давления ниже минимального порога. +- Ожидание: аварийный останов с сообщением "Давление ниже допустимого", запись события. + +### Тест 6: Формирование отчёта +- Действие: успешно пройти все этапы или вызвать аварийный останов. +- Ожидание: после завершения вызывается `GET /api/report/last`, возвращается полный JSON отчёта, экран отчётов корректно отображает графики и события. + +### Тест 7: Отказ связи с моделью +- Действие: остановить процесс модели (или закрыть сокет) во время активного испытания. +- Ожидание: бэкенд после трёх неудачных попыток чтения объявляет аварию "Потеря связи с моделью", останавливает процесс, уведомляет пользователя. \ No newline at end of file From b10c7115a6a9902edc5d182b07a83b185f8d2081 Mon Sep 17 00:00:00 2001 From: lefff123 Date: Thu, 30 Apr 2026 23:48:56 +0500 Subject: [PATCH 02/58] =?UTF-8?q?=D0=A2=D0=97=20=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B8=D0=B8=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tz.md | 457 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 260 insertions(+), 197 deletions(-) diff --git a/Tz.md b/Tz.md index fe9e6a9..fa560a8 100644 --- a/Tz.md +++ b/Tz.md @@ -1,221 +1,284 @@ -# ТЕХНИЧЕСКОЕ ЗАДАНИЕ +# ТЕХНИЧЕСКОЕ ЗАДАНИЕ НА BACKEND (СВОДНОЕ, версия 2) -## 1. Причина разработки +## 1. Общие сведения и причина разработки -Разработка системы обусловлена необходимостью автоматизации процесса обкатки дизельных двигателей, включая: -- управление режимами испытаний; -- контроль критических параметров; -- снижение влияния человеческого фактора; -- повышение наглядности и безопасности процесса. +Система создаётся для автоматизации процесса обкатки дизельных двигателей на испытательном стенде. +**Причина разработки:** необходимость управления режимами испытаний, контроля критических параметров, снижение влияния человеческого фактора, повышение наглядности и безопасности. +Реализуется в учебных целях с использованием моделируемых датчиков и исполнительных механизмов. -Система реализуется в учебных целях с использованием моделируемых датчиков и исполнительных механизмов. +Бэкенд выполняется как **библиотека**, работающая в отдельном потоке (`QThread`), и предоставляет API фронтенду исключительно через сигналы и слоты Qt. +Хранилище данных — CSV-файлы. -## 2. Описание функций +## 2. Архитектура -Система должна обеспечивать выполнение следующих функций: +Бэкенд состоит из следующих модулей (каждый — `QObject`, живущий в рабочем потоке): -- управление процессом обкатки (запуск, остановка, выбор этапа, автоматический и ручной переход); -- настройка параметров испытания (длительности, обороты, нагрузка, критические пороги); -- управление исполнительными механизмами (электродвигатель в режиме прокрутки/торможения, дроссельная заслонка); -- циклический сбор данных с модели датчиков по протоколу Modbus TCP; -- визуализация текущих параметров и графиков в реальном времени; -- аварийная защита (контроль критических значений, автоматический останов); -- регистрация и хранение всех измерений и событий в базе данных; -- формирование отчёта по результатам испытания; -- предоставление HTTP API и WebSocket для взаимодействия с пользовательским интерфейсом. +- **Модуль связи с фронтом (BackendWorker)** – единственная точка входа для фронтенда. +- **Модуль управления процессом (StateMachine)** – конечный автомат этапов. +- **Модуль обработки данных (DataProcessor)** – проверка критических границ, генерация предупреждений и аварий. +- **Модуль связи с моделью (ModbusDataBridge)** – циклический опрос датчиков по Modbus TCP, запись управляющих сигналов. +- **Модуль хранения данных (DataStore)** – запись в CSV, чтение для отчётов. -## 3. Детальное описание функций +Обмен данными внутри потока – прямые вызовы методов; между потоком бэкенда и потоком фронтенда – сигналы/слоты Qt (`Qt::QueuedConnection`). +Все структуры данных зарегистрированы как метатипы Qt и передаются по значению через сигналы/слоты. -### 3.1. Управление процессом обкатки +## 3. Модули и их интерфейсы -Бэкенд реализует конечный автомат со следующими состояниями: +### 3.1. Модуль связи с фронтом (BackendWorker) -- `IDLE` – ожидание команды. -- `COLD_CRANKING` – холодная обкатка (притирка), двигатель прокручивается внешним электродвигателем, дизель не запущен. -- `START_AND_WARMUP` – запуск и прогрев, подача топлива, управление дроссельной заслонкой, электродвигатель отключён. -- `HOT_NO_LOAD` – горячая обкатка без нагрузки, двигатель работает, нагрузка отсутствует. -- `HOT_WITH_LOAD` – горячая обкатка под нагрузкой, электродвигатель переведён в режим электромагнитного тормоза. -- `COMPLETED` – испытание успешно завершено. -- `ABORTED` – аварийная остановка. +Живёт в рабочем потоке. Владеет экземплярами всех остальных модулей, соединяет их сигналы и слоты. -Переходы: -- команда `start` (с настройками) переводит `IDLE` в `COLD_CRANKING`; -- команда `nextStage` – ручной переход к следующему этапу; -- автоматический переход по истечении заданной длительности этапа (если настроено); -- команда `abort` или срабатывание аварийной защиты – немедленный переход в `ABORTED` из любого активного состояния; -- после последнего этапа – в `COMPLETED`. +**Слоты (команды от фронтенда):** -Каждый переход логируется в журнале событий. +| Слот | Описание | +|------|----------| +| `void onStart(Config config)` | Запуск нового испытания. | +| `void onStop()` | Экстренная остановка. | +| `void onNextStage()` | Принудительный переход к следующему этапу. | +| `void onGetStatus()` | Запрос текущего состояния. | +| `void onGetReport(quint64 runId)` | Запрос отчёта по завершённому испытанию. | +| `void onGetHistory()` | Запрос списка завершённых испытаний. | -### 3.2. Сбор и обработка данных +*(Большинство названий слотов предложено автором, кроме `onStop` и `onNextStage`, заложенных в ТЗ управления процессом.)* -Обмен данными с моделью объекта осуществляется по протоколу **Modbus TCP** через выделенный класс-клиент. -Период опроса: **20 мс** (50 измерений в секунду). -В каждом цикле опроса считываются следующие параметры (регистры): +**Сигналы (данные и события для фронтенда):** -- частота вращения (об/мин); -- крутящий момент (Н·м); -- температура дизеля (°C); -- температура электродвигателя (°C); -- температура тормозного резистора (°C); -- давление дизеля (кПа). +| Сигнал | Описание | +|--------|----------| +| `void statusUpdated(QString state, SensorFrame lastData)` | Периодическая отправка состояния (не реже 1 раза в сек). | +| `void liveData(SensorFrame frame)` | Каждое новое измерение. | +| `void alarmEvent(AlarmEvent event)` | Немедленное аварийное уведомление. | +| `void warningEvent(WarningEvent event)` | Предупреждение. | +| `void processFinished(Report report)` | Завершение испытания, передача отчёта. | +| `void historyReady(QVector runIds)` | Ответ на запрос истории. | -Дополнительно снимаются текущие значения управляющих сигналов: -- положение дроссельной заслонки (%); -- тормозной момент (Н·м). +*(`statusUpdated`, `liveData`, `alarmEvent`, `warningEvent` — из исходных требований «передача нештатных сообщений»; `processFinished`, `historyReady` — предложено автором.)* -Собранные данные (структура `SensorSnapshot`) передаются в две потокобезопасные очереди: -1. для сохранения в базу данных (поток `Logger`); -2. для рассылки подключённым WebSocket-клиентам (поток веб-сервера). +### 3.2. Модуль управления процессом (StateMachine) -### 3.3. Управление исполнительными механизмами +Хранит текущий этап, длительности, обрабатывает переходы. Не обязательно отдельный QObject, но для удобства может иметь сигнал смены этапа. -На основе состояния этапа и текущих измерений бэкенд формирует управляющие сигналы и записывает их в модель через Modbus: +**Слоты (вызываются BackendWorker'ом):** +- `void requestStart(Config config)` – войти в первый этап. +- `void requestNextStage()` – перейти к следующему этапу. +- `void requestAbort(QString reason)` – немедленно завершить испытание. -- **Холодная обкатка:** включается режим прокрутки, задаётся частота вращения электродвигателя (низкие обороты), дроссель закрыт, тормозной момент нулевой. -- **Запуск и прогрев:** электродвигатель отключается, дроссель устанавливается в положение, заданное оператором; частота вращения определяется моделью дизеля. -- **Горячая без нагрузки:** управление дросселем по профилю, электродвигатель отключён, нагрузка отсутствует. -- **Горячая под нагрузкой:** электродвигатель переводится в режим тормоза, задаётся тормозной момент из настроек, дроссель регулируется по программе. +**Сигналы (предложено автором):** +- `void stageChanged(int oldStage, int newStage)` – для логирования и обновления UI. -### 3.4. Аварийная защита +**Состояния:** +`IDLE`, `COLD_CRANKING`, `START_AND_WARMUP`, `HOT_NO_LOAD`, `HOT_WITH_LOAD`, `COMPLETED`, `ABORTED`. -На каждом такте опроса после получения измерений выполняется проверка критических границ, заданных оператором перед стартом: +### 3.3. Модуль обработки данных (DataProcessor) -- превышение критической частоты вращения (отдельные пороги для притирки и обкаточного режима); -- превышение критической температуры электродвигателя (особенно важно на малых оборотах притирки); -- превышение критической температуры дизеля; -- температура тормозного резистора выше допустимой; -- выход давления дизеля за пределы [минимальное, максимальное]. - -При нарушении любого из условий: -1. формируется событие аварии с указанием причины; -2. немедленно выставляется сигнал на останов (аварийный останов); -3. конечный автомат переводится в состояние `ABORTED`; -4. пользователю отправляется уведомление через WebSocket; -5. событие записывается в базу данных. - -### 3.5. Хранение данных +Принимает сырые измерения, сверяет с критическими порогами, формирует решение. -Используется **SQLite** (файл `diag_data.db`). Схема базы данных: - -- **runs** – информация об испытаниях (id, время старта/окончания, статус, полные настройки в JSON). -- **stages** – записи этапов (id, run_id, номер этапа, время начала/окончания). -- **sensor_data** – временные ряды показаний (run_id, stage_num, ts_ms, все измеренные и управляющие параметры). Индекс по `(run_id, stage_num, ts_ms)`. -- **events** – журнал событий (run_id, ts_ms, тип: stage_change, abort, warning, info, сообщение). - -Поток-логгер накапливает пачку измерений и вставляет их в БД одной транзакцией раз в 1 секунду (или по заполнению буфера). Это обеспечивает целостность и высокую скорость. - -Хранение не менее 1000 часов испытаний с периодом 20 мс (~180 млн строк) работоспособно при правильных индексах, но для учебного проекта объём не является проблемой. - -### 3.6. Формирование отчёта - -По завершении испытания (штатном или аварийном) бэкенд автоматически формирует отчёт либо по запросу `GET /api/report/{id}`. - -Состав отчёта (JSON): - -- дата и время испытания; -- режим обкатки (пройденные этапы); -- все заданные оператором параметры (длительности, обороты, нагрузка, критические пороги); -- фактически достигнутые параметры по этапам (мин/макс/среднее); -- ссылки на выборки данных для построения графиков; -- полный журнал событий; -- итог: "успешно", "аварийно остановлен (причина)". - -Фронтенд, получив этот JSON, рендерит страницу с графиками и таблицами. - -### 3.7. HTTP API и WebSocket - -Бэкенд предоставляет интерфейс для фронтенда через **cpp-httplib**: - -| Метод | Путь | Описание | -|-------|------|----------| -| POST | /api/start | Запуск испытания (передаются настройки JSON) | -| POST | /api/stop | Аварийная остановка | -| POST | /api/next | Принудительный переход к следующему этапу | -| GET | /api/status | Текущее состояние, последние показания датчиков | -| GET | /api/data?run=N&stage=S&from=T1&to=T2 | Выборка SensorSnapshot для графика | -| GET | /api/report/N | Полный отчёт по испытанию | -| GET | /api/runs | Список проведённых испытаний | - -WebSocket-эндпоинт: `/ws/live`. -При подключении клиент начинает получать JSON-пакеты с полями `SensorSnapshot` при каждом новом измерении (каждые 20 мс). - -### 3.8. Конфигурация - -Настройки (порт, период опроса, путь к БД и т.д.) вынесены в файл `config.json`, загружаемый при старте. - -## 4. Экранные формы - -### 4.1. Главный экран -- Кнопки «Старт», «Стоп», «Следующий этап». -- Индикатор текущего этапа и состояния системы. -- Таблица с текущими значениями всех измеряемых параметров. -- Область графиков (частота вращения, момент, температуры, давление) в реальном времени. - -### 4.2. Экран настройки -- Поля ввода длительности этапов. -- Задание целевой частоты вращения для холодной обкатки. -- Задание положения дросселя для каждого этапа. -- Уровень нагрузки (тормозной момент) для горячей обкатки под нагрузкой. -- Критические значения: - - макс. частота вращения (притирка / обкатка); - - макс. температура электродвигателя; - - макс. температура дизеля; - - макс. температура тормозного резистора; - - мин. / макс. давление дизеля. - -### 4.3. Экран отчётов -- Список проведённых испытаний с датами и статусом. -- Кнопка «Просмотреть отчёт». -- Графики выбранных параметров по этапам. -- Журнал событий испытания. -- Итоговое заключение. - -## 5. Сценарий использования - -1. Оператор открывает главный экран. -2. При необходимости переходит на экран настройки и вводит параметры испытания (или использует предустановленные). -3. Возвращается на главный экран и нажимает «Старт». -4. Система переходит в этап **Холодная обкатка**: - - включается электродвигатель в режиме прокрутки; - - на графиках отображаются текущие обороты, момент, температуры. -5. По истечении заданного времени или по нажатию «Следующий этап» система переходит в **Запуск и прогрев**: - - электродвигатель отключается; - - подаётся топливо, регулируется дроссель; - - контролируется прогрев дизеля. -6. Далее аналогично выполняются этапы **Горячая без нагрузки** и **Горячая под нагрузкой** (включается электромагнитный тормоз). -7. При любом нарушении критического порога: - - появляется аварийное сообщение; - - процесс немедленно останавливается; - - запись об аварии попадает в отчёт. -8. После завершения последнего этапа (или аварии) открывается экран отчёта, где можно просмотреть все параметры, графики и итог. - -## 6. Базовые сценарии тестирования - -### Тест 1: Запуск процесса -- Действие: нажать кнопку «Старт». -- Ожидание: система переходит в состояние `COLD_CRANKING`, начинают обновляться показания датчиков на экране, запускается WebSocket-поток. - -### Тест 2: Автоматический переход этапов -- Действие: задать длительность первого этапа 5 секунд, запустить, не нажимать «Следующий этап». -- Ожидание: через 5 секунд система автоматически переходит в `START_AND_WARMUP`, смена этапа отражается в интерфейсе и журнале событий. - -### Тест 3: Ручной переход -- Действие: во время этапа `COLD_CRANKING` нажать «Следующий этап». -- Ожидание: этап немедленно завершается, система переходит к следующему. - -### Тест 4: Аварийный останов по перегреву электродвигателя -- Действие: в настройках установить критическую температуру ЭД = 40 °C, запустить холодную обкатку и дождаться, пока модель выдаст температуру выше порога. -- Ожидание: процесс останавливается, появляется аварийное уведомление "Перегрев электродвигателя", система переходит в `ABORTED`, в БД фиксируется событие аварии. - -### Тест 5: Контроль давления -- Действие: смоделировать падение давления ниже минимального порога. -- Ожидание: аварийный останов с сообщением "Давление ниже допустимого", запись события. - -### Тест 6: Формирование отчёта -- Действие: успешно пройти все этапы или вызвать аварийный останов. -- Ожидание: после завершения вызывается `GET /api/report/last`, возвращается полный JSON отчёта, экран отчётов корректно отображает графики и события. - -### Тест 7: Отказ связи с моделью -- Действие: остановить процесс модели (или закрыть сокет) во время активного испытания. -- Ожидание: бэкенд после трёх неудачных попыток чтения объявляет аварию "Потеря связи с моделью", останавливает процесс, уведомляет пользователя. \ No newline at end of file +**Слоты:** +- `void processFrame(SensorFrame frame)` *(из ТЗ обработки данных – приём текущего пакета)*. + +**Сигналы:** +- `void decisionReady(Decision decision)` *(из ТЗ обработки данных)*. +- `void warningRaised(WarningEvent event)` *(из ТЗ обработки данных)*. +- `void alarmRaised(AlarmEvent event)` *(из ТЗ обработки данных)*. +- `void controlNeeded(ActuatorCommand correction)` *(предложено автором)* – если требуется корректирующее воздействие. + +### 3.4. Модуль связи с моделью (ModbusDataBridge) + +Реализует опрос Modbus TCP и запись команд. Владеет `QModbusTcpClient`. + +**Слоты:** +- `void onStartPolling()` – запуск таймера опроса. +- `void onStopPolling()` – остановка таймера. +- `void onWriteCommand(ActuatorCommand cmd)` – запись управляющих регистров в модель *(из ТЗ ModelClient)*. + +**Сигналы:** +- `void dataReady(SensorFrame frame)` – после успешного чтения регистров *(из ТЗ ModelClient)*. +- `void errorOccurred(QString errorMessage)` – при критическом сбое связи *(из ТЗ ModelClient)*. + +### 3.5. Модуль хранения данных (DataStore) + +Работает с CSV-файлами. Не имеет сигналов, только прямые методы и слоты для записи по сигналу. + +**Слоты (предложено автором):** +- `void writeRecord(MeasurementRecord record)` – дописать строку в CSV текущего испытания. +- `void writeEvent(EventRecord event)` – записать событие в журнал. + +**Прямые методы (вызываются из BackendWorker):** +- `QVector readRecords(quint64 runId)` +- `QVector readEvents(quint64 runId)` +- `Report buildReport(quint64 runId)` + +**Форматы файлов (для одного испытания `runId`):** +- `reports/run_.csv` – строки MeasurementRecord с заголовком. +- `reports/events_.csv` – строки EventRecord с заголовком. + +## 4. Структуры данных + +Все структуры имеют открытые поля и зарегистрированы как метатипы Qt. + +### Config (предложено автором) +```cpp +struct Config { + QString modbusHost = "127.0.0.1"; + int modbusPort = 502; + int pollIntervalMs = 1000; + + QVector stageDurationSec; + double targetRpmCold; + double throttleWarmup; + double throttleHotNoLoad; + double throttleHotLoad; + double brakeTorqueHotLoad; + + double maxRpmCold, maxRpmHot; + double maxMotorTemp, maxDieselTemp, maxResistorTemp; + double minDieselPressure, maxDieselPressure; +}; +``` + +### SensorFrame (из ТЗ ModelClient) +```cpp +struct SensorFrame { + qint64 runId; + int stage; + qint64 timestampMs; + double rpm, torque; + double dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; +``` + +### ActuatorCommand (из ТЗ ModelClient) +```cpp +struct ActuatorCommand { + bool motorEnabled; + double targetRpm; + double throttlePosition; + double brakeTorque; +}; +``` + +### Decision (из ТЗ обработки данных) +```cpp +enum class Status { OK, WARNING, ALARM }; + +struct Decision { + Status status; + QString message; + ActuatorCommand correction; // может быть пустым +}; +``` + +### WarningEvent (из ТЗ обработки данных) +```cpp +struct WarningEvent { + qint64 runId, timestampMs; + int stage; + QString parameter; + double currentValue, threshold; + QString description; +}; +``` + +### AlarmEvent (из ТЗ обработки данных) +```cpp +struct AlarmEvent { + qint64 runId, timestampMs; + int stage; + QString parameter; + double currentValue, criticalThreshold; + QString reason; +}; +``` + +### MeasurementRecord (предложено автором) +```cpp +struct MeasurementRecord { + qint64 runId; + int stage; + qint64 timestampMs; + double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; + QString flags; // "OK", "WARNING" и т.п. +}; +``` + +### StageSummary (предложено автором) +```cpp +struct StageSummary { + int stageNum; + QString startTime, endTime; + double avgRpm, avgTorque, maxTemperature; + QString notes; +}; +``` + +### EventRecord (предложено автором) +```cpp +struct EventRecord { + qint64 runId, timestampMs; + int stage; + QString type; // "stage_change", "abort", "warning", "info" + QString message; +}; +``` + +### Report (предложено автором) +```cpp +struct Report { + qint64 runId; + QString startTime, endTime; + QString finalStatus; // "completed" или "aborted" + QVector stages; + QVector events; +}; +``` + +## 5. Точка сборки (main) + +Не является модулем. Выполняет: +- создание `QApplication`; +- загрузку начального `Config` (из файла или аргументов); +- создание объекта `BackendStarter` (или аналогичного), который создаёт `QThread`, экземпляр `BackendWorker`, перемещает его в поток, подключает сигналы `BackendWorker` к слотам фронтендного объекта; +- запуск потока. + +Фронтенд далее общается только через сигналы/слоты, передавая структуры напрямую. + +## 6. Схема взаимодействия (поток данных) + +### 6.1. Запуск испытания +Фронтенд → `onStart(config)`. +`BackendWorker`: +- создаёт запись в CSV (через `DataStore`); +- переводит `StateMachine` в `COLD_CRANKING`; +- формирует первичный `ActuatorCommand` и отправляет в `ModbusDataBridge::onWriteCommand`; +- запускает опрос: `ModbusDataBridge::onStartPolling()`. + +### 6.2. Цикл измерений (каждый тик таймера) +1. `ModbusDataBridge` успешно считывает регистры → `dataReady(frame)`. +2. `BackendWorker` направляет `frame` в `DataProcessor::processFrame(frame)`. +3. `DataProcessor` проверяет все критические границы и испускает один из: + - `decisionReady(decision)` – всегда, + - `warningRaised(event)` – если есть предупреждение, + - `alarmRaised(event)` – если есть авария, + - `controlNeeded(correction)` – если нужна корректировка. +4. `BackendWorker` (подписан на эти сигналы): + - при `warningRaised`: записывает событие в журнал CSV, отправляет `warningEvent` фронтенду; + - при `alarmRaised`: + - вызывает `StateMachine::requestAbort(reason)`, + - вызывает `ModbusDataBridge::onStopPolling()` и аварийный `onWriteCommand(стоп)`, + - записывает событие, + - отправляет `alarmEvent` фронтенду; + - при `controlNeeded`: передаёт `correction` в `ModbusDataBridge::onWriteCommand`; + - при любом `decisionReady`: сохраняет измерение в `DataStore::writeRecord(record)` (добавляя флаги из `decision.status`), отправляет `liveData(frame)` фронтенду. +5. Если аварии нет и длительность этапа истекла (контролирует `StateMachine` через внутренний таймер или по команде `requestNextStage`), `StateMachine` переходит к следующему этапу, сигнализирует `stageChanged`. `BackendWorker` формирует новые управляющие команды и обновляет `DataStore`. + +### 6.3. Завершение испытания +- При успешном завершении последнего этапа `StateMachine` переходит в `COMPLETED`. +- При аварии – в `ABORTED`. +- В обоих случаях `BackendWorker` останавливает опрос, закрывает CSV, вызывает `DataStore::buildReport(runId)`, получает `Report` и отправляет `processFinished(report)` фронтенду. + +### 6.4. Запросы от фронта вне активного испытания +- `onGetStatus` → `BackendWorker` отправляет `statusUpdated` с последним сохранённым состоянием. +- `onGetReport` → вызов `DataStore::buildReport`, ответ через `processFinished` (можно переиспользовать сигнал, передавая `report` и идентифицируя запрос). +- `onGetHistory` → чтение списка CSV-файлов в папке `reports`, возврат `historyReady(ids)`. \ No newline at end of file From ba52df9dfa962402edab141398390fdc19e2581e Mon Sep 17 00:00:00 2001 From: Rikitick Date: Sat, 2 May 2026 03:14:16 +0500 Subject: [PATCH 03/58] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20Unit=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend_worker_test/backend_worker_test.h | 23 +++++++++++++++++ .../data_processor_test/data_processor_test.h | 16 ++++++++++++ unit_tests/data_store_test/data_store_test.h | 25 +++++++++++++++++++ .../modbus_data_bridge_test.h | 20 +++++++++++++++ .../state_machine_test/state_machine_test.h | 15 +++++++++++ 5 files changed, 99 insertions(+) create mode 100644 unit_tests/backend_worker_test/backend_worker_test.h create mode 100644 unit_tests/data_processor_test/data_processor_test.h create mode 100644 unit_tests/data_store_test/data_store_test.h create mode 100644 unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h create mode 100644 unit_tests/state_machine_test/state_machine_test.h diff --git a/unit_tests/backend_worker_test/backend_worker_test.h b/unit_tests/backend_worker_test/backend_worker_test.h new file mode 100644 index 0000000..74bf08b --- /dev/null +++ b/unit_tests/backend_worker_test/backend_worker_test.h @@ -0,0 +1,23 @@ +#include +#include <../src/backend_worker/backend_worker.h> + +class BackendWorkerTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + void isStart(); + void doubleStart(); + void isStop(); + void doubleStop + void moveOnNextStage(); + void correctCurrentStatus(); + void isFinishedReport(); + void isFinishedHistory(); + void isAlarmEvent(); + void isWarningEvent(); + void isProcessFinished(); + + +} // BackendWorkerTest \ No newline at end of file diff --git a/unit_tests/data_processor_test/data_processor_test.h b/unit_tests/data_processor_test/data_processor_test.h new file mode 100644 index 0000000..8862c75 --- /dev/null +++ b/unit_tests/data_processor_test/data_processor_test.h @@ -0,0 +1,16 @@ +#include +#include <../src/data_processor/data_processor.h> + +class DataProcessorTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + void correctFrame(); + void isDecisionReady(); + void isWarningRaised(); + void isAlarmRaised(); + void isControlProduced(); + +} // DataProcessorTest \ No newline at end of file diff --git a/unit_tests/data_store_test/data_store_test.h b/unit_tests/data_store_test/data_store_test.h new file mode 100644 index 0000000..468b6e8 --- /dev/null +++ b/unit_tests/data_store_test/data_store_test.h @@ -0,0 +1,25 @@ +#include +#include <../src/data_store/data_store.h> + +class DataStoreTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + void isFileExist(); + void isWriteRecord(); + void correctWriteRecord(); + void doubleWriteRecord(); + void rewritePrevRecord(); + void isWriteEvent(); + void correctWriteEvent(); + void doubleWriteEvent(); + void rewritePrevEvent(); + + void isReadRecord(); + void correctReadRecord(); + void isReadEvent(); + void correctReadEvent(); + +} // DataStoreTest \ No newline at end of file diff --git a/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h b/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h new file mode 100644 index 0000000..d34cffd --- /dev/null +++ b/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h @@ -0,0 +1,20 @@ +#include +#include <../src/modbus_data_bridge/modbus_data_bridge.h> + +class ModbusDataBridgeTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + void isStartPolling(); + void doubleStartPolling(); + void isStopPolling(); + void doubleStopPolling(); + void isWriteCommand(); + void correctWriteCommand(); + void isReadData(); + void correctReadData(); + void correctReadError(); + +} // ModbusDataBridgeTest \ No newline at end of file diff --git a/unit_tests/state_machine_test/state_machine_test.h b/unit_tests/state_machine_test/state_machine_test.h new file mode 100644 index 0000000..9569434 --- /dev/null +++ b/unit_tests/state_machine_test/state_machine_test.h @@ -0,0 +1,15 @@ +#include +#include <../src/state_machine/state_machine.h> + +class StateMachineTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + void isStart(); + void moveOnNextStage(); + void isAbort(); + void correctUIStatus(); + +} // StateMachineTest \ No newline at end of file From 5ae8fe3fa62807c6fcc8e405ed51f3aee4d5dbde Mon Sep 17 00:00:00 2001 From: Rikitick Date: Sat, 2 May 2026 03:23:01 +0500 Subject: [PATCH 04/58] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20CMakeLists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dfbd16c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.16) +project(ScadaForDiesel LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Test) + +# Библиотека для бэкенда +set(BACKEND_SOURCES + src/backend_worker/backend_worker.cpp + src/data_processor/data_processor.cpp + src/data_store/data_store.cpp + src/modbus_data_bridge/modbus_data_bridge.cpp + src/state_machine/state_machine.cpp +) + +add_library(backend_lib STATIC ${BACKEND_SOURCES}) + +# Указываем папки с заголовками +target_include_directories(backend_lib PUBLIC + src/backend_worker + src/data_store + src/data_processor + src/modbus_data_bridge + src/state_machine +) +target_link_libraries(backend_lib PUBLIC Qt6::Core) + +enable_testing() + +# Тест для BackendWorker +add_executable(test_backend_worker unit_tests/backend_worker_test/backend_worker_test.cpp) +target_link_libraries(test_backend_worker PRIVATE Qt6::Test backend_lib) +add_test(NAME BackendWorkerTest COMMAND test_backend_worker) + +# Тест для DataProcessor +add_executable(test_data_processor unit_tests/data_processor_test/data_processor_test.cpp) +target_link_libraries(test_data_processor PRIVATE Qt6::Test backend_lib) +add_test(NAME DataProcessorTest COMMAND test_data_processor) + +# Тест для DataStore +add_executable(test_data_store unit_tests/data_store_test/data_store_test.cpp) +target_link_libraries(test_data_store PRIVATE Qt6::Test backend_lib) +add_test(NAME DataStoreTest COMMAND test_data_store) + +# Тест для ModbusDataBridge +add_executable(test_modbus_data_bridge unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp) +target_link_libraries(test_modbus_data_bridge PRIVATE Qt6::Test backend_lib) +add_test(NAME ModbusDataBridgeTest COMMAND test_modbus_data_bridge) + +# Тест для StateMachine +add_executable(test_state_machine unit_tests/state_machine_test/state_machine_test.cpp) +target_link_libraries(test_state_machine PRIVATE Qt6::Test backend_lib) +add_test(NAME StateMachineTest COMMAND test_state_machine) \ No newline at end of file From 680627c7e26278bc382dcf6c7bd020b545d1c7ae Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Sat, 2 May 2026 08:39:22 +0500 Subject: [PATCH 05/58] =?UTF-8?q?=D0=A7=D0=B5=D1=80=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- continue.cpp | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++ inter_1.cpp | 74 ++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 continue.cpp create mode 100644 inter_1.cpp diff --git a/continue.cpp b/continue.cpp new file mode 100644 index 0000000..37bbfd3 --- /dev/null +++ b/continue.cpp @@ -0,0 +1,278 @@ +#include "DataProcessor.h" +#include +#include + +// Номера этапов должны совпадать: +// 0 IDLE, 1 COLD_CRANKING, 2 START_AND_WARMUP, +// 3 HOT_NO_LOAD, 4 HOT_WITH_LOAD, 5 COMPLETED, 6 ABORTED +namespace Stage +{ + constexpr int IDLE = 0; + constexpr int COLD_CRANKING = 1; + constexpr int START_AND_WARMUP = 2; + constexpr int HOT_NO_LOAD = 3; + constexpr int HOT_WITH_LOAD = 4; + constexpr int COMPLETED = 5; + constexpr int ABORTED = 6; +} +// % от критического порога, при достижении которой выдаём предупреждение +static constexpr double WARN_RATIO = 0.9; +DataProcessor::DataProcessor(QObject *parent) + : QObject(parent) +{ +} +void DataProcessor::setConfig(const Config &config) +{ + m_config = config; + m_configured = true; +} +void DataProcessor::onStageChanged(int newStage) +{ + m_currentStage = newStage; + // при переходе в COMPLETED/ABORTED дальнейшие проверки не нужны, + // но решение об этом принимает другой этап, здесь по лгике надо престать слать кадры +} +void DataProcessor::reset() +{ + m_alarmLatched = false; + m_currentStage = Stage::IDLE; + m_lastFrameTsMs = 0; +} +void DataProcessor::processFrame(SensorFrame frame) +{ + if (!m_configured) + { + qWarning() << "[DataProcessor] frame received before config; dropping"; + return; + } + // после аварии блокируем формирование новых решений + if (m_alarmLatched) + { + return; + } + m_lastFrameTsMs = frame.timestampMs; + // в режимах IDLE COMPLETED ABORTED проверки не нужны + if (m_currentStage == Stage::IDLE || + m_currentStage == Stage::COMPLETED || + m_currentStage == Stage::ABORTED) + { + Decision d; + d.status = Status::OK; + d.message = QStringLiteral("inactive stage"); + emit decisionReady(d); + return; + } + // выбор максимально допустимых оборотов в зависимости от этапа. + const double maxRpm = (m_currentStage == Stage::COLD_CRANKING) + ? m_config.maxRpmCold + : m_config.maxRpmHot; + // сборка набора проверок + QVector checks; + if (isParameterActiveOnStage("rpm", m_currentStage)) + { + checks.push_back({"rpm", + frame.rpm, + maxRpm * WARN_RATIO, + maxRpm, + true, + QStringLiteral("Превышение частоты вращения")}); + } + if (isParameterActiveOnStage("dieselTemp", m_currentStage)) + { + checks.push_back({"dieselTemp", + frame.dieselTemp, + m_config.maxDieselTemp * WARN_RATIO, + m_config.maxDieselTemp, + true, + QStringLiteral("Перегрев дизеля")}); + } + if (isParameterActiveOnStage("motorTemp", m_currentStage)) + { + checks.push_back({"motorTemp", + frame.motorTemp, + m_config.maxMotorTemp * WARN_RATIO, + m_config.maxMotorTemp, + true, + QStringLiteral("Перегрев электродвигателя")}); + } + if (isParameterActiveOnStage("resistorTemp", m_currentStage)) + { + checks.push_back({"resistorTemp", + frame.resistorTemp, + m_config.maxResistorTemp * WARN_RATIO, + m_config.maxResistorTemp, + true, + QStringLiteral("Перегрев тормозного резистора")}); + } + if (isParameterActiveOnStage("dieselPressureMax", m_currentStage)) + { + checks.push_back({"dieselPressure", + frame.dieselPressure, + m_config.maxDieselPressure * WARN_RATIO, + m_config.maxDieselPressure, + true, + QStringLiteral("Превышение давления дизеля")}); + } + if (isParameterActiveOnStage("dieselPressureMin", m_currentStage)) + { + // для нижней границы порог "warn" — чуть выше, чем alarm, пока заглушкой и просто зарезервируем место для этого, потом решим как правильно, нужно спросить у команды 1 + const double warn = m_config.minDieselPressure + (m_config.minDieselPressure * (1.0 - WARN_RATIO)); + checks.push_back({"dieselPressure", + frame.dieselPressure, + warn, + m_config.minDieselPressure, + false, + QStringLiteral("Падение давления дизеля")}); + } + // прогон проверок + Status worst = Status::OK; + QString worstMsg; + QString worstParam; + for (const auto &c : checks) + { + Status s = runCheck(c, frame); + if (static_cast(s) > static_cast(worst)) + { + worst = s; + worstMsg = c.description; + worstParam = c.parameter; + } + } + // принятие решения + Decision decision; + decision.status = worst; + decision.message = worstMsg; + if (worst == Status::ALARM) + { + m_alarmLatched = true; + decision.correction = makeStopCommand(); + emit decisionReady(decision); + // команду останова в модель отправляет другой модуль + // получив этот сигнал. DataProcessor сам в Modbus не пишит ничего. + emit controlNeeded(decision.correction, + QStringLiteral("ALARM: %1").arg(worstMsg)); + return; + } + if (worst == Status::WARNING) + { + // если параметр в жёлтой зоне — пробуем сформировать корректировку. + ActuatorCommand cmd; + QString reason; + if (buildCorrection(frame, cmd, reason)) + { + decision.correction = cmd; + emit controlNeeded(cmd, reason); + } + } + emit decisionReady(decision); +} +DataProcessor::Status DataProcessor::runCheck(const Check &c, const SensorFrame &frame) +{ + const bool overWarn = c.upperBound ? (c.value >= c.warnThreshold) + : (c.value <= c.warnThreshold); + const bool overAlarm = c.upperBound ? (c.value >= c.alarmThreshold) + : (c.value <= c.alarmThreshold); + if (overAlarm) + { + AlarmEvent ev; + ev.runId = frame.runId; + ev.timestampMs = frame.timestampMs; + ev.stage = frame.stage; + ev.parameter = c.parameter; + ev.currentValue = c.value; + ev.criticalThreshold = c.alarmThreshold; + ev.reason = c.description; + emit alarmRaised(ev); + return Status::ALARM; + } + if (overWarn) + { + WarningEvent ev; + ev.runId = frame.runId; + ev.timestampMs = frame.timestampMs; + ev.stage = frame.stage; + ev.parameter = c.parameter; + ev.currentValue = c.value; + ev.threshold = c.warnThreshold; + ev.description = c.description; + emit warningRaised(ev); + return Status::WARNING; + } + return Status::OK; +} +bool DataProcessor::isParameterActiveOnStage(const QString ¶meter, int stage) const +{ + // исключение ложных срабатываний в неактивных режимах + if (stage == Stage::IDLE || + stage == Stage::COMPLETED || + stage == Stage::ABORTED) + { + return false; + } + // давление дизеля имеет смысл только когда дизель запущен, так же? + if (parameter == "dieselPressureMin" || parameter == "dieselPressureMax") + { + return (stage == Stage::START_AND_WARMUP || + stage == Stage::HOT_NO_LOAD || + stage == Stage::HOT_WITH_LOAD); + } + // температура дизеля смотрим только после запуска. + if (parameter == "dieselTemp") + { + return (stage == Stage::START_AND_WARMUP || + stage == Stage::HOT_NO_LOAD || + stage == Stage::HOT_WITH_LOAD); + } + // температура тормозного резистора только на этапе с нагрузкой. + if (parameter == "resistorTemp") + { + return (stage == Stage::HOT_WITH_LOAD); + } + // rpm и motorTemp — на всех активных этапах. + return true; +} +bool DataProcessor::buildCorrection(const SensorFrame &frame, + ActuatorCommand &outCmd, + QString &outReason) const +{ + // если на этапе с нагрузкой растёт температура + // тормозного резистора — снижаем тормозной момент, но это надо уточнить у тех кто делает модель + if (m_currentStage == Stage::HOT_WITH_LOAD && + frame.resistorTemp >= m_config.maxResistorTemp * WARN_RATIO) + { + outCmd.motorEnabled = true; + outCmd.targetRpm = frame.rpm; + outCmd.throttlePosition = m_config.throttleHotLoad; + outCmd.brakeTorque = m_config.brakeTorqueHotLoad * 0.7; // снизили + outReason = QStringLiteral( + "Снижение тормозного момента: температура резистора %1 близка к пределу") + .arg(frame.resistorTemp); + return true; + } + // если обороты близки к лимиту надл прикрыть дроссель. + const double maxRpm = (m_currentStage == Stage::COLD_CRANKING) + ? m_config.maxRpmCold + : m_config.maxRpmHot; + if (frame.rpm >= maxRpm * WARN_RATIO) + { + outCmd.motorEnabled = (m_currentStage == Stage::COLD_CRANKING); + outCmd.targetRpm = m_config.targetRpmCold; + outCmd.throttlePosition = frame.throttle * 0.8; + outCmd.brakeTorque = frame.brakeTorque; + outReason = QStringLiteral( + "Снижение дросселя: обороты %1 близки к пределу %2") + .arg(frame.rpm) + .arg(maxRpm); + return true; + } + return false; +} +ActuatorCommand DataProcessor::makeStopCommand() +{ + ActuatorCommand cmd; + cmd.motorEnabled = false; + cmd.targetRpm = 0.0; + cmd.throttlePosition = 0.0; + cmd.brakeTorque = 0.0; + return cmd; +} \ No newline at end of file diff --git a/inter_1.cpp b/inter_1.cpp new file mode 100644 index 0000000..dfb124c --- /dev/null +++ b/inter_1.cpp @@ -0,0 +1,74 @@ +#ifndef DATAPROCESSOR_H +#define DATAPROCESSOR_H + +#include +#include +#include +#include +#include "DataTypes.h" // SensorFrame, Decision, WarningEvent, AlarmEvent, ActuatorCommand, Config + +/** + * ver_1_черновик + * Принимает измерительные пакеты, сверяет значения с + * критическими порогами и формирует: + * - Decision; + * - WarningEvent; + * - AlarmEvent; + * - ActuatorCommand - так и не поняла оставлять это или нет??. + */ +class DataProcessor : public QObject +{ + Q_OBJECT +public: + explicit DataProcessor(QObject *parent = nullptr); + // загрузка пороговых значений + void setConfig(const Config &config); +public slots: + // приём пакета. + void processFrame(SensorFrame frame); + // информирование об этапе испытания + void onStageChanged(int newStage); + // сброс внутреннего состояния + void reset(); +signals: + // итог обработки каждого пакета + void decisionReady(Decision decision); + // предупреждение + void warningRaised(WarningEvent event); + // авария + void alarmRaised(AlarmEvent event); + // коррекция + void controlNeeded(ActuatorCommand correction, QString reason); + +private: + // описания проверки + struct Check + { + QString parameter; // имя параметра (для события) + double value; // текущее значение + double warnThreshold; // порог предупреждения + double alarmThreshold; // критический порог + bool upperBound; // true: нарушение при value > threshold; + // false: нарушение при value < threshold + QString description; + }; + Status runCheck(const Check &c, const SensorFrame &frame); + bool isParameterActiveOnStage(const QString ¶meter, int stage) const; // допустим ли контроль данного параметра в текущем этапе? + /// формирование корректирующего воздействия. Возвращает true, если коррекция сформирована. + bool buildCorrection(const SensorFrame &frame, + ActuatorCommand &outCmd, + QString &outReason) const; + // аварийная команда остановки (мотор выкл, дроссель/тормоз = 0). + static ActuatorCommand makeStopCommand(); + +private: + Config m_config; + int m_currentStage = 0; // текущий этап + bool m_alarmLatched = false; // после аварии блокируем дальнейшую генерацию сигналов + bool m_configured = false; + // Таймаут отсутствия данных и контроль допустимости управляющих сигналов + // еще не решили. Поля зарезервированы на всякий случай. + qint64 m_lastFrameTsMs = 0; + // qint64 m_dataTimeoutMs = 2000; // TODO: согласовать +}; +#endif // DATAPROCESSOR_H \ No newline at end of file From 2cd756093802fcd1de43bf2c4f9d5a6f4c46404a Mon Sep 17 00:00:00 2001 From: lefff123 Date: Sat, 2 May 2026 19:38:17 +0500 Subject: [PATCH 06/58] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=83=D1=8E=20=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D1=83=20git.=20=D0=92=D1=81=D0=B5=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=BB=D0=BE=D0=B6=D0=B8=D0=BB=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BE=D1=87=D0=BA=D0=B0=D0=BC.=20=D0=A3?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20=D0=B1=D0=BB=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BC=D1=83=D0=BD=D0=BD=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8,=20=D0=BD=D0=B0=D0=B4=D0=BE=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tz.md => Tz_backend.md | 26 +------------- CMakeLists.txt => src/backend/CMakeLists.txt | 0 .../BackendWorker/backendcommunicator.cpp | 22 ++++++++++++ .../BackendWorker/backendcommunicator.h | 30 ++++++++++++++++ .../BackendWorker/backendworker.cpp | 28 +++++++++++++++ .../BackendWorker/backendworker.h | 36 +++++++++++++++++++ .../CMakeLists.txt | 24 +++++++++++++ .../backendcore.cpp | 12 +++++++ .../communication_with_frontend/backendcore.h | 26 ++++++++++++++ .../communication_with_frontend/struct.h | 27 ++++++++++++++ .../backend/data_processing/DataProcessor.cpp | 2 +- .../backend/data_processing/DataProcessor.h | 3 +- .../backend_worker_test/backend_worker_test.h | 0 .../data_processor_test/data_processor_test.h | 0 .../data_store_test/data_store_test.h | 0 .../modbus_data_bridge_test.h | 0 .../state_machine_test/state_machine_test.h | 0 17 files changed, 209 insertions(+), 27 deletions(-) rename Tz.md => Tz_backend.md (87%) rename CMakeLists.txt => src/backend/CMakeLists.txt (100%) create mode 100644 src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp create mode 100644 src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h create mode 100644 src/backend/communication_with_frontend/BackendWorker/backendworker.cpp create mode 100644 src/backend/communication_with_frontend/BackendWorker/backendworker.h create mode 100644 src/backend/communication_with_frontend/CMakeLists.txt create mode 100644 src/backend/communication_with_frontend/backendcore.cpp create mode 100644 src/backend/communication_with_frontend/backendcore.h create mode 100644 src/backend/communication_with_frontend/struct.h rename continue.cpp => src/backend/data_processing/DataProcessor.cpp (97%) rename inter_1.cpp => src/backend/data_processing/DataProcessor.h (94%) rename {unit_tests => src/backend/unit_tests}/backend_worker_test/backend_worker_test.h (100%) rename {unit_tests => src/backend/unit_tests}/data_processor_test/data_processor_test.h (100%) rename {unit_tests => src/backend/unit_tests}/data_store_test/data_store_test.h (100%) rename {unit_tests => src/backend/unit_tests}/modbus_data_bridge_test/modbus_data_bridge_test.h (100%) rename {unit_tests => src/backend/unit_tests}/state_machine_test/state_machine_test.h (100%) diff --git a/Tz.md b/Tz_backend.md similarity index 87% rename from Tz.md rename to Tz_backend.md index fa560a8..5d1bf90 100644 --- a/Tz.md +++ b/Tz_backend.md @@ -28,31 +28,7 @@ Живёт в рабочем потоке. Владеет экземплярами всех остальных модулей, соединяет их сигналы и слоты. -**Слоты (команды от фронтенда):** -| Слот | Описание | -|------|----------| -| `void onStart(Config config)` | Запуск нового испытания. | -| `void onStop()` | Экстренная остановка. | -| `void onNextStage()` | Принудительный переход к следующему этапу. | -| `void onGetStatus()` | Запрос текущего состояния. | -| `void onGetReport(quint64 runId)` | Запрос отчёта по завершённому испытанию. | -| `void onGetHistory()` | Запрос списка завершённых испытаний. | - -*(Большинство названий слотов предложено автором, кроме `onStop` и `onNextStage`, заложенных в ТЗ управления процессом.)* - -**Сигналы (данные и события для фронтенда):** - -| Сигнал | Описание | -|--------|----------| -| `void statusUpdated(QString state, SensorFrame lastData)` | Периодическая отправка состояния (не реже 1 раза в сек). | -| `void liveData(SensorFrame frame)` | Каждое новое измерение. | -| `void alarmEvent(AlarmEvent event)` | Немедленное аварийное уведомление. | -| `void warningEvent(WarningEvent event)` | Предупреждение. | -| `void processFinished(Report report)` | Завершение испытания, передача отчёта. | -| `void historyReady(QVector runIds)` | Ответ на запрос истории. | - -*(`statusUpdated`, `liveData`, `alarmEvent`, `warningEvent` — из исходных требований «передача нештатных сообщений»; `processFinished`, `historyReady` — предложено автором.)* ### 3.2. Модуль управления процессом (StateMachine) @@ -281,4 +257,4 @@ struct Report { ### 6.4. Запросы от фронта вне активного испытания - `onGetStatus` → `BackendWorker` отправляет `statusUpdated` с последним сохранённым состоянием. - `onGetReport` → вызов `DataStore::buildReport`, ответ через `processFinished` (можно переиспользовать сигнал, передавая `report` и идентифицируя запрос). -- `onGetHistory` → чтение списка CSV-файлов в папке `reports`, возврат `historyReady(ids)`. \ No newline at end of file +- `onGetHistory` → чтение списка CSV-файлов в папке `reports`, возврат `historyReady(ids)`. diff --git a/CMakeLists.txt b/src/backend/CMakeLists.txt similarity index 100% rename from CMakeLists.txt rename to src/backend/CMakeLists.txt diff --git a/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp b/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp new file mode 100644 index 0000000..d2a06f7 --- /dev/null +++ b/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp @@ -0,0 +1,22 @@ +#include "backendcommunicator.h" + +BackendCommunicator::BackendCommunicator(QObject *parent) + : QObject{parent} +{ + connect(this, &BackendCommunicator::CommandToBackend, &BackendCommunicator::onCommandToBackend); +} + +void BackendCommunicator::SendSensorFrameToFrontend(SensorFrame& sensorFrame) +{ + emit NewSensorFrame(sensorFrame); +} + +void BackendCommunicator::SendEmergencyStopInfoToFrontend() +{ + emit EmergencyStop(); +} + +void BackendCommunicator::onCommandToBackend(CommandToBackend command) +{ + //Отправка команды в модуль бизнес-логики +} diff --git a/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h b/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h new file mode 100644 index 0000000..43d84a1 --- /dev/null +++ b/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h @@ -0,0 +1,30 @@ +#ifndef BACKENDCOMMUNICATOR_H +#define BACKENDCOMMUNICATOR_H + +#include +#include "../struct.h" + +class BackendCommunicator : public QObject +{ + Q_OBJECT +public: + explicit BackendCommunicator(QObject *parent = nullptr); + + //Метод для отправки новых данных фронту + void SendSensorFrameToFrontend(SensorFrame& sensorFrame); + //Метод для отправки информации фронту об аварийной остановке + void SendEmergencyStopInfoToFrontend(); + +signals: + //Сигнал отправки новых данных фронту + void NewSensorFrame(SensorFrame sensorFrame); + //Сигнал отправки информации фронту об аварийной остановке + void EmergencyStop(); + //Сигнал на комманду от фронта + void CommandToBackend(CommandToBackend command); +private slots: + //Слот обработки комманды от фронта + void onCommandToBackend(CommandToBackend command); +}; + +#endif // BACKENDCOMMUNICATOR_H diff --git a/src/backend/communication_with_frontend/BackendWorker/backendworker.cpp b/src/backend/communication_with_frontend/BackendWorker/backendworker.cpp new file mode 100644 index 0000000..ac5ffec --- /dev/null +++ b/src/backend/communication_with_frontend/BackendWorker/backendworker.cpp @@ -0,0 +1,28 @@ +#include "backendworker.h" + +BackendWorker::BackendWorker(QObject *parent) : + m_backendCore(new BackendCore()), m_coreThread(QThread(this)), QObject{parent} +{ + +} + +void BackendWorker::RunCore() +{ + auto backendCommunicator = m_backendCore->GetBackendCommunicator(); + connect(&backendCommunicator, &BackendCommunicator::NewSensorFrame, this, &BackendWorker::NewSensorFrame, Qt::QueuedConnection); + connect(&backendCommunicator, &BackendCommunicator::EmergencyStop, this, &BackendWorker::EmergencyStop, Qt::QueuedConnection); + connect(&m_coreThread, &QThread::started, m_backendCore, &BackendCore::RunCore, Qt::QueuedConnection); + connect(&m_coreThread, &QThread::finished, m_backendCore, &BackendCore::StopCore, Qt::QueuedConnection); + m_backendCore->moveToThread(&m_coreThread); + m_coreThread.start(); +} + +void BackendWorker::StopCore() +{ + m_coreThread.quit(); +} + +BackendWorker::~BackendWorker() +{ + m_coreThread.deleteLater(); +} diff --git a/src/backend/communication_with_frontend/BackendWorker/backendworker.h b/src/backend/communication_with_frontend/BackendWorker/backendworker.h new file mode 100644 index 0000000..e5ee1da --- /dev/null +++ b/src/backend/communication_with_frontend/BackendWorker/backendworker.h @@ -0,0 +1,36 @@ +#ifndef BACKENDWORKER_H +#define BACKENDWORKER_H + +#include +#include +#include "../backendcore.h" +#include "../struct.h" + +//Класс взаимодействия с фронтом +class BackendWorker : public QObject +{ + Q_OBJECT +public: + explicit BackendWorker(QObject *parent = nullptr); + ~BackendWorker(); + + //Запуск ядра бэкэнда + void RunCore(); + //Остановка ядра бэкэнда + void StopCore(); + + //Команда бэкэнду от фронта + void CommandToBackend(CommandToBackend command); + +signals: + //Сигнал прихода новых данных для фронта + void NewSensorFrame(SensorFrame sensorFrame); + //Сигнал возникновения аварийной остановки для фронта + void EmergencyStop(); + +private: + QThread m_coreThread; + BackendCore* m_backendCore; +}; + +#endif // BACKENDWORKER_H diff --git a/src/backend/communication_with_frontend/CMakeLists.txt b/src/backend/communication_with_frontend/CMakeLists.txt new file mode 100644 index 0000000..63e5762 --- /dev/null +++ b/src/backend/communication_with_frontend/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.14) + +project(lib LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +add_library(lib SHARED + lib_global.h + BackendWorker/backendworker.h BackendWorker/backendworker.cpp + backendcore.h backendcore.cpp + struct.h + BackendWorker/backendcommunicator.h BackendWorker/backendcommunicator.cpp +) + +target_link_libraries(lib PRIVATE Qt${QT_VERSION_MAJOR}::Core) + +target_compile_definitions(lib PRIVATE LIB_LIBRARY) diff --git a/src/backend/communication_with_frontend/backendcore.cpp b/src/backend/communication_with_frontend/backendcore.cpp new file mode 100644 index 0000000..3bcc1a3 --- /dev/null +++ b/src/backend/communication_with_frontend/backendcore.cpp @@ -0,0 +1,12 @@ +#include "backendcore.h" + +BackendCore::BackendCore(QObject *parent) + : m_backendCommunicator(new BackendCommunicator(this)), QObject{parent} +{ + +} + +void BackendCore::RunCore() +{ + //Запуск таймера опроса модели +} diff --git a/src/backend/communication_with_frontend/backendcore.h b/src/backend/communication_with_frontend/backendcore.h new file mode 100644 index 0000000..837b7bf --- /dev/null +++ b/src/backend/communication_with_frontend/backendcore.h @@ -0,0 +1,26 @@ +#ifndef BACKENDCORE_H +#define BACKENDCORE_H + +#include +#include "BackendWorker/backendcommunicator.h" + +class BackendCore : public QObject +{ + Q_OBJECT +public: + explicit BackendCore(QObject *parent = nullptr); + ~BackendCore(); + + //Запуск ядра бэкэнда(запуск таймера) + void RunCore(); + //Остановка ядра + void StopCore(); + + //Геттер для получения указателя на коммуникатор + BackendCommunicator* GetBackendCommunicator(); + +private: + BackendCommunicator* m_backendCommunicator; +}; + +#endif // BACKENDCORE_H diff --git a/src/backend/communication_with_frontend/struct.h b/src/backend/communication_with_frontend/struct.h new file mode 100644 index 0000000..533798d --- /dev/null +++ b/src/backend/communication_with_frontend/struct.h @@ -0,0 +1,27 @@ +#ifndef STRUCT_H +#define STRUCT_H + +struct SensorFrame +{ + long runId; + int stage; + long timestampMs; + double rpm, torque; + double diselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; + +enum EngineState +{ + Stop = 0, + Run +}; + +struct CommandToBackend +{ + EngineState state; + double rpm; +}; + + +#endif // STRUCT_H diff --git a/continue.cpp b/src/backend/data_processing/DataProcessor.cpp similarity index 97% rename from continue.cpp rename to src/backend/data_processing/DataProcessor.cpp index 37bbfd3..b73eceb 100644 --- a/continue.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,4 +1,4 @@ -#include "DataProcessor.h" +#include "DataProcessor.h" #include #include diff --git a/inter_1.cpp b/src/backend/data_processing/DataProcessor.h similarity index 94% rename from inter_1.cpp rename to src/backend/data_processing/DataProcessor.h index dfb124c..4bae412 100644 --- a/inter_1.cpp +++ b/src/backend/data_processing/DataProcessor.h @@ -5,7 +5,8 @@ #include #include #include -#include "DataTypes.h" // SensorFrame, Decision, WarningEvent, AlarmEvent, ActuatorCommand, Config +#include "DataTypes.h" //Допишем +// SensorFrame, Decision, WarningEvent, AlarmEvent, ActuatorCommand, Config /** * ver_1_черновик diff --git a/unit_tests/backend_worker_test/backend_worker_test.h b/src/backend/unit_tests/backend_worker_test/backend_worker_test.h similarity index 100% rename from unit_tests/backend_worker_test/backend_worker_test.h rename to src/backend/unit_tests/backend_worker_test/backend_worker_test.h diff --git a/unit_tests/data_processor_test/data_processor_test.h b/src/backend/unit_tests/data_processor_test/data_processor_test.h similarity index 100% rename from unit_tests/data_processor_test/data_processor_test.h rename to src/backend/unit_tests/data_processor_test/data_processor_test.h diff --git a/unit_tests/data_store_test/data_store_test.h b/src/backend/unit_tests/data_store_test/data_store_test.h similarity index 100% rename from unit_tests/data_store_test/data_store_test.h rename to src/backend/unit_tests/data_store_test/data_store_test.h diff --git a/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h similarity index 100% rename from unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h rename to src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h diff --git a/unit_tests/state_machine_test/state_machine_test.h b/src/backend/unit_tests/state_machine_test/state_machine_test.h similarity index 100% rename from unit_tests/state_machine_test/state_machine_test.h rename to src/backend/unit_tests/state_machine_test/state_machine_test.h From 53973719a2aff38365e9f30eb00906ce8e9b03b6 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Sun, 3 May 2026 00:57:58 +0500 Subject: [PATCH 07/58] feat: add first version of modbus bridge and it's config builder --- include/backend/DataTypes.h | 29 +++ .../modbus_client/bridge/IModbusBridge.h | 61 +++++++ .../modbus_client/bridge/MockModbusBridge.h | 25 +++ .../modbus_client/bridge/QtModbusBridge.h | 33 ++++ .../config/IModbusConfigBuilder.h | 53 ++++++ .../config/MockModbusConfigBuilder.h | 8 + .../config/ModbusConfigBuilder.h | 10 ++ .../modbus_client/bridge/IModbusBridge.cpp | 3 + .../modbus_client/bridge/MockModbusBridge.cpp | 58 ++++++ .../modbus_client/bridge/QtModbusBridge.cpp | 167 ++++++++++++++++++ .../config/MockModbusConfigBuilder.cpp | 13 ++ .../config/ModbusConfigBuilder.cpp | 13 ++ 12 files changed, 473 insertions(+) create mode 100644 include/backend/DataTypes.h create mode 100644 include/backend/modbus_client/bridge/IModbusBridge.h create mode 100644 include/backend/modbus_client/bridge/MockModbusBridge.h create mode 100644 include/backend/modbus_client/bridge/QtModbusBridge.h create mode 100644 include/backend/modbus_client/config/IModbusConfigBuilder.h create mode 100644 include/backend/modbus_client/config/MockModbusConfigBuilder.h create mode 100644 include/backend/modbus_client/config/ModbusConfigBuilder.h create mode 100644 src/backend/modbus_client/bridge/IModbusBridge.cpp create mode 100644 src/backend/modbus_client/bridge/MockModbusBridge.cpp create mode 100644 src/backend/modbus_client/bridge/QtModbusBridge.cpp create mode 100644 src/backend/modbus_client/config/MockModbusConfigBuilder.cpp create mode 100644 src/backend/modbus_client/config/ModbusConfigBuilder.cpp diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h new file mode 100644 index 0000000..b17e996 --- /dev/null +++ b/include/backend/DataTypes.h @@ -0,0 +1,29 @@ +#pragma once +#include + +// Settings of model (for writing/reading operations) +struct ModelConfig +{ + bool motorEnabled; + double targetRpm; + double throttlePosition; + double brakeTorque; +}; + +// States of model (for only reading operations) +struct ModelInfo { + bool fanAd; + bool fanBall; + ModelConfig cfg; +}; + +// Data of sensors +struct SensorFrame +{ + qint64 runId; + int stage; + qint64 timestampMs; + double rpm, torque; + double dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; \ No newline at end of file diff --git a/include/backend/modbus_client/bridge/IModbusBridge.h b/include/backend/modbus_client/bridge/IModbusBridge.h new file mode 100644 index 0000000..cb150ec --- /dev/null +++ b/include/backend/modbus_client/bridge/IModbusBridge.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include +#include + +class ModbusConfig; +class ModelConfig; +class ModelInfo; +class SensorFrame; + +class IModbusBridge : public QObject +{ + Q_OBJECT +public: + explicit IModbusBridge(const ModbusConfig& cfg, QObject* parent = nullptr); + virtual ~IModbusBridge() = default; +public slots: + // Starting a scheduled data retrieval from the sensor using a timer + virtual void startPolling() = 0; + // Stopping a scheduled data retrieval from the sensor + virtual void stopPolling() = 0; + // Handling direct call to read sensor data + virtual void onReadSensors() = 0; + // Handling direct call to read the model info + virtual void onReadInfo() = 0; + // Handling direct call to write new model configuration + virtual void onWriteConfig(const ModelConfig& cmd) = 0; +signals: + // A response to the sensor data request has been received + void sensorsDataReady(const SensorFrame& data); + // A response to the model info request has been received + void modelInfoReady(const ModelInfo& data); + // Error signals + /** + * @brief Emitted when configuration fails during startup/setup. + * Groups QModbusDevice errors: ConfigurationError. + * Note: config validation should be handled by the config builder, + * so this error would not even happen. + */ + void configurationError(QModbusDevice::Error type, const QString& reason); + /** + * @brief Emitted when physical connection fails or is lost. + * Groups QModbusDevice errors: ConnectionError. + */ + void connectionError(QModbusDevice::Error type, const QString& reason); + /** + * @brief Emitted when a read/write request fails. + * Occurs during: Polling requests, direct read/write requests. + * Groups QModbusDevice errors: ReadError, WriteError, + * TimeoutError, ReplyAbortedError, InvalidResponseError, ProtocolError. + */ + void requestError(QModbusDevice::Error type, const QString& reason); + /** + * @brief Any error not fitting above categories + * Groups QModbusDevice errors: UnknownError. + */ + void generalError(QModbusDevice::Error type, const QString& reason); + // Transition states + void connectionLost(); + void connectionRestored(); +}; diff --git a/include/backend/modbus_client/bridge/MockModbusBridge.h b/include/backend/modbus_client/bridge/MockModbusBridge.h new file mode 100644 index 0000000..d78621f --- /dev/null +++ b/include/backend/modbus_client/bridge/MockModbusBridge.h @@ -0,0 +1,25 @@ +#pragma once +#include "IModbusBridge.h" +#include "IModbusConfigBuilder.h" +#include + +class ModelConfig; + +class MockModbusBridge : public IModbusBridge +{ + Q_OBJECT +public: + explicit MockModbusBridge(const ModbusConfig& cfg, QObject* parent = nullptr); +public slots: + void startPolling() override; + void stopPolling() override; + void onReadSensors() override; + void onReadInfo() override; + void onWriteConfig(const ModelConfig& cmd) override; +private slots: + void requestSensors(); + void requestInfo(); +private: + ModbusConfig m_cfg; + QTimer m_pollTimer; +}; diff --git a/include/backend/modbus_client/bridge/QtModbusBridge.h b/include/backend/modbus_client/bridge/QtModbusBridge.h new file mode 100644 index 0000000..3f2f867 --- /dev/null +++ b/include/backend/modbus_client/bridge/QtModbusBridge.h @@ -0,0 +1,33 @@ +#pragma once +#include "IModbusBridge.h" +#include "IModbusConfigBuilder.h" +#include +#include +#include + +class ModelConfig; + +class QtModbusBridge : public IModbusBridge +{ + Q_OBJECT +public: + explicit QtModbusBridge(const ModbusConfig& cfg, QObject* parent = nullptr); + ~QtModbusBridge() override; +public slots: + void startPolling() override; + void stopPolling() override; + void onReadSensors() override; + void onReadInfo() override; + void onWriteConfig(const ModelConfig& cmd) override; +private slots: + void parseSensorsResponse(QModbusReply* reply); + void requestSensors(); + void parseInfoResponse(QModbusReply* reply); + void requestInfo(); +private: + ModbusConfig m_cfg; + QModbusDataUnit m_dataUnit; + QModbusTcpClient m_client; + QTimer m_pollTimer; + QModbusDevice::State m_prevState = QModbusDevice::UnconnectedState; +}; diff --git a/include/backend/modbus_client/config/IModbusConfigBuilder.h b/include/backend/modbus_client/config/IModbusConfigBuilder.h new file mode 100644 index 0000000..2b1ce92 --- /dev/null +++ b/include/backend/modbus_client/config/IModbusConfigBuilder.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include + +// Control registers mapping, read/write, 1-bit +struct CoilsRegistersScheme +{ + bool motorEnabled; +}; + +// Device status registers mapping, only read, 1-bit +struct DiscreteRegistersScheme +{ + bool fanAd; + bool fanBall; +}; + +// Settings and variables registers mapping, read/write, 16-bit +struct HoldingRegistersScheme +{ + double targetRpm; + double throttlePosition; + double brakeTorque; +}; + +// Sensor data registers mapping, only read, 16-bit +struct InputRegistersScheme +{ + double rpm, torque; + double dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; + +struct ModbusConfig +{ + QString host; + quint16 port = 1502; + int pollFrequencyMs = 1000; + int timeoutMs = 1000; + int retries = 3; + int unitId = 1; // we assume one modbus device, so unitId will be ignored + // Start address is 0 for each register type + CoilsRegistersScheme coils; + DiscreteRegistersScheme discrete; + InputRegistersScheme input; + HoldingRegistersScheme holding; +}; + +class IModbusConfigBuilder +{ +public: + virtual std::optional get() const = 0; +}; diff --git a/include/backend/modbus_client/config/MockModbusConfigBuilder.h b/include/backend/modbus_client/config/MockModbusConfigBuilder.h new file mode 100644 index 0000000..03de632 --- /dev/null +++ b/include/backend/modbus_client/config/MockModbusConfigBuilder.h @@ -0,0 +1,8 @@ +#pragma once +#include "IModbusConfigBuilder.h" + +class MockModbusConfigBuilder : public IModbusConfigBuilder +{ +public: + std::optional get() const override; +}; diff --git a/include/backend/modbus_client/config/ModbusConfigBuilder.h b/include/backend/modbus_client/config/ModbusConfigBuilder.h new file mode 100644 index 0000000..2945cc1 --- /dev/null +++ b/include/backend/modbus_client/config/ModbusConfigBuilder.h @@ -0,0 +1,10 @@ +#pragma once +#include "IModbusConfigBuilder.h" + +class ModbusConfigBuilder : public IModbusConfigBuilder +{ +public: + std::optional get() const override; +private: + bool validate(); +}; diff --git a/src/backend/modbus_client/bridge/IModbusBridge.cpp b/src/backend/modbus_client/bridge/IModbusBridge.cpp new file mode 100644 index 0000000..fc83a47 --- /dev/null +++ b/src/backend/modbus_client/bridge/IModbusBridge.cpp @@ -0,0 +1,3 @@ +#include "IModbusBridge.h" + +IModbusBridge::IModbusBridge(const ModbusConfig& cfg, QObject* parent) : QObject(parent) {} diff --git a/src/backend/modbus_client/bridge/MockModbusBridge.cpp b/src/backend/modbus_client/bridge/MockModbusBridge.cpp new file mode 100644 index 0000000..45a09a2 --- /dev/null +++ b/src/backend/modbus_client/bridge/MockModbusBridge.cpp @@ -0,0 +1,58 @@ +#include "MockModbusBridge.h" +#include "DataTypes.h" +#include "IModbusConfigBuilder.h" +#include +#include +#include + +MockModbusBridge::MockModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModbusBridge(cfg, parent) +{ + m_cfg = cfg; + QObject::connect(&m_pollTimer, &QTimer::timeout, this, &MockModbusBridge::requestSensors); + qInfo() << "[MockModbusBridge] Initialization is successfull"; + QThread::msleep(1000); + qInfo() << "[MockModbusBridge] Connected to the Modbus Server"; +} + +void MockModbusBridge::startPolling() +{ + qInfo() << "[MockModbusBridge] Starting to poll server"; + m_pollTimer.start(m_cfg.pollFrequencyMs); +} + +void MockModbusBridge::stopPolling() +{ + qInfo() << "[MockModbusBridge] Stopping to poll server"; + m_pollTimer.stop(); +} + +void MockModbusBridge::onReadSensors() +{ + qInfo() << "[MockModbusBridge] Core is directly requesting to read sensor data"; + requestSensors(); +} + +void MockModbusBridge::onReadInfo() +{ + qInfo() << "[MockModbusBridge] Core is directly requesting to read the model info"; + requestInfo(); +} + +void MockModbusBridge::onWriteConfig(const ModelConfig& cmd) +{ + qInfo() << "[MockModbusBridge] Writing a new configuration to the model"; +} + +void MockModbusBridge::requestSensors() +{ + qInfo() << "[MockModbusBridge] Requesting data"; + SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + emit sensorsDataReady(frame); +} + +void MockModbusBridge::requestInfo() +{ + qInfo() << "[MockModbusBridge] Requesting model info"; + ModelInfo info = { 1, 1, {true, 1, 1, 1} }; + emit modelInfoReady(info); +} diff --git a/src/backend/modbus_client/bridge/QtModbusBridge.cpp b/src/backend/modbus_client/bridge/QtModbusBridge.cpp new file mode 100644 index 0000000..8076e74 --- /dev/null +++ b/src/backend/modbus_client/bridge/QtModbusBridge.cpp @@ -0,0 +1,167 @@ +#include "QtModbusBridge.h" +#include "DataTypes.h" +#include +#include +#include +#include +#include + +QtModbusBridge::QtModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModbusBridge(cfg, parent) +{ + m_cfg = cfg; + + m_client.setConnectionParameter(QModbusDevice::NetworkAddressParameter, QVariant::fromValue(m_cfg.host)); + m_client.setConnectionParameter(QModbusDevice::NetworkPortParameter, m_cfg.port); + m_client.setTimeout(m_cfg.timeoutMs); + m_client.setNumberOfRetries(m_cfg.retries); + + QObject::connect(&m_pollTimer, &QTimer::timeout, this, &QtModbusBridge::requestSensors); + + QObject::connect(&m_client, &QModbusDevice::stateChanged, this, [this](QModbusDevice::State state) + { + // Handling states change + if (state == QModbusDevice::UnconnectedState) + { + qDebug() << "[QtModbusBridge] Modbus client and server are disconnected"; + } + else if (state == QModbusDevice::ConnectingState) + { + qDebug() << "[QtModbusBridge] Connecting to the modbus device..."; + } + else if (state == QModbusDevice::ConnectedState) + { + qDebug() << "[QtModbusBridge] Connected to the modbus server succesfully"; + } + else if (state == QModbusDevice::ClosingState) + { + qDebug() << "[QtModbusBridge] Closing the modbus device..."; + } + // Handling state transitions + if (state == QModbusDevice::ConnectedState && m_prevState != QModbusDevice::ConnectedState) + { + emit connectionRestored(); + } + else if (m_prevState == QModbusDevice::ConnectedState && state != QModbusDevice::ConnectedState) + { + emit connectionLost(); + } + m_prevState = state; + }); + + QObject::connect(&m_client, &QModbusDevice::errorOccurred, this, [this]() + { + QModbusDevice::Error error = m_client.error(); + QString reason = m_client.errorString(); + if (error == QModbusDevice::ConfigurationError) + { + emit configurationError(error, reason); + } + else if (error == QModbusDevice::ConnectionError) + { + emit connectionError(error, reason); + } + else if (error != QModbusDevice::UnknownError) + { + emit requestError(error, reason); + } + else + { + emit generalError(error, reason); + } + }); + + qDebug() << "[QtModbusBridge] Initialization is successfull"; + + m_client.connectDevice(); +} + +QtModbusBridge::~QtModbusBridge() +{ + m_client.disconnectDevice(); +} + +void QtModbusBridge::startPolling() +{ + qDebug() << "[QtModbusBridge] Starting to poll server"; + m_pollTimer.start(m_cfg.pollFrequencyMs); +} + +void QtModbusBridge::stopPolling() +{ + qDebug() << "[QtModbusBridge] Starting to poll server"; + m_pollTimer.stop(); +} + +void QtModbusBridge::onReadSensors() +{ + requestSensors(); +} + +void QtModbusBridge::onReadInfo() +{ + requestInfo(); +} + +void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) +{ + +} + +void QtModbusBridge::parseSensorsResponse(QModbusReply* reply) +{ + if (reply->error() == QModbusDevice::NoError) + { + return; + } + + const QModbusDataUnit result = reply->result(); + const QVector values = result.values(); + + qDebug() << "[QtModbusBridge] Read registers:" << values.size(); + for (int i = 0; i < values.size(); ++i) + { + qDebug() << "[QtModbusBridge] Register #" << i << ":" << values[i]; + } +} + +void QtModbusBridge::requestSensors() +{ + qDebug() << "[QtModbusBridge] Requesting data"; + + m_dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); + m_dataUnit.setStartAddress(0); + m_dataUnit.setValueCount(sizeof(m_cfg.holding) / 2); + + // Sending request to the device with specified Unit Id + auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); + + if (!reply) + { + return; + } + + // Check if request failed immediately + if (reply->isFinished()) + { + reply->deleteLater(); + return; + } + + // Handling reply with finished signal + QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() + { + parseSensorsResponse(reply); + reply->deleteLater(); + }); +} + +void QtModbusBridge::parseInfoResponse(QModbusReply* reply) +{ + +} + +void QtModbusBridge::requestInfo() +{ + qDebug() << "[QtModbusBridge] Requesting model info"; + +} \ No newline at end of file diff --git a/src/backend/modbus_client/config/MockModbusConfigBuilder.cpp b/src/backend/modbus_client/config/MockModbusConfigBuilder.cpp new file mode 100644 index 0000000..ab7bffc --- /dev/null +++ b/src/backend/modbus_client/config/MockModbusConfigBuilder.cpp @@ -0,0 +1,13 @@ +#include "MockModbusConfigBuilder.h" + +std::optional MockModbusConfigBuilder::get() const +{ + return ModbusConfig{ + "localhost", + 502, + 1000, + 1000, + 3, + 1 + }; +} diff --git a/src/backend/modbus_client/config/ModbusConfigBuilder.cpp b/src/backend/modbus_client/config/ModbusConfigBuilder.cpp new file mode 100644 index 0000000..f8a6b65 --- /dev/null +++ b/src/backend/modbus_client/config/ModbusConfigBuilder.cpp @@ -0,0 +1,13 @@ +#include "ModbusConfigBuilder.h" + +std::optional ModbusConfigBuilder::get() const +{ + return ModbusConfig{ + "localhost", + 502, + 1000, + 1000, + 3, + 1, + }; +} From 86b2c24152c3d29bb0744446ca5851a8678e33ec Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Sun, 3 May 2026 01:24:37 +0500 Subject: [PATCH 08/58] docs: update modbus bridge part in the ToR --- Tz_backend.md | 86 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/Tz_backend.md b/Tz_backend.md index 5d1bf90..2a97928 100644 --- a/Tz_backend.md +++ b/Tz_backend.md @@ -58,18 +58,30 @@ - `void alarmRaised(AlarmEvent event)` *(из ТЗ обработки данных)*. - `void controlNeeded(ActuatorCommand correction)` *(предложено автором)* – если требуется корректирующее воздействие. -### 3.4. Модуль связи с моделью (ModbusDataBridge) +### 3.4. Модуль связи с моделью (IModbusDataBridge) -Реализует опрос Modbus TCP и запись команд. Владеет `QModbusTcpClient`. +Интерфейс реализует запросы к Modbus Server на чтение/запись данных. Интерфейс написан на Qt6.11. **Слоты:** - `void onStartPolling()` – запуск таймера опроса. - `void onStopPolling()` – остановка таймера. +- `void onReadSensors()` - обработка прямого запросо на чтение данных всех датчиков. +- `void onReadInfo()` - обработка прямого запроса на чтение настроек МК и состояний датчиков. - `void onWriteCommand(ActuatorCommand cmd)` – запись управляющих регистров в модель *(из ТЗ ModelClient)*. **Сигналы:** -- `void dataReady(SensorFrame frame)` – после успешного чтения регистров *(из ТЗ ModelClient)*. -- `void errorOccurred(QString errorMessage)` – при критическом сбое связи *(из ТЗ ModelClient)*. +- `void modelInfoReady(const ModelInfo& data)` – после успешного чтения информации модели. +- `void sensorsDataReady(const SensorFrame& data)` – после успешного чтения регистров датчиков *(из ТЗ ModelClient)*. +- `void configurationError(QModbusDevice::Error type, const QString& reason)` – сигнал ошибки. +- `void connectionError(QModbusDevice::Error type, const QString& reason)` – сигнал ошибки. +- `void requestError(QModbusDevice::Error type, const QString& reason)` – сигнал ошибки. +- `void generalError(QModbusDevice::Error type, const QString& reason)` – сигнал ошибки. +- `void connectionLost()` – потеря соединения. +- `void connectionRestored()` – соединение восстановлено. + +**Конфиг (IModbusConfigBuilder):** +- Выдаёт готовый конфиг через `std::optional get() const`. +- Валидирует конфиг, при ошибках `get()` выдаст пустое значение. ### 3.5. Модуль хранения данных (DataStore) @@ -92,13 +104,57 @@ Все структуры имеют открытые поля и зарегистрированы как метатипы Qt. +### ModbusConfig (схемы регистров могут быть другими) +```cpp +// Control registers mapping, read/write, 1-bit +struct CoilsRegistersScheme +{ + bool motorEnabled; +}; + +// Device status registers mapping, only read, 1-bit +struct DiscreteRegistersScheme +{ + bool fanAd; + bool fanBall; +}; + +// Settings and variables registers mapping, read/write, 16-bit +struct HoldingRegistersScheme +{ + double targetRpm; + double throttlePosition; + double brakeTorque; +}; + +// Sensor data registers mapping, only read, 16-bit +struct InputRegistersScheme +{ + double rpm, torque; + double dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; + +struct ModbusConfig +{ + QString host; + quint16 port = 1502; + int pollFrequencyMs = 1000; + int timeoutMs = 1000; + int retries = 3; + int unitId = 1; // we assume one modbus device, so unitId will be ignored + // Start address is 0 for each register type + CoilsRegistersScheme coils; + DiscreteRegistersScheme discrete; + InputRegistersScheme input; + HoldingRegistersScheme holding; +}; +``` + + ### Config (предложено автором) ```cpp struct Config { - QString modbusHost = "127.0.0.1"; - int modbusPort = 502; - int pollIntervalMs = 1000; - QVector stageDurationSec; double targetRpmCold; double throttleWarmup; @@ -124,9 +180,10 @@ struct SensorFrame { }; ``` -### ActuatorCommand (из ТЗ ModelClient) +### ModelConfig (из ТЗ ModelClient) ```cpp -struct ActuatorCommand { +struct ModelConfig +{ bool motorEnabled; double targetRpm; double throttlePosition; @@ -134,6 +191,15 @@ struct ActuatorCommand { }; ``` +### ModelInfo (из ТЗ ModelClient) +```cpp +struct ModelInfo { + bool fanAd; + bool fanBall; + ModelConfig cfg; +}; +``` + ### Decision (из ТЗ обработки данных) ```cpp enum class Status { OK, WARNING, ALARM }; From 78b8626201c9fd57f87dcc6536a2f860f0ee08a9 Mon Sep 17 00:00:00 2001 From: lefff123 Date: Sun, 3 May 2026 19:19:12 +0500 Subject: [PATCH 09/58] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=85=D1=8D=D0=B4=D1=8D=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=20State=20machine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/DataTypes.h | 10 ++++++ include/backend/state_machine.h | 40 +++++++++++++++++++++ src/backend/state_machine/state_machine.cpp | 17 +++++++++ 3 files changed, 67 insertions(+) create mode 100644 include/backend/state_machine.h create mode 100644 src/backend/state_machine/state_machine.cpp diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index b17e996..171fd22 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -26,4 +26,14 @@ struct SensorFrame double rpm, torque; double dieselTemp, motorTemp, resistorTemp, dieselPressure; double throttle, brakeTorque; +}; +//stages of experiment +enum DiagState{ + IDLE = 0, + COLD_CRANKING, + START_AND_WARMUP, + HOT_NO_LOAD, + HOT_WITH_LOAD, + COMPLETED, + ABORTED }; \ No newline at end of file diff --git a/include/backend/state_machine.h b/include/backend/state_machine.h new file mode 100644 index 0000000..f60ffd7 --- /dev/null +++ b/include/backend/state_machine.h @@ -0,0 +1,40 @@ +#ifndef STATE_MACHINE_H +#define STATE_MACHINE_H + +#include +#include +#include "DataTypes.h" + + +class StateMachine : public QObject +{ + Q_OBJECT +public: + explicit StateMachine(ModelConfig& config); + void requestStart(const ModelConfig &config); + void requestNextStage(); + void requestAbort(const QString &reason = QString()); + + DiagState currentState() const; + int currentStageIndex() const; + +signals: + void stageChanged(int oldStage, int newStage); + void finished(DiagState finalState); + +private slots: + void onStageTimeout(); + +private: + void transitionTo(DiagState newState); + + DiagState m_state = DiagState::IDLE; + ModelConfig m_config; + QTimer *m_stageTimer; + int m_currentStageIndex = -1; + int m_previousStageIndex = -1; +}; + + + +#endif //State_machine H \ No newline at end of file diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp new file mode 100644 index 0000000..e98c6bd --- /dev/null +++ b/src/backend/state_machine/state_machine.cpp @@ -0,0 +1,17 @@ +#include "state_machine.h" +#include + +StateMachine::StateMachine(ModelConfig& config){ + m_config = config; + m_stageTimer = new QTimer(this); +} + +StateMachine::~StateMachine(){ + try{ + delete m_stageTimer; + } + catch (const std::runtime_error& e) { + // Перехватываем исключение + std::cerr << "Ошибка: " << e.what() << std::endl; + } +} \ No newline at end of file From a31cb1bef414af4c8e156e8129b00f7464084a53 Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Mon, 4 May 2026 17:59:23 +0500 Subject: [PATCH 10/58] feat: implement raw DataStore.h --- include/backend/data_store/DataStore.h | 127 +++++++++++++++++++++++++ src/backend/data_store/DataStore.cpp | 0 2 files changed, 127 insertions(+) create mode 100644 include/backend/data_store/DataStore.h create mode 100644 src/backend/data_store/DataStore.cpp diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h new file mode 100644 index 0000000..8bae6f8 --- /dev/null +++ b/include/backend/data_store/DataStore.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include + +// Qt includes +#include +#include +#include +// #include + +// #include "../DataTypes.h" // MeasurementRecord, EventRecord, Report + +// Low-level layer +class Connector +{ +public: + explicit Connector(std::string path); // : path_(std::move(path)) {} + virtual ~Connector() noexcept = default; + + virtual int64_t write(const std::string &data) = 0; + virtual std::optional read(int64_t id) const = 0; + virtual bool update(int64_t id, const std::string &data) = 0; + virtual bool remove(int64_t id) = 0; + + size_t size() const noexcept; + const std::string &path() const noexcept { return path_; } + +private: + std::string path_; + size_t size_; +}; + +class CSVConnector : public Connector +{ +public: + explicit CSVConnector(std::string path); // : Connector(std::move(path)) {} + ~CSVConnector() noexcept override = default; + + int64_t write(const std::string &data) override; + std::optional read(int64_t id) const override; + bool update(int64_t id, const std::string &data) override; + bool remove(int64_t id) override; +}; + +// Qt layer +struct MeasurementRecord +{ + qint64 runId; + int stage; + qint64 timestampMs; + double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; + QString flags; // "OK", "WARNING" и т.п. +}; + +struct EventRecord +{ + qint64 runId, timestampMs; + int stage; + QString type; // "stage_change", "abort", "warning", "info" + QString message; +}; + +struct Report +{ + qint64 runId; + QString startTime, endTime; + QString finalStatus; // "completed" или "aborted" + QVector stages; + QVector events; +}; + +enum class StreamType +{ + Measurement, + Event +}; + +inline uint qHash(StreamType key, uint seed = 0) noexcept +{ + return qHash(static_cast(key), seed); +} + +struct StorageConnector +{ + Connector *connector; + qint64 nextId = 1; + bool headersWritten = false; +}; + +class DataStore : public QObject +{ + Q_OBJECT + +public: + DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent = nullptr); + // В случае ошибки возвращаю пустые объекты? + QVector readRecords(qint64 runId) const; + QVector readEvents(qint64 runId) const; + Report buildReport(qint64 runId) const; + +public slots: + void writeRecord(const MeasurementRecord &record); + void writeEvent(const EventRecord &event); + +private: + QString serializeMeasurement(const MeasurementRecord &record) const; + QString serializeEvent(const EventRecord &event) const; + + MeasurementRecord deserializeMeasurement(const QString &line) const; + EventRecord deserializeEvent(const QString &line) const; + + void ensureHeaders(StreamType type); + qint64 generateId(StreamType type) noexcept; + +private: + QHash connectors_; +}; + +// Выносить ли структуры в DataTypes? +// Нужно ли использовать исключения? (иначе работать с bool/константами, std::nullopt, возвращать пустые объекты?) +// Ситуации для обработки: +// 1) Чтение файла, которого нет по пути / пустого файла +// 2) Чтение/обновление/удаление по некорректному id +// Если файла нет и происходит запись, он будет создан +// Нужен ли QDateTime или работать через qint64? \ No newline at end of file diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp new file mode 100644 index 0000000..e69de29 From 4ec9aec26c560182a2c09963715203469db50661 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Tue, 5 May 2026 21:26:49 +0500 Subject: [PATCH 11/58] chore: move the ModbusConfig to DataTypes.h and restructure modbus client --- include/backend/DataTypes.h | 45 ++++++++++++++++ .../{bridge => }/IModbusBridge.h | 0 .../{bridge => }/MockModbusBridge.h | 0 .../{bridge => }/QtModbusBridge.h | 0 .../config/IModbusConfigBuilder.h | 53 ------------------- .../config/MockModbusConfigBuilder.h | 8 --- .../config/ModbusConfigBuilder.h | 10 ---- .../{bridge => }/IModbusBridge.cpp | 0 .../{bridge => }/MockModbusBridge.cpp | 0 .../{bridge => }/QtModbusBridge.cpp | 0 .../config/MockModbusConfigBuilder.cpp | 13 ----- .../config/ModbusConfigBuilder.cpp | 13 ----- 12 files changed, 45 insertions(+), 97 deletions(-) rename include/backend/modbus_client/{bridge => }/IModbusBridge.h (100%) rename include/backend/modbus_client/{bridge => }/MockModbusBridge.h (100%) rename include/backend/modbus_client/{bridge => }/QtModbusBridge.h (100%) delete mode 100644 include/backend/modbus_client/config/IModbusConfigBuilder.h delete mode 100644 include/backend/modbus_client/config/MockModbusConfigBuilder.h delete mode 100644 include/backend/modbus_client/config/ModbusConfigBuilder.h rename src/backend/modbus_client/{bridge => }/IModbusBridge.cpp (100%) rename src/backend/modbus_client/{bridge => }/MockModbusBridge.cpp (100%) rename src/backend/modbus_client/{bridge => }/QtModbusBridge.cpp (100%) delete mode 100644 src/backend/modbus_client/config/MockModbusConfigBuilder.cpp delete mode 100644 src/backend/modbus_client/config/ModbusConfigBuilder.cpp diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 171fd22..56e7e4b 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -1,5 +1,50 @@ #pragma once #include +#include + +// Control registers mapping, read/write, 1-bit +struct CoilsRegistersScheme +{ + bool motorEnabled; +}; + +// Device status registers mapping, only read, 1-bit +struct DiscreteRegistersScheme +{ + bool fanAd; + bool fanBall; +}; + +// Settings and variables registers mapping, read/write, 16-bit +struct HoldingRegistersScheme +{ + double targetRpm; + double throttlePosition; + double brakeTorque; +}; + +// Sensor data registers mapping, only read, 16-bit +struct InputRegistersScheme +{ + double rpm, torque; + double dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; + +struct ModbusConfig +{ + QString host; + quint16 port = 1502; + int pollFrequencyMs = 1000; + int timeoutMs = 1000; + int retries = 3; + int unitId = 1; // we assume one modbus device, so unitId will be ignored + // Start address is 0 for each register type + CoilsRegistersScheme coils; + DiscreteRegistersScheme discrete; + InputRegistersScheme input; + HoldingRegistersScheme holding; +}; // Settings of model (for writing/reading operations) struct ModelConfig diff --git a/include/backend/modbus_client/bridge/IModbusBridge.h b/include/backend/modbus_client/IModbusBridge.h similarity index 100% rename from include/backend/modbus_client/bridge/IModbusBridge.h rename to include/backend/modbus_client/IModbusBridge.h diff --git a/include/backend/modbus_client/bridge/MockModbusBridge.h b/include/backend/modbus_client/MockModbusBridge.h similarity index 100% rename from include/backend/modbus_client/bridge/MockModbusBridge.h rename to include/backend/modbus_client/MockModbusBridge.h diff --git a/include/backend/modbus_client/bridge/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h similarity index 100% rename from include/backend/modbus_client/bridge/QtModbusBridge.h rename to include/backend/modbus_client/QtModbusBridge.h diff --git a/include/backend/modbus_client/config/IModbusConfigBuilder.h b/include/backend/modbus_client/config/IModbusConfigBuilder.h deleted file mode 100644 index 2b1ce92..0000000 --- a/include/backend/modbus_client/config/IModbusConfigBuilder.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include -#include - -// Control registers mapping, read/write, 1-bit -struct CoilsRegistersScheme -{ - bool motorEnabled; -}; - -// Device status registers mapping, only read, 1-bit -struct DiscreteRegistersScheme -{ - bool fanAd; - bool fanBall; -}; - -// Settings and variables registers mapping, read/write, 16-bit -struct HoldingRegistersScheme -{ - double targetRpm; - double throttlePosition; - double brakeTorque; -}; - -// Sensor data registers mapping, only read, 16-bit -struct InputRegistersScheme -{ - double rpm, torque; - double dieselTemp, motorTemp, resistorTemp, dieselPressure; - double throttle, brakeTorque; -}; - -struct ModbusConfig -{ - QString host; - quint16 port = 1502; - int pollFrequencyMs = 1000; - int timeoutMs = 1000; - int retries = 3; - int unitId = 1; // we assume one modbus device, so unitId will be ignored - // Start address is 0 for each register type - CoilsRegistersScheme coils; - DiscreteRegistersScheme discrete; - InputRegistersScheme input; - HoldingRegistersScheme holding; -}; - -class IModbusConfigBuilder -{ -public: - virtual std::optional get() const = 0; -}; diff --git a/include/backend/modbus_client/config/MockModbusConfigBuilder.h b/include/backend/modbus_client/config/MockModbusConfigBuilder.h deleted file mode 100644 index 03de632..0000000 --- a/include/backend/modbus_client/config/MockModbusConfigBuilder.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "IModbusConfigBuilder.h" - -class MockModbusConfigBuilder : public IModbusConfigBuilder -{ -public: - std::optional get() const override; -}; diff --git a/include/backend/modbus_client/config/ModbusConfigBuilder.h b/include/backend/modbus_client/config/ModbusConfigBuilder.h deleted file mode 100644 index 2945cc1..0000000 --- a/include/backend/modbus_client/config/ModbusConfigBuilder.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include "IModbusConfigBuilder.h" - -class ModbusConfigBuilder : public IModbusConfigBuilder -{ -public: - std::optional get() const override; -private: - bool validate(); -}; diff --git a/src/backend/modbus_client/bridge/IModbusBridge.cpp b/src/backend/modbus_client/IModbusBridge.cpp similarity index 100% rename from src/backend/modbus_client/bridge/IModbusBridge.cpp rename to src/backend/modbus_client/IModbusBridge.cpp diff --git a/src/backend/modbus_client/bridge/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp similarity index 100% rename from src/backend/modbus_client/bridge/MockModbusBridge.cpp rename to src/backend/modbus_client/MockModbusBridge.cpp diff --git a/src/backend/modbus_client/bridge/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp similarity index 100% rename from src/backend/modbus_client/bridge/QtModbusBridge.cpp rename to src/backend/modbus_client/QtModbusBridge.cpp diff --git a/src/backend/modbus_client/config/MockModbusConfigBuilder.cpp b/src/backend/modbus_client/config/MockModbusConfigBuilder.cpp deleted file mode 100644 index ab7bffc..0000000 --- a/src/backend/modbus_client/config/MockModbusConfigBuilder.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "MockModbusConfigBuilder.h" - -std::optional MockModbusConfigBuilder::get() const -{ - return ModbusConfig{ - "localhost", - 502, - 1000, - 1000, - 3, - 1 - }; -} diff --git a/src/backend/modbus_client/config/ModbusConfigBuilder.cpp b/src/backend/modbus_client/config/ModbusConfigBuilder.cpp deleted file mode 100644 index f8a6b65..0000000 --- a/src/backend/modbus_client/config/ModbusConfigBuilder.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "ModbusConfigBuilder.h" - -std::optional ModbusConfigBuilder::get() const -{ - return ModbusConfig{ - "localhost", - 502, - 1000, - 1000, - 3, - 1, - }; -} From c2fa74a69c77119db5fd129f8554390fce91a4f4 Mon Sep 17 00:00:00 2001 From: igor66ru Date: Tue, 5 May 2026 23:38:01 +0500 Subject: [PATCH 12/58] change DataTypes.h --- include/backend/DataTypes.h | 118 +++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 27 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 56e7e4b..b910187 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -31,9 +31,12 @@ struct InputRegistersScheme double throttle, brakeTorque; }; +//Структра для задания конфигурации Modbus-клиента struct ModbusConfig { + //Ip-адрес Modbus-сервера QString host; + //Порт Modbus-сервера quint16 port = 1502; int pollFrequencyMs = 1000; int timeoutMs = 1000; @@ -46,39 +49,100 @@ struct ModbusConfig HoldingRegistersScheme holding; }; -// Settings of model (for writing/reading operations) +//Этапы эксперимента +enum DiagState +{ + IDLE = 0, + COLD_CRANKING, + START_AND_WARMUP, + HOT_NO_LOAD, + HOT_WITH_LOAD, + COMPLETED, + ABORTED +}; + +//Структра для управления моделью фронтом +struct FrontControl +{ + //Этап эксперимента + DiagState state; + //Максимальная частота в зависимости от этапа(необязательный параметр на некоторых этапах - может быть не задан) + int maxFreq; + //Время выполнения этапа в минутах(необязательный параметр на некоторых этапах - может быть не задан) + unsigned int time; +}; + +//Структра для задания максимальных значений модели struct ModelConfig { - bool motorEnabled; - double targetRpm; - double throttlePosition; - double brakeTorque; + //Максимальная температура ДВС + double maxDieselTemp; + //Максимальная температура АД + double maxAdTemp; + //Максимальная температура балластных резисторов + double maxResistorTemp; + //Максимальное давление в ДВС + double maxDieselPressure; + //Минимальное давление в ДВС + double minDieselPressure; }; -// States of model (for only reading operations) -struct ModelInfo { - bool fanAd; - bool fanBall; - ModelConfig cfg; +//Параметры модели, которые могут быть изменены в ходе эксперимента +enum DecisionType +{ + //Максимальная частота в режиме притирки + MAX_FREQ_LAP, + //Максимальная частота в режиме обкатки + MAX_FREQ_HOT, + //Состояние вентилятора ассинхронного двигателя + FAN_AD, + //Состояние вентилятора на балластных резисторах + FAN_RESISTOR }; -// Data of sensors +//Управляющее воздействие +struct ModelControl +{ + //Тип изменяемого параметра + DecisionType decisionType; + //Значение (Для FAN_AD и FAN_RESISTOR - значение 0(false), значение 1(true)) + int value; +}; + +struct Decision +{ + //Вектор управляющих воздействий + QVector controls; + //Этап эксперимента + DiagState state; +}; + +//Снимок датчиков struct SensorFrame { - qint64 runId; - int stage; - qint64 timestampMs; - double rpm, torque; - double dieselTemp, motorTemp, resistorTemp, dieselPressure; - double throttle, brakeTorque; + //Метка времени в UNIX-формате + qint64 timestampSec; + //Температура ДВС + double dieselTemp; + //Температура АД + double adTemp; + //Температура балластных резисторов + double resistorTemp; + //Давление + double dieselPressure; + //Момент + double moment; + //Частота + double freq; }; -//stages of experiment -enum DiagState{ - IDLE = 0, - COLD_CRANKING, - START_AND_WARMUP, - HOT_NO_LOAD, - HOT_WITH_LOAD, - COMPLETED, - ABORTED -}; \ No newline at end of file + +//Структра для DataStore +struct Data +{ + //Снимок датчиков + SensorFrame frame; + //Этап эксперимента + DiagState state; +}; + + From eee6e3428704e88f4cd1ec53d20003411c9ad89b Mon Sep 17 00:00:00 2001 From: igor66ru Date: Wed, 6 May 2026 07:37:40 +0500 Subject: [PATCH 13/58] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= =?UTF-8?q?=D1=8B=20BackendWorker=20=D0=B8=20BackendCommunicator.=20=D0=A0?= =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?Modbus-=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=B0=20=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20ConfigData.=20=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81?= =?UTF-8?q?=D0=B8=D0=B3=D0=BD=D0=B0=D0=BB=D0=BE=D0=B2=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20StateMachine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/DataTypes.h | 1 + .../backend_worker/backendcommunicator.h | 36 ++++++++++++ .../backend/backend_worker/backendworker.h | 47 +++++++++++++++ include/backend/config_data.h | 29 ++++++++++ include/backend/state_machine.h | 21 ++++++- .../backend_worker/backendcommunicator.cpp | 24 ++++++++ src/backend/backend_worker/backendworker.cpp | 27 +++++++++ .../BackendWorker/backendcommunicator.cpp | 22 ------- .../BackendWorker/backendcommunicator.h | 30 ---------- .../BackendWorker/backendworker.cpp | 28 --------- .../BackendWorker/backendworker.h | 36 ------------ .../CMakeLists.txt | 24 -------- .../backendcore.cpp | 12 ---- .../communication_with_frontend/backendcore.h | 26 --------- .../communication_with_frontend/struct.h | 27 --------- src/backend/config_data/config_data.cpp | 53 +++++++++++++++++ src/backend/state_machine/state_machine.cpp | 57 +++++++++++++++---- 17 files changed, 280 insertions(+), 220 deletions(-) create mode 100644 include/backend/backend_worker/backendcommunicator.h create mode 100644 include/backend/backend_worker/backendworker.h create mode 100644 include/backend/config_data.h create mode 100644 src/backend/backend_worker/backendcommunicator.cpp create mode 100644 src/backend/backend_worker/backendworker.cpp delete mode 100644 src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp delete mode 100644 src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h delete mode 100644 src/backend/communication_with_frontend/BackendWorker/backendworker.cpp delete mode 100644 src/backend/communication_with_frontend/BackendWorker/backendworker.h delete mode 100644 src/backend/communication_with_frontend/CMakeLists.txt delete mode 100644 src/backend/communication_with_frontend/backendcore.cpp delete mode 100644 src/backend/communication_with_frontend/backendcore.h delete mode 100644 src/backend/communication_with_frontend/struct.h create mode 100644 src/backend/config_data/config_data.cpp diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index b910187..6396444 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include // Control registers mapping, read/write, 1-bit struct CoilsRegistersScheme diff --git a/include/backend/backend_worker/backendcommunicator.h b/include/backend/backend_worker/backendcommunicator.h new file mode 100644 index 0000000..c916dd7 --- /dev/null +++ b/include/backend/backend_worker/backendcommunicator.h @@ -0,0 +1,36 @@ +#ifndef BACKENDCOMMUNICATOR_H +#define BACKENDCOMMUNICATOR_H + +#include +#include "../DataTypes.h" + +class BackendCommunicator : public QObject +{ + Q_OBJECT +public: + explicit BackendCommunicator(QObject *parent = nullptr); + + //Метод для отправки нового снимка датчиков фронту + void SendSensorFrameToFrontend(SensorFrame& sensorFrame); + //Метод для отправки информации фронту об аварийной остановке + void SendEmergencyStopInfoToFrontend(); + //Метод для отправки фидбека фронту на изменение этапа + void SendFeedbackToFrontend(bool isComplete); + //Метод для отправки данных фронту + void SendDataToFrontend(QVector& data); + +signals: + //Сигналы для отправки + void SendedSensorFrame(SensorFrame& sensorFrame); + void SendedEmergencyStopInfo(); + void SendedFeedback(bool isComplete); + void SendedData(QVector data); + + //Сигналы для получения + //Сигнал на приход нового конфига модели от фронта + void ReceivedModelConfig(ModelConfig config); + //Сигнал на приход запроса на изменения этапа эксперимента + void ReceivedFrontControl(FrontControl control); +}; + +#endif // BACKENDCOMMUNICATOR_H diff --git a/include/backend/backend_worker/backendworker.h b/include/backend/backend_worker/backendworker.h new file mode 100644 index 0000000..b6e036a --- /dev/null +++ b/include/backend/backend_worker/backendworker.h @@ -0,0 +1,47 @@ +#ifndef BACKENDWORKER_H +#define BACKENDWORKER_H + +#include +#include +#include "../state_machine.h" +#include "../DataTypes.h" + +//Класс взаимодействия с фронтом +class BackendWorker : public QObject +{ + Q_OBJECT +public: + explicit BackendWorker(QObject *parent = nullptr); + ~BackendWorker(); + + //Запуск бэкэнда + void Run(); + //Остановка бэкэнда + void Stop(); + + //Метод для отправки команды на изменение состояния модели бэкэнду + void SendFrontControlToBackend(FrontControl& control); + //Метод для отправки нового кофига модели бэкэнду + void SendModelConfigToBackend(ModelConfig& config); + +signals: + //Сигналы для получения(для фронта) + //Сигнал на приход нового снимка датчиков + void ReceivedSensorFrame(SensorFrame sensorFrame); + //Сигнал на приход информации об аварийной остановке + void ReceivedEmergencyStopInfo(); + //Сигнал на приход фидбека на изменение этапа + void ReceivedFeedback(bool isComplete); + //Сигнал на приход данных + void ReceivedData(QVector& data); + + //Сигналы для отправки(внутренние) + void SendedFrontControlToBackend(FrontControl control); + void SendedModelConfigToBackend(ModelConfig config); + +private: + QThread m_machineThread; + StateMachine* m_machine; +}; + +#endif // BACKENDWORKER_H diff --git a/include/backend/config_data.h b/include/backend/config_data.h new file mode 100644 index 0000000..224af07 --- /dev/null +++ b/include/backend/config_data.h @@ -0,0 +1,29 @@ +#ifndef CONFIG_DATA_H +#define CONFIG_DATA_H + +#include +#include +#include +#include +#include "DataTypes.h" + +class ConfigData : public QObject +{ +public: + ConfigData(); + ModbusConfig LoadConfig(); + +private: + const QString m_HOST = "host"; + const QString m_PORT = "port"; + const QString m_POLL_FREQ = "poll_freq_ms"; + const QString m_TIMEOUT = "timeout_ms"; + const QString m_RETRIES = "retries"; + const QString m_UNIT_ID = "unit_id"; + + const QString m_FILENAME = "config.txt"; +}; + + + +#endif //Config_data H diff --git a/include/backend/state_machine.h b/include/backend/state_machine.h index f60ffd7..18165a1 100644 --- a/include/backend/state_machine.h +++ b/include/backend/state_machine.h @@ -4,13 +4,20 @@ #include #include #include "DataTypes.h" - +#include "backend_worker/backendworker.h" +#include "backend_worker/backendcommunicator.h" +#include "config_data.h" +#include "modbus_client/QtModbusBridge.h" class StateMachine : public QObject { Q_OBJECT public: - explicit StateMachine(ModelConfig& config); + explicit StateMachine(BackendWorker* backendWorker, QObject *parent = nullptr); + + void Run(); + void Stop(); + void requestStart(const ModelConfig &config); void requestNextStage(); void requestAbort(const QString &reason = QString()); @@ -25,9 +32,17 @@ class StateMachine : public QObject private slots: void onStageTimeout(); + //Слот для обработки пришедшего запроса на изменения этапа эксперимента от фронта + void onReceivedFrontControl(FrontControl control); + //Слот для обработки пришедшего кофига от фронта + void onReceivedModelConfig(ModelConfig config); + private: void transitionTo(DiagState newState); + BackendCommunicator* m_communicator; + QtModbusBridge* m_modbusBridge; + DiagState m_state = DiagState::IDLE; ModelConfig m_config; QTimer *m_stageTimer; @@ -37,4 +52,4 @@ private slots: -#endif //State_machine H \ No newline at end of file +#endif //State_machine H diff --git a/src/backend/backend_worker/backendcommunicator.cpp b/src/backend/backend_worker/backendcommunicator.cpp new file mode 100644 index 0000000..164ea5c --- /dev/null +++ b/src/backend/backend_worker/backendcommunicator.cpp @@ -0,0 +1,24 @@ +#include "../../../include/backend/backend_worker/backendcommunicator.h" + +BackendCommunicator::BackendCommunicator(QObject *parent) + : QObject{parent} {} + +void BackendCommunicator::SendSensorFrameToFrontend(const SensorFrame& sensorFrame) +{ + emit SendedSensorFrame(sensorFrame); +} + +void BackendCommunicator::SendEmergencyStopInfoToFrontend() +{ + emit SendedEmergencyStopInfo(); +} + +void BackendCommunicator::SendFeedbackToFrontend(bool isComplete) +{ + emit SendedFeedback(isComplete); +} + +void BackendCommunicator::SendDataToFrontend(QVector& data) +{ + emit SendedData(data); +} diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp new file mode 100644 index 0000000..4f0689a --- /dev/null +++ b/src/backend/backend_worker/backendworker.cpp @@ -0,0 +1,27 @@ +#include "../../../include/backend/backend_worker/backendworker.h" + +BackendWorker::BackendWorker(QObject *parent) : + m_machine(new StateMachine()), m_machineThread(QThread(this)), QObject{parent} +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + connect(&m_machineThread, &QThread::started, m_machine, &StateMachine::Run, Qt::QueuedConnection); + connect(&m_machineThread, &QThread::finished, m_machine, &StateMachine::Stop, Qt::QueuedConnection); +} + +void BackendWorker::RunCore() +{ + m_machine->moveToThread(&m_machineThread); + m_machineThread.start(); +} + +void BackendWorker::StopCore() +{ + m_machineThread.quit(); +} + +BackendWorker::~BackendWorker() +{ + m_machine.deleteLater(); +} diff --git a/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp b/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp deleted file mode 100644 index d2a06f7..0000000 --- a/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "backendcommunicator.h" - -BackendCommunicator::BackendCommunicator(QObject *parent) - : QObject{parent} -{ - connect(this, &BackendCommunicator::CommandToBackend, &BackendCommunicator::onCommandToBackend); -} - -void BackendCommunicator::SendSensorFrameToFrontend(SensorFrame& sensorFrame) -{ - emit NewSensorFrame(sensorFrame); -} - -void BackendCommunicator::SendEmergencyStopInfoToFrontend() -{ - emit EmergencyStop(); -} - -void BackendCommunicator::onCommandToBackend(CommandToBackend command) -{ - //Отправка команды в модуль бизнес-логики -} diff --git a/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h b/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h deleted file mode 100644 index 43d84a1..0000000 --- a/src/backend/communication_with_frontend/BackendWorker/backendcommunicator.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef BACKENDCOMMUNICATOR_H -#define BACKENDCOMMUNICATOR_H - -#include -#include "../struct.h" - -class BackendCommunicator : public QObject -{ - Q_OBJECT -public: - explicit BackendCommunicator(QObject *parent = nullptr); - - //Метод для отправки новых данных фронту - void SendSensorFrameToFrontend(SensorFrame& sensorFrame); - //Метод для отправки информации фронту об аварийной остановке - void SendEmergencyStopInfoToFrontend(); - -signals: - //Сигнал отправки новых данных фронту - void NewSensorFrame(SensorFrame sensorFrame); - //Сигнал отправки информации фронту об аварийной остановке - void EmergencyStop(); - //Сигнал на комманду от фронта - void CommandToBackend(CommandToBackend command); -private slots: - //Слот обработки комманды от фронта - void onCommandToBackend(CommandToBackend command); -}; - -#endif // BACKENDCOMMUNICATOR_H diff --git a/src/backend/communication_with_frontend/BackendWorker/backendworker.cpp b/src/backend/communication_with_frontend/BackendWorker/backendworker.cpp deleted file mode 100644 index ac5ffec..0000000 --- a/src/backend/communication_with_frontend/BackendWorker/backendworker.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "backendworker.h" - -BackendWorker::BackendWorker(QObject *parent) : - m_backendCore(new BackendCore()), m_coreThread(QThread(this)), QObject{parent} -{ - -} - -void BackendWorker::RunCore() -{ - auto backendCommunicator = m_backendCore->GetBackendCommunicator(); - connect(&backendCommunicator, &BackendCommunicator::NewSensorFrame, this, &BackendWorker::NewSensorFrame, Qt::QueuedConnection); - connect(&backendCommunicator, &BackendCommunicator::EmergencyStop, this, &BackendWorker::EmergencyStop, Qt::QueuedConnection); - connect(&m_coreThread, &QThread::started, m_backendCore, &BackendCore::RunCore, Qt::QueuedConnection); - connect(&m_coreThread, &QThread::finished, m_backendCore, &BackendCore::StopCore, Qt::QueuedConnection); - m_backendCore->moveToThread(&m_coreThread); - m_coreThread.start(); -} - -void BackendWorker::StopCore() -{ - m_coreThread.quit(); -} - -BackendWorker::~BackendWorker() -{ - m_coreThread.deleteLater(); -} diff --git a/src/backend/communication_with_frontend/BackendWorker/backendworker.h b/src/backend/communication_with_frontend/BackendWorker/backendworker.h deleted file mode 100644 index e5ee1da..0000000 --- a/src/backend/communication_with_frontend/BackendWorker/backendworker.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef BACKENDWORKER_H -#define BACKENDWORKER_H - -#include -#include -#include "../backendcore.h" -#include "../struct.h" - -//Класс взаимодействия с фронтом -class BackendWorker : public QObject -{ - Q_OBJECT -public: - explicit BackendWorker(QObject *parent = nullptr); - ~BackendWorker(); - - //Запуск ядра бэкэнда - void RunCore(); - //Остановка ядра бэкэнда - void StopCore(); - - //Команда бэкэнду от фронта - void CommandToBackend(CommandToBackend command); - -signals: - //Сигнал прихода новых данных для фронта - void NewSensorFrame(SensorFrame sensorFrame); - //Сигнал возникновения аварийной остановки для фронта - void EmergencyStop(); - -private: - QThread m_coreThread; - BackendCore* m_backendCore; -}; - -#endif // BACKENDWORKER_H diff --git a/src/backend/communication_with_frontend/CMakeLists.txt b/src/backend/communication_with_frontend/CMakeLists.txt deleted file mode 100644 index 63e5762..0000000 --- a/src/backend/communication_with_frontend/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -cmake_minimum_required(VERSION 3.14) - -project(lib LANGUAGES CXX) - -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) - -add_library(lib SHARED - lib_global.h - BackendWorker/backendworker.h BackendWorker/backendworker.cpp - backendcore.h backendcore.cpp - struct.h - BackendWorker/backendcommunicator.h BackendWorker/backendcommunicator.cpp -) - -target_link_libraries(lib PRIVATE Qt${QT_VERSION_MAJOR}::Core) - -target_compile_definitions(lib PRIVATE LIB_LIBRARY) diff --git a/src/backend/communication_with_frontend/backendcore.cpp b/src/backend/communication_with_frontend/backendcore.cpp deleted file mode 100644 index 3bcc1a3..0000000 --- a/src/backend/communication_with_frontend/backendcore.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "backendcore.h" - -BackendCore::BackendCore(QObject *parent) - : m_backendCommunicator(new BackendCommunicator(this)), QObject{parent} -{ - -} - -void BackendCore::RunCore() -{ - //Запуск таймера опроса модели -} diff --git a/src/backend/communication_with_frontend/backendcore.h b/src/backend/communication_with_frontend/backendcore.h deleted file mode 100644 index 837b7bf..0000000 --- a/src/backend/communication_with_frontend/backendcore.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef BACKENDCORE_H -#define BACKENDCORE_H - -#include -#include "BackendWorker/backendcommunicator.h" - -class BackendCore : public QObject -{ - Q_OBJECT -public: - explicit BackendCore(QObject *parent = nullptr); - ~BackendCore(); - - //Запуск ядра бэкэнда(запуск таймера) - void RunCore(); - //Остановка ядра - void StopCore(); - - //Геттер для получения указателя на коммуникатор - BackendCommunicator* GetBackendCommunicator(); - -private: - BackendCommunicator* m_backendCommunicator; -}; - -#endif // BACKENDCORE_H diff --git a/src/backend/communication_with_frontend/struct.h b/src/backend/communication_with_frontend/struct.h deleted file mode 100644 index 533798d..0000000 --- a/src/backend/communication_with_frontend/struct.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef STRUCT_H -#define STRUCT_H - -struct SensorFrame -{ - long runId; - int stage; - long timestampMs; - double rpm, torque; - double diselTemp, motorTemp, resistorTemp, dieselPressure; - double throttle, brakeTorque; -}; - -enum EngineState -{ - Stop = 0, - Run -}; - -struct CommandToBackend -{ - EngineState state; - double rpm; -}; - - -#endif // STRUCT_H diff --git a/src/backend/config_data/config_data.cpp b/src/backend/config_data/config_data.cpp new file mode 100644 index 0000000..ac961ff --- /dev/null +++ b/src/backend/config_data/config_data.cpp @@ -0,0 +1,53 @@ +#include "../../../include/backend/config_data.h" + +ConfigData::ConfigData(){} + +ModbusConfig ConfigData::LoadConfig() +{ + QFile file(m_FILENAME); + if(!file.exists()) + { + qInfo("Файл конфигурации бэкэнда отсутствует. Будут загружены параметры по умолчанию"); + return ModbusConfig(); + } + + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)); + { + qInfo("Файл конфигурации защищен от чтения. Будут загружены параметры по умолчанию"); + return ModbusConfig(); + } + + ModbusConfig config; + while(!file.atEnd()) + { + QString line = file.readLine(); + QStringList partLine = line.split('='); + if(partLine.count() != 2) + continue; + QString& value = partLine[1]; + switch(partLine) + { + case m_HOST: + config.host = value; + break; + case m_PORT: + config.port = value; + break; + case m_POLL_FREQ: + config.pollFrequencyMs = value; + break; + case m_TIMEOUT: + config.timeoutMs = value; + break; + case m_RETRIES: + config.retries = value; + break; + case m_UNIT_ID: + config.unitId = value; + break; + } + } + + file.close(); + return config; +} diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index e98c6bd..0f7c4ed 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,17 +1,50 @@ -#include "state_machine.h" +#include "../../../include/backend/state_machine.h" #include -StateMachine::StateMachine(ModelConfig& config){ - m_config = config; +StateMachine::StateMachine(BackendWorker* backendWorker, QObject *parent) : + m_communicator(new BackendCommunicator(this)), + m_modbusBridge(new QtModbusBridge(ConfigData().LoadConfig())), QObject{parent} +{ + connect(backendWorker, &BackendWorker::SendedFrontControlToBackend, + m_communicator, &BackendCommunicator::ReceivedFrontControl, Qt::QuenedConnection); + connect(backendWorker, &BackendWorker::SendedModelConfigToBackend, + m_communicator, &BackendCommunicator::ReceivedModelConfig, Qt::QuenedConnection); + + connect(m_communicator, &BackendCommunicator::SendedSensorFrame, + backendWorker, &BackendWorker::ReceivedSensorFrame, Qt::QuenedConnection); + connect(m_communicator, &BackendCommunicator::SendedEmergencyStopInfo, + backendWorker, &BackendWorker::ReceivedEmergencyStopInfo, Qt::QuenedConnection); + connect(m_communicator, &BackendCommunicator::SendedFeedback, + backendWorker, &BackendWorker::ReceivedFeedback, Qt::QuenedConnection); + connect(m_communicator, &BackendCommunicator::SendedData, + backendWorker, &BackendWorker::ReceivedData, Qt::QuenedConnection); + + connect(m_communicator, &BackendCommunicator::ReceivedFrontControl, + this, &StateMachine::onReceivedFrontControl); + connect(m_communicator, &BackendCommunicator::ReceivedModelConfig, + this, &StateMachine::onReceivedModelConfig); + + //m_config = config; m_stageTimer = new QTimer(this); } -StateMachine::~StateMachine(){ - try{ - delete m_stageTimer; - } - catch (const std::runtime_error& e) { - // Перехватываем исключение - std::cerr << "Ошибка: " << e.what() << std::endl; - } -} \ No newline at end of file +void StateMachine::Run() +{ + //Здесь должен быть запуск таймера, запуск Modbus клиента +} + +void StateMachine::Stop() +{ + //Остановка всех таймеров +} + +StateMachine::~StateMachine() +{ + // try{ + // delete m_stageTimer; + // } + // catch (const std::runtime_error& e) { + // // Перехватываем исключение + // std::cerr << "Ошибка: " << e.what() << std::endl; + // } +} From cc04231b43c8e45e5b668ecb2692bc57ec21b1e7 Mon Sep 17 00:00:00 2001 From: igor66ru Date: Wed, 6 May 2026 11:51:26 +0500 Subject: [PATCH 14/58] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?ConfigData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend_worker/backendcommunicator.cpp | 2 +- src/backend/config_data/config_data.cpp | 44 +++++++++++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/backend/backend_worker/backendcommunicator.cpp b/src/backend/backend_worker/backendcommunicator.cpp index 164ea5c..9693fa8 100644 --- a/src/backend/backend_worker/backendcommunicator.cpp +++ b/src/backend/backend_worker/backendcommunicator.cpp @@ -3,7 +3,7 @@ BackendCommunicator::BackendCommunicator(QObject *parent) : QObject{parent} {} -void BackendCommunicator::SendSensorFrameToFrontend(const SensorFrame& sensorFrame) +void BackendCommunicator::SendSensorFrameToFrontend(SensorFrame& sensorFrame) { emit SendedSensorFrame(sensorFrame); } diff --git a/src/backend/config_data/config_data.cpp b/src/backend/config_data/config_data.cpp index ac961ff..3d2b5cb 100644 --- a/src/backend/config_data/config_data.cpp +++ b/src/backend/config_data/config_data.cpp @@ -25,27 +25,33 @@ ModbusConfig ConfigData::LoadConfig() if(partLine.count() != 2) continue; QString& value = partLine[1]; - switch(partLine) + if(m_HOST == value) { - case m_HOST: - config.host = value; - break; - case m_PORT: - config.port = value; - break; - case m_POLL_FREQ: - config.pollFrequencyMs = value; - break; - case m_TIMEOUT: - config.timeoutMs = value; - break; - case m_RETRIES: - config.retries = value; - break; - case m_UNIT_ID: - config.unitId = value; - break; + config.host = value; + continue; + } + if(m_PORT == value) + { + config.port = value.toUInt(); + continue; + } + if(m_POLL_FREQ == value) + { + config.pollFrequencyMs = value.toInt(); + continue; + } + if(m_TIMEOUT == value) + { + config.timeoutMs = value.toInt(); + continue; + } + if(m_RETRIES == value) + { + config.retries = value.toInt(); + continue; } + if(m_UNIT_ID == value) + config.unitId = value.toInt(); } file.close(); From a674f55b16bf9ed010fbe5787af21495d9cfb9fa Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 12:44:19 +0500 Subject: [PATCH 15/58] chore: update registers mapping according to new ModelConfig, ModelControl and SensorFrame --- include/backend/DataTypes.h | 39 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 6396444..835e7a2 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -6,7 +6,8 @@ // Control registers mapping, read/write, 1-bit struct CoilsRegistersScheme { - bool motorEnabled; + bool fanAd; + bool fanBall; }; // Device status registers mapping, only read, 1-bit @@ -19,17 +20,37 @@ struct DiscreteRegistersScheme // Settings and variables registers mapping, read/write, 16-bit struct HoldingRegistersScheme { - double targetRpm; - double throttlePosition; - double brakeTorque; + //Максимальная температура ДВС + double maxDieselTemp; + //Максимальная температура АД + double maxAdTemp; + //Максимальная температура балластных резисторов + double maxResistorTemp; + //Максимальное давление в ДВС + double maxDieselPressure; + //Минимальное давление в ДВС + double minDieselPressure; + //Максимальная частота в режиме притирки + int maxFreqLap; + //Максимальная частота в режиме обкатки + int maxFreqHot; }; // Sensor data registers mapping, only read, 16-bit struct InputRegistersScheme { - double rpm, torque; - double dieselTemp, motorTemp, resistorTemp, dieselPressure; - double throttle, brakeTorque; + //Температура ДВС + double dieselTemp; + //Температура АД + double adTemp; + //Температура балластных резисторов + double resistorTemp; + //Давление + double dieselPressure; + //Момент + double moment; + //Частота + double freq; }; //Структра для задания конфигурации Modbus-клиента @@ -43,7 +64,7 @@ struct ModbusConfig int timeoutMs = 1000; int retries = 3; int unitId = 1; // we assume one modbus device, so unitId will be ignored - // Start address is 0 for each register type + // Start address is 0 for each register type, registers ordered sequentially CoilsRegistersScheme coils; DiscreteRegistersScheme discrete; InputRegistersScheme input; @@ -145,5 +166,3 @@ struct Data //Этап эксперимента DiagState state; }; - - From 3ebed0ba45994c96485a18b638454565f14b4515 Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Wed, 6 May 2026 14:03:08 +0500 Subject: [PATCH 16/58] update: DataProcessor.cpp --- src/backend/data_processing/DataProcessor.cpp | 342 ++++++++---------- 1 file changed, 147 insertions(+), 195 deletions(-) diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index b73eceb..3f8bde0 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,10 +1,5 @@ -#include "DataProcessor.h" -#include -#include - -// Номера этапов должны совпадать: -// 0 IDLE, 1 COLD_CRANKING, 2 START_AND_WARMUP, -// 3 HOT_NO_LOAD, 4 HOT_WITH_LOAD, 5 COMPLETED, 6 ABORTED +#include "DataProcessor.h" +// этапы — должны совпадать с StateMachine (согласовать) namespace Stage { constexpr int IDLE = 0; @@ -15,264 +10,221 @@ namespace Stage constexpr int COMPLETED = 5; constexpr int ABORTED = 6; } -// % от критического порога, при достижении которой выдаём предупреждение +// доля от критического порога, при которой выдаём предаварийное состояние (тоже согласовать) static constexpr double WARN_RATIO = 0.9; -DataProcessor::DataProcessor(QObject *parent) - : QObject(parent) -{ -} -void DataProcessor::setConfig(const Config &config) +DataProcessor::DataProcessor(const ModelConfig &config, QObject *parent) + : QObject(parent), m_config(config) { - m_config = config; - m_configured = true; } void DataProcessor::onStageChanged(int newStage) { m_currentStage = newStage; - // при переходе в COMPLETED/ABORTED дальнейшие проверки не нужны, - // но решение об этом принимает другой этап, здесь по лгике надо престать слать кадры +} +void DataProcessor::onConfigChanged(ModelConfig config) +{ + m_config = config; } void DataProcessor::reset() { m_alarmLatched = false; m_currentStage = Stage::IDLE; - m_lastFrameTsMs = 0; } void DataProcessor::processFrame(SensorFrame frame) { - if (!m_configured) - { - qWarning() << "[DataProcessor] frame received before config; dropping"; - return; - } - // после аварии блокируем формирование новых решений + // после аварии решения больше не формируем if (m_alarmLatched) { return; } - m_lastFrameTsMs = frame.timestampMs; - // в режимах IDLE COMPLETED ABORTED проверки не нужны + // в неактивных режимах проверки нет if (m_currentStage == Stage::IDLE || m_currentStage == Stage::COMPLETED || m_currentStage == Stage::ABORTED) { Decision d; - d.status = Status::OK; - d.message = QStringLiteral("inactive stage"); + d.state = DiagState::Ok; + d.reason = QStringLiteral("inactive stage"); emit decisionReady(d); return; } - // выбор максимально допустимых оборотов в зависимости от этапа. - const double maxRpm = (m_currentStage == Stage::COLD_CRANKING) - ? m_config.maxRpmCold - : m_config.maxRpmHot; - // сборка набора проверок - QVector checks; - if (isParameterActiveOnStage("rpm", m_currentStage)) + // будем выбирать наихудшее состояние (приоритет — авария) + DiagState worstState = DiagState::Ok; + QString worstReason; + int worstSeverity = 0; // 0 = ok, 1 = пред alarm, 2 = alarm + auto promote = [&](int severity, DiagState st, const QString &rsn) { - checks.push_back({"rpm", - frame.rpm, - maxRpm * WARN_RATIO, - maxRpm, - true, - QStringLiteral("Превышение частоты вращения")}); - } - if (isParameterActiveOnStage("dieselTemp", m_currentStage)) + if (severity > worstSeverity) + { + worstSeverity = severity; + worstState = st; + worstReason = rsn; + } + }; + // обороты + if (isParamActiveOnStage("rpm", m_currentStage)) { - checks.push_back({"dieselTemp", - frame.dieselTemp, - m_config.maxDieselTemp * WARN_RATIO, - m_config.maxDieselTemp, - true, - QStringLiteral("Перегрев дизеля")}); + if (frame.rpm >= m_config.maxRpm) + { + promote(2, DiagState::Alarm_RpmOverspeed, + QStringLiteral("Превышение оборотов: %1 >= %2") + .arg(frame.rpm) + .arg(m_config.maxRpm)); + } + else if (frame.rpm >= m_config.maxRpm * WARN_RATIO) + { + promote(1, DiagState::PreWarn_RpmHigh, + QStringLiteral("Обороты приближаются к пределу: %1") + .arg(frame.rpm)); + } } - if (isParameterActiveOnStage("motorTemp", m_currentStage)) + // температура ДВС + if (isParamActiveOnStage("dieselTemp", m_currentStage)) { - checks.push_back({"motorTemp", - frame.motorTemp, - m_config.maxMotorTemp * WARN_RATIO, - m_config.maxMotorTemp, - true, - QStringLiteral("Перегрев электродвигателя")}); + if (frame.dieselTemp >= m_config.maxDieselTemp) + { + promote(2, DiagState::Alarm_DieselOverheat, + QStringLiteral("Перегрев ДВС: %1").arg(frame.dieselTemp)); + } + else if (frame.dieselTemp >= m_config.maxDieselTemp * WARN_RATIO) + { + promote(1, DiagState::PreWarn_DieselTempHigh, + QStringLiteral("ДВС близок к перегреву: %1").arg(frame.dieselTemp)); + } } - if (isParameterActiveOnStage("resistorTemp", m_currentStage)) + // температура АД + if (isParamActiveOnStage("motorTemp", m_currentStage)) { - checks.push_back({"resistorTemp", - frame.resistorTemp, - m_config.maxResistorTemp * WARN_RATIO, - m_config.maxResistorTemp, - true, - QStringLiteral("Перегрев тормозного резистора")}); + if (frame.motorTemp >= m_config.maxMotorTemp) + { + promote(2, DiagState::Alarm_MotorOverheat, + QStringLiteral("Перегрев АД: %1").arg(frame.motorTemp)); + } + else if (frame.motorTemp >= m_config.maxMotorTemp * WARN_RATIO) + { + promote(1, DiagState::PreWarn_MotorTempHigh, + QStringLiteral("АД близок к перегреву: %1").arg(frame.motorTemp)); + } } - if (isParameterActiveOnStage("dieselPressureMax", m_currentStage)) + // балансировочный резистор + if (isParamActiveOnStage("resistorBalance", m_currentStage)) { - checks.push_back({"dieselPressure", - frame.dieselPressure, - m_config.maxDieselPressure * WARN_RATIO, - m_config.maxDieselPressure, - true, - QStringLiteral("Превышение давления дизеля")}); + if (frame.resistorBalance >= m_config.maxResistorBalance) + { + promote(2, DiagState::Alarm_ResistorOverheat, + QStringLiteral("Превышение по балансировочному резистору: %1") + .arg(frame.resistorBalance)); + } + else if (frame.resistorBalance >= m_config.maxResistorBalance * WARN_RATIO) + { + promote(1, DiagState::PreWarn_ResistorHigh, + QStringLiteral("Балансировочный резистор близок к пределу: %1") + .arg(frame.resistorBalance)); + } } - if (isParameterActiveOnStage("dieselPressureMin", m_currentStage)) + // давление ДВС — верхняя граница + if (isParamActiveOnStage("dieselPressureMax", m_currentStage)) { - // для нижней границы порог "warn" — чуть выше, чем alarm, пока заглушкой и просто зарезервируем место для этого, потом решим как правильно, нужно спросить у команды 1 - const double warn = m_config.minDieselPressure + (m_config.minDieselPressure * (1.0 - WARN_RATIO)); - checks.push_back({"dieselPressure", - frame.dieselPressure, - warn, - m_config.minDieselPressure, - false, - QStringLiteral("Падение давления дизеля")}); + if (frame.dieselPressure >= m_config.maxDieselPressure) + { + promote(2, DiagState::Alarm_PressureOver, + QStringLiteral("Превышение давления ДВС: %1") + .arg(frame.dieselPressure)); + } + else if (frame.dieselPressure >= m_config.maxDieselPressure * WARN_RATIO) + { + promote(1, DiagState::PreWarn_PressureHigh, + QStringLiteral("Давление ДВС близко к верхнему пределу: %1") + .arg(frame.dieselPressure)); + } } - // прогон проверок - Status worst = Status::OK; - QString worstMsg; - QString worstParam; - for (const auto &c : checks) + // давление ДВС — нижняя граница + if (isParamActiveOnStage("dieselPressureMin", m_currentStage)) { - Status s = runCheck(c, frame); - if (static_cast(s) > static_cast(worst)) + const double warnLow = m_config.minDieselPressure + m_config.minDieselPressure * (1.0 - WARN_RATIO); + if (frame.dieselPressure <= m_config.minDieselPressure) + { + promote(2, DiagState::Alarm_PressureUnder, + QStringLiteral("Падение давления ДВС: %1") + .arg(frame.dieselPressure)); + } + else if (frame.dieselPressure <= warnLow) { - worst = s; - worstMsg = c.description; - worstParam = c.parameter; + promote(1, DiagState::PreWarn_PressureLow, + QStringLiteral("Давление ДВС приближается к нижнему пределу: %1") + .arg(frame.dieselPressure)); } } - // принятие решения Decision decision; - decision.status = worst; - decision.message = worstMsg; - if (worst == Status::ALARM) + decision.state = worstState; + decision.reason = worstReason; + if (worstSeverity == 2) { + // АВАРИЯ m_alarmLatched = true; - decision.correction = makeStopCommand(); + decision.controls = makeStopControls(); emit decisionReady(decision); - // команду останова в модель отправляет другой модуль - // получив этот сигнал. DataProcessor сам в Modbus не пишит ничего. - emit controlNeeded(decision.correction, - QStringLiteral("ALARM: %1").arg(worstMsg)); return; } - if (worst == Status::WARNING) + if (worstSeverity == 1) { - // если параметр в жёлтой зоне — пробуем сформировать корректировку. - ActuatorCommand cmd; - QString reason; - if (buildCorrection(frame, cmd, reason)) + // предаварийное состояние — корректирующее воздействие + ModelControl mc; + switch (worstState) { - decision.correction = cmd; - emit controlNeeded(cmd, reason); + case DiagState::PreWarn_RpmHigh: + mc.type = ControlType::Throttle; + mc.value = 0.0; // прикрыть дроссель (значение согласовать) + decision.controls.append(mc); + break; + case DiagState::PreWarn_ResistorHigh: + mc.type = ControlType::BrakeTorque; + mc.value = 0.0; // снизить тормозной момент + decision.controls.append(mc); + break; + // Перегревы и давление пока не доделаны + case DiagState::PreWarn_DieselTempHigh: + case DiagState::PreWarn_MotorTempHigh: + case DiagState::PreWarn_PressureHigh: + case DiagState::PreWarn_PressureLow: + default: + break; } } emit decisionReady(decision); } -DataProcessor::Status DataProcessor::runCheck(const Check &c, const SensorFrame &frame) -{ - const bool overWarn = c.upperBound ? (c.value >= c.warnThreshold) - : (c.value <= c.warnThreshold); - const bool overAlarm = c.upperBound ? (c.value >= c.alarmThreshold) - : (c.value <= c.alarmThreshold); - if (overAlarm) - { - AlarmEvent ev; - ev.runId = frame.runId; - ev.timestampMs = frame.timestampMs; - ev.stage = frame.stage; - ev.parameter = c.parameter; - ev.currentValue = c.value; - ev.criticalThreshold = c.alarmThreshold; - ev.reason = c.description; - emit alarmRaised(ev); - return Status::ALARM; - } - if (overWarn) - { - WarningEvent ev; - ev.runId = frame.runId; - ev.timestampMs = frame.timestampMs; - ev.stage = frame.stage; - ev.parameter = c.parameter; - ev.currentValue = c.value; - ev.threshold = c.warnThreshold; - ev.description = c.description; - emit warningRaised(ev); - return Status::WARNING; - } - return Status::OK; -} -bool DataProcessor::isParameterActiveOnStage(const QString ¶meter, int stage) const +bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const { - // исключение ложных срабатываний в неактивных режимах if (stage == Stage::IDLE || stage == Stage::COMPLETED || stage == Stage::ABORTED) { return false; } - // давление дизеля имеет смысл только когда дизель запущен, так же? - if (parameter == "dieselPressureMin" || parameter == "dieselPressureMax") + // давление и температура ДВС — после запуска + if (param == "dieselPressureMin" || + param == "dieselPressureMax" || + param == "dieselTemp") { return (stage == Stage::START_AND_WARMUP || stage == Stage::HOT_NO_LOAD || stage == Stage::HOT_WITH_LOAD); } - // температура дизеля смотрим только после запуска. - if (parameter == "dieselTemp") - { - return (stage == Stage::START_AND_WARMUP || - stage == Stage::HOT_NO_LOAD || - stage == Stage::HOT_WITH_LOAD); - } - // температура тормозного резистора только на этапе с нагрузкой. - if (parameter == "resistorTemp") + // балансировочный резистор — на этапе с нагрузкой + if (param == "resistorBalance") { return (stage == Stage::HOT_WITH_LOAD); } + // rpm и motorTemp — на всех активных этапах. return true; } -bool DataProcessor::buildCorrection(const SensorFrame &frame, - ActuatorCommand &outCmd, - QString &outReason) const -{ - // если на этапе с нагрузкой растёт температура - // тормозного резистора — снижаем тормозной момент, но это надо уточнить у тех кто делает модель - if (m_currentStage == Stage::HOT_WITH_LOAD && - frame.resistorTemp >= m_config.maxResistorTemp * WARN_RATIO) - { - outCmd.motorEnabled = true; - outCmd.targetRpm = frame.rpm; - outCmd.throttlePosition = m_config.throttleHotLoad; - outCmd.brakeTorque = m_config.brakeTorqueHotLoad * 0.7; // снизили - outReason = QStringLiteral( - "Снижение тормозного момента: температура резистора %1 близка к пределу") - .arg(frame.resistorTemp); - return true; - } - // если обороты близки к лимиту надл прикрыть дроссель. - const double maxRpm = (m_currentStage == Stage::COLD_CRANKING) - ? m_config.maxRpmCold - : m_config.maxRpmHot; - if (frame.rpm >= maxRpm * WARN_RATIO) - { - outCmd.motorEnabled = (m_currentStage == Stage::COLD_CRANKING); - outCmd.targetRpm = m_config.targetRpmCold; - outCmd.throttlePosition = frame.throttle * 0.8; - outCmd.brakeTorque = frame.brakeTorque; - outReason = QStringLiteral( - "Снижение дросселя: обороты %1 близки к пределу %2") - .arg(frame.rpm) - .arg(maxRpm); - return true; - } - return false; -} -ActuatorCommand DataProcessor::makeStopCommand() +QVector DataProcessor::makeStopControls() { - ActuatorCommand cmd; - cmd.motorEnabled = false; - cmd.targetRpm = 0.0; - cmd.throttlePosition = 0.0; - cmd.brakeTorque = 0.0; - return cmd; + QVector v; + v.append({ControlType::EmergencyStop, 1.0}); + v.append({ControlType::MotorEnable, 0.0}); + v.append({ControlType::Throttle, 0.0}); + v.append({ControlType::BrakeTorque, 0.0}); + v.append({ControlType::TargetRpm, 0.0}); + return v; } \ No newline at end of file From b88ce3684f4246869971e2c4c320be0ef4c4ba4d Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Wed, 6 May 2026 14:04:57 +0500 Subject: [PATCH 17/58] =?UTF-8?q?Add:=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BC=D1=8B=20=D0=B4=D0=BB=D1=8F=20DataProce?= =?UTF-8?q?ssor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/DataProcessor.h | 43 ++++++ include/backend/DataTypes.h | 243 ++++++++++---------------------- 2 files changed, 118 insertions(+), 168 deletions(-) create mode 100644 include/backend/DataProcessor.h diff --git a/include/backend/DataProcessor.h b/include/backend/DataProcessor.h new file mode 100644 index 0000000..2274aec --- /dev/null +++ b/include/backend/DataProcessor.h @@ -0,0 +1,43 @@ +#ifndef DATAPROCESSOR_H +#define DATAPROCESSOR_H +#include +#include +#include "DataTypes.h" +/** + * Модуль обработки данных. + * + * Принимает SensorFrame от StateMachine, сравнивает значения с порогами + * из ModelConfig (с учётом текущего этапа) и возвращает Decision — + * вектор управляющих воздействий + диагностическое состояние модели + * версия 2 - не финальная + */ +class DataProcessor : public QObject +{ + Q_OBJECT +public: + explicit DataProcessor(const ModelConfig &config, QObject *parent = nullptr); + +public slots: + void processFrame(SensorFrame frame); + // уведомление об актуальном этапе испытания (для учёта режимов) (согласовать) + void onStageChanged(int newStage); + // обновление конфигурации + void onConfigChanged(ModelConfig config); + // сброс защёлки аварии и внутреннего состояния (новый прогон) + void reset(); +signals: + // шотовое решение для StateMachine. + void decisionReady(Decision decision); + +private: + // активна ли проверка параметра на текущем этапе??? (исключение ложных срабатываний) + bool isParamActiveOnStage(const QString ¶m, int stage) const; + // Сформировать аварийную команду останова + static QVector makeStopControls(); + +private: + ModelConfig m_config; + int m_currentStage = 0; + bool m_alarmLatched = false; // после аварии решения не формируем +}; +#endif // DATAPROCESSOR_H \ No newline at end of file diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 835e7a2..f3a7c78 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -1,168 +1,75 @@ -#pragma once -#include -#include -#include - -// Control registers mapping, read/write, 1-bit -struct CoilsRegistersScheme -{ - bool fanAd; - bool fanBall; -}; - -// Device status registers mapping, only read, 1-bit -struct DiscreteRegistersScheme -{ - bool fanAd; - bool fanBall; -}; - -// Settings and variables registers mapping, read/write, 16-bit -struct HoldingRegistersScheme -{ - //Максимальная температура ДВС - double maxDieselTemp; - //Максимальная температура АД - double maxAdTemp; - //Максимальная температура балластных резисторов - double maxResistorTemp; - //Максимальное давление в ДВС - double maxDieselPressure; - //Минимальное давление в ДВС - double minDieselPressure; - //Максимальная частота в режиме притирки - int maxFreqLap; - //Максимальная частота в режиме обкатки - int maxFreqHot; -}; - -// Sensor data registers mapping, only read, 16-bit -struct InputRegistersScheme -{ - //Температура ДВС - double dieselTemp; - //Температура АД - double adTemp; - //Температура балластных резисторов - double resistorTemp; - //Давление - double dieselPressure; - //Момент - double moment; - //Частота - double freq; -}; - -//Структра для задания конфигурации Modbus-клиента -struct ModbusConfig -{ - //Ip-адрес Modbus-сервера - QString host; - //Порт Modbus-сервера - quint16 port = 1502; - int pollFrequencyMs = 1000; - int timeoutMs = 1000; - int retries = 3; - int unitId = 1; // we assume one modbus device, so unitId will be ignored - // Start address is 0 for each register type, registers ordered sequentially - CoilsRegistersScheme coils; - DiscreteRegistersScheme discrete; - InputRegistersScheme input; - HoldingRegistersScheme holding; -}; - -//Этапы эксперимента -enum DiagState -{ - IDLE = 0, - COLD_CRANKING, - START_AND_WARMUP, - HOT_NO_LOAD, - HOT_WITH_LOAD, - COMPLETED, - ABORTED -}; - -//Структра для управления моделью фронтом -struct FrontControl -{ - //Этап эксперимента - DiagState state; - //Максимальная частота в зависимости от этапа(необязательный параметр на некоторых этапах - может быть не задан) - int maxFreq; - //Время выполнения этапа в минутах(необязательный параметр на некоторых этапах - может быть не задан) - unsigned int time; -}; - -//Структра для задания максимальных значений модели -struct ModelConfig -{ - //Максимальная температура ДВС - double maxDieselTemp; - //Максимальная температура АД - double maxAdTemp; - //Максимальная температура балластных резисторов - double maxResistorTemp; - //Максимальное давление в ДВС - double maxDieselPressure; - //Минимальное давление в ДВС - double minDieselPressure; -}; - -//Параметры модели, которые могут быть изменены в ходе эксперимента -enum DecisionType -{ - //Максимальная частота в режиме притирки - MAX_FREQ_LAP, - //Максимальная частота в режиме обкатки - MAX_FREQ_HOT, - //Состояние вентилятора ассинхронного двигателя - FAN_AD, - //Состояние вентилятора на балластных резисторах - FAN_RESISTOR -}; - -//Управляющее воздействие -struct ModelControl -{ - //Тип изменяемого параметра - DecisionType decisionType; - //Значение (Для FAN_AD и FAN_RESISTOR - значение 0(false), значение 1(true)) - int value; -}; - -struct Decision -{ - //Вектор управляющих воздействий - QVector controls; - //Этап эксперимента - DiagState state; -}; - -//Снимок датчиков -struct SensorFrame -{ - //Метка времени в UNIX-формате - qint64 timestampSec; - //Температура ДВС - double dieselTemp; - //Температура АД - double adTemp; - //Температура балластных резисторов - double resistorTemp; - //Давление - double dieselPressure; - //Момент - double moment; - //Частота - double freq; -}; - -//Структра для DataStore -struct Data -{ - //Снимок датчиков - SensorFrame frame; - //Этап эксперимента - DiagState state; -}; +// DataTypes.h (фрагмент, относящийся к DataProcessor) + +#ifndef DATATYPES_H +#define DATATYPES_H +#include +#include +#include +// пакет измерений то, что приходит от ModbusClient через StateMachine (все требует уточнения) +struct SensorFrame +{ + double dieselTemp; //< температура ДВС + double motorTemp; //< температура АД (асинхронного двигателя) + double resistorBalance; //< балансировочный резистор (?) + double dieselPressure; //< давление ДВС + double torque; //< момент + double rpm; //< частота вращения + qint64 timestampMs; //< метка времени + int stage; //< № этапа +}; +Q_DECLARE_METATYPE(SensorFrame) +// лимиты, передаются в DataProcessor в конструкторе (тоже требует уточнения) +struct ModelConfig +{ + double maxDieselTemp; + double maxMotorTemp; + double maxResistorBalance; + double maxDieselPressure; + double minDieselPressure; + double maxRpm; //< общий лимит оборотов (можно расширить) +}; +Q_DECLARE_METATYPE(ModelConfig) +// тип управляющего воздействия (и это требует уточнения) +enum class ControlType +{ + None, + Throttle, // дроссель + BrakeTorque, // тормозной момент + TargetRpm, // целевые обороты + MotorEnable, // вкл/выкл мотора + EmergencyStop // аварийный стоп +}; +// одно управляющее воздействие (тип + значение) +struct ModelControl +{ + ControlType type = ControlType::None; + double value = 0.0; +}; +Q_DECLARE_METATYPE(ModelControl) +// диагностическое состояние модели (добавлены предаварийные) +enum class DiagState +{ + Ok, + PreWarn_RpmHigh, + PreWarn_DieselTempHigh, + PreWarn_MotorTempHigh, + PreWarn_ResistorHigh, + PreWarn_PressureHigh, + PreWarn_PressureLow, + Alarm_RpmOverspeed, + Alarm_DieselOverheat, + Alarm_MotorOverheat, + Alarm_ResistorOverheat, + Alarm_PressureOver, + Alarm_PressureUnder, + Alarm_Generic +}; +// вектор управляющих воздействий + диагностика +struct Decision +{ + QVector controls; + DiagState state = DiagState::Ok; + QString reason; // причина (для последующей записи в БД, если надо записывать, требует уточнения) +}; +Q_DECLARE_METATYPE(Decision) +#endif // DATATYPES_H \ No newline at end of file From ec18c4f03d04ba1d1f6c72e7c4ad7b3edc8c2ebf Mon Sep 17 00:00:00 2001 From: Rikitick Date: Wed, 6 May 2026 14:52:24 +0500 Subject: [PATCH 18/58] Update CMake files --- CMakeLists.txt | 30 ++++ src/backend/CMakeLists.txt | 151 +++++++++++++----- .../backend_worker_test/backend_worker_test.h | 2 +- 3 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f84bbb2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.16) + +# Главный проект +project(ScadaForDiesel + LANGUAGES CXX + DESCRIPTION "SCADA система стенда тестирования ДВС (дизельных двигателей)" + VERSION 1.0.0 +) + +# Глобальные настройки +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Директория для собранных файлов +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# Backend библиотека +add_subdirectory(src/backend) + +# Информация о сборке +message(STATUS "") +message(STATUS "=== ScadaForDiesel ===") +message(STATUS "Version: ${PROJECT_VERSION}") +message(STATUS "CMake version: ${CMAKE_VERSION}") +message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "") diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index dfbd16c..454021b 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -1,55 +1,128 @@ cmake_minimum_required(VERSION 3.16) project(ScadaForDiesel LANGUAGES CXX) +# Настройки проекта set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) -find_package(Qt6 REQUIRED COMPONENTS Core Test) +# Зависимости +find_package(Qt6 REQUIRED COMPONENTS + Core + Network + Test +) + +# Modbus опционально +find_package(Qt6 COMPONENTS Modbus QUIET) +if(NOT Qt6Modbus_FOUND) + message(WARNING "Qt6 Modbus not found - QtModbusBridge will be excluded from build") + message(WARNING "Install: sudo apt install qt6-serial-dev (or equivalent)") +endif() -# Библиотека для бэкенда +# Основная библиотека Backend set(BACKEND_SOURCES - src/backend_worker/backend_worker.cpp - src/data_processor/data_processor.cpp - src/data_store/data_store.cpp - src/modbus_data_bridge/modbus_data_bridge.cpp - src/state_machine/state_machine.cpp + backend_worker/backendworker.cpp + backend_worker/backendcommunicator.cpp + config_data/config_data.cpp + data_processing/DataProcessor.cpp + data_store/DataStore.cpp + modbus_client/IModbusBridge.cpp + modbus_client/MockModbusBridge.cpp + state_machine/state_machine.cpp +) + +# Добавляем QtModbusBridge только если Modbus доступен +if(Qt6Modbus_FOUND) + list(APPEND BACKEND_SOURCES modbus_client/QtModbusBridge.cpp) +endif() + +set(BACKEND_HEADERS + backend_worker/backendworker.h + backend_worker/backendcommunicator.h + data_processor/DataProcessor.h + data_store/DataStore.h + modbus_client/IModbusBridge.h + modbus_client/QtModbusBridge.h + modbus_client/MockModbusBridge.h ) add_library(backend_lib STATIC ${BACKEND_SOURCES}) -# Указываем папки с заголовками -target_include_directories(backend_lib PUBLIC - src/backend_worker - src/data_store - src/data_processor - src/modbus_data_bridge - src/state_machine +# Включаемые директории для библиотеки +target_include_directories(backend_lib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/backend_worker + ${CMAKE_CURRENT_SOURCE_DIR}/config_data + ${CMAKE_CURRENT_SOURCE_DIR}/data_processing + ${CMAKE_CURRENT_SOURCE_DIR}/data_store + ${CMAKE_CURRENT_SOURCE_DIR}/modbus_client + ${CMAKE_CURRENT_SOURCE_DIR}/state_machine +) + +# Включаемые директории из include/ +target_include_directories(backend_lib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../../include + ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend + ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend/backend_worker + ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend/data_store + ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend/modbus_client +) + +# Зависимости +target_link_libraries(backend_lib + PUBLIC + Qt6::Core + Qt6::Network + Qt6::Test ) -target_link_libraries(backend_lib PUBLIC Qt6::Core) +if(Qt6Modbus_FOUND) + target_link_libraries(backend_lib PUBLIC Qt6::Modbus) +endif() + +# Unit Tests enable_testing() -# Тест для BackendWorker -add_executable(test_backend_worker unit_tests/backend_worker_test/backend_worker_test.cpp) -target_link_libraries(test_backend_worker PRIVATE Qt6::Test backend_lib) -add_test(NAME BackendWorkerTest COMMAND test_backend_worker) - -# Тест для DataProcessor -add_executable(test_data_processor unit_tests/data_processor_test/data_processor_test.cpp) -target_link_libraries(test_data_processor PRIVATE Qt6::Test backend_lib) -add_test(NAME DataProcessorTest COMMAND test_data_processor) - -# Тест для DataStore -add_executable(test_data_store unit_tests/data_store_test/data_store_test.cpp) -target_link_libraries(test_data_store PRIVATE Qt6::Test backend_lib) -add_test(NAME DataStoreTest COMMAND test_data_store) - -# Тест для ModbusDataBridge -add_executable(test_modbus_data_bridge unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp) -target_link_libraries(test_modbus_data_bridge PRIVATE Qt6::Test backend_lib) -add_test(NAME ModbusDataBridgeTest COMMAND test_modbus_data_bridge) - -# Тест для StateMachine -add_executable(test_state_machine unit_tests/state_machine_test/state_machine_test.cpp) -target_link_libraries(test_state_machine PRIVATE Qt6::Test backend_lib) -add_test(NAME StateMachineTest COMMAND test_state_machine) \ No newline at end of file +# Макрос для создания тестов +macro(add_backend_test TEST_NAME TEST_FILE) + add_executable(${TEST_NAME} unit_tests/${TEST_FILE}) + target_include_directories(${TEST_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/backend_worker + ${CMAKE_CURRENT_SOURCE_DIR}/config_data + ${CMAKE_CURRENT_SOURCE_DIR}/data_processing + ${CMAKE_CURRENT_SOURCE_DIR}/data_store + ${CMAKE_CURRENT_SOURCE_DIR}/modbus_client + ${CMAKE_CURRENT_SOURCE_DIR}/state_machine + ${CMAKE_CURRENT_SOURCE_DIR}/../../include + ) + target_link_libraries(${TEST_NAME} PRIVATE + Qt6::Core + Qt6::Test + Qt6::Network + backend_lib + ) + if(Qt6Modbus_FOUND) + target_link_libraries(${TEST_NAME} PRIVATE Qt6::Modbus) + endif() + add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) +endmacro() + +# Добавляем тесты +add_backend_test(test_backend_worker backend_worker_test/backend_worker_test.h) +add_backend_test(test_data_processor data_processor_test/data_processor_test.h) +add_backend_test(test_data_store data_store_test/data_store_test.h) +add_backend_test(test_modbus_data_bridge modbus_data_bridge_test/modbus_data_bridge_test.h) +add_backend_test(test_state_machine state_machine_test/state_machine_test.h) + +# Вывод информации о сборке +message(STATUS "Backend Library: backend_lib") +message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") +message(STATUS "Qt6 Version: ${Qt6Core_VERSION}") +if(Qt6Modbus_FOUND) + message(STATUS "Qt6 Modbus: FOUND (QtModbusBridge enabled)") +else() + message(STATUS "Qt6 Modbus: NOT FOUND (using MockModbusBridge only)") +endif() +message(STATUS "Tests enabled") \ No newline at end of file diff --git a/src/backend/unit_tests/backend_worker_test/backend_worker_test.h b/src/backend/unit_tests/backend_worker_test/backend_worker_test.h index 74bf08b..b795ff3 100644 --- a/src/backend/unit_tests/backend_worker_test/backend_worker_test.h +++ b/src/backend/unit_tests/backend_worker_test/backend_worker_test.h @@ -10,7 +10,7 @@ private slots: void isStart(); void doubleStart(); void isStop(); - void doubleStop + void doubleStop(); void moveOnNextStage(); void correctCurrentStatus(); void isFinishedReport(); From 2416fbb10976aed1fa716b0bb1042f692a430e52 Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Wed, 6 May 2026 15:23:02 +0500 Subject: [PATCH 19/58] Delete src/backend/data_processing/DataProcessor.h --- src/backend/data_processing/DataProcessor.h | 75 --------------------- 1 file changed, 75 deletions(-) delete mode 100644 src/backend/data_processing/DataProcessor.h diff --git a/src/backend/data_processing/DataProcessor.h b/src/backend/data_processing/DataProcessor.h deleted file mode 100644 index 4bae412..0000000 --- a/src/backend/data_processing/DataProcessor.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef DATAPROCESSOR_H -#define DATAPROCESSOR_H - -#include -#include -#include -#include -#include "DataTypes.h" //Допишем -// SensorFrame, Decision, WarningEvent, AlarmEvent, ActuatorCommand, Config - -/** - * ver_1_черновик - * Принимает измерительные пакеты, сверяет значения с - * критическими порогами и формирует: - * - Decision; - * - WarningEvent; - * - AlarmEvent; - * - ActuatorCommand - так и не поняла оставлять это или нет??. - */ -class DataProcessor : public QObject -{ - Q_OBJECT -public: - explicit DataProcessor(QObject *parent = nullptr); - // загрузка пороговых значений - void setConfig(const Config &config); -public slots: - // приём пакета. - void processFrame(SensorFrame frame); - // информирование об этапе испытания - void onStageChanged(int newStage); - // сброс внутреннего состояния - void reset(); -signals: - // итог обработки каждого пакета - void decisionReady(Decision decision); - // предупреждение - void warningRaised(WarningEvent event); - // авария - void alarmRaised(AlarmEvent event); - // коррекция - void controlNeeded(ActuatorCommand correction, QString reason); - -private: - // описания проверки - struct Check - { - QString parameter; // имя параметра (для события) - double value; // текущее значение - double warnThreshold; // порог предупреждения - double alarmThreshold; // критический порог - bool upperBound; // true: нарушение при value > threshold; - // false: нарушение при value < threshold - QString description; - }; - Status runCheck(const Check &c, const SensorFrame &frame); - bool isParameterActiveOnStage(const QString ¶meter, int stage) const; // допустим ли контроль данного параметра в текущем этапе? - /// формирование корректирующего воздействия. Возвращает true, если коррекция сформирована. - bool buildCorrection(const SensorFrame &frame, - ActuatorCommand &outCmd, - QString &outReason) const; - // аварийная команда остановки (мотор выкл, дроссель/тормоз = 0). - static ActuatorCommand makeStopCommand(); - -private: - Config m_config; - int m_currentStage = 0; // текущий этап - bool m_alarmLatched = false; // после аварии блокируем дальнейшую генерацию сигналов - bool m_configured = false; - // Таймаут отсутствия данных и контроль допустимости управляющих сигналов - // еще не решили. Поля зарезервированы на всякий случай. - qint64 m_lastFrameTsMs = 0; - // qint64 m_dataTimeoutMs = 2000; // TODO: согласовать -}; -#endif // DATAPROCESSOR_H \ No newline at end of file From 2c07e1a30b000b180e0710116d9c3298b6b1f0b4 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 18:01:28 +0500 Subject: [PATCH 20/58] chore: fix errors to build project - remove QModbusDevice from IModbusBridge.h - add cpp files for tests - correctly add test files in CMakeLists.txt - address minor linking issues - return back necessary data to DataTypes.h that was removed by previous commits --- include/backend/DataTypes.h | 73 +++++++++++++++++++ .../backend_worker/backendcommunicator.h | 2 +- include/backend/modbus_client/IModbusBridge.h | 13 +--- .../backend/modbus_client/MockModbusBridge.h | 2 +- .../backend/modbus_client/QtModbusBridge.h | 2 +- include/backend/state_machine.h | 19 +++-- src/backend/CMakeLists.txt | 29 ++++++-- src/backend/backend_worker/backendworker.cpp | 11 +-- .../modbus_client/MockModbusBridge.cpp | 7 +- src/backend/state_machine/state_machine.cpp | 17 +++-- .../backend_worker_test.cpp | 19 +++++ .../backend_worker_test/backend_worker_test.h | 5 +- .../data_processor_test.cpp | 12 +++ .../data_processor_test/data_processor_test.h | 5 +- .../data_store_test/data_store_test.cpp | 20 +++++ .../data_store_test/data_store_test.h | 6 +- .../modbus_data_bridge_test.cpp | 16 ++++ .../modbus_data_bridge_test.h | 5 +- .../state_machine_test/state_machine_test.cpp | 11 +++ .../state_machine_test/state_machine_test.h | 5 +- 20 files changed, 211 insertions(+), 68 deletions(-) create mode 100644 src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp create mode 100644 src/backend/unit_tests/data_processor_test/data_processor_test.cpp create mode 100644 src/backend/unit_tests/data_store_test/data_store_test.cpp create mode 100644 src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp create mode 100644 src/backend/unit_tests/state_machine_test/state_machine_test.cpp diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index f3a7c78..120e256 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -5,6 +5,53 @@ #include #include #include +// Control registers mapping, read/write, 1-bit +struct CoilsRegistersScheme +{ + bool motorEnabled; +}; + +// Device status registers mapping, only read, 1-bit +struct DiscreteRegistersScheme +{ + bool fanAd; + bool fanBall; +}; + +// Settings and variables registers mapping, read/write, 16-bit +struct HoldingRegistersScheme +{ + double targetRpm; + double throttlePosition; + double brakeTorque; +}; + +// Sensor data registers mapping, only read, 16-bit +struct InputRegistersScheme +{ + double rpm, torque; + double dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; +}; + +//Структра для задания конфигурации Modbus-клиента +struct ModbusConfig +{ + //Ip-адрес Modbus-сервера + QString host; + //Порт Modbus-сервера + quint16 port = 1502; + int pollFrequencyMs = 1000; + int timeoutMs = 1000; + int retries = 3; + int unitId = 1; // we assume one modbus device, so unitId will be ignored + // Start address is 0 for each register type + CoilsRegistersScheme coils; + DiscreteRegistersScheme discrete; + InputRegistersScheme input; + HoldingRegistersScheme holding; +}; + // пакет измерений то, что приходит от ModbusClient через StateMachine (все требует уточнения) struct SensorFrame { @@ -18,6 +65,7 @@ struct SensorFrame int stage; //< № этапа }; Q_DECLARE_METATYPE(SensorFrame) + // лимиты, передаются в DataProcessor в конструкторе (тоже требует уточнения) struct ModelConfig { @@ -29,6 +77,7 @@ struct ModelConfig double maxRpm; //< общий лимит оборотов (можно расширить) }; Q_DECLARE_METATYPE(ModelConfig) + // тип управляющего воздействия (и это требует уточнения) enum class ControlType { @@ -39,6 +88,7 @@ enum class ControlType MotorEnable, // вкл/выкл мотора EmergencyStop // аварийный стоп }; + // одно управляющее воздействие (тип + значение) struct ModelControl { @@ -46,9 +96,11 @@ struct ModelControl double value = 0.0; }; Q_DECLARE_METATYPE(ModelControl) + // диагностическое состояние модели (добавлены предаварийные) enum class DiagState { + IDLE, Ok, PreWarn_RpmHigh, PreWarn_DieselTempHigh, @@ -64,6 +116,18 @@ enum class DiagState Alarm_PressureUnder, Alarm_Generic }; + +//Структра для управления моделью фронтом +struct FrontControl +{ + //Этап эксперимента + DiagState state; + //Максимальная частота в зависимости от этапа(необязательный параметр на некоторых этапах - может быть не задан) + int maxFreq; + //Время выполнения этапа в минутах(необязательный параметр на некоторых этапах - может быть не задан) + unsigned int time; +}; + // вектор управляющих воздействий + диагностика struct Decision { @@ -72,4 +136,13 @@ struct Decision QString reason; // причина (для последующей записи в БД, если надо записывать, требует уточнения) }; Q_DECLARE_METATYPE(Decision) + +//Структра для DataStore +struct Data +{ + //Снимок датчиков + SensorFrame frame; + //Этап эксперимента + DiagState state; +}; #endif // DATATYPES_H \ No newline at end of file diff --git a/include/backend/backend_worker/backendcommunicator.h b/include/backend/backend_worker/backendcommunicator.h index c916dd7..9f36c9b 100644 --- a/include/backend/backend_worker/backendcommunicator.h +++ b/include/backend/backend_worker/backendcommunicator.h @@ -24,7 +24,7 @@ class BackendCommunicator : public QObject void SendedSensorFrame(SensorFrame& sensorFrame); void SendedEmergencyStopInfo(); void SendedFeedback(bool isComplete); - void SendedData(QVector data); + void SendedData(QVector& data); //Сигналы для получения //Сигнал на приход нового конфига модели от фронта diff --git a/include/backend/modbus_client/IModbusBridge.h b/include/backend/modbus_client/IModbusBridge.h index cb150ec..7160bf2 100644 --- a/include/backend/modbus_client/IModbusBridge.h +++ b/include/backend/modbus_client/IModbusBridge.h @@ -1,7 +1,6 @@ #pragma once #include #include -#include class ModbusConfig; class ModelConfig; @@ -33,28 +32,24 @@ public slots: // Error signals /** * @brief Emitted when configuration fails during startup/setup. - * Groups QModbusDevice errors: ConfigurationError. * Note: config validation should be handled by the config builder, * so this error would not even happen. */ - void configurationError(QModbusDevice::Error type, const QString& reason); + void configurationError(const QString& reason); /** * @brief Emitted when physical connection fails or is lost. * Groups QModbusDevice errors: ConnectionError. */ - void connectionError(QModbusDevice::Error type, const QString& reason); + void connectionError(const QString& reason); /** * @brief Emitted when a read/write request fails. * Occurs during: Polling requests, direct read/write requests. - * Groups QModbusDevice errors: ReadError, WriteError, - * TimeoutError, ReplyAbortedError, InvalidResponseError, ProtocolError. */ - void requestError(QModbusDevice::Error type, const QString& reason); + void requestError(const QString& reason); /** * @brief Any error not fitting above categories - * Groups QModbusDevice errors: UnknownError. */ - void generalError(QModbusDevice::Error type, const QString& reason); + void generalError(const QString& reason); // Transition states void connectionLost(); void connectionRestored(); diff --git a/include/backend/modbus_client/MockModbusBridge.h b/include/backend/modbus_client/MockModbusBridge.h index d78621f..8b691b1 100644 --- a/include/backend/modbus_client/MockModbusBridge.h +++ b/include/backend/modbus_client/MockModbusBridge.h @@ -1,8 +1,8 @@ #pragma once #include "IModbusBridge.h" -#include "IModbusConfigBuilder.h" #include +class ModbusConfig; class ModelConfig; class MockModbusBridge : public IModbusBridge diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index 3f2f867..7af89a9 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -1,10 +1,10 @@ #pragma once #include "IModbusBridge.h" -#include "IModbusConfigBuilder.h" #include #include #include +class ModbusConfig; class ModelConfig; class QtModbusBridge : public IModbusBridge diff --git a/include/backend/state_machine.h b/include/backend/state_machine.h index 18165a1..31e147d 100644 --- a/include/backend/state_machine.h +++ b/include/backend/state_machine.h @@ -4,23 +4,25 @@ #include #include #include "DataTypes.h" -#include "backend_worker/backendworker.h" #include "backend_worker/backendcommunicator.h" #include "config_data.h" -#include "modbus_client/QtModbusBridge.h" + +class IModbusBridge; +class BackendWorker; class StateMachine : public QObject { Q_OBJECT public: - explicit StateMachine(BackendWorker* backendWorker, QObject *parent = nullptr); + explicit StateMachine(BackendWorker* backendWorker, QObject* parent = nullptr); + ~StateMachine(); void Run(); void Stop(); - void requestStart(const ModelConfig &config); + void requestStart(const ModelConfig& config); void requestNextStage(); - void requestAbort(const QString &reason = QString()); + void requestAbort(const QString& reason = QString()); DiagState currentState() const; int currentStageIndex() const; @@ -41,15 +43,12 @@ private slots: void transitionTo(DiagState newState); BackendCommunicator* m_communicator; - QtModbusBridge* m_modbusBridge; + IModbusBridge* m_modbusBridge; DiagState m_state = DiagState::IDLE; ModelConfig m_config; - QTimer *m_stageTimer; + QTimer* m_stageTimer; int m_currentStageIndex = -1; int m_previousStageIndex = -1; }; - - - #endif //State_machine H diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 454021b..8cfb936 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -86,8 +86,8 @@ endif() enable_testing() # Макрос для создания тестов -macro(add_backend_test TEST_NAME TEST_FILE) - add_executable(${TEST_NAME} unit_tests/${TEST_FILE}) +macro(add_backend_test TEST_NAME) + add_executable(${TEST_NAME} ${ARGN}) target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/backend_worker ${CMAKE_CURRENT_SOURCE_DIR}/config_data @@ -110,11 +110,26 @@ macro(add_backend_test TEST_NAME TEST_FILE) endmacro() # Добавляем тесты -add_backend_test(test_backend_worker backend_worker_test/backend_worker_test.h) -add_backend_test(test_data_processor data_processor_test/data_processor_test.h) -add_backend_test(test_data_store data_store_test/data_store_test.h) -add_backend_test(test_modbus_data_bridge modbus_data_bridge_test/modbus_data_bridge_test.h) -add_backend_test(test_state_machine state_machine_test/state_machine_test.h) +add_backend_test(test_backend_worker + unit_tests/backend_worker_test/backend_worker_test.cpp + unit_tests/backend_worker_test/backend_worker_test.h +) +add_backend_test(test_data_processor + unit_tests/data_processor_test/data_processor_test.cpp + unit_tests/data_processor_test/data_processor_test.h +) +add_backend_test(test_data_store + unit_tests/data_store_test/data_store_test.cpp + unit_tests/data_store_test/data_store_test.h +) +add_backend_test(test_modbus_data_bridge + unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp + unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h +) +add_backend_test(test_state_machine + unit_tests/state_machine_test/state_machine_test.cpp + unit_tests/state_machine_test/state_machine_test.h +) # Вывод информации о сборке message(STATUS "Backend Library: backend_lib") diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp index 4f0689a..54f87aa 100644 --- a/src/backend/backend_worker/backendworker.cpp +++ b/src/backend/backend_worker/backendworker.cpp @@ -1,7 +1,8 @@ #include "../../../include/backend/backend_worker/backendworker.h" +#include "../../../include/backend/state_machine.h" -BackendWorker::BackendWorker(QObject *parent) : - m_machine(new StateMachine()), m_machineThread(QThread(this)), QObject{parent} +BackendWorker::BackendWorker(QObject* parent) : + m_machine(new StateMachine(this, parent)), m_machineThread(QThread(this)), QObject{parent} { qRegisterMetaType(); qRegisterMetaType(); @@ -10,18 +11,18 @@ BackendWorker::BackendWorker(QObject *parent) : connect(&m_machineThread, &QThread::finished, m_machine, &StateMachine::Stop, Qt::QueuedConnection); } -void BackendWorker::RunCore() +void BackendWorker::Run() { m_machine->moveToThread(&m_machineThread); m_machineThread.start(); } -void BackendWorker::StopCore() +void BackendWorker::Stop() { m_machineThread.quit(); } BackendWorker::~BackendWorker() { - m_machine.deleteLater(); + m_machine->deleteLater(); } diff --git a/src/backend/modbus_client/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp index 45a09a2..1ac1d09 100644 --- a/src/backend/modbus_client/MockModbusBridge.cpp +++ b/src/backend/modbus_client/MockModbusBridge.cpp @@ -1,6 +1,5 @@ -#include "MockModbusBridge.h" #include "DataTypes.h" -#include "IModbusConfigBuilder.h" +#include "MockModbusBridge.h" #include #include #include @@ -46,13 +45,11 @@ void MockModbusBridge::onWriteConfig(const ModelConfig& cmd) void MockModbusBridge::requestSensors() { qInfo() << "[MockModbusBridge] Requesting data"; - SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1, 1 }; emit sensorsDataReady(frame); } void MockModbusBridge::requestInfo() { qInfo() << "[MockModbusBridge] Requesting model info"; - ModelInfo info = { 1, 1, {true, 1, 1, 1} }; - emit modelInfoReady(info); } diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 0f7c4ed..f158003 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,23 +1,24 @@ #include "../../../include/backend/state_machine.h" -#include +#include "../../../include/backend/backend_worker/backendworker.h" +#include "../../../include/backend/modbus_client/MockModbusBridge.h" StateMachine::StateMachine(BackendWorker* backendWorker, QObject *parent) : m_communicator(new BackendCommunicator(this)), - m_modbusBridge(new QtModbusBridge(ConfigData().LoadConfig())), QObject{parent} + m_modbusBridge(new MockModbusBridge(ConfigData().LoadConfig())), QObject{parent} { connect(backendWorker, &BackendWorker::SendedFrontControlToBackend, - m_communicator, &BackendCommunicator::ReceivedFrontControl, Qt::QuenedConnection); + m_communicator, &BackendCommunicator::ReceivedFrontControl, Qt::QueuedConnection); connect(backendWorker, &BackendWorker::SendedModelConfigToBackend, - m_communicator, &BackendCommunicator::ReceivedModelConfig, Qt::QuenedConnection); + m_communicator, &BackendCommunicator::ReceivedModelConfig, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::SendedSensorFrame, - backendWorker, &BackendWorker::ReceivedSensorFrame, Qt::QuenedConnection); + backendWorker, &BackendWorker::ReceivedSensorFrame, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::SendedEmergencyStopInfo, - backendWorker, &BackendWorker::ReceivedEmergencyStopInfo, Qt::QuenedConnection); + backendWorker, &BackendWorker::ReceivedEmergencyStopInfo, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::SendedFeedback, - backendWorker, &BackendWorker::ReceivedFeedback, Qt::QuenedConnection); + backendWorker, &BackendWorker::ReceivedFeedback, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::SendedData, - backendWorker, &BackendWorker::ReceivedData, Qt::QuenedConnection); + backendWorker, &BackendWorker::ReceivedData, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::ReceivedFrontControl, this, &StateMachine::onReceivedFrontControl); diff --git a/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp b/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp new file mode 100644 index 0000000..41fff2e --- /dev/null +++ b/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp @@ -0,0 +1,19 @@ +// backend_worker_test.cpp +#include "backend_worker_test.h" + +void BackendWorkerTest::init() {} +void BackendWorkerTest::cleanup() {} + +void BackendWorkerTest::isStart() {} +void BackendWorkerTest::doubleStart() {} +void BackendWorkerTest::isStop() {} +void BackendWorkerTest::doubleStop() {} +void BackendWorkerTest::moveOnNextStage() {} +void BackendWorkerTest::correctCurrentStatus() {} +void BackendWorkerTest::isFinishedReport() {} +void BackendWorkerTest::isFinishedHistory() {} +void BackendWorkerTest::isAlarmEvent() {} +void BackendWorkerTest::isWarningEvent() {} +void BackendWorkerTest::isProcessFinished() {} + +QTEST_MAIN(BackendWorkerTest) \ No newline at end of file diff --git a/src/backend/unit_tests/backend_worker_test/backend_worker_test.h b/src/backend/unit_tests/backend_worker_test/backend_worker_test.h index b795ff3..a3251e1 100644 --- a/src/backend/unit_tests/backend_worker_test/backend_worker_test.h +++ b/src/backend/unit_tests/backend_worker_test/backend_worker_test.h @@ -1,5 +1,4 @@ #include -#include <../src/backend_worker/backend_worker.h> class BackendWorkerTest : public QObject { Q_OBJECT @@ -18,6 +17,4 @@ private slots: void isAlarmEvent(); void isWarningEvent(); void isProcessFinished(); - - -} // BackendWorkerTest \ No newline at end of file +}; // BackendWorkerTest diff --git a/src/backend/unit_tests/data_processor_test/data_processor_test.cpp b/src/backend/unit_tests/data_processor_test/data_processor_test.cpp new file mode 100644 index 0000000..815d8ad --- /dev/null +++ b/src/backend/unit_tests/data_processor_test/data_processor_test.cpp @@ -0,0 +1,12 @@ +#include "data_processor_test.h" + +void DataProcessorTest::init() {} +void DataProcessorTest::cleanup() {} + +void DataProcessorTest::correctFrame() {} +void DataProcessorTest::isDecisionReady() {} +void DataProcessorTest::isWarningRaised() {} +void DataProcessorTest::isAlarmRaised() {} +void DataProcessorTest::isControlProduced() {} + +QTEST_MAIN(DataProcessorTest) \ No newline at end of file diff --git a/src/backend/unit_tests/data_processor_test/data_processor_test.h b/src/backend/unit_tests/data_processor_test/data_processor_test.h index 8862c75..b163a0a 100644 --- a/src/backend/unit_tests/data_processor_test/data_processor_test.h +++ b/src/backend/unit_tests/data_processor_test/data_processor_test.h @@ -1,16 +1,13 @@ #include -#include <../src/data_processor/data_processor.h> class DataProcessorTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); - void correctFrame(); void isDecisionReady(); void isWarningRaised(); void isAlarmRaised(); void isControlProduced(); - -} // DataProcessorTest \ No newline at end of file +}; // DataProcessorTest diff --git a/src/backend/unit_tests/data_store_test/data_store_test.cpp b/src/backend/unit_tests/data_store_test/data_store_test.cpp new file mode 100644 index 0000000..d4e968d --- /dev/null +++ b/src/backend/unit_tests/data_store_test/data_store_test.cpp @@ -0,0 +1,20 @@ +#include "data_store_test.h" + +void DataStoreTest::init() {} +void DataStoreTest::cleanup() {} + +void DataStoreTest::isFileExist() {} +void DataStoreTest::isWriteRecord() {} +void DataStoreTest::correctWriteRecord() {} +void DataStoreTest::doubleWriteRecord() {} +void DataStoreTest::rewritePrevRecord() {} +void DataStoreTest::isWriteEvent() {} +void DataStoreTest::correctWriteEvent() {} +void DataStoreTest::doubleWriteEvent() {} +void DataStoreTest::rewritePrevEvent() {} +void DataStoreTest::isReadRecord() {} +void DataStoreTest::correctReadRecord() {} +void DataStoreTest::isReadEvent() {} +void DataStoreTest::correctReadEvent() {} + +QTEST_MAIN(DataStoreTest) \ No newline at end of file diff --git a/src/backend/unit_tests/data_store_test/data_store_test.h b/src/backend/unit_tests/data_store_test/data_store_test.h index 468b6e8..3f7fe67 100644 --- a/src/backend/unit_tests/data_store_test/data_store_test.h +++ b/src/backend/unit_tests/data_store_test/data_store_test.h @@ -1,12 +1,10 @@ #include -#include <../src/data_store/data_store.h> class DataStoreTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); - void isFileExist(); void isWriteRecord(); void correctWriteRecord(); @@ -16,10 +14,8 @@ private slots: void correctWriteEvent(); void doubleWriteEvent(); void rewritePrevEvent(); - void isReadRecord(); void correctReadRecord(); void isReadEvent(); void correctReadEvent(); - -} // DataStoreTest \ No newline at end of file +}; // DataStoreTest diff --git a/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp new file mode 100644 index 0000000..2658619 --- /dev/null +++ b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp @@ -0,0 +1,16 @@ +#include "modbus_data_bridge_test.h" + +void ModbusDataBridgeTest::init() {} +void ModbusDataBridgeTest::cleanup() {} + +void ModbusDataBridgeTest::isStartPolling() {} +void ModbusDataBridgeTest::doubleStartPolling() {} +void ModbusDataBridgeTest::isStopPolling() {} +void ModbusDataBridgeTest::doubleStopPolling() {} +void ModbusDataBridgeTest::isWriteCommand() {} +void ModbusDataBridgeTest::correctWriteCommand() {} +void ModbusDataBridgeTest::isReadData() {} +void ModbusDataBridgeTest::correctReadData() {} +void ModbusDataBridgeTest::correctReadError() {} + +QTEST_MAIN(ModbusDataBridgeTest) \ No newline at end of file diff --git a/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h index d34cffd..9f71b43 100644 --- a/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h +++ b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h @@ -1,12 +1,10 @@ #include -#include <../src/modbus_data_bridge/modbus_data_bridge.h> class ModbusDataBridgeTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); - void isStartPolling(); void doubleStartPolling(); void isStopPolling(); @@ -16,5 +14,4 @@ private slots: void isReadData(); void correctReadData(); void correctReadError(); - -} // ModbusDataBridgeTest \ No newline at end of file +}; // ModbusDataBridgeTest diff --git a/src/backend/unit_tests/state_machine_test/state_machine_test.cpp b/src/backend/unit_tests/state_machine_test/state_machine_test.cpp new file mode 100644 index 0000000..a458545 --- /dev/null +++ b/src/backend/unit_tests/state_machine_test/state_machine_test.cpp @@ -0,0 +1,11 @@ +#include "state_machine_test.h" + +void StateMachineTest::init() {} +void StateMachineTest::cleanup() {} + +void StateMachineTest::isStart() {} +void StateMachineTest::moveOnNextStage() {} +void StateMachineTest::isAbort() {} +void StateMachineTest::correctUIStatus() {} + +QTEST_MAIN(StateMachineTest) \ No newline at end of file diff --git a/src/backend/unit_tests/state_machine_test/state_machine_test.h b/src/backend/unit_tests/state_machine_test/state_machine_test.h index 9569434..b326e9a 100644 --- a/src/backend/unit_tests/state_machine_test/state_machine_test.h +++ b/src/backend/unit_tests/state_machine_test/state_machine_test.h @@ -1,15 +1,12 @@ #include -#include <../src/state_machine/state_machine.h> class StateMachineTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); - void isStart(); void moveOnNextStage(); void isAbort(); void correctUIStatus(); - -} // StateMachineTest \ No newline at end of file +}; // StateMachineTest From 64c6765490cec5f325f66c4b2197d0e7aaebf088 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 20:52:38 +0500 Subject: [PATCH 21/58] chore: update CMakeLists for QtCreator to show header files in the sidebar --- src/backend/CMakeLists.txt | 79 ++++++++++--------- .../backend_worker/backendcommunicator.cpp | 2 +- src/backend/backend_worker/backendworker.cpp | 4 +- src/backend/config_data/config_data.cpp | 2 +- src/backend/data_processing/DataProcessor.cpp | 2 +- src/backend/modbus_client/IModbusBridge.cpp | 2 +- .../modbus_client/MockModbusBridge.cpp | 4 +- src/backend/modbus_client/QtModbusBridge.cpp | 4 +- src/backend/state_machine/state_machine.cpp | 6 +- 9 files changed, 55 insertions(+), 50 deletions(-) diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 8cfb936..df366a6 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -1,5 +1,4 @@ cmake_minimum_required(VERSION 3.16) -project(ScadaForDiesel LANGUAGES CXX) # Настройки проекта set(CMAKE_CXX_STANDARD 17) @@ -9,8 +8,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) # Зависимости -find_package(Qt6 REQUIRED COMPONENTS - Core +find_package(Qt6 REQUIRED COMPONENTS + Core Network Test ) @@ -22,7 +21,7 @@ if(NOT Qt6Modbus_FOUND) message(WARNING "Install: sudo apt install qt6-serial-dev (or equivalent)") endif() -# Основная библиотека Backend +# Исходники set(BACKEND_SOURCES backend_worker/backendworker.cpp backend_worker/backendcommunicator.cpp @@ -34,78 +33,78 @@ set(BACKEND_SOURCES state_machine/state_machine.cpp ) -# Добавляем QtModbusBridge только если Modbus доступен if(Qt6Modbus_FOUND) list(APPEND BACKEND_SOURCES modbus_client/QtModbusBridge.cpp) endif() +# Заголовки set(BACKEND_HEADERS - backend_worker/backendworker.h - backend_worker/backendcommunicator.h - data_processor/DataProcessor.h - data_store/DataStore.h - modbus_client/IModbusBridge.h - modbus_client/QtModbusBridge.h - modbus_client/MockModbusBridge.h + ${PROJECT_SOURCE_DIR}/include/backend/backend_worker/backendworker.h + ${PROJECT_SOURCE_DIR}/include/backend/backend_worker/backendcommunicator.h + ${PROJECT_SOURCE_DIR}/include/backend/DataProcessor.h + ${PROJECT_SOURCE_DIR}/include/backend/DataTypes.h + ${PROJECT_SOURCE_DIR}/include/backend/config_data.h + ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/IModbusBridge.h + ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/MockModbusBridge.h + ${PROJECT_SOURCE_DIR}/include/backend/state_machine.h ) -add_library(backend_lib STATIC ${BACKEND_SOURCES}) +# Библиотека +add_library(backend_lib STATIC) -# Включаемые директории для библиотеки -target_include_directories(backend_lib PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/backend_worker - ${CMAKE_CURRENT_SOURCE_DIR}/config_data - ${CMAKE_CURRENT_SOURCE_DIR}/data_processing - ${CMAKE_CURRENT_SOURCE_DIR}/data_store - ${CMAKE_CURRENT_SOURCE_DIR}/modbus_client - ${CMAKE_CURRENT_SOURCE_DIR}/state_machine +target_sources(backend_lib + PRIVATE + ${BACKEND_SOURCES} + ${BACKEND_HEADERS} ) -# Включаемые директории из include/ +# Include paths для компиляции target_include_directories(backend_lib PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../../include - ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend - ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend/backend_worker - ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend/data_store - ${CMAKE_CURRENT_SOURCE_DIR}/../../include/backend/modbus_client + ${PROJECT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} ) # Зависимости -target_link_libraries(backend_lib - PUBLIC +target_link_libraries(backend_lib + PUBLIC Qt6::Core Qt6::Network Qt6::Test ) +# Добавляем QtModbusBridge только если Modbus доступен if(Qt6Modbus_FOUND) target_link_libraries(backend_lib PUBLIC Qt6::Modbus) endif() +# Группировка файлов для QtCreator +# Это убирает попадание хедеров в +source_group(TREE "${PROJECT_SOURCE_DIR}/include" PREFIX "include" FILES ${BACKEND_HEADERS}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "src" FILES ${BACKEND_SOURCES}) + # Unit Tests enable_testing() # Макрос для создания тестов macro(add_backend_test TEST_NAME) add_executable(${TEST_NAME} ${ARGN}) + target_include_directories(${TEST_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/backend_worker - ${CMAKE_CURRENT_SOURCE_DIR}/config_data - ${CMAKE_CURRENT_SOURCE_DIR}/data_processing - ${CMAKE_CURRENT_SOURCE_DIR}/data_store - ${CMAKE_CURRENT_SOURCE_DIR}/modbus_client - ${CMAKE_CURRENT_SOURCE_DIR}/state_machine - ${CMAKE_CURRENT_SOURCE_DIR}/../../include + ${PROJECT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} ) - target_link_libraries(${TEST_NAME} PRIVATE + + target_link_libraries(${TEST_NAME} PRIVATE Qt6::Core Qt6::Test Qt6::Network backend_lib ) + if(Qt6Modbus_FOUND) target_link_libraries(${TEST_NAME} PRIVATE Qt6::Modbus) endif() + add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) endmacro() @@ -114,18 +113,22 @@ add_backend_test(test_backend_worker unit_tests/backend_worker_test/backend_worker_test.cpp unit_tests/backend_worker_test/backend_worker_test.h ) + add_backend_test(test_data_processor unit_tests/data_processor_test/data_processor_test.cpp unit_tests/data_processor_test/data_processor_test.h ) + add_backend_test(test_data_store unit_tests/data_store_test/data_store_test.cpp unit_tests/data_store_test/data_store_test.h ) + add_backend_test(test_modbus_data_bridge unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h ) + add_backend_test(test_state_machine unit_tests/state_machine_test/state_machine_test.cpp unit_tests/state_machine_test/state_machine_test.h @@ -135,9 +138,11 @@ add_backend_test(test_state_machine message(STATUS "Backend Library: backend_lib") message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") message(STATUS "Qt6 Version: ${Qt6Core_VERSION}") + if(Qt6Modbus_FOUND) message(STATUS "Qt6 Modbus: FOUND (QtModbusBridge enabled)") else() message(STATUS "Qt6 Modbus: NOT FOUND (using MockModbusBridge only)") endif() + message(STATUS "Tests enabled") \ No newline at end of file diff --git a/src/backend/backend_worker/backendcommunicator.cpp b/src/backend/backend_worker/backendcommunicator.cpp index 9693fa8..f468372 100644 --- a/src/backend/backend_worker/backendcommunicator.cpp +++ b/src/backend/backend_worker/backendcommunicator.cpp @@ -1,4 +1,4 @@ -#include "../../../include/backend/backend_worker/backendcommunicator.h" +#include "backend/backend_worker/backendcommunicator.h" BackendCommunicator::BackendCommunicator(QObject *parent) : QObject{parent} {} diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp index 54f87aa..ed8bf28 100644 --- a/src/backend/backend_worker/backendworker.cpp +++ b/src/backend/backend_worker/backendworker.cpp @@ -1,5 +1,5 @@ -#include "../../../include/backend/backend_worker/backendworker.h" -#include "../../../include/backend/state_machine.h" +#include "backend/backend_worker/backendworker.h" +#include "backend/state_machine.h" BackendWorker::BackendWorker(QObject* parent) : m_machine(new StateMachine(this, parent)), m_machineThread(QThread(this)), QObject{parent} diff --git a/src/backend/config_data/config_data.cpp b/src/backend/config_data/config_data.cpp index 3d2b5cb..8cba9e3 100644 --- a/src/backend/config_data/config_data.cpp +++ b/src/backend/config_data/config_data.cpp @@ -1,4 +1,4 @@ -#include "../../../include/backend/config_data.h" +#include "backend/config_data.h" ConfigData::ConfigData(){} diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 3f8bde0..0127018 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,4 +1,4 @@ -#include "DataProcessor.h" +#include "backend/DataProcessor.h" // этапы — должны совпадать с StateMachine (согласовать) namespace Stage { diff --git a/src/backend/modbus_client/IModbusBridge.cpp b/src/backend/modbus_client/IModbusBridge.cpp index fc83a47..afe3794 100644 --- a/src/backend/modbus_client/IModbusBridge.cpp +++ b/src/backend/modbus_client/IModbusBridge.cpp @@ -1,3 +1,3 @@ -#include "IModbusBridge.h" +#include "backend/modbus_client/IModbusBridge.h" IModbusBridge::IModbusBridge(const ModbusConfig& cfg, QObject* parent) : QObject(parent) {} diff --git a/src/backend/modbus_client/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp index 1ac1d09..f3a1bc1 100644 --- a/src/backend/modbus_client/MockModbusBridge.cpp +++ b/src/backend/modbus_client/MockModbusBridge.cpp @@ -1,5 +1,5 @@ -#include "DataTypes.h" -#include "MockModbusBridge.h" +#include "backend/DataTypes.h" +#include "backend/modbus_client/MockModbusBridge.h" #include #include #include diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 8076e74..6445108 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -1,5 +1,5 @@ -#include "QtModbusBridge.h" -#include "DataTypes.h" +#include "backend/DataTypes.h" +#include "backend/modbus_client/QtModbusBridge.h" #include #include #include diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index f158003..7844af1 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,6 +1,6 @@ -#include "../../../include/backend/state_machine.h" -#include "../../../include/backend/backend_worker/backendworker.h" -#include "../../../include/backend/modbus_client/MockModbusBridge.h" +#include "backend/state_machine.h" +#include "backend/backend_worker/backendworker.h" +#include "backend/modbus_client/MockModbusBridge.h" StateMachine::StateMachine(BackendWorker* backendWorker, QObject *parent) : m_communicator(new BackendCommunicator(this)), From dd7343b83de6a16ac6d63e9afb6f8e4725e9d7b7 Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Wed, 6 May 2026 21:32:18 +0500 Subject: [PATCH 22/58] chore: move headers to folders and add .gitignore --- .gitignore | 1 + .../backend_worker/backendcommunicator.h | 2 +- .../backend/backend_worker/backendworker.h | 4 +- .../backend/{ => config_data}/config_data.h | 2 +- .../{ => data_processing}/DataProcessor.h | 84 +++--- include/backend/data_store/DataStore.h | 254 +++++++++--------- .../{ => state_machine}/state_machine.h | 6 +- src/backend/CMakeLists.txt | 7 +- src/backend/backend_worker/backendworker.cpp | 2 +- src/backend/config_data/config_data.cpp | 2 +- src/backend/data_processing/DataProcessor.cpp | 2 +- src/backend/state_machine/state_machine.cpp | 2 +- 12 files changed, 185 insertions(+), 183 deletions(-) create mode 100644 .gitignore rename include/backend/{ => config_data}/config_data.h (94%) rename include/backend/{ => data_processing}/DataProcessor.h (96%) rename include/backend/{ => state_machine}/state_machine.h (91%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/include/backend/backend_worker/backendcommunicator.h b/include/backend/backend_worker/backendcommunicator.h index 9f36c9b..aeb7ee3 100644 --- a/include/backend/backend_worker/backendcommunicator.h +++ b/include/backend/backend_worker/backendcommunicator.h @@ -2,7 +2,7 @@ #define BACKENDCOMMUNICATOR_H #include -#include "../DataTypes.h" +#include "backend/DataTypes.h" class BackendCommunicator : public QObject { diff --git a/include/backend/backend_worker/backendworker.h b/include/backend/backend_worker/backendworker.h index b6e036a..7001336 100644 --- a/include/backend/backend_worker/backendworker.h +++ b/include/backend/backend_worker/backendworker.h @@ -3,8 +3,8 @@ #include #include -#include "../state_machine.h" -#include "../DataTypes.h" +#include "backend/state_machine/state_machine.h" +#include "backend/DataTypes.h" //Класс взаимодействия с фронтом class BackendWorker : public QObject diff --git a/include/backend/config_data.h b/include/backend/config_data/config_data.h similarity index 94% rename from include/backend/config_data.h rename to include/backend/config_data/config_data.h index 224af07..70b588a 100644 --- a/include/backend/config_data.h +++ b/include/backend/config_data/config_data.h @@ -5,7 +5,7 @@ #include #include #include -#include "DataTypes.h" +#include "backend/DataTypes.h" class ConfigData : public QObject { diff --git a/include/backend/DataProcessor.h b/include/backend/data_processing/DataProcessor.h similarity index 96% rename from include/backend/DataProcessor.h rename to include/backend/data_processing/DataProcessor.h index 2274aec..f499c08 100644 --- a/include/backend/DataProcessor.h +++ b/include/backend/data_processing/DataProcessor.h @@ -1,43 +1,43 @@ -#ifndef DATAPROCESSOR_H -#define DATAPROCESSOR_H -#include -#include -#include "DataTypes.h" -/** - * Модуль обработки данных. - * - * Принимает SensorFrame от StateMachine, сравнивает значения с порогами - * из ModelConfig (с учётом текущего этапа) и возвращает Decision — - * вектор управляющих воздействий + диагностическое состояние модели - * версия 2 - не финальная - */ -class DataProcessor : public QObject -{ - Q_OBJECT -public: - explicit DataProcessor(const ModelConfig &config, QObject *parent = nullptr); - -public slots: - void processFrame(SensorFrame frame); - // уведомление об актуальном этапе испытания (для учёта режимов) (согласовать) - void onStageChanged(int newStage); - // обновление конфигурации - void onConfigChanged(ModelConfig config); - // сброс защёлки аварии и внутреннего состояния (новый прогон) - void reset(); -signals: - // шотовое решение для StateMachine. - void decisionReady(Decision decision); - -private: - // активна ли проверка параметра на текущем этапе??? (исключение ложных срабатываний) - bool isParamActiveOnStage(const QString ¶m, int stage) const; - // Сформировать аварийную команду останова - static QVector makeStopControls(); - -private: - ModelConfig m_config; - int m_currentStage = 0; - bool m_alarmLatched = false; // после аварии решения не формируем -}; +#ifndef DATAPROCESSOR_H +#define DATAPROCESSOR_H +#include +#include +#include "backend/DataTypes.h" +/** + * Модуль обработки данных. + * + * Принимает SensorFrame от StateMachine, сравнивает значения с порогами + * из ModelConfig (с учётом текущего этапа) и возвращает Decision — + * вектор управляющих воздействий + диагностическое состояние модели + * версия 2 - не финальная + */ +class DataProcessor : public QObject +{ + Q_OBJECT +public: + explicit DataProcessor(const ModelConfig &config, QObject *parent = nullptr); + +public slots: + void processFrame(SensorFrame frame); + // уведомление об актуальном этапе испытания (для учёта режимов) (согласовать) + void onStageChanged(int newStage); + // обновление конфигурации + void onConfigChanged(ModelConfig config); + // сброс защёлки аварии и внутреннего состояния (новый прогон) + void reset(); +signals: + // шотовое решение для StateMachine. + void decisionReady(Decision decision); + +private: + // активна ли проверка параметра на текущем этапе??? (исключение ложных срабатываний) + bool isParamActiveOnStage(const QString ¶m, int stage) const; + // Сформировать аварийную команду останова + static QVector makeStopControls(); + +private: + ModelConfig m_config; + int m_currentStage = 0; + bool m_alarmLatched = false; // после аварии решения не формируем +}; #endif // DATAPROCESSOR_H \ No newline at end of file diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index 8bae6f8..9533222 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -1,127 +1,127 @@ -#pragma once - -#include -#include - -// Qt includes -#include -#include -#include -// #include - -// #include "../DataTypes.h" // MeasurementRecord, EventRecord, Report - -// Low-level layer -class Connector -{ -public: - explicit Connector(std::string path); // : path_(std::move(path)) {} - virtual ~Connector() noexcept = default; - - virtual int64_t write(const std::string &data) = 0; - virtual std::optional read(int64_t id) const = 0; - virtual bool update(int64_t id, const std::string &data) = 0; - virtual bool remove(int64_t id) = 0; - - size_t size() const noexcept; - const std::string &path() const noexcept { return path_; } - -private: - std::string path_; - size_t size_; -}; - -class CSVConnector : public Connector -{ -public: - explicit CSVConnector(std::string path); // : Connector(std::move(path)) {} - ~CSVConnector() noexcept override = default; - - int64_t write(const std::string &data) override; - std::optional read(int64_t id) const override; - bool update(int64_t id, const std::string &data) override; - bool remove(int64_t id) override; -}; - -// Qt layer -struct MeasurementRecord -{ - qint64 runId; - int stage; - qint64 timestampMs; - double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; - double throttle, brakeTorque; - QString flags; // "OK", "WARNING" и т.п. -}; - -struct EventRecord -{ - qint64 runId, timestampMs; - int stage; - QString type; // "stage_change", "abort", "warning", "info" - QString message; -}; - -struct Report -{ - qint64 runId; - QString startTime, endTime; - QString finalStatus; // "completed" или "aborted" - QVector stages; - QVector events; -}; - -enum class StreamType -{ - Measurement, - Event -}; - -inline uint qHash(StreamType key, uint seed = 0) noexcept -{ - return qHash(static_cast(key), seed); -} - -struct StorageConnector -{ - Connector *connector; - qint64 nextId = 1; - bool headersWritten = false; -}; - -class DataStore : public QObject -{ - Q_OBJECT - -public: - DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent = nullptr); - // В случае ошибки возвращаю пустые объекты? - QVector readRecords(qint64 runId) const; - QVector readEvents(qint64 runId) const; - Report buildReport(qint64 runId) const; - -public slots: - void writeRecord(const MeasurementRecord &record); - void writeEvent(const EventRecord &event); - -private: - QString serializeMeasurement(const MeasurementRecord &record) const; - QString serializeEvent(const EventRecord &event) const; - - MeasurementRecord deserializeMeasurement(const QString &line) const; - EventRecord deserializeEvent(const QString &line) const; - - void ensureHeaders(StreamType type); - qint64 generateId(StreamType type) noexcept; - -private: - QHash connectors_; -}; - -// Выносить ли структуры в DataTypes? -// Нужно ли использовать исключения? (иначе работать с bool/константами, std::nullopt, возвращать пустые объекты?) -// Ситуации для обработки: -// 1) Чтение файла, которого нет по пути / пустого файла -// 2) Чтение/обновление/удаление по некорректному id -// Если файла нет и происходит запись, он будет создан -// Нужен ли QDateTime или работать через qint64? \ No newline at end of file +// #pragma once + +// #include +// #include + +// // Qt includes +// #include +// #include +// #include +// // #include + +// // #include "../DataTypes.h" // MeasurementRecord, EventRecord, Report + +// // Low-level layer +// class Connector +// { +// public: +// explicit Connector(std::string path); // : path_(std::move(path)) {} +// virtual ~Connector() noexcept = default; + +// virtual int64_t write(const std::string &data) = 0; +// virtual std::optional read(int64_t id) const = 0; +// virtual bool update(int64_t id, const std::string &data) = 0; +// virtual bool remove(int64_t id) = 0; + +// size_t size() const noexcept; +// const std::string &path() const noexcept { return path_; } + +// private: +// std::string path_; +// size_t size_; +// }; + +// class CSVConnector : public Connector +// { +// public: +// explicit CSVConnector(std::string path); // : Connector(std::move(path)) {} +// ~CSVConnector() noexcept override = default; + +// int64_t write(const std::string &data) override; +// std::optional read(int64_t id) const override; +// bool update(int64_t id, const std::string &data) override; +// bool remove(int64_t id) override; +// }; + +// // Qt layer +// struct MeasurementRecord +// { +// qint64 runId; +// int stage; +// qint64 timestampMs; +// double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; +// double throttle, brakeTorque; +// QString flags; // "OK", "WARNING" и т.п. +// }; + +// struct EventRecord +// { +// qint64 runId, timestampMs; +// int stage; +// QString type; // "stage_change", "abort", "warning", "info" +// QString message; +// }; + +// struct Report +// { +// qint64 runId; +// QString startTime, endTime; +// QString finalStatus; // "completed" или "aborted" +// QVector stages; +// QVector events; +// }; + +// enum class StreamType +// { +// Measurement, +// Event +// }; + +// inline uint qHash(StreamType key, uint seed = 0) noexcept +// { +// return qHash(static_cast(key), seed); +// } + +// struct StorageConnector +// { +// Connector *connector; +// qint64 nextId = 1; +// bool headersWritten = false; +// }; + +// class DataStore : public QObject +// { +// Q_OBJECT + +// public: +// DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent = nullptr); +// // В случае ошибки возвращаю пустые объекты? +// QVector readRecords(qint64 runId) const; +// QVector readEvents(qint64 runId) const; +// Report buildReport(qint64 runId) const; + +// public slots: +// void writeRecord(const MeasurementRecord &record); +// void writeEvent(const EventRecord &event); + +// private: +// QString serializeMeasurement(const MeasurementRecord &record) const; +// QString serializeEvent(const EventRecord &event) const; + +// MeasurementRecord deserializeMeasurement(const QString &line) const; +// EventRecord deserializeEvent(const QString &line) const; + +// void ensureHeaders(StreamType type); +// qint64 generateId(StreamType type) noexcept; + +// private: +// QHash connectors_; +// }; + +// // Выносить ли структуры в DataTypes? +// // Нужно ли использовать исключения? (иначе работать с bool/константами, std::nullopt, возвращать пустые объекты?) +// // Ситуации для обработки: +// // 1) Чтение файла, которого нет по пути / пустого файла +// // 2) Чтение/обновление/удаление по некорректному id +// // Если файла нет и происходит запись, он будет создан +// // Нужен ли QDateTime или работать через qint64? \ No newline at end of file diff --git a/include/backend/state_machine.h b/include/backend/state_machine/state_machine.h similarity index 91% rename from include/backend/state_machine.h rename to include/backend/state_machine/state_machine.h index 31e147d..381a5f0 100644 --- a/include/backend/state_machine.h +++ b/include/backend/state_machine/state_machine.h @@ -3,9 +3,9 @@ #include #include -#include "DataTypes.h" -#include "backend_worker/backendcommunicator.h" -#include "config_data.h" +#include "backend/DataTypes.h" +#include "backend/backend_worker/backendcommunicator.h" +#include "backend/config_data/config_data.h" class IModbusBridge; class BackendWorker; diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index df366a6..a797519 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -41,12 +41,13 @@ endif() set(BACKEND_HEADERS ${PROJECT_SOURCE_DIR}/include/backend/backend_worker/backendworker.h ${PROJECT_SOURCE_DIR}/include/backend/backend_worker/backendcommunicator.h - ${PROJECT_SOURCE_DIR}/include/backend/DataProcessor.h + ${PROJECT_SOURCE_DIR}/include/backend/data_processing/DataProcessor.h + ${PROJECT_SOURCE_DIR}/include/backend/data_store/DataStore.h ${PROJECT_SOURCE_DIR}/include/backend/DataTypes.h - ${PROJECT_SOURCE_DIR}/include/backend/config_data.h + ${PROJECT_SOURCE_DIR}/include/backend/config_data/config_data.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/IModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/MockModbusBridge.h - ${PROJECT_SOURCE_DIR}/include/backend/state_machine.h + ${PROJECT_SOURCE_DIR}/include/backend/state_machine/state_machine.h ) # Библиотека diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp index ed8bf28..8ca1b81 100644 --- a/src/backend/backend_worker/backendworker.cpp +++ b/src/backend/backend_worker/backendworker.cpp @@ -1,5 +1,5 @@ #include "backend/backend_worker/backendworker.h" -#include "backend/state_machine.h" +#include "backend/state_machine/state_machine.h" BackendWorker::BackendWorker(QObject* parent) : m_machine(new StateMachine(this, parent)), m_machineThread(QThread(this)), QObject{parent} diff --git a/src/backend/config_data/config_data.cpp b/src/backend/config_data/config_data.cpp index 8cba9e3..c1b3779 100644 --- a/src/backend/config_data/config_data.cpp +++ b/src/backend/config_data/config_data.cpp @@ -1,4 +1,4 @@ -#include "backend/config_data.h" +#include "backend/config_data/config_data.h" ConfigData::ConfigData(){} diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 0127018..edcd3cd 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,4 +1,4 @@ -#include "backend/DataProcessor.h" +#include "backend/data_processing/DataProcessor.h" // этапы — должны совпадать с StateMachine (согласовать) namespace Stage { diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 7844af1..7ca8679 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,4 +1,4 @@ -#include "backend/state_machine.h" +#include "backend/state_machine/state_machine.h" #include "backend/backend_worker/backendworker.h" #include "backend/modbus_client/MockModbusBridge.h" From 05af80107a2f43a5657183f385f59ae94470aeb3 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 21:46:07 +0500 Subject: [PATCH 23/58] chore: update DataTypes.h and IModbusBridge.h - update register schemes - return back old SensorFrame structure - replace ModelInfo with ModelConfig - add onWriteControl method --- include/backend/DataTypes.h | 75 ++++++++++++++----- include/backend/modbus_client/IModbusBridge.h | 8 +- .../backend/modbus_client/MockModbusBridge.h | 2 + .../backend/modbus_client/QtModbusBridge.h | 2 + .../modbus_client/MockModbusBridge.cpp | 5 ++ src/backend/modbus_client/QtModbusBridge.cpp | 5 ++ 6 files changed, 74 insertions(+), 23 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 120e256..cd570c2 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -8,7 +8,8 @@ // Control registers mapping, read/write, 1-bit struct CoilsRegistersScheme { - bool motorEnabled; + bool fanAd; + bool fanBall; }; // Device status registers mapping, only read, 1-bit @@ -21,17 +22,37 @@ struct DiscreteRegistersScheme // Settings and variables registers mapping, read/write, 16-bit struct HoldingRegistersScheme { - double targetRpm; - double throttlePosition; - double brakeTorque; + //Максимальная температура ДВС + double maxDieselTemp; + //Максимальная температура АД + double maxMotorTemp; + //Максимальная температура балластных резисторов + double maxResistorTemp; + //Максимальное давление в ДВС + double maxDieselPressure; + //Минимальное давление в ДВС + double minDieselPressure; + //Максимальная частота в режиме притирки + int maxRpmLap; + //Максимальная частота в режиме обкатки + int maxRpmHot; }; // Sensor data registers mapping, only read, 16-bit struct InputRegistersScheme { - double rpm, torque; - double dieselTemp, motorTemp, resistorTemp, dieselPressure; - double throttle, brakeTorque; + //Температура ДВС + double dieselTemp; + //Температура АД + double motorTemp; + //Температура балластных резисторов + double resistorTemp; + //Давление + double dieselPressure; + //Момент + double torque; + //Частота вращения + double rpm; }; //Структра для задания конфигурации Modbus-клиента @@ -52,33 +73,47 @@ struct ModbusConfig HoldingRegistersScheme holding; }; -// пакет измерений то, что приходит от ModbusClient через StateMachine (все требует уточнения) +//Снимок датчиков struct SensorFrame { - double dieselTemp; //< температура ДВС - double motorTemp; //< температура АД (асинхронного двигателя) - double resistorBalance; //< балансировочный резистор (?) - double dieselPressure; //< давление ДВС - double torque; //< момент - double rpm; //< частота вращения - qint64 timestampMs; //< метка времени - int stage; //< № этапа + //Температура ДВС + double dieselTemp; + //Температура АД + double adTemp; + //Температура балластных резисторов + double resistorTemp; + //Давление + double dieselPressure; + //Момент + double torque; + //Частота + double rpm; + //Метка времени в UNIX-формате + qint64 timestampMs; }; Q_DECLARE_METATYPE(SensorFrame) -// лимиты, передаются в DataProcessor в конструкторе (тоже требует уточнения) +// лимиты, передаются в конструктор DataProcessor и в IModbusBridge для записи в модель struct ModelConfig { + //Максимальная температура ДВС double maxDieselTemp; + //Максимальная температура АД double maxMotorTemp; - double maxResistorBalance; + //Максимальная температура балластных резисторов + double maxResistorTemp; + //Максимальное давление в ДВС double maxDieselPressure; + //Минимальное давление в ДВС double minDieselPressure; - double maxRpm; //< общий лимит оборотов (можно расширить) + //Максимальная частота в режиме притирки + int maxRpmLap; + //Максимальная частота в режиме обкатки + int maxRpmHot; }; Q_DECLARE_METATYPE(ModelConfig) -// тип управляющего воздействия (и это требует уточнения) +// тип управляющего воздействия (это требует уточнения) enum class ControlType { None, diff --git a/include/backend/modbus_client/IModbusBridge.h b/include/backend/modbus_client/IModbusBridge.h index 7160bf2..2da94e8 100644 --- a/include/backend/modbus_client/IModbusBridge.h +++ b/include/backend/modbus_client/IModbusBridge.h @@ -4,7 +4,7 @@ class ModbusConfig; class ModelConfig; -class ModelInfo; +class ModelControl; class SensorFrame; class IModbusBridge : public QObject @@ -22,13 +22,15 @@ public slots: virtual void onReadSensors() = 0; // Handling direct call to read the model info virtual void onReadInfo() = 0; - // Handling direct call to write new model configuration + // Handling direct call to write new model configuration at startup virtual void onWriteConfig(const ModelConfig& cmd) = 0; + // Handling direct call to write controlling command at runtime + virtual void onWriteControl(const ModelControl& control) = 0; signals: // A response to the sensor data request has been received void sensorsDataReady(const SensorFrame& data); // A response to the model info request has been received - void modelInfoReady(const ModelInfo& data); + void modelInfoReady(const ModelConfig& data); // Error signals /** * @brief Emitted when configuration fails during startup/setup. diff --git a/include/backend/modbus_client/MockModbusBridge.h b/include/backend/modbus_client/MockModbusBridge.h index 8b691b1..76be5b5 100644 --- a/include/backend/modbus_client/MockModbusBridge.h +++ b/include/backend/modbus_client/MockModbusBridge.h @@ -4,6 +4,7 @@ class ModbusConfig; class ModelConfig; +class ModelControl; class MockModbusBridge : public IModbusBridge { @@ -16,6 +17,7 @@ public slots: void onReadSensors() override; void onReadInfo() override; void onWriteConfig(const ModelConfig& cmd) override; + void onWriteControl(const ModelControl& control) override; private slots: void requestSensors(); void requestInfo(); diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index 7af89a9..ab52fbc 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -6,6 +6,7 @@ class ModbusConfig; class ModelConfig; +class ModelControl; class QtModbusBridge : public IModbusBridge { @@ -19,6 +20,7 @@ public slots: void onReadSensors() override; void onReadInfo() override; void onWriteConfig(const ModelConfig& cmd) override; + void onWriteControl(const ModelControl& control) override; private slots: void parseSensorsResponse(QModbusReply* reply); void requestSensors(); diff --git a/src/backend/modbus_client/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp index f3a1bc1..6ea1cf2 100644 --- a/src/backend/modbus_client/MockModbusBridge.cpp +++ b/src/backend/modbus_client/MockModbusBridge.cpp @@ -42,6 +42,11 @@ void MockModbusBridge::onWriteConfig(const ModelConfig& cmd) qInfo() << "[MockModbusBridge] Writing a new configuration to the model"; } +void MockModbusBridge::onWriteControl(const ModelControl& control) +{ + qInfo() << "[MockModbusBridge] Sending a control command to the model"; +} + void MockModbusBridge::requestSensors() { qInfo() << "[MockModbusBridge] Requesting data"; diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 6445108..e7d7298 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -107,6 +107,11 @@ void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) } +void QtModbusBridge::onWriteControl(const ModelControl& control) +{ + +} + void QtModbusBridge::parseSensorsResponse(QModbusReply* reply) { if (reply->error() == QModbusDevice::NoError) From 98c4a26adcf739d28649159416e60759df1accb4 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 21:57:47 +0500 Subject: [PATCH 24/58] Merge branch 'backend' of https://github.com/VaryVA/ScadaForDiesel into backend --- include/backend/DataTypes.h | 12 ++++-------- src/backend/data_processing/DataProcessor.cpp | 12 ++++++------ src/backend/modbus_client/MockModbusBridge.cpp | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 1f2acd7..e278855 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -32,10 +32,8 @@ struct HoldingRegistersScheme double maxDieselPressure; //Минимальное давление в ДВС double minDieselPressure; - //Максимальная частота в режиме притирки - int maxRpmLap; - //Максимальная частота в режиме обкатки - int maxRpmHot; + //Общая частота оборотов (можно расширить) + int maxRpm; }; // Sensor data registers mapping, only read, 16-bit @@ -106,10 +104,8 @@ struct ModelConfig double maxDieselPressure; //Минимальное давление в ДВС double minDieselPressure; - //Максимальная частота в режиме притирки - int maxRpmLap; - //Максимальная частота в режиме обкатки - int maxRpmHot; + //Общая частота оборотов (можно расширить) + int maxRpm; }; Q_DECLARE_METATYPE(ModelConfig) diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index edcd3cd..1c3b582 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -106,19 +106,19 @@ void DataProcessor::processFrame(SensorFrame frame) } } // балансировочный резистор - if (isParamActiveOnStage("resistorBalance", m_currentStage)) + if (isParamActiveOnStage("resistorTemp", m_currentStage)) { - if (frame.resistorBalance >= m_config.maxResistorBalance) + if (frame.resistorTemp >= m_config.maxResistorTemp) { promote(2, DiagState::Alarm_ResistorOverheat, QStringLiteral("Превышение по балансировочному резистору: %1") - .arg(frame.resistorBalance)); + .arg(frame.resistorTemp)); } - else if (frame.resistorBalance >= m_config.maxResistorBalance * WARN_RATIO) + else if (frame.resistorTemp >= m_config.maxResistorTemp * WARN_RATIO) { promote(1, DiagState::PreWarn_ResistorHigh, QStringLiteral("Балансировочный резистор близок к пределу: %1") - .arg(frame.resistorBalance)); + .arg(frame.resistorTemp)); } } // давление ДВС — верхняя граница @@ -210,7 +210,7 @@ bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const stage == Stage::HOT_WITH_LOAD); } // балансировочный резистор — на этапе с нагрузкой - if (param == "resistorBalance") + if (param == "resistorTemp") { return (stage == Stage::HOT_WITH_LOAD); } diff --git a/src/backend/modbus_client/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp index 6ea1cf2..abfd066 100644 --- a/src/backend/modbus_client/MockModbusBridge.cpp +++ b/src/backend/modbus_client/MockModbusBridge.cpp @@ -50,7 +50,7 @@ void MockModbusBridge::onWriteControl(const ModelControl& control) void MockModbusBridge::requestSensors() { qInfo() << "[MockModbusBridge] Requesting data"; - SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1, 1 }; + SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1 }; emit sensorsDataReady(frame); } From 7b7dd1cc3ec01896a7f67f8e30607d04a5b19adc Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 22:36:32 +0500 Subject: [PATCH 25/58] chore: change onWriteControl to onWriteDecision --- include/backend/modbus_client/IModbusBridge.h | 6 +- .../backend/modbus_client/MockModbusBridge.h | 4 +- .../backend/modbus_client/QtModbusBridge.h | 4 +- .../modbus_client/MockModbusBridge.cpp | 4 +- src/backend/modbus_client/QtModbusBridge.cpp | 69 ++++++++++++++++++- 5 files changed, 75 insertions(+), 12 deletions(-) diff --git a/include/backend/modbus_client/IModbusBridge.h b/include/backend/modbus_client/IModbusBridge.h index 2da94e8..300435f 100644 --- a/include/backend/modbus_client/IModbusBridge.h +++ b/include/backend/modbus_client/IModbusBridge.h @@ -4,7 +4,7 @@ class ModbusConfig; class ModelConfig; -class ModelControl; +class Decision; class SensorFrame; class IModbusBridge : public QObject @@ -20,12 +20,12 @@ public slots: virtual void stopPolling() = 0; // Handling direct call to read sensor data virtual void onReadSensors() = 0; - // Handling direct call to read the model info + // Handling direct call to read the model info (holding registers or in other words model config) virtual void onReadInfo() = 0; // Handling direct call to write new model configuration at startup virtual void onWriteConfig(const ModelConfig& cmd) = 0; // Handling direct call to write controlling command at runtime - virtual void onWriteControl(const ModelControl& control) = 0; + virtual void onWriteDecision(const Decision& decision) = 0; signals: // A response to the sensor data request has been received void sensorsDataReady(const SensorFrame& data); diff --git a/include/backend/modbus_client/MockModbusBridge.h b/include/backend/modbus_client/MockModbusBridge.h index 76be5b5..1c3ba9f 100644 --- a/include/backend/modbus_client/MockModbusBridge.h +++ b/include/backend/modbus_client/MockModbusBridge.h @@ -4,7 +4,7 @@ class ModbusConfig; class ModelConfig; -class ModelControl; +class Decision; class MockModbusBridge : public IModbusBridge { @@ -17,7 +17,7 @@ public slots: void onReadSensors() override; void onReadInfo() override; void onWriteConfig(const ModelConfig& cmd) override; - void onWriteControl(const ModelControl& control) override; + void onWriteDecision(const Decision& decision) override; private slots: void requestSensors(); void requestInfo(); diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index ab52fbc..b3e3c50 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -6,7 +6,7 @@ class ModbusConfig; class ModelConfig; -class ModelControl; +class Decision; class QtModbusBridge : public IModbusBridge { @@ -20,7 +20,7 @@ public slots: void onReadSensors() override; void onReadInfo() override; void onWriteConfig(const ModelConfig& cmd) override; - void onWriteControl(const ModelControl& control) override; + void onWriteDecision(const Decision& decision) override; private slots: void parseSensorsResponse(QModbusReply* reply); void requestSensors(); diff --git a/src/backend/modbus_client/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp index abfd066..dd54771 100644 --- a/src/backend/modbus_client/MockModbusBridge.cpp +++ b/src/backend/modbus_client/MockModbusBridge.cpp @@ -42,9 +42,9 @@ void MockModbusBridge::onWriteConfig(const ModelConfig& cmd) qInfo() << "[MockModbusBridge] Writing a new configuration to the model"; } -void MockModbusBridge::onWriteControl(const ModelControl& control) +void MockModbusBridge::onWriteDecision(const Decision& decision) { - qInfo() << "[MockModbusBridge] Sending a control command to the model"; + qInfo() << "[MockModbusBridge] Sending a decision to the model"; } void MockModbusBridge::requestSensors() diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index e7d7298..2d3a6f6 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -104,10 +104,36 @@ void QtModbusBridge::onReadInfo() void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) { + qDebug() << "[QtModbusBridge] Requesting sensors data"; + m_dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); + m_dataUnit.setStartAddress(0); + m_dataUnit.setValueCount(sizeof(m_cfg.input) / 2); + + // Sending request to the device with specified Unit Id + auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); + + if (!reply) + { + return; + } + + // Check if request failed immediately + if (reply->isFinished()) + { + reply->deleteLater(); + return; + } + + // Handling reply with finished signal + QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() + { + parseSensorsResponse(reply); + reply->deleteLater(); + }); } -void QtModbusBridge::onWriteControl(const ModelControl& control) +void QtModbusBridge::onWriteDecision(const Decision& decision) { } @@ -131,11 +157,11 @@ void QtModbusBridge::parseSensorsResponse(QModbusReply* reply) void QtModbusBridge::requestSensors() { - qDebug() << "[QtModbusBridge] Requesting data"; + qDebug() << "[QtModbusBridge] Requesting sensors data"; m_dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); m_dataUnit.setStartAddress(0); - m_dataUnit.setValueCount(sizeof(m_cfg.holding) / 2); + m_dataUnit.setValueCount(sizeof(m_cfg.input) / 2); // Sending request to the device with specified Unit Id auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); @@ -162,11 +188,48 @@ void QtModbusBridge::requestSensors() void QtModbusBridge::parseInfoResponse(QModbusReply* reply) { + if (reply->error() == QModbusDevice::NoError) + { + return; + } + + const QModbusDataUnit result = reply->result(); + const QVector values = result.values(); + qDebug() << "[QtModbusBridge] Read registers:" << values.size(); + for (int i = 0; i < values.size(); ++i) + { + qDebug() << "[QtModbusBridge] Register #" << i << ":" << values[i]; + } } void QtModbusBridge::requestInfo() { qDebug() << "[QtModbusBridge] Requesting model info"; + m_dataUnit.setRegisterType(QModbusDataUnit::HoldingRegisters); + m_dataUnit.setStartAddress(0); + m_dataUnit.setValueCount(sizeof(m_cfg.holding) / 2); + + // Sending request to the device with specified Unit Id + auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); + + if (!reply) + { + return; + } + + // Check if request failed immediately + if (reply->isFinished()) + { + reply->deleteLater(); + return; + } + + // Handling reply with finished signal + QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() + { + parseInfoResponse(reply); + reply->deleteLater(); + }); } \ No newline at end of file From f77436de94cb94a9ec1986e22e12a275bc397145 Mon Sep 17 00:00:00 2001 From: lefff123 Date: Wed, 6 May 2026 22:38:34 +0500 Subject: [PATCH 26/58] =?UTF-8?q?=D0=A1=D1=8B=D1=80=D0=B0=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20Statem?= =?UTF-8?q?achine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/state_machine/state_machine.h | 63 +++-- src/backend/state_machine/state_machine.cpp | 228 ++++++++++++++++-- 2 files changed, 249 insertions(+), 42 deletions(-) diff --git a/include/backend/state_machine/state_machine.h b/include/backend/state_machine/state_machine.h index 381a5f0..745863c 100644 --- a/include/backend/state_machine/state_machine.h +++ b/include/backend/state_machine/state_machine.h @@ -3,12 +3,19 @@ #include #include -#include "backend/DataTypes.h" -#include "backend/backend_worker/backendcommunicator.h" -#include "backend/config_data/config_data.h" -class IModbusBridge; +#include "DataTypes.h" +#include "config_data.h" + +#include "backend_worker/backendcommunicator.h" +#include "modbus_client/IModbusBridge.h" +#include "data_store/DataStore.h" +#include "DataProcessor.h" + class BackendWorker; +class IModbusBridge; +class DataStore; +class DataProcessor; class StateMachine : public QObject { @@ -17,10 +24,9 @@ class StateMachine : public QObject explicit StateMachine(BackendWorker* backendWorker, QObject* parent = nullptr); ~StateMachine(); - void Run(); - void Stop(); + void start(const ModelConfig& config); + void stop(); - void requestStart(const ModelConfig& config); void requestNextStage(); void requestAbort(const QString& reason = QString()); @@ -32,23 +38,48 @@ class StateMachine : public QObject void finished(DiagState finalState); private slots: - void onStageTimeout(); + void pollModbus(); + void onSensorsDataReady(const SensorFrame& frame); + void onDecisionReady(const Decision& decision); + + // Ошибки Modbus + void onModbusConfigurationError(const QString& reason); + void onModbusConnectionError(const QString& reason); + void onModbusRequestError(const QString& reason); + void onModbusConnectionLost(); + void onModbusConnectionRestored(); - //Слот для обработки пришедшего запроса на изменения этапа эксперимента от фронта - void onReceivedFrontControl(FrontControl control); - //Слот для обработки пришедшего кофига от фронта - void onReceivedModelConfig(ModelConfig config); + // Команды фронта + void onReceivedFrontControl(const FrontControl& control); + void onReceivedModelConfig(const ModelConfig& config); private: void transitionTo(DiagState newState); + void applyControls(const QVector& controls); + // Коммуникатор BackendCommunicator* m_communicator; - IModbusBridge* m_modbusBridge; + // Модули + IModbusBridge* m_modbusBridge; + DataStore* m_dataStore; + DataProcessor* m_dataProcessor; + + // Хранилища низкого уровня + CSVConnector* m_measurementConnector; + CSVConnector* m_eventConnector; + + // Таймеры + QTimer* m_pollTimer; + QTimer* m_stageTimer; + + // Состояние DiagState m_state = DiagState::IDLE; ModelConfig m_config; - QTimer* m_stageTimer; + SensorFrame m_currentSensorFrame; int m_currentStageIndex = -1; - int m_previousStageIndex = -1; + int m_currentRunId = 0; // для связывания записей в datastore + qint64 m_runStartTime = 0; // для репортов }; -#endif //State_machine H + +#endif // STATE_MACHINE_H \ No newline at end of file diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 7ca8679..31399f5 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,51 +1,227 @@ +<<<<<<< HEAD +#include "../../../include/backend/state_machine.h" +#include "../../../include/backend/backend_worker/backendworker.h" +#include "../../../include/backend/modbus_client/MockModbusBridge.h" +#include +======= #include "backend/state_machine/state_machine.h" #include "backend/backend_worker/backendworker.h" #include "backend/modbus_client/MockModbusBridge.h" +>>>>>>> 98c4a26adcf739d28649159416e60759df1accb4 -StateMachine::StateMachine(BackendWorker* backendWorker, QObject *parent) : - m_communicator(new BackendCommunicator(this)), - m_modbusBridge(new MockModbusBridge(ConfigData().LoadConfig())), QObject{parent} +StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) + : QObject(parent) + , m_communicator(new BackendCommunicator(this)) + , m_pollTimer(new QTimer(this)) + , m_stageTimer(new QTimer(this)) { + ModbusConfig modbusCfg = ConfigData().LoadConfig(); + m_modbusBridge = new MockModbusBridge(modbusCfg, this); + + // ----- Подготавливаем CSV-коннекторы для хранилища ----- + // пути можно вынести в настройки, здесь для примера + m_measurementConnector = new CSVConnector("measurements.csv"); + m_eventConnector = new CSVConnector("events.csv"); + + // ----- Создаём DataStore ----- + m_dataStore = new DataStore(m_measurementConnector, m_eventConnector, this); + + m_dataProcessor = new DataProcessor(ModelConfig{}, this); + connect(backendWorker, &BackendWorker::SendedFrontControlToBackend, m_communicator, &BackendCommunicator::ReceivedFrontControl, Qt::QueuedConnection); connect(backendWorker, &BackendWorker::SendedModelConfigToBackend, m_communicator, &BackendCommunicator::ReceivedModelConfig, Qt::QueuedConnection); - connect(m_communicator, &BackendCommunicator::SendedSensorFrame, - backendWorker, &BackendWorker::ReceivedSensorFrame, Qt::QueuedConnection); - connect(m_communicator, &BackendCommunicator::SendedEmergencyStopInfo, - backendWorker, &BackendWorker::ReceivedEmergencyStopInfo, Qt::QueuedConnection); - connect(m_communicator, &BackendCommunicator::SendedFeedback, - backendWorker, &BackendWorker::ReceivedFeedback, Qt::QueuedConnection); - connect(m_communicator, &BackendCommunicator::SendedData, - backendWorker, &BackendWorker::ReceivedData, Qt::QueuedConnection); - connect(m_communicator, &BackendCommunicator::ReceivedFrontControl, this, &StateMachine::onReceivedFrontControl); connect(m_communicator, &BackendCommunicator::ReceivedModelConfig, this, &StateMachine::onReceivedModelConfig); - //m_config = config; - m_stageTimer = new QTimer(this); + connect(m_modbusBridge, &IModbusBridge::sensorsDataReady, + this, &StateMachine::onSensorsDataReady); + connect(m_modbusBridge, &IModbusBridge::configurationError, + this, &StateMachine::onModbusConfigurationError); + connect(m_modbusBridge, &IModbusBridge::connectionError, + this, &StateMachine::onModbusConnectionError); + connect(m_modbusBridge, &IModbusBridge::requestError, + this, &StateMachine::onModbusRequestError); + connect(m_modbusBridge, &IModbusBridge::connectionLost, + this, &StateMachine::onModbusConnectionLost); + connect(m_modbusBridge, &IModbusBridge::connectionRestored, + this, &StateMachine::onModbusConnectionRestored); + + connect(m_dataProcessor, &DataProcessor::decisionReady, + this, &StateMachine::onDecisionReady); + + connect(m_pollTimer, &QTimer::timeout, this, &StateMachine::pollModbus); } -void StateMachine::Run() +StateMachine::~StateMachine() { stop(); } + +void StateMachine::start(const ModelConfig& config) { - //Здесь должен быть запуск таймера, запуск Modbus клиента + m_config = config; + m_dataProcessor->onConfigChanged(config); + m_dataProcessor->reset(); + m_currentRunId = static_cast(QDateTime::currentSecsSinceEpoch()); + + int pollMs = ConfigData().LoadConfig().pollFrequencyMs; + m_pollTimer->start(pollMs); + + EventRecord startEvt; + startEvt.runId = m_currentRunId; + startEvt.timestampMs = QDateTime::currentMSecsSinceEpoch(); + startEvt.stage = -1; + startEvt.type = "start"; + startEvt.message = "Experiment started"; + m_dataStore->writeEvent(startEvt); + + transitionTo(DiagState::Ok); + m_currentStageIndex = 0; + m_modbusBridge->onReadSensors(); } -void StateMachine::Stop() +void StateMachine::stop() { - //Остановка всех таймеров + m_pollTimer->stop(); + m_stageTimer->stop(); + m_modbusBridge->stopPolling(); + + EventRecord stopEvt; + stopEvt.runId = m_currentRunId; + stopEvt.timestampMs = QDateTime::currentMSecsSinceEpoch(); + stopEvt.stage = m_currentStageIndex; + stopEvt.type = "stop"; + stopEvt.message = "Experiment stopped"; + m_dataStore->writeEvent(stopEvt); + + transitionTo(DiagState::IDLE); + emit finished(m_state); +} + +void StateMachine::requestNextStage() { /* ... как раньше ... */ } +void StateMachine::requestAbort(const QString& reason) { /* ... */ } + +// ---------- Слоты ---------- +void StateMachine::pollModbus() { + m_modbusBridge->onReadSensors(); } -StateMachine::~StateMachine() +void StateMachine::onSensorsDataReady(const SensorFrame& frame) { - // try{ - // delete m_stageTimer; - // } - // catch (const std::runtime_error& e) { - // // Перехватываем исключение - // std::cerr << "Ошибка: " << e.what() << std::endl; - // } + m_currentSensorFrame = frame; + m_currentSensorFrame.timestampMs = QDateTime::currentMSecsSinceEpoch(); + m_currentSensorFrame.stage = m_currentStageIndex; + + m_dataProcessor->processFrame(m_currentSensorFrame); + + // Отправка сырого кадра фронту через метод коммуникатора + m_communicator->SendSensorFrameToFrontend(m_currentSensorFrame); } + +void StateMachine::onDecisionReady(const Decision& decision) +{ + // Запись в хранилище + MeasurementRecord rec; + rec.runId = m_currentRunId; + rec.stage = m_currentSensorFrame.stage; + rec.timestampMs = m_currentSensorFrame.timestampMs; + rec.rpm = m_currentSensorFrame.rpm; + rec.torque = m_currentSensorFrame.torque; + rec.dieselTemp = m_currentSensorFrame.dieselTemp; + rec.motorTemp = m_currentSensorFrame.motorTemp; + rec.resistorTemp = 0; // маппинг уточнить + rec.dieselPressure = m_currentSensorFrame.dieselPressure; + rec.throttle = 0; // дополнить при необходимости + rec.brakeTorque = 0; + rec.flags = (decision.state == DiagState::Ok) ? "OK" : "WARNING"; + m_dataStore->writeRecord(rec); + + // Обработка смены состояния + if (decision.state != m_state) { + EventRecord evt; + evt.runId = m_currentRunId; + evt.timestampMs = QDateTime::currentMSecsSinceEpoch(); + evt.stage = m_currentStageIndex; + evt.type = "transition"; + evt.message = QString("State changed to %1").arg(static_cast(decision.state)); + m_dataStore->writeEvent(evt); + + applyControls(decision.controls); + transitionTo(decision.state); + + // Если аварийное состояние – уведомить фронт + if (decision.state >= DiagState::Alarm_RpmOverspeed) { + m_communicator->SendEmergencyStopInfoToFrontend(); + } + } + + // Отправка данных для фронта (вектор из одного элемента) + QVector dataVec; + Data data; + data.frame = m_currentSensorFrame; + data.state = decision.state; + dataVec.append(data); + m_communicator->SendDataToFrontend(dataVec); + + // Обратная связь: считается успешным, если состояние не аварийное + bool ok = (decision.state == DiagState::Ok || decision.state == DiagState::PreWarn_RpmHigh /* и т.п.*/); + m_communicator->SendFeedbackToFrontend(ok); +} + +void StateMachine::onReceivedFrontControl(const FrontControl& control) +{ + if (control.state == DiagState::Alarm_Generic) { + requestAbort("Front control emergency stop"); + } + // другая логика +} + +void StateMachine::onReceivedModelConfig(const ModelConfig& config) +{ + m_modbusBridge->onWriteConfig(config); + m_dataProcessor->onConfigChanged(config); +} + +void StateMachine::transitionTo(DiagState newState) { + if (m_state == newState) return; + m_state = newState; +} + +void StateMachine::applyControls(const QVector& controls) { + // Заглушка – реализовать после расширения IModbusBridge методами записи + for (const auto& ctrl : controls) Q_UNUSED(ctrl); +} + +// ---------- Ошибки Modbus ---------- +void StateMachine::onModbusConfigurationError(const QString& reason) { + // Логируем причину в хранилище + EventRecord evt; + evt.runId = m_currentRunId; + evt.timestampMs = QDateTime::currentMSecsSinceEpoch(); + evt.stage = m_currentStageIndex; + evt.type = "error"; + evt.message = "Modbus config error: " + reason; + m_dataStore->writeEvent(evt); + m_communicator->SendEmergencyStopInfoToFrontend(); +} + +void StateMachine::onModbusConnectionError(const QString& reason) { + // аналогично + m_communicator->SendEmergencyStopInfoToFrontend(); +} + +void StateMachine::onModbusRequestError(const QString& reason) { + m_communicator->SendEmergencyStopInfoToFrontend(); +} + +void StateMachine::onModbusConnectionLost() { + m_communicator->SendEmergencyStopInfoToFrontend(); + m_pollTimer->stop(); +} + +void StateMachine::onModbusConnectionRestored() { + if (m_state != DiagState::IDLE && m_state != DiagState::Alarm_Generic) + m_pollTimer->start(); +} \ No newline at end of file From f6d34396d61030eacf8a0a3e6c44fb1d5edae193 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Wed, 6 May 2026 23:45:03 +0500 Subject: [PATCH 27/58] chore: require SerialBus in the CMakeLists - also update files to address minor linking issues - also exclude unfinished state_machine.h and state_machine.cpp from CMakeLists for project to compile - also add QtModbusBridge to CMakeLists --- include/backend/state_machine/state_machine.h | 15 ++++---- src/backend/CMakeLists.txt | 35 ++++--------------- src/backend/backend_worker/backendworker.cpp | 4 +-- src/backend/modbus_client/QtModbusBridge.cpp | 8 ++--- 4 files changed, 21 insertions(+), 41 deletions(-) diff --git a/include/backend/state_machine/state_machine.h b/include/backend/state_machine/state_machine.h index 745863c..3dbdbdd 100644 --- a/include/backend/state_machine/state_machine.h +++ b/include/backend/state_machine/state_machine.h @@ -4,18 +4,19 @@ #include #include -#include "DataTypes.h" -#include "config_data.h" +#include "backend/DataTypes.h" +#include "backend/config_data/config_data.h" -#include "backend_worker/backendcommunicator.h" -#include "modbus_client/IModbusBridge.h" -#include "data_store/DataStore.h" -#include "DataProcessor.h" +#include "backend/backend_worker/backendcommunicator.h" +#include "backend/modbus_client/IModbusBridge.h" +#include "backend/data_store/DataStore.h" +#include "backend/data_processing/DataProcessor.h" class BackendWorker; class IModbusBridge; class DataStore; class DataProcessor; +class CSVConnector; class StateMachine : public QObject { @@ -24,7 +25,7 @@ class StateMachine : public QObject explicit StateMachine(BackendWorker* backendWorker, QObject* parent = nullptr); ~StateMachine(); - void start(const ModelConfig& config); + void start(); // const ModelConfig& config void stop(); void requestNextStage(); diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index a797519..d91992c 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -12,15 +12,9 @@ find_package(Qt6 REQUIRED COMPONENTS Core Network Test + SerialBus ) -# Modbus опционально -find_package(Qt6 COMPONENTS Modbus QUIET) -if(NOT Qt6Modbus_FOUND) - message(WARNING "Qt6 Modbus not found - QtModbusBridge will be excluded from build") - message(WARNING "Install: sudo apt install qt6-serial-dev (or equivalent)") -endif() - # Исходники set(BACKEND_SOURCES backend_worker/backendworker.cpp @@ -29,14 +23,11 @@ set(BACKEND_SOURCES data_processing/DataProcessor.cpp data_store/DataStore.cpp modbus_client/IModbusBridge.cpp + modbus_client/QtModbusBridge.cpp modbus_client/MockModbusBridge.cpp - state_machine/state_machine.cpp + # state_machine/state_machine.cpp ) -if(Qt6Modbus_FOUND) - list(APPEND BACKEND_SOURCES modbus_client/QtModbusBridge.cpp) -endif() - # Заголовки set(BACKEND_HEADERS ${PROJECT_SOURCE_DIR}/include/backend/backend_worker/backendworker.h @@ -46,8 +37,9 @@ set(BACKEND_HEADERS ${PROJECT_SOURCE_DIR}/include/backend/DataTypes.h ${PROJECT_SOURCE_DIR}/include/backend/config_data/config_data.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/IModbusBridge.h + ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/QtModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/MockModbusBridge.h - ${PROJECT_SOURCE_DIR}/include/backend/state_machine/state_machine.h + # ${PROJECT_SOURCE_DIR}/include/backend/state_machine/state_machine.h ) # Библиотека @@ -71,13 +63,9 @@ target_link_libraries(backend_lib Qt6::Core Qt6::Network Qt6::Test + Qt6::SerialBus ) -# Добавляем QtModbusBridge только если Modbus доступен -if(Qt6Modbus_FOUND) - target_link_libraries(backend_lib PUBLIC Qt6::Modbus) -endif() - # Группировка файлов для QtCreator # Это убирает попадание хедеров в source_group(TREE "${PROJECT_SOURCE_DIR}/include" PREFIX "include" FILES ${BACKEND_HEADERS}) @@ -99,13 +87,10 @@ macro(add_backend_test TEST_NAME) Qt6::Core Qt6::Test Qt6::Network + Qt6::SerialBus backend_lib ) - if(Qt6Modbus_FOUND) - target_link_libraries(${TEST_NAME} PRIVATE Qt6::Modbus) - endif() - add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) endmacro() @@ -140,10 +125,4 @@ message(STATUS "Backend Library: backend_lib") message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") message(STATUS "Qt6 Version: ${Qt6Core_VERSION}") -if(Qt6Modbus_FOUND) - message(STATUS "Qt6 Modbus: FOUND (QtModbusBridge enabled)") -else() - message(STATUS "Qt6 Modbus: NOT FOUND (using MockModbusBridge only)") -endif() - message(STATUS "Tests enabled") \ No newline at end of file diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp index 8ca1b81..84cb6cf 100644 --- a/src/backend/backend_worker/backendworker.cpp +++ b/src/backend/backend_worker/backendworker.cpp @@ -7,8 +7,8 @@ BackendWorker::BackendWorker(QObject* parent) : qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - connect(&m_machineThread, &QThread::started, m_machine, &StateMachine::Run, Qt::QueuedConnection); - connect(&m_machineThread, &QThread::finished, m_machine, &StateMachine::Stop, Qt::QueuedConnection); + connect(&m_machineThread, &QThread::started, m_machine, &StateMachine::start, Qt::QueuedConnection); + connect(&m_machineThread, &QThread::finished, m_machine, &StateMachine::stop, Qt::QueuedConnection); } void BackendWorker::Run() diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 2d3a6f6..64a52dd 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -54,19 +54,19 @@ QtModbusBridge::QtModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModb QString reason = m_client.errorString(); if (error == QModbusDevice::ConfigurationError) { - emit configurationError(error, reason); + emit configurationError(reason); } else if (error == QModbusDevice::ConnectionError) { - emit connectionError(error, reason); + emit connectionError(reason); } else if (error != QModbusDevice::UnknownError) { - emit requestError(error, reason); + emit requestError(reason); } else { - emit generalError(error, reason); + emit generalError(reason); } }); From b2c476c5b17e1fcc8b10b83336dcec0f8e2eefe7 Mon Sep 17 00:00:00 2001 From: Rikitick Date: Thu, 7 May 2026 03:49:12 +0500 Subject: [PATCH 28/58] change project configuration to 'dynamic library' --- include/backend/DataTypes.h | 2 + include/backend/data_store/DataStore.h | 219 +++++++++--------- include/backend/state_machine/state_machine.h | 3 +- src/backend/CMakeLists.txt | 6 +- src/backend/data_store/DataStore.cpp | 113 +++++++++ src/backend/state_machine/state_machine.cpp | 16 +- 6 files changed, 236 insertions(+), 123 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index e278855..2afa858 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -88,6 +88,8 @@ struct SensorFrame double rpm; //Метка времени в UNIX-формате qint64 timestampMs; + // Этап диагностики + int stage; }; Q_DECLARE_METATYPE(SensorFrame) diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index 9533222..a933fb4 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -1,122 +1,125 @@ -// #pragma once +#pragma once -// #include -// #include +#include +#include +#include +#include -// // Qt includes -// #include -// #include -// #include -// // #include +// Qt includes +#include +#include +#include +#include // // #include "../DataTypes.h" // MeasurementRecord, EventRecord, Report -// // Low-level layer -// class Connector -// { -// public: -// explicit Connector(std::string path); // : path_(std::move(path)) {} -// virtual ~Connector() noexcept = default; +// Low-level layer +class Connector +{ +public: + explicit Connector(std::string path); + virtual ~Connector() noexcept = default; -// virtual int64_t write(const std::string &data) = 0; -// virtual std::optional read(int64_t id) const = 0; -// virtual bool update(int64_t id, const std::string &data) = 0; -// virtual bool remove(int64_t id) = 0; + virtual int64_t write(const std::string &data) = 0; + virtual std::optional read(int64_t id) const = 0; + virtual bool update(int64_t id, const std::string &data) = 0; + virtual bool remove(int64_t id) = 0; -// size_t size() const noexcept; -// const std::string &path() const noexcept { return path_; } + size_t size() const noexcept; + const std::string &path() const noexcept { return path_; } -// private: -// std::string path_; +private: + std::string path_; +}; // size_t size_; // }; -// class CSVConnector : public Connector -// { -// public: -// explicit CSVConnector(std::string path); // : Connector(std::move(path)) {} -// ~CSVConnector() noexcept override = default; - -// int64_t write(const std::string &data) override; -// std::optional read(int64_t id) const override; -// bool update(int64_t id, const std::string &data) override; -// bool remove(int64_t id) override; -// }; - -// // Qt layer -// struct MeasurementRecord -// { -// qint64 runId; -// int stage; -// qint64 timestampMs; -// double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; -// double throttle, brakeTorque; -// QString flags; // "OK", "WARNING" и т.п. -// }; - -// struct EventRecord -// { -// qint64 runId, timestampMs; -// int stage; -// QString type; // "stage_change", "abort", "warning", "info" -// QString message; -// }; - -// struct Report -// { -// qint64 runId; -// QString startTime, endTime; -// QString finalStatus; // "completed" или "aborted" -// QVector stages; -// QVector events; -// }; - -// enum class StreamType -// { -// Measurement, -// Event -// }; - -// inline uint qHash(StreamType key, uint seed = 0) noexcept -// { -// return qHash(static_cast(key), seed); -// } - -// struct StorageConnector -// { -// Connector *connector; -// qint64 nextId = 1; -// bool headersWritten = false; -// }; - -// class DataStore : public QObject -// { -// Q_OBJECT - -// public: -// DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent = nullptr); -// // В случае ошибки возвращаю пустые объекты? -// QVector readRecords(qint64 runId) const; -// QVector readEvents(qint64 runId) const; -// Report buildReport(qint64 runId) const; - -// public slots: -// void writeRecord(const MeasurementRecord &record); -// void writeEvent(const EventRecord &event); - -// private: -// QString serializeMeasurement(const MeasurementRecord &record) const; -// QString serializeEvent(const EventRecord &event) const; - -// MeasurementRecord deserializeMeasurement(const QString &line) const; -// EventRecord deserializeEvent(const QString &line) const; - -// void ensureHeaders(StreamType type); -// qint64 generateId(StreamType type) noexcept; - -// private: -// QHash connectors_; -// }; +class CSVConnector : public Connector +{ +public: + explicit CSVConnector(std::string path); // : Connector(std::move(path)) {} + ~CSVConnector() noexcept override = default; + + int64_t write(const std::string &data) override; + std::optional read(int64_t id) const override; + bool update(int64_t id, const std::string &data) override; + bool remove(int64_t id) override; +}; + +// Qt layer +struct MeasurementRecord +{ + qint64 runId; + int stage; + qint64 timestampMs; + double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; + double throttle, brakeTorque; + QString flags; // "OK", "WARNING" и т.п. +}; + +struct EventRecord +{ + qint64 runId, timestampMs; + int stage; + QString type; // "stage_change", "abort", "warning", "info" + QString message; +}; + +struct Report +{ + qint64 runId; + QString startTime, endTime; + QString finalStatus; // "completed" или "aborted" + // QVector stages; + QVector events; +}; + +enum class StreamType +{ + Measurement, + Event +}; + +inline uint qHash(StreamType key, uint seed = 0) noexcept +{ + return qHash(static_cast(key), seed); +} + +struct StorageConnector +{ + Connector *connector; + qint64 nextId = 1; + bool headersWritten = false; +}; + +class DataStore : public QObject +{ + Q_OBJECT + +public: + DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent = nullptr); + // В случае ошибки возвращаю пустые объекты? + QVector readRecords(qint64 runId) const; + QVector readEvents(qint64 runId) const; + Report buildReport(qint64 runId) const; + +public slots: + void writeRecord(const MeasurementRecord &record); + void writeEvent(const EventRecord &event); + +private: + QString serializeMeasurement(const MeasurementRecord &record) const; + QString serializeEvent(const EventRecord &event) const; + + MeasurementRecord deserializeMeasurement(const QString &line) const; + EventRecord deserializeEvent(const QString &line) const; + + void ensureHeaders(StreamType type); + qint64 generateId(StreamType type) noexcept; + +private: + QHash connectors_; +}; // // Выносить ли структуры в DataTypes? // // Нужно ли использовать исключения? (иначе работать с bool/константами, std::nullopt, возвращать пустые объекты?) diff --git a/include/backend/state_machine/state_machine.h b/include/backend/state_machine/state_machine.h index 3dbdbdd..60813ed 100644 --- a/include/backend/state_machine/state_machine.h +++ b/include/backend/state_machine/state_machine.h @@ -9,7 +9,6 @@ #include "backend/backend_worker/backendcommunicator.h" #include "backend/modbus_client/IModbusBridge.h" -#include "backend/data_store/DataStore.h" #include "backend/data_processing/DataProcessor.h" class BackendWorker; @@ -25,7 +24,7 @@ class StateMachine : public QObject explicit StateMachine(BackendWorker* backendWorker, QObject* parent = nullptr); ~StateMachine(); - void start(); // const ModelConfig& config + void start(); void stop(); void requestNextStage(); diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index d91992c..663d372 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -25,7 +25,7 @@ set(BACKEND_SOURCES modbus_client/IModbusBridge.cpp modbus_client/QtModbusBridge.cpp modbus_client/MockModbusBridge.cpp - # state_machine/state_machine.cpp + state_machine/state_machine.cpp ) # Заголовки @@ -39,11 +39,11 @@ set(BACKEND_HEADERS ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/IModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/QtModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/MockModbusBridge.h - # ${PROJECT_SOURCE_DIR}/include/backend/state_machine/state_machine.h + ${PROJECT_SOURCE_DIR}/include/backend/state_machine/state_machine.h ) # Библиотека -add_library(backend_lib STATIC) +add_library(backend_lib SHARED) target_sources(backend_lib PRIVATE diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index e69de29..3b21a1f 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -0,0 +1,113 @@ +#include "backend/data_store/DataStore.h" +#include +#include +#include + +// Connector implementation +Connector::Connector(std::string path) : path_(std::move(path)) {} + +size_t Connector::size() const noexcept { + QFile file(QString::fromStdString(path_)); + return file.size(); +} + +// CSVConnector implementation +CSVConnector::CSVConnector(std::string path) : Connector(std::move(path)) {} + +int64_t CSVConnector::write(const std::string &data) { + QFile file(QString::fromStdString(path())); + if (!file.open(QIODevice::Append | QIODevice::Text)) { + qWarning() << "Cannot open file for writing:" << file.fileName(); + return -1; + } + QTextStream out(&file); + out << QString::fromStdString(data) << "\n"; + return file.size(); +} + +std::optional CSVConnector::read(int64_t id) const { + QFile file(QString::fromStdString(path())); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Cannot open file for reading:" << file.fileName(); + return std::nullopt; + } + QTextStream in(&file); + QString line; + int currentId = 1; + while (in.readLineInto(&line)) { + if (currentId == id) { + return line.toStdString(); + } + ++currentId; + } + return std::nullopt; +} + +bool CSVConnector::update(int64_t id, const std::string &data) { + // Stub: not implemented + return false; +} + +bool CSVConnector::remove(int64_t id) { + // Stub: not implemented + return false; +} + +// DataStore implementation +DataStore::DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent) + : QObject(parent) { + connectors_[StreamType::Measurement] = {measurementConnector}; + connectors_[StreamType::Event] = {eventConnector}; +} + +QVector DataStore::readRecords(qint64 runId) const { + // Stub + return {}; +} + +QVector DataStore::readEvents(qint64 runId) const { + // Stub + return {}; +} + +Report DataStore::buildReport(qint64 runId) const { + // Stub + return {}; +} + +void DataStore::writeRecord(const MeasurementRecord &record) { + // Stub +} + +void DataStore::writeEvent(const EventRecord &event) { + // Stub +} + +QString DataStore::serializeMeasurement(const MeasurementRecord &record) const { + // Stub + return {}; +} + +QString DataStore::serializeEvent(const EventRecord &event) const { + // Stub + return {}; +} + +MeasurementRecord DataStore::deserializeMeasurement(const QString &line) const { + // Stub + return {}; +} + +EventRecord DataStore::deserializeEvent(const QString &line) const { + // Stub + return {}; +} + +void DataStore::ensureHeaders(StreamType type) { + // Stub +} + +qint64 DataStore::generateId(StreamType type) noexcept { + // Stub + return 1; +} diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 31399f5..273371a 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,13 +1,8 @@ -<<<<<<< HEAD -#include "../../../include/backend/state_machine.h" +#include "../../../include/backend/state_machine/state_machine.h" #include "../../../include/backend/backend_worker/backendworker.h" #include "../../../include/backend/modbus_client/MockModbusBridge.h" +#include "../../../include/backend/data_store/DataStore.h" #include -======= -#include "backend/state_machine/state_machine.h" -#include "backend/backend_worker/backendworker.h" -#include "backend/modbus_client/MockModbusBridge.h" ->>>>>>> 98c4a26adcf739d28649159416e60759df1accb4 StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) : QObject(parent) @@ -59,10 +54,10 @@ StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) StateMachine::~StateMachine() { stop(); } -void StateMachine::start(const ModelConfig& config) +void StateMachine::start() { - m_config = config; - m_dataProcessor->onConfigChanged(config); + // m_config = config; + m_dataProcessor->onConfigChanged(m_config); m_dataProcessor->reset(); m_currentRunId = static_cast(QDateTime::currentSecsSinceEpoch()); @@ -180,6 +175,7 @@ void StateMachine::onReceivedFrontControl(const FrontControl& control) void StateMachine::onReceivedModelConfig(const ModelConfig& config) { + m_config = config; m_modbusBridge->onWriteConfig(config); m_dataProcessor->onConfigChanged(config); } From 3ef59e2308e1340817185dfe7184c28f5d1355c7 Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Thu, 7 May 2026 20:03:16 +0500 Subject: [PATCH 29/58] Update DataProcessor.h --- .../backend/data_processing/DataProcessor.h | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/include/backend/data_processing/DataProcessor.h b/include/backend/data_processing/DataProcessor.h index f499c08..408c6ee 100644 --- a/include/backend/data_processing/DataProcessor.h +++ b/include/backend/data_processing/DataProcessor.h @@ -1,43 +1,44 @@ #ifndef DATAPROCESSOR_H #define DATAPROCESSOR_H + #include #include -#include "backend/DataTypes.h" +#include "DataTypes.h" + /** * Модуль обработки данных. * - * Принимает SensorFrame от StateMachine, сравнивает значения с порогами - * из ModelConfig (с учётом текущего этапа) и возвращает Decision — - * вектор управляющих воздействий + диагностическое состояние модели - * версия 2 - не финальная + * Принимает SensorFrame от StateMachine, сравнивает с порогами из ModelConfig + * с учётом текущего этапа и ВОЗВРАЩАЕТ Decision (вектор управляющих воздействий + * + диагностическое состояние модели + причина). + * версия 3 + * + * + * */ class DataProcessor : public QObject { Q_OBJECT public: explicit DataProcessor(const ModelConfig &config, QObject *parent = nullptr); - + Decision processFrame(const SensorFrame &frame); public slots: - void processFrame(SensorFrame frame); - // уведомление об актуальном этапе испытания (для учёта режимов) (согласовать) + // уведомление об актуальном этапе (для исключения ложных срабатываний) void onStageChanged(int newStage); - // обновление конфигурации + // обновление конфигурации (порогов) void onConfigChanged(ModelConfig config); // сброс защёлки аварии и внутреннего состояния (новый прогон) void reset(); -signals: - // шотовое решение для StateMachine. - void decisionReady(Decision decision); private: - // активна ли проверка параметра на текущем этапе??? (исключение ложных срабатываний) + // активна ли проверка параметра на текущем этапе??? bool isParamActiveOnStage(const QString ¶m, int stage) const; - // Сформировать аварийную команду останова + // аварийная команда останова static QVector makeStopControls(); private: ModelConfig m_config; int m_currentStage = 0; - bool m_alarmLatched = false; // после аварии решения не формируем + bool m_alarmLatched = false; }; -#endif // DATAPROCESSOR_H \ No newline at end of file +#endif // DATAPROCESSOR_H From 7fbdc194e7aa180ecb48ef5f3b2bad172b80e071 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Thu, 7 May 2026 20:04:08 +0500 Subject: [PATCH 30/58] chore: update register mappings according to the model's register bank --- include/backend/DataTypes.h | 79 ++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 2afa858..fb0ed67 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -8,6 +8,7 @@ // Control registers mapping, read/write, 1-bit struct CoilsRegistersScheme { + bool fanICE; bool fanMotor; bool fanBall; }; @@ -15,42 +16,72 @@ struct CoilsRegistersScheme // Device status registers mapping, only read, 1-bit struct DiscreteRegistersScheme { - bool fanMotor; - bool fanBall; + bool hasFault; + bool hasLimitViolations; }; // Settings and variables registers mapping, read/write, 16-bit struct HoldingRegistersScheme { - //Максимальная температура ДВС - double maxDieselTemp; - //Максимальная температура АД - double maxMotorTemp; - //Максимальная температура балластных резисторов - double maxResistorTemp; - //Максимальное давление в ДВС - double maxDieselPressure; - //Минимальное давление в ДВС + // Максимально допустимая температура охлаждающей жидкости + double maxCoolantTemp; + // Минимально допустимое давление масла double minDieselPressure; - //Общая частота оборотов (можно расширить) - int maxRpm; + // Максимально допустимое давление масла + double maxDieselPressure; + // Максимально допустимая частота вращения ДВС в режиме притирки + int maxIceRpmPrir; + // Максимально допустимая частота вращения ДВС в режиме обкатки + int maxIceRpmRun; + // Максимально допустимая температура АД + double maxMotorTemp; + // Максимально допустимая температура балластных резисторов + double maxBallastTemp; + // Входная частота для АД / задание частоты АД + double motorFrequencyInput; + // Целевая механическая нагрузка / момент АД + double targetMotorTorque; + // Версия (ревизия) настроек holding-регистров + int holdingRevision; + // Команда на симуляцию + uint16_t simulationCommand; + // Запрос на симуляцию + uint16_t simulationRequest; + // Режим симуляции + uint16_t simulationMode; }; // Sensor data registers mapping, only read, 16-bit struct InputRegistersScheme { - //Температура ДВС - double dieselTemp; - //Температура АД + // Температура охлаждающей жидкости + double coolantTemp; + // Давление масла + double oilPressure; + // Частота вращения ДВС в режиме притирки + int iceRpmPrir; + // Частота вращения ДВС в режиме обкатки + int iceRpmRun; + // Температура АД double motorTemp; - //Температура балластных резисторов - double resistorTemp; - //Давление - double dieselPressure; - //Момент - double torque; - //Частота вращения - double rpm; + // Температура балластных резисторов + double ballastTemp; + // Момент АД + double motorTorque; + // Частота АД + double motorFrequency; + // Ревизия входных данных / версия источника + int revision; + // Ревизия источника входных данных + int sourceInputRevision; + // Время модели + uint32_t modelTime; + // Временная метка регистра Input Registers + uint32_t inputRegistersTimestamp; + // Состояние Input Registers + uint16_t inputRegistersState; + // Код неисправности + uint16_t faultCode; }; //Структра для задания конфигурации Modbus-клиента From 65057002e441fe2637d5b853240a07d87ef65b7d Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Thu, 7 May 2026 20:04:20 +0500 Subject: [PATCH 31/58] Update DataProcessor.cpp --- src/backend/data_processing/DataProcessor.cpp | 86 +++++++++---------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 1c3b582..7f0b010 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,5 +1,4 @@ -#include "backend/data_processing/DataProcessor.h" -// этапы — должны совпадать с StateMachine (согласовать) +#include "DataProcessor.h" namespace Stage { constexpr int IDLE = 0; @@ -10,7 +9,6 @@ namespace Stage constexpr int COMPLETED = 5; constexpr int ABORTED = 6; } -// доля от критического порога, при которой выдаём предаварийное состояние (тоже согласовать) static constexpr double WARN_RATIO = 0.9; DataProcessor::DataProcessor(const ModelConfig &config, QObject *parent) : QObject(parent), m_config(config) @@ -29,28 +27,29 @@ void DataProcessor::reset() m_alarmLatched = false; m_currentStage = Stage::IDLE; } -void DataProcessor::processFrame(SensorFrame frame) +Decision DataProcessor::processFrame(const SensorFrame &frame) { - // после аварии решения больше не формируем + Decision decision; + // после аварии — пустое решение if (m_alarmLatched) { - return; + decision.state = DiagState::Alarm_Generic; + decision.reason = QStringLiteral("alarm latched"); + return decision; } - // в неактивных режимах проверки нет + // неактивные режимы — проверки не выполняем if (m_currentStage == Stage::IDLE || m_currentStage == Stage::COMPLETED || m_currentStage == Stage::ABORTED) { - Decision d; - d.state = DiagState::Ok; - d.reason = QStringLiteral("inactive stage"); - emit decisionReady(d); - return; + decision.state = DiagState::Ok; + decision.reason = QStringLiteral("inactive stage"); + return decision; } - // будем выбирать наихудшее состояние (приоритет — авария) + // выбор наихудшего состояния (приоритет — авария) DiagState worstState = DiagState::Ok; QString worstReason; - int worstSeverity = 0; // 0 = ok, 1 = пред alarm, 2 = alarm + int worstSeverity = 0; // 0=ok, 1=пре alarm, 2=alarm auto promote = [&](int severity, DiagState st, const QString &rsn) { if (severity > worstSeverity) @@ -73,8 +72,7 @@ void DataProcessor::processFrame(SensorFrame frame) else if (frame.rpm >= m_config.maxRpm * WARN_RATIO) { promote(1, DiagState::PreWarn_RpmHigh, - QStringLiteral("Обороты приближаются к пределу: %1") - .arg(frame.rpm)); + QStringLiteral("Обороты приближаются к пределу: %1").arg(frame.rpm)); } } // температура ДВС @@ -106,19 +104,19 @@ void DataProcessor::processFrame(SensorFrame frame) } } // балансировочный резистор - if (isParamActiveOnStage("resistorTemp", m_currentStage)) + if (isParamActiveOnStage("resistorBalance", m_currentStage)) { - if (frame.resistorTemp >= m_config.maxResistorTemp) + if (frame.resistorBalance >= m_config.maxResistorBalance) { promote(2, DiagState::Alarm_ResistorOverheat, QStringLiteral("Превышение по балансировочному резистору: %1") - .arg(frame.resistorTemp)); + .arg(frame.resistorBalance)); } - else if (frame.resistorTemp >= m_config.maxResistorTemp * WARN_RATIO) + else if (frame.resistorBalance >= m_config.maxResistorBalance * WARN_RATIO) { promote(1, DiagState::PreWarn_ResistorHigh, QStringLiteral("Балансировочный резистор близок к пределу: %1") - .arg(frame.resistorTemp)); + .arg(frame.resistorBalance)); } } // давление ДВС — верхняя граница @@ -127,8 +125,7 @@ void DataProcessor::processFrame(SensorFrame frame) if (frame.dieselPressure >= m_config.maxDieselPressure) { promote(2, DiagState::Alarm_PressureOver, - QStringLiteral("Превышение давления ДВС: %1") - .arg(frame.dieselPressure)); + QStringLiteral("Превышение давления ДВС: %1").arg(frame.dieselPressure)); } else if (frame.dieselPressure >= m_config.maxDieselPressure * WARN_RATIO) { @@ -144,8 +141,7 @@ void DataProcessor::processFrame(SensorFrame frame) if (frame.dieselPressure <= m_config.minDieselPressure) { promote(2, DiagState::Alarm_PressureUnder, - QStringLiteral("Падение давления ДВС: %1") - .arg(frame.dieselPressure)); + QStringLiteral("Падение давления ДВС: %1").arg(frame.dieselPressure)); } else if (frame.dieselPressure <= warnLow) { @@ -154,34 +150,34 @@ void DataProcessor::processFrame(SensorFrame frame) .arg(frame.dieselPressure)); } } - Decision decision; decision.state = worstState; decision.reason = worstReason; if (worstSeverity == 2) { - // АВАРИЯ + // Авария m_alarmLatched = true; decision.controls = makeStopControls(); - emit decisionReady(decision); - return; + return decision; } if (worstSeverity == 1) { - // предаварийное состояние — корректирующее воздействие + // Предаварийное состояние ModelControl mc; switch (worstState) { case DiagState::PreWarn_RpmHigh: - mc.type = ControlType::Throttle; - mc.value = 0.0; // прикрыть дроссель (значение согласовать) + // Снизить уставку дросселя (0…1). + mc.type = ControlType::ThrottleSetpoint; + mc.value = m_config.throttleReduceTo; decision.controls.append(mc); break; case DiagState::PreWarn_ResistorHigh: - mc.type = ControlType::BrakeTorque; - mc.value = 0.0; // снизить тормозной момент + // Снизить уставку тормозного момента АД (Н·м). + mc.type = ControlType::BrakeTorqueSetpoint; + mc.value = m_config.brakeReduceTo; decision.controls.append(mc); break; - // Перегревы и давление пока не доделаны + // Перегревы и давление сами case DiagState::PreWarn_DieselTempHigh: case DiagState::PreWarn_MotorTempHigh: case DiagState::PreWarn_PressureHigh: @@ -190,7 +186,7 @@ void DataProcessor::processFrame(SensorFrame frame) break; } } - emit decisionReady(decision); + return decision; } bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const { @@ -200,7 +196,6 @@ bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const { return false; } - // давление и температура ДВС — после запуска if (param == "dieselPressureMin" || param == "dieselPressureMax" || param == "dieselTemp") @@ -209,22 +204,19 @@ bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const stage == Stage::HOT_NO_LOAD || stage == Stage::HOT_WITH_LOAD); } - // балансировочный резистор — на этапе с нагрузкой - if (param == "resistorTemp") + if (param == "resistorBalance") { return (stage == Stage::HOT_WITH_LOAD); } - - // rpm и motorTemp — на всех активных этапах. + // rpm и motorTemp — на всех активных этапах return true; } QVector DataProcessor::makeStopControls() { + // Аварийный стоп + // Команды режима/опроса при аварии не отправляем QVector v; - v.append({ControlType::EmergencyStop, 1.0}); - v.append({ControlType::MotorEnable, 0.0}); - v.append({ControlType::Throttle, 0.0}); - v.append({ControlType::BrakeTorque, 0.0}); - v.append({ControlType::TargetRpm, 0.0}); + v.append({ControlType::ThrottleSetpoint, 0.0}); + v.append({ControlType::BrakeTorqueSetpoint, 0.0}); return v; -} \ No newline at end of file +} From 43d037b4632406df8a7a19a18dbd0fae65e97b8d Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Fri, 8 May 2026 01:26:54 +0500 Subject: [PATCH 32/58] feat: implement read operations for modbus client --- include/backend/DataTypes.h | 135 +++++---- .../backend/modbus_client/MockModbusBridge.h | 8 +- .../backend/modbus_client/QtModbusBridge.h | 18 +- src/backend/data_processing/DataProcessor.cpp | 6 +- .../modbus_client/MockModbusBridge.cpp | 22 +- src/backend/modbus_client/QtModbusBridge.cpp | 286 +++++++++--------- 6 files changed, 231 insertions(+), 244 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index fb0ed67..ce0f807 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -5,83 +5,91 @@ #include #include #include -// Control registers mapping, read/write, 1-bit -struct CoilsRegistersScheme +// InputRegisters offset (sensors data, only read operations) +namespace InputRegisters { - bool fanICE; - bool fanMotor; - bool fanBall; -}; - -// Device status registers mapping, only read, 1-bit -struct DiscreteRegistersScheme -{ - bool hasFault; - bool hasLimitViolations; + static constexpr qsizetype size = 50; + static constexpr uint16_t count = 14; + // Температура охлаждающей жидкости + static constexpr uint16_t T_cool = 0; + // Давление масла + static constexpr uint16_t P_oil = 4; + // Частота вращения ДВС в режиме притирки + static constexpr uint16_t omega_ICE_prir = 8; + // Частота вращения ДВС в режиме обкатки + static constexpr uint16_t omega_ICE_run = 12; + // Температура АД + static constexpr uint16_t T_AD = 16; + // Температура балластных резисторов + static constexpr uint16_t T_ballast = 20; + // Момент АД + static constexpr uint16_t M_AD = 24; + // Частота АД + static constexpr uint16_t f_AD = 28; + // Ревизия входных данных / версия источника + static constexpr uint16_t revision = 32; + // Ревизия источника входных данных + static constexpr uint16_t sourceInputRevision = 36; + // Время модели + static constexpr uint16_t modelTime = 40; + // Временная метка регистра Input Registers + static constexpr uint16_t timestamp_ir = 44; + // Состояние Input Registers + static constexpr uint16_t state_ir = 48; + // Код неисправности + static constexpr uint16_t faultCode = 49; }; -// Settings and variables registers mapping, read/write, 16-bit -struct HoldingRegistersScheme +// HoldingRegisters offset (settings and variables, read/write operations) +namespace HoldingRegisters { + static constexpr qsizetype size = 43; + static constexpr uint16_t count = 13; // Максимально допустимая температура охлаждающей жидкости - double maxCoolantTemp; + static constexpr uint16_t T_cool_max = 0; // Минимально допустимое давление масла - double minDieselPressure; + static constexpr uint16_t P_oil_min = 4; // Максимально допустимое давление масла - double maxDieselPressure; + static constexpr uint16_t P_oil_max = 8; // Максимально допустимая частота вращения ДВС в режиме притирки - int maxIceRpmPrir; + static constexpr uint16_t omega_ICE_max_prir = 12; // Максимально допустимая частота вращения ДВС в режиме обкатки - int maxIceRpmRun; + static constexpr uint16_t omega_ICE_max_run = 16; // Максимально допустимая температура АД - double maxMotorTemp; + static constexpr uint16_t T_AD_max = 20; // Максимально допустимая температура балластных резисторов - double maxBallastTemp; + static constexpr uint16_t T_ballast_max = 24; // Входная частота для АД / задание частоты АД - double motorFrequencyInput; - // Целевая механическая нагрузка / момент АД - double targetMotorTorque; + static constexpr uint16_t f_AD_Input = 28; + // Целевая механическая нагрузка / момент АД + static constexpr uint16_t M_AD_target = 32; // Версия (ревизия) настроек holding-регистров - int holdingRevision; + static constexpr uint16_t revision_h = 36; // Команда на симуляцию - uint16_t simulationCommand; + static constexpr uint16_t simulationCommand = 40; // Запрос на симуляцию - uint16_t simulationRequest; + static constexpr uint16_t simulationRequest = 41; // Режим симуляции - uint16_t simulationMode; + static constexpr uint16_t simulationMode = 42; }; -// Sensor data registers mapping, only read, 16-bit -struct InputRegistersScheme +// Coils offset (control registers, read/write operations) +namespace CoilsRegisters { - // Температура охлаждающей жидкости - double coolantTemp; - // Давление масла - double oilPressure; - // Частота вращения ДВС в режиме притирки - int iceRpmPrir; - // Частота вращения ДВС в режиме обкатки - int iceRpmRun; - // Температура АД - double motorTemp; - // Температура балластных резисторов - double ballastTemp; - // Момент АД - double motorTorque; - // Частота АД - double motorFrequency; - // Ревизия входных данных / версия источника - int revision; - // Ревизия источника входных данных - int sourceInputRevision; - // Время модели - uint32_t modelTime; - // Временная метка регистра Input Registers - uint32_t inputRegistersTimestamp; - // Состояние Input Registers - uint16_t inputRegistersState; - // Код неисправности - uint16_t faultCode; + static constexpr qsizetype size = 3; + static constexpr uint16_t count = 3; + static constexpr uint16_t fan_ICE = 0; + static constexpr uint16_t fan_AD = 1; + static constexpr uint16_t fan_ballast = 2; +}; + +// Discrete Inputs offset (device status, only read operations) +namespace DiscreteRegisters +{ + static constexpr qsizetype size = 2; + static constexpr uint16_t count = 2; + static constexpr uint16_t hasFault = 0; + static constexpr uint16_t hasLimitViolations = 1; }; //Структра для задания конфигурации Modbus-клиента @@ -95,11 +103,6 @@ struct ModbusConfig int timeoutMs = 1000; int retries = 3; int unitId = 1; // we assume one modbus device, so unitId will be ignored - // Start address is 0 for each register type - CoilsRegistersScheme coils; - DiscreteRegistersScheme discrete; - InputRegistersScheme input; - HoldingRegistersScheme holding; }; //Снимок датчиков @@ -137,8 +140,10 @@ struct ModelConfig double maxDieselPressure; //Минимальное давление в ДВС double minDieselPressure; - //Общая частота оборотов (можно расширить) - int maxRpm; + //Общая частота оборотов в режиме притирки + int maxRpmPrir; + //Общая частота оборотов в режиме обкатки + int maxRpmRun; }; Q_DECLARE_METATYPE(ModelConfig) diff --git a/include/backend/modbus_client/MockModbusBridge.h b/include/backend/modbus_client/MockModbusBridge.h index 1c3ba9f..420d144 100644 --- a/include/backend/modbus_client/MockModbusBridge.h +++ b/include/backend/modbus_client/MockModbusBridge.h @@ -1,11 +1,8 @@ #pragma once #include "IModbusBridge.h" +#include "backend/DataTypes.h" #include -class ModbusConfig; -class ModelConfig; -class Decision; - class MockModbusBridge : public IModbusBridge { Q_OBJECT @@ -18,9 +15,6 @@ public slots: void onReadInfo() override; void onWriteConfig(const ModelConfig& cmd) override; void onWriteDecision(const Decision& decision) override; -private slots: - void requestSensors(); - void requestInfo(); private: ModbusConfig m_cfg; QTimer m_pollTimer; diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index b3e3c50..e705ab3 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -1,13 +1,10 @@ #pragma once #include "IModbusBridge.h" +#include "backend/DataTypes.h" #include #include #include -class ModbusConfig; -class ModelConfig; -class Decision; - class QtModbusBridge : public IModbusBridge { Q_OBJECT @@ -22,13 +19,16 @@ public slots: void onWriteConfig(const ModelConfig& cmd) override; void onWriteDecision(const Decision& decision) override; private slots: - void parseSensorsResponse(QModbusReply* reply); - void requestSensors(); - void parseInfoResponse(QModbusReply* reply); - void requestInfo(); + void onStateChange(QModbusDevice::State state); + void onErrorOccured(); +private: + QModbusReply* sendReadRequest(const QModbusDataUnit& dataUnit); + QModbusReply* sendWriteRequest(const QModbusDataUnit& dataUnit); + void parseSensorsResponse(const QVector& values); + void parseInfoResponse(const QVector& values); + std::optional> extractValues(QModbusReply* reply, qsizetype expectedSize); private: ModbusConfig m_cfg; - QModbusDataUnit m_dataUnit; QModbusTcpClient m_client; QTimer m_pollTimer; QModbusDevice::State m_prevState = QModbusDevice::UnconnectedState; diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 1c3b582..43d3bbb 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -63,14 +63,14 @@ void DataProcessor::processFrame(SensorFrame frame) // обороты if (isParamActiveOnStage("rpm", m_currentStage)) { - if (frame.rpm >= m_config.maxRpm) + if (frame.rpm >= m_config.maxRpmPrir) { promote(2, DiagState::Alarm_RpmOverspeed, QStringLiteral("Превышение оборотов: %1 >= %2") .arg(frame.rpm) - .arg(m_config.maxRpm)); + .arg(m_config.maxRpmPrir)); } - else if (frame.rpm >= m_config.maxRpm * WARN_RATIO) + else if (frame.rpm >= m_config.maxRpmPrir * WARN_RATIO) { promote(1, DiagState::PreWarn_RpmHigh, QStringLiteral("Обороты приближаются к пределу: %1") diff --git a/src/backend/modbus_client/MockModbusBridge.cpp b/src/backend/modbus_client/MockModbusBridge.cpp index dd54771..4ff1201 100644 --- a/src/backend/modbus_client/MockModbusBridge.cpp +++ b/src/backend/modbus_client/MockModbusBridge.cpp @@ -7,7 +7,7 @@ MockModbusBridge::MockModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModbusBridge(cfg, parent) { m_cfg = cfg; - QObject::connect(&m_pollTimer, &QTimer::timeout, this, &MockModbusBridge::requestSensors); + QObject::connect(&m_pollTimer, &QTimer::timeout, this, &MockModbusBridge::onReadSensors); qInfo() << "[MockModbusBridge] Initialization is successfull"; QThread::msleep(1000); qInfo() << "[MockModbusBridge] Connected to the Modbus Server"; @@ -27,14 +27,14 @@ void MockModbusBridge::stopPolling() void MockModbusBridge::onReadSensors() { - qInfo() << "[MockModbusBridge] Core is directly requesting to read sensor data"; - requestSensors(); + qInfo() << "[MockModbusBridge] Requesting data"; + SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1 }; + emit sensorsDataReady(frame); } void MockModbusBridge::onReadInfo() { - qInfo() << "[MockModbusBridge] Core is directly requesting to read the model info"; - requestInfo(); + qInfo() << "[MockModbusBridge] Requesting model info"; } void MockModbusBridge::onWriteConfig(const ModelConfig& cmd) @@ -46,15 +46,3 @@ void MockModbusBridge::onWriteDecision(const Decision& decision) { qInfo() << "[MockModbusBridge] Sending a decision to the model"; } - -void MockModbusBridge::requestSensors() -{ - qInfo() << "[MockModbusBridge] Requesting data"; - SensorFrame frame = { 1, 1, 1, 1, 1, 1, 1 }; - emit sensorsDataReady(frame); -} - -void MockModbusBridge::requestInfo() -{ - qInfo() << "[MockModbusBridge] Requesting model info"; -} diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 64a52dd..c15d795 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -4,7 +4,6 @@ #include #include #include -#include QtModbusBridge::QtModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModbusBridge(cfg, parent) { @@ -15,62 +14,11 @@ QtModbusBridge::QtModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModb m_client.setTimeout(m_cfg.timeoutMs); m_client.setNumberOfRetries(m_cfg.retries); - QObject::connect(&m_pollTimer, &QTimer::timeout, this, &QtModbusBridge::requestSensors); - - QObject::connect(&m_client, &QModbusDevice::stateChanged, this, [this](QModbusDevice::State state) - { - // Handling states change - if (state == QModbusDevice::UnconnectedState) - { - qDebug() << "[QtModbusBridge] Modbus client and server are disconnected"; - } - else if (state == QModbusDevice::ConnectingState) - { - qDebug() << "[QtModbusBridge] Connecting to the modbus device..."; - } - else if (state == QModbusDevice::ConnectedState) - { - qDebug() << "[QtModbusBridge] Connected to the modbus server succesfully"; - } - else if (state == QModbusDevice::ClosingState) - { - qDebug() << "[QtModbusBridge] Closing the modbus device..."; - } - // Handling state transitions - if (state == QModbusDevice::ConnectedState && m_prevState != QModbusDevice::ConnectedState) - { - emit connectionRestored(); - } - else if (m_prevState == QModbusDevice::ConnectedState && state != QModbusDevice::ConnectedState) - { - emit connectionLost(); - } - m_prevState = state; - }); - - QObject::connect(&m_client, &QModbusDevice::errorOccurred, this, [this]() - { - QModbusDevice::Error error = m_client.error(); - QString reason = m_client.errorString(); - if (error == QModbusDevice::ConfigurationError) - { - emit configurationError(reason); - } - else if (error == QModbusDevice::ConnectionError) - { - emit connectionError(reason); - } - else if (error != QModbusDevice::UnknownError) - { - emit requestError(reason); - } - else - { - emit generalError(reason); - } - }); + QObject::connect(&m_pollTimer, &QTimer::timeout, this, &QtModbusBridge::onReadSensors); + QObject::connect(&m_client, &QModbusDevice::stateChanged, this, &QtModbusBridge::onStateChange); + QObject::connect(&m_client, &QModbusDevice::errorOccurred, this, &QtModbusBridge::onErrorOccured); - qDebug() << "[QtModbusBridge] Initialization is successfull"; + qDebug() << "[QtModbusBridge] Initialization is successful"; m_client.connectDevice(); } @@ -80,156 +28,208 @@ QtModbusBridge::~QtModbusBridge() m_client.disconnectDevice(); } -void QtModbusBridge::startPolling() +void QtModbusBridge::onStateChange(QModbusDevice::State state) { - qDebug() << "[QtModbusBridge] Starting to poll server"; - m_pollTimer.start(m_cfg.pollFrequencyMs); + // Handling states change + if (state == QModbusDevice::UnconnectedState) + { + qDebug() << "[QtModbusBridge] Modbus client and server are disconnected"; + } + else if (state == QModbusDevice::ConnectingState) + { + qDebug() << "[QtModbusBridge] Connecting to the modbus device..."; + } + else if (state == QModbusDevice::ConnectedState) + { + qDebug() << "[QtModbusBridge] Connected to the modbus server successfully"; + } + else if (state == QModbusDevice::ClosingState) + { + qDebug() << "[QtModbusBridge] Closing the modbus device..."; + } + // Handling state transitions + if (state == QModbusDevice::ConnectedState && m_prevState != QModbusDevice::ConnectedState) + { + emit connectionRestored(); + } + else if (m_prevState == QModbusDevice::ConnectedState && state != QModbusDevice::ConnectedState) + { + emit connectionLost(); + } + m_prevState = state; } -void QtModbusBridge::stopPolling() +void QtModbusBridge::onErrorOccured() { - qDebug() << "[QtModbusBridge] Starting to poll server"; - m_pollTimer.stop(); + QModbusDevice::Error error = m_client.error(); + if (error == QModbusDevice::NoError) + return; + + QString reason = m_client.errorString(); + if (error == QModbusDevice::ConfigurationError) + { + emit configurationError(reason); + } + else if (error == QModbusDevice::ConnectionError) + { + emit connectionError(reason); + } + else if (error != QModbusDevice::UnknownError) + { + emit requestError(reason); + } + else + { + emit generalError(reason); + } } -void QtModbusBridge::onReadSensors() +void QtModbusBridge::startPolling() { - requestSensors(); + qDebug() << "[QtModbusBridge] Starting to poll server"; + m_pollTimer.start(m_cfg.pollFrequencyMs); } -void QtModbusBridge::onReadInfo() +void QtModbusBridge::stopPolling() { - requestInfo(); + qDebug() << "[QtModbusBridge] Stopping to poll server"; + m_pollTimer.stop(); } -void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) +void QtModbusBridge::onReadSensors() { qDebug() << "[QtModbusBridge] Requesting sensors data"; - m_dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); - m_dataUnit.setStartAddress(0); - m_dataUnit.setValueCount(sizeof(m_cfg.input) / 2); + QModbusDataUnit dataUnit; + dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); + dataUnit.setStartAddress(0); + dataUnit.setValueCount(InputRegisters::count); // Sending request to the device with specified Unit Id - auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); - + auto* reply = sendReadRequest(dataUnit); if (!reply) - { return; - } - // Check if request failed immediately - if (reply->isFinished()) + // Handling reply with finished signal + QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() { + auto values = extractValues(reply, InputRegisters::size); + if (!values.has_value()) + return; + parseSensorsResponse(values.value()); reply->deleteLater(); + }); +} + +void QtModbusBridge::onReadInfo() +{ + qDebug() << "[QtModbusBridge] Requesting model info"; + + QModbusDataUnit dataUnit; + dataUnit.setRegisterType(QModbusDataUnit::HoldingRegisters); + dataUnit.setStartAddress(0); + dataUnit.setValueCount(HoldingRegisters::count); + + // Sending request to the device with specified Unit Id + auto* reply = sendReadRequest(dataUnit); + if (!reply) return; - } // Handling reply with finished signal QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() { - parseSensorsResponse(reply); + auto values = extractValues(reply, HoldingRegisters::size); + if (!values.has_value()) + return; + parseInfoResponse(values.value()); reply->deleteLater(); }); } -void QtModbusBridge::onWriteDecision(const Decision& decision) +void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) { + qDebug() << "[QtModbusBridge] Writing a new configuration to the model"; + // TODO: implement later +} +void QtModbusBridge::onWriteDecision(const Decision& decision) +{ + qDebug() << "[QtModbusBridge] Sending a decision to the model"; + // TODO: implement later } -void QtModbusBridge::parseSensorsResponse(QModbusReply* reply) +std::optional> QtModbusBridge::extractValues(QModbusReply* reply, qsizetype expectedSize) { - if (reply->error() == QModbusDevice::NoError) - { - return; - } + if (reply->error() != QModbusDevice::NoError) + return std::nullopt; const QModbusDataUnit result = reply->result(); const QVector values = result.values(); - qDebug() << "[QtModbusBridge] Read registers:" << values.size(); - for (int i = 0; i < values.size(); ++i) + if (values.size() != expectedSize) { - qDebug() << "[QtModbusBridge] Register #" << i << ":" << values[i]; + emit requestError("Size of received data differs from expected HoldingRegisters::size"); + return std::nullopt; } + + return values; } -void QtModbusBridge::requestSensors() +void QtModbusBridge::parseSensorsResponse(const QVector& values) { - qDebug() << "[QtModbusBridge] Requesting sensors data"; - - m_dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); - m_dataUnit.setStartAddress(0); - m_dataUnit.setValueCount(sizeof(m_cfg.input) / 2); + SensorFrame frame = { + static_cast(values[InputRegisters::T_cool]), + static_cast(values[InputRegisters::T_AD]), + static_cast(values[InputRegisters::T_ballast]), + static_cast(values[InputRegisters::P_oil]), + static_cast(values[InputRegisters::M_AD]), + static_cast(values[InputRegisters::f_AD]), + static_cast(values[InputRegisters::timestamp_ir]), + 1 // TODO: clarify what does stage mean + }; + emit sensorsDataReady(frame); +} - // Sending request to the device with specified Unit Id - auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); +void QtModbusBridge::parseInfoResponse(const QVector& values) +{ + ModelConfig info = { + static_cast(values[HoldingRegisters::T_cool_max]), + static_cast(values[HoldingRegisters::T_AD_max]), + static_cast(values[HoldingRegisters::T_ballast_max]), + static_cast(values[HoldingRegisters::P_oil_max]), + static_cast(values[HoldingRegisters::P_oil_min]), + static_cast(values[HoldingRegisters::omega_ICE_max_prir]), + static_cast(values[HoldingRegisters::omega_ICE_max_run]), + }; + emit modelInfoReady(info); +} +QModbusReply* QtModbusBridge::sendWriteRequest(const QModbusDataUnit& dataUnit) { + auto* reply = m_client.sendWriteRequest(dataUnit, m_cfg.unitId); if (!reply) { - return; + return nullptr; } - // Check if request failed immediately if (reply->isFinished()) { reply->deleteLater(); - return; - } - - // Handling reply with finished signal - QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() - { - parseSensorsResponse(reply); - reply->deleteLater(); - }); -} - -void QtModbusBridge::parseInfoResponse(QModbusReply* reply) -{ - if (reply->error() == QModbusDevice::NoError) - { - return; - } - - const QModbusDataUnit result = reply->result(); - const QVector values = result.values(); - - qDebug() << "[QtModbusBridge] Read registers:" << values.size(); - for (int i = 0; i < values.size(); ++i) - { - qDebug() << "[QtModbusBridge] Register #" << i << ":" << values[i]; + return nullptr; } + return reply; } -void QtModbusBridge::requestInfo() -{ - qDebug() << "[QtModbusBridge] Requesting model info"; - - m_dataUnit.setRegisterType(QModbusDataUnit::HoldingRegisters); - m_dataUnit.setStartAddress(0); - m_dataUnit.setValueCount(sizeof(m_cfg.holding) / 2); - - // Sending request to the device with specified Unit Id - auto *reply = m_client.sendReadRequest(m_dataUnit, m_cfg.unitId); - +QModbusReply* QtModbusBridge::sendReadRequest(const QModbusDataUnit& dataUnit) { + auto* reply = m_client.sendReadRequest(dataUnit, m_cfg.unitId); if (!reply) { - return; + return nullptr; } - // Check if request failed immediately if (reply->isFinished()) { reply->deleteLater(); - return; + return nullptr; } - - // Handling reply with finished signal - QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() - { - parseInfoResponse(reply); - reply->deleteLater(); - }); + return reply; } \ No newline at end of file From d63cb88fd90e163d63138c31f0327fe5e467365a Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Fri, 8 May 2026 01:30:04 +0500 Subject: [PATCH 33/58] Merge branch 'backend' of https://github.com/VaryVA/ScadaForDiesel into backend From 9954b3d646d32b4c66ec036f940d1b11f597bb33 Mon Sep 17 00:00:00 2001 From: igor66ru Date: Fri, 8 May 2026 05:32:22 +0500 Subject: [PATCH 34/58] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BE=D0=BC=D0=BC=D1=83=D0=BD=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D1=81=20=D1=84=D1=80=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=B8=20=D0=BD=D0=B0=D0=BE=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D1=82.=20=D0=92=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D0=B5=D0=BA=D1=82=D1=8B=20=D0=B2=20state=5Fmachine?= =?UTF-8?q?.cpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/DataTypes.h | 38 ++++++++++--------- .../backend_worker/backendcommunicator.h | 17 ++++++--- .../backend/backend_worker/backendworker.h | 15 +++++--- .../backend/data_processing/DataProcessor.h | 2 +- .../backend_worker/backendcommunicator.cpp | 9 ++++- src/backend/backend_worker/backendworker.cpp | 20 +++++++++- src/backend/data_processing/DataProcessor.cpp | 2 +- src/backend/state_machine/state_machine.cpp | 34 +++++++++++------ 8 files changed, 92 insertions(+), 45 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index ce0f807..56f12c4 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -38,7 +38,7 @@ namespace InputRegisters static constexpr uint16_t state_ir = 48; // Код неисправности static constexpr uint16_t faultCode = 49; -}; +} // HoldingRegisters offset (settings and variables, read/write operations) namespace HoldingRegisters @@ -71,7 +71,7 @@ namespace HoldingRegisters static constexpr uint16_t simulationRequest = 41; // Режим симуляции static constexpr uint16_t simulationMode = 42; -}; +} // Coils offset (control registers, read/write operations) namespace CoilsRegisters @@ -81,7 +81,7 @@ namespace CoilsRegisters static constexpr uint16_t fan_ICE = 0; static constexpr uint16_t fan_AD = 1; static constexpr uint16_t fan_ballast = 2; -}; +} // Discrete Inputs offset (device status, only read operations) namespace DiscreteRegisters @@ -90,7 +90,7 @@ namespace DiscreteRegisters static constexpr uint16_t count = 2; static constexpr uint16_t hasFault = 0; static constexpr uint16_t hasLimitViolations = 1; -}; +} //Структра для задания конфигурации Modbus-клиента struct ModbusConfig @@ -108,17 +108,17 @@ struct ModbusConfig //Снимок датчиков struct SensorFrame { - //Температура ДВС + //Температура ДВС(температура охлаждающей жидкости) double dieselTemp; //Температура АД double motorTemp; //Температура балластных резисторов double resistorTemp; - //Давление + //Давление масла double dieselPressure; - //Момент + //Момент АД double torque; - //Частота + //Частота вращения ДВС double rpm; //Метка времени в UNIX-формате qint64 timestampMs; @@ -130,15 +130,15 @@ Q_DECLARE_METATYPE(SensorFrame) // лимиты, передаются в конструктор DataProcessor и в IModbusBridge для записи в модель struct ModelConfig { - //Максимальная температура ДВС + //Максимальная температура ДВС(температура охлаждающей жидкости) double maxDieselTemp; //Максимальная температура АД double maxMotorTemp; //Максимальная температура балластных резисторов double maxResistorTemp; - //Максимальное давление в ДВС + //Максимальное давление масла в ДВС double maxDieselPressure; - //Минимальное давление в ДВС + //Минимальное давление масла в ДВС double minDieselPressure; //Общая частота оборотов в режиме притирки int maxRpmPrir; @@ -189,12 +189,14 @@ enum class DiagState //Структра для управления моделью фронтом struct FrontControl { - //Этап эксперимента - DiagState state; - //Максимальная частота в зависимости от этапа(необязательный параметр на некоторых этапах - может быть не задан) - int maxFreq; - //Время выполнения этапа в минутах(необязательный параметр на некоторых этапах - может быть не задан) - unsigned int time; + //Время обкатки ДВС в режиме притирки(в минутах) + unsigned int timePrir; + //Время горячей обкатки ДВС(в минутах) + unsigned int timeHot; + //Время горячей обкатки ДВС с нагрузкой(в минутах) + unsigned int timeHotWithLoad; + //Структура для задания минимальных/максимальных значений в модели + ModelConfig config; }; // вектор управляющих воздействий + диагностика @@ -214,4 +216,4 @@ struct Data //Этап эксперимента DiagState state; }; -#endif // DATATYPES_H \ No newline at end of file +#endif // DATATYPES_H diff --git a/include/backend/backend_worker/backendcommunicator.h b/include/backend/backend_worker/backendcommunicator.h index aeb7ee3..ee4e26b 100644 --- a/include/backend/backend_worker/backendcommunicator.h +++ b/include/backend/backend_worker/backendcommunicator.h @@ -14,23 +14,28 @@ class BackendCommunicator : public QObject void SendSensorFrameToFrontend(SensorFrame& sensorFrame); //Метод для отправки информации фронту об аварийной остановке void SendEmergencyStopInfoToFrontend(); - //Метод для отправки фидбека фронту на изменение этапа - void SendFeedbackToFrontend(bool isComplete); + //Метод для отправки фидбека фронту на операцию + void SendFeedbackToFrontend(DiagState state); //Метод для отправки данных фронту void SendDataToFrontend(QVector& data); + //Метод для отправки предупреждений фронту + void SendWarnToFrontend(DiagState state); signals: - //Сигналы для отправки + //Сигналы для отправки(внутренние) void SendedSensorFrame(SensorFrame& sensorFrame); void SendedEmergencyStopInfo(); - void SendedFeedback(bool isComplete); + void SendedFeedback(DiagState state); void SendedData(QVector& data); + void SendedWarnToFrontend(DiagState state); //Сигналы для получения - //Сигнал на приход нового конфига модели от фронта - void ReceivedModelConfig(ModelConfig config); //Сигнал на приход запроса на изменения этапа эксперимента void ReceivedFrontControl(FrontControl control); + //Сигнал на запуск обкатки + void ReceivedStartEngine(); + //Сигнал на остановку обкатки + void ReceivedStopEngine(); }; #endif // BACKENDCOMMUNICATOR_H diff --git a/include/backend/backend_worker/backendworker.h b/include/backend/backend_worker/backendworker.h index 7001336..37056e4 100644 --- a/include/backend/backend_worker/backendworker.h +++ b/include/backend/backend_worker/backendworker.h @@ -19,10 +19,12 @@ class BackendWorker : public QObject //Остановка бэкэнда void Stop(); - //Метод для отправки команды на изменение состояния модели бэкэнду + //Метод для отправки параметров модели бэкэнду void SendFrontControlToBackend(FrontControl& control); - //Метод для отправки нового кофига модели бэкэнду - void SendModelConfigToBackend(ModelConfig& config); + //Запустить обкатку + void StartEngine(); + //Остановить обкатку + void StopEngine(); signals: //Сигналы для получения(для фронта) @@ -31,13 +33,16 @@ class BackendWorker : public QObject //Сигнал на приход информации об аварийной остановке void ReceivedEmergencyStopInfo(); //Сигнал на приход фидбека на изменение этапа - void ReceivedFeedback(bool isComplete); + void ReceivedFeedback(DiagState state); //Сигнал на приход данных void ReceivedData(QVector& data); + //Сигнал на приход предупреждений в работе двигателя + void ReceivedWarn(DiagState state); //Сигналы для отправки(внутренние) void SendedFrontControlToBackend(FrontControl control); - void SendedModelConfigToBackend(ModelConfig config); + void StartedEngine(); + void StopedEngine(); private: QThread m_machineThread; diff --git a/include/backend/data_processing/DataProcessor.h b/include/backend/data_processing/DataProcessor.h index 408c6ee..a1311e4 100644 --- a/include/backend/data_processing/DataProcessor.h +++ b/include/backend/data_processing/DataProcessor.h @@ -3,7 +3,7 @@ #include #include -#include "DataTypes.h" +#include "backend/DataTypes.h" /** * Модуль обработки данных. diff --git a/src/backend/backend_worker/backendcommunicator.cpp b/src/backend/backend_worker/backendcommunicator.cpp index f468372..567ff04 100644 --- a/src/backend/backend_worker/backendcommunicator.cpp +++ b/src/backend/backend_worker/backendcommunicator.cpp @@ -13,12 +13,17 @@ void BackendCommunicator::SendEmergencyStopInfoToFrontend() emit SendedEmergencyStopInfo(); } -void BackendCommunicator::SendFeedbackToFrontend(bool isComplete) +void BackendCommunicator::SendFeedbackToFrontend(DiagState state) { - emit SendedFeedback(isComplete); + emit SendedFeedback(state); } void BackendCommunicator::SendDataToFrontend(QVector& data) { emit SendedData(data); } + +void BackendCommunicator::SendWarnToFrontend(DiagState state) +{ + emit SendedWarnToFrontend(state); +} diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp index 84cb6cf..a6af545 100644 --- a/src/backend/backend_worker/backendworker.cpp +++ b/src/backend/backend_worker/backendworker.cpp @@ -2,7 +2,7 @@ #include "backend/state_machine/state_machine.h" BackendWorker::BackendWorker(QObject* parent) : - m_machine(new StateMachine(this, parent)), m_machineThread(QThread(this)), QObject{parent} + m_machine(new StateMachine(this)), QObject{parent} { qRegisterMetaType(); qRegisterMetaType(); @@ -11,6 +11,21 @@ BackendWorker::BackendWorker(QObject* parent) : connect(&m_machineThread, &QThread::finished, m_machine, &StateMachine::stop, Qt::QueuedConnection); } +void BackendWorker::SendFrontControlToBackend(FrontControl& control) +{ + emit SendedFrontControlToBackend(control); +} + +void BackendWorker::StartEngine() +{ + emit StartedEngine(); +} + +void BackendWorker::StopEngine() +{ + emit StopedEngine(); +} + void BackendWorker::Run() { m_machine->moveToThread(&m_machineThread); @@ -24,5 +39,8 @@ void BackendWorker::Stop() BackendWorker::~BackendWorker() { + if(m_machineThread.isRunning()) + m_machineThread.quit(); + m_machineThread.wait(); m_machine->deleteLater(); } diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 898e822..158a2e0 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,4 +1,4 @@ -#include "DataProcessor.h" +#include "backend/data_processing/DataProcessor.h" namespace Stage { constexpr int IDLE = 0; diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 273371a..6b81798 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -1,7 +1,7 @@ -#include "../../../include/backend/state_machine/state_machine.h" -#include "../../../include/backend/backend_worker/backendworker.h" -#include "../../../include/backend/modbus_client/MockModbusBridge.h" -#include "../../../include/backend/data_store/DataStore.h" +#include "backend/state_machine/state_machine.h" +#include "backend/backend_worker/backendworker.h" +#include "backend/modbus_client/MockModbusBridge.h" +#include "backend/data_store/DataStore.h" #include StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) @@ -23,15 +23,27 @@ StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) m_dataProcessor = new DataProcessor(ModelConfig{}, this); + //Коннекты для коммуникатора connect(backendWorker, &BackendWorker::SendedFrontControlToBackend, m_communicator, &BackendCommunicator::ReceivedFrontControl, Qt::QueuedConnection); - connect(backendWorker, &BackendWorker::SendedModelConfigToBackend, - m_communicator, &BackendCommunicator::ReceivedModelConfig, Qt::QueuedConnection); + connect(backendWorker, &BackendWorker::StartedEngine, + m_communicator, &BackendCommunicator::ReceivedStartEngine, Qt::QueuedConnection); + connect(backendWorker, &BackendWorker::StopedEngine, + m_communicator, &BackendCommunicator::ReceivedStopEngine, Qt::QueuedConnection); + + connect(m_communicator, &BackendCommunicator::SendedSensorFrame, + backendWorker, &BackendWorker::ReceivedSensorFrame, Qt::QueuedConnection); + connect(m_communicator, &BackendCommunicator::SendedEmergencyStopInfo, + backendWorker, &BackendWorker::ReceivedEmergencyStopInfo, Qt::QueuedConnection); + connect(m_communicator, &BackendCommunicator::SendedFeedback, + backendWorker, &BackendWorker::ReceivedFeedback, Qt::QueuedConnection); + connect(m_communicator, &BackendCommunicator::SendedData, + backendWorker, &BackendWorker::ReceivedData, Qt::QueuedConnection); + connect(m_communicator, &BackendCommunicator::SendedWarnToFrontend, + backendWorker, &BackendWorker::ReceivedWarn, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::ReceivedFrontControl, this, &StateMachine::onReceivedFrontControl); - connect(m_communicator, &BackendCommunicator::ReceivedModelConfig, - this, &StateMachine::onReceivedModelConfig); connect(m_modbusBridge, &IModbusBridge::sensorsDataReady, this, &StateMachine::onSensorsDataReady); @@ -46,8 +58,8 @@ StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) connect(m_modbusBridge, &IModbusBridge::connectionRestored, this, &StateMachine::onModbusConnectionRestored); - connect(m_dataProcessor, &DataProcessor::decisionReady, - this, &StateMachine::onDecisionReady); + // connect(m_dataProcessor, &DataProcessor::decisionReady, + // this, &StateMachine::onDecisionReady); connect(m_pollTimer, &QTimer::timeout, this, &StateMachine::pollModbus); } @@ -220,4 +232,4 @@ void StateMachine::onModbusConnectionLost() { void StateMachine::onModbusConnectionRestored() { if (m_state != DiagState::IDLE && m_state != DiagState::Alarm_Generic) m_pollTimer->start(); -} \ No newline at end of file +} From bb25824daccd026bcbe3dc25ba398dc0e6336f4a Mon Sep 17 00:00:00 2001 From: igor66ru Date: Fri, 8 May 2026 05:48:32 +0500 Subject: [PATCH 35/58] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D1=84=D1=80=D0=BE=D0=BD=D1=82=D1=83=20=D0=BE?= =?UTF-8?q?=D0=B1=20=D1=83=D1=81=D0=BF=D0=B5=D1=88=D0=BD=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D0=B8=20?= =?UTF-8?q?=D1=8D=D1=82=D0=B0=D0=BF=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/backend_worker/backendcommunicator.h | 3 +++ include/backend/backend_worker/backendworker.h | 2 ++ src/backend/backend_worker/backendcommunicator.cpp | 5 +++++ src/backend/state_machine/state_machine.cpp | 2 ++ 4 files changed, 12 insertions(+) diff --git a/include/backend/backend_worker/backendcommunicator.h b/include/backend/backend_worker/backendcommunicator.h index ee4e26b..ef1b436 100644 --- a/include/backend/backend_worker/backendcommunicator.h +++ b/include/backend/backend_worker/backendcommunicator.h @@ -20,6 +20,8 @@ class BackendCommunicator : public QObject void SendDataToFrontend(QVector& data); //Метод для отправки предупреждений фронту void SendWarnToFrontend(DiagState state); + //Метод для отправки сообщения о завершенном этапе + void SendStageCompleteInfoToFrontend(); signals: //Сигналы для отправки(внутренние) @@ -28,6 +30,7 @@ class BackendCommunicator : public QObject void SendedFeedback(DiagState state); void SendedData(QVector& data); void SendedWarnToFrontend(DiagState state); + void SendedStageCompleteInfoToFrontend(); //Сигналы для получения //Сигнал на приход запроса на изменения этапа эксперимента diff --git a/include/backend/backend_worker/backendworker.h b/include/backend/backend_worker/backendworker.h index 37056e4..f7bbecc 100644 --- a/include/backend/backend_worker/backendworker.h +++ b/include/backend/backend_worker/backendworker.h @@ -38,6 +38,8 @@ class BackendWorker : public QObject void ReceivedData(QVector& data); //Сигнал на приход предупреждений в работе двигателя void ReceivedWarn(DiagState state); + //Сигнал при окончании выполнения этапа обкатки + void ReceivedStageCompleteInfo(); //Сигналы для отправки(внутренние) void SendedFrontControlToBackend(FrontControl control); diff --git a/src/backend/backend_worker/backendcommunicator.cpp b/src/backend/backend_worker/backendcommunicator.cpp index 567ff04..8df85c3 100644 --- a/src/backend/backend_worker/backendcommunicator.cpp +++ b/src/backend/backend_worker/backendcommunicator.cpp @@ -27,3 +27,8 @@ void BackendCommunicator::SendWarnToFrontend(DiagState state) { emit SendedWarnToFrontend(state); } + +void BackendCommunicator::SendStageCompleteInfoToFrontend() +{ + emit SendedStageCompleteInfoToFrontend(); +} diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 6b81798..9bd5db8 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -41,6 +41,8 @@ StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) backendWorker, &BackendWorker::ReceivedData, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::SendedWarnToFrontend, backendWorker, &BackendWorker::ReceivedWarn, Qt::QueuedConnection); + connect(m_communicator, &BackendCommunicator::SendedStageCompleteInfoToFrontend, + backendWorker, &BackendWorker::ReceivedStageCompleteInfo, Qt::QueuedConnection); connect(m_communicator, &BackendCommunicator::ReceivedFrontControl, this, &StateMachine::onReceivedFrontControl); From a746178330a7dda83b8f080fe496fb8ac285e618 Mon Sep 17 00:00:00 2001 From: Vova22013 Date: Fri, 8 May 2026 17:04:59 +0500 Subject: [PATCH 36/58] Unit tests --- .../backend_worker_test.cpp | 282 +++++++++++- .../data_processor_test.cpp | 427 +++++++++++++++++- .../data_store_test/data_store_test.cpp | 343 +++++++++++++- .../modbus_data_bridge_test.cpp | 288 +++++++++++- .../state_machine_test/state_machine_test.cpp | 270 ++++++++++- 5 files changed, 1538 insertions(+), 72 deletions(-) diff --git a/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp b/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp index 41fff2e..f819885 100644 --- a/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp +++ b/src/backend/unit_tests/backend_worker_test/backend_worker_test.cpp @@ -1,19 +1,263 @@ -// backend_worker_test.cpp -#include "backend_worker_test.h" - -void BackendWorkerTest::init() {} -void BackendWorkerTest::cleanup() {} - -void BackendWorkerTest::isStart() {} -void BackendWorkerTest::doubleStart() {} -void BackendWorkerTest::isStop() {} -void BackendWorkerTest::doubleStop() {} -void BackendWorkerTest::moveOnNextStage() {} -void BackendWorkerTest::correctCurrentStatus() {} -void BackendWorkerTest::isFinishedReport() {} -void BackendWorkerTest::isFinishedHistory() {} -void BackendWorkerTest::isAlarmEvent() {} -void BackendWorkerTest::isWarningEvent() {} -void BackendWorkerTest::isProcessFinished() {} - -QTEST_MAIN(BackendWorkerTest) \ No newline at end of file +#include +#include +#include +#include "../../../include/backend/backend_worker/backendworker.h" +#include "../../../include/backend/DataTypes.h" + +class BackendWorkerTest : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + // Тесты конструктора и деструктора + void testConstructor(); + void testDestructor(); + + // Тесты запуска/остановки + void testRun(); + void testStop(); + void testRunStopSequence(); + + // Тесты отправки команд + void testSendFrontControlToBackend(); + void testSendModelConfigToBackend(); + + // Тесты сигналов получения данных + void testReceivedSensorFrame(); + void testReceivedEmergencyStopInfo(); + void testReceivedFeedback(); + void testReceivedData(); + + // Тесты внутренних сигналов + void testSendedFrontControlToBackend(); + void testSendedModelConfigToBackend(); + + // Тесты многопоточности + void testThreadSafety(); + void testMultipleRunStopCycles(); + +private: + BackendWorker* m_worker; + QThread* m_testThread; +}; + +void BackendWorkerTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); +} + +void BackendWorkerTest::cleanupTestCase() +{ +} + +void BackendWorkerTest::init() +{ + m_worker = new BackendWorker(); + m_testThread = new QThread(); + m_worker->moveToThread(m_testThread); + m_testThread->start(); +} + +void BackendWorkerTest::cleanup() +{ + m_testThread->quit(); + m_testThread->wait(); + delete m_worker; + delete m_testThread; +} + +void BackendWorkerTest::testConstructor() +{ + BackendWorker* worker = new BackendWorker(); + QVERIFY(worker != nullptr); + delete worker; +} + +void BackendWorkerTest::testDestructor() +{ + BackendWorker* worker = new BackendWorker(); + delete worker; + // Если деструктор отработал без ошибок - тест пройден + QVERIFY(true); +} + +void BackendWorkerTest::testRun() +{ + QSignalSpy runSpy(m_worker, &BackendWorker::Run); + + QMetaObject::invokeMethod(m_worker, "Run", Qt::QueuedConnection); + QTest::qWait(100); + + // Проверяем, что поток запустился + QVERIFY(m_testThread->isRunning()); +} + +void BackendWorkerTest::testStop() +{ + QMetaObject::invokeMethod(m_worker, "Run", Qt::QueuedConnection); + QTest::qWait(100); + + QMetaObject::invokeMethod(m_worker, "Stop", Qt::QueuedConnection); + QTest::qWait(100); + + // Проверяем, что поток остановился + QVERIFY(!m_testThread->isRunning()); +} + +void BackendWorkerTest::testRunStopSequence() +{ + for(int i = 0; i < 5; ++i) { + QMetaObject::invokeMethod(m_worker, "Run", Qt::QueuedConnection); + QTest::qWait(50); + QMetaObject::invokeMethod(m_worker, "Stop", Qt::QueuedConnection); + QTest::qWait(50); + } + QVERIFY(true); +} + +void BackendWorkerTest::testSendFrontControlToBackend() +{ + QSignalSpy internalSpy(m_worker, &BackendWorker::SendedFrontControlToBackend); + + FrontControl control; + // control.control = ControlType::NextStage; // Настройка в зависимости от реализации + + QMetaObject::invokeMethod(m_worker, "SendFrontControlToBackend", + Qt::QueuedConnection, Q_ARG(FrontControl&, control)); + QTest::qWait(100); + + QCOMPARE(internalSpy.count(), 1); +} + +void BackendWorkerTest::testSendModelConfigToBackend() +{ + QSignalSpy internalSpy(m_worker, &BackendWorker::SendedModelConfigToBackend); + + ModelConfig config; + config.maxRpm = 3000; + config.maxDieselTemp = 120; + config.maxMotorTemp = 100; + + QMetaObject::invokeMethod(m_worker, "SendModelConfigToBackend", + Qt::QueuedConnection, Q_ARG(ModelConfig&, config)); + QTest::qWait(100); + + QCOMPARE(internalSpy.count(), 1); +} + +void BackendWorkerTest::testReceivedSensorFrame() +{ + QSignalSpy spy(m_worker, &BackendWorker::ReceivedSensorFrame); + + SensorFrame frame; + frame.rpm = 1500; + frame.dieselTemp = 85; + frame.timestampMs = QDateTime::currentMSecsSinceEpoch(); + + emit m_worker->ReceivedSensorFrame(frame); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); + + QList arguments = spy.takeFirst(); + SensorFrame receivedFrame = arguments.at(0).value(); + QCOMPARE(receivedFrame.rpm, 1500); + QCOMPARE(receivedFrame.dieselTemp, 85); +} + +void BackendWorkerTest::testReceivedEmergencyStopInfo() +{ + QSignalSpy spy(m_worker, &BackendWorker::ReceivedEmergencyStopInfo); + + emit m_worker->ReceivedEmergencyStopInfo(); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); +} + +void BackendWorkerTest::testReceivedFeedback() +{ + QSignalSpy spy(m_worker, &BackendWorker::ReceivedFeedback); + + emit m_worker->ReceivedFeedback(true); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); + + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.at(0).toBool(), true); +} + +void BackendWorkerTest::testReceivedData() +{ + QSignalSpy spy(m_worker, &BackendWorker::ReceivedData); + + QVector dataVector; + // Добавление тестовых данных + + emit m_worker->ReceivedData(dataVector); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); +} + +void BackendWorkerTest::testSendedFrontControlToBackend() +{ + QSignalSpy spy(m_worker, &BackendWorker::SendedFrontControlToBackend); + + FrontControl control; + emit m_worker->SendedFrontControlToBackend(control); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); +} + +void BackendWorkerTest::testSendedModelConfigToBackend() +{ + QSignalSpy spy(m_worker, &BackendWorker::SendedModelConfigToBackend); + + ModelConfig config; + emit m_worker->SendedModelConfigToBackend(config); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); +} + +void BackendWorkerTest::testThreadSafety() +{ + QAtomicInt counter = 0; + QSignalSpy spy(m_worker, &BackendWorker::ReceivedSensorFrame); + + // Эмитируем сигналы из разных потоков + for(int i = 0; i < 10; ++i) { + SensorFrame frame; + frame.rpm = i * 100; + QMetaObject::invokeMethod(m_worker, [&]() { + emit m_worker->ReceivedSensorFrame(frame); + counter.ref(); + }, Qt::QueuedConnection); + } + + QTest::qWait(500); + QCOMPARE(counter.load(), 10); + QCOMPARE(spy.count(), 10); +} + +void BackendWorkerTest::testMultipleRunStopCycles() +{ + for(int i = 0; i < 3; ++i) { + QMetaObject::invokeMethod(m_worker, "Run", Qt::QueuedConnection); + QTest::qWait(100); + QVERIFY(m_testThread->isRunning()); + + QMetaObject::invokeMethod(m_worker, "Stop", Qt::QueuedConnection); + QTest::qWait(100); + } + QVERIFY(true); +} \ No newline at end of file diff --git a/src/backend/unit_tests/data_processor_test/data_processor_test.cpp b/src/backend/unit_tests/data_processor_test/data_processor_test.cpp index 815d8ad..791d6b2 100644 --- a/src/backend/unit_tests/data_processor_test/data_processor_test.cpp +++ b/src/backend/unit_tests/data_processor_test/data_processor_test.cpp @@ -1,12 +1,421 @@ -#include "data_processor_test.h" +#include +#include +#include "../../../include/backend/data_processing/DataProcessor.h" +#include "../../../include/backend/DataTypes.h" -void DataProcessorTest::init() {} -void DataProcessorTest::cleanup() {} +class DataProcessorTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + // Тесты конструктора + void testConstructor(); + + // Тесты обработки кадров + void testProcessFrameNormal(); + void testProcessFrameRpmOverspeed(); + void testProcessFrameDieselOverheat(); + void testProcessFrameMotorOverheat(); + void testProcessFrameResistorOverheat(); + void testProcessFramePressureOver(); + void testProcessFramePressureUnder(); + + // Тесты предупреждений + void testPreWarningRpmHigh(); + void testPreWarningDieselTempHigh(); + void testPreWarningMotorTempHigh(); + void testPreWarningResistorHigh(); + void testPreWarningPressureHigh(); + void testPreWarningPressureLow(); + + // Тесты смены этапов + void testStageChange(); + void testDifferentStages(); + + // Тест сброса + void testReset(); + void testAlarmLatch(); + + // Тест конфигурации + void testConfigChange(); + + // Тесты сигналов + void testDecisionReadySignal(); + + // Тесты на граничных значениях + void testBoundaryValues(); + void testInvalidFrames(); -void DataProcessorTest::correctFrame() {} -void DataProcessorTest::isDecisionReady() {} -void DataProcessorTest::isWarningRaised() {} -void DataProcessorTest::isAlarmRaised() {} -void DataProcessorTest::isControlProduced() {} +private: + DataProcessor* m_processor; + ModelConfig m_config; +}; -QTEST_MAIN(DataProcessorTest) \ No newline at end of file +void DataProcessorTest::init() +{ + m_config.maxRpm = 3000; + m_config.maxDieselTemp = 120; + m_config.maxMotorTemp = 100; + m_config.maxResistorBalance = 150; + m_config.maxDieselPressure = 10.0; + m_config.minDieselPressure = 2.0; + + m_processor = new DataProcessor(m_config); +} + +void DataProcessorTest::cleanup() +{ + delete m_processor; +} + +void DataProcessorTest::testConstructor() +{ + DataProcessor* processor = new DataProcessor(m_config); + QVERIFY(processor != nullptr); + delete processor; +} + +void DataProcessorTest::testProcessFrameNormal() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.dieselTemp = 90; + frame.motorTemp = 80; + frame.resistorBalance = 100; + frame.dieselPressure = 5.0; + frame.stage = 3; // HOT_NO_LOAD + + m_processor->onStageChanged(3); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Ok); +} + +void DataProcessorTest::testProcessFrameRpmOverspeed() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 3500; // Превышение (max 3000) + frame.dieselTemp = 90; + frame.motorTemp = 80; + frame.resistorBalance = 100; + frame.dieselPressure = 5.0; + frame.stage = 3; + + m_processor->onStageChanged(3); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_RpmOverspeed); + QVERIFY(!decision.reason.isEmpty()); + QVERIFY(decision.controls.size() > 0); + + // Проверяем, что есть EmergencyStop + bool hasEmergencyStop = false; + for(const auto& control : decision.controls) { + if(control.type == ControlType::EmergencyStop) { + hasEmergencyStop = true; + break; + } + } + QVERIFY(hasEmergencyStop); +} + +void DataProcessorTest::testProcessFrameDieselOverheat() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.dieselTemp = 150; // Превышение (max 120) + frame.motorTemp = 80; + frame.resistorBalance = 100; + frame.dieselPressure = 5.0; + frame.stage = 3; + + m_processor->onStageChanged(3); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_DieselOverheat); +} + +void DataProcessorTest::testProcessFrameMotorOverheat() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.dieselTemp = 90; + frame.motorTemp = 120; // Превышение (max 100) + frame.resistorBalance = 100; + frame.dieselPressure = 5.0; + + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_MotorOverheat); +} + +void DataProcessorTest::testProcessFrameResistorOverheat() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.dieselTemp = 90; + frame.motorTemp = 80; + frame.resistorBalance = 180; // Превышение (max 150) + frame.dieselPressure = 5.0; + frame.stage = 4; // HOT_WITH_LOAD + + m_processor->onStageChanged(4); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_ResistorOverheat); +} + +void DataProcessorTest::testProcessFramePressureOver() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.dieselTemp = 90; + frame.motorTemp = 80; + frame.resistorBalance = 100; + frame.dieselPressure = 12.0; // Превышение (max 10.0) + frame.stage = 2; // START_AND_WARMUP + + m_processor->onStageChanged(2); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_PressureOver); +} + +void DataProcessorTest::testProcessFramePressureUnder() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.dieselTemp = 90; + frame.motorTemp = 80; + frame.resistorBalance = 100; + frame.dieselPressure = 1.0; // Ниже минимума (min 2.0) + frame.stage = 2; + + m_processor->onStageChanged(2); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_PressureUnder); +} + +void DataProcessorTest::testPreWarningRpmHigh() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2750; // 90% от 3000 + frame.stage = 3; + + m_processor->onStageChanged(3); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::PreWarn_RpmHigh); +} + +void DataProcessorTest::testPreWarningDieselTempHigh() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.dieselTemp = 108; // 90% от 120 + frame.stage = 3; + + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::PreWarn_DieselTempHigh); +} + +void DataProcessorTest::testPreWarningMotorTempHigh() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.motorTemp = 90; // 90% от 100 + frame.stage = 3; + + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::PreWarn_MotorTempHigh); +} + +void DataProcessorTest::testStageChange() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 3500; // Вызовет аварию + + m_processor->onStageChanged(1); // COLD_CRANKING + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_RpmOverspeed); +} + +void DataProcessorTest::testDifferentStages() +{ + QVector stages = {1, 2, 3, 4, 5, 6}; + SensorFrame normalFrame; + normalFrame.rpm = 2000; + normalFrame.dieselTemp = 90; + normalFrame.motorTemp = 80; + + for(int stage : stages) { + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + m_processor->onStageChanged(stage); + m_processor->processFrame(normalFrame); + + // В неактивных этапах не должно быть проверок + if(stage == 0 || stage == 5 || stage == 6) { + QCOMPARE(spy.count(), 1); + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Ok); + } + } +} + +void DataProcessorTest::testReset() +{ + SensorFrame alarmFrame; + alarmFrame.rpm = 3500; + alarmFrame.stage = 3; + + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + m_processor->onStageChanged(3); + m_processor->processFrame(alarmFrame); + QCOMPARE(spy.count(), 1); + + // После аварии новые фреймы не должны обрабатываться + m_processor->processFrame(alarmFrame); + QCOMPARE(spy.count(), 1); // Количество не изменилось + + m_processor->reset(); + + // После сброса должно снова обрабатывать + m_processor->processFrame(alarmFrame); + QCOMPARE(spy.count(), 2); // Увеличилось +} + +void DataProcessorTest::testAlarmLatch() +{ + SensorFrame alarmFrame; + alarmFrame.rpm = 3500; + alarmFrame.stage = 3; + + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + m_processor->onStageChanged(3); + m_processor->processFrame(alarmFrame); + QCOMPARE(spy.count(), 1); + + SensorFrame normalFrame; + normalFrame.rpm = 2000; + normalFrame.stage = 3; + + m_processor->processFrame(normalFrame); + QCOMPARE(spy.count(), 1); // Не увеличилось - защелка аварии +} + +void DataProcessorTest::testConfigChange() +{ + ModelConfig newConfig; + newConfig.maxRpm = 4000; + newConfig.maxDieselTemp = 150; + + m_processor->onConfigChanged(newConfig); + + SensorFrame frame; + frame.rpm = 3800; // Было бы аварией со старой конфигурацией + frame.stage = 3; + + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + m_processor->onStageChanged(3); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Ok); // С новой конфигурацией - норма +} + +void DataProcessorTest::testDecisionReadySignal() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + SensorFrame frame; + frame.rpm = 2000; + frame.stage = 3; + + m_processor->onStageChanged(3); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + QVERIFY(spy.isValid()); +} + +void DataProcessorTest::testBoundaryValues() +{ + QSignalSpy spy(m_processor, &DataProcessor::decisionReady); + + // Точные граничные значения + SensorFrame frame; + frame.rpm = 3000; // Точно на границе + frame.dieselTemp = 120; // Точно на границе + frame.motorTemp = 100; // Точно на границе + frame.resistorBalance = 150; // Точно на границе + frame.dieselPressure = 10.0; // Точно на границе + frame.stage = 4; + + m_processor->onStageChanged(4); + m_processor->processFrame(frame); + + QCOMPARE(spy.count(), 1); + Decision decision = spy.takeFirst().at(0).value(); + QCOMPARE(decision.state, DiagState::Alarm_RpmOverspeed); // Должна быть авария +} \ No newline at end of file diff --git a/src/backend/unit_tests/data_store_test/data_store_test.cpp b/src/backend/unit_tests/data_store_test/data_store_test.cpp index d4e968d..d58a507 100644 --- a/src/backend/unit_tests/data_store_test/data_store_test.cpp +++ b/src/backend/unit_tests/data_store_test/data_store_test.cpp @@ -1,20 +1,323 @@ -#include "data_store_test.h" - -void DataStoreTest::init() {} -void DataStoreTest::cleanup() {} - -void DataStoreTest::isFileExist() {} -void DataStoreTest::isWriteRecord() {} -void DataStoreTest::correctWriteRecord() {} -void DataStoreTest::doubleWriteRecord() {} -void DataStoreTest::rewritePrevRecord() {} -void DataStoreTest::isWriteEvent() {} -void DataStoreTest::correctWriteEvent() {} -void DataStoreTest::doubleWriteEvent() {} -void DataStoreTest::rewritePrevEvent() {} -void DataStoreTest::isReadRecord() {} -void DataStoreTest::correctReadRecord() {} -void DataStoreTest::isReadEvent() {} -void DataStoreTest::correctReadEvent() {} - -QTEST_MAIN(DataStoreTest) \ No newline at end of file +#include +#include +#include +#include +#include "../../../include/backend/data_store/DataStore.h" + +class DataStoreTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + // Тесты конструктора + void testConstructor(); + + // Тесты записи + void testWriteRecord(); + void testWriteEvent(); + void testMultipleWriteRecord(); + void testMultipleWriteEvent(); + + // Тесты чтения + void testReadRecords(); + void testReadEvents(); + void testReadNonExistent(); + + // Тесты отчета + void testBuildReportCompleted(); + void testBuildReportAborted(); + + // Тесты работы с файлами + void testFileCreation(); + void testHeadersWritten(); + void testEmptyFileRead(); + + // Тесты граничных случаев + void testLargeData(); + void testConcurrentWrites(); + +private: + QTemporaryDir m_tempDir; + CSVConnector* m_measurementConnector; + CSVConnector* m_eventConnector; + DataStore* m_dataStore; + QString m_measurementPath; + QString m_eventPath; +}; + +void DataStoreTest::init() +{ + QVERIFY(m_tempDir.isValid()); + m_measurementPath = m_tempDir.path() + "/measurements.csv"; + m_eventPath = m_tempDir.path() + "/events.csv"; + + m_measurementConnector = new CSVConnector(m_measurementPath.toStdString()); + m_eventConnector = new CSVConnector(m_eventPath.toStdString()); + m_dataStore = new DataStore(m_measurementConnector, m_eventConnector); +} + +void DataStoreTest::cleanup() +{ + delete m_dataStore; + delete m_measurementConnector; + delete m_eventConnector; + + // Очищаем временные файлы + QFile::remove(m_measurementPath); + QFile::remove(m_eventPath); +} + +void DataStoreTest::testConstructor() +{ + DataStore* store = new DataStore(m_measurementConnector, m_eventConnector); + QVERIFY(store != nullptr); + delete store; +} + +void DataStoreTest::testWriteRecord() +{ + MeasurementRecord record; + record.runId = 1; + record.stage = 1; + record.timestampMs = QDateTime::currentMSecsSinceEpoch(); + record.rpm = 1500; + record.torque = 100; + record.dieselTemp = 85; + record.motorTemp = 75; + record.resistorTemp = 60; + record.dieselPressure = 5.0; + record.throttle = 50; + record.brakeTorque = 30; + record.flags = "OK"; + + m_dataStore->writeRecord(record); + QTest::qWait(100); + + QVector records = m_dataStore->readRecords(1); + QCOMPARE(records.size(), 1); + QCOMPARE(records[0].runId, record.runId); + QCOMPARE(records[0].rpm, record.rpm); +} + +void DataStoreTest::testWriteEvent() +{ + EventRecord event; + event.runId = 1; + event.timestampMs = QDateTime::currentMSecsSinceEpoch(); + event.stage = 1; + event.type = "stage_change"; + event.message = "Entered stage 1"; + + m_dataStore->writeEvent(event); + QTest::qWait(100); + + QVector events = m_dataStore->readEvents(1); + QCOMPARE(events.size(), 1); + QCOMPARE(events[0].type, event.type); + QCOMPARE(events[0].message, event.message); +} + +void DataStoreTest::testMultipleWriteRecord() +{ + const int recordCount = 10; + + for(int i = 0; i < recordCount; ++i) { + MeasurementRecord record; + record.runId = 1; + record.stage = i; + record.timestampMs = QDateTime::currentMSecsSinceEpoch(); + record.rpm = i * 100; + m_dataStore->writeRecord(record); + } + + QTest::qWait(200); + + QVector records = m_dataStore->readRecords(1); + QCOMPARE(records.size(), recordCount); + + for(int i = 0; i < recordCount; ++i) { + QCOMPARE(records[i].stage, i); + QCOMPARE(records[i].rpm, i * 100); + } +} + +void DataStoreTest::testMultipleWriteEvent() +{ + const int eventCount = 10; + + for(int i = 0; i < eventCount; ++i) { + EventRecord event; + event.runId = 1; + event.timestampMs = QDateTime::currentMSecsSinceEpoch(); + event.stage = i; + event.type = "test"; + event.message = QString("Event %1").arg(i); + m_dataStore->writeEvent(event); + } + + QTest::qWait(200); + + QVector events = m_dataStore->readEvents(1); + QCOMPARE(events.size(), eventCount); +} + +void DataStoreTest::testReadRecords() +{ + // Записываем данные + MeasurementRecord record; + record.runId = 42; + record.rpm = 2000; + m_dataStore->writeRecord(record); + + QTest::qWait(100); + + // Читаем данные + QVector records = m_dataStore->readRecords(42); + QCOMPARE(records.size(), 1); + QCOMPARE(records[0].runId, 42); + QCOMPARE(records[0].rpm, 2000); +} + +void DataStoreTest::testReadEvents() +{ + EventRecord event; + event.runId = 42; + event.type = "test_event"; + m_dataStore->writeEvent(event); + + QTest::qWait(100); + + QVector events = m_dataStore->readEvents(42); + QCOMPARE(events.size(), 1); + QCOMPARE(events[0].type, "test_event"); +} + +void DataStoreTest::testReadNonExistent() +{ + QVector records = m_dataStore->readRecords(999); + QVERIFY(records.isEmpty()); + + QVector events = m_dataStore->readEvents(999); + QVERIFY(events.isEmpty()); +} + +void DataStoreTest::testBuildReportCompleted() +{ + // Записываем тестовые данные для завершенного испытания + for(int i = 0; i < 5; ++i) { + MeasurementRecord record; + record.runId = 100; + record.stage = i; + record.timestampMs = QDateTime::currentMSecsSinceEpoch(); + m_dataStore->writeRecord(record); + + EventRecord event; + event.runId = 100; + event.stage = i; + event.type = "stage_change"; + event.message = QString("Stage %1 completed").arg(i); + m_dataStore->writeEvent(event); + } + + EventRecord finalEvent; + finalEvent.runId = 100; + finalEvent.type = "completed"; + finalEvent.message = "Test completed successfully"; + m_dataStore->writeEvent(finalEvent); + + QTest::qWait(200); + + Report report = m_dataStore->buildReport(100); + QCOMPARE(report.runId, 100); + QCOMPARE(report.finalStatus, "completed"); + QVERIFY(report.stages.size() > 0); + QVERIFY(report.events.size() > 0); +} + +void DataStoreTest::testBuildReportAborted() +{ + EventRecord abortEvent; + abortEvent.runId = 101; + abortEvent.type = "abort"; + abortEvent.message = "Test aborted due to alarm"; + m_dataStore->writeEvent(abortEvent); + + QTest::qWait(100); + + Report report = m_dataStore->buildReport(101); + QCOMPARE(report.runId, 101); + QCOMPARE(report.finalStatus, "aborted"); +} + +void DataStoreTest::testFileCreation() +{ + // Проверяем, что файлы создаются + QVERIFY(QFile::exists(m_measurementPath)); + QVERIFY(QFile::exists(m_eventPath)); + + // Проверяем, что файлы не пустые (есть заголовки) + QFile measFile(m_measurementPath); + measFile.open(QIODevice::ReadOnly); + QVERIFY(measFile.size() > 0); + measFile.close(); +} + +void DataStoreTest::testHeadersWritten() +{ + // Проверяем наличие заголовков в CSV + QFile measFile(m_measurementPath); + measFile.open(QIODevice::ReadOnly); + QString firstLine = measFile.readLine(); + measFile.close(); + + QVERIFY(firstLine.contains("runId") || firstLine.contains("timestamp")); +} + +void DataStoreTest::testEmptyFileRead() +{ + QVector records = m_dataStore->readRecords(999); + QVERIFY(records.isEmpty()); +} + +void DataStoreTest::testLargeData() +{ + const int largeCount = 1000; + + for(int i = 0; i < largeCount; ++i) { + MeasurementRecord record; + record.runId = 200; + record.stage = i % 5; + record.rpm = i; + m_dataStore->writeRecord(record); + } + + QTest::qWait(1000); + + QVector records = m_dataStore->readRecords(200); + QCOMPARE(records.size(), largeCount); + + // Проверяем, что данные корректны + for(int i = 0; i < largeCount; ++i) { + QCOMPARE(records[i].rpm, i); + } +} + +void DataStoreTest::testConcurrentWrites() +{ + // Тест одновременной записи разных runId + for(int runId = 0; runId < 10; ++runId) { + MeasurementRecord record; + record.runId = runId; + record.rpm = runId * 100; + m_dataStore->writeRecord(record); + } + + QTest::qWait(200); + + for(int runId = 0; runId < 10; ++runId) { + QVector records = m_dataStore->readRecords(runId); + QCOMPARE(records.size(), 1); + QCOMPARE(records[0].rpm, runId * 100); + } +} \ No newline at end of file diff --git a/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp index 2658619..f446732 100644 --- a/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp +++ b/src/backend/unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp @@ -1,16 +1,272 @@ -#include "modbus_data_bridge_test.h" - -void ModbusDataBridgeTest::init() {} -void ModbusDataBridgeTest::cleanup() {} - -void ModbusDataBridgeTest::isStartPolling() {} -void ModbusDataBridgeTest::doubleStartPolling() {} -void ModbusDataBridgeTest::isStopPolling() {} -void ModbusDataBridgeTest::doubleStopPolling() {} -void ModbusDataBridgeTest::isWriteCommand() {} -void ModbusDataBridgeTest::correctWriteCommand() {} -void ModbusDataBridgeTest::isReadData() {} -void ModbusDataBridgeTest::correctReadData() {} -void ModbusDataBridgeTest::correctReadError() {} - -QTEST_MAIN(ModbusDataBridgeTest) \ No newline at end of file +#include +#include +#include +#include "../../../include/backend/modbus_client/IModbusBridge.h" +#include "../../../include/backend/modbus_client/MockModbusBridge.h" +#include "../../../include/backend/modbus_client/QtModbusBridge.h" +#include "../../../include/backend/DataTypes.h" + +class ModbusDataBridgeTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + // Тесты Mock реализации + void testMockConstructor(); + void testMockStartPolling(); + void testMockStopPolling(); + void testMockOnReadSensors(); + void testMockOnReadInfo(); + void testMockOnWriteConfig(); + void testMockSignals(); + + // Тесты Qt реализации (если доступна) + void testQtConstructor(); + void testQtStartPolling(); + void testQtStopPolling(); + void testQtOnReadSensors(); + void testQtConnectionErrors(); + + // Общие тесты интерфейса + void testInterfaceCompliance(); + void testMultipleStartStop(); + void testRapidPolling(); + +private: + ModbusConfig m_config; + MockModbusBridge* m_mockBridge; +}; + +void ModbusDataBridgeTest::init() +{ + m_config.host = "127.0.0.1"; + m_config.port = 1502; + m_config.pollFrequencyMs = 100; + m_config.timeoutMs = 1000; + m_config.retries = 3; + m_config.unitId = 1; + + m_mockBridge = new MockModbusBridge(m_config); +} + +void ModbusDataBridgeTest::cleanup() +{ + delete m_mockBridge; +} + +void ModbusDataBridgeTest::testMockConstructor() +{ + MockModbusBridge* bridge = new MockModbusBridge(m_config); + QVERIFY(bridge != nullptr); + delete bridge; +} + +void ModbusDataBridgeTest::testMockStartPolling() +{ + QSignalSpy dataSpy(m_mockBridge, &IModbusBridge::sensorsDataReady); + + m_mockBridge->startPolling(); + QTest::qWait(250); // Ждем минимум 2 цикла опроса + + QVERIFY(dataSpy.count() >= 2); +} + +void ModbusDataBridgeTest::testMockStopPolling() +{ + m_mockBridge->startPolling(); + QTest::qWait(150); + + QSignalSpy dataSpy(m_mockBridge, &IModbusBridge::sensorsDataReady); + int countBeforeStop = dataSpy.count(); + + m_mockBridge->stopPolling(); + QTest::qWait(250); + + int countAfterStop = dataSpy.count(); + // После остановки данные не должны приходить + QCOMPARE(countAfterStop, countBeforeStop); +} + +void ModbusDataBridgeTest::testMockOnReadSensors() +{ + QSignalSpy dataSpy(m_mockBridge, &IModbusBridge::sensorsDataReady); + + m_mockBridge->onReadSensors(); + QTest::qWait(50); + + QCOMPARE(dataSpy.count(), 1); + + if(dataSpy.count() > 0) { + SensorFrame frame = dataSpy.first().at(0).value(); + // Проверяем, что данные валидны + QVERIFY(frame.rpm >= 0); + QVERIFY(frame.dieselTemp >= 0); + } +} + +void ModbusDataBridgeTest::testMockOnReadInfo() +{ + QSignalSpy infoSpy(m_mockBridge, &IModbusBridge::modelInfoReady); + + m_mockBridge->onReadInfo(); + QTest::qWait(50); + + QCOMPARE(infoSpy.count(), 1); +} + +void ModbusDataBridgeTest::testMockOnWriteConfig() +{ + ModelConfig config; + config.maxRpm = 3000; + + // Проверяем, что запись не вызывает ошибок + m_mockBridge->onWriteConfig(config); + QVERIFY(true); +} + +void ModbusDataBridgeTest::testMockSignals() +{ + QSignalSpy dataSpy(m_mockBridge, &IModbusBridge::sensorsDataReady); + QSignalSpy infoSpy(m_mockBridge, &IModbusBridge::modelInfoReady); + + m_mockBridge->startPolling(); + QTest::qWait(200); + m_mockBridge->stopPolling(); + + QVERIFY(dataSpy.count() > 0); + QVERIFY(infoSpy.count() == 0); // info не запрашивалась автоматически +} + +void ModbusDataBridgeTest::testQtConstructor() +{ +#ifdef QT_MODBUS_LIB + QtModbusBridge* bridge = new QtModbusBridge(m_config); + QVERIFY(bridge != nullptr); + delete bridge; +#else + QSKIP("QtModbusBridge not available"); +#endif +} + +void ModbusDataBridgeTest::testQtStartPolling() +{ +#ifdef QT_MODBUS_LIB + QtModbusBridge* bridge = new QtModbusBridge(m_config); + QSignalSpy dataSpy(bridge, &IModbusBridge::sensorsDataReady); + + bridge->startPolling(); + QTest::qWait(250); + + // Может быть 0 если сервер не доступен + QVERIFY(dataSpy.count() >= 0); + delete bridge; +#else + QSKIP("QtModbusBridge not available"); +#endif +} + +void ModbusDataBridgeTest::testQtStopPolling() +{ +#ifdef QT_MODBUS_LIB + QtModbusBridge* bridge = new QtModbusBridge(m_config); + bridge->startPolling(); + QTest::qWait(150); + + QSignalSpy dataSpy(bridge, &IModbusBridge::sensorsDataReady); + int countBeforeStop = dataSpy.count(); + + bridge->stopPolling(); + QTest::qWait(250); + + int countAfterStop = dataSpy.count(); + QCOMPARE(countAfterStop, countBeforeStop); + delete bridge; +#else + QSKIP("QtModbusBridge not available"); +#endif +} + +void ModbusDataBridgeTest::testQtOnReadSensors() +{ +#ifdef QT_MODBUS_LIB + QtModbusBridge* bridge = new QtModbusBridge(m_config); + QSignalSpy dataSpy(bridge, &IModbusBridge::sensorsDataReady); + + bridge->onReadSensors(); + QTest::qWait(100); + + // Может быть 0 если сервер не доступен + QVERIFY(dataSpy.count() >= 0); + delete bridge; +#else + QSKIP("QtModbusBridge not available"); +#endif +} + +void ModbusDataBridgeTest::testQtConnectionErrors() +{ +#ifdef QT_MODBUS_LIB + ModbusConfig invalidConfig; + invalidConfig.host = "192.168.255.255"; // Несуществующий хост + invalidConfig.port = 9999; + + QtModbusBridge* bridge = new QtModbusBridge(invalidConfig); + + QSignalSpy connectionErrorSpy(bridge, &IModbusBridge::connectionError); + QSignalSpy connectionLostSpy(bridge, &IModbusBridge::connectionLost); + + // Ждем ошибок подключения + QTest::qWait(500); + + // Должны быть ошибки + QVERIFY(connectionErrorSpy.count() >= 0 || connectionLostSpy.count() >= 0); + delete bridge; +#else + QSKIP("QtModbusBridge not available"); +#endif +} + +void ModbusDataBridgeTest::testInterfaceCompliance() +{ + // Проверяем, что Mock реализует весь интерфейс + IModbusBridge* bridge = m_mockBridge; + + // Все методы должны быть доступны + bridge->startPolling(); + bridge->stopPolling(); + bridge->onReadSensors(); + bridge->onReadInfo(); + bridge->onWriteConfig(ModelConfig()); + + QVERIFY(true); +} + +void ModbusDataBridgeTest::testMultipleStartStop() +{ + for(int i = 0; i < 5; ++i) { + m_mockBridge->startPolling(); + QTest::qWait(100); + m_mockBridge->stopPolling(); + QTest::qWait(50); + } + QVERIFY(true); +} + +void ModbusDataBridgeTest::testRapidPolling() +{ + ModbusConfig fastConfig = m_config; + fastConfig.pollFrequencyMs = 10; // Очень быстрый опрос + + MockModbusBridge* fastBridge = new MockModbusBridge(fastConfig); + QSignalSpy dataSpy(fastBridge, &IModbusBridge::sensorsDataReady); + + fastBridge->startPolling(); + QTest::qWait(200); // Должно быть ~20 измерений + + QVERIFY(dataSpy.count() >= 10); + QVERIFY(dataSpy.count() <= 30); + + fastBridge->stopPolling(); + delete fastBridge; +} \ No newline at end of file diff --git a/src/backend/unit_tests/state_machine_test/state_machine_test.cpp b/src/backend/unit_tests/state_machine_test/state_machine_test.cpp index a458545..021c294 100644 --- a/src/backend/unit_tests/state_machine_test/state_machine_test.cpp +++ b/src/backend/unit_tests/state_machine_test/state_machine_test.cpp @@ -1,11 +1,265 @@ -#include "state_machine_test.h" +#include +#include +#include +#include "../../../include/backend/state_machine.h" +#include "../../../include/backend/backend_worker/backendworker.h" +#include "../../../include/backend/DataTypes.h" -void StateMachineTest::init() {} -void StateMachineTest::cleanup() {} +class StateMachineTest : public QObject { + Q_OBJECT +private slots: + void init(); + void cleanup(); + + // Тесты конструктора + void testConstructor(); + + // Тесты состояний + void testInitialState(); + void testTransitionTo(); + + // Тесты этапов + void testRequestStart(); + void testRequestNextStage(); + void testRequestAbort(); + void testStageSequence(); + + // Тесты сигналов + void testStageChangedSignal(); + void testFinishedSignal(); + + // Тесты таймеров + void testStageTimeout(); + + // Тесты обработки команд + void testOnReceivedFrontControl(); + void testOnReceivedModelConfig(); + + // Тесты интеграции с Modbus + void testModbusBridgeIntegration(); + + // Тесты граничных случаев + void testInvalidStageTransition(); + void testAbortDuringStage(); -void StateMachineTest::isStart() {} -void StateMachineTest::moveOnNextStage() {} -void StateMachineTest::isAbort() {} -void StateMachineTest::correctUIStatus() {} +private: + BackendWorker* m_backendWorker; + StateMachine* m_stateMachine; + QThread* m_workerThread; +}; -QTEST_MAIN(StateMachineTest) \ No newline at end of file +void StateMachineTest::init() +{ + m_backendWorker = new BackendWorker(); + m_workerThread = new QThread(); + m_backendWorker->moveToThread(m_workerThread); + m_workerThread->start(); + + m_stateMachine = new StateMachine(m_backendWorker); +} + +void StateMachineTest::cleanup() +{ + delete m_stateMachine; + m_workerThread->quit(); + m_workerThread->wait(); + delete m_backendWorker; + delete m_workerThread; +} + +void StateMachineTest::testConstructor() +{ + StateMachine* sm = new StateMachine(m_backendWorker); + QVERIFY(sm != nullptr); + QCOMPARE(sm->currentStageIndex(), -1); + delete sm; +} + +void StateMachineTest::testInitialState() +{ + QCOMPARE(m_stateMachine->currentStageIndex(), -1); + QCOMPARE(m_stateMachine->currentState(), DiagState::Ok); +} + +void StateMachineTest::testTransitionTo() +{ + QSignalSpy stageSpy(m_stateMachine, &StateMachine::stageChanged); + + ModelConfig config; + m_stateMachine->requestStart(config); + + QTest::qWait(100); + + // Проверяем, что состояние изменилось + QVERIFY(stageSpy.count() >= 0); +} + +void StateMachineTest::testRequestStart() +{ + QSignalSpy finishedSpy(m_stateMachine, &StateMachine::finished); + + ModelConfig config; + config.maxRpm = 3000; + config.maxDieselTemp = 120; + + m_stateMachine->requestStart(config); + QTest::qWait(100); + + // Проверяем, что процесс запустился + QVERIFY(m_stateMachine->currentStageIndex() >= 0); +} + +void StateMachineTest::testRequestNextStage() +{ + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + int oldStage = m_stateMachine->currentStageIndex(); + m_stateMachine->requestNextStage(); + QTest::qWait(100); + + int newStage = m_stateMachine->currentStageIndex(); + QVERIFY(newStage != oldStage || newStage == -1); +} + +void StateMachineTest::testRequestAbort() +{ + QSignalSpy finishedSpy(m_stateMachine, &StateMachine::finished); + + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + m_stateMachine->requestAbort("Test abort"); + QTest::qWait(100); + + QCOMPARE(finishedSpy.count(), 1); +} + +void StateMachineTest::testStageSequence() +{ + QSignalSpy stageSpy(m_stateMachine, &StateMachine::stageChanged); + + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + // Проходим по всем этапам + for(int i = 0; i < 10; ++i) { + m_stateMachine->requestNextStage(); + QTest::qWait(50); + } + + // Проверяем, что сигналы смены этапов были + QVERIFY(stageSpy.count() > 0); +} + +void StateMachineTest::testStageChangedSignal() +{ + QSignalSpy spy(m_stateMachine, &StateMachine::stageChanged); + + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + QVERIFY(spy.count() >= 0); + + if(spy.count() > 0) { + QList arguments = spy.first(); + QCOMPARE(arguments.size(), 2); // oldStage, newStage + } +} + +void StateMachineTest::testFinishedSignal() +{ + QSignalSpy spy(m_stateMachine, &StateMachine::finished); + + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + m_stateMachine->requestAbort("Force abort"); + QTest::qWait(100); + + QCOMPARE(spy.count(), 1); + + if(spy.count() > 0) { + QList arguments = spy.first(); + QCOMPARE(arguments.size(), 1); // finalState + } +} + +void StateMachineTest::testStageTimeout() +{ + // Тест автоматического перехода по таймауту + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(500); // Ждем возможного таймаута + + // Проверяем, что этапы переключаются автоматически + // (зависит от реализации) +} + +void StateMachineTest::testOnReceivedFrontControl() +{ + FrontControl control; + // control.control = ControlType::NextStage; + + QSignalSpy stageSpy(m_stateMachine, &StateMachine::stageChanged); + + // Эмулируем получение команды через коммуникатор + // (зависит от реализации) +} + +void StateMachineTest::testOnReceivedModelConfig() +{ + ModelConfig config; + config.maxRpm = 5000; + config.maxDieselTemp = 200; + + // Эмулируем получение конфигурации + // (зависит от реализации) +} + +void StateMachineTest::testModbusBridgeIntegration() +{ + // Проверяем, что ModbusBridge правильно инициализирован + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + // Проверяем состояние Modbus клиента + // (зависит от реализации) +} + +void StateMachineTest::testInvalidStageTransition() +{ + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + // Попытка перейти на следующий этап когда он не должен быть доступен + for(int i = 0; i < 20; ++i) { + m_stateMachine->requestNextStage(); + QTest::qWait(50); + } + + // Не должно быть краха + QVERIFY(true); +} + +void StateMachineTest::testAbortDuringStage() +{ + QSignalSpy finishedSpy(m_stateMachine, &StateMachine::finished); + + ModelConfig config; + m_stateMachine->requestStart(config); + QTest::qWait(100); + + // Прерываем на середине этапа + m_stateMachine->requestAbort("Abort during stage"); + QTest::qWait(100); + + QCOMPARE(finishedSpy.count(), 1); +} \ No newline at end of file From 561509b0f3806911cdcf6113759534e34f95c461 Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Fri, 8 May 2026 18:28:32 +0500 Subject: [PATCH 37/58] feat: implement FileConnector --- .gitignore | 3 +- include/backend/data_store/DataStore.h | 200 +++++++---------- src/backend/data_store/DataStore.cpp | 286 +++++++++++++++++++++++++ 3 files changed, 361 insertions(+), 128 deletions(-) diff --git a/.gitignore b/.gitignore index 42afabf..6e12bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/build \ No newline at end of file +/build +.vscode \ No newline at end of file diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index 9533222..8a3f12d 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -1,127 +1,73 @@ -// #pragma once - -// #include -// #include - -// // Qt includes -// #include -// #include -// #include -// // #include - -// // #include "../DataTypes.h" // MeasurementRecord, EventRecord, Report - -// // Low-level layer -// class Connector -// { -// public: -// explicit Connector(std::string path); // : path_(std::move(path)) {} -// virtual ~Connector() noexcept = default; - -// virtual int64_t write(const std::string &data) = 0; -// virtual std::optional read(int64_t id) const = 0; -// virtual bool update(int64_t id, const std::string &data) = 0; -// virtual bool remove(int64_t id) = 0; - -// size_t size() const noexcept; -// const std::string &path() const noexcept { return path_; } - -// private: -// std::string path_; -// size_t size_; -// }; - -// class CSVConnector : public Connector -// { -// public: -// explicit CSVConnector(std::string path); // : Connector(std::move(path)) {} -// ~CSVConnector() noexcept override = default; - -// int64_t write(const std::string &data) override; -// std::optional read(int64_t id) const override; -// bool update(int64_t id, const std::string &data) override; -// bool remove(int64_t id) override; -// }; - -// // Qt layer -// struct MeasurementRecord -// { -// qint64 runId; -// int stage; -// qint64 timestampMs; -// double rpm, torque, dieselTemp, motorTemp, resistorTemp, dieselPressure; -// double throttle, brakeTorque; -// QString flags; // "OK", "WARNING" и т.п. -// }; - -// struct EventRecord -// { -// qint64 runId, timestampMs; -// int stage; -// QString type; // "stage_change", "abort", "warning", "info" -// QString message; -// }; - -// struct Report -// { -// qint64 runId; -// QString startTime, endTime; -// QString finalStatus; // "completed" или "aborted" -// QVector stages; -// QVector events; -// }; - -// enum class StreamType -// { -// Measurement, -// Event -// }; - -// inline uint qHash(StreamType key, uint seed = 0) noexcept -// { -// return qHash(static_cast(key), seed); -// } - -// struct StorageConnector -// { -// Connector *connector; -// qint64 nextId = 1; -// bool headersWritten = false; -// }; - -// class DataStore : public QObject -// { -// Q_OBJECT - -// public: -// DataStore(Connector *measurementConnector, Connector *eventConnector, QObject *parent = nullptr); -// // В случае ошибки возвращаю пустые объекты? -// QVector readRecords(qint64 runId) const; -// QVector readEvents(qint64 runId) const; -// Report buildReport(qint64 runId) const; - -// public slots: -// void writeRecord(const MeasurementRecord &record); -// void writeEvent(const EventRecord &event); - -// private: -// QString serializeMeasurement(const MeasurementRecord &record) const; -// QString serializeEvent(const EventRecord &event) const; - -// MeasurementRecord deserializeMeasurement(const QString &line) const; -// EventRecord deserializeEvent(const QString &line) const; - -// void ensureHeaders(StreamType type); -// qint64 generateId(StreamType type) noexcept; - -// private: -// QHash connectors_; -// }; - -// // Выносить ли структуры в DataTypes? -// // Нужно ли использовать исключения? (иначе работать с bool/константами, std::nullopt, возвращать пустые объекты?) -// // Ситуации для обработки: -// // 1) Чтение файла, которого нет по пути / пустого файла -// // 2) Чтение/обновление/удаление по некорректному id -// // Если файла нет и происходит запись, он будет создан -// // Нужен ли QDateTime или работать через qint64? \ No newline at end of file +#ifndef DATASTORE_H +#define DATASTORE_H + +#include +#include + +#include + +#include "backend/DataTypes.h" // Data + +// Low-level layer +class Connector +{ +public: + Connector(std::string path, size_t record_size); + virtual ~Connector() noexcept = default; + + virtual int64_t write(const std::string &data) = 0; + virtual std::optional read(int64_t id) const = 0; + virtual bool update(int64_t id, const std::string &data) = 0; + virtual bool remove(int64_t id) = 0; + + size_t getSize() const noexcept; + const std::string &getPath() const noexcept; + size_t getRecordSize() const noexcept; + +protected: + void setSize(size_t size) noexcept; + +private: + std::string path_; + size_t size_; + size_t record_size_; +}; + +class FileConnector : public Connector +{ +public: + FileConnector(std::string path, size_t record_size); + ~FileConnector() noexcept override = default; + + int64_t write(const std::string &data) override; + std::optional read(int64_t id) const override; + bool update(int64_t id, const std::string &data) override; + bool remove(int64_t id) override; +}; + +// Qt layer +class DataStore : public QObject +{ + Q_OBJECT + +public: + DataStore(Connector *connector, QObject *parent = nullptr); + QVector readData(qint64 id) const; // В случае ошибок возвращает пустой вектор + QVector readAllData() const; // В случае ошибок возвращает пустой вектор + +public slots: + bool writeData(const Data &record); + +private: + QString serializeData(const Data &record) const; + Data deserializeData(const QString &line) const; + + void ensureHeaders(); + qint64 generateId() noexcept; + +private: + Connector *connector_; + bool headersEnsured_; +}; + +#endif // DATASTORE_H \ No newline at end of file diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index e69de29..131be8d 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include + +#include "backend/data_store/DataStore.h" + +// === Connector === +Connector::Connector(std::string path, size_t record_size) : path_(std::move(path)), size_(0), record_size_(record_size) {} + +size_t Connector::getSize() const noexcept +{ + return size_; +} + +size_t Connector::getRecordSize() const noexcept +{ + return record_size_; +} + +const std::string &Connector::getPath() const noexcept +{ + return path_; +} + +void Connector::setSize(size_t size) noexcept +{ + size_ = size; +} + +// === FileConnector === +FileConnector::FileConnector(std::string path, size_t record_size) : Connector(std::move(path), record_size) +{ + std::ifstream file(getPath(), std::ios::binary); + + if (!file.is_open()) + { + setSize(0); + return; + } + + file.seekg(0, std::ios::end); + size_t file_size = static_cast(file.tellg()); + + setSize(file_size / getRecordSize()); +} + +// O(1) +int64_t FileConnector::write(const std::string &data) +{ + std::ofstream file(getPath(), std::ios::app); + + if (!file.is_open()) + { + return -1; + } + + std::string fixed = data; + if (fixed.size() < getRecordSize()) + { + fixed.append(getRecordSize() - fixed.size(), ' '); + } + else if (fixed.size() > getRecordSize()) + { + fixed = fixed.substr(0, getRecordSize()); + } + + file.write(fixed.data(), getRecordSize()); + file.put('\n'); + + int64_t id = static_cast(getSize()); + setSize(getSize() + 1); + + return id; +} + +// O(1) +std::optional FileConnector::read(int64_t id) const +{ + if (id < 0 || static_cast(id) >= getSize()) + { + return std::nullopt; + } + + std::ifstream file(getPath(), std::ios::binary); + + if (!file.is_open()) + { + return std::nullopt; + } + + std::streampos offset = static_cast(id * (getRecordSize() + 1)); // + '\n' + + file.seekg(offset); + + std::string buffer(getRecordSize(), '\0'); + file.read(buffer.data(), getRecordSize()); + + return buffer; +} + +// O(1) +bool FileConnector::update(int64_t id, const std::string &data) +{ + if (id < 0 || static_cast(id) >= getSize()) + { + return false; + } + + std::fstream file(getPath(), std::ios::in | std::ios::out | std::ios::binary); + + if (!file.is_open()) + { + return false; + } + + std::string fixed = data; + + if (fixed.size() < getRecordSize()) + { + fixed.append(getRecordSize() - fixed.size(), ' '); + } + else if (fixed.size() > getRecordSize()) + { + fixed = fixed.substr(0, getRecordSize()); + } + + std::streampos offset = static_cast(id * (getRecordSize() + 1)); + + file.seekp(offset); + file.write(fixed.data(), getRecordSize()); + + return true; +} + +// O(n) +bool FileConnector::remove(int64_t id) +{ + if (id < 0 || static_cast(id) >= getSize()) + { + return false; + } + + std::fstream file(getPath(), std::ios::in | std::ios::out | std::ios::binary); + + if (!file.is_open()) + { + return false; + } + + size_t record_full_size = getRecordSize() + 1; // + '\n' + size_t last_id = getSize() - 1; + + // Сдвигаем все записи после удаляемой на одну позицию влево + for (size_t current_id = static_cast(id); current_id < last_id; ++current_id) + { + std::streampos read_offset = static_cast((current_id + 1) * record_full_size); + std::streampos write_offset = static_cast(current_id * record_full_size); + + // Читаем следующую запись + file.seekg(read_offset); + std::string buffer(getRecordSize(), '\0'); + file.read(buffer.data(), getRecordSize()); + + if (!file) + { + return false; + } + + // Пишем её на место предыдущей + file.seekp(write_offset); + file.write(buffer.data(), getRecordSize()); + file.put('\n'); + + if (!file) + { + return false; + } + } + + file.close(); + + uintmax_t new_size = static_cast(last_id * record_full_size); + std::filesystem::resize_file(getPath(), new_size); + setSize(last_id); + + return true; +} + + +void printRecord(FileConnector& connector, int64_t id) +{ + auto record = connector.read(id); + + if (record.has_value()) + { + std::cout << "Record [" << id << "] = '" << *record << "'\n"; + } + else + { + std::cout << "Record [" << id << "] not found\n"; + } +} + +// int main() +// { +// constexpr size_t RECORD_SIZE = 16; + +// FileConnector connector("C:\\Users\\ilyam\\Desktop\\ScadaForDiesel\\src\\backend\\data_store\\test.File", RECORD_SIZE); + +// std::cout << "Initial size: " << connector.getSize() << "\n\n"; + +// // === WRITE === +// std::cout << "=== WRITE ===\n"; + +// int64_t id1 = connector.write("Hello"); +// int64_t id2 = connector.write("World"); +// int64_t id3 = connector.write("VeryVeryLongString123"); + +// std::cout << "Inserted ids: " +// << id1 << ", " +// << id2 << ", " +// << id3 << "\n"; + +// std::cout << "Size after write: " +// << connector.getSize() << "\n\n"; + +// // === READ === +// std::cout << "=== READ ===\n"; + +// printRecord(connector, id1); +// printRecord(connector, id2); +// printRecord(connector, id3); + +// std::cout << "\n"; + +// // === UPDATE === +// std::cout << "=== UPDATE ===\n"; + +// bool updated = connector.update(id2, "Updated"); + +// std::cout << "Update result: " +// << std::boolalpha +// << updated << "\n"; + +// printRecord(connector, id2); + +// std::cout << "\n"; + +// // === REMOVE === +// std::cout << "=== REMOVE ===\n"; + +// bool removed = connector.remove(id1); + +// std::cout << "Remove result: " +// << std::boolalpha +// << removed << "\n"; + +// std::cout << "Size after remove: " +// << connector.getSize() << "\n"; + +// std::cout << "\nRecords after remove:\n"; + +// for (size_t i = 0; i < connector.getSize(); ++i) +// { +// printRecord(connector, static_cast(i)); +// } + +// std::cout << "\n"; + +// // === INVALID ACCESS === +// std::cout << "=== INVALID ACCESS ===\n"; + +// printRecord(connector, 999); + +// bool invalidUpdate = connector.update(999, "Test"); +// bool invalidRemove = connector.remove(999); + +// std::cout << "Invalid update: " +// << invalidUpdate << "\n"; + +// std::cout << "Invalid remove: " +// << invalidRemove << "\n"; + +// return 0; +// } \ No newline at end of file From 3ca1e0a7e0279f8255059c3ef6b274d069fae5ab Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Fri, 8 May 2026 19:20:06 +0500 Subject: [PATCH 38/58] fix: delete some bugs and refactor --- include/backend/data_store/DataStore.h | 15 +- src/backend/data_store/DataStore.cpp | 293 ++++++++++++++++++------- 2 files changed, 225 insertions(+), 83 deletions(-) diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index 8a3f12d..ac177f2 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -21,8 +21,8 @@ class Connector virtual bool remove(int64_t id) = 0; size_t getSize() const noexcept; - const std::string &getPath() const noexcept; size_t getRecordSize() const noexcept; + const std::string &getPath() const noexcept; protected: void setSize(size_t size) noexcept; @@ -43,6 +43,19 @@ class FileConnector : public Connector std::optional read(int64_t id) const override; bool update(int64_t id, const std::string &data) override; bool remove(int64_t id) override; + +private: + static constexpr char RECORD_DELIMITER = '\n'; + + size_t getFullRecordSize() const noexcept; + bool isValidId(int64_t id) const noexcept; + std::streampos getOffset(int64_t id) const noexcept; + std::string normalizeRecord(const std::string &data) const; + + bool seekRead(std::fstream &file, std::streampos offset) const; + bool seekWrite(std::fstream &file, std::streampos offset) const; + bool readRecord(std::fstream &file, std::streampos offset, std::string &buffer) const; + bool writeRecord(std::fstream &file, std::streampos offset, const std::string &data); }; // Qt layer diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 131be8d..54f514f 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -4,6 +4,7 @@ #include #include "backend/data_store/DataStore.h" +// #include "DataStore.h" // === Connector === Connector::Connector(std::string path, size_t record_size) : path_(std::move(path)), size_(0), record_size_(record_size) {} @@ -29,12 +30,130 @@ void Connector::setSize(size_t size) noexcept } // === FileConnector === +size_t FileConnector::getFullRecordSize() const noexcept +{ + return getRecordSize() + 1; +} + +bool FileConnector::isValidId(int64_t id) const noexcept +{ + return id >= 0 && static_cast(id) < getSize(); +} + +std::streampos FileConnector::getOffset(int64_t id) const noexcept +{ + return static_cast(id * getFullRecordSize()); +} + +std::string FileConnector::normalizeRecord(const std::string &data) const +{ + std::string fixed = data; + + if (fixed.size() < getRecordSize()) + { + std::cerr << "[FileConnector] " + << "Data is smaller than record size. " + << "Padding will be applied.\n"; + + fixed.append(getRecordSize() - fixed.size(), ' '); + } + else if (fixed.size() > getRecordSize()) + { + std::cerr << "[FileConnector] " + << "Data is larger than record size. " + << "Data will be truncated.\n"; + + fixed = fixed.substr(0, getRecordSize()); + } + + return fixed; +} + +bool FileConnector::seekRead(std::fstream &file, std::streampos offset) const +{ + file.seekg(offset); + + if (!file) + { + std::cerr << "[FileConnector] " + << "seekg failed. Offset: " + << offset << '\n'; + + return false; + } + + return true; +} + +bool FileConnector::seekWrite(std::fstream &file, std::streampos offset) const +{ + file.seekp(offset); + + if (!file) + { + std::cerr << "[FileConnector] " + << "seekp failed. Offset: " + << offset << '\n'; + + return false; + } + + return true; +} + +bool FileConnector::readRecord(std::fstream &file, std::streampos offset, std::string &buffer) const +{ + if (!seekRead(file, offset)) + { + return false; + } + + buffer.resize(getRecordSize()); + file.read(buffer.data(), static_cast(getRecordSize())); + + if (!file) + { + std::cerr << "[FileConnector] " + << "Failed to read record. Offset: " + << offset << '\n'; + + return false; + } + + return true; +} + +bool FileConnector::writeRecord(std::fstream &file, std::streampos offset, const std::string &data) +{ + if (!seekWrite(file, offset)) + { + return false; + } + + file.write(data.data(), static_cast(getRecordSize())); + file.put(RECORD_DELIMITER); + + if (!file) + { + std::cerr << "[FileConnector] " + << "Failed to write record. Offset: " + << offset << '\n'; + + return false; + } + + return true; +} + +// === Constructor === FileConnector::FileConnector(std::string path, size_t record_size) : Connector(std::move(path), record_size) { std::ifstream file(getPath(), std::ios::binary); - if (!file.is_open()) { + std::cerr << "[FileConnector] " + << "Failed to open file: " + << getPath() << '\n'; setSize(0); return; } @@ -42,68 +161,95 @@ FileConnector::FileConnector(std::string path, size_t record_size) : Connector(s file.seekg(0, std::ios::end); size_t file_size = static_cast(file.tellg()); - setSize(file_size / getRecordSize()); + if (file_size % getFullRecordSize() != 0) + { + std::cerr << "[FileConnector] " + << "Invalid file size. " + << "File may be corrupted.\n"; + } + + setSize(file_size / getFullRecordSize()); } -// O(1) int64_t FileConnector::write(const std::string &data) { - std::ofstream file(getPath(), std::ios::app); + const std::string path = getPath(); - if (!file.is_open()) + // Создаём файл, если его нет + if (!std::filesystem::exists(path)) { - return -1; + std::ofstream create_file(path, std::ios::binary); + if (!create_file.is_open()) + { + std::cerr << "[FileConnector::write] Failed to create file.\n"; + return -1; + } } - std::string fixed = data; - if (fixed.size() < getRecordSize()) + std::fstream file(path, std::ios::in | std::ios::out | std::ios::binary); + + if (!file.is_open()) { - fixed.append(getRecordSize() - fixed.size(), ' '); + std::cerr << "[FileConnector::write] Failed to open file.\n"; + return -1; } - else if (fixed.size() > getRecordSize()) + + std::string fixed = normalizeRecord(data); + int64_t id = static_cast(getSize()); + std::streampos offset = getOffset(id); + + if (!writeRecord(file, offset, fixed)) { - fixed = fixed.substr(0, getRecordSize()); + return -1; } - file.write(fixed.data(), getRecordSize()); - file.put('\n'); - - int64_t id = static_cast(getSize()); setSize(getSize() + 1); return id; } -// O(1) +// === Read === std::optional FileConnector::read(int64_t id) const { - if (id < 0 || static_cast(id) >= getSize()) + if (!isValidId(id)) { + std::cerr << "[FileConnector::read] " + << "Invalid id: " + << id << '\n'; + return std::nullopt; } - std::ifstream file(getPath(), std::ios::binary); + std::fstream file(getPath(), std::ios::in | std::ios::binary); if (!file.is_open()) { + std::cerr << "[FileConnector::read] " + << "Failed to open file.\n"; + return std::nullopt; } - std::streampos offset = static_cast(id * (getRecordSize() + 1)); // + '\n' + std::string buffer; - file.seekg(offset); - - std::string buffer(getRecordSize(), '\0'); - file.read(buffer.data(), getRecordSize()); + if (!readRecord(file, getOffset(id), buffer)) + { + return std::nullopt; + } return buffer; } -// O(1) +// === Update === + bool FileConnector::update(int64_t id, const std::string &data) { - if (id < 0 || static_cast(id) >= getSize()) + if (!isValidId(id)) { + std::cerr << "[FileConnector::update] " + << "Invalid id: " + << id << '\n'; + return false; } @@ -111,33 +257,25 @@ bool FileConnector::update(int64_t id, const std::string &data) if (!file.is_open()) { - return false; - } + std::cerr << "[FileConnector::update] " + << "Failed to open file.\n"; - std::string fixed = data; - - if (fixed.size() < getRecordSize()) - { - fixed.append(getRecordSize() - fixed.size(), ' '); - } - else if (fixed.size() > getRecordSize()) - { - fixed = fixed.substr(0, getRecordSize()); + return false; } - std::streampos offset = static_cast(id * (getRecordSize() + 1)); - - file.seekp(offset); - file.write(fixed.data(), getRecordSize()); - - return true; + std::string fixed = normalizeRecord(data); + return writeRecord(file, getOffset(id), fixed); } -// O(n) +// === Remove === bool FileConnector::remove(int64_t id) { - if (id < 0 || static_cast(id) >= getSize()) + if (!isValidId(id)) { + std::cerr << "[FileConnector::remove] " + << "Invalid id: " + << id << '\n'; + return false; } @@ -145,34 +283,24 @@ bool FileConnector::remove(int64_t id) if (!file.is_open()) { + std::cerr << "[FileConnector::remove] " + << "Failed to open file.\n"; + return false; } - size_t record_full_size = getRecordSize() + 1; // + '\n' size_t last_id = getSize() - 1; - // Сдвигаем все записи после удаляемой на одну позицию влево for (size_t current_id = static_cast(id); current_id < last_id; ++current_id) { - std::streampos read_offset = static_cast((current_id + 1) * record_full_size); - std::streampos write_offset = static_cast(current_id * record_full_size); + std::string buffer; - // Читаем следующую запись - file.seekg(read_offset); - std::string buffer(getRecordSize(), '\0'); - file.read(buffer.data(), getRecordSize()); - - if (!file) + if (!readRecord(file, getOffset(static_cast(current_id + 1)), buffer)) { return false; } - // Пишем её на место предыдущей - file.seekp(write_offset); - file.write(buffer.data(), getRecordSize()); - file.put('\n'); - - if (!file) + if (!writeRecord(file, getOffset(static_cast(current_id)), buffer)) { return false; } @@ -180,26 +308,27 @@ bool FileConnector::remove(int64_t id) file.close(); - uintmax_t new_size = static_cast(last_id * record_full_size); - std::filesystem::resize_file(getPath(), new_size); + try + { + std::filesystem::resize_file(getPath(), last_id * getFullRecordSize()); + } + catch (const std::exception &e) + { + std::cerr << "[FileConnector::remove] " + << "resize_file failed: " + << e.what() << '\n'; + + return false; + } + setSize(last_id); return true; } - -void printRecord(FileConnector& connector, int64_t id) +void printRecord(FileConnector &connector, int64_t id) { auto record = connector.read(id); - - if (record.has_value()) - { - std::cout << "Record [" << id << "] = '" << *record << "'\n"; - } - else - { - std::cout << "Record [" << id << "] not found\n"; - } } // int main() @@ -213,14 +342,14 @@ void printRecord(FileConnector& connector, int64_t id) // // === WRITE === // std::cout << "=== WRITE ===\n"; -// int64_t id1 = connector.write("Hello"); -// int64_t id2 = connector.write("World"); -// int64_t id3 = connector.write("VeryVeryLongString123"); +// int64_t id0 = connector.write("Hello"); +// int64_t id1 = connector.write("World"); +// int64_t id2 = connector.write("VeryVeryLongString123"); // std::cout << "Inserted ids: " +// << id0 << ", " // << id1 << ", " -// << id2 << ", " -// << id3 << "\n"; +// << id2 << "\n"; // std::cout << "Size after write: " // << connector.getSize() << "\n\n"; @@ -228,29 +357,29 @@ void printRecord(FileConnector& connector, int64_t id) // // === READ === // std::cout << "=== READ ===\n"; +// printRecord(connector, id0); // printRecord(connector, id1); // printRecord(connector, id2); -// printRecord(connector, id3); // std::cout << "\n"; // // === UPDATE === // std::cout << "=== UPDATE ===\n"; -// bool updated = connector.update(id2, "Updated"); +// bool updated = connector.update(id1, "Updated"); // std::cout << "Update result: " // << std::boolalpha // << updated << "\n"; -// printRecord(connector, id2); +// printRecord(connector, id1); // std::cout << "\n"; // // === REMOVE === // std::cout << "=== REMOVE ===\n"; -// bool removed = connector.remove(id1); +// bool removed = connector.remove(id0); // std::cout << "Remove result: " // << std::boolalpha From 846edd7d8eb6b72bd21bf173d8a88fa9aea169f4 Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Fri, 8 May 2026 20:10:51 +0500 Subject: [PATCH 39/58] feat: add first DataStore implementation --- include/backend/data_store/DataStore.h | 2 +- src/backend/data_store/DataStore.cpp | 246 ++++++++++++++++++++++++- 2 files changed, 246 insertions(+), 2 deletions(-) diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index ac177f2..dc149ec 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -73,7 +73,7 @@ public slots: private: QString serializeData(const Data &record) const; - Data deserializeData(const QString &line) const; + bool deserializeData(const QString &line, Data &data) const; void ensureHeaders(); qint64 generateId() noexcept; diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 54f514f..5b2c833 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -6,6 +6,7 @@ #include "backend/data_store/DataStore.h" // #include "DataStore.h" +// Low-level layer // === Connector === Connector::Connector(std::string path, size_t record_size) : path_(std::move(path)), size_(0), record_size_(record_size) {} @@ -241,7 +242,6 @@ std::optional FileConnector::read(int64_t id) const } // === Update === - bool FileConnector::update(int64_t id, const std::string &data) { if (!isValidId(id)) @@ -331,6 +331,250 @@ void printRecord(FileConnector &connector, int64_t id) auto record = connector.read(id); } +// === DataStore (Qt layer) === +namespace +{ +QString diagStateToString(DiagState state) +{ + return QString::number(static_cast(state)); +} + +bool stringToDiagState(const QString &value, DiagState &state) +{ + bool ok = false; + const int raw = value.trimmed().toInt(&ok); + if (!ok) + { + return false; + } + + state = static_cast(raw); + return true; +} +} // namespace + +DataStore::DataStore(Connector *connector, QObject *parent) + : QObject(parent) + , connector_(connector) + , headersEnsured_(false) +{ + if (!connector_) + { + qWarning().noquote() << "[DataStore] Null connector passed to constructor."; + return; + } + + ensureHeaders(); +} + +QString DataStore::serializeData(const Data &record) const +{ + const auto &frame = record.frame; + + QStringList parts; + parts.reserve(9); + parts << QString::number(frame.dieselTemp, 'g', 17) + << QString::number(frame.motorTemp, 'g', 17) + << QString::number(frame.resistorTemp, 'g', 17) + << QString::number(frame.dieselPressure, 'g', 17) + << QString::number(frame.torque, 'g', 17) + << QString::number(frame.rpm, 'g', 17) + << QString::number(frame.timestampMs) + << QString::number(frame.stage) + << diagStateToString(record.state); + + return parts.join(';'); +} + +bool DataStore::deserializeData(const QString &line, Data &data) const +{ + const QStringList parts = line.trimmed().split(';', Qt::KeepEmptyParts); + if (parts.size() != 9) + { + qWarning().noquote() + << "[DataStore] Invalid record format. Expected 9 fields, got" + << parts.size() << "Line:" << line; + return false; + } + + bool ok = false; + + data.frame.dieselTemp = parts.at(0).trimmed().toDouble(&ok); + if (!ok) return false; + data.frame.motorTemp = parts.at(1).trimmed().toDouble(&ok); + if (!ok) return false; + data.frame.resistorTemp = parts.at(2).trimmed().toDouble(&ok); + if (!ok) return false; + data.frame.dieselPressure = parts.at(3).trimmed().toDouble(&ok); + if (!ok) return false; + data.frame.torque = parts.at(4).trimmed().toDouble(&ok); + if (!ok) return false; + data.frame.rpm = parts.at(5).trimmed().toDouble(&ok); + if (!ok) return false; + data.frame.timestampMs = parts.at(6).trimmed().toLongLong(&ok); + if (!ok) return false; + data.frame.stage = parts.at(7).trimmed().toInt(&ok); + if (!ok) return false; + if (!stringToDiagState(parts.at(8), data.state)) + { + qWarning().noquote() + << "[DataStore] Failed to parse diagnostic state:" << parts.at(8); + return false; + } + + return true; +} + +void DataStore::ensureHeaders() +{ + if (headersEnsured_) + { + return; + } + + if (!connector_) + { + qWarning().noquote() << "[DataStore] Cannot ensure headers: connector is null."; + return; + } + + // Формат хранения здесь без отдельной строки заголовков; + // метод оставлен как точка расширения и для совместимости с интерфейсом. + headersEnsured_ = true; +} + +qint64 DataStore::generateId() noexcept +{ + if (!connector_) + { + return -1; + } + + return static_cast(connector_->getSize()); +} + +bool DataStore::writeData(const Data &record) +{ + if (!connector_) + { + qWarning().noquote() << "[DataStore] writeData failed: connector is null."; + return false; + } + + ensureHeaders(); + + const QString serialized = serializeData(record); + const QByteArray payload = serialized.toUtf8(); + const size_t payloadSize = static_cast(payload.size()); + + if (payloadSize > connector_->getRecordSize()) + { + qWarning().noquote() + << "[DataStore] Record is too large for FileConnector fixed record size." + << "Size:" << payloadSize + << "Limit:" << connector_->getRecordSize() + << "Data:" << serialized; + return false; + } + + const int64_t id = connector_->write(payload.toStdString()); + if (id < 0) + { + qWarning().noquote() << "[DataStore] Failed to write record."; + return false; + } + + return true; +} + +QVector DataStore::readData(qint64 id) const +{ + QVector result; + + if (!connector_) + { + qWarning().noquote() << "[DataStore] readData failed: connector is null."; + return result; + } + + if (id < 0 || static_cast(id) >= connector_->getSize()) + { + qWarning().noquote() << "[DataStore] readData failed: invalid id:" << id; + return result; + } + + const std::optional raw = connector_->read(id); + if (!raw.has_value()) + { + qWarning().noquote() << "[DataStore] readData failed: unable to read record with id" << id; + return result; + } + + const QString line = QString::fromStdString(*raw); + const QStringList parts = line.trimmed().split(';', Qt::KeepEmptyParts); + if (parts.size() != 9) + { + qWarning().noquote() + << "[DataStore] readData failed: malformed record with id" + << id << "Fields:" << parts.size(); + return result; + } + + Data data{}; + if (!deserializeData(line, data)) + { + return result; + } + + result.append(data); + return result; +} + +QVector DataStore::readAllData() const +{ + QVector result; + + if (!connector_) + { + qWarning().noquote() << "[DataStore] readAllData failed: connector is null."; + return result; + } + + const size_t size = connector_->getSize(); + result.reserve(static_cast(size)); + + for (size_t i = 0; i < size; ++i) + { + const std::optional raw = connector_->read(static_cast(i)); + if (!raw.has_value()) + { + qWarning().noquote() << "[DataStore] Skipping unreadable record with id" << static_cast(i); + continue; + } + + const QString line = QString::fromStdString(*raw); + const QStringList parts = line.trimmed().split(';', Qt::KeepEmptyParts); + if (parts.size() != 9) + { + qWarning().noquote() + << "[DataStore] Skipping malformed record with id" + << static_cast(i) + << "Fields:" << parts.size(); + continue; + } + + Data data{}; + if (!deserializeData(line, data)) + { + continue; + } + + result.append(data); + } + + return result; +} + // int main() // { // constexpr size_t RECORD_SIZE = 16; From 4ba7e93da201cdb74cc949b398bf5769d4b62c7a Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Fri, 8 May 2026 20:24:11 +0500 Subject: [PATCH 40/58] fix: add necessary includes --- src/backend/data_store/DataStore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 5b2c833..25f7721 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include + #include "backend/data_store/DataStore.h" // #include "DataStore.h" From ab3b0cabeb2a7dcf6bda396bf8156305ff3b7fda Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Fri, 8 May 2026 20:50:33 +0500 Subject: [PATCH 41/58] refactor: DataStore --- include/backend/data_store/DataStore.h | 2 +- src/backend/data_store/DataStore.cpp | 112 ++++++++++++------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index dc149ec..e750223 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -72,7 +72,7 @@ public slots: bool writeData(const Data &record); private: - QString serializeData(const Data &record) const; + QString serializeData(qint64 id, const Data &record) const; bool deserializeData(const QString &line, Data &data) const; void ensureHeaders(); diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 25f7721..460e9a9 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -370,13 +370,15 @@ DataStore::DataStore(Connector *connector, QObject *parent) ensureHeaders(); } -QString DataStore::serializeData(const Data &record) const +QString DataStore::serializeData(qint64 id, const Data &record) const { const auto &frame = record.frame; QStringList parts; - parts.reserve(9); - parts << QString::number(frame.dieselTemp, 'g', 17) + parts.reserve(10); + + parts << QString::number(id) + << QString::number(frame.dieselTemp, 'g', 17) << QString::number(frame.motorTemp, 'g', 17) << QString::number(frame.resistorTemp, 'g', 17) << QString::number(frame.dieselPressure, 'g', 17) @@ -392,36 +394,48 @@ QString DataStore::serializeData(const Data &record) const bool DataStore::deserializeData(const QString &line, Data &data) const { const QStringList parts = line.trimmed().split(';', Qt::KeepEmptyParts); - if (parts.size() != 9) + + if (parts.size() != 10) { qWarning().noquote() - << "[DataStore] Invalid record format. Expected 9 fields, got" - << parts.size() << "Line:" << line; + << "[DataStore] Invalid record format. Expected 10 fields, got" + << parts.size() + << "Line:" << line; return false; } bool ok = false; - data.frame.dieselTemp = parts.at(0).trimmed().toDouble(&ok); + // parts[0] = id (игнорируем) + + data.frame.dieselTemp = parts.at(1).trimmed().toDouble(&ok); if (!ok) return false; - data.frame.motorTemp = parts.at(1).trimmed().toDouble(&ok); + + data.frame.motorTemp = parts.at(2).trimmed().toDouble(&ok); if (!ok) return false; - data.frame.resistorTemp = parts.at(2).trimmed().toDouble(&ok); + + data.frame.resistorTemp = parts.at(3).trimmed().toDouble(&ok); if (!ok) return false; - data.frame.dieselPressure = parts.at(3).trimmed().toDouble(&ok); + + data.frame.dieselPressure = parts.at(4).trimmed().toDouble(&ok); if (!ok) return false; - data.frame.torque = parts.at(4).trimmed().toDouble(&ok); + + data.frame.torque = parts.at(5).trimmed().toDouble(&ok); if (!ok) return false; - data.frame.rpm = parts.at(5).trimmed().toDouble(&ok); + + data.frame.rpm = parts.at(6).trimmed().toDouble(&ok); if (!ok) return false; - data.frame.timestampMs = parts.at(6).trimmed().toLongLong(&ok); + + data.frame.timestampMs = parts.at(7).trimmed().toLongLong(&ok); if (!ok) return false; - data.frame.stage = parts.at(7).trimmed().toInt(&ok); + + data.frame.stage = parts.at(8).trimmed().toInt(&ok); if (!ok) return false; - if (!stringToDiagState(parts.at(8), data.state)) + + if (!stringToDiagState(parts.at(9), data.state)) { qWarning().noquote() - << "[DataStore] Failed to parse diagnostic state:" << parts.at(8); + << "[DataStore] Failed to parse diagnostic state:" << parts.at(9); return false; } @@ -446,16 +460,6 @@ void DataStore::ensureHeaders() headersEnsured_ = true; } -qint64 DataStore::generateId() noexcept -{ - if (!connector_) - { - return -1; - } - - return static_cast(connector_->getSize()); -} - bool DataStore::writeData(const Data &record) { if (!connector_) @@ -466,22 +470,27 @@ bool DataStore::writeData(const Data &record) ensureHeaders(); - const QString serialized = serializeData(record); + const qint64 id = generateId(); + if (id < 0) + { + qWarning().noquote() << "[DataStore] writeData failed: unable to generate id."; + return false; + } + + const QString serialized = serializeData(id, record); const QByteArray payload = serialized.toUtf8(); - const size_t payloadSize = static_cast(payload.size()); - if (payloadSize > connector_->getRecordSize()) + if (static_cast(payload.size()) > connector_->getRecordSize()) { qWarning().noquote() << "[DataStore] Record is too large for FileConnector fixed record size." - << "Size:" << payloadSize + << "Size:" << payload.size() << "Limit:" << connector_->getRecordSize() << "Data:" << serialized; return false; } - const int64_t id = connector_->write(payload.toStdString()); - if (id < 0) + if (connector_->write(payload.toStdString()) < 0) { qWarning().noquote() << "[DataStore] Failed to write record."; return false; @@ -508,24 +517,19 @@ QVector DataStore::readData(qint64 id) const const std::optional raw = connector_->read(id); if (!raw.has_value()) - { - qWarning().noquote() << "[DataStore] readData failed: unable to read record with id" << id; - return result; - } - - const QString line = QString::fromStdString(*raw); - const QStringList parts = line.trimmed().split(';', Qt::KeepEmptyParts); - if (parts.size() != 9) { qWarning().noquote() - << "[DataStore] readData failed: malformed record with id" - << id << "Fields:" << parts.size(); + << "[DataStore] readData failed: unable to read record with id" + << id; return result; } Data data{}; - if (!deserializeData(line, data)) + if (!deserializeData(QString::fromStdString(*raw), data)) { + qWarning().noquote() + << "[DataStore] readData failed: deserialize error for id" + << id; return result; } @@ -548,27 +552,23 @@ QVector DataStore::readAllData() const for (size_t i = 0; i < size; ++i) { - const std::optional raw = connector_->read(static_cast(i)); - if (!raw.has_value()) - { - qWarning().noquote() << "[DataStore] Skipping unreadable record with id" << static_cast(i); - continue; - } + const std::optional raw = + connector_->read(static_cast(i)); - const QString line = QString::fromStdString(*raw); - const QStringList parts = line.trimmed().split(';', Qt::KeepEmptyParts); - if (parts.size() != 9) + if (!raw.has_value()) { qWarning().noquote() - << "[DataStore] Skipping malformed record with id" - << static_cast(i) - << "Fields:" << parts.size(); + << "[DataStore] Skipping unreadable record with id" + << static_cast(i); continue; } Data data{}; - if (!deserializeData(line, data)) + if (!deserializeData(QString::fromStdString(*raw), data)) { + qWarning().noquote() + << "[DataStore] Skipping malformed record with id" + << static_cast(i); continue; } From 286ae11157f67afc103f7a11e7fc92110fed0e9e Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Fri, 8 May 2026 22:04:49 +0500 Subject: [PATCH 42/58] feat: add rounding of numbers to serializeData --- include/backend/data_store/DataStore.h | 4 ++-- src/backend/data_store/DataStore.cpp | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index e750223..20cd774 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -36,7 +36,7 @@ class Connector class FileConnector : public Connector { public: - FileConnector(std::string path, size_t record_size); + FileConnector(std::string path, size_t record_size); // Необходимо определить макрос для размера одной записи (128 байт хватит); #define RECORD_SIZE 128 ~FileConnector() noexcept override = default; int64_t write(const std::string &data) override; @@ -72,7 +72,7 @@ public slots: bool writeData(const Data &record); private: - QString serializeData(qint64 id, const Data &record) const; + QString serializeData(qint64 id, const Data &record, int precision = 3) const; bool deserializeData(const QString &line, Data &data) const; void ensureHeaders(); diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 460e9a9..34a0ab0 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -370,20 +370,27 @@ DataStore::DataStore(Connector *connector, QObject *parent) ensureHeaders(); } -QString DataStore::serializeData(qint64 id, const Data &record) const +QString DataStore::serializeData(qint64 id, + const Data &record, + int precision) const { const auto &frame = record.frame; + auto fmt = [precision](double value) + { + return QString::number(value, 'f', precision); + }; + QStringList parts; parts.reserve(10); parts << QString::number(id) - << QString::number(frame.dieselTemp, 'g', 17) - << QString::number(frame.motorTemp, 'g', 17) - << QString::number(frame.resistorTemp, 'g', 17) - << QString::number(frame.dieselPressure, 'g', 17) - << QString::number(frame.torque, 'g', 17) - << QString::number(frame.rpm, 'g', 17) + << fmt(frame.dieselTemp) + << fmt(frame.motorTemp) + << fmt(frame.resistorTemp) + << fmt(frame.dieselPressure) + << fmt(frame.torque) + << fmt(frame.rpm) << QString::number(frame.timestampMs) << QString::number(frame.stage) << diagStateToString(record.state); From 1012968d3f0bc8f3d41094ff25ee8c60fb606494 Mon Sep 17 00:00:00 2001 From: lefff123 Date: Fri, 8 May 2026 22:33:57 +0500 Subject: [PATCH 43/58] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20Statemachine=20=D0=BF=D0=BE=D0=B4=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B5=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/state_machine/state_machine.h | 9 +- src/backend/state_machine/state_machine.cpp | 84 ++++++++----------- 2 files changed, 39 insertions(+), 54 deletions(-) diff --git a/include/backend/state_machine/state_machine.h b/include/backend/state_machine/state_machine.h index 60813ed..70cfc5d 100644 --- a/include/backend/state_machine/state_machine.h +++ b/include/backend/state_machine/state_machine.h @@ -15,7 +15,7 @@ class BackendWorker; class IModbusBridge; class DataStore; class DataProcessor; -class CSVConnector; +class FileConnector; class StateMachine : public QObject { @@ -50,7 +50,7 @@ private slots: void onModbusConnectionRestored(); // Команды фронта - void onReceivedFrontControl(const FrontControl& control); + void onReceivedFrontControl(const ModelControl& control); void onReceivedModelConfig(const ModelConfig& config); private: @@ -65,9 +65,8 @@ private slots: DataStore* m_dataStore; DataProcessor* m_dataProcessor; - // Хранилища низкого уровня - CSVConnector* m_measurementConnector; - CSVConnector* m_eventConnector; + //Коннектор + FileConnector* m_Connector; // Таймеры QTimer* m_pollTimer; diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index 9bd5db8..f01d810 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -15,11 +15,10 @@ StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) // ----- Подготавливаем CSV-коннекторы для хранилища ----- // пути можно вынести в настройки, здесь для примера - m_measurementConnector = new CSVConnector("measurements.csv"); - m_eventConnector = new CSVConnector("events.csv"); + m_Connector = new FileConnector("measurements.csv", SIZE_FILE_STR); // ----- Создаём DataStore ----- - m_dataStore = new DataStore(m_measurementConnector, m_eventConnector, this); + m_dataStore = new DataStore(m_Connector, this); m_dataProcessor = new DataProcessor(ModelConfig{}, this); @@ -78,13 +77,11 @@ void StateMachine::start() int pollMs = ConfigData().LoadConfig().pollFrequencyMs; m_pollTimer->start(pollMs); - EventRecord startEvt; - startEvt.runId = m_currentRunId; - startEvt.timestampMs = QDateTime::currentMSecsSinceEpoch(); - startEvt.stage = -1; - startEvt.type = "start"; - startEvt.message = "Experiment started"; - m_dataStore->writeEvent(startEvt); + Data startEvt; + startEvt.frame.timestampMs = QDateTime::currentMSecsSinceEpoch(); + startEvt.frame.stage = -1; + startEvt.state = DiagState::Ok; + m_dataStore->writeData(startEvt); transitionTo(DiagState::Ok); m_currentStageIndex = 0; @@ -97,13 +94,10 @@ void StateMachine::stop() m_stageTimer->stop(); m_modbusBridge->stopPolling(); - EventRecord stopEvt; - stopEvt.runId = m_currentRunId; - stopEvt.timestampMs = QDateTime::currentMSecsSinceEpoch(); - stopEvt.stage = m_currentStageIndex; - stopEvt.type = "stop"; - stopEvt.message = "Experiment stopped"; - m_dataStore->writeEvent(stopEvt); + Data stopEvt; + stopEvt.frame.timestampMs = QDateTime::currentMSecsSinceEpoch(); + stopEvt.frame.stage = m_currentStageIndex; + m_dataStore->writeData(stopEvt); transitionTo(DiagState::IDLE); emit finished(m_state); @@ -132,30 +126,24 @@ void StateMachine::onSensorsDataReady(const SensorFrame& frame) void StateMachine::onDecisionReady(const Decision& decision) { // Запись в хранилище - MeasurementRecord rec; - rec.runId = m_currentRunId; - rec.stage = m_currentSensorFrame.stage; - rec.timestampMs = m_currentSensorFrame.timestampMs; - rec.rpm = m_currentSensorFrame.rpm; - rec.torque = m_currentSensorFrame.torque; - rec.dieselTemp = m_currentSensorFrame.dieselTemp; - rec.motorTemp = m_currentSensorFrame.motorTemp; - rec.resistorTemp = 0; // маппинг уточнить - rec.dieselPressure = m_currentSensorFrame.dieselPressure; - rec.throttle = 0; // дополнить при необходимости - rec.brakeTorque = 0; - rec.flags = (decision.state == DiagState::Ok) ? "OK" : "WARNING"; - m_dataStore->writeRecord(rec); + Data rec; + rec.frame.stage = m_currentSensorFrame.stage; + rec.frame.timestampMs = m_currentSensorFrame.timestampMs; + rec.frame.rpm = m_currentSensorFrame.rpm; + rec.frame.torque = m_currentSensorFrame.torque; + rec.frame.dieselTemp = m_currentSensorFrame.dieselTemp; + rec.frame.motorTemp = m_currentSensorFrame.motorTemp; + rec.frame.resistorTemp = 0; // маппинг уточнить + rec.frame.dieselPressure = m_currentSensorFrame.dieselPressure; + m_dataStore->writeData(rec); // Обработка смены состояния if (decision.state != m_state) { - EventRecord evt; - evt.runId = m_currentRunId; - evt.timestampMs = QDateTime::currentMSecsSinceEpoch(); - evt.stage = m_currentStageIndex; - evt.type = "transition"; - evt.message = QString("State changed to %1").arg(static_cast(decision.state)); - m_dataStore->writeEvent(evt); + Data evt; + evt.frame.timestampMs = QDateTime::currentMSecsSinceEpoch(); + evt.frame.stage = m_currentStageIndex; + evt.state = DiagState::Ok; + m_dataStore->writeData(evt); applyControls(decision.controls); transitionTo(decision.state); @@ -175,13 +163,13 @@ void StateMachine::onDecisionReady(const Decision& decision) m_communicator->SendDataToFrontend(dataVec); // Обратная связь: считается успешным, если состояние не аварийное - bool ok = (decision.state == DiagState::Ok || decision.state == DiagState::PreWarn_RpmHigh /* и т.п.*/); - m_communicator->SendFeedbackToFrontend(ok); + //bool ok = (decision.state == DiagState::Ok || decision.state == DiagState::PreWarn_RpmHigh /* и т.п.*/); + m_communicator->SendFeedbackToFrontend(decision.state); } -void StateMachine::onReceivedFrontControl(const FrontControl& control) +void StateMachine::onReceivedFrontControl(const ModelControl& control) { - if (control.state == DiagState::Alarm_Generic) { + if (control.type == ControlType::EmergencyStop) { requestAbort("Front control emergency stop"); } // другая логика @@ -207,13 +195,11 @@ void StateMachine::applyControls(const QVector& controls) { // ---------- Ошибки Modbus ---------- void StateMachine::onModbusConfigurationError(const QString& reason) { // Логируем причину в хранилище - EventRecord evt; - evt.runId = m_currentRunId; - evt.timestampMs = QDateTime::currentMSecsSinceEpoch(); - evt.stage = m_currentStageIndex; - evt.type = "error"; - evt.message = "Modbus config error: " + reason; - m_dataStore->writeEvent(evt); + Data evt; + evt.frame.timestampMs = QDateTime::currentMSecsSinceEpoch(); + evt.frame.stage = m_currentStageIndex; + evt.state = DiagState::Alarm_Generic; + m_dataStore->writeData(evt); m_communicator->SendEmergencyStopInfoToFrontend(); } From e351de981467f1e432c450b86cca89d2d94cfa0f Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Sat, 9 May 2026 01:49:35 +0500 Subject: [PATCH 44/58] feat: implement register converter - also update reading operations - implement write config operation --- .../backend/modbus_client/QtModbusBridge.h | 5 +- .../backend/modbus_client/RegisterConverter.h | 15 ++++ src/backend/CMakeLists.txt | 5 +- src/backend/modbus_client/QtModbusBridge.cpp | 88 ++++++++----------- .../modbus_client/RegisterConverter.cpp | 44 ++++++++++ 5 files changed, 102 insertions(+), 55 deletions(-) create mode 100644 include/backend/modbus_client/RegisterConverter.h create mode 100644 src/backend/modbus_client/RegisterConverter.cpp diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index e705ab3..1e3b3df 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -1,6 +1,7 @@ #pragma once #include "IModbusBridge.h" #include "backend/DataTypes.h" +#include "backend/modbus_client/RegisterConverter.h" #include #include #include @@ -22,14 +23,14 @@ private slots: void onStateChange(QModbusDevice::State state); void onErrorOccured(); private: - QModbusReply* sendReadRequest(const QModbusDataUnit& dataUnit); - QModbusReply* sendWriteRequest(const QModbusDataUnit& dataUnit); void parseSensorsResponse(const QVector& values); void parseInfoResponse(const QVector& values); std::optional> extractValues(QModbusReply* reply, qsizetype expectedSize); + void setRegisterValues(QModbusDataUnit& dataUnit, qsizetype startAddress, const QList& values); private: ModbusConfig m_cfg; QModbusTcpClient m_client; QTimer m_pollTimer; + RegisterConverter m_decoder; QModbusDevice::State m_prevState = QModbusDevice::UnconnectedState; }; diff --git a/include/backend/modbus_client/RegisterConverter.h b/include/backend/modbus_client/RegisterConverter.h new file mode 100644 index 0000000..ffed103 --- /dev/null +++ b/include/backend/modbus_client/RegisterConverter.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include + +class RegisterConverter +{ +public: + double fromRegisterWordDouble(const quint16* value); + qint64 fromRegisterWordQint(const quint16* value); + int fromRegisterWordInt(const quint16* value); + template + QList toRegisterWords(const ValueT& value); +private: + quint64 fromRegisterWordRawValue(const quint16* value, qsizetype count); +}; diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 663d372..0da4c20 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -25,6 +25,7 @@ set(BACKEND_SOURCES modbus_client/IModbusBridge.cpp modbus_client/QtModbusBridge.cpp modbus_client/MockModbusBridge.cpp + modbus_client/RegisterConverter.cpp state_machine/state_machine.cpp ) @@ -39,11 +40,13 @@ set(BACKEND_HEADERS ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/IModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/QtModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/MockModbusBridge.h + ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/RegisterConverter.h ${PROJECT_SOURCE_DIR}/include/backend/state_machine/state_machine.h ) # Библиотека -add_library(backend_lib SHARED) +add_library(backend_lib SHARED + TypeDecoder.h) target_sources(backend_lib PRIVATE diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index c15d795..619e5d3 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -100,13 +100,10 @@ void QtModbusBridge::onReadSensors() { qDebug() << "[QtModbusBridge] Requesting sensors data"; - QModbusDataUnit dataUnit; - dataUnit.setRegisterType(QModbusDataUnit::InputRegisters); - dataUnit.setStartAddress(0); - dataUnit.setValueCount(InputRegisters::count); + QModbusDataUnit dataUnit(QModbusDataUnit::InputRegisters, 0, InputRegisters::count); // Sending request to the device with specified Unit Id - auto* reply = sendReadRequest(dataUnit); + auto* reply = m_client.sendReadRequest(dataUnit, m_cfg.unitId); if (!reply) return; @@ -125,13 +122,10 @@ void QtModbusBridge::onReadInfo() { qDebug() << "[QtModbusBridge] Requesting model info"; - QModbusDataUnit dataUnit; - dataUnit.setRegisterType(QModbusDataUnit::HoldingRegisters); - dataUnit.setStartAddress(0); - dataUnit.setValueCount(HoldingRegisters::count); + QModbusDataUnit dataUnit(QModbusDataUnit::HoldingRegisters, 0, HoldingRegisters::count); // Sending request to the device with specified Unit Id - auto* reply = sendReadRequest(dataUnit); + auto* reply = m_client.sendReadRequest(dataUnit, m_cfg.unitId); if (!reply) return; @@ -149,13 +143,25 @@ void QtModbusBridge::onReadInfo() void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) { qDebug() << "[QtModbusBridge] Writing a new configuration to the model"; - // TODO: implement later + + QModbusDataUnit dataUnit(QModbusDataUnit::HoldingRegisters, 0, HoldingRegisters::count); + setRegisterValues(dataUnit, HoldingRegisters::omega_ICE_max_prir, m_decoder.toRegisterWords(cmd.maxRpmPrir)); + setRegisterValues(dataUnit, HoldingRegisters::omega_ICE_max_run, m_decoder.toRegisterWords(cmd.maxRpmRun)); + setRegisterValues(dataUnit, HoldingRegisters::P_oil_max, m_decoder.toRegisterWords(cmd.maxDieselPressure)); + setRegisterValues(dataUnit, HoldingRegisters::P_oil_min, m_decoder.toRegisterWords(cmd.minDieselPressure)); + setRegisterValues(dataUnit, HoldingRegisters::T_cool_max, m_decoder.toRegisterWords(cmd.maxDieselTemp)); + setRegisterValues(dataUnit, HoldingRegisters::T_AD_max, m_decoder.toRegisterWords(cmd.maxMotorTemp)); + setRegisterValues(dataUnit, HoldingRegisters::T_ballast_max, m_decoder.toRegisterWords(cmd.maxResistorTemp)); + + auto* reply = m_client.sendWriteRequest(dataUnit, m_cfg.unitId); + if (!reply) + return; } void QtModbusBridge::onWriteDecision(const Decision& decision) { qDebug() << "[QtModbusBridge] Sending a decision to the model"; - // TODO: implement later + QModbusDataUnit dataUnit(QModbusDataUnit::HoldingRegisters, 0, HoldingRegisters::count); } std::optional> QtModbusBridge::extractValues(QModbusReply* reply, qsizetype expectedSize) @@ -178,13 +184,13 @@ std::optional> QtModbusBridge::extractValues(QModbusReply* repl void QtModbusBridge::parseSensorsResponse(const QVector& values) { SensorFrame frame = { - static_cast(values[InputRegisters::T_cool]), - static_cast(values[InputRegisters::T_AD]), - static_cast(values[InputRegisters::T_ballast]), - static_cast(values[InputRegisters::P_oil]), - static_cast(values[InputRegisters::M_AD]), - static_cast(values[InputRegisters::f_AD]), - static_cast(values[InputRegisters::timestamp_ir]), + m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_cool]), + m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_AD]), + m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_ballast]), + m_decoder.fromRegisterWordDouble(&values[InputRegisters::P_oil]), + m_decoder.fromRegisterWordDouble(&values[InputRegisters::M_AD]), + m_decoder.fromRegisterWordDouble(&values[InputRegisters::f_AD]), + m_decoder.fromRegisterWordQint(&values[InputRegisters::timestamp_ir]), 1 // TODO: clarify what does stage mean }; emit sensorsDataReady(frame); @@ -193,43 +199,21 @@ void QtModbusBridge::parseSensorsResponse(const QVector& values) void QtModbusBridge::parseInfoResponse(const QVector& values) { ModelConfig info = { - static_cast(values[HoldingRegisters::T_cool_max]), - static_cast(values[HoldingRegisters::T_AD_max]), - static_cast(values[HoldingRegisters::T_ballast_max]), - static_cast(values[HoldingRegisters::P_oil_max]), - static_cast(values[HoldingRegisters::P_oil_min]), - static_cast(values[HoldingRegisters::omega_ICE_max_prir]), - static_cast(values[HoldingRegisters::omega_ICE_max_run]), + m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::T_cool_max]), + m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::T_AD_max]), + m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::T_ballast_max]), + m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::P_oil_max]), + m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::P_oil_min]), + m_decoder.fromRegisterWordInt(&values[HoldingRegisters::omega_ICE_max_prir]), + m_decoder.fromRegisterWordInt(&values[HoldingRegisters::omega_ICE_max_run]), }; emit modelInfoReady(info); } -QModbusReply* QtModbusBridge::sendWriteRequest(const QModbusDataUnit& dataUnit) { - auto* reply = m_client.sendWriteRequest(dataUnit, m_cfg.unitId); - if (!reply) - { - return nullptr; - } - // Check if request failed immediately - if (reply->isFinished()) - { - reply->deleteLater(); - return nullptr; - } - return reply; -} - -QModbusReply* QtModbusBridge::sendReadRequest(const QModbusDataUnit& dataUnit) { - auto* reply = m_client.sendReadRequest(dataUnit, m_cfg.unitId); - if (!reply) - { - return nullptr; - } - // Check if request failed immediately - if (reply->isFinished()) +void QtModbusBridge::setRegisterValues(QModbusDataUnit& dataUnit, qsizetype startAddress, const QList& values) +{ + for (qsizetype i = 0; i < values.size(); ++i) { - reply->deleteLater(); - return nullptr; + dataUnit.setValue(startAddress + i, values[i]); } - return reply; } \ No newline at end of file diff --git a/src/backend/modbus_client/RegisterConverter.cpp b/src/backend/modbus_client/RegisterConverter.cpp new file mode 100644 index 0000000..e493942 --- /dev/null +++ b/src/backend/modbus_client/RegisterConverter.cpp @@ -0,0 +1,44 @@ +#include "backend/modbus_client/RegisterConverter.h" +#include + +double RegisterConverter::fromRegisterWordDouble(const quint16* value) +{ + return static_cast(fromRegisterWordRawValue(value, 4)); +} + +qint64 RegisterConverter::fromRegisterWordQint(const quint16* value) +{ + return static_cast(fromRegisterWordRawValue(value, 4)); +} + +int RegisterConverter::fromRegisterWordInt(const quint16* value) +{ + return static_cast(fromRegisterWordRawValue(value, 4)); +} + +template +QList RegisterConverter::toRegisterWords(const ValueT& value) +{ + if (sizeof(ValueT) % sizeof(quint16) != 0) + { + qWarning() << "Value type must be divisible by register word"; + return {}; + } + auto bits = std::bit_cast>(value); + return QVector(bits.begin(), bits.end()); +} + +quint64 RegisterConverter::fromRegisterWordRawValue(const quint16* value, qsizetype count) +{ + if (count > 4) + { + qWarning() << "Can't read into quint64 more than 4 registers, function returned 0"; + return 0; + } + quint64 res; + for (qsizetype i = 0; i < count; ++i) + { + res |= static_cast(*(value + i)) << (count - i - 1) * 16; + } + return res; +} \ No newline at end of file From 089c4ca26ab2a3fd62f067a451a917f80a167483 Mon Sep 17 00:00:00 2001 From: igor66ru Date: Sat, 9 May 2026 06:52:12 +0500 Subject: [PATCH 45/58] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2=20?= =?UTF-8?q?ConfigData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/config_data/config_data.h | 2 +- src/backend/backend_worker/backendworker.cpp | 2 +- src/backend/config_data/config_data.cpp | 16 +++++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/include/backend/config_data/config_data.h b/include/backend/config_data/config_data.h index 70b588a..ed0625a 100644 --- a/include/backend/config_data/config_data.h +++ b/include/backend/config_data/config_data.h @@ -21,7 +21,7 @@ class ConfigData : public QObject const QString m_RETRIES = "retries"; const QString m_UNIT_ID = "unit_id"; - const QString m_FILENAME = "config.txt"; + const QString m_FILENAME = "configBackend.txt"; }; diff --git a/src/backend/backend_worker/backendworker.cpp b/src/backend/backend_worker/backendworker.cpp index a6af545..43a2348 100644 --- a/src/backend/backend_worker/backendworker.cpp +++ b/src/backend/backend_worker/backendworker.cpp @@ -2,7 +2,7 @@ #include "backend/state_machine/state_machine.h" BackendWorker::BackendWorker(QObject* parent) : - m_machine(new StateMachine(this)), QObject{parent} + m_machine(new StateMachine(this, nullptr)), QObject{parent} { qRegisterMetaType(); qRegisterMetaType(); diff --git a/src/backend/config_data/config_data.cpp b/src/backend/config_data/config_data.cpp index c1b3779..cccc0f6 100644 --- a/src/backend/config_data/config_data.cpp +++ b/src/backend/config_data/config_data.cpp @@ -11,7 +11,7 @@ ModbusConfig ConfigData::LoadConfig() return ModbusConfig(); } - if(!file.open(QIODevice::ReadOnly | QIODevice::Text)); + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qInfo("Файл конфигурации защищен от чтения. Будут загружены параметры по умолчанию"); return ModbusConfig(); @@ -21,36 +21,38 @@ ModbusConfig ConfigData::LoadConfig() while(!file.atEnd()) { QString line = file.readLine(); + line.chop(1); QStringList partLine = line.split('='); if(partLine.count() != 2) continue; + QString& key = partLine[0]; QString& value = partLine[1]; - if(m_HOST == value) + if(m_HOST == key) { config.host = value; continue; } - if(m_PORT == value) + if(m_PORT == key) { config.port = value.toUInt(); continue; } - if(m_POLL_FREQ == value) + if(m_POLL_FREQ == key) { config.pollFrequencyMs = value.toInt(); continue; } - if(m_TIMEOUT == value) + if(m_TIMEOUT == key) { config.timeoutMs = value.toInt(); continue; } - if(m_RETRIES == value) + if(m_RETRIES == key) { config.retries = value.toInt(); continue; } - if(m_UNIT_ID == value) + if(m_UNIT_ID == key) config.unitId = value.toInt(); } From d2c354f3b36d8d8fcbf25a5b31cb0dbead9b066e Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Sat, 9 May 2026 16:03:17 +0500 Subject: [PATCH 46/58] Update DataProcessor.h --- .../backend/data_processing/DataProcessor.h | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/include/backend/data_processing/DataProcessor.h b/include/backend/data_processing/DataProcessor.h index a1311e4..9800621 100644 --- a/include/backend/data_processing/DataProcessor.h +++ b/include/backend/data_processing/DataProcessor.h @@ -7,14 +7,21 @@ /** * Модуль обработки данных. - * - * Принимает SensorFrame от StateMachine, сравнивает с порогами из ModelConfig - * с учётом текущего этапа и ВОЗВРАЩАЕТ Decision (вектор управляющих воздействий - * + диагностическое состояние модели + причина). - * версия 3 - * + * версия 4 + * Принимает SensorFrame, сравнивает с порогами из ModelConfig (с учётом этапа), + * формирует Decision: вектор управляющих воздействий + DiagState + reason. * * + * используем только существующие ControlType + * (Throttle, BrakeTorque, TargetRpm, MotorEnable, EmergencyStop). + * Вентиляторы охлаждения управляются через MotorEnable/коды режимов + * не могут — поэтому при перегревах формируем только DiagState + * и текстовую причину; включением вентиляторов будет заниматься тот, + * кто реализует applyControls (в Modbus у нас есть CoilsRegisters::fan_*). + * - дублируем выдачу Decision двумя путями: + * 1) processFrame возвращает Decision (если кто-то захочет использовать); + * 2) сигнал decisionReady(Decision) + * закомментированный connect в state_machine.cpp. */ class DataProcessor : public QObject { @@ -23,17 +30,15 @@ class DataProcessor : public QObject explicit DataProcessor(const ModelConfig &config, QObject *parent = nullptr); Decision processFrame(const SensorFrame &frame); public slots: - // уведомление об актуальном этапе (для исключения ложных срабатываний) void onStageChanged(int newStage); - // обновление конфигурации (порогов) void onConfigChanged(ModelConfig config); - // сброс защёлки аварии и внутреннего состояния (новый прогон) void reset(); +signals: + void decisionReady(const Decision &decision); + private: - // активна ли проверка параметра на текущем этапе??? bool isParamActiveOnStage(const QString ¶m, int stage) const; - // аварийная команда останова static QVector makeStopControls(); private: @@ -41,4 +46,5 @@ public slots: int m_currentStage = 0; bool m_alarmLatched = false; }; + #endif // DATAPROCESSOR_H From 80e884d3b9ca312995b10c03e15c4af3c8dbfa93 Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Sat, 9 May 2026 16:04:06 +0500 Subject: [PATCH 47/58] Update DataProcessor.cpp --- src/backend/data_processing/DataProcessor.cpp | 132 ++++++++++-------- 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 158a2e0..6168258 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,14 +1,15 @@ #include "backend/data_processing/DataProcessor.h" +// Этапы — нумерация условная, должна совпадать со StateMachine. +// state_machine.cpp использует m_currentStageIndex как int. namespace Stage { - constexpr int IDLE = 0; - constexpr int COLD_CRANKING = 1; - constexpr int START_AND_WARMUP = 2; - constexpr int HOT_NO_LOAD = 3; - constexpr int HOT_WITH_LOAD = 4; - constexpr int COMPLETED = 5; - constexpr int ABORTED = 6; + constexpr int IDLE = -1; // в state_machine.cpp m_currentStageIndex = -1 до start() + constexpr int COLD_CRANKING = 0; + constexpr int START_AND_WARMUP = 1; + constexpr int HOT_NO_LOAD = 2; + constexpr int HOT_WITH_LOAD = 3; } +// Порог предупреждения = 90% от критического. static constexpr double WARN_RATIO = 0.9; DataProcessor::DataProcessor(const ModelConfig &config, QObject *parent) : QObject(parent), m_config(config) @@ -30,26 +31,32 @@ void DataProcessor::reset() Decision DataProcessor::processFrame(const SensorFrame &frame) { Decision decision; - // после аварии — пустое решение + // После аварии — пустое решение. if (m_alarmLatched) { decision.state = DiagState::Alarm_Generic; decision.reason = QStringLiteral("alarm latched"); + emit decisionReady(decision); return decision; } - // неактивные режимы — проверки не выполняем - if (m_currentStage == Stage::IDLE || - m_currentStage == Stage::COMPLETED || - m_currentStage == Stage::ABORTED) + const int stage = frame.stage; + // неактивные режимы — проверки выключены. + if (stage < Stage::COLD_CRANKING) { decision.state = DiagState::Ok; decision.reason = QStringLiteral("inactive stage"); + emit decisionReady(decision); return decision; } - // выбор наихудшего состояния (приоритет — авария) + const double maxRpm = + (stage == Stage::COLD_CRANKING) + ? static_cast(m_config.maxRpmPrir) + : static_cast(m_config.maxRpmRun); + DiagState worstState = DiagState::Ok; QString worstReason; - int worstSeverity = 0; // 0=ok, 1=пре alarm, 2=alarm + int worstSeverity = 0; // 0=ok, 1=prewarn, 2=alarm + auto promote = [&](int severity, DiagState st, const QString &rsn) { if (severity > worstSeverity) @@ -59,24 +66,22 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) worstReason = rsn; } }; - // обороты - if (isParamActiveOnStage("rpm", m_currentStage)) + // обороты ДВС + if (isParamActiveOnStage("rpm", stage)) { - if (frame.rpm >= m_config.maxRpmPrir) + if (frame.rpm >= maxRpm) { promote(2, DiagState::Alarm_RpmOverspeed, - QStringLiteral("Превышение оборотов: %1 >= %2") - .arg(frame.rpm) - .arg(m_config.maxRpmPrir)); + QStringLiteral("Превышение оборотов: %1 >= %2").arg(frame.rpm).arg(maxRpm)); } - else if (frame.rpm >= m_config.maxRpmPrir * WARN_RATIO) + else if (frame.rpm >= maxRpm * WARN_RATIO) { promote(1, DiagState::PreWarn_RpmHigh, QStringLiteral("Обороты приближаются к пределу: %1").arg(frame.rpm)); } } - // температура ДВС - if (isParamActiveOnStage("dieselTemp", m_currentStage)) + // температура ДВС (охлаждающей жидкости) + if (isParamActiveOnStage("dieselTemp", stage)) { if (frame.dieselTemp >= m_config.maxDieselTemp) { @@ -86,11 +91,12 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) else if (frame.dieselTemp >= m_config.maxDieselTemp * WARN_RATIO) { promote(1, DiagState::PreWarn_DieselTempHigh, - QStringLiteral("ДВС близок к перегреву: %1").arg(frame.dieselTemp)); + QStringLiteral("ДВС близок к перегреву: %1 (требуется охлаждение)") + .arg(frame.dieselTemp)); } } // температура АД - if (isParamActiveOnStage("motorTemp", m_currentStage)) + if (isParamActiveOnStage("motorTemp", stage)) { if (frame.motorTemp >= m_config.maxMotorTemp) { @@ -100,56 +106,57 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) else if (frame.motorTemp >= m_config.maxMotorTemp * WARN_RATIO) { promote(1, DiagState::PreWarn_MotorTempHigh, - QStringLiteral("АД близок к перегреву: %1").arg(frame.motorTemp)); + QStringLiteral("АД близок к перегреву: %1 (требуется охлаждение)") + .arg(frame.motorTemp)); } } - // балансировочный резистор - if (isParamActiveOnStage("resistorBalance", m_currentStage)) + // температура балластных резисторов + if (isParamActiveOnStage("resistorTemp", stage)) { - if (frame.resistorBalance >= m_config.maxResistorBalance) + if (frame.resistorTemp >= m_config.maxResistorTemp) { promote(2, DiagState::Alarm_ResistorOverheat, - QStringLiteral("Превышение по балансировочному резистору: %1") - .arg(frame.resistorBalance)); + QStringLiteral("Перегрев балластных резисторов: %1").arg(frame.resistorTemp)); } - else if (frame.resistorBalance >= m_config.maxResistorBalance * WARN_RATIO) + else if (frame.resistorTemp >= m_config.maxResistorTemp * WARN_RATIO) { promote(1, DiagState::PreWarn_ResistorHigh, - QStringLiteral("Балансировочный резистор близок к пределу: %1") - .arg(frame.resistorBalance)); + QStringLiteral("Балластные резисторы близки к перегреву: %1 (требуется охлаждение)") + .arg(frame.resistorTemp)); } } - // давление ДВС — верхняя граница - if (isParamActiveOnStage("dieselPressureMax", m_currentStage)) + // давление масла ДВС — верхняя граница + if (isParamActiveOnStage("dieselPressureMax", stage)) { if (frame.dieselPressure >= m_config.maxDieselPressure) { promote(2, DiagState::Alarm_PressureOver, - QStringLiteral("Превышение давления ДВС: %1").arg(frame.dieselPressure)); + QStringLiteral("Превышение давления масла: %1").arg(frame.dieselPressure)); } else if (frame.dieselPressure >= m_config.maxDieselPressure * WARN_RATIO) { promote(1, DiagState::PreWarn_PressureHigh, - QStringLiteral("Давление ДВС близко к верхнему пределу: %1") + QStringLiteral("Давление масла близко к верхнему пределу: %1") .arg(frame.dieselPressure)); } } - // давление ДВС — нижняя граница - if (isParamActiveOnStage("dieselPressureMin", m_currentStage)) + // давление масла ДВС — нижняя граница + if (isParamActiveOnStage("dieselPressureMin", stage)) { const double warnLow = m_config.minDieselPressure + m_config.minDieselPressure * (1.0 - WARN_RATIO); if (frame.dieselPressure <= m_config.minDieselPressure) { promote(2, DiagState::Alarm_PressureUnder, - QStringLiteral("Падение давления ДВС: %1").arg(frame.dieselPressure)); + QStringLiteral("Падение давления масла: %1").arg(frame.dieselPressure)); } else if (frame.dieselPressure <= warnLow) { promote(1, DiagState::PreWarn_PressureLow, - QStringLiteral("Давление ДВС приближается к нижнему пределу: %1") + QStringLiteral("Давление масла приближается к нижнему пределу: %1") .arg(frame.dieselPressure)); } } + // ---- Формирование Decision ---- decision.state = worstState; decision.reason = worstReason; if (worstSeverity == 2) @@ -157,27 +164,30 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) // Авария m_alarmLatched = true; decision.controls = makeStopControls(); + emit decisionReady(decision); return decision; } if (worstSeverity == 1) { - // Предаварийное состояние + // предаварийное состояние ModelControl mc; switch (worstState) { case DiagState::PreWarn_RpmHigh: - // Снизить уставку дросселя (0…1). - mc.type = ControlType::ThrottleSetpoint; - mc.value = m_config.throttleReduceTo; + // Снижаем дроссель. + mc.type = ControlType::Throttle; + mc.value = 0.0; decision.controls.append(mc); break; case DiagState::PreWarn_ResistorHigh: - // Снизить уставку тормозного момента АД (Н·м). - mc.type = ControlType::BrakeTorqueSetpoint; - mc.value = m_config.brakeReduceTo; + // Снижаем тормозной момент. + mc.type = ControlType::BrakeTorque; + mc.value = 0.0; decision.controls.append(mc); break; - // Перегревы и давление сами + // Перегревы ДВС / АД и проблемы с давлением: + // включением вентиляторов в applyControls будет заниматься + // потребитель Decision — DataProcessor возвращает только DiagState и reason case DiagState::PreWarn_DieselTempHigh: case DiagState::PreWarn_MotorTempHigh: case DiagState::PreWarn_PressureHigh: @@ -186,16 +196,17 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) break; } } + + emit decisionReady(decision); return decision; } bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const { - if (stage == Stage::IDLE || - stage == Stage::COMPLETED || - stage == Stage::ABORTED) + if (stage < Stage::COLD_CRANKING) { return false; } + // давление и температура ДВС — после прогрева. if (param == "dieselPressureMin" || param == "dieselPressureMax" || param == "dieselTemp") @@ -204,19 +215,22 @@ bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const stage == Stage::HOT_NO_LOAD || stage == Stage::HOT_WITH_LOAD); } - if (param == "resistorBalance") + // балластные резисторы — на этапе с нагрузкой. + if (param == "resistorTemp") { return (stage == Stage::HOT_WITH_LOAD); } - // rpm и motorTemp — на всех активных этапах + // rpm и motorTemp — на всех активных этапах. return true; } + QVector DataProcessor::makeStopControls() { - // Аварийный стоп - // Команды режима/опроса при аварии не отправляем QVector v; - v.append({ControlType::ThrottleSetpoint, 0.0}); - v.append({ControlType::BrakeTorqueSetpoint, 0.0}); + v.append({ControlType::EmergencyStop, 1.0}); + v.append({ControlType::MotorEnable, 0.0}); + v.append({ControlType::Throttle, 0.0}); + v.append({ControlType::BrakeTorque, 0.0}); + v.append({ControlType::TargetRpm, 0.0}); return v; } From 54c71fb3fbc95d6b946a9e0285da014abb7560c7 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Sun, 10 May 2026 11:01:46 +0500 Subject: [PATCH 48/58] feat: implement write methods for modbus client --- src/backend/modbus_client/QtModbusBridge.cpp | 32 +++++++++++++++++++ .../modbus_client/RegisterConverter.cpp | 3 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 619e5d3..20338e1 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -162,6 +162,38 @@ void QtModbusBridge::onWriteDecision(const Decision& decision) { qDebug() << "[QtModbusBridge] Sending a decision to the model"; QModbusDataUnit dataUnit(QModbusDataUnit::HoldingRegisters, 0, HoldingRegisters::count); + for (const auto& control : decision.controls) + { + qsizetype startAddress = -1; + switch(control.type) + { + case ControlType::BrakeTorque: + startAddress = HoldingRegisters::M_AD_target; + break; + case ControlType::EmergencyStop: + startAddress = HoldingRegisters::simulationCommand; + break; + case ControlType::MotorEnable: + startAddress = HoldingRegisters::simulationCommand; + break; + case ControlType::TargetRpm: + startAddress = HoldingRegisters::f_AD_Input; + break; + case ControlType::Throttle: + startAddress = HoldingRegisters::simulationRequest; + break; + case ControlType::None: + break; + default: + qWarning() << "[QtModbusBridge] Unexpected ControlType was received in onWriteDecision"; + } + if (startAddress == -1) + continue; + dataUnit.setValue(startAddress, control.value); + } + auto* reply = m_client.sendWriteRequest(dataUnit, m_cfg.unitId); + if (!reply) + return; } std::optional> QtModbusBridge::extractValues(QModbusReply* reply, qsizetype expectedSize) diff --git a/src/backend/modbus_client/RegisterConverter.cpp b/src/backend/modbus_client/RegisterConverter.cpp index e493942..4e61660 100644 --- a/src/backend/modbus_client/RegisterConverter.cpp +++ b/src/backend/modbus_client/RegisterConverter.cpp @@ -1,4 +1,5 @@ #include "backend/modbus_client/RegisterConverter.h" +#include #include double RegisterConverter::fromRegisterWordDouble(const quint16* value) @@ -35,7 +36,7 @@ quint64 RegisterConverter::fromRegisterWordRawValue(const quint16* value, qsizet qWarning() << "Can't read into quint64 more than 4 registers, function returned 0"; return 0; } - quint64 res; + quint64 res = 0; for (qsizetype i = 0; i < count; ++i) { res |= static_cast(*(value + i)) << (count - i - 1) * 16; From 36d75ed55e0d0f228ca06d22b95b99abbf0c09aa Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Sun, 10 May 2026 11:08:25 +0500 Subject: [PATCH 49/58] chore: update C++ version and remove leftover line --- CMakeLists.txt | 2 +- src/backend/CMakeLists.txt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f84bbb2..6a51fac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(ScadaForDiesel ) # Глобальные настройки -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 0da4c20..ce35854 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) # Настройки проекта -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -45,8 +45,7 @@ set(BACKEND_HEADERS ) # Библиотека -add_library(backend_lib SHARED - TypeDecoder.h) +add_library(backend_lib SHARED) target_sources(backend_lib PRIVATE From ddcf3d4b0b7f496f8f5295b16df2108c29aab0b3 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Sun, 10 May 2026 12:05:05 +0500 Subject: [PATCH 50/58] fix: fix undefined reference error for RegisterConverter - move template method definition to header --- include/backend/modbus_client/RegisterConverter.h | 14 ++++++++++++++ src/backend/modbus_client/RegisterConverter.cpp | 13 ------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/backend/modbus_client/RegisterConverter.h b/include/backend/modbus_client/RegisterConverter.h index ffed103..85da976 100644 --- a/include/backend/modbus_client/RegisterConverter.h +++ b/include/backend/modbus_client/RegisterConverter.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include class RegisterConverter { @@ -13,3 +15,15 @@ class RegisterConverter private: quint64 fromRegisterWordRawValue(const quint16* value, qsizetype count); }; + +template +QList RegisterConverter::toRegisterWords(const ValueT& value) +{ + if (sizeof(ValueT) % sizeof(quint16) != 0) + { + qWarning() << "Value type must be divisible by register word"; + return {}; + } + auto bits = std::bit_cast>(value); + return QList(bits.begin(), bits.end()); +} diff --git a/src/backend/modbus_client/RegisterConverter.cpp b/src/backend/modbus_client/RegisterConverter.cpp index 4e61660..1a5619b 100644 --- a/src/backend/modbus_client/RegisterConverter.cpp +++ b/src/backend/modbus_client/RegisterConverter.cpp @@ -1,6 +1,5 @@ #include "backend/modbus_client/RegisterConverter.h" #include -#include double RegisterConverter::fromRegisterWordDouble(const quint16* value) { @@ -17,18 +16,6 @@ int RegisterConverter::fromRegisterWordInt(const quint16* value) return static_cast(fromRegisterWordRawValue(value, 4)); } -template -QList RegisterConverter::toRegisterWords(const ValueT& value) -{ - if (sizeof(ValueT) % sizeof(quint16) != 0) - { - qWarning() << "Value type must be divisible by register word"; - return {}; - } - auto bits = std::bit_cast>(value); - return QVector(bits.begin(), bits.end()); -} - quint64 RegisterConverter::fromRegisterWordRawValue(const quint16* value, qsizetype count) { if (count > 4) From 1cc1209353151bb2a5cc22f98892e20b86054ac0 Mon Sep 17 00:00:00 2001 From: igor66ru Date: Sun, 10 May 2026 17:39:39 +0500 Subject: [PATCH 51/58] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D1=8F=D1=8E=D1=89?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B2=D0=BE=D0=B7=D0=B4=D0=B5=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B8=D1=8F=20=D0=B2=20DataTypes.h?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/backend/DataTypes.h | 47 +++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 56f12c4..964573e 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -96,7 +96,7 @@ namespace DiscreteRegisters struct ModbusConfig { //Ip-адрес Modbus-сервера - QString host; + QString host = "127.0.0.1"; //Порт Modbus-сервера quint16 port = 1502; int pollFrequencyMs = 1000; @@ -144,25 +144,58 @@ struct ModelConfig int maxRpmPrir; //Общая частота оборотов в режиме обкатки int maxRpmRun; + //Частота АД + double freqAD; + //Момент АД + double momentAD; }; Q_DECLARE_METATYPE(ModelConfig) + +//Команды на симуляцию +enum class SimulationCommand : uint8_t +{ + None, + Start, + Stop, + Reset, + EmergencyStop +}; + +//Запрос на симуляцию +enum class SimulationRequest : uint8_t +{ + None, + ReadCurrentState, + StepAndRead +}; + +//Режим симуляции +enum class SimulationMode : uint8_t +{ + ColdRun, + StartWarmup, + HotNoLoad, + HotLoad +}; + // тип управляющего воздействия (это требует уточнения) enum class ControlType { None, - Throttle, // дроссель - BrakeTorque, // тормозной момент - TargetRpm, // целевые обороты - MotorEnable, // вкл/выкл мотора - EmergencyStop // аварийный стоп + SimulationCommand, // Команда на симуляцию (simulationCommand) + SimulationRequest, // Запрос на симуляцию (simulationRequest) + SimulationMode, // Режим симуляции (simulationMode) + Fan_ICE, + Fan_AD, + Fan_Ballast }; // одно управляющее воздействие (тип + значение) struct ModelControl { ControlType type = ControlType::None; - double value = 0.0; + uint8_t value = 0; }; Q_DECLARE_METATYPE(ModelControl) From 15edd814e42a9dcad9d9cf6963ed35a967bb4afc Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Mon, 11 May 2026 00:45:33 +0500 Subject: [PATCH 52/58] fix: make modbus client signals work - add global macros for keywords necessary to use shared libaries - remove forward declarations from IModbusBridge - add more logging into QtModbusBridge - ensure QtModbusBridge can connect to modbus client and request sensor data --- include/backend/BackendGlobal.h | 8 ++ include/backend/modbus_client/IModbusBridge.h | 9 +- .../backend/modbus_client/QtModbusBridge.h | 2 +- .../backend/modbus_client/RegisterConverter.h | 1 + src/backend/CMakeLists.txt | 97 ++++++++++--------- src/backend/modbus_client/QtModbusBridge.cpp | 10 +- 6 files changed, 72 insertions(+), 55 deletions(-) create mode 100644 include/backend/BackendGlobal.h diff --git a/include/backend/BackendGlobal.h b/include/backend/BackendGlobal.h new file mode 100644 index 0000000..6b89eee --- /dev/null +++ b/include/backend/BackendGlobal.h @@ -0,0 +1,8 @@ +#pragma once +#include + +#if defined(BACKEND_LIBRARY) +# define BACKEND_EXPORT Q_DECL_EXPORT +#else +# define BACKEND_EXPORT Q_DECL_IMPORT +#endif diff --git a/include/backend/modbus_client/IModbusBridge.h b/include/backend/modbus_client/IModbusBridge.h index 300435f..c654287 100644 --- a/include/backend/modbus_client/IModbusBridge.h +++ b/include/backend/modbus_client/IModbusBridge.h @@ -1,13 +1,10 @@ #pragma once #include #include +#include "backend/DataTypes.h" +#include "backend/BackendGlobal.h" -class ModbusConfig; -class ModelConfig; -class Decision; -class SensorFrame; - -class IModbusBridge : public QObject +class BACKEND_EXPORT IModbusBridge : public QObject { Q_OBJECT public: diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index 1e3b3df..574a937 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -6,7 +6,7 @@ #include #include -class QtModbusBridge : public IModbusBridge +class BACKEND_EXPORT QtModbusBridge : public IModbusBridge { Q_OBJECT public: diff --git a/include/backend/modbus_client/RegisterConverter.h b/include/backend/modbus_client/RegisterConverter.h index 85da976..56d6cc0 100644 --- a/include/backend/modbus_client/RegisterConverter.h +++ b/include/backend/modbus_client/RegisterConverter.h @@ -3,6 +3,7 @@ #include #include #include +#include class RegisterConverter { diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index ce35854..14d66d6 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -36,6 +36,7 @@ set(BACKEND_HEADERS ${PROJECT_SOURCE_DIR}/include/backend/data_processing/DataProcessor.h ${PROJECT_SOURCE_DIR}/include/backend/data_store/DataStore.h ${PROJECT_SOURCE_DIR}/include/backend/DataTypes.h + ${PROJECT_SOURCE_DIR}/include/backend/BackendGlobal.h ${PROJECT_SOURCE_DIR}/include/backend/config_data/config_data.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/IModbusBridge.h ${PROJECT_SOURCE_DIR}/include/backend/modbus_client/QtModbusBridge.h @@ -47,6 +48,8 @@ set(BACKEND_HEADERS # Библиотека add_library(backend_lib SHARED) +target_compile_definitions(backend_lib PRIVATE BACKEND_LIBRARY) + target_sources(backend_lib PRIVATE ${BACKEND_SOURCES} @@ -74,53 +77,53 @@ source_group(TREE "${PROJECT_SOURCE_DIR}/include" PREFIX "include" FILES ${BACKE source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "src" FILES ${BACKEND_SOURCES}) # Unit Tests -enable_testing() - -# Макрос для создания тестов -macro(add_backend_test TEST_NAME) - add_executable(${TEST_NAME} ${ARGN}) - - target_include_directories(${TEST_NAME} PRIVATE - ${PROJECT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR} - ) - - target_link_libraries(${TEST_NAME} PRIVATE - Qt6::Core - Qt6::Test - Qt6::Network - Qt6::SerialBus - backend_lib - ) - - add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) -endmacro() - -# Добавляем тесты -add_backend_test(test_backend_worker - unit_tests/backend_worker_test/backend_worker_test.cpp - unit_tests/backend_worker_test/backend_worker_test.h -) - -add_backend_test(test_data_processor - unit_tests/data_processor_test/data_processor_test.cpp - unit_tests/data_processor_test/data_processor_test.h -) - -add_backend_test(test_data_store - unit_tests/data_store_test/data_store_test.cpp - unit_tests/data_store_test/data_store_test.h -) - -add_backend_test(test_modbus_data_bridge - unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp - unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h -) - -add_backend_test(test_state_machine - unit_tests/state_machine_test/state_machine_test.cpp - unit_tests/state_machine_test/state_machine_test.h -) +# enable_testing() + +# # Макрос для создания тестов +# macro(add_backend_test TEST_NAME) +# add_executable(${TEST_NAME} ${ARGN}) + +# target_include_directories(${TEST_NAME} PRIVATE +# ${PROJECT_SOURCE_DIR}/include +# ${CMAKE_CURRENT_SOURCE_DIR} +# ) + +# target_link_libraries(${TEST_NAME} PRIVATE +# Qt6::Core +# Qt6::Test +# Qt6::Network +# Qt6::SerialBus +# backend_lib +# ) + +# add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) +# endmacro() + +# # Добавляем тесты +# add_backend_test(test_backend_worker +# unit_tests/backend_worker_test/backend_worker_test.cpp +# unit_tests/backend_worker_test/backend_worker_test.h +# ) + +# add_backend_test(test_data_processor +# unit_tests/data_processor_test/data_processor_test.cpp +# unit_tests/data_processor_test/data_processor_test.h +# ) + +# add_backend_test(test_data_store +# unit_tests/data_store_test/data_store_test.cpp +# unit_tests/data_store_test/data_store_test.h +# ) + +# add_backend_test(test_modbus_data_bridge +# unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.cpp +# unit_tests/modbus_data_bridge_test/modbus_data_bridge_test.h +# ) + +# add_backend_test(test_state_machine +# unit_tests/state_machine_test/state_machine_test.cpp +# unit_tests/state_machine_test/state_machine_test.h +# ) # Вывод информации о сборке message(STATUS "Backend Library: backend_lib") diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 20338e1..0228b6e 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -110,8 +110,10 @@ void QtModbusBridge::onReadSensors() // Handling reply with finished signal QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() { + qDebug() << "[QtModbusBridge] Received reply with sensor data, begin to extract values"; auto values = extractValues(reply, InputRegisters::size); if (!values.has_value()) + qWarning() << "[QtModbusBridge] Failed to extract data"; return; parseSensorsResponse(values.value()); reply->deleteLater(); @@ -199,14 +201,18 @@ void QtModbusBridge::onWriteDecision(const Decision& decision) std::optional> QtModbusBridge::extractValues(QModbusReply* reply, qsizetype expectedSize) { if (reply->error() != QModbusDevice::NoError) + { return std::nullopt; + } const QModbusDataUnit result = reply->result(); const QVector values = result.values(); if (values.size() != expectedSize) { - emit requestError("Size of received data differs from expected HoldingRegisters::size"); + qWarning() << "[extractValues] size mismatch, values.size() =" << values.size() + << ", expectedSize =" << expectedSize; + emit requestError("Size of received data differs from expected size"); return std::nullopt; } @@ -215,6 +221,8 @@ std::optional> QtModbusBridge::extractValues(QModbusReply* repl void QtModbusBridge::parseSensorsResponse(const QVector& values) { + qDebug() << "[parseSensorsResponse] entered, values.size() =" << values.size(); + SensorFrame frame = { m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_cool]), m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_AD]), From 56fead1c2f4d877d92075c87ed22a8e123cff11f Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Mon, 11 May 2026 11:16:15 +0500 Subject: [PATCH 53/58] fix: prevent unwanted rewriting of all registers - write request is created for each contiguous register block - updated register mapping --- include/backend/DataTypes.h | 31 ++-- .../backend/modbus_client/QtModbusBridge.h | 6 +- .../backend/modbus_client/RegisterConverter.h | 7 +- src/backend/modbus_client/QtModbusBridge.cpp | 133 +++++++++++++----- 4 files changed, 122 insertions(+), 55 deletions(-) diff --git a/include/backend/DataTypes.h b/include/backend/DataTypes.h index 964573e..ffa8754 100644 --- a/include/backend/DataTypes.h +++ b/include/backend/DataTypes.h @@ -8,8 +8,7 @@ // InputRegisters offset (sensors data, only read operations) namespace InputRegisters { - static constexpr qsizetype size = 50; - static constexpr uint16_t count = 14; + static constexpr qsizetype count = 50; // Температура охлаждающей жидкости static constexpr uint16_t T_cool = 0; // Давление масла @@ -43,8 +42,7 @@ namespace InputRegisters // HoldingRegisters offset (settings and variables, read/write operations) namespace HoldingRegisters { - static constexpr qsizetype size = 43; - static constexpr uint16_t count = 13; + static constexpr qsizetype count = 59; // Максимально допустимая температура охлаждающей жидкости static constexpr uint16_t T_cool_max = 0; // Минимально допустимое давление масла @@ -55,28 +53,34 @@ namespace HoldingRegisters static constexpr uint16_t omega_ICE_max_prir = 12; // Максимально допустимая частота вращения ДВС в режиме обкатки static constexpr uint16_t omega_ICE_max_run = 16; + // Лишний регистр + static constexpr uint16_t rpm_max_lapping = 20; + // Лишний регистр + static constexpr uint16_t rpm_max_run = 24; + // Лишний регистр + static constexpr uint16_t target_brake_torque_nm = 28; + static constexpr uint16_t throttle_position = 32; // Максимально допустимая температура АД - static constexpr uint16_t T_AD_max = 20; + static constexpr uint16_t T_AD_max = 36; // Максимально допустимая температура балластных резисторов - static constexpr uint16_t T_ballast_max = 24; + static constexpr uint16_t T_ballast_max = 40; // Входная частота для АД / задание частоты АД - static constexpr uint16_t f_AD_Input = 28; + static constexpr uint16_t f_AD_Input = 44; // Целевая механическая нагрузка / момент АД - static constexpr uint16_t M_AD_target = 32; + static constexpr uint16_t M_AD_target = 48; // Версия (ревизия) настроек holding-регистров - static constexpr uint16_t revision_h = 36; + static constexpr uint16_t revision_h = 52; // Команда на симуляцию - static constexpr uint16_t simulationCommand = 40; + static constexpr uint16_t simulationCommand = 56; // Запрос на симуляцию - static constexpr uint16_t simulationRequest = 41; + static constexpr uint16_t simulationRequest = 57; // Режим симуляции - static constexpr uint16_t simulationMode = 42; + static constexpr uint16_t simulationMode = 58; } // Coils offset (control registers, read/write operations) namespace CoilsRegisters { - static constexpr qsizetype size = 3; static constexpr uint16_t count = 3; static constexpr uint16_t fan_ICE = 0; static constexpr uint16_t fan_AD = 1; @@ -86,7 +90,6 @@ namespace CoilsRegisters // Discrete Inputs offset (device status, only read operations) namespace DiscreteRegisters { - static constexpr qsizetype size = 2; static constexpr uint16_t count = 2; static constexpr uint16_t hasFault = 0; static constexpr uint16_t hasLimitViolations = 1; diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index 574a937..6ff1093 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -6,6 +6,8 @@ #include #include +using RegisterPair = std::pair>; + class BACKEND_EXPORT QtModbusBridge : public IModbusBridge { Q_OBJECT @@ -26,11 +28,11 @@ private slots: void parseSensorsResponse(const QVector& values); void parseInfoResponse(const QVector& values); std::optional> extractValues(QModbusReply* reply, qsizetype expectedSize); - void setRegisterValues(QModbusDataUnit& dataUnit, qsizetype startAddress, const QList& values); + void writeRegisterVector(std::span regs); private: ModbusConfig m_cfg; QModbusTcpClient m_client; QTimer m_pollTimer; - RegisterConverter m_decoder; + RegisterConverter m_reg_converter; QModbusDevice::State m_prevState = QModbusDevice::UnconnectedState; }; diff --git a/include/backend/modbus_client/RegisterConverter.h b/include/backend/modbus_client/RegisterConverter.h index 56d6cc0..2bd34fd 100644 --- a/include/backend/modbus_client/RegisterConverter.h +++ b/include/backend/modbus_client/RegisterConverter.h @@ -12,19 +12,20 @@ class RegisterConverter qint64 fromRegisterWordQint(const quint16* value); int fromRegisterWordInt(const quint16* value); template - QList toRegisterWords(const ValueT& value); + QVector toRegisterWords(const ValueT& value); private: quint64 fromRegisterWordRawValue(const quint16* value, qsizetype count); }; template -QList RegisterConverter::toRegisterWords(const ValueT& value) +QVector RegisterConverter::toRegisterWords(const ValueT& value) { + static_assert(std::is_trivially_copyable_v); if (sizeof(ValueT) % sizeof(quint16) != 0) { qWarning() << "Value type must be divisible by register word"; return {}; } auto bits = std::bit_cast>(value); - return QList(bits.begin(), bits.end()); + return QVector(bits.begin(), bits.end()); } diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 0228b6e..76b5673 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -63,7 +63,9 @@ void QtModbusBridge::onErrorOccured() { QModbusDevice::Error error = m_client.error(); if (error == QModbusDevice::NoError) + { return; + } QString reason = m_client.errorString(); if (error == QModbusDevice::ConfigurationError) @@ -105,7 +107,9 @@ void QtModbusBridge::onReadSensors() // Sending request to the device with specified Unit Id auto* reply = m_client.sendReadRequest(dataUnit, m_cfg.unitId); if (!reply) + { return; + } // Handling reply with finished signal QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() @@ -113,8 +117,10 @@ void QtModbusBridge::onReadSensors() qDebug() << "[QtModbusBridge] Received reply with sensor data, begin to extract values"; auto values = extractValues(reply, InputRegisters::size); if (!values.has_value()) - qWarning() << "[QtModbusBridge] Failed to extract data"; + { + qWarning() << "[QtModbusBridge] Failed to extract sensor data"; return; + } parseSensorsResponse(values.value()); reply->deleteLater(); }); @@ -129,14 +135,20 @@ void QtModbusBridge::onReadInfo() // Sending request to the device with specified Unit Id auto* reply = m_client.sendReadRequest(dataUnit, m_cfg.unitId); if (!reply) + { return; + } // Handling reply with finished signal QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() { + qDebug() << "[QtModbusBridge] Received reply with model info data, begin to extract values"; auto values = extractValues(reply, HoldingRegisters::size); if (!values.has_value()) + { + qWarning() << "[QtModbusBridge] Failed to extract model info data"; return; + } parseInfoResponse(values.value()); reply->deleteLater(); }); @@ -145,25 +157,42 @@ void QtModbusBridge::onReadInfo() void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) { qDebug() << "[QtModbusBridge] Writing a new configuration to the model"; - - QModbusDataUnit dataUnit(QModbusDataUnit::HoldingRegisters, 0, HoldingRegisters::count); - setRegisterValues(dataUnit, HoldingRegisters::omega_ICE_max_prir, m_decoder.toRegisterWords(cmd.maxRpmPrir)); - setRegisterValues(dataUnit, HoldingRegisters::omega_ICE_max_run, m_decoder.toRegisterWords(cmd.maxRpmRun)); - setRegisterValues(dataUnit, HoldingRegisters::P_oil_max, m_decoder.toRegisterWords(cmd.maxDieselPressure)); - setRegisterValues(dataUnit, HoldingRegisters::P_oil_min, m_decoder.toRegisterWords(cmd.minDieselPressure)); - setRegisterValues(dataUnit, HoldingRegisters::T_cool_max, m_decoder.toRegisterWords(cmd.maxDieselTemp)); - setRegisterValues(dataUnit, HoldingRegisters::T_AD_max, m_decoder.toRegisterWords(cmd.maxMotorTemp)); - setRegisterValues(dataUnit, HoldingRegisters::T_ballast_max, m_decoder.toRegisterWords(cmd.maxResistorTemp)); - - auto* reply = m_client.sendWriteRequest(dataUnit, m_cfg.unitId); - if (!reply) - return; + regs.emplace_back( + HoldingRegisters::omega_ICE_max_prir, + m_reg_converter.toRegisterWords(cmd.maxRpmRun) + ); + regs.emplace_back( + HoldingRegisters::omega_ICE_max_run, + m_reg_converter.toRegisterWords(cmd.maxDieselPressure) + ); + regs.emplace_back( + HoldingRegisters::P_oil_max, + m_reg_converter.toRegisterWords(cmd.maxDieselPressure) + ); + regs.emplace_back( + HoldingRegisters::P_oil_min, + m_reg_converter.toRegisterWords(cmd.minDieselPressure) + ); + regs.emplace_back( + HoldingRegisters::T_cool_max, + m_reg_converter.toRegisterWords(cmd.maxDieselTemp) + ); + regs.emplace_back( + HoldingRegisters::T_AD_max, + m_reg_converter.toRegisterWords(cmd.maxMotorTemp) + ); + regs.emplace_back( + HoldingRegisters::T_ballast_max, + m_reg_converter.toRegisterWords(cmd.maxResistorTemp) + ); + writeRegisterVector(regs); } void QtModbusBridge::onWriteDecision(const Decision& decision) { qDebug() << "[QtModbusBridge] Sending a decision to the model"; - QModbusDataUnit dataUnit(QModbusDataUnit::HoldingRegisters, 0, HoldingRegisters::count); + QVector regs; + regs.reserve(decision.controls.size()); for (const auto& control : decision.controls) { qsizetype startAddress = -1; @@ -191,11 +220,9 @@ void QtModbusBridge::onWriteDecision(const Decision& decision) } if (startAddress == -1) continue; - dataUnit.setValue(startAddress, control.value); + regs.emplace_back(startAddress, std::move(control.value)); } - auto* reply = m_client.sendWriteRequest(dataUnit, m_cfg.unitId); - if (!reply) - return; + writeRegisterVector(regs); } std::optional> QtModbusBridge::extractValues(QModbusReply* reply, qsizetype expectedSize) @@ -224,13 +251,13 @@ void QtModbusBridge::parseSensorsResponse(const QVector& values) qDebug() << "[parseSensorsResponse] entered, values.size() =" << values.size(); SensorFrame frame = { - m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_cool]), - m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_AD]), - m_decoder.fromRegisterWordDouble(&values[InputRegisters::T_ballast]), - m_decoder.fromRegisterWordDouble(&values[InputRegisters::P_oil]), - m_decoder.fromRegisterWordDouble(&values[InputRegisters::M_AD]), - m_decoder.fromRegisterWordDouble(&values[InputRegisters::f_AD]), - m_decoder.fromRegisterWordQint(&values[InputRegisters::timestamp_ir]), + m_reg_converter.fromRegisterWordDouble(&values[InputRegisters::T_cool]), + m_reg_converter.fromRegisterWordDouble(&values[InputRegisters::T_AD]), + m_reg_converter.fromRegisterWordDouble(&values[InputRegisters::T_ballast]), + m_reg_converter.fromRegisterWordDouble(&values[InputRegisters::P_oil]), + m_reg_converter.fromRegisterWordDouble(&values[InputRegisters::M_AD]), + m_reg_converter.fromRegisterWordDouble(&values[InputRegisters::f_AD]), + m_reg_converter.fromRegisterWordQint(&values[InputRegisters::timestamp_ir]), 1 // TODO: clarify what does stage mean }; emit sensorsDataReady(frame); @@ -239,21 +266,55 @@ void QtModbusBridge::parseSensorsResponse(const QVector& values) void QtModbusBridge::parseInfoResponse(const QVector& values) { ModelConfig info = { - m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::T_cool_max]), - m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::T_AD_max]), - m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::T_ballast_max]), - m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::P_oil_max]), - m_decoder.fromRegisterWordDouble(&values[HoldingRegisters::P_oil_min]), - m_decoder.fromRegisterWordInt(&values[HoldingRegisters::omega_ICE_max_prir]), - m_decoder.fromRegisterWordInt(&values[HoldingRegisters::omega_ICE_max_run]), + m_reg_converter.fromRegisterWordDouble(&values[HoldingRegisters::T_cool_max]), + m_reg_converter.fromRegisterWordDouble(&values[HoldingRegisters::T_AD_max]), + m_reg_converter.fromRegisterWordDouble(&values[HoldingRegisters::T_ballast_max]), + m_reg_converter.fromRegisterWordDouble(&values[HoldingRegisters::P_oil_max]), + m_reg_converter.fromRegisterWordDouble(&values[HoldingRegisters::P_oil_min]), + m_reg_converter.fromRegisterWordInt(&values[HoldingRegisters::omega_ICE_max_prir]), + m_reg_converter.fromRegisterWordInt(&values[HoldingRegisters::omega_ICE_max_run]), }; emit modelInfoReady(info); } -void QtModbusBridge::setRegisterValues(QModbusDataUnit& dataUnit, qsizetype startAddress, const QList& values) +void QtModbusBridge::writeRegisterVector(std::span regs) { - for (qsizetype i = 0; i < values.size(); ++i) + if (regs.empty()) + return; + + quint16 batchStart = regs[0].first; + quint16 expectedAddress = batchStart; + QVector batchValues; + batchValues.reserve(regs.size()); + + auto flush = [&]() { - dataUnit.setValue(startAddress + i, values[i]); + if (batchValues.isEmpty()) + return; + + QModbusDataUnit unit( + QModbusDataUnit::HoldingRegisters, + batchStart, + batchValues + ); + + m_client.sendWriteRequest(unit, m_cfg.unitId); + }; + + for (const auto& [address, values] : regs) + { + if (address != expectedAddress) + { + flush(); + + batchStart = address; + batchValues.clear(); + expectedAddress = address; + } + + batchValues.append(values); + expectedAddress += values.size(); } + + flush(); } \ No newline at end of file From 65fbbc04510e76a2a0d50ed29240c6999e0ccaa2 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Mon, 11 May 2026 12:55:13 +0500 Subject: [PATCH 54/58] fix: update modbus client to new DataTypes - add regType and update writeRegisterVector - add sorting into writeRegisterVector --- .../backend/modbus_client/QtModbusBridge.h | 10 ++- src/backend/modbus_client/QtModbusBridge.cpp | 89 ++++++++++++++----- 2 files changed, 74 insertions(+), 25 deletions(-) diff --git a/include/backend/modbus_client/QtModbusBridge.h b/include/backend/modbus_client/QtModbusBridge.h index 6ff1093..78455b5 100644 --- a/include/backend/modbus_client/QtModbusBridge.h +++ b/include/backend/modbus_client/QtModbusBridge.h @@ -6,7 +6,12 @@ #include #include -using RegisterPair = std::pair>; +struct RegisterBlock +{ + QModbusDataUnit::RegisterType type; + quint16 startAddress; + QVector values; +}; class BACKEND_EXPORT QtModbusBridge : public IModbusBridge { @@ -28,7 +33,8 @@ private slots: void parseSensorsResponse(const QVector& values); void parseInfoResponse(const QVector& values); std::optional> extractValues(QModbusReply* reply, qsizetype expectedSize); - void writeRegisterVector(std::span regs); + //OPTIMIZE: move sorting elsewhere and receive regs by reference if encountered speed issues + void writeRegisterVector(QVector regs); private: ModbusConfig m_cfg; QModbusTcpClient m_client; diff --git a/src/backend/modbus_client/QtModbusBridge.cpp b/src/backend/modbus_client/QtModbusBridge.cpp index 76b5673..d19ee43 100644 --- a/src/backend/modbus_client/QtModbusBridge.cpp +++ b/src/backend/modbus_client/QtModbusBridge.cpp @@ -4,6 +4,7 @@ #include #include #include +#include QtModbusBridge::QtModbusBridge(const ModbusConfig& cfg, QObject* parent) : IModbusBridge(cfg, parent) { @@ -115,7 +116,7 @@ void QtModbusBridge::onReadSensors() QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() { qDebug() << "[QtModbusBridge] Received reply with sensor data, begin to extract values"; - auto values = extractValues(reply, InputRegisters::size); + auto values = extractValues(reply, InputRegisters::count); if (!values.has_value()) { qWarning() << "[QtModbusBridge] Failed to extract sensor data"; @@ -143,7 +144,7 @@ void QtModbusBridge::onReadInfo() QObject::connect(reply, &QModbusReply::finished, this, [this, reply]() { qDebug() << "[QtModbusBridge] Received reply with model info data, begin to extract values"; - auto values = extractValues(reply, HoldingRegisters::size); + auto values = extractValues(reply, HoldingRegisters::count); if (!values.has_value()) { qWarning() << "[QtModbusBridge] Failed to extract model info data"; @@ -157,62 +158,93 @@ void QtModbusBridge::onReadInfo() void QtModbusBridge::onWriteConfig(const ModelConfig& cmd) { qDebug() << "[QtModbusBridge] Writing a new configuration to the model"; + + QVector regs; + regs.reserve(9); + regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::omega_ICE_max_prir, m_reg_converter.toRegisterWords(cmd.maxRpmRun) ); regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::omega_ICE_max_run, m_reg_converter.toRegisterWords(cmd.maxDieselPressure) ); regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::P_oil_max, m_reg_converter.toRegisterWords(cmd.maxDieselPressure) ); regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::P_oil_min, m_reg_converter.toRegisterWords(cmd.minDieselPressure) ); regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::T_cool_max, m_reg_converter.toRegisterWords(cmd.maxDieselTemp) ); regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::T_AD_max, m_reg_converter.toRegisterWords(cmd.maxMotorTemp) ); regs.emplace_back( + QModbusDataUnit::HoldingRegisters, HoldingRegisters::T_ballast_max, m_reg_converter.toRegisterWords(cmd.maxResistorTemp) ); + regs.emplace_back( + QModbusDataUnit::HoldingRegisters, + HoldingRegisters::f_AD_Input, + m_reg_converter.toRegisterWords(cmd.freqAD) + ); + regs.emplace_back( + QModbusDataUnit::HoldingRegisters, + HoldingRegisters::M_AD_target, + m_reg_converter.toRegisterWords(cmd.momentAD) + ); writeRegisterVector(regs); } void QtModbusBridge::onWriteDecision(const Decision& decision) { qDebug() << "[QtModbusBridge] Sending a decision to the model"; - QVector regs; + QVector regs; regs.reserve(decision.controls.size()); for (const auto& control : decision.controls) { + QModbusDataUnit::RegisterType regType = QModbusDataUnit::Invalid; qsizetype startAddress = -1; switch(control.type) { - case ControlType::BrakeTorque: - startAddress = HoldingRegisters::M_AD_target; - break; - case ControlType::EmergencyStop: - startAddress = HoldingRegisters::simulationCommand; - break; - case ControlType::MotorEnable: + case ControlType::SimulationCommand: + regType = QModbusDataUnit::HoldingRegisters; startAddress = HoldingRegisters::simulationCommand; break; - case ControlType::TargetRpm: - startAddress = HoldingRegisters::f_AD_Input; + case ControlType::SimulationMode: + regType = QModbusDataUnit::HoldingRegisters; + startAddress = HoldingRegisters::simulationMode; break; - case ControlType::Throttle: + case ControlType::SimulationRequest: + regType = QModbusDataUnit::HoldingRegisters; startAddress = HoldingRegisters::simulationRequest; break; + case ControlType::Fan_AD: + regType = QModbusDataUnit::Coils; + startAddress = CoilsRegisters::fan_AD; + break; + case ControlType::Fan_Ballast: + regType = QModbusDataUnit::Coils; + startAddress = CoilsRegisters::fan_ballast; + break; + case ControlType::Fan_ICE: + regType = QModbusDataUnit::Coils; + startAddress = CoilsRegisters::fan_ICE; + break; case ControlType::None: break; default: @@ -220,7 +252,11 @@ void QtModbusBridge::onWriteDecision(const Decision& decision) } if (startAddress == -1) continue; - regs.emplace_back(startAddress, std::move(control.value)); + regs.emplace_back( + regType, + startAddress, + QVector{ static_cast(control.value) } + ); } writeRegisterVector(regs); } @@ -277,13 +313,19 @@ void QtModbusBridge::parseInfoResponse(const QVector& values) emit modelInfoReady(info); } -void QtModbusBridge::writeRegisterVector(std::span regs) +void QtModbusBridge::writeRegisterVector(QVector regs) { if (regs.empty()) return; - quint16 batchStart = regs[0].first; + std::sort(regs.begin(), regs.end(), [](const auto& lhs, const auto& rhs) { + return std::tie(lhs.type, lhs.startAddress) < std::tie(rhs.type, rhs.startAddress); + }); + + QModbusDataUnit::RegisterType batchType = regs[0].type; + quint16 batchStart = regs[0].startAddress; quint16 expectedAddress = batchStart; + QVector batchValues; batchValues.reserve(regs.size()); @@ -293,7 +335,7 @@ void QtModbusBridge::writeRegisterVector(std::span regs) return; QModbusDataUnit unit( - QModbusDataUnit::HoldingRegisters, + batchType, batchStart, batchValues ); @@ -301,19 +343,20 @@ void QtModbusBridge::writeRegisterVector(std::span regs) m_client.sendWriteRequest(unit, m_cfg.unitId); }; - for (const auto& [address, values] : regs) + for (const RegisterBlock& reg : regs) { - if (address != expectedAddress) + if (reg.startAddress != expectedAddress || reg.type != batchType) { flush(); - batchStart = address; + batchType = reg.type; + batchStart = reg.startAddress; batchValues.clear(); - expectedAddress = address; + expectedAddress = reg.startAddress; } - batchValues.append(values); - expectedAddress += values.size(); + batchValues.append(reg.values); + expectedAddress += reg.values.size(); } flush(); From 17a4fe8a922d9e5dfd39eab2c0c19c61244848bd Mon Sep 17 00:00:00 2001 From: karskanovas <155314713+karskanovas@users.noreply.github.com> Date: Tue, 12 May 2026 09:43:58 +0500 Subject: [PATCH 55/58] Update DataProcessor.cpp --- src/backend/data_processing/DataProcessor.cpp | 77 +++++++++++++------ 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index 6168258..ac1042f 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,46 +1,56 @@ -#include "backend/data_processing/DataProcessor.h" +#include "DataProcessor.h" + // Этапы — нумерация условная, должна совпадать со StateMachine. -// state_machine.cpp использует m_currentStageIndex как int. namespace Stage { - constexpr int IDLE = -1; // в state_machine.cpp m_currentStageIndex = -1 до start() + constexpr int IDLE = -1; constexpr int COLD_CRANKING = 0; constexpr int START_AND_WARMUP = 1; constexpr int HOT_NO_LOAD = 2; constexpr int HOT_WITH_LOAD = 3; } + // Порог предупреждения = 90% от критического. static constexpr double WARN_RATIO = 0.9; + DataProcessor::DataProcessor(const ModelConfig &config, QObject *parent) : QObject(parent), m_config(config) { } + void DataProcessor::onStageChanged(int newStage) { m_currentStage = newStage; } + void DataProcessor::onConfigChanged(ModelConfig config) { m_config = config; } + void DataProcessor::reset() { m_alarmLatched = false; m_currentStage = Stage::IDLE; } + Decision DataProcessor::processFrame(const SensorFrame &frame) { Decision decision; - // После аварии — пустое решение. + + // После аварии — пустое решение (только аварийная остановка). if (m_alarmLatched) { decision.state = DiagState::Alarm_Generic; decision.reason = QStringLiteral("alarm latched"); + decision.controls = makeStopControls(); emit decisionReady(decision); return decision; } + const int stage = frame.stage; - // неактивные режимы — проверки выключены. + + // Неактивные режимы — проверки выключены. if (stage < Stage::COLD_CRANKING) { decision.state = DiagState::Ok; @@ -48,6 +58,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) emit decisionReady(decision); return decision; } + const double maxRpm = (stage == Stage::COLD_CRANKING) ? static_cast(m_config.maxRpmPrir) @@ -66,6 +77,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) worstReason = rsn; } }; + // обороты ДВС if (isParamActiveOnStage("rpm", stage)) { @@ -80,6 +92,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) QStringLiteral("Обороты приближаются к пределу: %1").arg(frame.rpm)); } } + // температура ДВС (охлаждающей жидкости) if (isParamActiveOnStage("dieselTemp", stage)) { @@ -95,6 +108,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) .arg(frame.dieselTemp)); } } + // температура АД if (isParamActiveOnStage("motorTemp", stage)) { @@ -110,6 +124,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) .arg(frame.motorTemp)); } } + // температура балластных резисторов if (isParamActiveOnStage("resistorTemp", stage)) { @@ -125,6 +140,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) .arg(frame.resistorTemp)); } } + // давление масла ДВС — верхняя граница if (isParamActiveOnStage("dieselPressureMax", stage)) { @@ -140,10 +156,12 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) .arg(frame.dieselPressure)); } } + // давление масла ДВС — нижняя граница if (isParamActiveOnStage("dieselPressureMin", stage)) { - const double warnLow = m_config.minDieselPressure + m_config.minDieselPressure * (1.0 - WARN_RATIO); + const double warnLow = m_config.minDieselPressure + + m_config.minDieselPressure * (1.0 - WARN_RATIO); if (frame.dieselPressure <= m_config.minDieselPressure) { promote(2, DiagState::Alarm_PressureUnder, @@ -156,40 +174,46 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) .arg(frame.dieselPressure)); } } + // ---- Формирование Decision ---- decision.state = worstState; decision.reason = worstReason; + if (worstSeverity == 2) { - // Авария + // Авария — фиксируем и формируем команду аварийной остановки. m_alarmLatched = true; decision.controls = makeStopControls(); emit decisionReady(decision); return decision; } + if (worstSeverity == 1) { - // предаварийное состояние + // Предаварийное состояние — включаем соответствующие вентиляторы + // через CoilsRegisters (Fan_ICE / Fan_AD / Fan_Ballast). ModelControl mc; switch (worstState) { - case DiagState::PreWarn_RpmHigh: - // Снижаем дроссель. - mc.type = ControlType::Throttle; - mc.value = 0.0; + case DiagState::PreWarn_DieselTempHigh: + mc.type = ControlType::Fan_ICE; + mc.value = 1; + decision.controls.append(mc); + break; + case DiagState::PreWarn_MotorTempHigh: + mc.type = ControlType::Fan_AD; + mc.value = 1; decision.controls.append(mc); break; case DiagState::PreWarn_ResistorHigh: - // Снижаем тормозной момент. - mc.type = ControlType::BrakeTorque; - mc.value = 0.0; + mc.type = ControlType::Fan_Ballast; + mc.value = 1; decision.controls.append(mc); break; - // Перегревы ДВС / АД и проблемы с давлением: - // включением вентиляторов в applyControls будет заниматься - // потребитель Decision — DataProcessor возвращает только DiagState и reason - case DiagState::PreWarn_DieselTempHigh: - case DiagState::PreWarn_MotorTempHigh: + // Для предупреждений по оборотам и давлению масла + // DataProcessor только выставляет DiagState и reason — + // конкретные действия выполняет потребитель Decision. + case DiagState::PreWarn_RpmHigh: case DiagState::PreWarn_PressureHigh: case DiagState::PreWarn_PressureLow: default: @@ -200,6 +224,7 @@ Decision DataProcessor::processFrame(const SensorFrame &frame) emit decisionReady(decision); return decision; } + bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const { if (stage < Stage::COLD_CRANKING) @@ -227,10 +252,12 @@ bool DataProcessor::isParamActiveOnStage(const QString ¶m, int stage) const QVector DataProcessor::makeStopControls() { QVector v; - v.append({ControlType::EmergencyStop, 1.0}); - v.append({ControlType::MotorEnable, 0.0}); - v.append({ControlType::Throttle, 0.0}); - v.append({ControlType::BrakeTorque, 0.0}); - v.append({ControlType::TargetRpm, 0.0}); + // Аварийная остановка симуляции через SimulationCommand. + v.append({ControlType::SimulationCommand, + static_cast(SimulationCommand::EmergencyStop)}); + // Принудительно включаем все вентиляторы охлаждения. + v.append({ControlType::Fan_ICE, 1}); + v.append({ControlType::Fan_AD, 1}); + v.append({ControlType::Fan_Ballast, 1}); return v; } From b8e9fa195a2e0e339fac57eb40ab003672a81fab Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Tue, 12 May 2026 10:38:01 +0500 Subject: [PATCH 56/58] chore: update path to header --- src/backend/data_processing/DataProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/data_processing/DataProcessor.cpp b/src/backend/data_processing/DataProcessor.cpp index ac1042f..94e62af 100644 --- a/src/backend/data_processing/DataProcessor.cpp +++ b/src/backend/data_processing/DataProcessor.cpp @@ -1,4 +1,4 @@ -#include "DataProcessor.h" +#include "backend/data_processing/DataProcessor.h" // Этапы — нумерация условная, должна совпадать со StateMachine. namespace Stage From 4f91d867cffa4f9c7e57d78af4e0c12c870f22e4 Mon Sep 17 00:00:00 2001 From: Lenar Gatin Date: Tue, 12 May 2026 10:45:53 +0500 Subject: [PATCH 57/58] chore: hot fix issues to successfully compile project --- include/backend/state_machine/state_machine.h | 2 +- src/backend/data_store/DataStore.cpp | 5 +++++ src/backend/state_machine/state_machine.cpp | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/backend/state_machine/state_machine.h b/include/backend/state_machine/state_machine.h index 70cfc5d..a5eb734 100644 --- a/include/backend/state_machine/state_machine.h +++ b/include/backend/state_machine/state_machine.h @@ -50,7 +50,7 @@ private slots: void onModbusConnectionRestored(); // Команды фронта - void onReceivedFrontControl(const ModelControl& control); + void onReceivedFrontControl(); void onReceivedModelConfig(const ModelConfig& config); private: diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 34a0ab0..29ddd4a 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -467,6 +467,11 @@ void DataStore::ensureHeaders() headersEnsured_ = true; } +qint64 DataStore::generateId() noexcept +{ + return 0; +} + bool DataStore::writeData(const Data &record) { if (!connector_) diff --git a/src/backend/state_machine/state_machine.cpp b/src/backend/state_machine/state_machine.cpp index f01d810..7dd555e 100644 --- a/src/backend/state_machine/state_machine.cpp +++ b/src/backend/state_machine/state_machine.cpp @@ -15,6 +15,7 @@ StateMachine::StateMachine(BackendWorker* backendWorker, QObject* parent) // ----- Подготавливаем CSV-коннекторы для хранилища ----- // пути можно вынести в настройки, здесь для примера + size_t SIZE_FILE_STR = 10; m_Connector = new FileConnector("measurements.csv", SIZE_FILE_STR); // ----- Создаём DataStore ----- @@ -167,9 +168,10 @@ void StateMachine::onDecisionReady(const Decision& decision) m_communicator->SendFeedbackToFrontend(decision.state); } -void StateMachine::onReceivedFrontControl(const ModelControl& control) +void StateMachine::onReceivedFrontControl() { - if (control.type == ControlType::EmergencyStop) { + ModelControl control; + if (control.type == ControlType::SimulationRequest) { requestAbort("Front control emergency stop"); } // другая логика From 3cdd6bf84a3726436ebd7d795495b5407b08f5db Mon Sep 17 00:00:00 2001 From: ilyamikhailov16 Date: Tue, 12 May 2026 12:40:21 +0500 Subject: [PATCH 58/58] fix: add generateId method and implement ID update on deletion --- include/backend/data_store/DataStore.h | 3 - src/backend/data_store/DataStore.cpp | 155 +++++-------------------- 2 files changed, 29 insertions(+), 129 deletions(-) diff --git a/include/backend/data_store/DataStore.h b/include/backend/data_store/DataStore.h index 20cd774..70bae47 100644 --- a/include/backend/data_store/DataStore.h +++ b/include/backend/data_store/DataStore.h @@ -74,13 +74,10 @@ public slots: private: QString serializeData(qint64 id, const Data &record, int precision = 3) const; bool deserializeData(const QString &line, Data &data) const; - - void ensureHeaders(); qint64 generateId() noexcept; private: Connector *connector_; - bool headersEnsured_; }; #endif // DATASTORE_H \ No newline at end of file diff --git a/src/backend/data_store/DataStore.cpp b/src/backend/data_store/DataStore.cpp index 29ddd4a..59db6e7 100644 --- a/src/backend/data_store/DataStore.cpp +++ b/src/backend/data_store/DataStore.cpp @@ -7,7 +7,6 @@ #include #include "backend/data_store/DataStore.h" -// #include "DataStore.h" // Low-level layer // === Connector === @@ -275,10 +274,7 @@ bool FileConnector::remove(int64_t id) { if (!isValidId(id)) { - std::cerr << "[FileConnector::remove] " - << "Invalid id: " - << id << '\n'; - + std::cerr << "[FileConnector::remove] Invalid id: " << id << '\n'; return false; } @@ -286,23 +282,34 @@ bool FileConnector::remove(int64_t id) if (!file.is_open()) { - std::cerr << "[FileConnector::remove] " - << "Failed to open file.\n"; - + std::cerr << "[FileConnector::remove] Failed to open file.\n"; return false; } size_t last_id = getSize() - 1; - for (size_t current_id = static_cast(id); current_id < last_id; ++current_id) { std::string buffer; + // читаем следующую запись if (!readRecord(file, getOffset(static_cast(current_id + 1)), buffer)) { return false; } + // пересчёт id внутри строки + size_t delimiter = buffer.find(';'); + + if (delimiter == std::string::npos) + { + std::cerr << "[FileConnector::remove] " + << "Malformed record: no id delimiter.\n"; + return false; + } + + buffer.replace(0, delimiter, std::to_string(current_id)); + + // записываем в текущую позицию if (!writeRecord(file, getOffset(static_cast(current_id)), buffer)) { return false; @@ -317,8 +324,7 @@ bool FileConnector::remove(int64_t id) } catch (const std::exception &e) { - std::cerr << "[FileConnector::remove] " - << "resize_file failed: " + std::cerr << "[FileConnector::remove] resize_file failed: " << e.what() << '\n'; return false; @@ -356,18 +362,23 @@ bool stringToDiagState(const QString &value, DiagState &state) } } // namespace -DataStore::DataStore(Connector *connector, QObject *parent) - : QObject(parent) - , connector_(connector) - , headersEnsured_(false) +qint64 DataStore::generateId() noexcept +{ + if (!connector_) + { + return -1; + } + + return static_cast(connector_->getSize()); +} + +DataStore::DataStore(Connector *connector, QObject *parent) : QObject(parent), connector_(connector) { if (!connector_) { qWarning().noquote() << "[DataStore] Null connector passed to constructor."; return; } - - ensureHeaders(); } QString DataStore::serializeData(qint64 id, @@ -449,29 +460,6 @@ bool DataStore::deserializeData(const QString &line, Data &data) const return true; } -void DataStore::ensureHeaders() -{ - if (headersEnsured_) - { - return; - } - - if (!connector_) - { - qWarning().noquote() << "[DataStore] Cannot ensure headers: connector is null."; - return; - } - - // Формат хранения здесь без отдельной строки заголовков; - // метод оставлен как точка расширения и для совместимости с интерфейсом. - headersEnsured_ = true; -} - -qint64 DataStore::generateId() noexcept -{ - return 0; -} - bool DataStore::writeData(const Data &record) { if (!connector_) @@ -480,8 +468,6 @@ bool DataStore::writeData(const Data &record) return false; } - ensureHeaders(); - const qint64 id = generateId(); if (id < 0) { @@ -588,87 +574,4 @@ QVector DataStore::readAllData() const } return result; -} - -// int main() -// { -// constexpr size_t RECORD_SIZE = 16; - -// FileConnector connector("C:\\Users\\ilyam\\Desktop\\ScadaForDiesel\\src\\backend\\data_store\\test.File", RECORD_SIZE); - -// std::cout << "Initial size: " << connector.getSize() << "\n\n"; - -// // === WRITE === -// std::cout << "=== WRITE ===\n"; - -// int64_t id0 = connector.write("Hello"); -// int64_t id1 = connector.write("World"); -// int64_t id2 = connector.write("VeryVeryLongString123"); - -// std::cout << "Inserted ids: " -// << id0 << ", " -// << id1 << ", " -// << id2 << "\n"; - -// std::cout << "Size after write: " -// << connector.getSize() << "\n\n"; - -// // === READ === -// std::cout << "=== READ ===\n"; - -// printRecord(connector, id0); -// printRecord(connector, id1); -// printRecord(connector, id2); - -// std::cout << "\n"; - -// // === UPDATE === -// std::cout << "=== UPDATE ===\n"; - -// bool updated = connector.update(id1, "Updated"); - -// std::cout << "Update result: " -// << std::boolalpha -// << updated << "\n"; - -// printRecord(connector, id1); - -// std::cout << "\n"; - -// // === REMOVE === -// std::cout << "=== REMOVE ===\n"; - -// bool removed = connector.remove(id0); - -// std::cout << "Remove result: " -// << std::boolalpha -// << removed << "\n"; - -// std::cout << "Size after remove: " -// << connector.getSize() << "\n"; - -// std::cout << "\nRecords after remove:\n"; - -// for (size_t i = 0; i < connector.getSize(); ++i) -// { -// printRecord(connector, static_cast(i)); -// } - -// std::cout << "\n"; - -// // === INVALID ACCESS === -// std::cout << "=== INVALID ACCESS ===\n"; - -// printRecord(connector, 999); - -// bool invalidUpdate = connector.update(999, "Test"); -// bool invalidRemove = connector.remove(999); - -// std::cout << "Invalid update: " -// << invalidUpdate << "\n"; - -// std::cout << "Invalid remove: " -// << invalidRemove << "\n"; - -// return 0; -// } \ No newline at end of file +} \ No newline at end of file