huang
2025-11-20 366ba040d2447bacd3455299425e3166f1f992bb
mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java
@@ -7,12 +7,14 @@
import com.github.xingshuangs.iot.protocol.s7.serializer.S7Parameter;
import com.mes.device.entity.DeviceConfig;
import com.mes.device.util.ConfigJsonHelper;
import com.mes.entity.PlcAddress;
import com.mes.s7.enhanced.EnhancedS7Serializer;
import com.mes.service.PlcDynamicDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.github.xingshuangs.iot.protocol.s7.serializer.S7Variable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -21,7 +23,7 @@
/**
 * PLC动态数据读写服务实现
 * 通过PlcAddress中的addressMapping配置动态读写任意字段组合
 * 通过DeviceConfig中的configJson配置动态读写任意字段组合
 * 
 * @author huang
 * @date 2025/11/05
@@ -33,194 +35,6 @@
    private final ObjectMapper objectMapper = new ObjectMapper();
    private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
    /**
     * 根据PlcAddress配置和字段名称读取PLC数据
     *
     * @param config PLC地址映射配置
     * @param fieldNames 要读取的字段名称列表
     * @param s7Serializer S7序列化器
     * @return 字段名->值 的Map
     */
    @Override
    public Map<String, Object> readPlcData(PlcAddress config, List<String> fieldNames, EnhancedS7Serializer s7Serializer) {
        if (config == null || config.getAddressMapping() == null) {
            throw new IllegalArgumentException("PlcAddress配置或addressMapping不能为空");
        }
        try {
            // 解析addressMapping JSON配置
            JSONObject addressMapping = JSONObject.parseObject(config.getAddressMapping());
            // 构建S7Parameter列表
            List<S7Parameter> parameters = buildS7Parameters(config, addressMapping, fieldNames);
            // 从PLC读取数据
            List<S7Parameter> results = s7Serializer.read(parameters);
            // 将结果转换为Map
            Map<String, Object> resultMap = new HashMap<>();
            for (int i = 0; i < fieldNames.size() && i < results.size(); i++) {
                String fieldName = fieldNames.get(i);
                Object value = results.get(i).getValue();
                resultMap.put(fieldName, value);
            }
            return resultMap;
        } catch (Exception e) {
            log.error("读取PLC数据失败,请检查:1.PLC IP地址是否正确[{}] 2.PLC设备是否在线 3.网络连接是否正常,详细错误: {}",
                config.getPlcIp(), e.getMessage(), e);
            return new HashMap<>();
        }
    }
    /**
     * 根据PlcAddress配置和数据Map写入PLC
     *
     * @param config PLC地址映射配置
     * @param dataMap 字段名->值 的Map
     * @param s7Serializer S7序列化器
     */
    @Override
    public void writePlcData(PlcAddress config, Map<String, Object> dataMap, EnhancedS7Serializer s7Serializer) {
        if (config == null || config.getAddressMapping() == null) {
            throw new IllegalArgumentException("PlcAddress配置或addressMapping不能为空");
        }
        try {
            // 解析addressMapping JSON配置
            JSONObject addressMapping = JSONObject.parseObject(config.getAddressMapping());
            // 构建S7Parameter列表,并填充值
            List<S7Parameter> parameters = buildS7ParametersWithValues(config, addressMapping, dataMap);
            // 写入PLC
            s7Serializer.write(parameters);
        } catch (Exception e) {
            log.error("写入PLC数据失败,请检查:1.PLC IP地址是否正确[{}] 2.PLC设备是否在线 3.网络连接是否正常,详细错误: {}",
                config.getPlcIp(), e.getMessage(), e);
        }
    }
    /**
     * 读取PLC所有字段
     *
     * @param config PLC地址映射配置
     * @param s7Serializer S7序列化器
     * @return 所有字段的值
     */
    @Override
    public Map<String, Object> readAllPlcData(PlcAddress config, EnhancedS7Serializer s7Serializer) {
        if (config == null || config.getAddressMapping() == null) {
            throw new IllegalArgumentException("PlcAddress配置或addressMapping不能为空");
        }
        // 获取所有字段名
        JSONObject addressMapping = JSONObject.parseObject(config.getAddressMapping());
        List<String> allFields = new ArrayList<>(addressMapping.keySet());
        // 读取所有字段
        return readPlcData(config, allFields, s7Serializer);
    }
    /**
     * 读取单个字段
     *
     * @param config PLC地址映射配置
     * @param fieldName 字段名
     * @param s7Serializer S7序列化器
     * @return 字段值
     */
    @Override
    public Object readPlcField(PlcAddress config, String fieldName, EnhancedS7Serializer s7Serializer) {
        List<String> fields = new ArrayList<>();
        fields.add(fieldName);
        Map<String, Object> result = readPlcData(config, fields, s7Serializer);
        return result.get(fieldName);
    }
    /**
     * 写入单个字段
     *
     * @param config PLC地址映射配置
     * @param fieldName 字段名
     * @param value 字段值
     * @param s7Serializer S7序列化器
     */
    @Override
    public void writePlcField(PlcAddress config, String fieldName, Object value, EnhancedS7Serializer s7Serializer) {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put(fieldName, value);
        writePlcData(config, dataMap, s7Serializer);
    }
    /**
     * 构建S7Parameter列表(不包含值)
     *
     * @param config PLC地址配置
     * @param addressMapping 地址映射
     * @param fieldNames 字段名列表
     * @return S7Parameter列表
     */
    private List<S7Parameter> buildS7Parameters(PlcAddress config, JSONObject addressMapping, List<String> fieldNames) {
        List<S7Parameter> parameters = new ArrayList<>();
        for (String fieldName : fieldNames) {
            if (!addressMapping.containsKey(fieldName)) {
                log.warn("字段 {} 在addressMapping中不存在,跳过", fieldName);
                continue;
            }
            // 获取字段的偏移地址
            int offset = addressMapping.getInteger(fieldName);
            // 构建完整地址:dbArea + offset(如:DB12.2)
            String fullAddress = config.getDbArea() + "." + offset;
            // 创建S7Parameter,默认使用UINT16类型(16位无符号整数)
            S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1);
            parameters.add(parameter);
        }
        return parameters;
    }
    /**
     * 构建S7Parameter列表(包含值)
     *
     * @param config PLC地址配置
     * @param addressMapping 地址映射
     * @param dataMap 字段名->值 的Map
     * @return S7Parameter列表
     */
    private List<S7Parameter> buildS7ParametersWithValues(PlcAddress config, JSONObject addressMapping, Map<String, Object> dataMap) {
        List<S7Parameter> parameters = new ArrayList<>();
        for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
            if (!addressMapping.containsKey(fieldName)) {
                log.warn("字段 {} 在addressMapping中不存在,跳过", fieldName);
                continue;
            }
            // 获取字段的偏移地址
            int offset = addressMapping.getInteger(fieldName);
            // 构建完整地址
            String fullAddress = config.getDbArea() + "." + offset;
            // 创建S7Parameter,设置值
            S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1);
            parameter.setValue(value);
            parameters.add(parameter);
        }
        return parameters;
    }
    /**
     * 从DeviceConfig中提取地址映射配置
     * 
@@ -391,12 +205,18 @@
            throw new IllegalArgumentException("设备配置不能为空");
        }
        
        String addressMapping = extractAddressMapping(device);
        if (addressMapping == null || addressMapping.isEmpty()) {
            throw new IllegalArgumentException("设备配置中addressMapping不能为空");
        if (s7Serializer == null) {
            log.error("S7Serializer为空,无法写入PLC数据: deviceId={}", device.getId());
            return;
        }
        
        try {
            String addressMapping = extractAddressMapping(device);
            if (addressMapping == null || addressMapping.isEmpty()) {
                log.error("设备配置中addressMapping为空: deviceId={}", device.getId());
                return;
            }
            // 解析addressMapping JSON配置
            JSONObject addressMappingObj = JSONObject.parseObject(addressMapping);
            
@@ -418,17 +238,28 @@
            throw new IllegalArgumentException("设备配置不能为空");
        }
        
        String addressMapping = extractAddressMapping(device);
        if (addressMapping == null || addressMapping.isEmpty()) {
            throw new IllegalArgumentException("设备配置中addressMapping不能为空");
        if (s7Serializer == null) {
            log.error("S7Serializer为空,无法读取PLC数据: deviceId={}", device.getId());
            return new HashMap<>();
        }
        
        // 获取所有字段名
        JSONObject addressMappingObj = JSONObject.parseObject(addressMapping);
        List<String> allFields = new ArrayList<>(addressMappingObj.keySet());
        // 读取所有字段
        return readPlcData(device, allFields, s7Serializer);
        try {
            String addressMapping = extractAddressMapping(device);
            if (addressMapping == null || addressMapping.isEmpty()) {
                log.error("设备配置中addressMapping为空: deviceId={}", device.getId());
                return new HashMap<>();
            }
            // 获取所有字段名
            JSONObject addressMappingObj = JSONObject.parseObject(addressMapping);
            List<String> allFields = new ArrayList<>(addressMappingObj.keySet());
            // 读取所有字段
            return readPlcData(device, allFields, s7Serializer);
        } catch (Exception e) {
            log.error("读取所有PLC数据失败: deviceId={}", device.getId(), e);
            return new HashMap<>();
        }
    }
    
    @Override
@@ -448,11 +279,123 @@
        writePlcData(device, dataMap, s7Serializer);
    }
    
    @Override
    public <T> void writePlcDataByEntity(DeviceConfig device, T entity, EnhancedS7Serializer s7Serializer) {
        if (device == null || entity == null) {
            throw new IllegalArgumentException("设备配置和实体对象不能为空");
        }
        try {
            // 1. 从configJson中获取地址映射(字段名 -> 偏移量)
            Map<String, Object> addressMapping = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper);
            if (addressMapping.isEmpty()) {
                throw new IllegalArgumentException("设备配置中未找到地址映射配置, deviceId=" + device.getId());
            }
            // 2. 获取dbArea和beginIndex
            String dbArea = extractDbArea(device);
            int beginIndex = extractBeginIndex(device);
            // 3. 获取字段配置(类型和count)
            Map<String, FieldConfig> fieldConfigMap = extractFieldConfigMap(device);
            // 4. 解析实体类,获取所有带@S7Variable注解的字段
            Class<?> entityClass = entity.getClass();
            List<S7Parameter> parameters = new ArrayList<>();
            for (Field field : entityClass.getDeclaredFields()) {
                S7Variable annotation = field.getAnnotation(S7Variable.class);
                if (annotation == null) {
                    continue;
                }
                // 获取字段名(从注解的address获取,对应configJson中的paramKey)
                String fieldName = annotation.address();
                if (fieldName == null || fieldName.isEmpty()) {
                    continue;
                }
                // 从addressMapping中获取偏移量
                Object offsetObj = addressMapping.get(fieldName);
                if (offsetObj == null) {
                    log.warn("字段 {} 在configJson地址映射中不存在,跳过", fieldName);
                    continue;
                }
                int offset;
                if (offsetObj instanceof Number) {
                    offset = ((Number) offsetObj).intValue();
                } else {
                    offset = Integer.parseInt(String.valueOf(offsetObj));
                }
                // 构建完整地址:dbArea + (beginIndex + offset)
                String fullAddress = dbArea + "." + (beginIndex + offset);
                // 确定数据类型和count
                // 优先级:1. 注解中的type和count 2. configJson中的配置 3. 根据字段名推断
                EDataType dataType = annotation.type();
                int count = annotation.count();
                // 如果注解中没有指定count,尝试从configJson或字段名推断
                if (count <= 0) {
                    FieldConfig fieldConfig = fieldConfigMap.get(fieldName);
                    if (fieldConfig != null && fieldConfig.count > 0) {
                        count = fieldConfig.count;
                    } else {
                        count = determineFieldCountByName(fieldName);
                    }
                }
                // 如果注解中的类型是UINT16但字段是String类型,尝试从configJson获取
                if (dataType == EDataType.UINT16 && field.getType() == String.class) {
                    FieldConfig fieldConfig = fieldConfigMap.get(fieldName);
                    if (fieldConfig != null && fieldConfig.dataType != null) {
                        dataType = fieldConfig.dataType;
                    } else {
                        dataType = determineFieldTypeByName(fieldName);
                    }
                }
                // 获取字段值
                field.setAccessible(true);
                Object value = field.get(entity);
                if (value == null) {
                    continue; // 跳过null值
                }
                // 创建S7Parameter并设置值
                S7Parameter parameter = new S7Parameter(fullAddress, dataType, count);
                parameter.setValue(value);
                parameters.add(parameter);
            }
            if (parameters.isEmpty()) {
                log.warn("实体类 {} 中没有找到有效的字段,无法写入PLC", entityClass.getSimpleName());
                return;
            }
            // 5. 写入PLC
            s7Serializer.write(parameters);
            log.info("根据实体类写入PLC数据成功: deviceId={}, entityClass={}, fields={}",
                    device.getId(), entityClass.getSimpleName(), parameters.size());
        } catch (Exception e) {
            log.error("根据实体类写入PLC数据失败: deviceId={}, entityClass={}",
                    device.getId(), entity != null ? entity.getClass().getSimpleName() : "null", e);
            throw new RuntimeException("写入PLC数据失败: " + e.getMessage(), e);
        }
    }
    /**
     * 构建S7Parameter列表(不包含值)- 基于DeviceConfig
     */
    private List<S7Parameter> buildS7ParametersForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, List<String> fieldNames) {
        List<S7Parameter> parameters = new ArrayList<>();
        // 获取字段配置(从configJson中解析类型和count)
        Map<String, FieldConfig> fieldConfigMap = extractFieldConfigMap(device);
        
        for (String fieldName : fieldNames) {
            if (!addressMapping.containsKey(fieldName)) {
@@ -460,14 +403,25 @@
                continue;
            }
            
            // 获取字段的偏移地址
            int offset = addressMapping.getInteger(fieldName);
            // 获取字段的偏移地址(addressMapping中只存储数字偏移量)
            Object offsetObj = addressMapping.get(fieldName);
            int offset;
            if (offsetObj instanceof Number) {
                offset = ((Number) offsetObj).intValue();
            } else {
                offset = Integer.parseInt(String.valueOf(offsetObj));
            }
            
            // 构建完整地址:dbArea + offset(如:DB12.2)
            String fullAddress = dbArea + "." + offset;
            
            // 创建S7Parameter,默认使用UINT16类型(16位无符号整数)
            S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1);
            // 获取字段类型和长度(从configJson或根据字段名推断)
            FieldConfig fieldConfig = fieldConfigMap.getOrDefault(fieldName, new FieldConfig());
            EDataType dataType = fieldConfig.dataType != null ? fieldConfig.dataType : determineFieldTypeByName(fieldName);
            int count = fieldConfig.count > 0 ? fieldConfig.count : determineFieldCountByName(fieldName);
            // 创建S7Parameter
            S7Parameter parameter = new S7Parameter(fullAddress, dataType, count);
            parameters.add(parameter);
        }
        
@@ -480,6 +434,9 @@
    private List<S7Parameter> buildS7ParametersWithValuesForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, Map<String, Object> dataMap) {
        List<S7Parameter> parameters = new ArrayList<>();
        
        // 获取字段配置(从configJson中解析类型和count)
        Map<String, FieldConfig> fieldConfigMap = extractFieldConfigMap(device);
        for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
@@ -489,18 +446,151 @@
                continue;
            }
            
            // 获取字段的偏移地址
            int offset = addressMapping.getInteger(fieldName);
            // 获取字段的偏移地址(addressMapping中只存储数字偏移量)
            Object offsetObj = addressMapping.get(fieldName);
            int offset;
            if (offsetObj instanceof Number) {
                offset = ((Number) offsetObj).intValue();
            } else {
                offset = Integer.parseInt(String.valueOf(offsetObj));
            }
            
            // 构建完整地址
            String fullAddress = dbArea + "." + offset;
            
            // 获取字段类型和长度(从configJson或根据字段名推断)
            FieldConfig fieldConfig = fieldConfigMap.getOrDefault(fieldName, new FieldConfig());
            EDataType dataType = fieldConfig.dataType != null ? fieldConfig.dataType : determineFieldTypeByName(fieldName);
            int count = fieldConfig.count > 0 ? fieldConfig.count : determineFieldCountByName(fieldName);
            // 创建S7Parameter,设置值
            S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1);
            S7Parameter parameter = new S7Parameter(fullAddress, dataType, count);
            parameter.setValue(value);
            parameters.add(parameter);
        }
        
        return parameters;
    }
    /**
     * 字段配置信息
     */
    private static class FieldConfig {
        EDataType dataType;
        int count;
        FieldConfig() {
            this.dataType = null;
            this.count = 0;
        }
        FieldConfig(EDataType dataType, int count) {
            this.dataType = dataType;
            this.count = count;
        }
    }
    /**
     * 从设备配置中提取字段配置映射(类型和count)
     * configJson格式: [{paramKey: "plcGlassId1", paramValue: "4", description: "玻璃id1", dataType: "STRING", count: 20}]
     */
    private Map<String, FieldConfig> extractFieldConfigMap(DeviceConfig device) {
        Map<String, FieldConfig> configMap = new HashMap<>();
        try {
            String configJson = device.getConfigJson();
            if (configJson == null || configJson.trim().isEmpty()) {
                return configMap;
            }
            String trimmed = configJson.trim();
            // 如果configJson是数组格式,解析数组
            if (trimmed.startsWith("[")) {
                List<Map<String, Object>> paramList = objectMapper.readValue(trimmed,
                    new TypeReference<List<Map<String, Object>>>() {});
                for (Map<String, Object> param : paramList) {
                    Object paramKeyObj = param.get("paramKey");
                    if (paramKeyObj == null) {
                        continue;
                    }
                    String paramKey = String.valueOf(paramKeyObj);
                    EDataType dataType = null;
                    int count = 0;
                    // 解析dataType
                    Object dataTypeObj = param.get("dataType");
                    if (dataTypeObj != null) {
                        String dataTypeStr = String.valueOf(dataTypeObj).toUpperCase();
                        try {
                            dataType = EDataType.valueOf(dataTypeStr);
                        } catch (IllegalArgumentException e) {
                            log.debug("无法解析数据类型: {}, 字段: {}", dataTypeStr, paramKey);
                        }
                    }
                    // 解析count
                    Object countObj = param.get("count");
                    if (countObj != null) {
                        if (countObj instanceof Number) {
                            count = ((Number) countObj).intValue();
                        } else {
                            try {
                                count = Integer.parseInt(String.valueOf(countObj));
                            } catch (NumberFormatException e) {
                                log.debug("无法解析count值: {}, 字段: {}", countObj, paramKey);
                            }
                        }
                    }
                    if (dataType != null || count > 0) {
                        configMap.put(paramKey, new FieldConfig(dataType, count));
                    }
                }
            }
        } catch (Exception e) {
            log.debug("解析字段配置映射失败: {}", e.getMessage());
        }
        return configMap;
    }
    /**
     * 根据字段名推断数据类型
     */
    private EDataType determineFieldTypeByName(String fieldName) {
        if (fieldName == null) {
            return EDataType.UINT16;
        }
        String lowerName = fieldName.toLowerCase();
        // 玻璃ID字段通常是字符串
        if (lowerName.contains("glassid") || lowerName.contains("glass_id") ||
            lowerName.startsWith("plcglassid")) {
            return EDataType.STRING;
        }
        // 默认返回UINT16
        return EDataType.UINT16;
    }
    /**
     * 根据字段名推断字段长度/数量
     */
    private int determineFieldCountByName(String fieldName) {
        if (fieldName == null) {
            return 1;
        }
        String lowerName = fieldName.toLowerCase();
        // 玻璃ID通常是20个字符
        if (lowerName.contains("glassid") || lowerName.contains("glass_id") ||
            lowerName.startsWith("plcglassid")) {
            return 20; // 默认20个字符
        }
        // 默认返回1
        return 1;
    }
}