JAVA對接OPC協議-Utgard

一、準備工作

  •  下載OPC服務器,推薦KEPServer,推薦此篇博客
  •  OPC和DCOM配置,不細說了,推薦此篇博客
  • 建立些模擬設備

二、OPC系統連接和讀寫操作-Utgard方式

  • 同步讀寫很簡單,網上找找就能有,我就不細說了
  • 異步寫, 暫時沒研究,我遇到的場景是寫入併發少,讀取併發多,畢竟是用於工控領域
  • 所以本篇博客主要是針對於異步讀取(側重點)與同步寫入(比較簡單)

1.異步讀取

  • 網上大都是用的Async20Access這個類去實現的,這個寫法的優點:能隨時獲取到當前的item屬性,缺點:最麻煩的、最坑的,如果 當我們是一次性查詢一個list或者說多條數據,他的回調函數居然是一條一條回來的,而不是以一整個的結果集以一次回調回來。有個想法或者方案是說,起多個線程異步去執行 access.addItem(itemId1, new DevDataCallback());這句,在回調的時候根據item屬性再把每次回調的值放到本地的map,之後在while判斷是否和輸入的list長度相等,相等再將拼湊的數據返回到調用端。但之前沒想這方面的實現,而是找到了下面講的的第二種寫法
  • 後面想了下,如果要按照“有個想法或者方案是說”這裏的來做,可以建議大家去看看JUC提供的例如:CountDownLatch看能否實現,感覺可能可以~~  哈哈 畢竟我沒試過~~
ackage com.qxzn.shangf.service.impl;

import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.core.JIString;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.*;

import java.util.Random;
import java.util.concurrent.Executors;

/**
 * @author Colde
 * @mail [email protected]
 * @date 2020/6/10 10:26
 */
public class TestOpc {

    public static void main(String[] args) throws Exception{
        // 連接信息
        final ConnectionInformation ci = new ConnectionInformation();
        ci.setHost("********");         // 電腦IP
        ci.setUser("*********");             // 電腦上自己建好的用戶名
        ci.setPassword("******");          // 用戶名的密碼

        // 使用MatrikonOPC Server的配置
        // ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305"); // MatrikonOPC的註冊表ID,可以在“組件服務”裏看到
        // final String itemId = "u.u";    // MatrikonOPC Server上配置的項的名字按實際

        // 使用KEPServer的配置
        ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // KEPServer的註冊表ID,可以在“組件服務”裏看到
        final String itemId1 = "channel1.dev1.Tag1";    // KEPServer上配置的項的名字,沒有實際PLC,用的模擬器:simulator
        final String itemId2 = "channel1.dev1.Tag2";
        final String itemId3 = "channel1.dev1.Tag3";
        // final String itemId = "通道 1.設備 1.標記 1";
//        Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
//        server.connect();
//        Group group = server.addGroup();
//        Item item = group.addItem(itemId3);
//        System.out.println("ItemName:[" + item.getId()+ "],value:" + item.read(false).getValue());
        // 啓動服務
        final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

        try {
            // 連接到服務
            server.connect();
            // add sync access, poll e8very 500 ms,啓動一個同步的access用來讀取地址上的值,線程池每500ms讀值一次
            // 這個是用來循環讀值的,只讀一次值不用這樣
            final AccessBase access = new Async20Access(server, 500,false);
            // 這是個回調函數,就是讀到值後執行這個打印,是用匿名類寫的,當然也可以寫到外面去
            access.addItem(itemId1, new DevDataCallback());
            access.addItem(itemId2, new DevDataCallback());
            access.addItem(itemId3, new DevDataCallback());



//            final Group group = server.addGroup("test");
//            final Item item1 = group.addItem(itemId1);
//            final Item item2= group.addItem(itemId2);
//            final Item item3 = group.addItem(itemId3);
//            final JIVariant value = new JIVariant("false");
//            item2.write(value);
//            access.addItem(itemId1, new DevDataCallback());

            // start reading,開始讀值
            access.bind();
            // wait a little bit,有個10秒延時
            Thread.sleep(100* 1000);
            // stop reading,停止讀取
            //access.unbind();
        } catch (final JIException e) {
            System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));
        }
    }

}

class DevDataCallback implements DataCallback{

    @Override
    public void changed(Item item, ItemState itemState) {
        int type = 0;
        try {
            type = itemState.getValue().getType(); // 類型實際是數字,用常量定義的
        } catch (JIException e) {
            e.printStackTrace();
        }

        System.out.println("監控項的數據類型是:-----" + type);
        System.out.println("監控項的item是:-----" + item.getId());
        System.out.println("監控項的時間戳是:-----" + itemState.getTimestamp().getTime());
        System.out.println("監控項的詳細信息是:-----" + itemState);

        // 如果讀到是short類型的值
        if (type == JIVariant.VT_I2) {
            short n = 0;
            try {
                n = itemState.getValue().getObjectAsShort();
            } catch (JIException e) {
                e.printStackTrace();
            }
            System.out.println("-----short類型值: " + n);
        }

        // 如果讀到是字符串類型的值
        if(type == JIVariant.VT_BSTR) {  // 字符串的類型是8
            JIString value = null;
            try {
                value = itemState.getValue().getObjectAsString();
            } catch (JIException e) {
                e.printStackTrace();
            } // 按字符串讀取
            String str = value.getString(); // 得到字符串
            System.out.println("-----String類型值: " + str);
        }
    }
}
  • 第二種方式是利用OPCAsyncIO2的原生read方法去寫,這個寫法的優點是可以按照list批量的item查詢,而且回調返回的也是以map方式返回而且是以一次的回調返回;但缺點是表面上我們沒知道返回結果集中的順序關係是否是和輸入list一致,不過後面,經過我斷點觀察... ... (確實 網上關於這方面的資料有點少)發現lKeyedResultSet<Integer, ValueData> result裏面的key和輸入list的下標是對應的。
  • 如上圖   0下標對應的是Tag1     1下標對應的是Tag2   2下標對應的是Tag3
  • 如上圖返回的map的順不是和輸入保持一致,第一個的key是1 表示是輸入數組的下標也就是第二個也就是Tag2,看圖也對應上了;類似可以考證其他屬性值
  • 另外附加一個很重要的點:就是如果存在高併發的問題,我們怎麼去維護能取到一個確定的回調呢?這個什麼意思呢?也就是說第一個獲取OPC服務器數據的請求來了,然後網絡可能卡頓了下,第一個回調還沒有來;第二個請求又來了,那你怎麼知道後面來的回調對應的是哪個請求的呢?這裏的話,解決方案是我們手動去維護一個transactionId,asyncIO2.read(opcReadAndWriteDTO.getTransactionId(), serverHandles); read方法第一個參數就是我們需要傳入的代表這次請求的一個標記,然後在回調函數的參數也會返回對應的值,這樣我們就可以綁定到一起了。
    /**
    *
    *真正讀取的時候
    */
    final OPCAsyncIO2.AsyncResult asyncResult = asyncIO2.read(opcReadAndWriteDTO.getTransactionId(), serverHandles);
    
    
    
    /**
    *
    *
    *此爲回調函數
    *transactionId就是前面opcReadAndWriteDTO.getTransactionId()獲取的
    /
    public void readComplete(final int transactionId,
                                 final int serverGroupHandle, final int masterQuality,
                                 final int masterErrorCode,
                                 final KeyedResultSet<Integer, ValueData> result) {
    
    //業務邏輯
    
    }
    
    
    
    
    

    2.同步寫入

  • 邏輯很簡單我就貼下主要代碼(不全)
  • group = mServer.addGroup("opc_write");
                    final Item item = group.addItem(opcReadAndWriteDTO.getItemIdList().get(0));
                    final JIVariant value = new JIVariant(opcReadAndWriteDTO.getValue());
                    item.write(value);

    3. 依賴

  • <!-- OPC服務 -->
    		<dependency>
    			<groupId>org.kohsuke.jinterop</groupId>
    			<artifactId>j-interop</artifactId>
    			<version>2.0.5</version>
    		</dependency>
    		<dependency>
    			<groupId>org.openscada.external</groupId>
    			<artifactId>org.openscada.external.jcifs</artifactId>
    			<version>1.2.25</version>
    		</dependency>
    		<dependency>
    			<groupId>org.openscada.jinterop</groupId>
    			<artifactId>org.openscada.jinterop.core</artifactId>
    			<version>2.1.8</version>
    		</dependency>
    		<dependency>
    			<groupId>org.openscada.jinterop</groupId>
    			<artifactId>org.openscada.jinterop.deps</artifactId>
    			<version>1.5.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.openscada.utgard</groupId>
    			<artifactId>org.openscada.opc.dcom</artifactId>
    			<version>1.5.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.openscada.utgard</groupId>
    			<artifactId>org.openscada.opc.lib</artifactId>
    			<version>1.5.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.bouncycastle</groupId>
    			<artifactId>bcprov-jdk15on</artifactId>
    			<version>1.59</version>
    		</dependency>

    4. 工具類鏈接

  • 還在上傳,審覈過了,再掛鏈接。沒積分介意私聊我,但是其實只要你寫點東西上傳,讓別人也用用,你也就有積分了~~ 豈不美哉。
  • 鏈接地址:https://download.csdn.net/download/baidu_35751704/12531861

三、借鑑的博客

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章