Java实现Modbus Tcp协议读写模拟工具数据

标题

    • 前言
    • 一、读写模拟工具中数据
      • (1) 定义Controller层
      • (2) 定义Service层实现
    • 二、调试
      • (1) 读数据
      • (2) 向寄存器写单个数据
      • (3) 向寄存器写多个数据

前言

参考文章:https://www.cnblogs.com/ioufev/p/10831289.html

该文中谈及常见的几种读取设备数据实现,说到modbus4j的通讯实现方式是同步的,实际应用中可能会读取大量的数据,需要异步进行,可以用modbus-master-tcp

本文也是基于modbus-master-tcp依赖库进行开发,它底层是基于Netty写的,所以具备高性能、支持异步

SpringBoot项目配置的核心依赖

<!--Modbus Master -->
<dependency><groupId>com.digitalpetri.modbus</groupId><artifactId>modbus-master-tcp</artifactId><version>1.2.0</version>
</dependency><!--Modbus Slave -->
<dependency><groupId>com.digitalpetri.modbus</groupId><artifactId>modbus-slave-tcp</artifactId><version>1.2.0</version>
</dependency>

设备模拟工具选择Modbus Slave,网上很多下载资源

一、读写模拟工具中数据

在这里插入图片描述
一开始我也把Server和Client搞混了,大家注意下面重点知识

重点知识

Modbus一主多从讲的是一次只有一个主机(Master)连接到网络,只有主设备(Master)可以启动通信并向从设备(Slave)发送请求,从设备不能主动发送;从设备(Slave)只能向主设备(Master)发送回复,且从设备就不能自己主动发送。

一个Master(物理网平台),多个Slave(设备);平台是Client,设备是Server

实现如下

(1) 定义Controller层

@RestController
@RequestMapping("/modbus")
public class ModbusController {@Autowiredprivate ModbusTcpService modbusTcpService;// 连接slave设备@GetMapping(value = "/connect_slave")public ResponseMessage connectSlave(){return modbusTcpService.connectSlave();}// 读取保持寄存器@PostMapping(value = "/readHoldingRegisters")public ResponseMessage readHoldingRegisters(@RequestBody SlaveDto slaveDto) throws ExecutionException, InterruptedException {return modbusTcpService.readHoldingRegisters(slaveDto);}// 写单个寄存器@PostMapping(value = "/writeSingleRegister")public ResponseMessage writeSingleRegister(@RequestBody WriteSlaveDto wsDto) {return modbusTcpService.writeSingleRegister(wsDto);}// 写多个寄存器@PostMapping(value = "/writeMultipleRegisters")public ResponseMessage writeMultipleRegisters(@RequestBody WriteSlaveDto wsDto){return modbusTcpService.writeMultipleRegisters(wsDto);}}

其他相关类

SlaveDto.java

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class SlaveDto {private Integer slaveId;private Integer address;private Integer quantity;
}

WriteSlaveDto.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class WriteSlaveDto extends SlaveDto {// 写单个数值private Integer value;// 写多个数值private int[] values;
}

ResponseMessage.java就是常见的返回msg、code和data的类,随便找一个符合就行

(2) 定义Service层实现

@Service
@Slf4j
public class ModbusTcpService{@Autowiredprivate ModbusMasterUtil modbusMasterUtil;public ResponseMessage connectSlave() {try {modbusMasterUtil.createModbusConnector(ModbusClientConstants.IP, ModbusClientConstants.TCP_PORT);return ResponseMessage.ok();}catch (Exception e){log.error("connect slave fail, {}", e.getMessage());return ResponseMessage.error(e.getMessage());}}private ModbusTcpMaster checkConnectSlave() {return modbusMasterUtil.getModbusTcpMaster();}public ResponseMessage readHoldingRegisters(SlaveDto slaveDto) throws ExecutionException, InterruptedException {if (checkConnectSlave() == null) return ResponseMessage.error("not connect slave");CompletableFuture<int[]> registerFuture = modbusMasterUtil.readHoldingRegisters(slaveDto.getSlaveId(), slaveDto.getAddress(), slaveDto.getQuantity());int[] registerValues = registerFuture.get();if (registerFuture == null) return ResponseMessage.error("Read exception, please check json parameters");log.info("readHoldingRegisters info={}", Arrays.toString(registerValues));return ResponseMessage.ok(registerValues);}public ResponseMessage writeSingleRegister(WriteSlaveDto wsDto) {if (checkConnectSlave() == null) return ResponseMessage.error("not connect slave");modbusMasterUtil.writeSingleRegister(wsDto.getSlaveId(), wsDto.getAddress(), wsDto.getValue());return ResponseMessage.ok();}public ResponseMessage writeMultipleRegisters(WriteSlaveDto wsDto) {if (checkConnectSlave() == null) return ResponseMessage.error("not connect slave");if(wsDto.getValues().length != wsDto.getQuantity()) return ResponseMessage.error("quantity error");modbusMasterUtil.writeMultipleRegisters(wsDto.getSlaveId(), wsDto.getAddress(), wsDto.getQuantity(), wsDto.getValues());return ResponseMessage.ok();}
}

IP和Port就是一个常量类,写着Modbus默认的127.0.0.1和502

Modbus Tcp的工具类为ModbusMasterUtil,这来自于网上的,还挺好用

@Component
@Slf4j
public class ModbusMasterUtil {private ModbusTcpMaster modbusMaster = null;public ModbusTcpMaster getModbusTcpMaster() {return modbusMaster;}/*** 将两个int数拼接成为一个浮点数** @param highValue 高16位数值* @param lowValue  低16位数值* @return 返回拼接好的浮点数* @author huangji*/public static float concatenateFloat(int highValue, int lowValue) {int combinedValue = ((highValue << 16) | (lowValue & 0xFFFF));return Float.intBitsToFloat(combinedValue);}public static int[] floatToIntArray(float floatValue) {int combinedIntValue = Float.floatToIntBits(floatValue);int[] resultArray = new int[2];resultArray[0] = (combinedIntValue >> 16) & 0xFFFF;resultArray[1] = combinedIntValue & 0xFFFF;return resultArray;}/*** 将传入的boolean[]类型数组按位转换成byte[]类型数组** @param booleans 传入的boolean数组* @return 返回转化后的 byte[]* @author huangji*/public static byte[] booleanToByte(boolean[] booleans) {BitSet bitSet = new BitSet(booleans.length);for (int i = 0; i < booleans.length; i++) {bitSet.set(i, booleans[i]);}return bitSet.toByteArray();}/*** 将传入的int[]类型数组转换成为byte[]类型数组** @param values 传入的int[]数组* @return 返回 byte[]类型的数组* @author huangji*/public static byte[] intToByte(int[] values) {byte[] bytes = new byte[values.length * 2];for (int i = 0; i < bytes.length; i += 2) {bytes[i] = (byte) (values[i / 2] >> 8 & 0xFF);bytes[i + 1] = (byte) (values[i / 2] & 0xFF);}return bytes;}/*** 根据传入的ip地址,创建modbus连接器** @param ipAddr ip地址* @return 创建连接器,并进行连接,之后返回此连接器* @author huangji*/public CompletableFuture<ModbusTcpMaster> createModbusConnector(String ipAddr) {return createModbusConnector(ipAddr, ModbusClientConstants.TCP_PORT);}/*** 根据传入的ip地址,创建modbus连接器** @param ipAddr ip地址* @param port   端口号* @return 创建连接器,并进行连接,之后返回此连接器* @author huangji*/public CompletableFuture<ModbusTcpMaster> createModbusConnector(String ipAddr, int port) {return createModbusConnector(new ModbusNetworkAddress(ipAddr, port));}/*** 根据传入的ModbusNetworkAddress\引用,创建modbus连接器** @param modbusNetworkAddress ModbusNetworkAddress类型的实体对象引用* @return 创建连接器,并进行连接,之后返回此连接器* @author huangji*/public CompletableFuture<ModbusTcpMaster> createModbusConnector(ModbusNetworkAddress modbusNetworkAddress) {String ipAddr = modbusNetworkAddress.getIpAddr();int port = modbusNetworkAddress.getPort();if (modbusMaster == null) {ModbusTcpMasterConfig masterConfig = new ModbusTcpMasterConfig.Builder(ipAddr).setPort(port).setTimeout(Duration.parse(ModbusClientConstants.TIMEOUT_DURATION)).setPersistent(true).setLazy(false).build();modbusMaster = new ModbusTcpMaster(masterConfig);}return modbusMaster.connect();}public void setBooleanArray(short unsignedShortValue, int[] array, int index, int size) {for (int i = index; i < index + size; i++) {array[i] = (unsignedShortValue & (0x01 << (i - index))) != 0 ? 1 : 0;}}/*** 异步方法,读取modbus设备的线圈值,对应功能号01** @param slaveId  设备id* @param address  要读取的寄存器地址* @param quantity 要读取的寄存器数量* @return 返回 CompletableFuture<int[]>* @author huangji*/public CompletableFuture<int[]> readCoils(int slaveId, int address, int quantity) {CompletableFuture<ReadCoilsResponse> futureResponse = modbusMaster.sendRequest(new ReadCoilsRequest(address, quantity),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return null;} else {ByteBuf byteBuf = response.getCoilStatus();int[] values = new int[quantity];int minimum = Math.min(quantity, byteBuf.capacity() * 8);for (int i = 0; i < minimum; i += 8) {setBooleanArray(byteBuf.readUnsignedByte(), values, i, Math.min(minimum - i, 8));}ReferenceCountUtil.release(response);return values;}});}/*** 异步方法,读取modbus设备的离散输入值,对应功能号02** @param slaveId  设备id* @param address  要读取的寄存器地址* @param quantity 要读取的寄存器数量* @return 返回 CompletableFuture<int[]>* @author huangji*/public CompletableFuture<int[]> readDiscreteInputs(int slaveId, int address, int quantity) {CompletableFuture<ReadDiscreteInputsResponse> futureResponse = modbusMaster.sendRequest(new ReadDiscreteInputsRequest(address, quantity),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return null;} else {ByteBuf byteBuf = response.getInputStatus();int[] values = new int[quantity];int minimum = Math.min(quantity, byteBuf.capacity() * 8);for (int i = 0; i < minimum; i += 8) {setBooleanArray(byteBuf.readUnsignedByte(), values, i, Math.min(minimum - i, 8));}ReferenceCountUtil.release(response);return values;}});}/*** 异步方法,读取modbus设备的保持寄存器值,对应功能号03** @param slaveId  设备id* @param address  要读取的寄存器地址* @param quantity 要读取的寄存器数量* @return 返回 CompletableFuture<int[]>* @author huangji*/public CompletableFuture<int[]> readHoldingRegisters(int slaveId, int address, int quantity) {CompletableFuture<ReadHoldingRegistersResponse> futureResponse = modbusMaster.sendRequest(new ReadHoldingRegistersRequest(address, quantity),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return null;} else {ByteBuf byteBuf = response.getRegisters();int[] values = new int[quantity];for (int i = 0; i < byteBuf.capacity() / 2; i++) {values[i] = byteBuf.readUnsignedShort();}ReferenceCountUtil.release(response);return values;}});}/*** 异步方法,读取modbus设备的输入寄存器值,对应功能号04** @param slaveId  设备id* @param address  要读取的寄存器地址* @param quantity 要读取的寄存器数量* @return 返回 CompletableFuture<int[]>* @author huangji*/public CompletableFuture<int[]> readInputRegisters(int slaveId, int address, int quantity) {CompletableFuture<ReadInputRegistersResponse> futureResponse = modbusMaster.sendRequest(new ReadInputRegistersRequest(address, quantity),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return null;} else {ByteBuf byteBuf = response.getRegisters();int[] values = new int[quantity];for (int i = 0; i < byteBuf.capacity() / 2; i++) {values[i] = byteBuf.readUnsignedShort();}ReferenceCountUtil.release(response);return values;}});}/*** 异步方法,写入单个线圈的数值,对应功能号05** @param slaveId 设备id* @param address 要读取的寄存器地址* @param value   要写入的boolean值* @return 返回 CompletableFuture<Boolean>* @author huangji*/public CompletableFuture<Boolean> writeSingleCoil(int slaveId, int address, boolean value) {CompletableFuture<WriteSingleCoilResponse> futureResponse = modbusMaster.sendRequest(new WriteSingleCoilRequest(address, value),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return false;} else {boolean responseValue = response.getValue() != 0;ReferenceCountUtil.release(response);return responseValue == value;}});}/*** 异步方法,写入单个寄存器的数值,对应功能号06** @param slaveId 设备id* @param address 要读取的寄存器地址* @param value   要写入的值* @return 返回 CompletableFuture<Boolean>* @author huangji*/public CompletableFuture<Boolean> writeSingleRegister(int slaveId, int address, int value) {CompletableFuture<WriteSingleRegisterResponse> futureResponse = modbusMaster.sendRequest(new WriteSingleRegisterRequest(address, value),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return false;} else {int responseValue = response.getValue();ReferenceCountUtil.release(response);return responseValue == value;}});}/*** 异步方法,写入多个线圈的数值,对应功能号15** @param slaveId  设备id* @param address  要写入的寄存器地址* @param quantity 要写入的寄存器个数* @param values   要写入的boolean[]* @return 返回 CompletableFuture<Boolean>* @author huangji*/public CompletableFuture<Boolean> writeMultipleCoils(int slaveId, int address, int quantity, boolean[] values) {byte[] bytes = booleanToByte(values);CompletableFuture<WriteMultipleCoilsResponse> futureResponse = modbusMaster.sendRequest(new WriteMultipleCoilsRequest(address, quantity, bytes),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return false;} else {int responseQuantity = response.getQuantity();ReferenceCountUtil.release(response);return values.length == responseQuantity;}});}/*** 异步方法,写入多个寄存器的数值,对应功能号16** @param slaveId  设备id* @param address  要写入的寄存器地址* @param quantity 要写入的寄存器个数* @param values   要写入的int[]* @return 返回 CompletableFuture<Boolean>* @author huangji*/public CompletableFuture<Boolean> writeMultipleRegisters(int slaveId, int address, int quantity, int[] values) {byte[] bytes = intToByte(values);CompletableFuture<WriteMultipleRegistersResponse> futureResponse = modbusMaster.sendRequest(new WriteMultipleRegistersRequest(address, quantity, bytes),slaveId);return futureResponse.handle((response, ex) -> {if (ex != null) {ReferenceCountUtil.release(response);return false;} else {int responseQuantity = response.getQuantity();ReferenceCountUtil.release(response);return values.length == responseQuantity;}});}/*** 关闭连接器并释放相关资源** @author huangji*/public void disposeModbusConnector() {if (modbusMaster != null) {modbusMaster.disconnect();}Modbus.releaseSharedResources();}}

二、调试

首先要在Modbus Slave工具点击Connect进行连接,随便输入一些数字,然后发送请求;

注:可以先用connect_slave接口发送请求建立平台与设备的连接

(1) 读数据

在这里插入图片描述

(2) 向寄存器写单个数据

在这里插入图片描述

(3) 向寄存器写多个数据

在这里插入图片描述

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

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

相关文章

【数据结构】二叉树之堆的实现

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、二叉树的顺序结构 &#x1f4d2;1.1顺序存储 &#x1f4d2;1.2堆的性质…

Linux下的基本指令

目录 01. ls 指令 02. pwd命令 03. cd 指令 04. touch指令 05.mkdir指令&#xff08;重要&#xff09;&#xff1a; 06.rmdir指令 && rm 指令&#xff08;重要&#xff09;&#xff1a; 07.man指令&#xff08;重要&#xff09;&#xff1a; 08mv指令&#xff…

Eureka服务器注册

一。Eureka服务器注册 1.pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://mav…

安卓系统--翻译手机rom语言 添加多国语言 编译apk 反编译ODEX 工具步骤解析

很多小品牌机型不具备多语言设置。国内大都是中文。要想换为其他语言除非固件支持。例如国际版固件等等。大厂基本都有中文或者英文或者其他语言配置。而小品牌机型只能通过修改rom来达到多语言调用. 工具步骤演示 今天给友友介绍一款工具&#xff0c;可以用来翻译手机rom语言…

手摸手图解 CodeWhisperer 的安装使用

CodeWhisperer 是亚⻢逊出品的一款基于机器学习的通用代码生成器&#xff0c;可实时提供代码建议。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术&#xff0c;观点…

20230919后台面经整理

1.你认为什么是操作系统&#xff0c;操作系统有哪些功能 os是&#xff1a;管理资源、向用户提供服务、硬件机器的扩展 1.进程线程管理&#xff1a;状态、控制、通信等 2.存储管理&#xff1a;分配回收、地址转换 3.文件管理&#xff1a;目录、操作、磁盘、存取 4.设备管理&…

24. 图论 - 图的表示种类

Hi&#xff0c;你好。我是茶桁。 之前的一节课中&#xff0c;我们了解了图的来由和构成&#xff0c;简单的理解了一下图的一些相关概念。那么这节课&#xff0c;我们要了解一下图的表示&#xff0c;种类。相应的&#xff0c;我们中间需要穿插一些新的知识点用于更好的去理解图…

C#,数值计算——Multinormaldev的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Multinormaldev : Ran { public Cholesky chol { get; set; } null; private int mm { get; set; } private double[] mean { get; set; } private double[,] xvar {…

shardingjdbc分库分表数据均衡性讨论

问题引入 最近一个业务系统中&#xff0c;因为数据量很大&#xff0c;经过技术选型&#xff0c;综合权衡选择了sharding-Jdbc&#xff0c;本文主要讨论的是分库分表的表达式 我们有一个批次总表A&#xff0c;还有一个明细表B&#xff0c;我们需要对明细表B进行水平拆分&#…

InputAction的使用

感觉Unity中InputAction的使用&#xff0c;步步都是坑。 需求点介绍 当用户长按0.5s 键盘X或者VR left controller primaryButton (即X键)时&#xff0c;显示下一个图片。 步骤总览 创建InputAction资产将该InputAction资产绑定到某个GameObject上在对应的script中&#xf…

计算机视觉与深度学习-经典网络解析-VGG-[北邮鲁鹏]

目录标题 VGG参考VGG网络贡献使用尺寸更小的$3 \times 3$卷积串联来获得更大的感受野放弃使用$11 \times 11$和$5 \times 5$这样的大尺寸卷积核深度更深、非线性更强&#xff0c;网络的参数也更少&#xff1b;去掉了AlexNet中的局部响应归一化层(LRN)层。 网络结构主要改进输入…

21 mysql ref 查询

前言 这里主要是 探究一下 explain $sql 中各个 type 诸如 const, ref, range, index, all 的查询的影响, 以及一个初步的效率的判断 这里会调试源码来看一下 各个类型的查询 需要 lookUp 的记录 以及 相关的差异 此系列文章建议从 mysql const 查询 开始看 测试表结构…

基于abaqus的非等速生长Voronoi晶体模型生成插件

1. 非等速生长晶体模型简介 对于标准Voronoi而言&#xff0c;每个晶粒的生长速率是相同的&#xff0c;任意两个晶粒的交界线为其形核点连线的垂直平分线&#xff0c;交界线为一条直线&#xff0c;如图1.1所示。 图1.1 标准Voronoi晶粒交界线 而对于非等速生长Voronoi晶体而言…

肖sir__项目环境之全流程__005

一、测试流程&#xff08;h模型&#xff09; 1、需求文档&#xff08;产品&#xff09; 需求文档&#xff08;软件需求规格说明书srs&#xff09; &#xff08;1&#xff09;如何分析需求 a、显示需求&#xff08;主流程、功能&#xff0c;业务&#xff09; b、隐性需求&#x…

js惰性函数

看下面这份ts代码 实现的效果也很简单,就是将一份文本,复制到剪切板上,未了兼容更多的浏览器(没错说的就是你>ie !),做了一个兼容性判断, 当浏览器支持navigator.clipboard这个api时,就直接调用这个api将文本复制到剪切板中, 如果不支持这个api的话,就执行else里面的代码,这…

解决qml编译时出现错误ninja: build stopped: subcommand failed.

qml编译时出现错误ninja: build stopped: subcommand failed. 如下图&#xff1a; 解决这个编译错误其实很简单&#xff0c;我把Window写错了&#xff0c;写成了window, 如果有类似的报错&#xff0c;可以检查一下qml代码是否有问题。当然在Qt Creator里也没有错误提示&#x…

MySQL 远程连接1130问题

通过后台进入mysql 1,切换到mysql库 2.查询user表信息 3.更新你想远程登录的用户的host信息,我这里想用root进行远程登录,所以修改如下 4.刷新权限 5.大功告成 快来和博主打成一片吧^_^

【Vue】修饰符、表单提交方式、自定义组件的关键步骤

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Vue快速入门》。&#x1f3af;&#x1f3af; &…

识别准确率达 95%,华能东方电厂财务机器人实践探索

摘 要&#xff1a;基于华能集团公司大数据与人工智能构想理念&#xff0c;结合东方电厂实际工作需要&#xff0c;财务工作要向数字化、智能化纵深推进&#xff0c;随着财务数字化转型和升级加速&#xff0c;信息化水平不断提升&#xff0c;以及内部信息互联互通不断加深&#x…

git之撤销工作区的修改和版本回溯

有时候在工作区做了一些修改和代码调试不想要了,可如下做 (1)步骤1:删除目录代码,确保.git目录不能修改 (2)git log 得到相关的commit sha值 可配合git reflog 得到相要的sha值 (3)执行git reset --hard sha值,可以得到时间轴任意版本的代码 git reset --hard sha值干净的代…