You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
13 KiB
344 lines
13 KiB
package com.xujie.sys.common.utils;
|
|
|
|
import com.ghgande.j2mod.modbus.facade.ModbusTCPMaster;
|
|
import com.ghgande.j2mod.modbus.procimg.Register;
|
|
import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
|
|
import com.ghgande.j2mod.modbus.util.BitVector;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
public class ModbusUtils {
|
|
private static final Logger log = LoggerFactory.getLogger(ModbusUtils.class);
|
|
|
|
/** Modbus TCP 套接字超时(毫秒),用于建连与读写;弱网或从站响应慢时可适当加大 */
|
|
private static final int MODBUS_TCP_TIMEOUT_MS = 20000;
|
|
|
|
/**
|
|
*
|
|
* @param highRegister 高 16 位所在寄存器的 {@code getValue()}
|
|
* @param lowRegister 低 16 位所在寄存器的 {@code getValue()}
|
|
*/
|
|
public static float holdingRegistersToFloatBigEndian(int highRegister, int lowRegister) {
|
|
int bits = (highRegister & 0xFFFF) << 16 | (lowRegister & 0xFFFF);
|
|
return Float.intBitsToFloat(bits);
|
|
}
|
|
|
|
/**
|
|
* 读取单个保持寄存器值
|
|
*
|
|
* @param ip 设备IP地址
|
|
* @param port 端口号(默认502)
|
|
* @param unitId 单元ID(默认1)
|
|
* @param ref 寄存器地址(从0开始)
|
|
* @return 寄存器值,失败返回null
|
|
*/
|
|
public static Integer readSingleRegister(String ip, int port, int unitId, int ref) {
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
master.connect();
|
|
Register[] registers = master.readMultipleRegisters(unitId, ref, 1);
|
|
if (registers != null && registers.length > 0) {
|
|
log.info("读取保持寄存器成功 - IP: {}, Port: {}, Ref: {}, Value: {}",
|
|
ip, port, ref, registers[0].getValue());
|
|
return registers[0].getValue();
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("读取单个寄存器失败 - IP: {}, Port: {}, Ref: {}, Error: {}",
|
|
ip, port, ref, e.getMessage());
|
|
throw new RuntimeException("请重试,连接寄存器失败:"+e);
|
|
} finally {
|
|
try {
|
|
master.disconnect();
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 读取多个保持寄存器值
|
|
*
|
|
* @param ip 设备IP地址
|
|
* @param port 端口号
|
|
* @param ref 起始寄存器地址(从0开始)
|
|
* @param count 读取数量
|
|
* @return 寄存器值列表,失败返回空列表
|
|
*/
|
|
public static List<Integer> readMultipleRegisters(String ip, int port, int ref, int count) {
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
master.connect();
|
|
Register[] registers = master.readMultipleRegisters(ref, count);
|
|
if (registers != null) {
|
|
return Arrays.stream(registers)
|
|
.map(Register::getValue)
|
|
.collect(Collectors.toList());
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("读取多个寄存器失败 - IP: {}, Port: {}, Ref: {}, Count: {}, Error: {}",
|
|
ip, port, ref, count, e.getMessage());
|
|
} finally {
|
|
try {
|
|
master.disconnect();
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
return new ArrayList<>();
|
|
}
|
|
|
|
/**
|
|
* 读取线圈状态
|
|
*
|
|
* @param ip 设备IP地址
|
|
* @param port 端口号
|
|
* @param ref 起始线圈地址(从0开始)
|
|
* @param count 读取数量
|
|
* @return BitVector对象,失败返回null
|
|
*/
|
|
public static BitVector readCoils(String ip, int port, int ref, int count) {
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
master.connect();
|
|
return master.readCoils(ref, count);
|
|
} catch (Exception e) {
|
|
log.error("读取线圈失败 - IP: {}, Port: {}, Ref: {}, Count: {}, Error: {}",
|
|
ip, port, ref, count, e.getMessage());
|
|
} finally {
|
|
try {
|
|
master.disconnect();
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 读取单个线圈状态
|
|
*
|
|
* @param ip 设备IP地址
|
|
* @param port 端口号
|
|
* @param ref 线圈地址(从0开始)
|
|
* @return true表示ON,false表示OFF,失败返回null
|
|
*/
|
|
public static Boolean readSingleCoil(String ip, int port, int ref) {
|
|
BitVector coils = readCoils(ip, port, ref, 1);
|
|
if (coils != null) {
|
|
return coils.getBit(0);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 批量读取寄存器(分批读取,每批最多100个)
|
|
*
|
|
* @param ip 设备IP地址
|
|
* @param port 端口号
|
|
* @param startRef 起始地址
|
|
* @param totalCount 总数量
|
|
* @return 寄存器值列表
|
|
*/
|
|
public static List<Integer> readRegistersBatch(String ip, int port, int startRef, int totalCount) {
|
|
List<Integer> result = new ArrayList<>();
|
|
int batchSize = 100; // 每批最多读取100个
|
|
|
|
// 分批读取
|
|
for (int i = 0; i < totalCount / batchSize; i++) {
|
|
int ref = startRef + i * batchSize;
|
|
List<Integer> batch = readMultipleRegisters(ip, port, ref, batchSize);
|
|
result.addAll(batch);
|
|
}
|
|
|
|
// 读取剩余部分
|
|
int remainder = totalCount % batchSize;
|
|
if (remainder > 0) {
|
|
int ref = startRef + (totalCount / batchSize) * batchSize;
|
|
List<Integer> batch = readMultipleRegisters(ip, port, ref, remainder);
|
|
result.addAll(batch);
|
|
}
|
|
|
|
// 验证读取数量
|
|
if (result.size() != totalCount) {
|
|
log.warn("读取寄存器数量不匹配 - 期望: {}, 实际: {}", totalCount, result.size());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static Map<Integer, Integer> readHoldingRegisters(String ip, int port, int slaveId, int offset, int quantity) {
|
|
Map<Integer, Integer> resultMap = new HashMap<>();
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
|
|
// 开启连接
|
|
master.connect();
|
|
|
|
// 验证连接是否成功建立
|
|
if (!master.isConnected()) {
|
|
log.error("连接失败 - IP: {}, Port: {}", ip, port);
|
|
return resultMap;
|
|
}
|
|
|
|
// 注意:j2mod 的 ref/address 一般从 0 开始;调用方需自行确保 offset 基准正确
|
|
Register[] registers = master.readMultipleRegisters(slaveId, offset, quantity);
|
|
if (registers == null) {
|
|
log.warn("读取保持寄存器返回null - IP: {}, Port: {}, offset: {}, quantity: {}",
|
|
ip, port, offset, quantity);
|
|
return resultMap;
|
|
}
|
|
|
|
for (int i = 0; i < registers.length; i++) {
|
|
resultMap.put(offset + i, registers[i].getValue());
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
log.error("读取保持寄存器失败 - IP: {}, Port: {}, offset: {}, quantity: {}, Error: {}",
|
|
ip, port, offset, quantity, e.getMessage(), e);
|
|
} finally {
|
|
try {
|
|
if (master.isConnected()) {
|
|
master.disconnect();
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
|
|
return resultMap;
|
|
}
|
|
|
|
/**
|
|
* 连续读取多字保持寄存器,按批拆分后合并。
|
|
* Modbus FC03 单次请求的字数超过从站允许上限(常见 100~125)时,从站会返回异常码 0x03 Illegal Data Value。
|
|
*/
|
|
public static Map<Integer, Integer> readHoldingRegistersBatched(String ip, int port, int slaveId, int offset, int quantity) {
|
|
final int maxPerRequest = 100;
|
|
Map<Integer, Integer> merged = new HashMap<>();
|
|
int pos = offset;
|
|
int left = quantity;
|
|
while (left > 0) {
|
|
int chunk = Math.min(maxPerRequest, left);
|
|
Map<Integer, Integer> part = readHoldingRegisters(ip, port, slaveId, pos, chunk);
|
|
if (part == null || part.isEmpty()) {
|
|
log.warn("分批读取保持寄存器中断 - offset={}, 本批 quantity={}, 已合并 {} 字", pos, chunk, merged.size());
|
|
break;
|
|
}
|
|
merged.putAll(part);
|
|
pos += chunk;
|
|
left -= chunk;
|
|
}
|
|
if (merged.size() < quantity) {
|
|
log.warn("分批读取保持寄存器未凑满 - 期望 {} 字, 实际 {} 字", quantity, merged.size());
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
public static Map<Integer, Float> readFloatHoldingRegisters(String ip, int port, int slaveId, int offset, int quantity) {
|
|
Map<Integer, Float> resultMap = new HashMap<>();
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
|
|
// 开启连接
|
|
master.connect();
|
|
|
|
// 验证连接是否成功建立
|
|
if (!master.isConnected()) {
|
|
log.error("连接失败 - IP: {}, Port: {}", ip, port);
|
|
return resultMap;
|
|
}
|
|
|
|
// 注意:j2mod 的 ref/address 一般从 0 开始;调用方需自行确保 offset 基准正确
|
|
Register[] registers = master.readMultipleRegisters(slaveId, offset, quantity);
|
|
if (registers == null) {
|
|
log.warn("读取保持寄存器返回null - IP: {}, Port: {}, offset: {}, quantity: {}",
|
|
ip, port, offset, quantity);
|
|
return resultMap;
|
|
}
|
|
|
|
for (int i = 0; i < registers.length; i++) {
|
|
resultMap.put(offset + i, Float.valueOf(registers[i].getValue()));
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
log.error("读取保持寄存器失败 - IP: {}, Port: {}, offset: {}, quantity: {}, Error: {}",
|
|
ip, port, offset, quantity, e.getMessage(), e);
|
|
} finally {
|
|
try {
|
|
if (master.isConnected()) {
|
|
master.disconnect();
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
|
|
return resultMap;
|
|
}
|
|
|
|
public static boolean writeSingleHoldingRegister(String ip, int port, int unitId, int ref, int value) {
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
master.connect();
|
|
if (!master.isConnected()) {
|
|
log.error("连接失败 - IP: {}, Port: {}", ip, port);
|
|
return false;
|
|
}
|
|
master.writeSingleRegister(unitId, ref, new SimpleRegister(value));
|
|
return true;
|
|
} catch (Exception e) {
|
|
log.error("写入保持寄存器失败 - IP: {}, Port: {}, ref: {}, value: {}, Error: {}",
|
|
ip, port, ref, value, e.getMessage(), e);
|
|
return false;
|
|
} finally {
|
|
try {
|
|
if (master.isConnected()) {
|
|
master.disconnect();
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void resetRegisters(String ip, int port, int unitId, List<Integer> refs) {
|
|
if (refs == null || refs.isEmpty()) {
|
|
return;
|
|
}
|
|
ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
|
|
try {
|
|
master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
|
|
master.connect();
|
|
if (!master.isConnected()) {
|
|
log.error("连接失败 - IP: {}, Port: {}", ip, port);
|
|
return;
|
|
}
|
|
for (Integer ref : refs) {
|
|
if (ref != null) {
|
|
master.writeSingleRegister(unitId, ref, new SimpleRegister(0));
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("批量复位寄存器失败 - IP: {}, Port: {}, refs: {}, Error: {}",
|
|
ip, port, refs, e.getMessage(), e);
|
|
} finally {
|
|
try {
|
|
if (master.isConnected()) {
|
|
master.disconnect();
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("断开连接失败", e);
|
|
}
|
|
}
|
|
}
|
|
}
|