目录
什么是Utgard?
如果你发现了Utgard,那么你可能知道它是干什么的,但对于不熟悉的人来说,这里有一个简短的介绍。
OPC诞生以前,硬件的驱动器和与其连接的应用程序之间的接口并没有统一的标准。例如,在FA(FactoryAutomation)——工厂自动化领域,连接PLC(Programmable Logic Controller)等控制设备和SCADA/HMI软件,需要不同的FA网络系统构成。根据某调查结果,在控制系统软件开发的所需费用中,各种各样机器的应用程序设计占费用的7成,而开发机器设备间的连接接口则占了3成。此外,在PA(Process Automation)——过程自动化领域,当希望把分布式控制系统(DCS——Distributed Control System)中所有的过程数据传送到生产管理系统时,必须按照各个供应厂商的各个机种开发特定的接口,例如,利用C语言DLL(动态链路数据库)连接DDE(动态数据交换)服务器或者利用FTP(文件传送协定)的文本等设计应用程序。如由4种控制设备和与其连接的监视、趋势图以及表报3种应用程序所构成的系统时,必须花费大量时间去开发分别对应设备A,B,C,D的监视,趋势图以及表报应用程序的接口软件共计要用12种驱动器。同时由于系统中共存各种各样的驱动器,也使维护运转环境的稳定性和信赖性更加困难。
而OPC是为了不同供应厂商的设备和应用程序之间的软件接口标准化,使其间的数据交换更加简单化的目的而提出的。作为结果,从而可以向用户提供不依靠于特定开发语言和开发环境的可以自由组合使用的过程控制软件组件产品……
Utgard用于快速支持java连接OPC 服务器。
Utgard只支持OPC DA 2.0标准的客户端部分,不支持OPC UA标准(越来越流行)。
在本教程中,使用的OPC服务器为 TOP Server,也可以使用其它OPC Server,如:KepServerEX。
先提条件
- 安装OPC服务器的Windows电脑(注意,由于Windows操作系统的限制,Windows的家庭版可能无法使用)。
- 已经安装jdk,版本不低于6
- 已经安装Eclipse,版本不低于3.5,其它编辑器亦可
配置 TOP Server
TOP Server很简单,与正常的windows程序一样,没什么特别的,棘手的部分实际上是DCOM的配置,在TOP Server的网站上有一个全面的指南,其中解释了如何配置,如果你完成了所有的步骤,DCOM配置应该可以工作,至少在我们的测试设置中是这样。如果不能,请联系你的本地系统管理员或OPC Guru来帮助你解决。问题可能来自许多原因(防火墙、路由器、权限...)。
重要的是,请尝试从不同的计算机访问服务器,以确保通信确实是通过DCOM而不是通过本地COM调用。你可以使用一个OPC客户端访问,这里我们使用Matrikon OPC Explorer。
即使没有配置额外的项目,你也应该能够访问TOP Server的内部项目,如截图中所示。设置安全性可能很棘手,在这个例子中,我必须在客户机上创建相同的用户,然后用这个用户启动OPC Explorer,才能访问服务器。
这已经足够开始第一步了,从一个现有的项目中获取更新。
读取值
正如我们在上面一节中看到的,已经有一些项目可用,我们想使用一个永久更新的项目,所以在我们的例子中,我们将使用标签: "_System._Time_Second"
首先需要下载Utgard所需要的库, 下载页面 , 需要下载以下文件: Utgard binaries (plain zip), jinterop binaries (plain zip) 与 External binaries (plain zip).
启动Eclipse,创建一个新的项目,把下面的lib放到lib文件夹中,并把这些添加到构建路径中。
- slf4j.api_1.6.4.jar (自行下载;openscada在整个项目中使用slf4j,这也是jinterop分叉的原因之一)
- ch.qos.logback.classic_1.0.0.jar (自行下载;slf4j需要一个实现,你可以使用一个不同的实现,比如log4j;openscada使用logback,所以我们在本教程中也使用它)
- ch.qos.logback.core_1.0.0.jar (自行下载;slf4j需要一个实现,你可以使用一个不同的实现,比如log4j;openscada使用logback,所以我们在本教程中也使用它。)
- org.openscada.external.jcifs_1.2.25.201303051448.jar (自行下载;是对jinterop的依赖)
- org.openscada.jinterop.core_2.0.8.201303051454.jar (来自jinterop;为java提供dcom访问的库)
- org.openscada.jinterop.deps_1.0.0.201303051454.jar (来自jinterop;jinterop的一些依赖性)
- org.openscada.opc.dcom_1.0.0.201303051455.jar (来自utgard;OPC特定的DCOM东西)
- org.openscada.opc.lib_1.0.0.201303051455.jar(来自utgard;实际的Utgard api)
到目前为止,你的Eclipse应该看起来有点像这样:
之后,我们创建一个名为UtgardTutorial1的java文件,内容如下:
package org.openscada.opc.tutorial;
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;
public class UtgardTutorial1 {
public static void main(String[] args) throws Exception {
// create connection information
final ConnectionInformation ci = new ConnectionInformation();
ci.setHost("your host");
ci.setDomain("");
ci.setUser("your user");
ci.setPassword("your password");
ci.setProgId("SWToolbox.TOPServer.V5");
// ci.setClsid("680DFBF7-C92D-484D-84BE-06DC3DECCD68"); // if ProgId is not working, try it using the Clsid instead
final String itemId = "_System._Time_Second";
// 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())));
}
}
}
这基本上是OPCTest2的一个稍加修改的变体,代码
基本上就是这样了。输出应该是这样的:
... snip ...
Mai 15, 2013 11:51:34 AM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 11:51:34 AM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
Value: [[org.jinterop.dcom.core.JIUnsignedInteger@7731802b]], Timestamp: Mi Mai 15 11:51:33 MESZ 2013, Quality: 192, ErrorCode: 00000000
Mai 15, 2013 11:51:34 AM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 11:51:34 AM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
Value: [[org.jinterop.dcom.core.JIUnsignedInteger@f4e7d3d]], Timestamp: Mi Mai 15 11:51:34 MESZ 2013, Quality: 192, ErrorCode: 00000000
Mai 15, 2013 11:51:35 AM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 11:51:35 AM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
Value: [[org.jinterop.dcom.core.JIUnsignedInteger@549ad97c]], Timestamp: Mi Mai 15 11:51:34 MESZ 2013, Quality: 192, ErrorCode: 00000000
... snip ...
这些例子也有一些代码。
写入值
为此,我们需要在TOP Server中配置一个真正可写的标签。因此,在TOP服务器配置中,点击 "新通道",选择 "模拟器 "作为类型,保留名称 "Channel1",继续点击 "下一步",直到最后点击 "完成"。
现在,点击 "新设备"。同时保留名称 "Device1",并点击到向导的最后。
现在我们可以创建标签。点击新标签,将其称为 "Tag1",地址为K0000,类型为 "DWord",客户端访问为 "读/写"。
之后,窗口应该看起来像这样。
现在我们要对代码进行一些修改,我们改进了输出,添加了一个组,将项目放入其中,并添加一个线程,每3秒写一个值。
package org.openscada.opc.tutorial;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
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 UtgardTutorial2 {
public static void main(String[] args) throws Exception {
// create connection information
final ConnectionInformation ci = new ConnectionInformation();
ci.setHost("your host");
ci.setDomain("");
ci.setUser("your user");
ci.setPassword("your password");
ci.setProgId("SWToolbox.TOPServer.V5");
// ci.setClsid("680DFBF7-C92D-484D-84BE-06DC3DECCD68"); // if ProgId is not working, try it using the Clsid instead
final String itemId = "Channel1.Device1.Tag1";
// 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();
final AtomicInteger i = new AtomicInteger(0);
writeThread.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
final JIVariant value = new JIVariant(i.incrementAndGet());
try {
System.out.println(">>> " + "writing value " + i.get());
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())));
}
}
}
输出应该看起来像这样。
Mai 15, 2013 4:31:30 PM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 4:31:30 PM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
<<< Value: [[org.jinterop.dcom.core.JIUnsignedInteger@6f14021e]], Timestamp: Mi Mai 15 16:31:30 MESZ 2013, Quality: 192, ErrorCode: 00000000 / value = 0
Mai 15, 2013 4:31:30 PM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 4:31:30 PM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
<<< Value: [[org.jinterop.dcom.core.JIUnsignedInteger@15de3027]], Timestamp: Mi Mai 15 16:31:30 MESZ 2013, Quality: 192, ErrorCode: 00000000 / value = 0
>>> writing value 1
Mai 15, 2013 4:31:31 PM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 4:31:31 PM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
Mai 15, 2013 4:31:31 PM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 4:31:31 PM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
<<< Value: [[org.jinterop.dcom.core.JIUnsignedInteger@69d5ee81]], Timestamp: Mi Mai 15 16:31:30 MESZ 2013, Quality: 192, ErrorCode: 00000000 / value = 0
Mai 15, 2013 4:31:31 PM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 4:31:31 PM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
<<< Value: [[org.jinterop.dcom.core.JIUnsignedInteger@2a4c6a7d]], Timestamp: Mi Mai 15 16:31:31 MESZ 2013, Quality: 192, ErrorCode: 00000000 / value = 1
Mai 15, 2013 4:31:32 PM rpc.DefaultConnection processOutgoing
INFO:
Sending REQUEST
Mai 15, 2013 4:31:32 PM rpc.DefaultConnection processIncoming
INFO:
Recieved RESPONSE
<<< Value: [[org.jinterop.dcom.core.JIUnsignedInteger@17f99aa6]], Timestamp: Mi Mai 15 16:31:31 MESZ 2013, Quality: 192, ErrorCode: 00000000 / value = 1
就这样,几乎是这样。
不幸的是,这在很多情况下是不够的。在这个例子中,没有错误处理,而这是非常需要的。在openSCADA项目的 "OPC Driver "中的代码,有更完备的例子。