package com.mes.plc.client.impl; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.mes.connect.modbus.ModbusTcpClient; import com.mes.device.entity.DeviceConfig; import com.mes.device.util.ConfigJsonHelper; import com.mes.plc.client.PlcClient; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.util.*; /** * Modbus协议PLC客户端实现 *

* 基于项目中已有的Modbus实现 *

* * @author huang * @date 2025/12/19 */ @Slf4j public class ModbusPlcClient implements PlcClient { // PLC IP地址 private final String plcIp; // PLC端口 private final int plcPort; // 从站地址 private final int unitId; // 设备配置 private final DeviceConfig device; // Modbus客户端实例 private ModbusTcpClient modbusClient; // 连接状态 private boolean connected = false; // 超时时间(毫秒) private int timeout = 5000; // ObjectMapper用于JSON解析 private final ObjectMapper objectMapper = new ObjectMapper(); // 地址映射缓存:字段名 -> Modbus地址 private Map addressMappingCache; /** * 构造函数 * * @param device 设备配置 */ public ModbusPlcClient(DeviceConfig device) { this.device = device; this.plcIp = device.getPlcIp(); this.plcPort = device.getPlcPort() != null ? device.getPlcPort() : 502; // 从配置中获取从站地址,默认1 int unitIdValue = 1; try { Map extraParams = parseExtraParams(device.getExtraParams()); if (extraParams != null) { Object unitIdObj = extraParams.get("unitId"); if (unitIdObj instanceof Number) { unitIdValue = ((Number) unitIdObj).intValue(); } else if (unitIdObj instanceof String) { unitIdValue = Integer.parseInt((String) unitIdObj); } } } catch (Exception e) { log.warn("解析unitId失败,使用默认值1: deviceId={}", device.getId(), e); } this.unitId = unitIdValue; // 初始化地址映射 this.addressMappingCache = loadAddressMapping(); } /** * 解析设备的extraParams * * @param extraParamsJson extraParams的JSON字符串 * @return 解析后的Map */ private Map parseExtraParams(String extraParamsJson) { if (extraParamsJson == null || extraParamsJson.isEmpty()) { return new HashMap<>(); } try { TypeReference> typeRef = new TypeReference>() {}; return objectMapper.readValue(extraParamsJson, typeRef); } catch (Exception e) { log.error("解析extraParams失败: {}", extraParamsJson, e); return new HashMap<>(); } } /** * 加载地址映射配置(从configJson或extraParams.addressMapping) * * @return 字段名 -> Modbus地址的映射 */ private Map loadAddressMapping() { Map mapping = new HashMap<>(); try { // 1. 优先从configJson获取 Map configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper); if (!configParams.isEmpty()) { for (Map.Entry entry : configParams.entrySet()) { String fieldName = entry.getKey(); Object addressObj = entry.getValue(); if (addressObj != null) { mapping.put(fieldName, String.valueOf(addressObj)); } } if (!mapping.isEmpty()) { log.info("从configJson加载Modbus地址映射成功: deviceId={}, count={}", device.getId(), mapping.size()); return mapping; } } // 2. 从extraParams.addressMapping获取 Map extraParams = parseExtraParams(device.getExtraParams()); Object addressMappingObj = extraParams.get("addressMapping"); if (addressMappingObj != null) { if (addressMappingObj instanceof Map) { @SuppressWarnings("unchecked") Map addrMap = (Map) addressMappingObj; for (Map.Entry entry : addrMap.entrySet()) { mapping.put(entry.getKey(), String.valueOf(entry.getValue())); } } else if (addressMappingObj instanceof String) { // 如果是JSON字符串,解析它 TypeReference> typeRef = new TypeReference>() {}; Map addrMap = objectMapper.readValue((String) addressMappingObj, typeRef); for (Map.Entry entry : addrMap.entrySet()) { mapping.put(entry.getKey(), String.valueOf(entry.getValue())); } } if (!mapping.isEmpty()) { log.info("从extraParams.addressMapping加载Modbus地址映射成功: deviceId={}, count={}", device.getId(), mapping.size()); return mapping; } } log.warn("未找到Modbus地址映射配置: deviceId={}", device.getId()); } catch (Exception e) { log.error("加载Modbus地址映射失败: deviceId={}", device.getId(), e); } return mapping; } /** * 获取字段对应的Modbus地址 * * @param fieldName 字段名 * @return Modbus地址(格式:功能码.寄存器地址,如 "3.40001") */ private String getModbusAddress(String fieldName) { String address = addressMappingCache.get(fieldName); if (address == null || address.isEmpty()) { log.warn("字段 {} 未找到Modbus地址映射: deviceId={}", fieldName, device.getId()); return null; } return address; } /** * 推断数据类型(根据字段名或值) */ private String inferDataType(String fieldName, Object value) { if (value == null) { // 根据字段名推断 String lowerName = fieldName.toLowerCase(); if (lowerName.contains("float") || lowerName.contains("real")) { return "float"; } else if (lowerName.contains("string") || lowerName.contains("str") || lowerName.contains("id")) { return "string"; } return "int"; // 默认int } // 根据值类型推断 if (value instanceof Float || value instanceof Double) { return "float"; } else if (value instanceof String) { return "string"; } else if (value instanceof Number) { return "int"; } return "int"; } @Override public boolean connect() { try { if (modbusClient != null && isConnected()) { return true; } // 创建Modbus客户端实例 this.modbusClient = new ModbusTcpClient(this.plcIp, this.plcPort, this.unitId); // 连接PLC this.modbusClient.connect(); this.connected = true; log.info("Modbus PLC连接成功: {}:{},从站地址: {}", this.plcIp, this.plcPort, this.unitId); return true; } catch (Exception e) { log.error("Modbus PLC连接失败: {}:{}", this.plcIp, this.plcPort, e); this.connected = false; return false; } } @Override public void disconnect() { try { if (this.modbusClient != null) { this.modbusClient.disconnect(); log.info("Modbus PLC断开连接: {}:{}", this.plcIp, this.plcPort); } } catch (Exception e) { log.error("Modbus PLC断开连接失败: {}:{}", this.plcIp, this.plcPort, e); } finally { this.connected = false; this.modbusClient = null; } } @Override public Map readAllData() { if (!isConnected() && !connect()) { log.error("Modbus PLC未连接,无法读取数据: {}:{}", this.plcIp, this.plcPort); return Collections.emptyMap(); } try { if (addressMappingCache.isEmpty()) { log.warn("Modbus地址映射为空,无法读取所有数据: deviceId={}", device.getId()); return Collections.emptyMap(); } // 读取所有配置的字段 Map result = new HashMap<>(); for (String fieldName : addressMappingCache.keySet()) { try { Object value = readFieldValue(fieldName); if (value != null) { result.put(fieldName, value); } } catch (Exception e) { log.warn("读取字段失败: fieldName={}, deviceId={}, error={}", fieldName, device.getId(), e.getMessage()); } } log.info("Modbus读取所有数据成功: deviceId={}, fieldCount={}", device.getId(), result.size()); return result; } catch (Exception e) { log.error("Modbus PLC读取所有数据失败: {}:{}", this.plcIp, this.plcPort, e); this.connected = false; return Collections.emptyMap(); } } @Override public Map readData(String... fields) { if (!isConnected() && !connect()) { log.error("Modbus PLC未连接,无法读取数据: {}:{}", this.plcIp, this.plcPort); return Collections.emptyMap(); } if (fields == null || fields.length == 0) { return readAllData(); } try { Map result = new HashMap<>(); for (String fieldName : fields) { if (fieldName == null || fieldName.isEmpty()) { continue; } try { Object value = readFieldValue(fieldName); if (value != null) { result.put(fieldName, value); } } catch (Exception e) { log.warn("读取字段失败: fieldName={}, deviceId={}, error={}", fieldName, device.getId(), e.getMessage()); } } log.info("Modbus读取指定字段数据成功: deviceId={}, fields={}, resultCount={}", device.getId(), Arrays.toString(fields), result.size()); return result; } catch (Exception e) { log.error("Modbus PLC读取数据失败: {}:{}", this.plcIp, this.plcPort, e); this.connected = false; return Collections.emptyMap(); } } /** * 读取单个字段的值 */ private Object readFieldValue(String fieldName) throws IOException { String address = getModbusAddress(fieldName); if (address == null) { return null; } // 根据字段名推断数据类型(优先从配置中获取) String dataType = inferDataType(fieldName, null); // 从extraParams中获取字段的数据类型配置 Map extraParams = parseExtraParams(device.getExtraParams()); @SuppressWarnings("unchecked") Map fieldConfigs = (Map) extraParams.get("fieldConfigs"); if (fieldConfigs != null) { @SuppressWarnings("unchecked") Map fieldConfig = (Map) fieldConfigs.get(fieldName); if (fieldConfig != null && fieldConfig.get("dataType") != null) { dataType = String.valueOf(fieldConfig.get("dataType")); } } // 根据数据类型读取 switch (dataType.toLowerCase()) { case "float": case "real": return modbusClient.readFloat(address); case "string": case "str": // 字符串需要指定长度,默认32字符 int stringLength = 32; if (fieldConfigs != null) { @SuppressWarnings("unchecked") Map fieldConfig = (Map) fieldConfigs.get(fieldName); if (fieldConfig != null && fieldConfig.get("length") != null) { stringLength = ((Number) fieldConfig.get("length")).intValue(); } } return modbusClient.readString(address, stringLength); case "int": case "integer": case "word": default: return modbusClient.readRegister(address); } } @Override public boolean writeData(Map data) { if (!isConnected() && !connect()) { log.error("Modbus PLC未连接,无法写入数据: {}:{}", this.plcIp, this.plcPort); return false; } if (data == null || data.isEmpty()) { log.warn("写入数据为空,跳过操作: deviceId={}", device.getId()); return true; } try { int successCount = 0; int failCount = 0; for (Map.Entry entry : data.entrySet()) { String fieldName = entry.getKey(); Object value = entry.getValue(); if (value == null) { continue; // 跳过null值 } try { writeFieldValue(fieldName, value); successCount++; } catch (Exception e) { log.error("写入字段失败: fieldName={}, value={}, deviceId={}, error={}", fieldName, value, device.getId(), e.getMessage()); failCount++; } } if (failCount > 0) { log.warn("Modbus写入数据部分失败: deviceId={}, success={}, fail={}", device.getId(), successCount, failCount); return false; } log.info("Modbus写入数据成功: deviceId={}, fieldCount={}", device.getId(), successCount); return true; } catch (Exception e) { log.error("Modbus PLC写入数据失败: {}:{}", this.plcIp, this.plcPort, e); this.connected = false; return false; } } /** * 写入单个字段的值 */ private void writeFieldValue(String fieldName, Object value) throws IOException { String address = getModbusAddress(fieldName); if (address == null) { throw new IllegalArgumentException("字段 " + fieldName + " 未找到Modbus地址映射"); } // 根据值类型推断数据类型 String dataType = inferDataType(fieldName, value); // 从extraParams中获取字段的数据类型配置 Map extraParams = parseExtraParams(device.getExtraParams()); @SuppressWarnings("unchecked") Map fieldConfigs = (Map) extraParams.get("fieldConfigs"); if (fieldConfigs != null) { @SuppressWarnings("unchecked") Map fieldConfig = (Map) fieldConfigs.get(fieldName); if (fieldConfig != null && fieldConfig.get("dataType") != null) { dataType = String.valueOf(fieldConfig.get("dataType")); } } // 根据数据类型写入 switch (dataType.toLowerCase()) { case "float": case "real": float floatValue; if (value instanceof Number) { floatValue = ((Number) value).floatValue(); } else { floatValue = Float.parseFloat(String.valueOf(value)); } modbusClient.writeFloat(address, floatValue); break; case "string": case "str": String stringValue = String.valueOf(value); modbusClient.writeString(address, stringValue); break; case "int": case "integer": case "word": default: int intValue; if (value instanceof Number) { intValue = ((Number) value).intValue(); } else { intValue = Integer.parseInt(String.valueOf(value)); } modbusClient.writeRegister(address, intValue); break; } } @Override public boolean isConnected() { try { if (!this.connected || this.modbusClient == null) { return false; } // 检查连接状态 return this.modbusClient.isConnected(); } catch (Exception e) { this.connected = false; return false; } } @Override public String getPlcType() { return "MODBUS"; } @Override public int getTimeout() { return this.timeout; } @Override public void setTimeout(int timeout) { this.timeout = timeout; } }