分别使用netty和apache.plc4x测试读取modbus协议的设备信号

news2024/9/19 10:53:52

记录一下常见的工业协议数据读取方法

目录

  • 前言
  • Modbus协议说明
  • Netty 读取测试
  • 使用plc4x 读取测试
  • 结束语

前言

Modbus 是一种通讯协议,用于在工业控制系统中进行数据通信和控制。Modbus 协议主要分为两种常用的变体:Modbus RTU 和 Modbus TCP/IP

  • Modbus RTU:Modbus RTU 是一种基于串行通信的协议。

  • Modbus TCP/IP:Modbus TCP/IP 是一种基于 TCP/IP 网络的协议。
    本次使用TCP协议,一般常见使用这种协议。

Modbus 协议一般工业设备例如光电信号,各类传感器和执行器等。
一些电力设备(如变压器、开关设备、仪表等)

Modbus协议说明

如果要使用netty读取modbus协议数据必须了解一下协议报文格式。

参考: https://neugates.io/docs/zh/latest/appendix/protocol/modbus_tcp.html

如果设备数量< 30个可以尝试使用 Neuro 产品读取,里面包含配置监控SDK等。

Modbus=MBAP(报文头)+PDU(帧结构)

Netty 读取测试

假设有一个光电IO模块对接了8个激光设备,激光扫描到障碍物为1 没有扫到位0,先通过厂家自带的web端管理界面查看目前的实际信号情况:
在这里插入图片描述

这边测试的Netty代码如下:


package org.example.modbus;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class ModbusClient {

    private final String host;
    private final int port;

    public ModbusClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<Channel>() {
                        @Override
                        protected void initChannel(Channel ch) {
                            ch.pipeline()
                                    .addLast(new ModbusClientHandler());
                        }
                    });

            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    private static class ModbusClientHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            // writeShort 一次写2个字节   writeByte 一次写1个字节
            ByteBuf buffer = Unpooled.buffer();

            // >>>>>>>>>>>>构造 M B A P Header(报文头)<<<<<<<<<<<<<
            // 事务标识符 占用2个字节
            // 可以解释为报文的序列号,例如测试使用的 Modbus Poll 客户端一直发送数据,
            // 所以每发送一次数据标识符就加一。服务器接收时会把这个数据原封返回。
            buffer.writeShort(1);

            // 协议类型 占用2个字节, 十六进制格式"00 00" 表示Modbus TCP 协议
            buffer.writeShort(0);

            // 长度 占用2个字节, 6 表示报文长度(后面有6个字节),包括 M B A P Header 和 PDU
            // 表示从单元标识符开始后面数据的长度。如:00 06 表示后面有 0X06 个字节长度的数据。
            buffer.writeShort(6);

            // 单元标识符 占用1个字节, 17 表示设备存储单元编号
            buffer.writeByte(17);

            // >>>>>>>>>>>>构造 PDU PDU=功能码+数据<<<<<<<<<<<<<
            // 功能码 占用1个字节, 02 表示读离散量输入
            buffer.writeByte(2);

            // 开始读的数据的地址。从 00 32 开始读数据。
            buffer.writeShort(32);

            // 读取的寄存器数量。从开始位置读 00 08 个寄存器数据。
            buffer.writeShort(8);
            ctx.writeAndFlush(buffer);
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf buffer = (ByteBuf) msg;
            byte[] response = new byte[buffer.readableBytes()];
            buffer.readBytes(response);
            System.out.println("Response: " + bytesToHex(response));
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    private static String bytesToHex(byte[] bytes) {
        // 这个 bytesToHex 方法用于将一个 byte 数组转换为十六进制格式的字符串。
        // 每个字节被转换为两个十六进制字符,并用空格分隔,
        // 最终返回一个表示十六进制表示形式的字符串
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString();
    }

    public static void main(String[] args) throws InterruptedException {
        String host = "192.168.50.41";
        int port = 28899;
        new ModbusClient(host, port).start();
    }
}

光电IO设备模块IP地址为 192.168.50.41 端口使用 28899,上面代码是使用netty 向IO模块发送读取 8个光电的离散量信号报文,地址从32开始,然后获得modbus协议的结果报文,最终获得的结果报文解析成十六进制的字符串形式如下:

Response: 00 01 00 00 00 04 11 02 01 CD 

还是建议参考上面推荐的文档,这里截取主要信息:
在这里插入图片描述
根据上面的样例说明,我们其实想要得到的结果是最后2位16进制数据 DD,占据1个字节,因为我们读取的是离散值(类似true或fase 一般是1或者0),因此我们将 CD转换为二进制数据:

1 1 0 1 1 1 0 1

从低位开始(从右 至 左) 对应厂家web管理界面中的 DI-1 DI-2 …
绿色=1 灰色=0
在这里插入图片描述
可以发现netty读取到的信号和厂家web管理界面显示的数据一致。

使用plc4x 读取测试

apache旗下工业协议适配工具库,具体文档查看官网:
链接: https://plc4x.apache.org/users/protocols/modbus.html

pom.xml文件引入maven依赖包:

    <properties>
        <plc4x.version>0.12.0</plc4x.version>
    </properties>
    
	 <dependency>
		<groupId>org.apache.plc4x</groupId>
		<artifactId>plc4j-api</artifactId>
		<version>${plc4x.version}</version>
	</dependency>

	<dependency>
		<groupId>org.apache.plc4x</groupId>
		<artifactId>plc4j-driver-modbus</artifactId>
		<version>${plc4x.version}</version>
	</dependency>

代码如下:

package cn.guzt.modbustest;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * apache PLX modbus测试用例
 *
 * @author guzt
 */
public class ModbusExample {

    protected static final Logger logger = LoggerFactory.getLogger(ModbusExample.class);

    public static void main(String[] args) {
        String ip = "192.168.50.41";
        String port = "28899";
        // 单元标识符:相当于设备的地址
        String unitIdentifier = "17";
        String timeout = "5000";
        String urlFormat = "modbus-tcp:tcp://{}:{}?unit-identifier={}&request-timeout={}";

        // try里面会自动关闭连接
        try (PlcConnection plcConnection = PlcDriverManager
                .getDefault()
                .getConnectionManager()
                .getConnection(StrUtil.format(urlFormat, ip, port, unitIdentifier, timeout))) {
            // Check if this connection support reading of data.
            if (!plcConnection.getMetadata().isReadSupported()) {
                logger.info(">>>>>>>>>>>>>>This connection doesn't support reading.");
                return;
            }
            // Check if this connection support writing of data.
            if (!plcConnection.getMetadata().isWriteSupported()) {
                logger.info(">>>>>>>>>>>>>>This connection doesn't support writing.");
                return;
            }

            if (plcConnection.isConnected()) {
                logger.info(">>>>>>>>>>>>>>Modbus已经连上..............");
            }
            // Create a new read request:
            // You will need to pass the reference you are asking for
            PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();
            // 一次性读取几个寄存器里面的内容
            int count = 8;
            // 这里面的起始地址为实际为 32,传递参数时候加1
            int startAddress = 33;
            for (int i = 0; i < count; i++) {
                // 功能码 (tagAddress) Modbus 的操作对象有四种:线圈、离散输入、输入寄存器、保持寄存器。
                // 1. 线圈:相当于开关,在 Modbus 中可读可写,数据只有 00 和 01。
                // 2. 离散量:输入位,开关量,在 Modbus 中只读。
                // 3. 输入寄存器:只能从模拟量输入端改变的寄存器,在 Modbus 中只读。
                // 4. 保持寄存器:用于输出模拟量信号的寄存器,在 Modbus 中可读可写。
                // 查看参考:https://neugates.io/docs/zh/latest/appendix/protocol/modbus_tcp.html
                // 不同功能码对应不同的地址格式:参看 org.apache.plc4x.java.modbus.base.tag.ModbusTagHandler
                builder.addTagAddress("第" + (i + 1) + "个光电信号:", "discrete-input:" + (startAddress + i));
            }

            // 这种方式一次性读取8个:builder.addTagAddress("DI-count8N", "discrete-input:33:BOOL[8]")
            PlcReadRequest readRequest = builder.build();
            logger.info(">>>>>>>>>>>>>>开始读取");

            // Execute the request
            PlcReadResponse response = readRequest.execute().get();
            // Handle the response
            // 创建了一个写请求,尝试将地址1的线圈设置为true
            for (String fieldName : response.getTagNames()) {
                if (response.getObject(fieldName) instanceof Boolean) {
                    logger.info(">>>>>>>>>>>>>>Boolean[" + fieldName + "]: " + response.getBoolean(fieldName));
                } else if (ArrayUtil.isArray(response.getObject(fieldName))) {
                    logger.info(">>>>>>>>>>>>>>Array[" + fieldName + "]: " + response.getObject(fieldName));
                } else {
                    logger.info(">>>>>>>>>>>>>>Object[" + fieldName + "]: " + response.getObject(fieldName));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行打印结果如下:

...
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第1个光电信号:]: true
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第2个光电信号:]: false
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第3个光电信号:]: true
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第4个光电信号:]: true
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第5个光电信号:]: true
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第6个光电信号:]: false
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第7个光电信号:]: true
16:20:25.378 [main] INFO cn.guzt.modbustest.ModbusExample - >>>>>>>>>>>>>>Boolean[第8个光电信号:]: true

...

从第1行至第8行记录值 对应厂家web管理界面中的 DI-1 DI-2 …
绿色=true 灰色=fase
在这里插入图片描述
可以发现plc4x读取到的信号和厂家web管理界面显示的数据一致。

结束语

读取Modbus的开源库有很多,这里列举常见的使用库,尤其是 Plc4x 这个适配了主流的工业协议,值得我们去研究。

使用netty的话对基本功要求比较高,如果对modbus工业协议包括TCP/IP协议一知半解估计应该是写不出成功案例。

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

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

相关文章

平面点云格网过程及可视化介绍(python)

1、背景介绍 实际人工构造物中&#xff0c;很多物体表面为平面结构&#xff0c;因此将点云投影在二维平面上进行处理&#xff0c;如进行点云面积计算、点云边缘提取等。 具体案例可以参考博客&#xff1a;详解基于格网法统计平面点云面积_点云格网法计算xy投影面积-CSDN博客、点…

分页处理封装+分页查询题目列表

文章目录 1.sun-club-common封装分页1.com/sunxiansheng/subject/common/eneity/PageInfo.java2.com/sunxiansheng/subject/common/eneity/PageResult.java 2.sun-club-application-controller1.SubjectInfoDTO.java 继承PageInfo并新增字段2.SubjectController.java 3.sun-clu…

8个Unity开发高手都在用的秘密技巧!

1. 不要重新发明轮子&#xff0c;使用内置的引擎工具 在使用任何引擎时&#xff0c;比如Unity或Unreal Engine&#xff0c;一些开发者&#xff0c;主要是来自计算机科学领域的开发者&#xff0c;可能会倾向于从头开始开发大型算法或结构&#xff0c;而不去了解引擎中是否已经存…

三角洲行动卡顿严重?这样快速解决三角洲行动国服卡顿问题

三角洲行动官方精心设计的游戏地图和敌人布局&#xff0c;加上“曼德尔砖”等目标导向性道具的引入&#xff0c;更是为玩家之间的竞技和争夺增添了无数的变数。每一次的争夺都如同是一场智慧与勇气的较量&#xff0c;让人热血沸腾&#xff0c;无法自拔。在这个战场上&#xff0…

免费可视化工具如何提升工作效率?

免费可视化工具能为我们的工作带来什么好处&#xff1f;在如今数据密集的工作环境中&#xff0c;如何高效地处理和展示数据成为了每个行业的重要任务。传统的工具如Excel虽然强大&#xff0c;但在处理复杂数据和创建高级图表时往往显得力不从心。而免费可视化工具的出现&#x…

Arduino - 光敏传感器

Arduino - Light Sensor Arduino - 光传感器 In this tutorial, we are going to learn: 在本教程中&#xff0c;我们将学习&#xff1a; How light sensor works 光传感器的工作原理How to connect the light sensor to Arduino 如何将光传感器连接到ArduinoHow to progra…

C#udpClient组播

一、0udpClient 控件&#xff1a; button&#xff08;打开&#xff0c;关闭&#xff0c;发送&#xff09;&#xff0c;textbox&#xff0c;richTextBox 打开UDP&#xff1a; UdpClient udp: namespace _01udpClient {public partial class Form1 : Form{public Form1(){Initi…

如何在Windows 11上设置默认麦克风和相机?这里有详细步骤

如果你的Windows 11计算机上连接了多个麦克风或网络摄像头&#xff0c;并且希望自动使用特定设备&#xff0c;而不必每次都在设置中乱动&#xff0c;则必须将首选设备设置为默认设备。我们将向你展示如何做到这一点。 如何在Windows 11上更改默认麦克风 有两种方法可以将麦克…

[游戏开发][UE5]引擎使用学习记录

C Log和蓝图Log C Log 方法 UE_Log(参数1&#xff0c;参数2&#xff0c;参数3) //举例: UE_LOG(LogTemp, Error, TEXT("Log Info: %s"),"Test Log"); 三个参数的作用 参数1&#xff1a;输出窗口归类使用&#xff0c;你写什么它就显示什么 参数2&#x…

网络安全入门必选:十款免费的抓包工具有哪些?

下面给大家推荐几款好用的免费的抓包工具软件&#xff0c;有需要的小伙伴们来了解一下。 1. Wireshark抓包分析工具 4.0.1 Wireshark是一款功能强大的网络协议分析器&#xff0c;可以实时检测和抓取网络通讯数据。它支持多种协议和媒体类型&#xff0c;并具备丰富的显示过滤…

从0-1搭建一个web项目(package.json)详解

本章分析package.json文件详解 本文主要对packge.json配置子文件详解 ObJack-Admin一款基于 Vue3.3、TypeScript、Vite3、Pinia、Element-Plus 开源的后台管理框架。在一定程度上节省您的开发效率。另外本项目还封装了一些常用组件、hooks、指令、动态路由、按钮级别权限控制等…

干货:ANR日志分析全面解析

ANR类型 出现ANR的一般有以下几种类型&#xff1a; 1:KeyDispatchTimeout&#xff08;常见&#xff09; input事件在5S内没有处理完成发生了ANR。 logcat日志关键字&#xff1a;Input event dispatching timed out 2:BroadcastTimeout 前台Broadcast&#xff1a;onReceiver在…

MYSQL 四、mysql进阶 5(InnoDB数据存储结构)

一、数据库的存储结构&#xff1a;页 索引结构给我们提供了高效的索引方式&#xff0c;不过索引信息以及数据记录都是保存在文件上的&#xff0c;确切说时存储在页结构中&#xff0c;另一方面&#xff0c;索引是在存储引擎中实现的&#xff0c;Mysql服务器上的存储引擎负责对表…

eNSP中VRRP的配置和使用

一、基础配置 1.新建拓扑图 2.配置vlan a.CORE-S1 <Huawei>system-view [Huawei]sysname CORE-S1 [CORE-S1]vlan 10 [CORE-S1-vlan10]vlan 20 [CORE-S1-vlan20]vlan 30 b.CORE-S2 <Huawei>system-view [Huawei]sysname CORE-S2 [CORE-S2]vlan 10 [CORE…

君諾外匯:为什么巴菲特现在加倍下注油气股票?油价上涨是主因吗?

近年来&#xff0c;以巴菲特为代表的一些顶级投资者开始在能源领域加大投资力度&#xff0c;特别是油气股票。这一转变引发了广泛关注&#xff0c;特别是在油价上涨的背景下。本文将Juno markets外匯深入分析巴菲特投资策略的变化原因&#xff0c;探讨其在能源市场的布局及未来…

Linux OpenGrok搭建

文章目录 一、目的二、环境三、相关概念3.1 OpenGrok3.2 CTags3.3 Tomcat 四、OpenGrok搭建4.1 安装jdk4.2 安装ctags依赖4.3 安装universal-ctags4.3.1 下载universal-ctags4.3.2 编译&&安装universal-ctags 4.4 安装Tomcat4.4.1 下载&&解压Tomcat4.4.2 启动T…

IDEA无法输入中文,怎么破

1.导航栏处&#xff0c;点击help菜单&#xff0c;选择Edit Custom VM Options.. 2.编辑文件&#xff0c;在文件末尾添加&#xff1a; -Drecreate.x11.input.methodtrue 3.保存文件即可&#xff0c;如果还是不行&#xff0c;就关闭所有Idea程序&#xff0c;重新启动Idea

Linux:进程和计划任务管理

目录 一、程序和进程 1.1、程序 1.2、进程 1.3、线程 1.4、协程 二、查看进程相关命令 2.1、ps命令&#xff08;查看静态的进程统计信息&#xff09; 第一行为列表标题&#xff0c;其中各字段的含义描述如下 2.2、top命令&#xff08;查看进程动态信息&#xff09; 2…

Session会话与请求域的区别

session会话和请求域&#xff08;也称为request域&#xff09;都是用于存储和管理用户特定信息的重要概念&#xff0c;但它们在作用范围和生命周期上有显著的不同。 请求域 (Request Domain) 作用范围&#xff1a;请求域是面向单次请求的。每次HTTP请求都会创建一个新的request…

Vue报错:Component name “xxx” should always be multi-word vue/multi-word-component

问题&#xff1a;搭建脚手架时报错&#xff0c;具体错误如下&#xff1a; ERROR in [eslint] E:\personalProject\VueProjects\vueproject2\src\components\Student.vue10:14 error Component name "Student" should always be multi-word vue/multi-word-compon…