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<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
|
|
/**
|
* 从DeviceConfig中提取地址映射配置
|
*
|
* @param device 设备配置
|
* @return 地址映射JSON字符串
|
*/
|
private String extractAddressMapping(DeviceConfig device) {
|
// configJson 现在仅存放字段地址映射(数组形式)
|
Map<String, Object> 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<String, Object> 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<String, Object> plcConfig = getPlcConfig(device);
|
Object dbArea = plcConfig.get("dbArea");
|
if (dbArea != null) {
|
return String.valueOf(dbArea);
|
}
|
|
// 兼容旧结构:extraParams根节点
|
Map<String, Object> 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<String, Object> plcConfig = getPlcConfig(device);
|
Object beginIndex = plcConfig.get("beginIndex");
|
if (beginIndex != null) {
|
return parseInteger(beginIndex);
|
}
|
|
// 兼容旧结构:extraParams根节点
|
Map<String, Object> extraParams = parseExtraParams(device);
|
Object legacyBeginIndex = extraParams.get("beginIndex");
|
if (legacyBeginIndex != null) {
|
return parseInteger(legacyBeginIndex);
|
}
|
|
// 默认值
|
return 0;
|
}
|
|
private Map<String, Object> 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<String, Object> getPlcConfig(DeviceConfig device) {
|
Map<String, Object> extraParams = parseExtraParams(device);
|
Object plcConfig = extraParams.get("plcConfig");
|
if (plcConfig instanceof Map) {
|
return (Map<String, Object>) 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<String, Object> readPlcData(DeviceConfig device, List<String> 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<S7Parameter> parameters = buildS7ParametersForDevice(device, dbArea, addressMappingObj, 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.网络连接是否正常,deviceId: {}, 详细错误: {}",
|
device.getPlcIp(), device.getId(), e.getMessage(), e);
|
return new HashMap<>();
|
}
|
}
|
|
@Override
|
public void writePlcData(DeviceConfig device, Map<String, Object> 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<S7Parameter> 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<String, Object> 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<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
|
public Object readPlcField(DeviceConfig device, String fieldName, EnhancedS7Serializer s7Serializer) {
|
List<String> fields = new ArrayList<>();
|
fields.add(fieldName);
|
|
Map<String, Object> result = readPlcData(device, fields, s7Serializer);
|
return result.get(fieldName);
|
}
|
|
@Override
|
public void writePlcField(DeviceConfig device, String fieldName, Object value, EnhancedS7Serializer s7Serializer) {
|
Map<String, Object> dataMap = new HashMap<>();
|
dataMap.put(fieldName, value);
|
|
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)) {
|
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<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();
|
|
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<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;
|
}
|
}
|