package com.mes.service.impl; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.xingshuangs.iot.common.enums.EDataType; import com.github.xingshuangs.iot.protocol.s7.serializer.S7Parameter; import com.mes.device.entity.DeviceConfig; import com.mes.device.util.ConfigJsonHelper; 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; import java.util.List; import java.util.Map; /** * PLC动态数据读写服务实现 * 通过DeviceConfig中的configJson配置动态读写任意字段组合 * * @author huang * @date 2025/11/05 */ @Slf4j @Service public class PlcDynamicDataServiceImpl implements PlcDynamicDataService { private final ObjectMapper objectMapper = new ObjectMapper(); private static final TypeReference> MAP_TYPE = new TypeReference>() {}; /** * 从DeviceConfig中提取地址映射配置 * * @param device 设备配置 * @return 地址映射JSON字符串 */ private String extractAddressMapping(DeviceConfig device) { // configJson 现在仅存放字段地址映射(数组形式) Map configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper); if (!configParams.isEmpty()) { try { return objectMapper.writeValueAsString(configParams); } catch (Exception e) { log.warn("序列化configJson地址映射失败, deviceId={}", device.getId(), e); } } // 其次从extraParams中获取(兼容旧结构) Map extraParams = parseExtraParams(device); Object addressMapping = extraParams.get("addressMapping"); if (addressMapping != null) { if (addressMapping instanceof String) { return (String) addressMapping; } else { try { return objectMapper.writeValueAsString(addressMapping); } catch (Exception e) { log.warn("序列化extraParams.addressMapping失败, deviceId={}", device.getId(), e); } } } throw new IllegalArgumentException("设备配置中未找到addressMapping, deviceId=" + device.getId()); } /** * 从DeviceConfig中提取dbArea * * @param device 设备配置 * @return dbArea */ private String extractDbArea(DeviceConfig device) { // 从extraParams.plcConfig中获取(新结构) Map plcConfig = getPlcConfig(device); Object dbArea = plcConfig.get("dbArea"); if (dbArea != null) { return String.valueOf(dbArea); } // 兼容旧结构:extraParams根节点 Map extraParams = parseExtraParams(device); Object legacyDbArea = extraParams.get("dbArea"); if (legacyDbArea != null) { return String.valueOf(legacyDbArea); } // 默认值 return "DB12"; } /** * 从DeviceConfig中提取beginIndex * * @param device 设备配置 * @return beginIndex */ private int extractBeginIndex(DeviceConfig device) { // 从extraParams.plcConfig中获取 Map plcConfig = getPlcConfig(device); Object beginIndex = plcConfig.get("beginIndex"); if (beginIndex != null) { return parseInteger(beginIndex); } // 兼容旧结构:extraParams根节点 Map extraParams = parseExtraParams(device); Object legacyBeginIndex = extraParams.get("beginIndex"); if (legacyBeginIndex != null) { return parseInteger(legacyBeginIndex); } // 默认值 return 0; } private Map parseExtraParams(DeviceConfig device) { if (device.getExtraParams() == null || device.getExtraParams().trim().isEmpty()) { return Collections.emptyMap(); } try { return objectMapper.readValue(device.getExtraParams(), MAP_TYPE); } catch (Exception e) { log.warn("解析设备extraParams失败, deviceId={}", device.getId(), e); return Collections.emptyMap(); } } @SuppressWarnings("unchecked") private Map getPlcConfig(DeviceConfig device) { Map extraParams = parseExtraParams(device); Object plcConfig = extraParams.get("plcConfig"); if (plcConfig instanceof Map) { return (Map) plcConfig; } if (plcConfig instanceof String) { try { return objectMapper.readValue((String) plcConfig, MAP_TYPE); } catch (Exception e) { log.warn("解析extraParams.plcConfig失败, deviceId={}", device.getId(), e); } } return Collections.emptyMap(); } private int parseInteger(Object value) { if (value instanceof Number) { return ((Number) value).intValue(); } try { return Integer.parseInt(String.valueOf(value)); } catch (NumberFormatException ex) { log.warn("无法解析整数值: {}", value); return 0; } } @Override public Map readPlcData(DeviceConfig device, List fieldNames, EnhancedS7Serializer s7Serializer) { if (device == null) { throw new IllegalArgumentException("设备配置不能为空"); } String addressMapping = extractAddressMapping(device); if (addressMapping == null || addressMapping.isEmpty()) { throw new IllegalArgumentException("设备配置中addressMapping不能为空"); } try { // 解析addressMapping JSON配置 JSONObject addressMappingObj = JSONObject.parseObject(addressMapping); // 构建S7Parameter列表 String dbArea = extractDbArea(device); List parameters = buildS7ParametersForDevice(device, dbArea, addressMappingObj, fieldNames); // 从PLC读取数据 List results = s7Serializer.read(parameters); // 将结果转换为Map Map 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.网络连接是否正常,deviceId: {}, 详细错误: {}", device.getPlcIp(), device.getId(), e.getMessage(), e); return new HashMap<>(); } } @Override public void writePlcData(DeviceConfig device, Map dataMap, EnhancedS7Serializer s7Serializer) { if (device == null) { throw new IllegalArgumentException("设备配置不能为空"); } 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); // 构建S7Parameter列表,并填充值 String dbArea = extractDbArea(device); List parameters = buildS7ParametersWithValuesForDevice(device, dbArea, addressMappingObj, dataMap); // 写入PLC s7Serializer.write(parameters); } catch (Exception e) { log.error("写入PLC数据失败,请检查:1.PLC IP地址是否正确[{}] 2.PLC设备是否在线 3.网络连接是否正常,deviceId: {}, 详细错误: {}", device.getPlcIp(), device.getId(), e.getMessage(), e); } } @Override public Map readAllPlcData(DeviceConfig device, EnhancedS7Serializer s7Serializer) { if (device == null) { throw new IllegalArgumentException("设备配置不能为空"); } if (s7Serializer == null) { log.error("S7Serializer为空,无法读取PLC数据: deviceId={}", device.getId()); return new HashMap<>(); } 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 allFields = new ArrayList<>(addressMappingObj.keySet()); // 读取所有字段 return readPlcData(device, allFields, s7Serializer); } catch (Exception e) { log.error("读取所有PLC数据失败: deviceId={}", device.getId(), e); return new HashMap<>(); } } @Override public Object readPlcField(DeviceConfig device, String fieldName, EnhancedS7Serializer s7Serializer) { List fields = new ArrayList<>(); fields.add(fieldName); Map result = readPlcData(device, fields, s7Serializer); return result.get(fieldName); } @Override public void writePlcField(DeviceConfig device, String fieldName, Object value, EnhancedS7Serializer s7Serializer) { Map dataMap = new HashMap<>(); dataMap.put(fieldName, value); writePlcData(device, dataMap, s7Serializer); } @Override public void writePlcDataByEntity(DeviceConfig device, T entity, EnhancedS7Serializer s7Serializer) { if (device == null || entity == null) { throw new IllegalArgumentException("设备配置和实体对象不能为空"); } try { // 1. 从configJson中获取地址映射(字段名 -> 偏移量) Map 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 fieldConfigMap = extractFieldConfigMap(device); // 4. 解析实体类,获取所有带@S7Variable注解的字段 Class entityClass = entity.getClass(); List 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 buildS7ParametersForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, List fieldNames) { List parameters = new ArrayList<>(); // 获取字段配置(从configJson中解析类型和count) Map fieldConfigMap = extractFieldConfigMap(device); for (String fieldName : fieldNames) { if (!addressMapping.containsKey(fieldName)) { log.warn("字段 {} 在addressMapping中不存在,跳过", fieldName); continue; } // 获取字段的偏移地址(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; // 获取字段类型和长度(从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); } return parameters; } /** * 构建S7Parameter列表(包含值)- 基于DeviceConfig */ private List buildS7ParametersWithValuesForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, Map dataMap) { List parameters = new ArrayList<>(); // 获取字段配置(从configJson中解析类型和count) Map fieldConfigMap = extractFieldConfigMap(device); for (Map.Entry entry : dataMap.entrySet()) { String fieldName = entry.getKey(); Object value = entry.getValue(); if (!addressMapping.containsKey(fieldName)) { log.warn("字段 {} 在addressMapping中不存在,跳过", fieldName); continue; } // 获取字段的偏移地址(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, 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 extractFieldConfigMap(DeviceConfig device) { Map 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> paramList = objectMapper.readValue(trimmed, new TypeReference>>() {}); for (Map 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; } // 联机状态等布尔标记 if (lowerName.contains("online")) { return EDataType.BOOL; } // 默认返回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; } }