Java连接OPC-DA服务器

Cantabile 发布于 2025-11-17 2035 次阅读


前言

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 - 西门子WinCC
  • Matrikon.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;
        }
    }
}

连接管理

在实际应用中,还需要考虑:

  1. 连接池管理 - 复用连接,避免频繁创建
  2. 自动重连 - 连接断开时自动重连
  3. 异常处理 - 完善的错误处理和日志记录
  4. 资源清理 - 确保正确释放资源

常见问题

连接失败

问题: Class not registered 或 连接被拒绝

  • 确保OPC-DA服务器已安装并运行
  • 检查服务器IP地址和端口
  • 验证用户名和密码
  • 检查DCOM配置和防火墙设置

权限问题

问题: Access is denied

  • 确保运行用户有访问OPC服务器的权限
  • 检查DCOM权限配置
  • 可能需要以管理员身份运行应用

标签读取失败

问题: 标签添加失败或读取返回null

  • 验证标签名称是否正确
  • 检查标签是否存在
  • 确认标签访问权限
  • 尝试使用同步读取方式

类型转换错误

问题: 类型转换异常

  • 先检查JIVariant的类型码
  • 使用对应的类型转换方法
  • 处理数组和特殊类型

参考资料

Home Page - OPC Foundation

Maven Repository: org.openscada.utgard

WinCC V8.0:组态和通信

DCOM配置手册