You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1340 lines
50 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
  1. package com.gaotao.modules.dashboard.task;
  2. import com.beust.ah.A;
  3. import com.fasterxml.jackson.databind.JsonNode;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import com.gaotao.common.utils.HttpUtils;
  6. import com.gaotao.modules.automatedWarehouse.entity.tusk.AgvStatus;
  7. import com.gaotao.modules.automatedWarehouse.entity.tusk.TuskResponse;
  8. import com.gaotao.modules.automatedWarehouse.service.TuskClientService;
  9. import com.gaotao.modules.dashboard.service.DashboardWebSocketService;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.beans.factory.annotation.Value;
  13. import org.springframework.scheduling.annotation.Scheduled;
  14. import org.springframework.stereotype.Component;
  15. import java.util.*;
  16. /**
  17. * 看板数据推送定时任务
  18. *
  19. * <p><b>功能说明</b></p>
  20. * <ul>
  21. * <li>定时从WCS Board API获取最新数据</li>
  22. * <li>检测到数据变更后通过WebSocket推送到前端</li>
  23. * <li>相比轮询只在数据变更时推送减少无效传输</li>
  24. * </ul>
  25. *
  26. * <p><b>推送策略</b></p>
  27. * <ul>
  28. * <li>每5秒检查一次机械臂拣选数据</li>
  29. * <li>数据有变化时立即推送</li>
  30. * <li>推送失败时记录日志不影响下次推送</li>
  31. * </ul>
  32. *
  33. * @author System
  34. * @since 2025-01-23
  35. */
  36. @Slf4j
  37. @Component
  38. public class DashboardPushTask {
  39. @Autowired
  40. private DashboardWebSocketService webSocketService;
  41. @Value("${custom.wcs-board-api}")
  42. private String wcsBoardApi;
  43. // 看板推送任务开关配置(所有看板共用一个开关)
  44. @Value("${dashboard.push.enabled:true}")
  45. private boolean dashboardPushEnabled;
  46. /**
  47. * 上次推送的数据哈希值用于检测数据变更
  48. */
  49. private Map<String, Integer> lastDataHash = new HashMap<>();
  50. @Autowired
  51. private com.gaotao.modules.dashboard.dao.DashboardDao dashboardDao;
  52. @Autowired(required = false)
  53. private TuskClientService tuskClientService;
  54. @Value("${custom.wcs-url}")
  55. private String wcsUrl;
  56. /**
  57. * 每10秒检查人工拣选数据并推送
  58. *
  59. * <p><b>数据来源</b>WCS系统 - /api/wms/query-auto-sorting-info</p>
  60. *
  61. * <p><b>数据说明</b></p>
  62. * <ul>
  63. * <li>sortingStation=1043 - 第一个表格人工拣选1</li>
  64. * <li>sortingStation=1028 - 第二个表格人工拣选2</li>
  65. * </ul>
  66. *
  67. * <p><b>配置开关</b></p>
  68. * <ul>
  69. * <li>dashboard.push.enabled - 看板推送总开关</li>
  70. * </ul>
  71. */
  72. @Scheduled(fixedRate = 5000)
  73. public void pushManualPickingData() {
  74. // 检查总开关
  75. if (!dashboardPushEnabled) {
  76. log.trace("看板推送已禁用");
  77. return;
  78. }
  79. try {
  80. log.debug("=== 开始获取人工拣选看板数据 ===");
  81. // 从WCS获取人工拣选数据
  82. Map<String, Object> data = getManualPickingDataFromWcs();
  83. // 如果返回null,转换为空数据
  84. if (data == null) {
  85. data = createEmptyManualPickingData();
  86. }
  87. // 计算数据哈希值并推送
  88. int currentHash = data.hashCode();
  89. webSocketService.pushManualPickingData(data);
  90. lastDataHash.put("manual-picking", currentHash);
  91. log.debug("=== 人工拣选数据推送完成 ===");
  92. } catch (Exception e) {
  93. log.error("推送人工拣选数据失败,推送空数据清空前端列表: {}", e.getMessage(), e);
  94. // 异常时推送空数据,避免前端显示过期数据
  95. try {
  96. Map<String, Object> emptyData = createEmptyManualPickingData();
  97. webSocketService.pushManualPickingData(emptyData);
  98. lastDataHash.put("manual-picking", emptyData.hashCode());
  99. } catch (Exception ex) {
  100. log.error("推送空数据失败: {}", ex.getMessage());
  101. }
  102. }
  103. }
  104. /**
  105. * 从WCS获取人工拣选数据
  106. *
  107. * @return 人工拣选数据
  108. */
  109. private Map<String, Object> getManualPickingDataFromWcs() {
  110. try {
  111. String url = wcsUrl + "query-auto-sorting-info";
  112. ObjectMapper mapper = new ObjectMapper();
  113. // 请求1043站数据
  114. log.info("调用WCS人工拣选API (1043站): {}", url);
  115. List<Map<String, Object>> station1043List = getStationData(url, "1043", mapper);
  116. log.info("1043站数据获取完成: {}条", station1043List.size());
  117. // 请求1028站数据
  118. log.info("调用WCS人工拣选API (1028站): {}", url);
  119. List<Map<String, Object>> station1028List = getStationData(url, "1028", mapper);
  120. log.info("1028站数据获取完成: {}条", station1028List.size());
  121. // 构造返回数据
  122. Map<String, Object> resultData = new HashMap<>();
  123. resultData.put("station1043List", station1043List);
  124. resultData.put("station1028List", station1028List);
  125. log.info("=== 人工拣选数据处理完成 === 1043站:{}条, 1028站:{}条",
  126. station1043List.size(), station1028List.size());
  127. return resultData;
  128. } catch (Exception e) {
  129. log.error("从WCS获取人工拣选数据失败: {}", e.getMessage(), e);
  130. return null;
  131. }
  132. }
  133. /**
  134. * 获取指定工作站的数据
  135. *
  136. * @param url WCS API地址
  137. * @param devCode 设备编码工作站号
  138. * @param mapper JSON解析器
  139. * @return 工作站数据列表
  140. */
  141. private List<Map<String, Object>> getStationData(String url, String devCode, ObjectMapper mapper) {
  142. List<Map<String, Object>> stationList = new ArrayList<>();
  143. try {
  144. // 构造POST请求参数 - 直接传devCode字符串
  145. String jsonBody = "\"" + devCode + "\"";
  146. String wcsResponse = HttpUtils.doPost(url, jsonBody, null);
  147. log.debug("WCS API返回数据 ({}站): {}", devCode, wcsResponse);
  148. // 解析JSON数据
  149. JsonNode rootNode = mapper.readTree(wcsResponse);
  150. // 获取sortingStations数组
  151. if (!rootNode.has("sortingStations")) {
  152. log.warn("WCS返回数据中没有sortingStations ({}站)", devCode);
  153. return stationList;
  154. }
  155. JsonNode sortingStations = rootNode.get("sortingStations");
  156. // 处理返回的数据
  157. for (JsonNode station : sortingStations) {
  158. String sortingStation = station.get("sortingStation").asText();
  159. JsonNode materials = station.get("materials");
  160. // 只处理匹配的工作站数据
  161. if (devCode.equals(sortingStation) && materials != null && materials.isArray()) {
  162. for (JsonNode material : materials) {
  163. Map<String, Object> item = convertWcsMaterialToPickingItem(material);
  164. stationList.add(item);
  165. }
  166. }
  167. }
  168. } catch (Exception e) {
  169. log.error("获取{}站数据失败: {}", devCode, e.getMessage(), e);
  170. }
  171. return stationList;
  172. }
  173. /**
  174. * 将WCS返回的material数据转换为人工拣选前端需要的格式
  175. *
  176. * @param material WCS返回的物料数据
  177. * @return 前端表格数据格式
  178. */
  179. private Map<String, Object> convertWcsMaterialToPickingItem(JsonNode material) {
  180. Map<String, Object> item = new HashMap<>();
  181. // 拣选托盘码 (源托盘)
  182. item.put("pickingBatchNo", material.has("sourcePalletCode") ?
  183. material.get("sourcePalletCode").asText() : "");
  184. // 拣选物料名称 (SKU)
  185. item.put("pickingMaterialName", material.has("sku") ?
  186. material.get("sku").asText() : "");
  187. // RFID条码
  188. item.put("rfidBarcode", material.has("rfidBarcode") ?
  189. material.get("rfidBarcode").asText() : "");
  190. // 状态 (根据isCompleted判断)
  191. boolean isCompleted = material.has("isCompleted") && material.get("isCompleted").asBoolean();
  192. item.put("status", isCompleted ? "完成" : "等待分拣");
  193. // 存放位置 (工作站编号)
  194. item.put("storageLocation", material.has("sortingStation") ?
  195. material.get("sortingStation").asText() : "");
  196. return item;
  197. }
  198. /**
  199. * 创建空的人工拣选数据
  200. *
  201. * @return 空的人工拣选数据结构
  202. */
  203. private Map<String, Object> createEmptyManualPickingData() {
  204. Map<String, Object> emptyData = new HashMap<>();
  205. emptyData.put("station1043List", new ArrayList<>());
  206. emptyData.put("station1028List", new ArrayList<>());
  207. return emptyData;
  208. }
  209. /**
  210. * 每5秒检查机械臂拣选数据并推送
  211. *
  212. * <p>注意这个间隔可以根据实际需求调整</p>
  213. * <ul>
  214. * <li>如果数据变化频繁可以缩短间隔如2-3秒</li>
  215. * <li>如果数据变化不频繁可以延长间隔如10-15秒</li>
  216. * </ul>
  217. *
  218. * <p><b>配置开关</b></p>
  219. * <ul>
  220. * <li>dashboard.push.enabled - 看板推送总开关</li>
  221. * </ul>
  222. */
  223. @Scheduled(fixedRate = 10000)
  224. public void pushRobotPickingData() {
  225. // 检查总开关
  226. if (!dashboardPushEnabled) {
  227. log.trace("看板推送已禁用");
  228. return;
  229. }
  230. try {
  231. // 从WCS Board API获取机械臂拣选数据
  232. Map<String, Object> data = getRobotPickingDataFromWcs();
  233. // 如果返回null,转换为空数据(避免前端显示过期数据)
  234. if (data == null) {
  235. data = createEmptyData();
  236. }
  237. // 计算数据哈希值
  238. int currentHash = data.hashCode();
  239. webSocketService.pushRobotPickingData(data);
  240. lastDataHash.put("robot-picking", currentHash);
  241. } catch (Exception e) {
  242. log.error("推送机械臂拣选数据失败,推送空数据清空前端列表: {}", e.getMessage(), e);
  243. // 异常时推送空数据,避免前端显示过期数据
  244. try {
  245. Map<String, Object> emptyData = createEmptyData();
  246. webSocketService.pushRobotPickingData(emptyData);
  247. lastDataHash.put("robot-picking", emptyData.hashCode());
  248. } catch (Exception ex) {
  249. log.error("推送空数据失败: {}", ex.getMessage());
  250. }
  251. }
  252. }
  253. /**
  254. * 从WCS Board API获取机械臂拣选数据
  255. *
  256. * @return 机械臂拣选数据
  257. */
  258. private Map<String, Object> getRobotPickingDataFromWcs() {
  259. try {
  260. // 调用WCS Board API
  261. String url = wcsBoardApi + "WmsDashboard/auto-sorting-info";
  262. String wcsResponse = HttpUtils.doGet(url, null, null);
  263. // 解析JSON数据
  264. ObjectMapper mapper = new ObjectMapper();
  265. JsonNode rootNode = mapper.readTree(wcsResponse);
  266. // 检查返回码
  267. int resCode = rootNode.get("resCode").asInt();
  268. if (resCode != 200) {
  269. log.warn("WCS API返回错误: code={}", resCode);
  270. return null;
  271. }
  272. // 获取sortingStations数组
  273. JsonNode resData = rootNode.get("resData");
  274. if (resData == null || !resData.has("sortingStations")) {
  275. log.warn("WCS返回数据中没有sortingStations");
  276. return createEmptyData();
  277. }
  278. JsonNode sortingStations = resData.get("sortingStations");
  279. // 按照sortingStation分类处理数据
  280. List<Map<String, Object>> containerList = new ArrayList<>(); // 1071-周转箱
  281. List<Map<String, Object>> materialList = new ArrayList<>(); // 1060-原材
  282. for (JsonNode station : sortingStations) {
  283. String sortingStation = station.get("sortingStation").asText();
  284. JsonNode materials = station.get("materials");
  285. if (materials != null && materials.isArray()) {
  286. for (JsonNode material : materials) {
  287. Map<String, Object> item = convertMaterialToItem(material, sortingStation);
  288. // 根据工作站分类
  289. if ("1071".equals(sortingStation)) {
  290. containerList.add(item);
  291. } else if ("1060".equals(sortingStation)) {
  292. materialList.add(item);
  293. }
  294. }
  295. }
  296. }
  297. // 构造返回数据
  298. Map<String, Object> resultData = new HashMap<>();
  299. resultData.put("containerList", containerList);
  300. resultData.put("materialList", materialList);
  301. return resultData;
  302. } catch (Exception e) {
  303. log.error("从WCS获取机械臂拣选数据失败: {}", e.getMessage());
  304. return null;
  305. }
  306. }
  307. /**
  308. * 将WCS返回的material数据转换为前端需要的格式
  309. *
  310. * @param material WCS返回的物料数据
  311. * @param sortingStation 工作站编号
  312. * @return 前端表格数据格式
  313. */
  314. private Map<String, Object> convertMaterialToItem(JsonNode material, String sortingStation) {
  315. Map<String, Object> item = new HashMap<>();
  316. // 拣选托盘码 (源托盘)
  317. item.put("pickingBatchNo", material.has("sourcePalletCode") ?
  318. material.get("sourcePalletCode").asText() : "");
  319. // 拣选物料名称 (SKU)
  320. item.put("pickingMaterialName", material.has("sku") ?
  321. material.get("sku").asText() : "");
  322. // RFID条码
  323. item.put("rfidBarcode", material.has("rfidBarcode") ?
  324. material.get("rfidBarcode").asText() : "");
  325. // 状态 (根据isCompleted判断)
  326. boolean isCompleted = material.has("isCompleted") && material.get("isCompleted").asBoolean();
  327. item.put("status", isCompleted ? "完成" : "等待分拣");
  328. // 存放托盘码 (目标托盘)
  329. item.put("storageBatchNo", material.has("targetPalletCode") ?
  330. material.get("targetPalletCode").asText() : "");
  331. // 存放位置 (工作站编号)
  332. item.put("storageLocation", material.has("sortingStation") ?
  333. material.get("sortingStation").asText() : sortingStation);
  334. return item;
  335. }
  336. /**
  337. * 创建空数据
  338. *
  339. * @return 空的数据结构
  340. */
  341. private Map<String, Object> createEmptyData() {
  342. Map<String, Object> emptyData = new HashMap<>();
  343. emptyData.put("containerList", new ArrayList<>());
  344. emptyData.put("materialList", new ArrayList<>());
  345. return emptyData;
  346. }
  347. /**
  348. * 判断数据是否为空
  349. *
  350. * @param data 待检查的数据
  351. * @return true=数据为空false=数据不为空
  352. */
  353. private boolean isDataEmpty(Map<String, Object> data) {
  354. if (data == null || data.isEmpty()) {
  355. return true;
  356. }
  357. List<?> containerList = (List<?>) data.get("containerList");
  358. List<?> materialList = (List<?>) data.get("materialList");
  359. return (containerList == null || containerList.isEmpty())
  360. && (materialList == null || materialList.isEmpty());
  361. }
  362. /**
  363. * 每5秒检查分切区看板数据并推送
  364. *
  365. * <p><b>数据来源</b></p>
  366. * <ul>
  367. * <li>view_board_slitting_assist_arm - 助力臂区数据</li>
  368. * <li>view_board_slitting_inbound - 分切入库区数据</li>
  369. * </ul>
  370. */
  371. @Scheduled(fixedRate = 10000)
  372. public void pushSlittingBoardData() {
  373. // 检查总开关
  374. if (!dashboardPushEnabled) {
  375. log.trace("看板推送已禁用");
  376. return;
  377. }
  378. try {
  379. // 从数据库视图获取分切区数据
  380. Map<String, Object> data = getSlittingBoardDataFromDb();
  381. // 如果返回null,转换为空数据
  382. if (data == null) {
  383. data = createEmptySlittingData();
  384. }
  385. // 计算数据哈希值
  386. int currentHash = data.hashCode();
  387. webSocketService.pushSlittingBoardData(data);
  388. lastDataHash.put("slitting-board", currentHash);
  389. } catch (Exception e) {
  390. log.error("推送分切区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e);
  391. // 异常时推送空数据,避免前端显示过期数据
  392. try {
  393. Map<String, Object> emptyData = createEmptySlittingData();
  394. webSocketService.pushSlittingBoardData(emptyData);
  395. lastDataHash.put("slitting-board", emptyData.hashCode());
  396. } catch (Exception ex) {
  397. log.error("推送空数据失败: {}", ex.getMessage());
  398. }
  399. }
  400. }
  401. /**
  402. * 从数据库视图获取分切区看板数据
  403. *
  404. * @return 分切区看板数据
  405. */
  406. private Map<String, Object> getSlittingBoardDataFromDb() {
  407. try {
  408. // 查询助力臂区数据
  409. List<Map<String, Object>> assistArmList = dashboardDao.querySlittingAssistArmData();
  410. log.debug("查询到助力臂区数据: {}条", assistArmList != null ? assistArmList.size() : 0);
  411. // 查询分切入库区数据
  412. List<Map<String, Object>> inboundList = dashboardDao.querySlittingInboundData();
  413. log.debug("查询到分切入库区数据: {}条", inboundList != null ? inboundList.size() : 0);
  414. // 构造返回数据
  415. Map<String, Object> resultData = new HashMap<>();
  416. resultData.put("assistArmList", assistArmList != null ? assistArmList : new ArrayList<>());
  417. resultData.put("slittingInboundList", inboundList != null ? inboundList : new ArrayList<>());
  418. return resultData;
  419. } catch (Exception e) {
  420. log.error("从数据库获取分切区看板数据失败: {}", e.getMessage(), e);
  421. return null;
  422. }
  423. }
  424. /**
  425. * 创建空的分切区数据
  426. *
  427. * @return 空的分切区数据结构
  428. */
  429. private Map<String, Object> createEmptySlittingData() {
  430. Map<String, Object> emptyData = new HashMap<>();
  431. emptyData.put("assistArmList", new ArrayList<>());
  432. emptyData.put("slittingInboundList", new ArrayList<>());
  433. return emptyData;
  434. }
  435. /**
  436. * 判断分切区数据是否为空
  437. *
  438. * @param data 待检查的数据
  439. * @return true=数据为空false=数据不为空
  440. */
  441. private boolean isSlittingDataEmpty(Map<String, Object> data) {
  442. if (data == null || data.isEmpty()) {
  443. return true;
  444. }
  445. List<?> assistArmList = (List<?>) data.get("assistArmList");
  446. List<?> inboundList = (List<?>) data.get("slittingInboundList");
  447. return (assistArmList == null || assistArmList.isEmpty())
  448. && (inboundList == null || inboundList.isEmpty());
  449. }
  450. /**
  451. * 每5秒检查智能立体仓库看板数据并推送
  452. *
  453. * <p><b>数据来源</b></p>
  454. * <ul>
  455. * <li>任务统计数据 - queryWarehouseTaskStats</li>
  456. * <li>库位利用率 - queryWarehouseStorageUtilization</li>
  457. * <li>机器人状态 - queryWarehouseRobotStatus</li>
  458. * <li>AGV状态 - queryWarehouseAgvStatus</li>
  459. * <li>领料申请单统计 - queryWarehouseMaterialRequestStats</li>
  460. * <li>发货统计 - queryWarehouseShipmentStats</li>
  461. * </ul>
  462. */
  463. @Scheduled(fixedRate = 10000)
  464. public void pushWarehouse3dBoardData() {
  465. // 检查总开关
  466. if (!dashboardPushEnabled) {
  467. log.trace("看板推送已禁用");
  468. return;
  469. }
  470. try {
  471. // 从数据库获取立体仓库看板数据
  472. Map<String, Object> data = getWarehouse3dBoardDataFromDb();
  473. // 如果返回null,转换为空数据
  474. if (data == null) {
  475. data = createEmptyWarehouse3dData();
  476. }
  477. // 计算数据哈希值
  478. int currentHash = data.hashCode();
  479. webSocketService.pushWarehouse3dBoardData(data);
  480. lastDataHash.put("warehouse-3d", currentHash);
  481. } catch (Exception e) {
  482. log.error("推送智能立体仓库看板数据失败,推送空数据: {}", e.getMessage(), e);
  483. // 异常时推送空数据,避免前端显示过期数据
  484. try {
  485. Map<String, Object> emptyData = createEmptyWarehouse3dData();
  486. webSocketService.pushWarehouse3dBoardData(emptyData);
  487. lastDataHash.put("warehouse-3d", emptyData.hashCode());
  488. } catch (Exception ex) {
  489. log.error("推送空数据失败: {}", ex.getMessage());
  490. }
  491. }
  492. }
  493. /**
  494. * 从数据库获取智能立体仓库看板数据
  495. *
  496. * @return 智能立体仓库看板数据
  497. */
  498. private Map<String, Object> getWarehouse3dBoardDataFromDb() {
  499. try {
  500. log.debug("开始从数据库获取智能立体仓库看板数据");
  501. // 查询任务统计数据
  502. Map<String, Object> taskStats = dashboardDao.queryWarehouseTaskStats();
  503. log.debug("任务统计数据: {}", taskStats);
  504. // 查询库位利用率数据(从WCS Board API获取)
  505. Map<String, Object> storageUtilization = getInventoryStatsFromWcs();
  506. log.debug("库位利用率数据: {}", storageUtilization);
  507. // 查询机器人状态数据(从WMS Dashboard API获取)
  508. List<Map<String, Object>> robotStatus = getRobotSortingInfoFromWms();
  509. log.debug("查询到机器人状态数据: {}条", robotStatus != null ? robotStatus.size() : 0);
  510. // 查询AGV状态数据(从TUSK系统获取)
  511. List<Map<String, Object>> agvStatus = getAgvStatusFromTusk();
  512. log.debug("查询到AGV状态数据: {}条", agvStatus != null ? agvStatus.size() : 0);
  513. // 查询领料申请单统计
  514. //Map<String, Object> materialRequestStats = dashboardDao.queryWarehouseMaterialRequestStats();
  515. Map<String, Object> materialRequestStats = new HashMap<>();
  516. log.debug("领料申请单统计: {}", materialRequestStats);
  517. // 查询发货统计
  518. //Map<String, Object> shipmentStats = dashboardDao.queryWarehouseShipmentStats();
  519. Map<String, Object> shipmentStats = new HashMap<>();
  520. log.debug("发货统计: {}", shipmentStats);
  521. // 查询库存趋势数据
  522. List<Map<String, Object>> rawMaterialTrend = dashboardDao.queryRawMaterialInventoryTrend();
  523. log.debug("查询到原材料库存趋势数据: {}条", rawMaterialTrend != null ? rawMaterialTrend.size() : 0);
  524. List<Map<String, Object>> specifiedMaterialTrend = dashboardDao.querySpecifiedMaterialInventoryTrend();
  525. log.debug("查询到规格料库存趋势数据: {}条", specifiedMaterialTrend != null ? specifiedMaterialTrend.size() : 0);
  526. List<Map<String, Object>> finishedGoodsTrend = dashboardDao.queryFinishedGoodsInventoryTrend();
  527. log.debug("查询到产成品库存趋势数据: {}条", finishedGoodsTrend != null ? finishedGoodsTrend.size() : 0);
  528. // 构造返回数据
  529. Map<String, Object> resultData = new HashMap<>();
  530. resultData.put("taskData", taskStats != null ? taskStats : new HashMap<>());
  531. resultData.put("storageData", storageUtilization != null ? storageUtilization : new HashMap<>());
  532. resultData.put("robotData", robotStatus != null ? robotStatus : new ArrayList<>());
  533. resultData.put("agvData", agvStatus != null ? agvStatus : new ArrayList<>());
  534. resultData.put("materialRequestData", materialRequestStats != null ? materialRequestStats : new HashMap<>());
  535. resultData.put("shipmentData", shipmentStats != null ? shipmentStats : new HashMap<>());
  536. // 添加库存趋势数据
  537. resultData.put("rawMaterialTrend", rawMaterialTrend != null ? rawMaterialTrend : new ArrayList<>());
  538. resultData.put("specifiedMaterialTrend", specifiedMaterialTrend != null ? specifiedMaterialTrend : new ArrayList<>());
  539. resultData.put("finishedGoodsTrend", finishedGoodsTrend != null ? finishedGoodsTrend : new ArrayList<>());
  540. log.debug("智能立体仓库看板数据组装完成");
  541. return resultData;
  542. } catch (Exception e) {
  543. log.error("从数据库获取智能立体仓库看板数据失败: {}", e.getMessage(), e);
  544. return null;
  545. }
  546. }
  547. /**
  548. * 从WCS Board API获取库存统计数据
  549. *
  550. * <p><b>API说明</b></p>
  551. * <ul>
  552. * <li>接口地址: /api/WmsDashboard/inventory-stats</li>
  553. * <li>返回托盘库存统计平托框架托钢托</li>
  554. * <li>返回空容器库存数量</li>
  555. * </ul>
  556. *
  557. * <p><b>数据转换</b></p>
  558. * <ul>
  559. * <li>flatPallet平托 -> flatPallet</li>
  560. * <li>framePallet框架托 -> guardPallet围挡托盘</li>
  561. * <li>steelPallet钢托 -> steelPallet</li>
  562. * <li>emptyContainerInventory -> otherPallet其他</li>
  563. * </ul>
  564. *
  565. * @return 库存统计数据
  566. */
  567. private Map<String, Object> getInventoryStatsFromWcs() {
  568. try {
  569. // 调用WCS Board API
  570. String url = wcsBoardApi + "WmsDashboard/inventory-stats";
  571. log.debug("调用WCS库存统计API: {}", url);
  572. String wcsResponse = HttpUtils.doGet(url, null, null);
  573. log.debug("WCS API返回数据: {}", wcsResponse);
  574. // 解析JSON数据
  575. ObjectMapper mapper = new ObjectMapper();
  576. JsonNode rootNode = mapper.readTree(wcsResponse);
  577. // 检查返回码
  578. int resCode = rootNode.get("resCode").asInt();
  579. if (resCode != 200) {
  580. String resMsg = rootNode.has("resMsg") ? rootNode.get("resMsg").asText() : "未知错误";
  581. log.warn("WCS API返回错误: code={}, msg={}", resCode, resMsg);
  582. return createEmptyStorageData();
  583. }
  584. // 获取resData数据
  585. JsonNode resData = rootNode.get("resData");
  586. if (resData == null || resData.isNull()) {
  587. log.warn("WCS返回数据中没有resData");
  588. return createEmptyStorageData();
  589. }
  590. // 获取materialInventory(物料库存)
  591. JsonNode materialInventory = resData.get("materialInventory");
  592. if (materialInventory == null || materialInventory.isNull()) {
  593. log.warn("WCS返回数据中没有materialInventory");
  594. return createEmptyStorageData();
  595. }
  596. // 提取物料盘各类托盘数量
  597. int materialFlatPallet = materialInventory.has("flatPallet") ?
  598. materialInventory.get("flatPallet").asInt() : 0;
  599. int materialFramePallet = materialInventory.has("framePallet") ?
  600. materialInventory.get("framePallet").asInt() : 0;
  601. int materialSteelPallet = materialInventory.has("steelPallet") ?
  602. materialInventory.get("steelPallet").asInt() : 0;
  603. // 提取空容器各类托盘数量
  604. JsonNode emptyContainerInventory = resData.get("emptyContainerInventory");
  605. int emptyFlatPallet = 0;
  606. int emptyFramePallet = 0;
  607. int emptySteelPallet = 0;
  608. if (emptyContainerInventory != null && !emptyContainerInventory.isNull()) {
  609. emptyFlatPallet = emptyContainerInventory.has("flatPallet") ?
  610. emptyContainerInventory.get("flatPallet").asInt() : 0;
  611. emptyFramePallet = emptyContainerInventory.has("framePallet") ?
  612. emptyContainerInventory.get("framePallet").asInt() : 0;
  613. emptySteelPallet = emptyContainerInventory.has("steelPallet") ?
  614. emptyContainerInventory.get("steelPallet").asInt() : 0;
  615. }
  616. // 计算总使用库位和利用率(物料盘+空盘)
  617. int usedSlots = materialFlatPallet + materialFramePallet + materialSteelPallet
  618. + emptyFlatPallet + emptyFramePallet + emptySteelPallet;
  619. int totalSlots = 1960; // 固定值
  620. double utilizationRate = totalSlots > 0 ?
  621. Math.round((double) usedSlots / totalSlots * 1000.0) / 10.0 : 0.0;
  622. // 构造物料盘库存数据
  623. Map<String, Object> materialInventoryData = new HashMap<>();
  624. materialInventoryData.put("flatPallet", materialFlatPallet+emptyFlatPallet); // 物料盘-平托
  625. materialInventoryData.put("framePallet", materialFramePallet+emptyFramePallet); // 物料盘-围框托盘
  626. materialInventoryData.put("steelPallet", materialSteelPallet+emptySteelPallet); // 物料盘-钢底托
  627. // 构造空盘库存数据
  628. Map<String, Object> emptyContainerInventoryData = new HashMap<>();
  629. emptyContainerInventoryData.put("flatPallet", emptyFlatPallet); // 空盘-平托
  630. emptyContainerInventoryData.put("framePallet", emptyFramePallet); // 空盘-围框托盘
  631. emptyContainerInventoryData.put("steelPallet", emptySteelPallet); // 空盘-钢底托
  632. // 构造返回数据
  633. Map<String, Object> storageData = new HashMap<>();
  634. storageData.put("totalSlots", totalSlots); // 总库位数
  635. storageData.put("usedSlots", usedSlots); // 已使用库位数
  636. storageData.put("utilizationRate", utilizationRate); // 利用率
  637. storageData.put("materialInventory", materialInventoryData); // 物料盘库存
  638. storageData.put("emptyContainerInventory", emptyContainerInventoryData); // 空盘库存
  639. log.info("库存统计数据获取成功 - 物料盘[平托:{}, 框架托:{}, 钢托:{}], 空盘[平托:{}, 框架托:{}, 钢托:{}], 总使用:{}, 利用率:{}%",
  640. materialFlatPallet, materialFramePallet, materialSteelPallet,
  641. emptyFlatPallet, emptyFramePallet, emptySteelPallet, usedSlots, utilizationRate);
  642. return storageData;
  643. } catch (Exception e) {
  644. log.error("从WCS获取库存统计数据失败: {}", e.getMessage(), e);
  645. return createEmptyStorageData();
  646. }
  647. }
  648. /**
  649. * 创建空的库存统计数据
  650. *
  651. * @return 空的库存统计数据结构
  652. */
  653. private Map<String, Object> createEmptyStorageData() {
  654. // 构造空的物料盘库存数据
  655. Map<String, Object> emptyMaterialInventory = new HashMap<>();
  656. emptyMaterialInventory.put("flatPallet", 0);
  657. emptyMaterialInventory.put("framePallet", 0);
  658. emptyMaterialInventory.put("steelPallet", 0);
  659. // 构造空的空盘库存数据
  660. Map<String, Object> emptyContainerInventory = new HashMap<>();
  661. emptyContainerInventory.put("flatPallet", 0);
  662. emptyContainerInventory.put("framePallet", 0);
  663. emptyContainerInventory.put("steelPallet", 0);
  664. // 构造返回数据
  665. Map<String, Object> emptyData = new HashMap<>();
  666. emptyData.put("totalSlots", 1960);
  667. emptyData.put("usedSlots", 0);
  668. emptyData.put("utilizationRate", 0.0);
  669. emptyData.put("materialInventory", emptyMaterialInventory);
  670. emptyData.put("emptyContainerInventory", emptyContainerInventory);
  671. return emptyData;
  672. }
  673. /**
  674. * 创建空的智能立体仓库数据
  675. *
  676. * @return 空的智能立体仓库数据结构
  677. */
  678. private Map<String, Object> createEmptyWarehouse3dData() {
  679. Map<String, Object> emptyData = new HashMap<>();
  680. emptyData.put("taskData", new HashMap<>());
  681. emptyData.put("storageData", new HashMap<>());
  682. emptyData.put("robotData", new ArrayList<>());
  683. emptyData.put("agvData", new ArrayList<>());
  684. emptyData.put("materialRequestData", new HashMap<>());
  685. emptyData.put("shipmentData", new HashMap<>());
  686. return emptyData;
  687. }
  688. /**
  689. * 判断智能立体仓库数据是否为空
  690. *
  691. * @param data 待检查的数据
  692. * @return true=数据为空false=数据不为空
  693. */
  694. private boolean isWarehouse3dDataEmpty(Map<String, Object> data) {
  695. if (data == null || data.isEmpty()) {
  696. return true;
  697. }
  698. Map<?, ?> taskData = (Map<?, ?>) data.get("taskData");
  699. Map<?, ?> storageData = (Map<?, ?>) data.get("storageData");
  700. List<?> robotData = (List<?>) data.get("robotData");
  701. List<?> agvData = (List<?>) data.get("agvData");
  702. return (taskData == null || taskData.isEmpty())
  703. && (storageData == null || storageData.isEmpty())
  704. && (robotData == null || robotData.isEmpty())
  705. && (agvData == null || agvData.isEmpty());
  706. }
  707. /**
  708. * 从TUSK系统获取AGV状态数据
  709. *
  710. * <p><b>数据转换说明</b></p>
  711. * <ul>
  712. * <li>从TUSK获取原始AGV状态</li>
  713. * <li>转换为看板需要的格式</li>
  714. * <li>映射状态码为状态文本</li>
  715. * </ul>
  716. *
  717. * @return AGV状态列表
  718. */
  719. private List<Map<String, Object>> getAgvStatusFromTusk() {
  720. List<Map<String, Object>> agvList = new ArrayList<>();
  721. try {
  722. // 如果TUSK客户端服务未配置,返回空列表
  723. if (tuskClientService == null) {
  724. log.debug("TUSK客户端服务未配置,跳过AGV状态查询");
  725. return agvList;
  726. }
  727. // 调用TUSK接口获取在线AGV列表
  728. TuskResponse<List<AgvStatus>> response = tuskClientService.getOnlineRobots();
  729. if (!response.isSuccess() || response.getData() == null) {
  730. log.warn("从TUSK获取AGV状态失败: {}", response.getMsg());
  731. return agvList;
  732. }
  733. // 转换TUSK数据为看板格式
  734. List<AgvStatus> tuskAgvList = response.getData();
  735. for (AgvStatus agvStatus : tuskAgvList) {
  736. Map<String, Object> agv = new HashMap<>();
  737. // AGV编号
  738. agv.put("id", agvStatus.getId());
  739. agv.put("name", "AGV#" + agvStatus.getId());
  740. // 状态转换
  741. String status = convertAgvStatus(agvStatus.getAgvStat());
  742. agv.put("status", status.toLowerCase()); // working/idle/charging/error
  743. agv.put("statusText", getAgvStatusText(agvStatus.getAgvStat()));
  744. // 电量
  745. agv.put("battery", agvStatus.getSoc());
  746. // 当前任务数(根据状态判断:运行中为1,否则为0)
  747. int tasks = (agvStatus.getAgvStat() >= 1 && agvStatus.getAgvStat() <= 12) ? 1 : 0;
  748. agv.put("tasks", tasks);
  749. agvList.add(agv);
  750. }
  751. log.debug("成功从TUSK获取{}个AGV状态", agvList.size());
  752. } catch (Exception e) {
  753. log.error("从TUSK获取AGV状态异常: {}", e.getMessage(), e);
  754. }
  755. return agvList;
  756. }
  757. /**
  758. * 转换AGV状态码为标准状态
  759. *
  760. * @param agvStat TUSK系统的AGV状态码
  761. * @return 标准状态 (working/idle/charging/error)
  762. */
  763. private String convertAgvStatus(Integer agvStat) {
  764. if (agvStat == null) {
  765. return "idle";
  766. }
  767. if (agvStat == 0) {
  768. return "idle"; // 空闲
  769. } else if (agvStat >= 1 && agvStat <= 12) {
  770. return "working"; // 运行中
  771. } else if (agvStat == 13) {
  772. return "charging"; // 充电中
  773. } else if (agvStat >= 128) {
  774. return "error"; // 异常状态
  775. }
  776. return "idle";
  777. }
  778. /**
  779. * 获取AGV状态文本中文
  780. *
  781. * @param agvStat TUSK系统的AGV状态码
  782. * @return 状态文本
  783. */
  784. private String getAgvStatusText(Integer agvStat) {
  785. if (agvStat == null) {
  786. return "空闲";
  787. }
  788. if (agvStat == 0) {
  789. return "空闲";
  790. } else if (agvStat == 1) {
  791. return "运行中";
  792. } else if (agvStat == 2) {
  793. return "直线运动中";
  794. } else if (agvStat == 3) {
  795. return "旋转中";
  796. } else if (agvStat == 13) {
  797. return "充电中";
  798. } else if (agvStat == 23) {
  799. return "暂停";
  800. } else if (agvStat == 128) {
  801. return "异常状态";
  802. } else if (agvStat == 129) {
  803. return "急停";
  804. } else if (agvStat == 130) {
  805. return "碰撞告警";
  806. } else if (agvStat == 131) {
  807. return "告警";
  808. } else if (agvStat >= 1 && agvStat <= 12) {
  809. return "运行中";
  810. } else if (agvStat >= 128) {
  811. return "异常";
  812. }
  813. return "未知状态";
  814. }
  815. /**
  816. * 每5秒检查成品入库出库区看板数据并推送
  817. *
  818. * <p><b>数据来源</b></p>
  819. * <ul>
  820. * <li>view_board_finish_package - 成品包装区数据</li>
  821. * <li>view_board_finish_inbound - 成品入库区数据</li>
  822. * </ul>
  823. */
  824. @Scheduled(fixedRate = 10000)
  825. public void pushFinishedProductBoardData() {
  826. // 检查总开关
  827. if (!dashboardPushEnabled) {
  828. log.trace("看板推送已禁用");
  829. return;
  830. }
  831. try {
  832. // 从数据库视图获取成品区数据
  833. Map<String, Object> data = getFinishedProductBoardDataFromDb();
  834. // 如果返回null,转换为空数据
  835. if (data == null) {
  836. data = createEmptyFinishedProductData();
  837. }
  838. // 计算数据哈希值
  839. int currentHash = data.hashCode();
  840. webSocketService.pushFinishedProductBoardData(data);
  841. lastDataHash.put("finished-product", currentHash);
  842. } catch (Exception e) {
  843. log.error("推送成品入库出库区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e);
  844. // 异常时推送空数据,避免前端显示过期数据
  845. try {
  846. Map<String, Object> emptyData = createEmptyFinishedProductData();
  847. webSocketService.pushFinishedProductBoardData(emptyData);
  848. lastDataHash.put("finished-product", emptyData.hashCode());
  849. } catch (Exception ex) {
  850. log.error("推送空数据失败: {}", ex.getMessage());
  851. }
  852. }
  853. }
  854. /**
  855. * 从数据库视图获取成品入库出库区看板数据
  856. *
  857. * @return 成品入库出库区看板数据
  858. */
  859. private Map<String, Object> getFinishedProductBoardDataFromDb() {
  860. try {
  861. // 查询成品包装区数据
  862. List<Map<String, Object>> packageList = dashboardDao.queryFinishPackageData();
  863. log.debug("查询到成品包装区数据: {}条", packageList != null ? packageList.size() : 0);
  864. // 查询成品入库区数据
  865. List<Map<String, Object>> inboundList = dashboardDao.queryFinishInboundData();
  866. log.debug("查询到成品入库区数据: {}条", inboundList != null ? inboundList.size() : 0);
  867. // 构造返回数据
  868. Map<String, Object> resultData = new HashMap<>();
  869. resultData.put("packagingList", packageList != null ? packageList : new ArrayList<>());
  870. resultData.put("inboundList", inboundList != null ? inboundList : new ArrayList<>());
  871. return resultData;
  872. } catch (Exception e) {
  873. log.error("从数据库获取成品入库出库区看板数据失败: {}", e.getMessage(), e);
  874. return null;
  875. }
  876. }
  877. /**
  878. * 创建空的成品入库出库区数据
  879. *
  880. * @return 空的成品入库出库区数据结构
  881. */
  882. private Map<String, Object> createEmptyFinishedProductData() {
  883. Map<String, Object> emptyData = new HashMap<>();
  884. emptyData.put("packagingList", new ArrayList<>());
  885. emptyData.put("inboundList", new ArrayList<>());
  886. return emptyData;
  887. }
  888. /**
  889. * 判断成品入库出库区数据是否为空
  890. *
  891. * @param data 待检查的数据
  892. * @return true=数据为空false=数据不为空
  893. */
  894. private boolean isFinishedProductDataEmpty(Map<String, Object> data) {
  895. if (data == null || data.isEmpty()) {
  896. return true;
  897. }
  898. List<?> packageList = (List<?>) data.get("packagingList");
  899. List<?> inboundList = (List<?>) data.get("inboundList");
  900. return (packageList == null || packageList.isEmpty())
  901. && (inboundList == null || inboundList.isEmpty());
  902. }
  903. /**
  904. * 每5秒检查原材收货区看板数据并推送
  905. *
  906. * <p><b>数据来源</b></p>
  907. * <ul>
  908. * <li>view_board_receiving_receive - 原材收货区数据</li>
  909. * <li>view_board_receiving_inbound - 原材入库区数据</li>
  910. * </ul>
  911. */
  912. @Scheduled(fixedRate = 10000)
  913. public void pushMaterialReceivingBoardData() {
  914. // 检查总开关
  915. if (!dashboardPushEnabled) {
  916. log.trace("看板推送已禁用");
  917. return;
  918. }
  919. try {
  920. // 从数据库视图获取原材收货区数据
  921. Map<String, Object> data = getMaterialReceivingBoardDataFromDb();
  922. // 如果返回null,转换为空数据
  923. if (data == null) {
  924. data = createEmptyMaterialReceivingData();
  925. }
  926. // 计算数据哈希值
  927. int currentHash = data.hashCode();
  928. webSocketService.pushMaterialReceivingBoardData(data);
  929. lastDataHash.put("material-receiving", currentHash);
  930. } catch (Exception e) {
  931. log.error("推送原材收货区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e);
  932. // 异常时推送空数据,避免前端显示过期数据
  933. try {
  934. Map<String, Object> emptyData = createEmptyMaterialReceivingData();
  935. webSocketService.pushMaterialReceivingBoardData(emptyData);
  936. lastDataHash.put("material-receiving", emptyData.hashCode());
  937. } catch (Exception ex) {
  938. log.error("推送空数据失败: {}", ex.getMessage());
  939. }
  940. }
  941. }
  942. /**
  943. * 从数据库视图获取原材收货区看板数据
  944. *
  945. * @return 原材收货区看板数据
  946. */
  947. private Map<String, Object> getMaterialReceivingBoardDataFromDb() {
  948. try {
  949. // 查询原材收货区数据
  950. List<Map<String, Object>> receiveList = dashboardDao.queryReceivingReceiveData();
  951. log.debug("查询到原材收货区数据: {}条", receiveList != null ? receiveList.size() : 0);
  952. // 查询原材入库区数据
  953. List<Map<String, Object>> inboundList = dashboardDao.queryReceivingInboundData();
  954. log.debug("查询到原材入库区数据: {}条", inboundList != null ? inboundList.size() : 0);
  955. // 构造返回数据
  956. Map<String, Object> resultData = new HashMap<>();
  957. resultData.put("receivingList", receiveList != null ? receiveList : new ArrayList<>());
  958. resultData.put("inboundList", inboundList != null ? inboundList : new ArrayList<>());
  959. return resultData;
  960. } catch (Exception e) {
  961. log.error("从数据库获取原材收货区看板数据失败: {}", e.getMessage(), e);
  962. return null;
  963. }
  964. }
  965. /**
  966. * 创建空的原材收货区数据
  967. *
  968. * @return 空的原材收货区数据结构
  969. */
  970. private Map<String, Object> createEmptyMaterialReceivingData() {
  971. Map<String, Object> emptyData = new HashMap<>();
  972. emptyData.put("receivingList", new ArrayList<>());
  973. emptyData.put("inboundList", new ArrayList<>());
  974. return emptyData;
  975. }
  976. /**
  977. * 判断原材收货区数据是否为空
  978. *
  979. * @param data 待检查的数据
  980. * @return true=数据为空false=数据不为空
  981. */
  982. private boolean isMaterialReceivingDataEmpty(Map<String, Object> data) {
  983. if (data == null || data.isEmpty()) {
  984. return true;
  985. }
  986. List<?> receiveList = (List<?>) data.get("receivingList");
  987. List<?> inboundList = (List<?>) data.get("inboundList");
  988. return (receiveList == null || receiveList.isEmpty())
  989. && (inboundList == null || inboundList.isEmpty());
  990. }
  991. /**
  992. * 从WMS Dashboard API获取机械臂分拣信息
  993. *
  994. * <p><b>数据转换说明</b></p>
  995. * <ul>
  996. * <li>从WMS获取原始机械臂分拣状态</li>
  997. * <li>转换为看板需要的格式</li>
  998. * <li>映射工作模式和设备状态枚举值</li>
  999. * <li>1071=机械手1, 1060=机械手2</li>
  1000. * </ul>
  1001. *
  1002. * @return 机械臂分拣状态列表
  1003. */
  1004. private List<Map<String, Object>> getRobotSortingInfoFromWms() {
  1005. List<Map<String, Object>> robotList = new ArrayList<>();
  1006. try {
  1007. // 调用WMS Dashboard API(使用配置文件中的URL)
  1008. String url = wcsBoardApi + "WmsDashboard/robot-sorting-info";
  1009. log.debug("调用WMS机械臂分拣API: {}", url);
  1010. String wmsResponse = HttpUtils.doGet(url, null, null);
  1011. log.debug("WMS API返回数据: {}", wmsResponse);
  1012. // 解析JSON数据
  1013. ObjectMapper mapper = new ObjectMapper();
  1014. JsonNode rootNode = mapper.readTree(wmsResponse);
  1015. // 检查返回码
  1016. int resCode = rootNode.get("resCode").asInt();
  1017. if (resCode != 200) {
  1018. String resMsg = rootNode.has("resMsg") ? rootNode.get("resMsg").asText() : "未知错误";
  1019. log.warn("WMS机械臂分拣API返回错误: code={}, msg={}", resCode, resMsg);
  1020. return robotList;
  1021. }
  1022. // 获取resData.sortingStations数组
  1023. if (!rootNode.has("resData") || !rootNode.get("resData").has("sortingStations")) {
  1024. log.warn("WMS返回数据中没有sortingStations");
  1025. return robotList;
  1026. }
  1027. JsonNode sortingStations = rootNode.get("resData").get("sortingStations");
  1028. if (!sortingStations.isArray()) {
  1029. log.warn("sortingStations不是数组格式");
  1030. return robotList;
  1031. }
  1032. // 转换数据为看板格式
  1033. for (JsonNode station : sortingStations) {
  1034. Map<String, Object> robot = new HashMap<>();
  1035. // 分拣站编号
  1036. String sortingStation = station.has("sortingStation") ? station.get("sortingStation").asText() : "";
  1037. robot.put("id", sortingStation);
  1038. // 机械手名称映射:1071=机械手1, 1060=机械手2
  1039. String robotName;
  1040. if ("1071".equals(sortingStation)) {
  1041. robotName = "机械手 1";
  1042. } else if ("1060".equals(sortingStation)) {
  1043. robotName = "机械手 2";
  1044. } else {
  1045. robotName = "机械手 " + sortingStation;
  1046. }
  1047. robot.put("name", robotName);
  1048. // 待处理物料数量(任务数)
  1049. int pendingMaterialCount = station.has("pendingMaterialCount") ? station.get("pendingMaterialCount").asInt() : 0;
  1050. robot.put("tasks", pendingMaterialCount);
  1051. // 工作模式(OperationMode枚举)
  1052. int workModel = station.has("workModel") ? station.get("workModel").asInt() : 0;
  1053. robot.put("workModel", workModel);
  1054. robot.put("workModelText", getWorkModelText(workModel));
  1055. // 设备状态(DeviceStatus枚举)
  1056. int workStatus = station.has("workStatus") ? station.get("workStatus").asInt() : 0;
  1057. robot.put("workStatus", workStatus);
  1058. robot.put("workStatusText", getWorkStatusText(workStatus));
  1059. // 转换为标准状态(working/idle/error/maintenance)
  1060. String status = convertRobotStatus(workModel, workStatus);
  1061. robot.put("status", status);
  1062. robot.put("statusText", getWorkStatusText(workStatus));
  1063. robotList.add(robot);
  1064. }
  1065. log.info("从WMS获取到{}个机械臂分拣站状态", robotList.size());
  1066. } catch (Exception e) {
  1067. log.error("从WMS获取机械臂分拣数据失败", e);
  1068. }
  1069. return robotList;
  1070. }
  1071. /**
  1072. * 获取工作模式文本
  1073. *
  1074. * @param workModel 工作模式枚举值
  1075. * @return 工作模式文本
  1076. */
  1077. private String getWorkModelText(int workModel) {
  1078. switch (workModel) {
  1079. case 1:
  1080. return "自动模式";
  1081. case 2:
  1082. return "半自动模式";
  1083. case 3:
  1084. return "手动模式";
  1085. case 4:
  1086. return "报警模式";
  1087. case 5:
  1088. return "维护模式";
  1089. default:
  1090. return "未定义";
  1091. }
  1092. }
  1093. /**
  1094. * 获取设备状态文本
  1095. *
  1096. * @param workStatus 设备状态枚举值
  1097. * @return 设备状态文本
  1098. */
  1099. private String getWorkStatusText(int workStatus) {
  1100. switch (workStatus) {
  1101. case 1:
  1102. return "空闲";
  1103. case 2:
  1104. return "取货中";
  1105. case 3:
  1106. return "RFID检测中";
  1107. case 4:
  1108. return "放货中";
  1109. case 5:
  1110. return "等待上位反馈完成";
  1111. case 6:
  1112. return "等待流线托盘到位";
  1113. default:
  1114. return "未定义";
  1115. }
  1116. }
  1117. /**
  1118. * 转换机械臂状态为标准状态
  1119. *
  1120. * @param workModel 工作模式
  1121. * @param workStatus 设备状态
  1122. * @return 标准状态 (working/idle/error/maintenance)
  1123. */
  1124. private String convertRobotStatus(int workModel, int workStatus) {
  1125. // 报警模式
  1126. if (workModel == 4) {
  1127. return "error";
  1128. }
  1129. // 维护模式
  1130. if (workModel == 5) {
  1131. return "maintenance";
  1132. }
  1133. // 根据设备状态判断
  1134. if (workStatus == 1) {
  1135. return "idle"; // 空闲
  1136. } else if (workStatus >= 2 && workStatus <= 6) {
  1137. return "working"; // 工作中
  1138. }
  1139. return "idle";
  1140. }
  1141. }