概念

1 OPC是什么

        OLE (object linking and embedding) for Process Control

        Open Platform Communications

        OPC可以被看作是工业设备之间数据交换的一种协议。最新版本的OPC使用的是UA架构(Unified Architecture),是一种跨平台的通用协议,就像英语是通用语言一样。旧版本的OPC使用了DA架构(Data Access)是不支持跨平台的。

2 OPC的特性和好处

 

 3 OPC UA 对比 OPC DA

        最基本的区别就是版本,旧版本的是DA,新版本试UA。

        DA基于OPC经典模型,它在应用层之上(DCOM),依赖且只支持WIndows操作系统,而且需要关闭防火墙。然而,UA不依赖于DCOM,因此它是支持跨平台的,例如Linux、MacOS、Windows,OPC DA只允许访问当前数据,不能生成告警和历史事件,而OPC UA在传输层之上,支持历史事件、多层次结构并且提供方法和程序(命令)等特性。

OPC DA:

OPC UA:

        OPC DA的局限性之一是安全性不足,因为在当今世界,安全是主要问题,因为系统更频繁地受到一些复杂的病毒和恶意软件的攻击,而这个安全问题在更高版本的OPC UA中得到了解决。

        如果硬件支持,优先选择UA架构。DA尽量不要用在公网里。

服务端 

1 准备

        1.1 VMwareWorkstation

                用来安装虚拟机,官方下载即可,测试环境使用的16.x版本。

        1.2 Windows 7 64位(虚拟机)

                服务端,用来部署opc服务。 选择win7是因为它够老且不会再自动更新,由于选择使用utgard,它对操作系统版本要求比较多,操作系统太新了会报错,随操作系统自动更新也会报错,例如win10的1909版本更新到21xx版本,就不能运行了。      

                win7 64位专业版镜像
                链接:https://pan.baidu.com/s/1QVYiX_fcPPwOwexIINyjHw 
                提取码:j31x 

        1.3 opc模拟器

                虚拟机上的opc服务,opc服务端。

                MatrikonOPCSimulation
                链接:https://pan.baidu.com/s/13PtADnoellrd2cAJsEz2Tw 
                提取码:c06r

        1.4 win10(如果需要)

                如果一定要使用win10,推荐1806或者1909版本,再新的就不用试了,肯定用不了。

                选择好系统之后需要关闭自动更新,关闭防火墙,特此整理了手顺:

                系统基础设置:
                        虚拟机装机时需指定一个用户,默认admin,改成Administrator
                        新系统,ping宿主机能通,宿主机ping虚拟机不通,虚拟机关掉防火墙,能ping通
                        关掉系统自动更新:services.msc -> Windows Update 禁用,恢复都改成无操作;        gpedit.msc 计算机配置 --> 管理模板 --> Windows组件--> Windows更新 --> 配置自动更新 --> 禁用
                        新增一个硬盘,系统自动重启,硬盘新建卷(用于安装应用,不安装在C盘)
                        创建FTP目录(用于和宿主机共享文件)
                        切换administrator用户,创建密码,加到DCOM组

                安装OPC软件(这里是力控):
                        右键以管理员身份运行安装文件,除了“安装资源程序”其它都正常安装,最后重启一次。
                        右键以管理员身份运行ForceControl,如果不弹出确认弹窗,表示当前用户权限是可以的
                        开发,选择启动程序,除了opcserver其它都可以关掉

                运行opc client:
                        matrikon explorer/opc client,连接PCAuto Server,连接正常。至此,opcserver安装成功并且连接正常。

                系统权限设置:
                        Administrator创建密码
                        计算机管理,Administrator加到DCOM 用户组里
                        组件管理,计算机修改属性
                        组件管理,OpcEnum修改权限
                        组件管理,opcserver修改权限

                注:如果是32位软件运行在64位系统,需要执行 mmc comexp.msc /32 打开组件管理,64位的是dcomcnfg

2 安装VMware

        根据官方指导安装即可。

3 安装Windows虚拟机

        1core、4G、20G即可。

4 Windows7虚拟机基本设置

        4.1 关闭防火墙(DA需要)

        4.2 默认用户为Administrator(例子中是admin),修改登录密码(例如:123456)

        4.3 计算机 --> 右键属性 --> 远程设置

         4.4 使用物理机执行远程连接,用admin/123456登录成功即可

        

5 Windows7系统备份

        为防止系统配置错误,建议对已配置好的虚拟机进行系统备份。本地是把分配的系统文件夹直接压缩了一份,恢复时直接删掉现有的文件夹,再解压出来。(如果有更好的解决方案欢迎交流)。

6 安装MatrikonOPCSimulation

        把安装包直接复制到虚拟机,默认安装即可。

7 组件服务相关配置

        7.1 配置虚拟机登录权限

                打开计算机管理,添加或修改用户到Dcom用户组。

                打开组件服务程序

        右键我的电脑

        点击第一个编辑限制,点击添加,输入admin,确定。         

         把admin的远程访问勾上,确定。

         重复上述步骤,把几个都点了。

        7.4 OPCEnum配置

这个配置权限不对,会报class未注册的异常。

 

 

         7.3 DCOM服务配置

这里以力控为例,其它同理。如果配置错误,会导致报连接失败的相关错误。

 

 

 

 ↓ 这是matrikon的服务名称

        和上面一个套路,把admin加进去并给上权限。

客户端

1 Windows 10 64 客户端

        本地开发环境

2 Java运行环境

        jdk8

3 代码及配置项

        3.1 maven依赖

<!--utgard -->
        <dependency>
            <groupId>org.openscada.external</groupId>
            <artifactId>org.openscada.external.jcifs</artifactId>
            <version>1.2.25</version>
        </dependency>
        <dependency>
            <groupId>org.openscada.jinterop</groupId>
            <artifactId>org.openscada.jinterop.core</artifactId>
            <version>2.1.8</version>
        </dependency>
        <dependency>
            <groupId>org.openscada.jinterop</groupId>
            <artifactId>org.openscada.jinterop.deps</artifactId>
            <version>1.5.0</version>
        </dependency>
        <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.61</version>
        </dependency>

        3.2 同步定时读取

package com.ruoyi.opc.util;
import java.util.concurrent.Executors;

import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.core.JIString;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.AccessBase;
import org.openscada.opc.lib.da.DataCallback;
import org.openscada.opc.lib.da.Item;
import org.openscada.opc.lib.da.ItemState;
import org.openscada.opc.lib.da.Server;
import org.openscada.opc.lib.da.SyncAccess;

public class UtgardAsyncLoop {

    public static void main(String[] args) throws Exception {
        // 连接信息
        final ConnectionInformation ci = new ConnectionInformation();
        ci.setHost("192.168.222.129");         // 虚拟机ip
        ci.setDomain("");                  // 域,为空就行
        ci.setUser("admin");             // 虚拟机登录的用户名
        ci.setPassword("123456");          // 虚拟机登录密码
        ci.setClsid("F8582CF2-XXXX-11D0-XXXX-00C0F0104305"); //MatrikonOPCSimulation的注册表ID,可以在“组件服务”里看到,如果不对则需要去注册列表里找
        final String itemId = "g1.test1";    // MatrikonOPCSimulation上配置的项的名字,没有实际PLC,用的模拟器:simulator

        // 启动服务
        final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

        try {
            // 连接到服务
            server.connect();
            // add sync access, poll every 500 ms,启动一个同步的access用来读取地址上的值,线程池每500ms读值一次
            // 这个是用来循环读值的,只读一次值不用这样
            final AccessBase access = new SyncAccess(server, 500);
            // 这是个回调函数,就是读到值后执行这个打印,是用匿名类写的,当然也可以写到外面去
            access.addItem(itemId, new DataCallback() {
                @Override
                public void changed(Item item, ItemState itemState) {
                    int type = 0;
                    try {
                        type = itemState.getValue().getType(); // 类型实际是数字,用常量定义的
                    } catch (JIException e) {
                        e.printStackTrace();
                    }
                    System.out.println("监控项的数据类型是:-----" + type);
                    System.out.println("监控项的时间戳是:-----" + itemState.getTimestamp().getTime());
                    System.out.println("监控项的详细信息是:-----" + itemState);

                    // 如果读到是short类型的值
                    if (type == JIVariant.VT_I2) {
                        short n = 0;
                        try {
                            n = itemState.getValue().getObjectAsShort();
                        } catch (JIException e) {
                            e.printStackTrace();
                        }
                        System.out.println("-----short类型值: " + n);
                    }

                    // 如果读到是字符串类型的值
                    if(type == JIVariant.VT_BSTR) {  // 字符串的类型是8
                        JIString value = null;
                        try {
                            value = itemState.getValue().getObjectAsString();
                        } catch (JIException e) {
                            e.printStackTrace();
                        } // 按字符串读取
                        String str = value.getString(); // 得到字符串
                        System.out.println("-----String类型值: " + str);
                    }
                }
            });
            // start reading,开始读值
            access.bind();
            // wait a little bit,有个10秒延时
            Thread.sleep(60 * 1000);
            // stop reading,停止读取
//            access.unbind();
        } catch (final JIException e) {
            System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));
            if (null != server) {
                server.disconnect();
            }
        }
    }
}

        3.3 异步定时读取

package com.ruoyi.opc.util;

import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.core.JIString;
import org.jinterop.dcom.core.JIVariant;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.da.*;

import java.util.*;
import java.util.concurrent.Executors;

/**
 * @author renqing
 * @date 07/27/2021
 */
public class UtgardPsp {

    private static final int PERIOD = 100;

    private static final int SLEEP = 1000 * 60 * 10;

    public static void main(String[] args) throws Exception {
        // 连接信息
        final ConnectionInformation ci = new ConnectionInformation();
        ci.setHost("192.168.222.129");         // 本机IP
        ci.setDomain("");                  // 域,为空就行
        ci.setUser("admin");             // 本机上自己建好的用户名
        ci.setPassword("123456");          // 用户名的密码
        ci.setClsid("F8582CF2-XXXX-11D0-XXXX-00C0F0104305"); //注册表ID,可以在“组件服务”里看到

        Map<String, String> items = new HashMap<String, String>();
        items.put("itemId1","g1.test1");
        items.put("itemId2","g1.test2");
        items.put("itemId3","g1.test3");
        items.put("itemId4","g1.test4");

        // 启动服务
        final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

        // 连接到服务
        server.connect();
        AccessBase access = new Async20Access(server, PERIOD, true);

        // 循环一遍所有的itemid
        Set<String> keys = items.keySet();
        for (String key : keys) {
            String val = items.get(key);

            access.addItem(val, new DataCallback() {
                private int count;
                @Override
                public void changed(Item item, ItemState itemstate) {
                    System.out.println("[" + (++count) + "],ItemName:["
                            + item.getId() + "],value:" + itemstate.getValue());
                }
            });
        }

        access.bind();
//        Thread.sleep(SLEEP);
        while(true) {

        }
//        access.unbind();
//        server.dispose();
    }
}

        3.4 同步读取

 public String readSync(String itemIds, Server server) throws JIException, AddFailedException, IllegalArgumentException, UnknownHostException, NotConnectedException, DuplicateGroupException {
    	Group group = server.addGroup();
        Item item = group.addItem(itemIds);
        
        JIVariant value = item.read(false).getValue();
		return value.getObjectAsString2();
    }

        3.5 根据服务ID连接,规避使用clsid

/**
     * Create a new instance and a new DCOM session
     * @param host the host to contact
     * @param user the user to use for authentication
     * @param password the password to use for authentication
     * @param domain The domain to use for authentication
     * @throws IllegalArgumentException
     * @throws UnknownHostException
     * @throws JIException
     */
    public ServerList ( final String host, final String user, final String password, final String domain ) throws IllegalArgumentException, UnknownHostException, JIException
    {
        this ( JISession.createSession ( domain, user, password ), host );
    }

/** 使用ip、用户名、密码、域名(可以为空),创建ServerList对象,
  * 可以推出OPC服务列表,服务名称对应的进程名(ProgressId),使用ProgId可以推出clsid
  */
Collection<ClassDetails> detailsList = serverList.listServersWithDetails(
                    new Category[] { Categories.OPCDAServer10, Categories.OPCDAServer20, Categories.OPCDAServer30 }, new Category[] {});
            
            for (final ClassDetails details : detailsList) {
                System.out.println("---------------->ClsId=" + details.getClsId() + " ProgId=" + details.getProgId() + " Description="
                        + details.getDescription());
            }

serverList.getClsIdFromProgId(progId); // progId是服务名,例如:Kepware.KEPServerEX.V6

         3.6 server断线重连

// 使用自动重连控制器包装一次server
AutoReconnectController controller = new AutoReconnectController(serverInstance);
            controller.connect();

        3.7 遍历item 

FlatBrowser flatBrowser = server.getFlatBrowser();
        if (flatBrowser == null) {
            return false;
        }
 
        try {
            Collection<String> collection = flatBrowser.browse();
            return collection.containsAll(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        try {
			TreeBrowser treeBrowser = server.getTreeBrowser();
			Branch browse = treeBrowser.browse();
			Collection<Branch> branches = browse.getBranches();
			for (Branch branch : branches) {
				Collection<Leaf> leaves = branch.getLeaves();
			}
			
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (JIException e) {
			e.printStackTrace();
		}

已测试通过

        MatrikonOPC

        KEPServer 

        GRM(广州巨控)

        北京力控。读取OPC数据是成功了,但是给操作系统带来的副作用很大。网络设备、磁盘管理器等都变得不可操作,整个操作系统变得很混乱。例如:我的电脑右键属性报错、本地连接右键报错、磁盘管理器报错等,初步怀疑和力控软件有关。

测试未通过

        北京亚控(组态王)。理论上是可以的,后面没有精力再试了。

异常处理

        异常及错误码

                1、未安装
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]
org.jinterop.dcom.common.JIException: The system cannot find the file specified. Is the COM Server present ? Please check DCOMCNFG for it's listing. [0x80070002]

                2、用户名/密码写错
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]

                3、服务ip写错
org.jinterop.dcom.common.JIException: An internal error occurred. [0x8001FFFF]

                4、写错Clsid
org.jinterop.dcom.common.JIException: 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]

                5、属性都写对,服务端配置错误
org.jinterop.dcom.common.JIException: Access is denied.  [0x80070005]

                6、opc服务崩了

org.jinterop.dcom.common.JIException: Server execution failed. [0x80080005]

                7、服务安装不正确,考虑换个版本试试

org.jinterop.dcom.common.JIException: Interface not registered. [0x80040155]

                8、windows版本太高,出现在win10及以上操作系统中,win --> 设置 --> 系统 --> 关于,查看版本,类似2xxx的都不行,最高1909。类似这种都不行,需要回退版本。

org.jinterop.dcom.common.JIException: Message not found for errorCode: 0x80010111

               9、------- 汇总

0x8000401A
The server process could not be started because the configured identity is incorrect. Check the username and password.

0x80004005
Unspecified Error

0x800706BA
RPC_S_SERVER_UNAVAILABLE
The RPC server is unavailable

0x800706BF
远程过程调用失败且未执行

0x8007000E
E_OUTOFMEMORY
Not enough memory to complete the requested operation. This can happen any time the server needs to allocate memory to complete the requested operation.

0x80070776
Couldn’t create connection with advise sink,
Reason for error: There is a problem resolving the computer name.
Solution: This problem can be solved by specifying the IP address of the server instead of specifying the computer name

0x80070005
0x80070005 appears in the OPC Client application when it succeeds in launching an OPC Server or OpcEnum, but fails to receive a reply from either of the applications. This error could be caused under several conditions: On the OPC Server PC, the OPC Client User Account does not have the right Access Control List (ACL) permissions in the System-Wide DCOM settings, Access Permissions, Edit Default.
On the OPC Client PC, the OPC Server User Account does not have the right Access Control List (ACL) permissions in the System-Wide DCOM settings, Access Permissions, Edit Limits.

On the OPC Client PC, the DCOM Default Impersonation Level is set to “Anonymous” instead of “Identify”, and the “ANONYMOUS LOGON” Access Control Entry (ACE) does not exist in the OPC Client PC, Access Control List (ACL) permissions in the System-Wide DCOM settings, Access Permissions, Edit Limits.

Failure to obtain a CLSID
OPC Client application failed to find the OPC Server. The two most common causes are:

  1. Failure to find the OPC server in the Windows Registry
  2. Failure to connect to OPCENUM.EXE

Failed to connect to 2.0 data access interface for group … Falling back to 1.0 interface
可能原因:计算机重名或局域网DNS服务故障,或者在不同子网中
 

0x8001FFFF
设备连接数量超过OPCserver支持的连接量,需要重启电脑即可


0x80010108 The object invoked has
disconnected from its clients.
Re-initialize your OPC Server Connection.
0x80040004 There is no connection for
this connection ID
0x80040005 Need to run the object to
perform this operation
0x80040007 Uninitialized object
0x80040154 Class not registered The OPC Server, or a component needed to
make the OPC connection is not registered
with Windows. This may mean that you simply
need to register a DLL or OCX file.
0x80040155 Interface not registered The OPC Server does not support the
interface that you are trying to connect to.
Examples may include Item Browsing,
Asynchronous I/O or OPC DA v2.x or 3.x
interfaces etc.
0x800401f3 Invalid class string The GUID/CLSID of the specified OPC Server
is not valid.
0x80040200 • Unable to
impersonate DCOM
Client
• Unknown OLE status
code
DCOM security problem, typically on the
Client side. This error typically occurs when
trying to specify a callback address for
Asynchronous I/O.
0x80040202 Cannot Connect Error typically occurs when a call is made to
Advise on the connection point. This often
means that OPCPROXY.DLL is not the same
version on your different computers.
0x80070002 The system cannot find the
file specified.
Re-install your software.
0x80070005 Access is denied. You need to configure your DCOM Security
settings. See our DCOM Tutorial:
http://www.softwaretoolbox.com/dcom Page 6 of 7
Hex Code Description Resolution
0x80070057 The parameter is incorrect. The OPC Server has rejected your request,
indicating that the parameter(s) you specified
are not valid for the type of request being
made. You will need more details on the
actual OPC calls being made between the
Client and Server.
0x8007041d The service did not respond
to the start or control request
in a timely fashion.
Specific to Windows Services. The service did
not start within the allowed time-frame. This
indicates an initialization problem with the
Windows service.
0x800705b4 This operation returned
because the timeout period
expired.
This is a timeout. You may need to increase
your timeout settings.
0x800706ea A floating-point underflow
occurred at the RPC server.
0x80070725 Incompatible version of the
RPC stub.
0x80080005 Server execution failed There is a problem with the OPC Server
preventing it from being started by Windows.
This may be the result of file-permissions,
DCOM Security permissions, or a lack of
resources.
0x80004002 No such interface supported The OPC Server does not support the
interface that you are trying to connect to.
Examples may include Item Browsing,
Asynchronous I/O or OPC DA v2.x or 3.x
interfaces etc.
0x80004005 Unspecified error The most common message seen, that yields
the least information. In these cases you often
need to check the event-logs at your OPC
Server for more information.
0x8000401a The server process could not
be started because the
configured identity is
incorrect. Check the
username and password.
DCOM Configuration permissions. Modify the
identity that the application should run under,
perhaps specify a named account or choose
“Interactive User’.
0x800706ba The RPC server is
unavailable.
The OPC Server could not be contacted. This
is usually the result of a firewall blocking the
application

                10、OPCServer:使用Matrikon OPC Server Simulation - ioufev - 博客园使用Matrikon OPC Server Simulationhttps://www.cnblogs.com/ioufev/p/9366426.html查看用法

总结

1、如果不清楚用户组针对哪些服务权限,就不操作用户组。给所需要的服务权限控制上直接加用户就可以了。

2、经测试,客户端的用户名和服务端的一样都是admin,但是密码不同,依然可以调用opc服务端。

3、连接失败的原因,基本都由于服务端的配置(包括授权配置、网络配置),或者程序中参数写错(或找错)导致。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐