java opc client 开源Utgard学习及踩坑

巫马令
2023-12-01

最近的项目中需要运用到opc client。需要从opc server中获取数据,或修改参数。主要运用的语言为java。

准备知识: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协议不兼容,客户端没法通用。

com:Component Object Model对象组件模型,是微软定义的一套软件的二进制接口,可以实现跨编程语言的进程间通信,进而实现复用。

dcom: Microsoft Distributed Component Object Model,网络传输数据的COM协议,客户端也可以通过互联网分布在各个角落。

 

然后准备开工,一番查找后,发现存在两个开源类库

1.JEasyOPC Client

2.Utgard

JEasy是java调用动态连接库,底层用的是jni,dll库比较老。

Utgard则是纯java编写,就是不支持opc 3.0协议。

JEasy在调用的时候,jni总是报错,无力解决。

最终使用了Utgard的库,在其官网上学习使用方法http://openscada.org/projects/utgard/,最终还是跑起来了,其中opc服务端的配置,一般工程人员会配置好,不需要操心dcom配置之类的问题。最终项目是maven配置完成。最终运行的时候,发现一个group死活发现不了,跟我说是新增加的,但是opc 服务端却没有重新配置,导致无法发现,最后还是研究了手册,自己手动配置完成了。

关键API示例

  • 列举某Server下的所有OPC连接
ServerList serverList = new ServerList("10.1.5.123", "freud",
		"password", "");

Collection<ClassDetails> classDetails = serverList
		.listServersWithDetails(new Category[] {
				Categories.OPCDAServer10, Categories.OPCDAServer20,
				Categories.OPCDAServer30 }, new Category[] {});

for (ClassDetails cds : classDetails) {
	System.out.println(cds.getProgId() + "=" + cds.getDescription());
}
  • 列举连接下的所有Group和Item
public static void main(String[] args) throws Exception {
	ConnectionInformation ci = new ConnectionInformation();
	ci.setHost("10.1.5.123");
	ci.setDomain("");
	ci.setUser("freud");
	ci.setPassword("password");
	ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

	Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

	server.connect();

	dumpTree(server.getTreeBrowser().browse(), 0);
	dumpFlat(server.getFlatBrowser());

	server.disconnect();
}

private static void dumpFlat(final FlatBrowser browser)
		throws IllegalArgumentException, UnknownHostException, JIException {
	for (String name : browser.browse()) {
		System.out.println(name);
	}
}

private static void dumpTree(final Branch branch, final int level) {

	for (final Leaf leaf : branch.getLeaves()) {
		dumpLeaf(leaf, level);
	}
	for (final Branch subBranch : branch.getBranches()) {
		dumpBranch(subBranch, level);
		dumpTree(subBranch, level + 1);
	}
}

private static String printTab(int level) {
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < level; i++) {
		sb.append("\t");
	}
	return sb.toString();
}

private static void dumpLeaf(final Leaf leaf, final int level) {
	System.out.println(printTab(level) + "Leaf: " + leaf.getName() + ":"
			+ leaf.getItemId());
}

private static void dumpBranch(final Branch branch, final int level) {
	System.out.println(printTab(level) + "Branch: " + branch.getName());
}
  • Item的同步查询
public static void main(String[] args) throws Exception {

	ConnectionInformation ci = new ConnectionInformation();
	ci.setHost("10.1.5.123");
	ci.setDomain("");
	ci.setUser("freud");
	ci.setPassword("password");
	ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

	Server server = new Server(ci,
			Executors.newSingleThreadScheduledExecutor());

	server.connect();

	Group group = server.addGroup();
	Item item = group.addItem("Random.Real5");

	Map<String, Item> items = group.addItems("Random.Real1",
			"Random.Real2", "Random.Real3", "Random.Real4");

	dumpItem(item);

	for (Entry<String, Item> temp : items.entrySet()) {
		dumpItem(temp.getValue());
	}

	server.dispose();
}

private static void dumpItem(Item item) throws JIException {
	System.out.println("[" + (++count) + "],ItemName:[" + item.getId()
			+ "],value:" + item.read(false).getValue());
}

private static int count;
  • Item的异步查询
private static final int PERIOD = 100;

private static final int SLEEP = 2000;

public static void main(String[] args) throws Exception {

	ConnectionInformation ci = new ConnectionInformation();
	ci.setHost("10.1.5.123");
	ci.setDomain("");
	ci.setUser("freud");
	ci.setPassword("password");
	ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

	Server server = new Server(ci,
			Executors.newSingleThreadScheduledExecutor());

	server.connect();

	AccessBase access = new SyncAccess(server, PERIOD);

	access.addItem("Random.Real5", new DataCallback() {
		private int i;

		public void changed(Item item, ItemState itemstate) {
			System.out.println("[" + (++i) + "],ItemName:[" + item.getId()
					+ "],value:" + itemstate.getValue());
		}
	});

	access.bind();
	Thread.sleep(SLEEP);
	access.unbind();
	server.dispose();
}
  • Item的发布订阅查询
private static final int PERIOD = 100;

private static final int SLEEP = 2000;

public static void main(String[] args) throws Exception {

	ConnectionInformation ci = new ConnectionInformation();
	ci.setHost("10.1.5.123");
	ci.setDomain("");
	ci.setUser("freud");
	ci.setPassword("password");
	ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

	Server server = new Server(ci,
			Executors.newSingleThreadScheduledExecutor());

	server.connect();

	AccessBase access = new Async20Access(server, PERIOD, false);

	access.addItem("Random.Real5", new DataCallback() {

		private int count;

		public void changed(Item item, ItemState itemstate) {
			System.out.println("[" + (++count) + "],ItemName:["
					+ item.getId() + "],value:" + itemstate.getValue());
		}
	});

	access.bind();
	Thread.sleep(SLEEP);
	access.unbind();
	server.dispose();
}
  • 自动重连Item异步读取
private static final int PERIOD = 100;

private static final int SLEEP = 2000;

public static void main(String[] args) throws Exception {

	ConnectionInformation ci = new ConnectionInformation();
	ci.setHost("10.1.5.123");
	ci.setDomain("");
	ci.setUser("freud");
	ci.setPassword("password");
	ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

	Server server = new Server(ci,
			Executors.newSingleThreadScheduledExecutor());

	AutoReconnectController controller = new AutoReconnectController(server);

	controller.connect();

	AccessBase access = new SyncAccess(server, PERIOD);

	access.addItem("Random.Real5", new DataCallback() {
		private int i;

		public void changed(Item item, ItemState itemstate) {
			System.out.println("[" + (++i) + "],ItemName:[" + item.getId()
					+ "],value:" + itemstate.getValue());
		}
	});

	access.bind();
	Thread.sleep(SLEEP);
	access.unbind();
	controller.disconnect();
}
  • Item同步写入
public static void main(String[] args) throws Exception {

	ConnectionInformation ci = new ConnectionInformation();
	ci.setHost("10.1.5.123");
	ci.setDomain("");
	ci.setUser("freud");
	ci.setPassword("password");
	ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

	Server server = new Server(ci,
			Executors.newSingleThreadScheduledExecutor());

	server.connect();

	Group group = server.addGroup();
	Item item = group.addItem("Square Waves.Real4");

	final Float[] integerData = new Float[] { 1202f, 1203f, 1204f };
	final JIArray array = new JIArray(integerData, false);
	final JIVariant value = new JIVariant(array);

	item.write(value);
	Thread.sleep(2000);

	dumpItem(item);

	server.dispose();

}

private static void dumpItem(Item item) throws JIException {
	System.out.println("[" + (++count) + "],ItemName:[" + item.getId()
			+ "],value:" + item.read(true).getValue());
}

private static int count;
  • Item异步写入

 

参考资料

Matrikon官网: http://www.matrikonopc.cn/downloads/drivers-index.aspx

 

 

 类似资料: