Java OPC client開發(fā)踩坑記

最近一個項目中需要用到OPC client,從OPC Server中獲取數(shù)據(jù)。主要的編程語言使用Java實現(xiàn)。實際開發(fā)中遇到了各種坑彼城,其實也和自己沒有這方面的經(jīng)驗有關(guān),現(xiàn)在寫一篇文章分享下整個項目中遇到的一些問題退个。

準備知識

開發(fā)OPC Client之前需要一些準備知識募壕,需要一些知識儲備,否則根本搞不清楚里面的門道∮镉現(xiàn)在對一些預(yù)先準備的知識點做一概述司抱。OPC是什么就不說了。

OPC Server端的協(xié)議

OPC Server端目前常見的有以下幾種協(xié)議:

  • OPC DA: Data Access協(xié)議黎烈,是最基本的OPC協(xié)議习柠。OPC DA服務(wù)器本身不存儲數(shù)據(jù),只負責(zé)顯示數(shù)據(jù)收集點的當(dāng)前值照棋∽世#客戶端可以設(shè)置一個refresh interval,定期刷新這個值烈炭。目前常見的協(xié)議版本號為2.0和3.0溶锭,兩個協(xié)議不完全兼容。也就是用OPC DA 2.0協(xié)議的客戶端連不上OPC DA 3.0的Server
  • OPC HDA: Historical Data Access協(xié)議符隙。前面說過DA只顯示當(dāng)前狀態(tài)值趴捅,不存儲數(shù)據(jù)。而HDA協(xié)議是由數(shù)據(jù)庫提供霹疫,提供了歷史數(shù)據(jù)訪問的能力拱绑。比如價格昂貴的Historian數(shù)據(jù)庫,就是提供HDA協(xié)議接口訪問OPC的歷史數(shù)據(jù)丽蝎。HDA的Java客戶端目前我沒找到免費的猎拨。
  • OPC UA: Unified Architecture統(tǒng)一架構(gòu)協(xié)議。誕生于2008年,摒棄了前面老的OPC協(xié)議繁雜红省,互不兼容等劣勢额各,并且不再需要COM口訪問,大大簡化了編程的難度吧恃∠豪玻基于OPC UA的開源客戶端非常多。不過由于誕生時間較晚痕寓,目前在國內(nèi)工業(yè)上未大規(guī)模應(yīng)用傲醉,并且這個協(xié)議本身就跟舊的DA協(xié)議不兼容,客戶端沒法通用厂抽。

我們的目標(biāo)環(huán)境絕大多數(shù)是OPC DA 2.0的Server,極個別可能有OPC DA 3.0丁眼。當(dāng)時找到的很多類庫實現(xiàn)的都是OPC UA的筷凤。

第一坑: 基于JAVA開發(fā)的OPC Client非常少,大部分是商業(yè)的苞七,售價不菲∶晔兀現(xiàn)場環(huán)境又是OPC DA的Server,開源client只有兩個可選蹂风,找工具和評估就花了不少時間卢厂。

OPC存儲格式

OPC存儲和傳統(tǒng)的關(guān)系型數(shù)據(jù)庫存儲格式有很大的不同,不同于關(guān)系型數(shù)據(jù)庫的表存儲惠啄,OPC存儲格式是樹形結(jié)構(gòu)慎恒,Server端的存儲格式如下:

host
`-- OPC Server Name
    `-- tag1: value, type, timestamp, ...,
    `-- tag2: value, type, timestamp, ...,
    `-- tag3: ...
    ...

每個主機上可能存在多個OPC Server,每個Server下面有若干個tag撵渡,就是各個數(shù)據(jù)收集點當(dāng)前的值融柬,會定期更新。每個tag包含的內(nèi)容大致有當(dāng)前值趋距,值類型粒氧,時間戳等等數(shù)據(jù)。是一種樹形結(jié)構(gòu)节腐。所以客戶端連接的時候需要指明服務(wù)器的ip或主機名外盯,需要連接的OPC服務(wù)名,以及監(jiān)聽哪些tag的數(shù)據(jù)翼雀。

Client端存儲的格式如下:

Group1
`-- tag1
`-- tag2
`-- tag3
Group2
`-- tag4
`-- tag5
...

這個就比較有意思了饱苟,Client是可以自己維護一個存儲層級Group。也就是服務(wù)端存儲的都是一個個tag狼渊,客戶端可以自己維護一個個Group掷空,分類存放這些tag。所以O(shè)PC的Client就和傳統(tǒng)的關(guān)系型數(shù)據(jù)庫有很大的不同√沟埽客戶端除了指明上述Server端的信息之外护锤,還需要創(chuàng)建一個個Group,將Server端的tag一個個放到這些Group中酿傍,然后對應(yīng)的tag才能持續(xù)的獲得數(shù)據(jù)烙懦。

第二坑: 這種存儲格式在其他數(shù)據(jù)庫十分罕見,當(dāng)時這里就迷茫了好一陣子赤炒,通過了解協(xié)議的人講解氯析,才明白原來客戶端還可以維護一套存儲結(jié)構(gòu)。當(dāng)時沒理清楚Group和tag的關(guān)系莺褒,從服務(wù)端看不到Group掩缓,客戶端卻要填一個Group,不知道這個Group從哪來遵岩。后來才搞清楚你辣。

COM

Component Object Model對象組件模型,是微軟定義的一套軟件的二進制接口尘执,可以實現(xiàn)跨編程語言的進程間通信舍哄,進而實現(xiàn)復(fù)用。

DCOM

Microsoft Distributed Component Object Model誊锭,坑最多的一個玩意表悬。字面意思看起來是分布式的COM,簡單理解就是可以利用網(wǎng)絡(luò)傳輸數(shù)據(jù)的COM協(xié)議丧靡,客戶端也可以通過互聯(lián)網(wǎng)分布在各個角落蟆沫,不再限制在同一臺主機上了。

上面描述來看這玩意好像挺美好是吧温治?實際操作開發(fā)中才發(fā)現(xiàn)饥追,這玩意簡直是坑王之王,對于不熟悉的人來說充滿了坑罐盔,十分折騰但绕。配置過程可以參考一些文章

  • DCOM是windows上的服務(wù),使用前需要啟用
  • DCOM是遠程連接的協(xié)議惶看,需要配置相關(guān)的權(quán)限捏顺,以及防火墻規(guī)則放行
  • 特別注意這一點,前兩項配置在網(wǎng)上都能找到纬黎,這一條是我在經(jīng)歷無數(shù)次痛之后才意識到的幅骄。DCOM遠程連接和http不同,是通過本地用戶認證的本今,需要以本地用戶身份登錄服務(wù)器拆座,拿到相應(yīng)的權(quán)限主巍,才能使用DCOM。有點繞是吧挪凑?你可以類比Windows的遠程桌面登錄孕索,需要拿到服務(wù)器的用戶名密碼才能登錄并操作系統(tǒng),權(quán)限受到登錄用戶的權(quán)限所限制躏碳。而DCOM就是用的這種方式搞旭。關(guān)于各種錯誤網(wǎng)上能找出一大堆解決方案,可能還沒一個能解決你的問題的菇绵。甚至可能progID無論無何也通不了肄渗,始終報錯,不得不改用CLSID這種方法咬最,十分坑翎嫡。

神坑: DCOM。從配置開始就充滿了陷阱和坑永乌。不但配置繁瑣復(fù)雜惑申,還會受到各種權(quán)限以及防火墻規(guī)則的影響。最惡心的是這玩意隨時可能報各種奇葩的錯誤铆遭,由于缺乏足夠的錯誤信息硝桩,很難解決沿猜,基本憑借經(jīng)驗解決DCOM的故障枚荣。

開發(fā)過程

收集到足夠的準備知識后,就可以開工了啼肩。OPC Server是DA 2.0的橄妆,因此找到了以下兩個開源類庫。
JEasyOPC Client

  • 底層依賴JNI祈坠,只能跑在windows環(huán)境害碾,不能跨平臺
  • 整個類庫比較古老,使用的dll是32位的赦拘,整個項目只能使用32位的JRE運行
  • 同時支持DA 2.0與3.0協(xié)議慌随,算是亮點

Utgard

  • OpenSCADA項目底下的子項目
  • 純Java編寫,具有跨平臺特性
  • 全部基于DCOM實現(xiàn)(劃重點)
  • 目前只支持DA 2.0協(xié)議躺同,3.0協(xié)議的支持還在開發(fā)中

這兩個類庫都試過阁猜,JEasyOPC底層用了JNI,調(diào)用代碼量倒不是很大蹋艺,使用也足夠簡單剃袍,坑也遇到了點,就是64位的JRE運行會報錯捎谨,說dll是ia32架構(gòu)的民效,不能運行于AMD64平臺下憔维,換了32位版本的JRE之后運行起來了,但是一直報錯Unknown Error畏邢,從JNI報出來的业扒,不明所以,實在無力解決棵红,只能放棄凶赁。

只剩下Utgard一種選擇了,也慶幸目標(biāo)Server是DA 2.0的逆甜,用這個類庫完全夠用虱肄。這個類庫全部使用DCOM協(xié)議連接OPC Server,所以對于本地連接OPC Server交煞,理論上不需要COM口咏窿,但是這個類庫全部使用DCOM協(xié)議連接,所以依舊需要配置主機名素征,以及登錄的用戶名密碼集嵌。使用之前必須先配置DCOM,其中痛苦不足為外人道也御毅,在上面準備知識部分已經(jīng)寫道了根欧。

經(jīng)過一番折騰,總算將項目跑起來了端蛆,最終參考的工程代碼如下(項目實用Gradle構(gòu)建凤粗,代碼使用Utgard官方的tutorial范例):
build.gradle:

apply plugin: 'java'
apply plugin: 'application'

repositories {
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
    jcenter()
    maven { url 'http://neutronium.openscada.org/maven/' }
}

dependencies {
    compile 'org.openscada.utgard:org.openscada.opc.lib:1.3.0-SNAPSHOT'
    compile 'org.openscada.utgard:org.openscada.opc.dcom:1.2.0-SNAPSHOT'
    compile 'org.jinterop:j-interop:2.0.4'
    compile 'ch.qos.logback:logback-core:1.2.3'
    compile 'org.slf4j:slf4j-api:1.7.25'
}

mainClassName = 'UtgardTutorial1'

src/main/java/UtgardTutorial1.java:

import org.jinterop.dcom.common.JIException;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.AccessBase;
import org.openscada.opc.lib.da.Server;
import org.openscada.opc.lib.da.SyncAccess;

import java.util.concurrent.Executors;

public class UtgardTutorial1 {

    public static void main(String[] args) throws Exception {
        // create connection information
        final ConnectionInformation ci = new ConnectionInformation();
        ci.setHost("localhost");
        ci.setUser("Administrator");
        ci.setPassword("mypassword");
        ci.setProgId("TLSvrRDK.OPCTOOLKIT.DEMO");
//        ci.setClsid("08a3cc25-5953-47c1-9f81-efe3046f2d8c"); // if ProgId is not working, try it using the Clsid instead
        final String itemId = "tag1";
        // create a new server
        final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

        try {
            // connect to server
            server.connect();
            // add sync access, poll every 500 ms
            final AccessBase access = new SyncAccess(server, 500);
            access.addItem(itemId, (item, state) ->
                    System.out.println("Resut: " + state.toString()));
            // start reading
            access.bind();
            // wait a little bit
            Thread.sleep(10 * 1000);
            // stop reading
            access.unbind();
        } catch (final JIException e) {
            System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));
            e.printStackTrace();
        }
    }
}

最終項目運行輸出如下:

 Recieved RESPONSE
Resut: Value: [[]]], Timestamp: 星期三 七月 05 00:32:29 CST 2017, Quality: 192, ErrorCode: 00000000
七月 05, 2017 12:32:27 上午 rpc.DefaultConnection processOutgoing
信息:
 Sending REQUEST
七月 05, 2017 12:32:27 上午 rpc.DefaultConnection processIncoming
信息:
 Recieved RESPONSE
Resut: Value: [[]]], Timestamp: 星期三 七月 05 00:32:29 CST 2017, Quality: 192, ErrorCode: 00000000
七月 05, 2017 12:32:28 上午 rpc.DefaultConnection processOutgoing
信息:
 Sending REQUEST
七月 05, 2017 12:32:28 上午 rpc.DefaultConnection processIncoming
信息:
 Recieved RESPONSE
Resut: Value: [[U]], Timestamp: 星期三 七月 05 00:32:30 CST 2017, Quality: 192, ErrorCode: 00000000
七月 05, 2017 12:32:28 上午 rpc.DefaultConnection processOutgoing
信息:
 Sending REQUEST
七月 05, 2017 12:32:28 上午 rpc.DefaultConnection processIncoming
信息:
 Recieved RESPONSE
Resut: Value: [[U]], Timestamp: 星期三 七月 05 00:32:30 CST 2017, Quality: 192, ErrorCode: 00000000
七月 05, 2017 12:32:29 上午 rpc.DefaultConnection processOutgoing
信息:

總算跑起來了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末今豆,一起剝皮案震驚了整個濱河市嫌拣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌呆躲,老刑警劉巖异逐,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異插掂,居然都是意外死亡灰瞻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門辅甥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酝润,“玉大人,你說我怎么就攤上這事肆氓∨圩妫” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵谢揪,是天一觀的道長蕉陋。 經(jīng)常有香客問我捐凭,道長,這世上最難降的妖魔是什么凳鬓? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任茁肠,我火速辦了婚禮,結(jié)果婚禮上缩举,老公的妹妹穿的比我還像新娘垦梆。我一直安慰自己,他們只是感情好仅孩,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布托猩。 她就那樣靜靜地躺著,像睡著了一般辽慕。 火紅的嫁衣襯著肌膚如雪京腥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天溅蛉,我揣著相機與錄音公浪,去河邊找鬼。 笑死船侧,一個胖子當(dāng)著我的面吹牛欠气,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播镜撩,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼预柒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琐鲁?” 一聲冷哼從身側(cè)響起卫旱,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤人灼,失蹤者是張志新(化名)和其女友劉穎围段,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體投放,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡奈泪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灸芳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涝桅。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烙样,靈堂內(nèi)的尸體忽然破棺而出冯遂,到底是詐尸還是另有隱情,我是刑警寧澤谒获,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布蛤肌,位于F島的核電站壁却,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏裸准。R本人自食惡果不足惜展东,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炒俱。 院中可真熱鬧盐肃,春花似錦、人聲如沸权悟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峦阁。三九已至处硬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拇派,已是汗流浹背荷辕。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留件豌,地道東北人疮方。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像茧彤,于是被迫代替她去往敵國和親骡显。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理曾掂,服務(wù)發(fā)現(xiàn)惫谤,斷路器,智...
    卡卡羅2017閱讀 134,711評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,310評論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法珠洗,類相關(guān)的語法溜歪,內(nèi)部類的語法,繼承相關(guān)的語法许蓖,異常的語法蝴猪,線程的語...
    子非魚_t_閱讀 31,664評論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,814評論 0 11
  • 公元2017年1月5日,凌晨膊爪!我準備睡覺的時候自阱,在一個微信群里看到了這樣一個人! 是的米酬,全部是對話沛豌!開始!【下面是...
    一凡SU閱讀 248評論 0 0