package com.mes.interaction.vehicle.handler;
|
|
import com.mes.device.entity.DeviceConfig;
|
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.vo.DeviceGroupVO;
|
import com.mes.device.vo.DevicePlcVO;
|
import com.mes.interaction.BaseDeviceLogicHandler;
|
import com.mes.interaction.vehicle.coordination.VehicleStatusManager;
|
import com.mes.interaction.vehicle.model.VehiclePosition;
|
import com.mes.interaction.vehicle.model.VehicleState;
|
import com.mes.interaction.vehicle.model.VehicleStatus;
|
import com.mes.interaction.vehicle.model.VehicleTask;
|
import com.mes.s7.enhanced.EnhancedS7Serializer;
|
import com.mes.s7.provider.S7SerializerProvider;
|
import com.mes.service.PlcDynamicDataService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.stereotype.Component;
|
|
import javax.annotation.PreDestroy;
|
import java.util.*;
|
import java.util.concurrent.*;
|
|
/**
|
* 大车设备逻辑处理器
|
* 所有大车设备实例共享这个处理器
|
* 集成多实例状态管理和协调功能
|
*
|
* @author huang
|
* @since 2025-11-21
|
*/
|
@Slf4j
|
@Component
|
public class LoadVehicleLogicHandler extends BaseDeviceLogicHandler {
|
|
private final GlassInfoService glassInfoService;
|
|
@Autowired
|
private VehicleStatusManager statusManager;
|
|
@Autowired(required = false)
|
private DeviceConfigService deviceConfigService;
|
|
@Autowired(required = false)
|
private DeviceGroupRelationService deviceGroupRelationService;
|
|
@Autowired(required = false)
|
private PlcDynamicDataService plcDynamicDataService;
|
|
@Autowired(required = false)
|
private S7SerializerProvider s7SerializerProvider;
|
|
// MES字段列表(进片和出片共用同一套协议)
|
private static final List<String> MES_FIELDS = Arrays.asList(
|
"mesSend", "mesGlassId", "mesWidth", "mesHeight",
|
"startSlot", "targetSlot", "workLine"
|
);
|
|
// 监控线程池:用于定期检查大车状态并协调卧转立设备
|
private final ScheduledExecutorService stateMonitorExecutor = Executors.newScheduledThreadPool(5, r -> {
|
Thread t = new Thread(r, "VehicleStateMonitor");
|
t.setDaemon(true);
|
return t;
|
});
|
|
// 空闲监控线程池:用于保持plcRequest=1
|
private final ScheduledExecutorService idleMonitorExecutor = Executors.newScheduledThreadPool(3, r -> {
|
Thread t = new Thread(r, "VehicleIdleMonitor");
|
t.setDaemon(true);
|
return t;
|
});
|
|
// 任务监控线程池:用于监控任务执行和状态切换
|
private final ScheduledExecutorService taskMonitorExecutor = Executors.newScheduledThreadPool(5, r -> {
|
Thread t = new Thread(r, "VehicleTaskMonitor");
|
t.setDaemon(true);
|
return t;
|
});
|
|
// 记录正在监控的设备:deviceId -> 监控任务
|
private final Map<String, ScheduledFuture<?>> monitoringTasks = new ConcurrentHashMap<>();
|
|
// 记录空闲监控任务:deviceId -> 空闲监控任务
|
private final Map<String, ScheduledFuture<?>> idleMonitoringTasks = new ConcurrentHashMap<>();
|
|
// 记录任务监控任务:deviceId -> 任务监控任务
|
private final Map<String, ScheduledFuture<?>> taskMonitoringTasks = new ConcurrentHashMap<>();
|
|
// 记录已协调的设备:deviceId -> 已协调的state字段集合(避免重复协调)
|
private final Map<String, List<String>> coordinatedStates = new ConcurrentHashMap<>();
|
|
// 记录当前任务:deviceId -> 任务信息
|
private final Map<String, MesTaskInfo> currentTasks = new ConcurrentHashMap<>();
|
|
@Autowired
|
public LoadVehicleLogicHandler(
|
DevicePlcOperationService devicePlcOperationService,
|
@Qualifier("deviceGlassInfoService") GlassInfoService glassInfoService) {
|
super(devicePlcOperationService);
|
this.glassInfoService = glassInfoService;
|
}
|
|
@Override
|
public String getDeviceType() {
|
return DeviceConfig.DeviceType.LOAD_VEHICLE;
|
}
|
|
@Override
|
protected DevicePlcVO.OperationResult doExecute(
|
DeviceConfig deviceConfig,
|
String operation,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
String deviceId = deviceConfig.getDeviceId();
|
log.info("执行大车设备操作: deviceId={}, deviceName={}, operation={}",
|
deviceId, deviceConfig.getDeviceName(), operation);
|
|
// 1. 检查这个设备实例的状态(对于需要状态检查的操作)
|
if (needsStateCheck(operation)) {
|
VehicleStatus status = statusManager.getVehicleStatus(deviceId);
|
if (status != null && !status.isAvailable()) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message(String.format("车辆 %s (%s) 当前状态为 %s,无法执行操作 %s",
|
deviceConfig.getDeviceName(),
|
deviceId,
|
status.getState(),
|
operation))
|
.build();
|
}
|
}
|
|
// 2. 标记为执行中(对于需要状态管理的操作)
|
if (needsStateManagement(operation)) {
|
statusManager.updateVehicleStatus(deviceId, deviceConfig.getDeviceName(), VehicleState.EXECUTING);
|
|
// 创建任务信息
|
VehicleTask task = createVehicleTask(deviceConfig, operation, params, logicParams);
|
statusManager.setVehicleTask(deviceId, task);
|
}
|
|
try {
|
// 3. 执行原有逻辑
|
DevicePlcVO.OperationResult result;
|
switch (operation) {
|
case "feedGlass":
|
result = handleFeedGlass(deviceConfig, params, logicParams);
|
break;
|
case "triggerRequest":
|
result = handleTriggerRequest(deviceConfig, params, logicParams);
|
break;
|
case "triggerReport":
|
result = handleTriggerReport(deviceConfig, params, logicParams);
|
break;
|
case "reset":
|
result = handleReset(deviceConfig, params, logicParams);
|
break;
|
case "clearGlass":
|
case "clearPlc":
|
case "clear":
|
result = handleClearGlass(deviceConfig, params, logicParams);
|
break;
|
case "checkStateAndCoordinate":
|
result = handleCheckStateAndCoordinate(deviceConfig, params, logicParams);
|
break;
|
case "startIdleMonitor":
|
result = handleStartIdleMonitor(deviceConfig, params, logicParams);
|
break;
|
case "stopIdleMonitor":
|
result = handleStopIdleMonitor(deviceConfig);
|
break;
|
case "checkMesTask":
|
result = handleCheckMesTask(deviceConfig, params, logicParams);
|
break;
|
case "checkMesOutboundTask":
|
// 出片任务也使用同一套协议,通过handleCheckMesTask处理
|
result = handleCheckMesTask(deviceConfig, params, logicParams);
|
break;
|
case "startTaskMonitor":
|
result = handleStartTaskMonitor(deviceConfig, params, logicParams);
|
break;
|
case "stopTaskMonitor":
|
result = handleStopTaskMonitor(deviceConfig);
|
break;
|
default:
|
log.warn("不支持的操作类型: {}", operation);
|
result = DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("不支持的操作: " + operation)
|
.build();
|
}
|
|
return result;
|
|
} catch (Exception e) {
|
log.error("执行大车设备操作异常: deviceId={}, operation={}", deviceId, operation, e);
|
// 发生异常时,将状态设置为错误
|
if (needsStateManagement(operation)) {
|
statusManager.updateVehicleStatus(deviceId, VehicleState.ERROR);
|
}
|
throw e;
|
} finally {
|
// 4. 执行完成后恢复为空闲状态(对于需要状态管理的操作)
|
if (needsStateManagement(operation)) {
|
// 注意:这里不立即设置为IDLE,因为实际执行可能需要时间
|
// 真正的状态更新应该在任务完成后通过回调或状态查询来更新
|
// 这里先保持EXECUTING状态,等待外部确认完成后再更新
|
log.debug("操作执行完成,保持执行中状态,等待外部确认: deviceId={}", deviceId);
|
}
|
}
|
}
|
|
/**
|
* 判断操作是否需要状态检查
|
*/
|
private boolean needsStateCheck(String operation) {
|
// 所有操作都需要检查状态,除了查询类操作
|
return !"query".equals(operation) && !"status".equals(operation);
|
}
|
|
/**
|
* 判断操作是否需要状态管理
|
*/
|
private boolean needsStateManagement(String operation) {
|
// feedGlass 需要状态管理,其他操作根据实际情况
|
return "feedGlass".equals(operation);
|
}
|
|
/**
|
* 创建车辆任务信息
|
*/
|
private VehicleTask createVehicleTask(
|
DeviceConfig deviceConfig,
|
String operation,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
VehicleTask task = new VehicleTask();
|
task.setTaskId(generateTaskId(deviceConfig.getDeviceId()));
|
task.setTaskName("大车设备-" + operation);
|
task.setOperation(operation);
|
|
// 从参数中提取位置信息
|
String positionCode = (String) params.get("positionCode");
|
Integer positionValue = (Integer) params.get("positionValue");
|
|
if (positionCode != null || positionValue != null) {
|
VehiclePosition position = new VehiclePosition(positionCode, positionValue);
|
task.getPlannedPath().setStartPosition(position);
|
task.getPlannedPath().setEndPosition(position);
|
}
|
|
// 从配置中获取速度(如果有)
|
Double speed = getLogicParam(logicParams, "vehicleSpeed", null);
|
if (speed != null) {
|
task.setSpeed(speed);
|
task.calculateEstimatedEndTime();
|
}
|
|
task.setParameters(new HashMap<>(params));
|
|
return task;
|
}
|
|
/**
|
* 生成任务ID
|
*/
|
private String generateTaskId(String deviceId) {
|
return "TASK_" + deviceId + "_" + System.currentTimeMillis();
|
}
|
|
/**
|
* 处理玻璃上料操作
|
*/
|
private DevicePlcVO.OperationResult handleFeedGlass(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
// 从逻辑参数中获取配置(从 extraParams.deviceLogic 读取)
|
Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000);
|
Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000);
|
Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true);
|
Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5);
|
|
// 从运行时参数中获取数据(从接口调用时传入)
|
List<GlassInfo> glassInfos = extractGlassInfos(params);
|
if (glassInfos.isEmpty()) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("未提供有效的玻璃信息")
|
.build();
|
}
|
|
String positionCode = (String) params.get("positionCode");
|
Integer positionValue = (Integer) params.get("positionValue");
|
Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed);
|
|
List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity,
|
deviceConfig.getDeviceId());
|
if (plannedGlasses == null) {
|
// 玻璃没有长度时返回null表示错误
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("玻璃信息缺少长度数据,无法进行容量计算。请检查MES程序是否正确提供玻璃长度。")
|
.build();
|
}
|
if (plannedGlasses.isEmpty()) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("当前玻璃尺寸超出车辆容量,无法装载")
|
.build();
|
}
|
|
// 构建写入数据
|
Map<String, Object> payload = new HashMap<>();
|
|
// 写入玻璃ID
|
int plcSlots = Math.min(plannedGlasses.size(), 6);
|
for (int i = 0; i < plcSlots; i++) {
|
String fieldName = "plcGlassId" + (i + 1);
|
payload.put(fieldName, plannedGlasses.get(i).getGlassId());
|
}
|
payload.put("plcGlassCount", plcSlots);
|
|
// 写入位置信息
|
if (positionValue != null) {
|
payload.put("inPosition", 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);
|
}
|
}
|
|
// 自动触发请求字
|
if (triggerRequest != null && triggerRequest) {
|
payload.put("plcRequest", 1);
|
}
|
|
String operationName = "大车设备-玻璃上料";
|
if (positionCode != null) {
|
operationName += "(" + positionCode + ")";
|
}
|
|
log.info("大车设备玻璃上料: deviceId={}, glassCount={}, position={}, plannedGlassIds={}",
|
deviceConfig.getId(), plcSlots, positionCode, plannedGlasses);
|
|
if (glassIntervalMs != null && glassIntervalMs > 0) {
|
try {
|
Thread.sleep(glassIntervalMs);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
}
|
}
|
|
DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
|
deviceConfig.getId(), payload, operationName);
|
|
// 如果执行成功,更新位置信息到状态,并启动状态监控
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
VehicleStatus status = statusManager.getOrCreateVehicleStatus(
|
deviceConfig.getDeviceId(), deviceConfig.getDeviceName());
|
if (positionCode != null || positionValue != null) {
|
VehiclePosition position = new VehiclePosition(positionCode, positionValue);
|
status.setCurrentPosition(position);
|
}
|
|
// 启动自动状态监控,当 state=1 时自动协调卧转立设备
|
startStateMonitoring(deviceConfig, logicParams);
|
}
|
|
return result;
|
}
|
|
/**
|
* 处理触发请求操作
|
*/
|
private DevicePlcVO.OperationResult handleTriggerRequest(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcRequest", 1);
|
|
log.info("大车设备触发请求: deviceId={}", deviceConfig.getId());
|
return devicePlcOperationService.writeFields(
|
deviceConfig.getId(),
|
payload,
|
"大车设备-触发请求"
|
);
|
}
|
|
/**
|
* 处理触发汇报操作
|
*/
|
private DevicePlcVO.OperationResult handleTriggerReport(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcReport", 1);
|
|
log.info("大车设备触发汇报: deviceId={}", deviceConfig.getId());
|
return devicePlcOperationService.writeFields(
|
deviceConfig.getId(),
|
payload,
|
"大车设备-触发汇报"
|
);
|
}
|
|
/**
|
* 处理重置操作
|
*/
|
private DevicePlcVO.OperationResult handleReset(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcRequest", 0);
|
payload.put("plcReport", 0);
|
|
log.info("大车设备重置: deviceId={}", deviceConfig.getId());
|
|
DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
|
deviceConfig.getId(),
|
payload,
|
"大车设备-重置"
|
);
|
|
// 重置时,清除任务并恢复为空闲状态,停止监控
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
statusManager.clearVehicleTask(deviceConfig.getDeviceId());
|
statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
|
stopStateMonitoring(deviceConfig.getDeviceId());
|
}
|
|
return result;
|
}
|
|
/**
|
* 清空PLC中的玻璃数据
|
*/
|
private DevicePlcVO.OperationResult handleClearGlass(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
Map<String, Object> payload = new HashMap<>();
|
|
int slotCount = getLogicParam(logicParams, "glassSlotCount", 6);
|
if (slotCount <= 0) {
|
slotCount = 6;
|
}
|
|
List<String> slotFields = resolveGlassSlotFields(logicParams, slotCount);
|
for (String field : slotFields) {
|
payload.put(field, "");
|
}
|
|
payload.put("plcGlassCount", 0);
|
payload.put("plcRequest", 0);
|
payload.put("plcReport", 0);
|
|
if (params != null && params.containsKey("positionValue")) {
|
payload.put("inPosition", params.get("positionValue"));
|
} else if (params != null && Boolean.TRUE.equals(params.get("clearPosition"))) {
|
payload.put("inPosition", 0);
|
}
|
|
log.info("清空大车设备PLC玻璃数据: deviceId={}, clearedSlots={}", deviceConfig.getId(), slotFields.size());
|
|
DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
|
deviceConfig.getId(),
|
payload,
|
"大车设备-清空玻璃数据"
|
);
|
|
// 清空后,恢复为空闲状态,停止监控
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
statusManager.clearVehicleTask(deviceConfig.getDeviceId());
|
statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
|
stopStateMonitoring(deviceConfig.getDeviceId());
|
}
|
|
return result;
|
}
|
|
private List<String> resolveGlassSlotFields(Map<String, Object> logicParams, int fallbackCount) {
|
List<String> fields = new ArrayList<>();
|
if (logicParams != null) {
|
Object slotFieldConfig = logicParams.get("glassSlotFields");
|
if (slotFieldConfig instanceof List) {
|
List<?> configured = (List<?>) slotFieldConfig;
|
for (Object item : configured) {
|
if (item != null) {
|
String fieldName = String.valueOf(item).trim();
|
if (!fieldName.isEmpty()) {
|
fields.add(fieldName);
|
}
|
}
|
}
|
}
|
}
|
|
if (fields.isEmpty()) {
|
for (int i = 1; i <= fallbackCount; i++) {
|
fields.add("plcGlassId" + i);
|
}
|
}
|
return fields;
|
}
|
|
@Override
|
public String validateLogicParams(DeviceConfig deviceConfig) {
|
Map<String, Object> logicParams = parseLogicParams(deviceConfig);
|
|
// 验证必填参数
|
Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", null);
|
if (vehicleCapacity == null || vehicleCapacity <= 0) {
|
return "车辆容量(vehicleCapacity)必须大于0";
|
}
|
|
Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", null);
|
if (glassIntervalMs != null && glassIntervalMs < 0) {
|
return "玻璃间隔时间(glassIntervalMs)不能为负数";
|
}
|
|
return null; // 验证通过
|
}
|
|
@Override
|
public String getDefaultLogicParams() {
|
Map<String, Object> defaultParams = new HashMap<>();
|
defaultParams.put("vehicleCapacity", 6000);
|
defaultParams.put("glassIntervalMs", 1000);
|
defaultParams.put("autoFeed", true);
|
defaultParams.put("maxRetryCount", 5);
|
defaultParams.put("defaultGlassLength", 2000);
|
|
// MES任务相关配置
|
defaultParams.put("vehicleSpeed", 1.0); // 车辆速度(格/秒,grid/s),默认1格/秒
|
defaultParams.put("minRange", 1); // 最小运动距离(格子)
|
defaultParams.put("maxRange", 100); // 最大运动距离(格子),例如100格
|
defaultParams.put("homePosition", 0); // 初始位置(格子)
|
defaultParams.put("idleMonitorIntervalMs", 2000); // 空闲监控间隔(毫秒)
|
defaultParams.put("taskMonitorIntervalMs", 1000); // 任务监控间隔(毫秒)
|
defaultParams.put("mesConfirmTimeoutMs", 30000); // MES确认超时(毫秒)
|
|
|
// gridPositionMapping: 格子编号到位置的映射表(可选)
|
// 如果不配置,则格子编号直接作为位置值
|
Map<String, Integer> gridPositionMapping = new HashMap<>();
|
defaultParams.put("gridPositionMapping", gridPositionMapping);
|
|
Map<String, Integer> positionMapping = new HashMap<>();
|
positionMapping.put("POS1", 1);
|
positionMapping.put("POS2", 2);
|
defaultParams.put("positionMapping", positionMapping);
|
|
try {
|
return objectMapper.writeValueAsString(defaultParams);
|
} catch (Exception e) {
|
log.error("生成默认逻辑参数失败", e);
|
return "{}";
|
}
|
}
|
|
@SuppressWarnings("unchecked")
|
private List<GlassInfo> extractGlassInfos(Map<String, Object> params) {
|
List<GlassInfo> result = new ArrayList<>();
|
Object rawGlassInfos = params.get("glassInfos");
|
if (rawGlassInfos instanceof List) {
|
List<?> list = (List<?>) rawGlassInfos;
|
for (Object item : list) {
|
GlassInfo info = convertToGlassInfo(item);
|
if (info != null) {
|
result.add(info);
|
}
|
}
|
}
|
|
if (result.isEmpty()) {
|
List<String> glassIds = (List<String>) params.get("glassIds");
|
if (glassIds != null && !glassIds.isEmpty()) {
|
// 从数据库查询玻璃尺寸
|
Map<String, Integer> lengthMap = glassInfoService.getGlassLengthMap(glassIds);
|
for (String glassId : glassIds) {
|
Integer length = lengthMap.get(glassId);
|
result.add(new GlassInfo(glassId, length));
|
}
|
log.debug("从数据库查询玻璃尺寸: glassIds={}, lengthMap={}", glassIds, lengthMap);
|
}
|
}
|
return result;
|
}
|
|
private GlassInfo convertToGlassInfo(Object source) {
|
if (source instanceof GlassInfo) {
|
return (GlassInfo) source;
|
}
|
if (source instanceof Map) {
|
Map<?, ?> map = (Map<?, ?>) source;
|
Object id = map.get("glassId");
|
if (id == null) {
|
id = map.get("id");
|
}
|
if (id == null) {
|
return null;
|
}
|
Integer length = parseLength(map.get("length"));
|
if (length == null) {
|
length = parseLength(map.get("size"));
|
}
|
return new GlassInfo(String.valueOf(id), length);
|
}
|
if (source instanceof String) {
|
return new GlassInfo((String) source, null);
|
}
|
return null;
|
}
|
|
private Integer parseLength(Object value) {
|
if (value instanceof Number) {
|
return ((Number) value).intValue();
|
}
|
if (value instanceof String) {
|
try {
|
return Integer.parseInt((String) value);
|
} catch (NumberFormatException ignored) {
|
}
|
}
|
return null;
|
}
|
|
/**
|
* 规划玻璃装载
|
* @param source 源玻璃列表
|
* @param vehicleCapacity 车辆容量
|
* @param deviceId 设备ID(用于日志)
|
* @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序)
|
*/
|
private List<GlassInfo> planGlassLoading(List<GlassInfo> source,
|
int vehicleCapacity,
|
String deviceId) {
|
List<GlassInfo> planned = new ArrayList<>();
|
int usedLength = 0;
|
int capacity = Math.max(vehicleCapacity, 1);
|
|
for (GlassInfo info : source) {
|
Integer glassLength = info.getLength();
|
|
if (glassLength == null || glassLength <= 0) {
|
// 玻璃没有长度,直接报错(用于测试MES程序)
|
log.error("玻璃[{}]缺少长度数据,无法进行容量计算。deviceId={},请检查MES程序是否正确提供玻璃长度。",
|
info.getGlassId(), deviceId);
|
return null;
|
}
|
|
int length = glassLength;
|
|
if (planned.isEmpty()) {
|
planned.add(info.withLength(length));
|
usedLength = length;
|
continue;
|
}
|
if (usedLength + length <= capacity) {
|
planned.add(info.withLength(length));
|
usedLength += length;
|
} else {
|
break;
|
}
|
}
|
return planned;
|
}
|
|
/**
|
* 启动状态监控
|
* 定期检查大车的 state1~6,当检测到 state=1 时自动协调卧转立设备
|
*/
|
private void startStateMonitoring(DeviceConfig deviceConfig, Map<String, Object> logicParams) {
|
String deviceId = deviceConfig.getDeviceId();
|
|
// 如果已经在监控,先停止旧的监控任务
|
stopStateMonitoring(deviceId);
|
|
// 获取监控配置
|
Integer monitorIntervalMs = getLogicParam(logicParams, "stateMonitorIntervalMs", 1000);
|
Integer monitorTimeoutMs = getLogicParam(logicParams, "stateMonitorTimeoutMs", 300000);
|
if (monitorIntervalMs == null || monitorIntervalMs <= 0) {
|
monitorIntervalMs = 1000;
|
}
|
if (monitorTimeoutMs == null || monitorTimeoutMs <= 0) {
|
monitorTimeoutMs = 300000;
|
}
|
final int finalMonitorIntervalMs = monitorIntervalMs;
|
final int finalMonitorTimeoutMs = monitorTimeoutMs;
|
|
// 初始化已协调状态记录
|
coordinatedStates.put(deviceId, new CopyOnWriteArrayList<>());
|
|
// 记录监控开始时间
|
final long startTime = System.currentTimeMillis();
|
|
// 启动监控任务
|
ScheduledFuture<?> future = stateMonitorExecutor.scheduleWithFixedDelay(() -> {
|
try {
|
// 检查超时
|
if (System.currentTimeMillis() - startTime > finalMonitorTimeoutMs) {
|
log.info("大车状态监控超时,停止监控: deviceId={}, timeout={}ms", deviceId, finalMonitorTimeoutMs);
|
stopStateMonitoring(deviceId);
|
return;
|
}
|
|
// 检查车辆是否还在执行任务
|
VehicleStatus status = statusManager.getVehicleStatus(deviceId);
|
if (status == null || status.getState() != VehicleState.EXECUTING) {
|
log.debug("大车状态已改变,停止监控: deviceId={}, state={}",
|
deviceId, status != null ? status.getState() : "null");
|
stopStateMonitoring(deviceId);
|
return;
|
}
|
|
// 执行状态检查和协调
|
checkAndCoordinateState(deviceConfig);
|
|
} catch (Exception e) {
|
log.error("大车状态监控异常: deviceId={}", deviceId, e);
|
}
|
}, finalMonitorIntervalMs, finalMonitorIntervalMs, TimeUnit.MILLISECONDS);
|
|
monitoringTasks.put(deviceId, future);
|
log.info("已启动大车状态监控: deviceId={}, interval={}ms, timeout={}ms",
|
deviceId, finalMonitorIntervalMs, finalMonitorTimeoutMs);
|
}
|
|
/**
|
* 停止状态监控
|
*/
|
private void stopStateMonitoring(String deviceId) {
|
ScheduledFuture<?> future = monitoringTasks.remove(deviceId);
|
if (future != null && !future.isCancelled()) {
|
future.cancel(false);
|
log.debug("已停止大车状态监控: deviceId={}", deviceId);
|
}
|
coordinatedStates.remove(deviceId);
|
}
|
|
/**
|
* 检查大车状态并协调卧转立设备(内部方法,由监控线程调用)
|
*/
|
private void checkAndCoordinateState(DeviceConfig deviceConfig) {
|
String deviceId = deviceConfig.getDeviceId();
|
List<String> alreadyCoordinated = coordinatedStates.get(deviceId);
|
if (alreadyCoordinated == null) {
|
alreadyCoordinated = new CopyOnWriteArrayList<>();
|
coordinatedStates.put(deviceId, alreadyCoordinated);
|
}
|
|
try {
|
// 读取 state1~6 字段
|
List<String> stateFields = Arrays.asList("state1", "state2", "state3", "state4", "state5", "state6");
|
Map<String, Object> stateValues = new HashMap<>();
|
|
// 从 PLC 读取状态字段
|
DevicePlcVO.StatusInfo statusInfo = devicePlcOperationService.readStatus(deviceConfig.getId());
|
if (statusInfo == null || statusInfo.getFieldValues() == null) {
|
return;
|
}
|
|
for (String field : stateFields) {
|
Object value = statusInfo.getFieldValues().get(field);
|
if (value != null) {
|
stateValues.put(field, value);
|
}
|
}
|
|
// 检查是否有任何一个 state 为 1,且尚未协调过
|
List<String> newStateOneFields = new ArrayList<>();
|
for (String field : stateFields) {
|
Integer stateValue = parseInteger(stateValues.get(field));
|
if (stateValue != null && stateValue == 1 && !alreadyCoordinated.contains(field)) {
|
newStateOneFields.add(field);
|
}
|
}
|
|
if (newStateOneFields.isEmpty()) {
|
return; // 没有新的 state=1,无需协调
|
}
|
|
log.info("检测到大车新的 state=1: deviceId={}, stateFields={}", deviceId, newStateOneFields);
|
|
// 查找同组的卧转立设备
|
List<DeviceConfig> transferDevices = findTransferDevicesInSameGroup(deviceConfig);
|
if (transferDevices.isEmpty()) {
|
log.warn("未找到同组的卧转立设备: deviceId={}", deviceId);
|
// 即使找不到设备,也标记为已协调,避免重复检查
|
alreadyCoordinated.addAll(newStateOneFields);
|
return;
|
}
|
|
// 将每个卧转立设备的 plcRequest 置 0
|
boolean allSuccess = true;
|
for (DeviceConfig transferDevice : transferDevices) {
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcRequest", 0);
|
|
DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
|
transferDevice.getId(),
|
payload,
|
"大车自动协调-清空卧转立请求"
|
);
|
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
log.info("已自动清空卧转立设备 plcRequest: vehicleDeviceId={}, transferDeviceId={}, transferDeviceName={}, stateFields={}",
|
deviceId, transferDevice.getId(), transferDevice.getDeviceName(), newStateOneFields);
|
} else {
|
log.warn("自动清空卧转立设备 plcRequest 失败: vehicleDeviceId={}, transferDeviceId={}, message={}",
|
deviceId, transferDevice.getId(), result.getMessage());
|
allSuccess = false;
|
}
|
}
|
|
// 标记为已协调(无论成功与否,避免重复协调)
|
if (allSuccess) {
|
alreadyCoordinated.addAll(newStateOneFields);
|
log.info("大车状态协调完成: deviceId={}, coordinatedStateFields={}", deviceId, newStateOneFields);
|
}
|
|
} catch (Exception e) {
|
log.error("检查大车状态并协调卧转立设备异常: deviceId={}", deviceId, e);
|
}
|
}
|
|
/**
|
* 检查大车状态并协调卧转立设备(手动调用接口)
|
* 当 state1~6 中任何一个变为 1(上车完成)时,将同组卧转立设备的 plcRequest 置 0
|
*/
|
private DevicePlcVO.OperationResult handleCheckStateAndCoordinate(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
try {
|
// 读取 state1~6 字段
|
List<String> stateFields = Arrays.asList("state1", "state2", "state3", "state4", "state5", "state6");
|
Map<String, Object> stateValues = new HashMap<>();
|
|
// 从 PLC 读取状态字段
|
DevicePlcVO.StatusInfo statusInfo = devicePlcOperationService.readStatus(deviceConfig.getId());
|
if (statusInfo != null && statusInfo.getFieldValues() != null) {
|
for (String field : stateFields) {
|
Object value = statusInfo.getFieldValues().get(field);
|
if (value != null) {
|
stateValues.put(field, value);
|
}
|
}
|
}
|
|
// 检查是否有任何一个 state 为 1
|
boolean hasStateOne = false;
|
List<String> stateOneFields = new ArrayList<>();
|
for (String field : stateFields) {
|
Integer stateValue = parseInteger(stateValues.get(field));
|
if (stateValue != null && stateValue == 1) {
|
hasStateOne = true;
|
stateOneFields.add(field);
|
}
|
}
|
|
if (!hasStateOne) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("当前无 state=1 的状态,无需协调")
|
.build();
|
}
|
|
log.info("检测到大车 state=1: deviceId={}, stateFields={}",
|
deviceConfig.getId(), stateOneFields);
|
|
// 查找同组的卧转立设备
|
List<DeviceConfig> transferDevices = findTransferDevicesInSameGroup(deviceConfig);
|
if (transferDevices.isEmpty()) {
|
log.warn("未找到同组的卧转立设备: deviceId={}", deviceConfig.getId());
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("未找到同组的卧转立设备,跳过协调")
|
.build();
|
}
|
|
// 将每个卧转立设备的 plcRequest 置 0
|
List<DevicePlcVO.OperationResult> results = new ArrayList<>();
|
for (DeviceConfig transferDevice : transferDevices) {
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcRequest", 0);
|
|
DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
|
transferDevice.getId(),
|
payload,
|
"大车协调-清空卧转立请求"
|
);
|
results.add(result);
|
|
if (Boolean.TRUE.equals(result.getSuccess())) {
|
log.info("已清空卧转立设备 plcRequest: transferDeviceId={}, transferDeviceName={}",
|
transferDevice.getId(), transferDevice.getDeviceName());
|
} else {
|
log.warn("清空卧转立设备 plcRequest 失败: transferDeviceId={}, message={}",
|
transferDevice.getId(), result.getMessage());
|
}
|
}
|
|
boolean allSuccess = results.stream()
|
.allMatch(r -> Boolean.TRUE.equals(r.getSuccess()));
|
|
return DevicePlcVO.OperationResult.builder()
|
.success(allSuccess)
|
.message(String.format("已协调 %d 个卧转立设备,成功: %d",
|
transferDevices.size(),
|
results.stream().mapToInt(r -> Boolean.TRUE.equals(r.getSuccess()) ? 1 : 0).sum()))
|
.build();
|
|
} catch (Exception e) {
|
log.error("检查大车状态并协调卧转立设备异常: deviceId={}", deviceConfig.getId(), e);
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("协调异常: " + e.getMessage())
|
.build();
|
}
|
}
|
|
/**
|
* 查找同组内的卧转立设备
|
*/
|
private List<DeviceConfig> findTransferDevicesInSameGroup(DeviceConfig vehicleDevice) {
|
List<DeviceConfig> transferDevices = new ArrayList<>();
|
|
if (deviceGroupRelationService == null || deviceConfigService == null) {
|
log.warn("DeviceGroupRelationService 或 DeviceConfigService 未注入,无法查找同组设备");
|
return transferDevices;
|
}
|
|
try {
|
// 获取大车所属的设备组
|
List<DeviceGroupVO.GroupInfo> groups = deviceGroupRelationService.getDeviceGroups(vehicleDevice.getId());
|
if (groups.isEmpty()) {
|
log.debug("大车设备未加入任何设备组: deviceId={}", vehicleDevice.getId());
|
return transferDevices;
|
}
|
|
// 遍历所有设备组,查找卧转立设备
|
for (DeviceGroupVO.GroupInfo group : groups) {
|
List<DeviceGroupVO.DeviceInfo> groupDevices = deviceGroupRelationService.getGroupDevices(group.getId());
|
for (DeviceGroupVO.DeviceInfo deviceInfo : groupDevices) {
|
// 检查是否为卧转立设备
|
if (DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(deviceInfo.getDeviceType())) {
|
DeviceConfig transferDevice = deviceConfigService.getDeviceById(deviceInfo.getId());
|
if (transferDevice != null) {
|
transferDevices.add(transferDevice);
|
}
|
}
|
}
|
}
|
|
log.debug("找到同组卧转立设备: vehicleDeviceId={}, transferDeviceCount={}",
|
vehicleDevice.getId(), transferDevices.size());
|
|
} catch (Exception e) {
|
log.error("查找同组卧转立设备异常: vehicleDeviceId={}", vehicleDevice.getId(), e);
|
}
|
|
return transferDevices;
|
}
|
|
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 static class GlassInfo {
|
private final String glassId;
|
private final Integer length;
|
|
GlassInfo(String glassId, Integer length) {
|
this.glassId = glassId;
|
this.length = length;
|
}
|
|
public String getGlassId() {
|
return glassId;
|
}
|
|
public Integer getLength() {
|
return length;
|
}
|
|
public GlassInfo withLength(Integer newLength) {
|
return new GlassInfo(this.glassId, newLength);
|
}
|
|
@Override
|
public String toString() {
|
return glassId;
|
}
|
|
@Override
|
public int hashCode() {
|
return Objects.hash(glassId, length);
|
}
|
|
@Override
|
public boolean equals(Object obj) {
|
if (this == obj) return true;
|
if (obj == null || getClass() != obj.getClass()) return false;
|
GlassInfo other = (GlassInfo) obj;
|
return Objects.equals(glassId, other.glassId) && Objects.equals(length, other.length);
|
}
|
}
|
|
/**
|
* 启动空闲监控(没有任务时,plcRequest一直保持为1)
|
*/
|
private DevicePlcVO.OperationResult handleStartIdleMonitor(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
String deviceId = deviceConfig.getDeviceId();
|
|
// 停止旧的监控任务
|
handleStopIdleMonitor(deviceConfig);
|
|
// 获取监控间隔
|
Integer monitorIntervalMs = getLogicParam(logicParams, "idleMonitorIntervalMs", 2000); // 默认2秒
|
|
// 启动监控任务
|
ScheduledFuture<?> future = idleMonitorExecutor.scheduleWithFixedDelay(() -> {
|
try {
|
// 检查是否有任务在执行
|
VehicleStatus status = statusManager.getVehicleStatus(deviceId);
|
if (status != null && status.getState() == VehicleState.EXECUTING) {
|
// 有任务在执行,不设置plcRequest
|
return;
|
}
|
|
// 检查是否有待处理的进片或出片任务
|
if (plcDynamicDataService != null && s7SerializerProvider != null) {
|
EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
|
if (serializer != null) {
|
// 检查进片任务
|
Map<String, Object> mesData = plcDynamicDataService.readPlcData(
|
deviceConfig, MES_FIELDS, serializer);
|
Integer mesSend = parseInteger(mesData != null ? mesData.get("mesSend") : null);
|
|
// 进片和出片共用mesSend字段,只需检查一次
|
// 如果有待处理的任务,不设置plcRequest(等待任务处理)
|
if (mesSend != null && mesSend == 1) {
|
log.debug("大车空闲监控: deviceId={}, 检测到待处理任务(mesSend=1),不设置plcRequest", deviceId);
|
return;
|
}
|
}
|
}
|
|
// 没有任务,保持plcRequest=1
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcRequest", 1);
|
|
devicePlcOperationService.writeFields(deviceConfig.getId(), payload, "大车空闲监控-保持请求");
|
log.debug("大车空闲监控: deviceId={}, 保持plcRequest=1", deviceId);
|
|
} catch (Exception e) {
|
log.error("大车空闲监控异常: deviceId={}", deviceId, e);
|
}
|
}, monitorIntervalMs, monitorIntervalMs, TimeUnit.MILLISECONDS);
|
|
idleMonitoringTasks.put(deviceId, future);
|
log.info("已启动大车空闲监控: deviceId={}, interval={}ms", deviceId, monitorIntervalMs);
|
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("空闲监控已启动")
|
.build();
|
}
|
|
/**
|
* 停止空闲监控
|
*/
|
private DevicePlcVO.OperationResult handleStopIdleMonitor(DeviceConfig deviceConfig) {
|
String deviceId = deviceConfig.getDeviceId();
|
ScheduledFuture<?> future = idleMonitoringTasks.remove(deviceId);
|
if (future != null && !future.isCancelled()) {
|
future.cancel(false);
|
log.info("已停止大车空闲监控: deviceId={}", deviceId);
|
}
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("空闲监控已停止")
|
.build();
|
}
|
|
/**
|
* 检查MES任务(当mesSend=1时,读取MES参数并创建任务)
|
* 进片和出片共用同一套协议字段,通过位置信息判断任务类型
|
*/
|
private DevicePlcVO.OperationResult handleCheckMesTask(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
if (plcDynamicDataService == null || s7SerializerProvider == null) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("PlcDynamicDataService或S7SerializerProvider未注入")
|
.build();
|
}
|
|
String deviceId = deviceConfig.getDeviceId();
|
EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
|
if (serializer == null) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("获取PLC序列化器失败")
|
.build();
|
}
|
|
try {
|
// 读取MES字段(进片和出片共用)
|
Map<String, Object> mesData = plcDynamicDataService.readPlcData(
|
deviceConfig, MES_FIELDS, serializer);
|
if (mesData == null || mesData.isEmpty()) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("读取MES字段失败")
|
.build();
|
}
|
|
Integer mesSend = parseInteger(mesData.get("mesSend"));
|
if (mesSend == null || mesSend == 0) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("暂无MES任务(mesSend=0)")
|
.build();
|
}
|
|
// mesSend=1,读取任务参数
|
String glassId = parseString(mesData.get("mesGlassId"));
|
Integer startSlot = parseInteger(mesData.get("startSlot")); // 起始位置编号
|
Integer targetSlot = parseInteger(mesData.get("targetSlot")); // 目标位置编号
|
Integer workLine = parseInteger(mesData.get("workLine"));
|
|
if (glassId == null || glassId.isEmpty()) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("MES未提供玻璃ID")
|
.build();
|
}
|
|
// 判断是进片还是出片任务
|
// 方法:通过startSlot判断
|
// - 如果startSlot是卧转立编号(如900/901),则是进片任务
|
// - 如果startSlot是格子编号(在大理片笼范围内),则是出片任务
|
boolean isOutbound = isOutboundTask(startSlot, logicParams);
|
|
// 位置映射
|
Integer startPosition;
|
if (isOutbound) {
|
// 出片任务:startSlot是格子编号,需要映射到实际位置
|
startPosition = mapOutboundPosition(startSlot, logicParams);
|
} else {
|
// 进片任务:startSlot是卧转立编号,通过positionMapping映射
|
startPosition = mapPosition(startSlot, logicParams);
|
}
|
|
// targetSlot统一通过positionMapping映射
|
Integer targetPosition = mapPosition(targetSlot, logicParams);
|
|
if (startPosition == null || targetPosition == null) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message(String.format("位置映射失败: startSlot=%s, targetSlot=%s, isOutbound=%s",
|
startSlot, targetSlot, isOutbound))
|
.build();
|
}
|
|
// 读取当前位置
|
Integer currentPosition = getCurrentPosition(deviceConfig, logicParams);
|
|
// 计算时间
|
TimeCalculation timeCalc = calculateTime(
|
currentPosition, startPosition, targetPosition, logicParams);
|
|
// 创建任务信息
|
MesTaskInfo taskInfo = new MesTaskInfo();
|
taskInfo.glassId = glassId;
|
taskInfo.startSlot = startSlot;
|
taskInfo.targetSlot = targetSlot;
|
taskInfo.startPosition = startPosition;
|
taskInfo.targetPosition = targetPosition;
|
taskInfo.currentPosition = currentPosition;
|
taskInfo.gotime = timeCalc.gotime;
|
taskInfo.cartime = timeCalc.cartime;
|
taskInfo.workLine = workLine;
|
taskInfo.createdTime = System.currentTimeMillis();
|
taskInfo.isOutbound = isOutbound;
|
|
currentTasks.put(deviceId, taskInfo);
|
|
// 清空plcRequest(表示已接收任务)
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcRequest", 0);
|
plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
|
|
// 更新车辆状态为执行中
|
statusManager.updateVehicleStatus(deviceId, deviceConfig.getDeviceName(), VehicleState.EXECUTING);
|
|
// 启动任务监控
|
handleStartTaskMonitor(deviceConfig, params, logicParams);
|
|
String taskType = isOutbound ? "出片" : "进片";
|
log.info("MES{}任务已创建: deviceId={}, glassId={}, startSlot={}(位置{}格), targetSlot={}(位置{}格), 距离{}格->{}格, gotime={}ms({}秒), cartime={}ms({}秒)",
|
taskType, deviceId, glassId, startSlot, startPosition, targetSlot, targetPosition,
|
Math.abs(startPosition - currentPosition), Math.abs(targetPosition - startPosition),
|
timeCalc.gotime, timeCalc.gotime / 1000.0, timeCalc.cartime, timeCalc.cartime / 1000.0);
|
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message(String.format("MES%s任务已创建: glassId=%s, start=%d, target=%d",
|
taskType, glassId, startPosition, targetPosition))
|
.build();
|
|
} catch (Exception e) {
|
log.error("检查MES任务异常: deviceId={}", deviceId, e);
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("处理异常: " + e.getMessage())
|
.build();
|
}
|
}
|
|
/**
|
* 判断是否为出片任务
|
* 通过startSlot判断:
|
* - 如果startSlot在positionMapping中,且不在大理片笼格子范围内,则是进片任务
|
* - 如果startSlot不在positionMapping中,或在大理片笼格子范围内,则是出片任务
|
*/
|
private boolean isOutboundTask(Integer startSlot, Map<String, Object> logicParams) {
|
if (startSlot == null) {
|
return false;
|
}
|
|
// 方法1:检查startSlot是否在positionMapping中(卧转立编号)
|
@SuppressWarnings("unchecked")
|
Map<String, Integer> positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>());
|
if (positionMapping.containsKey(String.valueOf(startSlot))) {
|
// startSlot在positionMapping中,说明是卧转立编号,是进片任务
|
return false;
|
}
|
|
// 方法2:检查startSlot是否在大理片笼格子范围内
|
// 通过查找同组的大理片笼设备,检查格子范围
|
// 这里简化处理:如果startSlot不在positionMapping中,且是数字,可能是格子编号
|
// 可以通过配置指定格子编号范围,或者通过查找同组设备判断
|
|
// 方法3:通过配置指定车辆运动格子范围(兼容旧配置outboundSlotRanges)
|
@SuppressWarnings("unchecked")
|
List<Integer> vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null);
|
if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) {
|
// 兼容旧配置
|
vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null);
|
}
|
if (vehicleSlotRange != null && !vehicleSlotRange.isEmpty()) {
|
// 如果配置了车辆运动格子范围,检查startSlot是否在范围内
|
// 例如:[1, 101] 表示车辆只能在格子1~101之间运动
|
if (vehicleSlotRange.size() >= 2) {
|
int minSlot = vehicleSlotRange.get(0);
|
int maxSlot = vehicleSlotRange.get(1);
|
if (startSlot >= minSlot && startSlot <= maxSlot) {
|
return true;
|
}
|
}
|
}
|
|
// 默认:如果startSlot不在positionMapping中,且是较小的数字(可能是格子编号),判断为出片
|
// 这里可以根据实际需求调整判断逻辑
|
// 暂时:如果startSlot不在positionMapping中,判断为出片任务
|
return true;
|
}
|
|
|
/**
|
* 映射出片源位置(格子编号转换为实际位置)
|
* 通过查找同组的大理片笼设备配置,将格子编号转换为位置
|
*/
|
private Integer mapOutboundPosition(Integer gridNumber, Map<String, Object> logicParams) {
|
if (gridNumber == null) {
|
return null;
|
}
|
|
// 方法1:如果配置了格子到位置的映射表
|
@SuppressWarnings("unchecked")
|
Map<String, Integer> gridPositionMapping = getLogicParam(logicParams, "gridPositionMapping", new HashMap<>());
|
Integer position = gridPositionMapping.get(String.valueOf(gridNumber));
|
if (position != null) {
|
return position;
|
}
|
|
// 方法2:格子编号直接作为位置(如果格子编号就是位置值)
|
// 例如:格子1对应位置1格,格子52对应位置52格
|
// 这里可以根据实际需求调整映射逻辑
|
|
// 暂时直接使用格子编号作为位置
|
log.debug("使用格子编号作为位置: gridNumber={}", gridNumber);
|
return gridNumber;
|
}
|
|
/**
|
* 位置映射:将MES给的编号(如900/901)转换为实际位置值(如100/500)
|
*/
|
private Integer mapPosition(Integer slotNumber, Map<String, Object> logicParams) {
|
if (slotNumber == null) {
|
return null;
|
}
|
|
// 从配置中获取位置映射表
|
@SuppressWarnings("unchecked")
|
Map<String, Integer> positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>());
|
|
// 查找映射
|
Integer position = positionMapping.get(String.valueOf(slotNumber));
|
if (position != null) {
|
return position;
|
}
|
|
// 如果没有配置映射,尝试直接使用编号
|
log.warn("位置映射未找到: slotNumber={}, 使用编号作为位置值", slotNumber);
|
return slotNumber;
|
}
|
|
/**
|
* 获取当前位置
|
*/
|
private Integer getCurrentPosition(DeviceConfig deviceConfig, Map<String, Object> logicParams) {
|
// 从状态管理器获取
|
VehicleStatus status = statusManager.getVehicleStatus(deviceConfig.getDeviceId());
|
if (status != null && status.getCurrentPosition() != null) {
|
return status.getCurrentPosition().getPositionValue();
|
}
|
|
// 从配置中获取默认位置
|
return getLogicParam(logicParams, "homePosition", 0);
|
}
|
|
/**
|
* 时间计算:根据速度、当前位置、目标位置计算gotime和cartime
|
* 速度单位:格/秒(grid/s)
|
* 位置和距离单位:格子(grid)
|
*/
|
private TimeCalculation calculateTime(Integer currentPos, Integer startPos,
|
Integer targetPos, Map<String, Object> logicParams) {
|
// 验证车辆运动格子范围
|
@SuppressWarnings("unchecked")
|
List<Integer> vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null);
|
if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) {
|
// 兼容旧配置
|
vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null);
|
}
|
if (vehicleSlotRange != null && vehicleSlotRange.size() >= 2) {
|
int minSlot = vehicleSlotRange.get(0);
|
int maxSlot = vehicleSlotRange.get(1);
|
// 验证startPos和targetPos是否在允许的范围内
|
if (startPos != null && (startPos < minSlot || startPos > maxSlot)) {
|
log.warn("起始位置 {} 超出车辆运动格子范围 [{}, {}]", startPos, minSlot, maxSlot);
|
}
|
if (targetPos != null && (targetPos < minSlot || targetPos > maxSlot)) {
|
log.warn("目标位置 {} 超出车辆运动格子范围 [{}, {}]", targetPos, minSlot, maxSlot);
|
}
|
}
|
|
// 获取速度(格/秒,grid/s)
|
Double speed = getLogicParam(logicParams, "vehicleSpeed", 1.0);
|
if (speed == null || speed <= 0) {
|
speed = 1.0; // 默认1格/秒
|
}
|
|
// 获取运动距离范围(格子)
|
Integer minRange = getLogicParam(logicParams, "minRange", 1);
|
Integer maxRange = getLogicParam(logicParams, "maxRange", 100);
|
|
// 计算gotime:从当前位置到起始位置的时间(毫秒)
|
// 公式:时间(ms) = 距离(格) / 速度(格/秒) * 1000
|
long gotime = 0;
|
if (currentPos != null && startPos != null) {
|
int distance = Math.abs(startPos - currentPos); // 距离(格子)
|
// 限制在范围内
|
distance = Math.max(minRange, Math.min(maxRange, distance));
|
gotime = (long) (distance / speed * 1000); // 转换为毫秒
|
}
|
|
// 计算cartime:从起始位置到目标位置的时间(毫秒)
|
// 公式:时间(ms) = 距离(格) / 速度(格/秒) * 1000
|
long cartime = 0;
|
if (startPos != null && targetPos != null) {
|
int distance = Math.abs(targetPos - startPos); // 距离(格子)
|
// 限制在范围内
|
distance = Math.max(minRange, Math.min(maxRange, distance));
|
cartime = (long) (distance / speed * 1000); // 转换为毫秒
|
}
|
|
return new TimeCalculation(gotime, cartime);
|
}
|
|
/**
|
* 启动任务监控(监控state状态切换和任务完成)
|
*/
|
private DevicePlcVO.OperationResult handleStartTaskMonitor(
|
DeviceConfig deviceConfig,
|
Map<String, Object> params,
|
Map<String, Object> logicParams) {
|
|
String deviceId = deviceConfig.getDeviceId();
|
|
// 停止旧的监控任务
|
handleStopTaskMonitor(deviceConfig);
|
|
MesTaskInfo taskInfo = currentTasks.get(deviceId);
|
if (taskInfo == null) {
|
return DevicePlcVO.OperationResult.builder()
|
.success(false)
|
.message("没有正在执行的任务")
|
.build();
|
}
|
|
// 获取监控间隔
|
Integer monitorIntervalMs = getLogicParam(logicParams, "taskMonitorIntervalMs", 1000); // 默认1秒
|
|
// 启动监控任务
|
ScheduledFuture<?> future = taskMonitorExecutor.scheduleWithFixedDelay(() -> {
|
try {
|
monitorTaskExecution(deviceConfig, taskInfo, logicParams);
|
} catch (Exception e) {
|
log.error("任务监控异常: deviceId={}", deviceId, e);
|
}
|
}, monitorIntervalMs, monitorIntervalMs, TimeUnit.MILLISECONDS);
|
|
taskMonitoringTasks.put(deviceId, future);
|
log.info("已启动任务监控: deviceId={}, interval={}ms", deviceId, monitorIntervalMs);
|
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("任务监控已启动")
|
.build();
|
}
|
|
/**
|
* 监控任务执行
|
*/
|
private void monitorTaskExecution(DeviceConfig deviceConfig,
|
MesTaskInfo taskInfo,
|
Map<String, Object> logicParams) {
|
|
if (plcDynamicDataService == null || s7SerializerProvider == null) {
|
return;
|
}
|
|
String deviceId = deviceConfig.getDeviceId();
|
EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
|
if (serializer == null) {
|
return;
|
}
|
|
try {
|
// 读取state1~6
|
List<String> stateFields = Arrays.asList("state1", "state2", "state3", "state4", "state5", "state6");
|
Map<String, Object> stateValues = new HashMap<>();
|
|
DevicePlcVO.StatusInfo statusInfo = devicePlcOperationService.readStatus(deviceConfig.getId());
|
if (statusInfo != null && statusInfo.getFieldValues() != null) {
|
for (String field : stateFields) {
|
Object value = statusInfo.getFieldValues().get(field);
|
if (value != null) {
|
stateValues.put(field, value);
|
}
|
}
|
}
|
|
// 根据时间计算更新state状态
|
long currentTime = System.currentTimeMillis();
|
long elapsed = currentTime - taskInfo.createdTime;
|
|
// 计算状态切换时间点
|
long state1Time = taskInfo.gotime; // 到达起始位置,上车完成
|
long state2Time = taskInfo.gotime + taskInfo.cartime; // 到达目标位置,运输完成
|
|
// 更新state状态
|
if (elapsed >= state1Time && elapsed < state2Time) {
|
// state应该为1
|
if (taskInfo.isOutbound) {
|
// 出片任务:到达源位置(大理片笼),取片完成
|
updateStateIfNeeded(deviceConfig, serializer, stateValues, 1, taskInfo);
|
} else {
|
// 进片任务:到达起始位置(卧转立),上车完成
|
updateStateIfNeeded(deviceConfig, serializer, stateValues, 1, taskInfo);
|
}
|
} else if (elapsed >= state2Time) {
|
// state应该为2(运输完成)
|
updateStateIfNeeded(deviceConfig, serializer, stateValues, 2, taskInfo);
|
|
// 检查是否所有state都>=2,如果是则给MES汇报
|
if (allStatesCompleted(stateValues)) {
|
reportToMes(deviceConfig, serializer, taskInfo, logicParams);
|
}
|
}
|
|
} catch (Exception e) {
|
log.error("监控任务执行异常: deviceId={}", deviceId, e);
|
}
|
}
|
|
/**
|
* 更新state状态(如果需要)
|
*/
|
private void updateStateIfNeeded(DeviceConfig deviceConfig,
|
EnhancedS7Serializer serializer,
|
Map<String, Object> currentStates,
|
int targetState,
|
MesTaskInfo taskInfo) {
|
|
// 这里可以根据实际需求更新state字段
|
// 暂时只记录日志,实际更新可能需要根据具体PLC字段配置
|
log.debug("任务状态更新: deviceId={}, targetState={}",
|
deviceConfig.getDeviceId(), targetState);
|
}
|
|
/**
|
* 检查是否所有state都已完成(>=2)
|
*/
|
private boolean allStatesCompleted(Map<String, Object> stateValues) {
|
for (Object value : stateValues.values()) {
|
Integer state = parseInteger(value);
|
if (state == null || state < 2) {
|
return false;
|
}
|
}
|
return !stateValues.isEmpty();
|
}
|
|
/**
|
* 给MES汇报
|
*/
|
private void reportToMes(DeviceConfig deviceConfig,
|
EnhancedS7Serializer serializer,
|
MesTaskInfo taskInfo,
|
Map<String, Object> logicParams) {
|
|
try {
|
// 设置汇报字
|
Map<String, Object> payload = new HashMap<>();
|
payload.put("plcReport", 1);
|
plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
|
|
String taskType = taskInfo.isOutbound ? "出片" : "进片";
|
log.info("已给MES汇报({}任务): deviceId={}, glassId={}",
|
taskType, deviceConfig.getDeviceId(), taskInfo.glassId);
|
|
// 等待MES确认
|
waitForMesConfirm(deviceConfig, serializer, taskInfo, logicParams);
|
|
} catch (Exception e) {
|
log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e);
|
}
|
}
|
|
/**
|
* 等待MES确认
|
*/
|
private void waitForMesConfirm(DeviceConfig deviceConfig,
|
EnhancedS7Serializer serializer,
|
MesTaskInfo taskInfo,
|
Map<String, Object> logicParams) {
|
|
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后重试
|
}
|
|
log.warn("等待MES确认超时: deviceId={}, glassId={}",
|
deviceConfig.getDeviceId(), taskInfo.glassId);
|
|
} catch (Exception e) {
|
log.error("等待MES确认异常: deviceId={}", deviceConfig.getDeviceId(), e);
|
}
|
}
|
|
/**
|
* 清空任务状态
|
*/
|
private void clearTaskStates(DeviceConfig deviceConfig, EnhancedS7Serializer serializer) {
|
try {
|
Map<String, Object> payload = new HashMap<>();
|
// 清空state1~6
|
for (int i = 1; i <= 6; i++) {
|
payload.put("state" + i, 0);
|
}
|
// 清空汇报字
|
payload.put("plcReport", 0);
|
plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
|
} catch (Exception e) {
|
log.error("清空任务状态异常: deviceId={}", deviceConfig.getDeviceId(), e);
|
}
|
}
|
|
/**
|
* 停止任务监控
|
*/
|
private DevicePlcVO.OperationResult handleStopTaskMonitor(DeviceConfig deviceConfig) {
|
String deviceId = deviceConfig.getDeviceId();
|
ScheduledFuture<?> future = taskMonitoringTasks.remove(deviceId);
|
if (future != null && !future.isCancelled()) {
|
future.cancel(false);
|
log.info("已停止任务监控: deviceId={}", deviceId);
|
}
|
return DevicePlcVO.OperationResult.builder()
|
.success(true)
|
.message("任务监控已停止")
|
.build();
|
}
|
|
/**
|
* 时间计算结果
|
*/
|
private static class TimeCalculation {
|
final long gotime; // 到起始位置的时间(毫秒)
|
final long cartime; // 从起始到目标位置的时间(毫秒)
|
|
TimeCalculation(long gotime, long cartime) {
|
this.gotime = gotime;
|
this.cartime = cartime;
|
}
|
}
|
|
/**
|
* MES任务信息
|
*/
|
private static class MesTaskInfo {
|
String glassId;
|
Integer startSlot;
|
Integer targetSlot;
|
Integer startPosition;
|
Integer targetPosition;
|
Integer currentPosition;
|
long gotime;
|
long cartime;
|
Integer workLine;
|
long createdTime;
|
boolean isOutbound = false; // 是否为出片任务(false=进片,true=出片)
|
}
|
|
/**
|
* 应用关闭时清理资源
|
*/
|
@PreDestroy
|
public void destroy() {
|
log.info("正在关闭大车监控线程池...");
|
|
// 停止所有监控任务
|
for (String deviceId : new ArrayList<>(monitoringTasks.keySet())) {
|
stopStateMonitoring(deviceId);
|
}
|
for (String deviceId : new ArrayList<>(idleMonitoringTasks.keySet())) {
|
ScheduledFuture<?> future = idleMonitoringTasks.remove(deviceId);
|
if (future != null) future.cancel(false);
|
}
|
for (String deviceId : new ArrayList<>(taskMonitoringTasks.keySet())) {
|
ScheduledFuture<?> future = taskMonitoringTasks.remove(deviceId);
|
if (future != null) future.cancel(false);
|
}
|
|
// 关闭线程池
|
shutdownExecutor(stateMonitorExecutor, "状态监控");
|
shutdownExecutor(idleMonitorExecutor, "空闲监控");
|
shutdownExecutor(taskMonitorExecutor, "任务监控");
|
|
log.info("大车监控线程池已关闭");
|
}
|
|
private void shutdownExecutor(ScheduledExecutorService executor, String name) {
|
executor.shutdown();
|
try {
|
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
executor.shutdownNow();
|
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
log.warn("{}线程池未能正常关闭", name);
|
}
|
}
|
} catch (InterruptedException e) {
|
executor.shutdownNow();
|
Thread.currentThread().interrupt();
|
}
|
}
|
}
|