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

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

目录

  • 前言
  • 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>() {@Overrideprotected 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 {@Overridepublic 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);}@Overridepublic 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));}@Overridepublic 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 forPlcReadRequest.Builder builder = plcConnection.readRequestBuilder();// 一次性读取几个寄存器里面的内容int count = 8;// 这里面的起始地址为实际为 32,传递参数时候加1int 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.ModbusTagHandlerbuilder.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 requestPlcReadResponse response = readRequest.execute().get();// Handle the response// 创建了一个写请求,尝试将地址1的线圈设置为truefor (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.rhkb.cn/news/367795.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式Linux之Uboot简介和移植

uboot简介 uboot 的全称是 Universal Boot Loader&#xff0c;uboot 是一个遵循 GPL 协议的开源软件&#xff0c;uboot是一个裸机代码&#xff0c;可以看作是一个裸机综合例程。现在的 uboot 已经支持液晶屏、网络、USB 等高级功能。 也就是说&#xff0c;可以在没有系统的情况…

苹果手机收不到短信怎么恢复?90%的人都在这么做

在使用苹果手机的过程中&#xff0c;有时候会遇到无法接收短信的问题。这不仅影响正常的沟通&#xff0c;还可能错过重要的通知和验证码。那么&#xff0c;手机收不到短信怎么恢复呢&#xff1f;别担心&#xff0c;90%的人都在使用这些简单而有效的方法来解决这一问题。 本文将…

Halcon支持向量机

一 支持向量机 1 支持向量机介绍&#xff1a; 支持向量机(Support Vector Machine&#xff0c;SVM)是Corinna Cortes和Vapnik于1995年首先提出的&#xff0c;它在解决小样本、非线性及高维模式识别表现出许多特有的优势。 2 支持向量机原理: 在n维空间中找到一个分类超平面…

14 卡尔曼滤波及代码实现

文章目录 14 卡尔曼滤波及代码实现14.0 基本概念14.1 公式推导14.2 代码实现 14 卡尔曼滤波及代码实现 14.0 基本概念 卡尔曼滤波是一种利用线性系统状态方程&#xff0c;通过系统输入输出观测数据&#xff0c;对系统状态进行最优估计的算法。由于观测数据包括系统中的噪声和…

React Native V0.74 — 稳定版已发布

嗨,React Native开发者们, React Native 世界中令人兴奋的消息是,V0.74刚刚在几天前发布,有超过 1600 次提交。亮点如下: Yoga 3.0New Architecture: Bridgeless by DefaultNew Architecture: Batched onLayout UpdatesYarn 3 for New Projects让我们深入了解每一个新亮点…

移动智能终端数据安全管理方案

随着信息技术的飞速发展&#xff0c;移动设备已成为企业日常运营不可或缺的工具。特别是随着智能手机和平板电脑等移动设备的普及&#xff0c;这些设备存储了大量的个人和敏感数据&#xff0c;如银行信息、电子邮件等。员工通过智能手机和平板电脑访问企业资源&#xff0c;提高…

【vue3】【vant】 移动端中国传统文化和民间传说案例

更多项目点击&#x1f446;&#x1f446;&#x1f446;完整项目成品专栏 【vue3】【vant】 移动端中国传统文化和民间传说案例 获取源码方式项目说明&#xff1a;其中功能包括项目包含&#xff1a;项目运行环境运行截图和视频 获取源码方式 加Q群&#xff1a;632562109项目说…

Linux_管道通信

目录 一、匿名管道 1、介绍进程间通信 2、理解管道 3、管道通信 4、用户角度看匿名管道 5、内核角度看匿名管道 6、代码实现匿名管道 6.1 创建子进程 6.2 实现通信 7、匿名管道阻塞情况 8、匿名管道的读写原子性 二、命名管道 1、命名管道 1.1 命名管道通信 …

源代码层面分析Appium-inspector工作原理

Appium-inspector功能 Appium Inspector 基于 Appium 框架&#xff0c;Appium 是一个开源工具&#xff0c;用于自动化移动应用&#xff08;iOS 和 Android&#xff09;和桌面应用&#xff08;Windows 和 Mac&#xff09;。Appium 采用了客户端-服务器架构&#xff0c;允许用户通…

C++初学者指南-3.自定义类型(第一部分)-异常

C初学者指南-3.自定义类型(第一部分)-异常 文章目录 C初学者指南-3.自定义类型(第一部分)-异常简介什么是异常&#xff1f;第一个示例用途:报告违反规则的行为异常的替代方案标准库异常处理 问题和保证资源泄露使用 RAII 避免内存泄漏&#xff01;析构函数&#xff1a;不要让异…

Taogogo Taocms v3.0.2 远程代码执行漏洞(CVE-2022-25578)

前言 CVE-2022-25578 是一个存在于 Taogogo Taocms v3.0.2 中的代码注入漏洞。此漏洞允许攻击者通过任意编辑 .htaccess 文件来执行代码注入。 漏洞详情 漏洞描述&#xff1a;攻击者可以利用此漏洞上传一个 .htaccess 文件到网站&#xff0c;并在文件中注入恶意代码&#xf…

CesiumJS【Basic】- #058 绘制网格填充多边形(Entity方式)-使用shader

文章目录 绘制网格填充多边形(Entity方式)-使用shader1 目标2 代码2.1 main.ts绘制网格填充多边形(Entity方式)-使用shader 1 目标 使用Entity方式绘制绘制网格填充多边形 - 使用shader 2 代码 2.1 main.ts import * as Cesium from cesium;// 创建 Cesium Viewer 实例…

主流国产服务器操作系统技术分析

主流国产服务器操作系统 信创 "信创"&#xff0c;即信息技术应用创新&#xff0c;作为科技自立自强的核心词汇&#xff0c;在我国信息化建设的进程中扮演着至关重要的角色。自2016年起步&#xff0c;2020年开始蓬勃兴起&#xff0c;信创的浪潮正席卷整个信息与通信技…

程序员AI提效案例:统计B站课程耗时情况

文章目录 一&#xff0c;时长统计需求二&#xff0c;一波三折三&#xff0c;终极方案 AIJava总结 今天为了写一篇博客&#xff0c;这篇博客介绍了B站的一个Java项目&#xff0c;这个项目分为三个阶段&#xff1a; 初级篇高级篇运维篇 一&#xff0c;时长统计需求 我想根据每个…

Spring+SpringMVC+MyBatis整合

目录 1.SSM介绍1.1 什么是SSM&#xff1f;1.2 SSM框架1.2.1 Spring1.2.2 SpringMVC1.2.3 MyBatis 2.SSM框架整合2.1 建库建表2.2 创建工程2.3 pom.xml2.4 log4j.properties2.5 db.properties2.6 applicationContext-dao.xml2.7.applicationContext-tx.xml2.8 applicationContex…

昇思25天学习打卡营第9天|静态图模式的深度剖析与应用指南

目录 背景介绍 动态图模式 静态图模式 静态图模式的使用场景 静态图模式开启方式 基于装饰器的开启方式 基于context的开启方式 静态图的语法约束 JitConfig配置选项 静态图高级编程技巧 背景介绍 AI 编译框架主要包含两种运行模式&#xff0c;即动态图模式与静态图模…

Docker(八)-Docker运行mysql8容器实例

1.运行mysql8容器实例并挂载数据卷 -e:配置环境变量 --lower_case_table_names1 设置忽略表名大小写一定要放在镜像之后运行mysql8容器实例之前&#xff0c;先查看是否存在mysql8镜像以及是否存在已运行的mysql实例docker run -d -p 3306:3306 --privilegedtrue -v 【宿主机日…

【windows】电脑如何关闭Bitlocker硬盘锁

如果你的硬盘显示这样的一把锁&#xff0c;说明开启了Bitlocker硬盘加密。 Bitlocker硬盘锁&#xff0c;可以保护硬盘被盗&#xff0c;加密防止打开查看数据。 方法一&#xff1a;进入“控制面板->BitLocker 驱动器加密”进行设置。或者“控制面板\系统和安全->BitLocke…

数据库对比脚本,java如何对比两个数据库的表字段的不同

因为有时候开发环境和 测试环境&#xff0c;有时候会有不同的数据库表&#xff0c;比如有些加字段了&#xff0c;所以这个脚本就实现了对比两个数据库连接的数据库到底哪里不一样&#xff0c;输出到控制台 package com.junfun.pms;import lombok.extern.slf4j.Slf4j;import ja…

SQL执行慢排查以及优化思路

数据库服务器的优化步骤 当我们遇到数据库调优问题的时候&#xff0c;该如何思考呢&#xff1f;我把思考的流程整理成了下面这张图。 整个流程划分成了观察&#xff08;Show status&#xff09;和行动&#xff08;Action&#xff09;两个部分。字母 S 的部分代表观察&#xf…