前期的协议知识了解
opc server协议了解
常见三种的协议
OPC DA: Data Access协议,是最基本的OPC协议。OPC DA服务器本身不存储数据,只负责显示数据收集点的当前值。客户端可以设置一个refresh interval,定期刷新这个值。目前常见的协议版本号为2.0和3.0,两个协议不完全兼容。也就是用OPC DA 2.0协议的客户端连不上OPC DA 3.0的Server
OPC HDA: Historical Data Access协议。前面说过DA只显示当前状态值,不存储数据。而HDA协议是由数据库提供,提供了历史数据访问的能力。比如价格昂贵的Historian数据库,就是提供HDA协议接口访问OPC的历史数据。HDA的Java客户端目前我没找到免费的。
OPC UA: Unified Architecture统一架构协议。诞生于2008年,摒弃了前面老的OPC协议繁杂,互不兼容等劣势,并且不再需要COM口访问,大大简化了编程的难度。基于OPC UA的开源客户端非常多。不过由于诞生时间较晚,目前在国内工业上未大规模应用,并且这个协议本身就跟旧的DA协议不兼容,客户端没法通用。
实际情况
目标环境是 OPC DA 2.0的server,基于JAVA开发的OPC Client非常少,大部分是商业的,现场环境又是OPC DA的Server,开源client只有两个可选。
开发过程
JEasyOPC Client
底层依赖JNI,只能跑在windows环境,不能跨平台
整个类库比较古老,使用的dll是32位的,整个项目只能使用32位的JRE运行
同时支持DA 2.0与3.0协议,算是亮点
Utgard
OpenSCADA项目底下的子项目
纯Java编写,具有跨平台特性
全部基于DCOM实现(划重点)
目前只支持DA 2.0协议,3.0协议的支持还在开发中
综合考虑一下决定选用Utgard进行开发,参考了一下网上的一个表格
对比项 | Utgard | Jeasyopc |
---|---|---|
Linux下 | 支持(纯Java编写的) | 不支持() |
Windows 64位下 | 支持 | 不支持 |
用户名密码 | 需要 | 不需要 |
组查询 | 不支持 | 支持 |
压力测试(单线程同步) | 略快7W点大约在4224ms | 略慢7W点大约在22540ms |
压力测试(单线程异步) | 略快 | 略慢 |
压力测试(单线程发布订阅) | 无 | 无 |
环境准备
需要配置OPCserver。
服务端准备
使用Matrikon OPC Server Simulation
代码开发
读取数据
package com.skytech.opc;
import java.util.concurrent.Executors;
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.DataCallback;
import org.openscada.opc.lib.da.Item;
import org.openscada.opc.lib.da.ItemState;
import org.openscada.opc.lib.da.Server;
import org.openscada.opc.lib.da.SyncAccess;
/**
* Created by michloas on 2018/9/6.
*/
public class UtgardTutorialRead {
public static void main(String[] args) throws Exception {
// 连接信息
final ConnectionInformation ci = new ConnectionInformation();
ci.setHost("localhost");
ci.setDomain("");
ci.setUser("opc");
ci.setPassword("123456");
//ci.setProgId("Matrikon.OPC.Simulation.1");
// MatrikonOPC Server
ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");
final String itemId = "uu.u";//项的名字按实际
// KEPServer
// ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
// final String itemId = "u.u.u";// 项的名字按实际
// final String itemId = "通道 1.设备 1.标记 1";
// 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, new DataCallback() {
@Override
public void changed(Item item, ItemState state) {
System.out.println("-----" + state);
}
});
// 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())));
}
}
}
写入数据
package com.skytech.opc;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.AccessBase;
import org.openscada.opc.lib.da.DataCallback;
import org.openscada.opc.lib.da.Group;
import org.openscada.opc.lib.da.Item;
import org.openscada.opc.lib.da.ItemState;
import org.openscada.opc.lib.da.Server;
import org.openscada.opc.lib.da.SyncAccess;
public class UtgardTutorialWrite {
public static void main(String[] args) throws Exception {
// 连接信息
final ConnectionInformation ci = new ConnectionInformation();
ci.setHost("127.0.0.1");
ci.setDomain("");
ci.setUser("OPCUser");
ci.setPassword("123456");
// MatrikonOPC Server
ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");
final String itemId = "uu.u";//项的名字按实际
// KEPServer
//ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
//final String itemId = "u.u.u";// 项的名字按实际
// final String itemId = "通道 1.设备 1.标记 1";
// 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, new DataCallback() {
@Override
public void changed(Item item, ItemState state) {
// also dump value
try {
if (state.getValue().getType() == JIVariant.VT_UI4) {
System.out.println("<<< " + state + " / value = " + state.getValue().getObjectAsUnsigned().getValue());
} else {
System.out.println("<<< " + state + " / value = " + state.getValue().getObject());
}
} catch (JIException e) {
e.printStackTrace();
}
}
});
// Add a new group
final Group group = server.addGroup("test");
// Add a new item to the group
final Item item = group.addItem(itemId);
// start reading
access.bind();
// add a thread for writing a value every 3 seconds
//写入线程,至于为什么要用线程,要一直写,,如果只写一次就不用写线程了
ScheduledExecutorService writeThread = Executors.newSingleThreadScheduledExecutor();
writeThread.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
final JIVariant value = new JIVariant("24");//一直写入24
try {
System.out.println(">>> " + "writing value " + "24");
item.write(value);
} catch (JIException e) {
e.printStackTrace();
}
}
}, 5, 3, TimeUnit.SECONDS);
// wait a little bit
Thread.sleep(20 * 1000);
writeThread.shutdownNow();
// stop reading
access.unbind();
} catch (final JIException e) {
System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));
}
}
}