huang
2 天以前 14763d895151f3ddad09906f2233057b8b967881
添加plc通讯协议工厂,支持后续多种plc协议
5个文件已添加
2个文件已修改
1149 ■■■■■ 已修改文件
mes-processes/mes-plcSend/src/main/java/com/mes/plc/client/PlcClient.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/plc/client/impl/ModbusPlcClient.java 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/plc/client/impl/S7PlcClient.java 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/plc/factory/PlcClientFactory.java 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/device/DeviceEditDialog.vue 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
快速参考指南.md 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/plc/client/PlcClient.java
New file
@@ -0,0 +1,78 @@
package com.mes.plc.client;
import java.util.Map;
/**
 * PLC客户端接口
 * <p>
 * å®šä¹‰PLC通信的通用方法,支持多种PLC协议(S7、Modbus等)
 * </p>
 *
 * @author huang
 * @date 2025/12/19
 */
public interface PlcClient {
    /**
     * è¿žæŽ¥PLC
     *
     * @return æ˜¯å¦è¿žæŽ¥æˆåŠŸ
     */
    boolean connect();
    /**
     * æ–­å¼€PLC连接
     */
    void disconnect();
    /**
     * è¯»å–PLC的所有数据
     *
     * @return PLC数据,key为字段名,value为字段值
     */
    Map<String, Object> readAllData();
    /**
     * è¯»å–PLC的指定字段数据
     *
     * @param fields éœ€è¦è¯»å–的字段列表
     * @return PLC数据,key为字段名,value为字段值
     */
    Map<String, Object> readData(String... fields);
    /**
     * å†™å…¥PLC数据
     *
     * @param data éœ€è¦å†™å…¥çš„æ•°æ®ï¼Œkey为字段名,value为字段值
     * @return æ˜¯å¦å†™å…¥æˆåŠŸ
     */
    boolean writeData(Map<String, Object> data);
    /**
     * æ£€æŸ¥PLC连接状态
     *
     * @return æ˜¯å¦è¿žæŽ¥æˆåŠŸ
     */
    boolean isConnected();
    /**
     * èŽ·å–PLC类型
     *
     * @return PLC类型
     */
    String getPlcType();
    /**
     * èŽ·å–è¿žæŽ¥è¶…æ—¶æ—¶é—´
     *
     * @return è¶…时时间(毫秒)
     */
    int getTimeout();
    /**
     * è®¾ç½®è¿žæŽ¥è¶…æ—¶æ—¶é—´
     *
     * @param timeout è¶…时时间(毫秒)
     */
    void setTimeout(int timeout);
}
mes-processes/mes-plcSend/src/main/java/com/mes/plc/client/impl/ModbusPlcClient.java
New file
@@ -0,0 +1,213 @@
package com.mes.plc.client.impl;
import com.mes.connect.modbus.ModbusTcpClient;
import com.mes.device.entity.DeviceConfig;
import com.mes.plc.client.PlcClient;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
 * 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;
    // Modbus客户端实例
    private ModbusTcpClient modbusClient;
    // è¿žæŽ¥çŠ¶æ€
    private boolean connected = false;
    // è¶…时时间(毫秒)
    private int timeout = 5000;
    /**
     * æž„造函数
     *
     * @param device è®¾å¤‡é…ç½®
     */
    public ModbusPlcClient(DeviceConfig 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;
    }
    /**
     * è§£æžè®¾å¤‡çš„extraParams
     *
     * @param extraParamsJson extraParams的JSON字符串
     * @return è§£æžåŽçš„Map
     */
    private Map<String, Object> parseExtraParams(String extraParamsJson) {
        if (extraParamsJson == null || extraParamsJson.isEmpty()) {
            return null;
        }
        try {
            // è¿™é‡Œç®€åŒ–处理,实际项目中应该使用Jackson或Gson解析
            // ç”±äºŽé¡¹ç›®ä¸­å·²æœ‰ObjectMapper,这里暂时返回空Map,后续完善
            return new HashMap<>();
        } catch (Exception e) {
            log.error("解析extraParams失败: {}", extraParamsJson, e);
            return null;
        }
    }
    @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 {
            // TODO: å®žçްModbus读取所有数据
            // è¿™é‡Œæš‚时返回空Map,后续完善
            log.warn("Modbus readAllData未实现,返回空Map");
            return new HashMap<>();
        } 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();
        }
        try {
            // TODO: å®žçްModbus读取指定字段数据
            // è¿™é‡Œæš‚时返回空Map,后续完善
            log.warn("Modbus readData未实现,返回空Map");
            return new HashMap<>();
        } catch (Exception e) {
            log.error("Modbus PLC读取数据失败: {}:{}", this.plcIp, this.plcPort, e);
            this.connected = false;
            return Collections.emptyMap();
        }
    }
    @Override
    public boolean writeData(Map<String, Object> data) {
        if (!isConnected() && !connect()) {
            log.error("Modbus PLC未连接,无法写入数据: {}:{}", this.plcIp, this.plcPort);
            return false;
        }
        try {
            // TODO: å®žçްModbus写入数据
            // è¿™é‡Œæš‚时返回true,后续完善
            log.warn("Modbus writeData未实现,返回成功");
            return true;
        } catch (Exception e) {
            log.error("Modbus PLC写入数据失败: {}:{}", this.plcIp, this.plcPort, e);
            this.connected = false;
            return false;
        }
    }
    @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;
        // ModbusTcpClient不支持直接设置超时,这里仅记录超时时间
    }
}
mes-processes/mes-plcSend/src/main/java/com/mes/plc/client/impl/S7PlcClient.java
New file
@@ -0,0 +1,254 @@
package com.mes.plc.client.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
import com.mes.device.entity.DeviceConfig;
import com.mes.plc.client.PlcClient;
import com.mes.service.PlcDynamicDataService;
import com.mes.s7.enhanced.EnhancedS7Serializer;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * S7协议PLC客户端实现
 * <p>
 * åŸºäºŽçŽ°æœ‰çš„S7库实现PlcClient接口
 * </p>
 *
 * @author huang
 * @date 2025/12/19
 */
@Slf4j
public class S7PlcClient implements PlcClient {
    // PLC类型
    private final EPlcType plcType;
    // PLC IP地址
    private final String plcIp;
    // PLC端口
    private final int plcPort;
    // è®¾å¤‡é…ç½®
    private final DeviceConfig device;
    // æ¨¡å—名称
    private final String moduleName;
    // S7 PLC实例
    private S7PLC s7Plc;
    // S7序列化器
    private EnhancedS7Serializer s7Serializer;
    // è¿žæŽ¥çŠ¶æ€
    private boolean connected = false;
    // è¶…时时间(毫秒)
    private int timeout = 5000;
    // PLC类型映射
    private static final Map<String, EPlcType> PLC_TYPE_MAP = new ConcurrentHashMap<>();
    // PLC动态数据服务
    private PlcDynamicDataService plcDynamicDataService;
    /**
     * è®¾ç½®PLC动态数据服务
     *
     * @param plcDynamicDataService PLC动态数据服务实例
     */
    public void setPlcDynamicDataService(PlcDynamicDataService plcDynamicDataService) {
        this.plcDynamicDataService = plcDynamicDataService;
    }
    static {
        // åˆå§‹åŒ–PLC类型映射
        PLC_TYPE_MAP.put("S7_1200", EPlcType.S1200);
        PLC_TYPE_MAP.put("S7_1500", EPlcType.S1500);
        PLC_TYPE_MAP.put("S7_200", EPlcType.S200);
        PLC_TYPE_MAP.put("S7_300", EPlcType.S300);
        PLC_TYPE_MAP.put("S7_400", EPlcType.S400);
    }
    /**
     * æž„造函数
     *
     * @param device è®¾å¤‡é…ç½®
     * @param s7Serializer S7序列化器
     */
    public S7PlcClient(DeviceConfig device, EnhancedS7Serializer s7Serializer) {
        this.device = device;
        this.plcIp = device.getPlcIp();
        this.plcPort = device.getPlcPort() != null ? device.getPlcPort() : 102;
        this.moduleName = device.getModuleName() != null ? device.getModuleName() : "DB1";
        this.s7Serializer = s7Serializer;
        // è§£æžPLC类型
        String plcTypeValue = device.getPlcType();
        this.plcType = PLC_TYPE_MAP.getOrDefault(plcTypeValue, EPlcType.S1200);
    }
    @Override
    public boolean connect() {
        try {
            if (s7Plc != null && isConnected()) {
                return true;
            }
            // åˆ›å»ºS7PLC实例
            this.s7Plc = new S7PLC(this.plcType, this.plcIp, this.plcPort);
            // è¿žæŽ¥PLC
            this.s7Plc.connect();
            this.connected = true;
            log.info("S7 PLC连接成功: {}:{},类型: {}", this.plcIp, this.plcPort, this.plcType);
            return true;
        } catch (Exception e) {
            log.error("S7 PLC连接失败: {}:{}", this.plcIp, this.plcPort, e);
            this.connected = false;
            return false;
        }
    }
    @Override
    public void disconnect() {
        try {
            if (this.s7Plc != null) {
                log.info("S7 PLC断开连接: {}:{}", this.plcIp, this.plcPort);
            }
        } catch (Exception e) {
            log.error("S7 PLC断开连接失败: {}:{}", this.plcIp, this.plcPort, e);
        } finally {
            this.connected = false;
            this.s7Plc = null;
        }
    }
    @Override
    public Map<String, Object> readAllData() {
        if (!isConnected() && !connect()) {
            log.error("S7 PLC未连接,无法读取数据: {}:{}", this.plcIp, this.plcPort);
            return Collections.emptyMap();
        }
        try {
            if (this.plcDynamicDataService == null) {
                log.error("PLC动态数据服务未初始化,无法读取数据: {}:{}", this.plcIp, this.plcPort);
                return Collections.emptyMap();
            }
            // ä½¿ç”¨PlcDynamicDataService读取所有PLC数据
            Map<String, Object> data = this.plcDynamicDataService.readAllPlcData(this.device, this.s7Serializer);
            if (data == null) {
                log.warn("PLC动态数据服务返回空数据: {}:{}", this.plcIp, this.plcPort);
                return new HashMap<>();
            }
            log.info("S7 PLC读取所有数据成功: {}:{}, dataSize={}", this.plcIp, this.plcPort, data.size());
            return data;
        } catch (Exception e) {
            log.error("S7 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("S7 PLC未连接,无法读取数据: {}:{}", this.plcIp, this.plcPort);
            return Collections.emptyMap();
        }
        try {
            if (this.plcDynamicDataService == null) {
                log.error("PLC动态数据服务未初始化,无法读取数据: {}:{}", this.plcIp, this.plcPort);
                return Collections.emptyMap();
            }
            if (fields == null || fields.length == 0) {
                return readAllData();
            }
            // ä½¿ç”¨PlcDynamicDataService读取指定字段PLC数据
            Map<String, Object> data = this.plcDynamicDataService.readPlcData(this.device, java.util.Arrays.asList(fields), this.s7Serializer);
            if (data == null) {
                log.warn("PLC动态数据服务返回空数据: {}:{}", this.plcIp, this.plcPort);
                return new HashMap<>();
            }
            log.info("S7 PLC读取指定字段数据成功: {}:{}, fields={}", this.plcIp, this.plcPort, java.util.Arrays.toString(fields));
            return data;
        } catch (Exception e) {
            log.error("S7 PLC读取数据失败: {}:{}", this.plcIp, this.plcPort, e);
            this.connected = false;
            return Collections.emptyMap();
        }
    }
    @Override
    public boolean writeData(Map<String, Object> data) {
        if (!isConnected() && !connect()) {
            log.error("S7 PLC未连接,无法写入数据: {}:{}", this.plcIp, this.plcPort);
            return false;
        }
        try {
            if (this.plcDynamicDataService == null) {
                log.error("PLC动态数据服务未初始化,无法写入数据: {}:{}", this.plcIp, this.plcPort);
                return false;
            }
            if (data == null || data.isEmpty()) {
                log.warn("写入数据为空,跳过操作: {}:{}", this.plcIp, this.plcPort);
                return true;
            }
            // ä½¿ç”¨PlcDynamicDataService写入PLC数据
            this.plcDynamicDataService.writePlcData(this.device, data, this.s7Serializer);
            log.info("S7 PLC写入数据成功: {}:{}, fields={}", this.plcIp, this.plcPort, data.keySet());
            return true;
        } catch (Exception e) {
            log.error("S7 PLC写入数据失败: {}:{}", this.plcIp, this.plcPort, e);
            this.connected = false;
            return false;
        }
    }
    @Override
    public boolean isConnected() {
        try {
            // S7PLC不支持直接检查连接状态,使用内部标志
            return this.connected && this.s7Plc != null;
        } catch (Exception e) {
            this.connected = false;
            return false;
        }
    }
    @Override
    public String getPlcType() {
        return this.plcType.name();
    }
    @Override
    public int getTimeout() {
        return this.timeout;
    }
    @Override
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}
mes-processes/mes-plcSend/src/main/java/com/mes/plc/factory/PlcClientFactory.java
New file
@@ -0,0 +1,219 @@
package com.mes.plc.factory;
import com.mes.device.entity.DeviceConfig;
import com.mes.plc.client.PlcClient;
import com.mes.plc.client.impl.ModbusPlcClient;
import com.mes.plc.client.impl.S7PlcClient;
import com.mes.service.PlcDynamicDataService;
import com.mes.s7.enhanced.EnhancedS7Serializer;
import com.mes.s7.provider.S7SerializerProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
 * PLC客户端工厂
 * <p>
 * æ ¹æ®è®¾å¤‡é…ç½®åˆ›å»ºä¸åŒç±»åž‹çš„PLC客户端实例
 * </p>
 *
 * @author huang
 * @date 2025/12/19
 */
@Slf4j
@Component
public class PlcClientFactory {
   // S7序列化器提供者
    @Autowired
    private S7SerializerProvider s7SerializerProvider;
    // PLC动态数据服务
    @Autowired
    private PlcDynamicDataService plcDynamicDataService;
    // å®¢æˆ·ç«¯ç¼“存,提高性能
    private final ConcurrentMap<String, PlcClient> clientCache = new ConcurrentHashMap<>();
    /**
     * æ ¹æ®è®¾å¤‡é…ç½®åˆ›å»ºæˆ–获取PLC客户端
     *
     * @param device è®¾å¤‡é…ç½®
     * @return PLC客户端实例
     */
    public PlcClient getClient(DeviceConfig device) {
        if (device == null) {
            log.error("设备配置为空,无法创建PLC客户端");
            return null;
        }
        // ç”Ÿæˆç¼“存键
        String cacheKey = generateCacheKey(device);
        // å°è¯•从缓存中获取客户端
        PlcClient client = clientCache.get(cacheKey);
        if (client != null && client.isConnected()) {
            return client;
        }
        // ç¼“存中没有或连接已断开,创建新客户端
        try {
            client = createClient(device);
            if (client != null) {
                // è¿žæŽ¥PLC
                if (client.connect()) {
                    // å°†å®¢æˆ·ç«¯æ”¾å…¥ç¼“å­˜
                    clientCache.put(cacheKey, client);
                    log.info("PLC客户端创建成功: cacheKey={}, plcType={}", cacheKey, client.getPlcType());
                } else {
                    log.error("PLC客户端连接失败: cacheKey={}, plcType={}", cacheKey, client.getPlcType());
                    // è¿žæŽ¥å¤±è´¥ï¼Œä¸æ”¾å…¥ç¼“å­˜
                    client = null;
                }
            }
        } catch (Exception e) {
            log.error("创建PLC客户端失败: cacheKey={}", cacheKey, e);
            client = null;
        }
        return client;
    }
    /**
     * ç”Ÿæˆç¼“存键
     *
     * @param device è®¾å¤‡é…ç½®
     * @return ç¼“存键
     */
    private String generateCacheKey(DeviceConfig device) {
        StringBuilder sb = new StringBuilder();
        sb.append("device:")
          .append(device.getId())
          .append(":")
          .append(device.getPlcType())
          .append(":")
          .append(device.getPlcIp())
          .append(":")
          .append(device.getPlcPort());
        return sb.toString();
    }
    /**
     * æ ¹æ®è®¾å¤‡ç±»åž‹åˆ›å»ºä¸åŒçš„PLC客户端
     *
     * @param device è®¾å¤‡é…ç½®
     * @return PLC客户端实例
     */
    private PlcClient createClient(DeviceConfig device) {
        if (device == null) {
            return null;
        }
        String plcType = device.getPlcType();
        if (plcType == null || plcType.isEmpty()) {
            log.error("PLC类型为空,无法创建客户端: deviceId={}", device.getId());
            return null;
        }
        // æ ¹æ®PLC类型创建不同的客户端
        if (plcType.startsWith("S7")) {
            // S7协议客户端
            return createS7Client(device);
        } else if (plcType.equals("MODBUS")) {
            // Modbus协议客户端
            return createModbusClient(device);
        } else {
            log.error("不支持的PLC类型: deviceId={}, plcType={}", device.getId(), plcType);
            return null;
        }
    }
    /**
     * åˆ›å»ºS7协议客户端
     *
     * @param device è®¾å¤‡é…ç½®
     * @return S7客户端实例
     */
    private PlcClient createS7Client(DeviceConfig device) {
        try {
            // èŽ·å–S7序列化器
            EnhancedS7Serializer s7Serializer = s7SerializerProvider.getSerializer(device);
            if (s7Serializer == null) {
                log.error("获取S7Serializer失败: deviceId={}", device.getId());
                return null;
            }
            // åˆ›å»ºS7客户端
            S7PlcClient client = new S7PlcClient(device, s7Serializer);
            // è®¾ç½®PLC动态数据服务
            client.setPlcDynamicDataService(this.plcDynamicDataService);
            return client;
        } catch (Exception e) {
            log.error("创建S7客户端失败: deviceId={}", device.getId(), e);
            return null;
        }
    }
    /**
     * åˆ›å»ºModbus协议客户端
     *
     * @param device è®¾å¤‡é…ç½®
     * @return Modbus客户端实例
     */
    private PlcClient createModbusClient(DeviceConfig device) {
        try {
            // åˆ›å»ºModbus客户端
            return new ModbusPlcClient(device);
        } catch (Exception e) {
            log.error("创建Modbus客户端失败: deviceId={}", device.getId(), e);
            return null;
        }
    }
    /**
     * å…³é—­å¹¶ç§»é™¤æŒ‡å®šè®¾å¤‡çš„PLC客户端
     *
     * @param device è®¾å¤‡é…ç½®
     */
    public void removeClient(DeviceConfig device) {
        if (device == null) {
            return;
        }
        String cacheKey = generateCacheKey(device);
        PlcClient client = clientCache.remove(cacheKey);
        if (client != null) {
            client.disconnect();
            log.info("PLC客户端已移除: cacheKey={}", cacheKey);
        }
    }
    /**
     * å…³é—­å¹¶æ¸…除所有PLC客户端
     */
    public void clearAllClients() {
        log.info("开始清除所有PLC客户端,当前缓存大小: {}", clientCache.size());
        for (PlcClient client : clientCache.values()) {
            try {
                client.disconnect();
            } catch (Exception e) {
                log.error("关闭PLC客户端失败", e);
            }
        }
        clientCache.clear();
        log.info("所有PLC客户端已清除");
    }
    /**
     * èŽ·å–å®¢æˆ·ç«¯ç¼“å­˜å¤§å°
     *
     * @return ç¼“存大小
     */
    public int getCacheSize() {
        return clientCache.size();
    }
}
mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java
@@ -39,16 +39,18 @@
    @Resource
    private PlcDynamicDataService plcDynamicDataService;
    
    @Resource
    private com.mes.plc.factory.PlcClientFactory plcClientFactory;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
    private static final int ON = 1;
    private static final int OFF = 0;
    
    // ç¼“存不同设备的S7Serializer实例
    // ç¼“存不同设备的S7Serializer实例(保持兼容,逐步迁移)
    private final ConcurrentMap<String, EnhancedS7Serializer> serializerCache = new ConcurrentHashMap<>();
    // ==================== åŸºäºŽDeviceConfig的新API(推荐使用) ====================
    
    /**
     * æ ¹æ®è®¾å¤‡ID模拟PLC发送请求字
@@ -63,16 +65,65 @@
            return false;
        }
        try {
            // å°è¯•使用新的PLC客户端工厂
            com.mes.plc.client.PlcClient plcClient = plcClientFactory.getClient(device);
            if (plcClient != null) {
                // ä½¿ç”¨æ–°çš„PLC客户端读取数据
                Map<String, Object> currentData = plcClient.readAllData();
                if (currentData != null && !currentData.isEmpty()) {
                    // æ£€æŸ¥è”机状态
                    Integer onlineState = parseInteger(currentData.get("onlineState"));
                    if (onlineState != null && onlineState == OFF) {
                        log.info("当前PLC联机模式为0,停止联机: deviceId={}", deviceId);
                        return false;
                    }
                    // æ£€æŸ¥æ±‡æŠ¥å­—,如果为1则重置为0
                    Integer plcReport = parseInteger(currentData.get("plcReport"));
                    if (plcReport != null && plcReport == ON) {
                        log.info("当前上片PLC汇报字为1,重置为0: deviceId={}", deviceId);
                        currentData.put("plcReport", OFF);
                    }
                    // è®¾ç½®è¯·æ±‚字为1
                    currentData.put("plcRequest", ON);
                    // ä½¿ç”¨æ–°çš„PLC客户端写入数据
                    boolean success = plcClient.writeData(currentData);
                    if (success) {
                        log.info("模拟PLC发送请求字成功:plcRequest=1, deviceId={}", deviceId);
                        return true;
                    }
                }
            }
            // å¦‚果新客户端失败,回退到旧实现(保持兼容)
            log.warn("新PLC客户端失败,回退到旧实现: deviceId={}", deviceId);
            return simulatePlcRequestByDeviceLegacy(device);
        } catch (Exception e) {
            log.error("根据设备模拟PLC请求字失败: deviceId={}", deviceId, e);
            return false;
        }
    }
    /**
     * æ—§ç‰ˆå®žçŽ°ï¼šæ ¹æ®è®¾å¤‡ID模拟PLC发送请求字
     *
     * @param device è®¾å¤‡é…ç½®
     * @return æ˜¯å¦æˆåŠŸ
     */
    private boolean simulatePlcRequestByDeviceLegacy(DeviceConfig device) {
        try {
            EnhancedS7Serializer s7Serializer = getSerializerForDevice(device);
            if (s7Serializer == null) {
                log.error("获取S7Serializer失败: deviceId={}", deviceId);
                log.error("获取S7Serializer失败: deviceId={}", device.getId());
                return false;
            }
            
            // ä½¿ç”¨PlcDynamicDataService读取数据(支持addressMapping)
            Map<String, Object> currentData = plcDynamicDataService.readAllPlcData(device, s7Serializer);
            if (currentData == null || currentData.isEmpty()) {
                log.error("读取PLC数据失败,返回空: deviceId={}", deviceId);
                log.error("读取PLC数据失败,返回空: deviceId={}", device.getId());
                return false;
            }
            
@@ -89,13 +140,13 @@
                            onlineState = Integer.parseInt(strValue);
                        }
                    } catch (NumberFormatException e) {
                        log.warn("解析onlineState失败: deviceId={}, value={}", deviceId, onlineStateObj, e);
                        log.warn("解析onlineState失败: deviceId={}, value={}", device.getId(), onlineStateObj, e);
                    }
                }
            }
            
            if (onlineState != null && onlineState == OFF) {
                log.info("当前PLC联机模式为0,停止联机: deviceId={}", deviceId);
                log.info("当前PLC联机模式为0,停止联机: deviceId={}", device.getId());
                return false;
            }
            
@@ -112,13 +163,13 @@
                            plcReport = Integer.parseInt(strValue);
                        }
                    } catch (NumberFormatException e) {
                        log.warn("解析plcReport失败: deviceId={}, value={}", deviceId, plcReportObj, e);
                        log.warn("解析plcReport失败: deviceId={}, value={}", device.getId(), plcReportObj, e);
                    }
                }
            }
            
            if (plcReport != null && plcReport == ON) {
                log.info("当前上片PLC汇报字为1,重置为0: deviceId={}", deviceId);
                log.info("当前上片PLC汇报字为1,重置为0: deviceId={}", device.getId());
                currentData.put("plcReport", OFF);
            }
            
@@ -128,10 +179,10 @@
            // ä½¿ç”¨PlcDynamicDataService写入数据
            plcDynamicDataService.writePlcData(device, currentData, s7Serializer);
            
            log.info("模拟PLC发送请求字成功:plcRequest=1, deviceId={}", deviceId);
            log.info("模拟PLC发送请求字成功(旧版):plcRequest=1, deviceId={}", device.getId());
            return true;
        } catch (Exception e) {
            log.error("根据设备模拟PLC请求字失败: deviceId={}", deviceId, e);
            log.error("根据设备模拟PLC请求字失败(旧版): deviceId={}", device.getId(), e);
            return false;
        }
    }
@@ -329,7 +380,7 @@
                    EnhancedS7Serializer serializer = EnhancedS7Serializer.newInstance(s7Plc);
                    if (serializer == null) {
                        log.error("创建EnhancedS7Serializer失败: deviceId={}, plcIp={}, plcType={}", 
                            device.getId(), plcIp, plcType);
                            device.getId(), plcIp, plcType.name());
                    }
                    return serializer;
                } catch (Exception e) {
mes-web/src/views/device/DeviceEditDialog.vue
@@ -50,11 +50,11 @@
              </el-select>
            </el-form-item>
            <el-form-item label="PLC类型" prop="plcType">
              <el-select v-model="deviceForm.plcType" placeholder="选择PLC类型" style="width: 100%;" clearable>
            <el-form-item label="通讯类型" prop="plcType">
              <el-select v-model="deviceForm.plcType" placeholder="选择通讯类型" style="width: 100%;" clearable>
                <el-option label="西门子 S7-1200" value="S1200" />
                <el-option label="西门子 S7-1500" value="S1500" />
                <el-option label="Modbus æŽ§åˆ¶å™¨" value="MODBUS" />
                <el-option label="Modbus TCP" value="MODBUS" />
              </el-select>
            </el-form-item>
@@ -105,22 +105,7 @@
              />
            </el-form-item>
            <el-form-item label="通讯协议" prop="protocolType">
              <el-select
                v-model="deviceForm.protocolType"
                placeholder="选择通讯协议"
                style="width: 100%;"
                @change="handleProtocolTypeChange"
              >
                <el-option label="S7 Communication" value="S7 Communication" />
                <el-option label="Modbus TCP" value="Modbus TCP" />
                <el-option label="OPC UA" value="OPC UA" />
                <el-option label="EtherNet/IP" value="EtherNet/IP" />
                <el-option label="Profinet" value="Profinet" />
                <el-option label="其他" value="其他" />
              </el-select>
              <span class="form-tip">S7系列PLC通常使用S7 Communication协议</span>
            </el-form-item>
            <el-form-item label="超时时间(秒)" prop="timeout">
              <el-input-number
@@ -423,9 +408,7 @@
  moduleName: [
    { required: true, message: '请输入模块名称', trigger: 'blur' }
  ],
  protocolType: [
    { required: true, message: '请选择通讯协议', trigger: 'change' }
  ],
  timeout: [
    { required: true, message: '请输入超时时间', trigger: 'blur' },
    { type: 'number', min: 1, max: 300, message: '超时时间在 1 åˆ° 300 ç§’之间', trigger: 'blur' }
@@ -491,21 +474,7 @@
  }
})
// å¤„理通讯协议变化
const handleProtocolTypeChange = (value) => {
  if (!deviceForm.plcType || !value) {
    return
  }
  if (value !== 'S7 Communication' && S7_PLC_TYPES.includes(deviceForm.plcType)) {
      ElMessage.warning('S7系列PLC通常使用S7 Communication协议,请确认协议选择是否正确')
    return
    }
  if (value !== 'Modbus TCP' && MODBUS_PLC_TYPES.includes(deviceForm.plcType)) {
    ElMessage.warning('Modbus ç±»åž‹PLC通常使用 Modbus TCP åè®®ï¼Œè¯·ç¡®è®¤åè®®é€‰æ‹©æ˜¯å¦æ­£ç¡®')
  }
}
// æ–¹æ³•定义
// åŠ è½½è®¾å¤‡ç±»åž‹åˆ—è¡¨
¿ìËٲο¼Ö¸ÄÏ.md
New file
@@ -0,0 +1,271 @@
# MES_Test_Project å¿«é€Ÿå‚考指南
## ðŸš€ å¿«é€Ÿå¼€å§‹
### åŽç«¯å¯åЍ
```bash
# å¯åŠ¨ç½‘å…³
cd gateway
mvn spring-boot:run
# å¯åЍPLC服务(端口10018)
cd mes-processes/mes-plcSend
mvn spring-boot:run
```
### å‰ç«¯å¯åЍ
```bash
cd mes-web
npm install
npm run dev
```
---
## ðŸ“ æ ¸å¿ƒç›®å½•速查
### åŽç«¯æ ¸å¿ƒç›®å½•
| è·¯å¾„ | è¯´æ˜Ž |
|------|------|
| `mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java` | **任务执行引擎(核心)** |
| `mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java` | å¤§è½¦è®¾å¤‡é€»è¾‘处理器 |
| `mes-processes/mes-plcSend/src/main/java/com/mes/device/service/DeviceCoordinationService.java` | è®¾å¤‡åè°ƒæœåŠ¡ |
| `mes-common/serverBase/src/main/java/com/mes/exception/GlobalExceptionHandler.java` | å…¨å±€å¼‚常处理 |
### å‰ç«¯æ ¸å¿ƒç›®å½•
| è·¯å¾„ | è¯´æ˜Ž |
|------|------|
| `mes-web/src/main.js` | åº”用入口 |
| `mes-web/src/router/index.js` | è·¯ç”±é…ç½® |
| `mes-web/src/utils/request.js` | HTTP请求封装 |
| `mes-web/public/config.js` | æœåŠ¡å™¨åœ°å€é…ç½® |
---
## ðŸ”§ å¸¸ç”¨æ“ä½œ
### æ·»åŠ æ–°è®¾å¤‡ç±»åž‹
1. **定义设备类型常量**
   ```java
   // DeviceConfig.DeviceType
   public static final String NEW_DEVICE = "新设备类型";
   ```
2. **创建设备逻辑处理器**
   ```java
   @Component
   public class NewDeviceLogicHandler extends BaseDeviceLogicHandler {
       @Override
       public String getDeviceType() {
           return DeviceConfig.DeviceType.NEW_DEVICE;
       }
       @Override
       protected DevicePlcVO.OperationResult doExecute(...) {
           // å®žçŽ°é€»è¾‘
       }
   }
   ```
3. **配置设备逻辑参数**(前端)
   ```json
   {
     "deviceLogic": {
       "timeout": 5000,
       "customParam": "value"
     }
   }
   ```
### æ·»åŠ æ–°é¡µé¢
1. **创建Vue组件**
   ```vue
   <!-- mes-web/src/views/newModule/NewPage.vue -->
   <template>
     <div>新页面</div>
   </template>
   ```
2. **添加路由**
   ```javascript
   // mes-web/src/router/index.js
   {
     path: '/newModule/newPage',
     name: 'newPage',
     component: () => import('../views/newModule/NewPage.vue')
   }
   ```
3. **添加API接口**
   ```javascript
   // mes-web/src/api/newModule.js
   export function getNewData() {
     return request.get('/api/newModule/data')
   }
   ```
---
## ðŸ“Š è®¾å¤‡ç±»åž‹é€ŸæŸ¥
| è®¾å¤‡ç±»åž‹ | å¸¸é‡ | å¤„理器 | è¯´æ˜Ž |
|---------|------|--------|------|
| å¤§è½¦è®¾å¤‡ | `LOAD_VEHICLE` | `LoadVehicleLogicHandler` | å¤šå®žä¾‹åè°ƒï¼ŒMES任务处理 |
| å¤§ç†ç‰‡ç¬¼ | `LARGE_GLASS` | `LargeGlassLogicHandler` | æ ¼å­èŒƒå›´é…ç½® |
| å§è½¬ç«‹æ‰«ç  | `WORKSTATION_SCANNER` | `HorizontalScannerLogicHandler` | å®šæ—¶æ‰«æï¼Œæ•°æ®è½åº“ |
| å§è½¬ç«‹ | `WORKSTATION_TRANSFER` | `HorizontalTransferLogicHandler` | 30s缓冲,批量处理 |
| å§å¼ç¼“å­˜ | `GLASS_STORAGE` | `GlassStorageLogicHandler` | å·²å®žçŽ°ï¼Œå½“å‰ä¸ä½¿ç”¨ |
---
## ðŸ”„ æ‰§è¡Œæµç¨‹é€ŸæŸ¥
### ä»»åŠ¡æ‰§è¡Œæµç¨‹
```
Controller â†’ Service â†’ TaskExecutionEngine â†’ DeviceInteraction/Handler â†’ PLC
```
### è®¾å¤‡åè°ƒæµç¨‹
```
设备A完成 â†’ æ•°æ®ä¼ é€’ â†’ è®¾å¤‡B读取 â†’ è®¾å¤‡B执行 â†’ çŠ¶æ€åŒæ­¥
```
---
## ðŸ“ API端点速查
### è®¾å¤‡ç®¡ç†
- `GET /device/config/list` - è®¾å¤‡åˆ—表
- `POST /device/config/create` - åˆ›å»ºè®¾å¤‡
- `PUT /device/config/update` - æ›´æ–°è®¾å¤‡
- `DELETE /device/config/delete/{id}` - åˆ é™¤è®¾å¤‡
### è®¾å¤‡ç»„管理
- `GET /device/group/list` - è®¾å¤‡ç»„列表
- `POST /device/group/create` - åˆ›å»ºè®¾å¤‡ç»„
- `POST /device/group/{groupId}/devices` - æ·»åŠ è®¾å¤‡åˆ°ç»„
### ä»»åŠ¡ç®¡ç†
- `POST /device/task/start` - å¯åŠ¨ä»»åŠ¡
- `GET /device/task/list` - ä»»åŠ¡åˆ—è¡¨
- `GET /device/task/{taskId}` - ä»»åŠ¡è¯¦æƒ…
- `POST /device/task/{taskId}/cancel` - å–消任务
### å®žæ—¶ç›‘控(SSE)
- `GET /task/notification/sse?taskId=xxx` - ç›‘听指定任务
- `GET /task/notification/sse/all` - ç›‘听所有任务
---
## ðŸ› å¸¸è§é—®é¢˜
### 1. è®¾å¤‡çŠ¶æ€ä¸¢å¤±
**原因**:大车设备状态存储在内存中,服务重启后丢失
**解决**:扩展支持数据库持久化
### 2. ä»»åŠ¡æ‰§è¡Œå¤±è´¥
**检查**:
- è®¾å¤‡çŠ¶æ€ï¼ˆåœ¨çº¿/离线)
- PLC连接状态
- è®¾å¤‡åè°ƒæ£€æŸ¥
- æŸ¥çœ‹ä»»åŠ¡æ­¥éª¤è¯¦æƒ…
### 3. SSE连接断开
**原因**:连接超时(30分钟)
**解决**:前端重新连接
### 4. è®¾å¤‡é€»è¾‘不生效
**检查**:
- è®¾å¤‡ç±»åž‹æ˜¯å¦æ­£ç¡®
- `extraParams.deviceLogic` é…ç½®æ˜¯å¦æ­£ç¡®
- Handler是否已注册(查看日志)
---
## ðŸ” è°ƒè¯•技巧
### æŸ¥çœ‹æ³¨å†Œçš„设备处理器
```java
@Autowired
private DeviceLogicHandlerFactory factory;
Set<String> types = factory.getSupportedDeviceTypes();
```
### æŸ¥çœ‹æ—¥å¿—
- ä»»åŠ¡æ‰§è¡Œï¼š`TaskExecutionEngine` æ—¥å¿—
- è®¾å¤‡æ‰§è¡Œï¼šå…·ä½“ Handler æ—¥å¿—
- åè°ƒæœåŠ¡ï¼š`DeviceCoordinationService` æ—¥å¿—
### å‰ç«¯è°ƒè¯•
- æ‰“开浏览器开发者工具
- æŸ¥çœ‹ Network æ ‡ç­¾ï¼ˆAPI请求)
- æŸ¥çœ‹ Console æ ‡ç­¾ï¼ˆé”™è¯¯ä¿¡æ¯ï¼‰
---
## ðŸ“¦ æ¨¡å—依赖关系
```
mes-parent (父模块)
├── mes-common (公共模块)
│   â”œâ”€â”€ springSecurity (安全)
│   â”œâ”€â”€ serverBase (基础服务)
│   â”œâ”€â”€ communication (通信)
│   â””── model (数据模型)
├── mes-processes (业务流程)
│   â””── mes-plcSend (PLC服务)
└── gateway (API网关)
```
**依赖关系**:
- `mes-plcSend` ä¾èµ– `springSecurity` å’Œ `serverBase`
- `serverBase` ä¾èµ– `model`
- `gateway` ç‹¬ç«‹è¿è¡Œ
---
## âš™ï¸ é…ç½®æ–‡ä»¶ä½ç½®
| é…ç½®æ–‡ä»¶ | ä½ç½® | è¯´æ˜Ž |
|---------|------|------|
| åŽç«¯ä¸»é…ç½® | `mes-processes/mes-plcSend/src/main/resources/application.yml` | PLC服务配置 |
| å‰ç«¯é…ç½® | `mes-web/public/config.js` | æœåŠ¡å™¨åœ°å€ |
| è·¯ç”±é…ç½® | `mes-web/src/router/index.js` | å‰ç«¯è·¯ç”± |
| æ•°æ®åº“迁移 | `mes-processes/mes-plcSend/src/main/resources/db/migration/` | æ•°æ®åº“脚本 |
---
## ðŸŽ¯ å¼€å‘规范
### å‘½åè§„范
- **类名**:大驼峰(PascalCase)
- **方法名**:小驼峰(camelCase)
- **常量**:全大写下划线(UPPER_SNAKE_CASE)
- **包名**:全小写
### ä»£ç ç»„织
- **简单设备**:放在 `interaction/impl/`
- **复杂设备**:创建独立包(如 `interaction/vehicle/`)
- **需要协调的设备**:创建 `coordination/` å­åŒ…
### æ³¨é‡Šè§„范
- ç±»å’Œæ–¹æ³•必须有JavaDoc注释
- å¤æ‚逻辑需要行内注释
- TODO标记待完成功能
---
## ðŸ“š ç›¸å…³æ–‡æ¡£
- **详细报告**:`项目详细报告.md`
- **PLC服务README**:`mes-processes/mes-plcSend/README.md`
- **架构文档**:`mes-processes/mes-plcSend/ARCHITECTURE.md`
---
**最后更新**:2025年