package com.mes.interaction.workstation.scanner.handler; import com.mes.device.entity.DeviceConfig; import com.mes.device.entity.GlassInfo; import com.mes.device.service.DevicePlcOperationService; import com.mes.device.service.GlassInfoService; import com.mes.device.vo.DevicePlcVO; import com.mes.interaction.workstation.base.WorkstationBaseHandler; import com.mes.interaction.workstation.config.WorkstationLogicConfig; 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; /** * 卧转立扫码设备逻辑处理器 * 负责从MES写区读取玻璃尺寸,并落库 glass_info */ @Slf4j @Component public class HorizontalScannerLogicHandler extends WorkstationBaseHandler { private static final List MES_FIELDS = Arrays.asList("mesSend", "mesGlassId", "mesWidth", "mesHeight", "workLine"); private final PlcDynamicDataService plcDynamicDataService; private final GlassInfoService glassInfoService; private final S7SerializerProvider s7SerializerProvider; public HorizontalScannerLogicHandler(DevicePlcOperationService devicePlcOperationService, PlcDynamicDataService plcDynamicDataService, GlassInfoService glassInfoService, S7SerializerProvider s7SerializerProvider) { super(devicePlcOperationService); this.plcDynamicDataService = plcDynamicDataService; this.glassInfoService = glassInfoService; this.s7SerializerProvider = s7SerializerProvider; } @Override public String getDeviceType() { return DeviceConfig.DeviceType.WORKSTATION_SCANNER; } @Override protected DevicePlcVO.OperationResult doExecute(DeviceConfig deviceConfig, String operation, Map params, Map logicParams) { EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); if (serializer == null) { 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 params) { try { // 从参数中获取玻璃ID(定时器每次只处理一个) String inputGlassId = null; if (params != null) { Object glassIdObj = params.get("glassId"); if (glassIdObj != null) { inputGlassId = String.valueOf(glassIdObj).trim(); } } // 执行单次扫描(定时器会循环调用此方法) 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, "scanOnce", false, "处理异常: " + e.getMessage(), null); } } /** * 执行单次扫描 */ private DevicePlcVO.OperationResult executeSingleScan(DeviceConfig deviceConfig, WorkstationLogicConfig config, EnhancedS7Serializer serializer, String inputGlassId, Map params) throws InterruptedException { // 1. 写入plcRequest=1和plcGlassId(如果提供了玻璃ID) triggerScanRequest(deviceConfig, serializer, inputGlassId); // 2. 等待MES回写mesSend=1以及玻璃信息 Map 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 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 params, String glassId) { if (params == null || !StringUtils.hasText(glassId)) { return; } Object contextObj = params.get("_taskContext"); if (contextObj instanceof TaskExecutionContext) { TaskExecutionContext context = (TaskExecutionContext) contextObj; List scannedGlassIds = (List) context.getSharedData() .computeIfAbsent("scannedGlassIds", k -> new java.util.ArrayList<>()); if (!scannedGlassIds.contains(glassId)) { scannedGlassIds.add(glassId); log.debug("已保存扫描到的玻璃ID到共享数据: glassId={}", glassId); } } } private DevicePlcVO.OperationResult clearPlc(DeviceConfig deviceConfig, EnhancedS7Serializer serializer) { try { // 只清空PLC操作区字段(plcRequest、plcGlassId),不清空MES写区字段 Map 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 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 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 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 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 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()); // mesWidth=表宽 -> glassWidth, mesHeight=长 -> glassLength if (width != null) { glassInfo.setGlassWidth(width); // 表宽 } if (height != null) { glassInfo.setGlassLength(height); // 长 } 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; } private Integer parseInteger(Object value) { if (value instanceof Number) { return ((Number) value).intValue(); } if (value == null) { return null; } try { return Integer.parseInt(String.valueOf(value)); } catch (NumberFormatException e) { return null; } } private String parseString(Object value) { return value == null ? null : String.valueOf(value).trim(); } private DevicePlcVO.OperationResult buildResult(DeviceConfig deviceConfig, String operation, boolean success, String message, Map data) { return DevicePlcVO.OperationResult.builder() .deviceId(deviceConfig.getId()) .deviceName(deviceConfig.getDeviceName()) .deviceCode(deviceConfig.getDeviceCode()) .projectId(deviceConfig.getProjectId() != null ? String.valueOf(deviceConfig.getProjectId()) : null) .operation(operation) .success(success) .message(message) .timestamp(LocalDateTime.now()) .data(data) .build(); } }