Browse Source

refactor(modbustcp): 设备请求列表

wangpx 9 months ago
parent
commit
68da914b79

+ 3 - 2
admin/src/main/java/com/dcs/equipment/domain/DeviceRequest.java

@@ -7,14 +7,15 @@ import lombok.Data;
 import java.util.List;
 
 /**
+ * 设备数据请求 - 支持 ModbusTCP、OPC UA、MQTT 协议
  * @author: wangpx
  * @date: 2025-03-14 14:11
  */
 @Data
 @ApiModel(value = "设备数据请求")
 public class DeviceRequest {
-    @ApiModelProperty("协议类型:MODBUS_TCP, OPC_UA, MQTT,使用 CommProtocolType 常量")
-    private int protocol;
+    @ApiModelProperty("协议类型:1-MODBUS_TCP, 2-OPC_UA, 3-MQTT,使用 CommProtocolType 常量")
+    private Integer protocol;
     @ApiModelProperty("设备ID(Modbus slaveId / OPC UA session or node short id / MQTT clientId)")
     private Integer unitId;
     @ApiModelProperty(value = "数据类型 (1布尔, 2浮点数, 3整型)")

+ 226 - 94
admin/src/main/java/com/dcs/equipment/utils/ModbusUtil.java

@@ -151,112 +151,251 @@ public class ModbusUtil {
         }
     }
 
+    /**
+     * 生成设备请求列表
+     * 按IP:端口 -> 协议ID -> 从机ID -> 功能码 -> 数据类型的层次结构分组
+     */
     public static List<DeviceRequest> getDeviceRequests(List<EquipmentParam> equipmentParamList) {
-        // 保存modbus请求信息
+        if (equipmentParamList == null || equipmentParamList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
         List<DeviceRequest> deviceRequestList = new ArrayList<>();
         
-        // 根据IP地址和端口进行分组
-        Map<String, List<EquipmentParam>> ipPortGroupMap = new HashMap<>();
+        // 按IP:端口分组
+        Map<String, List<EquipmentParam>> ipPortGroups = groupByIpPort(equipmentParamList);
         
-        // 按IP地址和端口分组设备参数
-        for (EquipmentParam equipment : equipmentParamList) {
+        // 处理每个IP:端口组
+        for (Map.Entry<String, List<EquipmentParam>> entry : ipPortGroups.entrySet()) {
+            String[] ipPort = parseIpPort(entry.getKey());
+            String ipAddress = ipPort[0];
+            Integer port = Integer.parseInt(ipPort[1]);
+            List<EquipmentParam> groupEquipmentList = entry.getValue();
+            
+            // 按协议ID分组
+            Map<Integer, List<EquipmentParam>> protocolGroups = groupByProtocol(groupEquipmentList);
+            
+            // 处理每个协议组
+            for (Map.Entry<Integer, List<EquipmentParam>> protocolEntry : protocolGroups.entrySet()) {
+                Integer protocolId = protocolEntry.getKey();
+                List<EquipmentParam> protocolEquipmentList = protocolEntry.getValue();
+                
+                // 按从机ID分组
+                Map<Integer, List<EquipmentParam>> slaveGroups = groupBySlaveId(protocolEquipmentList);
+                
+                // 处理每个从机组
+                for (Map.Entry<Integer, List<EquipmentParam>> slaveEntry : slaveGroups.entrySet()) {
+                    Integer unitId = slaveEntry.getKey();
+                    List<EquipmentParam> slaveEquipmentList = slaveEntry.getValue();
+                    
+                    // 生成该从机的设备请求
+                    List<DeviceRequest> requests = generateDeviceRequestsForSlave(
+                        slaveEquipmentList, ipAddress, port, unitId, protocolId);
+                    deviceRequestList.addAll(requests);
+                }
+            }
+        }
+        
+        return deviceRequestList;
+    }
+
+    /**
+     * 按IP和端口分组
+     */
+    private static Map<String, List<EquipmentParam>> groupByIpPort(List<EquipmentParam> equipmentList) {
+        Map<String, List<EquipmentParam>> groups = new HashMap<>();
+        for (EquipmentParam equipment : equipmentList) {
             String ipAddress = equipment.getIpAddress();
             Integer port = equipment.getPort();
             if (ipAddress != null && port != null) {
                 String key = ipAddress + ":" + port;
-                ipPortGroupMap.computeIfAbsent(key, k -> new ArrayList<>()).add(equipment);
+                groups.computeIfAbsent(key, k -> new ArrayList<>()).add(equipment);
+            }
+        }
+        return groups;
+    }
+
+    /**
+     * 按协议ID分组
+     */
+    private static Map<Integer, List<EquipmentParam>> groupByProtocol(List<EquipmentParam> equipmentList) {
+        Map<Integer, List<EquipmentParam>> groups = new HashMap<>();
+        for (EquipmentParam equipment : equipmentList) {
+            Integer protocolId = equipment.getProtocolId();
+            if (protocolId != null) {
+                groups.computeIfAbsent(protocolId, k -> new ArrayList<>()).add(equipment);
             }
         }
+        return groups;
+    }
+
+    /**
+     * 按从机ID分组
+     */
+    private static Map<Integer, List<EquipmentParam>> groupBySlaveId(List<EquipmentParam> equipmentList) {
+        Map<Integer, List<EquipmentParam>> groups = new HashMap<>();
+        for (EquipmentParam equipment : equipmentList) {
+            Integer unitId = equipment.getUnitId();
+            if (unitId != null) {
+                groups.computeIfAbsent(unitId, k -> new ArrayList<>()).add(equipment);
+            }
+        }
+        return groups;
+    }
+
+    /**
+     * 解析IP:端口字符串
+     */
+    private static String[] parseIpPort(String ipPort) {
+        return ipPort.split(":");
+    }
+
+    /**
+     * 为单个从机生成设备请求
+     */
+    private static List<DeviceRequest> generateDeviceRequestsForSlave(
+            List<EquipmentParam> slaveEquipmentList, 
+            String ipAddress, 
+            Integer port, 
+            Integer unitId, 
+            Integer protocolId) {
         
-        // 对每个IP地址和端口组合进行处理
-        for (Map.Entry<String, List<EquipmentParam>> entry : ipPortGroupMap.entrySet()) {
-            String[] ipPort = entry.getKey().split(":");
-            String ipAddress = ipPort[0];
-            Integer port = Integer.parseInt(ipPort[1]);
-            List<EquipmentParam> groupEquipmentList = entry.getValue();
-            
-            // 根据功能码分类的设备信息
-            Map<String, List<EquipmentParam>> responseMap = new HashMap<>();
-            // 根据功能码分类的设备地址信息
-            Map<String, Map<String, List<Integer>>> addressMap = new HashMap<>();
+        List<DeviceRequest> requests = new ArrayList<>();
+        
+        // 按功能码和数据类型分组
+        Map<String, Map<String, List<Integer>>> addressGroups = groupByFunctionCodeAndDataType(slaveEquipmentList);
+        
+        // 为每个功能码和数据类型组合生成请求
+        for (Map.Entry<String, Map<String, List<Integer>>> functionEntry : addressGroups.entrySet()) {
+            String functionCode = functionEntry.getKey();
+            Map<String, List<Integer>> dataTypeGroups = functionEntry.getValue();
             
-            // 线圈数据
-            responseMap.put(COIL_FUNCTION_CODE, new ArrayList<>());
-            // 输入寄存器数据
-            responseMap.put(INPUT_FUNCTION_CODE, new ArrayList<>());
-            // 保持寄存器数据
-            responseMap.put(HOLDING_FUNCTION_CODE, new ArrayList<>());
+            for (Map.Entry<String, List<Integer>> dataTypeEntry : dataTypeGroups.entrySet()) {
+                String dataType = dataTypeEntry.getKey();
+                List<Integer> addresses = dataTypeEntry.getValue();
+                
+                if (addresses.isEmpty()) {
+                    continue;
+                }
+                
+                // 生成分段读取请求
+                List<DeviceRequest> segmentRequests = generateSegmentRequests(
+                    addresses, functionCode, dataType, ipAddress, port, unitId, protocolId);
+                requests.addAll(segmentRequests);
+            }
+        }
+        
+        return requests;
+    }
+
+    /**
+     * 按功能码和数据类型分组地址
+     */
+    private static Map<String, Map<String, List<Integer>>> groupByFunctionCodeAndDataType(List<EquipmentParam> equipmentList) {
+        Map<String, Map<String, List<Integer>>> groups = new HashMap<>();
+        
+        // 初始化分组结构
+        initializeGroupStructure(groups);
+        
+        // 填充数据
+        for (EquipmentParam equipment : equipmentList) {
+            String registerCode = equipment.getRegisterCode();
+            String dataType = equipment.getDataType();
+            Integer address = equipment.getAddress();
             
-            // 线圈数据存储地址
-            Map<String, List<Integer>> coilAddressMap = new HashMap<>();
-            coilAddressMap.put(COIL_DATA_TYPE, new ArrayList<>());
-            addressMap.put(COIL_FUNCTION_CODE, coilAddressMap);
+            if (registerCode != null && !registerCode.isEmpty() && dataType != null && address != null) {
+                groups.get(registerCode).get(dataType).add(address);
+            }
+        }
+        
+        return groups;
+    }
+
+    /**
+     * 初始化分组结构
+     */
+    private static void initializeGroupStructure(Map<String, Map<String, List<Integer>>> groups) {
+        // 线圈数据
+        Map<String, List<Integer>> coilGroups = new HashMap<>();
+        coilGroups.put(COIL_DATA_TYPE, new ArrayList<>());
+        groups.put(COIL_FUNCTION_CODE, coilGroups);
+        
+        // 输入寄存器数据
+        Map<String, List<Integer>> inputGroups = new HashMap<>();
+        inputGroups.put(INT_DATA_TYPE, new ArrayList<>());
+        inputGroups.put(FLOAT_DATA_TYPE, new ArrayList<>());
+        groups.put(INPUT_FUNCTION_CODE, inputGroups);
+        
+        // 保持寄存器数据
+        Map<String, List<Integer>> holdingGroups = new HashMap<>();
+        holdingGroups.put(INT_DATA_TYPE, new ArrayList<>());
+        holdingGroups.put(FLOAT_DATA_TYPE, new ArrayList<>());
+        groups.put(HOLDING_FUNCTION_CODE, holdingGroups);
+    }
+
+    /**
+     * 生成分段读取请求
+     */
+    private static List<DeviceRequest> generateSegmentRequests(
+            List<Integer> addresses, 
+            String functionCode, 
+            String dataType, 
+            String ipAddress, 
+            Integer port, 
+            Integer unitId, 
+            Integer protocolId) {
+        
+        List<DeviceRequest> requests = new ArrayList<>();
+        
+        // 排序地址
+        addresses.sort(Integer::compareTo);
+        
+        // 计算地址范围
+        int firstAddress = addresses.get(0);
+        int lastAddress = addresses.get(addresses.size() - 1) + 1;
+        int addressRange = lastAddress - firstAddress;
+        
+        // 计算数据类型长度
+        int dataTypeLength = DATA_ADDRESS_LENGTH_MAP.get(dataType);
+        int maxReadLength = DATA_TYPE_LENGTH_MAP.get(dataType);
+        
+        // 计算总读取长度
+        int totalReadLength = MathUtil.ceil(addressRange, dataTypeLength);
+        
+        // 计算分段数量
+        int segmentCount = MathUtil.ceil(totalReadLength, maxReadLength);
+        
+        // 生成每个分段的请求
+        for (int i = 1; i <= segmentCount; i++) {
+            DeviceRequest request = new DeviceRequest();
             
-            // 输入寄存器数据存储地址
-            Map<String, List<Integer>> inputAddressMap = new HashMap<>();
-            inputAddressMap.put(INT_DATA_TYPE, new ArrayList<>());
-            inputAddressMap.put(FLOAT_DATA_TYPE, new ArrayList<>());
-            addressMap.put(INPUT_FUNCTION_CODE, inputAddressMap);
+            // 设置基本参数
+            request.setResource(functionCode);
+            request.setDataType(dataType);
+            request.setIpAddress(ipAddress);
+            request.setPort(port);
+            request.setUnitId(unitId);
+            request.setProtocol(protocolId);
             
-            // 保持寄存器数据存储地址
-            Map<String, List<Integer>> holdingAddressMap = new HashMap<>();
-            holdingAddressMap.put(INT_DATA_TYPE, new ArrayList<>());
-            holdingAddressMap.put(FLOAT_DATA_TYPE, new ArrayList<>());
-            addressMap.put(HOLDING_FUNCTION_CODE, holdingAddressMap);
+            // 计算偏移量
+            int offset = firstAddress + (maxReadLength * (i - 1) * dataTypeLength);
+            request.setOffsetOrIndex(offset);
             
-            // 保存寄存器数据和地址数据
-            for (EquipmentParam equipment : groupEquipmentList) {
-                String registerCode = equipment.getRegisterCode();
-                if (registerCode != null && !registerCode.isEmpty()) {
-                    responseMap.get(registerCode).add(equipment);
-                    addressMap.get(registerCode).get(equipment.getDataType()).add(equipment.getAddress());
-                }
+            // 计算读取数量
+            int quantity;
+            if (totalReadLength < maxReadLength * i) {
+                // 最后一个分段
+                quantity = (int) Math.ceil(totalReadLength - (maxReadLength * (i - 1)));
+            } else {
+                // 完整分段
+                quantity = (int) Math.ceil(maxReadLength);
             }
-
-            // 将地址排序并计算分段读取
-            addressMap.forEach((registerCode, dataTypeMap) -> {
-                if (dataTypeMap == null || dataTypeMap.isEmpty()) { // dataTypeMap为空时,跳过
-                    return;
-                }
-                for (Map.Entry<String, List<Integer>> dataTypeEntry : dataTypeMap.entrySet()) {
-                    if (dataTypeEntry.getValue() == null || dataTypeEntry.getValue().isEmpty()) { // 地址为空时,跳过
-                        continue;
-                    }
-                    String dataType = dataTypeEntry.getKey();
-                    List<Integer> addresses = dataTypeEntry.getValue();
-                    addresses.sort(Integer::compareTo);
-                    int lastAddress = addresses.get(addresses.size() - 1) + 1;
-                    int firstAddress = addresses.get(0);
-                    // 需要读取的长度
-                    int readQuantityLength = MathUtil.ceil((lastAddress - firstAddress), DATA_ADDRESS_LENGTH_MAP.get(dataType));
-                    // 根据读取长度分段读取 超过一次可读取的长度的地址将被分为多个请求
-                    for (int i = 1; i <= MathUtil.ceil(readQuantityLength, DATA_TYPE_LENGTH_MAP.get(dataType)); i++){
-                        DeviceRequest deviceRequest = new DeviceRequest();
-                        // TODO 设备从机地址需要从数据库中获取
-                        deviceRequest.setUnitId(1);
-                        deviceRequest.setResource(registerCode);
-                        deviceRequest.setDataType(dataType);
-                        deviceRequest.setOffsetOrIndex(addresses.get(0) + (DATA_TYPE_LENGTH_MAP.get(dataType) * (i - 1) * DATA_ADDRESS_LENGTH_MAP.get(dataType)));
-                        int quantity;
-                        // 读取长度是否超过一次可读取的长度
-                        if (readQuantityLength < DATA_TYPE_LENGTH_MAP.get(dataType) * i) {
-                            // 未超过
-                            quantity = (int) Math.ceil(readQuantityLength - (DATA_TYPE_LENGTH_MAP.get(dataType) * (i - 1)));
-                        } else {
-                            // 超过
-                            quantity = (int) Math.ceil(DATA_TYPE_LENGTH_MAP.get(dataType));
-                        }
-                        deviceRequest.setLengthOrQos(quantity);
-                        // 设置IP地址和端口
-                        deviceRequest.setIpAddress(ipAddress);
-                        deviceRequest.setPort(port);
-                        deviceRequestList.add(deviceRequest);
-                    }
-                }
-            });
+            request.setLengthOrQos(quantity);
+            
+            requests.add(request);
         }
         
-        return deviceRequestList;
+        return requests;
     }
 
     private static ModbusMaster getMaster(String host, Integer port) throws DeviceIOException {
@@ -532,13 +671,6 @@ public class ModbusUtil {
         for (Object value : values) {
             System.out.println(value);
         }
-//        int i = 1;
-//        int num = 5;
-//        int offset = 0;
-//        float[] holdingRegistersFloatValues = getHoldingRegistersFloatValues(ip, port, 1, offset, num);
-//        for (float f : holdingRegistersFloatValues) {
-//            System.out.println("第 " + i++ + " Value: " + f);
-//        }
         disconnect(ip, port);
     }
 }