b74b172daac9b2b21af1149bfcfb935bc879e5ef..411ff551ae7641dfc5c9331e99bf8b6e5770e2fa
9 天以前 Junjie
#mcp
411ff5 对比 | 目录
9 天以前 Junjie
#
f5e49b 对比 | 目录
13个文件已添加
19个文件已修改
2111 ■■■■■ 已修改文件
src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/controller/McpController.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/WcsQueryService.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/DualCrnController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/BasCrnp.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/BasDualCrnp.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/WrkMast.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/WrkMastLog.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/utils/Utils.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/entity/FindCrnNoResult.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/service/CommonService.java 156 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/RedisKeyType.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/plugin/FakeProcess.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/WmsOperateUtils.java 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/WrkMastLogMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/WrkMastMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basDualCrnp/basDualCrnp.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/wrkMastLog/wrkMastLog.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basDualCrnp/basDualCrnp.html 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/wrkMast/wrkMast.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
New file
@@ -0,0 +1,232 @@
package com.zy.ai.mcp.config;
import com.alibaba.fastjson.JSONObject;
import com.zy.ai.mcp.dto.McpToolHandler;
import com.zy.ai.mcp.dto.ToolDefinition;
import com.zy.ai.mcp.dto.ToolRegistry;
import com.zy.ai.mcp.service.WcsDataFacade;
import java.util.*;
public class McpToolsBootstrap {
    public static void registerAll(ToolRegistry registry, final WcsDataFacade facade) {
        registry.register(tool(
                "device.get_crn_status",
                "Query realtime status of a crn device by deviceNo.",
                schemaObj(
                        propInt("crnNos", true)
                ),
                schemaObj(
                        propObj("devices", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getCrnDeviceStatus(args);
                    }
                }
        ));
        registry.register(tool(
                "device.get_station_status",
                "Query realtime status of a station device",
                schemaObj(
                ),
                schemaObj(
                        propObj("stations", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getStationDeviceStatus(args);
                    }
                }
        ));
        registry.register(tool(
                "device.get_rgv_status",
                "Query realtime status of a rgv device by deviceNo.",
                schemaObj(
                        propInt("rgvNos", true)
                ),
                schemaObj(
                        propObj("devices", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getRgvDeviceStatus(args);
                    }
                }
        ));
        registry.register(tool(
                "task.list",
                "List tasks by filters (status/CrnDevice/RgvDevice//time window).",
                schemaObj(
                        propInt("crnNo", false),
                        propInt("rgvNo", false),
                        propArr("taskNos", false, "integer"),
                        propInt("limit", false)
                ),
                schemaObj(propArr("tasks", true, "object")),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getTasks(args);
                    }
                }
        ));
        registry.register(tool(
                "log.query",
                "Query logs by keyword/level/time window/device/task. Return clipped log lines.",
                schemaObj(
                        propInt("limit", false)
                ),
                schemaObj(propArr("logs", true, "object")),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getLogs(args);
                    }
                }
        ));
        registry.register(tool(
                "config.get_device_config",
                "Get device config by deviceCode.",
                schemaObj(
                        propArr("crnNos", false, "integer"),
                        propArr("rgvNos", false, "integer"),
                        propArr("devpNos", false, "integer")
                ),
                schemaObj(propObj("deviceConfigs", true)),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getDeviceConfig(args);
                    }
                }
        ));
        registry.register(tool(
                "config.get_system_config",
                "Get key system configs for diagnosis.",
                schemaObj(
                ),
                schemaObj(
                        propObj("systemConfigs", true)
                ),
                new McpToolHandler() {
                    public Object handle(JSONObject args) {
                        return facade.getSystemConfig(args);
                    }
                }
        ));
//        // ★ 诊断聚合快照:一次拿全
//        registry.register(tool(
//                "build_diagnosis_snapshot",
//                "Aggregate diagnosis snapshot: tasks + device realtime + configs + clipped logs for diagnosis.",
//                schemaObj(
//                        propStr("warehouseCode", true),
//                        propArr("deviceCodes", false, "string"),     // 不传则按任务涉及设备推导
//                        propStr("taskNo", false),
//                        propStr("fromTime", true),
//                        propStr("toTime", true),
//                        propInt("taskLimit", false),
//                        propInt("logLimit", false),
//                        propArr("logKeywords", false, "string"),
//                        propBool("includeSystemConfig", false),
//                        propBool("includeDeviceConfig", false)
//                ),
//                schemaObj(
//                        propObj("snapshot", true),
//                        propArr("hints", false, "string")
//                ),
//                new McpToolHandler() {
//                    public Object handle(JSONObject args) {
//                        return facade.buildDiagnosisSnapshot(args);
//                    }
//                }
//        ));
    }
    // ---------- schema helpers ----------
    private static ToolDefinition tool(String name, String desc,
                                       Map<String, Object> in, Map<String, Object> out,
                                       McpToolHandler handler) {
        ToolDefinition d = new ToolDefinition();
        d.setName(name);
        d.setDescription(desc);
        d.setInputSchema(in);
        d.setOutputSchema(out);
        d.setHandler(handler);
        return d;
    }
    private static Map<String, Object> schemaObj(Object... props) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("type", "object");
        Map<String, Object> properties = new LinkedHashMap<String, Object>();
        List<String> required = new ArrayList<String>();
        for (Object p : props) {
            @SuppressWarnings("unchecked")
            Map<String, Object> pm = (Map<String, Object>) p;
            String name = String.valueOf(pm.get("name"));
            boolean req = Boolean.TRUE.equals(pm.get("required"));
            pm.remove("name");
            pm.remove("required");
            properties.put(name, pm);
            if (req) required.add(name);
        }
        m.put("properties", properties);
        if (!required.isEmpty()) m.put("required", required);
        return m;
    }
    private static Map<String, Object> propStr(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "string");
        return m;
    }
    private static Map<String, Object> propInt(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "integer");
        return m;
    }
    private static Map<String, Object> propBool(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "boolean");
        return m;
    }
    private static Map<String, Object> propObj(String name, boolean required) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "object");
        return m;
    }
    private static Map<String, Object> propArr(String name, boolean required, String itemType) {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("required", required);
        m.put("type", "array");
        Map<String, Object> items = new LinkedHashMap<String, Object>();
        items.put("type", itemType);
        m.put("items", items);
        return m;
    }
}
src/main/java/com/zy/ai/mcp/controller/McpController.java
New file
@@ -0,0 +1,90 @@
package com.zy.ai.mcp.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.ai.mcp.config.McpToolsBootstrap;
import com.zy.ai.mcp.dto.JsonRpcRequest;
import com.zy.ai.mcp.dto.JsonRpcResponse;
import com.zy.ai.mcp.dto.ToolDefinition;
import com.zy.ai.mcp.dto.ToolRegistry;
import com.zy.ai.mcp.service.WcsDataFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/ai/mcp")
public class McpController {
    private final ToolRegistry registry = new ToolRegistry();
    @Autowired
    private WcsDataFacade wcsDataFacade;
    public McpController(WcsDataFacade wcsDataFacade) {
        this.wcsDataFacade = wcsDataFacade;
    }
    @PostConstruct
    public void init() {
        McpToolsBootstrap.registerAll(registry, wcsDataFacade);
        log.info("MCP initialized, tools={}", registry.listTools().size());
    }
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public Object handle(@RequestBody JsonRpcRequest req,
                         @RequestHeader(value = "Authorization", required = false) String auth) {
        // (建议)做一个简单鉴权:防止被随便调用生产系统
        // if (!"Bearer your-token".equals(auth)) return JsonRpcResponse.err(null, 401, "Unauthorized", null);
        String id = req.getId();
        String method = req.getMethod();
        JSONObject params = JSON.parseObject(JSON.toJSONString(req.getParams()));
        try {
            if ("initialize".equals(method)) {
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                result.put("serverName", "wcs-mcp");
                result.put("serverVersion", "1.0.0");
                result.put("capabilities", Arrays.asList("tools"));
                return JsonRpcResponse.ok(id, result);
            }
            if ("tools/list".equals(method)) {
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                result.put("tools", registry.listTools());
                // cursor/paging 你后面需要再加
                return JsonRpcResponse.ok(id, result);
            }
            if ("tools/call".equals(method)) {
                String toolName = params.getString("name");
                JSONObject arguments = params.getJSONObject("arguments");
                if (toolName == null || toolName.trim().isEmpty()) {
                    return JsonRpcResponse.err(id, -32602, "Invalid params: missing tool name", null);
                }
                ToolDefinition def = registry.get(toolName);
                if (def == null) {
                    return JsonRpcResponse.err(id, -32601, "Method not found: tool " + toolName, null);
                }
                Object output = def.getHandler().handle(arguments == null ? new JSONObject() : arguments);
                Map<String, Object> result = new LinkedHashMap<String, Object>();
                result.put("content", output); // 你也可以按 MCP 常见返回结构做 text/json 分段
                return JsonRpcResponse.ok(id, result);
            }
            return JsonRpcResponse.err(id, -32601, "Method not found: " + method, null);
        } catch (Exception e) {
            log.error("MCP handle error, method={}, params={}", method, params, e);
            return JsonRpcResponse.err(id, -32000, "Server error", e.getMessage());
        }
    }
}
src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java
New file
@@ -0,0 +1,16 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
@Data
class JsonRpcError {
    private int code;
    private String message;
    private Object data;
    public JsonRpcError(int code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
}
src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java
New file
@@ -0,0 +1,13 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
import java.util.Map;
@Data
public class JsonRpcRequest {
    private String jsonrpc; // "2.0"
    private String id;      // string/number 都行,这里用 string
    private String method;  // "tools/list" | "tools/call"
    private Map<String, Object> params;
}
src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java
New file
@@ -0,0 +1,25 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
@Data
public class JsonRpcResponse {
    private String jsonrpc = "2.0";
    private String id;
    private Object result;
    private JsonRpcError error;
    public static JsonRpcResponse ok(String id, Object result) {
        JsonRpcResponse r = new JsonRpcResponse();
        r.id = id;
        r.result = result;
        return r;
    }
    public static JsonRpcResponse err(String id, int code, String message, Object data) {
        JsonRpcResponse r = new JsonRpcResponse();
        r.id = id;
        r.error = new JsonRpcError(code, message, data);
        return r;
    }
}
src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java
New file
@@ -0,0 +1,7 @@
package com.zy.ai.mcp.dto;
import com.alibaba.fastjson.JSONObject;
public interface McpToolHandler {
    Object handle(JSONObject arguments) throws Exception;
}
src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java
New file
@@ -0,0 +1,25 @@
package com.zy.ai.mcp.dto;
import lombok.Data;
import java.util.*;
@Data
public class ToolDefinition {
    private String name;
    private String description;
    private Map<String, Object> inputSchema;   // JSON Schema as Map
    private Map<String, Object> outputSchema;  // JSON Schema as Map
    private McpToolHandler handler;
    public Map<String, Object> toMcpToolJson() {
        Map<String, Object> m = new LinkedHashMap<String, Object>();
        m.put("name", name);
        m.put("description", description);
        m.put("inputSchema", inputSchema);
        m.put("outputSchema", outputSchema);
        return m;
    }
}
src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java
New file
@@ -0,0 +1,30 @@
package com.zy.ai.mcp.dto;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ToolRegistry {
    private final Map<String, ToolDefinition> tools = new ConcurrentHashMap<String, ToolDefinition>();
    public void register(ToolDefinition def) {
        tools.put(def.getName(), def);
    }
    public List<Map<String, Object>> listTools() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (ToolDefinition def : tools.values()) {
            list.add(def.toMcpToolJson());
        }
        // 为了稳定输出,按 name 排序
        Collections.sort(list, new Comparator<Map<String, Object>>() {
            public int compare(Map<String, Object> a, Map<String, Object> b) {
                return String.valueOf(a.get("name")).compareTo(String.valueOf(b.get("name")));
            }
        });
        return list;
    }
    public ToolDefinition get(String name) {
        return tools.get(name);
    }
}
src/main/java/com/zy/ai/mcp/service/WcsDataFacade.java
New file
@@ -0,0 +1,25 @@
package com.zy.ai.mcp.service;
import com.alibaba.fastjson.JSONObject;
public interface WcsDataFacade {
    Object getCrnDeviceStatus(JSONObject args);
    Object getStationDeviceStatus(JSONObject args);
    Object getRgvDeviceStatus(JSONObject args);
    Object getTasks(JSONObject args);
    Object getLogs(JSONObject args);
    Object getDeviceConfig(JSONObject args);
    Object getSystemConfig(JSONObject args);
    /**
     * ★ 聚合快照:核心诊断输入
     */
    Object buildDiagnosisSnapshot(JSONObject args);
}
src/main/java/com/zy/ai/mcp/service/impl/WcsDataFacadeImpl.java
New file
@@ -0,0 +1,324 @@
package com.zy.ai.mcp.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.ai.entity.DeviceConfigsData;
import com.zy.ai.log.AiLogAppender;
import com.zy.ai.mcp.service.WcsDataFacade;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.BasRgv;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.BasRgvService;
import com.zy.asrs.service.WrkMastService;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.RgvProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.RgvThread;
import com.zy.core.thread.StationThread;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service("wcsDataFacade")
@RequiredArgsConstructor
public class WcsDataFacadeImpl implements WcsDataFacade {
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private BasRgvService basRgvService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private ConfigService configService;
    @Override
    public Object getCrnDeviceStatus(JSONObject args) {
        List<Integer> deviceNoList = optIntList(args, "crnNos");
        EntityWrapper<BasCrnp> wrapper = new EntityWrapper<>();
        if (deviceNoList != null && deviceNoList.size() > 0) {
            wrapper.in("crn_no", deviceNoList);
        }
        JSONObject data = new JSONObject();
        List<Object> deviceList = new ArrayList<>();
        List<BasCrnp> basCrnps = basCrnpService.selectList(wrapper);
        for (BasCrnp basCrnp : basCrnps) {
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                continue;
            }
            CrnProtocol protocol = crnThread.getStatus();
            deviceList.add(protocol);
        }
        data.put("devices", deviceList);
        return data;
    }
    @Override
    public Object getStationDeviceStatus(JSONObject args) {
        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
        JSONObject data = new JSONObject();
        List<Object> stationList = new ArrayList<>();
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> map = stationThread.getStatusMap();
            for (StationObjModel stationObjModel : basDevp.getStationList$()) {
                StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                stationList.add(stationProtocol);
            }
        }
        data.put("stations", stationList);
        return data;
    }
    @Override
    public Object getRgvDeviceStatus(JSONObject args) {
        List<Integer> deviceNoList = optIntList(args, "rgvNos");
        EntityWrapper<BasRgv> wrapper = new EntityWrapper<>();
        if (deviceNoList != null && deviceNoList.size() > 0) {
            wrapper.in("rgv_no", deviceNoList);
        }
        JSONObject data = new JSONObject();
        List<Object> deviceList = new ArrayList<>();
        List<BasRgv> basRgvs = basRgvService.selectList(wrapper);
        for (BasRgv basRgv : basRgvs) {
            RgvThread rgvThread = (RgvThread) SlaveConnection.get(SlaveType.Rgv, basRgv.getRgvNo());
            if (rgvThread == null) {
                continue;
            }
            RgvProtocol rgvProtocol = rgvThread.getStatus();
            deviceList.add(rgvProtocol);
        }
        data.put("devices", deviceList);
        return data;
    }
    @Override
    public Object getTasks(JSONObject args) {
        int crnNo = optInt(args, "crnNo", -1);
        int rgvNo = optInt(args, "rgvNo", -1);
        List<Integer> taskNos = optIntList(args, "taskNos");
        int limit = optInt(args, "limit", 200);
        EntityWrapper<WrkMast> wrapper = new EntityWrapper<>();
        if (taskNos != null && taskNos.size() > 0) {
            wrapper.in("wrk_no", taskNos);
        }
        if (crnNo != -1) {
            wrapper.eq("crn_no", crnNo);
        }
        if (rgvNo != -1) {
            wrapper.eq("rgv_no", rgvNo);
        }
        List<WrkMast> tasks = wrkMastService.selectList(wrapper);
        JSONObject data = new JSONObject();
        data.put("tasks", tasks);
        return data;
    }
    @Override
    public Object getLogs(JSONObject args) {
        int limit = optInt(args, "limit", 500);
        List<String> logs = AiLogAppender.getRecentLogs(limit);
        JSONObject data = new JSONObject();
        data.put("logs", logs);
        return data;
    }
    @Override
    public Object getDeviceConfig(JSONObject args) {
        JSONObject data = new JSONObject();
        List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
        List<Integer> crnNoList = optIntList(args, "crnNos");
        EntityWrapper<BasCrnp> crnWrapper = new EntityWrapper<>();
        if (crnNoList != null && crnNoList.size() > 0) {
            crnWrapper.in("crn_no", crnNoList);
        }
        List<Integer> rgvNoList = optIntList(args, "rgvNos");
        EntityWrapper<BasRgv> rgvWrapper = new EntityWrapper<>();
        if (rgvNoList != null && rgvNoList.size() > 0) {
            rgvWrapper.in("rgv_no", rgvNoList);
        }
        List<Integer> devpNoList = optIntList(args, "devpNos");
        EntityWrapper<BasDevp> devpWrapper = new EntityWrapper<>();
        if (devpNoList != null && devpNoList.size() > 0) {
            devpWrapper.in("devp_no", devpNoList);
        }
        List<BasCrnp> basCrnps = basCrnpService.selectList(crnWrapper);
        for (BasCrnp basCrnp : basCrnps) {
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basCrnp.getCrnNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Crn));
            deviceConfigsData.setDeviceData(basCrnp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        List<BasRgv> basRgvs = basRgvService.selectList(rgvWrapper);
        for (BasRgv basRgv : basRgvs) {
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basRgv.getRgvNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Rgv));
            deviceConfigsData.setDeviceData(basRgv);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        List<BasDevp> basDevps = basDevpService.selectList(devpWrapper);
        for (BasDevp basDevp : basDevps) {
            DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
            deviceConfigsData.setDeviceNo(basDevp.getDevpNo());
            deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Devp));
            deviceConfigsData.setDeviceData(basDevp);
            deviceConfigsDataList.add(deviceConfigsData);
        }
        data.put("deviceConfigs", deviceConfigsDataList);
        return data;
    }
    @Override
    public Object getSystemConfig(JSONObject args) {
        JSONObject data = new JSONObject();
        List<Config> systemConfigList = configService.selectList(new EntityWrapper<Config>().notIn("dingdingReportUrl"));
        data.put("systemConfigs", systemConfigList);
        return data;
    }
    @Override
    public Object buildDiagnosisSnapshot(JSONObject args) {
        String wh = mustStr(args, "warehouseCode");
        List<String> crnDeviceNos = optStrList(args, "crnDeviceNos");
        List<String> taskIds = optStrList(args, "taskIds");
        int lookbackSeconds = optInt(args, "lookbackSeconds", 300);
        int logMaxLines = optInt(args, "logMaxLines", 600);
        boolean includeConfig = optBool(args, "includeConfig", true);
        long now = System.currentTimeMillis();
        long fromTs = now - lookbackSeconds * 1000L;
        // 1) crn devices
        JSONObject devArgs = new JSONObject();
        devArgs.put("deviceNos", crnDeviceNos);
        JSONObject devices = (JSONObject) getCrnDeviceStatus(devArgs);
        // 2) tasks
        JSONObject taskArgs = new JSONObject();
        taskArgs.put("warehouseCode", wh);
        taskArgs.put("taskIds", taskIds);
        taskArgs.put("limit", 200);
        JSONObject tasks = (JSONObject) getTasks(taskArgs);
        // 3) logs (一次性取回,然后做分桶+排序+截断)
        JSONObject logArgs = new JSONObject();
        logArgs.put("warehouseCode", wh);
        logArgs.put("fromTs", fromTs);
        logArgs.put("toTs", now);
//        logArgs.put("deviceIds", deviceIds);
        logArgs.put("taskIds", taskIds);
        logArgs.put("maxLines", logMaxLines);
        JSONObject logs = (JSONObject) getLogs(logArgs);
        // 4) 结构化快照输出(建议:分桶)
        JSONObject snapshot = new JSONObject();
        snapshot.put("warehouseCode", wh);
        snapshot.put("generatedTs", now);
        snapshot.put("timeRange", new JSONObject()
                .fluentPut("fromTs", fromTs)
                .fluentPut("toTs", now)
                .fluentPut("lookbackSeconds", lookbackSeconds));
        snapshot.put("devices", devices);
        snapshot.put("tasks", tasks);
        snapshot.put("logs", logs);
        JSONArray hints = new JSONArray();
        hints.add("Prefer diagnosing with snapshot.devices + snapshot.tasks + snapshot.logs");
        hints.add("Logs are already filtered by time range; if missing, expand lookbackSeconds");
        snapshot.put("hints", hints);
        JSONObject data = new JSONObject();
        data.put("snapshot", snapshot);
        return data;
    }
    // --------- helpers ---------
    private String mustStr(JSONObject o, String key) {
        if (o == null || o.getString(key) == null || o.getString(key).trim().isEmpty())
            throw new IllegalArgumentException(key + " is required");
        return o.getString(key).trim();
    }
    private long mustLong(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) throw new IllegalArgumentException(key + " is required");
        return o.getLongValue(key);
    }
    private int optInt(JSONObject o, String key, int def) {
        if (o == null || !o.containsKey(key)) return def;
        return o.getIntValue(key);
    }
    private boolean optBool(JSONObject o, String key, boolean def) {
        if (o == null || !o.containsKey(key)) return def;
        return o.getBooleanValue(key);
    }
    private List<String> optStrList(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return Collections.emptyList();
        JSONArray arr = o.getJSONArray(key);
        if (arr == null) return Collections.emptyList();
        List<String> list = new ArrayList<>();
        for (int i = 0; i < arr.size(); i++) {
            String s = arr.getString(i);
            if (s != null && !s.trim().isEmpty()) list.add(s.trim());
        }
        return list;
    }
    private List<Integer> optIntList(JSONObject o, String key) {
        if (o == null || !o.containsKey(key)) return Collections.emptyList();
        JSONArray arr = o.getJSONArray(key);
        if (arr == null) return Collections.emptyList();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < arr.size(); i++) {
            String s = arr.getString(i);
            if (s != null && !s.trim().isEmpty()) list.add(Integer.parseInt(s.trim()));
        }
        return list;
    }
}
src/main/java/com/zy/ai/service/WcsQueryService.java
New file
@@ -0,0 +1,129 @@
package com.zy.ai.service;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.*;
@Service
public class WcsQueryService {
    // 这里注入你现有的各类 Service/Mapper:deviceService/taskService/logService/configService...
    public Map<String, Object> buildDiagnosisSnapshot(Map<String, Object> args) {
        Map<String, Object> focus = (Map<String, Object>) args.get("focus");
        List<String> deviceIds = focus == null ? Collections.emptyList() : (List<String>) focus.get("deviceIds");
        List<String> taskIds   = focus == null ? Collections.emptyList() : (List<String>) focus.get("taskIds");
        int timeWindowSec = getInt(args, "timeWindowSec", 600);
        int logLimit      = getInt(args, "logLimit", 400);
        boolean includeConfigs = getBool(args, "includeConfigs", true);
        boolean includeDerived = getBool(args, "includeDerivedSignals", true);
        Date now = new Date();
        Date from = new Date(now.getTime() - timeWindowSec * 1000L);
        // 1) devices
        List<Object> devices = new ArrayList<>();
        for (String deviceId : safeList(deviceIds)) {
            Map<String, Object> a = new HashMap<>();
            a.put("deviceId", deviceId);
            devices.add(getDeviceState(a));
        }
        // 2) tasks
        Map<String, Object> taskArgs = new HashMap<>();
        if (!safeList(taskIds).isEmpty()) {
            // 你可以让 listTasks 支持 taskIds 精确过滤
            taskArgs.put("taskIds", taskIds);
        } else if (!safeList(deviceIds).isEmpty()) {
            taskArgs.put("deviceId", deviceIds.get(0)); // 或改成支持 deviceIds 数组
            taskArgs.put("statuses", Arrays.asList("WAITING","RUNNING","SUSPENDED"));
            taskArgs.put("limit", 200);
        }
        Map<String, Object> taskResult = (Map<String, Object>) listTasks(taskArgs);
        List<Object> tasks = taskResult == null ? new ArrayList<>() : (List<Object>) taskResult.get("tasks");
        // 3) logs
        Map<String, Object> logArgs = new HashMap<>();
        logArgs.put("timeFrom", iso(from));
        logArgs.put("timeTo", iso(now));
        logArgs.put("limit", logLimit);
        if (!safeList(deviceIds).isEmpty()) logArgs.put("deviceId", deviceIds.get(0));
        if (!safeList(taskIds).isEmpty())   logArgs.put("taskId", taskIds.get(0));
        Map<String, Object> logResult = (Map<String, Object>) queryLogs(logArgs);
        List<Object> logs = logResult == null ? new ArrayList<>() : (List<Object>) logResult.get("logs");
        // 4) configs
        Object configs = null;
        if (includeConfigs) {
            configs = getSystemConfig(new HashMap<String, Object>());
        }
        // 5) derived
        Object derived = null;
        if (includeDerived) {
            derived = deriveSignals(devices, tasks, logs);
        }
        // output
        Map<String, Object> out = new LinkedHashMap<>();
        Map<String, Object> meta = new LinkedHashMap<>();
        meta.put("snapshotId", UUID.randomUUID().toString());
        meta.put("generatedAt", iso(now));
        meta.put("timeFrom", iso(from));
        meta.put("timeTo", iso(now));
        out.put("meta", meta);
        out.put("devices", devices);
        out.put("tasks", tasks);
        out.put("logs", logs);
        out.put("configs", configs);
        out.put("derivedSignals", derived);
        return out;
    }
    private Map<String, Object> deriveSignals(List<Object> devices, List<Object> tasks, List<Object> logs) {
        Map<String, Object> d = new LinkedHashMap<>();
        List<String> flags = new ArrayList<>();
        List<String> causes = new ArrayList<>();
        List<String> bottlenecks = new ArrayList<>();
        // TODO:把你 WCS 领域规则塞这里(心跳超时、等待确认、命令无ACK、站台满、下游阻塞等)
        // 先给个示例:
        if (tasks != null && !tasks.isEmpty() && (devices == null || devices.isEmpty())) {
            flags.add("HAS_TASKS_BUT_NO_DEVICE_SNAPSHOT");
        }
        d.put("anomalyFlags", flags);
        d.put("suspectedRootCauses", causes);
        d.put("suspectedBottlenecks", bottlenecks);
        return d;
    }
    // ======= 下面这些函数,你用现有 service 实现即可 =======
    public Map<String, Object> getDeviceState(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    public Map<String, Object> listTasks(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    public Map<String, Object> queryLogs(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    public Object getSystemConfig(Map<String, Object> args) { /* ... */ return new HashMap<>(); }
    // helpers
    private int getInt(Map<String, Object> m, String k, int def) {
        Object v = m.get(k);
        if (v == null) return def;
        if (v instanceof Number) return ((Number) v).intValue();
        return Integer.parseInt(String.valueOf(v));
    }
    private boolean getBool(Map<String, Object> m, String k, boolean def) {
        Object v = m.get(k);
        if (v == null) return def;
        if (v instanceof Boolean) return (Boolean) v;
        return Boolean.parseBoolean(String.valueOf(v));
    }
    private List<String> safeList(List<String> l) { return l == null ? Collections.emptyList() : l; }
    private String iso(Date d) {
        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
        f.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        return f.format(d);
    }
}
src/main/java/com/zy/asrs/controller/DualCrnController.java
@@ -103,7 +103,7 @@
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getPickAndPutCommand(sourceLocNo, targetLocNo, 9999, crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(3, command));
        return R.ok();
    }
@@ -121,7 +121,7 @@
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getPickCommand(targetLocNo, 9999, crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(3, command));
        return R.ok();
    }
@@ -139,7 +139,7 @@
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getPutCommand(targetLocNo, 9999, crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(3, command));
        return R.ok();
    }
    
@@ -161,7 +161,7 @@
        if (station != null) {
            command.setStation(station.shortValue());
        }
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(3, command));
        return R.ok();
    }
@@ -178,7 +178,7 @@
            return R.error("线程不存在");
        }
        DualCrnCommand command = crnThread.getResetCommand(crnNo, station);
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, command));
        MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(3, command));
        return R.ok();
    }
}
src/main/java/com/zy/asrs/entity/BasCrnp.java
@@ -6,7 +6,6 @@
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotations.TableField;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
src/main/java/com/zy/asrs/entity/BasDualCrnp.java
@@ -1,10 +1,17 @@
package com.zy.asrs.entity;
import com.core.common.Cools;import com.baomidou.mybatisplus.annotations.TableId;
import com.core.common.Cools;
import com.zy.core.model.StationObjModel;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.annotations.TableField;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;
import io.swagger.annotations.ApiModelProperty;
@@ -195,5 +202,43 @@
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
    }
    public List<StationObjModel> getInStationList$(){
        List<StationObjModel> list = new ArrayList<>();
        if (Cools.isEmpty(this.inStationList)){
            return list;
        }
        List<StationObjModel> jsonList = JSON.parseArray(this.inStationList,StationObjModel.class);
        for (StationObjModel json : jsonList){
            list.add(json);
        }
        return list;
    }
    public List<StationObjModel> getOutStationList$(){
        List<StationObjModel> list = new ArrayList<>();
        if (Cools.isEmpty(this.outStationList)){
            return list;
        }
        List<StationObjModel> jsonList = JSON.parseArray(this.outStationList,StationObjModel.class);
        for (StationObjModel json : jsonList){
            list.add(json);
        }
        return list;
    }
    public List<List<Integer>> getControlRows$(){
        List<List<Integer>> rowList = new ArrayList<>();
        if(Cools.isEmpty(this.controlRows)){
            return rowList;
        }
        JSONArray list = JSON.parseArray(this.controlRows);
        for (Object o : list) {
            List<Integer> rows = JSON.parseArray(JSON.toJSONString(o), Integer.class);
            rowList.add(rows);
        }
        return rowList;
    }
}
src/main/java/com/zy/asrs/entity/WrkMast.java
@@ -148,6 +148,13 @@
    private Integer crnNo;
    /**
     * 双工位堆垛机号
     */
    @ApiModelProperty(value= "双工位堆垛机号")
    @TableField(value = "dual_crn_no",strategy = FieldStrategy.IGNORED)
    private Integer dualCrnNo;
    /**
     * RGV号
     */
    @ApiModelProperty(value= "RGV号")
src/main/java/com/zy/asrs/entity/WrkMastLog.java
@@ -3,7 +3,6 @@
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.FieldStrategy;
import com.baomidou.mybatisplus.enums.IdType;
import com.core.common.Cools;
import com.core.common.SpringUtils;
@@ -153,6 +152,13 @@
    @TableField(value = "crn_no")
    private Integer crnNo;
     /**
     * 双工位堆垛机号
     */
    @ApiModelProperty(value= "双工位堆垛机号")
    @TableField(value = "dual_crn_no")
    private Integer dualCrnNo;
    /**
     * RGV号
     */
src/main/java/com/zy/asrs/utils/Utils.java
@@ -10,18 +10,22 @@
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.model.NavigateNode;
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.CrnModeType;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.SlaveType;
import com.zy.core.enums.WrkIoType;
import com.zy.core.enums.*;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.DualCrnThread;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
@@ -140,15 +144,28 @@
    }
    //获取入库任务可用排
    public static List<Integer> getInTaskEnableRow() {
        return getInTaskEnableRow(new ArrayList<>(), true);
    public static List<Integer> getInTaskEnableRow(Integer stationId) {
        return getInTaskEnableRow(stationId, new ArrayList<>(), new ArrayList<>(), true);
    }
    //获取入库任务可用排
    public static List<Integer> getInTaskEnableRow(List<Integer> excludeCrnList, boolean maxInTaskControl) {
    public static List<Integer> getInTaskEnableRow(Integer stationId, List<Integer> excludeCrnList, List<Integer> excludeDualCrnList, boolean maxInTaskControl) {
        //获取堆垛机入库任务可用排
        List<Integer> rowList = getCrnInTaskEnableRow(stationId, excludeCrnList, maxInTaskControl);
        //获取双工位堆垛机入库任务可用排
        List<Integer> dualRowList = getDualCrnInTaskEnableRow(stationId, excludeDualCrnList, maxInTaskControl);
        if (!dualRowList.isEmpty()) {
            rowList.addAll(dualRowList);
        }
        return rowList;
    }
    //获取堆垛机入库任务可用排
    public static List<Integer> getCrnInTaskEnableRow(Integer stationId, List<Integer> excludeCrnList, boolean maxInTaskControl) {
        List<Integer> list = new ArrayList<>();
        try {
            RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
            NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
            WrkMastService wrkMastService = SpringUtils.getBean(WrkMastService.class);
            BasCrnpService basCrnpService = SpringUtils.getBean(BasCrnpService.class);
@@ -209,7 +226,25 @@
                if (excludeCrnList.contains(basCrnp.getCrnNo())) {
                    continue;
                }
                enabledCrnps.add(basCrnp);
                //计算站点是否可达该堆垛机
                List<StationObjModel> inStationList = basCrnp.getInStationList$();
                boolean enableGo = false;
                for (StationObjModel stationObjModel : inStationList) {
                    try {
                        List<NavigateNode> navigateNodes = navigateUtils.calcByStationId(stationId, stationObjModel.getStationId());
                        if(navigateNodes != null && !navigateNodes.isEmpty()) {
                            enableGo = true;
                            break;
                        }
                    } catch (Exception e) {
                    }
                }
                if (enableGo) {
                    enabledCrnps.add(basCrnp);
                }
            }
            enabledCrnps.sort(Comparator.comparingInt(o -> map.getOrDefault(o.getCrnNo(), 0)));
@@ -226,6 +261,86 @@
        return list;
    }
    //获取双工位堆垛机入库任务可用排
    public static List<Integer> getDualCrnInTaskEnableRow(Integer stationId, List<Integer> excludeCrnList, boolean maxInTaskControl) {
        List<Integer> list = new ArrayList<>();
        try {
            RedisUtil redisUtil = SpringUtils.getBean(RedisUtil.class);
            NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
            WrkMastService wrkMastService = SpringUtils.getBean(WrkMastService.class);
            BasDualCrnpService basDualCrnpService = SpringUtils.getBean(BasDualCrnpService.class);
            Wrapper<BasDualCrnp> wrapper = new EntityWrapper<BasDualCrnp>()
                    .eq("in_enable", "Y")
                    .eq("status", 1);
            HashMap<Integer, Integer> map = new HashMap<>();
            List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<>());
            List<BasDualCrnp> basDualCrnps = basDualCrnpService.selectList(wrapper);
            for (WrkMast wrkMast : wrkMasts) {
                Integer dualCrnNo = wrkMast.getDualCrnNo();
                map.put(dualCrnNo, map.getOrDefault(dualCrnNo, 0) + 1);
            }
            List<BasDualCrnp> enabledCrnps = new ArrayList<>();
            for (BasDualCrnp basDualCrnp : basDualCrnps) {
                DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, basDualCrnp.getCrnNo());
                if (dualCrnThread == null) {
                    continue;
                }
                DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
                if (dualCrnProtocol.getMode() != DualCrnModeType.AUTO.id) {
                    continue;
                }
                List<WrkMast> inWrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                        .eq("dual_crn_no", basDualCrnp.getCrnNo())
                        .eq("io_type", WrkIoType.IN.id)
                );
                // 检查是否超过最大入库任务数
                if (maxInTaskControl && inWrkMasts.size() >= basDualCrnp.getMaxInTask()) {
                    News.info("堆垛机:{} 已达最大入库任务数,当前任务数:{}", basDualCrnp.getCrnNo(), inWrkMasts.size());
                    continue;
                }
                if (excludeCrnList.contains(basDualCrnp.getCrnNo())) {
                    continue;
                }
                //计算站点是否可达该堆垛机
                List<StationObjModel> inStationList = basDualCrnp.getInStationList$();
                boolean enableGo = false;
                for (StationObjModel stationObjModel : inStationList) {
                    try {
                        List<NavigateNode> navigateNodes = navigateUtils.calcByStationId(stationId, stationObjModel.getStationId());
                        if(navigateNodes != null && !navigateNodes.isEmpty()) {
                            enableGo = true;
                            break;
                        }
                    } catch (Exception e) {
                    }
                }
                if (enableGo) {
                    enabledCrnps.add(basDualCrnp);
                }
            }
            enabledCrnps.sort(Comparator.comparingInt(o -> map.getOrDefault(o.getCrnNo(), 0)));
            for (BasDualCrnp basDualCrnp : enabledCrnps) {
                List<List<Integer>> rowList = basDualCrnp.getControlRows$();
                for (List<Integer> rows : rowList) {
                    list.addAll(rows);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }
    public static Map<String, Object> convertObjectToMap(Object obj) {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = obj.getClass();
src/main/java/com/zy/common/entity/FindCrnNoResult.java
New file
@@ -0,0 +1,13 @@
package com.zy.common.entity;
import com.zy.core.enums.SlaveType;
import lombok.Data;
@Data
public class FindCrnNoResult {
    private Integer crnNo;
    private SlaveType crnType;
}
src/main/java/com/zy/common/service/CommonService.java
@@ -9,11 +9,13 @@
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.asrs.utils.Utils;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.model.NavigateNode;
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.model.StationObjModel;
@@ -21,6 +23,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -36,6 +39,8 @@
    private LocMastService locMastService;
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @Autowired
    private NavigateUtils navigateUtils;
    @Autowired
@@ -138,19 +143,20 @@
            ioPri = param.getTaskPri().doubleValue();
        }
        Integer sourceCrnNo = this.findCrnNoByLocNo(sourceLocMast.getLocNo());
        if (sourceCrnNo == null) {
        FindCrnNoResult sourceCrnResult = this.findCrnNoByLocNo(sourceLocMast.getLocNo());
        if (sourceCrnResult == null) {
            throw new CoolException("未找到对应堆垛机");
        }
        Integer crnNo = this.findCrnNoByLocNo(locMast.getLocNo());
        if (crnNo == null) {
        FindCrnNoResult targetCrnResult = this.findCrnNoByLocNo(locMast.getLocNo());
        if (targetCrnResult == null) {
            throw new CoolException("未找到对应堆垛机");
        }
        if (!sourceCrnNo.equals(crnNo)) {
        if (!sourceCrnResult.getCrnNo().equals(targetCrnResult.getCrnNo())) {
            throw new CoolException("源库位和目标库位不在同一巷道");
        }
        Integer crnNo = targetCrnResult.getCrnNo();
        // 获取工作号
        int workNo = getWorkNo(WrkIoType.LOC_MOVE.id);
@@ -202,10 +208,11 @@
            ioPri = param.getTaskPri().doubleValue();
        }
        Integer crnNo = this.findCrnNoByLocNo(locMast.getLocNo());
        if (crnNo == null) {
        FindCrnNoResult findCrnResult = this.findCrnNoByLocNo(locMast.getLocNo());
        if (findCrnResult == null) {
            throw new CoolException("未找到对应堆垛机");
        }
        Integer crnNo = findCrnResult.getCrnNo();
        // 获取工作号
        int workNo = getWorkNo(WrkIoType.IN.id);
@@ -221,9 +228,20 @@
        wrkMast.setStaNo(param.getStaNo());//目标站
        wrkMast.setWmsWrkNo(param.getTaskNo());
        wrkMast.setBarcode(param.getBarcode());
        wrkMast.setCrnNo(crnNo);
        wrkMast.setAppeTime(now);
        wrkMast.setModiTime(now);
        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)) {
            wrkMast.setDualCrnNo(findCrnResult.getCrnNo());
        }else {
            throw new CoolException("未知设备类型");
        }
        boolean res = wrkMastService.insert(wrkMast);
        if (!res) {
            News.error("入库任务 --- 保存工作档失败!");
@@ -233,9 +251,6 @@
        locMast.setLocSts("S");
        locMast.setModiTime(new Date());
        locMastService.updateById(locMast);
        //缓存记录当前命令堆垛机编号
        redisUtil.set(RedisKeyType.CURRENT_CIRCLE_TASK_CRN_NO.key, crnNo, 60 * 60 * 24);
        return wrkMast;
    }
@@ -256,28 +271,15 @@
            ioPri = param.getTaskPri().doubleValue();
        }
        Integer crnNo = this.findCrnNoByLocNo(locMast.getLocNo());
        if (crnNo == null) {
        FindCrnNoResult findCrnResult = this.findCrnNoByLocNo(locMast.getLocNo());
        if (findCrnResult == null) {
            throw new CoolException("未找到对应堆垛机");
        }
        Integer crnNo = findCrnResult.getCrnNo();
        Integer sourceStationId = this.findOutStationId(crnNo, param.getStaNo());
        Integer sourceStationId = this.findOutStationId(findCrnResult, param.getStaNo());
        if (sourceStationId == null) {
            throw new CoolException("未找到输送目标站点可走行路径");
        }
        BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
        if(basCrnp == null) {
            throw new CoolException("未找到对应堆垛机数据");
        }
        List<WrkMast> outWrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                .eq("crn_no", crnNo)
                .eq("io_type", WrkIoType.OUT.id)
        );
        // 检查是否超过最大出库任务数
        if(outWrkMasts.size() >= basCrnp.getMaxOutTask()){
            News.info("堆垛机:{} 已达最大出库任务数,当前任务数:{}", basCrnp.getCrnNo(), outWrkMasts.size());
            throw new CoolException("堆垛机:" + basCrnp.getCrnNo() + "已达最大出库任务数,当前任务数:" + outWrkMasts.size());
        }
        // 获取工作号
@@ -294,9 +296,45 @@
        wrkMast.setStaNo(param.getStaNo());//目标站
        wrkMast.setWmsWrkNo(param.getTaskNo());
        wrkMast.setBarcode(locMast.getBarcode());
        wrkMast.setCrnNo(crnNo);
        wrkMast.setAppeTime(now);
        wrkMast.setModiTime(now);
        if (findCrnResult.getCrnType().equals(SlaveType.Crn)) {
            BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
            if(basCrnp == null) {
                throw new CoolException("未找到对应堆垛机数据");
            }
            List<WrkMast> outWrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                    .eq("crn_no", crnNo)
                    .eq("io_type", WrkIoType.OUT.id)
            );
            // 检查是否超过最大出库任务数
            if(outWrkMasts.size() >= basCrnp.getMaxOutTask()){
                News.info("堆垛机:{} 已达最大出库任务数,当前任务数:{}", basCrnp.getCrnNo(), outWrkMasts.size());
                throw new CoolException("堆垛机:" + basCrnp.getCrnNo() + "已达最大出库任务数,当前任务数:" + outWrkMasts.size());
            }
            wrkMast.setCrnNo(findCrnResult.getCrnNo());
        } else if (findCrnResult.getCrnType().equals(SlaveType.DualCrn)) {
            BasDualCrnp basDualCrnp = basDualCrnpService.selectOne(new EntityWrapper<BasDualCrnp>().eq("crn_no", crnNo));
            if(basDualCrnp == null) {
                throw new CoolException("未找到对应双工位堆垛机数据");
            }
            List<WrkMast> outWrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                    .eq("dual_crn_no", crnNo)
                    .eq("io_type", WrkIoType.OUT.id)
            );
            // 检查是否超过最大出库任务数
            if(outWrkMasts.size() >= basDualCrnp.getMaxOutTask()){
                News.info("双工位堆垛机:{} 已达最大出库任务数,当前任务数:{}", basDualCrnp.getCrnNo(), outWrkMasts.size());
                throw new CoolException("双工位堆垛机:" + basDualCrnp.getCrnNo() + "已达最大出库任务数,当前任务数:" + outWrkMasts.size());
            }
            wrkMast.setDualCrnNo(findCrnResult.getCrnNo());
        }else {
            throw new CoolException("未知设备类型");
        }
        boolean res = wrkMastService.insert(wrkMast);
        if (!res) {
            News.error("出库任务 --- 保存工作档失败!");
@@ -309,27 +347,53 @@
        return true;
    }
    public Integer findCrnNoByLocNo(String locNo) {
    public FindCrnNoResult findCrnNoByLocNo(String locNo) {
        List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            List<List<Integer>> rowList = basCrnp.getControlRows$();
            for (List<Integer> rows : rowList) {
                if(rows.contains(Utils.getRow(locNo))) {
                    return basCrnp.getCrnNo();
                    FindCrnNoResult result = new FindCrnNoResult();
                    result.setCrnNo(basCrnp.getCrnNo());
                    result.setCrnType(SlaveType.Crn);
                    return result;
                }
            }
        }
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.selectList(new EntityWrapper<>());
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            List<List<Integer>> rowList = basDualCrnp.getControlRows$();
            for (List<Integer> rows : rowList) {
                if(rows.contains(Utils.getRow(locNo))) {
                    FindCrnNoResult result = new FindCrnNoResult();
                    result.setCrnNo(basDualCrnp.getCrnNo());
                    result.setCrnType(SlaveType.DualCrn);
                    return result;
                }
            }
        }
        return null;
    }
    public Integer findInStationId(Integer crnNo, Integer sourceStationId) {
        BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
        if(basCrnp == null) {
            return null;
    public Integer findInStationId(FindCrnNoResult findCrnNoResult, Integer sourceStationId) {
        List<StationObjModel> stationList = new ArrayList<>();
        Integer crnNo = findCrnNoResult.getCrnNo();
        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
            BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
            if(basCrnp == null) {
                return null;
            }
            stationList = basCrnp.getInStationList$();
        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
            BasDualCrnp basDualCrnp = basDualCrnpService.selectOne(new EntityWrapper<BasDualCrnp>().eq("crn_no", crnNo));
            if(basDualCrnp == null) {
                return null;
            }
            stationList = basDualCrnp.getInStationList$();
        }
        Integer targetStationId = null;
        List<StationObjModel> stationList = basCrnp.getInStationList$();
        for (StationObjModel stationObjModel : stationList) {
            try {
                List<NavigateNode> navigateNodes = navigateUtils.calcByStationId(sourceStationId, stationObjModel.getStationId());
@@ -344,14 +408,24 @@
        return targetStationId;
    }
    public Integer findOutStationId(Integer crnNo, Integer targetStationId) {
        BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
        if(basCrnp == null) {
            return null;
    public Integer findOutStationId(FindCrnNoResult findCrnNoResult, Integer targetStationId) {
        List<StationObjModel> stationList = new ArrayList<>();
        Integer crnNo = findCrnNoResult.getCrnNo();
        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
            BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
            if(basCrnp == null) {
                return null;
            }
            stationList = basCrnp.getOutStationList$();
        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
            BasDualCrnp basDualCrnp = basDualCrnpService.selectOne(new EntityWrapper<BasDualCrnp>().eq("crn_no", crnNo));
            if(basDualCrnp == null) {
                return null;
            }
            stationList = basDualCrnp.getOutStationList$();
        }
        Integer finalSourceStationId = null;
        List<StationObjModel> stationList = basCrnp.getOutStationList$();
        for (StationObjModel stationObjModel : stationList) {
            try {
                List<NavigateNode> navigateNodes = navigateUtils.calcByStationId(stationObjModel.getStationId(), targetStationId);
src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -9,6 +9,8 @@
    LIFT_WORK_FLAG("lift_wrk_no_"),
    LIFT_FLAG("lift_"),
    DUAL_CRN_COMMAND_("dual_crn_command_"),
    QUEUE_CRN("queue_crn_"),
    QUEUE_DEVP("queue_devp_"),
    QUEUE_RGV("queue_rgv_"),
@@ -31,11 +33,14 @@
    CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT("check_out_station_stay_time_out_limit_"),
    CHECK_IN_STATION_STAY_TIME_OUT_LIMIT("check_in_station_stay_time_out_limit_"),
    CRN_IO_EXECUTE_FINISH_LIMIT("crn_io_execute_finish_limit_"),
    DUAL_CRN_IO_EXECUTE_FINISH_LIMIT("dual_crn_io_execute_finish_limit_"),
    STATION_IN_EXECUTE_LIMIT("station_in_execute_limit_"),
    STATION_OUT_EXECUTE_LIMIT("station_out_execute_limit_"),
    CHECK_STATION_RUN_BLOCK_LIMIT_("check_station_run_block_limit_"),
    CHECK_SHALLOW_LOC_STATUS_LIMIT("check_shallow_loc_status_limit_"),
    DUAL_CRN_PICK_WAIT_NEXT_TASK("dual_crn_pick_wait_next_task_"),
    CURRENT_CIRCLE_TASK_CRN_NO("current_circle_task_crn_no_"),
    AI_CHAT_HISTORY("ai_chat_history_"),
    AI_CHAT_META("ai_chat_meta_"),
src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
@@ -64,11 +64,15 @@
    private void commandTaskComplete(DualCrnCommand command) {
        if(command.getStation() == 1) {
            this.crnStatus.setTaskNo(0);
            if (crnStatus.getLoaded() == 0) {
                this.crnStatus.setTaskNo(0);
            }
            this.crnStatus.setTaskReceive(0);
            this.crnStatus.setStatus(CrnStatusType.IDLE.id);
        }else {
            this.crnStatus.setTaskNoTwo(0);
            if (crnStatus.getLoadedTwo() == 0) {
                this.crnStatus.setTaskNoTwo(0);
            }
            this.crnStatus.setTaskReceiveTwo(0);
            this.crnStatus.setStatusTwo(CrnStatusType.IDLE.id);
        }
src/main/java/com/zy/core/plugin/FakeProcess.java
@@ -8,6 +8,7 @@
import com.zy.asrs.domain.param.CreateOutTaskParam;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.model.StartupDto;
import com.zy.common.service.CommonService;
import com.zy.common.utils.RedisUtil;
@@ -20,11 +21,14 @@
import com.zy.core.model.command.CrnCommand;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.properties.SystemProperties;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.DualCrnThread;
import com.zy.core.thread.StationThread;
import com.zy.core.utils.CrnOperateProcessUtils;
import com.zy.core.utils.DualCrnOperateProcessUtils;
import com.zy.core.utils.StationOperateProcessUtils;
import com.zy.core.utils.WmsOperateUtils;
import com.zy.system.entity.Config;
@@ -61,6 +65,8 @@
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private CrnOperateProcessUtils crnOperateUtils;
@@ -68,6 +74,8 @@
    private StationOperateProcessUtils stationOperateProcessUtils;
    @Autowired
    private WmsOperateUtils wmsOperateUtils;
    @Autowired
    private DualCrnOperateProcessUtils dualCrnOperateProcessUtils;
    @Override
    public void run() {
@@ -84,6 +92,11 @@
        stationOperateProcessUtils.stationOutExecute();
        //检测输送站点出库任务执行完成
        stationOperateProcessUtils.stationOutExecuteFinish();
        //执行双工位堆垛机任务
        dualCrnOperateProcessUtils.dualRrnIoExecute();
        //双工位堆垛机任务执行完成
        dualCrnOperateProcessUtils.dualCrnIoExecuteFinish();
    }
    public void asyncRun() {
@@ -293,12 +306,12 @@
                    int nextInt = new Random().nextInt(locMastList.size());
                    LocMast locMast = locMastList.get(nextInt);
                    Integer crnNo = commonService.findCrnNoByLocNo(locMast.getLocNo());
                    if (crnNo == null) {
                    FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locMast.getLocNo());
                    if (findCrnNoResult == null) {
                        continue;
                    }
                    Integer targetStationId = commonService.findInStationId(crnNo, stationId);
                    Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId);
                    if (targetStationId == null) {
                        continue;
                    }
@@ -374,11 +387,6 @@
                    }
                    LocMast locMast = locMastList.get(0);
                    Integer crnNo = commonService.findCrnNoByLocNo(locMast.getLocNo());
                    if (crnNo == null) {
                        continue;
                    }
                    CreateOutTaskParam taskParam = new CreateOutTaskParam();
                    taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.OUT.id)));
@@ -541,38 +549,53 @@
                News.info("堆垛机:{} 入库站点未设置", basCrnp.getCrnNo());
                continue;
            }
            checkInStationListCrnTake(inStationList);
        }
            for (StationObjModel stationObjModel : inStationList) {
                Object lock = redisUtil.get(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId());
                if(lock != null){
                    continue;
                }
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.selectList(new EntityWrapper<>());
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            List<StationObjModel> inStationList = basDualCrnp.getInStationList$();
            if(inStationList.isEmpty()){
                News.info("双工位堆垛机:{} 入库站点未设置", basDualCrnp.getCrnNo());
                continue;
            }
            checkInStationListCrnTake(inStationList);
        }
    }
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                if(stationThread == null){
                    continue;
                }
    private void checkInStationListCrnTake(List<StationObjModel> inStationList) {
        for (StationObjModel stationObjModel : inStationList) {
            Object lock = redisUtil.get(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId());
            if(lock != null){
                continue;
            }
                StationCommand command = stationThread.getMoveCommand(0, stationObjModel.getStationId(), 0, 0);
                if(command == null){
                    continue;
                }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
            if(stationThread == null){
                continue;
            }
                Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
                StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
            StationCommand command = stationThread.getMoveCommand(0, stationObjModel.getStationId(), 0, 0);
            if(command == null){
                continue;
            }
                if(stationProtocol.getTaskNo() > 0) {
                    WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
                    if (wrkMast == null) {
                        MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
                        redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(), "lock",10);
                        News.info("输送站点重置命令下发成功(task_over),站点号={},命令数据={}", stationObjModel.getStationId(), JSON.toJSONString(command));
                    }else {
                        if (wrkMast.getWrkSts() != WrkStsType.NEW_INBOUND.sts && wrkMast.getWrkSts() != WrkStsType.INBOUND_DEVICE_RUN.sts) {
                            Integer crnNo = wrkMast.getCrnNo();
            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                continue;
            }
            if(stationProtocol.getTaskNo() > 0) {
                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
                if (wrkMast == null) {
                    MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
                    redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(), "lock",10);
                    News.info("输送站点重置命令下发成功(task_over),站点号={},命令数据={}", stationObjModel.getStationId(), JSON.toJSONString(command));
                }else {
                    if (wrkMast.getWrkSts() != WrkStsType.NEW_INBOUND.sts && wrkMast.getWrkSts() != WrkStsType.INBOUND_DEVICE_RUN.sts) {
                        Integer crnNo = wrkMast.getCrnNo();
                        if (crnNo != null) {
                            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, crnNo);
                            if (crnThread == null) {
                                continue;
@@ -585,6 +608,20 @@
                            MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
                            redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(), "lock",10);
                            News.info("输送站点重置命令下发成功(crn_fetch),站点号={},命令数据={}", stationObjModel.getStationId(), JSON.toJSONString(command));
                        }else {
                            Integer dualCrnNo = wrkMast.getDualCrnNo();
                            DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, dualCrnNo);
                            if (dualCrnThread == null) {
                                continue;
                            }
                            DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
                            if (!dualCrnProtocol.getStatusType().equals(DualCrnStatusType.PUT_MOVING) && !dualCrnProtocol.getStatusType().equals(DualCrnStatusType.PUTTING)) {
                                continue;
                            }
                            MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
                            redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(), "lock",10);
                            News.info("输送站点重置命令下发成功(crn_fetch),站点号={},命令数据={}", stationObjModel.getStationId(), JSON.toJSONString(command));
                        }
                    }
                }
src/main/java/com/zy/core/thread/impl/ZySiemensDualCrnThread.java
@@ -1,6 +1,8 @@
package com.zy.core.thread.impl;
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.DateUtils;
import com.core.common.SpringUtils;
@@ -12,11 +14,10 @@
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.utils.Utils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.OutputQueue;
import com.zy.core.enums.DualCrnTaskModeType;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.SlaveType;
import com.zy.core.enums.*;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.Task;
import com.zy.core.model.command.DualCrnCommand;
@@ -30,6 +31,8 @@
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
@@ -51,6 +54,7 @@
    private volatile boolean closed = false;
    private ScheduledExecutorService readExecutor;
    private ScheduledExecutorService processExecutor;
    private ScheduledExecutorService commandExecutor;
    public ZySiemensDualCrnThread(DeviceConfig deviceConfig, RedisUtil redisUtil) {
        this.deviceConfig = deviceConfig;
@@ -102,13 +106,93 @@
                if (task != null) {
                    step = task.getStep();
                }
                if (step == 2 && task != null) {
                if (step == 2) {
                    List<DualCrnCommand> commandList = (List<DualCrnCommand>) task.getData();
                    DualCrnCommand command = commandList.get(0);
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("commands", commandList);
                    map.put("idx", 1);
                    redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_.key + command.getTaskNo(), JSON.toJSONString(map, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24);
                    sendCommand(command);
                } else if (step == 3) {
                    sendCommand((DualCrnCommand) task.getData());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 0, 200, TimeUnit.MILLISECONDS);
        commandExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("DualCrnCommand-" + deviceConfig.getDeviceNo());
                t.setDaemon(true);
                return t;
            }
        });
        commandExecutor.scheduleAtFixedRate(() -> {
            if (closed || Thread.currentThread().isInterrupted()) {
                return;
            }
            try {
                if(crnProtocol.getMode() != DualCrnModeType.AUTO.id) {
                    return;
                }
                if(crnProtocol.getAlarm() != 0) {
                    return;
                }
                //等待下一个任务
                Object wait = redisUtil.get(RedisKeyType.DUAL_CRN_PICK_WAIT_NEXT_TASK.key + crnProtocol.getCrnNo());
                if (wait != null) {
                    return;
                }
                if(crnProtocol.getTaskNo() > 0 && crnProtocol.getStatus() == DualCrnStatusType.IDLE.id) {
                    Integer taskNo = crnProtocol.getTaskNo();
                    Object commandObj = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_.key + taskNo);
                    if (commandObj == null) {
                        News.error("双工位堆垛机,工位1空闲等待下发命令,但未找到命令。堆垛机号={},工作号={}", crnProtocol.getCrnNo(), taskNo);
                        return;
                    }
                    JSONObject commandMap = JSON.parseObject(commandObj.toString());
                    Integer idx = commandMap.getInteger("idx");
                    List<DualCrnCommand> commandList = commandMap.getJSONArray("commands").toJavaList(DualCrnCommand.class);
                    DualCrnCommand dualCommand = commandList.get(idx);
                    idx++;
                    commandMap.put("idx", idx);
                    sendCommand(dualCommand);
                    redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_.key + taskNo, commandMap.toJSONString(), 60 * 60 * 24);
                }
                if(crnProtocol.getTaskNoTwo() > 0 && crnProtocol.getStatusTwo() == DualCrnStatusType.IDLE.id) {
                    Integer taskNo = crnProtocol.getTaskNoTwo();
                    Object commandObj = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_.key + taskNo);
                    if (commandObj == null) {
                        News.error("双工位堆垛机,工位2空闲等待下发命令,但未找到命令。堆垛机号={},工作号={}", crnProtocol.getCrnNo(), taskNo);
                        return;
                    }
                    JSONObject commandMap = JSON.parseObject(commandObj.toString());
                    Integer idx = commandMap.getInteger("idx");
                    List<DualCrnCommand> commandList = commandMap.getJSONArray("commands").toJavaList(DualCrnCommand.class);
                    DualCrnCommand dualCommand = commandList.get(idx);
                    idx++;
                    commandMap.put("idx", idx);
                    sendCommand(dualCommand);
                    redisUtil.set(RedisKeyType.DUAL_CRN_COMMAND_.key + taskNo, commandMap.toJSONString(), 60 * 60 * 24);
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error("DualCrnCommandThread Fail", e);
            }
        }, 0, 200, TimeUnit.MILLISECONDS);
    }
    /**
src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
New file
@@ -0,0 +1,495 @@
package com.zy.core.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.exception.CoolException;
import com.zy.asrs.domain.param.CreateLocMoveTaskParam;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.LocMastService;
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.utils.Utils;
import com.zy.common.model.StartupDto;
import com.zy.common.service.CommonService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.*;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.Task;
import com.zy.core.model.command.CrnCommand;
import com.zy.core.model.command.DualCrnCommand;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.DualCrnThread;
import com.zy.core.thread.StationThread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class DualCrnOperateProcessUtils {
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @Autowired
    private LocMastService locMastService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private WmsOperateUtils wmsOperateUtils;
    @Autowired
    private CommonService commonService;
    //入出库  ===>>  双工位堆垛机入出库作业下发
    public synchronized void dualRrnIoExecute() {
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.selectList(new EntityWrapper<>());
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, basDualCrnp.getCrnNo());
            if(dualCrnThread == null){
                continue;
            }
            DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
            if(dualCrnProtocol == null){
                continue;
            }
            List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                    .eq("dual_crn_no", basDualCrnp.getCrnNo())
                    .in("wrk_sts", WrkStsType.INBOUND_RUN.sts, WrkStsType.OUTBOUND_RUN.sts)
            );
            if(wrkMasts.size() >= 2){
                continue;
            }
            if(dualCrnProtocol.getMode() != DualCrnModeType.AUTO.id) {
                continue;
            }
            if(dualCrnProtocol.getAlarm() != 0) {
                continue;
            }
            int executeTaskNo = 0;
            if (dualCrnProtocol.getTaskNo() > 0) {
                executeTaskNo = dualCrnProtocol.getTaskNo();
            }
            if (dualCrnProtocol.getTaskNoTwo() > 0) {
                executeTaskNo = dualCrnProtocol.getTaskNoTwo();
            }
            if (executeTaskNo > 0) {
                WrkMast wrkMast = wrkMastService.selectByWorkNo(executeTaskNo);
                if (wrkMast != null) {
                    if (wrkMast.getIoType().equals(WrkIoType.IN.id)) {
                        this.crnExecuteIn(basDualCrnp, dualCrnThread); //  入库
                    } else if (wrkMast.getIoType().equals(WrkIoType.OUT.id)) {
                        this.crnExecuteOut(basDualCrnp, dualCrnThread); //  出库
                    }else {
                        continue;
                    }
                }
            }
            // 如果最近一次是入库模式
            if (dualCrnProtocol.getLastIo().equals("I")) {
                if (basDualCrnp.getInEnable().equals("Y")) {
                    this.crnExecuteIn(basDualCrnp, dualCrnThread); //  入库
                    dualCrnProtocol.setLastIo("O");
                } else if (basDualCrnp.getOutEnable().equals("Y")) {
                    this.crnExecuteOut(basDualCrnp, dualCrnThread); //  出库
                    dualCrnProtocol.setLastIo("I");
                }
            }
            // 如果最近一次是出库模式
            else if (dualCrnProtocol.getLastIo().equals("O")) {
                if (basDualCrnp.getOutEnable().equals("Y")) {
                    this.crnExecuteOut(basDualCrnp, dualCrnThread); //  出库
                    dualCrnProtocol.setLastIo("I");
                } else if (basDualCrnp.getInEnable().equals("Y")) {
                    this.crnExecuteIn(basDualCrnp, dualCrnThread); //  入库
                    dualCrnProtocol.setLastIo("O");
                }
            }
        }
    }
    private synchronized void crnExecuteIn(BasDualCrnp basDualCrnp, DualCrnThread dualCrnThread) {
        DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
        if(dualCrnProtocol == null){
            return;
        }
        if(!basDualCrnp.getInEnable().equals("Y")){
            News.info("双工位堆垛机:{} 可入信号不满足", basDualCrnp.getCrnNo());
            return;
        }
        List<StationObjModel> inStationList = basDualCrnp.getInStationList$();
        if(inStationList.isEmpty()){
            News.info("双工位堆垛机:{} 入库站点未设置", basDualCrnp.getCrnNo());
            return;
        }
        Integer crnNo = basDualCrnp.getCrnNo();
        int station = calcStation(dualCrnProtocol);
        if(station == 0){
            News.info("双工位堆垛机:{} 无可用工位", basDualCrnp.getCrnNo());
            return;
        }
        for (StationObjModel stationObjModel : inStationList) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationProtocolMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = stationProtocolMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                continue;
            }
            if (!stationProtocol.isAutoing()) {
                continue;
            }
            if (!stationProtocol.isLoading()) {
                continue;
            }
            if (stationProtocol.getTaskNo() <= 0) {
                continue;
            }
            if (!stationProtocol.isInEnable()) {
                News.taskInfo(stationProtocol.getTaskNo(), "取货站点:{} 没有可入信号", stationObjModel.getStationId());
                continue;
            }
            // 获取任务
            WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
            if (null == wrkMast) {
                News.taskInfo(stationProtocol.getTaskNo(), "工作号:{} 任务信息不存在", stationProtocol.getTaskNo());
                continue;
            }
            if(wrkMast.getWrkSts() != WrkStsType.INBOUND_DEVICE_RUN.sts){
                News.taskInfo(stationProtocol.getTaskNo(), "工作号:{} 任务状态异常", stationProtocol.getTaskNo());
                continue;
            }
            // 获取库位信息
            LocMast locMast = locMastService.selectById(wrkMast.getLocNo());
            if (locMast == null) {
                News.taskInfo(wrkMast.getWrkNo(), "目标库位:{} 信息不存在", wrkMast.getLocNo());
                continue;
            }
            if (!locMast.getLocSts().equals("S")) {
                News.taskInfo(wrkMast.getWrkNo(), "目标库位:{} 状态异常", wrkMast.getLocNo());
                continue;
            }
            //检测浅库位状态
            boolean checkStatus = checkShallowLocStatus(locMast.getLocNo(), wrkMast.getWrkNo());
            if (!checkStatus) {
                News.taskInfo(wrkMast.getWrkNo(), "因浅库位堵塞无法执行");
                continue;
            }
            String sourceLocNo = Utils.getLocNo(stationObjModel.getDeviceRow(), stationObjModel.getDeviceBay(), stationObjModel.getDeviceLev());
            List<DualCrnCommand> commandList = new ArrayList<>();
            DualCrnCommand pickCommand = dualCrnThread.getPickCommand(sourceLocNo, wrkMast.getWrkNo(), crnNo, station);
            DualCrnCommand putCommand = dualCrnThread.getPutCommand(wrkMast.getLocNo(), wrkMast.getWrkNo(), crnNo, station);
            commandList.add(pickCommand);
            commandList.add(putCommand);
            wrkMast.setWrkSts(WrkStsType.INBOUND_RUN.sts);
            wrkMast.setDualCrnNo(crnNo);
            wrkMast.setSystemMsg("");
            wrkMast.setIoTime(new Date());
            if (wrkMastService.updateById(wrkMast)) {
                MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, commandList));
                News.info("双工位堆垛机命令下发成功,堆垛机号={},任务数据={}", crnNo, JSON.toJSON(commandList));
            }
        }
    }
    private synchronized void crnExecuteOut(BasDualCrnp basDualCrnp, DualCrnThread dualCrnThread) {
        DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
        if(dualCrnProtocol == null){
            return;
        }
        if(!basDualCrnp.getOutEnable().equals("Y")){
            News.info("双工位堆垛机:{} 可出信号不满足", basDualCrnp.getCrnNo());
            return;
        }
        List<StationObjModel> outStationList = basDualCrnp.getOutStationList$();
        if(outStationList.isEmpty()){
            News.info("双工位堆垛机:{} 出库站点未设置", basDualCrnp.getCrnNo());
            return;
        }
        Integer crnNo = basDualCrnp.getCrnNo();
        int station = calcStation(dualCrnProtocol);
        if(station == 0){
            News.info("双工位堆垛机:{} 无可用工位", basDualCrnp.getCrnNo());
            return;
        }
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                .eq("crn_no", crnNo)
                .eq("wrk_sts", WrkStsType.NEW_OUTBOUND.sts)
        );
        for (WrkMast wrkMast : wrkMasts) {
            for (StationObjModel stationObjModel : outStationList) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                if (stationThread == null) {
                    continue;
                }
                Map<Integer, StationProtocol> stationProtocolMap = stationThread.getStatusMap();
                StationProtocol stationProtocol = stationProtocolMap.get(stationObjModel.getStationId());
                if (stationProtocol == null) {
                    continue;
                }
                if (!stationProtocol.isAutoing()) {
                    continue;
                }
                if (stationProtocol.isLoading()) {
                    continue;
                }
                if (stationProtocol.getTaskNo() != 0) {
                    continue;
                }
                if (!stationProtocol.isOutEnable()) {
                    News.info("放货站点:{} 没有可出信号", stationObjModel.getStationId());
                    continue;
                }
                // 获取库位信息
                LocMast locMast = locMastService.selectById(wrkMast.getSourceLocNo());
                if (locMast == null) {
                    News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 信息不存在", wrkMast.getSourceLocNo());
                    continue;
                }
                if (!locMast.getLocSts().equals("R")) {
                    News.taskInfo(wrkMast.getWrkNo(), "源库位:{} 状态异常", wrkMast.getSourceLocNo());
                    continue;
                }
                //检测浅库位状态
                boolean checkStatus = checkShallowLocStatus(locMast.getLocNo(), wrkMast.getWrkNo());
                if (!checkStatus) {
                    News.taskInfo(wrkMast.getWrkNo(), "因浅库位堵塞无法执行");
                    continue;
                }
                String targetLocNo = Utils.getLocNo(stationObjModel.getDeviceRow(), stationObjModel.getDeviceBay(), stationObjModel.getDeviceLev());
                List<DualCrnCommand> commandList = new ArrayList<>();
                DualCrnCommand pickCommand = dualCrnThread.getPickCommand(wrkMast.getSourceLocNo(), wrkMast.getWrkNo(), crnNo, station);
                DualCrnCommand putCommand = dualCrnThread.getPutCommand(targetLocNo, wrkMast.getWrkNo(), crnNo, station);
                commandList.add(pickCommand);
                commandList.add(putCommand);
                wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN.sts);
                wrkMast.setDualCrnNo(crnNo);
                wrkMast.setSystemMsg("");
                wrkMast.setIoTime(new Date());
                if (wrkMastService.updateById(wrkMast)) {
                    MessageQueue.offer(SlaveType.DualCrn, crnNo, new Task(2, commandList));
                    //取货后等待下一个任务时长
                    redisUtil.set(RedisKeyType.DUAL_CRN_PICK_WAIT_NEXT_TASK.key + crnNo, "wait", 5);
                    News.info("双工位堆垛机命令下发成功,堆垛机号={},任务数据={}", crnNo, JSON.toJSON(commandList));
                    return;
                }
            }
        }
    }
    //双工位堆垛机任务执行完成
    public synchronized void dualCrnIoExecuteFinish() {
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.selectList(new EntityWrapper<>());
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn, basDualCrnp.getCrnNo());
            if(dualCrnThread == null){
                continue;
            }
            DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
            if(dualCrnProtocol == null){
                continue;
            }
            if(dualCrnProtocol.getMode() != DualCrnModeType.AUTO.id) {
                continue;
            }
            if(dualCrnProtocol.getAlarm() != 0) {
                continue;
            }
            if(dualCrnProtocol.getTaskNo() > 0 && dualCrnProtocol.getStatus() == DualCrnStatusType.WAITING.id) {
                executeFinish(basDualCrnp, dualCrnThread, dualCrnProtocol, dualCrnProtocol.getTaskNo(), 1);
                continue;
            }
            if(dualCrnProtocol.getTaskNoTwo() > 0 && dualCrnProtocol.getStatusTwo() == DualCrnStatusType.WAITING.id) {
                executeFinish(basDualCrnp, dualCrnThread, dualCrnProtocol, dualCrnProtocol.getTaskNoTwo(), 2);
                continue;
            }
        }
    }
    private void executeFinish(BasDualCrnp basDualCrnp, DualCrnThread dualCrnThread, DualCrnProtocol dualCrnProtocol, int taskNo, int station) {
        Object lock = redisUtil.get(RedisKeyType.DUAL_CRN_IO_EXECUTE_FINISH_LIMIT.key + basDualCrnp.getCrnNo() + "_" + taskNo);
        if (lock != null) {
            return;
        }
        // 获取待确认工作档
        WrkMast wrkMast = wrkMastService.selectByWorkNo(taskNo);
        if (wrkMast == null) {
            News.error("双工位堆垛机处于等待确认且任务完成状态,但未找到工作档。堆垛机号={},工作号={}", basDualCrnp.getCrnNo(), taskNo);
            return;
        }
        Object commandObj = redisUtil.get(RedisKeyType.DUAL_CRN_COMMAND_.key + wrkMast.getWrkNo());
        if (commandObj == null) {
            News.error("双工位堆垛机处于等待确认且任务完成状态,但未找到命令。堆垛机号={},工作号={}", basDualCrnp.getCrnNo(), taskNo);
            return;
        }
        JSONObject commandMap = JSON.parseObject(commandObj.toString());
        Integer idx = commandMap.getInteger("idx");
        List<DualCrnCommand> commandList = commandMap.getJSONArray("commands").toJavaList(DualCrnCommand.class);
        if (idx >= commandList.size()) {
            Long updateWrkSts = null;
            if (wrkMast.getWrkSts() == WrkStsType.INBOUND_RUN.sts) {
                updateWrkSts = WrkStsType.COMPLETE_INBOUND.sts;
            } else if (wrkMast.getWrkSts() == WrkStsType.OUTBOUND_RUN.sts) {
                updateWrkSts = WrkStsType.OUTBOUND_RUN_COMPLETE.sts;
            } else if (wrkMast.getWrkSts() == WrkStsType.LOC_MOVE_RUN.sts) {
                updateWrkSts = WrkStsType.COMPLETE_LOC_MOVE.sts;
            } else {
                News.error("双工位堆垛机处于等待确认且任务完成状态,但工作状态异常。堆垛机号={},工作号={}", basDualCrnp.getCrnNo(), taskNo);
                return;
            }
            wrkMast.setWrkSts(updateWrkSts);
            wrkMast.setSystemMsg("");
            wrkMast.setIoTime(new Date());
            if (wrkMastService.updateById(wrkMast)) {
                DualCrnCommand resetCommand = dualCrnThread.getResetCommand(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", 10);
        }else {
            DualCrnCommand resetCommand = dualCrnThread.getResetCommand(dualCrnProtocol.getCrnNo(), station);
            MessageQueue.offer(SlaveType.DualCrn, dualCrnProtocol.getCrnNo(), new Task(3, resetCommand));
            News.info("双工位堆垛机命令完成确认成功,堆垛机号={},工作号={}", basDualCrnp.getCrnNo(), taskNo);
        }
    }
    //检测浅库位状态
    public synchronized boolean checkShallowLocStatus(String locNo, Integer taskNo) {
        String checkDeepLocOutTaskBlockReport = "Y";
        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj != null) {
            HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
            checkDeepLocOutTaskBlockReport = systemConfigMap.get("checkDeepLocOutTaskBlockReport");
        }
        if (!checkDeepLocOutTaskBlockReport.equals("Y")) {
            return true;
        }
        Object lock = redisUtil.get(RedisKeyType.CHECK_SHALLOW_LOC_STATUS_LIMIT.key + taskNo);
        if (lock != null) {
            return false;
        }
        redisUtil.set(RedisKeyType.CHECK_SHALLOW_LOC_STATUS_LIMIT.key + taskNo, "lock", 5);
        Integer shallowRow = Utils.getShallowRowByDeepRow(Utils.getRow(locNo));
        if (shallowRow == null) {
            return true;
        }
        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;
        }
        if (shallowLocMast.getLocSts().equals("O")) {
            return true;
        }
        if (shallowLocMast.getLocSts().equals("F")) {
            //浅库位状态有货,申请更换库位
            String response = wmsOperateUtils.applyChangeLocNo(shallowLocNo);
            if (response == null) {
                News.taskError(taskNo, "WCS申请在库库位更换库位失败,WMS接口未响应!!!response:{}", response);
                return false;
            }
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.getInteger("code").equals(200)) {
                StartupDto dto = jsonObject.getObject("data", StartupDto.class);
                String moveLocNo = dto.getLocNo();
                CreateLocMoveTaskParam moveTaskParam = new CreateLocMoveTaskParam();
                moveTaskParam.setTaskNo(dto.getTaskNo());
                moveTaskParam.setSourceLocNo(shallowLocNo);
                moveTaskParam.setLocNo(moveLocNo);
                try {
                    boolean result = commonService.createLocMoveTask(moveTaskParam);
                } catch (CoolException e) {
                    News.taskInfo(taskNo, e.getMessage());
                }
            } else {
                News.error("请求WMS申请更换库位接口失败!!!response:{}", response);
            }
        }
        return false;
    }
    private int calcStation(DualCrnProtocol dualCrnProtocol) {
        int station = 0;
        if(dualCrnProtocol.getTaskNo() == 0){
            station = 1;
        }else if (dualCrnProtocol.getTaskNoTwo() == 0){
            station = 2;
        }
        return station;
    }
}
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -3,8 +3,10 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.exception.CoolException;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.model.StartupDto;
import com.zy.common.service.CommonService;
import com.zy.common.utils.RedisUtil;
@@ -91,13 +93,13 @@
                    }
                    String locNo = wrkMast.getLocNo();
                    Integer crnNo = commonService.findCrnNoByLocNo(locNo);
                    if (crnNo == null) {
                    FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
                    if (findCrnNoResult == null) {
                        News.taskInfo(wrkMast.getWrkNo(), "未匹配到堆垛机");
                        continue;
                    }
                    Integer targetStationId = commonService.findInStationId(crnNo, stationId);
                    Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId);
                    if (targetStationId == null) {
                        News.taskInfo(wrkMast.getWrkNo(), "搜索入库站点失败");
                        continue;
@@ -246,7 +248,7 @@
                    if (runBlockReassignLocStationList.contains(stationProtocol.getStationId())) {
                        //站点处于重新分配库位区域
                        //运行堵塞,重新申请任务
                        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo());
                        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
                        if (response == null) {
                            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,接口未响应!!!response:{}", response);
                            continue;
@@ -280,13 +282,14 @@
                                continue;
                            }
                            Integer crnNo = commonService.findCrnNoByLocNo(locNo);
                            if (crnNo == null) {
                            FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
                            if (findCrnNoResult == null) {
                                News.taskInfo(wrkMast.getWrkNo(), "未匹配到堆垛机");
                                continue;
                            }
                            Integer crnNo = findCrnNoResult.getCrnNo();
                            Integer targetStationId = commonService.findInStationId(crnNo, stationProtocol.getStationId());
                            Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId());
                            if (targetStationId == null) {
                                News.taskInfo(wrkMast.getWrkNo(), "搜索入库站点失败");
                                continue;
@@ -310,8 +313,16 @@
                            //更新工作档数据
                            wrkMast.setLocNo(locNo);
                            wrkMast.setCrnNo(crnNo);
                            wrkMast.setStaNo(targetStationId);
                            if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
                                wrkMast.setCrnNo(crnNo);
                            } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
                                wrkMast.setDualCrnNo(crnNo);
                            }else {
                                throw new CoolException("未知设备类型");
                            }
                            if (wrkMastService.updateById(wrkMast)) {
                                MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
                            }
src/main/java/com/zy/core/utils/WmsOperateUtils.java
@@ -3,16 +3,22 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.entity.HttpRequestLog;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.HttpRequestLogService;
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.utils.Utils;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.service.CommonService;
import com.zy.common.utils.HttpHandler;
import com.zy.core.News;
import com.zy.core.enums.SlaveType;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -37,6 +43,8 @@
    private CommonService commonService;
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    //申请入库任务
    public synchronized String applyInTask(String barcode, Integer sourceStaNo, Integer locType1) {
@@ -69,7 +77,7 @@
            requestParam.put("barcode", barcode);
            requestParam.put("sourceStaNo", sourceStaNo);
            requestParam.put("locType1", locType1);
            requestParam.put("row", Utils.getInTaskEnableRow());
            requestParam.put("row", Utils.getInTaskEnableRow(sourceStaNo));
            response = new HttpHandler.Builder()
                    .setUri(wmsUrl)
@@ -104,7 +112,7 @@
    }
    //申请任务重新分配库位
    public synchronized String applyReassignTaskLocNo(Integer taskNo) {
    public synchronized String applyReassignTaskLocNo(Integer taskNo, Integer stationId) {
        String wmsUrl = null;
        Config wmsSystemUriConfig = configService.selectOne(new EntityWrapper<Config>().eq("code", "wmsSystemUri"));
        if (wmsSystemUriConfig != null) {
@@ -122,7 +130,7 @@
            wmsSystemReassignInTaskUrl = wmsSystemReassignInTaskUrlConfig.getValue();
        }
        if(wmsSystemReassignInTaskUrl == null){
        if (wmsSystemReassignInTaskUrl == null) {
            News.error("未配置WMS任务重新分配入库库位接口地址,配置文件Code编码:wmsSystemReassignInTaskUrl");
            return null;
        }
@@ -137,8 +145,17 @@
        String response = null;
        int result = 0;
        try {
            List<Integer> excludeCrnList = new ArrayList<>();
            List<Integer> excludeDualCrnList = new ArrayList<>();
            if (!Cools.isEmpty(wrkMast.getCrnNo())) {
                excludeCrnList.add(wrkMast.getCrnNo());
            }
            if (!Cools.isEmpty(wrkMast.getDualCrnNo())) {
                excludeDualCrnList.add(wrkMast.getDualCrnNo());
            }
            requestParam.put("taskNo", wrkMast.getWmsWrkNo());
            requestParam.put("row", Utils.getInTaskEnableRow(new ArrayList<>(wrkMast.getCrnNo()), false));
            requestParam.put("row", Utils.getInTaskEnableRow(stationId, excludeCrnList, excludeDualCrnList, false));
            response = new HttpHandler.Builder()
                    .setUri(wmsUrl)
@@ -152,10 +169,10 @@
                if (jsonObject.getInteger("code") == 200) {
                    result = 1;
                    News.info("请求申请任务重新分配入库接口成功!!!url:{};request:{};response:{}", wmsUrl + wmsSystemReassignInTaskUrl, JSON.toJSONString(requestParam), response);
                }else {
                } else {
                    News.info("请求申请任务重新分配入库接口失败,接口返回Code异常!!!url:{};request:{};response:{}", wmsUrl + wmsSystemReassignInTaskUrl, JSON.toJSONString(requestParam), response);
                }
            }else {
            } else {
                News.info("请求申请任务重新分配入库接口失败,接口未响应!!!url:{};request:{};response:{}", wmsUrl + wmsSystemReassignInTaskUrl, JSON.toJSONString(requestParam), response);
            }
        } catch (Exception e) {
@@ -196,19 +213,33 @@
            return null;
        }
        Integer crnNo = commonService.findCrnNoByLocNo(locNo);
        if (crnNo == null) {
        FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
        if (findCrnNoResult == null) {
            return null;
        }
        BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
        if (basCrnp == null) {
            return null;
        }
        Integer crnNo = findCrnNoResult.getCrnNo();
        List<Integer> crnRows = new ArrayList<>();
        List<List<Integer>> rowList = basCrnp.getControlRows$();
        for (List<Integer> list : rowList) {
            crnRows.addAll(list);
        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
            BasCrnp basCrnp = basCrnpService.selectOne(new EntityWrapper<BasCrnp>().eq("crn_no", crnNo));
            if (basCrnp == null) {
                return null;
            }
            List<List<Integer>> rowList = basCrnp.getControlRows$();
            for (List<Integer> list : rowList) {
                crnRows.addAll(list);
            }
        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
            BasDualCrnp basDualCrnp = basDualCrnpService.selectOne(new EntityWrapper<BasDualCrnp>().eq("crn_no", crnNo));
            if (basDualCrnp == null) {
                return null;
            }
            List<List<Integer>> rowList = basDualCrnp.getControlRows$();
            for (List<Integer> list : rowList) {
                crnRows.addAll(list);
            }
        }else {
            throw new CoolException("未知设备类型");
        }
        HashMap<String, Object> requestParam = new HashMap<>();
src/main/resources/mapper/WrkMastLogMapper.xml
@@ -23,8 +23,9 @@
        <result column="error_memo" property="errorMemo" />
        <result column="memo" property="memo" />
        <result column="barcode" property="barcode" />
        <result column="lift_no" property="liftNo" />
        <result column="shuttle_no" property="shuttleNo" />
        <result column="crn_no" property="crnNo" />
        <result column="dual_crn_no" property="dualCrnNo" />
        <result column="rgv_no" property="rgvNo" />
        <result column="wms_wrk_no" property="wmsWrkNo" />
        <result column="system_msg" property="systemMsg" />
    </resultMap>
src/main/resources/mapper/WrkMastMapper.xml
@@ -23,6 +23,7 @@
        <result column="memo" property="memo" />
        <result column="barcode" property="barcode" />
        <result column="crn_no" property="crnNo" />
        <result column="dual_crn_no" property="dualCrnNo" />
        <result column="rgv_no" property="rgvNo" />
        <result column="wms_wrk_no" property="wmsWrkNo" />
        <result column="system_msg" property="systemMsg" />
src/main/webapp/static/js/basDualCrnp/basDualCrnp.js
@@ -24,7 +24,7 @@
            {type: 'checkbox'}
            ,{field: 'crnNo', align: 'center',title: '编号'}
            ,{field: 'status$', align: 'center',title: '状态'}
            ,{field: 'wrkNo', align: 'center',title: '工作号'}
            // ,{field: 'wrkNo', align: 'center',title: '工作号'}
            ,{field: 'inEnable', align: 'center',title: '可入(checkBox)'}
            ,{field: 'outEnable', align: 'center',title: '可出(checkBox)'}
            // ,{field: 'createBy', align: 'center',title: '创建人员'}
src/main/webapp/static/js/wrkMastLog/wrkMastLog.js
@@ -31,6 +31,7 @@
            ,{field: 'sourceLocNo', align: 'center',title: '源库位'}
            ,{field: 'locNo', align: 'center',title: '目标库位'}
            ,{field: 'crnNo', align: 'center',title: '堆垛机'}
            ,{field: 'dualCrnNo', align: 'center',title: '双工位堆垛机'}
            ,{field: 'modiUser$', align: 'center',title: '修改人员', hide:true}
            ,{field: 'modiTime$', align: 'center',title: '修改时间', hide:true}
            // ,{field: 'appeUser$', align: 'center',title: '创建者',event: 'appeUser', style: 'cursor:pointer'}
src/main/webapp/views/basDualCrnp/basDualCrnp.html
@@ -69,7 +69,7 @@
<!-- 表单弹窗 -->
<script type="text/html" id="editDialog">
    <form id="detail" lay-filter="detail" class="layui-form admin-form model-form">
        <input name="id" type="hidden">
        <input name="crnNo" type="hidden">
        <div class="layui-row">
            <div class="layui-col-md12">
                <div class="layui-form-item">
@@ -82,12 +82,12 @@
                        </select>
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">工作号: </label>
                    <div class="layui-input-block">
                        <input class="layui-input" name="wrkNo" placeholder="请输入工作号">
                    </div>
                </div>
<!--                <div class="layui-form-item">-->
<!--                    <label class="layui-form-label">工作号: </label>-->
<!--                    <div class="layui-input-block">-->
<!--                        <input class="layui-input" name="wrkNo" placeholder="请输入工作号">-->
<!--                    </div>-->
<!--                </div>-->
                <div class="layui-form-item">
                    <label class="layui-form-label">可入(checkBox): </label>
                    <div class="layui-input-block">
src/main/webapp/views/wrkMast/wrkMast.html
@@ -58,6 +58,8 @@
                        </el-table-column>
                        <el-table-column property="crnNo" label="堆垛机">
                        </el-table-column>
                        <el-table-column property="dualCrnNo" label="双工位堆垛机">
                        </el-table-column>
                        <el-table-column property="systemMsg" label="系统消息">
                        </el-table-column>
                        <el-table-column label="操作" width="100">