Java访问OPC-DA数据

公司项目中需要通过OPC-DA完成数据采集,其中踩了许多坑,记录一下

先引用一句名句,一下戳中了我这次的痛
名句.png

环境

OPC服务端

  • Windows10(1809)
  • Matrikon模拟

OPC客户端

  • macOS(15.6)、Windows10(1809)
  • Java8_391
  • Utgard 1.5

示例代码

  • pom
    如果不引入bcprov-jdk15on,有可能会报错,也可以在dependencyManagement里加上
1
<!-- DA -->
2
<!-- Utgard OPC-DA 核心库 -->
3
<dependency>
4
    <groupId>org.openscada.utgard</groupId>
5
    <artifactId>org.openscada.opc.lib</artifactId>
6
    <version>1.5.0</version>
7
</dependency>
8
 
9
<!-- Utgard DCOM 支撑库 -->
10
<dependency>
11
    <groupId>org.openscada.utgard</groupId>
12
    <artifactId>org.openscada.opc.dcom</artifactId>
13
    <version>1.5.0</version>
14
</dependency>
15
 
16
<!-- BouncyCastle,加密库(Utgard 依赖) -->
17
<dependency>
18
    <groupId>org.bouncycastle</groupId>
19
    <artifactId>bcprov-jdk15on</artifactId>
20
    <version>1.50</version>
21
</dependency>
  • java
1
public class OpcTest {
2
    public static void main(String[] args) throws Exception {
3
        ConnectionInformation ci = new ConnectionInformation();
4
        // 主机
5
        ci.setHost("127.0.0.1");
6
        // Windows 域(没有就空字符串)
7
        ci.setDomain("");
8
        // Windows用户
9
        ci.setUser("Administrator");
10
        // Windows密码
11
        ci.setPassword("系统密码");
12
        // ProgID/ClsID,推荐使用ClsID,不容易报错
13
        // ci.setProgId("Matrikon.OPC.Simulation.1");
14
        ci.setClsid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
15
        
16
        Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());
17
        server.connect();
18
19
        // 每秒刷新一次,采集数据
20
        AccessBase access = new SyncAccess(server, 1000);
21
        AccessBase access = new Async20Access(server, 1000, true);
22
        ab = new Async20Access(s, opcConfig.getPeriod(), false);
23
        
24
        access.addItem("Random.int2", (item, itemState) -> {
25
            String id = item.getId();
26
            JIVariant jiv = itemState.getValue();
27
            System.out.println("Value: " + jiv);
28
        });
29
        
30
        access.bind();
31
        Thread.sleep(10000);
32
        access.unbind();
33
        server.disconnect();
34
    }
35
}

可能遇到的问题

代码量非常非常少,就一点点,但是实际调试过程非常复杂,到处都是坑

坑1

  • 报错
1
Exception in thread "main" org.jinterop.dcom.common.JIException: Message not found for errorCode: 0xC0000001
2
	at org.jinterop.winreg.smb.JIWinRegStub.winreg_OpenHKLM(JIWinRegStub.java:121)
3
	at org.jinterop.dcom.core.JIProgId.getIdFromWinReg(JIProgId.java:145)
4
	at org.jinterop.dcom.core.JIProgId.getCorrespondingCLSID(JIProgId.java:181)
5
	at org.jinterop.dcom.core.JIComServer.<init>(JIComServer.java:483)
6
	at org.openscada.opc.lib.da.Server.connect(Server.java:114)
7
	at com.zjars.opccollection.OpcDaReader.main(OpcDaReader.java:36)
8
Caused by: jcifs.smb.SmbException
9
jcifs.util.transport.TransportException
  • 原因
    多半是使用了ProgID,会从注册表去查ClsID,但是查不到,导致J-Interop库连接失败(网上查的)
  • 解决办法
    不要用ProgID,换成ClsID,可以在DCOM属性里找到对应的值
    clsid.png

坑2

  • 报错
1
Exception in thread "main" org.jinterop.dcom.common.JIException: Access is denied, please check whether the [domain-username-password] are correct. Also, if not already done please check the GETTING STARTED and FAQ sections in readme.htm. They provide information on how to correctly configure the Windows machine for DCOM access, so as to avoid such exceptions.  [0x00000005]
2
	at org.jinterop.dcom.core.JIComServer.init(JIComServer.java:654)
3
	at org.jinterop.dcom.core.JIComServer.initialise(JIComServer.java:561)
4
	at org.jinterop.dcom.core.JIComServer.<init>(JIComServer.java:524)
5
	at org.openscada.opc.lib.da.Server.connect(Server.java:108)
6
	at com.zjars.opccollection.OpcDaReader.main(OpcDaReader.java:30)
7
Caused by: rpc.FaultException: Received fault. (unknown)
8
	at rpc.ConnectionOrientedEndpoint.call(ConnectionOrientedEndpoint.java:147)
9
	at rpc.Stub.call(Stub.java:134)
10
	at org.jinterop.dcom.core.JIComServer.init(JIComServer.java:649)
11
	... 4 more
  • 原因
    这个问题困扰了我很久,在网上疯狂搜索,ChatGPT都要被我问得不耐烦了,试过:换Windows账号、换JDK位数、检查各种DCOM权限,均无果
    最终在一篇文章里找到思路,通过Windows自带的日志发现,每次OPC发起连接时,日志中就会出现一条记录:
    坑2原因3.png
    ChatGPT是这样解释的
    坑2原因2.png
    继续查,结果查到了一篇文章
    坑2原因1.png
    这个方法试过也无济于事,最终某一篇文章的评论区找到的解决办法
  • 解决办法1
    如果是访问远程OPC服务器,可以尝试:
    在注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System中,创建一个DWORD(32位)的值
    LocalAccountTokenFilterPolicy,数据值设置为1
  • 解决办法2
    如果是访问本机OPC服务器,那只能系统降级😅,从22H2降到1809之后,终于不再报错也能访问到数据了,目前暂时未找到更佳解决方案

坑3

  • 报错
1
Caused by: org.jinterop.dcom.common.JIRuntimeException: Class not registered. If you are using a DLL/OCX , please make sure it has "DllSurrogate" flag set. Faq A(6) in readme.html. [0x80040154]
2
	at org.jinterop.dcom.core.JIRemActivation.read(JIRemActivation.java:225)
3
	at ndr.NdrObject.decode(NdrObject.java:41)
4
	at rpc.ConnectionOrientedEndpoint.call(ConnectionOrientedEndpoint.java:141)
5
	at rpc.Stub.call(Stub.java:134)
6
	at org.jinterop.dcom.core.JIComServer.init(JIComServer.java:649)
7
	... 5 more
  • 原因
    多半是ClsID配置错了
  • 解决办法
    去dcomcnfg里找到正确的ClsID

坑4

  • 报错
1
org.openscada.opc.lib.da.Server.connect(Server.java:132) - Failed to connect to server
2
org.jinterop.dcom.common.JIException: An internal error occurred. [0x8001FFFF]
3
	at org.jinterop.dcom.core.JIComServer.init(JIComServer.java:659) ~[org.openscada.jinterop.core-2.1.8.jar:?]
4
	at org.jinterop.dcom.core.JIComServer.initialise(JIComServer.java:561) ~[org.openscada.jinterop.core-2.1.8.jar:?]
5
	at org.jinterop.dcom.core.JIComServer.<init>(JIComServer.java:524) ~[org.openscada.jinterop.core-2.1.8.jar:?]
6
	at org.openscada.opc.lib.da.Server.connect(Server.java:108) [org.openscada.opc.lib-1.5.0.jar:?]
  • 原因
    网络原因
  • 解决办法
    检查网络是否打通,ping一下IP,或者用OPCClient尝试读一下数据;
    或者检查一下是不是用ProgID在连接,换成ClsID试试;

坑5

  • 报错
1
org.jinterop.dcom.common.JIException: The RPC server is unavailable. Please check if the COM server is up and running and that route to the COM Server is accessible (A simple "Ping" to the Server machine would do). Also please confirm if the Windows Firewall is not blocking DCOM access. [0x800706BA]
2
	at org.jinterop.dcom.core.JIComServer.call(JIComServer.java:1004) ~[org.openscada.jinterop.core-2.1.8.jar:?]
3
	at org.jinterop.dcom.core.JIComServer.call(JIComServer.java:951) ~[org.openscada.jinterop.core-2.1.8.jar:?]
4
	at org.jinterop.dcom.core.JIComObjectImpl.call(JIComObjectImpl.java:293) ~[org.openscada.jinterop.core-2.1.8.jar:?]
5
	at org.jinterop.dcom.core.JIComObjectImpl.call(JIComObjectImpl.java:159) ~[org.openscada.jinterop.core-2.1.8.jar:?]
6
	at org.jinterop.dcom.core.JIFrameworkHelper.attachEventHandler(JIFrameworkHelper.java:285) ~[org.openscada.jinterop.core-2.1.8.jar:?]
7
	at org.openscada.opc.dcom.da.impl.OPCGroupStateMgt.attach(OPCGroupStateMgt.java:179) ~[org.openscada.opc.dcom-1.5.0.jar:?]
8
	at org.openscada.opc.lib.da.Group.attach(Group.java:444) ~[org.openscada.opc.lib-1.5.0.jar:?]
9
	at org.openscada.opc.lib.da.Async20Access.start(Async20Access.java:58) ~[org.openscada.opc.lib-1.5.0.jar:?]
10
	at org.openscada.opc.lib.da.AccessBase.connectionStateChanged(AccessBase.java:181) [org.openscada.opc.lib-1.5.0.jar:?]
11
	at org.openscada.opc.lib.da.Server.addStateListener(Server.java:427) [org.openscada.opc.lib-1.5.0.jar:?]
12
	at org.openscada.opc.lib.da.AccessBase.bind(AccessBase.java:86) [org.openscada.opc.lib-1.5.0.jar:?]
13
	at com.zjars.opccollection.OpcClient.handlerCallback(OpcClient.java:189) [classes/:?]
14
	at com.zjars.opccollection.OpcClient.connect(OpcClient.java:104) [classes/:?]
15
	at com.zjars.opccollection.OpcCollectionApplication.main(OpcCollectionApplication.java:45) [classes/:?]
  • 原因
    原因有些复杂,似乎是因为频繁是用Async20Access订阅导致与服务端的通讯出问题?
  • 解决办法
    换成SyncAccess类访问数据

总结

  • OPC服务端Windows版本不能过高,否则会访问被拒(暂时不知如何解决)
  • OPC客户端Windows版本没有要求
  • 使用ClsID连接
如果文章对您有帮助,欢迎评论或打赏,感谢支持!