|
|
@@ -0,0 +1,153 @@
|
|
|
+package cn.vber.collection.service;
|
|
|
+
|
|
|
+import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
|
|
|
+import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest;
|
|
|
+import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse;
|
|
|
+import com.ghgande.j2mod.modbus.msg.WriteCoilRequest;
|
|
|
+import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersRequest;
|
|
|
+import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse;
|
|
|
+import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.net.InetAddress;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class ModbusTcpService {
|
|
|
+ /**
|
|
|
+ * 通用连接方法(抽取公共逻辑,避免重复代码)
|
|
|
+ */
|
|
|
+ private TCPMasterConnection getConnection(String ip, int port, int timeout) throws Exception {
|
|
|
+ TCPMasterConnection connection = new TCPMasterConnection(InetAddress.getByName(ip));
|
|
|
+ connection.setPort(port);
|
|
|
+ connection.setTimeout(timeout); // 超时时间(采集器维护时可配置)
|
|
|
+ connection.connect();
|
|
|
+ if (!connection.isConnected()) {
|
|
|
+ throw new Exception("采集器连接失败:IP=" + ip + ", Port=" + port);
|
|
|
+ }
|
|
|
+ return connection;
|
|
|
+ }
|
|
|
+// ==================== 线圈读取(核心业务场景) ====================
|
|
|
+ /**
|
|
|
+ * 读取线圈状态(功能码01)
|
|
|
+ * @param ip 采集器IP
|
|
|
+ * @param port 采集器端口
|
|
|
+ * @param slaveId PLC从站ID(MBAP单元标识)
|
|
|
+ * @param startAddr 线圈起始地址(注意:Modbus地址00001对应j2mod的0)
|
|
|
+ * @param readLength 读取线圈数量(按位计数)
|
|
|
+ * @param timeout 超时时间(毫秒)
|
|
|
+ * @return 线圈状态数组(boolean[]:true=通/1,false=断/0)
|
|
|
+ */
|
|
|
+ public boolean[] readCoils(String ip, int port, int slaveId, int startAddr, int readLength, int timeout) throws Exception {
|
|
|
+ TCPMasterConnection connection = null;
|
|
|
+ try {
|
|
|
+ connection = getConnection(ip, port, timeout);
|
|
|
+
|
|
|
+ // 1. 创建线圈读取请求(功能码01)
|
|
|
+ ReadCoilsRequest request = new ReadCoilsRequest(startAddr, readLength);
|
|
|
+ request.setUnitID(slaveId); // 区分同一采集器下的不同PLC
|
|
|
+ request.setHeadless(false); // 启用MBAP头(必须)
|
|
|
+
|
|
|
+ // 2. 执行事务
|
|
|
+ ModbusTCPTransaction transaction = new ModbusTCPTransaction(connection);
|
|
|
+ transaction.setRequest(request);
|
|
|
+ transaction.execute();
|
|
|
+
|
|
|
+ // 3. 解析响应
|
|
|
+ ReadCoilsResponse response = (ReadCoilsResponse) transaction.getResponse();
|
|
|
+ boolean[] coilStatus = new boolean[readLength];
|
|
|
+ for (int i = 0; i < readLength; i++) {
|
|
|
+ coilStatus[i] = response.getCoilStatus(i); // 按位读取线圈状态
|
|
|
+ }
|
|
|
+ return coilStatus;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("读取线圈失败:IP={}, SlaveId={}, 起始地址={}", ip, slaveId, startAddr, e);
|
|
|
+ throw new Exception("读取线圈异常:" + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (connection != null && connection.isConnected()) {
|
|
|
+ connection.close(); // 释放连接
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 寄存器读取(核心业务场景) ====================
|
|
|
+ /**
|
|
|
+ * 读取保持寄存器(功能码03)
|
|
|
+ * @param ip 采集器IP
|
|
|
+ * @param port 采集器端口
|
|
|
+ * @param slaveId PLC从站ID
|
|
|
+ * @param startAddr 寄存器起始地址(Modbus地址40001对应j2mod的0)
|
|
|
+ * @param readLength 读取寄存器数量(按字计数)
|
|
|
+ * @param timeout 超时时间(毫秒)
|
|
|
+ * @return 寄存器数值数组(int[])
|
|
|
+ */
|
|
|
+ public int[] readHoldingRegisters(String ip, int port, int slaveId, int startAddr, int readLength, int timeout) throws Exception {
|
|
|
+ TCPMasterConnection connection = null;
|
|
|
+ try {
|
|
|
+ connection = getConnection(ip, port, timeout);
|
|
|
+
|
|
|
+ // 1. 创建寄存器读取请求(功能码03)
|
|
|
+ ReadMultipleRegistersRequest request = new ReadMultipleRegistersRequest (startAddr, readLength);
|
|
|
+ request.setUnitID(slaveId);
|
|
|
+ request.setHeadless(false);
|
|
|
+
|
|
|
+ // 2. 执行事务
|
|
|
+ ModbusTCPTransaction transaction = new ModbusTCPTransaction(connection);
|
|
|
+ transaction.setRequest(request);
|
|
|
+ transaction.execute();
|
|
|
+
|
|
|
+ // 3. 解析响应
|
|
|
+ ReadMultipleRegistersResponse response = (ReadMultipleRegistersResponse ) transaction.getResponse();
|
|
|
+ // 将BitVector转换为int数组
|
|
|
+ int[] registerValues = new int[readLength];
|
|
|
+ for (int i = 0; i < readLength; i++) {
|
|
|
+ if (i < response.getByteCount()/2) { // 每个寄存器占用2字节
|
|
|
+ registerValues[i] = response.getRegisterValue(i);
|
|
|
+ }
|
|
|
+ } // 直接返回寄存器数值数组
|
|
|
+ return registerValues;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("读取寄存器失败:IP={}, SlaveId={}, 起始地址={}", ip, slaveId, startAddr, e);
|
|
|
+ throw new Exception("读取寄存器异常:" + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (connection != null && connection.isConnected()) {
|
|
|
+ connection.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 设备维护:写入线圈(控制设备) ====================
|
|
|
+ /**
|
|
|
+ * 写入单个线圈(功能码05)- 设备维护核心功能(如远程启停风机)
|
|
|
+ * @param ip 采集器IP
|
|
|
+ * @param port 采集器端口
|
|
|
+ * @param slaveId PLC从站ID
|
|
|
+ * @param addr 线圈地址
|
|
|
+ * @param status 线圈状态(true=置1/启动,false=置0/停止)
|
|
|
+ */
|
|
|
+ public void writeSingleCoil(String ip, int port, int slaveId, int addr, boolean status) throws Exception {
|
|
|
+ TCPMasterConnection connection = null;
|
|
|
+ try {
|
|
|
+ connection = getConnection(ip, port, 3000);
|
|
|
+
|
|
|
+ // 创建写线圈请求(功能码05)
|
|
|
+ WriteCoilRequest request = new WriteCoilRequest(addr, status);
|
|
|
+ request.setUnitID(slaveId);
|
|
|
+
|
|
|
+ ModbusTCPTransaction transaction = new ModbusTCPTransaction(connection);
|
|
|
+ transaction.setRequest(request);
|
|
|
+ transaction.execute();
|
|
|
+
|
|
|
+ log.info("写入线圈成功:IP={}, SlaveId={}, 地址={}, 状态={}", ip, slaveId, addr, status);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("写入线圈失败:IP={}, SlaveId={}, 地址={}", ip, slaveId, addr, e);
|
|
|
+ throw new Exception("写入线圈异常:" + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (connection != null && connection.isConnected()) {
|
|
|
+ connection.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|