前言
OPC(OLE for Process Control)是工业自动化领域广泛使用的通信标准。OPC-DA(Data Access)是OPC规范中最常用的数据访问接口,用于实时读取和写入工业设备的数据。本文将介绍如何使用Java连接OPC-DA服务器,并采集OPC-DA服务器上的数据。
什么是OPC-DA
OPC-DA(OPC Data Access)是OPC基金会制定的数据访问规范,它基于Microsoft的COM/DCOM技术,允许客户端应用程序访问OPC服务器中的数据。OPC-DA服务器通常运行在Windows系统上,提供实时数据访问能力。
应用场景
- 工业数据采集
- SCADA系统集成
- 生产监控系统
- 数据分析和报表系统
技术栈
由于OPC-DA基于COM/DCOM技术,而Java本身不直接支持COM,因此需要使用第三方库来实现连接。
本示例使用OpenSCADA Utgard库,它提供了完整的OPC-DA客户端实现。
系统环境
- 操作系统: Windows(COM/DCOM需要Windows环境)
- Java版本: Java 17或更高版本
- 构建工具: Maven 3.6+
Maven依赖配置
在pom.xml中添加以下依赖:
<!-- OpenSCADA OPC-DA 依赖 -->
<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.70</version>
</dependency>
连接信息配置
首先需要创建连接信息对象,包含服务器地址、用户名、密码等:
import org.openscada.opc.lib.common.ConnectionInformation;
ConnectionInformation ci = new ConnectionInformation();
ci.setHost("192.168.1.100"); // OPC服务器IP地址
ci.setDomain(""); // 域(通常为空)
ci.setUser("Administrator"); // 用户名
ci.setPassword("password"); // 密码
服务器标识(CLSID/ProgID)
OPC-DA服务器通过CLSID(Class ID)或ProgID(Programmatic ID)来标识。有两种方式设置:
方式一:直接使用CLSID
// 默认OPC-DA服务器CLSID
ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");
方式二:通过ProgID解析CLSID(推荐)
import org.openscada.opc.lib.list.ServerList;
// 通过ProgID解析CLSID
ServerList serverList = new ServerList(host, username, password, "");
String clsid = serverList.getClsIdFromProgId("OPCServer.WinCC.1");
ci.setClsid(clsid);
常用OPC-DA服务器ProgID:
OPCServer.WinCC.1- 西门子WinCCMatrikon.OPC.Simulation.1- Matrikon模拟服务器KEPServerEX.V5- KEPServerEX
建立连接
创建Server对象并建立连接:
import org.openscada.opc.lib.da.Server;
import java.util.concurrent.Executors;
// 创建服务器对象(需要提供线程池)
Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
// 建立连接
server.connect();
创建OPC组
OPC-DA使用组(Group)来组织标签项(Item):
import org.openscada.opc.lib.da.Group;
// 创建组
Group group = server.addGroup("MyGroup");
group.setActive(true); // 激活组
读取标签值
添加标签项并读取值:
import org.openscada.opc.lib.da.Item;
import org.openscada.opc.lib.da.ItemState;
import org.jinterop.dcom.core.JIVariant;
// 添加标签项
Item item = group.addItem("TagName");
// 读取值(异步读取)
ItemState state = item.read(true);
// 或同步读取
ItemState state = item.read(false);
// 获取值
JIVariant variant = state.getValue();
Object value = variant.getObject();
// 获取质量码和时间戳
int quality = state.getQuality();
Date timestamp = state.getTimestamp();
类型转换
返回的数据类型是JIVariant,需要根据实际类型进行转换:
private Object extractValue(JIVariant variant) {
int type = variant.getType();
// 根据类型转换
if (type == JIVariant.VT_R4) {
return variant.getObjectAsFloat();
} else if (type == JIVariant.VT_R8) {
return variant.getObjectAsDouble();
} else if (type == JIVariant.VT_I4) {
return variant.getObjectAsInt();
} else if (type == JIVariant.VT_BOOL) {
return variant.getObjectAsBoolean();
} else if (type == JIVariant.VT_BSTR) {
return variant.getObjectAsString2();
}
// ... 其他类型处理
return variant.getObject();
}
浏览标签
获取服务器上所有可用的标签:
import org.openscada.opc.lib.da.browser.FlatBrowser;
FlatBrowser browser = server.getFlatBrowser();
Collection<String> items = browser.browse();
// 遍历所有标签
for (String tagName : items) {
System.out.println("Tag: " + tagName);
}
断开连接
使用完毕后,需要正确断开连接:
// 移除标签项
group.removeItem(item.getId());
// 移除组
server.removeGroup(group, true);
// 断开服务器连接
server.disconnect();
示例代码
以下是一个完整的连接和读取示例:
package com.example.opcda;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.*;
import org.openscada.opc.lib.da.browser.FlatBrowser;
import org.openscada.opc.lib.list.ServerList;
import org.jinterop.dcom.core.JIVariant;
import java.util.Collection;
import java.util.concurrent.Executors;
public class OpcDaClient {
private Server server;
private Group group;
/**
* 连接到OPC-DA服务器
*/
public boolean connect(String host, String username, String password, String progId) {
try {
// 创建连接信息
ConnectionInformation ci = new ConnectionInformation();
ci.setHost(host);
ci.setDomain("");
ci.setUser(username);
ci.setPassword(password);
// 通过ProgID解析CLSID
ServerList serverList = new ServerList(host, username, password, "");
String clsid = serverList.getClsIdFromProgId(progId);
ci.setClsid(clsid);
// 创建服务器并连接
server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
server.connect();
// 创建组
group = server.addGroup("DefaultGroup");
group.setActive(true);
System.out.println("成功连接到OPC-DA服务器: " + host);
return true;
} catch (Exception e) {
System.err.println("连接失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 读取标签值
*/
public Object readTag(String tagName) {
try {
Item item = group.addItem(tagName);
ItemState state = item.read(true);
if (state != null) {
JIVariant variant = state.getValue();
return extractValue(variant);
}
return null;
} catch (Exception e) {
System.err.println("读取标签失败: " + e.getMessage());
return null;
}
}
/**
* 浏览所有标签
*/
public Collection<String> browseTags() {
try {
FlatBrowser browser = server.getFlatBrowser();
return browser.browse();
} catch (Exception e) {
System.err.println("浏览标签失败: " + e.getMessage());
return null;
}
}
/**
* 提取值
*/
private Object extractValue(JIVariant variant) {
try {
int type = variant.getType();
if (type == JIVariant.VT_R4) return variant.getObjectAsFloat();
if (type == JIVariant.VT_R8) return variant.getObjectAsDouble();
if (type == JIVariant.VT_I4) return variant.getObjectAsInt();
if (type == JIVariant.VT_I2) return variant.getObjectAsShort();
if (type == JIVariant.VT_BOOL) return variant.getObjectAsBoolean();
if (type == JIVariant.VT_BSTR) return variant.getObjectAsString2();
return variant.getObject();
} catch (Exception e) {
return variant.getObject();
}
}
/**
* 断开连接
*/
public void disconnect() {
try {
if (group != null && server != null) {
server.removeGroup(group, true);
}
if (server != null) {
server.disconnect();
}
} catch (Exception e) {
System.err.println("断开连接失败: " + e.getMessage());
}
}
/**
* 测试方法
*/
public static void main(String[] args) {
OpcDaClient client = new OpcDaClient();
// 连接服务器
if (client.connect("192.168.1.100", "Administrator", "password",
"OPCServer.WinCC.1")) {
// 浏览标签
Collection<String> tags = client.browseTags();
System.out.println("找到 " + tags.size() + " 个标签");
// 读取第一个标签的值
if (!tags.isEmpty()) {
String firstTag = tags.iterator().next();
Object value = client.readTag(firstTag);
System.out.println("标签: " + firstTag + ", 值: " + value);
}
// 断开连接
client.disconnect();
}
}
}
服务类设计
在实际项目中,可以将OPC-DA连接封装为服务类,便于管理和复用:
@Service
public class OpcDaService {
// 存储多个服务器连接
private final Map<String, Server> serverConnections = new ConcurrentHashMap<>();
private final Map<String, Group> serverGroups = new ConcurrentHashMap<>();
/**
* 连接到OPC服务器
*/
public boolean connectToServer(String serverName, String host,
String username, String password,
String progId) {
try {
ConnectionInformation ci = new ConnectionInformation();
ci.setHost(host);
ci.setDomain("");
ci.setUser(username);
ci.setPassword(password);
// 解析CLSID
if (progId != null && !progId.isEmpty()) {
ServerList serverList = new ServerList(host, username, password, "");
String clsid = serverList.getClsIdFromProgId(progId);
ci.setClsid(clsid);
} else {
ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");
}
Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
server.connect();
serverConnections.put(serverName, server);
Group group = server.addGroup("Group_" + serverName);
group.setActive(true);
serverGroups.put(serverName, group);
return true;
} catch (Exception e) {
log.error("连接失败: {}", e.getMessage());
return false;
}
}
/**
* 读取标签值
*/
public Map<String, Object> readTagValue(String serverName, String tagName) {
try {
Group group = serverGroups.get(serverName);
if (group == null) {
return null;
}
Item item = group.addItem(tagName);
ItemState state = item.read(true);
if (state != null) {
Map<String, Object> result = new HashMap<>();
result.put("Value", extractValue(state.getValue()));
result.put("Quality", state.getQuality());
result.put("Timestamp", state.getTimestamp());
return result;
}
return null;
} catch (Exception e) {
log.error("读取标签失败: {}", e.getMessage());
return null;
}
}
}
连接管理
在实际应用中,还需要考虑:
- 连接池管理 - 复用连接,避免频繁创建
- 自动重连 - 连接断开时自动重连
- 异常处理 - 完善的错误处理和日志记录
- 资源清理 - 确保正确释放资源
常见问题
连接失败
问题: Class not registered 或 连接被拒绝
- 确保OPC-DA服务器已安装并运行
- 检查服务器IP地址和端口
- 验证用户名和密码
- 检查DCOM配置和防火墙设置
权限问题
问题: Access is denied
- 确保运行用户有访问OPC服务器的权限
- 检查DCOM权限配置
- 可能需要以管理员身份运行应用
标签读取失败
问题: 标签添加失败或读取返回null
- 验证标签名称是否正确
- 检查标签是否存在
- 确认标签访问权限
- 尝试使用同步读取方式
类型转换错误
问题: 类型转换异常
- 先检查
JIVariant的类型码 - 使用对应的类型转换方法
- 处理数组和特殊类型
Comments NOTHING