| | |
| | | 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; |
| | | |
| | |
| | | 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); |
| | | } |
| | | |
| | | // 读取到MES数据后,重置mesSend,避免重复消费 |
| | | plcDynamicDataService.writePlcField(deviceConfig, "mesSend", 0, serializer); |
| | | |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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()) |
| | |
| | | .success(success) |
| | | .message(message) |
| | | .timestamp(LocalDateTime.now()) |
| | | .data(data) |
| | | .build(); |
| | | } |
| | | } |