陳昌浩, 李曉明
(浙江理工大學(xué) 機(jī)械與自動(dòng)控制學(xué)院, 杭州 310018)
虛擬儀器(Visual Instruments, VI)是指結(jié)合軟、硬件資源構(gòu)造測(cè)量?jī)x器, 即用戶在通用計(jì)算機(jī)的硬件和軟件平臺(tái)上, 可自定義儀器操作面板和測(cè)量功能的軟件系統(tǒng)[1].軟件在虛擬儀器中有著舉足輕重的地位, 美國(guó)國(guó)家儀器公司(NI)曾提出“軟件即儀器”(Software is Instrument)這一理念, 形象的概括了軟件的重要地位,它擔(dān)負(fù)著儀器系統(tǒng)數(shù)據(jù)分析處理、實(shí)時(shí)顯示、動(dòng)態(tài)修改等重?fù)?dān)[2].而衡量一款虛擬儀器軟件的好壞很大程度上取決于前面板設(shè)計(jì)的方便與否.目前, 虛擬儀器前面板的開(kāi)發(fā)主要有兩種: 一種是基于文本語(yǔ)言來(lái)編寫(xiě)儀器軟件的底層驅(qū)動(dòng)、數(shù)據(jù)處理算法、虛擬儀器面板顯示等, 這類語(yǔ)言主要有C/C++, Java, C#等.近年也出現(xiàn)了使用諸于G 語(yǔ)言[3], Unity3D 等新興編程語(yǔ)言來(lái)完成虛擬儀器軟件的開(kāi)發(fā)[4], 另一種是以NI 的LabView 為代表的可視化圖形編程語(yǔ)言開(kāi)發(fā)方式[5].兩種方式各有優(yōu)缺點(diǎn), 前者運(yùn)行效率高, 容易實(shí)現(xiàn)復(fù)雜的數(shù)據(jù)處理算法, 有較為完備的圖形框架, 容易自由定制復(fù)雜功能的儀器面板界面, 但是編程語(yǔ)言上手門(mén)檻較高, 學(xué)習(xí)成本大, 開(kāi)發(fā)周期較長(zhǎng), 后者使用圖形化界面開(kāi)發(fā), 容易上手, 開(kāi)發(fā)周期短, 但是有不易于拓展、運(yùn)行效率低等缺點(diǎn).
目前國(guó)內(nèi)外對(duì)儀器軟件開(kāi)發(fā)平臺(tái)的研究不多, 大多數(shù)科研人員都是應(yīng)用儀器技術(shù)來(lái)解決自身領(lǐng)域的問(wèn)題[6,7].特別是國(guó)內(nèi), 雖然有一些學(xué)者, 如華中科技大學(xué)的學(xué)者基于iOS 的虛擬儀器瀏覽器[8]、基于前端響應(yīng)式技術(shù)的可重構(gòu)手機(jī)虛擬儀器[9]、基于Unity3D 的虛擬儀器模型[4], 浙江大學(xué)學(xué)者設(shè)計(jì)的虛擬儀器共享平臺(tái)[10]等, 雖然取得了一些成果, 但大部分虛擬儀器測(cè)試系統(tǒng)主要還是基于國(guó)外軟件平臺(tái)的二次開(kāi)發(fā)和硬件方案提供.雖然國(guó)外以NI 的LabView 為代表的虛擬儀器開(kāi)發(fā)軟件在重用和硬件解耦等方面有很大的改善, 但還是有些不足: 軟件模塊的開(kāi)發(fā)閉源, 用戶只能使用軟件提供的模塊, 對(duì)于軟件進(jìn)行功能拓展還是需要重新開(kāi)發(fā),開(kāi)發(fā)成本高[11].
基于以上的不足, 本文借助Java 語(yǔ)言和SWT/JFace圖形包, 研究并實(shí)現(xiàn)了通用虛擬儀器前面板的運(yùn)行平臺(tái), 定義了一套儀器顯示組件的標(biāo)準(zhǔn), 平臺(tái)界面由各個(gè)不同的組件組合完成, 開(kāi)發(fā)者可根據(jù)需求自己設(shè)計(jì)組件拓展.提供了儀器軟件與硬件系統(tǒng)的數(shù)據(jù)交換的接口, 減少了軟件硬件的耦合性, 使得應(yīng)用軟件能適用非主流特別是自主研發(fā)的硬件系統(tǒng).
儀器前面板運(yùn)行平臺(tái)既可獨(dú)立運(yùn)行, 也能作為基礎(chǔ)平臺(tái)嵌入其他自動(dòng)測(cè)試系統(tǒng), 它的工作原理如圖1所示, 平臺(tái)加載保存有寫(xiě)好的組件的jar 包, 加載來(lái)自網(wǎng)絡(luò)或本地的儀器腳本, 解析腳本生成儀器前面板虛擬界面, 并與底層硬件系統(tǒng)通訊, 交換硬件系統(tǒng)和虛擬面板中的數(shù)據(jù).具體工作方式如下:
腳本: 一般是XML 文件, 存放界面中組件的信息[12],包括容器組件和一般組件, 腳本文件可以是本地文件也可以是網(wǎng)絡(luò)文件.本地文件存放在指定的文件夾中,在Windows 和Linux 操作系統(tǒng)中, 一般存放于項(xiàng)目所在根目錄下的UI 文件夾中, 網(wǎng)絡(luò)文件存儲(chǔ)在其他PC機(jī)器之中, 如果遠(yuǎn)程用戶想運(yùn)行平臺(tái), 就可以通過(guò)TCP 協(xié)議傳輸網(wǎng)絡(luò)腳本數(shù)據(jù)到平臺(tái).為區(qū)別于以.xml 結(jié)尾的配置文件, 腳本文件的格式設(shè)置為.uixml
組件jar 包: 平臺(tái)采用模塊化的設(shè)計(jì), 組件獨(dú)立于平臺(tái), 存放在jar 包中, 方便拓展.一般將jar 包放入一個(gè)文件夾中, 統(tǒng)一管理, 在PC 系統(tǒng)中, 組件jar 包通常存放于UI 文件夾下的component 文件夾.
界面運(yùn)行: 平臺(tái)加載腳本解析之后生成可視化圖形界面, 用戶可以直接操作面板, 平臺(tái)提供數(shù)據(jù)交換支持.平臺(tái)使用Java 語(yǔ)言和SWT/JFace 圖形包來(lái)生成圖形界面, 滿足跨平臺(tái)需要, 該平臺(tái)和界面可以運(yùn)行在Windows 系統(tǒng)和Linux 系統(tǒng)之上.
圖1 前面板運(yùn)行平臺(tái)工作原理
虛擬儀器的基本組成模塊是組件, 組件是具有某種功能的獨(dú)立模塊, 依據(jù)實(shí)現(xiàn)功能的不同有容器組件和功能組件, 組件之間的關(guān)系如圖2 所示.容器組件是用來(lái)安放其他組件的組件, 功能組件是實(shí)現(xiàn)人機(jī)交互功能的組件.將容器組件和功能組件按照一定順序組裝好就能形成一個(gè)虛擬儀器.下文將圍繞控件介紹運(yùn)行平臺(tái)的實(shí)現(xiàn)方法.
圖2 前面板界面示意圖
觀察者模式是一種一對(duì)多的設(shè)計(jì)思想, 多個(gè)觀察者訂閱同一個(gè)主題時(shí), 當(dāng)主題有變化時(shí), 通知觀察者獲取新的主題, 當(dāng)一個(gè)觀察者改變主題的屬性值時(shí), 其他觀察者也能及時(shí)接收到改變[13].組件與組件之間需要通訊, 組件與硬件系統(tǒng)之間也需要通訊, 為了解決這種一對(duì)多的依賴關(guān)系, 本課題采用了數(shù)據(jù)池?cái)?shù)據(jù)綁定的數(shù)據(jù)交換機(jī)制: 平臺(tái)中開(kāi)辟一個(gè)數(shù)據(jù)池, 采用KVO(Key-Value-Object)的方式存儲(chǔ)數(shù)據(jù), key 對(duì)應(yīng)一個(gè)屬性, value 對(duì)應(yīng)組件的屬性值, 一個(gè)屬性可以被多個(gè)組件綁定; 同時(shí), 數(shù)據(jù)池中的數(shù)據(jù)也可以通過(guò)接口和硬件系統(tǒng)交換數(shù)據(jù).
數(shù)據(jù)池?cái)?shù)據(jù)綁定的工作原理是: 數(shù)據(jù)池中定義一個(gè)數(shù)據(jù)對(duì)象A, 組件B 需要對(duì)象A 的數(shù)據(jù), 組件D 也需要對(duì)象A 的數(shù)據(jù), 硬件系統(tǒng)中的硬件模塊C 輸出的數(shù)據(jù)對(duì)應(yīng)對(duì)象A 的數(shù)據(jù), 當(dāng)C 交換數(shù)據(jù)將對(duì)象A 的數(shù)據(jù)改變時(shí), 數(shù)據(jù)池會(huì)發(fā)送通知給組件B 和D, 告訴B 和D 對(duì)象A 的最新數(shù)據(jù).數(shù)據(jù)綁定的關(guān)系如圖3 所示.
圖3 數(shù)據(jù)綁定關(guān)系
數(shù)據(jù)池使用單例設(shè)計(jì)模式, 平臺(tái)中只有一個(gè)數(shù)據(jù)池, 要綁定的數(shù)據(jù)都在數(shù)據(jù)池中操作, 使用數(shù)據(jù)綁定的方法有: register (String tag, IDataBinder binder); //注冊(cè)變量到數(shù)據(jù)池, bind (String tag, IDataBinder binder);//組件綁定變量, publish (String tag, Object value,IDataBinder binder); //改變變量的屬性值, 通知綁定的其他組件新的屬性值; IDataBinder 是一個(gè)數(shù)據(jù)綁定的接口, 組件都要繼承該接口, 接口中只有一個(gè)方法:newValue (String tag, Object value), 每個(gè)組件都要實(shí)現(xiàn)該方法, 數(shù)據(jù)池使用組件的該方法通知到各個(gè)組件新的屬性值.
平臺(tái)使用了模塊化的設(shè)計(jì)思想, 組件是一個(gè)個(gè)獨(dú)立的模塊, 可以自由的拓展.為了方便組件的添加, 可將設(shè)計(jì)好的組件打包成jar 包, 存放到項(xiàng)目根路徑下的UI 文件夾下面, 在使用到這個(gè)組件時(shí), 平臺(tái)動(dòng)態(tài)加載jar 包尋找該組件, 并使用Java 反射機(jī)制創(chuàng)建組件.
2.2.1 雙親委派機(jī)制
雙親委派機(jī)制是JVM (Java 虛擬機(jī))加載類的機(jī)制.JVM 虛擬機(jī)根據(jù)類的全限定名來(lái)加載類, 而類加載器是JVM 加載類的實(shí)現(xiàn), 圖4 是類加載器之間關(guān)系, 雙親委派機(jī)制是為了避免不同的類加載器加載同一個(gè)類[14].
圖4 類加載器之間關(guān)系
雙親委派機(jī)制的工作流程如下:
(1) 類加載器收到類加載請(qǐng)求;
(2) 把這個(gè)請(qǐng)求委托給父加載器去完成, 一直向上委托直到啟動(dòng)類加載器;
(3) 類加載器檢查能不能加載這個(gè)類, 如果可以就加載這個(gè)類并結(jié)束加載, 如果不能, 拋出異常, 通知子加載器進(jìn)行加載;
(4) 循環(huán)步驟(3).
2.2.2 運(yùn)行時(shí)類加載器
編寫(xiě)好的靜態(tài)代碼通常是由AppClassLoader 類加載器來(lái)加載, 但是這種類加載器只能加載在編譯之前就已經(jīng)寫(xiě)好的代碼, 這樣組件信息和平臺(tái)耦合在了一起.根據(jù)雙親委派機(jī)制, 本方案使用了URLClassLoader來(lái)加載jar 包中的組件, URLClassLoader 是拓展類加載器的一種, 是JDK 提供的一種可加載外部文件中的類的信息的類加載器, 配合應(yīng)用程序加載器即可加載平臺(tái)中預(yù)寫(xiě)好的組件和外部拓展的組件.本文中, 我們對(duì)URLClassLoader 進(jìn)行封裝, 使用RuntimeClassLoader來(lái)處理組件jar 包的加載, 組件加載的步驟如圖5 所示.
平臺(tái)每次啟動(dòng)的時(shí)候讀取路徑信息, 加載組件jar 包到classPath 中, 讓JVM 能夠?qū)ふ业浇M件的信息,加載的過(guò)程使用RuntimeClassLoader 的addJarToPath(String path)方法; 當(dāng)有腳本運(yùn)行時(shí), 平臺(tái)解析腳本文件, 讀取其中的組件類名和初始化參數(shù), 使用Runtime ClassLoader 的loadClass (String className)提供反射支持, 生成組件, 同時(shí)調(diào)用組件的init()方法加載初始化參數(shù)完成組件的初始化.
圖5 組件加載過(guò)程
2.3.1 組件模型
組件根據(jù)功能側(cè)重點(diǎn)的不同, 可分為容器組件和功能組件, 這兩種組件之間有一些共同點(diǎn), 也有一些不同點(diǎn), 容器組件可看做是功能組件的拓展, 具有可放置組件的容器.組件的模型如圖6 所示.
圖6 組件模型
組件包括界面顯示的內(nèi)容以及人機(jī)交互處理, 包含兩大部分: 顯示部分和數(shù)據(jù)部分, 顯示部分顯示組件的外貌, 數(shù)據(jù)部分處理與用戶有關(guān)的輸入和輸出, 以及相關(guān)的事件.本方案設(shè)計(jì)中事件也是按照數(shù)據(jù)的方式進(jìn)行處理.
容器組件可看做特殊的組件, 其界面UI 不止有實(shí)現(xiàn)部分, 還有可放置組件的容器凹槽, 起到布局定位的作用, 容器組件界面UI 如圖7 所示.
圖7 容器組件界面UI
組件的裝配原理: 將組件放入容器凹槽即可.
2.3.2 組件類定義
平臺(tái)的所有組件必須遵從相同的接口原則, 這樣才能相互通訊, 也方便拓展.為了統(tǒng)一組件的開(kāi)發(fā)方法,提高開(kāi)發(fā)效率, 本文設(shè)計(jì)了組件的接口, 及其基礎(chǔ)實(shí)現(xiàn)類, 開(kāi)發(fā)的組件只需要繼承基礎(chǔ)實(shí)現(xiàn)類, 按照功能需求重寫(xiě)相關(guān)的方法即可快速開(kāi)發(fā)該組件, 組件接口和實(shí)現(xiàn)類的關(guān)系如圖8 所示.
Component 接口定義了所有組件都需要用到的一些方法, Container 接口繼承Component 接口定義了容器組件需要用到的方法, ComponentAdapter 抽象類實(shí)現(xiàn)了Component 接口, 給一些方法提供默認(rèn)實(shí)現(xiàn),ContainerAdapter 抽象類繼承ComponentAdapter 并實(shí)現(xiàn)Container 接口給容器組件的一些方法提供默認(rèn)實(shí)現(xiàn).除默認(rèn)實(shí)現(xiàn)方法外, 拓展的組件還需重寫(xiě)和實(shí)現(xiàn)部分方法, 如表1 所示.
2.3.3 組件組合原則
容器組件規(guī)范了組件的相對(duì)位置,、大小以及其他裝飾物等.而功能組件專門(mén)處理人機(jī)交互功能, 功能組件可以簡(jiǎn)單到類似于一個(gè)標(biāo)簽, 一個(gè)按鈕, 也可以復(fù)雜到一個(gè)功能完備的示波器界面.容器組件側(cè)重布局, 而功能組件側(cè)重交互處理, 所以需要將兩種組件恰好的組合在一起, 形成前面板的界面顯示[15].下面是組件組合的一些原則:
(1) 容器組件上可以放置任意界面組件, 功能組件只能放置在容器組件內(nèi).
(2) 任何一個(gè)程序界面至少包含一個(gè)容器組件.
(3) 容器組件放置組件的位置如果已經(jīng)放置有組件, 不能再放置組件.
2.4.1 儀器腳本描述方法
平臺(tái)采用腳本作為描述組件組合的工具, 腳本語(yǔ)言采用可拓展標(biāo)記語(yǔ)言(eXtensiable Markup Language,XML)[16].XML 語(yǔ)言支持拓展, 我們使用自定義標(biāo)簽描述組件以及組件的屬性和數(shù)據(jù)綁定[17,18].為方便識(shí)別組件的屬性和數(shù)據(jù)綁定, 采用組件標(biāo)簽嵌套屬性標(biāo)簽的格式來(lái)描述組件.根據(jù)組件功能特性的不同, 我們采用<component>, <container>兩種標(biāo)簽分別來(lái)描述功能組件和容器組件.
圖8 組件類圖
表1 組件類實(shí)現(xiàn)和重寫(xiě)方法
功能組件的通用描述格式如下:
Component 標(biāo)簽表示該組件是功能組件, name 表示組件的名稱, class 表示組件的全限定類名, 平臺(tái)解析腳本時(shí)需要根據(jù)類名使用反射來(lái)生成組件, param 標(biāo)簽描述組件的屬性, 組件需要根據(jù)param 標(biāo)簽的屬性完成初始化.
Data-binding 標(biāo)簽表示數(shù)據(jù)綁定, local 對(duì)應(yīng)組件自身的屬性的數(shù)據(jù)綁定, external 表示要對(duì)外進(jìn)行數(shù)據(jù)交換的屬性, 即該標(biāo)簽描述了組件的輸入輸出端口.某些儀器功能中, 有的組件可能相對(duì)獨(dú)立, 不受外部系統(tǒng)的輸入輸出影響, 可以省略external 屬性, 其他屬性根據(jù)組件的功能不同而變化.
容器組件的通用描述格式如下:
Container 標(biāo)簽該組件是容器組件, 相比于功能組件省略了數(shù)據(jù)綁定的描述, 添加了slot 標(biāo)簽描述組件的布局位置, slot 標(biāo)簽表示容器凹槽, name 是凹槽的名稱, ratio 是凹槽占該容器組件的比例, 容器凹槽標(biāo)簽下可以繼續(xù)嵌套其他組件標(biāo)簽.
儀器腳本由組件組合而成, 平臺(tái)定義一個(gè)XML 腳本表示一個(gè)儀器前面板, 使用標(biāo)簽<application>來(lái)描述,即該標(biāo)簽是儀器腳本的根元素.根元素下可放置組件,若組件為容器組件, 容器凹槽內(nèi)可繼續(xù)嵌套組件, 若干個(gè)組件一層層嵌套就組合成一個(gè)布局和功能完整的儀器腳本.
下面一段腳本描述了一個(gè)包含啟動(dòng)和停止按鈕的儀器界面:
appliaciton 表示該腳本文件是儀器前面板腳本,FillContainer 容器組件包含兩個(gè)InstrumentButton 按鈕組件, ratio 屬性設(shè)置其各容器凹槽的布局, direct 屬性設(shè)置容器凹槽的水平和垂直排列, InstrumentButton 是一個(gè)按鈕組件, name 是組件的名稱, text 是按鈕組件顯示的文本, 兩個(gè)組件都綁定了mouse.down 的內(nèi)部屬性, 啟動(dòng)按鍵綁定數(shù)據(jù)常量池startButtonOut 對(duì)象, 停止按鍵綁定stopButtonOut 對(duì)象, 如果其他組件想要響應(yīng)按鈕, 只需綁定按鈕對(duì)應(yīng)的數(shù)據(jù)常量池對(duì)象即可.
2.4.2 儀器腳本解析
腳本文件描述了組件的屬性和組件組合的方式,平臺(tái)的作用是按順序完整讀取腳本并生成前面板界面.平臺(tái)解析腳本的流程如圖9 所示.
圖9 平臺(tái)解析腳本流程圖
一個(gè)腳本文件我們默認(rèn)為一個(gè)儀器前面板界面,則腳本文件只能有一個(gè)根節(jié)點(diǎn), 且該根節(jié)點(diǎn)標(biāo)簽為application, 如果不是application 或根節(jié)點(diǎn)多于一個(gè),則腳本讀取失敗.讀取根節(jié)點(diǎn)下組件生成組件對(duì)象, 讀取組件的參數(shù)完成組件初始化, 如果組件為功能組件,完成組件的數(shù)據(jù)綁定, 如果為容器組件則遍歷讀取容器凹槽內(nèi)的組件, 若凹槽內(nèi)組件為容器組件, 則遞歸調(diào)用處理容器組件的方法, 否則調(diào)用處理功能組件的方法[19].待組件都處理完成, 則啟動(dòng)平臺(tái), 生成儀器界面,用戶可在界面中與平臺(tái)交互.
自動(dòng)測(cè)試實(shí)驗(yàn)中經(jīng)常用到信號(hào)發(fā)生器和示波器,信號(hào)發(fā)生器可以產(chǎn)生多種類型的信號(hào), 示波器負(fù)責(zé)顯示輸入的信號(hào), 本文將兩種裝置組合在一起, 設(shè)計(jì)了一個(gè)信號(hào)發(fā)生器顯示裝置.
它由5 個(gè)組件組成, 一個(gè)示波器組件, 一個(gè)信號(hào)發(fā)生器組件, 一個(gè)啟動(dòng)示波器按鈕, 一個(gè)停止示波器按鈕,一個(gè)控制臺(tái)顯示組件.它的工作原理是: 用戶點(diǎn)擊示波器啟動(dòng)按鈕, 平臺(tái)接收到按鈕狀態(tài)的改變, 發(fā)送通知給示波器組件, 示波器組件開(kāi)始工作, 等待信號(hào)輸入, 用戶點(diǎn)擊信號(hào)發(fā)生器按鈕, 啟動(dòng)或停止信號(hào)發(fā)生器的工作, 按鈕的工作狀態(tài)改變會(huì)輸出到控制臺(tái)中方便用戶觀察當(dāng)前狀態(tài).發(fā)生器工作時(shí), 每隔一定時(shí)間就生成一定幅度、頻率、相移的信號(hào)數(shù)據(jù), 并更新信號(hào)的數(shù)據(jù)池, 平臺(tái)發(fā)送通知給示波器輸入信號(hào)的改變, 示波器接收輸入信號(hào), 更新顯示的波形.圖10 是腳本運(yùn)行界面.
圖10 信號(hào)發(fā)生顯示器運(yùn)行界面
本文在分析了當(dāng)前虛擬儀器的研究現(xiàn)狀, 針對(duì)當(dāng)前大多虛擬儀器開(kāi)發(fā)軟件不易拓展的問(wèn)題, 提出了一種通用儀器軟件的前面板運(yùn)行平臺(tái), 采用模塊化組件的方式, 用數(shù)據(jù)常量池?cái)?shù)據(jù)綁定來(lái)交換數(shù)據(jù), 以XML 腳本語(yǔ)言為載體, 組合組件來(lái)滿足不同的測(cè)試功能需求.上文信號(hào)發(fā)生顯示器的運(yùn)行也說(shuō)明了該通用儀器前面板運(yùn)行平臺(tái)的可行性.不過(guò)儀器腳本文件的編寫(xiě)格式較復(fù)雜和冗余, 接下來(lái)的研究工作是設(shè)計(jì)更通用的腳本標(biāo)簽格式, 采用圖形化的編輯界面來(lái)設(shè)計(jì)儀器前面板, 搭建一個(gè)較為完備的通用儀器軟硬件開(kāi)發(fā)平臺(tái).