标题
- 前言
- 一、读写模拟工具中数据
- (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 {
@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接口发送请求建立平台与设备的连接