| | |
| | | package com.mes.interaction.vehicle.handler; |
| | | |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.entity.DeviceStatus; |
| | | import com.mes.device.service.DeviceConfigService; |
| | | import com.mes.device.service.DeviceGroupRelationService; |
| | | import com.mes.device.service.DevicePlcOperationService; |
| | | import com.mes.device.service.GlassInfoService; |
| | | import com.mes.device.service.DeviceStatusService; |
| | | import com.mes.device.vo.DeviceGroupVO; |
| | | import com.mes.device.vo.DevicePlcVO; |
| | | import com.mes.interaction.BaseDeviceLogicHandler; |
| | |
| | | |
| | | @Autowired(required = false) |
| | | private DeviceGroupRelationService deviceGroupRelationService; |
| | | |
| | | @Autowired(required = false) |
| | | private DeviceStatusService deviceStatusService; |
| | | |
| | | @Autowired(required = false) |
| | | private PlcDynamicDataService plcDynamicDataService; |
| | |
| | | case "stopTaskMonitor": |
| | | result = handleStopTaskMonitor(deviceConfig); |
| | | break; |
| | | case "setOnlineState": |
| | | result = handleSetOnlineState(deviceConfig, params, logicParams); |
| | | break; |
| | | default: |
| | | log.warn("不支持的操作类型: {}", operation); |
| | | result = DevicePlcVO.OperationResult.builder() |
| | |
| | | * 判断操作是否需要状态检查 |
| | | */ |
| | | private boolean needsStateCheck(String operation) { |
| | | // 所有操作都需要检查状态,除了查询类操作 |
| | | return !"query".equals(operation) && !"status".equals(operation); |
| | | // 所有操作都需要检查状态,除了查询类操作和特定内部检查 |
| | | if ("query".equals(operation) || "status".equals(operation)) { |
| | | return false; |
| | | } |
| | | if ("checkMesConfirm".equals(operation)) { |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // 从逻辑参数中获取配置(从 extraParams.deviceLogic 读取) |
| | | Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000); |
| | | // 优先使用运行时参数中的glassIntervalMs(从任务参数传入),如果没有则使用设备配置的 |
| | | Integer glassIntervalMs = null; |
| | | if (params.containsKey("glassIntervalMs") && params.get("glassIntervalMs") != null) { |
| | | Object intervalObj = params.get("glassIntervalMs"); |
| | | if (intervalObj instanceof Number) { |
| | | glassIntervalMs = ((Number) intervalObj).intValue(); |
| | | } else if (intervalObj instanceof String) { |
| | | try { |
| | | glassIntervalMs = Integer.parseInt((String) intervalObj); |
| | | } catch (NumberFormatException e) { |
| | | // 忽略 |
| | | } |
| | | } |
| | | } |
| | | if (glassIntervalMs == null) { |
| | | glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000); |
| | | } |
| | | Integer glassGap = getLogicParam(logicParams, "glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm |
| | | Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true); |
| | | Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5); |
| | | |
| | |
| | | Integer positionValue = (Integer) params.get("positionValue"); |
| | | Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed); |
| | | |
| | | List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, |
| | | List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, glassGap, |
| | | deviceConfig.getDeviceId()); |
| | | if (plannedGlasses == null) { |
| | | // 玻璃没有长度时返回null表示错误 |
| | |
| | | } |
| | | payload.put("plcGlassCount", plcSlots); |
| | | |
| | | // 写入位置信息 |
| | | // 写入位置信息:PLC侧期望的是 MES 编号(如1001/1002),而不是位置映射后的格子值 |
| | | Integer plcPosition = null; |
| | | if (positionValue != null) { |
| | | payload.put("inPosition", positionValue); |
| | | // 如果调用方直接传了数值,则认为这是MES编号,直接写入 |
| | | plcPosition = positionValue; |
| | | } else if (positionCode != null) { |
| | | // 从位置映射中获取位置值 |
| | | @SuppressWarnings("unchecked") |
| | | Map<String, Integer> positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>()); |
| | | Integer mappedValue = positionMapping.get(positionCode); |
| | | if (mappedValue != null) { |
| | | payload.put("inPosition", mappedValue); |
| | | // 尝试将位置代码解析为数字(例如 "900" -> 900) |
| | | try { |
| | | plcPosition = Integer.parseInt(positionCode.trim()); |
| | | } catch (NumberFormatException ignore) { |
| | | // 非数字编码时,不写入inPosition,由PLC或后续逻辑自行处理 |
| | | } |
| | | } |
| | | if (plcPosition != null) { |
| | | payload.put("inPosition", plcPosition); |
| | | } |
| | | |
| | | // 自动触发请求字 |
| | |
| | | DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( |
| | | deviceConfig.getId(), payload, operationName); |
| | | |
| | | // 注意:glassIntervalMs 的等待应该在批次之间(在TaskExecutionEngine中处理), |
| | | // 而不是在这里等待,因为这里等待会阻塞大车的正常装玻璃流程 |
| | | // 如果需要在写入后等待,应该在批次之间等待,让大车有时间处理当前批次的玻璃 |
| | | |
| | | // 如果执行成功,更新位置信息到状态,并启动状态监控 |
| | | // 如果执行成功,更新位置信息到状态 |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | VehicleStatus status = statusManager.getOrCreateVehicleStatus( |
| | | deviceConfig.getDeviceId(), deviceConfig.getDeviceName()); |
| | |
| | | VehiclePosition position = new VehiclePosition(positionCode, positionValue); |
| | | status.setCurrentPosition(position); |
| | | } |
| | | |
| | | // 启动自动状态监控,当 state=1 时自动协调卧转立设备 |
| | | startStateMonitoring(deviceConfig, logicParams); |
| | | |
| | | // 仅在“非多设备任务”场景下,才启动大车自身的自动状态监控和 MES 任务监控 |
| | | boolean inMultiDeviceTask = params != null && params.containsKey("_taskContext"); |
| | | if (!inMultiDeviceTask) { |
| | | // 启动自动状态监控,当 state=1 时自动协调卧转立设备 |
| | | startStateMonitoring(deviceConfig, logicParams); |
| | | |
| | | // 从 PLC/MES 创建正式任务并启动监控的逻辑,保留给独立 MES 场景使用 |
| | | // 多设备任务场景下,这部分交由 TaskExecutionEngine 统一编排 |
| | | } |
| | | } |
| | | |
| | | return result; |
| | |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcRequest", 0); |
| | | payload.put("plcReport", 0); |
| | | payload.put("onlineState", Boolean.TRUE); |
| | | |
| | | log.info("大车设备重置: deviceId={}", deviceConfig.getId()); |
| | | |
| | |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | stopStateMonitoring(deviceConfig.getDeviceId()); |
| | | updateDeviceOnlineStatus(deviceConfig, true); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 设置联机状态 |
| | | * @param deviceConfig 设备配置 |
| | | * @param params 参数,可包含 onlineState(1=联机,0=脱机) |
| | | * @param logicParams 逻辑参数 |
| | | * @return 操作结果 |
| | | */ |
| | | private DevicePlcVO.OperationResult handleSetOnlineState( |
| | | DeviceConfig deviceConfig, |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | // 从参数中获取联机状态值,默认为true(联机) |
| | | boolean onlineState = true; |
| | | if (params != null && params.containsKey("onlineState")) { |
| | | Object stateObj = params.get("onlineState"); |
| | | if (stateObj instanceof Boolean) { |
| | | onlineState = (Boolean) stateObj; |
| | | } else if (stateObj instanceof Number) { |
| | | onlineState = ((Number) stateObj).intValue() != 0; |
| | | } else if (stateObj instanceof String) { |
| | | try { |
| | | String str = ((String) stateObj).trim(); |
| | | if ("true".equalsIgnoreCase(str)) { |
| | | onlineState = true; |
| | | } else if ("false".equalsIgnoreCase(str)) { |
| | | onlineState = false; |
| | | } else { |
| | | onlineState = Integer.parseInt(str) != 0; |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | log.warn("解析onlineState失败,使用默认值true: deviceId={}, value={}", |
| | | deviceConfig.getId(), stateObj); |
| | | } |
| | | } |
| | | } |
| | | |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("onlineState", onlineState); |
| | | |
| | | String stateText = onlineState ? "联机" : "脱机"; |
| | | log.info("大车设备设置联机状态: deviceId={}, onlineState={} ({})", |
| | | deviceConfig.getId(), onlineState, stateText); |
| | | |
| | | DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( |
| | | deviceConfig.getId(), |
| | | payload, |
| | | "大车设备-设置联机状态(" + stateText + ")" |
| | | ); |
| | | |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | updateDeviceOnlineStatus(deviceConfig, onlineState); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | |
| | | payload.put("plcGlassCount", 0); |
| | | payload.put("plcRequest", 0); |
| | | payload.put("plcReport", 0); |
| | | payload.put("onlineState", Boolean.TRUE); |
| | | |
| | | if (params != null && params.containsKey("positionValue")) { |
| | | payload.put("inPosition", params.get("positionValue")); |
| | |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | stopStateMonitoring(deviceConfig.getDeviceId()); |
| | | updateDeviceOnlineStatus(deviceConfig, true); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | private void updateDeviceOnlineStatus(DeviceConfig deviceConfig, boolean online) { |
| | | if (deviceStatusService == null || deviceConfig == null || deviceConfig.getId() == null) { |
| | | return; |
| | | } |
| | | try { |
| | | String status = online ? DeviceStatus.Status.ONLINE : DeviceStatus.Status.OFFLINE; |
| | | deviceStatusService.updateDeviceOnlineStatus(deviceConfig.getId(), status); |
| | | } catch (Exception e) { |
| | | log.warn("同步设备在线状态到数据库失败: deviceId={}, online={}, error={}", |
| | | deviceConfig.getDeviceId(), online, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private List<String> resolveGlassSlotFields(Map<String, Object> logicParams, int fallbackCount) { |
| | |
| | | return "车辆容量(vehicleCapacity)必须大于0"; |
| | | } |
| | | |
| | | Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", null); |
| | | if (glassIntervalMs != null && glassIntervalMs < 0) { |
| | | return "玻璃间隔时间(glassIntervalMs)不能为负数"; |
| | | Integer glassGap = getLogicParam(logicParams, "glassGap", null); |
| | | if (glassGap != null && glassGap < 0) { |
| | | return "玻璃间隔(glassGap)不能为负数"; |
| | | } |
| | | |
| | | return null; // 验证通过 |
| | |
| | | public String getDefaultLogicParams() { |
| | | Map<String, Object> defaultParams = new HashMap<>(); |
| | | defaultParams.put("vehicleCapacity", 6000); |
| | | defaultParams.put("glassIntervalMs", 1000); |
| | | defaultParams.put("glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm |
| | | defaultParams.put("autoFeed", true); |
| | | defaultParams.put("maxRetryCount", 5); |
| | | defaultParams.put("defaultGlassLength", 2000); |
| | |
| | | /** |
| | | * 规划玻璃装载 |
| | | * @param source 源玻璃列表 |
| | | * @param vehicleCapacity 车辆容量 |
| | | * @param vehicleCapacity 车辆容量(mm) |
| | | * @param glassGap 玻璃之间的物理间隔(mm),默认200mm |
| | | * @param deviceId 设备ID(用于日志) |
| | | * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序) |
| | | */ |
| | | private List<GlassInfo> planGlassLoading(List<GlassInfo> source, |
| | | int vehicleCapacity, |
| | | int glassGap, |
| | | String deviceId) { |
| | | List<GlassInfo> planned = new ArrayList<>(); |
| | | int usedLength = 0; |
| | | int capacity = Math.max(vehicleCapacity, 1); |
| | | int gap = Math.max(glassGap, 0); // 确保间隔不为负数 |
| | | |
| | | for (GlassInfo info : source) { |
| | | Integer glassLength = info.getLength(); |
| | |
| | | int length = glassLength; |
| | | |
| | | if (planned.isEmpty()) { |
| | | // 第一块玻璃,不需要间隙 |
| | | planned.add(info.withLength(length)); |
| | | usedLength = length; |
| | | continue; |
| | | } |
| | | if (usedLength + length <= capacity) { |
| | | |
| | | // 后续玻璃需要考虑间隙:玻璃长度 + 间隙 |
| | | int requiredLength = length + gap; |
| | | if (usedLength + requiredLength <= capacity) { |
| | | planned.add(info.withLength(length)); |
| | | usedLength += length; |
| | | usedLength += requiredLength; // 包含间隙 |
| | | } else { |
| | | // 装不下了,停止添加 |
| | | break; |
| | | } |
| | | } |
| | | |
| | | log.debug("玻璃装载规划: deviceId={}, total={}, planned={}, usedLength={}, capacity={}, glassGap={}", |
| | | deviceId, source.size(), planned.size(), usedLength, capacity, gap); |
| | | |
| | | return planned; |
| | | } |
| | | |
| | |
| | | log.info("已给MES汇报({}任务): deviceId={}, glassId={}", |
| | | taskType, deviceConfig.getDeviceId(), taskInfo.glassId); |
| | | |
| | | // 等待MES确认 |
| | | waitForMesConfirm(deviceConfig, serializer, taskInfo, logicParams); |
| | | |
| | | // 多设备任务场景下,不在这里阻塞等待MES确认,由任务引擎定时调用checkMesConfirm |
| | | } catch (Exception e) { |
| | | log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 等待MES确认 |
| | | * 检查MES确认状态(供任务引擎周期性调用) |
| | | * 返回OperationResult.data中的 completed 标志表示是否已确认完成 |
| | | */ |
| | | private void waitForMesConfirm(DeviceConfig deviceConfig, |
| | | EnhancedS7Serializer serializer, |
| | | MesTaskInfo taskInfo, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | public DevicePlcVO.OperationResult checkMesConfirm(DeviceConfig deviceConfig, |
| | | Map<String, Object> logicParams) { |
| | | if (plcDynamicDataService == null || s7SerializerProvider == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("PlcDynamicDataService或S7SerializerProvider未注入") |
| | | .build(); |
| | | } |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("获取PLC序列化器失败") |
| | | .build(); |
| | | } |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | try { |
| | | // 读取确认字(假设字段名为mesConfirm) |
| | | Integer maxWaitTime = getLogicParam(logicParams, "mesConfirmTimeoutMs", 30000); // 默认30秒 |
| | | long startTime = System.currentTimeMillis(); |
| | | |
| | | while (System.currentTimeMillis() - startTime < maxWaitTime) { |
| | | Object confirmValue = plcDynamicDataService.readPlcField( |
| | | deviceConfig, "mesConfirm", serializer); |
| | | Integer confirm = parseInteger(confirmValue); |
| | | |
| | | if (confirm != null && confirm == 1) { |
| | | // MES已确认,清空state和汇报字 |
| | | clearTaskStates(deviceConfig, serializer); |
| | | |
| | | // 任务完成,恢复为空闲状态 |
| | | statusManager.updateVehicleStatus( |
| | | deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | |
| | | // 移除任务 |
| | | currentTasks.remove(deviceConfig.getDeviceId()); |
| | | |
| | | // 停止任务监控 |
| | | handleStopTaskMonitor(deviceConfig); |
| | | |
| | | // 恢复plcRequest=1(可以接收新任务) |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcRequest", 1); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | |
| | | log.info("MES任务已完成: deviceId={}, glassId={}", |
| | | deviceConfig.getDeviceId(), taskInfo.glassId); |
| | | return; |
| | | } |
| | | |
| | | Thread.sleep(500); // 等待500ms后重试 |
| | | Object confirmValue = plcDynamicDataService.readPlcField( |
| | | deviceConfig, "mesConfirm", serializer); |
| | | Integer confirm = parseInteger(confirmValue); |
| | | boolean completed = confirm != null && confirm == 1; |
| | | data.put("completed", completed); |
| | | |
| | | if (completed) { |
| | | // MES已确认,清空state和汇报字 |
| | | clearTaskStates(deviceConfig, serializer); |
| | | |
| | | // 任务完成,恢复为空闲状态 |
| | | statusManager.updateVehicleStatus( |
| | | deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | |
| | | // 移除任务记录(如果有) |
| | | currentTasks.remove(deviceConfig.getDeviceId()); |
| | | |
| | | // 停止任务监控 |
| | | handleStopTaskMonitor(deviceConfig); |
| | | |
| | | // 恢复plcRequest=1(可以接收新任务) |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcRequest", 1); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | |
| | | log.info("MES任务已确认完成: deviceId={}", deviceConfig.getDeviceId()); |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message("MES任务已确认完成") |
| | | .data(data) |
| | | .build(); |
| | | } |
| | | |
| | | log.warn("等待MES确认超时: deviceId={}, glassId={}", |
| | | deviceConfig.getDeviceId(), taskInfo.glassId); |
| | | |
| | | |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message("等待MES确认中") |
| | | .data(data) |
| | | .build(); |
| | | } catch (Exception e) { |
| | | log.error("等待MES确认异常: deviceId={}", deviceConfig.getDeviceId(), e); |
| | | log.error("检查MES确认状态异常: deviceId={}", deviceConfig.getDeviceId(), e); |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("检查MES确认状态异常: " + e.getMessage()) |
| | | .data(data) |
| | | .build(); |
| | | } |
| | | } |
| | | |