От PLC к своему HMI и AI-анализу

от автора

В первой статье и второй статье мы заставили крутиться PMSM-мотор под управлением комплекта P-NUCLEO-IHM03, внедрив в прошивку поддержку FDCAN. Чтобы превратить DIY-проект в нечто более серьезное, нам нужен верхний уровень: наглядный интерфейс, интеграция с индустриальными стандартами и, конечно, немного магии современных LLM для диагностики.

Сегодня мы построим распределенную систему управления, где OpenPLC берет на себя логику, Node-RED — визуализацию, а AI-агент — роль инженера-диагноста.

HMI на стероидах: Когда консоли уже мало

Просто видеть сырые данные в терминале — сомнительное удовольствие. Для управления и мониторинга привода нам нужен Dashbord, который легко использовать. Node-RED здесь хорошо подходит: он легкий, живет на Orange Pi и имеет нативную поддержку всего, что нам нужно.

Мы не будем просто рисовать графики. Наша задача — создать мост между CAN bus, Modbus и миром IP-протоколов.

Установка и настройка Node-RED на Orange Pi

Node-RED — это визуальная IoT-платформа с потоковой моделью программирования. В нашем проекте она выполняет сразу несколько ролей: читает фреймы с CAN bus через SocketCAN, опрашивает регистры OpenPLC по Modbus TCP, отображает всё это в HMI-дашборде и запускает анализ телеметрии через AI-агент. Альтернативы — Grafana с плагинами или написанный с нуля веб-интерфейс — при тех же задачах потребовали бы значительно больше усилий.

Mosquitto — легковесный MQTT-брокер. В проекте он служит шиной сообщений между Node-RED и n8n: Node-RED публикует подготовленную телеметрию в топик node-red/request, n8n подписывается на него, выполняет RAG + LLM анализ и возвращает диагноз обратно в топик node-red/data. MQTT здесь удобнее прямого HTTP-вызова: n8n и Node-RED развязаны асинхронно и не зависят от порядка запуска.

Оба инструмента хорошо документированы. Для установки на Orange Pi (Debian/Ubuntu-based) достаточно официальных инструкций:

Автоматический запуск интерфейса CAN bus

Для того чтобы интерфейс can0 поднимался автоматически при загрузке системы на Orange Pi, лучше всего использовать systemd или файл конфигурации сетевых интерфейсов.

  1. Создайте файл конфигурации:

    sudo nano /etc/systemd/network/80-can0.network
  2. Вставьте следующее содержимое:

    [Match]Name=can0[CAN]BitRate=500K

    Примечание: 500K эквивалентно скорости 500000. Скорость CAN bus 500 kbit/sec ранее настраивалась для платы управления PMSM.

  3. Включите службу:

    sudo systemctl enable systemd-networkdsudo systemctl restart systemd-networkd

Для ручного запуска интерфейса CAN bus (если автоматический запуска не настроен) необходимо выполнить команду

sudo ip link set can0 up type can bitrate 500000

Установка необходимых плагинов Node-red

Напомним, что установка плагинов производится с помощью меню «Управление палитрой». Или можно установить в консоли командой npm install.
Для демо проекта нам необходимы плагины:

Установка Ollama, n8n и Qdrant через Docker Compose

Для работы AI-агента потребуются три сервиса: Ollama (локальный LLM-сервер с embedding-моделью), n8n (платформа для построения AI-агентов) и Qdrant (векторная база данных для RAG). Удобнее всего поднять их вместе через Docker Compose.
Готовый файл docker-compose.yml находится в репозитории.

Модель эмбеддингов qllama/bge-small-en-v1.5

В entrypoint сервиса Ollama автоматически скачивается модель qllama/bge-small-en-v1.5 — компактная (~33 МБ) модель для вычисления векторных эмбеддингов текста, оптимизированная для задач семантического поиска. Она используется в RAG-пайплайне: перед отправкой данных в Qdrant и при поиске релевантных документов в n8n-агенте.

Запуск

docker compose up -d

Сервисы будут доступны по адресам:

  • n8n: http://<IP>:5678

  • Ollama API: http://<IP>:11434

  • Qdrant: http://<IP>:6333

При первом запуске Ollama автоматически загрузит модель qllama/bge-small-en-v1.5. Проверить можно командой:

docker logs ollama

Описание HMI-интерфейса

Далее идет краткое описание каждой панели интерфейса и скрин соответствующей части логики в node-red flow. Содержимое раздела скорее похоже на справочный мануал, которые обычно скучный и пролистывается. Поэтому он приведен для понимания общей картины и как справочное описание. Ниже в разделе тестирования постараюсь наглядно показать, как это работает. А кто желает подробнее разобраться, может склонировать репозиторий и запустить проект.

Для открытия HM-интерфейса необходимо набрать в строке браузера http://<IP_ADDRESS>:1880/dashboard

Speed dashboard

Панель мониторинга и контроля работы двигателем PMSM.

Получение и отправка команд для двигателя происходит с помощью компонентов socketcan.
SocketCAN — это набор опенсорсных драйверов CAN-протокола и сетевой стек, реализованный в ядре Linux.
Если говорить проще, SocketCAN превращает CAN-интерфейс (который обычно работает как последовательный порт) в стандартное сетевое устройство, подобное Ethernet (eth0 или wlan0). Это позволяет работать с CAN bus через привычный программный интерфейс сокетов.

Верхняя группа индикаторов

  • Вверху круговая диаграмма текущего значения скорости

  • State — индикатор состояния работы двигателя (Состояния описаны в предыдущей статье)

  • Ниже линейный график (Speed_chart) — сравнивает значение заданной скорости с текущей. Активный только при включенном слайдере Analyze

Группа ручного управления

  • Ползунок Speed — ручное задание скорости вращения двигателя

  • Кнопки:

    • Start — ручной запуск двигателя с минимальной скоростью

    • Stop — ручная остановка двигателя

    • ACK — сброс состояния ошибок двигателя

Группа компонентов анализа телеметрии

  • Индикатор Analyzing — горит зеленым, если в данным момент идет анализ телеметрии работы двигателя

  • Слайдер Analyze — запуск анализа телеметрии работы двигателя

  • Черное текстовое поле (Result) — результат анализа телеметрии

Для выполнения анализа нужно включить слайдер Analyze и запустить автоматический цикл системы с помощью кнопки START на панели FLOW. Функция collect соберет необходимую телеметрию двигателя и отправит её на анализ через MQTT-топик node-red/request.
Результат анализа поступает через MQTT-топик node-red/data на текстовое поле Result

Flow dashboard

Панель автоматического запуска цикла работы системы (описан в разделе OpenPLC)

  • Слайдер Manual — переключает режим управления, делает активными или неактивными другие кнопки управления

  • State — индикатор состояния цикла работы (переменная состояния CYRCLE_STATE %MW1 OpenPLC)

  • Кнопка Start — запуск автоматического цикла

K1 dashboard

Панель мониторинга и контроля работы реле К1

  • Индикатор состояния реле: красный — выключено, зеленый — включено

  • Кнопки:

    • Coil ON — ручное включение реле

    • Coil OFF — ручное выключение реле

AI-агент: Твой личный дежурный инженер на n8n

Звучит красиво и многообещающе. Но так ли это на самом деле. Как говорят: «It depends», то есть это зависит от многих факторов.
Представьте: у вас есть двигатель, управляемый по алгоритму FOC. Датчики каждую секунду шлют данные по CAN bus — скорость, токи d/q осей, температуру, параметры PID-регулятора. Двигатель работает, вращается, но периодически ведёт себя странно: то скорость скачет, то на целевое значение выходит с большим запозданием.
Как понять, что именно не так? И главное — как это сделать автоматически, не нанимая инженера, который будет сидеть и смотреть на графики?

Классический подход: ETL → ML → правила

В традиционном машинном обучении задача решается примерно так:

Сырые данные с датчиков        │        ▼   Preprocessing   (нормализация, заполнение пропусков, выравнивание временных рядов)        │        ▼   Feature Engineering   (std_dev скорости, FFT для выявления гармоник, скользящее среднее)        │        ▼   QC Rules / Labelling   (эксперт вручную размечает: это «нормально», это «осцилляция», это «стык»)        │        ▼   Обучение модели   (классификатор, autoencoder для аномалий, регрессия)        │        ▼   Инференс + пороговые правила   (если anomaly_score > 0.7 → alert)

Это работает. Но у подхода есть очевидные издержки:

  • Нужны размеченные данные — а их обычно нет, пока не накопишь достаточно аварий.

  • Модель не объясняет себя: «аномалия с вероятностью 0.83» — это не диагноз.

  • При изменении конфигурации двигателя или PID-коэффициентов модель устаревает.

  • Весь пайплайн нужно поддерживать как отдельный проект: датасет, обучение, деплой.

Наш подход: стриминг → анализ на месте → LLM с контекстом

Мы пошли другим путём. Вместо обучения модели — инженерный здравый смысл на входе и языковая модель на выходе.

Общая архитектура

  CAN-шина (SocketCAN)        │        ▼  ┌─────────────────────────┐  │      Node-RED           │  │  Function node          │  │  (Collector & Analyser) │  │                         │  │  • Парсит CAN-фреймы    │  │  • Накапливает сессию   │  │  • Вычисляет статистику │  │  • Генерирует симптомы  │  │  • Выбирает RAG-запрос  │  └────────────┬────────────┘               │  MQTT  msg.payload JSON               ▼  ┌─────────────────────────────────────────┐  │                  n8n                    │  │                                         │  │  Extract Summary ──► Ollama Embed       │  │       │                   │             │  │       │              Qdrant Search      │  │       │                   │             │  │       └──────► Build Prompt ◄───────────┘  │                    │                    │  │              Basic LLM Chain            │  │            (Ollama gemma4:31b)          │  │                    │                    │  │               MQTT publish              │  └─────────────────────────────────────────┘               │               ▼        JSON-диагноз  {state, diagnosis, root_cause,   recommendations, confidence}

Что делает Node-RED: детектор событий

Ключевая идея первого звена: Node-RED не просто собирает данные — он уже анализирует их на лету.

Когда двигатель запускается, открывается сессия. Каждый входящий CAN-фрейм обновляет текущее состояние (speed_rpm, iq, id, motor_state, pid_*). Рядом с каждым снимком фиксируется и target_rpm — текущее задание со стороны оператора.

Когда двигатель возвращается в IDLE (или истекает тайм-аут), сессия флашится: вычисляется весь аналитический блок.

Хитрость с осевшими сэмплами

Самая частая ошибка при анализе телеметрии двигателя — считать среднее значение скорости avg(speed_rpm) по всей сессии. Если двигатель разгонялся от 0 до 1000 об/мин, среднее будет ~500, и LLM решит, что мотор недотягивал до цели. На самом деле он просто разгонялся.

Поэтому мы отделяем сэмплы переходного процесса от сэмплов установившегося режима:

settledSamples = runSamples    WHERE target_rpm == lastTargetRpm   ← только последнее задание    AND   speed_rpm  >= 0.9 * lastTarget ← скорость ≥ 90% от цели

Только на этом подмножестве считается settled_speed_avg, settled_speed_std и ошибка регулирования. Рампа разгона не влияет на оценку качества регулятора.

Автоматический выбор RAG-запроса

Ещё один важный элемент: вместо того чтобы передавать LLM сырой JSON и просить его «придумай запрос к базе знаний» — Node-RED детерминированно выбирает поисковую фразу по правилам:

Условие

Запрос в Qdrant

Нет RUN-состояния

FOC startup failure alignment revup current

Пик Iq > 1 А

FOC current saturation anti-windup Iq limit

std_dev > 30 об/мин (в settled)

FOC speed loop PI oscillation gain tuning

Ошибка > 50 об/мин

FOC speed steady-state error PI gain integral

Нулевые момент и мощность

FOC zero torque Iq reference power loss

Норма

PMSM FOC speed control normal operation

Это короткая фраза без JSON-символов — её нельзя испортить даже если в данных есть странные значения.
Был испытан вариант с передачей LLM телеметрии и указанием — проанализировать и подготовить запрос в RAG. Использовались различные модели от Llama, Qwen, Gemini flash. Но так и не удалось добиться стабильного результата.

Что делает n8n: RAG + LLM как эксперт без учителя

Второе звено берёт подготовленный Node-RED’ом пакет и добавляет контекст из базы знаний.

RAG — почему это может быть полезным

LLM хорошо рассуждает, но не знает конкретики вашего SDK. Документация STM32 Motor Control Workbench была заранее загружена в Qdrant (векторная БД).
Для этого с помощью Github copilot был составлен скрипт ingest_rag.py.
Теперь при каждом диагнозе автоматически находятся 4-5 наиболее релевантных фрагмента — например, описание параметров pid_speed_kp/ki и что происходит при их неправильной настройке.

Модель получает не просто «pid_speed_kp = 2730», а ещё и инженерный контекст: когда этот параметр актуален, какую проблему он решает, что с ним делать.

Временной ряд в промпте

В промпт также вставляется таблица реального временного ряда (до 60 строк):

TIME SERIES — RUN state, 42 sample(s):  t_ms    | target_rpm | speed_rpm | iq_A    | id_A  --------|------------|-----------|---------|--------  0       | 100        | 132.0     | 0.0216  | 0.0017  499     | 100        | 60.0      | 0.0249  | 0.0054  999     | 1000       | 90.0      | 0.0308  | 0.0007  1499    | 1000       | 498.0     | 0.0244  | -0.0042  1999    | 1000       | 870.0     | 0.0246  | -0.0039  2499    | 1000       | 990.0     | 0.0206  | -0.001  ...

Это позволяет модели видеть динамику: как менялось задание, как реагировала скорость, как вёл себя ток.

Пример реального вывода

Входные данные — сессия 10 секунд, двигатель разогнался с 100 до 1000 об/мин и вышел на установившийся режим:

  target_rpm           = 1000  settled_speed_avg    = 996.5     ← фактическая скорость в УР  settled_speed_std    = 4.2       ← почти нет осцилляций  steady_state_err_rpm = 3.5       ← ошибка < 0.4%  symptoms             = no anomalies detected

Ответ LLM:

{  "state": "normal",  "confidence": 0.92,  "diagnosis": "Motor successfully ramped from 100 RPM to 1000 RPM target.    Settled speed avg 996.5 RPM gives steady-state error of 3.5 RPM (0.35%),    well within acceptable limits. settled_speed_std 4.2 RPM indicates    stable regulation with no oscillation.",  "root_cause": "No fault detected — normal operation.",  "recommendations": [    {      "parameter": "pid_speed_kp",      "current_value": 2730,      "action": "monitor",      "reason": "Current gains produce acceptable steady-state error;        monitor under varying load conditions"    }  ]}

Без исправления settled_speed — LLM видел бы avg=626, std_dev=404 и писал про «нестабильность» и «недостаточное усиление ПИД».

Код, написанный с помощью промпта

Есть момент, который стоит упомянуть отдельно: часть кода в этом проекте написана не вручную. Функция-коллектор в Node-RED с парсингом CAN-фреймов, логикой сессий, вычислением settledSamples и генерацией симптомов — создана через промпт в Github copilot. Код обработки в n8n — узлы Extract Telemetry Summary и Build Prompt — тоже.

Это не значит «нажал кнопку и получил готовое». Это итеративный процесс: сформулировал требование, получил черновик, указал на ошибку, уточнил поведение на граничном случае, снова уточнил. Но принципиальная разница с написанием кода вручную — в том, где уходит время инженера. Модель пишет черновик. Инженер проверяет логику, находит, что порог 90% не работает при смене цели в середине сессии — и уточняет: снимок target_rpm должен быть в каждом сэмпле, а lastTargetRpm берётся из последней записи в targetHistory. Модель исправляет. Ещё одна итерация.

Время на «написать код» сокращается в разы. Время на думать, что код должен делать — остаётся прежним. И это правильное распределение усилий.

Промпт как документация

Есть и практический бонус: промпт, по которому сгенерирован код, сам по себе является спецификацией. Именно поэтому в этом проекте рядом с кодом лежат файлы node-red-collect.md и n8n-PMSM_Diagnostic.md — не просто документация, а полные промпты, по которым можно воссоздать функции с нуля. Если через полгода нужно будет изменить логику анализа или добавить новый тип CAN-фрейма — берёшь промпт, вносишь изменение в требование и перегенерируешь нужный фрагмент.

Где это не работает

Промпт-генерация хороша для структурированного шаблона и обработки данных с чёткими правилами. Она хуже справляется с задачами, где правильный ответ неочевиден самому инженеру — например, выбор оптимальных порогов для детекции осцилляций или архитектурные решения с долгосрочными последствиями. Там по-прежнему нужен думающий человек. Модель — инструмент, а не замена экспертизе.

Что можно улучшить.

Адаптивный порог для анализа

Сейчас считается, что двигатель «вышел в режим», когда скорость ≥ 90% от цели. Это фиксированный порог. Для быстрых высокоинерционных нагрузок он слишком мягкий, для лёгких — слишком жёсткий. Хорошим улучшением было бы вычислять порог динамически — например, на основе замеренного времени разгона предыдущих сессий.

Накопление истории сессий

Сейчас каждая сессия анализируется независимо. Если бы Node-RED или n8n сохранял историю (хотя бы последние 50 сессий в простую БД — SQLite, InfluxDB), LLM мог бы видеть тренд: «за последние 3 дня settled_std стабильно растёт — деградация подшипника или усиление нагрузки». Сейчас такое сравнение недоступно.

Обратная связь по диагнозу

LLM может ошибиться. Прямо сейчас результат уходит в MQTT и нигде не проверяется. Можно добавить в Node-RED простую UI-панель с кнопками «диагноз верный / неверный». Эти оценки копятся и периодически обогащают базу знаний в Qdrant — фактически RAG улучшается от работы с реальными данными без переобучения.

Несколько LLM для консенсуса

Модель gemma4:31b справляется хорошо, но у языковых моделей бывают «галлюцинации». Для критичных случаев (state=unstable, confidence < 0.7) можно отправлять тот же промпт дополнительной модели и сравнивать state. Если они не согласны — фиксировать как «спорный диагноз» и отправлять инженеру.

Итог

Основная идея проста: дайте инструменту правильно подготовленные данные, и он объяснит вам, что происходит, без предварительного обучения на исторических авариях.

Node-RED здесь — не просто шина данных. Это уровень инженерной интерпретации: он знает, что такое переходный процесс, что такое установившийся режим, и не путает одно с другим. n8n добавляет документацию через RAG. LLM связывает всё в читаемый диагноз.
Такой стек можно поднять локально на обычном сервере или использовать облачные ресурсы. LLM используется как облачный ресурс. А сервисы Qdrant, Node-RED и n8n отлично работают на Orange pi.
Исходный код проекта Node-red, n8n находится в этом репозитории.

ссылка на оригинал статьи https://habr.com/ru/articles/1025712/