【应用】OPC 通讯协议

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

OPC 通讯协议

OPC 通讯协议基础

OPC 简介

OPC 全称 OLE For Process Control即用于控制过程的 OLE是一个工业标准管理该标准的国际组织是 OPC 基金会。

OPC 出现的目的是为不同的供应商设备与应用程序之间的接口标准化从而使其间的数据交换更加简单因此使我们可以开发不依靠于特定开发语言和开发环境的、可以自由组合的过程控制软件。

利用驱动器的系统连接
在这里插入图片描述
利用 OPC 控制的系统组成
在这里插入图片描述

OPC 的分层结构

OPC 对象中最上层的对象是 OPC 服务器一个 OPC 服务器中可以设置一个以上的 OPC 组。OPC 服务器常对应于某种特定的控制设备如 DCS 以及 PLC 等。

OPC 组是可以进行数据访问的多个 OPC 标签的集合OPC 应用程序可以将需要的数据分组进行批量读取也可以以组为单位启动或者停止数据访问。此外OPC 组还提供组内 OPC 标签数据变化时向 OPC 应用程序通知的事件。

在这里插入图片描述

OPC 与 OPC UA

OPC DA 与 OPC UA 都是 OPC 协议的标准

  • OPC 是一种通过微软 COM/DCOM 技术来实现自动化控制的协定采用 C/S 架构。开发人员只需要按照 OPC 的标准编写 OPC-Client 访问 OPC-Server 进行读写操作即可实现与硬件设备的通信。OPC 的协定中包括

    • DA (Data Access)访问数据的主要规范

    • A&E (Alarm and Event)基于事件提供 Client 端订阅事件触发后 Server 主动提交数据

    • HDA (History Data Access)历史数据访问

  • OPC UA 是 OPC 协议的新版其不再依赖于 COM/DCOM 技术这意味着其具有跨平台性不再局限于 Windows 系统。OPC UA 提供了可靠的通信机制接口简单一致。

举例说明两者之间的区别

对传统的三种不同类型OPC服务器的访问数据访问 DA、报警和事件 AE、历史数据访问 HDA要获得一个温度传感器的当前值、一个高温度事件和温度的历史平均值要依次使用不同的命令执行

而使用 OPC UA仅用一个组件就非常容易地完成了。配置和工程的时间也因此可以大大缩短。

OPC 逻辑对象模型

包括类对象OPC Server 对象、OPC Group 对象、OPC Item 对象每类对象都包括一系列接口。

OPC Server 对象

主要功能

  • 创建和管理 OPC Group 对象

  • 管理服务器内部的状态信息

OPC Group 对象

主要功能

  • 创建和管理 OPC Item 对象

  • 管理 OPC Group 对象的内部状态信息

  • OPC Server 内部实时数据的读写服务

属性

  • name组名由客户端自定义

  • active组的激活状态若为 false 则组失效无法对服务器进行读写

  • update rate更新速率该值应大于服务器设定的最小值

  • percent data band数据死区

注意

  • Group 分为公共组和私有组公共组对所有连接到服务器的客户端都有效而私有组仅对建立该组的客户端有效

OPC Item 对象

主要功能

  • 用以描述实时数据代表了与服务器数据源的连接

属性

  • name项名在服务器中对应 Item ID

  • active项的激活状态

  • value项的数据值

  • quality项的品质代表数值的可信度

  • timestamp时间戳代表数据的存取事件

注意

  • Item 的存储类型为 VARIANTItem 的数据类型为 VARTYPE

  • 一个项不能被 OPC 客户端直接访问因为 OPC 协议中没有对应于项的 COM 接口对项的访问必须通过 OPC Group 实现

  • Item 在服务端的定义对应于硬件的实际地址。客户端连接到服务器后创建并添加 OPC Group并创建一系列的 OPC Item将逻辑上等价的一组 OPC Item 添加到 OPC Group 中即可通过组对象对数据进行读写操作

OPC 通信方式

  • 同步通信OPC Client 对 OPC Server 进行读取操作时OPC Client 必须等到 OPC Server 完成对应操作后才能返回在此期间 OPC Client 处于一直等待的状态。

  • 异步通信OPC Client 对 OPC Server 进行读取操作时OPC Client 发送请求后立即返回不用等待 OPC Server当 OPC Server 完成操作后再通知 OPC Client 程序。

  • 订阅需要 OPC Server 支持OPC A&E规范由 OPC Client 设定数据的变化限度如果数据源的实时数据变化超过了该限度OPC Server 通过回调返回数据给OPC Client。

Java 实现 OPC 的方式

OPC Client 开发大致流程

  1. COM 组件初始化
  2. 创建服务器 Server 对象
  3. 创建组 Group 对象
  4. 创建项 Item 对象
  5. 添加 Item 到 Group 中
  6. 添加 Group 到 Server 对象中
  7. 连接服务器完成相应操作
  8. COM 组件关闭

OPC DA

Java 关于 OPC DA 的开源库只有 Utgard 和 Jeasyopc两者区别如下

UtgardJeasyopc
Linux支持不支持
Windows支持不支持
用户名和密码需要不需要
组查询支持不支持
压力测试(单线程同步)略快 7W 点约 4224ms略慢 7W 点约 22540ms
DCOM通过 DCOM 实现需进行配置不需要配置
开源库现状作者删库跑路只支持 32 位系统

OPC UA

推荐使用 Eclipse 的 milo 开源库

Java 实现 OPC-client

本测试使用的 OPC-Server 软件为 KEPServerEX6具体下载与使用参考博客OPCServer使用KEPServer

OPC-DA

因为开源库 Jeasyopc 不支持 windows 和 linux 系统且只支持 32 位系统因此此处使用 Utgard 库实现。本测试采用虚拟机实现使用的系统为 Windows 10 专业版版本号 1903。

Utgard 开源库通过 DCOM 技术实现因此首先需要配置 DCOM参考博客OPC和DCOM配置

引入相应的依赖

        <dependency>
            <groupId>org.kohsuke.jinterop</groupId>
            <artifactId>j-interop</artifactId>
            <version>2.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.openscada.utgard</groupId>
            <artifactId>org.openscada.opc.lib</artifactId>
            <version>1.5.0</version>
        </dependency>

从 OPC-Server 读取数据

public class OPCRead {

    public static void main(String[] args) {
        // 配置连接信息
        final ConnectionInformation ci = new ConnectionInformation();
        ci.setHost("localhost");         // 本机IP
        ci.setDomain("");                // 域为空就行
        ci.setUser("OPCServer");         // 用户名
        ci.setPassword("OPCServer");     // 密码

        // 配置 KEPServer
        ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // KEPServer 的注册表ID可以在“组件服务”里看到
        final String itemId = "通道 1.设备 1.标记 2";    // KEPServer 上配置的项的名字

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

        try {
            // 连接服务器
            server.connect();
            // 创建 Group用于对 Item 的访问
            final Group group = server.addGroup("test");
            // 将要访问的 Item 加入创建的 Group
            final Item item = group.addItem(itemId);
            // 读取 Item 状态
            ItemState itemState = item.read(true);
            // 获取 Item 的数据类型该类型使用常量定义见 JIVariant 类
            int type = 0;
            try {
                type = itemState.getValue().getType(); // 类型实际是数字用常量定义的
            } catch (JIException e) {
                e.printStackTrace();
            }
            // 打印 Item 相应状态
            System.out.println(">>>监控项的数据类型是" + type);
            System.out.println(">>>监控项的时间戳是" + itemState.getTimestamp().getTime());
            System.out.println(">>>监控项的详细信息是" + itemState);
            // 若读到是 short 类型对应数字 2
            if (type == JIVariant.VT_I2) {
                short value = 0;
                try {
                    value = itemState.getValue().getObjectAsShort();
                } catch (JIException e) {
                    e.printStackTrace();
                }
                System.out.println(">>>short类型值 " + value);
            }
            // 删除 Group
            server.removeGroup(group, true);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

从 OPC-Server 写入数据

public class OPCWrite {

    public static void main(String[] args) {
        // 配置连接信息
        final ConnectionInformation ci = new ConnectionInformation();
        ci.setHost("localhost");         // 本机IP
        ci.setDomain("");                // 域为空就行
        ci.setUser("OPCServer");         // 用户名
        ci.setPassword("OPCServer");     // 密码

        // 配置 KEPServer
        ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // KEPServer 的注册表ID可以在“组件服务”里看到
        final String itemId = "通道 1.设备 1.标记 2";    // KEPServer 上配置的项的名字

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

        try {
            // 连接服务器
            server.connect();
            // 创建 Group用于对 Item 的访问
            final Group group = server.addGroup("test");
            // 将要访问的 Item 加入创建的 Group
            final Item item = group.addItem(itemId);
            // 写入前打印 Item 状态及对应数据
            printRead(item);
            // 写入数据
            item.write(new JIVariant("100"));
            // 写入后打印 Item 状态及对应数据
            printRead(item);
            // 删除 Group
            server.removeGroup(group, true);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取 Item 状态、数据
     * @param item item
     * @throws JIException
     */
    public static void printRead(Item item) throws JIException {
        // 读取 Item 状态
        ItemState itemState = item.read(true);
        int type = 0;
        try {
            type = itemState.getValue().getType(); // 类型实际是数字用常量定义的
        } catch (JIException e) {
            e.printStackTrace();
        }
        // 打印 Item 相应状态
        System.out.println(">>>监控项的数据类型是" + type);
        System.out.println(">>>监控项的时间戳是" + itemState.getTimestamp().getTime());
        System.out.println(">>>监控项的详细信息是" + itemState);
        // 若读到是 short 类型对应数字 2
        if (type == JIVariant.VT_I2) {
            short value = 0;
            try {
                value = itemState.getValue().getObjectAsShort();
            } catch (JIException e) {
                e.printStackTrace();
            }
            System.out.println(">>>short类型值 " + value);
        }
    }

}

OPC-UA

OPC-UA 是目前比较流行的协议采用开源库 milo 实现引入相应依赖

        <dependency>
            <groupId>org.eclipse.milo</groupId>
            <artifactId>sdk-client</artifactId>
            <version>0.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.milo</groupId>
            <artifactId>sdk-server</artifactId>
            <version>0.6.3</version>
        </dependency>

使用 opc-ua 实现数据读写

public class OpcUaDemo {

    public static void main(String[] args) throws Exception {
        // 创建OPC UA客户端
        OpcUaClient opcUaClient = createClient();

        // 开启连接
        opcUaClient.connect().get();

        // 遍历节点
        browseNode(opcUaClient, null);

        // 读
        readNode(opcUaClient);

        // 写
        writeNodeValue(opcUaClient);
        readNode(opcUaClient);

        // 关闭连接
        opcUaClient.disconnect().get();
    }

    /**
     * 创建 opc-ua 客户端
     * @return OpcUaClient
     * @throws Exception
     */
    private static OpcUaClient createClient() throws Exception {
        String endPointUrl = "opc.tcp://127.0.0.1:49320";
        Path securityTmpdir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
        Files.createDirectories(securityTmpdir);
        if (!Files.exists(securityTmpdir)) {
            throw new Exception("unable to create security dir: " + securityTmpdir);
        }
        return OpcUaClient.create(endPointUrl,
                endpoints ->
                        endpoints.stream()
                                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                                .findFirst(),
                configBuilder ->
                        configBuilder
                                .setApplicationName(LocalizedText.english("KEPServerEX/UA Client Driver"))
                                .setApplicationUri("urn:Thinkbook-ZQF:Kepware.KEPServerEX.V6:UA%20Client%20Driver")
                                //访问方式
                                .setIdentityProvider(new AnonymousProvider())
                                .setRequestTimeout(UInteger.valueOf(5000))
                                .build()
        );
    }

    /**
     * 遍历树形节点
     * @param client 客户端
     * @param uaNode 节点
     * @throws Exception
     */
    private static void browseNode(OpcUaClient client, UaNode uaNode) throws Exception {
        List<? extends UaNode> nodes;
        if (uaNode == null) {
            nodes = client.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
        } else {
            nodes = client.getAddressSpace().browseNodes(uaNode);
        }
        for (UaNode nd : nodes) {
            //排除系统行性节点这些系统性节点名称一般都是以"_"开头
            if (Objects.requireNonNull(nd.getBrowseName().getName()).contains("_")) {
                continue;
            }
            System.out.println("Node= " + nd.getBrowseName().getName());
            browseNode(client, nd);
        }
    }

    /**
     * 读取节点数据
     *
     * @param client OPC UA客户端
     * @throws Exception
     */
    private static void readNode(OpcUaClient client) throws Exception {
        int namespaceIndex = 2;
        String identifier = "通道 1.设备 1.标记 1";
        //节点
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        //读取节点数据
        DataValue value = client.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
        //标识符
        System.out.println(identifier + ": " + String.valueOf(value.getValue().getValue()));
    }

    /**
     * 写入节点数据
     *
     * @param client
     * @throws Exception
     */
    private static void writeNodeValue(OpcUaClient client) throws Exception {
        //节点
        NodeId nodeId = new NodeId(2, "通道 1.设备 1.标记 1");
        short i = 3;
        //创建数据对象,此处的数据对象一定要定义类型不然会出现类型错误导致无法写入
        DataValue nowValue = new DataValue(new Variant(i), null, null);
        //写入节点数据
        StatusCode statusCode = client.writeValue(nodeId, nowValue).join();
        System.out.println("结果" + statusCode.isGood());
    }

}

模拟数据进行代码测试

OPC-DA 代码验证

使用 KEPServerEX 6 作为 OPC-Server 进行测试百度网盘下载链接如下

链接https://pan.baidu.com/s/1pigppR62xTsE_4ecXx9m8Q?pwd=3aig 
提取码3aig

在界面上可以右键单击标记名称进行数据格式的设置

在这里插入图片描述
单击工具栏最后一个创建一个 OPC Quick client可以观察到此时通道 1.设备 1.标记 2的值为 0

在这里插入图片描述
修改 opc-da 写入程序写入数值为 300

在这里插入图片描述
执行写入程序查看 client 中的对应值写入成功

在这里插入图片描述
使用读取程序对数据进行读取同样可以读取到对应的数值

在这里插入图片描述

OPC-UA 代码验证

在验证之前首先右键项目选择属性修改 OPC-UA 属性中的”允许匿名登录“为是

在这里插入图片描述

配置写入数据的方法将对应的数值从 300 修改为 3

在这里插入图片描述

执行代码查看输出结果

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6