Возвращаясь к первой статье хочется объяснить откуда появилась необходимость разработки механизма расширения с графическим интерфейсом (GUI) и подробней объяснить механизм создания плагинов.

Было получено задание на разработку технологического программного обеспечения для настройки, регулирования, контроля, сбора и анализа информации САУ(системы автоматизированного управления) двигателя, на российской операционной системе. Где обмен между САУ и технологической программой осуществляется по интерфейсу RS-232 или RS422, по специальному протоколу обмена.

После рассмотрения и анализа возможностей списка российских операционных систем была выбрана операционная система AstraLinux. Это система специального назначения на базе дистрибутива Debian, созданная для комплексной защиты информации и построения защищённых автоматизированных систем. Операционная система Астра Линукс разрабатывается в двух модификациях: Common Edition(бесплатная, общего назначения) и Special Edition(платная, специального назначения с пакетом алгоритмов защиты).
При разработке программного обеспечения была использована библиотека Qt.

Программное обеспечение состоит из функциональных модулей, каждый из которых позволяет производить специфичные работы по настройки двигателя.

Функциональный модуль представляет собой виджет состоящий из различных визуальных компонентов, графиков и таблиц, где в удобном для пользователя виде отображаются параметры двигателя.


Функциональный модуль “Приборная панель”


Функциональный модуль, позволяющий динамически контролировать значения параметров в допустимых границах


Функциональный модуль для настройки характеристик САУ

Разработанное ПО (технологическая программа) было предназначено только для определенного типа двигателя. Для нового типа двигателя приходилось разрабатывать новое ПО, что приводило к значительному увеличению трудозатрат на разработку, тестирование, отладку и, как следствие, задержкам на внедрение данного ПО — при том, что, подобное ПО востребовано уже на этапе разработки САУ.

Для того, чтобы уйти от подобной затратной практики разработки технологического программного обеспечения было принято решение о разработки программного комплекса, который будет адаптироваться к любому двигателю, САУ которого поддерживает одинаковый протокол обмена.

Концепцией при разработке программного комплекса служила возможность расширения функциональности на базе технологии плагинов с поддержкой графических модулей.

Программный комплекс состоит из трех основных частей:

  1. Функциональное ядро комплекса
  2. Механизм системы расширений
  3. Набор плагинов

В функции ядра входит обеспечение надежного обмен данными с САУ и выполнение следующих задач:

  • Обмен информацией по интерфейсу RS232 с использованием специального протокола
  • Непрерывный мониторинг списка параметров двигателя
  • Запрос на изменение параметров двигателя
  • Запросы на чтения параметров двигателя
  • Отображение параметров виде табличной формы
  • Обработка запросов от расширений(создания нового списка мониторинга по необходимости, одиночные запросы на запись и считывание)

Система расширения со следующими функциями:

  • Поиск расширений
  • Получение графических объектов расширений
  • Отображение объектов расширений
  • Задача по связыванию запросов(сигналов) от объектов к ядру ПО

Каждый плагин является фабрикой объектов, и основной функцией плагина является создание графического объекта.

Задачей системы расширения является получение объекта и связывание его с ядром программы (с помощью известных интерфейсов ), инициализация и активация объекта.

Связь объектов с ядром происходит с помощью виртуального класса интерфейсов. Для разработки нового расширения необходимо знать только интерфейсы которые поддерживает ядро технологической программы. Структура программы представлена на след рисунке.



Разработка иерархия плагина.



Рассмотрим иерархию графического объекта, создаваемого плагином. Так как при работе с плагинами следует оперировать объектами Qt, основным классом, как и в библиотеке Qt, будет класс QObject, который поддерживает, сигнально-слотовый механизм Qt.

Следующим классом является QWidget — он необходим для создания своих графических форм.

Далее необходим свой класс интерфейсов interfaceWidget. Для этого создаётся абстрактный класс, который будет наследоваться от QWidget. В этом классе объявляются интерфейсы (сигналы, функции, слоты) которые будут связывать основной проект и объект плагина. Абстрактный класс обеспечивает поддержку интерфейса в основном проекте.

Класс MyFormQt, который наследуется от класса интерфейсов interfaceWidget и определяет интерфейсы и и в котором разрабатывается графика и внутренняя функциональность модуля. Благодаря такой схеме наследования класс MyFormQt, поддерживает функционал класса QWidget и разработанные интерфейсы связи с основным объектом приложения.

/**
   \class interfaceWidget
   \brief Виртуальный класс,объявляющий интерфейсные функции  графического функционального объекта
*/
class interfaceWidget: public QWidget
{
public:
        /// \brief Деструктор
        virtual ~interfaceWidget() = default;
        /// \brief Функция получения байт
        /// \param ba, полученные байт из САУ
        /// \param ok, флаг успешного считывания
        virtual void getByte(QByteArray ba, bool ok) = 0;
        /// \brief Функция получения циклических данных
        /// \param ba, полученные байт из САУ
        virtual void getRetrieveDate(QByteArray ba, bool ok) = 0;
        /// \brief Функция успешной записи данных
        /// \param fl, флаг записи
        virtual void successfullyWritten(bool fl) = 0;
        /// \brief Фунуция для запроса регистрации параметров
        /// \param ok Флаг необходимости регистрации параметролв
        /// \return параметрцы которые необходимо зарегистрировать в потоке мониторинга
        virtual QVector<QString > regParam(bool &ok) = 0;
signals:
     
        /// \brief сигнал запроса записи данных в САУ
        /// \param Вектор название параметров и их значений
        virtual void  signal_writeByteByName(QVector<TVARPARAM > vecParam) = 0;
        /// \brief Сигнал запроса данных по имени
        /// \param nameParam вектор параметров
        virtual void signal_getByteByName(QVector<QString > nameParam) = 0;
        /// \brief Сигнал запроса данных по адрессу
        /// \param addr адресс откуда надо начать считывать данные
        /// \param ndata кол-во байт для считывания
        virtual void signal_getByteByAddress(quint32 addr, quint32 ndata) = 0;
        /// \brief Сигнал запроса записи данных по адрессу
        /// \param addr адресс куда надо записать данные
        /// \param ba байты для записи
        virtual void signal_writeByteByAddress(quint32 addr, QByteArray ba) = 0;
        /// \brief Сигнал запроса на остановку мониторинга
        /// \param fl, true старт мониторинга, false стоп мониторинг
        virtual void signal_enableMonitoring(bool fl) = 0;
};

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

Процесс инициализации и обмена данными.Весь проект основан на MDI области следственно все плагины будут загружаться в виде отдельного MDI окна.

Плагин-фабрика создает объект класса MyFormQt, преобразует указатель на созданный объект к указателю на QObject и передает его в технологическую программу, где следует преобразование объекта c помощью операции dynamic_cast на объекта класса interfaceWidget, в следствии чего получаем полный доступ к графической форме и интерфейсам этого объекта.

Функция фабрики (создание графического функционального объекта в плагине).

//------------------------------------------------------------------
QObject *screenOnlinePlugin::getPluginWidget()
{
   MyFormQt *screen = new MyFormQt();
   return qobject_cast<QObject *>(screen); // Преобразование в объект
}
//-------------------------------------------

Функция получения и инициации графического функционального объекта в технологической программе (ядро).

//-----------------------------------------------------------------
void PluginSystem::showPluginWidget(QString dirFabrica)
{	
   QMdiSubWindow *sWPS = new QMdiSubWindow;
   pQtTM->m_pma->addSubWindow(sWPS);
   sWPS->setAttribute(Qt::WA_DeleteOnClose, true);
  //--------Создание плагина
    QPluginLoader loader(dirFabrica);	   // Загрузка фабрики 
    QObject *pobj = qobject_cast<QObject*>(loader.instance());
      if(!pobj)
        return;
    pluginInterface *fabrica = qobject_cast<pluginInterface *>(pobj);
       if(!fabric)
         return;
    QObject *ob = fabrica>getPluginWidget();  // Получение графического объекта
       if(!ob)
         return;
 interfaceWidget *interFaceW = dynamic_cast<interfaceWidget *>(ob);
      if(!interFaceW)
        return;
 delete fabrica;

  //  Инициализация плагина 
  connect(interFaceW, SIGNAL(), this, SLOT());
		
  sWPS->setWidget(interFaceW);
  sWPS->show();
    
  //  Регистрация параметров
  QVector<QString > vecParam;
  vecParam = interFaceW>regParam(paramOK); // Запрос параметров
  if(paramOK)
    regParamList(vecPlugin[i].namePlugin ,vecParam);
}
//--------------------------------------------------------------------------------

Объект класса interfaceWidget (графический функциональный объект) помещается на созданное окно QmdiSubWindow, тем самым устанавливая его дочерним.

Далее связываются(connect) сигналы и слоты объекта с сигналами и слотами ядра технологической программы и последним этапом показ окна методом show(). При закрытия окна QmdiSubWindow, также будет удален объект класса interfaceWidget, т. к. заранее для subWindow в котором помещен объект было установлено свойство DeleteOnClose.

Обмен данными между графическими функциональными объектами и технологической программой проходит используя маршалинг данных и два режима:
• Однократный режим (запрос-ответ);
• Режим мониторинга, постоянно получаем данные с периодом, определяемым механизмом обмена данными с целевым устройством;

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

При однократном режиме пользователь нажимая кнопку в графическом функциональном объекте (виджете) генерирует сигнал, содержащий список параметров, данные которых следует считать в целевом устройстве (САУ) и передать в графический функциональный объект. Механизм обмена данными в основной программе обрабатывает сигнал и начинает запрашивать у САУ данные по RS-232, получаемые данные пакуются в QbyteArray и отправляются с сигналом в графический объект. Объект обладает списком запрашиваемых параметров и, следовательно, знает их типы, распаковывает данные, которые содержаться в QbyteArray и отображает их виде графиков и таблиц и специализированных графических виджетов (стрелочные индикаторы, цифровые индикаторы, кнопки с подсветкой и т.д.).

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

На данный момент проходит тестирование и одновременно разработка групп плагинов под разные задачи.

Был создан плагин шаблон который ускоряет процесс разработки новых плагинов. Используя плагин-шаблон для разработки нового функционального модуля необходимо создать новую графическую форму и реализовать алгоритмы обработки и визуализации данных.

Были разработаны программные модули-драйверы физического уровня для проводного интерфейса RS-232 и беспроводных WIFI и Bluetooth.

На сегодняшний день программный комплекс работает в 2-х канальном режиме, т. е. может работать одновременно с 2 САУ (это было требование заказчика). К любому из 2-х каналов пользователем подключаются плагины функциональных модулей.

При тестирование удалось одновременно запустить 12 плагинов которые успешно обрабатывались ядром. И даже 12 плагинов не являются пределом для данной системы. За счёт системы двойной буферизации Qt, время затрачиваемое на рисование плагинов уменьшается и предотвращает мерцания плагинов, поэтому при наращивания количества плагинов в дальнейшем основная нагрузка будет идти только на обмен данных по интерфейсу RS-232. Хотя практика показывает, что во время отладки, настройки, тестирования САУ двигателя достаточно 3-4 одновременно работающих плагина.

В результате работы получили программный комплекс для быстрой разработки технологических программ предназначенных для настройки, наладки и тестирования САУ для различных типов двигателей (мехатронных систем).

Были разработаны 2 комплекта плагинов для разных типов двигателя.

Подключая комплект плагинов для определенного типа САУ к программному комплексу получаем многофункциональную программу для настройки, наладки и тестирования этого типа САУ.



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

Комментарии (30)


  1. andy_p
    26.06.2019 17:24

    А какой лицензией Qt вы пользуетесь?


    1. CIUK Автор
      26.06.2019 18:37

      Лицензия LGPL


  1. 0serg
    26.06.2019 19:46
    +1

    О, вспомнилась молодость, когда-то очень давно занимался похожими проектами :).
    Сейчас правда QWidget — устаревший подход. Посмотрите в сторону QML, он гораздо интереснее. У меня он «зашел» далеко не с первой попытки, но когда я пересилил привычки и освоился то в полной мере оценил его выдающиеся достоинства.


    1. wxmaper
      26.06.2019 22:22

      Виджеты вполне удовлетворяют требованиям «под десктоп» и при необходимости достаточно легко кастомизуются/стилизуются и адаптируются под современный, простите, свистопердящий интерфейс. Никто из digia не нарекал их устаревшими.
      QML же больше для игр, мобилок и встраиваемых систем.


      1. 0serg
        27.06.2019 00:54
        +1

        На QML разработка и поддержка сильно упрощаются. Что совсем неочевидно при чтении документации, но когда начинаешь работать — там очень натурально и легко получается хороший MVC. Нет, виджеты конечно тоже работают, по-прежнему удобны, и на них 90% старых разработчиков сидит. Но новые приложения на них имхо смысла нет делать. Не только для мобилок но и для самого что ни на есть классического десктопа. Тем более что за мобилками в том или ином виде будущеее да и новые вкусные технологии типа того же webgl streaming в виджетах не поддерживаются.


      1. midday
        27.06.2019 22:58

        Зря вы так. Это просто консерватизм. Всё совсем наоборот. Плюс вы забываете, что там даже кастомные виджеты писать даже удобнее. ( на том же С++ кстати)


    1. Mike255
      27.06.2019 08:40
      +1

      Сделал 5 полноценных проектов в связки QML/Qt за последние лет 7. И в конце каждого проекта понимал, что лучше бы сделал на чистом Qt. Код для связки Qt и QML получается слишком большой. Быстро надоедает его писать.


      1. 0serg
        27.06.2019 08:58

        Никто и не говорил что в MVC кода меньше чем в условном «монолите», особенно со спагетти-кодом. Но если это поддеживать и развивать дальше планируется то затраты на организацию быстро окупаются.


        1. mapron
          27.06.2019 09:43

          затраты на организацию быстро окупаются

          Мой опыт показывает об обратном. Постоянно упираться в «ой а на QML так делать не получится» при реализации специфичных штук. Затраты меньше только на код, относящийся к GUI, но приложение гуи кодом не ограничивается. Интеграция с нативной частью почти всегда уже сжирает все преимущества, когда у вас 30-50к строк на QML.


          1. 0serg
            27.06.2019 11:20

            Qt весь построен вокруг сигналов и слотов (при правильной реализации, а не когда от QThread наследуются и переопределяют run() ). Все сигналы и слоты абсолютно прозрачно пробрасываются в QML. Где конкретно возникает проблема? Что конкретно нельзя делать на QML?


            1. mapron
              27.06.2019 13:25

              Я не говорю что что-то совсем нельзя сделать на QML. Делаешь нативную часть, к ней обвязку, и тп. я про то что выгода очевидная от QML заканчивается довольно быстро. А потом этого монстра еще и поддерживать)


              1. 0serg
                27.06.2019 13:52

                По-моему все с точностью до наоборот :). На виджетах набросать прототип может быть быстрее, но QML заставляет написать код правильно, так что его в дальнейшем легко поддерживать.


        1. Mike255
          27.06.2019 15:22

          В том-то и дело, что сначала кажется, что кода мало. А потом он начинает плодится и занимать % 20 от всего проекта. Да, в начале проекта QML позволяет быстро сделать красивую картинку, но с ростом проекта выгода от него начинает уменьшаться. В результате начинаешь думать, что лучше бы потратил время на написание собственных контролов в Qt в начале проекта.


          1. KanuTaH
            27.06.2019 15:35

            QML тоже нужно уметь готовить в том плане, что лучше на нем в начале написать некую продуманную библиотечку «своих» контролов (если не хватает стандартных — скажем, QQC), чем по-быстрому накидать лапшу из примитивов (на лапше из примитивов «красивая картинка» делается быстро, это да), а потом эту лапшу плодить. Ну или если видишь, что начинает плодиться лапша, лучше вовремя сделать рефакторинг, вынести дублирующийся код в местную библиотечку компонентов, продумать его интерфейс. QML тоже вполне модульный, этим можно и нужно пользоваться для уменьшения количества кода.


          1. 0serg
            27.06.2019 15:53
            +1

            QML идеально приспособлен для написания собственных контролов.
            Я серьезно. Набросать на QML кастомный компонент легче чем сделать кастомный виджет. Я в своих проектах кучу кастомных контролов так сооружаю, даже если типовой контрол — всего лишь специальным образом оформленная кнопочка используемая в четырех местах. Делается-то это тривиально и быстро, поэтому не жалко потратить чуть-чуть времени чтобы убрать копипасту.


            1. KanuTaH
              27.06.2019 15:58

              Набросать на QML кастомный компонент легче чем сделать кастомный виджет.

              +1. Причем намнооого легче.


            1. Mike255
              27.06.2019 22:45

              Полностью согласен. Писать легко, контролы получаются красивыми и быстрыми, минимум кода. Именно это и подкупает в начале проекта. Но связка Qt с QML в итоге занимает много кода и времени. Возможно если всю логику проекта перегнать на сторону QML (под JS) это будет большой проблемой. Но когда логика на C++, а на QML только интерфейс, то для меня это проблема.


              1. 0serg
                27.06.2019 23:45

                Связка Qt с QML идет через сигналы и слоты, что, вообще говоря, и для чисто нативного кода является наиболее «Qt-style» способом общения между собой разных частей приложения. В чем проблема-то? Может я что-то упускаю?


                1. Mike255
                  27.06.2019 23:53

                  Сигналы/слоты, это если у вас только клавиши. Как только появляются поля ввода, то начинаются свойства, с процедурами чтения/установки и т.д. А если нужно передавать список или таблицу, то программного кода получается очень много. В результате на каждую сущность нужно городить модель. И в конце проекта получается этих моделей штук 20-30, иногда больше.


                  1. 0serg
                    27.06.2019 23:58

                    Модели точно так же надо и для виджетов делать. Для тривиальных вариантов типа списка строк есть стандартные модели.


    1. mapron
      27.06.2019 09:39
      +1

      Посмотрите в сторону QML, он гораздо интереснее.

      Для поиграться может и поинтереснее, а для долгоиграющего проекта лучше начинать писать на виджетах. Все реализовать на QML нельзя, как только начинается интеграция нативного кода, сложность быстро растет.
      Хотя да, какой-нибудь там автомат по продаже пирожков и газировки на QML делать самое то)


      1. 0serg
        27.06.2019 11:18

        А что конкретно там растет и не может быть реализовано? Я пока с таким не сталкивался, поделитесь опытом. Нативный код абсолютно прозрачно доступен из QML через штатные сигналы и слоты.


        1. mapron
          27.06.2019 13:23

          Ну например… первое что вспомнил — подскажите, как в QML отобразить произвольный форматированный текст (пришедший с веба например), в качестве надписи? В частности, часть текста подсветить красным. Заранее фрагмент, разумеется неизвестен.
          Я вот не нашел способа это сделать)


          1. 0serg
            27.06.2019 13:50

            Берем QML, помещаем туда компонент Text
            Берем QObject который отображает нативный функционал в QML, добавляем ему слот в который приложение будет помещать полученный из веба текст:

            Q_PROPERTY(QString myText READ myText NOTIFY myTextChanged)

            Биндим в QML его со свойством text нашего QML-объекта Text
            Text {
                text: cppInterface.myText    
            }

            И вроде бы на этом всё :). Нам приходит из веба текст, нативный код говорит emit myTextChanged, QML запрашивает строку у нативного кода в геттере myText() и отображает ее. Поддерживаемое форматирование — HTML и CSS. Пример выделения одного слова красным цветом
            Hello <span style="font-color:red">world</span> in HTML

            Передаем подобную строку в text — она отобразится с выделением слова world красным цветом.
            По-моему тут от виджетов каких-то принципиальных отличий нету


            1. mapron
              28.06.2019 10:39
              -1

              Спасибо что так все подробно расписали, в этом не было необходимости :) Я знаю как прокидывать разное, речь как раз про ограниченность текста:
              отличие принципиальное одно — указанный вами пример не работает.
              Не будет он ничего подсвечивать красным. Только что перепроверил с Qt 5.11, ну вдруг меня память подвела и я что-то там не попробовал.


              1. 0serg
                28.06.2019 12:27

                Я конечно понимаю что вы высокого мнения о себе, но эти комментарии, вообще-то, не только вы читаете. Как сформулировали вопрос — такой и получили ответ.

                Не будет он ничего подсвечивать красным.

                Ну если тупо копипастить не включая голову то да, «Qt работать» не будет. Чтобы заработало надо в компоненте Text дописать

                textFormat: text.RichText

                и в HTML использовать не font-color а просто color:
                Hello <span style="color:red">world</span> in HTML

                Все естественно прекрасно работает. Пруф (Qt 5.12):



                Отдельно доставляет то что вы мне заявляли что «подробно расписывать не было необходимости». По всей видимости я как раз недостаточно подробно все для вас разжевал :).


                1. mapron
                  29.06.2019 18:11
                  +1

                  Ладно, был неправ (интересно что с этим же вопросом на форуме Qt мне сказали что это работать не будет, видимо тоже не знали про RichText).
                  Это лишь одно из препятствий, с которым пришлось столкнуться. Лично мое мнение — перспектив для десктопного применения технология QML не несёт.


    1. CIUK Автор
      27.06.2019 14:32

      Я не против QML, но пока что для разработки десктопных приложений виджеты, себя полностью оправдывают, не тенет переходить на QML. И для данной темы(плагины) всё таки виджеты подходят больше.
      А в плане интереса то в QML больше заинтересовало возможность работы с 3D моделями и их интеграция из других IDE.


      1. KanuTaH
        27.06.2019 15:49

        Для десктопа да, виджеты вполне норм. Но для мобильной и embedded разработки QML лучше. Я, когда QML только появился, тоже сначала с неохотой на него переползал, но потом разобрался, проникся и пользую с удовольствием.


        1. midday
          27.06.2019 23:06
          +1

          Для десктопа QML подходит точно так же хорошо. Просто надо проникнуться.
          Для примера можно взглянуть на blog.qt.io/blog/2018/03/06/slate-basic-pixel-art-editor-built-qt-quick
          Но и то, там можно было сделать еще куда более удобно.