一、准备工作
- 下载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 a1345629820@qq.com
* @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. 工具类链接
- 还在上传,审核过了,再挂链接。没积分介意私聊我,但是其实只要你写点东西上传,让别人也用用,你也就有积分了~~ 岂不美哉。