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客户端实现
|
* <p>
|
* 基于项目中已有的Modbus实现
|
* </p>
|
*
|
* @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<String, String> 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<String, Object> 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<String, Object> parseExtraParams(String extraParamsJson) {
|
if (extraParamsJson == null || extraParamsJson.isEmpty()) {
|
return new HashMap<>();
|
}
|
|
try {
|
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};
|
return objectMapper.readValue(extraParamsJson, typeRef);
|
} catch (Exception e) {
|
log.error("解析extraParams失败: {}", extraParamsJson, e);
|
return new HashMap<>();
|
}
|
}
|
|
/**
|
* 加载地址映射配置(从configJson或extraParams.addressMapping)
|
*
|
* @return 字段名 -> Modbus地址的映射
|
*/
|
private Map<String, String> loadAddressMapping() {
|
Map<String, String> mapping = new HashMap<>();
|
|
try {
|
// 1. 优先从configJson获取
|
Map<String, Object> configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper);
|
if (!configParams.isEmpty()) {
|
for (Map.Entry<String, Object> 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<String, Object> extraParams = parseExtraParams(device.getExtraParams());
|
Object addressMappingObj = extraParams.get("addressMapping");
|
if (addressMappingObj != null) {
|
if (addressMappingObj instanceof Map) {
|
@SuppressWarnings("unchecked")
|
Map<String, Object> addrMap = (Map<String, Object>) addressMappingObj;
|
for (Map.Entry<String, Object> entry : addrMap.entrySet()) {
|
mapping.put(entry.getKey(), String.valueOf(entry.getValue()));
|
}
|
} else if (addressMappingObj instanceof String) {
|
// 如果是JSON字符串,解析它
|
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};
|
Map<String, Object> addrMap = objectMapper.readValue((String) addressMappingObj, typeRef);
|
for (Map.Entry<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> extraParams = parseExtraParams(device.getExtraParams());
|
@SuppressWarnings("unchecked")
|
Map<String, Object> fieldConfigs = (Map<String, Object>) extraParams.get("fieldConfigs");
|
if (fieldConfigs != null) {
|
@SuppressWarnings("unchecked")
|
Map<String, Object> fieldConfig = (Map<String, Object>) 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<String, Object> fieldConfig = (Map<String, Object>) 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<String, Object> 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<String, Object> 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<String, Object> extraParams = parseExtraParams(device.getExtraParams());
|
@SuppressWarnings("unchecked")
|
Map<String, Object> fieldConfigs = (Map<String, Object>) extraParams.get("fieldConfigs");
|
if (fieldConfigs != null) {
|
@SuppressWarnings("unchecked")
|
Map<String, Object> fieldConfig = (Map<String, Object>) 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;
|
}
|
}
|