JAVA实现OPC DA遇到的常见问题及解决方法

前言

由于工作对接工业协议比较多,平时采集少量数据都是采用的modbus协议,但是最近要采集大量数据,modbus就不适用了,所以采用opc形式。期间遇到0x00000005,0x80070005,0x80040153,0x80010111等问题,避免大家踩坑,记录一下解决方法。

为什么使用OPC DA

虽然OPC DA从1.0,2.0到3.0经历过几次更新,由于其复杂的环境配置和win的局限性,da的形式也逐渐不建议使用,推出了更加简单方便跨平台的OPC UA,但是老旧的控制系统依然只支持DA这种形式,所以为了兼容过去的DCS,PLC等系统必须支持OPC DA的读写方式。

关于OPC UA

如果可以使用UA尽量不要使用DA,推荐Eclipse的milo开源库,后面我会单独出一期聊聊UA相关。

实现方式

查找了好久java的OPC DA开源库只有Utgard和Jeasyopc两种

UtgardJeasyopc
Linux支持(纯Java编写)不支持       
Windows64支持(纯Java编写)不支持
用户名密码需要不需要
组查询不支持支持
压力测试(单线程同步)略快7W点大约在4224ms略慢7W点大约在22540ms
DCOM通过DCOM实现,必须配置不需要配置
现状 作者删库跑路只支持 IA 32,不支持AMD 64

其中Jeasyopc是通过调用JCustomOpc.dll,需要配置JCustomOpc.dll文件,而且支持只32位系统。所以选用了Utgard来实现。

OPC Server模拟

参考这位大佬:OPCServer:使用KEPServer - ioufev - 博客园

DCOM配置

还是参考这位大佬:OPC和DCOM配置 - ioufev - 博客园

Maven依赖

        <dependency>
            <groupId>org.jinterop</groupId>
            <artifactId>j-interop</artifactId>
            <version>3.0.0</version>
        </dependency>

这是Utgard调用的类库,不过Utgard引用的是2.0.4版本,一定要更换成3.0.0版本,否则会有可能碰到0x80010111错误。

Utgard的类库不建议通过maven坐标方式引入,因为其中有很多bug我们需要做一些简单的修改,建议下载源码,修改完成后通过jar方式引入maven,或者直接复制到项目下。

代码实现

具体读写代码我这里就不贴了,可以参考一下上面那位大佬:Java OPC 代码 - ioufev - 博客园

我这里做一下补充

首先是获取主机上的所有OPC Server列表,可以直接获取ClsId和ProgId等信息

        ServerList serverList = new ServerList(HOST,USERNAME, PASSWORD,DOMAIN);

        Collection<ClassDetails> detailsList =
                serverList.listServersWithDetails(new Category[] {Categories.OPCDAServer10, Categories.OPCDAServer20 ,Categories.OPCDAServer30}, new Category[] {});
        for (final ClassDetails details : detailsList) {
            log.info("ClsId=" + details.getClsId() + " ProgId=" + details.getProgId() + " Description=" + details.getDescription());
        }

上文提到的大佬读取数据可以用下面这段进行解析,省的大家再去debug返回的对象结构

private static Object getVal(JIVariant var) throws JIException {
        Object value;
        int type = var.getType();
        switch (type) {
            case JIVariant.VT_I2:
                value = var.getObjectAsShort();
                break;
            case JIVariant.VT_I4:
                value = var.getObjectAsInt();
                break;
            case JIVariant.VT_I8:
                value = var.getObjectAsLong();
                break;
            case JIVariant.VT_R4:
                value = var.getObjectAsFloat();
                break;
            case JIVariant.VT_R8:
                value = var.getObjectAsDouble();
                break;
            case JIVariant.VT_BSTR:
                value = var.getObjectAsString2();
                break;
            case JIVariant.VT_BOOL:
                value = var.getObjectAsBoolean();
                break;
            case JIVariant.VT_UI2:
            case JIVariant.VT_UI4:
                value = var.getObjectAsUnsigned().getValue();
                break;
            case JIVariant.VT_EMPTY:
                throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is Empty.");
            case JIVariant.VT_NULL:
                throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is null.");
            default:
                throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Unknown Type.");
        }
        return value;
    }

获取服务器下所有ITEM列表

Collection<String> items = server.getFlatBrowser().browse();

遇到的问题

0x00000005

原因:用户校验不通过。

解决方法:检查用户名和密码,用户名不要使用全名

如果用户名和密码没有问题,那么就有可能是本地安全策略的问题,可以通过下面方法解决。

1、win+r运行  secpol.msc 

2、找到本地策略 - 安全选项 - 网络访问:本地账户的共享和安全模型

 3、修改属性为“经典:对本地账户进行身份验证,不改变其本来身份”

0x8001FFFF

解决方法:检查dcom配置是否正确,防火墙设置是否正确,按照上面大佬的步骤一步一步重新配置一下。

0x80070005

解决方法:这个就是上文提到的不建议maven方式直接引入Utgard,需要修改 org.openscada.opc.lib.da包下的Server.class,开启会话安全项,再原来connect()方法添加两处this.session.useSessionSecurity(true);

而且Utgard的通过ProgId建立连接也有问题,具体为什么获取不到连接我没仔细查找,不过也可以在这个地方做修改实现通过ProgID建立连接。

修改完成如下

public synchronized void connect() throws IllegalArgumentException, UnknownHostException, JIException, AlreadyConnectedException {
        if (isConnected()) {
            throw new AlreadyConnectedException();
        }

        final int socketTimeout = Integer.getInteger("rpc.socketTimeout", 0);
        log.debug(String.format("Socket timeout: %s ", socketTimeout));

        try {
            if (this.connectionInformation.getClsid() != null) {
                this.session = JISession.createSession(
                        this.connectionInformation.getDomain(),
                        this.connectionInformation.getUser(),
                        this.connectionInformation.getPassword());
                this.session.setGlobalSocketTimeout(socketTimeout);
                this.session.useSessionSecurity(true);
                this.comServer = new JIComServer(
                        JIClsid.valueOf(this.connectionInformation.getClsid()),
                        this.connectionInformation.getHost(), this.session);
            } else if (this.connectionInformation.getProgId() != null) {
                this.session = JISession.createSession(
                        this.connectionInformation.getDomain(),
                        this.connectionInformation.getUser(),
                        this.connectionInformation.getPassword());
                this.session.setGlobalSocketTimeout(socketTimeout);

                ServerList serverList = new ServerList(this.connectionInformation.getHost(),
                        this.connectionInformation.getUser(),
                        this.connectionInformation.getPassword(),
                        this.connectionInformation.getDomain());
                String clsIdFromProgId = serverList.getClsIdFromProgId(this.connectionInformation.getProgId());

//                this.comServer = new JIComServer(JIProgId.valueOf(this.connectionInformation.getProgId()),this.connectionInformation.getHost(), this.session);

                this.comServer = new JIComServer(
                        JIClsid.valueOf(clsIdFromProgId),
                        this.connectionInformation.getHost(), this.session);
            } else {
                throw new IllegalArgumentException("Neither clsid nor progid is valid!");
            }

            this.server = new OPCServer(this.comServer.createInstance());
            this.errorMessageResolver = new ErrorMessageResolver(
                    this.server.getCommon(), this.defaultLocaleID);
        } catch (final UnknownHostException e) {
            log.error("Unknown host when connecting to server", e);
            cleanup();
            throw e;
        } catch (final JIException e) {
            log.error("Failed to connect to server", e);
            cleanup();
            throw e;
        } catch (final Throwable e) {
            log.error("Unknown error", e);
            cleanup();
            throw new RuntimeException(e);
        }

        notifyConnectionStateChange(true);
    }

0x80040154

原因:ClsId不正确

解决方法:检查OPCServer的ClsId,或者通过上文提到的获取OPC Server列表的方式获取ClsId

0x80040153

原因:系统中有过OPCServer的注册表没有删除干净,所以通过ClsId找不到OPCServer

解决方法:这个问题困扰了我很长时间,因为注册表实在太多,没法找到并删除,而电脑里有很多东西又不想重做系统。可以通过修改org.openscada.opc.lib.list;包下的 listServersWithDetails(final Category[] implemented, final Category[] required)方法处理该异常

修改完成如下

public Collection<ClassDetails> listServersWithDetails(final Category[] implemented, final Category[] required) throws IllegalArgumentException, UnknownHostException, JIException {
        Collection<String> resultString = listServers(implemented, required);

        List<ClassDetails> result = new ArrayList<ClassDetails>(resultString.size());

        for (String clsId : resultString) {
            //TODO 注册表没清理干净会报错 ,只做了异常处理
            try {
                result.add(getDetails(clsId));
            }catch (JIException e){
                logger.error(clsId+":Message not found for errorCode: 0x80040153");
                e.printStackTrace();
            }
        }

        return result;
    }

0x80010111

原因:utgard的协议5.6,但windows 10 2004之后的协议都是5.7

解决方法:使用 j-interop 3.0

        <dependency>
            <groupId>org.jinterop</groupId>
            <artifactId>j-interop</artifactId>
            <version>3.0.0</version>
        </dependency>

 使用j-interop3.0需要再maven中添加repository如下,否则会报错找不到各种包

    <repositories>        
        <repository>
            <id>clojars</id>
            <name>Clojars</name>
            <url>https://repo.clojars.org/</url>
        </repository>
    </repositories>

Utgard源码

我自己项目里用的,已经改好了上文提到的部分,直接复制到自己项目里或者打jar就可以使用

链接:https://pan.baidu.com/s/13vcXF74gym-cvhp_myU9rw?pwd=pnjm 
提取码:pnjm

J-interop 3.0 jar

链接:https://pan.baidu.com/s/1b49Z8BvL-ADhe2YZPZVoEg?pwd=9fdp 
提取码:9fdp 

  • 16
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 47
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值