一、準備工作
二、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
三、借鑑的博客
- https://blog.csdn.net/ioufev/article/details/81240572
- https://www.cnblogs.com/myboat/p/11738989.html