From 3f797dd834a2de283cf5eff2ff1124e5a0ccb233 Mon Sep 17 00:00:00 2001
From: Administrator <XS@163.COM>
Date: 星期六, 25 四月 2026 11:18:50 +0800
Subject: [PATCH] 更改 2026-4-25

---
 src/main/webapp/components/MapCanvas.js                              |  196 ++
 src/main/java/com/zy/core/thread/impl/ZySiemensCrnV2Thread.java      |    4 
 src/main/webapp/static/js/basDevp/basDevp.js                         |    2 
 src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java        |    4 
 src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java        |  112 +
 src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java          |   24 
 src/main/java/com/zy/asrs/task/WrkMastScheduler.java                 |   40 
 src/main/java/com/zy/ai/service/LlmChatService.java                  |  727 +++++++---
 src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java         |    3 
 src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java  |    4 
 src/main/java/com/zy/core/plugin/XiaosongProcess.java                |   13 
 src/main/java/com/zy/core/model/protocol/StationProtocol.java        |    3 
 src/main/webapp/views/basDevp/basDevp.html                           |   13 
 src/main/java/com/zy/ai/service/PythonService.java                   |   60 
 src/main/java/com/zy/system/timer/LicenseTimer.java                  |   12 
 src/main/java/com/zy/core/thread/CrnThread.java                      |    2 
 src/main/java/com/zy/common/service/CommonService.java               |   18 
 src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java    |  202 ++
 src/main/java/com/zy/core/plugin/FakeProcess.java                    |  108 
 src/main/java/com/zy/core/model/command/StationCommand.java          |    6 
 src/main/webapp/views/watch/console.html                             |   28 
 src/main/java/com/zy/core/utils/StationOperateProcessUtils.java      |  672 ++++++++++
 src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java     |   88 
 src/main/java/com/zy/common/model/NavigateNode.java                  |    1 
 src/main/java/com/zy/ai/controller/WcsDiagnosisController.java       |    4 
 src/main/java/com/zy/core/network/real/ZyStationV3RealConnect.java   |    3 
 src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java      |   53 
 src/main/webapp/components/WatchDualCrnCard.js                       |   70 +
 src/main/java/com/zy/system/entity/license/LinuxServerInfos.java     |    8 
 src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java     |  132 +
 src/main/java/com/zy/core/ServerBootstrap.java                       |    2 
 src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java     |   21 
 src/main/java/com/zy/core/plugin/NormalProcess.java                  |    2 
 src/main/java/com/zy/core/network/ZyStationConnectDriver.java        |    8 
 src/main/java/com/zy/core/network/real/ZyCrnV2RealConnect.java       |    6 
 src/main/webapp/views/login.html                                     |    3 
 src/main/java/com/zy/system/controller/LicenseCreatorController.java |   20 
 src/main/java/com/zy/asrs/controller/DualCrnController.java          |   46 
 src/main/java/com/zy/system/entity/license/CustomLicenseManager.java |   22 
 src/main/java/com/zy/asrs/entity/BasDevp.java                        |   53 
 src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java         |    8 
 src/main/java/com/zy/common/utils/NavigateUtils.java                 |  213 ++
 src/main/java/com/zy/asrs/controller/BasMapController.java           |   40 
 src/main/java/com/zy/core/task/InitLocMapScheduler.java              |    7 
 src/main/webapp/components/DevpCard.js                               |  210 +++
 src/main/java/com/zy/ai/service/WcsDiagnosisService.java             |   19 
 src/main/webapp/views/locMap/locMap.html                             |  232 +++
 src/main/java/com/zy/asrs/utils/MapExcelUtils.java                   |   24 
 src/main/java/com/zy/asrs/controller/OpenController.java             |    3 
 src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java                   |    3 
 src/main/java/com/zy/asrs/controller/CrnController.java              |    2 
 src/main/java/com/zy/core/thread/DualCrnThread.java                  |    2 
 src/main/java/com/zy/asrs/controller/StationController.java          |   90 +
 src/main/java/com/zy/asrs/controller/ConsoleController.java          |   16 
 src/main/java/com/zy/core/enums/WrkStsType.java                      |    1 
 src/main/java/com/zy/asrs/domain/enums/NotifyMsgType.java            |    1 
 src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java  |    3 
 src/main/java/com/zy/system/entity/license/WindowsServerInfos.java   |    8 
 src/main/java/com/zy/core/enums/RedisKeyType.java                    |   13 
 src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java  |  117 +
 src/main/resources/application.yml                                   |    8 
 61 files changed, 3,053 insertions(+), 762 deletions(-)

diff --git a/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java b/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
index 15857a0..127c9ed 100644
--- a/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
+++ b/src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -33,7 +33,7 @@
                 WcsDiagnosisRequest request = aiUtils.makeAiRequest(1000, "瀵瑰綋鍓嶇郴缁熻繘琛屽贰妫�锛屽鏋滄湁寮傚父鎯呭喌灏辫繘琛岃缁嗙殑鍒嗘瀽锛屽鏋滄病鏈夊紓甯告儏鍐靛垯褰撴垚涓�娆℃鏌n\n");
                 wcsDiagnosisService.diagnoseStream(request, emitter);
             } catch (Exception e) {
-                emitter.completeWithError(e);
+                try { emitter.complete(); } catch (Exception ignore) {}
             }
         }).start();
 
@@ -50,7 +50,7 @@
                 WcsDiagnosisRequest request = aiUtils.makeAiRequest(100, null);
                 wcsDiagnosisService.askStream(request, prompt, chatId, reset, emitter);
             } catch (Exception e) {
-                emitter.completeWithError(e);
+                try { emitter.complete(); } catch (Exception ignore) {}
             }
         }).start();
         return emitter;
diff --git a/src/main/java/com/zy/ai/service/LlmChatService.java b/src/main/java/com/zy/ai/service/LlmChatService.java
index ddb333a..3e25561 100644
--- a/src/main/java/com/zy/ai/service/LlmChatService.java
+++ b/src/main/java/com/zy/ai/service/LlmChatService.java
@@ -1,7 +1,12 @@
 package com.zy.ai.service;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.zy.ai.entity.ChatCompletionRequest;
 import com.zy.ai.entity.ChatCompletionResponse;
+import com.zy.ai.entity.LlmCallLog;
+import com.zy.ai.entity.LlmRouteConfig;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
@@ -9,37 +14,38 @@
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
 import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
 import reactor.core.publisher.Flux;
 
-import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
-import java.util.function.Consumer;
+import java.util.UUID;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
+import java.util.function.Consumer;
 
 @Slf4j
 @Service
 @RequiredArgsConstructor
 public class LlmChatService {
 
-    private final WebClient llmWebClient;
+    private static final int LOG_TEXT_LIMIT = 16000;
 
-    @Value("${llm.api-key}")
-    private String apiKey;
+    private final LlmRoutingService llmRoutingService;
+    private final LlmCallLogService llmCallLogService;
 
-    @Value("${llm.model}")
-    private String model;
+    @Value("${llm.base-url:}")
+    private String fallbackBaseUrl;
 
-    @Value("${llm.pythonPlatformUrl}")
-    private String pythonPlatformUrl;
+    @Value("${llm.api-key:}")
+    private String fallbackApiKey;
 
-    @Value("${llm.thinking}")
-    private String thinking;
+    @Value("${llm.model:}")
+    private String fallbackModel;
+
+    @Value("${llm.thinking:false}")
+    private String fallbackThinking;
 
     /**
      * 閫氱敤瀵硅瘽鏂规硶锛氫紶鍏� messages锛岃繑鍥炲ぇ妯″瀷鏂囨湰鍥炲
@@ -49,27 +55,12 @@
                        Integer maxTokens) {
 
         ChatCompletionRequest req = new ChatCompletionRequest();
-        req.setModel(model);
         req.setMessages(messages);
         req.setTemperature(temperature != null ? temperature : 0.3);
         req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
         req.setStream(false);
 
-        ChatCompletionResponse response = llmWebClient.post()
-                .uri("/chat/completions")
-                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
-                .contentType(MediaType.APPLICATION_JSON)
-                .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
-                .bodyValue(req)
-                .exchangeToMono(resp -> resp.bodyToFlux(String.class)
-                        .collectList()
-                        .map(list -> {
-                            String payload = String.join("\n\n", list);
-                            return parseCompletion(payload);
-                        }))
-                .doOnError(ex -> log.error("璋冪敤 LLM 澶辫触", ex))
-                .onErrorResume(ex -> Mono.empty())
-                .block();
+        ChatCompletionResponse response = complete(req, "chat");
 
         if (response == null ||
                 response.getChoices() == null ||
@@ -88,45 +79,81 @@
                                                  Integer maxTokens,
                                                  List<Object> tools) {
         ChatCompletionRequest req = new ChatCompletionRequest();
-        req.setModel(model);
         req.setMessages(messages);
         req.setTemperature(temperature != null ? temperature : 0.3);
         req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
         req.setStream(false);
-
-        if(thinking.equals("enable")) {
-            ChatCompletionRequest.Thinking thinking = new ChatCompletionRequest.Thinking();
-            thinking.setType("enable");
-            req.setThinking(thinking);
-        }
         if (tools != null && !tools.isEmpty()) {
             req.setTools(tools);
             req.setTool_choice("auto");
         }
-        return complete(req);
+        return complete(req, tools != null && !tools.isEmpty() ? "chat_completion_tools" : "chat_completion");
     }
 
     public ChatCompletionResponse complete(ChatCompletionRequest req) {
-        try {
-            return llmWebClient.post()
-                    .uri("/chat/completions")
-                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
-                    .contentType(MediaType.APPLICATION_JSON)
-                    .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
-                    .bodyValue(req)
-                    .exchangeToMono(resp -> resp.bodyToFlux(String.class)
-                            .collectList()
-                            .map(list -> {
-                                String payload = String.join("\n\n", list);
-                                return parseCompletion(payload);
-                            }))
-                    .doOnError(ex -> log.error("璋冪敤 LLM 澶辫触", ex))
-                    .onErrorResume(ex -> Mono.empty())
-                    .block();
-        } catch (Exception e) {
-            log.error("璋冪敤 LLM 澶辫触", e);
+        return complete(req, "completion");
+    }
+
+    private ChatCompletionResponse complete(ChatCompletionRequest req, String scene) {
+        String traceId = nextTraceId();
+        List<ResolvedRoute> routes = resolveRoutes();
+        if (routes.isEmpty()) {
+            log.error("璋冪敤 LLM 澶辫触: 鏈厤缃彲鐢� LLM 璺敱");
+            recordCall(traceId, scene, false, 1, null, false, null, 0L, req, null, "none",
+                    new RuntimeException("鏈厤缃彲鐢� LLM 璺敱"), "no_route");
             return null;
         }
+
+        Throwable last = null;
+        for (int i = 0; i < routes.size(); i++) {
+            ResolvedRoute route = routes.get(i);
+            boolean hasNext = i < routes.size() - 1;
+            ChatCompletionRequest routeReq = applyRoute(cloneRequest(req), route, false);
+            long start = System.currentTimeMillis();
+            try {
+                CompletionCallResult callResult = callCompletion(route, routeReq);
+                ChatCompletionResponse resp = callResult.response;
+                if (!isValidCompletion(resp)) {
+                    RuntimeException ex = new RuntimeException("LLM 鍝嶅簲涓虹┖");
+                    boolean canSwitch = shouldSwitch(route, false);
+                    markFailure(route, ex, canSwitch);
+                    recordCall(traceId, scene, false, i + 1, route, false, callResult.statusCode,
+                            System.currentTimeMillis() - start, routeReq, callResult.payload, "error", ex,
+                            "invalid_completion");
+                    if (hasNext && canSwitch) {
+                        log.warn("LLM 鍒囨崲鍒颁笅涓�璺敱, current={}, reason={}", route.tag(), ex.getMessage());
+                        continue;
+                    }
+                    log.error("璋冪敤 LLM 澶辫触, route={}", route.tag(), ex);
+                    last = ex;
+                    break;
+                }
+                markSuccess(route);
+                recordCall(traceId, scene, false, i + 1, route, true, callResult.statusCode,
+                        System.currentTimeMillis() - start, routeReq, buildResponseText(resp, callResult.payload),
+                        "none", null, null);
+                return resp;
+            } catch (Throwable ex) {
+                last = ex;
+                boolean quota = isQuotaExhausted(ex);
+                boolean canSwitch = shouldSwitch(route, quota);
+                markFailure(route, ex, canSwitch);
+                recordCall(traceId, scene, false, i + 1, route, false, statusCodeOf(ex),
+                        System.currentTimeMillis() - start, routeReq, responseBodyOf(ex),
+                        quota ? "quota" : "error", ex, null);
+                if (hasNext && canSwitch) {
+                    log.warn("LLM 鍒囨崲鍒颁笅涓�璺敱, current={}, reason={}", route.tag(), errorText(ex));
+                    continue;
+                }
+                log.error("璋冪敤 LLM 澶辫触, route={}", route.tag(), ex);
+                break;
+            }
+        }
+
+        if (last != null) {
+            log.error("璋冪敤 LLM 鍏ㄩ儴璺敱澶辫触: {}", errorText(last));
+        }
+        return null;
     }
 
     public void chatStream(List<ChatCompletionRequest.Message> messages,
@@ -137,92 +164,12 @@
                            Consumer<Throwable> onError) {
 
         ChatCompletionRequest req = new ChatCompletionRequest();
-        req.setModel(model);
         req.setMessages(messages);
         req.setTemperature(temperature != null ? temperature : 0.3);
         req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
         req.setStream(true);
 
-
-        Flux<String> flux = llmWebClient.post()
-                .uri("/chat/completions")
-                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
-                .contentType(MediaType.APPLICATION_JSON)
-                .accept(MediaType.TEXT_EVENT_STREAM)
-                .bodyValue(req)
-                .retrieve()
-                .bodyToFlux(String.class)
-                .doOnError(ex -> log.error("璋冪敤 LLM 娴佸紡澶辫触", ex));
-
-        AtomicBoolean doneSeen = new AtomicBoolean(false);
-        AtomicBoolean errorSeen = new AtomicBoolean(false);
-        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
-
-        Thread drain = new Thread(() -> {
-            try {
-                while (true) {
-                    String s = queue.poll(2, TimeUnit.SECONDS);
-                    if (s != null) {
-                        try { onChunk.accept(s); } catch (Exception ignore) {}
-                    }
-                    if (doneSeen.get() && queue.isEmpty()) {
-                        if (!errorSeen.get()) {
-                            try { if (onComplete != null) onComplete.run(); } catch (Exception ignore) {}
-                        }
-                        break;
-                    }
-                }
-            } catch (InterruptedException ignore) {
-                ignore.printStackTrace();
-            }
-        });
-        drain.setDaemon(true);
-        drain.start();
-
-        flux.subscribe(payload -> {
-            if (payload == null || payload.isEmpty()) return;
-            String[] events = payload.split("\\r?\\n\\r?\\n");
-            for (String part : events) {
-                String s = part;
-                if (s == null || s.isEmpty()) continue;
-                if (s.startsWith("data:")) {
-                    s = s.substring(5);
-                    if (s.startsWith(" ")) s = s.substring(1);
-                }
-                if ("[DONE]".equals(s.trim())) {
-                    doneSeen.set(true);
-                    continue;
-                }
-                try {
-                    JSONObject obj = JSON.parseObject(s);
-                    JSONArray choices = obj.getJSONArray("choices");
-                    if (choices != null && !choices.isEmpty()) {
-                        JSONObject c0 = choices.getJSONObject(0);
-                        JSONObject delta = c0.getJSONObject("delta");
-                        if (delta != null) {
-                            String content = delta.getString("content");
-                            if (content != null) {
-                                try { queue.offer(content); } catch (Exception ignore) {}
-                            }
-                        }
-                    }
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
-            }
-        }, err -> {
-            errorSeen.set(true);
-            doneSeen.set(true);
-            if (onError != null) onError.accept(err);
-        }, () -> {
-            if (!doneSeen.get()) {
-                errorSeen.set(true);
-                doneSeen.set(true);
-                if (onError != null) onError.accept(new RuntimeException("LLM 娴佹剰澶栧畬鎴�"));
-            } else {
-                doneSeen.set(true);
-            }
-        });
+        streamWithFailover(req, onChunk, onComplete, onError, "chat_stream");
     }
 
     public void chatStreamWithTools(List<ChatCompletionRequest.Message> messages,
@@ -233,120 +180,54 @@
                                     Runnable onComplete,
                                     Consumer<Throwable> onError) {
         ChatCompletionRequest req = new ChatCompletionRequest();
-        req.setModel(model);
         req.setMessages(messages);
         req.setTemperature(temperature != null ? temperature : 0.3);
         req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
         req.setStream(true);
-        if(thinking.equals("enable")) {
-            ChatCompletionRequest.Thinking thinking = new ChatCompletionRequest.Thinking();
-            thinking.setType("enable");
-            req.setThinking(thinking);
-        }
         if (tools != null && !tools.isEmpty()) {
             req.setTools(tools);
             req.setTool_choice("auto");
         }
-        Flux<String> flux = llmWebClient.post()
-                .uri("/chat/completions")
-                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
-                .contentType(MediaType.APPLICATION_JSON)
-                .accept(MediaType.TEXT_EVENT_STREAM)
-                .bodyValue(req)
-                .retrieve()
-                .bodyToFlux(String.class)
-                .doOnError(ex -> log.error("璋冪敤 LLM 娴佸紡澶辫触", ex));
-
-        AtomicBoolean doneSeen = new AtomicBoolean(false);
-        AtomicBoolean errorSeen = new AtomicBoolean(false);
-        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
-
-        Thread drain = new Thread(() -> {
-            try {
-                while (true) {
-                    String s = queue.poll(5, TimeUnit.SECONDS);
-                    if (s != null) {
-                        try { onChunk.accept(s); } catch (Exception ignore) {}
-                    }
-                    if (doneSeen.get() && queue.isEmpty()) {
-                        if (!errorSeen.get()) {
-                            try { if (onComplete != null) onComplete.run(); } catch (Exception ignore) {}
-                        }
-                        break;
-                    }
-                }
-            } catch (InterruptedException ignore) {
-                ignore.printStackTrace();
-            }
-        });
-        drain.setDaemon(true);
-        drain.start();
-
-        flux.subscribe(payload -> {
-            if (payload == null || payload.isEmpty()) return;
-            String[] events = payload.split("\\r?\\n\\r?\\n");
-            for (String part : events) {
-                String s = part;
-                if (s == null || s.isEmpty()) continue;
-                if (s.startsWith("data:")) {
-                    s = s.substring(5);
-                    if (s.startsWith(" ")) s = s.substring(1);
-                }
-                if ("[DONE]".equals(s.trim())) {
-                    doneSeen.set(true);
-                    continue;
-                }
-                try {
-                    JSONObject obj = JSON.parseObject(s);
-                    JSONArray choices = obj.getJSONArray("choices");
-                    if (choices != null && !choices.isEmpty()) {
-                        JSONObject c0 = choices.getJSONObject(0);
-                        JSONObject delta = c0.getJSONObject("delta");
-                        if (delta != null) {
-                            String content = delta.getString("content");
-                            if (content != null) {
-                                try { queue.offer(content); } catch (Exception ignore) {}
-                            }
-                        }
-                    }
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
-            }
-        }, err -> {
-            errorSeen.set(true);
-            doneSeen.set(true);
-            if (onError != null) onError.accept(err);
-        }, () -> {
-            if (!doneSeen.get()) {
-                errorSeen.set(true);
-                doneSeen.set(true);
-                if (onError != null) onError.accept(new RuntimeException("LLM 娴佹剰澶栧畬鎴�"));
-            } else {
-                doneSeen.set(true);
-            }
-        });
+        streamWithFailover(req, onChunk, onComplete, onError, tools != null && !tools.isEmpty() ? "chat_stream_tools" : "chat_stream");
     }
 
-    public void chatStreamRunPython(String prompt, String chatId, Consumer<String> onChunk,
+    private void streamWithFailover(ChatCompletionRequest req,
+                                    Consumer<String> onChunk,
                                     Runnable onComplete,
-                                    Consumer<Throwable> onError) {
-        HashMap<String, Object> req = new HashMap<>();
-        req.put("prompt", prompt);
-        req.put("chatId", chatId);
+                                    Consumer<Throwable> onError,
+                                    String scene) {
+        String traceId = nextTraceId();
+        List<ResolvedRoute> routes = resolveRoutes();
+        if (routes.isEmpty()) {
+            recordCall(traceId, scene, true, 1, null, false, null, 0L, req, null, "none",
+                    new RuntimeException("鏈厤缃彲鐢� LLM 璺敱"), "no_route");
+            if (onError != null) onError.accept(new RuntimeException("鏈厤缃彲鐢� LLM 璺敱"));
+            return;
+        }
+        attemptStream(routes, 0, req, onChunk, onComplete, onError, traceId, scene);
+    }
 
-        Flux<String> flux = llmWebClient.post()
-                .uri(pythonPlatformUrl)
-                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
-                .contentType(MediaType.APPLICATION_JSON)
-                .accept(MediaType.TEXT_EVENT_STREAM)
-                .bodyValue(req)
-                .retrieve()
-                .bodyToFlux(String.class)
-                .doOnError(ex -> log.error("璋冪敤 LLM 娴佸紡澶辫触", ex));
+    private void attemptStream(List<ResolvedRoute> routes,
+                               int index,
+                               ChatCompletionRequest req,
+                               Consumer<String> onChunk,
+                               Runnable onComplete,
+                               Consumer<Throwable> onError,
+                               String traceId,
+                               String scene) {
+        if (index >= routes.size()) {
+            if (onError != null) onError.accept(new RuntimeException("LLM 璺敱鍏ㄩ儴澶辫触"));
+            return;
+        }
+
+        ResolvedRoute route = routes.get(index);
+        ChatCompletionRequest routeReq = applyRoute(cloneRequest(req), route, true);
+        long start = System.currentTimeMillis();
+        StringBuilder outputBuffer = new StringBuilder();
 
         AtomicBoolean doneSeen = new AtomicBoolean(false);
         AtomicBoolean errorSeen = new AtomicBoolean(false);
+        AtomicBoolean emitted = new AtomicBoolean(false);
         LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
 
         Thread drain = new Thread(() -> {
@@ -354,6 +235,7 @@
                 while (true) {
                     String s = queue.poll(2, TimeUnit.SECONDS);
                     if (s != null) {
+                        emitted.set(true);
                         try {
                             onChunk.accept(s);
                         } catch (Exception ignore) {
@@ -370,13 +252,12 @@
                     }
                 }
             } catch (InterruptedException ignore) {
-                ignore.printStackTrace();
             }
         });
         drain.setDaemon(true);
         drain.start();
 
-        flux.subscribe(payload -> {
+        streamFlux(route, routeReq).subscribe(payload -> {
             if (payload == null || payload.isEmpty()) return;
             String[] events = payload.split("\\r?\\n\\r?\\n");
             for (String part : events) {
@@ -390,10 +271,6 @@
                     doneSeen.set(true);
                     continue;
                 }
-                if("<think>".equals(s.trim()) || "</think>".equals(s.trim())) {
-                    queue.offer(s.trim());
-                    continue;
-                }
                 try {
                     JSONObject obj = JSON.parseObject(s);
                     JSONArray choices = obj.getJSONArray("choices");
@@ -403,30 +280,200 @@
                         if (delta != null) {
                             String content = delta.getString("content");
                             if (content != null) {
-                                try {
-                                    queue.offer(content);
-                                } catch (Exception ignore) {
-                                }
+                                queue.offer(content);
+                                appendLimited(outputBuffer, content);
                             }
                         }
                     }
                 } catch (Exception e) {
-                    e.printStackTrace();
+                    log.warn("瑙f瀽 LLM stream 鐗囨澶辫触: {}", e.getMessage());
                 }
             }
         }, err -> {
             errorSeen.set(true);
             doneSeen.set(true);
+            boolean quota = isQuotaExhausted(err);
+            boolean canSwitch = shouldSwitch(route, quota);
+            markFailure(route, err, canSwitch);
+            recordCall(traceId, scene, true, index + 1, route, false, statusCodeOf(err),
+                    System.currentTimeMillis() - start, routeReq, outputBuffer.toString(),
+                    quota ? "quota" : "error", err, "emitted=" + emitted.get());
+            if (!emitted.get() && canSwitch && index < routes.size() - 1) {
+                log.warn("LLM 璺敱澶辫触锛岃嚜鍔ㄥ垏鎹紝current={}, reason={}", route.tag(), errorText(err));
+                attemptStream(routes, index + 1, req, onChunk, onComplete, onError, traceId, scene);
+                return;
+            }
             if (onError != null) onError.accept(err);
         }, () -> {
             if (!doneSeen.get()) {
+                RuntimeException ex = new RuntimeException("LLM 娴佹剰澶栧畬鎴�");
                 errorSeen.set(true);
                 doneSeen.set(true);
-                if (onError != null) onError.accept(new RuntimeException("LLM 娴佹剰澶栧畬鎴�"));
+                boolean canSwitch = shouldSwitch(route, false);
+                markFailure(route, ex, canSwitch);
+                recordCall(traceId, scene, true, index + 1, route, false, 200,
+                        System.currentTimeMillis() - start, routeReq, outputBuffer.toString(),
+                        "error", ex, "unexpected_stream_end");
+                if (!emitted.get() && canSwitch && index < routes.size() - 1) {
+                    log.warn("LLM 璺敱娴佸紓甯稿畬鎴愶紝鑷姩鍒囨崲锛宑urrent={}", route.tag());
+                    attemptStream(routes, index + 1, req, onChunk, onComplete, onError, traceId, scene);
+                } else {
+                    if (onError != null) onError.accept(ex);
+                }
             } else {
+                markSuccess(route);
+                recordCall(traceId, scene, true, index + 1, route, true, 200,
+                        System.currentTimeMillis() - start, routeReq, outputBuffer.toString(),
+                        "none", null, null);
                 doneSeen.set(true);
             }
         });
+    }
+
+    private Flux<String> streamFlux(ResolvedRoute route, ChatCompletionRequest req) {
+        WebClient client = WebClient.builder().baseUrl(route.baseUrl).build();
+        return client.post()
+                .uri("/chat/completions")
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + route.apiKey)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.TEXT_EVENT_STREAM)
+                .bodyValue(req)
+                .exchangeToFlux(resp -> {
+                    int status = resp.rawStatusCode();
+                    if (status >= 200 && status < 300) {
+                        return resp.bodyToFlux(String.class);
+                    }
+                    return resp.bodyToMono(String.class)
+                            .defaultIfEmpty("")
+                            .flatMapMany(body -> Flux.error(new LlmRouteException(status, body)));
+                })
+                .doOnError(ex -> log.error("璋冪敤 LLM 娴佸紡澶辫触, route={}", route.tag(), ex));
+    }
+
+    private CompletionCallResult callCompletion(ResolvedRoute route, ChatCompletionRequest req) {
+        WebClient client = WebClient.builder().baseUrl(route.baseUrl).build();
+        RawCompletionResult raw = client.post()
+                .uri("/chat/completions")
+                .header(HttpHeaders.AUTHORIZATION, "Bearer " + route.apiKey)
+                .contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.APPLICATION_JSON, MediaType.TEXT_EVENT_STREAM)
+                .bodyValue(req)
+                .exchangeToMono(resp -> resp.bodyToFlux(String.class)
+                        .collectList()
+                        .map(list -> new RawCompletionResult(resp.rawStatusCode(), String.join("\\n\\n", list))))
+                .block();
+
+        if (raw == null) {
+            throw new RuntimeException("LLM 杩斿洖涓虹┖");
+        }
+        if (raw.statusCode < 200 || raw.statusCode >= 300) {
+            throw new LlmRouteException(raw.statusCode, raw.payload);
+        }
+        return new CompletionCallResult(raw.statusCode, raw.payload, parseCompletion(raw.payload));
+    }
+
+    private ChatCompletionRequest applyRoute(ChatCompletionRequest req, ResolvedRoute route, boolean stream) {
+        req.setModel(route.model);
+        req.setStream(stream);
+        if (route.thinkingEnabled) {
+            ChatCompletionRequest.Thinking t = new ChatCompletionRequest.Thinking();
+            t.setType("enable");
+            req.setThinking(t);
+        } else {
+            req.setThinking(null);
+        }
+        return req;
+    }
+
+    private ChatCompletionRequest cloneRequest(ChatCompletionRequest src) {
+        ChatCompletionRequest req = new ChatCompletionRequest();
+        req.setModel(src.getModel());
+        req.setMessages(src.getMessages());
+        req.setTemperature(src.getTemperature());
+        req.setMax_tokens(src.getMax_tokens());
+        req.setStream(src.getStream());
+        req.setTools(src.getTools());
+        req.setTool_choice(src.getTool_choice());
+        req.setThinking(src.getThinking());
+        return req;
+    }
+
+    private boolean isValidCompletion(ChatCompletionResponse response) {
+        if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) {
+            return false;
+        }
+        ChatCompletionRequest.Message message = response.getChoices().get(0).getMessage();
+        if (message == null) {
+            return false;
+        }
+        if (!isBlank(message.getContent())) {
+            return true;
+        }
+        return message.getTool_calls() != null && !message.getTool_calls().isEmpty();
+    }
+
+    private boolean shouldSwitch(ResolvedRoute route, boolean quota) {
+        return quota ? route.switchOnQuota : route.switchOnError;
+    }
+
+    private void markSuccess(ResolvedRoute route) {
+        if (route.id != null) {
+            llmRoutingService.markSuccess(route.id);
+        }
+    }
+
+    private void markFailure(ResolvedRoute route, Throwable ex, boolean enterCooldown) {
+        if (route.id != null) {
+            llmRoutingService.markFailure(route.id, errorText(ex), enterCooldown, route.cooldownSeconds);
+        }
+    }
+
+    private String errorText(Throwable ex) {
+        if (ex == null) return "unknown";
+        if (ex instanceof LlmRouteException) {
+            LlmRouteException e = (LlmRouteException) ex;
+            String body = e.body == null ? "" : e.body;
+            if (body.length() > 240) {
+                body = body.substring(0, 240);
+            }
+            return "status=" + e.statusCode + ", body=" + body;
+        }
+        return ex.getMessage() == null ? ex.toString() : ex.getMessage();
+    }
+
+    private boolean isQuotaExhausted(Throwable ex) {
+        if (!(ex instanceof LlmRouteException)) return false;
+        LlmRouteException e = (LlmRouteException) ex;
+        if (e.statusCode == 429) return true;
+        String text = (e.body == null ? "" : e.body).toLowerCase();
+        return text.contains("insufficient_quota")
+                || text.contains("quota")
+                || text.contains("浣欓")
+                || text.contains("鐢ㄩ噺")
+                || text.contains("瓒呴檺")
+                || text.contains("rate limit");
+    }
+
+    private List<ResolvedRoute> resolveRoutes() {
+        List<ResolvedRoute> routes = new ArrayList<>();
+        List<LlmRouteConfig> dbRoutes = llmRoutingService.listAvailableRoutes();
+        for (LlmRouteConfig c : dbRoutes) {
+            routes.add(ResolvedRoute.fromDb(c));
+        }
+        // 鍏煎锛氭暟鎹簱涓虹┖鏃讹紝鍥為��鍒� yml
+        if (routes.isEmpty() && !isBlank(fallbackBaseUrl) && !isBlank(fallbackApiKey) && !isBlank(fallbackModel)) {
+            routes.add(ResolvedRoute.fromFallback(fallbackBaseUrl, fallbackApiKey, fallbackModel, isFallbackThinkingEnabled()));
+        }
+        return routes;
+    }
+
+    private boolean isFallbackThinkingEnabled() {
+        String x = fallbackThinking == null ? "" : fallbackThinking.trim().toLowerCase();
+        return "true".equals(x) || "1".equals(x) || "enable".equals(x);
+    }
+
+    private boolean isBlank(String s) {
+        return s == null || s.trim().isEmpty();
     }
 
     private ChatCompletionResponse mergeSseChunk(ChatCompletionResponse acc, String payload) {
@@ -452,7 +499,7 @@
                         ChatCompletionResponse.Choice choice = new ChatCompletionResponse.Choice();
                         ChatCompletionRequest.Message msg = new ChatCompletionRequest.Message();
                         choice.setMessage(msg);
-                        java.util.ArrayList<ChatCompletionResponse.Choice> list = new java.util.ArrayList<>();
+                        ArrayList<ChatCompletionResponse.Choice> list = new ArrayList<>();
                         list.add(choice);
                         acc.setChoices(list);
                     }
@@ -490,7 +537,8 @@
                 if (created != null) acc.setCreated(created);
                 String object = obj.getString("object");
                 if (object != null && !object.isEmpty()) acc.setObjectName(object);
-            } catch (Exception ignore) {}
+            } catch (Exception ignore) {
+            }
         }
         return acc;
     }
@@ -502,7 +550,8 @@
             if (r != null && r.getChoices() != null && !r.getChoices().isEmpty() && r.getChoices().get(0).getMessage() != null) {
                 return r;
             }
-        } catch (Exception ignore) {}
+        } catch (Exception ignore) {
+        }
         ChatCompletionResponse sse = mergeSseChunk(new ChatCompletionResponse(), payload);
         if (sse.getChoices() != null && !sse.getChoices().isEmpty() && sse.getChoices().get(0).getMessage() != null && sse.getChoices().get(0).getMessage().getContent() != null) {
             return sse;
@@ -513,9 +562,181 @@
         msg.setRole("assistant");
         msg.setContent(payload);
         choice.setMessage(msg);
-        java.util.ArrayList<ChatCompletionResponse.Choice> list = new java.util.ArrayList<>();
+        ArrayList<ChatCompletionResponse.Choice> list = new ArrayList<>();
         list.add(choice);
         r.setChoices(list);
         return r;
     }
+
+    private String nextTraceId() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    private void appendLimited(StringBuilder sb, String text) {
+        if (sb == null || text == null || text.isEmpty()) {
+            return;
+        }
+        int remain = LOG_TEXT_LIMIT - sb.length();
+        if (remain <= 0) {
+            return;
+        }
+        if (text.length() <= remain) {
+            sb.append(text);
+        } else {
+            sb.append(text, 0, remain);
+        }
+    }
+
+    private Integer statusCodeOf(Throwable ex) {
+        if (ex instanceof LlmRouteException) {
+            return ((LlmRouteException) ex).statusCode;
+        }
+        return null;
+    }
+
+    private String responseBodyOf(Throwable ex) {
+        if (ex instanceof LlmRouteException) {
+            return cut(((LlmRouteException) ex).body, LOG_TEXT_LIMIT);
+        }
+        return null;
+    }
+
+    private String buildResponseText(ChatCompletionResponse resp, String fallbackPayload) {
+        if (resp != null && resp.getChoices() != null && !resp.getChoices().isEmpty()
+                && resp.getChoices().get(0) != null && resp.getChoices().get(0).getMessage() != null) {
+            ChatCompletionRequest.Message m = resp.getChoices().get(0).getMessage();
+            if (!isBlank(m.getContent())) {
+                return cut(m.getContent(), LOG_TEXT_LIMIT);
+            }
+            if (m.getTool_calls() != null && !m.getTool_calls().isEmpty()) {
+                return cut(JSON.toJSONString(m), LOG_TEXT_LIMIT);
+            }
+        }
+        return cut(fallbackPayload, LOG_TEXT_LIMIT);
+    }
+
+    private String safeName(Throwable ex) {
+        return ex == null ? null : ex.getClass().getSimpleName();
+    }
+
+    private String cut(String text, int maxLen) {
+        if (text == null) return null;
+        String clean = text.replace("\r", " ");
+        return clean.length() > maxLen ? clean.substring(0, maxLen) : clean;
+    }
+
+    private void recordCall(String traceId,
+                            String scene,
+                            boolean stream,
+                            int attemptNo,
+                            ResolvedRoute route,
+                            boolean success,
+                            Integer httpStatus,
+                            long latencyMs,
+                            ChatCompletionRequest req,
+                            String response,
+                            String switchMode,
+                            Throwable err,
+                            String extra) {
+        LlmCallLog item = new LlmCallLog();
+        item.setTraceId(cut(traceId, 64));
+        item.setScene(cut(scene, 64));
+        item.setStream((short) (stream ? 1 : 0));
+        item.setAttemptNo(attemptNo);
+        if (route != null) {
+            item.setRouteId(route.id);
+            item.setRouteName(cut(route.name, 128));
+            item.setBaseUrl(cut(route.baseUrl, 255));
+            item.setModel(cut(route.model, 128));
+        }
+        item.setSuccess((short) (success ? 1 : 0));
+        item.setHttpStatus(httpStatus);
+        item.setLatencyMs(latencyMs < 0 ? 0 : latencyMs);
+        item.setSwitchMode(cut(switchMode, 32));
+        item.setRequestContent(cut(JSON.toJSONString(req), LOG_TEXT_LIMIT));
+        item.setResponseContent(cut(response, LOG_TEXT_LIMIT));
+        item.setErrorType(cut(safeName(err), 128));
+        item.setErrorMessage(err == null ? null : cut(errorText(err), 1024));
+        item.setExtra(cut(extra, 512));
+        item.setCreateTime(new Date());
+        llmCallLogService.saveIgnoreError(item);
+    }
+
+    private static class CompletionCallResult {
+        private final int statusCode;
+        private final String payload;
+        private final ChatCompletionResponse response;
+
+        private CompletionCallResult(int statusCode, String payload, ChatCompletionResponse response) {
+            this.statusCode = statusCode;
+            this.payload = payload;
+            this.response = response;
+        }
+    }
+
+    private static class RawCompletionResult {
+        private final int statusCode;
+        private final String payload;
+
+        private RawCompletionResult(int statusCode, String payload) {
+            this.statusCode = statusCode;
+            this.payload = payload;
+        }
+    }
+
+    private static class LlmRouteException extends RuntimeException {
+        private final int statusCode;
+        private final String body;
+
+        private LlmRouteException(int statusCode, String body) {
+            super("http status=" + statusCode);
+            this.statusCode = statusCode;
+            this.body = body;
+        }
+    }
+
+    private static class ResolvedRoute {
+        private Long id;
+        private String name;
+        private String baseUrl;
+        private String apiKey;
+        private String model;
+        private boolean thinkingEnabled;
+        private boolean switchOnQuota;
+        private boolean switchOnError;
+        private Integer cooldownSeconds;
+
+        private static ResolvedRoute fromDb(LlmRouteConfig c) {
+            ResolvedRoute r = new ResolvedRoute();
+            r.id = c.getId();
+            r.name = c.getName();
+            r.baseUrl = c.getBaseUrl();
+            r.apiKey = c.getApiKey();
+            r.model = c.getModel();
+            r.thinkingEnabled = c.getThinking() != null && c.getThinking() == 1;
+            r.switchOnQuota = c.getSwitchOnQuota() == null || c.getSwitchOnQuota() == 1;
+            r.switchOnError = c.getSwitchOnError() == null || c.getSwitchOnError() == 1;
+            r.cooldownSeconds = c.getCooldownSeconds();
+            return r;
+        }
+
+        private static ResolvedRoute fromFallback(String baseUrl, String apiKey, String model, boolean thinkingEnabled) {
+            ResolvedRoute r = new ResolvedRoute();
+            r.name = "fallback-yml";
+            r.baseUrl = baseUrl;
+            r.apiKey = apiKey;
+            r.model = model;
+            r.thinkingEnabled = thinkingEnabled;
+            r.switchOnQuota = true;
+            r.switchOnError = true;
+            r.cooldownSeconds = 300;
+            return r;
+        }
+
+        private String tag() {
+            String showName = name == null ? "unnamed" : name;
+            String showModel = model == null ? "" : (" model=" + model);
+            return showName + showModel;
+        }
+    }
 }
diff --git a/src/main/java/com/zy/ai/service/PythonService.java b/src/main/java/com/zy/ai/service/PythonService.java
index adc712b..82b77a9 100644
--- a/src/main/java/com/zy/ai/service/PythonService.java
+++ b/src/main/java/com/zy/ai/service/PythonService.java
@@ -12,36 +12,36 @@
     @Autowired
     private LlmChatService llmChatService;
 
-    public boolean runPython(String prompt, String chatId, SseEmitter emitter) {
-        try {
-            llmChatService.chatStreamRunPython(prompt, chatId, s -> {
-                try {
-                    String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
-                    if (!safe.isEmpty()) {
-                        sse(emitter, safe);
-                    }
-                } catch (Exception ignore) {
-                }
-            }, () -> {
-                try {
-                    sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堟甯哥粨鏉燂級\\n\\n");
-                    log.info("AI MCP diagnose stopped: final end");
-                    emitter.complete();
-                } catch (Exception ignore) {
-                }
-            }, e -> {
-                sse(emitter, "\\n\\n銆怉I銆戝垎鏋愬嚭閿欙紝姝e湪鍥為��...\\n\\n");
-            });
-            return true;
-        } catch (Exception e) {
-            try {
-                sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級\\n\\n");
-                log.error("AI MCP diagnose stopped: error", e);
-                emitter.completeWithError(e);
-            } catch (Exception ignore) {}
-            return true;
-        }
-    }
+//    public boolean runPython(String prompt, String chatId, SseEmitter emitter) {
+//        try {
+//            llmChatService.chatStreamRunPython(prompt, chatId, s -> {
+//                try {
+//                    String safe = s == null ? "" : s.replace("\r", "").replace("\n", "\\n");
+//                    if (!safe.isEmpty()) {
+//                        sse(emitter, safe);
+//                    }
+//                } catch (Exception ignore) {
+//                }
+//            }, () -> {
+//                try {
+//                    sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堟甯哥粨鏉燂級\\n\\n");
+//                    log.info("AI MCP diagnose stopped: final end");
+//                    emitter.complete();
+//                } catch (Exception ignore) {
+//                }
+//            }, e -> {
+//                sse(emitter, "\\n\\n銆怉I銆戝垎鏋愬嚭閿欙紝姝e湪鍥為��...\\n\\n");
+//            });
+//            return true;
+//        } catch (Exception e) {
+//            try {
+//                sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級\\n\\n");
+//                log.error("AI MCP diagnose stopped: error", e);
+//                emitter.completeWithError(e);
+//            } catch (Exception ignore) {}
+//            return true;
+//        }
+//    }
 
     private void sse(SseEmitter emitter, String data) {
         if (data == null) return;
diff --git a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
index 189e57d..45dea58 100644
--- a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
+++ b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -11,7 +11,6 @@
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.enums.RedisKeyType;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -28,8 +27,6 @@
 
     private static final long CHAT_TTL_SECONDS = 7L * 24 * 3600;
 
-    @Value("${llm.platform}")
-    private String platform;
     @Autowired
     private LlmChatService llmChatService;
     @Autowired
@@ -40,8 +37,6 @@
     private AiUtils aiUtils;
     @Autowired(required = false)
     private McpController mcpController;
-    @Autowired
-    private PythonService pythonService;
 
     public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
         List<ChatCompletionRequest.Message> messages = new ArrayList<>();
@@ -85,7 +80,7 @@
             try {
                 try { emitter.send(SseEmitter.event().data("銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級")); } catch (Exception ignore) {}
                 log.error("AI diagnose stream stopped: error", e);
-                emitter.completeWithError(e);
+                emitter.complete();
             } catch (Exception ignore) {}
         });
     }
@@ -95,11 +90,6 @@
                           String chatId,
                           boolean reset,
                           SseEmitter emitter) {
-        if (platform.equals("python")) {
-            pythonService.runPython(prompt, chatId, emitter);
-            return;
-        }
-
         List<ChatCompletionRequest.Message> messages = new ArrayList<>();
 
         List<ChatCompletionRequest.Message> history = null;
@@ -187,7 +177,10 @@
                 emitter.complete();
             } catch (Exception ignore) {}
         }, e -> {
-            try { emitter.completeWithError(e); } catch (Exception ignore) {}
+            try {
+                try { emitter.send(SseEmitter.event().data("銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級")); } catch (Exception ignore) {}
+                emitter.complete();
+            } catch (Exception ignore) {}
         });
     }
 
@@ -380,7 +373,7 @@
             try {
                 sse(emitter, "\\n\\n銆怉I銆戣繍琛屽凡鍋滄锛堝紓甯革級\\n\\n");
                 log.error("AI MCP diagnose stopped: error", e);
-                emitter.completeWithError(e);
+                emitter.complete();
             } catch (Exception ignore) {}
             return true;
         }
diff --git a/src/main/java/com/zy/asrs/controller/BasMapController.java b/src/main/java/com/zy/asrs/controller/BasMapController.java
index 8a199cc..9dfa3f9 100644
--- a/src/main/java/com/zy/asrs/controller/BasMapController.java
+++ b/src/main/java/com/zy/asrs/controller/BasMapController.java
@@ -177,6 +177,8 @@
             HashMap<Integer, List<StationObjModel>> inStationMap = new HashMap<>();
             HashMap<Integer, List<StationObjModel>> outStationMap = new HashMap<>();
             HashMap<Integer, List<StationObjModel>> runBlockReassignStationMap = new HashMap<>();
+            HashMap<Integer, List<StationObjModel>> isOutOrderStationMap = new HashMap<>();
+            HashMap<Integer, List<StationObjModel>> isLiftTransferStationMap = new HashMap<>();
 
             for (Map.Entry<Integer, List<List<HashMap<String, Object>>>> entry : dataMap.entrySet()) {
                 Integer lev = entry.getKey();
@@ -192,17 +194,18 @@
                         HashMap<String, Object> nodeData = new HashMap<>();
                         nodeData.put("value", map.get("value"));
 
-                        String nodeType = map.get("bgColor").toString();
-                        if (nodeType.equals("RGB(0,176,80)")) {
+                        String bgColor = map.get("bgColor").toString();
+                        String nodeType = map.get("nodeType").toString();
+                        if (nodeType.equals("shelf")) {
                             //璐ф灦
                             nodeData.put("type", "shelf");
-                        } else if (nodeType.equals("RGB(255,192,0)")) {
+                        } else if (nodeType.equals("crn")) {
                             //鍫嗗灈鏈�
                             nodeData.put("type", "crn");
-                        } else if (nodeType.equals("RGB(255,255,0)")) {
+                        } else if (nodeType.equals("dualCrn")) {
                             //鍙屽伐浣嶅爢鍨涙満
                             nodeData.put("type", "dualCrn");
-                        } else if (nodeType.equals("RGB(0,112,192)")) {
+                        } else if (nodeType.equals("devp")) {
                             //杈撻�佺嚎
                             nodeData.put("type", "devp");
 
@@ -266,7 +269,21 @@
                                 runBlockReassignStationList.add(stationObjModel);
                                 runBlockReassignStationMap.put(deviceNo, runBlockReassignStationList);
                             }
-                        } else if (nodeType.equals("RGB(0,176,240)")) {
+
+                            Integer isOutOrder = value.getInteger("isOutOrder");
+                            if (isOutOrder != null && isOutOrder == 1) {
+                                List<StationObjModel> isOutOrderStationList = isOutOrderStationMap.getOrDefault(deviceNo, new ArrayList<>());
+                                isOutOrderStationList.add(stationObjModel);
+                                isOutOrderStationMap.put(deviceNo, isOutOrderStationList);
+                            }
+
+                            Integer isLiftTransfer = value.getInteger("isLiftTransfer");
+                            if (isLiftTransfer != null && isLiftTransfer == 1) {
+                                List<StationObjModel> isLiftTransferStationList = isLiftTransferStationMap.getOrDefault(deviceNo, new ArrayList<>());
+                                isLiftTransferStationList.add(stationObjModel);
+                                isLiftTransferStationMap.put(deviceNo, isLiftTransferStationList);
+                            }
+                        } else if (nodeType.equals("rgv")) {
                             //RGV
                             nodeData.put("type", "rgv");
                         } else if (nodeType.equals("none")) {
@@ -275,6 +292,7 @@
                         } else if (nodeType.equals("merge")) {
                             //鍚堝苟鍖哄煙
                             nodeData.put("type", "merge");
+                            nodeData.put("mergeType", map.get("mergeType"));
                         }
 
                         nodeData.put("cellWidth", map.get("cellWidth"));
@@ -314,6 +332,8 @@
                 List<StationObjModel> inStationList = inStationMap.get(deviceNo);
                 List<StationObjModel> outStationList = outStationMap.get(deviceNo);
                 List<StationObjModel> runBlockReassignStationList = runBlockReassignStationMap.get(deviceNo);
+                List<StationObjModel> isOutOrderStationList = isOutOrderStationMap.get(deviceNo);
+                List<StationObjModel> isLiftTransferStationList = isLiftTransferStationMap.get(deviceNo);
 
                 if (barcodeStationList != null) {
                     basDevp.setBarcodeStationList(JSON.toJSONString(barcodeStationList, SerializerFeature.DisableCircularReferenceDetect));
@@ -331,6 +351,14 @@
                     basDevp.setRunBlockReassignLocStationList(JSON.toJSONString(runBlockReassignStationList, SerializerFeature.DisableCircularReferenceDetect));
                 }
 
+                if (isOutOrderStationList != null) {
+                    basDevp.setIsOutOrderList(JSON.toJSONString(isOutOrderStationList, SerializerFeature.DisableCircularReferenceDetect));
+                }
+
+                if (isLiftTransferStationList != null) {
+                    basDevp.setIsLiftTransferList(JSON.toJSONString(isLiftTransferStationList, SerializerFeature.DisableCircularReferenceDetect));
+                }
+
                 basDevp.setStationList(JSON.toJSONString(stationList, SerializerFeature.DisableCircularReferenceDetect));
                 basDevp.setUpdateTime(new Date());
                 basDevpService.insertOrUpdate(basDevp);
diff --git a/src/main/java/com/zy/asrs/controller/ConsoleController.java b/src/main/java/com/zy/asrs/controller/ConsoleController.java
index 554305b..51b7777 100644
--- a/src/main/java/com/zy/asrs/controller/ConsoleController.java
+++ b/src/main/java/com/zy/asrs/controller/ConsoleController.java
@@ -57,6 +57,10 @@
     private RedisUtil redisUtil;
     @Autowired
     private LocMastService locMastService;
+    @Autowired
+    private BasMapService basMapService;
+    @Autowired
+    private StationCycleCapacityService stationCycleCapacityService;
 
     @PostMapping("/system/running/status")
     @ManagerAuth(memo = "绯荤粺杩愯鐘舵��")
@@ -122,6 +126,7 @@
                 vo.setErrorMsg(stationProtocol.getErrorMsg()); // 鎶ヨ淇℃伅
                 vo.setBarcode(stationProtocol.getBarcode()); // 鏉$爜
                 vo.setWeight(stationProtocol.getWeight());//閲嶉噺
+                vo.setTaskWriteIdx(stationProtocol.getTaskWriteIdx());//浠诲姟鍙啓鍖�
                 String stationStatus = StationStatusType.process(stationProtocol).toString().toLowerCase().replaceAll("_", "-");
                 if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() > 0 && !stationProtocol.isRunBlock()) {
                     String taskClass = getStationTaskClass(stationProtocol.getTaskNo(), inTaskRange, outTaskRange);
@@ -264,6 +269,11 @@
         return R.ok().add(vos);
     }
 
+    @GetMapping("/latest/data/station/cycle/capacity")
+    public R stationCycleCapacity() {
+        return R.ok().add(stationCycleCapacityService.getLatestSnapshot());
+    }
+
     // @PostMapping("/latest/data/barcode")
     // @ManagerAuth(memo = "鏉$爜鎵弿浠疄鏃舵暟鎹�")
     // public R barcodeLatestData(){
@@ -341,10 +351,10 @@
     @GetMapping("/map/{lev}/auth")
     public R getLocMap(@PathVariable Integer lev) {
         Object object = redisUtil.get(RedisKeyType.LOC_MAP_BASE.key);
-        if (object == null) {
-            return R.error("鍦板浘鏈垵濮嬪寲");
+        List<List<HashMap<String, Object>>> mapNodeList = null;
+        if (object != null) {
+            mapNodeList = (List<List<HashMap<String, Object>>>) object;
         }
-        List<List<HashMap<String, Object>>> mapNodeList = (List<List<HashMap<String, Object>>>) object;
         List<LocMast> locMastList = locMastService.selectLocByLev(lev);
         for (LocMast locMast : locMastList) {
             String[] locType = locMast.getLocType().split("-");
diff --git a/src/main/java/com/zy/asrs/controller/CrnController.java b/src/main/java/com/zy/asrs/controller/CrnController.java
index cacd496..ba23d00 100644
--- a/src/main/java/com/zy/asrs/controller/CrnController.java
+++ b/src/main/java/com/zy/asrs/controller/CrnController.java
@@ -173,7 +173,7 @@
             return R.error("绾跨▼涓嶅瓨鍦�");
         }
 
-        CrnCommand command = crnThread.getResetCommand(crnNo);
+        CrnCommand command = crnThread.getResetCommand(9999, crnNo);
         MessageQueue.offer(SlaveType.Crn, crnNo, new Task(2, command));
         return R.ok();
     }
diff --git a/src/main/java/com/zy/asrs/controller/DualCrnController.java b/src/main/java/com/zy/asrs/controller/DualCrnController.java
index 3a75fb3..48cb7b8 100644
--- a/src/main/java/com/zy/asrs/controller/DualCrnController.java
+++ b/src/main/java/com/zy/asrs/controller/DualCrnController.java
@@ -5,11 +5,14 @@
 import com.core.common.Cools;
 import com.core.common.R;
 import com.zy.asrs.domain.param.DualCrnCommandParam;
+import com.zy.asrs.domain.param.DualCrnUpdateTaskNoParam;
 import com.zy.asrs.domain.vo.DualCrnStateTableVo;
 import com.zy.asrs.entity.BasDualCrnp;
 import com.zy.asrs.service.BasDualCrnpService;
+import com.zy.common.utils.RedisUtil;
 import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.SlaveType;
 import com.zy.core.model.Task;
 import com.zy.core.model.command.DualCrnCommand;
@@ -31,6 +34,8 @@
 
     @Autowired
     private BasDualCrnpService basDualCrnpService;
+    @Autowired
+    private RedisUtil redisUtil;
 
     @PostMapping("/dualcrn/table/crn/state")
     @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満淇℃伅琛�")
@@ -51,6 +56,8 @@
             }
             vo.setTaskNo(p.getTaskNo());
             vo.setTaskNoTwo(p.getTaskNoTwo());
+            vo.setDeviceTaskNo(p.getDeviceTaskNo());
+            vo.setDeviceTaskNoTwo(p.getDeviceTaskNoTwo());
             vo.setMode(p.getModeType() == null ? "-" : p.getModeType().desc);
             vo.setStatus(p.getStatusType() == null ? "-" : p.getStatusType().desc);
             vo.setStatusTwo(p.getStatusTypeTwo() == null ? "-" : p.getStatusTypeTwo().desc);
@@ -62,8 +69,10 @@
             vo.setForkOffsetTwo(p.getForkPosTypeTwo() == null ? "-" : p.getForkPosTypeTwo().desc);
             vo.setLiftPos(p.getLiftPosType() == null ? "-" : p.getLiftPosType().desc);
             vo.setWalkPos(p.getWalkPos() != null && p.getWalkPos() == 0 ? "鍦ㄥ畾浣�" : "涓嶅湪瀹氫綅");
-            vo.setTaskReceive(p.getTaskReceive() != null && p.getTaskReceive() == 1 ? "鎺ユ敹" : "鏃犱换鍔�");
-            vo.setTaskReceiveTwo(p.getTaskReceiveTwo() != null && p.getTaskReceiveTwo() == 1 ? "鎺ユ敹" : "鏃犱换鍔�");
+            vo.setTaskReceive(String.valueOf(p.getTaskReceive()));
+            vo.setTaskReceiveTwo(String.valueOf(p.getTaskReceiveTwo()));
+            vo.setTaskSend(String.valueOf(p.getTaskSend()));
+            vo.setTaskSendTwo(String.valueOf(p.getTaskSendTwo()));
 
             vo.setXspeed(String.format("%.2f", p.getXSpeed()));
             vo.setYspeed(String.format("%.2f", p.getYSpeed()));
@@ -175,8 +184,39 @@
         if (crnThread == null) {
             return R.error("绾跨▼涓嶅瓨鍦�");
         }
-        DualCrnCommand command = crnThread.getResetCommand(crnNo, station);
+        DualCrnCommand command = crnThread.getResetCommand(9999, crnNo, station);
         MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(3, command));
         return R.ok();
     }
+
+    @PostMapping("/dualcrn/command/updateTaskNo")
+    @ManagerAuth(memo = "鍙屽伐浣嶅爢鍨涙満缂栬緫浠诲姟鍙�")
+    public R dualCrnUpdateTaskNo(@RequestBody DualCrnUpdateTaskNoParam param) {
+        if (Cools.isEmpty(param) || param.getCrnNo() == null || param.getStation() == null || param.getTaskNo() == null) {
+            return R.error("缂哄皯鍙傛暟");
+        }
+        Integer station = param.getStation();
+        if (station != 1 && station != 2) {
+            return R.error("宸ヤ綅鍙傛暟閿欒");
+        }
+        Integer taskNo = param.getTaskNo();
+        if (taskNo < 0) {
+            return R.error("浠诲姟鍙蜂笉鑳藉皬浜�0");
+        }
+        Integer crnNo = param.getCrnNo();
+        DualCrnThread crnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, crnNo);
+        if (crnThread == null) {
+            return R.error("绾跨▼涓嶅瓨鍦�");
+        }
+        DualCrnProtocol protocol = crnThread.getStatus();
+        if (protocol == null) {
+            return R.error("璁惧鐘舵�佷笉瀛樺湪");
+        }
+        if (station == 1) {
+            redisUtil.set(RedisKeyType.DUAL_CRN_STATION1_FLAG.key + crnNo, taskNo, 60 * 60 * 24);
+        } else {
+            redisUtil.set(RedisKeyType.DUAL_CRN_STATION2_FLAG.key + crnNo, taskNo, 60 * 60 * 24);
+        }
+        return R.ok();
+    }
 }
diff --git a/src/main/java/com/zy/asrs/controller/OpenController.java b/src/main/java/com/zy/asrs/controller/OpenController.java
index 3325ec3..fbc6706 100644
--- a/src/main/java/com/zy/asrs/controller/OpenController.java
+++ b/src/main/java/com/zy/asrs/controller/OpenController.java
@@ -235,8 +235,7 @@
         // 鑾峰彇杈撻�佺珯鐐规暟鎹�
         List<StationProtocol> stationProtocols = new ArrayList<>();
         List<DeviceConfig> devpList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
-                .eq("device_type", String.valueOf(SlaveType.Devp))
-        );
+                .eq("device_type", String.valueOf(SlaveType.Devp)));
         for (DeviceConfig device : devpList) {
             StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, device.getDeviceNo());
             if (stationThread == null) {
diff --git a/src/main/java/com/zy/asrs/controller/StationController.java b/src/main/java/com/zy/asrs/controller/StationController.java
index d232d99..75a7367 100644
--- a/src/main/java/com/zy/asrs/controller/StationController.java
+++ b/src/main/java/com/zy/asrs/controller/StationController.java
@@ -1,12 +1,18 @@
 package com.zy.asrs.controller;
 
 import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.zy.asrs.domain.param.StationCommandBarcodeParam;
 import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.DeviceConfig;
 import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.service.DeviceConfigService;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.StationCommandType;
 import com.zy.core.model.StationObjModel;
+import com.zy.system.entity.Config;
+import com.zy.system.service.ConfigService;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -29,10 +35,16 @@
 @RequestMapping("/station")
 public class StationController {
 
+    @Value("${mainProcessPlugin}")
+    private String mainProcessPlugin;
     @Autowired
     private BasDevpService basDevpService;
     @Autowired
     private RedisUtil redisUtil;
+    @Autowired
+    private ConfigService configService;
+    @Autowired
+    private DeviceConfigService deviceConfigService;
 
     @PostMapping("/command/move")
     public R commandMove(@RequestBody StationCommandMoveParam param) {
@@ -44,21 +56,7 @@
         Integer taskNo = param.getTaskNo();
         Integer targetStationId = param.getTargetStationId();
 
-        StationObjModel finalStation = null;
-        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
-        for (BasDevp basDevp : basDevps) {
-            List<StationObjModel> list = basDevp.getStationList$();
-            for (StationObjModel entity : list) {
-                if(entity.getStationId().equals(stationId)){
-                    finalStation = entity;
-                    break;
-                }
-            }
-
-            if(finalStation != null){
-                break;
-            }
-        }
+        StationObjModel finalStation = findStation(stationId);
 
         if(finalStation == null){
             return R.error("绔欑偣涓嶅瓨鍦�");
@@ -76,6 +74,51 @@
         return R.ok();
     }
 
+    @PostMapping("/command/barcode")
+    public R commandBarcode(@RequestBody StationCommandBarcodeParam param) {
+        if (Cools.isEmpty(param) || Cools.isEmpty(param.getStationId())) {
+            return R.error("缂哄皯鍙傛暟");
+        }
+
+        if (!mainProcessPlugin.contains("Fake")) {
+            return R.error("褰撳墠绯荤粺鏈惎鐢ㄤ豢鐪熸彃浠�");
+        }
+
+        Config enableFakeConfig = configService.selectOne(new EntityWrapper<Config>().eq("code", "enableFake"));
+        if (enableFakeConfig == null || !"Y".equals(enableFakeConfig.getValue())) {
+            return R.error("褰撳墠闈炰豢鐪熻繍琛屾ā寮忥紝绂佹淇敼鏉$爜");
+        }
+
+        Integer stationId = param.getStationId();
+        StationObjModel finalStation = findStation(stationId);
+        if (finalStation == null) {
+            return R.error("绔欑偣涓嶅瓨鍦�");
+        }
+
+        Integer devpNo = finalStation.getDeviceNo();
+        DeviceConfig deviceConfig = deviceConfigService.selectOne(new EntityWrapper<DeviceConfig>()
+                .eq("device_no", devpNo)
+                .eq("device_type", String.valueOf(SlaveType.Devp)));
+        if (deviceConfig == null || deviceConfig.getFake() == null || deviceConfig.getFake() != 1) {
+            return R.error("褰撳墠绔欑偣璁惧鏈惎鐢ㄤ豢鐪燂紝绂佹淇敼鏉$爜");
+        }
+
+        StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, devpNo);
+        if (stationThread == null) {
+            return R.error("绾跨▼涓嶅瓨鍦�");
+        }
+
+        String barcode = param.getBarcode();
+        if (barcode == null) {
+            barcode = "";
+        }
+
+        StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9997, stationId, stationId, 0);
+        command.setBarcode(barcode.trim());
+        MessageQueue.offer(SlaveType.Devp, devpNo, new Task(2, command));
+        return R.ok();
+    }
+
     @PostMapping("/command/reset")
     public R commandReset(@RequestBody StationCommandMoveParam param) {
         if (Cools.isEmpty(param)) {
@@ -87,4 +130,21 @@
         return R.ok();
     }
 
+    private StationObjModel findStation(Integer stationId) {
+        if (Cools.isEmpty(stationId)) {
+            return null;
+        }
+
+        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            List<StationObjModel> list = basDevp.getStationList$();
+            for (StationObjModel entity : list) {
+                if (entity.getStationId().equals(stationId)) {
+                    return entity;
+                }
+            }
+        }
+        return null;
+    }
+
 }
diff --git a/src/main/java/com/zy/asrs/domain/enums/NotifyMsgType.java b/src/main/java/com/zy/asrs/domain/enums/NotifyMsgType.java
index 25b9419..5f8ec1d 100644
--- a/src/main/java/com/zy/asrs/domain/enums/NotifyMsgType.java
+++ b/src/main/java/com/zy/asrs/domain/enums/NotifyMsgType.java
@@ -20,6 +20,7 @@
     DUAL_CRN_TRANSFER_TASK_COMPLETE("dual_crn_transfer_task_complete", "鍙屽伐浣嶅爢鍨涙満绉诲簱浠诲姟鎵ц瀹屾垚"),
 
     STATION_OUT_TASK_RUN("station_out_task_run","杈撻�佺珯鐐瑰嚭搴撲换鍔¤繍琛屼腑"),
+    STATION_OUT_TASK_RUN_COMPLETE("station_out_task_run_complete","杈撻�佺珯鐐瑰嚭搴撲换鍔¤繍琛屽畬鎴�"),
     ;
 
     public String flag;
diff --git a/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java b/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java
index 0da191a..189150c 100644
--- a/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java
+++ b/src/main/java/com/zy/asrs/domain/vo/DualCrnStateTableVo.java
@@ -15,6 +15,10 @@
 
     private Integer taskNoTwo = 0;
 
+    private Integer deviceTaskNo = 0;
+
+    private Integer deviceTaskNoTwo = 0;
+
     private String status = "-";
 
     private String statusTwo = "-";
@@ -31,6 +35,10 @@
 
     private String taskReceiveTwo = "-";
 
+    private String taskSend = "-";
+
+    private String taskSendTwo = "-";
+
     private Integer bay;
 
     private Integer lev;
diff --git a/src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java b/src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java
index c41359e..749a635 100644
--- a/src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java
+++ b/src/main/java/com/zy/asrs/domain/vo/StationLatestDataVo.java
@@ -55,4 +55,7 @@
     //閲嶉噺
     private Double weight;
 
+    //浠诲姟鍙啓鍖�
+    private Integer taskWriteIdx;
+
 }
diff --git a/src/main/java/com/zy/asrs/entity/BasDevp.java b/src/main/java/com/zy/asrs/entity/BasDevp.java
index cafa3fd..5b15930 100644
--- a/src/main/java/com/zy/asrs/entity/BasDevp.java
+++ b/src/main/java/com/zy/asrs/entity/BasDevp.java
@@ -113,6 +113,20 @@
     @TableField("run_block_reassign_loc_station_list")
     private String runBlockReassignLocStationList;
 
+    /**
+     * 鍑哄簱鎺掑簭浜や簰鐐�
+     */
+    @ApiModelProperty(value= "鍑哄簱鎺掑簭浜や簰鐐�")
+    @TableField("is_out_order_list")
+    private String isOutOrderList;
+
+    /**
+     * 椤跺崌绉绘牻鐐�
+     */
+    @ApiModelProperty(value= "椤跺崌绉绘牻鐐�")
+    @TableField("is_lift_transfer_list")
+    private String isLiftTransferList;
+
     public BasDevp() {}
 
     public BasDevp(Integer devpNo,Integer status,Long createBy,Date createTime,Long updateBy,Date updateTime,String memo,String stationList,String barcodeStationList,String inStationList,String outStationList) {
@@ -229,4 +243,43 @@
         return list;
     }
 
+    public List<StationObjModel> getOutOrderList$(){
+        List<StationObjModel> list = new ArrayList<>();
+        if (Cools.isEmpty(this.isOutOrderList)){
+            return list;
+        }
+
+        List<StationObjModel> jsonList = JSON.parseArray(this.isOutOrderList, StationObjModel.class);
+        for (StationObjModel json : jsonList){
+            list.add(json);
+        }
+        return list;
+    }
+
+    public List<Integer> getOutOrderIntList(){
+        List<Integer> list = new ArrayList<>();
+        if (Cools.isEmpty(this.isOutOrderList)){
+            return list;
+        }
+
+        List<StationObjModel> jsonList = JSON.parseArray(this.isOutOrderList, StationObjModel.class);
+        for (StationObjModel json : jsonList){
+            list.add(json.getStationId());
+        }
+        return list;
+    }
+
+    public List<StationObjModel> getLiftTransferList$(){
+        List<StationObjModel> list = new ArrayList<>();
+        if (Cools.isEmpty(this.isLiftTransferList)){
+            return list;
+        }
+
+        List<StationObjModel> jsonList = JSON.parseArray(this.isLiftTransferList, StationObjModel.class);
+        for (StationObjModel json : jsonList){
+            list.add(json);
+        }
+        return list;
+    }
+
 }
diff --git a/src/main/java/com/zy/asrs/task/WrkMastScheduler.java b/src/main/java/com/zy/asrs/task/WrkMastScheduler.java
index 707d3b9..9f2a2bd 100644
--- a/src/main/java/com/zy/asrs/task/WrkMastScheduler.java
+++ b/src/main/java/com/zy/asrs/task/WrkMastScheduler.java
@@ -95,8 +95,8 @@
                 continue;
             }
 
-            if (!locMast.getLocSts().equals("R")) {
-                log.info("[workNo={}]搴撲綅鐘舵�佷笉澶勪簬R", wrkMast.getWrkNo());
+            if (!(locMast.getLocSts().equals("R") || locMast.getLocSts().equals("O"))) {
+                log.info("[workNo={}]搴撲綅鐘舵�佷笉澶勪簬R or O", wrkMast.getWrkNo());
                 continue;
             }
 
@@ -237,4 +237,40 @@
         }
     }
 
+    @Scheduled(cron = "0/1 * * * * ? ")
+    @Transactional
+    public void processOutStationRun(){
+        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN.sts));
+        if (wrkMasts.isEmpty()) {
+            return;
+        }
+
+        for (WrkMast wrkMast : wrkMasts) {
+            String locNo = wrkMast.getSourceLocNo();
+            LocMast locMast = locMastService.queryByLoc(locNo);
+            if (locMast == null) {
+                log.info("[workNo={}]搴撲綅涓嶅瓨鍦�", wrkMast.getWrkNo());
+                continue;
+            }
+
+            if (locMast.getLocSts().equals("O")) {
+                continue;
+            }
+
+            if (!locMast.getLocSts().equals("R")) {
+                log.info("[workNo={}]搴撲綅鐘舵�佷笉澶勪簬R", wrkMast.getWrkNo());
+                continue;
+            }
+
+            locMast.setLocSts("O");
+            locMast.setBarcode("");
+            locMast.setModiTime(new Date());
+            boolean result = locMastService.updateById(locMast);
+            if (!result) {
+                log.info("[workNo={}]搴撲綅鐘舵�丱鏇存柊澶辫触", wrkMast.getWrkNo());
+                continue;
+            }
+        }
+    }
+
 }
diff --git a/src/main/java/com/zy/asrs/utils/MapExcelUtils.java b/src/main/java/com/zy/asrs/utils/MapExcelUtils.java
index cdca8ca..d61d64e 100644
--- a/src/main/java/com/zy/asrs/utils/MapExcelUtils.java
+++ b/src/main/java/com/zy/asrs/utils/MapExcelUtils.java
@@ -322,7 +322,9 @@
                             }
 
                             HashMap<String, Object> mapData = data.get(k - 2).get(l - 2);
-                            mapData.put("bgColor", "merge");
+                            Object devp = map.get("nodeType");
+                            mapData.put("nodeType", "merge");
+                            mapData.put("mergeType", devp);
                             mapData.put("value", map.get("value"));
                         }
                     }
@@ -342,6 +344,7 @@
         if (cell == null) {
             HashMap<String, Object> map = new HashMap<>();
             map.put("bgColor", "none");
+            map.put("nodeType", "none");
             map.put("cellWidth", "");
             map.put("cellHeight", "");
             map.put("value", "");
@@ -354,6 +357,25 @@
 
         String bgColor = getCellBackgroundColor(cell);
         map.put("bgColor", bgColor);
+        if(bgColor.equals("RGB(0,176,80)")) {
+            //璐ф灦
+            map.put("nodeType", "shelf");
+        } else if (bgColor.equals("RGB(255,192,0)")) {
+            //鍫嗗灈鏈�
+            map.put("nodeType", "crn");
+        } else if (bgColor.equals("RGB(255,255,0)")) {
+            //鍙屽伐浣嶅爢鍨涙満
+            map.put("nodeType", "dualCrn");
+        } else if (bgColor.equals("RGB(0,112,192)")) {
+            //杈撻�佺嚎
+            map.put("nodeType", "devp");
+        } else if (bgColor.equals("RGB(0,176,240)")) {
+            //RGV
+            map.put("nodeType", "rgv");
+        }else {
+            //绌虹櫧鍖哄煙
+            map.put("nodeType", "none");
+        }
 
         int columnIndex = cell.getColumnIndex();
         int columnWidth = sheet.getColumnWidth(columnIndex);//鑾峰彇鍒楀
diff --git a/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java b/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
index 6d3a357..63ef73c 100644
--- a/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
+++ b/src/main/java/com/zy/asrs/ws/ConsoleWebSocket.java
@@ -103,6 +103,9 @@
             } else if ("/console/latest/data/dualcrn".equals(url)) {
                 ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
                 resObj = consoleController.dualCrnLatestData();
+            } else if ("/console/latest/data/station/cycle/capacity".equals(url)) {
+                ConsoleController consoleController = SpringUtils.getBean(ConsoleController.class);
+                resObj = consoleController.stationCycleCapacity();
             } else if ("/crn/table/crn/state".equals(url)) {
                 resObj = SpringUtils.getBean(CrnController.class).crnStateTable();
             } else if ("/rgv/table/rgv/state".equals(url)) {
diff --git a/src/main/java/com/zy/common/model/NavigateNode.java b/src/main/java/com/zy/common/model/NavigateNode.java
index 360c464..63f2136 100644
--- a/src/main/java/com/zy/common/model/NavigateNode.java
+++ b/src/main/java/com/zy/common/model/NavigateNode.java
@@ -22,6 +22,7 @@
     private NavigateNode Father;//鐖惰妭鐐�
     private List<String> directionList;//鍏佽琛岃蛋鏂瑰悜
     private Boolean isInflectionPoint;//鏄惁涓烘嫄鐐�
+    private Boolean isLiftTransferPoint;//鏄惁涓洪《鍗囩Щ鏍界偣
     private String direction;//琛岃蛋鏂瑰悜
     private String nodeValue;//鑺傜偣鏁版嵁
     private String nodeType;//鑺傜偣绫诲瀷
diff --git a/src/main/java/com/zy/common/service/CommonService.java b/src/main/java/com/zy/common/service/CommonService.java
index 6026fd8..6c69e6f 100644
--- a/src/main/java/com/zy/common/service/CommonService.java
+++ b/src/main/java/com/zy/common/service/CommonService.java
@@ -12,19 +12,13 @@
 import com.zy.common.utils.NavigateUtils;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
-import com.zy.core.enums.RedisKeyType;
-import com.zy.core.enums.SlaveType;
-import com.zy.core.enums.WrkIoType;
-import com.zy.core.enums.WrkStsType;
+import com.zy.core.enums.*;
 import com.zy.core.model.StationObjModel;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Random;
+import java.util.*;
 
 @Slf4j
 @Service
@@ -226,10 +220,10 @@
         if (null == locMast) {
             throw new CoolException(param.getLocNo() + "鐩爣搴撲綅涓嶅瓨鍦�");
         }
-
         if (!locMast.getLocSts().equals("O")) {
             throw new CoolException(locMast.getLocNo() + "鐩爣搴撲綅涓嶅浜庣┖搴撶姸鎬�");
         }
+        News.info("浠诲姟鍙�:{} 鐩爣搴撲綅淇℃伅:{}",param.getTaskNo(), param.getLocNo());
 
         double ioPri = 100D;
         if (param.getTaskPri() != null) {
@@ -261,7 +255,6 @@
 
         if (findCrnResult.getCrnType().equals(SlaveType.Crn)) {
             wrkMast.setCrnNo(findCrnResult.getCrnNo());
-
             //缂撳瓨璁板綍褰撳墠鍛戒护鍫嗗灈鏈虹紪鍙�
             redisUtil.set(RedisKeyType.CURRENT_CIRCLE_TASK_CRN_NO.key, crnNo, 60 * 60 * 24);
         } else if (findCrnResult.getCrnType().equals(SlaveType.DualCrn)) {
@@ -400,10 +393,15 @@
     }
 
     public FindCrnNoResult findCrnNoByLocNo(String locNo) {
+        if (Objects.isNull(locNo)) {
+            News.error("浠诲姟鍙蜂负绌猴紒");
+            return null;
+        }
         List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
         for (BasCrnp basCrnp : basCrnps) {
             List<List<Integer>> rowList = basCrnp.getControlRows$();
             for (List<Integer> rows : rowList) {
+//                System.out.println("搴撲綅鍙凤細" + locNo);
                 if(rows.contains(Utils.getRow(locNo))) {
                     FindCrnNoResult result = new FindCrnNoResult();
                     result.setCrnNo(basCrnp.getCrnNo());
diff --git a/src/main/java/com/zy/common/utils/NavigateUtils.java b/src/main/java/com/zy/common/utils/NavigateUtils.java
index fb734e1..e6b004e 100644
--- a/src/main/java/com/zy/common/utils/NavigateUtils.java
+++ b/src/main/java/com/zy/common/utils/NavigateUtils.java
@@ -6,16 +6,20 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.zy.asrs.entity.BasDevp;
 import com.zy.asrs.entity.BasStation;
+import com.zy.asrs.service.BasDevpService;
 import com.zy.asrs.service.BasStationService;
 import com.zy.core.News;
+import com.zy.core.model.StationObjModel;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.mapper.EntityWrapper;
 import com.core.common.SpringUtils;
 import com.core.exception.CoolException;
 import com.zy.common.model.NavigateNode;
@@ -31,6 +35,11 @@
 @Component
 public class NavigateUtils {
 
+    private static final String CFG_STATION_PATH_LEN_WEIGHT_PERCENT = "stationPathLenWeightPercent";
+    private static final String CFG_STATION_PATH_CONG_WEIGHT_PERCENT = "stationPathCongWeightPercent";
+    private static final String CFG_STATION_PATH_PASS_OTHER_OUT_STATION_WEIGHT_PERCENT = "stationPathPassOtherOutStationWeightPercent";
+    private static final String CFG_STATION_PATH_PASS_OTHER_OUT_STATION_FORCE_SKIP = "stationPathPassOtherOutStationForceSkip";
+
     @Autowired
     private BasStationService basStationService;
 
@@ -45,12 +54,12 @@
         List<List<NavigateNode>> stationMap = navigateSolution.getStationMap(lev);
 
         NavigateNode startNode = navigateSolution.findStationNavigateNode(stationMap, startStationId);
-        if (startNode == null){
+        if (startNode == null) {
             throw new CoolException("鏈壘鍒拌 璧风偣 瀵瑰簲鐨勮妭鐐�");
         }
 
         NavigateNode endNode = navigateSolution.findStationNavigateNode(stationMap, endStationId);
-        if (endNode == null){
+        if (endNode == null) {
             throw new CoolException("鏈壘鍒拌 缁堢偣 瀵瑰簲鐨勮妭鐐�");
         }
 
@@ -58,7 +67,8 @@
         News.info("[WCS Debug] 绔欑偣璺緞寮�濮嬭绠�,startStationId={},endStationId={}", startStationId, endStationId);
         List<List<NavigateNode>> allList = navigateSolution.allSimplePaths(stationMap, startNode, endNode, 120, 500, 300);
         if (allList.isEmpty()) {
-            throw new CoolException("鏈壘鍒拌璺緞");
+//            throw new CoolException("鏈壘鍒拌璺緞");
+            return new ArrayList<>();
         }
         News.info("[WCS Debug] 绔欑偣璺緞璁$畻瀹屾垚锛岃�楁椂锛歿}ms", System.currentTimeMillis() - startTime);
 
@@ -70,9 +80,12 @@
         //鍘婚噸
         HashSet<Integer> set = new HashSet<>();
         List<NavigateNode> fitlerList = new ArrayList<>();
-        for(NavigateNode navigateNode : list){
+        for (NavigateNode navigateNode : list) {
             JSONObject valuObject = JSON.parseObject(navigateNode.getNodeValue());
-            if(set.add(valuObject.getInteger("stationId"))){
+            if (valuObject.containsKey("rgvCalcFlag")) {
+                continue;
+            }
+            if (set.add(valuObject.getInteger("stationId"))) {
                 fitlerList.add(navigateNode);
             }
         }
@@ -80,6 +93,20 @@
         for (int i = 0; i < fitlerList.size(); i++) {
             NavigateNode currentNode = fitlerList.get(i);
             currentNode.setIsInflectionPoint(false);
+            currentNode.setIsLiftTransferPoint(false);
+
+            try {
+                JSONObject valueObject = JSON.parseObject(currentNode.getNodeValue());
+                if (valueObject != null) {
+                    Object isLiftTransfer = valueObject.get("isLiftTransfer");
+                    if (isLiftTransfer != null) {
+                        String isLiftTransferStr = isLiftTransfer.toString();
+                        if ("1".equals(isLiftTransferStr) || "true".equalsIgnoreCase(isLiftTransferStr)) {
+                            currentNode.setIsLiftTransferPoint(true);
+                        }
+                    }
+                }
+            } catch (Exception ignore) {}
 
             NavigateNode nextNode = (i + 1 < fitlerList.size()) ? fitlerList.get(i + 1) : null;
             NavigateNode prevNode = (i - 1 >= 0) ? fitlerList.get(i - 1) : null;
@@ -112,7 +139,8 @@
         News.info("[WCS Debug] RGV璺緞寮�濮嬭绠�,startTrackSiteNo:{},endTrackSiteNo={}", startTrackSiteNo, endTrackSiteNo);
         NavigateNode res_node = navigateSolution.astarSearchJava(rgvTrackMap, startNode, endNode);
         if (res_node == null) {
-            throw new CoolException("鏈壘鍒拌璺緞");
+//            throw new CoolException("鏈壘鍒拌璺緞");
+            return new ArrayList<>();
         }
         News.info("[WCS Debug] RGV璺緞璁$畻瀹屾垚锛岃�楁椂锛歿}ms", System.currentTimeMillis() - startTime);
 
@@ -214,10 +242,28 @@
             }
         } catch (Exception ignore) {}
 
+        Set<Integer> outStationIdSet = loadAllOutStationIdSet();
+
+        double lenWeightPercent = 50.0;
+        double congWeightPercent = 50.0;
+        double passOtherOutStationWeightPercent = 100.0;
+        boolean forceSkipPassOtherOutStation = false;
+        try {
+            ConfigService configService = SpringUtils.getBean(ConfigService.class);
+            if (configService != null) {
+                lenWeightPercent = loadDoubleConfig(configService, CFG_STATION_PATH_LEN_WEIGHT_PERCENT, lenWeightPercent);
+                congWeightPercent = loadDoubleConfig(configService, CFG_STATION_PATH_CONG_WEIGHT_PERCENT, congWeightPercent);
+                passOtherOutStationWeightPercent = loadDoubleConfig(configService, CFG_STATION_PATH_PASS_OTHER_OUT_STATION_WEIGHT_PERCENT, passOtherOutStationWeightPercent);
+                forceSkipPassOtherOutStation = loadBooleanConfig(configService, CFG_STATION_PATH_PASS_OTHER_OUT_STATION_FORCE_SKIP, false);
+            }
+        } catch (Exception ignore) {}
+
         List<List<NavigateNode>> candidates = new ArrayList<>();
         List<Integer> lens = new ArrayList<>();
         List<Integer> tasksList = new ArrayList<>();
         List<Double> congs = new ArrayList<>();
+        List<Integer> passOtherOutStationCounts = new ArrayList<>();
+        int skippedByPassOtherOutStation = 0;
 
         for (List<NavigateNode> path : allList) {
             if (path == null || path.isEmpty()) {
@@ -247,13 +293,27 @@
                 }
             }
             double cong = len <= 0 ? 0.0 : (double) tasks / (double) len;
+            int passOtherOutStationCount = countPassOtherOutStations(path, outStationIdSet);
+            if (forceSkipPassOtherOutStation && passOtherOutStationCount > 0) {
+                skippedByPassOtherOutStation++;
+                News.info("[WCS Debug] 绔欑偣璺緞鍊欓�夊凡璺宠繃锛屽洜缁忚繃鍏朵粬鍑哄簱绔欑偣,startStationId={},endStationId={},passOtherOutStationCount={}",
+                        extractStationId(path.get(0)),
+                        extractStationId(path.get(path.size() - 1)),
+                        passOtherOutStationCount);
+                continue;
+            }
             candidates.add(path);
             lens.add(len);
             tasksList.add(tasks);
             congs.add(cong);
+            passOtherOutStationCounts.add(passOtherOutStationCount);
         }
 
         if (candidates.isEmpty()) {
+            if (forceSkipPassOtherOutStation && skippedByPassOtherOutStation > 0) {
+                News.info("[WCS Debug] 鎵�鏈夌珯鐐硅矾寰勫�欓�夊潎鍥犵粡杩囧叾浠栧嚭搴撶珯鐐硅寮哄埗璺宠繃");
+                return new ArrayList<>();
+            }
             return allList.get(0);
         }
 
@@ -261,59 +321,50 @@
         int maxLen = Integer.MIN_VALUE;
         double minCong = Double.MAX_VALUE;
         double maxCong = -Double.MAX_VALUE;
+        int minPassOtherOutStationCount = Integer.MAX_VALUE;
+        int maxPassOtherOutStationCount = Integer.MIN_VALUE;
         for (int i = 0; i < candidates.size(); i++) {
             int l = lens.get(i);
             double c = congs.get(i);
+            int p = passOtherOutStationCounts.get(i);
             if (l < minLen) minLen = l;
             if (l > maxLen) maxLen = l;
             if (c < minCong) minCong = c;
             if (c > maxCong) maxCong = c;
+            if (p < minPassOtherOutStationCount) minPassOtherOutStationCount = p;
+            if (p > maxPassOtherOutStationCount) maxPassOtherOutStationCount = p;
         }
 
-        //闀垮害鏉冮噸鐧惧垎姣�
-        double lenWeightPercent = 50.0;
-        //鎷ュ牭鏉冮噸鐧惧垎姣�
-        double congWeightPercent = 50.0;
-        try {
-            ConfigService configService = SpringUtils.getBean(ConfigService.class);
-            if (configService != null) {
-                Config cfgLen = configService.selectOne(new EntityWrapper<Config>().eq("code", "stationPathLenWeightPercent"));
-                if (cfgLen != null && cfgLen.getValue() != null) {
-                    String v = cfgLen.getValue().trim();
-                    if (v.endsWith("%")) v = v.substring(0, v.length() - 1);
-                    try { lenWeightPercent = Double.parseDouble(v); } catch (Exception ignore) {}
-                }
-                Config cfgCong = configService.selectOne(new EntityWrapper<Config>().eq("code", "stationPathCongWeightPercent"));
-                if (cfgCong != null && cfgCong.getValue() != null) {
-                    String v = cfgCong.getValue().trim();
-                    if (v.endsWith("%")) v = v.substring(0, v.length() - 1);
-                    try { congWeightPercent = Double.parseDouble(v); } catch (Exception ignore) {}
-                }
-            }
-        } catch (Exception ignore) {}
-
-        double weightSum = lenWeightPercent + congWeightPercent;
+        double weightSum = lenWeightPercent + congWeightPercent + passOtherOutStationWeightPercent;
         double lenW = weightSum <= 0 ? 0.5 : lenWeightPercent / weightSum;
         double congW = weightSum <= 0 ? 0.5 : congWeightPercent / weightSum;
+        double passOtherOutStationW = weightSum <= 0 ? 0.0 : passOtherOutStationWeightPercent / weightSum;
 
         List<NavigateNode> best = null;
         double bestCost = Double.MAX_VALUE;
+        int bestPassOtherOutStationCount = Integer.MAX_VALUE;
         int bestTasks = Integer.MAX_VALUE;
         int bestLen = Integer.MAX_VALUE;
         for (int i = 0; i < candidates.size(); i++) {
             int l = lens.get(i);
             int t = tasksList.get(i);
             double c = congs.get(i);
+            int p = passOtherOutStationCounts.get(i);
             //褰掍竴鍖�
             double lenNorm = (maxLen - minLen) <= 0 ? 0.0 : (l - minLen) / (double) (maxLen - minLen);
             double congNorm = (maxCong - minCong) <= 0 ? 0.0 : (c - minCong) / (double) (maxCong - minCong);
+            double passOtherOutStationNorm = (maxPassOtherOutStationCount - minPassOtherOutStationCount) <= 0
+                    ? 0.0
+                    : (p - minPassOtherOutStationCount) / (double) (maxPassOtherOutStationCount - minPassOtherOutStationCount);
             //鑾峰彇鏉冮噸
-            double cost = lenNorm * lenW + congNorm * congW;
+            double cost = lenNorm * lenW + congNorm * congW + passOtherOutStationNorm * passOtherOutStationW;
             if (cost < bestCost
-                    || (cost == bestCost && t < bestTasks)
-                    || (cost == bestCost && t == bestTasks && l < bestLen)) {
+                    || (cost == bestCost && p < bestPassOtherOutStationCount)
+                    || (cost == bestCost && p == bestPassOtherOutStationCount && t < bestTasks)
+                    || (cost == bestCost && p == bestPassOtherOutStationCount && t == bestTasks && l < bestLen)) {
                 best = candidates.get(i);
                 bestCost = cost;
+                bestPassOtherOutStationCount = p;
                 bestTasks = t;
                 bestLen = l;
             }
@@ -325,6 +376,102 @@
         return best;
     }
 
+    private Set<Integer> loadAllOutStationIdSet() {
+        Set<Integer> outStationIdSet = new HashSet<>();
+        try {
+            BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class);
+            if (basDevpService == null) {
+                return outStationIdSet;
+            }
+            List<BasDevp> basDevpList = basDevpService.selectList(new EntityWrapper<BasDevp>().eq("status", 1));
+            for (BasDevp basDevp : basDevpList) {
+                List<StationObjModel> outStationList = basDevp.getOutStationList$();
+                for (StationObjModel stationObjModel : outStationList) {
+                    if (stationObjModel != null && stationObjModel.getStationId() != null) {
+                        outStationIdSet.add(stationObjModel.getStationId());
+                    }
+                }
+            }
+        } catch (Exception ignore) {}
+        return outStationIdSet;
+    }
+
+    private int countPassOtherOutStations(List<NavigateNode> path, Set<Integer> outStationIdSet) {
+        if (path == null || path.size() < 3 || outStationIdSet == null || outStationIdSet.isEmpty()) {
+            return 0;
+        }
+        Integer startStationId = extractStationId(path.get(0));
+        Integer endStationId = extractStationId(path.get(path.size() - 1));
+        Set<Integer> hitStationIdSet = new HashSet<>();
+        for (int i = 1; i < path.size() - 1; i++) {
+            Integer stationId = extractStationId(path.get(i));
+            if (stationId == null) {
+                continue;
+            }
+            if (startStationId != null && startStationId.equals(stationId)) {
+                continue;
+            }
+            if (endStationId != null && endStationId.equals(stationId)) {
+                continue;
+            }
+            if (outStationIdSet.contains(stationId)) {
+                hitStationIdSet.add(stationId);
+            }
+        }
+        return hitStationIdSet.size();
+    }
+
+    private Integer extractStationId(NavigateNode node) {
+        if (node == null || node.getNodeValue() == null) {
+            return null;
+        }
+        try {
+            JSONObject value = JSON.parseObject(node.getNodeValue());
+            if (value == null) {
+                return null;
+            }
+            return value.getInteger("stationId");
+        } catch (Exception ignore) {}
+        return null;
+    }
+
+    private double loadDoubleConfig(ConfigService configService, String code, double defaultValue) {
+        if (configService == null || code == null) {
+            return defaultValue;
+        }
+        Config config = configService.selectOne(new EntityWrapper<Config>().eq("code", code));
+        if (config == null || config.getValue() == null) {
+            return defaultValue;
+        }
+        String value = config.getValue().trim();
+        if (value.endsWith("%")) {
+            value = value.substring(0, value.length() - 1);
+        }
+        try {
+            return Double.parseDouble(value);
+        } catch (Exception ignore) {}
+        return defaultValue;
+    }
+
+    private boolean loadBooleanConfig(ConfigService configService, String code, boolean defaultValue) {
+        if (configService == null || code == null) {
+            return defaultValue;
+        }
+        Config config = configService.selectOne(new EntityWrapper<Config>().eq("code", code));
+        if (config == null || config.getValue() == null) {
+            return defaultValue;
+        }
+        String value = config.getValue().trim();
+        if (value.isEmpty()) {
+            return defaultValue;
+        }
+        return "1".equals(value)
+                || "true".equalsIgnoreCase(value)
+                || "yes".equalsIgnoreCase(value)
+                || "y".equalsIgnoreCase(value)
+                || "on".equalsIgnoreCase(value);
+    }
+
     //鍒ゆ柇褰撳墠鑺傜偣鍒颁笅涓�涓妭鐐规槸鍚︿负鎷愮偣
     public HashMap<String,Object> searchInflectionPoint(NavigateNode currentNode, NavigateNode fatherNode, NavigateNode nextNode) {
         HashMap<String, Object> map = new HashMap<>();
diff --git a/src/main/java/com/zy/core/ServerBootstrap.java b/src/main/java/com/zy/core/ServerBootstrap.java
index a4a5e65..953f986 100644
--- a/src/main/java/com/zy/core/ServerBootstrap.java
+++ b/src/main/java/com/zy/core/ServerBootstrap.java
@@ -135,6 +135,8 @@
                     thread = new ZyStationThread(deviceConfig, redisUtil);
                 } else if (deviceConfig.getThreadImpl().equals("ZyStationV3Thread")) {
                     thread = new ZyStationV3Thread(deviceConfig, redisUtil);
+                } else if (deviceConfig.getThreadImpl().equals("ZyStationV4Thread")) {
+                    thread = new ZyStationV4Thread(deviceConfig, redisUtil);
                 } else {
                     throw new CoolException("鏈煡鐨勭嚎绋嬪疄鐜�");
                 }
diff --git a/src/main/java/com/zy/core/enums/RedisKeyType.java b/src/main/java/com/zy/core/enums/RedisKeyType.java
index eff66b9..7f9a454 100644
--- a/src/main/java/com/zy/core/enums/RedisKeyType.java
+++ b/src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -2,15 +2,11 @@
 
 public enum RedisKeyType {
 
-    SHUTTLE_WORK_FLAG("shuttle_wrk_no_"),
-    SHUTTLE_FLAG("shuttle_"),
-    FORK_LIFT_WORK_FLAG("fork_lift_wrk_no_"),
-    FORK_LIFT_FLAG("fork_lift_"),
-    LIFT_WORK_FLAG("lift_wrk_no_"),
-    LIFT_FLAG("lift_"),
-
+    DUAL_CRN_STATION1_FLAG("dual_crn_station1_flag_"),
+    DUAL_CRN_STATION2_FLAG("dual_crn_station2_flag_"),
     DUAL_CRN_COMMAND_("dual_crn_command_"),
     DUAL_CRN_COMMAND_IDX("dual_crn_command_idx_"),
+    DUAL_CRN_COMMAND_LAST("dual_crn_command_LAST_"),
 
     QUEUE_CRN("queue_crn_"),
     QUEUE_DUAL_CRN("queue_dual_crn_"),
@@ -59,6 +55,9 @@
     DUAL_CRN_OUT_TASK_COMPLETE_STATION_INFO("dual_crn_out_task_complete_station_info_"),
     CRN_OUT_TASK_COMPLETE_STATION_INFO("crn_out_task_complete_station_info_"),
 
+    WATCH_CIRCLE_STATION_("watch_circle_station_"),
+    STATION_CYCLE_LOAD_RESERVE("station_cycle_load_reserve"),
+
     CURRENT_CIRCLE_TASK_CRN_NO("current_circle_task_crn_no_"),
     ASYNC_WMS_IN_TASK_REQUEST("async_wms_in_task_request_"),
     ASYNC_WMS_IN_TASK_RESPONSE("async_wms_in_task_response_"),
diff --git a/src/main/java/com/zy/core/enums/WrkStsType.java b/src/main/java/com/zy/core/enums/WrkStsType.java
index 59b4665..381ccfc 100644
--- a/src/main/java/com/zy/core/enums/WrkStsType.java
+++ b/src/main/java/com/zy/core/enums/WrkStsType.java
@@ -15,6 +15,7 @@
     OUTBOUND_RUN(102, "璁惧鎼繍涓�"),
     OUTBOUND_RUN_COMPLETE(103, "璁惧鎼繍瀹屾垚"),
     STATION_RUN(104, "绔欑偣杩愯涓�"),
+    STATION_RUN_COMPLETE(105, "绔欑偣杩愯瀹屾垚"),
     COMPLETE_OUTBOUND(109, "鍑哄簱瀹屾垚"),
     SETTLE_OUTBOUND(110, "鍑哄簱搴撳瓨鏇存柊"),
 
diff --git a/src/main/java/com/zy/core/model/command/StationCommand.java b/src/main/java/com/zy/core/model/command/StationCommand.java
index a896e33..44cf588 100644
--- a/src/main/java/com/zy/core/model/command/StationCommand.java
+++ b/src/main/java/com/zy/core/model/command/StationCommand.java
@@ -19,8 +19,14 @@
 
     private List<Integer> navigatePath;
 
+    // 璺緞涓殑椤跺崌绉绘牻鐐癸紙鎸夎矾寰勯『搴忥級
+    private List<Integer> liftTransferPath;
+
     private List<Integer> originalNavigatePath;
 
     private StationCommandType commandType;
 
+    // 浠跨湡妯″紡涓嬪彲閫夛細鎵嬪伐鎸囧畾鏉$爜
+    private String barcode;
+
 }
diff --git a/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java b/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
index 44440a4..72848b9 100644
--- a/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/DualCrnProtocol.java
@@ -1,9 +1,9 @@
 package com.zy.core.model.protocol;
 
-import com.zy.core.enums.DualCrnForkPosType;
-import com.zy.core.enums.DualCrnLiftPosType;
-import com.zy.core.enums.DualCrnModeType;
-import com.zy.core.enums.DualCrnStatusType;
+import com.core.common.Cools;
+import com.core.common.SpringUtils;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.*;
 
 import lombok.Data;
 
@@ -12,31 +12,41 @@
 @Data
 public class DualCrnProtocol {
 
-    private Integer crnNo;
+    private volatile Integer crnNo;
 
     /**
      * 1 = 鎵嬪姩妯″紡
      * 2 = 鑷姩妯″紡
      * 3 = 鐢佃剳妯″紡
      */
-    public Integer mode;
+    public volatile Integer mode;
 
-    public DualCrnModeType modeType;
+    public volatile DualCrnModeType modeType;
 
     /**
      * 寮傚父鐮�
      */
-    public Integer alarm;
+    public volatile Integer alarm;
 
     /**
-     * 宸ヤ綅1浠诲姟鍙�
+     * WCS宸ヤ綅1浠诲姟鍙�
      */
-    public Integer taskNo = 0;
+    public volatile Integer taskNo = 0;
 
     /**
-     * 宸ヤ綅2浠诲姟鍙�
+     * WCS宸ヤ綅2浠诲姟鍙�
      */
-    public Integer taskNoTwo = 0;
+    public volatile Integer taskNoTwo = 0;
+
+    /**
+     * 璁惧宸ヤ綅1浠诲姟鍙�
+     */
+    public volatile Integer deviceTaskNo = 0;
+
+    /**
+     * 璁惧宸ヤ綅2浠诲姟鍙�
+     */
+    public volatile Integer deviceTaskNoTwo = 0;
 
     /**
      * 宸ヤ綅1褰撳墠鐘舵��
@@ -51,42 +61,42 @@
      * 90锛氫换鍔″畬鎴愮瓑寰匴CS纭
      * 99锛氭姤璀�
      */
-    public Integer status;
+    public volatile Integer status;
 
     /**
      * 宸ヤ綅2褰撳墠鐘舵��
      */
-    public Integer statusTwo;
+    public volatile Integer statusTwo;
 
     /**
      * 宸ヤ綅1鐘舵�佹灇涓�
      */
-    public DualCrnStatusType statusType;
+    public volatile DualCrnStatusType statusType;
 
     /**
      * 宸ヤ綅2鐘舵�佹灇涓�
      */
-    public DualCrnStatusType statusTypeTwo;
+    public volatile DualCrnStatusType statusTypeTwo;
 
     /**
      * 宸ヤ綅1鍫嗗灈鏈哄綋鍓嶅垪鍙�
      */
-    public Integer bay;
+    public volatile Integer bay;
 
     /**
      * 宸ヤ綅2鍫嗗灈鏈哄綋鍓嶅垪鍙�
      */
-    public Integer bayTwo;
+    public volatile Integer bayTwo;
 
     /**
      * 宸ヤ綅1鍫嗗灈鏈哄綋鍓嶅眰鍙�
      */
-    public Integer level;
+    public volatile Integer level;
 
     /**
      * 宸ヤ綅2鍫嗗灈鏈哄綋鍓嶅眰鍙�
      */
-    public Integer levelTwo;
+    public volatile Integer levelTwo;
 
     /**
      * 宸ヤ綅1褰撳墠璐у弶浣嶇疆
@@ -94,7 +104,7 @@
      * 1 = 璐у弶鍦ㄥ乏渚�
      * 2 = 璐у弶鍦ㄥ彸渚�
      */
-    public Integer forkPos;
+    public volatile Integer forkPos;
 
     /**
      * 宸ヤ綅2褰撳墠璐у弶浣嶇疆
@@ -102,51 +112,55 @@
      * 1 = 璐у弶鍦ㄥ乏渚�
      * 2 = 璐у弶鍦ㄥ彸渚�
      */
-    public Integer forkPosTwo;
+    public volatile Integer forkPosTwo;
 
-    public DualCrnForkPosType forkPosType;
+    public volatile DualCrnForkPosType forkPosType;
 
-    public DualCrnForkPosType forkPosTypeTwo;
+    public volatile DualCrnForkPosType forkPosTypeTwo;
 
     /**
      * 褰撳墠杞借揣鍙颁綅缃�
      * 0 = 涓嬪畾浣�
      * 1 = 涓婂畾浣�
      */
-    public Integer liftPos;
+    public volatile Integer liftPos;
 
-    public Integer liftPosTwo;
+    public volatile Integer liftPosTwo;
 
-    public DualCrnLiftPosType liftPosType;
+    public volatile DualCrnLiftPosType liftPosType;
 
-    public DualCrnLiftPosType liftPosTypeTwo;
+    public volatile DualCrnLiftPosType liftPosTypeTwo;
 
     /**
      * 璧拌鍦ㄥ畾浣�
      * 0 = 鍦ㄥ畾浣�
      * 1 = 涓嶅湪瀹氫綅
      */
-    public Integer walkPos;
+    public volatile Integer walkPos;
 
-    public Integer walkPosTwo;
+    public volatile Integer walkPosTwo;
 
     /**
      * 杞借揣鍙版湁鐗�
      */
-    public Integer loaded;
+    public volatile  Integer loaded;
 
-    public Integer loadedTwo;
+    public volatile Integer loadedTwo;
 
     /**
      * 浠诲姟鎺ユ敹鐘舵��
      * 0 = 鏈帴鏀�
      * 1 = 宸叉帴鏀�
      */
-    public Integer taskReceive;
+    public volatile Integer taskReceive;
 
-    public Integer taskReceiveTwo;
+    public volatile Integer taskReceiveTwo;
 
-    private Integer temp1;
+    public volatile Integer taskSend;
+
+    public volatile Integer taskSendTwo;
+
+    private volatile Integer temp1;
 
     private Integer temp2;
 
@@ -292,4 +306,32 @@
         this.statusTwo = DualCrnStatusType.get(type).id;
     }
 
+    public Integer getTaskNo() {
+        RedisUtil redisUtil = null;
+        try {
+            redisUtil = SpringUtils.getBean(RedisUtil.class);
+        }catch (Exception e) {}
+        if (null != redisUtil) {
+            Object o = redisUtil.get(RedisKeyType.DUAL_CRN_STATION1_FLAG.key + this.crnNo);
+            if (!Cools.isEmpty(o)) {
+                this.taskNo = Integer.parseInt(String.valueOf(o));
+            }
+        }
+        return this.taskNo == null ? 0 : this.taskNo;
+    }
+
+    public Integer getTaskNoTwo() {
+        RedisUtil redisUtil = null;
+        try {
+            redisUtil = SpringUtils.getBean(RedisUtil.class);
+        }catch (Exception e) {}
+        if (null != redisUtil) {
+            Object o = redisUtil.get(RedisKeyType.DUAL_CRN_STATION2_FLAG.key + this.crnNo);
+            if (!Cools.isEmpty(o)) {
+                this.taskNoTwo = Integer.parseInt(String.valueOf(o));
+            }
+        }
+        return this.taskNoTwo == null ? 0 : this.taskNoTwo;
+    }
+
 }
diff --git a/src/main/java/com/zy/core/model/protocol/StationProtocol.java b/src/main/java/com/zy/core/model/protocol/StationProtocol.java
index 0d4a589..d3406d8 100644
--- a/src/main/java/com/zy/core/model/protocol/StationProtocol.java
+++ b/src/main/java/com/zy/core/model/protocol/StationProtocol.java
@@ -64,6 +64,9 @@
     //WCS绯荤粺鎶ヨ
     private String systemWarning;
 
+    //浠诲姟鍙啓鍖�
+    private Integer taskWriteIdx;
+
    /**
     * 鎵╁睍鏁版嵁
     */
diff --git a/src/main/java/com/zy/core/network/ZyStationConnectDriver.java b/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
index 59d0d1b..62b1b93 100644
--- a/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
+++ b/src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -11,8 +11,10 @@
 import java.util.List;
 import com.zy.core.network.fake.ZyStationFakeConnect;
 import com.zy.core.network.fake.ZyStationFakeSegConnect;
+import com.zy.core.network.fake.ZyStationV4FakeSegConnect;
 import com.zy.core.network.real.ZyStationRealConnect;
 import com.zy.core.network.real.ZyStationV3RealConnect;
+import com.zy.core.network.real.ZyStationV4RealConnect;
 import lombok.extern.slf4j.Slf4j;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -27,6 +29,7 @@
 
     private static final ZyStationFakeConnect zyStationFakeConnect = new ZyStationFakeConnect();
     private static final ZyStationFakeSegConnect zyStationFakeSegConnect = new ZyStationFakeSegConnect();
+    private static final ZyStationV4FakeSegConnect zyStationV4FakeSegConnect = new ZyStationV4FakeSegConnect();
 
     private boolean connected = false;
     private DeviceConfig deviceConfig;
@@ -50,6 +53,8 @@
         if (deviceConfig.getFake() == 0) {
             if ("ZyStationV3Thread".equals(deviceConfig.getThreadImpl())) {
                 zyStationConnectApi = new ZyStationV3RealConnect(deviceConfig, redisUtil);
+            } else if ("ZyStationV4Thread".equals(deviceConfig.getThreadImpl())) {
+                zyStationConnectApi = new ZyStationV4RealConnect(deviceConfig, redisUtil);
             } else {
                 zyStationConnectApi = new ZyStationRealConnect(deviceConfig, redisUtil);
             }
@@ -57,6 +62,9 @@
             if ("ZyStationV3Thread".equals(deviceConfig.getThreadImpl())) {
                 zyStationFakeSegConnect.addFakeConnect(deviceConfig, redisUtil);
                 zyStationConnectApi = zyStationFakeSegConnect;
+            } else if ("ZyStationV4Thread".equals(deviceConfig.getThreadImpl())) {
+                zyStationV4FakeSegConnect.addFakeConnect(deviceConfig, redisUtil);
+                zyStationConnectApi = zyStationV4FakeSegConnect;
             } else {
                 zyStationFakeConnect.addFakeConnect(deviceConfig, redisUtil);
                 zyStationConnectApi = zyStationFakeConnect;
diff --git a/src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java b/src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java
index 33dd5f3..b2c516a 100644
--- a/src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java
+++ b/src/main/java/com/zy/core/network/entity/ZyDualCrnStatusEntity.java
@@ -72,6 +72,10 @@
 
     public Integer taskReceiveTwo;
 
+    public Integer taskSend;
+
+    public Integer taskSendTwo;
+
     /**
      * 鍫嗗灈鏈哄綋鍓嶅垪鍙�
      */
diff --git a/src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java b/src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java
index 40bdbfa..caf2695 100644
--- a/src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java
+++ b/src/main/java/com/zy/core/network/entity/ZyStationStatusEntity.java
@@ -50,6 +50,9 @@
     //閲嶉噺
     private Double weight;
 
+    //浠诲姟鍙啓鍖�
+    private Integer taskWriteIdx;
+
     //杩愯鍫靛
     private boolean runBlock = false;
 
diff --git a/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java b/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
index d529843..79762ed 100644
--- a/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
+++ b/src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
@@ -2,7 +2,7 @@
 
 import com.alibaba.fastjson.JSON;
 import com.zy.asrs.entity.DeviceConfig;
-import com.zy.core.enums.CrnStatusType;
+import com.zy.core.enums.DualCrnStatusType;
 import com.zy.core.enums.DualCrnTaskModeType;
 import com.zy.core.model.CommandResponse;
 import com.zy.core.model.command.DualCrnCommand;
@@ -20,6 +20,8 @@
     private final ExecutorService executor = Executors
             .newFixedThreadPool(9999);
     private int taskExecuteStation = 0;
+    private DualCrnCommand station1LastCommand = null;
+    private DualCrnCommand station2LastCommand = null;
 
     public ZyDualCrnFakeConnect(DeviceConfig deviceConfig) {
         this.deviceConfig = deviceConfig;
@@ -71,13 +73,29 @@
                 this.crnStatus.setTaskNo(0);
             }
             this.crnStatus.setTaskReceive(0);
-            this.crnStatus.setStatus(CrnStatusType.IDLE.id);
+            if (station1LastCommand == null) {
+                this.crnStatus.setStatus(DualCrnStatusType.IDLE.id);
+            }else {
+                if (station1LastCommand.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
+                    this.crnStatus.setStatus(DualCrnStatusType.FETCH_COMPLETE.id);
+                }else {
+                    this.crnStatus.setStatus(DualCrnStatusType.IDLE.id);
+                }
+            }
         }else {
             if (crnStatus.getLoadedTwo() == 0) {
                 this.crnStatus.setTaskNoTwo(0);
             }
             this.crnStatus.setTaskReceiveTwo(0);
-            this.crnStatus.setStatusTwo(CrnStatusType.IDLE.id);
+            if (station2LastCommand == null) {
+                this.crnStatus.setStatusTwo(DualCrnStatusType.IDLE.id);
+            }else {
+                if (station1LastCommand.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
+                    this.crnStatus.setStatusTwo(DualCrnStatusType.FETCH_COMPLETE.id);
+                }else {
+                    this.crnStatus.setStatusTwo(DualCrnStatusType.IDLE.id);
+                }
+            }
         }
     }
 
@@ -103,16 +121,16 @@
 
         if(command.getStation() == 1) {
             this.crnStatus.setTaskNo(taskNo);
-            this.crnStatus.setStatus(CrnStatusType.MOVING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.PUT_MOVING.id);
             this.crnStatus.setTaskReceive(1);
             moveYZ(this.crnStatus.getBay(), destinationPosY, this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.WAITING.id);
         }else {
             this.crnStatus.setTaskNoTwo(taskNo);
-            this.crnStatus.setStatusTwo(CrnStatusType.MOVING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.PUT_MOVING.id);
             this.crnStatus.setTaskReceive(1);
             moveYZ(this.crnStatus.getBay(), destinationPosY, this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.WAITING.id);
         }
 
         taskExecuteStation = 0;
@@ -143,48 +161,52 @@
 
         if(command.getStation() == 1) {
             this.crnStatus.setTaskNo(taskNo);
-            this.crnStatus.setStatus(CrnStatusType.FETCH_MOVING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.FETCH_MOVING.id);
             this.crnStatus.setTaskReceive(1);
 
             moveYZ(this.crnStatus.getBay(), sourcePosY,this.crnStatus.getLevel(), sourcePosZ,command.getStation());
-            this.crnStatus.setStatus(CrnStatusType.FETCHING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.FETCHING.id);
             sleep(2000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
 
             this.crnStatus.setLoaded(1);
-            this.crnStatus.setStatus(CrnStatusType.PUT_MOVING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.PUT_MOVING.id);
             moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatus(CrnStatusType.PUTTING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.PUTTING.id);
             sleep(2000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
             this.crnStatus.setLoaded(0);
-            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.WAITING.id);
+
+            this.station1LastCommand = command;
         }else {
             this.crnStatus.setTaskNoTwo(taskNo);
-            this.crnStatus.setStatusTwo(CrnStatusType.FETCH_MOVING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.FETCH_MOVING.id);
             this.crnStatus.setTaskReceiveTwo(1);
 
             moveYZ(this.crnStatus.getBay(), sourcePosY,this.crnStatus.getLevel(), sourcePosZ, command.getStation());
-            this.crnStatus.setStatusTwo(CrnStatusType.FETCHING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.FETCHING.id);
             sleep(2000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
 
             this.crnStatus.setLoadedTwo(1);
-            this.crnStatus.setStatusTwo(CrnStatusType.PUT_MOVING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.PUT_MOVING.id);
             moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatusTwo(CrnStatusType.PUTTING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.PUTTING.id);
             sleep(2000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
             this.crnStatus.setLoadedTwo(0);
-            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.WAITING.id);
+
+            this.station2LastCommand = command;
         }
 
         taskExecuteStation = 0;
@@ -212,30 +234,34 @@
 
         if(command.getStation() == 1) {
             this.crnStatus.setTaskNo(taskNo);
-            this.crnStatus.setStatus(CrnStatusType.FETCH_MOVING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.FETCH_MOVING.id);
             this.crnStatus.setTaskReceive(1);
 
             moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatus(CrnStatusType.FETCHING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.FETCHING.id);
             sleep(3000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
             this.crnStatus.setLoaded(1);
-            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.WAITING.id);
+
+            this.station1LastCommand = command;
         }else {
             this.crnStatus.setTaskNoTwo(taskNo);
-            this.crnStatus.setStatusTwo(CrnStatusType.FETCH_MOVING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.FETCH_MOVING.id);
             this.crnStatus.setTaskReceiveTwo(1);
 
             moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatusTwo(CrnStatusType.FETCHING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.FETCHING.id);
             sleep(3000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
             this.crnStatus.setLoadedTwo(1);
-            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.WAITING.id);
+
+            this.station2LastCommand = command;
         }
 
         taskExecuteStation = 0;
@@ -263,30 +289,34 @@
 
         if(command.getStation() == 1) {
             this.crnStatus.setTaskNo(taskNo);
-            this.crnStatus.setStatus(CrnStatusType.PUT_MOVING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.PUT_MOVING.id);
             this.crnStatus.setTaskReceive(1);
 
             moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatus(CrnStatusType.PUTTING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.PUTTING.id);
             sleep(3000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
             this.crnStatus.setLoaded(0);
-            this.crnStatus.setStatus(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatus(DualCrnStatusType.WAITING.id);
+
+            this.station1LastCommand = command;
         }else {
             this.crnStatus.setTaskNoTwo(taskNo);
-            this.crnStatus.setStatusTwo(CrnStatusType.PUT_MOVING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.PUT_MOVING.id);
             this.crnStatus.setTaskReceiveTwo(1);
 
             moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
-            this.crnStatus.setStatusTwo(CrnStatusType.PUTTING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.PUTTING.id);
             sleep(3000);
             if (Thread.currentThread().isInterrupted()) {
                 return;
             }
             this.crnStatus.setLoadedTwo(0);
-            this.crnStatus.setStatusTwo(CrnStatusType.WAITING.id);
+            this.crnStatus.setStatusTwo(DualCrnStatusType.WAITING.id);
+
+            this.station2LastCommand = command;
         }
 
         taskExecuteStation = 0;
diff --git a/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java b/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
index 99d4dd9..c5ac97e 100644
--- a/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
+++ b/src/main/java/com/zy/core/network/fake/ZyStationFakeConnect.java
@@ -129,6 +129,10 @@
         }
 
         if(commandType == StationCommandType.WRITE_INFO){
+            if (command.getBarcode() != null) {
+                updateStationBarcode(deviceNo, stationId, command.getBarcode());
+                return;
+            }
             if (taskNo == 9998 && targetStationId == 0) {
                 //鐢熸垚鍑哄簱绔欑偣浠跨湡鏁版嵁
                 generateFakeOutStationData(deviceNo, stationId);
@@ -199,6 +203,23 @@
         }
     }
 
+    private void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) {
+        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
+        if (statusList == null) {
+            return;
+        }
+
+        ZyStationStatusEntity status = statusList.stream()
+                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
+        if (status == null) {
+            return;
+        }
+
+        synchronized (status) {
+            status.setBarcode(barcode);
+        }
+    }
+
     private void currentLevCommand(StationCommand command, boolean generateBarcode) {
         NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
         if (navigateUtils == null) {
diff --git a/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java b/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
index 4530d45..b5a077f 100644
--- a/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
+++ b/src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
@@ -144,12 +144,20 @@
                 if (command != null) {
                     taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
 
-                    // 棣栨鎺ユ敹鍛戒护鏃跺垵濮嬪寲
-                    if (finalTargetStationId == null) {
-                        finalTargetStationId = command.getTargetStaNo();
-                        if (checkTaskNoInArea(taskNo)) {
-                            generateBarcode = true;
+                    // 姣忔鎺ユ敹鍛戒护閮藉埛鏂扮洰鏍囷紝閬垮厤娌跨敤鏃х洰鏍囧鑷寸姸鎬佹姈鍔�
+                    Integer commandTargetStationId = command.getTargetStaNo();
+                    if (commandTargetStationId != null) {
+                        if (!commandTargetStationId.equals(finalTargetStationId)) {
+                            News.info("[WCS Debug] 浠诲姟{}鍒囨崲鐩爣: {} -> {}", taskNo, finalTargetStationId,
+                                    commandTargetStationId);
                         }
+                        finalTargetStationId = commandTargetStationId;
+                        // 褰撳墠绔欑偣鍏堝悓姝ユ渶鏂扮洰鏍囷紝閬垮厤涓婂眰鍦ㄧ獥鍙f湡閲嶅涓嬪彂鍚屼竴璺緞
+                        syncCurrentStationTarget(taskNo, currentStationId, finalTargetStationId);
+                    }
+
+                    if (!generateBarcode && checkTaskNoInArea(taskNo)) {
+                        generateBarcode = true;
                     }
 
                     // 灏嗘柊璺緞杩藉姞鍒板緟鎵ц闃熷垪
@@ -157,15 +165,7 @@
                     if (newPath != null && !newPath.isEmpty()) {
                         // 鑾峰彇闃熷垪涓渶鍚庝竴涓珯鐐癸紙鐢ㄤ簬琛旀帴鐐瑰幓閲嶏級
                         Integer lastInQueue = getLastInQueue(pendingPathQueue);
-                        if (lastInQueue == null) {
-                            lastInQueue = currentStationId;
-                        }
-
-                        int startIndex = 0;
-                        // 濡傛灉鏂拌矾寰勭殑璧风偣涓庡綋鍓嶄綅缃垨闃熷垪鏈熬閲嶅锛屽垯璺宠繃
-                        if (lastInQueue != null && !newPath.isEmpty() && newPath.get(0).equals(lastInQueue)) {
-                            startIndex = 1;
-                        }
+                        int startIndex = getPathAppendStartIndex(newPath, currentStationId, lastInQueue);
 
                         for (int i = startIndex; i < newPath.size(); i++) {
                             pendingPathQueue.offer(newPath.get(i));
@@ -261,7 +261,7 @@
                     // 璺緞闃熷垪涓虹┖锛岀瓑寰呮柊鐨勫垎娈靛懡浠�
                     if (currentStationId != null && finalTargetStationId != null
                             && currentStationId.equals(finalTargetStationId)) {
-                        // 宸插埌杈炬渶缁堢洰鏍囷紝姝e父缁撴潫
+                        // 宸插埌杈剧洰鏍囷細绔嬪嵆娓呯┖褰撳墠闃熷垪骞剁粨鏉熸湰杞墽琛岋紝鍚庣画鏂板懡浠ら噸鏂板垱寤烘墽琛岀嚎绋�
                         if (generateBarcode) {
                             Integer targetDeviceNo = getDeviceNoByStationId(finalTargetStationId);
                             if (targetDeviceNo != null) {
@@ -269,10 +269,13 @@
                                 News.info("[WCS Debug] 浠诲姟{}鍒拌揪鐩爣{}骞剁敓鎴愭潯鐮�", taskNo, finalTargetStationId);
                             }
                         }
+                        commandQueue.clear();
+                        pendingPathQueue.clear();
+                        News.info("[WCS Debug] 浠诲姟{}鍒拌揪鐩爣鍚庢竻绌洪槦鍒楀苟缁撴潫锛岀瓑寰呭悗缁柊鍛戒护閲嶅惎", taskNo);
                         break;
                     }
 
-                    // 鏈埌杈炬渶缁堢洰鏍囷紝绛夊緟鏂扮殑鍒嗘鍛戒护
+                    // 缁х画绛夊緟鏂扮殑鍒嗘鍛戒护
                     Long lastTime = taskLastUpdateTime.get(taskNo);
                     if (lastTime != null && System.currentTimeMillis() - lastTime > 30000) {
                         // 瓒呮椂锛�30绉掑唴娌℃湁鏀跺埌鏂板垎娈靛懡浠�
@@ -302,6 +305,67 @@
             last = item;
         }
         return last;
+    }
+
+    /**
+     * 璁$畻鏂拌矾寰勫湪闃熷垪涓殑杩藉姞璧风偣锛岄伩鍏嶉噸澶嶄笅鍙戝鑷磋矾寰勬潵鍥炶烦
+     */
+    private int getPathAppendStartIndex(List<Integer> newPath, Integer currentStationId, Integer lastInQueue) {
+        if (newPath == null || newPath.isEmpty()) {
+            return 0;
+        }
+
+        if (lastInQueue != null) {
+            int idx = newPath.lastIndexOf(lastInQueue);
+            if (idx >= 0) {
+                return idx + 1;
+            }
+        }
+
+        if (currentStationId != null) {
+            int idx = newPath.lastIndexOf(currentStationId);
+            if (idx >= 0) {
+                return idx + 1;
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * 鍛戒护鍒氬埌杈炬椂鍚屾褰撳墠绔欑偣鐩爣锛岄檷浣庝笂灞傞噸澶嶅彂鍚屼竴璺緞鐨勬鐜�
+     */
+    private void syncCurrentStationTarget(Integer taskNo, Integer currentStationId, Integer targetStationId) {
+        if (currentStationId == null || targetStationId == null) {
+            return;
+        }
+        Integer currentDeviceNo = getDeviceNoByStationId(currentStationId);
+        if (currentDeviceNo == null) {
+            return;
+        }
+
+        lockStations(currentStationId);
+        try {
+            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentDeviceNo);
+            if (statusList == null) {
+                return;
+            }
+
+            ZyStationStatusEntity currentStatus = statusList.stream()
+                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
+            if (currentStatus == null) {
+                return;
+            }
+
+            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
+                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
+                return;
+            }
+
+            updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null);
+        } finally {
+            unlockStations(currentStationId);
+        }
     }
 
     /**
@@ -347,6 +411,10 @@
         }
 
         if (command.getCommandType() == StationCommandType.WRITE_INFO) {
+            if (command.getBarcode() != null) {
+                updateStationBarcode(deviceNo, stationId, command.getBarcode());
+                return;
+            }
             if (taskNo == 9998 && targetStationId == 0) {
                 // 鐢熸垚鍑哄簱绔欑偣浠跨湡鏁版嵁
                 generateFakeOutStationData(deviceNo, stationId);
@@ -402,6 +470,23 @@
         }
     }
 
+    private void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) {
+        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
+        if (statusList == null) {
+            return;
+        }
+
+        ZyStationStatusEntity status = statusList.stream()
+                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
+        if (status == null) {
+            return;
+        }
+
+        synchronized (status) {
+            status.setBarcode(barcode);
+        }
+    }
+
     // segmentedPathCommand 鏂规硶宸插垹闄わ紝鍔熻兘宸叉暣鍚堝埌 runTaskLoop
 
     private Integer getDeviceNoByStationId(Integer stationId) {
diff --git a/src/main/java/com/zy/core/network/real/ZyCrnV2RealConnect.java b/src/main/java/com/zy/core/network/real/ZyCrnV2RealConnect.java
index 5e577af..6904c7f 100644
--- a/src/main/java/com/zy/core/network/real/ZyCrnV2RealConnect.java
+++ b/src/main/java/com/zy/core/network/real/ZyCrnV2RealConnect.java
@@ -109,7 +109,7 @@
                             Object lock = redisUtil.get(RedisKeyType.CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo());
                             if (lock == null) {
                                 OperateResult taskClearResult = siemensNet.Write("DB100.0", array);
-                                redisUtil.set(RedisKeyType.CLEAR_CRN_TASK_LIMIT.key + deviceConfig.getDeviceNo(), "lock", 3);
+                                redisUtil.set(RedisKeyType.CLEAR_CRN_TASK_LIMIT.key + deviceConfig.getDeviceNo(), "lock", 1);
                             }
                         }
                     }
@@ -131,7 +131,7 @@
     @Override
     public CommandResponse sendCommand(CrnCommand command) {
         RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
-        redisUtil.set(RedisKeyType.CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 10);
+        redisUtil.set(RedisKeyType.CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 2);
         CommandResponse response = new CommandResponse(false);
         try {
             if (null == command) {
@@ -239,7 +239,7 @@
                 OutputQueue.CRN.offer(MessageFormat.format("銆恵0}銆慬id:{1}] >>>>> 鍛戒护涓嬪彂锛� {2}", DateUtils.convert(new Date()), command.getCrnNo(), JSON.toJSON(command)));
                 response.setResult(true);
                 response.setMessage("鍛戒护涓嬪彂鎴愬姛");
-                redisUtil.set(RedisKeyType.CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 5);
+                redisUtil.set(RedisKeyType.CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 1);
             } else {
                 News.error("鍐欏叆鍫嗗灈鏈簆lc鏁版嵁澶辫触 ===>> [id:{}]", command.getCrnNo());
                 OutputQueue.CRN.offer(MessageFormat.format("銆恵0}銆戝啓鍏ュ爢鍨涙満plc鏁版嵁澶辫触 ===>> [id:{1}]", DateUtils.convert(new Date()), command.getCrnNo()));
diff --git a/src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java b/src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java
index d73416b..3abf33b 100644
--- a/src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java
+++ b/src/main/java/com/zy/core/network/real/ZyDualCrnRealConnect.java
@@ -17,6 +17,8 @@
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
 import com.zy.core.cache.OutputQueue;
+import com.zy.core.enums.CrnTaskModeType;
+import com.zy.core.enums.DualCrnTaskModeType;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.core.model.CommandResponse;
 import com.zy.core.model.command.DualCrnCommand;
@@ -25,6 +27,7 @@
 
 import java.text.MessageFormat;
 import java.util.Date;
+import java.util.HashMap;
 
 public class ZyDualCrnRealConnect implements ZyDualCrnConnectApi {
 
@@ -97,20 +100,33 @@
                 crnStatus.setXDuration((double) siemensNet.getByteTransform().TransSingle(result.Content, 62));
                 crnStatus.setYDuration((double) siemensNet.getByteTransform().TransSingle(result.Content, 66));
 
-                if ((crnStatus.getStatus() == 0 || crnStatus.getStatus() == 20) && crnStatus.getTaskReceive() == 0) {
-                    OperateResultExOne<byte[]> taskResult = siemensNet.Read("DB100.0", (short) 18);
-                    if (taskResult.IsSuccess) {
-                        short taskNo = siemensNet.getByteTransform().TransInt16(taskResult.Content, 0);
-                        short taskMode = siemensNet.getByteTransform().TransInt16(taskResult.Content, 2);
-                        short sourcePosX = siemensNet.getByteTransform().TransInt16(taskResult.Content, 4);
-                        short sourcePosY = siemensNet.getByteTransform().TransInt16(taskResult.Content, 6);
-                        short sourcePosZ = siemensNet.getByteTransform().TransInt16(taskResult.Content, 8);
-                        short destinationPosX = siemensNet.getByteTransform().TransInt16(taskResult.Content, 10);
-                        short destinationPosY = siemensNet.getByteTransform().TransInt16(taskResult.Content, 12);
-                        short destinationPosZ = siemensNet.getByteTransform().TransInt16(taskResult.Content, 14);
-                        short confirm = siemensNet.getByteTransform().TransInt16(taskResult.Content, 16);
+                OperateResultExOne<byte[]> taskResult = siemensNet.Read("DB100.0", (short) 18);
+                if (taskResult.IsSuccess) {
+                    short taskNo = siemensNet.getByteTransform().TransInt16(taskResult.Content, 0);
+                    short taskMode = siemensNet.getByteTransform().TransInt16(taskResult.Content, 2);
+                    short sourcePosX = siemensNet.getByteTransform().TransInt16(taskResult.Content, 4);
+                    short sourcePosY = siemensNet.getByteTransform().TransInt16(taskResult.Content, 6);
+                    short sourcePosZ = siemensNet.getByteTransform().TransInt16(taskResult.Content, 8);
+                    short destinationPosX = siemensNet.getByteTransform().TransInt16(taskResult.Content, 10);
+                    short destinationPosY = siemensNet.getByteTransform().TransInt16(taskResult.Content, 12);
+                    short destinationPosZ = siemensNet.getByteTransform().TransInt16(taskResult.Content, 14);
+                    short confirm = siemensNet.getByteTransform().TransInt16(taskResult.Content, 16);
 
-                        if(taskNo != 0 || taskMode != 0 || sourcePosX != 0 || sourcePosY != 0 || sourcePosZ != 0 || destinationPosX != 0 || destinationPosY != 0 || destinationPosZ != 0 || confirm != 0) {
+                    int hasData = 0;
+                    if(taskNo != 0 || taskMode != 0 || sourcePosX != 0 || sourcePosY != 0 || sourcePosZ != 0 || destinationPosX != 0 || destinationPosY != 0 || destinationPosZ != 0 || confirm != 0) {
+                        hasData = 1;
+                        if (crnStatus.getTaskReceive() == 1) {
+                            HashMap<String, Object> map = new HashMap<>();
+                            map.put("taskNo", taskNo);
+                            map.put("taskMode", taskMode);
+                            map.put("sourcePosX", sourcePosX);
+                            map.put("sourcePosY", sourcePosY);
+                            map.put("sourcePosZ", sourcePosZ);
+                            map.put("destinationPosX", destinationPosX);
+                            map.put("destinationPosY", destinationPosY);
+                            map.put("destinationPosZ", destinationPosZ);
+                            map.put("confirm", confirm);
+
                             short[] array = new short[9];
                             array[0] = (short) 0;
                             array[1] = (short) 0;
@@ -122,30 +138,57 @@
                             array[7] = (short) 0;
                             array[8] = (short) 0;
 
-                            RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
-                            Object lock = redisUtil.get(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo());
-                            if (lock == null) {
-                                OperateResult taskClearResult = siemensNet.Write("DB100.0", array);
-                                redisUtil.set(RedisKeyType.CLEAR_DUAL_CRN_TASK_LIMIT.key + deviceConfig.getDeviceNo(), "lock", 3);
+                            boolean clear = false;
+                            if (taskMode == DualCrnTaskModeType.CONFIRM.id) {
+//                                if (crnStatus.getStatus() == 0) {
+//                                    clear = true;
+//                                }
+                                clear = true;
+                            } else {
+                                clear = true;
+                            }
+
+                            if (clear) {
+                                RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
+                                Object lock = redisUtil.get(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo());
+                                if (lock == null) {
+                                    OperateResult taskClearResult = siemensNet.Write("DB100.0", array);
+                                    redisUtil.set(RedisKeyType.CLEAR_DUAL_CRN_TASK_LIMIT.key + deviceConfig.getDeviceNo(), "lock", 3);
+                                    News.info("鍙屽伐浣嶅爢鍨涙満:{}鍙穥}宸ヤ綅鍛戒护鍖哄煙娓呯┖锛屽師濮嬫暟鎹�:{}", deviceConfig.getDeviceNo(), 1, JSON.toJSONString(map));
+                                }
                             }
                         }
                     }
+                    crnStatus.setTaskSend(hasData);
                 }
 
-                if ((crnStatus.getStatusTwo() == 0 || crnStatus.getStatusTwo() == 20) && crnStatus.getTaskReceiveTwo() == 0) {
-                    OperateResultExOne<byte[]> taskResult = siemensNet.Read("DB100.18", (short) 18);
-                    if (taskResult.IsSuccess) {
-                        short taskNo = siemensNet.getByteTransform().TransInt16(taskResult.Content, 0);
-                        short taskMode = siemensNet.getByteTransform().TransInt16(taskResult.Content, 2);
-                        short sourcePosX = siemensNet.getByteTransform().TransInt16(taskResult.Content, 4);
-                        short sourcePosY = siemensNet.getByteTransform().TransInt16(taskResult.Content, 6);
-                        short sourcePosZ = siemensNet.getByteTransform().TransInt16(taskResult.Content, 8);
-                        short destinationPosX = siemensNet.getByteTransform().TransInt16(taskResult.Content, 10);
-                        short destinationPosY = siemensNet.getByteTransform().TransInt16(taskResult.Content, 12);
-                        short destinationPosZ = siemensNet.getByteTransform().TransInt16(taskResult.Content, 14);
-                        short confirm = siemensNet.getByteTransform().TransInt16(taskResult.Content, 16);
+                OperateResultExOne<byte[]> taskResult2 = siemensNet.Read("DB100.18", (short) 18);
+                if (taskResult2.IsSuccess) {
+                    short taskNo = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 0);
+                    short taskMode = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 2);
+                    short sourcePosX = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 4);
+                    short sourcePosY = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 6);
+                    short sourcePosZ = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 8);
+                    short destinationPosX = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 10);
+                    short destinationPosY = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 12);
+                    short destinationPosZ = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 14);
+                    short confirm = siemensNet.getByteTransform().TransInt16(taskResult2.Content, 16);
 
-                        if(taskNo != 0 || taskMode != 0 || sourcePosX != 0 || sourcePosY != 0 || sourcePosZ != 0 || destinationPosX != 0 || destinationPosY != 0 || destinationPosZ != 0 || confirm != 0) {
+                    int hasData = 0;
+                    if(taskNo != 0 || taskMode != 0 || sourcePosX != 0 || sourcePosY != 0 || sourcePosZ != 0 || destinationPosX != 0 || destinationPosY != 0 || destinationPosZ != 0 || confirm != 0) {
+                        hasData = 1;
+                        if (crnStatus.getTaskReceiveTwo() == 1) {
+                            HashMap<String, Object> map = new HashMap<>();
+                            map.put("taskNo", taskNo);
+                            map.put("taskMode", taskMode);
+                            map.put("sourcePosX", sourcePosX);
+                            map.put("sourcePosY", sourcePosY);
+                            map.put("sourcePosZ", sourcePosZ);
+                            map.put("destinationPosX", destinationPosX);
+                            map.put("destinationPosY", destinationPosY);
+                            map.put("destinationPosZ", destinationPosZ);
+                            map.put("confirm", confirm);
+
                             short[] array = new short[9];
                             array[0] = (short) 0;
                             array[1] = (short) 0;
@@ -157,13 +200,28 @@
                             array[7] = (short) 0;
                             array[8] = (short) 0;
 
-                            RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
-                            Object lock = redisUtil.get(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo());
-                            if (lock == null) {
-                                OperateResult taskClearResult = siemensNet.Write("DB100.18", array);
+                            boolean clear = false;
+                            if (taskMode == DualCrnTaskModeType.CONFIRM.id) {
+//                                if (crnStatus.getStatusTwo() == 0) {
+//                                    clear = true;
+//                                }
+                                clear = true;
+                            } else {
+                                clear = true;
+                            }
+
+                            if (clear) {
+                                RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
+                                Object lock = redisUtil.get(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo());
+                                if (lock == null) {
+                                    OperateResult taskClearResult = siemensNet.Write("DB100.18", array);
+                                    redisUtil.set(RedisKeyType.CLEAR_DUAL_CRN_TASK_LIMIT.key + deviceConfig.getDeviceNo(), "lock", 3);
+                                    News.info("鍙屽伐浣嶅爢鍨涙満:{}鍙穥}宸ヤ綅鍛戒护鍖哄煙娓呯┖锛屽師濮嬫暟鎹�:{}", deviceConfig.getDeviceNo(), 2, JSON.toJSONString(map));
+                                }
                             }
                         }
                     }
+                    crnStatus.setTaskSendTwo(hasData);
                 }
 
                 return crnStatus;
@@ -182,7 +240,7 @@
     @Override
     public CommandResponse sendCommand(DualCrnCommand command) {
         RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
-        redisUtil.set(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 10);
+        redisUtil.set(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 3);
         CommandResponse response = new CommandResponse(false);
          try {
              if (null == command) {
@@ -261,7 +319,7 @@
                  OutputQueue.CRN.offer(MessageFormat.format("銆恵0}銆慬id:{1}] >>>>> 鍛戒护涓嬪彂锛� {2}", DateUtils.convert(new Date()), command.getCrnNo(), JSON.toJSON(command)));
                  response.setResult(true);
                  response.setMessage("鍛戒护涓嬪彂鎴愬姛");
-                 redisUtil.set(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 5);
+                 redisUtil.set(RedisKeyType.DUAL_CRN_SEND_COMMAND_LOCK.key + deviceConfig.getDeviceNo(), "lock", 3);
              } else {
                  News.error("鍙屽伐浣嶅爢鍨涙満鍐欏叆鍫嗗灈鏈簆lc鏁版嵁澶辫触 ===>> [id:{}]", command.getCrnNo());
                  OutputQueue.CRN.offer(MessageFormat.format("銆恵0}銆戝啓鍏ュ爢鍨涙満plc鏁版嵁澶辫触 ===>> [id:{1}]", DateUtils.convert(new Date()), command.getCrnNo()));
diff --git a/src/main/java/com/zy/core/network/real/ZyStationV3RealConnect.java b/src/main/java/com/zy/core/network/real/ZyStationV3RealConnect.java
index 7781e5f..6da637a 100644
--- a/src/main/java/com/zy/core/network/real/ZyStationV3RealConnect.java
+++ b/src/main/java/com/zy/core/network/real/ZyStationV3RealConnect.java
@@ -146,7 +146,8 @@
                 if (barcodeEntity == null) {
                     continue;
                 }
-                String barcode = siemensNet.getByteTransform().TransString(result2.Content, i * 16 + 2, 14, "UTF-8");
+                byte length = 10;//result2.Content[1];
+                String barcode = siemensNet.getByteTransform().TransString(result2.Content, i * 16 + 2, length, "UTF-8");
                 barcode = barcode.trim();
                 barcodeEntity.setBarcode(barcode);
             }
diff --git a/src/main/java/com/zy/core/plugin/FakeProcess.java b/src/main/java/com/zy/core/plugin/FakeProcess.java
index ffccad7..8bdb08f 100644
--- a/src/main/java/com/zy/core/plugin/FakeProcess.java
+++ b/src/main/java/com/zy/core/plugin/FakeProcess.java
@@ -125,6 +125,12 @@
         stationOperateProcessUtils.crnStationOutExecute();
         // 妫�娴嬭緭閫佺珯鐐瑰嚭搴撲换鍔℃墽琛屽畬鎴�
         stationOperateProcessUtils.stationOutExecuteFinish();
+        // 妫�娴嬩换鍔¤浆瀹屾垚
+        stationOperateProcessUtils.checkTaskToComplete();
+        // 妫�娴嬪嚭搴撴帓搴�
+        stationOperateProcessUtils.checkStationOutOrder();
+        // 鐩戞帶缁曞湀绔欑偣
+        stationOperateProcessUtils.watchCircleStation();
 
         // 鎵ц鍙屽伐浣嶅爢鍨涙満浠诲姟
         dualCrnOperateProcessUtils.dualCrnIoExecute();
@@ -349,64 +355,68 @@
 
     // 鐢熸垚浠跨湡妯℃嫙鍑哄簱浠诲姟
     private synchronized void generateFakeOutTask() {
-        if (!enableFake.equals("Y")) {
-            return;
-        }
-
-        if (fakeRealTaskRequestWms.equals("Y")) {
-            return;
-        }
-
-        if (!fakeGenerateOutTask.equals("Y")) {
-            return;
-        }
-
-        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
-        for (BasDevp basDevp : basDevps) {
-            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-            if (stationThread == null) {
-                continue;
+        try {
+            if (!enableFake.equals("Y")) {
+                return;
             }
 
-            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
+            if (fakeRealTaskRequestWms.equals("Y")) {
+                return;
+            }
 
-            List<StationObjModel> list = basDevp.getOutStationList$();
-            for (StationObjModel entity : list) {
-                Integer stationId = entity.getStationId();
-                if (!stationMap.containsKey(stationId)) {
+            if (!fakeGenerateOutTask.equals("Y")) {
+                return;
+            }
+
+            List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
+            for (BasDevp basDevp : basDevps) {
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+                if (stationThread == null) {
                     continue;
                 }
 
-                StationProtocol stationProtocol = stationMap.get(stationId);
-                if (stationProtocol == null) {
-                    continue;
-                }
+                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
 
-                Object object = redisUtil.get(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId);
-                if (object != null) {
-                    return;
-                }
-
-                // 婊¤冻鑷姩銆佹棤鐗┿�佸伐浣滃彿0锛岀敓鎴愬嚭搴撴暟鎹�
-                if (stationProtocol.isAutoing()
-                        && !stationProtocol.isLoading()
-                        && stationProtocol.getTaskNo() == 0) {
-                    List<LocMast> locMastList = locMastService
-                            .selectList(new EntityWrapper<LocMast>().eq("loc_sts", String.valueOf(LocStsType.F)));
-                    if (locMastList.isEmpty()) {
+                List<StationObjModel> list = basDevp.getOutStationList$();
+                for (StationObjModel entity : list) {
+                    Integer stationId = entity.getStationId();
+                    if (!stationMap.containsKey(stationId)) {
                         continue;
                     }
 
-                    LocMast locMast = locMastList.get(0);
+                    StationProtocol stationProtocol = stationMap.get(stationId);
+                    if (stationProtocol == null) {
+                        continue;
+                    }
 
-                    CreateOutTaskParam taskParam = new CreateOutTaskParam();
-                    taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.OUT.id)));
-                    taskParam.setStaNo(stationId);
-                    taskParam.setLocNo(locMast.getLocNo());
-                    boolean result = commonService.createOutTask(taskParam);
-                    redisUtil.set(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId, "lock", 10);
+                    Object object = redisUtil.get(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId);
+                    if (object != null) {
+                        return;
+                    }
+
+                    // 婊¤冻鑷姩銆佹棤鐗┿�佸伐浣滃彿0锛岀敓鎴愬嚭搴撴暟鎹�
+                    if (stationProtocol.isAutoing()
+                            && !stationProtocol.isLoading()
+                            && stationProtocol.getTaskNo() == 0) {
+                        List<LocMast> locMastList = locMastService
+                                .selectList(new EntityWrapper<LocMast>().eq("loc_sts", String.valueOf(LocStsType.F)));
+                        if (locMastList.isEmpty()) {
+                            continue;
+                        }
+
+                        LocMast locMast = locMastList.get(0);
+
+                        CreateOutTaskParam taskParam = new CreateOutTaskParam();
+                        taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.OUT.id)));
+                        taskParam.setStaNo(stationId);
+                        taskParam.setLocNo(locMast.getLocNo());
+                        boolean result = commonService.createOutTask(taskParam);
+                        redisUtil.set(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId, "lock", 10);
+                    }
                 }
             }
+        } catch (Exception e) {
+            e.printStackTrace();
         }
     }
 
@@ -477,7 +487,7 @@
                         if (lock != null) {
                             continue;
                         }
-                        redisUtil.set(RedisKeyType.GENERATE_IN_TASK_LIMIT.key + stationId, "lock", 10);
+                        redisUtil.set(RedisKeyType.GENERATE_IN_TASK_LIMIT.key + stationId, "lock", 3);
 
                         String barcode = stationProtocol.getBarcode();
                         Integer stationIdVal = stationProtocol.getStationId();
@@ -539,7 +549,7 @@
                                         stationProtocol.getPalletHeight());
                                 redisUtil.set(RedisKeyType.GENERATE_IN_TASK_LIMIT.key + stationId, "lock", 2);
 
-                                stationProtocol.setSystemWarning("璇锋眰鍏ュ簱澶辫触锛學MS鏃犺繑鍥�");
+//                                stationProtocol.setSystemWarning("璇锋眰鍏ュ簱澶辫触锛學MS鏃犺繑鍥�");
                             }
                         }
                     }
@@ -596,7 +606,7 @@
                     continue;
                 }
 
-                if (System.currentTimeMillis() - stayTime > 1000 * 15) {
+                if (System.currentTimeMillis() - stayTime > 1000 * 60) {
                     StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                             stationObjModel.getDeviceNo());
                     if (stationThread == null) {
@@ -801,7 +811,7 @@
                 wrkMast.setSystemMsg("");
                 wrkMast.setIoTime(new Date());
                 if (wrkMastService.updateById(wrkMast)) {
-                    CrnCommand resetCommand = crnThread.getResetCommand(crnProtocol.getCrnNo());
+                    CrnCommand resetCommand = crnThread.getResetCommand(crnProtocol.getTaskNo(), crnProtocol.getCrnNo());
                     MessageQueue.offer(SlaveType.Crn, crnProtocol.getCrnNo(), new Task(2, resetCommand));
                     News.info("鍫嗗灈鏈轰换鍔$姸鎬佹洿鏂版垚鍔燂紝鍫嗗灈鏈哄彿={}锛屽伐浣滃彿={}", basCrnp.getCrnNo(), crnProtocol.getTaskNo());
                 }
diff --git a/src/main/java/com/zy/core/plugin/NormalProcess.java b/src/main/java/com/zy/core/plugin/NormalProcess.java
index ce5404f..7321111 100644
--- a/src/main/java/com/zy/core/plugin/NormalProcess.java
+++ b/src/main/java/com/zy/core/plugin/NormalProcess.java
@@ -190,7 +190,7 @@
                                 wmsOperateUtils.applyInTaskAsync(barcode, stationIdVal,
                                         stationProtocol.getPalletHeight());
                                 redisUtil.set(RedisKeyType.GENERATE_IN_TASK_LIMIT.key + stationId, "lock", 2);
-                                stationProtocol.setSystemWarning("璇锋眰鍏ュ簱澶辫触锛學MS鏃犺繑鍥�");
+//                                stationProtocol.setSystemWarning("璇锋眰鍏ュ簱澶辫触锛學MS鏃犺繑鍥�");
                             }
                         }
                     }
diff --git a/src/main/java/com/zy/core/plugin/XiaosongProcess.java b/src/main/java/com/zy/core/plugin/XiaosongProcess.java
index 2f1a2ba..b047396 100644
--- a/src/main/java/com/zy/core/plugin/XiaosongProcess.java
+++ b/src/main/java/com/zy/core/plugin/XiaosongProcess.java
@@ -77,6 +77,8 @@
         stationOperateProcessUtils.dualCrnStationOutExecute();
         //妫�娴嬭緭閫佺珯鐐瑰嚭搴撲换鍔℃墽琛屽畬鎴�
         stationOperateProcessUtils.stationOutExecuteFinish();
+        // 妫�娴嬩换鍔¤浆瀹屾垚
+        stationOperateProcessUtils.checkTaskToComplete();
         //妫�娴嬭緭閫佺珯鐐规槸鍚﹁繍琛屽牭濉�
         stationOperateProcessUtils.checkStationRunBlock();
 
@@ -140,6 +142,10 @@
                         }
 
                         if (stationProtocol.getError() > 0) {
+                            Object lock = redisUtil.get(RedisKeyType.GENERATE_STATION_BACK_LIMIT.key + stationProtocol.getTaskNo());
+                            if (lock != null) {
+                                continue;
+                            }
                             StationObjModel backStation = entity.getBackStation();
                             StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.STATION_BACK.id), stationId, backStation.getStationId(), 0);
                             if (command == null) {
@@ -148,11 +154,14 @@
                             }
                             MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
                             News.taskInfo(stationProtocol.getTaskNo(), "{}鎵爜寮傚父锛屽凡閫�鍥炶嚦{}", backStation.getStationId());
+                            redisUtil.set(RedisKeyType.GENERATE_STATION_BACK_LIMIT.key + stationProtocol.getTaskNo(), "lock", 10);
+                                continue;
                         }
 
                         // 妫�娴嬩换鍔℃槸鍚︾敓鎴�
                         List<WrkMast> wrkMasts = wrkMastService
-                                .selectList(new EntityWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode()));
+                                .selectList(new EntityWrapper<WrkMast>()
+                                        .eq("barcode", stationProtocol.getBarcode()));
                         if (!wrkMasts.isEmpty()) {
                             continue;
                         }
@@ -210,7 +219,7 @@
                                 wmsOperateUtils.applyInTaskAsync(barcode, stationIdVal,
                                         stationProtocol.getPalletHeight());
                                 redisUtil.set(RedisKeyType.GENERATE_IN_TASK_LIMIT.key + stationId, "lock", 2);
-                                stationProtocol.setSystemWarning("璇锋眰鍏ュ簱澶辫触锛學MS鏃犺繑鍥�");
+//                                stationProtocol.setSystemWarning("璇锋眰鍏ュ簱澶辫触锛學MS鏃犺繑鍥�");
                             }
                         }
                     }
diff --git a/src/main/java/com/zy/core/task/InitLocMapScheduler.java b/src/main/java/com/zy/core/task/InitLocMapScheduler.java
index b8b2314..e1d4da1 100644
--- a/src/main/java/com/zy/core/task/InitLocMapScheduler.java
+++ b/src/main/java/com/zy/core/task/InitLocMapScheduler.java
@@ -53,9 +53,14 @@
                 mapNode.put("id", i + "-" + j);
 
                 String nodeType = map.getString("type");
+                mapNode.put("type", nodeType);
                 if("shelf".equals(nodeType)) {
                     mapNode.put("value", MapNodeType.NORMAL_PATH.id);
-                }else {
+                } else if ("devp".equals(nodeType)) {
+                    mapNode.put("value", MapNodeType.DISABLE.id);
+                } else if ("crn".equals(nodeType) || "dualCrn".equals(nodeType) || "rgv".equals(nodeType)) {
+                    mapNode.put("value", MapNodeType.MAIN_PATH.id);
+                } else {
                     mapNode.put("value", MapNodeType.DISABLE.id);
                 }
 
diff --git a/src/main/java/com/zy/core/thread/CrnThread.java b/src/main/java/com/zy/core/thread/CrnThread.java
index 46cbd00..341a7f9 100644
--- a/src/main/java/com/zy/core/thread/CrnThread.java
+++ b/src/main/java/com/zy/core/thread/CrnThread.java
@@ -13,7 +13,7 @@
 
     CrnCommand getMoveCommand(String targetLocNo, Integer taskNo, Integer crnNo);//绉诲姩
 
-    CrnCommand getResetCommand(Integer crnNo);//澶嶄綅
+    CrnCommand getResetCommand(Integer taskNo, Integer crnNo);//澶嶄綅
 
     CommandResponse sendCommand(CrnCommand command);//涓嬪彂鍛戒护
 
diff --git a/src/main/java/com/zy/core/thread/DualCrnThread.java b/src/main/java/com/zy/core/thread/DualCrnThread.java
index b670bc1..fa4e454 100644
--- a/src/main/java/com/zy/core/thread/DualCrnThread.java
+++ b/src/main/java/com/zy/core/thread/DualCrnThread.java
@@ -17,7 +17,7 @@
 
     DualCrnCommand getMoveCommand(String targetLocNo, Integer taskNo, Integer crnNo);//绉诲姩
 
-    DualCrnCommand getResetCommand(Integer crnNo, Integer station);//澶嶄綅
+    DualCrnCommand getResetCommand(Integer taskNo, Integer crnNo, Integer station);//澶嶄綅
 
     CommandResponse sendCommand(DualCrnCommand command);//涓嬪彂鍛戒护
 
diff --git a/src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java b/src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java
index b680358..aa80bab 100644
--- a/src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java
+++ b/src/main/java/com/zy/core/thread/impl/ZySiemensCrnThread.java
@@ -283,10 +283,10 @@
     }
 
     @Override
-    public CrnCommand getResetCommand(Integer crnNo) {
+    public CrnCommand getResetCommand(Integer taskNo, Integer crnNo) {
         CrnCommand crnCommand = new CrnCommand();
         crnCommand.setCrnNo(crnNo); // 鍫嗗灈鏈虹紪鍙�
-        crnCommand.setTaskNo(0); // 宸ヤ綔鍙�
+        crnCommand.setTaskNo(taskNo); // 宸ヤ綔鍙�
         crnCommand.setAckFinish(1);  // 浠诲姟瀹屾垚纭浣�
         crnCommand.setTaskMode(CrnTaskModeType.NONE.id); // 浠诲姟妯″紡
         crnCommand.setSourcePosX(0);     // 婧愬簱浣嶆帓
diff --git a/src/main/java/com/zy/core/thread/impl/ZySiemensCrnV2Thread.java b/src/main/java/com/zy/core/thread/impl/ZySiemensCrnV2Thread.java
index d382648..15b0450 100644
--- a/src/main/java/com/zy/core/thread/impl/ZySiemensCrnV2Thread.java
+++ b/src/main/java/com/zy/core/thread/impl/ZySiemensCrnV2Thread.java
@@ -283,10 +283,10 @@
     }
 
     @Override
-    public CrnCommand getResetCommand(Integer crnNo) {
+    public CrnCommand getResetCommand(Integer taskNo, Integer crnNo) {
         CrnCommand crnCommand = new CrnCommand();
         crnCommand.setCrnNo(crnNo); // 鍫嗗灈鏈虹紪鍙�
-        crnCommand.setTaskNo(0); // 宸ヤ綔鍙�
+        crnCommand.setTaskNo(taskNo); // 宸ヤ綔鍙�
         crnCommand.setAckFinish(1);  // 浠诲姟瀹屾垚纭浣�
         crnCommand.setTaskMode(CrnTaskModeType.RESET.id); // 浠诲姟妯″紡
         crnCommand.setSourcePosX(0);     // 婧愬簱浣嶆帓
diff --git a/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java b/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
index c33d25b..2f643ae 100644
--- a/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
+++ b/src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
@@ -9,6 +9,7 @@
 import com.zy.asrs.entity.*;
 import com.zy.asrs.service.BasDualCrnpOptService;
 import com.zy.asrs.service.BasDualCrnpService;
+import com.zy.asrs.service.WrkMastService;
 import com.zy.asrs.utils.Utils;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
@@ -48,7 +49,7 @@
     private RedisUtil redisUtil;
     private ZyDualCrnConnectDriver zyDualCrnConnectDriver;
     private DualCrnProtocol crnProtocol;
-    private int deviceLogCollectTime = 200;
+    private volatile int deviceLogCollectTime = 200;
 
     public ZySiemensDualCrnThread(DeviceConfig deviceConfig, RedisUtil redisUtil) {
         this.deviceConfig = deviceConfig;
@@ -66,7 +67,7 @@
                 try {
                     deviceLogCollectTime = Utils.getDeviceLogCollectTime();
                     readStatus();
-                    Thread.sleep(50);
+                    Thread.sleep(100);
                 } catch (Exception e) {
                     log.error("DualCrnThread Fail", e);
                 }
@@ -87,10 +88,14 @@
                         List<SendDualCrnCommandParam> commandList = (List<SendDualCrnCommandParam>) task.getData();
                         for (SendDualCrnCommandParam sendDualCrnCommandParam : commandList) {
                             DualCrnCommand dualCrnCommand = sendDualCrnCommandParam.getCommands().get(0);
-                            redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_IDX.key + dualCrnCommand.getTaskNo(), sendDualCrnCommandParam.getCommandIdx(), 60 * 60 * 24);
+                            redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_IDX.key + dualCrnCommand.getTaskNo(), 0, 60 * 60 * 24);
+                            redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_.key + dualCrnCommand.getTaskNo(), JSON.toJSONString(sendDualCrnCommandParam, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24);
+                            if (sendDualCrnCommandParam.getStation() == 1) {
+                                redisUtil.set(RedisKeyType.DUAL_CRN_STATION1_FLAG.key + crnProtocol.getCrnNo(), dualCrnCommand.getTaskNo(), 60 * 60 * 24);
+                            } else {
+                                redisUtil.set(RedisKeyType.DUAL_CRN_STATION2_FLAG.key + crnProtocol.getCrnNo(), dualCrnCommand.getTaskNo(), 60 * 60 * 24);
+                            }
                         }
-
-                        redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_.key + crnProtocol.getCrnNo(), JSON.toJSONString(commandList, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24);
                     } else if (step == 3) {
                         sendCommand((DualCrnCommand) task.getData());
                     }
@@ -106,11 +111,13 @@
         Thread commandThread = new Thread(() -> {
             while (true) {
                 try {
-                    if(crnProtocol.getMode() != DualCrnModeType.AUTO.id) {
+                    Thread.sleep(100);
+
+                    if (crnProtocol.getMode() != DualCrnModeType.AUTO.id) {
                         continue;
                     }
 
-                    if(crnProtocol.getAlarm() != 0) {
+                    if (crnProtocol.getAlarm() != 0) {
                         continue;
                     }
 
@@ -120,16 +127,11 @@
                         continue;
                     }
 
-                    Object commandListObj = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_.key + crnProtocol.getCrnNo());
-                    if (commandListObj == null) {
-                        continue;
-                    }
-                    List<SendDualCrnCommandParam> commandList = JSON.parseArray(commandListObj.toString(), SendDualCrnCommandParam.class);
+                    List<SendDualCrnCommandParam> commandList = getDualCrnCommandList();
                     for (SendDualCrnCommandParam commandParam : commandList) {
                         processStation(commandParam);
                     }
 
-                    Thread.sleep(100);
                 } catch (Exception e) {
                     log.error("DualCrnCommand Fail", e);
                 }
@@ -143,7 +145,7 @@
         List<DualCrnCommand> commandList = commandParam.getCommands();
         DualCrnCommand firstCommand = commandList.get(0);
         Object idxObj = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_IDX.key + firstCommand.getTaskNo());
-        if(idxObj == null) {
+        if (idxObj == null) {
             return;
         }
         Integer idx = (Integer) idxObj;
@@ -153,29 +155,61 @@
         DualCrnCommand dualCommand = commandList.get(idx);
 
         boolean send = false;
+        //TODO 鍒ゆ柇绔欑偣1锛�2浠诲姟绫诲瀷鏄惁涓�鑷�
         if (station == 1) {
-            if (crnProtocol.getStatus().equals(DualCrnStatusType.FETCH_COMPLETE.id)
-                    || crnProtocol.getStatus().equals(DualCrnStatusType.IDLE.id)
-            ) {
-                send = true;
+            log.info("宸ヤ綅2浠诲姟==========>{}, 浠诲姟妯″紡---->{}", crnProtocol.getTaskNoTwo(), crnProtocol.getMode());
+            log.info("宸ヤ綅1浠诲姟==========>{}, 浠诲姟妯″紡---->{}", crnProtocol.getTaskNo(), crnProtocol.getMode());
+            Integer taskNo = crnProtocol.getTaskNoTwo();
+            if (crnProtocol.getTaskSend() == 0) {
+                //涓や釜宸ヤ綅鍙厑璁告斁鐩稿悓绫诲瀷浠诲姟
+//                if (taskNo != null && taskNo > 0) {
+//                    WrkMastService mastService = SpringUtils.getBean(WrkMastService.class);
+//                    WrkMast wrkMast = mastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", taskNo));
+//                    WrkMast wrkMast1 = mastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", dualCommand.getTaskNo()));
+//                    if (!Objects.isNull(wrkMast) && !Objects.isNull(wrkMast1)) {
+//                        if (wrkMast1.getIoType() != wrkMast.getIoType()) {
+////                            return;
+//                        }
+//                    }
+//                }
+                // && sendComm(taskNo, dualCommand.getTaskNo())
+                if (dualCommand.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
+                    if (crnProtocol.getStatus().equals(DualCrnStatusType.IDLE.id)) {
+                        send = true;
+                    }
+                } else if (dualCommand.getTaskMode().intValue() == DualCrnTaskModeType.PUT.id) {
+                    if (crnProtocol.getStatus().equals(DualCrnStatusType.FETCH_COMPLETE.id)) {
+                        send = true;
+                    }
+                }
             }
-        }else {
-            if (crnProtocol.getStatusTwo().equals(DualCrnStatusType.FETCH_COMPLETE.id)
-                    || crnProtocol.getStatusTwo().equals(DualCrnStatusType.IDLE.id)
-            ) {
-                send = true;
+        } else {
+            log.info("宸ヤ綅1浠诲姟==========>{}, 浠诲姟妯″紡---->{}", crnProtocol.getTaskNo(), crnProtocol.getMode());
+            log.info("宸ヤ綅2浠诲姟==========>{}, 浠诲姟妯″紡---->{}", crnProtocol.getTaskNoTwo(), crnProtocol.getMode());
+            Integer taskNo = crnProtocol.getTaskNo();
+            // && sendComm(taskNo, dualCommand.getTaskNo())
+            if (crnProtocol.getTaskSendTwo() == 0) {
+                if (dualCommand.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
+                    if (crnProtocol.getStatusTwo().equals(DualCrnStatusType.IDLE.id)) {
+                        send = true;
+                    }
+                } else if (dualCommand.getTaskMode().intValue() == DualCrnTaskModeType.PUT.id) {
+                    if (crnProtocol.getStatusTwo().equals(DualCrnStatusType.FETCH_COMPLETE.id)) {
+                        send = true;
+                    }
+                }
             }
         }
 
         if (idx == 0) {
-            if(send) {
+            if (send) {
                 idx++;
                 redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_IDX.key + firstCommand.getTaskNo(), idx, 60 * 60 * 24);
                 sendCommand(dualCommand);
-                redisUtil.set(RedisKeyType.DUAL_CRN_PICK_WAIT_NEXT_TASK.key + crnProtocol.getCrnNo(), "lock", 5);
+                redisUtil.set(RedisKeyType.DUAL_CRN_PICK_WAIT_NEXT_TASK.key + crnProtocol.getCrnNo(), "lock", 3);
             }
             return;
-        }else {
+        } else {
             if (dualCommand.getTaskMode() == DualCrnTaskModeType.PUT.id.shortValue()) {
                 //绛夊緟涓嬩竴涓换鍔�
                 Object wait = redisUtil.get(RedisKeyType.DUAL_CRN_PICK_WAIT_NEXT_TASK.key + crnProtocol.getCrnNo());
@@ -213,13 +247,29 @@
                 }
             }
 
-            if(send) {
+            if (send) {
                 idx++;
                 redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_IDX.key + firstCommand.getTaskNo(), idx, 60 * 60 * 24);
                 sendCommand(dualCommand);
             }
             return;
         }
+    }
+
+    public boolean sendComm(Integer taskNo, Integer current) {
+        //涓や釜宸ヤ綅鍙厑璁告斁鐩稿悓绫诲瀷浠诲姟
+        if (taskNo != null && taskNo > 0) {
+            WrkMastService mastService = SpringUtils.getBean(WrkMastService.class);
+            WrkMast wrkMast = mastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", taskNo));
+            WrkMast wrkMast1 = mastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", current));
+            if (!Objects.isNull(wrkMast) && !Objects.isNull(wrkMast1)) {
+                if (wrkMast1.getIoType() != wrkMast.getIoType()) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
     }
 
     /**
@@ -234,6 +284,7 @@
 
         //宸ヤ綅1
         crnProtocol.setTaskNo(0);
+        crnProtocol.setDeviceTaskNo(0);
         crnProtocol.setStatus(-1);
         crnProtocol.setBay(0);
         crnProtocol.setLevel(0);
@@ -244,6 +295,7 @@
 
         //宸ヤ綅2
         crnProtocol.setTaskNoTwo(0);
+        crnProtocol.setDeviceTaskNoTwo(0);
         crnProtocol.setStatusTwo(-1);
         crnProtocol.setBayTwo(0);
         crnProtocol.setLevelTwo(0);
@@ -264,12 +316,12 @@
         BasDualCrnpService basDualCrnpService = null;
         try {
             basDualCrnpService = SpringUtils.getBean(BasDualCrnpService.class);
-        }catch (Exception e){
+        } catch (Exception e) {
 
         }
         if (basDualCrnpService != null) {
             BasDualCrnp basDualCrnp = basDualCrnpService.selectOne(new EntityWrapper<BasDualCrnp>().eq("crn_no", deviceConfig.getDeviceNo()));
-            if(basDualCrnp == null) {
+            if (basDualCrnp == null) {
                 basDualCrnp = new BasDualCrnp();
                 basDualCrnp.setCrnNo(deviceConfig.getDeviceNo());
                 basDualCrnp.setStatus(1);
@@ -294,7 +346,7 @@
     /**
      * 璇诲彇鐘舵��
      */
-    private void readStatus(){
+    private void readStatus() {
         ZyDualCrnStatusEntity crnStatus = zyDualCrnConnectDriver.getStatus();
         if (crnStatus == null) {
             OutputQueue.DUAL_CRN.offer(MessageFormat.format("銆恵0}銆戣鍙栧弻宸ヤ綅鍫嗗灈鏈簆lc鐘舵�佷俊鎭け璐� ===>> [id:{1}] [ip:{2}] [port:{3}]", DateUtils.convert(new Date()), deviceConfig.getDeviceNo(), deviceConfig.getIp(), deviceConfig.getPort()));
@@ -303,18 +355,20 @@
         crnProtocol.setMode(crnStatus.getMode());
 
         //宸ヤ綅1
-        crnProtocol.setTaskNo(crnStatus.getTaskNo());
+        crnProtocol.setDeviceTaskNo(crnStatus.getTaskNo());
         crnProtocol.setStatus(crnStatus.getStatus());
         crnProtocol.setForkPos(crnStatus.getForkPos());
         crnProtocol.setLoaded(crnStatus.getLoaded());
         crnProtocol.setTaskReceive(crnStatus.getTaskReceive());
+        crnProtocol.setTaskSend(crnStatus.getTaskSend());
 
         //宸ヤ綅2
-        crnProtocol.setTaskNoTwo(crnStatus.getTaskNoTwo());
+        crnProtocol.setDeviceTaskNoTwo(crnStatus.getTaskNoTwo());
         crnProtocol.setStatusTwo(crnStatus.getStatusTwo());
         crnProtocol.setForkPosTwo(crnStatus.getForkPosTwo());
         crnProtocol.setLoadedTwo(crnStatus.getLoadedTwo());
         crnProtocol.setTaskReceiveTwo(crnStatus.getTaskReceiveTwo());
+        crnProtocol.setTaskSendTwo(crnStatus.getTaskSendTwo());
 
         crnProtocol.setBay(crnStatus.getBay());
         crnProtocol.setLevel(crnStatus.getLevel());
@@ -335,7 +389,7 @@
         crnProtocol.setGoodsType(crnStatus.getGoodsType());
         crnProtocol.setBarcode(crnStatus.getBarcode());
 
-        OutputQueue.DUAL_CRN.offer(MessageFormat.format("銆恵0}銆慬id:{1}] <<<<< 瀹炴椂鏁版嵁鏇存柊鎴愬姛",DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
+        OutputQueue.DUAL_CRN.offer(MessageFormat.format("銆恵0}銆慬id:{1}] <<<<< 瀹炴椂鏁版嵁鏇存柊鎴愬姛", DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
 
         if (crnProtocol.getAlarm() > 0) {
             crnProtocol.setLastCommandTime(-1L);
@@ -358,7 +412,7 @@
             //鏇存柊閲囬泦鏃堕棿
             crnProtocol.setDeviceDataLog(System.currentTimeMillis());
         }
-        
+
         try {
             BasDualCrnpErrLogService errLogService = SpringUtils.getBean(BasDualCrnpErrLogService.class);
             if (errLogService != null) {
@@ -394,7 +448,8 @@
                     }
                 }
             }
-        } catch (Exception ignore) {}
+        } catch (Exception ignore) {
+        }
     }
 
     @Override
@@ -477,10 +532,10 @@
     }
 
     @Override
-    public DualCrnCommand getResetCommand(Integer crnNo, Integer station) {
+    public DualCrnCommand getResetCommand(Integer taskNo, Integer crnNo, Integer station) {
         DualCrnCommand crnCommand = new DualCrnCommand();
         crnCommand.setCrnNo(crnNo); // 鍫嗗灈鏈虹紪鍙�
-        crnCommand.setTaskNo(0); // 宸ヤ綔鍙�
+        crnCommand.setTaskNo(taskNo); // 宸ヤ綔鍙�
         crnCommand.setTaskMode(DualCrnTaskModeType.CONFIRM.id); // 浠诲姟妯″紡:  纭
         crnCommand.setSourcePosX(0);     // 婧愬簱浣嶆帓
         crnCommand.setSourcePosY(0);     // 婧愬簱浣嶅垪
@@ -525,4 +580,77 @@
             }
         }
     }
+
+    public List<SendDualCrnCommandParam> getDualCrnCommandList() {
+        List<SendDualCrnCommandParam> commandList = new ArrayList<>();
+        SendDualCrnCommandParam command1 = getDualCrnCommandList(1);
+        SendDualCrnCommandParam command2 = getDualCrnCommandList(2);
+        if (command1 != null) {
+            commandList.add(command1);
+        }
+
+        if (command2 != null) {
+            commandList.add(command2);
+        }
+
+        return commandList;
+    }
+
+    public SendDualCrnCommandParam getDualCrnCommandList(int station) {
+        SendDualCrnCommandParam sendDualCrnCommandParam = null;
+        String key = null;
+        if (station == 1 && crnProtocol.getTaskNo() > 0) {
+            key = RedisKeyType.DUAL_CRN_COMMAND_.key + crnProtocol.getTaskNo();
+        } else if (station == 2 && crnProtocol.getTaskNoTwo() > 0) {
+            key = RedisKeyType.DUAL_CRN_COMMAND_.key + crnProtocol.getTaskNoTwo();
+        }
+
+        if (key == null) {
+            return null;
+        }
+        Object commandObj = redisUtil.get(key);
+
+        if (commandObj == null) {
+            return null;
+        }
+
+        sendDualCrnCommandParam = JSON.parseObject(commandObj.toString(), SendDualCrnCommandParam.class);
+        if (sendDualCrnCommandParam == null) {
+            return null;
+        }
+
+        if (station == 1) {
+            if (crnProtocol.getTaskNoTwo() > 0) {
+                Object object = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_.key + crnProtocol.getTaskNoTwo());
+                if (object != null) {
+                    SendDualCrnCommandParam jsonObject = JSON.parseObject(object.toString(), SendDualCrnCommandParam.class);
+                    ;
+                    if (!jsonObject.getCommands().get(0).getTaskMode().equals(sendDualCrnCommandParam.getCommands().get(0).getTaskMode())) {
+                        return null;
+                    }
+                }
+            }
+        } else {
+            if (crnProtocol.getTaskNo() > 0) {
+                WrkMastService mastService = SpringUtils.getBean(WrkMastService.class);
+                WrkMast wrkMast = mastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", crnProtocol.getTaskNo()));
+                WrkMast wrkMast1 = mastService.selectOne(new EntityWrapper<WrkMast>().eq("wrk_no", crnProtocol.getTaskNoTwo()));
+                if (!Objects.isNull(wrkMast) && !Objects.isNull(wrkMast1)) {
+                    if (!wrkMast1.getIoType().equals(wrkMast.getIoType())) {
+                        return null;
+                    }
+                }
+//                Object object = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_.key + crnProtocol.getTaskNo());
+//                if (object != null) {
+//                    SendDualCrnCommandParam jsonObject = JSON.parseObject(object.toString(), SendDualCrnCommandParam.class);
+//                    ;
+//                    if (!jsonObject.getCommands().get(0).getTaskMode().equals(sendDualCrnCommandParam.getCommands().get(0).getTaskMode())) {
+//                        return null;
+//                    }
+//                }
+            }
+        }
+
+        return sendDualCrnCommandParam;
+    }
 }
diff --git a/src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java b/src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
index c81bf43..85dd39c 100644
--- a/src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
@@ -54,6 +54,8 @@
     private CommonService commonService;
     @Autowired
     private NotifyUtils notifyUtils;
+    @Autowired
+    private StationOperateProcessUtils stationOperateProcessUtils;
 
     public synchronized void crnIoExecute() {
         Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
@@ -84,6 +86,7 @@
             List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                     .eq("crn_no", basCrnp.getCrnNo())
                     .in("wrk_sts", WrkStsType.INBOUND_RUN.sts, WrkStsType.OUTBOUND_RUN.sts)
+                    .orderBy("batch_seq", false)
             );
             if(!wrkMasts.isEmpty()){
                 continue;
@@ -257,11 +260,27 @@
             return false;
         }
 
+        int stationMaxTaskCount = 30;
+        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
+        if (systemConfigMapObj != null) {
+            try {
+                HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
+                stationMaxTaskCount = Integer.parseInt(systemConfigMap.getOrDefault("stationMaxTaskCountLimit", "30"));
+            } catch (Exception ignore) {}
+        }
+
+        int currentStationTaskCount = stationOperateProcessUtils.getCurrentStationTaskCount();
+        if (stationMaxTaskCount > 0 && currentStationTaskCount >= stationMaxTaskCount) {
+            News.warn("杈撻�佺珯鐐逛换鍔℃暟閲忚揪鍒颁笂闄愶紝宸插仠姝换鍔′笅鍙戙�傚綋鍓嶄换鍔℃暟={}锛屼笂闄�={}", currentStationTaskCount, stationMaxTaskCount);
+            return false;
+        }
+
         Integer crnNo = basCrnp.getCrnNo();
 
         List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                 .eq("crn_no", crnNo)
                 .eq("wrk_sts", WrkStsType.NEW_OUTBOUND.sts)
+                .orderBy("batch_seq", false)
         );
 
         for (WrkMast wrkMast : wrkMasts) {
@@ -628,7 +647,7 @@
                 wrkMast.setSystemMsg("");
                 wrkMast.setIoTime(new Date());
                 if (wrkMastService.updateById(wrkMast)) {
-                    CrnCommand resetCommand = crnThread.getResetCommand(crnProtocol.getCrnNo());
+                    CrnCommand resetCommand = crnThread.getResetCommand(crnProtocol.getTaskNo(), crnProtocol.getCrnNo());
                     MessageQueue.offer(SlaveType.Crn, crnProtocol.getCrnNo(), new Task(2, resetCommand));
                     News.info("鍫嗗灈鏈轰换鍔$姸鎬佹洿鏂版垚鍔燂紝鍫嗗灈鏈哄彿={}锛屽伐浣滃彿={}", basCrnp.getCrnNo(), crnProtocol.getTaskNo());
                 }
@@ -813,8 +832,7 @@
         String shallowLocNo = Utils.getLocNo(shallowRow, Utils.getBay(locNo), Utils.getLev(locNo));
         LocMast shallowLocMast = locMastService.queryByLoc(shallowLocNo);
         if (shallowLocMast == null) {
-            News.taskInfo(taskNo, "娴呭簱浣�:{} 鏁版嵁涓嶅瓨鍦�", shallowLocNo);
-            return false;
+            return true;
         }
 
         if (shallowLocMast.getLocSts().equals("O")) {
diff --git a/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java b/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
index 39895fe..919e0b0 100644
--- a/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
@@ -91,6 +91,10 @@
                 continue;
             }
 
+            if(dualCrnProtocol.getTaskSend() != 0 || dualCrnProtocol.getTaskSendTwo() != 0) {
+                continue;
+            }
+
             this.crnExecute(basDualCrnp, dualCrnThread);
         }
     }
@@ -195,18 +199,19 @@
         WrkMast stationOneWrkMast = null;
         WrkMast stationTwoWrkMast = null;
 
-        List<Integer> disableList = basDualCrnp.getDisableStationOneBays$();
+        List<Integer> disableOneList = basDualCrnp.getDisableStationOneBays$();
+        List<Integer> disableTwoList = basDualCrnp.getDisableStationTwoBays$();
 
         for (WrkMast wrkMast : outTaskList) {
             if (stationOneWrkMast == null) {
-                if (!disableList.contains(Utils.getBay(wrkMast.getSourceLocNo()))) {
+                if (!disableOneList.contains(Utils.getBay(wrkMast.getSourceLocNo()))) {
                     stationOneWrkMast = wrkMast;
                     continue;
                 }
             }
 
             if (stationTwoWrkMast == null) {
-                if (!disableList.contains(Utils.getBay(wrkMast.getSourceLocNo()))) {
+                if (!disableTwoList.contains(Utils.getBay(wrkMast.getSourceLocNo()))) {
                     stationTwoWrkMast = wrkMast;
                     continue;
                 }
@@ -461,6 +466,10 @@
 
         Integer station = inStationObjModel.getDualCrnExecuteStation();
         if (station == 1) {
+            if (dualCrnProtocol.getTaskNo() > 0) {
+                News.taskInfo(wrkMast.getWrkNo(), "宸ヤ綅1绯荤粺鍐呴儴璁板綍鏈変换鍔�");
+                return null;
+            }
             List<Integer> basList = basDualCrnp.getDisableStationOneBays$();
             if (basList.contains(Utils.getBay(wrkMast.getLocNo()))) {
                 //绂佹鏀捐揣鍒楋紝鐢宠閲嶆柊鍒嗛厤
@@ -468,6 +477,10 @@
                 return null;
             }
         }else {
+            if (dualCrnProtocol.getTaskNoTwo() > 0) {
+                News.taskInfo(wrkMast.getWrkNo(), "宸ヤ綅2绯荤粺鍐呴儴璁板綍鏈変换鍔�");
+                return null;
+            }
             List<Integer> basList = basDualCrnp.getDisableStationTwoBays$();
             if (basList.contains(Utils.getBay(wrkMast.getLocNo()))) {
                 //绂佹鏀捐揣鍒楋紝鐢宠閲嶆柊鍒嗛厤
@@ -516,6 +529,18 @@
         if (outStationList.isEmpty()) {
             News.info("鍙屽伐浣嶅爢鍨涙満:{} 鍑哄簱绔欑偣鏈缃�", basDualCrnp.getCrnNo());
             return null;
+        }
+
+        if (station == 1) {
+            if (dualCrnProtocol.getTaskNo() > 0) {
+                News.taskInfo(wrkMast.getWrkNo(), "宸ヤ綅1绯荤粺鍐呴儴璁板綍鏈変换鍔�");
+                return null;
+            }
+        }else {
+            if (dualCrnProtocol.getTaskNoTwo() > 0) {
+                News.taskInfo(wrkMast.getWrkNo(), "宸ヤ綅2绯荤粺鍐呴儴璁板綍鏈変换鍔�");
+                return null;
+            }
         }
 
         Integer crnNo = basDualCrnp.getCrnNo();
@@ -684,12 +709,14 @@
                 continue;
             }
 
-            if(dualCrnProtocol.getTaskNo() > 0 && dualCrnProtocol.getStatus() == DualCrnStatusType.WAITING.id) {
+            if((dualCrnProtocol.getTaskNo() > 0 && dualCrnProtocol.getDeviceTaskNo() > 0) && dualCrnProtocol.getTaskSend() == 0 && dualCrnProtocol.getStatus().equals(DualCrnStatusType.WAITING.id)) {
                 executeFinish(basDualCrnp, dualCrnThread, dualCrnProtocol, dualCrnProtocol.getTaskNo(), 1);
+                continue;
             }
 
-            if(dualCrnProtocol.getTaskNoTwo() > 0 && dualCrnProtocol.getStatusTwo() == DualCrnStatusType.WAITING.id) {
+            if((dualCrnProtocol.getTaskNoTwo() > 0 && dualCrnProtocol.getDeviceTaskNoTwo() > 0) && dualCrnProtocol.getTaskSendTwo() == 0 && dualCrnProtocol.getStatusTwo().equals(DualCrnStatusType.WAITING.id)) {
                 executeFinish(basDualCrnp, dualCrnThread, dualCrnProtocol, dualCrnProtocol.getTaskNoTwo(), 2);
+                continue;
             }
         }
     }
@@ -753,21 +780,28 @@
                 return;
             }
 
-            DualCrnCommand resetCommand = dualCrnThread.getResetCommand(dualCrnProtocol.getCrnNo(), station);
+            DualCrnCommand resetCommand = dualCrnThread.getResetCommand(taskNo, dualCrnProtocol.getCrnNo(), station);
             boolean offer = MessageQueue.offer(SlaveType.DualCrn, dualCrnProtocol.getCrnNo(), new Task(3, resetCommand));
             if (offer) {
+                if (station == 1) {
+                    redisUtil.set(RedisKeyType.DUAL_CRN_STATION1_FLAG.key + basDualCrnp.getCrnNo(), 0, 60 * 60 * 24);
+                }else {
+                    redisUtil.set(RedisKeyType.DUAL_CRN_STATION2_FLAG.key + basDualCrnp.getCrnNo(), 0, 60 * 60 * 24);
+                }
+
                 wrkMast.setWrkSts(updateWrkSts);
                 wrkMast.setSystemMsg("");
                 wrkMast.setIoTime(new Date());
                 if (wrkMastService.updateById(wrkMast)) {
                     News.info("鍙屽伐浣嶅爢鍨涙満浠诲姟鐘舵�佹洿鏂版垚鍔燂紝鍫嗗灈鏈哄彿={}锛屽伐浣滃彿={}", basDualCrnp.getCrnNo(), taskNo);
                 }
-                redisUtil.set(RedisKeyType.DUAL_CRN_IO_EXECUTE_FINISH_LIMIT.key + basDualCrnp.getCrnNo() + "_" + taskNo, "lock", 10);
+                redisUtil.set(RedisKeyType.DUAL_CRN_IO_EXECUTE_FINISH_LIMIT.key + basDualCrnp.getCrnNo() + "_" + taskNo, "lock", 3);
             }
         }else {
-            DualCrnCommand resetCommand = dualCrnThread.getResetCommand(dualCrnProtocol.getCrnNo(), station);
+            DualCrnCommand resetCommand = dualCrnThread.getResetCommand(taskNo, dualCrnProtocol.getCrnNo(), station);
             MessageQueue.offer(SlaveType.DualCrn, dualCrnProtocol.getCrnNo(), new Task(3, resetCommand));
             News.info("鍙屽伐浣嶅爢鍨涙満鍛戒护瀹屾垚纭鎴愬姛锛屽爢鍨涙満鍙�={}锛屽伐浣滃彿={}", basDualCrnp.getCrnNo(), taskNo);
+            redisUtil.set(RedisKeyType.DUAL_CRN_IO_EXECUTE_FINISH_LIMIT.key + basDualCrnp.getCrnNo() + "_" + taskNo, "lock", 3);
         }
     }
 
@@ -798,8 +832,7 @@
         String shallowLocNo = Utils.getLocNo(shallowRow, Utils.getBay(locNo), Utils.getLev(locNo));
         LocMast shallowLocMast = locMastService.queryByLoc(shallowLocNo);
         if (shallowLocMast == null) {
-            News.taskInfo(taskNo, "娴呭簱浣�:{} 鏁版嵁涓嶅瓨鍦�", shallowLocNo);
-            return false;
+            return true;
         }
 
         if (shallowLocMast.getLocSts().equals("O")) {
diff --git a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
index a41ff8a..72d192f 100644
--- a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -2,15 +2,21 @@
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.core.common.Cools;
 import com.core.exception.CoolException;
 import com.zy.asrs.domain.enums.NotifyMsgType;
+import com.zy.asrs.domain.vo.StationCycleCapacityVo;
+import com.zy.asrs.domain.vo.StationCycleLoopVo;
 import com.zy.asrs.entity.*;
 import com.zy.asrs.service.*;
 import com.zy.asrs.utils.NotifyUtils;
 import com.zy.common.entity.FindCrnNoResult;
+import com.zy.common.model.NavigateNode;
 import com.zy.common.model.StartupDto;
 import com.zy.common.service.CommonService;
+import com.zy.common.utils.NavigateUtils;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
 import com.zy.core.cache.MessageQueue;
@@ -28,6 +34,7 @@
 
 @Component
 public class StationOperateProcessUtils {
+    private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120;
 
     @Autowired
     private BasDevpService basDevpService;
@@ -36,10 +43,6 @@
     @Autowired
     private CommonService commonService;
     @Autowired
-    private BasCrnpService basCrnpService;
-    @Autowired
-    private BasDualCrnpService basDualCrnpService;
-    @Autowired
     private RedisUtil redisUtil;
     @Autowired
     private LocMastService locMastService;
@@ -47,10 +50,20 @@
     private WmsOperateUtils wmsOperateUtils;
     @Autowired
     private NotifyUtils notifyUtils;
+    @Autowired
+    private NavigateUtils navigateUtils;
+    @Autowired
+    private BasStationService basStationService;
+    @Autowired
+    private StationCycleCapacityService stationCycleCapacityService;
 
     //鎵ц杈撻�佺珯鐐瑰叆搴撲换鍔�
     public synchronized void stationInExecute() {
         try {
+            DispatchLimitConfig limitConfig = getDispatchLimitConfig();
+            int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()};
+            LoadGuardState loadGuardState = buildLoadGuardState(limitConfig);
+
             List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
             for (BasDevp basDevp : basDevps) {
                 StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
@@ -105,6 +118,12 @@
                             continue;
                         }
 
+                        LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), targetStationId, loadGuardState);
+
+                        if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
+                            return;
+                        }
+
                         StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0);
                         if (command == null) {
                             News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
@@ -120,6 +139,8 @@
                             MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
                             News.info("杈撻�佺珯鐐瑰叆搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command));
                             redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5);
+                            loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+                            saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
                         }
                     }
                 }
@@ -132,10 +153,16 @@
     //鎵ц鍫嗗灈鏈鸿緭閫佺珯鐐瑰嚭搴撲换鍔�
     public synchronized void crnStationOutExecute() {
         try {
+            DispatchLimitConfig limitConfig = getDispatchLimitConfig();
+            int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()};
+            LoadGuardState loadGuardState = buildLoadGuardState(limitConfig);
+
             List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                     .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
                     .isNotNull("crn_no")
             );
+            List<Integer> outOrderList = getAllOutOrderList();
+
             for (WrkMast wrkMast : wrkMasts) {
                 Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
                 if (infoObj == null) {
@@ -165,7 +192,30 @@
                         && stationProtocol.isLoading()
                         && stationProtocol.getTaskNo() == 0
                 ) {
-                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), wrkMast.getStaNo(), 0);
+                    Integer moveStaNo = wrkMast.getStaNo();
+
+                    if (!outOrderList.isEmpty()) {
+                        List<NavigateNode> nodes = navigateUtils.calcByStationId(stationProtocol.getStationId(), wrkMast.getStaNo());
+                        for (int i = nodes.size() - 1; i >= 0; i--) {
+                            NavigateNode node = nodes.get(i);
+                            JSONObject v = JSONObject.parseObject(node.getNodeValue());
+                            if (v != null) {
+                                Integer stationId = v.getInteger("stationId");
+                                if (outOrderList.contains(stationId)) {
+                                    moveStaNo = stationId;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+
+                    LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), moveStaNo, loadGuardState);
+
+                    if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
+                        return;
+                    }
+
+                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), moveStaNo, 0);
                     if (command == null) {
                         News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
                         continue;
@@ -178,8 +228,10 @@
                         MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
                         News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
                         redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
-                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60);
                         redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
+                        currentStationTaskCountRef[0]++;
+                        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+                        saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
                     }
                 }
             }
@@ -238,7 +290,6 @@
                         notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null);
                         News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
                         redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
-                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60);
                         redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
                     }
                 }
@@ -254,26 +305,74 @@
             List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN.sts));
             for (WrkMast wrkMast : wrkMasts) {
                 Integer wrkNo = wrkMast.getWrkNo();
+                Integer targetStaNo = wrkMast.getStaNo();
+
+                boolean complete = false;
+                BasStation basStation = basStationService.selectOne(new EntityWrapper<BasStation>().eq("station_id", targetStaNo));
+                if (basStation == null) {
+                    continue;
+                }
+
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+                StationProtocol stationProtocol = statusMap.get(basStation.getStationId());
+                if (stationProtocol == null) {
+                    continue;
+                }
+
+                if (stationProtocol.getTaskNo().equals(wrkNo)) {
+                    complete = true;
+                }
+
+                if (complete) {
+                    wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts);
+                    wrkMast.setIoTime(new Date());
+                    wrkMastService.updateById(wrkMast);
+                    notifyUtils.notify(String.valueOf(SlaveType.Devp), basStation.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null);
+                    redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    // 妫�娴嬩换鍔¤浆瀹屾垚
+    public synchronized void checkTaskToComplete() {
+        try {
+            List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts));
+            for (WrkMast wrkMast : wrkMasts) {
+                Integer wrkNo = wrkMast.getWrkNo();
+                Integer targetStaNo = wrkMast.getStaNo();
 
                 Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkNo);
                 if (lock != null) {
                     continue;
                 }
 
-                boolean complete = true;
-                List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
-                for (BasDevp basDevp : basDevps) {
-                    StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-                    if (stationThread == null) {
-                        continue;
-                    }
+                boolean complete = false;
+                BasStation basStation = basStationService.selectOne(new EntityWrapper<BasStation>().eq("station_id", targetStaNo));
+                if (basStation == null) {
+                    continue;
+                }
 
-                    List<StationProtocol> list = stationThread.getStatus();
-                    for (StationProtocol stationProtocol : list) {
-                        if (stationProtocol.getTaskNo().equals(wrkNo)) {
-                            complete = false;
-                        }
-                    }
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+                StationProtocol stationProtocol = statusMap.get(basStation.getStationId());
+                if (stationProtocol == null) {
+                    continue;
+                }
+
+                if (!stationProtocol.getTaskNo().equals(wrkNo)) {
+                    complete = true;
                 }
 
                 if (complete) {
@@ -426,10 +525,257 @@
 
     //鑾峰彇杈撻�佺嚎浠诲姟鏁伴噺
     public synchronized int getCurrentStationTaskCount() {
-        int currentStationTaskCount = 0;
+        return countCurrentStationTask();
+    }
+
+    // 妫�娴嬪嚭搴撴帓搴�
+    public synchronized void checkStationOutOrder() {
         List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
         for (BasDevp basDevp : basDevps) {
             StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getId());
+            if (stationThread == null) {
+                continue;
+            }
+            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+            List<StationObjModel> orderList = basDevp.getOutOrderList$();
+            for (StationObjModel stationObjModel : orderList) {
+                StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
+                if (stationProtocol == null) {
+                    continue;
+                }
+
+                if (!stationProtocol.isAutoing()) {
+                    continue;
+                }
+
+                if (!stationProtocol.isLoading()) {
+                    continue;
+                }
+
+                if (stationProtocol.getTaskNo() <= 0) {
+                    continue;
+                }
+
+                if (!stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) {
+                    continue;
+                }
+
+                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
+                if (wrkMast == null) {
+                    continue;
+                }
+
+                if (Cools.isEmpty(wrkMast.getBatch())) {
+                    continue;
+                }
+
+                if (Cools.isEmpty(wrkMast.getBatchSeq())) {
+                    continue;
+                }
+
+                List<WrkMast> batchWrkList = wrkMastService.selectList(new EntityWrapper<WrkMast>()
+                        .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts)
+                        .eq("batch", wrkMast.getBatch())
+                        .orderBy("batch")
+                );
+                if (batchWrkList.isEmpty()) {
+                    continue;
+                }
+                WrkMast firstWrkMast = batchWrkList.get(0);
+                Integer currentBatchSeq = firstWrkMast.getBatchSeq();
+
+                List<NavigateNode> initPath = navigateUtils.calcByStationId(wrkMast.getSourceStaNo(), wrkMast.getStaNo());
+
+                String commandType = "none";
+                Integer seq = getOutStationBatchSeq(initPath, stationProtocol.getStationId(), wrkMast.getBatch());
+                if (seq == null) {
+                    if (currentBatchSeq.equals(wrkMast.getBatchSeq())) {
+                        commandType = "toTarget";
+                    }else {
+                        commandType = "toCircle";
+                    }
+                }else {
+                    seq++;
+                    if (seq.equals(wrkMast.getBatchSeq()) && currentBatchSeq.equals(wrkMast.getBatchSeq())) {
+                        commandType = "toTarget";
+                    }else {
+                        commandType = "toCircle";
+                    }
+                }
+
+                if (commandType.equals("toTarget")) {
+                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), wrkMast.getStaNo(), 0);
+                    if (command == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+                        continue;
+                    }
+                    MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                    News.info("{}浠诲姟鐩存帴鍘荤洰鏍囩偣", wrkMast.getWrkNo());
+                } else if (commandType.equals("toCircle")) {
+                    Integer circleTarget = null;
+                    for (NavigateNode node : initPath) {
+                        JSONObject v = JSONObject.parseObject(node.getNodeValue());
+                        if (v != null) {
+                            Integer stationId = v.getInteger("stationId");
+                            try {
+                                List<NavigateNode> enableMovePath = navigateUtils.calcByStationId(stationProtocol.getStationId(), stationId);
+                                if (enableMovePath.isEmpty()) {
+                                    continue;
+                                }
+                            } catch (Exception e) {
+                                continue;
+                            }
+
+                            circleTarget = stationId;
+                            break;
+                        }
+                    }
+
+                    if (circleTarget == null) {
+                        continue;
+                    }
+
+                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), circleTarget, 0);
+                    if (command == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+                        continue;
+                    }
+                    MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                    redisUtil.set(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkMast.getWrkNo(), JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24);
+                    News.info("{}浠诲姟杩涜缁曞湀", wrkMast.getWrkNo());
+                }
+            }
+        }
+    }
+
+    // 鐩戞帶缁曞湀绔欑偣
+    public synchronized void watchCircleStation() {
+        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getId());
+            if (stationThread == null) {
+                continue;
+            }
+
+            List<Integer> outOrderList = basDevp.getOutOrderIntList();
+
+            for (StationProtocol stationProtocol : stationThread.getStatus()) {
+                if (!stationProtocol.isAutoing()) {
+                    continue;
+                }
+
+                if (!stationProtocol.isLoading()) {
+                    continue;
+                }
+
+                if (stationProtocol.getTaskNo() <= 0) {
+                    continue;
+                }
+
+                Object circleObj = redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + stationProtocol.getTaskNo());
+                if (circleObj == null) {
+                    continue;
+                }
+
+                StationCommand circleCommand = JSON.parseObject(circleObj.toString(), StationCommand.class);
+                if (!stationProtocol.getStationId().equals(circleCommand.getTargetStaNo())) {
+                    continue;
+                }
+
+                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
+                if (wrkMast == null) {
+                    continue;
+                }
+
+                Integer moveStaNo = wrkMast.getStaNo();
+
+                if (!outOrderList.isEmpty()) {
+                    List<NavigateNode> nodes = navigateUtils.calcByStationId(stationProtocol.getStationId(), wrkMast.getStaNo());
+                    for (int i = nodes.size() - 1; i >= 0; i--) {
+                        NavigateNode node = nodes.get(i);
+                        JSONObject v = JSONObject.parseObject(node.getNodeValue());
+                        if (v != null) {
+                            Integer stationId = v.getInteger("stationId");
+                            if (outOrderList.contains(stationId)) {
+                                moveStaNo = stationId;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), moveStaNo, 0);
+                if (command == null) {
+                    News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+                    continue;
+                }
+                MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+            }
+        }
+    }
+
+    public List<Integer> getAllOutOrderList() {
+        List<Integer> list = new ArrayList<>();
+        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            List<Integer> orderList = basDevp.getOutOrderIntList();
+            list.addAll(orderList);
+        }
+        return list;
+    }
+
+    public Integer getOutStationBatchSeq(List<NavigateNode> pathList, Integer searchStationId, String searchBatch) {
+        List<Integer> checkList = new ArrayList<>();
+        for (int i = pathList.size() - 1; i >= 0; i--) {
+            NavigateNode node = pathList.get(i);
+            JSONObject v = JSONObject.parseObject(node.getNodeValue());
+            if (v != null) {
+                Integer stationId = v.getInteger("stationId");
+                if (searchStationId.equals(stationId)) {
+                    break;
+                } else {
+                    checkList.add(stationId);
+                }
+            }
+        }
+
+        HashMap<String, Integer> batchMap = new HashMap<>();
+        for (Integer station : checkList) {
+            BasStation basStation = basStationService.selectOne(new EntityWrapper<BasStation>().eq("station_id", station));
+            if (basStation == null) {
+                continue;
+            }
+
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
+            if (stationThread == null) {
+                continue;
+            }
+            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+            StationProtocol checkStationProtocol = statusMap.get(station);
+            if (checkStationProtocol == null) {
+                continue;
+            }
+            if (checkStationProtocol.getTaskNo() > 0) {
+                WrkMast checkWrkMast = wrkMastService.selectByWorkNo(checkStationProtocol.getTaskNo());
+                if (checkWrkMast == null) {
+                    continue;
+                }
+
+                if (!Cools.isEmpty(checkWrkMast.getBatch())) {
+                    batchMap.put(checkWrkMast.getBatch(), checkWrkMast.getBatchSeq());
+                }
+            }
+        }
+
+        Integer seq = batchMap.get(searchBatch);
+        return seq;
+    }
+
+    private int countCurrentStationTask() {
+        int currentStationTaskCount = 0;
+        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
             if (stationThread == null) {
                 continue;
             }
@@ -440,9 +786,291 @@
                 }
             }
         }
-
         return currentStationTaskCount;
     }
 
+    private boolean isDispatchBlocked(DispatchLimitConfig config,
+                                      int currentStationTaskCount,
+                                      LoadGuardState loadGuardState,
+                                      boolean needReserveLoopLoad) {
+        if (config.loopModeEnable) {
+            double currentLoad = loadGuardState.currentLoad();
+            if (currentLoad >= config.circleMaxLoadLimit) {
+                News.warn("褰撳墠鎵胯浇閲忚揪鍒颁笂闄愶紝宸插仠姝㈢珯鐐逛换鍔′笅鍙戙�傚綋鍓嶆壙杞介噺={}锛屼笂闄�={}", formatPercent(currentLoad), formatPercent(config.circleMaxLoadLimit));
+                return true;
+            }
+
+            if (needReserveLoopLoad) {
+                double reserveLoad = loadGuardState.loadAfterReserve();
+                if (reserveLoad >= config.circleMaxLoadLimit) {
+                    News.warn("棰勫崰鍚庢壙杞介噺杈惧埌涓婇檺锛屽凡鍋滄绔欑偣浠诲姟涓嬪彂銆傞鍗犲悗鎵胯浇閲�={}锛屼笂闄�={}", formatPercent(reserveLoad), formatPercent(config.circleMaxLoadLimit));
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private LoadGuardState buildLoadGuardState(DispatchLimitConfig config) {
+        LoadGuardState state = new LoadGuardState();
+        if (!config.loopModeEnable) {
+            return state;
+        }
+
+        StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
+        if (capacityVo == null) {
+            return state;
+        }
+
+        state.totalStationCount = toNonNegative(capacityVo.getTotalStationCount());
+        state.projectedTaskStationCount = toNonNegative(capacityVo.getTaskStationCount());
+
+        List<StationCycleLoopVo> loopList = capacityVo.getLoopList();
+        if (loopList != null) {
+            for (StationCycleLoopVo loopVo : loopList) {
+                if (loopVo == null || loopVo.getStationIdList() == null) {
+                    continue;
+                }
+                Integer loopNo = loopVo.getLoopNo();
+                for (Integer stationId : loopVo.getStationIdList()) {
+                    if (stationId != null) {
+                        if (loopNo != null) {
+                            state.stationLoopNoMap.put(stationId, loopNo);
+                        }
+                    }
+                }
+            }
+        }
+        return state;
+    }
+
+    private LoopHitResult findPathLoopHit(DispatchLimitConfig config,
+                                          Integer sourceStationId,
+                                          Integer targetStationId,
+                                          LoadGuardState loadGuardState) {
+        if (!config.loopModeEnable) {
+            return LoopHitResult.NO_HIT;
+        }
+        if (sourceStationId == null || targetStationId == null) {
+            return LoopHitResult.NO_HIT;
+        }
+        if (loadGuardState.stationLoopNoMap.isEmpty()) {
+            return LoopHitResult.NO_HIT;
+        }
+
+        try {
+            List<NavigateNode> nodes = navigateUtils.calcByStationId(sourceStationId, targetStationId);
+            if (nodes == null || nodes.isEmpty()) {
+                return LoopHitResult.NO_HIT;
+            }
+
+            for (NavigateNode node : nodes) {
+                Integer stationId = getStationIdFromNode(node);
+                if (stationId == null) {
+                    continue;
+                }
+                Integer loopNo = loadGuardState.stationLoopNoMap.get(stationId);
+                if (loopNo != null) {
+                    return new LoopHitResult(true, loopNo, stationId);
+                }
+            }
+        } catch (Exception e) {
+            return LoopHitResult.NO_HIT;
+        }
+
+        return LoopHitResult.NO_HIT;
+    }
+
+    private Integer getStationIdFromNode(NavigateNode node) {
+        if (node == null || isBlank(node.getNodeValue())) {
+            return null;
+        }
+        try {
+            JSONObject v = JSONObject.parseObject(node.getNodeValue());
+            if (v == null) {
+                return null;
+            }
+            return v.getInteger("stationId");
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private int toNonNegative(Integer value) {
+        if (value == null || value < 0) {
+            return 0;
+        }
+        return value;
+    }
+
+    private void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) {
+        if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) {
+            return;
+        }
+        JSONObject reserveJson = new JSONObject();
+        reserveJson.put("wrkNo", wrkNo);
+        reserveJson.put("loopNo", loopHitResult.getLoopNo());
+        reserveJson.put("hitStationId", loopHitResult.getHitStationId());
+        reserveJson.put("createTime", System.currentTimeMillis());
+        redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString());
+        redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS);
+    }
+
+    private DispatchLimitConfig getDispatchLimitConfig() {
+        DispatchLimitConfig config = new DispatchLimitConfig();
+        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
+        if (!(systemConfigMapObj instanceof Map)) {
+            return config;
+        }
+        Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
+
+        config.circleMaxLoadLimit = parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.circleMaxLoadLimit);
+        String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable");
+        if (isBlank(loopModeValue)) {
+            loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable");
+        }
+        if (isBlank(loopModeValue)) {
+            loopModeValue = getConfigValue(systemConfigMap, "isCircleMode");
+        }
+        config.loopModeEnable = parseBoolean(loopModeValue, config.loopModeEnable);
+
+        return config;
+    }
+
+    private String getConfigValue(Map<?, ?> configMap, String key) {
+        Object value = configMap.get(key);
+        if (value == null) {
+            return null;
+        }
+        return String.valueOf(value).trim();
+    }
+
+    private boolean parseBoolean(String value, boolean defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        String lowValue = value.toLowerCase(Locale.ROOT);
+        if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue)
+                || "1".equals(lowValue) || "on".equals(lowValue)) {
+            return true;
+        }
+        if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue)
+                || "0".equals(lowValue) || "off".equals(lowValue)) {
+            return false;
+        }
+        return defaultValue;
+    }
+
+    private double parseLoadLimit(String value, double defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            String normalized = value.replace("%", "").trim();
+            double parsed = Double.parseDouble(normalized);
+            if (parsed > 1.0) {
+                parsed = parsed / 100.0;
+            }
+            if (parsed < 0.0) {
+                return 0.0;
+            }
+            if (parsed > 1.0) {
+                return 1.0;
+            }
+            return parsed;
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    private int parseInt(String value, int defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            int parsed = Integer.parseInt(value.trim());
+            return parsed < 0 ? defaultValue : parsed;
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    private String formatPercent(double value) {
+        return String.format(Locale.ROOT, "%.1f%%", value * 100.0);
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
+    private static class DispatchLimitConfig {
+        // 鍦堟渶澶ф壙杞借兘鍔涳紝榛樿80%
+        private double circleMaxLoadLimit = 0.8d;
+        // 鏄惁鍚敤缁曞湀妯″紡锛堜粎鍚敤鏃舵墠鐢熸晥鎵胯浇闄愬埗锛�
+        private boolean loopModeEnable = false;
+    }
+
+    private static class LoadGuardState {
+        private int totalStationCount = 0;
+        private int projectedTaskStationCount = 0;
+        private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>();
+
+        private double currentLoad() {
+            return calcLoad(this.projectedTaskStationCount, this.totalStationCount);
+        }
+
+        private double loadAfterReserve() {
+            return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount);
+        }
+
+        private void reserveLoopTask(Integer loopNo) {
+            if (loopNo == null || loopNo <= 0) {
+                return;
+            }
+            if (this.totalStationCount <= 0) {
+                return;
+            }
+            this.projectedTaskStationCount++;
+        }
+
+        private double calcLoad(int taskCount, int stationCount) {
+            if (stationCount <= 0 || taskCount <= 0) {
+                return 0.0;
+            }
+            double load = (double) taskCount / (double) stationCount;
+            if (load < 0.0) {
+                return 0.0;
+            }
+            if (load > 1.0) {
+                return 1.0;
+            }
+            return load;
+        }
+    }
+
+    private static class LoopHitResult {
+        private static final LoopHitResult NO_HIT = new LoopHitResult(false, null, null);
+        private final boolean throughLoop;
+        private final Integer loopNo;
+        private final Integer hitStationId;
+
+        private LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) {
+            this.throughLoop = throughLoop;
+            this.loopNo = loopNo;
+            this.hitStationId = hitStationId;
+        }
+
+        private boolean isThroughLoop() {
+            return throughLoop;
+        }
+
+        private Integer getLoopNo() {
+            return loopNo;
+        }
+
+        private Integer getHitStationId() {
+            return hitStationId;
+        }
+    }
 
 }
diff --git a/src/main/java/com/zy/system/controller/LicenseCreatorController.java b/src/main/java/com/zy/system/controller/LicenseCreatorController.java
index 0817de4..f40ee5a 100644
--- a/src/main/java/com/zy/system/controller/LicenseCreatorController.java
+++ b/src/main/java/com/zy/system/controller/LicenseCreatorController.java
@@ -1,6 +1,5 @@
 package com.zy.system.controller;
 
-import com.core.common.Cools;
 import com.core.common.R;
 import com.zy.system.entity.license.*;
 import com.zy.system.timer.LicenseTimer;
@@ -37,24 +36,7 @@
      */
     @RequestMapping(value = "/getServerInfos",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
     public LicenseCheck getServerInfos(@RequestParam(value = "osName",required = false) String osName) {
-        //鎿嶄綔绯荤粺绫诲瀷
-        if(Cools.isEmpty(osName)){
-            osName = System.getProperty("os.name");
-        }
-        osName = osName.toLowerCase();
-
-        AbstractServerInfos abstractServerInfos = null;
-
-        //鏍规嵁涓嶅悓鎿嶄綔绯荤粺绫诲瀷閫夋嫨涓嶅悓鐨勬暟鎹幏鍙栨柟娉�
-        if (osName.startsWith("windows")) {
-            abstractServerInfos = new WindowsServerInfos();
-        } else if (osName.startsWith("linux")) {
-            abstractServerInfos = new LinuxServerInfos();
-        }else{//鍏朵粬鏈嶅姟鍣ㄧ被鍨�
-            abstractServerInfos = new WindowsServerInfos();
-        }
-
-        return abstractServerInfos.getServerInfos();
+        return LicenseUtils.getServerInfos();
     }
 
     /**
diff --git a/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java b/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
index f670480..223706d 100644
--- a/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
+++ b/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
@@ -124,7 +124,7 @@
         //License涓彲琚厑璁哥殑鍙傛暟淇℃伅
         LicenseCheck expectedCheckModel = (LicenseCheck) content.getExtra();
         //褰撳墠鏈嶅姟鍣ㄧ湡瀹炵殑鍙傛暟淇℃伅
-        LicenseCheck serverCheckModel = getServerInfos();
+        LicenseCheck serverCheckModel = LicenseUtils.getServerInfos();
 
         if(expectedCheckModel != null && serverCheckModel != null){
             //鏍¢獙IP鍦板潃
@@ -180,26 +180,6 @@
         }
 
         return null;
-    }
-
-    /**
-     * 鑾峰彇褰撳墠鏈嶅姟鍣ㄩ渶瑕侀澶栨牎楠岀殑License鍙傛暟
-     */
-    private LicenseCheck getServerInfos(){
-        //鎿嶄綔绯荤粺绫诲瀷
-        String osName = System.getProperty("os.name").toLowerCase();
-        AbstractServerInfos abstractServerInfos = null;
-
-        //鏍规嵁涓嶅悓鎿嶄綔绯荤粺绫诲瀷閫夋嫨涓嶅悓鐨勬暟鎹幏鍙栨柟娉�
-        if (osName.startsWith("windows")) {
-            abstractServerInfos = new WindowsServerInfos();
-        } else if (osName.startsWith("linux")) {
-            abstractServerInfos = new LinuxServerInfos();
-        }else{//鍏朵粬鏈嶅姟鍣ㄧ被鍨�
-            abstractServerInfos = new WindowsServerInfos();
-        }
-
-        return abstractServerInfos.getServerInfos();
     }
 
     /**
diff --git a/src/main/java/com/zy/system/entity/license/LinuxServerInfos.java b/src/main/java/com/zy/system/entity/license/LinuxServerInfos.java
index 0f40daf..088301a 100644
--- a/src/main/java/com/zy/system/entity/license/LinuxServerInfos.java
+++ b/src/main/java/com/zy/system/entity/license/LinuxServerInfos.java
@@ -36,7 +36,11 @@
 
         if (inetAddresses != null && inetAddresses.size() > 0) {
             //2. 鑾峰彇鎵�鏈夌綉缁滄帴鍙g殑Mac鍦板潃
-            result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
+            result = inetAddresses.stream()
+                    .map(this::getMacByInetAddress)
+                    .filter(mac -> mac != null && !mac.trim().isEmpty())
+                    .distinct()
+                    .collect(Collectors.toList());
         }
 
         return result;
@@ -88,4 +92,4 @@
         reader.close();
         return serialNumber;
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/zy/system/entity/license/WindowsServerInfos.java b/src/main/java/com/zy/system/entity/license/WindowsServerInfos.java
index c575e6d..fc13a11 100644
--- a/src/main/java/com/zy/system/entity/license/WindowsServerInfos.java
+++ b/src/main/java/com/zy/system/entity/license/WindowsServerInfos.java
@@ -33,7 +33,11 @@
 
         if(inetAddresses != null && inetAddresses.size() > 0){
             //2. 鑾峰彇鎵�鏈夌綉缁滄帴鍙g殑Mac鍦板潃
-            result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
+            result = inetAddresses.stream()
+                    .map(this::getMacByInetAddress)
+                    .filter(mac -> mac != null && !mac.trim().isEmpty())
+                    .distinct()
+                    .collect(Collectors.toList());
         }
 
         return result;
@@ -82,4 +86,4 @@
         scanner.close();
         return serialNumber;
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/zy/system/timer/LicenseTimer.java b/src/main/java/com/zy/system/timer/LicenseTimer.java
index f6a724f..758d92d 100644
--- a/src/main/java/com/zy/system/timer/LicenseTimer.java
+++ b/src/main/java/com/zy/system/timer/LicenseTimer.java
@@ -73,17 +73,7 @@
 
     public void getRemoteLicense() {
         try {
-            AbstractServerInfos abstractServerInfos = null;
-            String osName = System.getProperty("os.name");
-            // 鏍规嵁涓嶅悓鎿嶄綔绯荤粺绫诲瀷閫夋嫨涓嶅悓鐨勬暟鎹幏鍙栨柟娉�
-            if (osName.startsWith("windows")) {
-                abstractServerInfos = new WindowsServerInfos();
-            } else if (osName.startsWith("linux")) {
-                abstractServerInfos = new LinuxServerInfos();
-            } else {// 鍏朵粬鏈嶅姟鍣ㄧ被鍨�
-                abstractServerInfos = new WindowsServerInfos();
-            }
-            LicenseCheck serverInfos = abstractServerInfos.getServerInfos();
+            LicenseCheck serverInfos = LicenseUtils.getServerInfos();
 
             HashMap<String, Object> map = new HashMap<>();
             map.put("subject", subject);
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 992768a..175c9f8 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -17,9 +17,9 @@
       validation-timeout: 3000
       connection-test-query: select 1
     driver-class-name: com.mysql.jdbc.Driver
-    url: jdbc:mysql://127.0.0.1:3306/wcs?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+    url: jdbc:mysql://192.168.238.31:3306/wcs?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
     username: root
-    password: root
+    password: zy@123
   mvc:
     static-path-pattern: /**
   redis:
@@ -61,7 +61,7 @@
 
 #License鐩稿叧閰嶇疆
 license:
-  subject: crnWcsDev
+  subject: jsxswcs1
   publicAlias: publicCert
   storePass: public_zhongyang_123456789
   licensePath: license.lic
@@ -72,7 +72,7 @@
   threadControlCount: 10
   liftType: lift
 
-mainProcessPlugin: FakeProcess
+mainProcessPlugin: XiaosongProcess
 
 deviceLogStorage:
   # 璁惧鏃ュ織瀛樺偍鏂瑰紡 mysql file
diff --git a/src/main/webapp/components/DevpCard.js b/src/main/webapp/components/DevpCard.js
index ba28546..39e69eb 100644
--- a/src/main/webapp/components/DevpCard.js
+++ b/src/main/webapp/components/DevpCard.js
@@ -43,8 +43,22 @@
                 <el-descriptions-item label="杩愯闃诲">{{ item.runBlock ? 'Y' : 'N' }}</el-descriptions-item>
                 <el-descriptions-item label="鍚姩鍏ュ簱">{{ item.enableIn ? 'Y' : 'N' }}</el-descriptions-item>
                 <el-descriptions-item label="鎵樼洏楂樺害">{{ item.palletHeight }}</el-descriptions-item>
-                <el-descriptions-item label="鏉$爜">{{ item.barcode }}</el-descriptions-item>
+                <el-descriptions-item label="鏉$爜">
+                  <el-popover v-if="item.barcode" placement="top" width="460" trigger="hover">
+                    <div style="text-align: center;">
+                      <img
+                        :src="getBarcodePreview(item.barcode)"
+                        :alt="'barcode-' + item.barcode"
+                        style="display: block; max-width: 100%; height: auto; margin: 0 auto; image-rendering: pixelated; background: #fff;"
+                      />
+                      <div style="margin-top: 4px; font-size: 12px; word-break: break-all;">{{ item.barcode }}</div>
+                    </div>
+                    <span slot="reference" @click.stop="handleBarcodeClick(item)" style="cursor: pointer; color: #409EFF;">{{ item.barcode }}</span>
+                  </el-popover>
+                  <span v-else @click.stop="handleBarcodeClick(item)" style="cursor: pointer; color: #409EFF;">-</span>
+                </el-descriptions-item>
                 <el-descriptions-item label="閲嶉噺">{{ item.weight }}</el-descriptions-item>
+                <el-descriptions-item label="浠诲姟鍙啓鍖�">{{ item.taskWriteIdx }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰浠g爜">{{ item.error }}</el-descriptions-item>
                 <el-descriptions-item label="鏁呴殰淇℃伅">{{ item.errorMsg }}</el-descriptions-item>
                 <el-descriptions-item label="鎵╁睍鏁版嵁">{{ item.extend }}</el-descriptions-item>
@@ -92,6 +106,7 @@
         taskNo: "",
         targetStationId: "",
       },
+      barcodePreviewCache: {},
       pageSize: 25,
       currentPage: 1,
       timer: null
@@ -136,6 +151,124 @@
       this.pageSize = size;
       this.currentPage = 1;
     },
+    getBarcodePreview(barcode) {
+      const value = String(barcode || "").trim();
+      if (!value) {
+        return "";
+      }
+      if (this.barcodePreviewCache[value]) {
+        return this.barcodePreviewCache[value];
+      }
+      const encodeResult = this.encodeCode128(value);
+      if (!encodeResult) {
+        return "";
+      }
+      const svg = this.buildCode128Svg(encodeResult, value);
+      const dataUrl = "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg);
+      this.$set(this.barcodePreviewCache, value, dataUrl);
+      return dataUrl;
+    },
+    encodeCode128(value) {
+      if (!value) {
+        return null;
+      }
+      const isNumeric = /^\d+$/.test(value);
+      if (isNumeric && value.length % 2 === 0) {
+        return this.encodeCode128C(value);
+      }
+      return this.encodeCode128B(value);
+    },
+    encodeCode128B(value) {
+      const codes = [104];
+      for (let i = 0; i < value.length; i++) {
+        const code = value.charCodeAt(i) - 32;
+        if (code < 0 || code > 94) {
+          return null;
+        }
+        codes.push(code);
+      }
+      return this.buildCode128Pattern(codes);
+    },
+    encodeCode128C(value) {
+      if (value.length % 2 !== 0) {
+        return null;
+      }
+      const codes = [105];
+      for (let i = 0; i < value.length; i += 2) {
+        codes.push(parseInt(value.substring(i, i + 2), 10));
+      }
+      return this.buildCode128Pattern(codes);
+    },
+    buildCode128Pattern(codes) {
+      const patterns = this.getCode128Patterns();
+      let checksum = codes[0];
+      for (let i = 1; i < codes.length; i++) {
+        checksum += codes[i] * i;
+      }
+      const checkCode = checksum % 103;
+      const fullCodes = codes.concat([checkCode, 106]);
+      let bars = "";
+      for (let i = 0; i < fullCodes.length; i++) {
+        const code = fullCodes[i];
+        if (patterns[code] == null) {
+          return null;
+        }
+        bars += patterns[code];
+      }
+      bars += "11";
+      return bars;
+    },
+    buildCode128Svg(bars, text) {
+      const quietModules = 20;
+      const modules = quietModules * 2 + bars.split("").reduce((sum, n) => sum + parseInt(n, 10), 0);
+      const moduleWidth = modules > 300 ? 1 : 2;
+      const width = modules * moduleWidth;
+      const barTop = 10;
+      const barHeight = 110;
+      let x = quietModules * moduleWidth;
+      let black = true;
+      let rects = "";
+      for (let i = 0; i < bars.length; i++) {
+        const w = parseInt(bars[i], 10) * moduleWidth;
+        if (black) {
+          rects += '<rect x="' + x + '" y="' + barTop + '" width="' + w + '" height="' + barHeight + '" fill="#000" shape-rendering="crispEdges" />';
+        }
+        x += w;
+        black = !black;
+      }
+      return (
+        '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="145" viewBox="0 0 ' + width + ' 145">' +
+        '<rect width="100%" height="100%" fill="#fff" />' +
+        rects +
+        '<text x="' + (width / 2) + '" y="136" text-anchor="middle" font-family="monospace" font-size="14" fill="#111">' +
+        this.escapeXml(text) +
+        "</text>" +
+        "</svg>"
+      );
+    },
+    getCode128Patterns() {
+      return [
+        "212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213",
+        "221312", "231212", "112232", "122132", "122231", "113222", "123122", "123221", "223211", "221132",
+        "221231", "213212", "223112", "312131", "311222", "321122", "321221", "312212", "322112", "322211",
+        "212123", "212321", "232121", "111323", "131123", "131321", "112313", "132113", "132311", "211313",
+        "231113", "231311", "112133", "112331", "132131", "113123", "113321", "133121", "313121", "211331",
+        "231131", "213113", "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111",
+        "314111", "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214",
+        "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", "134111",
+        "111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112", "421211", "212141",
+        "214121", "412121", "111143", "111341", "131141", "114113", "114311", "411113", "411311", "113141",
+        "114131", "311141", "411131", "211412", "211214", "211232", "2331112"
+      ];
+    },
+    escapeXml(text) {
+      return String(text)
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+        .replace(/"/g, "&quot;")
+        .replace(/'/g, "&apos;");
+    },
     getDevpStateInfo() {
       if (this.readOnly) {
           // Frontend filtering for readOnly mode
@@ -171,6 +304,81 @@
         }
       }
     },
+    handleBarcodeClick(item) {
+      if (this.readOnly || !item || item.stationId == null) {
+        return;
+      }
+
+      let that = this;
+      $.ajax({
+        url: baseUrl + "/openapi/getFakeSystemRunStatus",
+        headers: {
+          token: localStorage.getItem("token"),
+        },
+        method: "get",
+        success: (res) => {
+          if (res.code !== 200 || !res.data || !res.data.isFake || !res.data.running) {
+            that.$message({
+              message: "浠呬豢鐪熸ā寮忚繍琛屼腑鍙慨鏀规潯鐮�",
+              type: "warning",
+            });
+            return;
+          }
+
+          that.$prompt("璇疯緭鍏ユ柊鐨勬潯鐮佸�硷紙鍙暀绌烘竻绌猴級", "淇敼鏉$爜", {
+            confirmButtonText: "纭畾",
+            cancelButtonText: "鍙栨秷",
+            inputValue: item.barcode || "",
+            inputPlaceholder: "璇疯緭鍏ユ潯鐮�",
+          }).then(({ value }) => {
+            that.updateStationBarcode(item.stationId, value == null ? "" : String(value).trim());
+          }).catch(() => {});
+        },
+      });
+    },
+    updateStationBarcode(stationId, barcode) {
+      let that = this;
+      $.ajax({
+        url: baseUrl + "/station/command/barcode",
+        headers: {
+          token: localStorage.getItem("token"),
+        },
+        contentType: "application/json",
+        method: "post",
+        data: JSON.stringify({
+          stationId: stationId,
+          barcode: barcode,
+        }),
+        success: (res) => {
+          if (res.code == 200) {
+            that.syncLocalBarcode(stationId, barcode);
+            that.$message({
+              message: "鏉$爜淇敼鎴愬姛",
+              type: "success",
+            });
+          } else {
+            that.$message({
+              message: res.msg || "鏉$爜淇敼澶辫触",
+              type: "warning",
+            });
+          }
+        },
+      });
+    },
+    syncLocalBarcode(stationId, barcode) {
+      let updateFn = (list) => {
+        if (!list || list.length === 0) {
+          return;
+        }
+        list.forEach((row) => {
+          if (row.stationId == stationId) {
+            row.barcode = barcode;
+          }
+        });
+      };
+      updateFn(this.stationList);
+      updateFn(this.fullStationList);
+    },
     openControl() {
       this.showControl = !this.showControl;
     },
diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index 3147540..f04f4ea 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -1,7 +1,21 @@
 Vue.component('map-canvas', {
   template: `
     <div style="width: 100%; height: 100%; position: relative;">
-      <div ref="pixiView"></div>
+      <div ref="pixiView" style="position: absolute; inset: 0;"></div>
+      <div style="position: absolute; top: 12px; left: 14px; z-index: 30; pointer-events: none; max-width: 52%;">
+        <div style="display: flex; flex-direction: column; gap: 6px; align-items: flex-start;">
+          <div v-for="item in cycleCapacity.loopList"
+               :key="'loop-' + item.loopNo"
+               @mouseenter="handleLoopCardEnter(item)"
+               @mouseleave="handleLoopCardLeave(item)"
+               style="padding: 6px 10px; border-radius: 4px; background: rgba(11, 35, 58, 0.72); color: #fff; font-size: 12px; line-height: 1.4; white-space: nowrap; pointer-events: auto;">
+            鍦坽{ item.loopNo }} |
+            绔欑偣: {{ item.stationCount || 0 }} |
+            浠诲姟: {{ item.taskCount || 0 }} |
+            鎵胯浇: {{ formatLoadPercent(item.currentLoad) }}
+          </div>
+        </div>
+      </div>
       <div v-show="shelfTooltip.visible"
            :style="shelfTooltipStyle()">
         {{ shelfTooltip.text }}
@@ -80,14 +94,25 @@
         item: null
       },
       shelfTooltipMinScale: 0.4,
+      containerResizeObserver: null,
       timer: null,
       adjustLabelTimer: null,
-      isSwitchingFloor: false
+      isSwitchingFloor: false,
+      cycleCapacity: {
+        loopList: [],
+        totalStationCount: 0,
+        taskStationCount: 0,
+        currentLoad: 0
+      },
+      hoverLoopNo: null,
+      hoverLoopStationIdSet: new Set(),
+      loopHighlightColor: 0xfff34d
     }
   },
     mounted() {
     this.currentLev = this.lev || 1;
     this.createMap();
+    this.startContainerResizeObserve();
     this.loadMapTransformConfig();
     this.loadLocList();
     this.connectWs();
@@ -100,6 +125,7 @@
       this.getCrnInfo();
       this.getDualCrnInfo();
       this.getSiteInfo();
+      this.getCycleCapacityInfo();
       this.getRgvInfo();
     }, 1000);
   },
@@ -108,6 +134,7 @@
 
     if (this.hoverRaf) { cancelAnimationFrame(this.hoverRaf); this.hoverRaf = null; }
     if (this.pixiApp) { this.pixiApp.destroy(true, { children: true }); }
+    if (this.containerResizeObserver) { this.containerResizeObserver.disconnect(); this.containerResizeObserver = null; }
     window.removeEventListener('resize', this.resizeToContainer);
     if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
     if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { try { this.ws.close(); } catch (e) {} }
@@ -277,11 +304,33 @@
       });
       //*******************FPS*******************
     },
+    startContainerResizeObserve() {
+      if (typeof ResizeObserver === 'undefined' || !this.$el) { return; }
+      this.containerResizeObserver = new ResizeObserver(() => {
+        this.resizeToContainer();
+      });
+      this.containerResizeObserver.observe(this.$el);
+    },
+    getViewportSize() {
+      if (!this.pixiApp || !this.pixiApp.renderer) { return { width: 0, height: 0 }; }
+      const screen = this.pixiApp.renderer.screen;
+      if (screen && screen.width > 0 && screen.height > 0) {
+        return { width: screen.width, height: screen.height };
+      }
+      const rect = this.pixiApp.view ? this.pixiApp.view.getBoundingClientRect() : null;
+      return { width: rect ? rect.width : 0, height: rect ? rect.height : 0 };
+    },
     resizeToContainer() {
       const w = this.$el.clientWidth || 0;
       const h = this.$el.clientHeight || 0;
       if (w > 0 && h > 0 && this.pixiApp) {
+        const vw = this.pixiApp.renderer && this.pixiApp.renderer.screen ? this.pixiApp.renderer.screen.width : 0;
+        const vh = this.pixiApp.renderer && this.pixiApp.renderer.screen ? this.pixiApp.renderer.screen.height : 0;
+        if (vw === w && vh === h) { return; }
         this.pixiApp.renderer.resize(w, h);
+        if (this.mapContentSize && this.mapContentSize.width > 0 && this.mapContentSize.height > 0) {
+          this.applyMapTransform(true);
+        }
       }
     },
     getMap() {
@@ -289,6 +338,7 @@
     },
     changeFloor(lev) {
       this.currentLev = lev;
+      this.clearLoopStationHighlight();
       this.isSwitchingFloor = true;
       this.hideShelfTooltip();
       this.hoveredShelfCell = null;
@@ -313,6 +363,7 @@
       this.getMap();
     },
     createMapData(map) {
+      this.clearLoopStationHighlight();
       this.hideShelfTooltip();
       this.hoveredShelfCell = null;
       this.mapRowOffsets = [];
@@ -627,21 +678,21 @@
           sta.statusObj = null;
           if (sta.textObj.parent !== sta) { sta.addChild(sta.textObj); sta.textObj.position.set(sta.width / 2, sta.height / 2); }
         }
+        let baseColor = 0xb8b8b8;
         if (status === "site-auto") {
-          this.updateColor(sta, 0x78ff81);
+          baseColor = 0x78ff81;
         } else if (status === "site-auto-run" || status === "site-auto-id" || status === "site-auto-run-id") {
-          this.updateColor(sta, 0xfa51f6);
+          baseColor = 0xfa51f6;
         } else if (status === "site-unauto") {
-          this.updateColor(sta, 0xb8b8b8);
+          baseColor = 0xb8b8b8;
         } else if (status === "machine-pakin") {
-          this.updateColor(sta, 0x30bffc);
+          baseColor = 0x30bffc;
         } else if (status === "machine-pakout") {
-          this.updateColor(sta, 0x97b400);
+          baseColor = 0x97b400;
         } else if (status === "site-run-block") {
-          this.updateColor(sta, 0xe69138);
-        } else {
-          this.updateColor(sta, 0xb8b8b8);
+          baseColor = 0xe69138;
         }
+        this.setStationBaseColor(sta, baseColor);
       });
     },
     getCrnInfo() {
@@ -659,6 +710,38 @@
     getRgvInfo() {
       if (this.isSwitchingFloor) { return; }
       this.sendWs(JSON.stringify({ url: "/console/latest/data/rgv", data: {} }));
+    },
+    getCycleCapacityInfo() {
+      if (this.isSwitchingFloor) { return; }
+      this.sendWs(JSON.stringify({ url: "/console/latest/data/station/cycle/capacity", data: {} }));
+    },
+    setCycleCapacityInfo(res) {
+      const payload = res && res.code === 200 ? res.data : null;
+      if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; return; }
+      if (!payload) { return; }
+      const loopList = Array.isArray(payload.loopList) ? payload.loopList : [];
+      this.cycleCapacity = {
+        loopList: loopList,
+        totalStationCount: payload.totalStationCount || 0,
+        taskStationCount: payload.taskStationCount || 0,
+        currentLoad: typeof payload.currentLoad === 'number' ? payload.currentLoad : parseFloat(payload.currentLoad || 0)
+      };
+      if (this.hoverLoopNo != null) {
+        const targetLoop = loopList.find(v => v && v.loopNo === this.hoverLoopNo);
+        if (targetLoop) {
+          this.hoverLoopStationIdSet = this.buildStationIdSet(targetLoop.stationIdList);
+          this.applyLoopStationHighlight();
+        } else {
+          this.clearLoopStationHighlight();
+        }
+      }
+    },
+    formatLoadPercent(load) {
+      let value = typeof load === 'number' ? load : parseFloat(load || 0);
+      if (!isFinite(value)) { value = 0; }
+      if (value < 0) { value = 0; }
+      if (value > 1) { value = 1; }
+      return (value * 100).toFixed(1) + "%";
     },
     setCrnInfo(res) {
       let crns = Array.isArray(res) ? res : (res && res.code === 200 ? res.data : null);
@@ -814,6 +897,7 @@
       if (this.wsReconnectTimer) { clearTimeout(this.wsReconnectTimer); this.wsReconnectTimer = null; }
       this.wsReconnectAttempts = 0;
       this.getMap(this.currentLev);
+      this.getCycleCapacityInfo();
     },
     webSocketOnError(e) {
       this.scheduleReconnect();
@@ -828,6 +912,8 @@
         this.setDualCrnInfo(JSON.parse(result.data));
       } else if (result.url === "/console/latest/data/rgv") {
         this.setRgvInfo(JSON.parse(result.data));
+      } else if (result.url === "/console/latest/data/station/cycle/capacity") {
+        this.setCycleCapacityInfo(JSON.parse(result.data));
       } else if (typeof result.url === "string" && result.url.indexOf("/basMap/lev/") === 0) {
         this.setMap(JSON.parse(result.data));
       }
@@ -1175,7 +1261,11 @@
         text.position.set(sprite.width / 2, sprite.height / 2);
         sprite.addChild(text);
         sprite.textObj = text;
-        if (siteId != null && siteId !== -1) { this.pixiStaMap.set(parseInt(siteId), sprite); }
+        const stationIdInt = parseInt(siteId, 10);
+        if (!isNaN(stationIdInt)) { this.pixiStaMap.set(stationIdInt, sprite); }
+        sprite._stationId = isNaN(stationIdInt) ? null : stationIdInt;
+        sprite._baseColor = 0x00ff7f;
+        sprite._loopHighlighted = false;
         sprite.interactive = true;
         sprite.buttonMode = true;
         sprite.on('pointerdown', () => {
@@ -1376,6 +1466,74 @@
         return;
       }
       sprite.tint = color;
+    },
+    setStationBaseColor(sprite, color) {
+      if (!sprite) { return; }
+      sprite._baseColor = color;
+      if (this.isStationInHoverLoop(sprite)) {
+        this.applyHighlightColor(sprite);
+      } else {
+        this.updateColor(sprite, color);
+        sprite._loopHighlighted = false;
+      }
+    },
+    applyHighlightColor(sprite) {
+      if (!sprite) { return; }
+      this.updateColor(sprite, this.loopHighlightColor);
+      sprite._loopHighlighted = true;
+    },
+    isStationInHoverLoop(sprite) {
+      if (!sprite || sprite._stationId == null || !this.hoverLoopStationIdSet) { return false; }
+      return this.hoverLoopStationIdSet.has(sprite._stationId);
+    },
+    buildStationIdSet(stationIdList) {
+      const set = new Set();
+      if (!Array.isArray(stationIdList)) { return set; }
+      stationIdList.forEach((id) => {
+        const v = parseInt(id, 10);
+        if (!isNaN(v)) { set.add(v); }
+      });
+      return set;
+    },
+    applyLoopStationHighlight() {
+      if (!this.pixiStaMap) { return; }
+      this.pixiStaMap.forEach((sprite) => {
+        if (!sprite) { return; }
+        if (this.isStationInHoverLoop(sprite)) {
+          this.applyHighlightColor(sprite);
+        } else if (sprite._loopHighlighted) {
+          const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8;
+          this.updateColor(sprite, baseColor);
+          sprite._loopHighlighted = false;
+        }
+      });
+    },
+    clearLoopStationHighlight() {
+      if (this.pixiStaMap) {
+        this.pixiStaMap.forEach((sprite) => {
+          if (!sprite || !sprite._loopHighlighted) { return; }
+          const baseColor = (typeof sprite._baseColor === 'number') ? sprite._baseColor : 0xb8b8b8;
+          this.updateColor(sprite, baseColor);
+          sprite._loopHighlighted = false;
+        });
+      }
+      this.hoverLoopNo = null;
+      this.hoverLoopStationIdSet = new Set();
+    },
+    handleLoopCardEnter(loopItem) {
+      if (!loopItem) { return; }
+      this.hoverLoopNo = loopItem.loopNo;
+      this.hoverLoopStationIdSet = this.buildStationIdSet(loopItem.stationIdList);
+      this.applyLoopStationHighlight();
+    },
+    handleLoopCardLeave(loopItem) {
+      if (!loopItem) {
+        this.clearLoopStationHighlight();
+        return;
+      }
+      if (this.hoverLoopNo === loopItem.loopNo) {
+        this.clearLoopStationHighlight();
+      }
     },
     isJson(str) {
       try { JSON.parse(str); return true; } catch (e) { return false; }
@@ -1642,8 +1800,9 @@
     adjustLabelScale() {
       const s = this.pixiApp && this.pixiApp.stage ? Math.abs(this.pixiApp.stage.scale.x || 1) : 1;
       const minPx = 14;
-      const vw = this.pixiApp.view.width;
-      const vh = this.pixiApp.view.height;
+      const viewport = this.getViewportSize();
+      const vw = viewport.width;
+      const vh = viewport.height;
       const margin = 50;
       const mirrorSign = this.mapMirrorX ? -1 : 1;
       const inverseRotation = -((this.mapRotation % 360) * Math.PI / 180);
@@ -1821,8 +1980,9 @@
       const contentW = size.width || 0;
       const contentH = size.height || 0;
       if (contentW <= 0 || contentH <= 0) { return; }
-      const vw = this.pixiApp.view.width;
-      const vh = this.pixiApp.view.height;
+      const viewport = this.getViewportSize();
+      const vw = viewport.width;
+      const vh = viewport.height;
       let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
       if (!isFinite(scale) || scale <= 0) { scale = 1; }
       const baseW = this.mapContentSize.width || contentW;
@@ -1856,12 +2016,6 @@
     }
   }
 });
-
-
-
-
-
-
 
 
 
diff --git a/src/main/webapp/components/WatchDualCrnCard.js b/src/main/webapp/components/WatchDualCrnCard.js
index d221857..6845338 100644
--- a/src/main/webapp/components/WatchDualCrnCard.js
+++ b/src/main/webapp/components/WatchDualCrnCard.js
@@ -47,8 +47,28 @@
               <el-descriptions border direction="vertical">
                 <el-descriptions-item label="妯″紡">{{ item.mode }}</el-descriptions-item>
                 <el-descriptions-item label="寮傚父鐮�">{{ item.warnCode }}</el-descriptions-item>
-                <el-descriptions-item label="宸ヤ綅1浠诲姟鍙�">{{ item.taskNo }}</el-descriptions-item>
-                <el-descriptions-item label="宸ヤ綅2浠诲姟鍙�">{{ item.taskNoTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1浠诲姟鍙�">
+                  <span v-if="readOnly">{{ item.taskNo }}</span>
+                  <el-button
+                    v-else
+                    type="text"
+                    size="mini"
+                    style="padding:0;"
+                    @click.stop="editTaskNo(item, 1)"
+                  >{{ item.taskNo }}</el-button>
+                </el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2浠诲姟鍙�">
+                  <span v-if="readOnly">{{ item.taskNoTwo }}</span>
+                  <el-button
+                    v-else
+                    type="text"
+                    size="mini"
+                    style="padding:0;"
+                    @click.stop="editTaskNo(item, 2)"
+                  >{{ item.taskNoTwo }}</el-button>
+                </el-descriptions-item>
+                <el-descriptions-item label="璁惧宸ヤ綅1浠诲姟鍙�">{{ item.deviceTaskNo }}</el-descriptions-item>
+                <el-descriptions-item label="璁惧宸ヤ綅2浠诲姟鍙�">{{ item.deviceTaskNoTwo }}</el-descriptions-item>
                 <el-descriptions-item label="宸ヤ綅1鐘舵��">{{ item.status }}</el-descriptions-item>
                 <el-descriptions-item label="宸ヤ綅2鐘舵��">{{ item.statusTwo }}</el-descriptions-item>
                 <el-descriptions-item label="宸ヤ綅1鏄惁鏈夌墿">{{ item.loading }}</el-descriptions-item>
@@ -57,6 +77,8 @@
                 <el-descriptions-item label="宸ヤ綅2璐у弶瀹氫綅">{{ item.forkOffsetTwo }}</el-descriptions-item>
                 <el-descriptions-item label="宸ヤ綅1浠诲姟鎺ユ敹">{{ item.taskReceive }}</el-descriptions-item>
                 <el-descriptions-item label="宸ヤ綅2浠诲姟鎺ユ敹">{{ item.taskReceiveTwo }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅1涓嬪彂鏁版嵁">{{ item.taskSend }}</el-descriptions-item>
+                <el-descriptions-item label="宸ヤ綅2涓嬪彂鏁版嵁">{{ item.taskSendTwo }}</el-descriptions-item>
                 <el-descriptions-item label="鍒�">{{ item.bay }}</el-descriptions-item>
                 <el-descriptions-item label="灞�">{{ item.lev }}</el-descriptions-item>
                 <el-descriptions-item label="杞借揣鍙板畾浣�">{{ item.liftPos }}</el-descriptions-item>
@@ -161,6 +183,50 @@
     openControl() {
       this.showControl = !this.showControl;
     },
+    editTaskNo(item, station) {
+      let that = this;
+      const isStationOne = station === 1;
+      const fieldName = isStationOne ? "taskNo" : "taskNoTwo";
+      const stationName = isStationOne ? "宸ヤ綅1" : "宸ヤ綅2";
+      const currentTaskNo = item[fieldName] == null ? "" : String(item[fieldName]);
+      that.$prompt("璇疯緭鍏�" + stationName + "浠诲姟鍙�", "缂栬緫浠诲姟鍙�", {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        inputValue: currentTaskNo,
+        inputPattern: /^\d+$/,
+        inputErrorMessage: "浠诲姟鍙峰繀椤绘槸闈炶礋鏁存暟",
+      }).then(({ value }) => {
+        const taskNo = Number(value);
+        $.ajax({
+          url: baseUrl + "/dualcrn/command/updateTaskNo",
+          headers: {
+            token: localStorage.getItem("token"),
+          },
+          contentType: "application/json",
+          method: "post",
+          data: JSON.stringify({
+            crnNo: item.crnNo,
+            station: station,
+            taskNo: taskNo,
+          }),
+          success: (res) => {
+            if (res.code == 200) {
+              item[fieldName] = taskNo;
+              that.$message({
+                message: stationName + "浠诲姟鍙锋洿鏂版垚鍔�",
+                type: "success",
+              });
+              that.getDualCrnStateInfo();
+            } else {
+              that.$message({
+                message: res.msg,
+                type: "warning",
+              });
+            }
+          },
+        });
+      }).catch(() => {});
+    },
     getDualCrnStateInfo() {
       if (this.$root.sendWs) {
         this.$root.sendWs(JSON.stringify({
diff --git a/src/main/webapp/static/js/basDevp/basDevp.js b/src/main/webapp/static/js/basDevp/basDevp.js
index f323842..a373c0a 100644
--- a/src/main/webapp/static/js/basDevp/basDevp.js
+++ b/src/main/webapp/static/js/basDevp/basDevp.js
@@ -38,6 +38,8 @@
           { field: "inStationList", align: "center", title: "鍏ュ簱绔欑偣鏁版嵁" },
           { field: "outStationList", align: "center", title: "鍑哄簱绔欑偣鏁版嵁" },
           { field: "runBlockReassignLocStationList", align: "center", title: "杩愯鍫靛閲嶆柊鍒嗛厤搴撲綅绔欑偣鏁版嵁" },
+          { field: "isOutOrderList", align: "center", title: "鍑哄簱鎺掑簭浜や簰鐐�" },
+          { field: "isLiftTransferList", align: "center", title: "椤跺崌绉绘牻鐐�" },
 
           {
             fixed: "right",
diff --git a/src/main/webapp/views/basDevp/basDevp.html b/src/main/webapp/views/basDevp/basDevp.html
index 98bb472..215b640 100644
--- a/src/main/webapp/views/basDevp/basDevp.html
+++ b/src/main/webapp/views/basDevp/basDevp.html
@@ -149,6 +149,18 @@
                         <input class="layui-input" name="runBlockReassignLocStationList" placeholder="璇疯緭鍏ヨ繍琛屽牭濉為噸鏂板垎閰嶅簱浣嶇珯鐐规暟鎹�">
                     </div>
                 </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">鍑哄簱鎺掑簭浜や簰鐐�: </label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" name="isOutOrderList" placeholder="璇疯緭鍏ュ嚭搴撴帓搴忎氦浜掔偣">
+                    </div>
+                </div>
+                <div class="layui-form-item">
+                    <label class="layui-form-label">椤跺崌绉绘牻鐐�: </label>
+                    <div class="layui-input-block">
+                        <input class="layui-input" name="isLiftTransferList" placeholder="璇疯緭鍏ョ數姊腑杞偣">
+                    </div>
+                </div>
 
              </div>
         </div>
@@ -188,4 +200,3 @@
     </form>
 </script>
 </html>
-
diff --git a/src/main/webapp/views/locMap/locMap.html b/src/main/webapp/views/locMap/locMap.html
index 5d34f56..36fdee8 100644
--- a/src/main/webapp/views/locMap/locMap.html
+++ b/src/main/webapp/views/locMap/locMap.html
@@ -59,9 +59,9 @@
   >
     <div v-if="drawerLocNoData!=null">
       <div style="margin: 10px;">
-        <div style="margin-top: 5px;">鎺掞細{{drawerLocNoData.row}}</div>
-        <div style="margin-top: 5px;">鍒楋細{{drawerLocNoData.bay}}</div>
-        <div style="margin-top: 5px;">灞傦細{{drawerLocNoData.lev}}</div>
+<!--        <div style="margin-top: 5px;">鎺掞細{{drawerLocNoData.row}}</div>-->
+<!--        <div style="margin-top: 5px;">鍒楋細{{drawerLocNoData.bay}}</div>-->
+<!--        <div style="margin-top: 5px;">灞傦細{{drawerLocNoData.lev}}</div>-->
         <div style="margin-top: 5px;">搴撲綅鍙凤細{{drawerLocNoData.locNo}}</div>
         <div style="margin-top: 5px;">搴撲綅鐘舵�侊細{{drawerLocNoData.locSts}}</div>
       </div>
@@ -101,7 +101,12 @@
   let pixiStageList = [];
   let pixiStaMap = new Map();
   let objectsContainer;
+  let objectsContainer3;
+  let tracksGraphics;
+  let mapRoot;
+  let mapContentSize = { width: 0, height: 0 };
   let graphics0;
+  let graphicsF;
   let graphics3;
   let graphics4;
   let graphics5;
@@ -125,6 +130,12 @@
       currentLevStaList: [],//褰撳墠妤煎眰绔欑偣list
       drawerSta: false,
       drawerStaData: null,
+      mapRotation: 0,
+      mapMirrorX: false,
+      mapConfigCodes: {
+        rotate: 'map_canvas_rotation',
+        mirror: 'map_canvas_mirror_x'
+      }
     },
     mounted() {
       this.init()
@@ -157,6 +168,7 @@
         ws.onclose = this.webSocketClose
 
 
+        this.loadMapTransformConfig()
         this.initLev()//鍒濆鍖栨ゼ灞備俊鎭�
         setTimeout(() => {
           that.getMap(this.currentLev)
@@ -205,6 +217,7 @@
       },
       changeFloor(lev) {
         this.currentLev = lev
+        this.loadMapTransformConfig()
         this.reloadMap = true
         this.getMap(lev)
       },
@@ -229,13 +242,18 @@
         graphics67 = pixiApp.renderer.generateTexture(getContainer(67));
         graphicsLock = pixiApp.renderer.generateTexture(getContainer(-999));
 
+        mapRoot = new PIXI.Container();
+        pixiApp.stage.addChild(mapRoot);
         // 鍒涘缓涓�涓鍣ㄦ潵绠$悊澶ф壒閲忕殑鏄剧ず瀵硅薄
         objectsContainer = new PIXI.Container();
-        pixiApp.stage.addChild(objectsContainer);
+        mapRoot.addChild(objectsContainer);
+
+        tracksGraphics = new PIXI.Graphics();
+        mapRoot.addChild(tracksGraphics);
 
         // 鍒涘缓涓�涓鍣ㄦ潵绠$悊澶ф壒閲忕殑鏄剧ず瀵硅薄
         objectsContainer3 = new PIXI.Container();
-        pixiApp.stage.addChild(objectsContainer3);
+        mapRoot.addChild(objectsContainer3);
 
         //*******************鎷栧姩鐢诲竷*******************
         let stageOriginalPos;
@@ -285,16 +303,22 @@
         pixiApp.view.addEventListener('wheel', (event) => {
           event.stopPropagation();
           event.preventDefault();
-          const sx = event.clientX;
-          const sy = event.clientY;
-          const oldZoom = pixiApp.stage.scale.x;
+          const rect = pixiApp.view.getBoundingClientRect();
+          const sx = event.clientX - rect.left;
+          const sy = event.clientY - rect.top;
+          const oldZoomX = pixiApp.stage.scale.x || 1;
+          const oldZoomY = pixiApp.stage.scale.y || 1;
+          const oldZoomAbs = Math.abs(oldZoomX) || 1;
           const delta = event.deltaY;
-          let newZoom = oldZoom * 0.999 ** delta;
-          const worldX = (sx - pixiApp.stage.position.x) / oldZoom;
-          const worldY = (sy - pixiApp.stage.position.y) / oldZoom;
-          const newPosX = sx - worldX * newZoom;
-          const newPosY = sy - worldY * newZoom;
-          pixiApp.stage.setTransform(newPosX, newPosY, newZoom, newZoom, 0, 0, 0, 0, 0);
+          let newZoomAbs = oldZoomAbs * 0.999 ** delta;
+          const mirrorX = this.mapMirrorX ? -1 : 1;
+          const newZoomX = mirrorX * newZoomAbs;
+          const newZoomY = newZoomAbs;
+          const worldX = (sx - pixiApp.stage.position.x) / oldZoomX;
+          const worldY = (sy - pixiApp.stage.position.y) / oldZoomY;
+          const newPosX = sx - worldX * newZoomX;
+          const newPosY = sy - worldY * newZoomY;
+          pixiApp.stage.setTransform(newPosX, newPosY, newZoomX, newZoomY, 0, 0, 0, 0, 0);
 
         });
         //*******************缂╂斁鐢诲竷*******************
@@ -328,6 +352,7 @@
           pixiStageList = [map.length]//鍒濆鍖栧垪琛�
           pixiStaMap = new Map();//閲嶇疆
           objectsContainer.removeChildren()
+          if (tracksGraphics) { tracksGraphics.clear(); }
           map.forEach((item,index) => {
             pixiStageList[index] = [item.length]
             for (let idx = 0; idx < item.length; idx++) {
@@ -388,21 +413,160 @@
           });
 
           const b1 = objectsContainer.getLocalBounds();
-          const minX = Math.min(b1.x);
-          const minY = Math.min(b1.y);
-          const maxX = Math.max(b1.x + b1.width);
-          const maxY = Math.max(b1.y + b1.height);
+          const minX = b1.x;
+          const minY = b1.y;
+          const maxX = b1.x + b1.width;
+          const maxY = b1.y + b1.height;
           const contentW = Math.max(0, maxX - minX);
           const contentH = Math.max(0, maxY - minY);
-          const vw = pixiApp.view.width;
-          const vh = pixiApp.view.height;
-          let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
-          if (!isFinite(scale) || scale <= 0) { scale = 1; }
-          const posX = (vw - contentW * scale) / 2 - minX * scale;
-          const posY = (vh - contentH * scale) / 2 - minY * scale;
-          pixiApp.stage.setTransform(posX, posY, scale, scale, 0, 0, 0, 0, 0);
+          mapContentSize = { width: contentW, height: contentH };
+          this.drawTracks(map);
+          this.applyMapTransform(true);
         }
         this.map = map;
+      },
+      isTrackCell(cell) {
+        if (!cell) { return false; }
+        const type = cell.type ? String(cell.type).toLowerCase() : '';
+        if (type === 'track' || type === 'crn' || type === 'dualcrn' || type === 'rgv') { return true; }
+        if (cell.trackSiteNo != null) { return true; }
+        const v = parseInt(cell.value, 10);
+        if (v === 3 || v === 9) { return true; }
+        if (cell.value != null) {
+          try {
+            const obj = (typeof cell.value === 'string') ? JSON.parse(cell.value) : cell.value;
+            if (obj && (obj.trackSiteNo != null || (obj.deviceNo != null && (type === 'crn' || type === 'dualcrn' || type === 'rgv')))) {
+              return true;
+            }
+          } catch (e) {}
+        }
+        return false;
+      },
+      drawTracks(map) {
+        if (!tracksGraphics || !Array.isArray(map)) { return; }
+        tracksGraphics.clear();
+        const railColor = 0x6c727a;
+        const railWidth = Math.max(1, Math.round(Math.min(width, height) * 0.08));
+        tracksGraphics.lineStyle(railWidth, railColor, 1);
+
+        for (let r = 0; r < map.length; r++) {
+          const row = map[r];
+          if (!Array.isArray(row)) { continue; }
+          for (let c = 0; c < row.length; c++) {
+            const cell = row[c];
+            if (!this.isTrackCell(cell)) { continue; }
+            const cx = c * width + width / 2;
+            const cy = r * height + height / 2;
+            const up = (r - 1 >= 0 && Array.isArray(map[r - 1])) ? map[r - 1][c] : null;
+            const right = (c + 1 < row.length) ? row[c + 1] : null;
+            const down = (r + 1 < map.length && Array.isArray(map[r + 1])) ? map[r + 1][c] : null;
+            const left = (c - 1 >= 0) ? row[c - 1] : null;
+            const hasN = this.isTrackCell(up);
+            const hasE = this.isTrackCell(right);
+            const hasS = this.isTrackCell(down);
+            const hasW = this.isTrackCell(left);
+            const seg = Math.min(width, height) * 0.5;
+            let drew = false;
+            if (hasN) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx, cy - seg); drew = true; }
+            if (hasE) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx + seg, cy); drew = true; }
+            if (hasS) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx, cy + seg); drew = true; }
+            if (hasW) { tracksGraphics.moveTo(cx, cy); tracksGraphics.lineTo(cx - seg, cy); drew = true; }
+            if (!drew) {
+              tracksGraphics.moveTo(cx - seg * 0.4, cy);
+              tracksGraphics.lineTo(cx + seg * 0.4, cy);
+            }
+          }
+        }
+      },
+      parseRotation(value) {
+        const num = parseInt(value, 10);
+        if (!isFinite(num)) { return 0; }
+        const rot = ((num % 360) + 360) % 360;
+        return (rot === 90 || rot === 180 || rot === 270) ? rot : 0;
+      },
+      parseMirror(value) {
+        if (value === true || value === false) { return value; }
+        if (value == null) { return false; }
+        const str = String(value).toLowerCase();
+        return str === '1' || str === 'true' || str === 'y';
+      },
+      loadMapTransformConfig() {
+        if (!window.$ || typeof baseUrl === 'undefined') { return; }
+        $.ajax({
+          url: baseUrl + "/config/listAll/auth",
+          headers: { 'token': localStorage.getItem('token') },
+          dataType: 'json',
+          method: 'GET',
+          success: (res) => {
+            if (!res || res.code !== 200 || !Array.isArray(res.data)) {
+              if (res && res.code === 403) { parent.location.href = baseUrl + "/login"; }
+              return;
+            }
+            const byCode = {};
+            res.data.forEach((item) => {
+              if (item && item.code) { byCode[item.code] = item; }
+            });
+            const rotateCfg = byCode[this.mapConfigCodes.rotate];
+            const mirrorCfg = byCode[this.mapConfigCodes.mirror];
+            if (rotateCfg && rotateCfg.value != null) {
+              this.mapRotation = this.parseRotation(rotateCfg.value);
+            }
+            if (mirrorCfg && mirrorCfg.value != null) {
+              this.mapMirrorX = this.parseMirror(mirrorCfg.value);
+            }
+            if (mapContentSize && mapContentSize.width > 0 && mapContentSize.height > 0) {
+              this.applyMapTransform(true);
+            }
+          }
+        });
+      },
+      getViewportSize() {
+        if (!pixiApp || !pixiApp.renderer) { return { width: 0, height: 0 }; }
+        const screen = pixiApp.renderer.screen;
+        if (screen && screen.width > 0 && screen.height > 0) {
+          return { width: screen.width, height: screen.height };
+        }
+        const rect = pixiApp.view ? pixiApp.view.getBoundingClientRect() : null;
+        return { width: rect ? rect.width : 0, height: rect ? rect.height : 0 };
+      },
+      getTransformedContentSize() {
+        const size = mapContentSize || { width: 0, height: 0 };
+        const w = size.width || 0;
+        const h = size.height || 0;
+        const rot = ((this.mapRotation % 360) + 360) % 360;
+        const swap = rot === 90 || rot === 270;
+        return { width: swap ? h : w, height: swap ? w : h };
+      },
+      fitStageToContent() {
+        if (!pixiApp || !mapContentSize) { return; }
+        const size = this.getTransformedContentSize();
+        const contentW = size.width || 0;
+        const contentH = size.height || 0;
+        if (contentW <= 0 || contentH <= 0) { return; }
+        const viewport = this.getViewportSize();
+        const vw = viewport.width;
+        const vh = viewport.height;
+        let scale = Math.min(vw / contentW, vh / contentH) * 0.95;
+        if (!isFinite(scale) || scale <= 0) { scale = 1; }
+        const baseW = mapContentSize.width || contentW;
+        const baseH = mapContentSize.height || contentH;
+        const mirrorX = this.mapMirrorX ? -1 : 1;
+        const scaleX = scale * mirrorX;
+        const scaleY = scale;
+        const posX = (vw / 2) - (baseW / 2) * scaleX;
+        const posY = (vh / 2) - (baseH / 2) * scaleY;
+        pixiApp.stage.setTransform(posX, posY, scaleX, scaleY, 0, 0, 0, 0, 0);
+      },
+      applyMapTransform(fitToView) {
+        if (!mapRoot || !mapContentSize) { return; }
+        const contentW = mapContentSize.width || 0;
+        const contentH = mapContentSize.height || 0;
+        if (contentW <= 0 || contentH <= 0) { return; }
+        mapRoot.pivot.set(contentW / 2, contentH / 2);
+        mapRoot.position.set(contentW / 2, contentH / 2);
+        mapRoot.rotation = (this.mapRotation % 360) * Math.PI / 180;
+        mapRoot.scale.set(1, 1);
+        if (fitToView) { this.fitStageToContent(); }
       },
       rightEvent(x, y, e) {
         this.drawerLocNo = true
@@ -520,12 +684,16 @@
       sprite = new PIXI.Sprite(graphics0);
     }
     sprite.position.set(x, y);
-    sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
-    sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚閽�
-
-    sprite.on('pointerdown', (e) => {
-      pointerDownEvent(e)
-    })
+    const type = item && item.type ? String(item.type).toLowerCase() : '';
+    const numVal = parseInt(value, 10);
+    const isTrackCell = numVal === 3 || numVal === 9 || type === 'track' || type === 'crn' || type === 'dualcrn' || type === 'rgv';
+    if (!isTrackCell) {
+      sprite.interactive = true; // 蹇呴』瑕佽缃墠鑳芥帴鏀朵簨浠�
+      sprite.buttonMode = true; // 璁╁厜鏍囧湪hover鏃跺彉涓烘墜鍨嬫寚閽�
+      sprite.on('pointerdown', (e) => {
+        pointerDownEvent(e)
+      })
+    }
 
     return sprite;
   }
@@ -542,4 +710,4 @@
 
 </script>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/main/webapp/views/login.html b/src/main/webapp/views/login.html
index 96fe62b..d82ce9a 100644
--- a/src/main/webapp/views/login.html
+++ b/src/main/webapp/views/login.html
@@ -184,7 +184,6 @@
         $('#btn-server-info').click(function() {
             $.ajax({
                 url: baseUrl + "/license/getServerInfos",
-                headers: {'token': localStorage.getItem('token')},
                 method: 'GET',
                 success: function (res) {
                     var pretty = '';
@@ -254,7 +253,6 @@
                 layer.close(index);
                 $.ajax({
                     url: baseUrl + "/license/activate",
-                    headers: {'token': localStorage.getItem('token')},
                     method: 'POST',
                     success: function (res) {
                         if (res.code === 200){
@@ -275,7 +273,6 @@
         $('#btn-project-name').click(function() {
             $.ajax({
                 url: baseUrl + "/license/getProjectName",
-                headers: {'token': localStorage.getItem('token')},
                 method: 'GET',
                 success: function (res) {
                     if (res.code === 200){
diff --git a/src/main/webapp/views/watch/console.html b/src/main/webapp/views/watch/console.html
index 12f2c63..3dbcc3c 100644
--- a/src/main/webapp/views/watch/console.html
+++ b/src/main/webapp/views/watch/console.html
@@ -1,15 +1,23 @@
 <!DOCTYPE html>
 <html lang="en">
-		<head>
-			<meta charset="UTF-8">
-			<title>WCS鎺у埗涓績</title>
-			<link rel="stylesheet" href="../../static/css/animate.min.css">
-			<link rel="stylesheet" href="../../static/vue/element/element.css">
-			<link rel="stylesheet" href="../../static/css/watch/console_vue.css">
-			<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
-			<script type="text/javascript" src="../../static/layui/layui.js"></script>
-			<script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
-			<script type="text/javascript" src="../../static/js/common.js"></script>
+			<head>
+				<meta charset="UTF-8">
+				<title>WCS鎺у埗涓績</title>
+				<link rel="stylesheet" href="../../static/css/animate.min.css">
+				<link rel="stylesheet" href="../../static/vue/element/element.css">
+				<link rel="stylesheet" href="../../static/css/watch/console_vue.css">
+				<style>
+					html, body, #app {
+						width: 100%;
+						height: 100%;
+						margin: 0;
+						overflow: hidden;
+					}
+				</style>
+				<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
+				<script type="text/javascript" src="../../static/layui/layui.js"></script>
+				<script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
+				<script type="text/javascript" src="../../static/js/common.js"></script>
 			<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
 			<script type="text/javascript" src="../../static/vue/element/element.js"></script>
 			<script src="../../static/js/gsap.min.js"></script>

--
Gitblit v1.9.1