OPC介绍
OPC:是工业控制和生产自动化领域中使用的硬件和软件的接口标准,以便有效的在应用和过程设备之间读写数据。
OPC服务对象:服务器对象(Server),项对象(Item),组对象(Group)
OPC标准采用C/S模式,OPC服务器负责向OPC客户端不断提供数据
Java和PLC之间通信
在OPCServer上设置地址变量,不同的Client去读写这些变量的值,实现不同的Client之间的通信。
OPC分层结构
OPC对象中最上层的对象是OPC服务器。一个OPC服务器可以设置一个以上的OPC组,OPC组是可以进行某种目的的数据访问的多个OPC标签集合。有了OPC组,OPC应用程序从同时需要的数据为一批进行数据访问,也可以从OPC组为单位启动或停止数据访问。此外OPC组还提供组内任何OPC标签的数值变化时向OPC应用程序通知的数据变化事件。
-
通道 Channel:从PC到一个或多个外部设备之间的传播媒介,一个通道可以代表一个串行端口。
-
设备Device:代表了与服务器进行通信的PLC或其他硬件,受限于Channel的设备驱动程序
-
标签Tag:一个tag代表与服务器进行通话的PLC或其他硬件设备上的一个地址,服务器允许动态标签(客户端自定义创建)和用户定义的静态标签(服务端管理人员创建的标签)。动态标签是直接进入OPC客户端和指定设备存取数据;静态标签在服务器被创建的且支持标签拓展,可以从OPC客户端浏览,支持标签浏览。
配置OPC和DCOM
使用java实现OPC时需要配置用户、DCOM和防火墙等,C/C++不用。
系统要Win10专业版或企业版,家庭版不行。
配置OPC
实现代码
导入依赖
<dependencies>
<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.61</version>
</dependency>
</dependencies>
主方法
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
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.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 UtgardTutorial01 {
public static void main(String[] args) throws Exception {
// 连接信息
final ConnectionInformation ci = new ConnectionInformation();
ci.setHost("192.168.1.205"); // KEPServer服务器所在IP
ci.setDomain(""); // 域 为空
ci.setUser("OPCUser");
ci.setPassword("123456");
// 使用KEPServer的配置
ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // KEPServer的注册表ID,可以在“组件服务”中查到
// final String itemId = "Channel01.Device01.TAG1"; //
// KEPServer上配置的项的名字,没有实际PLC,用的模拟器:simulator
// final String itemId = "通道1.设备1.标记1";
final List<String> itemList = new ArrayList<>();
itemList.add("Channel01.Device02.TAG1");
//itemList.add("Channel01.Device02.TAG2");
itemList.add("Channel01.Device02.TAG3");
itemList.add("Channel01.Device02.TAG4");
itemList.add("Channel01.Device02.TAG5");
itemList.add("Channel01.Device02.TAG6");
itemList.add("Channel01.Device02.TAG7");
// 启动服务
final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
try {
// 连接到服务
server.connect();
// 启动一个同步的access用来读取地址上的值,线程池每XXms读值一次
final AccessBase access = new SyncAccess(server, 5 * 1000);
for (String itemId : itemList) {
// 回调函数,就是读到值后执行这个打印,用匿名类写
access.addItem(itemId, new 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("监控项的详细位置:" + itemId);
System.out.println("监控项的详细信息:" + itemState);
switch (type) {
case JIVariant.VT_I2 :// 如果读到short类型
short s = 0;
try {
s = itemState.getValue().getObjectAsShort();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("short类型值:" + s);
System.out.println("-------------");
break;
case JIVariant.VT_BSTR :// 如果读到String类型值
JIString value = null;
try {
value = itemState.getValue().getObjectAsString();
} catch (JIException e) {
e.printStackTrace();
}
String str = value.getString();
System.out.println("String类型值:" + str);
System.out.println("----------------");
break;
case JIVariant.VT_R4 :// 如果读到float类型值
float f = 0.0f;
try {
f = itemState.getValue().getObjectAsFloat();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("float值:" + f);
System.out.println("----------");
break;
case JIVariant.VT_I4 : // 读到int类型
int i = 0;
try {
i = itemState.getValue().getObjectAsInt();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("int值:"+i);
System.out.println("--------");
break;
case JIVariant.VT_R8 : // 读到double类型
double d = 0.0;
try {
d = itemState.getValue().getObjectAsDouble();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("double值:"+d);
System.out.println("-----------");
break;
case JIVariant.VT_I8 : // 读到long类型
long l = 0;
try {
l = itemState.getValue().getObjectAsLong();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("long值:"+l);
System.out.println("---------");
break;
case JIVariant.VT_BOOL : // 读到boolean类型
boolean b = false;
try {
b = itemState.getValue().getObjectAsBoolean();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("boolean值:"+b);
System.out.println("------------");
break;
case JIVariant.VT_UI2: // 读到word类型
Number n = null;
try {
n = itemState.getValue().getObjectAsUnsigned().getValue();
} catch (JIException e) {
e.printStackTrace();
}
System.out.println("word值:" + n);
System.out.println("---------");
break;
default :
break;
}
}
});
}
// 死循环 一直读下去
while (true) {
// 开始读值
access.bind();
// 10s延迟
// Thread.sleep(10*1000);
// 停止读值
// access.unbind();
}
} catch (final JIException e) {
System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));
}
}
}
运行结果
监控项的数据类型:5
监控项的详细位置:Channel01.Device02.TAG4
监控项的详细信息:Value: [[21.03]], Timestamp: 星期五 四月 02 15:21:31 CST 2021, Quality: 192, ErrorCode: 00000000
double值:21.03
-----------
监控项的数据类型:2
监控项的详细位置:Channel01.Device02.TAG1
监控项的详细信息:Value: [[555]], Timestamp: 星期五 四月 02 15:21:31 CST 2021, Quality: 192, ErrorCode: 00000000
short类型值:555
-------------
监控项的数据类型:3
监控项的详细位置:Channel01.Device02.TAG5
监控项的详细信息:Value: [[4578]], Timestamp: 星期五 四月 02 15:21:31 CST 2021, Quality: 192, ErrorCode: 00000000
int值:4578
--------
监控项的数据类型:4
监控项的详细位置:Channel01.Device02.TAG3
监控项的详细信息:Value: [[6.88]], Timestamp: 星期五 四月 02 15:21:31 CST 2021, Quality: 192, ErrorCode: 00000000
float值:6.88
----------
监控项的数据类型:8
监控项的详细位置:Channel01.Device02.TAG7
监控项的详细信息:Value: [[[Type: 1 , [world]]]], Timestamp: 星期五 四月 02 15:21:31 CST 2021, Quality: 192, ErrorCode: 00000000
String类型值:world
----------------
监控项的数据类型:11
监控项的详细位置:Channel01.Device02.TAG6
监控项的详细信息:Value: [[true]], Timestamp: 星期五 四月 02 15:21:31 CST 2021, Quality: 192, ErrorCode: 00000000
boolean值:true
------------
JIVariant类对应数据类型
JIVariant类中将数据类型以int形式表示
数据类型 | JIVariant属性 | 表示数值 |
---|---|---|
short | VT_I2 | 2 |
int | VT_I4 | 3 |
long | VT_I8 | 20 |
float | VT_R4 | 4 |
double | VT_R8 | 5 |
String | VT_BSTR | 8 |
Date | VT_DATE | 7 |
boolean | VT_BOOL | 11 |
2-byte unsigned integer (KEPServer中word类型) | VT_UI2 | 18 |