1. 程式人生 > >麒麟KY-RTI分佈模擬技術:第五章 Qt程式設計

麒麟KY-RTI分佈模擬技術:第五章 Qt程式設計

第五章 Qt程式設計

       本章講述瞭如何基於Qt Creator設計控制檯程式和圖形介面程式。控制檯程式相當於4.3節的聊天程式;圖形介面程式相當於4.4節的時間管理程式。圖形介面程式近似於真實模擬專案,講述瞭如何設計模擬專案,如何週期性地推進模擬,如何通過Qt臨界區和pthread訊號量實現主執行緒和回撥執行緒之間的同步等功能。

5.1 控制檯程式

       本節說明如何開發一個Qt控制檯程式,其結果表明將基於KY-RTI開發的GNU C++程式移植到Qt非常方便。

5.1.1需求分析

       本專案需要基於Qt Creator開發一個相當於4.3節的Qt聊天程式。

5.1.2專案設計

       基於Qt Creator先實現一個Qt程式框架,然後把4.3節的GNU C++聊天程式程式碼移植到Qt程式。

5.1.3專案開發

       下面以銀河麒麟作業系統中的Qt Creator為例。

       第1步

:啟動Qt Creator,選擇“New Project”建立一個Qt專案。

                                                       圖5.1 啟動Qt Creator

       第2步:選擇“Qt Console Application”建立一個控制檯專案。點選“Choose”繼續。

                                                      圖5.2 建立控制檯專案

       第3步:設定專案名稱為“QtChatConsole”、儲存目錄為“/home/lbq”,點選“Next”繼續,直至專案生成。

                                                      圖5.3 設計專案名和目錄

       圖5.4是專案生成後的介面。到目前為止,本專案有1個QtChatConsole.pro工程檔案和1個main.cpp原始碼檔案。

                                                      圖5.4 預設生成的Qt控制檯專案

       第4步:新增HLA回撥檔案。使用者應根據自己的目錄適當修改。

       (1)將RTI-1.3NGv6/Linux-x86_64-opt-mt/apps/chat目錄下的HwFederateAmbassador.cpp、HwFederateAmbassador.hh兩個檔案拷貝到該工程目錄下。這兩個檔案是4.3節聊天程式的原始檔。

         cd /home/lbq/RTI-1.3NGv6/Linux-x86_64-opt-mt/apps/chat

         cp HwFederateAmbassador.cpp  HwFederateAmbassador.hh /home/lbq/QtChatConsole/

       (2)在圖5.4中,選擇根節點“QtChatConsole”,按右鍵,新增已有檔案。將HwFederateAmbassador.cpp、HwFederateAmbassador.hh新增到本專案。

       第5步:修改main.cpp。

       將RTI-1.3NGv6/Linux-x86_64-opt-mt/apps/chat目錄下的Chat.cpp程式碼貼上到main.cpp,並做簡單修改,區別只在於第1、123-126這幾行語句。

                                                      表5.1 main.cpp

  1. #include <QCoreApplication>
  2. #include "HwFederateAmbassador.hh"
  3.  
  4. #include <RTI.hh>
  5. #include <fedtime.hh>
  6. #include <iostream>
  7. using namespace std;
  8.  
  9. RTI::InteractionClassHandle     hChatClass;
  10. RTI::ParameterHandle             hChatName;
  11. RTI::ParameterHandle             hChatSentence;
  12.  
  13. int hw_main(int argc, char *argv[])
  14. {
  15.     const char *federationExecutionName = "chat";
  16.     const char *FEDfile = "chat.fed";
  17.  
  18.     char federateName[50];
  19.     cout << "Please input your name: ";
  20.     cin >> federateName;
  21.  
  22.     try {
  23.         RTI::RTIambassador       rti;
  24.         HwFederateAmbassador     fedAmb;
  25.  
  26.         RTI::FederateHandle      federateId;
  27.  
  28.         try {
  29.             rti.createFederationExecution(federationExecutionName, FEDfile);
  30.         }
  31.  
  32.         catch ( RTI::FederationExecutionAlreadyExists& e ) {
  33.             //According to the HLA standard, only the first federate can call this service succesfully.
  34.             //cerr << "FED_HW: Note: Federation execution already exists." << e << endl;
  35.         } catch ( RTI::Exception& e ) {
  36.             cerr << "FED_HW: ERROR:" << e << endl;
  37.             return -1;
  38.         }
  39.  
  40.         try {
  41.             federateId = rti.joinFederationExecution(federateName, federationExecutionName, &fedAmb);
  42.         } catch (RTI::FederateAlreadyExecutionMember& e) {
  43.             cerr << "FED_HW: ERROR: " << argv[1]
  44.                  << " already exists in the Federation Execution "
  45.                  << federationExecutionName << "." << endl;
  46.             cerr << e << endl;
  47.             return -1;
  48.         } catch (RTI::FederationExecutionDoesNotExist&) {
  49.             cerr << "FED_HW: ERROR: Federation Execution "
  50.                  << "does not exists."<< endl;
  51.             return -1;
  52.         } catch ( RTI::Exception& e ) {
  53.             cerr << "FED_HW: ERROR:" << e << endl;
  54.             return -1;
  55.         }
  56.  
  57.         ///////////////////////////////////////////////////////////
  58.  
  59.         hChatClass = rti.getInteractionClassHandle("chat");
  60.         hChatName = rti.getParameterHandle("name", hChatClass);
  61.         hChatSentence = rti.getParameterHandle("sentence", hChatClass);
  62.  
  63.         //如果向外傳送,則需要公佈
  64.         rti.publishInteractionClass(hChatClass);
  65.         //如果需要接收,則必須訂購
  66.         rti.subscribeInteractionClass(hChatClass);
  67.  
  68.         string szSentence;
  69.         cin.ignore();
  70.         while (0 != strcmp(szSentence.c_str(), "exit")) {
  71.             cout << "Please input a sentence: ";
  72.             getline(cin, szSentence);
  73.  
  74.             RTI::ParameterHandleValuePairSet* pParams = NULL;
  75.             long numParams(2);
  76.             pParams = RTI::ParameterSetFactory::create (numParams);
  77.  
  78.             pParams->add(hChatName,(char*)federateName, strlen(federateName)+1);
  79.             pParams->add(hChatSentence,(char*)szSentence.c_str(), szSentence.size());
  80.  
  81.             try {
  82.                 rti.sendInteraction(hChatClass, *pParams, "");
  83.             } catch(...) {
  84.                 cerr << "Error: send interaction" << endl;
  85.             }
  86.  
  87.             pParams->empty();
  88.             delete pParams;   // Deallocate the memory
  89.         }
  90.  
  91.         try {
  92.     rti.resignFederationExecution( RTI::DELETE_OBJECTS_AND_RELEASE_ATTRIBUTES );
  93.         } catch ( RTI::Exception& e ) {
  94.             cerr << "FED_HW: ERROR:" << e << endl;
  95.             return -1;
  96.         }
  97.  
  98.         try {
  99.             rti.destroyFederationExecution( federationExecutionName );
  100.         } catch ( RTI::FederatesCurrentlyJoined& /* e */ ) {
  101.             cerr << "FED_HW: FederatesCurrentlyJoined" << endl;
  102.             return 0;
  103.         } catch ( RTI::FederationExecutionDoesNotExist& /* e */) {
  104.             cerr << "FED_HW: FederationExecutionDoesNotExist" << endl;
  105.             return 0;
  106.         } catch ( RTI::Exception& e ) {
  107.             cerr << "FED_HW: ERROR:" << e << endl;
  108.             return -1;
  109.         }
  110.     } catch (RTI::ConcurrentAccessAttempted& e) {
  111.         cerr << e << endl;
  112.         return -1;
  113.     } catch ( RTI::Exception& e ) {
  114.         cerr << "FED_HW: ERROR:" << e << endl;
  115.         return -1;
  116.     }
  117.  
  118.     return 0;
  119. }
  120.  
  121. int main(int argc, char *argv[])
  122. {
  123.     QCoreApplication a(argc, argv);
  124.  
  125.     hw_main(argc, argv);
  126.     return a.exec();
  127. }

 

5.1.4編譯執行

5.1.4.1編譯

       編譯之前,需要修改工程檔案。雙擊“QtChatConsole.pro”,開啟該檔案。在檔案末尾新增如下內容:

INCLUDEPATH += /home/lbq/RTI-1.3NGv6/Linux-x86_64-opt-mt/include

LIBS += -L/home/lbq/RTI-1.3NGv6/Linux-x86_64-opt-mt/lib -lRTI-NG -lfedtime -lMid -lMidUtil -lbz2mid -lpthread

DEFINES += RTI_USES_STD_FSTREAM

       通過下列兩種編譯方式,生成可執行程式QtChatConsole。

       (1)由Qt Creator構建(Build)。Qt Creator在構建專案時,會將生成的可執行程式和臨時檔案預設儲存到/home/lbq/build-QtChatConsole-Desktop-Debug目錄,而不是原始碼目錄。

       (2)在原始碼目錄手動構建,執行如下命令。

       qmake

       make

5.1.4.2測試執行

       執行程式:

       (1)啟動KY-RTI。

       (2)在圖5.4中,點選2次執行按鈕,啟動2個介面;或者啟動2個命令列介面,執行程式。如下圖所示。

       問題:左圖liu傳送一個“hello”,但是右圖zhang卻沒有收到。

                                                      圖5.5 開啟tick開關後執行控制檯程式

       原因:當程式執行時,會預設生成RTI.rid檔案。該檔案中打開了tick開關,然而本程式並沒有呼叫tick服務來接收回調,因此無法收到應答。

       解決:將RTI.rid檔案中的“;; UsingTickSwitch On”改為“;; UsingTickSwitch Off”。若沒有RTI.rid,則執行程式後會自動生成該檔案。

       重新啟動程式後,執行正常。

                                                      圖5.6 關閉tick開關後執行控制檯程式

5.2 圖形程式

       本節說明如何從專案需求開始,從頭設計和開發一個Qt圖形介面的HLA時間管理程式,功能相當於4.4節的GNU C++時間管理程式。整個專案分為4個階段:需求分析、專案設計、程式碼設計、編譯執行。其中,需求分析階段與HLA/RTI關係不大,甚至整個專案的設計框架與HLA/RTI關係也不大。模擬應用的需求千千萬,Qt的設計模式也多樣化,不同的模擬應用可採用不同的設計模式;HLA/RTI服務於模擬應用,用於系統中的不同模擬成員之間的資料通訊。HLA/RTI為模擬應用提供支援,而不是模擬應用反過來受HLA/RTI的制約,希望使用者通過本程式的設計過程能夠體會到這一點。現有的一些HLA/RTI程式碼自動生成工具實際上就是給使用者設計系統制定了一個約束框架,不管什麼型別的模擬專案都裝到一個套子裡,使得使用者設計程式缺乏較強的靈活性,也不利於基於HLA/RTI移植已有的非HLA/RTI專案。

5.2.1需求分析

       本模擬專案的名稱為“TimeManagementExample”,當然也可以叫做像“星球大戰”、“世界末日”之類的響亮名字。名稱規定了不是“TimeManagementExample”的程式不屬於本專案。對HLA/RTI程式來說,聯邦名稱為“TimeManagementExample”,不是該名字的模擬成員不屬於本專案。

       每個模擬成員擁有3架飛機,但其中只有1架飛機會起飛,飛機的x、y座標為隨機數(不考慮合理性),飛機每隔1秒釋出自己的二維態勢資訊。

       要求採用圖形介面顯示模擬結果。

5.2.2專案設計

       本專案所有模擬成員的功能相同,因此只需要開發一個Qt程式即可。在多次啟動該可執行程式後,多個模擬成員之間通過KY-RTI進行資料通訊。本專案的聯邦名稱為“TimeManagementExample”,模擬成員之間的通訊資料通過tracer.fed進行定義,包括一個plane物件類和xPos、yPos兩個屬性。另外,模擬週期為1秒,每個模擬成員週期性地釋出飛機起飛後的位置資訊,並接收其他飛機的位置資訊。

       Qt圖形程式包含2個圖形介面。一個圖形介面負責當程式啟動執行時,輸入模擬成員名稱;另一個圖形介面用來接收其他模擬成員的飛機位置資訊;由於接收的位置資訊會累積很多,所以當超過1000條資訊時清空1次(在實際專案中可儲存到資料庫)。

       時間間隔採用Qt定時器來設計,定時器每隔1秒中斷1次。

                                                      圖5.7 模擬成員的2個圖形介面示意圖

5.2.3程式碼設計

       第1步:啟動Qt Creator,選擇“New Project”建立一個Qt專案。

                                                      圖5.8 啟動Qt Creator

       第2步:選擇“Qt Widgets Application”建立一個圖形程式。點選“Choose”繼續。

                                                      圖5.9 建立圖形介面程式

       第3步:設定專案名稱“QtTimeManagement”和儲存的目錄“/home/lbq”,點選“Next”繼續,中間過程選擇預設選項,直至專案生成。圖5.10是專案生成後的介面。

                                                      圖5.10 預設生成的圖形程式介面

       程式執行後的圖形介面如下圖所示。該介面為主介面,可作為“專案設計”階段確定的介面2,後面需要再設計介面1來輸入模擬成員名稱,另外還需要完善主介面。

                                                      圖5.11 執行生成的圖形介面程式

       第4步:建立介面1,用於輸入模擬成員名。

       (1)右鍵點選圖5.10中的“Forms”,新建一個窗體。後面過程可參考下列幾幅圖,類名設定為NameDialog。

                                                      圖5.12 選擇窗體型別

                                                      圖5.13 選擇對話方塊模板

                                                      圖5.14 設定類名

       (2)為新建的NameDialog窗體新增QLabel、QLineEdit、QPushButton共3個控制元件,設定QLabel為“請輸入模擬成員名稱”並調整字型大小。編輯框的變數名預設設定為lineEdit。

                                                      圖5.15 設定介面1中的控制元件

       (3)為OK按鈕新增Click事件。與介面1相關的namedialog.h和namedialog.cpp就設計完成,分別如表5.2和表5.3所示。

表5.2 namedialog.h

  1. #ifndef NAMEDIALOG_H
  2. #define NAMEDIALOG_H
  3.  
  4. #include <QDialog>
  5.  
  6. namespace Ui
  7. {
  8. class NameDialog;
  9. }
  10.  
  11. class NameDialog : public QDialog
  12. {
  13.     Q_OBJECT
  14.  
  15. public:
  16.     explicit NameDialog(QWidget *parent = 0);
  17.     ~NameDialog();
  18.  
  19. private slots:
  20.     void on_okButton_clicked();
  21.  
  22. private:
  23.     Ui::NameDialog *ui;
  24. };
  25.  
  26. #endif // NAMEDIALOG_H

                                                      表5.3 namedialog.cpp

  1. #include "namedialog.h"
  2. #include "ui_namedialog.h"
  3.  
  4. QString federateName = "";
  5.  
  6. NameDialog::NameDialog(QWidget *parent) :
  7.     QDialog(parent),
  8.     ui(new Ui::NameDialog)
  9. {
  10.     ui->setupUi(this);
  11. }
  12.  
  13. NameDialog::~NameDialog()
  14. {
  15.     delete ui;
  16. }
  17.  
  18. void NameDialog::on_okButton_clicked()
  19. {
  20.     federateName = ui->lineEdit->text();
  21.     if(federateName.size() != 0) {
  22.         this->close();
  23.     }
  24. }

       第5步:設計介面2。

       (1)刪除MainWindow主介面窗體中的選單、工具條、狀態列,新增1個QLabel和1個QListWidget控制元件,分別用於顯示模擬成員名和顯示接收到的飛機態勢資訊。

                                                      圖5.16 設定介面2中的控制元件

       (2)新增定時器。到目前為止,整個程式的程式碼框架就已經完成,該框架本身與HLA/RTI並沒有太大關係。執行程式結果如下圖所示。左圖為介面1,右圖為介面2,右圖中的資料為程式設計的假想資料。

                                                      圖5.17 基於假想資料的程式執行結果

       程式由5個原始檔組成,除了表5.2和表5.3表示的namedialog.h、namedialog.cpp外,還有3個檔案main.cpp、mainwindow.h、mainwindow.cpp,參見表5.4、表5.5、表5.6。

                                                      表5.4 main.cpp 

  1. #include "mainwindow.h"
  2. #include <QApplication>
  3.  
  4. int main(int argc, char *argv[])
  5. {
  6.     QApplication a(argc, argv);
  7.  
  8.     MainWindow w;
  9.     w.show();
  10.  
  11.     return a.exec();
  12. }

                                                      表5.5 mainwindow.h

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3.  
  4. #include <QMainWindow>
  5. #include <QStringList>
  6. #include <QTimer>
  7.  
  8. namespace Ui
  9. {
  10. class MainWindow;
  11. }
  12.  
  13. class MainWindow : public QMainWindow
  14. {
  15.     Q_OBJECT
  16.  
  17. public:
  18.     explicit MainWindow(QWidget *parent = 0);
  19.     ~MainWindow();
  20.  
  21. private slots:
  22.     void onTimerOut();
  23.  
  24. private:
  25.     Ui::MainWindow *ui;
  26.     QStringList m_receivedMessages; //儲存接收到的飛機位置資訊
  27.  
  28.     QTimer *timer;
  29. };
  30.  
  31. #endif // MAINWINDOW_H

                                                      表5.6 mainwindow.cpp

  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include "namedialog.h"
  4.  
  5. extern QString federateName; //模擬成員名稱
  6.  
  7. MainWindow::MainWindow(QWidget *parent) :
  8.     QMainWindow(parent),
  9.     ui(new Ui::MainWindow)
  10. {
  11.     ui->setupUi(this);
  12.  
  13.     m_receivedMessages.clear();
  14.  
  15.     while (federateName.size() == 0) { //啟動介面1輸入模擬成員名稱
  16.         NameDialog n;
  17.         n.exec();
  18.     }
  19.  
  20.     //設定模擬成員名稱
  21.     ui->label->setText(federateName);
  22.     ui->label->setAlignment(Qt::AlignCenter);
  23.  
  24.     //新增定時器
  25.     timer = new QTimer();
  26.     timer->setInterval(1000);
  27.     timer->start();
  28.     connect(timer, SIGNAL(timeout()), this, SLOT(onTimerOut()));
  29. }
  30.  
  31. MainWindow::~MainWindow()
  32. {
  33.     delete ui;
  34. }
  35.  
  36. //定時處理
  37. void MainWindow::onTimerOut()
  38. {
  39.     if(ui->listWidget->count() > 1000) {
  40.         ui->listWidget->clear();
  41.     }
  42.  
  43.     QString imaginaryReceivedMsg = "myhandle  100  200  f15"; //假想的接收到的飛機位置資訊
  44.     m_receivedMessages.append(imaginaryReceivedMsg );
  45.  
  46.     for(int i=0; i<m_receivedMessages.size(); i++) {
  47.         ui->listWidget->addItem(new QListWidgetItem(m_receivedMessages[i]));
  48.     }
  49.  
  50.     m_receivedMessages.clear();
  51. }

       第6步:以上步驟構成的系統是靜態的,當啟動多次時,每個模擬成員都是獨立執行,模擬成員之間不能進行資料通訊。因此,需要加入HLA/RTI程式碼將模擬成員變成動態的,實現相互間的通訊。在已有的模擬系統中加入HLA/RTI程式碼,基本方法是:

      (1)刪除已有程式中的通訊程式碼(例如TCP/IP程式碼)。對本例而言,沒有這部分內容。

      (2)在程式的初始化部分新增建立聯邦執行、加入聯邦執行、公佈和訂購服務、註冊物件示例等;如果採用時間管理,則還要設定相關的時間管理標識(是否為受限成員、管控成員)。

       對於本例而言,初始化部分為MainDialog類的建構函式;因此,在該建構函式中完成這些工作,需要新增的程式碼可參考4.4節時間管理示例的TimeManagement.cpp。

      (3)MainWindow::onTimerOut()函式每秒執行1次,在一個模擬週期內主要處理2件事。一是該模擬週期內收到回撥後應該採取的動作;二是將自己下一步的態勢釋出出去。對於本例而言,一是將收到的其他飛機的態勢資訊顯示出來;而是呼叫HLA的updateAttributeValues服務更新飛機的下一步位置,並將模擬時間請求推進到下一步。當然,依照模擬模型不同,如果每個模擬週期只更新自己的當前態勢資訊,也未嘗不可;但這樣的訊息在HLA中應定義為RO(Receive Order)訊息而不是TSO(Time Stamp Order)訊息。

     (4)添加回調檔案。

       (a)將RTI-1.3NGv6/Linux-x86_64-opt-mt/apps/time-notick目錄下的HwFederateAmbassador.cpp、HwFederateAmbassador.hh兩個檔案拷貝到該工程目錄下。

  cd /home/lbq/RTI-1.3NGv6/Linux-x86_64-opt-mt/apps/time-notick

  cp HwFederateAmbassador.cpp  HwFederateAmbassador.hh /home/lbq/QtTimeManagement/

       (b)在圖5.4中,選擇根節點“QtTimeManagement”,按右鍵,新增已有檔案。將HwFederateAmbassador.cpp、HwFederateAmbassador.hh新增到本專案。

       至此,整個程式碼設計完成。整個程式包括namedialog.h、mainwindow.h、HwFederateAmbassador.h 3個.h檔案,以及main.cpp、namedialog.cpp、mainwindow