前言
由于工作对接工业协议比较多,平时采集少量数据都是采用的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两种
Utgard | Jeasyopc | |
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