huang
2025-12-02 628aa6a42e587e9f337e213f87f922fc2ab2af02
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/scanner/handler/HorizontalScannerLogicHandler.java
@@ -10,12 +10,16 @@
import com.mes.s7.enhanced.EnhancedS7Serializer;
import com.mes.s7.provider.S7SerializerProvider;
import com.mes.service.PlcDynamicDataService;
import com.mes.task.model.TaskExecutionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -53,65 +57,232 @@
                                                    String operation,
                                                    Map<String, Object> params,
                                                    Map<String, Object> logicParams) {
        WorkstationLogicConfig config = parseWorkstationConfig(logicParams);
        EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
        if (serializer == null) {
            return buildResult(deviceConfig, operation, false, "获取PLC序列化器失败");
            return buildResult(deviceConfig, operation, false, "获取PLC序列化器失败", null);
        }
        if ("clearPlc".equalsIgnoreCase(operation) || "reset".equalsIgnoreCase(operation)) {
            return clearPlc(deviceConfig, serializer);
        }
        WorkstationLogicConfig config = parseWorkstationConfig(logicParams);
        return executeScan(deviceConfig, config, serializer, params);
    }
    private DevicePlcVO.OperationResult executeScan(DeviceConfig deviceConfig,
                                                    WorkstationLogicConfig config,
                                                    EnhancedS7Serializer serializer,
                                                    Map<String, Object> params) {
        try {
            log.debug("卧转立扫码读取MES写区: deviceId={}, scanInterval={}ms",
                    deviceConfig.getId(), config.getScanIntervalMs());
            Map<String, Object> mesData = plcDynamicDataService.readPlcData(deviceConfig, MES_FIELDS, serializer);
            if (mesData == null || mesData.isEmpty()) {
                return buildResult(deviceConfig, operation, false, "读取MES写区失败");
            // 从参数中获取玻璃ID(定时器每次只处理一个)
            String inputGlassId = null;
            if (params != null) {
                Object glassIdObj = params.get("glassId");
                if (glassIdObj != null) {
                    inputGlassId = String.valueOf(glassIdObj).trim();
                }
            }
            Integer mesSend = parseInteger(mesData.get("mesSend"));
            if (mesSend == null || mesSend == 0) {
                return buildResult(deviceConfig, operation, true, "暂无待处理的玻璃信息");
            }
            String glassId = parseString(mesData.get("mesGlassId"));
            if (!StringUtils.hasText(glassId)) {
                return buildResult(deviceConfig, operation, false, "MES写区未提供玻璃ID");
            }
            Integer longSide = convertDimension(parseInteger(mesData.get("mesWidth")));
            Integer shortSide = convertDimension(parseInteger(mesData.get("mesHeight")));
            Integer workLine = parseInteger(mesData.get("workLine"));
            GlassInfo glassInfo = buildGlassInfo(glassId, longSide, shortSide, workLine);
            boolean saved = glassInfoService.saveOrUpdateGlassInfo(glassInfo);
            if (!saved) {
                return buildResult(deviceConfig, operation, false, "保存玻璃信息失败: " + glassId);
            }
            String msg = String.format("玻璃[%s] 尺寸[%s x %s] 已接收并入库,workLine=%s",
                    glassId,
                    longSide != null ? longSide + "mm" : "-",
                    shortSide != null ? shortSide + "mm" : "-",
                    workLine != null ? workLine : "-");
            return buildResult(deviceConfig, operation, true, msg);
            // 执行单次扫描(定时器会循环调用此方法)
            return executeSingleScan(deviceConfig, config, serializer, inputGlassId, params);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("卧转立扫码等待MES数据被中断, deviceId={}", deviceConfig.getId(), e);
            return buildResult(deviceConfig, "scanOnce", false, "等待MES数据被中断", null);
        } catch (Exception e) {
            log.error("卧转立扫码处理异常, deviceId={}", deviceConfig.getId(), e);
            return buildResult(deviceConfig, operation, false, "处理异常: " + e.getMessage());
            return buildResult(deviceConfig, "scanOnce", false, "处理异常: " + e.getMessage(), null);
        }
    }
    /**
     * 执行单次扫描
     */
    private DevicePlcVO.OperationResult executeSingleScan(DeviceConfig deviceConfig,
                                                          WorkstationLogicConfig config,
                                                          EnhancedS7Serializer serializer,
                                                          String inputGlassId,
                                                          Map<String, Object> params) throws InterruptedException {
        // 1. 写入plcRequest=1和plcGlassId(如果提供了玻璃ID)
        triggerScanRequest(deviceConfig, serializer, inputGlassId);
        // 2. 等待MES回写mesSend=1以及玻璃信息
        Map<String, Object> mesData = waitForMesData(deviceConfig, serializer, config);
        if (mesData == null || mesData.isEmpty()) {
            log.error("等待MES写入玻璃信息超时: deviceId={}, timeout={}ms",
                    deviceConfig.getId(), config.getScanIntervalMs());
            return buildResult(deviceConfig, "scanOnce", false,
                    String.format("等待MES写入玻璃信息超时(%dms)", config.getScanIntervalMs()), null);
        }
        // 3. 读取MES回写的玻璃信息
        String glassId = parseString(mesData.get("mesGlassId"));
        if (!StringUtils.hasText(glassId)) {
            return buildResult(deviceConfig, "scanOnce", false, "MES写区未提供玻璃ID", null);
        }
        // 读取MES尺寸数据:mesWidth=表宽,mesHeight=长
        Integer rawWidth = parseInteger(mesData.get("mesWidth"));
        Integer rawHeight = parseInteger(mesData.get("mesHeight"));
        Integer workLine = parseInteger(mesData.get("workLine"));
        // 4. 清空plcRequest和plcGlassId(只清除PLC字段)
        clearPlcRequestFields(deviceConfig, serializer);
            // 5. 保存玻璃信息到数据库
            GlassInfo glassInfo = buildGlassInfo(glassId, rawWidth, rawHeight, workLine);
            boolean saved = glassInfoService.saveOrUpdateGlassInfo(glassInfo);
            if (!saved) {
                return buildResult(deviceConfig, "scanOnce", false, "保存玻璃信息失败: " + glassId, null);
            }
            // 6. 将扫描到的玻璃ID保存到共享数据中(供大车设备定时器读取)
            saveScannedGlassId(params, glassId);
            String msg = String.format("玻璃[%s] 尺寸[表宽:%s x 长:%s] 已接收并入库,workLine=%s",
                    glassId,
                    rawWidth != null ? rawWidth + "mm" : "-",
                    rawHeight != null ? rawHeight + "mm" : "-",
                    workLine != null ? workLine : "-");
            Map<String, Object> resultData = new HashMap<>();
            resultData.put("glassIds", Collections.singletonList(glassId));
            if (workLine != null) {
                resultData.put("workLine", workLine);
            }
            return buildResult(deviceConfig, "scanOnce", true, msg, resultData);
    }
    /**
     * 设置暂停标志(供大车设备调用)
     */
    public static void setPauseFlag(TaskExecutionContext context, boolean pause) {
        if (context != null) {
            context.getSharedData().put("scannerPause", pause);
        }
    }
    /**
     * 保存扫描到的玻璃ID到共享数据中
     */
    @SuppressWarnings("unchecked")
    private void saveScannedGlassId(Map<String, Object> params, String glassId) {
        if (params == null || !StringUtils.hasText(glassId)) {
            return;
        }
        Object contextObj = params.get("_taskContext");
        if (contextObj instanceof TaskExecutionContext) {
            TaskExecutionContext context = (TaskExecutionContext) contextObj;
            List<String> scannedGlassIds = (List<String>) context.getSharedData()
                    .computeIfAbsent("scannedGlassIds", k -> new java.util.ArrayList<>());
            if (!scannedGlassIds.contains(glassId)) {
                scannedGlassIds.add(glassId);
                log.debug("已保存扫描到的玻璃ID到共享数据: glassId={}", glassId);
            }
        }
    }
    private GlassInfo buildGlassInfo(String glassId, Integer longSide, Integer shortSide, Integer workLine) {
    private DevicePlcVO.OperationResult clearPlc(DeviceConfig deviceConfig,
                                                 EnhancedS7Serializer serializer) {
        try {
            // 只清空PLC操作区字段(plcRequest、plcGlassId),不清空MES写区字段
            Map<String, Object> resetFields = new HashMap<>();
            resetFields.put("plcRequest", 0);
            resetFields.put("plcGlassId", "");
            plcDynamicDataService.writePlcData(deviceConfig, resetFields, serializer);
            return buildResult(deviceConfig, "clearPlc", true, "已清空PLC操作区字段(保留MES写区字段)", null);
        } catch (Exception e) {
            log.error("卧转立扫码清空PLC失败, deviceId={}", deviceConfig.getId(), e);
            return buildResult(deviceConfig, "clearPlc", false, "清空PLC失败: " + e.getMessage(), null);
        }
    }
    /**
     * 触发MES请求:写入plcRequest=1和plcGlassId(如果提供了玻璃ID)
     */
    private void triggerScanRequest(DeviceConfig deviceConfig, EnhancedS7Serializer serializer, String glassId) {
        Map<String, Object> writeFields = new HashMap<>();
        writeFields.put("plcRequest", 1);
        if (StringUtils.hasText(glassId)) {
            writeFields.put("plcGlassId", glassId);
        }
        plcDynamicDataService.writePlcData(deviceConfig, writeFields, serializer);
    }
    /**
     * 清空PLC请求字段:只清除plcRequest和plcGlassId(不清除MES写区字段)
     */
    private void clearPlcRequestFields(DeviceConfig deviceConfig, EnhancedS7Serializer serializer) {
        try {
            Map<String, Object> clearFields = new HashMap<>();
            clearFields.put("plcRequest", 0);
            clearFields.put("plcGlassId", "");
            plcDynamicDataService.writePlcData(deviceConfig, clearFields, serializer);
        } catch (Exception e) {
            log.error("清空PLC请求字段失败: deviceId={}", deviceConfig.getId(), e);
            // 不清空不影响主流程,只记录错误
        }
    }
    private Map<String, Object> waitForMesData(DeviceConfig deviceConfig,
                                               EnhancedS7Serializer serializer,
                                               WorkstationLogicConfig config) throws InterruptedException {
        long timeoutMs = Math.max(config.getScanIntervalMs(), 3_000);
        long deadline = System.currentTimeMillis() + timeoutMs;
        int pollInterval = Math.max(200, Math.min(config.getScanIntervalMs() / 5, 1_000));
        while (System.currentTimeMillis() < deadline) {
            Map<String, Object> mesData = plcDynamicDataService.readPlcData(deviceConfig, MES_FIELDS, serializer);
            if (mesData != null && !mesData.isEmpty()) {
                Integer mesSend = parseInteger(mesData.get("mesSend"));
                if (mesSend != null && mesSend == 1) {
                    log.info("检测到MES已写入数据: deviceId={}, mesSend=1, mesGlassId={}, mesWidth={}, mesHeight={}, workLine={}",
                            deviceConfig.getId(),
                            mesData.get("mesGlassId"),
                            mesData.get("mesWidth"),
                            mesData.get("mesHeight"),
                            mesData.get("workLine"));
                    return mesData;
                }
            }
            Thread.sleep(pollInterval);
        }
        // 超时前最后一次尝试读取
        log.warn("等待MES数据超时: deviceId={}, timeout={}ms", deviceConfig.getId(), timeoutMs);
        Map<String, Object> lastMesData = plcDynamicDataService.readPlcData(deviceConfig, MES_FIELDS, serializer);
        if (lastMesData != null && !lastMesData.isEmpty()) {
            log.warn("超时前最后一次读取到的数据: deviceId={}, mesData={}",
                    deviceConfig.getId(), lastMesData);
        }
        return Collections.emptyMap();
    }
    private GlassInfo buildGlassInfo(String glassId, Integer width, Integer height, Integer workLine) {
        GlassInfo glassInfo = new GlassInfo();
        glassInfo.setGlassId(glassId.trim());
        if (longSide != null) {
            glassInfo.setGlassLength(longSide);
        // mesWidth=表宽 -> glassWidth, mesHeight=长 -> glassLength
        if (width != null) {
            glassInfo.setGlassWidth(width);  // 表宽
        }
        if (shortSide != null) {
            glassInfo.setGlassWidth(shortSide);
        if (height != null) {
            glassInfo.setGlassLength(height); // 长
        }
        glassInfo.setStatus(GlassInfo.Status.ACTIVE);
        glassInfo.setStatus(GlassInfo.Status.PENDING);
        if (workLine != null) {
            glassInfo.setDescription("workLine=" + workLine);
        }
        Date now = new Date();
        glassInfo.setCreatedTime(now);
        glassInfo.setUpdatedTime(now);
        glassInfo.setCreatedBy("system");
        glassInfo.setUpdatedBy("system");
        return glassInfo;
    }
@@ -133,17 +304,11 @@
        return value == null ? null : String.valueOf(value).trim();
    }
    private Integer convertDimension(Integer raw) {
        if (raw == null) {
            return null;
        }
        return raw / 10;
    }
    private DevicePlcVO.OperationResult buildResult(DeviceConfig deviceConfig,
                                                    String operation,
                                                    boolean success,
                                                    String message) {
                                                    String message,
                                                    Map<String, Object> data) {
        return DevicePlcVO.OperationResult.builder()
                .deviceId(deviceConfig.getId())
                .deviceName(deviceConfig.getDeviceName())
@@ -153,6 +318,7 @@
                .success(success)
                .message(message)
                .timestamp(LocalDateTime.now())
                .data(data)
                .build();
    }
}