From dad0263459b30dbfa75f06dff062a0c85183517b Mon Sep 17 00:00:00 2001
From: huang <1532065656@qq.com>
Date: 星期一, 01 十二月 2025 17:01:51 +0800
Subject: [PATCH] 添加卧转立扫码设备交互逻辑,任务流转
---
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java | 10
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java | 9
mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java | 4
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceCoordinationServiceImpl.java | 8
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java | 82 +
mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java | 16
mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue | 20
/dev/null | 241 ------
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java | 55 +
mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue | 114 +-
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java | 32
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/scanner/handler/HorizontalScannerLogicHandler.java | 256 +++++-
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java | 1232 ++++++++++++++++++++++++++++++++-
mes-web/src/views/device/DeviceEditDialog.vue | 24
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java | 7
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java | 36
mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java | 2
mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceGroupVO.java | 1
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/TaskStatusNotificationServiceImpl.java | 14
mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue | 57 +
20 files changed, 1,779 insertions(+), 441 deletions(-)
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
index e1e45ad..12734de 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
@@ -35,7 +35,7 @@
@TableField("device_code")
private String deviceCode;
- @ApiModelProperty(value = "璁惧绫诲瀷", example = "澶ц溅璁惧/澶х悊鐗囩/鍗у紡缂撳瓨")
+ @ApiModelProperty(value = "璁惧绫诲瀷", example = "澶ц溅璁惧/澶х悊鐗囩")
@TableField("device_type")
private String deviceType;
@@ -110,9 +110,8 @@
public static final class DeviceType {
public static final String LOAD_VEHICLE = "澶ц溅璁惧"; // 澶ц溅璁惧
public static final String LARGE_GLASS = "澶х悊鐗囩"; // 澶х悊鐗囩
- public static final String GLASS_STORAGE = "鍗у紡缂撳瓨"; // 鍗у紡缂撳瓨
- public static final String WORKSTATION_SCANNER = "鍗ц浆绔嬫壂鐮�"; // 鍗ц浆绔嬫壂鐮佽澶�
- public static final String WORKSTATION_TRANSFER = "鍗ц浆绔�"; // 鍗ц浆绔嬭澶�
+ public static final String WORKSTATION_SCANNER = "鍗ц浆绔嬫壂鐮佽澶�"; // 鍗ц浆绔嬫壂鐮佽澶�
+ public static final String WORKSTATION_TRANSFER = "鍗ц浆绔嬭澶�"; // 鍗ц浆绔嬭澶�
}
// PLC绫诲瀷甯搁噺
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
index 48a403a..0da6396 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
@@ -58,7 +58,7 @@
*/
@Select("SELECT d.id, d.device_name as deviceName, d.device_code as deviceCode, " +
"d.device_type as deviceType, d.plc_ip as plcIp, dgr.role, d.status, " +
- "ds.last_heartbeat as lastHeartbeat, " +
+ "ds.last_heartbeat as lastHeartbeat, d.enabled, " +
"CASE WHEN ds.status = 'ONLINE' THEN TRUE ELSE FALSE END as isOnline " +
"FROM device_config d " +
"INNER JOIN device_group_relation dgr ON d.id = dgr.device_id " +
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java
index f022bf9..827d87c 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/GlassInfoService.java
@@ -60,5 +60,15 @@
* @return 鏄惁鎴愬姛
*/
boolean batchSaveOrUpdateGlassInfo(List<GlassInfo> glassInfos);
+
+ /**
+ * 鏌ヨ鏈�杩戞壂鐮佺殑鐜荤拑ID鍒楄〃
+ *
+ * @param minutesAgo 鏌ヨ鏈�杩戝灏戝垎閽熷唴鐨勮褰曪紝榛樿2鍒嗛挓
+ * @param maxCount 鏈�澶ц繑鍥炴暟閲忥紝榛樿20
+ * @param workLine 宸ヤ綔绾垮彿锛堝彲閫夛紝鐢ㄤ簬杩囨护锛�
+ * @return 鐜荤拑ID鍒楄〃
+ */
+ List<String> getRecentScannedGlassIds(Integer minutesAgo, Integer maxCount, String workLine);
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceCoordinationServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceCoordinationServiceImpl.java
index 4414ee3..9fb28a8 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceCoordinationServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceCoordinationServiceImpl.java
@@ -152,14 +152,8 @@
if (CollectionUtils.isEmpty(loadedGlassIds)) {
missingDependencies.add("澶ц溅璁惧鏈畬鎴愶紝缂哄皯鐜荤拑ID鍒楄〃");
}
- } else if (DeviceConfig.DeviceType.GLASS_STORAGE.equals(deviceType)) {
- // 鐜荤拑瀛樺偍璁惧闇�瑕佸ぇ鐞嗙墖璁惧鍏堝畬鎴愶紙浼樺厛锛夛紝鎴栧ぇ杞﹁澶囧畬鎴�
- List<String> processedGlassIds = context.getSafeProcessedGlassIds();
- List<String> loadedGlassIds = context.getSafeLoadedGlassIds();
- if (CollectionUtils.isEmpty(processedGlassIds) && CollectionUtils.isEmpty(loadedGlassIds)) {
- missingDependencies.add("鍓嶇疆璁惧鏈畬鎴愶紝缂哄皯鐜荤拑ID鍒楄〃");
- }
}
+ // 鍏朵粬璁惧绫诲瀷鏆備笉闇�瑕佷緷璧栨鏌�
// 妫�鏌ヨ澶囬厤缃腑鐨勪緷璧栧叧绯伙紙浠巈xtraParams涓鍙栵級
Map<String, Object> deviceDependencies = getDeviceDependencies(device);
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
index 94ed660..2e81b91 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
@@ -9,6 +9,7 @@
import org.springframework.stereotype.Service;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -124,5 +125,40 @@
return false;
}
}
+
+ @Override
+ public List<String> getRecentScannedGlassIds(Integer minutesAgo, Integer maxCount, String workLine) {
+ try {
+ // 榛樿鏌ヨ鏈�杩�2鍒嗛挓鍐呯殑璁板綍锛屾渶澶氳繑鍥�20鏉�
+ int minutes = minutesAgo != null && minutesAgo > 0 ? minutesAgo : 2;
+ int limit = maxCount != null && maxCount > 0 ? maxCount : 20;
+
+ Date timeThreshold = new Date(System.currentTimeMillis() - minutes * 60 * 1000L);
+
+ LambdaQueryWrapper<GlassInfo> wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(GlassInfo::getStatus, GlassInfo.Status.ACTIVE)
+ .ge(GlassInfo::getCreatedTime, timeThreshold)
+ .orderByDesc(GlassInfo::getCreatedTime)
+ .last("LIMIT " + limit);
+
+ // 濡傛灉鎸囧畾浜唚orkLine锛屽垯杩囨护description
+ if (workLine != null && !workLine.trim().isEmpty()) {
+ wrapper.like(GlassInfo::getDescription, "workLine=" + workLine);
+ }
+
+ List<GlassInfo> recentGlasses = baseMapper.selectList(wrapper);
+
+ // 鎻愬彇鐜荤拑ID鍒楄〃
+ return recentGlasses.stream()
+ .map(GlassInfo::getGlassId)
+ .filter(id -> id != null && !id.trim().isEmpty())
+ .collect(Collectors.toList());
+
+ } catch (Exception e) {
+ log.error("鏌ヨ鏈�杩戞壂鐮佺殑鐜荤拑ID澶辫触, minutesAgo={}, maxCount={}, workLine={}",
+ minutesAgo, maxCount, workLine, e);
+ return Collections.emptyList();
+ }
+ }
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceGroupVO.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceGroupVO.java
index c8bcfda..1ec61fd 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceGroupVO.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceGroupVO.java
@@ -31,6 +31,7 @@
private String status;
private Date lastHeartbeat;
private Boolean isOnline;
+ private Boolean enabled;
}
/**
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java
index 92b9093..d07dc8f 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java
@@ -28,6 +28,15 @@
@Override
public DevicePlcVO.OperationResult execute(DeviceConfig deviceConfig, String operation, Map<String, Object> params) {
try {
+ // 璁板綍鍙傛暟淇℃伅锛堢敤浜庤皟璇曪級
+ if (params != null) {
+ log.debug("BaseDeviceLogicHandler.execute鎺ユ敹鍙傛暟: deviceId={}, operation={}, paramsKeys={}, params={}",
+ deviceConfig.getId(), operation, params.keySet(), params);
+ } else {
+ log.warn("BaseDeviceLogicHandler.execute鎺ユ敹鍙傛暟涓簄ull: deviceId={}, operation={}",
+ deviceConfig.getId(), operation);
+ }
+
// 楠岃瘉璁惧閰嶇疆
String validationError = validateLogicParams(deviceConfig);
if (validationError != null) {
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java
index f5eb175..4f2bc92 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java
@@ -3,6 +3,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.HashMap;
@@ -33,8 +34,12 @@
if (handlers != null) {
for (DeviceLogicHandler handler : handlers) {
String deviceType = handler.getDeviceType();
- if (deviceType != null && !deviceType.isEmpty()) {
- handlerMap.put(deviceType, handler);
+ if (StringUtils.hasText(deviceType)) {
+ String normalized = normalizeDeviceType(deviceType);
+ handlerMap.put(normalized, handler);
+ if (!normalized.equals(deviceType)) {
+ handlerMap.put(deviceType, handler);
+ }
log.info("娉ㄥ唽璁惧閫昏緫澶勭悊鍣�: {} -> {}", deviceType, handler.getClass().getSimpleName());
}
}
@@ -49,10 +54,18 @@
* @return 璁惧閫昏緫澶勭悊鍣紝濡傛灉鏈壘鍒拌繑鍥瀗ull
*/
public DeviceLogicHandler getHandler(String deviceType) {
- if (deviceType == null || deviceType.isEmpty()) {
+ if (!StringUtils.hasText(deviceType)) {
return null;
}
- return handlerMap.get(deviceType);
+ DeviceLogicHandler handler = handlerMap.get(deviceType);
+ if (handler != null) {
+ return handler;
+ }
+ String normalized = normalizeDeviceType(deviceType);
+ if (!normalized.equals(deviceType)) {
+ handler = handlerMap.get(normalized);
+ }
+ return handler;
}
/**
@@ -73,5 +86,16 @@
public java.util.Set<String> getSupportedDeviceTypes() {
return handlerMap.keySet();
}
+
+ private String normalizeDeviceType(String deviceType) {
+ if (!StringUtils.hasText(deviceType)) {
+ return deviceType;
+ }
+ String trimmed = deviceType.trim();
+ if (trimmed.endsWith("璁惧")) {
+ return trimmed.substring(0, trimmed.length() - 2);
+ }
+ return trimmed;
+ }
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java
deleted file mode 100644
index 257a3fa..0000000
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.mes.interaction.flow;
-
-import com.mes.device.entity.DeviceConfig;
-import com.mes.interaction.DeviceInteraction;
-import com.mes.interaction.base.InteractionContext;
-import com.mes.interaction.base.InteractionResult;
-import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 鐜荤拑瀛樺偍浜や簰瀹炵幇
- */
-@Component
-public class GlassStorageInteraction implements DeviceInteraction {
-
- @Override
- public String getDeviceType() {
- return DeviceConfig.DeviceType.GLASS_STORAGE;
- }
-
- @Override
- public InteractionResult execute(InteractionContext context) {
- try {
- // 鍓嶇疆鏉′欢楠岃瘉
- if (context.getCurrentDevice() == null) {
- return InteractionResult.fail("璁惧閰嶇疆涓嶅瓨鍦�");
- }
-
- // 浼樺厛浣跨敤澶勭悊鍚庣殑鐜荤拑ID锛屽鏋滄病鏈夊垯浣跨敤澶ц溅璁惧鐨勭幓鐠僆D
- List<String> processed = context.getProcessedGlassIds();
- if (CollectionUtils.isEmpty(processed)) {
- processed = context.getLoadedGlassIds();
- if (CollectionUtils.isEmpty(processed)) {
- // 灏濊瘯浠庡叡浜暟鎹幏鍙�
- Object processedGlasses = context.getSharedData().get("processedGlasses");
- if (processedGlasses instanceof List) {
- @SuppressWarnings("unchecked")
- List<String> list = (List<String>) processedGlasses;
- processed = list;
- }
- }
- }
-
- if (CollectionUtils.isEmpty(processed)) {
- return InteractionResult.waitResult("娌℃湁鍙瓨鍌ㄧ殑鐜荤拑", null);
- }
-
- // 楠岃瘉鐜荤拑ID
- for (String glassId : processed) {
- if (glassId == null || glassId.trim().isEmpty()) {
- return InteractionResult.fail("鐜荤拑ID涓嶈兘涓虹┖");
- }
- }
-
- // 鎵ц瀛樺偍鎿嶄綔
- context.getSharedData().put("storedGlasses", processed);
- context.getSharedData().put("storageTime", System.currentTimeMillis());
-
- // 鍚庣疆鏉′欢妫�鏌�
- Object stored = context.getSharedData().get("storedGlasses");
- if (stored == null) {
- return InteractionResult.fail("鐜荤拑瀛樺偍澶辫触锛氬瓨鍌ㄦ暟鎹负绌�");
- }
-
- Map<String, Object> data = new HashMap<>();
- data.put("storedCount", processed.size());
- data.put("storedGlasses", processed);
- data.put("deviceId", context.getCurrentDevice().getId());
- data.put("deviceCode", context.getCurrentDevice().getDeviceCode());
- return InteractionResult.success(data);
- } catch (Exception e) {
- return InteractionResult.fail("鐜荤拑瀛樺偍浜や簰鎵ц寮傚父: " + e.getMessage());
- }
- }
-}
-
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java
deleted file mode 100644
index 6776cf5..0000000
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package com.mes.interaction.impl;
-
-import com.mes.device.entity.DeviceConfig;
-import com.mes.interaction.BaseDeviceLogicHandler;
-import com.mes.device.service.DevicePlcOperationService;
-import com.mes.device.vo.DevicePlcVO;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 鐜荤拑瀛樺偍璁惧閫昏緫澶勭悊鍣�
- *
- * @author mes
- * @since 2025-01-XX
- */
-@Slf4j
-@Component
-public class GlassStorageLogicHandler extends BaseDeviceLogicHandler {
-
- public GlassStorageLogicHandler(DevicePlcOperationService devicePlcOperationService) {
- super(devicePlcOperationService);
- }
-
- @Override
- public String getDeviceType() {
- return DeviceConfig.DeviceType.GLASS_STORAGE;
- }
-
- @Override
- protected DevicePlcVO.OperationResult doExecute(
- DeviceConfig deviceConfig,
- String operation,
- Map<String, Object> params,
- Map<String, Object> logicParams) {
-
- log.info("鎵ц鐜荤拑瀛樺偍璁惧鎿嶄綔: deviceId={}, operation={}", deviceConfig.getId(), operation);
-
- switch (operation) {
- case "storeGlass":
- return handleStoreGlass(deviceConfig, params, logicParams);
- case "retrieveGlass":
- return handleRetrieveGlass(deviceConfig, params, logicParams);
- case "triggerRequest":
- return handleTriggerRequest(deviceConfig, params, logicParams);
- case "triggerReport":
- return handleTriggerReport(deviceConfig, params, logicParams);
- case "reset":
- return handleReset(deviceConfig, params, logicParams);
- default:
- log.warn("涓嶆敮鎸佺殑鎿嶄綔绫诲瀷: {}", operation);
- return DevicePlcVO.OperationResult.builder()
- .success(false)
- .message("涓嶆敮鎸佺殑鎿嶄綔: " + operation)
- .build();
- }
- }
-
- /**
- * 澶勭悊瀛樺偍鐜荤拑鎿嶄綔
- */
- private DevicePlcVO.OperationResult handleStoreGlass(
- DeviceConfig deviceConfig,
- Map<String, Object> params,
- Map<String, Object> logicParams) {
-
- // 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃�
- Integer storageCapacity = getLogicParam(logicParams, "storageCapacity", 100);
- String retrievalMode = getLogicParam(logicParams, "retrievalMode", "FIFO");
- Boolean autoStore = getLogicParam(logicParams, "autoStore", true);
-
- // 浠庤繍琛屾椂鍙傛暟涓幏鍙栨暟鎹�
- String glassId = (String) params.get("glassId");
- Integer storagePosition = (Integer) params.get("storagePosition");
- Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoStore);
-
- // 鏋勫缓鍐欏叆鏁版嵁
- Map<String, Object> payload = new HashMap<>();
-
- if (glassId != null) {
- payload.put("plcGlassId", glassId);
- }
-
- if (storagePosition != null) {
- payload.put("storagePosition", storagePosition);
- }
-
- // 鑷姩瑙﹀彂璇锋眰
- if (triggerRequest != null && triggerRequest) {
- payload.put("plcRequest", 1);
- }
-
- log.info("鐜荤拑瀛樺偍: deviceId={}, glassId={}, position={}",
- deviceConfig.getId(), glassId, storagePosition);
-
- return devicePlcOperationService.writeFields(
- deviceConfig.getId(),
- payload,
- "鐜荤拑瀛樺偍-瀛樺偍鐜荤拑"
- );
- }
-
- /**
- * 澶勭悊鍙栬揣鎿嶄綔
- */
- private DevicePlcVO.OperationResult handleRetrieveGlass(
- DeviceConfig deviceConfig,
- Map<String, Object> params,
- Map<String, Object> logicParams) {
-
- // 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃�
- String retrievalMode = getLogicParam(logicParams, "retrievalMode", "FIFO");
- Boolean autoRetrieve = getLogicParam(logicParams, "autoRetrieve", true);
-
- // 浠庤繍琛屾椂鍙傛暟涓幏鍙栨暟鎹�
- Integer storagePosition = (Integer) params.get("storagePosition");
- String glassId = (String) params.get("glassId");
- Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoRetrieve);
-
- // 鏋勫缓鍐欏叆鏁版嵁
- Map<String, Object> payload = new HashMap<>();
-
- if (storagePosition != null) {
- payload.put("retrievePosition", storagePosition);
- }
-
- if (glassId != null) {
- payload.put("retrieveGlassId", glassId);
- }
-
- // 鑷姩瑙﹀彂璇锋眰
- if (triggerRequest != null && triggerRequest) {
- payload.put("plcRequest", 1);
- }
-
- log.info("鐜荤拑鍙栬揣: deviceId={}, position={}, glassId={}",
- deviceConfig.getId(), storagePosition, glassId);
-
- return devicePlcOperationService.writeFields(
- deviceConfig.getId(),
- payload,
- "鐜荤拑瀛樺偍-鍙栬揣"
- );
- }
-
- /**
- * 澶勭悊瑙﹀彂璇锋眰鎿嶄綔
- */
- 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());
- return devicePlcOperationService.writeFields(
- deviceConfig.getId(),
- payload,
- "鐜荤拑瀛樺偍-閲嶇疆"
- );
- }
-
- @Override
- public String validateLogicParams(DeviceConfig deviceConfig) {
- Map<String, Object> logicParams = parseLogicParams(deviceConfig);
-
- // 楠岃瘉蹇呭~鍙傛暟
- Integer storageCapacity = getLogicParam(logicParams, "storageCapacity", null);
- if (storageCapacity != null && storageCapacity <= 0) {
- return "瀛樺偍瀹归噺(storageCapacity)蹇呴』澶т簬0";
- }
-
- String retrievalMode = getLogicParam(logicParams, "retrievalMode", null);
- if (retrievalMode != null && !retrievalMode.matches("FIFO|LIFO|RANDOM")) {
- return "鍙栬揣妯″紡(retrievalMode)蹇呴』鏄疐IFO銆丩IFO鎴朢ANDOM";
- }
-
- return null; // 楠岃瘉閫氳繃
- }
-
- @Override
- public String getDefaultLogicParams() {
- Map<String, Object> defaultParams = new HashMap<>();
- defaultParams.put("storageCapacity", 100);
- defaultParams.put("retrievalMode", "FIFO");
- defaultParams.put("autoStore", true);
- defaultParams.put("autoRetrieve", true);
- defaultParams.put("maxRetryCount", 3);
-
- try {
- return objectMapper.writeValueAsString(defaultParams);
- } catch (Exception e) {
- log.error("鐢熸垚榛樿閫昏緫鍙傛暟澶辫触", e);
- return "{}";
- }
- }
-}
-
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
index 4a40192..2599607 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -16,6 +16,8 @@
import com.mes.s7.enhanced.EnhancedS7Serializer;
import com.mes.s7.provider.S7SerializerProvider;
import com.mes.service.PlcDynamicDataService;
+import com.mes.task.model.TaskExecutionContext;
+import com.mes.interaction.workstation.scanner.handler.HorizontalScannerLogicHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -256,7 +258,23 @@
}
// 浠庨厤缃腑鑾峰彇閫熷害锛堝鏋滄湁锛�
- Double speed = getLogicParam(logicParams, "vehicleSpeed", null);
+ Object speedObj = logicParams != null ? logicParams.get("vehicleSpeed") : null;
+ Double speed = null;
+ if (speedObj != null) {
+ if (speedObj instanceof Double) {
+ speed = (Double) speedObj;
+ } else if (speedObj instanceof Integer) {
+ speed = ((Integer) speedObj).doubleValue();
+ } else if (speedObj instanceof Number) {
+ speed = ((Number) speedObj).doubleValue();
+ } else {
+ try {
+ speed = Double.parseDouble(String.valueOf(speedObj));
+ } catch (NumberFormatException e) {
+ log.warn("鏃犳硶瑙f瀽vehicleSpeed: {}", speedObj);
+ }
+ }
+ }
if (speed != null) {
task.setSpeed(speed);
task.calculateEstimatedEndTime();
@@ -284,7 +302,23 @@
// 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃紙浠� extraParams.deviceLogic 璇诲彇锛�
Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000);
- Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000);
+ // 浼樺厛浣跨敤杩愯鏃跺弬鏁颁腑鐨刧lassIntervalMs锛堜粠浠诲姟鍙傛暟浼犲叆锛夛紝濡傛灉娌℃湁鍒欎娇鐢ㄨ澶囬厤缃殑
+ 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);
+ }
Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true);
Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5);
@@ -311,11 +345,20 @@
.build();
}
if (plannedGlasses.isEmpty()) {
+ // 瑁呬笉涓嬶紝閫氱煡鍗ц浆绔嬫壂鐮佽澶囨殏鍋�
+ notifyScannerPause(params, true);
+ log.warn("澶ц溅璁惧瑁呬笉涓嬶紝宸查�氱煡鍗ц浆绔嬫壂鐮佹殏鍋�: deviceId={}, glassCount={}, vehicleCapacity={}",
+ deviceConfig.getId(), glassInfos.size(), vehicleCapacity);
return DevicePlcVO.OperationResult.builder()
.success(false)
- .message("褰撳墠鐜荤拑灏哄瓒呭嚭杞﹁締瀹归噺锛屾棤娉曡杞�")
+ .message("褰撳墠鐜荤拑灏哄瓒呭嚭杞﹁締瀹归噺锛屾棤娉曡杞斤紝宸查�氱煡鍗ц浆绔嬫壂鐮佹殏鍋�")
.build();
}
+
+ // 瑁呭緱涓嬶紝纭繚鍗ц浆绔嬫壂鐮佺户缁繍琛�
+ notifyScannerPause(params, false);
+
+ // 缁х画鎵ц鍘熸湁閫昏緫
// 鏋勫缓鍐欏叆鏁版嵁
Map<String, Object> payload = new HashMap<>();
@@ -354,16 +397,13 @@
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();
- }
- }
-
+ // 鍐欏叆PLC锛岃澶ц溅寮�濮嬭鐜荤拑
DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
deviceConfig.getId(), payload, operationName);
+
+ // 娉ㄦ剰锛歡lassIntervalMs 鐨勭瓑寰呭簲璇ュ湪鎵规涔嬮棿锛堝湪TaskExecutionEngine涓鐞嗭級锛�
+ // 鑰屼笉鏄湪杩欓噷绛夊緟锛屽洜涓鸿繖閲岀瓑寰呬細闃诲澶ц溅鐨勬甯歌鐜荤拑娴佺▼
+ // 濡傛灉闇�瑕佸湪鍐欏叆鍚庣瓑寰咃紝搴旇鍦ㄦ壒娆′箣闂寸瓑寰咃紝璁╁ぇ杞︽湁鏃堕棿澶勭悊褰撳墠鎵规鐨勭幓鐠�
// 濡傛灉鎵ц鎴愬姛锛屾洿鏂颁綅缃俊鎭埌鐘舵�侊紝骞跺惎鍔ㄧ姸鎬佺洃鎺�
if (Boolean.TRUE.equals(result.getSuccess())) {
@@ -1754,5 +1794,25 @@
Thread.currentThread().interrupt();
}
}
+
+ /**
+ * 閫氱煡鍗ц浆绔嬫壂鐮佽澶囨殏鍋滄垨缁х画
+ * @param params 鍙傛暟锛屽寘鍚玙taskContext寮曠敤
+ * @param pause true=鏆傚仠锛宖alse=缁х画
+ */
+ private void notifyScannerPause(Map<String, Object> params, boolean pause) {
+ if (params == null) {
+ return;
+ }
+
+ Object contextObj = params.get("_taskContext");
+ if (contextObj instanceof TaskExecutionContext) {
+ TaskExecutionContext context = (TaskExecutionContext) contextObj;
+ HorizontalScannerLogicHandler.setPauseFlag(context, pause);
+ log.info("宸查�氱煡鍗ц浆绔嬫壂鐮佽澶噞}: pause={}", pause ? "鏆傚仠" : "缁х画", pause);
+ } else {
+ log.debug("鏈壘鍒癟askExecutionContext锛屾棤娉曢�氱煡鍗ц浆绔嬫壂鐮佽澶囨殏鍋�");
+ }
+ }
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/scanner/handler/HorizontalScannerLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/scanner/handler/HorizontalScannerLogicHandler.java
index 28e3728..fd1c2a1 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/scanner/handler/HorizontalScannerLogicHandler.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/scanner/handler/HorizontalScannerLogicHandler.java
@@ -10,12 +10,15 @@
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.HashMap;
import java.util.List;
import java.util.Map;
@@ -53,60 +56,222 @@
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("鍗ц浆绔嬫壂鐮佽鍙朚ES鍐欏尯: 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鍐欏尯鏈彁渚涚幓鐠僆D");
- }
-
- 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] 宸叉帴鏀跺苟鍏ュ簱锛寃orkLine=%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("鍗ц浆绔嬫壂鐮佺瓑寰匨ES鏁版嵁琚腑鏂�, 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鍜宲lcGlassId锛堝鏋滄彁渚涗簡鐜荤拑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鍐欏尯鏈彁渚涚幓鐠僆D", null);
+ }
+ // 璇诲彇MES灏哄鏁版嵁锛歮esWidth=琛ㄥ锛宮esHeight=闀�
+ Integer rawWidth = parseInteger(mesData.get("mesWidth"));
+ Integer rawHeight = parseInteger(mesData.get("mesHeight"));
+ Integer workLine = parseInteger(mesData.get("workLine"));
+
+ // 4. 娓呯┖plcRequest鍜宲lcGlassId锛堝彧娓呴櫎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. 灏嗘壂鎻忓埌鐨勭幓鐠僆D淇濆瓨鍒板叡浜暟鎹腑锛堜緵澶ц溅璁惧瀹氭椂鍣ㄨ鍙栵級
+ saveScannedGlassId(params, glassId);
+
+ String msg = String.format("鐜荤拑[%s] 灏哄[琛ㄥ:%s x 闀�:%s] 宸叉帴鏀跺苟鍏ュ簱锛寃orkLine=%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("宸蹭繚瀛樻壂鎻忓埌鐨勭幓鐠僆D鍒板叡浜暟鎹�: glassId={}", glassId);
+ }
}
}
- private GlassInfo buildGlassInfo(String glassId, Integer longSide, Integer shortSide, Integer workLine) {
+ private DevicePlcVO.OperationResult clearPlc(DeviceConfig deviceConfig,
+ EnhancedS7Serializer serializer) {
+ try {
+ // 鍙竻绌篜LC鎿嶄綔鍖哄瓧娈碉紙plcRequest銆乸lcGlassId锛夛紝涓嶆竻绌篗ES鍐欏尯瀛楁
+ Map<String, Object> resetFields = new HashMap<>();
+ resetFields.put("plcRequest", 0);
+ resetFields.put("plcGlassId", "");
+ plcDynamicDataService.writePlcData(deviceConfig, resetFields, serializer);
+ return buildResult(deviceConfig, "clearPlc", true, "宸叉竻绌篜LC鎿嶄綔鍖哄瓧娈碉紙淇濈暀MES鍐欏尯瀛楁锛�", null);
+ } catch (Exception e) {
+ log.error("鍗ц浆绔嬫壂鐮佹竻绌篜LC澶辫触, deviceId={}", deviceConfig.getId(), e);
+ return buildResult(deviceConfig, "clearPlc", false, "娓呯┖PLC澶辫触: " + e.getMessage(), null);
+ }
+ }
+
+ /**
+ * 瑙﹀彂MES璇锋眰锛氬啓鍏lcRequest=1鍜宲lcGlassId锛堝鏋滄彁渚涗簡鐜荤拑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鍜宲lcGlassId锛堜笉娓呴櫎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);
if (workLine != null) {
@@ -133,17 +298,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 +312,7 @@
.success(success)
.message(message)
.timestamp(LocalDateTime.now())
+ .data(data)
.build();
}
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java
index aaf2c7c..cb8be2c 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java
@@ -7,7 +7,6 @@
import lombok.Data;
import lombok.NoArgsConstructor;
-import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@@ -22,8 +21,7 @@
@ApiModel(value = "TaskParameters", description = "澶氳澶囦换鍔℃墽琛屽弬鏁�")
public class TaskParameters implements Serializable {
- @ApiModelProperty(value = "鐜荤拑ID鍒楄〃锛堜繚鎸佹墽琛岄『搴忥級", required = true)
- @NotEmpty(message = "鐜荤拑ID鍒楄〃涓嶈兘涓虹┖")
+ @ApiModelProperty(value = "鐜荤拑ID鍒楄〃锛堜繚鎸佹墽琛岄『搴忥級锛屽彲涓虹┖琛ㄧず鐢卞悗鍙拌嚜鍔ㄨ幏鍙栨渶杩戞壂鐮佺殑鐜荤拑ID")
private List<String> glassIds;
@ApiModelProperty(value = "涓婂ぇ杞︿綅缃紪鐮�")
@@ -41,6 +39,18 @@
@ApiModelProperty(value = "鎵ц闂撮殧(姣)")
private Integer executionInterval;
+ @ApiModelProperty(value = "鍗曠墖闂撮殧(姣)锛屽涓幓鐠僆D鏃舵瘡涓幓鐠僆D涔嬮棿鐨勯棿闅旀椂闂达紝0琛ㄧず涓�娆℃�у叏閮ㄤ紶閫�")
+ private Integer glassIntervalMs;
+
+ @ApiModelProperty(value = "鏄惁鍦ㄤ换鍔″紑濮嬪墠鍏堣Е鍙戣姹�(plcRequest)锛屼粎鍦ㄦ湭鎻愪緵鐜荤拑ID鏃剁敓鏁堬紝榛樿true")
+ private Boolean triggerRequestFirst;
+
+ @ApiModelProperty(value = "浠诲姟瓒呮椂鏃堕棿锛堝垎閽燂級锛岄粯璁�30鍒嗛挓")
+ private Integer timeoutMinutes;
+
+ @ApiModelProperty(value = "閲嶈瘯娆℃暟锛岄粯璁�3娆�")
+ private Integer retryCount;
+
@ApiModelProperty(value = "璁惧绾у埆鍙傛暟瑕嗙洊锛宬ey鍙互鏄澶囩被鍨嬫垨璁惧缂栫爜")
private Map<String, Map<String, Object>> deviceOverrides;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java
index 73deefa..da9c996 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java
@@ -70,6 +70,10 @@
@ApiModelProperty("閿欒淇℃伅")
private String errorMessage;
+ @TableField("success_message")
+ @ApiModelProperty("鎴愬姛淇℃伅")
+ private String successMessage;
+
@TableField("retry_count")
@ApiModelProperty("閲嶈瘯娆℃暟")
private Integer retryCount;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
index 8f995c0..986a06b 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -7,14 +7,16 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mes.device.entity.DeviceConfig;
import com.mes.device.entity.DeviceGroupConfig;
+import com.mes.device.service.DeviceCoordinationService;
+import com.mes.device.service.DeviceInteractionService;
+import com.mes.device.service.GlassInfoService;
+import com.mes.device.vo.DevicePlcVO;
import com.mes.interaction.DeviceInteraction;
import com.mes.interaction.DeviceInteractionRegistry;
import com.mes.interaction.DeviceLogicHandler;
import com.mes.interaction.DeviceLogicHandlerFactory;
import com.mes.interaction.base.InteractionContext;
import com.mes.interaction.base.InteractionResult;
-import com.mes.device.service.DeviceCoordinationService;
-import com.mes.device.service.DeviceInteractionService;
import com.mes.task.dto.TaskParameters;
import com.mes.task.entity.MultiDeviceTask;
import com.mes.task.entity.TaskStepDetail;
@@ -23,16 +25,16 @@
import com.mes.task.model.RetryPolicy;
import com.mes.task.model.TaskExecutionContext;
import com.mes.task.model.TaskExecutionResult;
-import com.mes.task.service.TaskStatusNotificationService;
-import com.mes.device.vo.DevicePlcVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* 澶氳澶囦换鍔℃墽琛屽紩鎿�
@@ -45,6 +47,8 @@
private static final Map<String, String> DEFAULT_OPERATIONS = new HashMap<>();
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
+ private static final int SCANNER_LOOKBACK_MINUTES = 2;
+ private static final int SCANNER_LOOKBACK_LIMIT = 20;
// 鎵ц妯″紡甯搁噺
private static final String EXECUTION_MODE_SERIAL = "SERIAL";
@@ -53,7 +57,8 @@
static {
DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LOAD_VEHICLE, "feedGlass");
DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LARGE_GLASS, "processGlass");
- DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.GLASS_STORAGE, "storeGlass");
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.WORKSTATION_SCANNER, "scanOnce");
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.WORKSTATION_TRANSFER, "checkAndProcess");
}
private final TaskStepDetailMapper taskStepDetailMapper;
@@ -64,6 +69,8 @@
private final DeviceCoordinationService deviceCoordinationService;
private final TaskStatusNotificationService notificationService;
private final ObjectMapper objectMapper;
+ @Qualifier("deviceGlassInfoService")
+ private final GlassInfoService glassInfoService;
// 绾跨▼姹犵敤浜庡苟琛屾墽琛�
private final ExecutorService executorService = Executors.newCachedThreadPool(r -> {
@@ -71,6 +78,18 @@
t.setDaemon(true);
return t;
});
+
+ // 瀹氭椂鍣ㄧ嚎绋嬫睜锛氱敤浜庤澶囧畾鏃舵壂鎻�
+ private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10, r -> {
+ Thread t = new Thread(r, "TaskExecutionEngine-Scheduled");
+ t.setDaemon(true);
+ return t;
+ });
+
+ // 瀛樺偍姣忎釜浠诲姟鐨勫畾鏃跺櫒浠诲姟锛歵askId -> List<ScheduledFuture>
+ private final Map<String, List<ScheduledFuture<?>>> taskScheduledTasks = new ConcurrentHashMap<>();
+ // 璁板綍姝e湪杩愯浠诲姟鐨勪笂涓嬫枃锛屼究浜庡彇娑堜换鍔℃椂璁块棶
+ private final Map<String, TaskExecutionContext> runningTaskContexts = new ConcurrentHashMap<>();
public TaskExecutionResult execute(MultiDeviceTask task,
DeviceGroupConfig groupConfig,
@@ -82,16 +101,8 @@
}
TaskExecutionContext context = new TaskExecutionContext(parameters);
+ runningTaskContexts.put(task.getTaskId(), context);
- // 璁惧鍗忚皟锛氭鏌ヤ緷璧栧叧绯诲拰鎵ц鏉′欢
- DeviceCoordinationService.CoordinationResult coordinationResult =
- deviceCoordinationService.coordinateExecution(groupConfig, devices, context);
- if (!coordinationResult.canExecute()) {
- log.warn("璁惧鍗忚皟澶辫触: {}", coordinationResult.getMessage());
- return TaskExecutionResult.failure(coordinationResult.getMessage(), Collections.emptyMap());
- }
- log.info("璁惧鍗忚皟鎴愬姛: {}", coordinationResult.getMessage());
-
task.setTotalSteps(devices.size());
task.setStatus(MultiDeviceTask.Status.RUNNING.name());
multiDeviceTaskMapper.updateById(task);
@@ -120,10 +131,133 @@
stepSummaries = new ArrayList<>();
success = true;
failureMessage = null;
- for (int i = 0; i < devices.size(); i++) {
- DeviceConfig device = devices.get(i);
- int order = i + 1;
- TaskStepDetail step = createStepRecord(task, device, order);
+
+ TaskParameters params = context.getParameters();
+ boolean hasGlassIds = !CollectionUtils.isEmpty(params.getGlassIds());
+ boolean triggerFirst = Boolean.TRUE.equals(params.getTriggerRequestFirst());
+
+ int currentOrder = 1;
+ // 缁熻澶ц溅璁惧鏁伴噺锛岀敤浜庡尯鍒嗚繘鐗囧ぇ杞﹀拰鍑虹墖澶ц溅
+ int loadVehicleCount = 0;
+ for (DeviceConfig device : devices) {
+ if (DeviceConfig.DeviceType.LOAD_VEHICLE.equals(device.getDeviceType())) {
+ loadVehicleCount++;
+ }
+ }
+ int currentLoadVehicleIndex = 0;
+
+ for (DeviceConfig device : devices) {
+ String deviceType = device.getDeviceType();
+ log.info("澶勭悊璁惧: deviceId={}, deviceType={}, deviceName={}, WORKSTATION_SCANNER甯搁噺={}, equals={}",
+ device.getId(), deviceType, device.getDeviceName(),
+ DeviceConfig.DeviceType.WORKSTATION_SCANNER,
+ DeviceConfig.DeviceType.WORKSTATION_SCANNER.equals(deviceType));
+ boolean isLoadVehicle = DeviceConfig.DeviceType.LOAD_VEHICLE.equals(deviceType);
+ boolean isScanner = DeviceConfig.DeviceType.WORKSTATION_SCANNER.equals(deviceType)
+ || (deviceType != null && (deviceType.contains("鎵爜") || deviceType.contains("SCANNER")));
+ boolean isLargeGlass = DeviceConfig.DeviceType.LARGE_GLASS.equals(deviceType);
+ boolean isTransfer = DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(deviceType);
+ log.info("璁惧绫诲瀷鍒ゆ柇: deviceId={}, isLoadVehicle={}, isScanner={}, isLargeGlass={}, isTransfer={}",
+ device.getId(), isLoadVehicle, isScanner, isLargeGlass, isTransfer);
+
+ // 1. 鍗ц浆绔嬫壂鐮佽澶囷細鍚姩瀹氭椂鍣ㄦ壂鎻忥紙姣�10绉掑鐞嗕竴涓幓鐠僆D锛�
+ if (isScanner) {
+ log.info("妫�娴嬪埌鎵爜璁惧锛屽噯澶囧惎鍔ㄥ畾鏃跺櫒: deviceId={}, deviceType={}, deviceName={}",
+ device.getId(), device.getDeviceType(), device.getDeviceName());
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+ ScheduledFuture<?> scannerTask = startScannerTimer(task, step, device, context);
+ if (scannerTask != null) {
+ registerScheduledTask(task.getTaskId(), scannerTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "瀹氭椂鍣ㄥ凡鍚姩锛屾瘡10绉掓壂鎻忎竴娆�"));
+ log.info("鎵爜璁惧瀹氭椂鍣ㄥ惎鍔ㄦ垚鍔�: deviceId={}, taskId={}", device.getId(), task.getTaskId());
+ } else {
+ log.warn("鎵爜璁惧瀹氭椂鍣ㄥ惎鍔ㄥけ璐ワ紝glassIds鍙兘涓虹┖: deviceId={}, taskId={}, contextParams={}",
+ device.getId(), task.getTaskId(), context.getParameters());
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "鍗ц浆绔嬫壂鐮佽澶囧惎鍔ㄥ畾鏃跺櫒澶辫触";
+ break;
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 2. 鍗ц浆绔嬭澶囷細鍚姩瀹氭椂鍣ㄥ畾鏈熸鏌ュ苟澶勭悊锛堜腑杞澶囷級
+ if (isTransfer) {
+ log.info("妫�娴嬪埌鍗ц浆绔嬭澶囷紝鍑嗗鍚姩瀹氭椂鍣�: deviceId={}, deviceType={}, deviceName={}",
+ device.getId(), device.getDeviceType(), device.getDeviceName());
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+ ScheduledFuture<?> transferTask = startTransferTimer(task, step, device, context);
+ if (transferTask != null) {
+ registerScheduledTask(task.getTaskId(), transferTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "瀹氭椂鍣ㄥ凡鍚姩锛屽畾鏈熸鏌ュ苟澶勭悊鐜荤拑鎵规"));
+ log.info("鍗ц浆绔嬭澶囧畾鏃跺櫒鍚姩鎴愬姛: deviceId={}, taskId={}", device.getId(), task.getTaskId());
+ } else {
+ log.warn("鍗ц浆绔嬭澶囧畾鏃跺櫒鍚姩澶辫触: deviceId={}, taskId={}", device.getId(), task.getTaskId());
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "鍗ц浆绔嬭澶囧惎鍔ㄥ畾鏃跺櫒澶辫触";
+ break;
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 3. 杩涚墖澶ц溅璁惧锛氬惎鍔ㄥ畾鏃跺櫒鎸佺画鐩戞帶瀹归噺锛堢涓�涓ぇ杞﹁澶囷級
+ if (isLoadVehicle) {
+ currentLoadVehicleIndex++;
+ boolean isInboundVehicle = currentLoadVehicleIndex == 1; // 绗竴涓ぇ杞︽槸杩涚墖澶ц溅
+
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+ ScheduledFuture<?> vehicleTask;
+ if (isInboundVehicle) {
+ // 杩涚墖澶ц溅锛氱洃鎺у閲忥紝鍔ㄦ�佸垽鏂�
+ vehicleTask = startInboundVehicleTimer(task, step, device, context);
+ if (vehicleTask != null) {
+ registerScheduledTask(task.getTaskId(), vehicleTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "杩涚墖澶ц溅瀹氭椂鍣ㄥ凡鍚姩锛屾寔缁洃鎺у閲�"));
+ } else {
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "杩涚墖澶ц溅璁惧鍚姩瀹氭椂鍣ㄥけ璐�";
+ break;
+ }
+ } else {
+ // 鍑虹墖澶ц溅锛氬惎鍔ㄥ畾鏃跺櫒鐩戞帶鍑虹墖浠诲姟
+ vehicleTask = startOutboundVehicleTimer(task, step, device, context);
+ if (vehicleTask != null) {
+ registerScheduledTask(task.getTaskId(), vehicleTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "鍑虹墖澶ц溅瀹氭椂鍣ㄥ凡鍚姩锛屾寔缁洃鎺у嚭鐗囦换鍔�"));
+ } else {
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "鍑虹墖澶ц溅璁惧鍚姩瀹氭椂鍣ㄥけ璐�";
+ break;
+ }
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 4. 澶х悊鐗囩璁惧锛氬惎鍔ㄥ畾鏃跺櫒閫昏緫澶勭悊锛堜笉娑夊強PLC浜や簰锛屽彧璐熻矗閫昏緫澶勭悊锛�
+ if (isLargeGlass) {
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+ ScheduledFuture<?> largeGlassTask = startLargeGlassTimer(task, step, device, context);
+ if (largeGlassTask != null) {
+ registerScheduledTask(task.getTaskId(), largeGlassTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "澶х悊鐗囩瀹氭椂鍣ㄥ凡鍚姩锛岄�昏緫澶勭悊涓�"));
+ } else {
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "澶х悊鐗囩璁惧鍚姩瀹氭椂鍣ㄥけ璐�";
+ break;
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 鍏朵粬璁惧锛氭甯告墽琛�
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
StepResult stepResult = executeStep(task, step, device, context);
stepSummaries.add(stepResult.toSummary());
if (!stepResult.isSuccess()) {
@@ -131,7 +265,35 @@
failureMessage = stepResult.getMessage();
break;
}
+ currentOrder++;
}
+
+ // 濡傛灉鎵�鏈夎澶囬兘鏄畾鏃跺櫒妯″紡锛屼换鍔′繚鎸佽繍琛岀姸鎬侊紝涓嶇瓑寰呭畬鎴�
+ // 瀹氭椂鍣ㄤ細鍦ㄥ悗鍙版寔缁繍琛岋紝鐩村埌鎵嬪姩鍋滄鎴栬秴鏃�
+ boolean hasScheduledTasks = !CollectionUtils.isEmpty(taskScheduledTasks.get(task.getTaskId()));
+ if (hasScheduledTasks) {
+ log.info("浠诲姟宸插惎鍔ㄦ墍鏈夊畾鏃跺櫒锛屼繚鎸佽繍琛岀姸鎬�: taskId={}, scheduledTasksCount={}",
+ task.getTaskId(), taskScheduledTasks.get(task.getTaskId()).size());
+ // 浠诲姟淇濇寔 RUNNING 鐘舵�侊紝瀹氭椂鍣ㄥ湪鍚庡彴杩愯
+ // 涓嶆洿鏂颁换鍔$姸鎬佷负 COMPLETED锛岃浠诲姟鎸佺画杩愯
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("steps", stepSummaries);
+ payload.put("groupId", groupConfig.getId());
+ payload.put("deviceCount", devices.size());
+ payload.put("executionMode", executionMode);
+ payload.put("message", "浠诲姟宸插惎鍔紝瀹氭椂鍣ㄥ湪鍚庡彴杩愯涓�");
+
+ // 閫氱煡浠诲姟鐘舵�侊紙淇濇寔 RUNNING锛�
+ notificationService.notifyTaskStatus(task);
+
+ if (success) {
+ return TaskExecutionResult.success(payload);
+ }
+ return TaskExecutionResult.failure(failureMessage != null ? failureMessage : "浠诲姟鎵ц澶辫触", payload);
+ }
+
+ // 濡傛灉娌℃湁瀹氭椂鍣ㄤ换鍔★紝绛夊緟鎵�鏈夋楠ゅ畬鎴�
+ // 杩欑鎯呭喌閫氬父涓嶄細鍙戠敓锛屽洜涓烘墍鏈夎澶囬兘鏄畾鏃跺櫒妯″紡
}
Map<String, Object> payload = new HashMap<>();
@@ -140,8 +302,15 @@
payload.put("deviceCount", devices.size());
payload.put("executionMode", executionMode);
+ // 鍋滄鎵�鏈夊畾鏃跺櫒浠诲姟
+ stopScheduledTasks(task.getTaskId());
+
+ boolean cancelled = isTaskCancelled(context);
// 鏇存柊浠诲姟鏈�缁堢姸鎬�
- if (success) {
+ if (cancelled) {
+ task.setStatus(MultiDeviceTask.Status.CANCELLED.name());
+ task.setErrorMessage("浠诲姟宸插彇娑�");
+ } else if (success) {
task.setStatus(MultiDeviceTask.Status.COMPLETED.name());
} else {
task.setStatus(MultiDeviceTask.Status.FAILED.name());
@@ -157,6 +326,744 @@
return TaskExecutionResult.success(payload);
}
return TaskExecutionResult.failure(failureMessage != null ? failureMessage : "浠诲姟鎵ц澶辫触", payload);
+ }
+
+ /**
+ * 璇锋眰鍙栨秷浠诲姟锛氬仠姝㈡墍鏈夊畾鏃跺櫒骞舵爣璁颁笂涓嬫枃
+ */
+ public void requestTaskCancellation(String taskId) {
+ TaskExecutionContext context = runningTaskContexts.get(taskId);
+ if (context != null) {
+ context.getSharedData().put("taskCancelled", true);
+ log.warn("宸叉爣璁颁换鍔″彇娑�: taskId={}", taskId);
+ } else {
+ log.warn("璇锋眰鍙栨秷浠诲姟浣嗘湭鎵惧埌涓婁笅鏂�: taskId={}", taskId);
+ }
+ stopScheduledTasks(taskId);
+ }
+
+ /**
+ * 鍚姩鍗ц浆绔嬫壂鐮佽澶囧畾鏃跺櫒锛氭瘡10绉掑鐞嗕竴涓幓鐠僆D
+ */
+ private ScheduledFuture<?> startScannerTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ TaskParameters params = context.getParameters();
+ List<String> glassIds = params.getGlassIds();
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鍒濆鍖�: taskId={}, deviceId={}, glassIds={}, glassIdsSize={}, isEmpty={}",
+ task.getTaskId(), device.getId(), glassIds,
+ glassIds != null ? glassIds.size() : 0,
+ CollectionUtils.isEmpty(glassIds));
+ if (CollectionUtils.isEmpty(glassIds)) {
+ log.warn("鍗ц浆绔嬫壂鐮佽澶囨病鏈夌幓鐠僆D锛屽畾鏃跺櫒涓嶅惎鍔�: deviceId={}", device.getId());
+ return null;
+ }
+
+ // 鍒涘缓寰呭鐞嗙幓鐠僆D闃熷垪
+ Queue<String> glassIdQueue = new ConcurrentLinkedQueue<>(glassIds);
+ AtomicInteger processedCount = new AtomicInteger(0);
+ AtomicInteger successCount = new AtomicInteger(0);
+ AtomicInteger failCount = new AtomicInteger(0);
+
+ final long CYCLE_INTERVAL_MS = 10_000; // 10绉掗棿闅�
+
+ log.info("鍚姩鍗ц浆绔嬫壂鐮佸畾鏃跺櫒: taskId={}, deviceId={}, glassCount={}, interval={}s, glassIds={}",
+ task.getTaskId(), device.getId(), glassIds.size(), CYCLE_INTERVAL_MS / 1000, glassIds);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.info("浠诲姟宸插彇娑堬紝鍋滄鍗ц浆绔嬫壂鐮佸畾鏃跺櫒: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ // 妫�鏌ユ槸鍚﹂渶瑕佹殏鍋�
+ if (shouldPauseScanner(context)) {
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鏆傚仠: taskId={}, deviceId={}", task.getTaskId(), device.getId());
+ return;
+ }
+
+ // 妫�鏌ユ槸鍚﹁繕鏈夊緟澶勭悊鐨勭幓鐠僆D
+ String glassId = glassIdQueue.poll();
+ if (glassId == null) {
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒瀹屾垚: taskId={}, deviceId={}, processed={}/{}, success={}, fail={}",
+ task.getTaskId(), device.getId(), processedCount.get(), glassIds.size(),
+ successCount.get(), failCount.get());
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.COMPLETED, context);
+ return;
+ }
+
+ int currentIndex = processedCount.incrementAndGet();
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶勭悊绗瑊}/{}涓幓鐠�: taskId={}, deviceId={}, glassId={}",
+ currentIndex, glassIds.size(), task.getTaskId(), device.getId(), glassId);
+
+ // 鎵ц鍗曟鎵弿
+ Map<String, Object> scanParams = new HashMap<>();
+ scanParams.put("glassId", glassId);
+ scanParams.put("_taskContext", context);
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鍑嗗鎵ц: taskId={}, deviceId={}, glassId={}, scanParams={}",
+ task.getTaskId(), device.getId(), glassId, scanParams);
+
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ // 灏唋ogicParams鍚堝苟鍒皊canParams涓�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ scanParams.put("_logicParams", logicParams);
+ }
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒璋冪敤handler.execute: taskId={}, deviceId={}, glassId={}, operation=scanOnce, scanParamsKeys={}, scanParams={}",
+ task.getTaskId(), device.getId(), glassId, scanParams.keySet(), scanParams);
+ DevicePlcVO.OperationResult result = handler.execute(device, "scanOnce", scanParams);
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒handler.execute杩斿洖: taskId={}, deviceId={}, glassId={}, success={}",
+ task.getTaskId(), device.getId(), glassId, result.getSuccess());
+
+ if (Boolean.TRUE.equals(result.getSuccess())) {
+ successCount.incrementAndGet();
+ log.info("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶勭悊鎴愬姛: taskId={}, deviceId={}, glassId={}",
+ task.getTaskId(), device.getId(), glassId);
+ } else {
+ failCount.incrementAndGet();
+ log.warn("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶勭悊澶辫触: taskId={}, deviceId={}, glassId={}, error={}",
+ task.getTaskId(), device.getId(), glassId, result.getMessage());
+ }
+
+ // 鏇存柊姝ラ鐘舵��
+ updateStepStatus(step, result);
+ // 閫氱煡姝ラ鏇存柊锛堣鍓嶇瀹炴椂鐪嬪埌姝ラ鐘舵�侊級
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鎵ц寮傚父: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ failCount.incrementAndGet();
+ }
+ }, 0, CYCLE_INTERVAL_MS, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶辫触: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩鍗ц浆绔嬭澶囧畾鏃跺櫒锛氬畾鏈熸鏌ュ苟澶勭悊鐜荤拑鎵规
+ */
+ private ScheduledFuture<?> startTransferTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ // 浠庤澶囬厤缃腑鑾峰彇鐩戞帶闂撮殧锛岄粯璁�5绉�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ Integer monitorIntervalMs = getLogicParam(logicParams, "monitorIntervalMs", 5_000);
+
+ log.info("鍚姩鍗ц浆绔嬭澶囧畾鏃跺櫒: taskId={}, deviceId={}, interval={}ms",
+ task.getTaskId(), device.getId(), monitorIntervalMs);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.info("浠诲姟宸插彇娑堬紝鍋滄鍗ц浆绔嬭澶囧畾鏃跺櫒: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ // 鏋勫缓鍙傛暟
+ Map<String, Object> params = new HashMap<>();
+ params.put("_taskContext", context);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ params.put("_logicParams", logicParams);
+ }
+
+ // 璋冪敤handler鎵цcheckAndProcess
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ DevicePlcVO.OperationResult result = handler.execute(device, "checkAndProcess", params);
+
+ // 鏇存柊姝ラ鐘舵��
+ updateStepStatus(step, result);
+ // 閫氱煡姝ラ鏇存柊锛堣鍓嶇瀹炴椂鐪嬪埌姝ラ鐘舵�侊級
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (opSuccess) {
+ log.debug("鍗ц浆绔嬭澶囧畾鏃跺櫒鎵ц鎴愬姛: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), result.getMessage());
+ } else {
+ log.warn("鍗ц浆绔嬭澶囧畾鏃跺櫒鎵ц澶辫触: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), result.getMessage());
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍗ц浆绔嬭澶囧畾鏃跺櫒鎵ц寮傚父: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, monitorIntervalMs, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩鍗ц浆绔嬭澶囧畾鏃跺櫒澶辫触: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩杩涚墖澶ц溅璁惧瀹氭椂鍣細鎸佺画鐩戞帶瀹归噺锛屽姩鎬佸垽鏂�
+ */
+ private ScheduledFuture<?> startInboundVehicleTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ final long MONITOR_INTERVAL_MS = 2_000; // 2绉掔洃鎺т竴娆�
+ final AtomicInteger lastProcessedCount = new AtomicInteger(0);
+
+ log.info("鍚姩杩涚墖澶ц溅璁惧瀹氭椂鍣�: taskId={}, deviceId={}, interval={}s",
+ task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.info("浠诲姟宸插彇娑堬紝鍋滄杩涚墖澶ц溅瀹氭椂鍣�: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ // 妫�鏌ユ槸鍚︽湁宸叉壂鎻忕殑鐜荤拑淇℃伅
+ List<String> scannedGlassIds = getScannedGlassIds(context);
+ if (CollectionUtils.isEmpty(scannedGlassIds)) {
+ // 娌℃湁宸叉壂鎻忕殑鐜荤拑锛岀‘淇濆崸杞珛鎵爜缁х画杩愯
+ setScannerPause(context, false);
+ return;
+ }
+
+ // 濡傛灉鐜荤拑ID鏁伴噺娌℃湁鍙樺寲锛岃鏄庢病鏈夋柊鐨勭幓鐠冿紝缁х画绛夊緟
+ int currentCount = scannedGlassIds.size();
+ if (currentCount == lastProcessedCount.get()) {
+ log.debug("澶ц溅璁惧瀹氭椂鍣細鐜荤拑ID鏁伴噺鏈彉鍖栵紝缁х画绛夊緟: taskId={}, deviceId={}, count={}",
+ task.getTaskId(), device.getId(), currentCount);
+ return;
+ }
+
+ log.info("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄦ娴嬪埌鏂扮殑鐜荤拑淇℃伅: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), currentCount);
+
+ // 妫�鏌ュ閲�
+ Map<String, Object> checkParams = new HashMap<>();
+ checkParams.put("glassIds", new ArrayList<>(scannedGlassIds));
+ checkParams.put("_taskContext", context);
+
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ // 灏唋ogicParams鍚堝苟鍒癱heckParams涓�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ checkParams.put("_logicParams", logicParams);
+ }
+ DevicePlcVO.OperationResult result = handler.execute(device, "feedGlass", checkParams);
+
+ if (Boolean.TRUE.equals(result.getSuccess())) {
+ log.info("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屾垚鍔�: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), scannedGlassIds.size());
+ // 灏嗗凡瑁呰浇鐨勭幓鐠僆D淇濆瓨鍒板叡浜暟鎹腑锛堜緵澶х悊鐗囩浣跨敤锛�
+ setLoadedGlassIds(context, new ArrayList<>(scannedGlassIds));
+ // 娓呯┖宸叉壂鎻忕殑鐜荤拑ID鍒楄〃锛堝凡澶勭悊锛�
+ clearScannedGlassIds(context);
+ lastProcessedCount.set(0);
+ // 纭繚鍗ц浆绔嬫壂鐮佺户缁繍琛�
+ setScannerPause(context, false);
+ } else {
+ // 瑁呬笉涓嬶紝閫氱煡鍗ц浆绔嬫壂鐮佹殏鍋�
+ log.warn("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄥ閲忎笉瓒�: taskId={}, deviceId={}, message={}, 宸查�氱煡鍗ц浆绔嬫壂鐮佹殏鍋�",
+ task.getTaskId(), device.getId(), result.getMessage());
+ setScannerPause(context, true);
+ lastProcessedCount.set(currentCount); // 璁板綍褰撳墠鏁伴噺锛岄伩鍏嶉噸澶嶆鏌�
+ }
+
+ // 鏇存柊姝ラ鐘舵��
+ updateStepStatus(step, result);
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ } catch (Exception e) {
+ log.error("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屽紓甯�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩杩涚墖澶ц溅璁惧瀹氭椂鍣ㄥけ璐�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩鍑虹墖澶ц溅璁惧瀹氭椂鍣細鎸佺画鐩戞帶鍑虹墖浠诲姟
+ */
+ private ScheduledFuture<?> startOutboundVehicleTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ final long MONITOR_INTERVAL_MS = 2_000; // 2绉掔洃鎺т竴娆�
+
+ log.info("鍚姩鍑虹墖澶ц溅璁惧瀹氭椂鍣�: taskId={}, deviceId={}, interval={}s",
+ task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.info("浠诲姟宸插彇娑堬紝鍋滄鍑虹墖澶ц溅瀹氭椂鍣�: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ // 妫�鏌ユ槸鍚︽湁宸插鐞嗙殑鐜荤拑淇℃伅锛堜粠澶х悊鐗囩鏉ョ殑锛�
+ List<String> processedGlassIds = getProcessedGlassIds(context);
+ if (CollectionUtils.isEmpty(processedGlassIds)) {
+ log.debug("鍑虹墖澶ц溅璁惧瀹氭椂鍣細鏆傛棤宸插鐞嗙殑鐜荤拑淇℃伅: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+
+ log.info("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ娴嬪埌宸插鐞嗙殑鐜荤拑淇℃伅: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), processedGlassIds.size());
+
+ // 鎵ц鍑虹墖鎿嶄綔
+ Map<String, Object> checkParams = new HashMap<>();
+ checkParams.put("glassIds", new ArrayList<>(processedGlassIds));
+ checkParams.put("_taskContext", context);
+
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ // 灏唋ogicParams鍚堝苟鍒癱heckParams涓�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ checkParams.put("_logicParams", logicParams);
+ }
+ DevicePlcVO.OperationResult result = handler.execute(device, "feedGlass", checkParams);
+
+ if (Boolean.TRUE.equals(result.getSuccess())) {
+ log.info("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屾垚鍔�: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), processedGlassIds.size());
+ // 娓呯┖宸插鐞嗙殑鐜荤拑ID鍒楄〃锛堝凡澶勭悊锛�
+ clearProcessedGlassIds(context);
+ } else {
+ log.debug("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屽け璐�: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), result.getMessage());
+ }
+
+ // 鏇存柊姝ラ鐘舵��
+ updateStepStatus(step, result);
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屽紓甯�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄥけ璐�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩澶х悊鐗囩璁惧瀹氭椂鍣細閫昏緫澶勭悊锛堜笉娑夊強PLC浜や簰锛屽彧璐熻矗閫昏緫澶勭悊锛屾瘮濡傚涔呯粰浠诲姟姹囨姤锛�
+ */
+ private ScheduledFuture<?> startLargeGlassTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ // 浠庤澶囬厤缃腑鑾峰彇澶勭悊鏃堕棿锛堥粯璁�30绉掞級
+ Map<String, Object> logicParams = parseLogicParams(device);
+ Integer processTimeSeconds = getLogicParam(logicParams, "processTimeSeconds", 30);
+ final long PROCESS_TIME_MS = processTimeSeconds * 1000;
+
+ log.info("鍚姩澶х悊鐗囩璁惧瀹氭椂鍣�: taskId={}, deviceId={}, processTime={}s",
+ task.getTaskId(), device.getId(), processTimeSeconds);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.info("浠诲姟宸插彇娑堬紝鍋滄澶х悊鐗囩瀹氭椂鍣�: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ // 妫�鏌ユ槸鍚︽湁宸茶杞界殑鐜荤拑淇℃伅锛堜粠杩涚墖澶ц溅鏉ョ殑锛�
+ List<String> loadedGlassIds = getLoadedGlassIds(context);
+ if (CollectionUtils.isEmpty(loadedGlassIds)) {
+ log.debug("澶х悊鐗囩璁惧瀹氭椂鍣細鏆傛棤宸茶杞界殑鐜荤拑淇℃伅: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+
+ // 妫�鏌ョ幓鐠冩槸鍚﹀凡缁忓鐞嗗畬鎴愶紙閫氳繃澶勭悊鏃堕棿鍒ゆ柇锛�
+ Long processStartTime = getProcessStartTime(context);
+ if (processStartTime == null) {
+ // 绗竴娆℃娴嬪埌鐜荤拑锛岃褰曞紑濮嬪鐞嗘椂闂�
+ setProcessStartTime(context, System.currentTimeMillis());
+ log.info("澶х悊鐗囩璁惧寮�濮嬪鐞�: taskId={}, deviceId={}, glassCount={}, processTime={}s",
+ task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds);
+ return;
+ }
+
+ long elapsed = System.currentTimeMillis() - processStartTime;
+ if (elapsed < PROCESS_TIME_MS) {
+ // 澶勭悊鏃堕棿鏈埌锛岀户缁瓑寰�
+ log.debug("澶х悊鐗囩璁惧澶勭悊涓�: taskId={}, deviceId={}, elapsed={}s, remaining={}s",
+ task.getTaskId(), device.getId(), elapsed / 1000, (PROCESS_TIME_MS - elapsed) / 1000);
+ return;
+ }
+
+ // 澶勭悊鏃堕棿宸插埌锛屽畬鎴愪换鍔℃眹鎶�
+ log.info("澶х悊鐗囩璁惧澶勭悊瀹屾垚: taskId={}, deviceId={}, glassCount={}, processTime={}s",
+ task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds);
+
+ // 灏嗗凡澶勭悊鐨勭幓鐠僆D杞Щ鍒板凡澶勭悊鍒楄〃锛堜緵鍑虹墖澶ц溅浣跨敤锛�
+ setProcessedGlassIds(context, new ArrayList<>(loadedGlassIds));
+ clearLoadedGlassIds(context);
+ clearProcessStartTime(context);
+
+ // 鏇存柊姝ラ鐘舵��
+ step.setStatus(TaskStepDetail.Status.COMPLETED.name());
+ step.setErrorMessage(null);
+ step.setOutputData(toJson(Collections.singletonMap("glassIds", loadedGlassIds)));
+ taskStepDetailMapper.updateById(step);
+
+ } catch (Exception e) {
+ log.error("澶х悊鐗囩璁惧瀹氭椂鍣ㄦ墽琛屽紓甯�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, 1_000, TimeUnit.MILLISECONDS); // 姣忕妫�鏌ヤ竴娆�
+
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩澶х悊鐗囩璁惧瀹氭椂鍣ㄥけ璐�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇閫昏緫鍙傛暟
+ */
+ @SuppressWarnings("unchecked")
+ private <T> T getLogicParam(Map<String, Object> logicParams, String key, T defaultValue) {
+ if (logicParams == null) {
+ return defaultValue;
+ }
+ Object value = logicParams.get(key);
+ if (value == null) {
+ return defaultValue;
+ }
+ try {
+ return (T) value;
+ } catch (ClassCastException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 鑾峰彇宸茶杞界殑鐜荤拑ID鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getLoadedGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("loadedGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 璁剧疆宸茶杞界殑鐜荤拑ID鍒楄〃
+ */
+ private void setLoadedGlassIds(TaskExecutionContext context, List<String> glassIds) {
+ if (context != null) {
+ context.getSharedData().put("loadedGlassIds", new ArrayList<>(glassIds));
+ }
+ }
+
+ /**
+ * 娓呯┖宸茶杞界殑鐜荤拑ID鍒楄〃
+ */
+ private void clearLoadedGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("loadedGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 鑾峰彇宸插鐞嗙殑鐜荤拑ID鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getProcessedGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("processedGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 璁剧疆宸插鐞嗙殑鐜荤拑ID鍒楄〃
+ */
+ private void setProcessedGlassIds(TaskExecutionContext context, List<String> glassIds) {
+ if (context != null) {
+ context.getSharedData().put("processedGlassIds", new ArrayList<>(glassIds));
+ }
+ }
+
+ /**
+ * 娓呯┖宸插鐞嗙殑鐜荤拑ID鍒楄〃
+ */
+ private void clearProcessedGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("processedGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 鑾峰彇澶勭悊寮�濮嬫椂闂�
+ */
+ private Long getProcessStartTime(TaskExecutionContext context) {
+ if (context == null) {
+ return null;
+ }
+ Object time = context.getSharedData().get("processStartTime");
+ if (time instanceof Number) {
+ return ((Number) time).longValue();
+ }
+ return null;
+ }
+
+ /**
+ * 璁剧疆澶勭悊寮�濮嬫椂闂�
+ */
+ private void setProcessStartTime(TaskExecutionContext context, long time) {
+ if (context != null) {
+ context.getSharedData().put("processStartTime", time);
+ }
+ }
+
+ /**
+ * 娓呯┖澶勭悊寮�濮嬫椂闂�
+ */
+ private void clearProcessStartTime(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().remove("processStartTime");
+ }
+ }
+
+ /**
+ * 璁剧疆鍗ц浆绔嬫壂鐮佹殏鍋滄爣蹇�
+ */
+ private void setScannerPause(TaskExecutionContext context, boolean pause) {
+ if (context != null) {
+ context.getSharedData().put("scannerPause", pause);
+ }
+ }
+
+ private boolean isTaskCancelled(TaskExecutionContext context) {
+ if (context == null) {
+ return false;
+ }
+ Object cancelled = context.getSharedData().get("taskCancelled");
+ return cancelled instanceof Boolean && (Boolean) cancelled;
+ }
+
+ /**
+ * 妫�鏌ユ槸鍚﹂渶瑕佹殏鍋滃崸杞珛鎵爜
+ */
+ private boolean shouldPauseScanner(TaskExecutionContext context) {
+ if (context == null) {
+ return false;
+ }
+ Object pauseFlag = context.getSharedData().get("scannerPause");
+ return pauseFlag instanceof Boolean && (Boolean) pauseFlag;
+ }
+
+ /**
+ * 鑾峰彇宸叉壂鎻忕殑鐜荤拑ID鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getScannedGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("scannedGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 娓呯┖宸叉壂鎻忕殑鐜荤拑ID鍒楄〃
+ */
+ private void clearScannedGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("scannedGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 娉ㄥ唽瀹氭椂鍣ㄤ换鍔�
+ */
+ private void registerScheduledTask(String taskId, ScheduledFuture<?> future) {
+ taskScheduledTasks.computeIfAbsent(taskId, k -> new ArrayList<>()).add(future);
+ }
+
+ /**
+ * 鍋滄鎵�鏈夊畾鏃跺櫒浠诲姟
+ */
+ private void stopScheduledTasks(String taskId) {
+ List<ScheduledFuture<?>> futures = taskScheduledTasks.remove(taskId);
+ if (futures != null) {
+ for (ScheduledFuture<?> future : futures) {
+ if (future != null && !future.isCancelled()) {
+ future.cancel(false);
+ }
+ }
+ log.info("宸插仠姝换鍔$殑鎵�鏈夊畾鏃跺櫒: taskId={}, count={}", taskId, futures.size());
+ }
+ runningTaskContexts.remove(taskId);
+ }
+
+ /**
+ * 绛夊緟瀹氭椂鍣ㄤ换鍔″畬鎴愶紙甯﹁秴鏃讹級
+ */
+ private void waitForScheduledTasks(String taskId, TaskExecutionContext context) {
+ // 鑾峰彇浠诲姟瓒呮椂鏃堕棿锛堥粯璁�30鍒嗛挓锛�
+ TaskParameters params = context.getParameters();
+ long timeoutMinutes = params != null && params.getTimeoutMinutes() != null
+ ? params.getTimeoutMinutes() : 30;
+ long timeoutMs = timeoutMinutes * 60 * 1000;
+ long deadline = System.currentTimeMillis() + timeoutMs;
+
+ log.info("绛夊緟瀹氭椂鍣ㄤ换鍔″畬鎴�: taskId={}, timeout={}鍒嗛挓", taskId, timeoutMinutes);
+
+ while (System.currentTimeMillis() < deadline) {
+ List<ScheduledFuture<?>> futures = taskScheduledTasks.get(taskId);
+ if (futures == null || futures.isEmpty()) {
+ break;
+ }
+
+ // 妫�鏌ユ槸鍚︽墍鏈変换鍔¢兘宸插畬鎴�
+ boolean allDone = true;
+ for (ScheduledFuture<?> future : futures) {
+ if (future != null && !future.isDone()) {
+ allDone = false;
+ break;
+ }
+ }
+
+ if (allDone) {
+ break;
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+
+ log.info("瀹氭椂鍣ㄤ换鍔$瓑寰呭畬鎴�: taskId={}", taskId);
+ }
+
+ /**
+ * 鏇存柊姝ラ鐘舵��
+ */
+ private void updateStepStatus(TaskStepDetail step, DevicePlcVO.OperationResult result) {
+ if (step == null || result == null) {
+ return;
+ }
+ boolean success = Boolean.TRUE.equals(result.getSuccess());
+ step.setStatus(success
+ ? TaskStepDetail.Status.COMPLETED.name()
+ : TaskStepDetail.Status.FAILED.name());
+ // 璁剧疆娑堟伅锛氭垚鍔熸椂濡傛灉鏈夋秷鎭篃淇濆瓨锛屽け璐ユ椂淇濆瓨閿欒娑堟伅
+ String message = result.getMessage();
+ if (success) {
+ // 鎴愬姛鏃讹紝濡傛灉鏈夋秷鎭垯淇濆瓨锛堢敤浜庢彁绀轰俊鎭級锛屽惁鍒欐竻绌�
+ step.setSuccessMessage(StringUtils.hasText(message) ? message : null);
+ } else {
+ // 澶辫触鏃朵繚瀛橀敊璇秷鎭�
+ step.setErrorMessage(message);
+ }
+ step.setOutputData(toJson(result));
+ taskStepDetailMapper.updateById(step);
+ }
+
+ /**
+ * 鍒涘缓姝ラ鎽樿
+ */
+ private Map<String, Object> createStepSummary(String deviceName, boolean success, String message) {
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("deviceName", deviceName);
+ summary.put("success", success);
+ summary.put("message", message);
+ return summary;
+ }
+
+ /**
+ * 瑙f瀽璁惧閫昏緫鍙傛暟
+ */
+ @SuppressWarnings("unchecked")
+ private Map<String, Object> parseLogicParams(DeviceConfig device) {
+ String extraParams = device.getExtraParams();
+ if (!StringUtils.hasText(extraParams)) {
+ return Collections.emptyMap();
+ }
+ try {
+ Map<String, Object> extraParamsMap = objectMapper.readValue(extraParams, MAP_TYPE);
+ Object deviceLogic = extraParamsMap.get("deviceLogic");
+ if (deviceLogic instanceof Map) {
+ return (Map<String, Object>) deviceLogic;
+ }
+ return Collections.emptyMap();
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧閫昏緫鍙傛暟澶辫触: deviceId={}", device.getId(), e);
+ return Collections.emptyMap();
+ }
}
/**
@@ -346,6 +1253,74 @@
}
}
+ /**
+ * 鍒嗘壒鎵ц澶ц溅璁惧鐜荤拑涓婃枡锛堝綋鐜荤拑ID鏁伴噺瓒呰繃6涓笖璁剧疆浜嗗崟鐗囬棿闅旀椂锛�
+ */
+ private StepResult executeLoadVehicleWithBatches(MultiDeviceTask task,
+ DeviceConfig device,
+ int order,
+ TaskExecutionContext context,
+ List<Map<String, Object>> stepSummaries) {
+ List<String> allGlassIds = context.getParameters().getGlassIds();
+ Integer glassIntervalMs = context.getParameters().getGlassIntervalMs();
+ int batchSize = 6; // 姣忔壒鏈�澶�6涓幓鐠僆D
+
+ // 鍒嗘壒澶勭悊
+ int totalBatches = (allGlassIds.size() + batchSize - 1) / batchSize;
+ log.info("澶ц溅璁惧鍒嗘壒涓婃枡: deviceId={}, totalGlassIds={}, batchSize={}, totalBatches={}, glassIntervalMs={}",
+ device.getId(), allGlassIds.size(), batchSize, totalBatches, glassIntervalMs);
+
+ for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
+ int startIndex = batchIndex * batchSize;
+ int endIndex = Math.min(startIndex + batchSize, allGlassIds.size());
+ List<String> batchGlassIds = allGlassIds.subList(startIndex, endIndex);
+
+ // 鍒涘缓涓存椂鍙傛暟锛屽彧鍖呭惈褰撳墠鎵规鐨勭幓鐠僆D
+ TaskParameters batchParams = new TaskParameters();
+ batchParams.setGlassIds(new ArrayList<>(batchGlassIds));
+ batchParams.setGlassIntervalMs(glassIntervalMs);
+ batchParams.setPositionCode(context.getParameters().getPositionCode());
+ batchParams.setPositionValue(context.getParameters().getPositionValue());
+
+ // 鍒涘缓涓存椂涓婁笅鏂�
+ TaskExecutionContext batchContext = new TaskExecutionContext(batchParams);
+
+ // 鍒涘缓姝ラ璁板綍
+ TaskStepDetail step = createStepRecord(task, device, order);
+ step.setStepName(step.getStepName() + String.format(" (鎵规 %d/%d)", batchIndex + 1, totalBatches));
+
+ // 鎵ц褰撳墠鎵规
+ StepResult stepResult = executeStep(task, step, device, batchContext);
+ stepSummaries.add(stepResult.toSummary());
+
+ if (!stepResult.isSuccess()) {
+ log.error("澶ц溅璁惧鍒嗘壒涓婃枡澶辫触: deviceId={}, batchIndex={}/{}, error={}",
+ device.getId(), batchIndex + 1, totalBatches, stepResult.getMessage());
+ return stepResult;
+ }
+
+ log.info("澶ц溅璁惧鍒嗘壒涓婃枡鎴愬姛: deviceId={}, batchIndex={}/{}, glassIds={}",
+ device.getId(), batchIndex + 1, totalBatches, batchGlassIds);
+
+ // 濡傛灉涓嶆槸鏈�鍚庝竴鎵癸紝绛夊緟闂撮殧锛堟ā鎷熺幓鐠冩瘡鐗囪繍鍔ㄧ殑鏃堕棿锛�
+ // 杩欎釜绛夊緟璁╁ぇ杞︽湁鏃堕棿澶勭悊褰撳墠鎵规鐨勭幓鐠冿紝鐒跺悗鍐嶄紶閫掍笅涓�鎵�
+ if (batchIndex < totalBatches - 1 && glassIntervalMs != null && glassIntervalMs > 0) {
+ try {
+ log.info("绛夊緟鍗曠墖闂撮殧锛堟ā鎷熺幓鐠冭繍鍔ㄦ椂闂达級: glassIntervalMs={}ms, 澶ц溅鍙湪姝ゆ湡闂寸户缁鐜荤拑", glassIntervalMs);
+ Thread.sleep(glassIntervalMs);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return StepResult.failure(device.getDeviceName(), "绛夊緟鍗曠墖闂撮殧鏃惰涓柇");
+ }
+ }
+ }
+
+ // 鏇存柊涓婁笅鏂囦腑鐨勫凡鍔犺浇鐜荤拑ID
+ context.setLoadedGlassIds(new ArrayList<>(allGlassIds));
+
+ return StepResult.success(device.getDeviceName(), "鍒嗘壒涓婃枡瀹屾垚锛屽叡" + totalBatches + "鎵�");
+ }
+
private TaskStepDetail createStepRecord(MultiDeviceTask task, DeviceConfig device, int order) {
TaskStepDetail step = new TaskStepDetail();
step.setTaskId(task.getTaskId());
@@ -362,6 +1337,18 @@
TaskStepDetail step,
DeviceConfig device,
TaskExecutionContext context) {
+ DeviceCoordinationService.DependencyCheckResult dependencyResult =
+ deviceCoordinationService.checkDependencies(device, context);
+ if (!dependencyResult.isSatisfied()) {
+ log.warn("璁惧渚濊禆鏈弧瓒�: deviceId={}, message={}", device.getId(), dependencyResult.getMessage());
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(dependencyResult.getMessage());
+ step.setStartTime(new Date());
+ step.setEndTime(new Date());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), dependencyResult.getMessage());
+ }
return executeStepWithRetry(task, step, device, context, getRetryPolicy(device));
}
@@ -384,6 +1371,10 @@
}
Map<String, Object> params = buildOperationParams(device, context);
+ // 灏哻ontext寮曠敤鏀惧叆params锛屼緵璁惧澶勭悊鍣ㄤ娇鐢紙鐢ㄤ簬璁惧鍗忚皟锛�
+ params.put("_taskContext", context);
+ log.info("executeStepWithRetry鏋勫缓鍙傛暟: deviceId={}, deviceType={}, operation={}, paramsKeys={}, params={}",
+ device.getId(), device.getDeviceType(), determineOperation(device, params), params.keySet(), params);
step.setInputData(toJson(params));
taskStepDetailMapper.updateById(step);
@@ -424,7 +1415,7 @@
notificationService.notifyStepUpdate(task.getTaskId(), step);
if (opSuccess) {
- updateContextAfterSuccess(device, context, params);
+ updateContextAfterSuccess(device, context, params, result);
// 鍚屾璁惧鐘舵��
deviceCoordinationService.syncDeviceStatus(device,
@@ -511,6 +1502,69 @@
return StepResult.failure(device.getDeviceName(),
String.format("鎵ц澶辫触锛堝凡閲嶈瘯%d娆★級", retryAttempt));
+ }
+
+ /**
+ * 鎵ц涓�娆$畝鍗曠殑璁惧鎿嶄綔姝ラ锛堜笉璧颁氦浜掑紩鎿庯級锛岀敤浜庤Е鍙戣姹傜瓑鍦烘櫙
+ */
+ private StepResult executeDirectOperationStep(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context,
+ String operation,
+ Map<String, Object> params) {
+ Date startTime = new Date();
+ step.setStartTime(startTime);
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ step.setRetryCount(0);
+ step.setInputData(toJson(params));
+ taskStepDetailMapper.updateById(step);
+
+ try {
+ DeviceCoordinationService.DependencyCheckResult dependencyResult =
+ deviceCoordinationService.checkDependencies(device, context);
+ if (!dependencyResult.isSatisfied()) {
+ log.warn("鐩存帴鎿嶄綔渚濊禆鏈弧瓒�: deviceId={}, message={}", device.getId(), dependencyResult.getMessage());
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(dependencyResult.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), dependencyResult.getMessage());
+ }
+
+ DevicePlcVO.OperationResult result = deviceInteractionService.executeOperation(
+ device.getId(), operation, params);
+
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateStepAfterOperation(step, result, opSuccess);
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+
+ if (opSuccess) {
+ updateContextAfterSuccess(device, context, params, result);
+ // 绠�鍗曞悓姝ヨ澶囩姸鎬佷负宸插畬鎴�
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.COMPLETED, context);
+ return StepResult.success(device.getDeviceName(), result.getMessage());
+ } else {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ return StepResult.failure(device.getDeviceName(), result.getMessage());
+ }
+ } catch (Exception e) {
+ log.error("鐩存帴璁惧鎿嶄綔寮傚父, deviceId={}, operation={}", device.getId(), operation, e);
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ return StepResult.failure(device.getDeviceName(), e.getMessage());
+ }
}
/**
@@ -652,7 +1706,15 @@
step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
}
step.setStatus(success ? TaskStepDetail.Status.COMPLETED.name() : TaskStepDetail.Status.FAILED.name());
- step.setErrorMessage(success ? null : result.getMessage());
+ // 璁剧疆娑堟伅锛氭垚鍔熸椂濡傛灉鏈夋秷鎭篃淇濆瓨锛屽け璐ユ椂淇濆瓨閿欒娑堟伅
+ String message = result != null ? result.getMessage() : null;
+ if (success) {
+ // 鎴愬姛鏃讹紝濡傛灉鏈夋秷鎭垯淇濆瓨锛堢敤浜庢彁绀轰俊鎭級锛屽惁鍒欐竻绌�
+ step.setErrorMessage(StringUtils.hasText(message) ? message : null);
+ } else {
+ // 澶辫触鏃朵繚瀛橀敊璇秷鎭�
+ step.setErrorMessage(message);
+ }
step.setOutputData(toJson(result));
taskStepDetailMapper.updateById(step);
}
@@ -671,20 +1733,48 @@
}
private void updateTaskProgress(MultiDeviceTask task, int currentStep, boolean success) {
- task.setCurrentStep(currentStep);
if (!success) {
task.setStatus(MultiDeviceTask.Status.FAILED.name());
}
+
+ // 璁$畻宸插畬鎴愮殑姝ラ鏁帮紙鐢ㄤ簬杩涘害鏄剧ず锛�
+ int completedSteps = countCompletedSteps(task.getTaskId());
+ int progressStep = success
+ ? completedSteps
+ : Math.max(completedSteps, currentStep); // 澶辫触鏃惰嚦灏戞樉绀哄綋鍓嶆楠�
+
LambdaUpdateWrapper<MultiDeviceTask> update = Wrappers.<MultiDeviceTask>lambdaUpdate()
.eq(MultiDeviceTask::getId, task.getId())
- .set(MultiDeviceTask::getCurrentStep, currentStep);
+ .set(MultiDeviceTask::getCurrentStep, progressStep);
if (!success) {
update.set(MultiDeviceTask::getStatus, MultiDeviceTask.Status.FAILED.name());
}
multiDeviceTaskMapper.update(null, update);
- // 閫氱煡浠诲姟鐘舵�佹洿鏂�
+ // 鏇存柊浠诲姟瀵硅薄鐨勮繘搴︼紝鐢ㄤ簬閫氱煡
+ task.setCurrentStep(progressStep);
+
+ // 閫氱煡浠诲姟鐘舵�佹洿鏂帮紙鍖呭惈杩涘害淇℃伅锛�
notificationService.notifyTaskStatus(task);
+ }
+
+ /**
+ * 缁熻宸插畬鎴愮殑姝ラ鏁�
+ */
+ private int countCompletedSteps(String taskId) {
+ if (taskId == null) {
+ return 0;
+ }
+ try {
+ return taskStepDetailMapper.selectCount(
+ Wrappers.<TaskStepDetail>lambdaQuery()
+ .eq(TaskStepDetail::getTaskId, taskId)
+ .eq(TaskStepDetail::getStatus, TaskStepDetail.Status.COMPLETED.name())
+ ).intValue();
+ } catch (Exception e) {
+ log.warn("缁熻宸插畬鎴愭楠ゆ暟澶辫触: taskId={}", taskId, e);
+ return 0;
+ }
}
private String determineOperation(DeviceConfig device, Map<String, Object> params) {
@@ -710,6 +1800,10 @@
if (taskParams.getPositionValue() != null) {
params.put("positionValue", taskParams.getPositionValue());
}
+ // 浼犻�掑崟鐗囬棿闅旈厤缃紝濡傛灉浠诲姟鍙傛暟涓湁璁剧疆锛屼紭鍏堜娇鐢ㄤ换鍔″弬鏁扮殑锛屽惁鍒欎娇鐢ㄨ澶囬厤缃殑
+ if (taskParams.getGlassIntervalMs() != null) {
+ params.put("glassIntervalMs", taskParams.getGlassIntervalMs());
+ }
params.put("triggerRequest", true);
break;
case DeviceConfig.DeviceType.LARGE_GLASS:
@@ -724,19 +1818,21 @@
params.put("processType", taskParams.getProcessType() != null ? taskParams.getProcessType() : 1);
params.put("triggerRequest", true);
break;
- case DeviceConfig.DeviceType.GLASS_STORAGE:
- List<String> processed = context.getSafeProcessedGlassIds();
- if (CollectionUtils.isEmpty(processed)) {
- processed = context.getSafeLoadedGlassIds();
+ case DeviceConfig.DeviceType.WORKSTATION_SCANNER:
+ // 鍗ц浆绔嬫壂鐮佽澶囷細浠庝换鍔″弬鏁颁腑鑾峰彇鐜荤拑ID鍒楄〃锛屽彇绗竴涓綔涓哄綋鍓嶈娴嬭瘯鐨勭幓鐠僆D
+ // 娉ㄦ剰锛氭壂鐮佽澶囬�氬父閫氳繃瀹氭椂鍣ㄦ墽琛岋紝浣嗗鏋滈�氳繃executeStep鎵ц锛屼篃闇�瑕佷紶閫抔lassId
+ log.info("buildOperationParams澶勭悊鎵爜璁惧: deviceId={}, taskParams.glassIds={}, isEmpty={}",
+ device.getId(), taskParams.getGlassIds(),
+ CollectionUtils.isEmpty(taskParams.getGlassIds()));
+ if (!CollectionUtils.isEmpty(taskParams.getGlassIds())) {
+ params.put("glassId", taskParams.getGlassIds().get(0));
+ params.put("glassIds", new ArrayList<>(taskParams.getGlassIds()));
+ log.info("buildOperationParams涓烘壂鐮佽澶囨坊鍔爂lassId: deviceId={}, glassId={}, glassIdsSize={}",
+ device.getId(), taskParams.getGlassIds().get(0), taskParams.getGlassIds().size());
+ } else {
+ log.warn("buildOperationParams鎵爜璁惧glassIds涓虹┖: deviceId={}, taskParams.glassIds={}, taskParams={}",
+ device.getId(), taskParams.getGlassIds(), taskParams);
}
- if (!CollectionUtils.isEmpty(processed)) {
- params.put("glassId", processed.get(0));
- params.put("glassIds", new ArrayList<>(processed));
- }
- if (taskParams.getStoragePosition() != null) {
- params.put("storagePosition", taskParams.getStoragePosition());
- }
- params.put("triggerRequest", true);
break;
default:
if (!CollectionUtils.isEmpty(taskParams.getExtra())) {
@@ -763,10 +1859,14 @@
private void updateContextAfterSuccess(DeviceConfig device,
TaskExecutionContext context,
- Map<String, Object> params) {
+ Map<String, Object> params,
+ DevicePlcVO.OperationResult result) {
List<String> glassIds = extractGlassIds(params);
-
+
switch (device.getDeviceType()) {
+ case DeviceConfig.DeviceType.WORKSTATION_SCANNER:
+ handleScannerSuccess(context, result);
+ break;
case DeviceConfig.DeviceType.LOAD_VEHICLE:
context.setLoadedGlassIds(glassIds);
// 鏁版嵁浼犻�掞細澶ц溅璁惧 -> 涓嬩竴涓澶�
@@ -792,6 +1892,23 @@
}
}
+ private void handleScannerSuccess(TaskExecutionContext context,
+ DevicePlcVO.OperationResult result) {
+ List<String> scannerGlassIds = extractGlassIdsFromResult(result);
+ if (CollectionUtils.isEmpty(scannerGlassIds)) {
+ String workLine = resolveWorkLineFromResult(result, context.getParameters());
+ scannerGlassIds = glassInfoService.getRecentScannedGlassIds(
+ SCANNER_LOOKBACK_MINUTES, SCANNER_LOOKBACK_LIMIT, workLine);
+ }
+ if (!CollectionUtils.isEmpty(scannerGlassIds)) {
+ context.getParameters().setGlassIds(new ArrayList<>(scannerGlassIds));
+ context.setLoadedGlassIds(new ArrayList<>(scannerGlassIds));
+ log.info("鍗ц浆绔嬫壂鐮佽幏鍙栧埌鐜荤拑ID: {}", scannerGlassIds);
+ } else {
+ log.warn("鍗ц浆绔嬫壂鐮佹湭鑾峰彇鍒扮幓鐠僆D锛屽悗缁澶囧彲鑳芥棤娉曟墽琛�");
+ }
+ }
+
private List<String> extractGlassIds(Map<String, Object> params) {
if (params == null) {
return Collections.emptyList();
@@ -809,6 +1926,45 @@
return Collections.emptyList();
}
+ @SuppressWarnings("unchecked")
+ private List<String> extractGlassIdsFromResult(DevicePlcVO.OperationResult result) {
+ if (result == null || result.getData() == null) {
+ return Collections.emptyList();
+ }
+ Object data = result.getData().get("glassIds");
+ if (data instanceof List) {
+ List<Object> raw = (List<Object>) data;
+ List<String> converted = new ArrayList<>();
+ for (Object item : raw) {
+ if (item != null) {
+ converted.add(String.valueOf(item));
+ }
+ }
+ return converted;
+ }
+ if (data instanceof String && StringUtils.hasText((String) data)) {
+ return Collections.singletonList((String) data);
+ }
+ return Collections.emptyList();
+ }
+
+ private String resolveWorkLineFromResult(DevicePlcVO.OperationResult result,
+ TaskParameters parameters) {
+ if (result != null && result.getData() != null) {
+ Object workLine = result.getData().get("workLine");
+ if (workLine != null && StringUtils.hasText(String.valueOf(workLine))) {
+ return String.valueOf(workLine);
+ }
+ }
+ if (parameters != null && !CollectionUtils.isEmpty(parameters.getExtra())) {
+ Object extraWorkLine = parameters.getExtra().get("workLine");
+ if (extraWorkLine != null) {
+ return String.valueOf(extraWorkLine);
+ }
+ }
+ return null;
+ }
+
private String toJson(Object value) {
try {
return objectMapper.writeValueAsString(value);
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java
index 2cdac06..c2d2e35 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java
@@ -20,7 +20,6 @@
import com.mes.task.service.MultiDeviceTaskService;
import com.mes.task.service.TaskExecutionEngine;
import com.mes.task.service.TaskStatusNotificationService;
-import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@@ -32,13 +31,13 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
/**
* 澶氳澶囦换鍔℃湇鍔″疄鐜�
*/
@Slf4j
@Service
-@RequiredArgsConstructor
public class MultiDeviceTaskServiceImpl extends ServiceImpl<MultiDeviceTaskMapper, MultiDeviceTask>
implements MultiDeviceTaskService {
@@ -48,6 +47,21 @@
private final TaskExecutionEngine taskExecutionEngine;
private final TaskStatusNotificationService notificationService;
private final ObjectMapper objectMapper;
+
+ public MultiDeviceTaskServiceImpl(
+ DeviceGroupConfigService deviceGroupConfigService,
+ DeviceGroupRelationMapper deviceGroupRelationMapper,
+ TaskStepDetailMapper taskStepDetailMapper,
+ TaskExecutionEngine taskExecutionEngine,
+ TaskStatusNotificationService notificationService,
+ ObjectMapper objectMapper) {
+ this.deviceGroupConfigService = deviceGroupConfigService;
+ this.deviceGroupRelationMapper = deviceGroupRelationMapper;
+ this.taskStepDetailMapper = taskStepDetailMapper;
+ this.taskExecutionEngine = taskExecutionEngine;
+ this.notificationService = notificationService;
+ this.objectMapper = objectMapper;
+ }
@Override
@Transactional(rollbackFor = Exception.class)
@@ -66,8 +80,15 @@
}
TaskParameters parameters = request.getParameters();
- if (parameters == null || CollectionUtils.isEmpty(parameters.getGlassIds())) {
- throw new IllegalArgumentException("鑷冲皯闇�瑕侀厤缃竴鏉$幓鐠僆D");
+ if (parameters == null) {
+ parameters = new TaskParameters();
+ }
+
+ // 榛樿鍏佽鍗ц浆绔嬫壂鐮佽澶囧湪浠诲姟鎵ц闃舵鑾峰彇鐜荤拑淇℃伅
+ boolean hasGlassIds = !CollectionUtils.isEmpty(parameters.getGlassIds());
+ if (!hasGlassIds) {
+ log.info("娴嬭瘯浠诲姟鏈彁渚涚幓鐠僆D锛屽皢鍦ㄨ澶囩粍娴佺▼涓敱鍗ц浆绔嬫壂鐮佽澶囬噰闆嗙幓鐠冧俊鎭�: groupId={}",
+ groupConfig.getId());
}
// 鍒涘缓浠诲姟璁板綍
@@ -113,11 +134,26 @@
// 鎵ц浠诲姟
TaskExecutionResult result = taskExecutionEngine.execute(task, groupConfig, devices, parameters);
- // 鏇存柊浠诲姟缁撴灉
+ // 妫�鏌ヤ换鍔℃暟鎹腑鏄惁鍖呭惈鎸佺画杩愯鐨勬爣璁�
+ Map<String, Object> resultData = result.getData();
+ boolean isContinuousTask = resultData != null && "浠诲姟宸插惎鍔紝瀹氭椂鍣ㄥ湪鍚庡彴杩愯涓�".equals(resultData.get("message"));
+
+ // 濡傛灉鏄寔缁繍琛岀殑浠诲姟锛堝畾鏃跺櫒妯″紡锛夛紝淇濇寔 RUNNING 鐘舵�侊紝涓嶆洿鏂颁负 COMPLETED
+ if (isContinuousTask && result.isSuccess()) {
+ log.info("浠诲姟宸插惎鍔ㄥ畾鏃跺櫒锛屼繚鎸佽繍琛岀姸鎬�: taskId={}, message={}",
+ task.getTaskId(), resultData.get("message"));
+ task.setResultData(writeJson(resultData));
+ updateById(task);
+ // 閫氱煡浠诲姟鐘舵�侊紙淇濇寔 RUNNING锛�
+ notificationService.notifyTaskStatus(task);
+ return;
+ }
+
+ // 鏇存柊浠诲姟缁撴灉锛堥潪鎸佺画杩愯鐨勪换鍔★級
task.setStatus(result.isSuccess() ? MultiDeviceTask.Status.COMPLETED.name() : MultiDeviceTask.Status.FAILED.name());
task.setErrorMessage(result.isSuccess() ? null : result.getMessage());
task.setEndTime(new Date());
- task.setResultData(writeJson(result.getData()));
+ task.setResultData(writeJson(resultData));
updateById(task);
// 閫氱煡浠诲姟瀹屾垚
@@ -164,9 +200,14 @@
if (!MultiDeviceTask.Status.RUNNING.name().equals(task.getStatus())) {
return false;
}
+ taskExecutionEngine.requestTaskCancellation(taskId);
task.setStatus(MultiDeviceTask.Status.CANCELLED.name());
task.setEndTime(new Date());
- return updateById(task);
+ boolean updated = updateById(task);
+ if (updated) {
+ notificationService.notifyTaskStatus(task);
+ }
+ return updated;
}
@Override
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/TaskStatusNotificationServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/TaskStatusNotificationServiceImpl.java
index 9924f4a..451e98a 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/TaskStatusNotificationServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/TaskStatusNotificationServiceImpl.java
@@ -204,7 +204,12 @@
.name(eventName)
.data(createMessage("", data)));
} catch (IOException e) {
- log.warn("鍙戦�丼SE娑堟伅澶辫触: taskId={}, event={}", taskId, eventName, e);
+ // 瀹㈡埛绔柇寮�杩炴帴鏄甯告儏鍐碉紝浣跨敤DEBUG绾у埆
+ if (e instanceof org.apache.catalina.connector.ClientAbortException) {
+ log.debug("瀹㈡埛绔柇寮�SSE杩炴帴: taskId={}, event={}", taskId, eventName);
+ } else {
+ log.warn("鍙戦�丼SE娑堟伅澶辫触: taskId={}, event={}", taskId, eventName, e);
+ }
toRemove.add(emitter);
}
}
@@ -228,7 +233,12 @@
.name(eventName)
.data(createMessage("", data)));
} catch (IOException e) {
- log.warn("鍙戦�丼SE娑堟伅澶辫触: event={}", eventName, e);
+ // 瀹㈡埛绔柇寮�杩炴帴鏄甯告儏鍐碉紝浣跨敤DEBUG绾у埆
+ if (e instanceof org.apache.catalina.connector.ClientAbortException) {
+ log.debug("瀹㈡埛绔柇寮�SSE杩炴帴: event={}", eventName);
+ } else {
+ log.warn("鍙戦�丼SE娑堟伅澶辫触: event={}", eventName, e);
+ }
toRemove.add(emitter);
}
}
diff --git a/mes-web/src/views/device/DeviceEditDialog.vue b/mes-web/src/views/device/DeviceEditDialog.vue
index 088e292..2845239 100644
--- a/mes-web/src/views/device/DeviceEditDialog.vue
+++ b/mes-web/src/views/device/DeviceEditDialog.vue
@@ -354,7 +354,7 @@
const deviceTypesLoading = ref(false)
// 璁惧閫昏緫鍙傛暟锛堟牴鎹澶囩被鍨嬪姩鎬佹樉绀猴級
-const deviceLogicParams = reactive({})
+const deviceLogicParams = ref({})
const S7_PLC_TYPES = ['S1200', 'S1500']
const MODBUS_PLC_TYPES = ['MODBUS']
@@ -683,15 +683,11 @@
}
// 鍔犺浇璁惧閫昏緫鍙傛暟
-const loadDeviceLogicParams = (deviceLogic, deviceType) => {
- // 娓呯┖鐜版湁鍙傛暟
- Object.keys(deviceLogicParams).forEach(key => {
- delete deviceLogicParams[key]
- })
-
- // 鏍规嵁璁惧绫诲瀷鍔犺浇瀵瑰簲鐨勫弬鏁�
+const loadDeviceLogicParams = (deviceLogic) => {
if (deviceLogic && Object.keys(deviceLogic).length > 0) {
- Object.assign(deviceLogicParams, deviceLogic)
+ deviceLogicParams.value = { ...deviceLogic }
+ } else {
+ deviceLogicParams.value = {}
}
}
@@ -701,9 +697,7 @@
deviceFormRef.value?.clearValidate()
// 閲嶇疆璁惧閫昏緫鍙傛暟
- Object.keys(deviceLogicParams).forEach(key => {
- delete deviceLogicParams[key]
- })
+ deviceLogicParams.value = {}
}
const addConfigParam = () => {
@@ -778,8 +772,10 @@
}
// 淇濆瓨璁惧閫昏緫鍙傛暟锛堢洿鎺ヤ娇鐢╠eviceLogicParams锛岀敱鍚勪釜閰嶇疆缁勪欢绠$悊锛�
- if (deviceLogicParams && Object.keys(deviceLogicParams).length > 0) {
- extraObj.deviceLogic = { ...deviceLogicParams }
+ if (deviceLogicParams.value && Object.keys(deviceLogicParams.value).length > 0) {
+ extraObj.deviceLogic = { ...deviceLogicParams.value }
+ } else {
+ delete extraObj.deviceLogic
}
// 鏋勫缓 configJson锛氬皢 configParams 鏁扮粍杞崲涓� JSON 瀛楃涓�
diff --git a/mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue b/mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue
index 5c2a3a7..3bdf568 100644
--- a/mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue
+++ b/mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue
@@ -9,6 +9,7 @@
:max="10000"
:step="100"
style="width: 100%;"
+ @change="emitConfigUpdate"
/>
<span class="form-tip">杞﹁締鏈�澶у閲�</span>
</el-form-item>
@@ -22,6 +23,7 @@
:step="0.1"
:precision="1"
style="width: 100%;"
+ @change="emitConfigUpdate"
/>
<span class="form-tip">杞﹁締杩愬姩閫熷害锛岄粯璁�1鏍�/绉�</span>
</el-form-item>
@@ -50,6 +52,7 @@
:max="10000"
:step="100"
style="width: 100%;"
+ @change="emitConfigUpdate"
/>
<span class="form-tip">褰撶幓鐠冩湭鎻愪緵闀垮害鏃朵娇鐢ㄧ殑榛樿鍊�</span>
</el-form-item>
@@ -65,6 +68,7 @@
:max="1000"
:step="1"
style="width: 100%;"
+ @change="emitConfigUpdate"
/>
<span class="form-tip">杞﹁締鍒濆浣嶇疆锛堟牸瀛愶級</span>
</el-form-item>
@@ -78,6 +82,7 @@
:step="1"
style="width: 40%;"
placeholder="鏈�灏�"
+ @change="emitConfigUpdate"
/>
<span style="margin: 0 2%;">~</span>
<el-input-number
@@ -87,6 +92,7 @@
:step="1"
style="width: 40%;"
placeholder="鏈�澶�"
+ @change="emitConfigUpdate"
/>
<span class="form-tip">杩愬姩璺濈鑼冨洿锛堟牸瀛愶級</span>
</el-form-item>
@@ -137,7 +143,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="鑷姩涓婃枡">
- <el-switch v-model="config.autoFeed" />
+ <el-switch v-model="config.autoFeed" @change="emitConfigUpdate" />
<span class="form-tip">鏄惁鑷姩瑙﹀彂涓婃枡璇锋眰</span>
</el-form-item>
</el-col>
@@ -152,6 +158,7 @@
:max="10"
:step="1"
style="width: 100%;"
+ @change="emitConfigUpdate"
/>
</el-form-item>
</el-col>
@@ -160,30 +167,31 @@
<el-form-item label="浣嶇疆鏄犲皠">
<div class="position-mapping">
<div
- v-for="(value, key, index) in config.positionMapping"
- :key="index"
+ v-for="(item, index) in positionList"
+ :key="item.id"
class="mapping-item"
>
<el-input
- v-model="mappingKeys[index]"
+ v-model="item.key"
placeholder="浣嶇疆浠g爜锛堝900/901锛�"
size="small"
style="width: 150px; margin-right: 10px;"
- @input="updatePositionMapping(index, $event, value)"
+ @change="handlePositionKeyChange(index)"
/>
<el-input-number
- v-model="config.positionMapping[mappingKeys[index] || key]"
+ v-model="item.value"
:min="0"
:max="1000"
:step="1"
size="small"
style="width: 120px; margin-right: 10px;"
placeholder="浣嶇疆鍊硷紙鏍硷級"
+ @change="handlePositionValueChange(index)"
/>
<el-button
type="danger"
size="small"
- @click="removePositionMapping(key)"
+ @click="removePositionMapping(index)"
>
鍒犻櫎
</el-button>
@@ -226,8 +234,40 @@
positionMapping: {}
})
-// 浣嶇疆鏄犲皠鐨勯敭鏁扮粍
-const mappingKeys = ref([])
+// 浣嶇疆鏄犲皠缂栬緫鍒楄〃
+const positionList = ref([])
+let suppressPositionSync = false
+let suppressEmit = false
+
+const emitConfigUpdate = () => {
+ if (suppressEmit) return
+ emit('update:modelValue', { ...config.value })
+}
+
+const syncPositionListFromConfig = () => {
+ suppressPositionSync = true
+ const entries = Object.entries(config.value.positionMapping || {})
+ positionList.value = entries.map(([key, value], idx) => ({
+ id: `${key}_${idx}_${Date.now()}`,
+ key,
+ value: value ?? 1
+ }))
+ suppressPositionSync = false
+}
+
+const applyPositionListToConfig = () => {
+ if (suppressPositionSync) return
+ const mapping = {}
+ positionList.value.forEach((item) => {
+ if (item.key && item.key.trim()) {
+ mapping[item.key.trim()] = item.value ?? 1
+ }
+ })
+ config.value.positionMapping = mapping
+ emitConfigUpdate()
+}
+
+syncPositionListFromConfig()
// 鏃堕棿瀛楁锛堢锛�- 鐢ㄤ簬鍓嶇鏄剧ず鍜岃緭鍏�
const glassIntervalSeconds = ref(1.0)
@@ -238,6 +278,7 @@
// 鐩戝惉props鍙樺寲
watch(() => props.modelValue, (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
+ suppressEmit = true
config.value = {
vehicleCapacity: newVal.vehicleCapacity ?? 6000,
vehicleSpeed: newVal.vehicleSpeed ?? 1.0,
@@ -258,67 +299,54 @@
idleMonitorIntervalSeconds.value = (config.value.idleMonitorIntervalMs ?? 2000) / 1000
taskMonitorIntervalSeconds.value = (config.value.taskMonitorIntervalMs ?? 1000) / 1000
mesConfirmTimeoutSeconds.value = (config.value.mesConfirmTimeoutMs ?? 30000) / 1000
- mappingKeys.value = Object.keys(config.value.positionMapping)
+ syncPositionListFromConfig()
+ suppressEmit = false
}
}, { immediate: true, deep: true })
// 鐩戝惉绉掑瓧娈靛彉鍖栵紝杞崲涓烘绉掑苟鏇存柊config
watch(glassIntervalSeconds, (val) => {
config.value.glassIntervalMs = Math.round(val * 1000)
- emit('update:modelValue', { ...config.value })
+ emitConfigUpdate()
})
watch(idleMonitorIntervalSeconds, (val) => {
config.value.idleMonitorIntervalMs = Math.round(val * 1000)
- emit('update:modelValue', { ...config.value })
+ emitConfigUpdate()
})
watch(taskMonitorIntervalSeconds, (val) => {
config.value.taskMonitorIntervalMs = Math.round(val * 1000)
- emit('update:modelValue', { ...config.value })
+ emitConfigUpdate()
})
watch(mesConfirmTimeoutSeconds, (val) => {
config.value.mesConfirmTimeoutMs = Math.round(val * 1000)
- emit('update:modelValue', { ...config.value })
+ emitConfigUpdate()
})
// 鐩戝惉config鍏朵粬瀛楁鍙樺寲锛屽悓姝ュ埌鐖剁粍浠�
-watch(() => [
- config.value.vehicleCapacity,
- config.value.vehicleSpeed,
- config.value.defaultGlassLength,
- config.value.homePosition,
- config.value.minRange,
- config.value.maxRange,
- config.value.autoFeed,
- config.value.maxRetryCount,
- config.value.positionMapping
-], () => {
- emit('update:modelValue', { ...config.value })
-}, { deep: true })
-
// 浣嶇疆鏄犲皠鐩稿叧鏂规硶
const addPositionMapping = () => {
- const newKey = `POS${Object.keys(config.value.positionMapping).length + 1}`
- config.value.positionMapping[newKey] = 1
- mappingKeys.value.push(newKey)
+ const nextIndex = positionList.value.length + 1
+ positionList.value.push({
+ id: `POS_${Date.now()}_${nextIndex}`,
+ key: '',
+ value: 1
+ })
}
-const removePositionMapping = (key) => {
- delete config.value.positionMapping[key]
- mappingKeys.value = mappingKeys.value.filter(k => k !== key)
+const removePositionMapping = (idx) => {
+ positionList.value.splice(idx, 1)
+ applyPositionListToConfig()
}
-const updatePositionMapping = (index, newKey, oldValue) => {
- const oldKey = mappingKeys.value[index]
- if (oldKey && oldKey !== newKey) {
- delete config.value.positionMapping[oldKey]
- }
- mappingKeys.value[index] = newKey
- if (newKey) {
- config.value.positionMapping[newKey] = oldValue || 1
- }
+const handlePositionKeyChange = () => {
+ applyPositionListToConfig()
+}
+
+const handlePositionValueChange = () => {
+ applyPositionListToConfig()
}
</script>
diff --git a/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue b/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
index e46b9c0..8729075 100644
--- a/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
+++ b/mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
@@ -105,8 +105,8 @@
{{ selectedDevice.moduleName }}
</el-descriptions-item>
<el-descriptions-item label="鏄惁鍚敤">
- <el-tag :type="selectedDevice.enabled ? 'success' : 'info'">
- {{ selectedDevice.enabled ? '鍚敤' : '鍋滅敤' }}
+ <el-tag :type="getEnabledType(selectedDevice.enabled)">
+ {{ getEnabledLabel(selectedDevice.enabled) }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
@@ -229,6 +229,22 @@
return String(status)
}
+const getEnabledType = (enabled) => {
+ // 鏀寔鏁板瓧 1/0銆佸竷灏斿�� true/false銆佸瓧绗︿覆 '1'/'0'
+ if (enabled === 1 || enabled === true || enabled === '1' || String(enabled).toUpperCase() === 'TRUE') {
+ return 'success'
+ }
+ return 'info'
+}
+
+const getEnabledLabel = (enabled) => {
+ // 鏀寔鏁板瓧 1/0銆佸竷灏斿�� true/false銆佸瓧绗︿覆 '1'/'0'
+ if (enabled === 1 || enabled === true || enabled === '1' || String(enabled).toUpperCase() === 'TRUE') {
+ return '鍚敤'
+ }
+ return '鍋滅敤'
+}
+
watch(
() => props.group,
() => {
diff --git a/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue b/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
index 74906ce..649cd93 100644
--- a/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
+++ b/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
@@ -26,21 +26,34 @@
</div>
<el-form :model="form" label-width="120px" :rules="rules" ref="formRef">
- <el-form-item label="鐜荤拑ID鍒楄〃" prop="glassIds" required>
+ <el-form-item label="鐜荤拑ID鍒楄〃" prop="glassIds">
<el-input
v-model="glassIdsInput"
type="textarea"
:rows="4"
- placeholder="璇疯緭鍏ョ幓鐠冩潯鐮侊紝鏀寔澶氳鎴栭�楀彿鍒嗛殧锛屾瘡琛屼竴涓垨閫楀彿鍒嗛殧"
+ placeholder="鍙�夛細濡傛灉杈撳叆鐜荤拑ID锛屽皢浣跨敤杈撳叆鐨処D杩涜娴嬭瘯锛堜唬鏇垮崸杞珛鎵爜锛夛紱濡傛灉涓嶈緭鍏ワ紝灏嗕粠鏁版嵁搴撹鍙栨渶杩戞壂鐮佺殑鐜荤拑ID杩涜娴嬭瘯"
show-word-limit
:maxlength="5000"
/>
<div class="form-tip">
- 宸茶緭鍏� {{ glassIds.length }} 涓幓鐠僆D
+ <span v-if="glassIds.length > 0">宸茶緭鍏� {{ glassIds.length }} 涓幓鐠僆D锛堟祴璇曟ā寮忥細浣跨敤杈撳叆鐨処D锛�</span>
+ <span v-else>鏈緭鍏ョ幓鐠僆D锛堟甯告ā寮忥細灏嗕粠鏁版嵁搴撹鍙栨渶杩戞壂鐮佺殑鐜荤拑ID锛�</span>
</div>
</el-form-item>
<el-divider content-position="left">鎵ц閰嶇疆</el-divider>
+
+ <el-form-item label="鍗曠墖闂撮殧 (绉�)">
+ <el-input-number
+ v-model="form.glassIntervalSeconds"
+ :min="0"
+ :max="60"
+ :step="0.1"
+ :precision="1"
+ placeholder="姣忎釜鐜荤拑ID涔嬮棿鐨勯棿闅旀椂闂�"
+ />
+ <div class="form-tip">澶氫釜鐜荤拑ID鏃讹紝姣忎釜鐜荤拑ID浼犻�掍箣闂寸殑闂撮殧鏃堕棿锛堢锛夛紝鐢ㄤ簬妯℃嫙鐜荤拑姣忕墖杩愬姩鐨勬椂闂淬��0琛ㄧず涓�娆℃�у叏閮ㄤ紶閫�</div>
+ </el-form-item>
<el-form-item label="鎵ц闂撮殧 (ms)">
<el-input-number
@@ -93,8 +106,9 @@
})
const emit = defineEmits(['task-started'])
-
+//閰嶇疆榛樿鍊�
const form = reactive({
+ glassIntervalSeconds: 10, // 鍗曠墖闂撮殧锛岄粯璁�10绉�
executionInterval: 1000,
timeoutMinutes: 30,
retryCount: 3
@@ -106,8 +120,10 @@
glassIds: [
{
validator: (rule, value, callback) => {
+ // 濡傛灉杈撳叆浜嗙幓鐠僆D锛屽垯杩涜楠岃瘉锛涘鏋滄病鏈夎緭鍏ワ紝鍒欏厑璁革紙灏嗕粠鏁版嵁搴撹鍙栵級
if (glassIds.value.length === 0) {
- callback(new Error('璇疯嚦灏戣緭鍏ヤ竴涓幓鐠僆D'))
+ // 鍏佽涓虹┖锛屽皢浠庢暟鎹簱璇诲彇鏈�杩戞壂鐮佺殑鐜荤拑ID
+ callback()
} else if (glassIds.value.length > 100) {
callback(new Error('鐜荤拑ID鏁伴噺涓嶈兘瓒呰繃100涓�'))
} else {
@@ -151,6 +167,8 @@
.filter((item) => item.length > 0)
})
+const normalizeType = (type) => (type || '').trim().toUpperCase()
+
const fetchLoadDevice = async () => {
loadDeviceId.value = null
loadDeviceName.value = ''
@@ -172,9 +190,15 @@
: Array.isArray(rawList?.data)
? rawList.data
: []
- const targetDevice =
- deviceList.find((item) => (item.deviceType || '').toUpperCase() === 'LOAD_VEHICLE') ||
- deviceList[0]
+ const scannerDevice = deviceList.find((item) => {
+ const type = normalizeType(item.deviceType)
+ return type.includes('SCANNER') || type.includes('鎵爜')
+ })
+ const loadVehicleDevice = deviceList.find((item) => {
+ const type = normalizeType(item.deviceType)
+ return type.includes('LOAD_VEHICLE') || type.includes('澶ц溅')
+ })
+ const targetDevice = scannerDevice || loadVehicleDevice || deviceList[0]
if (targetDevice && targetDevice.id) {
loadDeviceId.value = targetDevice.id
loadDeviceName.value = targetDevice.deviceName || targetDevice.deviceCode || `ID: ${targetDevice.id}`
@@ -202,17 +226,18 @@
return
}
- if (glassIds.value.length === 0) {
- ElMessage.warning('璇疯嚦灏戣緭鍏ヤ竴涓幓鐠僆D')
- return
- }
-
try {
loading.value = true
// 鏋勫缓浠诲姟鍙傛暟
+ // 濡傛灉杈撳叆浜嗙幓鐠僆D锛屼娇鐢ㄨ緭鍏ョ殑锛涘鏋滄病鏈夎緭鍏ワ紝glassIds涓虹┖鏁扮粍锛屽悗绔細浠庢暟鎹簱璇诲彇
+ // 灏嗙杞崲涓烘绉掍紶缁欏悗绔�
+ const glassIntervalMs = form.glassIntervalSeconds != null && form.glassIntervalSeconds !== undefined
+ ? Math.round(form.glassIntervalSeconds * 1000)
+ : 1000
const parameters = {
- glassIds: glassIds.value,
+ glassIds: glassIds.value.length > 0 ? glassIds.value : [],
+ glassIntervalMs: glassIntervalMs,
executionInterval: form.executionInterval || 1000
}
@@ -261,14 +286,14 @@
return
}
if (!loadDeviceId.value) {
- ElMessage.warning('鏈壘鍒颁笂澶ц溅璁惧锛屾棤娉曟竻绌篜LC')
+ ElMessage.warning('鏈壘鍒板搴旇澶囷紝鏃犳硶娓呯┖PLC')
return
}
try {
clearLoading.value = true
const response = await deviceInteractionApi.executeOperation({
deviceId: loadDeviceId.value,
- operation: 'clearGlass',
+ operation: 'clearPlc',
params: {}
})
if (response?.code !== 200) {
--
Gitblit v1.8.0