在Qt库上创建扩展系统-第2部分

回到第一篇文章,我想解释在何处需要开发具有图形界面(GUI)的扩展机制,并更详细地解释用于创建插件的机制。

接到一项任务,开发技术软件,用于在俄罗斯操作系统上从发动机的自行火炮(自动控制系统)调整,调节,控制,收集和分析信息。 根据特殊的交换协议,通过RS-232或RS422接口进行自动火炮与工艺程序之间的交换。

在查看并分析了俄罗斯操作系统列表的功能之后,选择了AstraLinux操作系统。 这是一个基于Debian发行版的专用系统,旨在全面保护信息并构建安全的自动化系统。 Astra Linux操作系统有两种版本:普通版(免费,通用)和特殊版(付费,专用,带有一组保护算法)。
在开发软件时,使用了Qt库。

该软件由功能模块组成,每个功能模块都允许您对引擎设置执行特定的工作。

功能模块是一个小部件,由各种可视组件,图形和表格组成,其中引擎参数以用户友好的形式显示。


功能模块“仪表板”


功能模块,可让您动态控制参数值在可接受的范围内


设置自行式火炮特性的功能模块

开发的软件(技术程序)仅用于特定类型的发动机。 对于新型发动机,有必要开发新软件,这导致开发,测试,调试的人工成本显着增加,结果导致该软件的实施延迟-尽管事实上在ACS开发阶段就已经需要这种软件。

为了摆脱开发技术软件的昂贵实践,决定开发一种软​​件包,以适应其ACS支持相同交换协议的任何引擎。

开发软件包的概念是可以基于插件技术扩展功能并支持图形模块。

该软件包包括三个主要部分:

  1. 综合体的功能核心
  2. 扩展系统引擎
  3. 插件集

核心功能包括确保与ACS进行可靠的数据交换并执行以下任务:

  • 使用特殊协议的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的指针,并将其传递给工艺程序,在该程序中,使用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窗口上,从而将其设置为子级。

接下来,使用show()方法将对象的信号和插槽与工艺程序核心的信号和插槽以及窗口显示的最后阶段连接(连接)。 当您关闭QmdiSubWindow窗口时,interfaceWidget类的对象也将被删除,因为为放置对象的subWindow预先设置了DeleteOnClose属性。

图形功能对象和工艺程序之间的数据交换使用数据封送和两种模式进行:
•单模式(请求-响应);
•监控模式,我们不断接收数据,该周期由与目标设备的数据交换机制确定;

封送处理是将数据转换为适合存储或传输的格式的过程。 当信息需要在一个程序的不同部分之间或从一个程序传输到另一个程序时使用。

在单一模式下,用户按下图形功能对象(窗口小部件)中的按钮,将生成一个包含参数列表的信号,应在目标设备(ACS)中读取其参数的数据并将其传输到图形功能对象。 主程序中的数据交换机制处理信号,并开始从自行火炮请求RS-232数据,接收到的数据打包在QbyteArray中,并与信号一起发送到图形对象。 该对象具有请求的参数的列表,因此,知道它们的类型,解压缩QbyteArray中包含的数据,并以图形和表格以及专用图形小部件(箭头指示器,数字指示器,背光按钮等)的形式显示它们。

在监视模式下,初始化插件时,系统会立即请求该插件的必要参数列表,并且在对其参数进行持续监视期间,它将读取该插件的其他参数,并在每个数据交换周期将其作为字节数据包给出。 数据交换周期的时间取决于请求的参数数量。

目前,测试正在进行中,与此同时,针对不同任务的插件组的开发也正在进行中。

已创建插件模板,以加快开发新插件的过程。 使用插件模板来开发新的功能模块时,必须创建新的图形形式并实现数据处理和可视化算法。

已经开发了用于有线RS-232接口以及无线WIFI和蓝牙的物理驱动程序软件模块。

迄今为止,该软件包以2通道模式运行,也就是说,它可以与2台自行火炮同时工作(这是客户的要求)。 用户将功能模块插件连接到两个通道中的任何一个。

在测试过程中,我们设法同时启动了由内核成功处理的12个插件。 甚至12个插件也不是该系统的限制。 由于采用了Qt双缓冲系统,因此减少了绘制插件所花费的时间并防止了插件闪烁,因此,将来在增加插件数量时,主要的负担将仅通过RS-232接口进行数据交换。 尽管实践表明,在ACS引擎的调试,调试和测试期间,同时工作3-4个插件就足够了。

作为工作的结果,我们收到了用于快速开发技术程序的软件包,该程序旨在调试,调试和测试各种类型的发动机(机电系统)的自行式火炮。

针对不同类型的引擎开发了2套插件。

通过将用于特定类型的自行火炮的一组插件连接到软件包,我们获得了用于配置,调试和测试这种自行火炮的多功能程序。



将来,我们计划写一篇有关我们自己的图形小部件开发,针对特定任务的文章,以及另一篇有关图形对象布局的文章。

Source: https://habr.com/ru/post/zh-CN457744/


All Articles