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

2023-09-18 14:23:04

前言

参考文章: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 {

    @Autowired
    private 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{

	@Autowired
    private 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) 向寄存器写多个数据

在这里插入图片描述

更多推荐

zookeeper

目录1、zookeeper理论1.1、简介1.2、Zookeeper工作机制.1.3、Zookeeper特点**1.5、Zookeeper应用场景1.6、Zookeeper选举机制****1.6.1、第一次启动选举机制1.6.2、非第一次启动选举机制1.6.3、选举Leader规则:2、kafka2.1、概述2.2、为

Flutter的oktoast插件详解

文章目录简介详细介绍安装和导入导入在MaterialApp外面套一层OKToast组件为什么是包住MaterialApp?显示Toast消息:高级使用Toast位置Toast持续时间自定义Toast样式高级用法使用场景提示消息表单验证操作反馈网络请求状态调试信息小结总结简介oktoast是一个Flutter库,它提供了

vue项目嵌套安卓壳子打包apk

1.确保你的项目可以正常运行2.vue.config.jspublicPath添加一个publicPath:'./',3.需要下载一个HBuilderX编辑器下载地址:HBuilderX-高效极客技巧4.新建一个项目选择5+App创建完成之后删除掉红框内的文件只保留一个manifest.json5.把自己要变成app的

Java 函数式编程思考 —— 授人以渔

引言最近在使用函数式编程时,突然有了一点心得体会,简单说,用好了函数式编程,可以极大的实现方法调用的解耦,业务逻辑高度内聚,同时减少不必要的分支语句(if-else)。一、函数式编程就是Lambda表达式吗?Java语言早在JDK8就提供了函数式编程的基础。你可能会问,函数编程不就是lambda表达式吗?的确,大多数开

Three.js 实现导出模型文件(.glb,.gltf)功能 GLTFExporter

Three.js提供了导出(.glb,.gltf)文件的APIGLTFExporter用于实现场景内容导出模型文件的功能导出模型文件主要使用parse方法,该方法接收三个参数:1.scene:要导出的场景对象。2.onComplete:解析完成后的回调函数,接收一个参数result,表示解析后的glTF数据。3.opt

常用排序算法

一、插入排序1、直接插入排序2、折半插入排序3、希尔排序二、交换排序1、冒泡排序2、快速排序三、选择排序1、简单选择排序2、堆排序(1)调整堆(2)创建堆四、归并排序五、基数排序六、各种排序方法的比较将一组杂乱无章的数据按一定规律顺次排列起来(由小到大或由大到小),即将无序序列排成一个有序序列的运算。排序方法的分类:一

展会预告 | 图扑邀您共聚 IOTE 国际物联网展·深圳站

参展时间:9月20日-22日图扑展位:9号馆9B35-1参展地址:深圳国际会展中心(宝安新馆)IOTE2023第二十届国际物联网展·深圳站,将于9月20日-22日在深圳国际会展中心(宝安)9、10、11号馆震撼来袭。本届展会以“IoT构建数字经济底座”为主题,将IoT技术引入实体经济领域,促进数字化转型和智能化升级,推

【运维】dockerfile 中的COPY 会覆盖文件夹吗

Dockerfile中的COPY命令会根据指定的源路径将文件或文件夹复制到容器中的目标路径。行为取决于两个因素:源路径和目标路径以及目标路径的类型。源路径是文件,目标路径是文件:如果源路径是文件,目标路径也是文件,则COPY命令会将源文件复制到目标路径,并覆盖目标路径中的任何现有文件。例如:COPY./source-f

中小企业生产信息化系统哪个好用?选亿发制造业管理系统提供商

中小型制造企业虽然规模相对较小,但同样是市场经济的重要组成部分。要在这个竞争环境中脱颖而出,智能化生产管理系统成为中小型制造企业不可或缺的工具。让各部门之间的数据无缝衔接,实现工厂的整体协调性和工作效率的大幅提升。让我们从几个关键方面来看中小型制造工厂如何选择适合的生产管理系统。生产计划管理:中小型工厂通常需要灵活的生

001 linux 导学

前言本文建立在您已经安装好linux环境后,本文会向您介绍Shell的一些常用指令什么是linuxLinux是一种自由和开放源代码的类UNIX操作系统,该操作系统的内核由林纳斯托瓦兹在1991年首次发布,之后,在加上用户空间的应用程序之后,就成为了Linux操作系统,并在全球范围内得到了广泛的使用和支持。Linux具有

新闻软文的写作要点有哪些?媒介盒子告诉你

信息时代,受众获取信息的方式越来越碎片化,他们对信息的敏感度越来越高,这就导致虽然广告的成本高了,但是广告的效果越来越不明显。这个时候可以考虑新闻软文,新闻体软文是软文与新闻的结合体,它能够提升企业的曝光率,为企业的宣传起到积极作用,那接下来媒介盒子就从三大方面告诉大家,新闻软文的写作方式。一、&nbsp;保证真实性新

热文推荐