【应用】OPC 通讯协议

news2024/11/24 18:45:35

OPC 通讯协议

  • OPC 通讯协议基础
    • OPC 简介
    • OPC 与 OPC UA
    • OPC 逻辑对象模型
    • OPC 通信方式
    • Java 实现 OPC 的方式
  • Java 实现 OPC-client
    • OPC-DA
    • OPC-UA
  • 模拟数据进行代码测试
    • OPC-DA 代码验证
    • OPC-UA 代码验证

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 逻辑对象模型

包括3类对象: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 的存储类型为 VARIANT;Item 的数据类型为 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

在这里插入图片描述

执行代码,查看输出结果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8. 发布确认高级

二八佳人体似酥&#xff0c;腰间仗剑斩愚夫。虽然不见人头落&#xff0c;暗里教君骨髓枯。 在生产环境中由于一些不明原因&#xff0c;导致 rabbitmq 重启&#xff0c;在 RabbitMQ重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&am…

PyQt - 使用多线程避免界面卡顿

PYQT作为界面程序包&#xff0c;为Pythoner快速构建界面&#xff0c;提供了便利性。特别是结合Pycharm扩展工具&#xff08;QTdesigner&#xff09;能够通过“拖拖拽拽”的方式构建简单界面。通过UIC将UI文件快速转化为PY文件&#xff0c;节省了时间。 PYQT的项目实践&#xff…

痞子衡嵌入式:我被邀请做贸泽电子与非网联合推出的《对话工程师》节目嘉宾...

《对话工程师》是「贸泽电子」赞助、「与非网」制作的一档网络节目&#xff0c;自2022年11月起&#xff0c;邀请不同技术领域的资深工程师&#xff0c;聊聊开发过程中的经验感悟&#xff0c;栏目共 10 期&#xff0c;痞子衡有幸被邀请做了第 4 期节目的嘉宾(12月5日在 「B站 - …

COVID

不信谣不传谣&#xff0c;只是自己的一点记录&#xff0c;有引用到各位大佬的内容&#xff0c;侵删&#xff0c;感谢感谢&#xff0c;对自己可以理中客&#xff0c;对别人还是尽可能主观上的友好&#xff0c;不要慷他人之慨。 准备 喉咙刺痛方面&#xff1a;柠檬&#xff08;…

Python爬虫实战,requests+openpyxl模块,爬取手机商品信息数据(附源码)

前言 今天给大家介绍的是Python爬取手机商品信息数据&#xff0c;在这里给需要的小伙伴们代码&#xff0c;并且给出一点小心得。 首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫&#xff0c;基本的是加请求头&#xff0c;但是这样的纯文本数据爬取的人会很多&…

读《冯诺伊曼传》

关于冯诺依曼几年前读了本冯诺依曼的书&#xff0c;冯诺依曼是20世纪的全才&#xff0c;原名约翰尼&#xff0c;匈牙利美籍科学家&#xff0c;被称为计算机之父和博弈论之父。计算机和博弈论都深刻改变人类的生活工作和思维方式&#xff0c;极大地促进了社会和人类文明的进步发…

CMake中add_definitions/add_compile_definitions的使用

CMake中的add_definitions命令用于在源文件的编译中添加-D定义标志&#xff0c;其格式如下&#xff1a; add_definitions(-DFOO -DBAR ...) 将当前目录中的target的定义添加到编译器命令行(compiler command line)中,无论是在调用此命令之前还是之后添加的,还是之后添加的子目…

C/C++入门004-C语言流程控制

文章目录流程控制顺序结构:选择结构if选择结构switch选择循环结构while循环do whilefor循环循环优化四大跳转break:continuegotoreturn案例参考&#xff1a;https://blog.csdn.net/weixin_44617968/article/details/117656810 流程控制 控制流程&#xff08;也称为流程控制&a…

HC-05蓝牙模块AT指令 ERROR问题

AT模式指令识别貌似有些问题,但不知道什么原因导致的&#xff0c;返回正常值后&#xff0c;一切通信正常。 查了一些资料&#xff0c;觉得下面两个最靠谱。 1.引脚电平问题 2.需要3.3v接EN引脚 等到故障再次发生&#xff0c;尝试EN接3.3V&#xff0c;数据收发一切正常。 结论&…

acwing基础课——bellman-ford

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板3——搜索与图论 - AcWing 基本思想&#xff1a; 逐遍的对图中每一个边去迭代计算起始点到其余各点的最短路径&#xff0c;执行n-1遍&#xff0c;最终得到起始点到其余各点的最短路径。&#xff08;n为连通图结点数…

CEF使用 libcef_dll_wrapper Debug版链接报错

编译后使用Release版编译链接运行正常&#xff0c;而Debug版报错如下&#xff1a; libcef_dll_wrapper.lib(cef_logging.obj) : error LNK2038: 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2”(CefAppEx.obj 中)具体原因是Debug版使用了Release版的库&…

图形化跟踪个股RPS走势,挖掘出源源不断的牛股!股票量化分析工具QTYX-V2.5.7...

概述RPS选股策略威廉欧奈尔把投资理念集中于他自创的CANSLIM选股系统&#xff0c;凭借着这个系统驰骋股票市场数十年&#xff0c;无论在牛市还是熊市&#xff0c;这个系统都是最稳定、表现最好的系统之一。CANSLIM选股系统中有一个RPS指标&#xff08;Relative Price Strength …

华为eNSP模拟器配置VRRP网关冗余

作用&#xff1a; 提供网关冗余功能&#xff0c;保证出口网关的高可用型。当网关发生故障的时候&#xff0c;能让PC快速的切换。 概念&#xff1a; 通过VRRP协议创建出一个虚拟网关&#xff0c;主用路由器down之后备用路由器能马上接替其主的位置&#xff0c;继续提供出口网…

【C/C++】斐波那契数列数列系列问题详解

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; C初阶 &#x1f319;励志卓越可以成为你努力的动力&#xff0c;…

1.JavaScript简介

**JavaScript ** 是什么&#xff1f;&#xff08;重点&#xff09; Js是一种专门为网页交互设计的客户端&#xff08;浏览器端&#xff09;的脚本语言&#xff1b; Js与html和css有相似之处&#xff0c;都在浏览器端解析&#xff1b; Js和java,c#,php等一样&#xff0c;是一…

Spring 通过 @Lazy 注解解决构造方法循环依赖问题

什么是循环依赖? 先定义两个类 Apple、Orange&#xff0c;如下所示&#xff1a; Component public class Apple{Autowiredprivate Orange orange; }Component public class Orange {Autowiredprivate Apple apple; }像这种在 Apple 里面有一个属性 Orange、Orange 中有一个属…

k8s 驱逐eviction机制源码分析

原理部分 1. 驱逐概念介绍 kubelet会定期监控node的内存&#xff0c;磁盘&#xff0c;文件系统等资源&#xff0c;当达到指定的阈值后&#xff0c;就会先尝试回收node级别的资源&#xff0c;比如当磁盘资源不足时会删除不同的image&#xff0c;如果仍然在阈值之上就会开始驱逐…

森林图

森林图 以统计指标和统计分析方法为基础&#xff0c;用数值计算绘制出的图形&#xff0c;通常是在平面直角坐标系中&#xff0c;以一条垂直的无效 线&#xff08;0或者1&#xff09;为中心&#xff0c;用平衡于x轴的多条线段描述每个组指标的中值和可信区间&#xff0c;最后一…

星空华文通过聆讯:吃《中国好声音》老本 华人文化是股东

雷递网 雷建平 12月9日星空华文控股有限公司&#xff08;简称&#xff1a;“星空华文”&#xff09;今日通过聆讯&#xff0c;准备在香港上市。星空华文的前身是上海灿星文化传媒股份有限公司&#xff08;简称“灿星文化”&#xff09;。2018年2月&#xff0c;灿星文化向上海证…

Web端H5播放RTSP

Web端H5播放RTSP一、要实现二、基础介绍1.RTSP是什么&#xff1f;2.RTSP播放测试工具VLC3.主流设备常用的RTSP格式三、方案1. webrtc-streamer2. 安装和配置环境3. 运行demo.html4.存疑5.参考了好多~一、要实现 不用萤石云等类似的平台&#xff0c;实现Web端直接显示监控视频。…