O
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

5 months ago
2 months ago
5 months ago
1 month ago
5 months ago
1 month ago
5 months ago
1 month ago
2 months ago
5 months ago
5 months ago
1 month ago
5 months ago
1 month ago
5 months ago
1 month ago
5 months ago
1 month ago
5 months ago
1 month ago
5 months ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
5 months ago
  1. package com.xujie.sys.common.utils;
  2. import com.ghgande.j2mod.modbus.facade.ModbusTCPMaster;
  3. import com.ghgande.j2mod.modbus.procimg.Register;
  4. import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
  5. import com.ghgande.j2mod.modbus.util.BitVector;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import java.util.*;
  9. import java.util.stream.Collectors;
  10. public class ModbusUtils {
  11. private static final Logger log = LoggerFactory.getLogger(ModbusUtils.class);
  12. /** Modbus TCP 套接字超时(毫秒),用于建连与读写;弱网或从站响应慢时可适当加大 */
  13. private static final int MODBUS_TCP_TIMEOUT_MS = 20000;
  14. /**
  15. *
  16. * @param highRegister 16 位所在寄存器的 {@code getValue()}
  17. * @param lowRegister 16 位所在寄存器的 {@code getValue()}
  18. */
  19. public static float holdingRegistersToFloatBigEndian(int highRegister, int lowRegister) {
  20. int bits = (highRegister & 0xFFFF) << 16 | (lowRegister & 0xFFFF);
  21. return Float.intBitsToFloat(bits);
  22. }
  23. /**
  24. * 读取单个保持寄存器值
  25. *
  26. * @param ip 设备IP地址
  27. * @param port 端口号默认502
  28. * @param unitId 单元ID默认1
  29. * @param ref 寄存器地址从0开始
  30. * @return 寄存器值失败返回null
  31. */
  32. public static Integer readSingleRegister(String ip, int port, int unitId, int ref) {
  33. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  34. try {
  35. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  36. master.connect();
  37. Register[] registers = master.readMultipleRegisters(unitId, ref, 1);
  38. if (registers != null && registers.length > 0) {
  39. log.info("读取保持寄存器成功 - IP: {}, Port: {}, Ref: {}, Value: {}",
  40. ip, port, ref, registers[0].getValue());
  41. return registers[0].getValue();
  42. }
  43. } catch (Exception e) {
  44. log.error("读取单个寄存器失败 - IP: {}, Port: {}, Ref: {}, Error: {}",
  45. ip, port, ref, e.getMessage());
  46. throw new RuntimeException("请重试,连接寄存器失败:"+e);
  47. } finally {
  48. try {
  49. master.disconnect();
  50. } catch (Exception e) {
  51. log.error("断开连接失败", e);
  52. }
  53. }
  54. return null;
  55. }
  56. /**
  57. * 读取多个保持寄存器值
  58. *
  59. * @param ip 设备IP地址
  60. * @param port 端口号
  61. * @param ref 起始寄存器地址从0开始
  62. * @param count 读取数量
  63. * @return 寄存器值列表失败返回空列表
  64. */
  65. public static List<Integer> readMultipleRegisters(String ip, int port, int ref, int count) {
  66. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  67. try {
  68. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  69. master.connect();
  70. Register[] registers = master.readMultipleRegisters(ref, count);
  71. if (registers != null) {
  72. return Arrays.stream(registers)
  73. .map(Register::getValue)
  74. .collect(Collectors.toList());
  75. }
  76. } catch (Exception e) {
  77. log.error("读取多个寄存器失败 - IP: {}, Port: {}, Ref: {}, Count: {}, Error: {}",
  78. ip, port, ref, count, e.getMessage());
  79. } finally {
  80. try {
  81. master.disconnect();
  82. } catch (Exception e) {
  83. log.error("断开连接失败", e);
  84. }
  85. }
  86. return new ArrayList<>();
  87. }
  88. /**
  89. * 读取线圈状态
  90. *
  91. * @param ip 设备IP地址
  92. * @param port 端口号
  93. * @param ref 起始线圈地址从0开始
  94. * @param count 读取数量
  95. * @return BitVector对象失败返回null
  96. */
  97. public static BitVector readCoils(String ip, int port, int ref, int count) {
  98. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  99. try {
  100. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  101. master.connect();
  102. return master.readCoils(ref, count);
  103. } catch (Exception e) {
  104. log.error("读取线圈失败 - IP: {}, Port: {}, Ref: {}, Count: {}, Error: {}",
  105. ip, port, ref, count, e.getMessage());
  106. } finally {
  107. try {
  108. master.disconnect();
  109. } catch (Exception e) {
  110. log.error("断开连接失败", e);
  111. }
  112. }
  113. return null;
  114. }
  115. /**
  116. * 读取单个线圈状态
  117. *
  118. * @param ip 设备IP地址
  119. * @param port 端口号
  120. * @param ref 线圈地址从0开始
  121. * @return true表示ONfalse表示OFF失败返回null
  122. */
  123. public static Boolean readSingleCoil(String ip, int port, int ref) {
  124. BitVector coils = readCoils(ip, port, ref, 1);
  125. if (coils != null) {
  126. return coils.getBit(0);
  127. }
  128. return null;
  129. }
  130. /**
  131. * 批量读取寄存器分批读取每批最多100个
  132. *
  133. * @param ip 设备IP地址
  134. * @param port 端口号
  135. * @param startRef 起始地址
  136. * @param totalCount 总数量
  137. * @return 寄存器值列表
  138. */
  139. public static List<Integer> readRegistersBatch(String ip, int port, int startRef, int totalCount) {
  140. List<Integer> result = new ArrayList<>();
  141. int batchSize = 100; // 每批最多读取100个
  142. // 分批读取
  143. for (int i = 0; i < totalCount / batchSize; i++) {
  144. int ref = startRef + i * batchSize;
  145. List<Integer> batch = readMultipleRegisters(ip, port, ref, batchSize);
  146. result.addAll(batch);
  147. }
  148. // 读取剩余部分
  149. int remainder = totalCount % batchSize;
  150. if (remainder > 0) {
  151. int ref = startRef + (totalCount / batchSize) * batchSize;
  152. List<Integer> batch = readMultipleRegisters(ip, port, ref, remainder);
  153. result.addAll(batch);
  154. }
  155. // 验证读取数量
  156. if (result.size() != totalCount) {
  157. log.warn("读取寄存器数量不匹配 - 期望: {}, 实际: {}", totalCount, result.size());
  158. }
  159. return result;
  160. }
  161. public static Map<Integer, Integer> readHoldingRegisters(String ip, int port, int slaveId, int offset, int quantity) {
  162. Map<Integer, Integer> resultMap = new HashMap<>();
  163. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  164. try {
  165. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  166. // 开启连接
  167. master.connect();
  168. // 验证连接是否成功建立
  169. if (!master.isConnected()) {
  170. log.error("连接失败 - IP: {}, Port: {}", ip, port);
  171. return resultMap;
  172. }
  173. // 注意:j2mod 的 ref/address 一般从 0 开始;调用方需自行确保 offset 基准正确
  174. Register[] registers = master.readMultipleRegisters(slaveId, offset, quantity);
  175. if (registers == null) {
  176. log.warn("读取保持寄存器返回null - IP: {}, Port: {}, offset: {}, quantity: {}",
  177. ip, port, offset, quantity);
  178. return resultMap;
  179. }
  180. for (int i = 0; i < registers.length; i++) {
  181. resultMap.put(offset + i, registers[i].getValue());
  182. }
  183. } catch (Exception e) {
  184. log.error("读取保持寄存器失败 - IP: {}, Port: {}, offset: {}, quantity: {}, Error: {}",
  185. ip, port, offset, quantity, e.getMessage(), e);
  186. } finally {
  187. try {
  188. if (master.isConnected()) {
  189. master.disconnect();
  190. }
  191. } catch (Exception e) {
  192. log.error("断开连接失败", e);
  193. }
  194. }
  195. return resultMap;
  196. }
  197. /**
  198. * 连续读取多字保持寄存器按批拆分后合并
  199. * Modbus FC03 单次请求的字数超过从站允许上限常见 100125从站会返回异常码 0x03 Illegal Data Value
  200. */
  201. public static Map<Integer, Integer> readHoldingRegistersBatched(String ip, int port, int slaveId, int offset, int quantity) {
  202. final int maxPerRequest = 100;
  203. Map<Integer, Integer> merged = new HashMap<>();
  204. int pos = offset;
  205. int left = quantity;
  206. while (left > 0) {
  207. int chunk = Math.min(maxPerRequest, left);
  208. Map<Integer, Integer> part = readHoldingRegisters(ip, port, slaveId, pos, chunk);
  209. if (part == null || part.isEmpty()) {
  210. log.warn("分批读取保持寄存器中断 - offset={}, 本批 quantity={}, 已合并 {} 字", pos, chunk, merged.size());
  211. break;
  212. }
  213. merged.putAll(part);
  214. pos += chunk;
  215. left -= chunk;
  216. }
  217. if (merged.size() < quantity) {
  218. log.warn("分批读取保持寄存器未凑满 - 期望 {} 字, 实际 {} 字", quantity, merged.size());
  219. }
  220. return merged;
  221. }
  222. public static Map<Integer, Float> readFloatHoldingRegisters(String ip, int port, int slaveId, int offset, int quantity) {
  223. Map<Integer, Float> resultMap = new HashMap<>();
  224. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  225. try {
  226. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  227. // 开启连接
  228. master.connect();
  229. // 验证连接是否成功建立
  230. if (!master.isConnected()) {
  231. log.error("连接失败 - IP: {}, Port: {}", ip, port);
  232. return resultMap;
  233. }
  234. // 注意:j2mod 的 ref/address 一般从 0 开始;调用方需自行确保 offset 基准正确
  235. Register[] registers = master.readMultipleRegisters(slaveId, offset, quantity);
  236. if (registers == null) {
  237. log.warn("读取保持寄存器返回null - IP: {}, Port: {}, offset: {}, quantity: {}",
  238. ip, port, offset, quantity);
  239. return resultMap;
  240. }
  241. for (int i = 0; i < registers.length; i++) {
  242. resultMap.put(offset + i, Float.valueOf(registers[i].getValue()));
  243. }
  244. } catch (Exception e) {
  245. log.error("读取保持寄存器失败 - IP: {}, Port: {}, offset: {}, quantity: {}, Error: {}",
  246. ip, port, offset, quantity, e.getMessage(), e);
  247. } finally {
  248. try {
  249. if (master.isConnected()) {
  250. master.disconnect();
  251. }
  252. } catch (Exception e) {
  253. log.error("断开连接失败", e);
  254. }
  255. }
  256. return resultMap;
  257. }
  258. public static boolean writeSingleHoldingRegister(String ip, int port, int unitId, int ref, int value) {
  259. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  260. try {
  261. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  262. master.connect();
  263. if (!master.isConnected()) {
  264. log.error("连接失败 - IP: {}, Port: {}", ip, port);
  265. return false;
  266. }
  267. master.writeSingleRegister(unitId, ref, new SimpleRegister(value));
  268. return true;
  269. } catch (Exception e) {
  270. log.error("写入保持寄存器失败 - IP: {}, Port: {}, ref: {}, value: {}, Error: {}",
  271. ip, port, ref, value, e.getMessage(), e);
  272. return false;
  273. } finally {
  274. try {
  275. if (master.isConnected()) {
  276. master.disconnect();
  277. }
  278. } catch (Exception e) {
  279. log.error("断开连接失败", e);
  280. }
  281. }
  282. }
  283. public static void resetRegisters(String ip, int port, int unitId, List<Integer> refs) {
  284. if (refs == null || refs.isEmpty()) {
  285. return;
  286. }
  287. ModbusTCPMaster master = new ModbusTCPMaster(ip, port);
  288. try {
  289. master.setTimeout(MODBUS_TCP_TIMEOUT_MS);
  290. master.connect();
  291. if (!master.isConnected()) {
  292. log.error("连接失败 - IP: {}, Port: {}", ip, port);
  293. return;
  294. }
  295. for (Integer ref : refs) {
  296. if (ref != null) {
  297. master.writeSingleRegister(unitId, ref, new SimpleRegister(0));
  298. }
  299. }
  300. } catch (Exception e) {
  301. log.error("批量复位寄存器失败 - IP: {}, Port: {}, refs: {}, Error: {}",
  302. ip, port, refs, e.getMessage(), e);
  303. } finally {
  304. try {
  305. if (master.isConnected()) {
  306. master.disconnect();
  307. }
  308. } catch (Exception e) {
  309. log.error("断开连接失败", e);
  310. }
  311. }
  312. }
  313. }