Code/.omc/state/agent-replay-358cfb75-d493-40fb-94ed-f795f943182b.jsonl
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,17 @@ {"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:brainstorming"} {"t":0,"agent":"aab4d29","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"aab4d29","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":301448} {"t":0,"agent":"a3ca65d","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"a3ca65d","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":110707} {"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:writing-plans"} {"t":0,"agent":"a8078d8","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"a8078d8","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":48817} {"t":0,"agent":"a789651","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"a789651","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":363405} {"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:subagent-driven-development"} {"t":0,"agent":"a9cfe1f","agent_type":"unknown","event":"agent_stop","success":true} {"t":0,"agent":"a089308","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"a089308","agent_type":"general-purpose","event":"agent_stop","success":true,"duration_ms":171986} {"t":0,"agent":"a4b1abc","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"ab8096e","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"} {"t":0,"agent":"adc57d5","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"} Code/.omc/state/checkpoints/checkpoint-2026-04-16T05-56-05-020Z.json
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,16 @@ { "created_at": "2026-04-16T05:56:05.018Z", "trigger": "auto", "active_modes": {}, "todo_summary": { "pending": 0, "in_progress": 0, "completed": 0 }, "wisdom_exported": false, "background_jobs": { "active": [], "recent": [], "stats": null } } Code/.omc/state/idle-notif-cooldown.json
@@ -1,3 +1,3 @@ { "lastSentAt": "2026-04-15T14:42:46.838Z" "lastSentAt": "2026-04-16T07:39:40.298Z" } Code/.omc/state/last-tool-error.json
@@ -1,7 +1,7 @@ { "tool_name": "Bash", "tool_input_preview": "{\"command\":\"dotnet build D:/Git/ShanMeiXinNengYuan/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln\",\"timeout\":300000,\"description\":\"Build WCS solution to verify fix\"}", "error": "Exit code 1\n æ£å¨ç¡®å®è¦è¿åç项ç®â¦\r\n ææé¡¹ç®åæ¯ææ°çï¼æ æ³è¿åã\r\n WIDESEAWCS_Common -> D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\bin\\Debug\\net8.0\\WIDESEAWCS_Common.dll\r\nD:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Communicator\\AllenBrandly\\AllenBrandlyEtherNetCommunicator.cs(110,80): warning CS1570: XML 注éåºç° XML æ ¼å¼é误 --âç»ææ è®°âparamâä¸å¼å§æ è®°âTâä¸å¹é ãâ [D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Communicator\\WIDESEAWCS_Communicator.csproj]\r\nD:\\Git\\ShanM...", "timestamp": "2026-04-15T14:12:47.386Z", "tool_input_preview": "{\"command\":\"cd /d D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WCS\\\\WIDESEAWCS_Server && dotnet build WIDESEAWCS_Server.sln 2>&1\",\"timeout\":120000,\"description\":\"Build WCS solution to verify changes\"}", "error": "Exit code 1\n/usr/bin/bash: line 1: cd: too many arguments", "timestamp": "2026-04-16T06:02:11.459Z", "retry_count": 1 } Code/.omc/state/mission-state.json
@@ -1,5 +1,5 @@ { "updatedAt": "2026-04-15T14:16:41.286Z", "updatedAt": "2026-04-16T06:05:51.361Z", "missions": [ { "id": "session:9007b9ea-1eb6-4d24-8fe7-2c3a949eac88:none", @@ -620,6 +620,132 @@ "sourceKey": "session-stop:a991ee262522932f5" } ] }, { "id": "session:358cfb75-d493-40fb-94ed-f795f943182b:none", "source": "session", "name": "none", "objective": "Session mission", "createdAt": "2026-04-16T02:59:26.042Z", "updatedAt": "2026-04-16T06:05:51.361Z", "status": "running", "workerCount": 8, "taskCounts": { "total": 8, "pending": 0, "blocked": 0, "inProgress": 3, "completed": 5, "failed": 0 }, "agents": [ { "name": "code-reviewer:aab4d29", "role": "code-reviewer", "ownership": "aab4d29faa1a3cd33", "status": "done", "currentStep": null, "latestUpdate": "completed", "completedSummary": null, "updatedAt": "2026-04-16T05:57:18.762Z" }, { "name": "code-reviewer:a3ca65d", "role": "code-reviewer", "ownership": "a3ca65d5e9e566acc", "status": "done", "currentStep": null, "latestUpdate": "completed", "completedSummary": null, "updatedAt": "2026-04-16T03:21:01.279Z" }, { "name": "code-reviewer:a8078d8", "role": "code-reviewer", "ownership": "a8078d862c25a2973", "status": "done", "currentStep": null, "latestUpdate": "completed", "completedSummary": null, "updatedAt": "2026-04-16T05:40:16.461Z" }, { "name": "code-reviewer:a789651", "role": "code-reviewer", "ownership": "a78965129a4ecaee2", "status": "done", "currentStep": null, "latestUpdate": "completed", "completedSummary": null, "updatedAt": "2026-04-16T05:50:59.155Z" }, { "name": "general-purpose:a089308", "role": "general-purpose", "ownership": "a089308fce13261b6", "status": "done", "currentStep": null, "latestUpdate": "completed", "completedSummary": null, "updatedAt": "2026-04-16T06:00:35.344Z" }, { "name": "general-purpose:a4b1abc", "role": "general-purpose", "ownership": "a4b1abcbbe45f220a", "status": "running", "currentStep": null, "latestUpdate": null, "completedSummary": null, "updatedAt": "2026-04-16T06:00:51.582Z" }, { "name": "general-purpose:ab8096e", "role": "general-purpose", "ownership": "ab8096ee80268e720", "status": "running", "currentStep": null, "latestUpdate": null, "completedSummary": null, "updatedAt": "2026-04-16T06:05:29.655Z" }, { "name": "general-purpose:adc57d5", "role": "general-purpose", "ownership": "adc57d5ba6bbc20c1", "status": "running", "currentStep": null, "latestUpdate": null, "completedSummary": null, "updatedAt": "2026-04-16T06:05:51.361Z" } ], "timeline": [ { "id": "session-start:a4b1abcbbe45f220a:2026-04-16T06:00:51.582Z", "at": "2026-04-16T06:00:51.582Z", "kind": "update", "agent": "general-purpose:a4b1abc", "detail": "started general-purpose:a4b1abc", "sourceKey": "session-start:a4b1abcbbe45f220a" }, { "id": "session-start:ab8096ee80268e720:2026-04-16T06:05:29.655Z", "at": "2026-04-16T06:05:29.655Z", "kind": "update", "agent": "general-purpose:ab8096e", "detail": "started general-purpose:ab8096e", "sourceKey": "session-start:ab8096ee80268e720" }, { "id": "session-start:adc57d5ba6bbc20c1:2026-04-16T06:05:51.361Z", "at": "2026-04-16T06:05:51.361Z", "kind": "update", "agent": "general-purpose:adc57d5", "detail": "started general-purpose:adc57d5", "sourceKey": "session-start:adc57d5ba6bbc20c1" } ] } ] } Code/.omc/state/subagent-tracking.json
@@ -377,10 +377,76 @@ "status": "completed", "completed_at": "2026-04-15T14:16:41.286Z", "duration_ms": 246360 }, { "agent_id": "aab4d29faa1a3cd33", "agent_type": "code-reviewer", "started_at": "2026-04-16T02:59:26.042Z", "parent_mode": "none", "status": "completed", "completed_at": "2026-04-16T03:04:27.490Z", "duration_ms": 301448 }, { "agent_id": "a3ca65d5e9e566acc", "agent_type": "code-reviewer", "started_at": "2026-04-16T03:19:10.572Z", "parent_mode": "none", "status": "completed", "completed_at": "2026-04-16T03:21:01.279Z", "duration_ms": 110707 }, { "agent_id": "a8078d862c25a2973", "agent_type": "code-reviewer", "started_at": "2026-04-16T05:39:27.644Z", "parent_mode": "none", "status": "completed", "completed_at": "2026-04-16T05:40:16.461Z", "duration_ms": 48817 }, { "agent_id": "a78965129a4ecaee2", "agent_type": "code-reviewer", "started_at": "2026-04-16T05:44:55.750Z", "parent_mode": "none", "status": "completed", "completed_at": "2026-04-16T05:50:59.155Z", "duration_ms": 363405 }, { "agent_id": "a089308fce13261b6", "agent_type": "general-purpose", "started_at": "2026-04-16T05:57:43.358Z", "parent_mode": "none", "status": "completed", "completed_at": "2026-04-16T06:00:35.344Z", "duration_ms": 171986 }, { "agent_id": "a4b1abcbbe45f220a", "agent_type": "general-purpose", "started_at": "2026-04-16T06:00:51.582Z", "parent_mode": "none", "status": "running" }, { "agent_id": "ab8096ee80268e720", "agent_type": "general-purpose", "started_at": "2026-04-16T06:05:29.655Z", "parent_mode": "none", "status": "running" }, { "agent_id": "adc57d5ba6bbc20c1", "agent_type": "general-purpose", "started_at": "2026-04-16T06:05:51.361Z", "parent_mode": "none", "status": "running" } ], "total_spawned": 42, "total_completed": 42, "total_spawned": 50, "total_completed": 47, "total_failed": 0, "last_updated": "2026-04-15T14:16:41.394Z" "last_updated": "2026-04-16T06:05:51.474Z" } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs
@@ -29,6 +29,13 @@ bool MarkAsUsed(List<int> positions); /// <summary> /// æ è®°æå®ç¹ä½ä¸ºå¯ç¨ï¼éæ¾ç¹ä½ï¼ /// </summary> /// <param name="positions">ç¹ä½ç´¢å¼å表</param> /// <returns>æ¯å¦æå</returns> bool MarkAsAvailable(List<int> positions); /// <summary> /// æ ¹æ®è¡ååè·åç¹ä½ç´¢å¼ /// </summary> /// <param name="row">è¡ï¼1-3ï¼</param> Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs
@@ -29,6 +29,13 @@ bool MarkAsUsed(List<int> positions); /// <summary> /// æ è®°æå®ç¹ä½ä¸ºå¯ç¨ï¼éæ¾ç¹ä½ï¼ /// </summary> /// <param name="positions">ç¹ä½ç´¢å¼å表</param> /// <returns>æ¯å¦æå</returns> bool MarkAsAvailable(List<int> positions); /// <summary> /// æ ¹æ®è¡ååè·åç¹ä½ç´¢å¼ /// </summary> /// <param name="row">è¡ï¼1-3ï¼</param> Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs
@@ -78,6 +78,18 @@ } /// <inheritdoc/> public bool MarkAsAvailable(List<int> positions) { if (positions == null || positions.Count == 0) return true; return Db.Updateable<Dt_FakeBatteryPosition>() .SetColumns(x => x.IsUsed, false) .Where(x => positions.Contains(x.PositionIndex)) .ExecuteCommand() > 0; } /// <inheritdoc/> public int? GetPositionIndex(int row, int col) { var entity = Db.Queryable<Dt_FakeBatteryPosition>() Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs
@@ -33,6 +33,12 @@ } /// <inheritdoc/> public bool MarkAsAvailable(List<int> positions) { return BaseDal.MarkAsAvailable(positions); } /// <inheritdoc/> public int? GetPositionIndex(int row, int col) { return BaseDal.GetPositionIndex(row, col); Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -125,7 +125,7 @@ // ç®åå½ä»¤å¤çå¨ï¼å¤çç¶ææ´æ°çç®åå½ä»¤ var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor, socketGateway); // åç¼å½ä»¤å¤çå¨ï¼å¤ç pickfinishedãputfinished ç另忰çå½ä»¤ var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway); var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway, fakeBatteryPositionService); // åå§åæ¶æ¯è·¯ç±å¨ _messageRouter = new RobotMessageHandler(socketGateway, _stateManager, cache, simpleCommandHandler, prefixCommandHandler, logger); Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
@@ -181,5 +181,27 @@ /// 彿£å¸¸çµè¯ä»»å¡å®æå设为 trueï¼æºå¨äººä»åçµè¯ä½ç½®è¡¥å çµè¯è³48个ã /// </remarks> public bool IsInFakeBatteryMode { get; set; } /// <summary> /// å½åæ¹æ¬¡èµ·å§ç¼å·ï¼ç¨äºéå¢è®¡ç®åè´§/æ¾è´§ç¼å·ï¼ /// </summary> /// <remarks> /// 卿¹æ¬¡æ¨¡å¼ä¸ï¼æ¯æ¹åè´§/æ¾è´§çèµ·å§ç¼å·ä»1å¼å§éå¢ã /// ç¨äºè®¡ç® {start}-{end} æ ¼å¼ä¸ç start å¼ã /// </remarks> public int CurrentBatchIndex { get; set; } = 1; /// <summary> /// æ¢çä»»å¡å½åé¶æ®µ /// </summary> /// <remarks> /// é¶æ®µå®ä¹ï¼ /// 0: æªå¼å§ /// 1: 忣叏çµè¯ï¼æµåBï¼ / ååçµè¯ï¼æµåAï¼ /// 2: æ¾æ£å¸¸çµè¯ï¼æµåBï¼ / æ¾åçµè¯ï¼æµåAï¼ /// 3: ååçµè¯ï¼æµåB Phase2ï¼ /// 4: æ¾åçµè¯å°5å·ä½ï¼æµåB Phase2ï¼ /// </remarks> public int ChangePalletPhase { get; set; } } } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -268,6 +268,125 @@ } /// <summary> /// è®¡ç®æ¹æ¬¡ç¼å·èå´ /// </summary> /// <remarks> /// è¿åæ ¼å¼ï¼(start, end) /// - remaining >= 4: (currentIndex, currentIndex + 3) /// - remaining > 1: (currentIndex, currentIndex + remaining - 1) /// - remaining == 1: (currentIndex, 0) -- å个ç©åç¨ 0 表示 end /// </remarks> /// <param name="currentIndex">å½åæ¹æ¬¡èµ·å§ç¼å·</param> /// <param name="remaining">å©ä½æ°é</param> /// <returns>(start, end) å ç»</returns> public (int Start, int End) BuildBatchRange(int currentIndex, int remaining) { if (remaining >= 4) return (currentIndex, currentIndex + 3); else if (remaining > 1) return (currentIndex, currentIndex + remaining - 1); else // remaining == 1 return (currentIndex, 0); } /// <summary> /// ä¸ååè´§æä»¤ï¼å¸¦æ¹æ¬¡æ ¼å¼åæ»æ°ï¼ /// </summary> /// <remarks> /// åé顺åºï¼ /// 1. PickTotalNum,{N} -- çå®çµè¯æ»æ° /// 2. Pickbattery,{ä½ç½®},{start}-{end} -- æ¹æ¬¡åè´§æä»¤ /// /// ä¸åæååæ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ§è¡ä¸"ã /// </remarks> /// <param name="task">è¦ä¸åçä»»å¡å¯¹è±¡</param> /// <param name="state">æºå¨äººå½åç¶æ</param> /// <param name="position">åè´§ä½ç½®</param> /// <param name="batchStart">æ¹æ¬¡èµ·å§ç¼å·</param> /// <param name="batchEnd">æ¹æ¬¡ç»æç¼å·</param> public async Task SendPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd) { // å åéæ»æ°æä»¤ string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}"; await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd); // ååéæ¹æ¬¡åè´§æä»¤ string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}"; string taskString = $"Pickbattery,{position},{range}"; bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString); if (result) { _logger.LogInformation("ä¸åæ¹æ¬¡åè´§æä»¤æåï¼æä»¤: {TaskString}ï¼æ¹æ¬¡: {Range}ï¼è®¾å¤: {DeviceName}", taskString, range, state.RobotCrane?.DeviceName); QuartzLogger.Info($"ä¸åæ¹æ¬¡åè´§æä»¤æåï¼æä»¤: {taskString}ï¼æ¹æ¬¡: {range}", state.RobotCrane?.DeviceName); task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); state.CurrentTask = task; if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.UpdateRobotTaskAsync(task); } } else { _logger.LogError("ä¸åæ¹æ¬¡åè´§æä»¤å¤±è´¥ï¼æä»¤: {TaskString}ï¼è®¾å¤: {DeviceName}", taskString, state.RobotCrane?.DeviceName); QuartzLogger.Error($"ä¸åæ¹æ¬¡åè´§æä»¤å¤±è´¥ï¼æä»¤: {taskString}", state.RobotCrane?.DeviceName); } } /// <summary> /// ä¸åæ¾è´§æä»¤ï¼å¸¦æ¹æ¬¡æ ¼å¼åæ»æ°ï¼ /// </summary> /// <remarks> /// åé顺åºï¼ /// 1. PutTotalNum,{N} -- çå®çµè¯æ»æ° /// 2. Putbattery,{ä½ç½®},{start}-{end} -- æ¹æ¬¡æ¾è´§æä»¤ /// /// ä¸åæååæ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ§è¡ä¸"ã /// </remarks> /// <param name="task">è¦ä¸åçä»»å¡å¯¹è±¡</param> /// <param name="state">æºå¨äººå½åç¶æ</param> /// <param name="position">æ¾è´§ä½ç½®</param> /// <param name="batchStart">æ¹æ¬¡èµ·å§ç¼å·</param> /// <param name="batchEnd">æ¹æ¬¡ç»æç¼å·</param> public async Task SendPutWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd) { // å åéæ»æ°æä»¤ string totalNumCmd = $"PutTotalNum,{task.RobotTaskTotalNum}"; await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd); // ååéæ¹æ¬¡æ¾è´§æä»¤ string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}"; string taskString = $"Putbattery,{position},{range}"; bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString); if (result) { _logger.LogInformation("ä¸åæ¾è´§æä»¤æåï¼æä»¤: {TaskString}ï¼æ¹æ¬¡: {Range}ï¼è®¾å¤: {DeviceName}", taskString, range, state.RobotCrane?.DeviceName); QuartzLogger.Info($"ä¸åæ¾è´§æä»¤æåï¼æä»¤: {taskString}ï¼æ¹æ¬¡: {range}", state.RobotCrane?.DeviceName); task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); state.CurrentTask = task; if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.UpdateRobotTaskAsync(task); } } else { _logger.LogError("ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {TaskString}ï¼è®¾å¤: {DeviceName}", taskString, state.RobotCrane?.DeviceName); QuartzLogger.Error($"ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {taskString}", state.RobotCrane?.DeviceName); } } /// <summary> /// å¤çå ¥åºä»»å¡åä¼ ï¼æç/ç»ç/æ¢çåºæ¯ï¼ /// </summary> /// <remarks> Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
@@ -58,22 +58,33 @@ private readonly ISocketClientGateway _socketClientGateway; /// <summary> /// åçµè¯ä½ç½®æå¡ /// </summary> /// <remarks> /// ç¨äºéæ¾åçµè¯ç¹ä½ã /// </remarks> private readonly IFakeBatteryPositionService _fakeBatteryPositionService; /// <summary> /// æé 彿° /// </summary> /// <param name="robotTaskService">任塿å¡</param> /// <param name="taskProcessor">ä»»å¡å¤çå¨</param> /// <param name="stateManager">ç¶æç®¡çå¨</param> /// <param name="socketClientGateway">Socket ç½å ³</param> /// <param name="fakeBatteryPositionService">åçµè¯ä½ç½®æå¡</param> public RobotPrefixCommandHandler( IRobotTaskService robotTaskService, RobotTaskProcessor taskProcessor, RobotStateManager stateManager, ISocketClientGateway socketClientGateway) ISocketClientGateway socketClientGateway, IFakeBatteryPositionService fakeBatteryPositionService) { _robotTaskService = robotTaskService; _taskProcessor = taskProcessor; _stateManager = stateManager; _socketClientGateway = socketClientGateway; _fakeBatteryPositionService = fakeBatteryPositionService; } /// <summary> @@ -162,24 +173,31 @@ /// <remarks> /// å¤çé»è¾ï¼ /// 1. å¦ææ¯æçä»»å¡ï¼æå»ºåºå DTO å¹¶è°ç¨æç API /// 2. æ´æ°å½åå¨ä½ä¸º"åè´§å®æ" /// 3. è®°å½åè´§å®æçä½ç½® /// 4. æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººåè´§å®æ" /// 5. å®å ¨æ´æ°ç¶æå° Redis /// 2. æ¢çä»»å¡ Phase3 ååçµè¯æ¶ä¸è°ç¨æç API /// 3. æ´æ°å½åå¨ä½ä¸º"åè´§å®æ" /// 4. è®°å½åè´§å®æçä½ç½® /// 5. æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººåè´§å®æ" /// 6. å®å ¨æ´æ°ç¶æå° Redis /// </remarks> /// <param name="state">æºå¨äººå½åç¶æ</param> /// <param name="positions">åè´§å®æçä½ç½®ç¼å·æ°ç»</param> /// <param name="task">æºå¨äººä»»å¡è®°å½</param> private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task) { // å¦ææ¯æçä»»å¡ if (state.IsSplitPallet) // è®°å½åè´§å®æçä½ç½® state.LastPickPositions = positions; // æ¢çä»»å¡ Phase3 ååçµè¯ï¼ä¸è°ç¨æç API if (state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode() && state.ChangePalletPhase == 3) { state.CurrentAction = "PickFinished"; } // æçä»»å¡ else if (state.IsSplitPallet) { // æå»ºåºå DTOï¼å å«ä½ç½®ä¿¡æ¯åæçæ¡ç var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); // è®°å½åè´§å®æçä½ç½® state.LastPickPositions = positions; // è°ç¨æç API var result = _taskProcessor.PostSplitPalletAsync(stockDTO); @@ -187,7 +205,6 @@ // 妿 API è°ç¨æå if (result.Data.Status && result.IsSuccess) { // æ´æ°å½åå¨ä½ä¸º"åè´§å®æ" state.CurrentAction = "PickFinished"; } } @@ -196,9 +213,6 @@ // éæçä»»å¡ï¼ç´æ¥æ´æ°å¨ä½ state.CurrentAction = "PickFinished"; } // è®°å½åè´§å®æçä½ç½®ï¼æ 论æ¯å¦æçé½è®°å½ï¼ state.LastPickPositions = positions; // 妿任å¡åå¨ if (task != null) @@ -220,10 +234,11 @@ /// <remarks> /// å¤çé»è¾ï¼ /// 1. 妿æ¯ç»çä»»å¡ï¼æå»ºåºå DTO å¹¶è°ç¨ç»ç/æ¢ç API /// 2. 妿ç»çæåï¼å¢å ä»»å¡è®¡æ° /// 3. æ´æ°å½åå¨ä½ä¸º"æ¾è´§å®æ" /// 4. æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ¾è´§å®æ" /// 5. å®å ¨æ´æ°ç¶æå° Redis /// 2. æ¢ç任塿 ¹æ®é¶æ®µåºåå¤çï¼æµåA Phase2 ä¸è°ç¨ APIï¼æµåB Phase2 æ£å¸¸è°ç¨ï¼Phase4 è°ç¨ MarkAsAvailable /// 3. 妿ç»çæåï¼å¢å ä»»å¡è®¡æ° /// 4. æ´æ°å½åå¨ä½ä¸º"æ¾è´§å®æ" /// 5. æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ¾è´§å®æ" /// 6. å®å ¨æ´æ°ç¶æå° Redis /// </remarks> /// <param name="state">æºå¨äººå½åç¶æ</param> /// <param name="positions">æ¾è´§å®æçä½ç½®ç¼å·æ°ç»</param> @@ -239,20 +254,62 @@ // è®°å½æ¾è´§å®æçä½ç½® state.LastPutPositions = positions; // æå»ºåºå DTO var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); // 夿æ¯å¦ä¸ºæ¢çä»»å¡ var isChangePallet = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode(); var isFlowA = state.CurrentTask?.RobotSourceAddressLineCode is "11001" or "11010"; // æ ¹æ®ä»»å¡ç±»åå³å®è°ç¨åªä¸ª API // æ¢çä»»å¡è°ç¨ ChangePalletAsyncï¼ç»çä»»å¡è°ç¨ GroupPalletAsync var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode() ? nameof(ConfigKey.ChangePalletAsync) : nameof(ConfigKey.GroupPalletAsync); if (isChangePallet) { // æ¢çä»»å¡ï¼æ ¹æ®é¶æ®µåºåå¤ç if (state.ChangePalletPhase == 2) { if (isFlowA) { // æµåA Phase2ï¼æ¾åçµè¯å°ç®æ æçï¼ä¸è°ç¨ APIï¼ä¸éå¢è®¡æ° // ä» æ´æ°ç¶æ } else { // æµåB Phase2ï¼æ¾æ£å¸¸çµè¯ï¼éå¢è®¡æ° state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; // è°ç¨ç»ç/æ¢ç API var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO); // æå»ºåºå DTO å¹¶è°ç¨ ChangePalletAsync API var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.ChangePalletAsync), stockDTO); putSuccess = result.Data.Status && result.IsSuccess; } } else if (state.ChangePalletPhase == 4) { // æµåB Phase4ï¼æ¾åçµè¯å°5å·ä½ï¼ä¸è°ç¨ APIï¼ä¸éå¢è®¡æ°ï¼éæ¾ç¹ä½ _fakeBatteryPositionService.MarkAsAvailable(positions.ToList()); } else { // éæ¹æ¬¡æ¨¡å¼ï¼æ£å¸¸éå¢è®¡æ°å¹¶è°ç¨ API state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; // æ£æ¥ API è¿åç¶æ putSuccess = result.Data.Status && result.IsSuccess; var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.GroupPalletAsync), stockDTO); putSuccess = result.Data.Status && result.IsSuccess; } } else { // ç»çä»»å¡ï¼åæé»è¾ var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.GroupPalletAsync), stockDTO); putSuccess = result.Data.Status && result.IsSuccess; // å¢å ä»»å¡è®¡æ° state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; } } // 妿æ¾è´§æå @@ -261,13 +318,12 @@ // æ´æ°å½åå¨ä½ä¸º"æ¾è´§å®æ" state.CurrentAction = "PutFinished"; // å¢å ä»»å¡è®¡æ°ï¼ç´¯å æ¬æ¬¡å®æçæ°éï¼ state.RobotTaskTotalNum += positions.Length; // 妿任å¡åå¨ï¼åæ¥æ´æ°ä»»å¡çè®¡æ° if (task != null) // éç»ç任塿¶å¢å 计æ°ï¼ç»çä»»å¡å·²å¨ä¸é¢éå¢ï¼ if (!state.IsGroupPallet) { task.RobotTaskTotalNum -= positions.Length; state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; } } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -138,11 +138,33 @@ // 夿任å¡ç±»å var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType; // åªææçææ¢çä»»å¡éè¦å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.SplitPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet) // æ¢çä»»å¡ï¼ä» 彿æé¶æ®µå®ææ¶æå¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.ChangePallet) { // å¤çå ¥åºä»»å¡åä¼ // useSourceAddress: true è¡¨ç¤ºä½¿ç¨æºå°åï¼æç/æ¢çåºæ¯ï¼ if (state.ChangePalletPhase == 0) { // ææé¶æ®µå®æï¼å¤çå ¥åº if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) { // å ¥åºæåï¼å é¤ä»»å¡è®°å½ _taskProcessor.DeleteTask(currentTask.RobotTaskId); await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished"); QuartzLogger.Info($"åéæ¶æ¯ï¼ãSwap,diskFinishedã", state.RobotCrane.DeviceName); // éç½®æ¹æ¬¡ç¶æ state.ChangePalletPhase = 0; state.CurrentBatchIndex = 1; state.IsInFakeBatteryMode = false; return true; } } // ä¸é´é¶æ®µä¸å¤çï¼ä» æ´æ°ç¶æ return true; } // æçä»»å¡ï¼ç´æ¥å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.SplitPallet) { if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) { // å ¥åºæåï¼å é¤ä»»å¡è®°å½ @@ -171,11 +193,41 @@ // 夿任å¡ç±»å var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType; // åªæç»çææ¢çä»»å¡éè¦å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.GroupPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet) // æ¢çä»»å¡ï¼ä» 彿æé¶æ®µå®ææ¶æå¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.ChangePallet) { if (state.ChangePalletPhase == 0) { // ææé¶æ®µå®æï¼å¤çå ¥åº if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) { // å ¥åºæåï¼å é¤ä»»å¡è®°å½ _taskProcessor.DeleteTask(currentTask.RobotTaskId); // æ¸ çç¶æï¼ä¸ºä¸ä¸ä¸ªä»»å¡ååå¤ state.CurrentTask = null; // æ¸ é¤å½åä»»å¡ state.RobotTaskTotalNum = 0; // é置任å¡è®¡æ° state.CellBarcode = new List<string>(); // æ¸ ç©ºæ¡ç å表 await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished"); QuartzLogger.Info($"åéæ¶æ¯ï¼ãGroup,diskFinishedã", state.RobotCrane.DeviceName); // éç½®æ¹æ¬¡ç¶æ state.ChangePalletPhase = 0; state.CurrentBatchIndex = 1; state.IsInFakeBatteryMode = false; return true; } } // ä¸é´é¶æ®µä¸å¤çï¼ä» æ´æ°ç¶æ return true; } // ç»çä»»å¡ï¼ç´æ¥å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.GroupPallet) { // å¤çå ¥åºä»»å¡åä¼ // useSourceAddress: false 表示使ç¨ç®æ å°åï¼ç»ç/æ¢çåºæ¯ï¼ // useSourceAddress: false 表示使ç¨ç®æ å°åï¼ç»çåºæ¯ï¼ if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) { // å ¥åºæåï¼å é¤ä»»å¡è®°å½ Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -152,7 +152,7 @@ /// </summary> /// <remarks> /// å½åè´§å®æåï¼åæºå¨äººåéæ¾è´§æä»¤ï¼Putbatteryï¼ã /// æºå¨äººæ¶å°æä»¤åä¼å°è´§ç©æ¾ç½®å°ç®æ å°åã /// æ¢çä»»å¡ä½¿ç¨æ¹æ¬¡æ ¼å¼ SendPutWithBatchAsyncã /// /// æä»¤æ ¼å¼ï¼Putbattery,{ç®æ å°å} /// ä¾å¦ï¼Putbattery,B01 表示å°è´§ç©æ¾ç½®å° B01 ä½ç½® @@ -161,38 +161,99 @@ /// <param name="ipAddress">æºå¨äºº IP å°å</param> private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress) { // æå»ºæ¾è´§æä»¤ï¼æ ¼å¼ï¼Putbattery,{ç®æ å°å} string taskString = $"Putbattery,{task.RobotTargetAddress}"; string taskString; // éè¿å®¢æ·ç«¯ç®¡çå¨åéæä»¤å°æºå¨äºº // æ¢çä»»å¡ä½¿ç¨æ¹æ¬¡æ ¼å¼ if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) { int targetNormalCount = task.RobotTaskTotalNum; var state = _stateManager.GetState(ipAddress); int currentCompletedCount = state?.RobotTaskTotalNum ?? 0; bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010"; // æµåA Phase 2ï¼æ¾åçµè¯å°ç®æ æç if (isFlowA && state?.ChangePalletPhase == 2) { int remaining = 48 - currentCompletedCount; if (remaining <= 0) return; int batchStart = targetNormalCount + 1 + (state.CurrentBatchIndex - 1); int putCount = Math.Min(4, remaining); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end); return; } // æµåB Phase 4ï¼æ¾åçµè¯å°5å·ä½ if (!isFlowA && state?.ChangePalletPhase == 4) { int fakeCount = 48 - targetNormalCount; int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount); int remainingFake = fakeCount - completedFake; if (remainingFake <= 0) return; var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake)); if (positions.Count == 0) { _logger.LogError("HandlePickFinishedStateAsyncï¼æ å¯ç¨åçµè¯ç¹ä½ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int start = positions.Min(); int end = positions.Max(); await _taskProcessor.SendPutWithBatchAsync(task, state, "5", start, end); return; } // æµåB Phase 2ï¼æ¾æ£å¸¸çµè¯å°ç®æ æç if (!isFlowA && state?.ChangePalletPhase == 2) { int remainingNormal = targetNormalCount - currentCompletedCount; if (remainingNormal <= 0) return; int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1; int putCount = Math.Min(4, remainingNormal); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end); return; } // é»è®¤ï¼ä½¿ç¨åææ ¼å¼ taskString = $"Putbattery,{task.RobotTargetAddress}"; } else { // 鿢çä»»å¡ï¼ä½¿ç¨åææ ¼å¼ taskString = $"Putbattery,{task.RobotTargetAddress}"; } bool result = await _clientManager.SendToClientAsync(ipAddress, taskString); if (result) { // åéæåï¼è®°å½ Info æ¥å¿ _logger.LogInformation("HandlePickFinishedStateAsyncï¼ä¸åæ¾è´§æä»¤æåï¼æä»¤: {TaskString}ï¼ä»»å¡å·: {TaskNum}", taskString, task.RobotTaskNum); QuartzLogger.Info($"ä¸åæ¾è´§æä»¤æåï¼æä»¤: {taskString}", task.RobotRoadway); // æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ§è¡ä¸" task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); // è·åææ°ç¶æå¹¶æ´æ°ä»»å¡å ³è var stateToUpdate = _stateManager.GetState(ipAddress); if (stateToUpdate != null) { stateToUpdate.CurrentTask = task; // å®å ¨æ´æ°ç¶æå° Redis if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate)) { // ç¶ææ´æ°æååï¼æ´æ°ä»»å¡è®°å½ await _robotTaskService.UpdateRobotTaskAsync(task); } } } else { // åé失败ï¼è®°å½ Error æ¥å¿ _logger.LogError("HandlePickFinishedStateAsyncï¼ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {TaskString}ï¼ä»»å¡å·: {TaskNum}", taskString, task.RobotTaskNum); QuartzLogger.Error($"ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {taskString}", task.RobotRoadway); } @@ -284,61 +345,179 @@ } else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) { // æ¢çä»»å¡ // ç®æ ï¼æ£å¸¸çµè¯æå宿åï¼è¡¥å åçµè¯è³48个 const int targetTotal = 48; const int fakeBatteryPickPosition = 5; // åçµè¯æåä½ç½® const int pickCountPerExecution = 4; // æ¯æ¬¡æåæ°é int targetNormalCount = task.RobotTaskTotalNum; int currentCompletedCount = stateForUpdate.RobotTaskTotalNum; int targetNormalCount = task.RobotTaskTotalNum; // æ£å¸¸çµè¯ç®æ æ°é int currentCompletedCount = stateForUpdate.RobotTaskTotalNum; // 已宿æ°é // 夿æµåï¼null-safeï¼ bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010"; // å¦æç®æ æ°é为48ï¼ç´æ¥ä¸åæ£å¸¸ä»»å¡ // ç®æ æ°é为48ï¼ç´æ¥èµ°åæé»è¾ï¼ä¸è¿å ¥æ¹æ¬¡æ¨¡å¼ if (targetNormalCount == targetTotal) { await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); } // 妿已宿æ°éå°äºç®æ æ°éï¼ç»§ç»æåæ£å¸¸çµè¯ else if (currentCompletedCount < targetNormalCount) { await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); } // æ£å¸¸çµè¯å·²å®æï¼è¿å ¥åçµè¯è¡¥å æ¨¡å¼ else if (currentCompletedCount == targetNormalCount && !stateForUpdate.IsInFakeBatteryMode) { // 馿¬¡è¿å ¥åçµè¯æ¨¡å¼ï¼è®¾ç½®æ å¿ stateForUpdate.IsInFakeBatteryMode = true; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ£å¸¸çµè¯æå宿ï¼è¿å ¥åçµè¯è¡¥å 模å¼ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Info($"æ£å¸¸çµè¯æå宿ï¼è¿å ¥åçµè¯è¡¥å 模å¼", stateForUpdate.RobotCrane?.DeviceName); return; } // 妿å¤äºåçµè¯è¡¥å 模å¼ï¼è®¡ç®å¹¶ä¸åè¡¥æ°ä»»å¡ if (stateForUpdate.IsInFakeBatteryMode) // åå§åæ¹æ¬¡æ¨¡å¼ if (stateForUpdate.ChangePalletPhase == 0) { int remaining = targetTotal - currentCompletedCount; if (remaining > 0) stateForUpdate.ChangePalletPhase = 1; stateForUpdate.CurrentBatchIndex = 1; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ¢çä»»å¡è¿å ¥æ¹æ¬¡æ¨¡å¼ï¼ä»»å¡å·: {TaskNum}ï¼æµå: {Flow}", task.RobotTaskNum, isFlowA ? "A" : "B"); } // ==================== æµåAï¼è¡¥åçµè¯å°ç®æ æç ==================== if (isFlowA) { // Phase 1: ååçµè¯ï¼ä»5å·ä½ï¼ä½¿ç¨ PositionIndexï¼ if (stateForUpdate.ChangePalletPhase == 1) { // è®¡ç®æ¯æ¬¡æåçæ°éï¼æå¤4ä¸ªï¼ int pickCount = Math.Min(pickCountPerExecution, remaining); int remaining = targetTotal - currentCompletedCount; if (remaining <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåA宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } // è·åå¯ç¨çåçµè¯å¹³é¢ç¹ä½ int pickCount = Math.Min(4, remaining); var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(pickCount); if (positions.Count == 0) { _logger.LogError("HandlePutFinishedStateAsyncï¼æ å¯ç¨åçµè¯ç¹ä½ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Error($"æ å¯ç¨åçµè¯ç¹ä½", stateForUpdate.RobotCrane?.DeviceName); return; } // ä¸ååçµè¯åè´§æä»¤ await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions); stateForUpdate.ChangePalletPhase = 2; } else // Phase 2: æ¾åçµè¯å°ç®æ æçï¼ä» targetNormalCount+1 å¼å§éå¢ï¼ else if (stateForUpdate.ChangePalletPhase == 2) { // åçµè¯è¡¥å 宿ï¼éç½®æ å¿ stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ¢çä»»å¡å®æï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Info($"æ¢çä»»å¡å®æ", stateForUpdate.RobotCrane?.DeviceName); int remaining = targetTotal - currentCompletedCount; if (remaining <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåA宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } // è®¡ç®æ¾è´§æ¹æ¬¡ç¼å·ï¼ä» targetNormalCount + 1 å¼å§ int batchStart = targetNormalCount + 1 + (stateForUpdate.CurrentBatchIndex - 1); int putCount = Math.Min(4, remaining); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end); stateForUpdate.CurrentBatchIndex += putCount; stateForUpdate.ChangePalletPhase = 1; } } // ==================== æµåBï¼åæ£å¸¸çµè¯ + åæ¶åçµè¯ ==================== else { // Phase 1: 忣叏çµè¯ï¼ä»æºå°åï¼ä»1å¼å§éå¢ï¼ if (stateForUpdate.ChangePalletPhase == 1) { int remainingNormal = targetNormalCount - currentCompletedCount; if (remainingNormal <= 0) { // æ£å¸¸çµè¯åå®ï¼åæ¢å° Phase 3 stateForUpdate.ChangePalletPhase = 3; stateForUpdate.CurrentBatchIndex = targetNormalCount + 1; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ£å¸¸çµè¯å ¨é¨åå®ï¼è¿å ¥Phase 3åæ¶åçµè¯ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int pickCount = Math.Min(4, remainingNormal); var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount); await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end); stateForUpdate.CurrentBatchIndex += pickCount; stateForUpdate.ChangePalletPhase = 2; } // Phase 2: æ¾æ£å¸¸çµè¯å°ç®æ æçï¼æ¾è´§ç¼å·ä¸åè´§ç¼å·ä¸è´ï¼ else if (stateForUpdate.ChangePalletPhase == 2) { int remainingNormal = targetNormalCount - currentCompletedCount; if (remainingNormal <= 0) { // æ£å¸¸çµè¯æ¾å®ï¼åæ¢å° Phase 3 stateForUpdate.ChangePalletPhase = 3; stateForUpdate.CurrentBatchIndex = targetNormalCount + 1; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ£å¸¸çµè¯å ¨é¨æ¾å®ï¼è¿å ¥Phase 3åæ¶åçµè¯ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } // è®¡ç®æ¬æ¹æ¾è´§ç¼å·ï¼åºäº currentCompletedCount æ¨å¯¼æ¹æ¬¡èµ·å§ int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1; int putCount = Math.Min(4, remainingNormal); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end); stateForUpdate.ChangePalletPhase = 1; } // Phase 3: ååçµè¯ï¼ä»æºå°åï¼ä» targetNormalCount+1 å¼å§éå¢ï¼ else if (stateForUpdate.ChangePalletPhase == 3) { int fakeCount = targetTotal - targetNormalCount; int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount); int remainingFake = fakeCount - completedFake; if (remainingFake <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåB宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int pickCount = Math.Min(4, remainingFake); var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount); await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end); stateForUpdate.CurrentBatchIndex += pickCount; stateForUpdate.ChangePalletPhase = 4; } // Phase 4: æ¾åçµè¯å°5å·ä½ï¼ä½¿ç¨ PositionIndexï¼ else if (stateForUpdate.ChangePalletPhase == 4) { int fakeCount = targetTotal - targetNormalCount; int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount); int remainingFake = fakeCount - completedFake; if (remainingFake <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåB宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake)); if (positions.Count == 0) { _logger.LogError("HandlePutFinishedStateAsyncï¼æ å¯ç¨åçµè¯ç¹ä½ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int start = positions.Min(); int end = positions.Max(); await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, "5", start, end); stateForUpdate.ChangePalletPhase = 3; } } } Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -75,7 +75,11 @@ "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjMwMTcyNzM5Mzk5NzYxOTIwIiwibmFtZSI6IlBBQ0voo4XphY3lt6XkvY0wMSIsIkZhY3RvcnlJZCI6IjEyMzQ1NiIsIlNpdGVJZCI6IjEyMzQ1NiIsIkNvZGUiOiJYWExQQUNLMDRBRTAzMiIsIm5iZiI6MTcwNDE4NzY5MCwiZXhwIjoyMTQ1NjkxNjkwLCJpc3MiOiJodHRwczovL3d3dy5oeW1zb24uY29tIiwiYXVkIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSJ9.An1BE7UgfcSP--LtTOmmmWVE2RQFPDahLkDg1xy5KqY" }, "RobotTaskAddressRules": { "11068": [ "1", "2" ] "11068": [ "1", "2" ], "11001": [ "3", "1" ], "11010": [ "4", "2" ], "2101": [ "1", "3" ], "2103": [ "2", "4" ] }, Code/docs/superpowers/plans/2026-04-16-change-pallet-batch-command.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1054 @@ # æ¢ç任塿¹æ¬¡æä»¤ä¸åæµåå®ç°è®¡å > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** å级æ¢çä»»å¡åè´§/æ¾è´§æä»¤æ ¼å¼ä¸ºæ¹æ¬¡æ¨¡å¼ï¼æ¯æ PickTotalNum/PutTotalNumï¼å¹¶æ ¹æ® RobotSourceAddressLineCode åºåä¸¤ç§æµå **Architecture:** - RobotSocketState æ°å¢æ¹æ¬¡ç¶æå段ï¼CurrentBatchIndex, ChangePalletPhaseï¼ - RobotTaskProcessor æ°å¢æ¹æ¬¡æä»¤æå»ºæ¹æ³ï¼BuildBatchRange, SendPickWithBatchAsync, SendPutWithBatchAsyncï¼ - RobotWorkflowOrchestrator éå HandlePutFinishedStateAsync å HandlePickFinishedStateAsync å®ç°ä¸¤ç§æµåçç¶ææº - RobotPrefixCommandHandler å RobotSimpleCommandHandler æ ¹æ®é¶æ®µåºåå¤çé»è¾ - åçµè¯æå¡å±æ°å¢ MarkAsAvailable æ¹æ³ç¨äºéæ¾ç¹ä½ **Tech Stack:** C# / .NET 8, SqlSugar ORM, Redisç¼å --- ## File Structure ``` WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/ = RobotSocketState.cs # +CurrentBatchIndex, +ChangePalletPhase = RobotTaskProcessor.cs # +BuildBatchRange, +SendPickWithBatchAsync, +SendPutWithBatchAsync = Workflow/RobotWorkflowOrchestrator.cs # éå HandlePutFinishedStateAsync + HandlePickFinishedStateAsync = Workflow/RobotPrefixCommandHandler.cs # putfinished/pickfinished é¶æ®µå¤æ = Workflow/RobotSimpleCommandHandler.cs # allpickfinished/allputfinished é¶æ®µå®å« WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/ = IFakeBatteryPositionService.cs # +MarkAsAvailable WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/ = FakeBatteryPositionService.cs # +MarkAsAvailable WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/ = IFakeBatteryPositionRepository.cs # +MarkAsAvailable WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/ = FakeBatteryPositionRepository.cs # +MarkAsAvailable ``` --- ## Task 1: RobotSocketState æ°å¢æ¹æ¬¡ç¶æå段 **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs` - [ ] **Step 1: æ·»å CurrentBatchIndex å ChangePalletPhase 屿§** å¨ `IsInFakeBatteryMode` 屿§åæ·»å ï¼ ```csharp /// <summary> /// æ¯å¦å¤äºåçµè¯è¡¥å æ¨¡å¼ /// </summary> public bool IsInFakeBatteryMode { get; set; } /// <summary> /// å½åæ¹æ¬¡èµ·å§ç¼å·ï¼ç¨äºéå¢è®¡ç®åè´§/æ¾è´§ç¼å·ï¼ /// </summary> /// <remarks> /// 卿¹æ¬¡æ¨¡å¼ä¸ï¼æ¯æ¹åè´§/æ¾è´§çèµ·å§ç¼å·ä»1å¼å§éå¢ã /// ç¨äºè®¡ç® {start}-{end} æ ¼å¼ä¸ç start å¼ã /// </remarks> public int CurrentBatchIndex { get; set; } = 1; /// <summary> /// æ¢çä»»å¡å½åé¶æ®µ /// </summary> /// <remarks> /// é¶æ®µå®ä¹ï¼ /// 0: æªå¼å§ /// 1: 忣叏çµè¯ï¼æµåBï¼ / ååçµè¯ï¼æµåAï¼ /// 2: æ¾æ£å¸¸çµè¯ï¼æµåBï¼ / æ¾åçµè¯ï¼æµåAï¼ /// 3: ååçµè¯ï¼æµåB Phase2ï¼ /// 4: æ¾åçµè¯å°5å·ä½ï¼æµåB Phase2ï¼ /// </remarks> public int ChangePalletPhase { get; set; } ``` - [ ] **Step 2: éªè¯ç¼è¯** ```bash cd D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj ``` Expected: 0 errors - [ ] **Step 3: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs git commit -m "feat(Robot): RobotSocketState æ°å¢ CurrentBatchIndex å ChangePalletPhase" ``` --- ## Task 2: åçµè¯ä»å¨å±æ°å¢ MarkAsAvailable æ¹æ³ **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs` - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs` - [ ] **Step 1: 卿¥å£ä¸æ·»å MarkAsAvailable æ¹æ³å£°æï¼å¨ MarkAsUsed æ¹æ³åï¼** å¨ `IFakeBatteryPositionRepository.cs` ç `MarkAsUsed` æ¹æ³åæ·»å ï¼ ```csharp /// <summary> /// æ è®°æå®ç¹ä½ä¸ºå¯ç¨ï¼éæ¾ç¹ä½ï¼ /// </summary> /// <param name="positions">ç¹ä½ç´¢å¼å表</param> /// <returns>æ¯å¦æå</returns> bool MarkAsAvailable(List<int> positions); ``` - [ ] **Step 2: å¨ä»å¨å®ç°ä¸æ·»å MarkAsAvailable æ¹æ³ï¼å¨ MarkAsUsed æ¹æ³åï¼** å¨ `FakeBatteryPositionRepository.cs` ç `MarkAsUsed` æ¹æ³åæ·»å ï¼ ```csharp /// <inheritdoc/> public bool MarkAsAvailable(List<int> positions) { if (positions == null || positions.Count == 0) return true; return Db.Updateable<Dt_FakeBatteryPosition>() .SetColumns(x => x.IsUsed, false) .Where(x => positions.Contains(x.PositionIndex)) .ExecuteCommand() > 0; } ``` - [ ] **Step 3: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Server.sln ``` Expected: 0 errors - [ ] **Step 4: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs git commit -m "feat(Robot): åçµè¯ä»å¨å±æ°å¢ MarkAsAvailable æ¹æ³" ``` --- ## Task 3: åçµè¯æå¡å±æ°å¢ MarkAsAvailable æ¹æ³ **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs` - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs` - [ ] **Step 1: 卿塿¥å£ä¸æ·»å MarkAsAvailable æ¹æ³å£°æï¼å¨ MarkAsUsed åï¼** å¨ `IFakeBatteryPositionService.cs` ç `MarkAsUsed` æ¹æ³åæ·»å ï¼ ```csharp /// <summary> /// æ è®°æå®ç¹ä½ä¸ºå¯ç¨ï¼éæ¾ç¹ä½ï¼ /// </summary> /// <param name="positions">ç¹ä½ç´¢å¼å表</param> /// <returns>æ¯å¦æå</returns> bool MarkAsAvailable(List<int> positions); ``` - [ ] **Step 2: 卿å¡å®ç°ä¸æ·»å MarkAsAvailable æ¹æ³** ```csharp /// <inheritdoc/> public bool MarkAsAvailable(List<int> positions) { return BaseDal.MarkAsAvailable(positions); } ``` - [ ] **Step 3: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Server.sln ``` Expected: 0 errors - [ ] **Step 4: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs git commit -m "feat(Robot): åçµè¯æå¡å±æ°å¢ MarkAsAvailable æ¹æ³" ``` --- ## Task 4: RobotTaskProcessor æ°å¢æ¹æ¬¡æä»¤è¾ 婿¹æ³ **注æï¼** `_fakeBatteryPositionService` åæ®µå·²å¨ä¸ä¸è½®è¿ä»£ä¸æ·»å ï¼æ éé夿³¨å ¥ã **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs` - [ ] **Step 1: æ·»å BuildBatchRange è¾ å©æ¹æ³** å¨ `GetNextAvailableFakeBatteryPositions` æ¹æ³åæ·»å ï¼ ```csharp /// <summary> /// è®¡ç®æ¹æ¬¡ç¼å·èå´ /// </summary> /// <remarks> /// è¿åæ ¼å¼ï¼(start, end) /// - remaining >= 4: (currentIndex, currentIndex + 3) /// - remaining > 1: (currentIndex, currentIndex + remaining - 1) /// - remaining == 1: (currentIndex, 0) -- å个ç©åç¨ 0 表示 end /// </remarks> /// <param name="currentIndex">å½åæ¹æ¬¡èµ·å§ç¼å·</param> /// <param name="remaining">å©ä½æ°é</param> /// <returns>(start, end) å ç»</returns> public (int Start, int End) BuildBatchRange(int currentIndex, int remaining) { if (remaining >= 4) return (currentIndex, currentIndex + 3); else if (remaining > 1) return (currentIndex, currentIndex + remaining - 1); else // remaining == 1 return (currentIndex, 0); } ``` - [ ] **Step 2: æ·»å SendPickWithBatchAsync æ¹æ³** ```csharp /// <summary> /// ä¸ååè´§æä»¤ï¼å¸¦æ¹æ¬¡æ ¼å¼åæ»æ°ï¼ /// </summary> /// <remarks> /// åé顺åºï¼ /// 1. PickTotalNum,{N} -- çå®çµè¯æ»æ° /// 2. Pickbattery,{ä½ç½®},{start}-{end} -- æ¹æ¬¡åè´§æä»¤ /// /// ä¸åæååæ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ§è¡ä¸"ã /// </remarks> /// <param name="task">è¦ä¸åçä»»å¡å¯¹è±¡</param> /// <param name="state">æºå¨äººå½åç¶æ</param> /// <param name="position">åè´§ä½ç½®</param> /// <param name="batchStart">æ¹æ¬¡èµ·å§ç¼å·</param> /// <param name="batchEnd">æ¹æ¬¡ç»æç¼å·</param> public async Task SendPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd) { // å åéæ»æ°æä»¤ string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}"; await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd); // ååéæ¹æ¬¡åè´§æä»¤ string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}"; string taskString = $"Pickbattery,{position},{range}"; bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString); if (result) { _logger.LogInformation("ä¸åæ¹æ¬¡åè´§æä»¤æåï¼æä»¤: {TaskString}ï¼æ¹æ¬¡: {Range}ï¼è®¾å¤: {DeviceName}", taskString, range, state.RobotCrane?.DeviceName); QuartzLogger.Info($"ä¸åæ¹æ¬¡åè´§æä»¤æåï¼æä»¤: {taskString}ï¼æ¹æ¬¡: {range}", state.RobotCrane?.DeviceName); task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); state.CurrentTask = task; if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.UpdateRobotTaskAsync(task); } } else { _logger.LogError("ä¸åæ¹æ¬¡åè´§æä»¤å¤±è´¥ï¼æä»¤: {TaskString}ï¼è®¾å¤: {DeviceName}", taskString, state.RobotCrane?.DeviceName); QuartzLogger.Error($"ä¸åæ¹æ¬¡åè´§æä»¤å¤±è´¥ï¼æä»¤: {taskString}", state.RobotCrane?.DeviceName); } } ``` - [ ] **Step 3: æ·»å SendPutWithBatchAsync æ¹æ³** ```csharp /// <summary> /// ä¸åæ¾è´§æä»¤ï¼å¸¦æ¹æ¬¡æ ¼å¼åæ»æ°ï¼ /// </summary> /// <remarks> /// åé顺åºï¼ /// 1. PutTotalNum,{N} -- çå®çµè¯æ»æ° /// 2. Putbattery,{ä½ç½®},{start}-{end} -- æ¹æ¬¡æ¾è´§æä»¤ /// /// ä¸åæååæ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ§è¡ä¸"ã /// </remarks> /// <param name="task">è¦ä¸åçä»»å¡å¯¹è±¡</param> /// <param name="state">æºå¨äººå½åç¶æ</param> /// <param name="position">æ¾è´§ä½ç½®</param> /// <param name="batchStart">æ¹æ¬¡èµ·å§ç¼å·</param> /// <param name="batchEnd">æ¹æ¬¡ç»æç¼å·</param> public async Task SendPutWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd) { // å åéæ»æ°æä»¤ string totalNumCmd = $"PutTotalNum,{task.RobotTaskTotalNum}"; await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd); // ååéæ¹æ¬¡æ¾è´§æä»¤ string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}"; string taskString = $"Putbattery,{position},{range}"; bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString); if (result) { _logger.LogInformation("ä¸åæ¾è´§æä»¤æåï¼æä»¤: {TaskString}ï¼æ¹æ¬¡: {Range}ï¼è®¾å¤: {DeviceName}", taskString, range, state.RobotCrane?.DeviceName); QuartzLogger.Info($"ä¸åæ¾è´§æä»¤æåï¼æä»¤: {taskString}ï¼æ¹æ¬¡: {range}", state.RobotCrane?.DeviceName); task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); state.CurrentTask = task; if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.UpdateRobotTaskAsync(task); } } else { _logger.LogError("ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {TaskString}ï¼è®¾å¤: {DeviceName}", taskString, state.RobotCrane?.DeviceName); QuartzLogger.Error($"ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {taskString}", state.RobotCrane?.DeviceName); } } ``` - [ ] **Step 4: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj ``` Expected: 0 errors - [ ] **Step 5: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs git commit -m "feat(Robot): RobotTaskProcessor æ°å¢æ¹æ¬¡æä»¤è¾ 婿¹æ³" ``` --- ## Task 5: RobotSimpleCommandHandler æ·»å é¶æ®µå®å« **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs` - [ ] **Step 1: ä¿®æ¹ allpickfinished 忝** æ¾å° `case "allpickfinished":` åæ¯ï¼æ¿æ¢ä¸ºï¼ ```csharp // å ¨é¨åè´§å®æ case "allpickfinished": { state.CurrentAction = "AllPickFinished"; var currentTask = state.CurrentTask; if (currentTask == null) return false; var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType; // æ¢çä»»å¡ï¼ä» 彿æé¶æ®µå®ææ¶æå¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.ChangePallet) { if (state.ChangePalletPhase == 0) { // ææé¶æ®µå®æï¼å¤çå ¥åº if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) { _taskProcessor.DeleteTask(currentTask.RobotTaskId); await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished"); QuartzLogger.Info($"åéæ¶æ¯ï¼ãSwap,diskFinishedã", state.RobotCrane.DeviceName); // éç½®æ¹æ¬¡ç¶æ state.ChangePalletPhase = 0; state.CurrentBatchIndex = 1; state.IsInFakeBatteryMode = false; return true; } } // ä¸é´é¶æ®µä¸å¤çï¼ä» æ´æ°ç¶æ return true; } // æçä»»å¡ï¼ç´æ¥å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.SplitPallet) { if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) { _taskProcessor.DeleteTask(currentTask.RobotTaskId); await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished"); QuartzLogger.Info($"åéæ¶æ¯ï¼ãSwap,diskFinishedã", state.RobotCrane.DeviceName); return true; } } return false; } ``` - [ ] **Step 2: ä¿®æ¹ allputfinished 忝** æ¾å° `case "allputfinished":` åæ¯ï¼æ¿æ¢ä¸ºï¼ ```csharp // å ¨é¨æ¾è´§å®æ case "allputfinished": { state.CurrentAction = "AllPutFinished"; var currentTask = state.CurrentTask; if (currentTask == null) return false; var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType; // æ¢çä»»å¡ï¼ä» 彿æé¶æ®µå®ææ¶æå¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.ChangePallet) { if (state.ChangePalletPhase == 0) { // ææé¶æ®µå®æï¼å¤çå ¥åº if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) { _taskProcessor.DeleteTask(currentTask.RobotTaskId); state.CurrentTask = null; state.RobotTaskTotalNum = 0; state.CellBarcode = new List<string>(); await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished"); QuartzLogger.Info($"åéæ¶æ¯ï¼ãGroup,diskFinishedã", state.RobotCrane.DeviceName); // éç½®æ¹æ¬¡ç¶æ state.ChangePalletPhase = 0; state.CurrentBatchIndex = 1; state.IsInFakeBatteryMode = false; return true; } } // ä¸é´é¶æ®µä¸å¤çï¼ä» æ´æ°ç¶æ return true; } // ç»çä»»å¡ï¼ç´æ¥å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.GroupPallet) { if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) { _taskProcessor.DeleteTask(currentTask.RobotTaskId); state.CurrentTask = null; state.RobotTaskTotalNum = 0; state.CellBarcode = new List<string>(); await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished"); QuartzLogger.Info($"åéæ¶æ¯ï¼ãGroup,diskFinishedã", state.RobotCrane.DeviceName); return true; } } return false; } ``` - [ ] **Step 3: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj ``` Expected: 0 errors - [ ] **Step 4: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs git commit -m "feat(Robot): RobotSimpleCommandHandler æ¢ç任塿·»å ChangePalletPhase é¶æ®µå®å«" ``` --- ## Task 6: RobotPrefixCommandHandler ä¿®æ¹ putfinished/pickfinished å¤çé»è¾ **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs` - [ ] **Step 1: æ·»å IFakeBatteryPositionService ä¾èµ** å¨ç±»é¡¶é¨æ·»å åæ®µåæé 彿°åæ°ï¼ ```csharp /// <summary> /// åçµè¯ä½ç½®æå¡ /// </summary> private readonly IFakeBatteryPositionService _fakeBatteryPositionService; public RobotPrefixCommandHandler( IRobotTaskService robotTaskService, RobotTaskProcessor taskProcessor, RobotStateManager stateManager, ISocketClientGateway socketClientGateway, IFakeBatteryPositionService fakeBatteryPositionService) { _robotTaskService = robotTaskService; _taskProcessor = taskProcessor; _stateManager = stateManager; _socketClientGateway = socketClientGateway; _fakeBatteryPositionService = fakeBatteryPositionService; } ``` - [ ] **Step 2: ä¿®æ¹ HandlePutFinishedAsync æ¹æ³ä¸çæ¾è´§å¤çé»è¾** æ¾å° `if (putSuccess)` åï¼ç°æä»£ç 约å¨ç¬¬258è¡éè¿ï¼ï¼æ¿æ¢å ¶ä¸çæ¾è´§æåå¤çé»è¾ä¸ºï¼ ```csharp // 妿æ¾è´§æå if (putSuccess) { state.CurrentAction = "PutFinished"; // 夿æ¯å¦ä¸ºæ¢çä»»å¡ä¸å¤äºæ¹æ¬¡æ¨¡å¼ var isChangePallet = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode(); var isFlowA = state.CurrentTask?.RobotSourceAddressLineCode is "11001" or "11010"; if (isChangePallet) { if (state.ChangePalletPhase == 2) { if (isFlowA) { // æµåA Phase 2ï¼æ¾åçµè¯å°ç®æ æçï¼ä¸è°ç¨ APIï¼ä¸éå¢è®¡æ° // ä¸åä»»ä½é¢å¤å¤çï¼ä» æ´æ°ç¶æ } else { // æµåB Phase 2ï¼æ¾æ£å¸¸çµè¯ï¼éå¢è®¡æ° state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; } } else if (state.ChangePalletPhase == 4) { // æµåB Phase 4ï¼æ¾åçµè¯å°5å·ä½ï¼ä¸è°ç¨ APIï¼ä¸éå¢è®¡æ°ï¼éæ¾ç¹ä½ _fakeBatteryPositionService.MarkAsAvailable(positions.ToList()); } else { // éæ¹æ¬¡æ¨¡å¼æéæ¢çä»»å¡ï¼éå¢è®¡æ° state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; } } else { // 鿢çä»»å¡ï¼åæé»è¾ state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; } } ``` - [ ] **Step 3: ä¿®æ¹ HandlePickFinishedAsync æ¹æ³** æ¾å° `if (state.IsSplitPallet)` åï¼ç°æä»£ç 约å¨ç¬¬173-198è¡ï¼ï¼æ¿æ¢ä¸ºï¼ ```csharp // å¦ææ¯æçä»»å¡ if (state.IsSplitPallet) { var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); state.LastPickPositions = positions; var result = _taskProcessor.PostSplitPalletAsync(stockDTO); if (result.Data.Status && result.IsSuccess) { state.CurrentAction = "PickFinished"; } } // æ¢çä»»å¡ååçµè¯æ¶ï¼Phase 3ï¼ä¸è°ç¨æç API else if (state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode() && state.ChangePalletPhase == 3) { state.CurrentAction = "PickFinished"; state.LastPickPositions = positions; } else { state.CurrentAction = "PickFinished"; state.LastPickPositions = positions; } ``` - [ ] **Step 4: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj ``` Expected: 0 errors - [ ] **Step 5: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs git commit -m "feat(Robot): RobotPrefixCommandHandler æ¢ç任塿 ¹æ®é¶æ®µåºåå¤çé»è¾" ``` --- ## Task 7: RobotWorkflowOrchestrator éå HandlePutFinishedStateAsync ç ChangePallet 忝 **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs` - [ ] **Step 1: æ¿æ¢ ChangePallet 忝é»è¾** æ¾å° `else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())` 忝ï¼å°å ¶å®æ´æ¿æ¢ä¸ºï¼ ```csharp else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) { const int targetTotal = 48; int targetNormalCount = task.RobotTaskTotalNum; int currentCompletedCount = stateForUpdate.RobotTaskTotalNum; // 夿æµåï¼null-safeï¼ bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010"; // ç®æ æ°é为48ï¼ç´æ¥èµ°åæé»è¾ï¼ä¸è¿å ¥æ¹æ¬¡æ¨¡å¼ if (targetNormalCount == targetTotal) { await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); return; } // åå§åæ¹æ¬¡æ¨¡å¼ if (stateForUpdate.ChangePalletPhase == 0) { stateForUpdate.ChangePalletPhase = 1; stateForUpdate.CurrentBatchIndex = 1; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ¢çä»»å¡è¿å ¥æ¹æ¬¡æ¨¡å¼ï¼ä»»å¡å·: {TaskNum}ï¼æµå: {Flow}", task.RobotTaskNum, isFlowA ? "A" : "B"); } // ==================== æµåAï¼è¡¥åçµè¯å°ç®æ æç ==================== if (isFlowA) { // Phase 1: ååçµè¯ï¼ä»5å·ä½ï¼ä½¿ç¨ PositionIndexï¼ if (stateForUpdate.ChangePalletPhase == 1) { int remaining = targetTotal - currentCompletedCount; if (remaining <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåA宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int pickCount = Math.Min(4, remaining); var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(pickCount); if (positions.Count == 0) { _logger.LogError("HandlePutFinishedStateAsyncï¼æ å¯ç¨åçµè¯ç¹ä½ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions); stateForUpdate.ChangePalletPhase = 2; } // Phase 2: æ¾åçµè¯å°ç®æ æçï¼ä» targetNormalCount+1 å¼å§éå¢ï¼ else if (stateForUpdate.ChangePalletPhase == 2) { int remaining = targetTotal - currentCompletedCount; if (remaining <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåA宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } // è®¡ç®æ¾è´§æ¹æ¬¡ç¼å·ï¼ä» targetNormalCount + 1 å¼å§ int batchStart = targetNormalCount + 1 + (stateForUpdate.CurrentBatchIndex - 1); int putCount = Math.Min(4, remaining); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end); stateForUpdate.CurrentBatchIndex += putCount; stateForUpdate.ChangePalletPhase = 1; } } // ==================== æµåBï¼åæ£å¸¸çµè¯ + åæ¶åçµè¯ ==================== else { // Phase 1: 忣叏çµè¯ï¼ä»æºå°åï¼ä»1å¼å§éå¢ï¼ if (stateForUpdate.ChangePalletPhase == 1) { int remainingNormal = targetNormalCount - currentCompletedCount; int pickCount = Math.Min(4, remainingNormal); var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount); await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end); // é墿¹æ¬¡ç´¢å¼ï¼å¦æå好å宿åä¸ä¸ªï¼ç´¢å¼ä¼è¶ è¿ç®æ ï¼ä½putfinished忥æ¶ä¼åæ¢é¶æ®µï¼ stateForUpdate.CurrentBatchIndex += pickCount; // åæ¢å° Phase 2 stateForUpdate.ChangePalletPhase = 2; } // Phase 2: æ¾æ£å¸¸çµè¯å°ç®æ æçï¼æ¾è´§ç¼å·ä¸åè´§ç¼å·ä¸è´ï¼ else if (stateForUpdate.ChangePalletPhase == 2) { int remainingNormal = targetNormalCount - currentCompletedCount; if (remainingNormal <= 0) { // æ£å¸¸çµè¯æ¾å®ï¼åæ¢å° Phase 3 stateForUpdate.ChangePalletPhase = 3; stateForUpdate.CurrentBatchIndex = targetNormalCount + 1; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æ£å¸¸çµè¯å ¨é¨æ¾å®ï¼è¿å ¥Phase 3åæ¶åçµè¯ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } // è®¡ç®æ¬æ¹æ¾è´§ç¼å·ï¼åºäº currentCompletedCount æ¨å¯¼æ¹æ¬¡èµ·å§ï¼ä¸åè´§ç¼å·ä¸è´ï¼ // batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1 int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1; int putCount = Math.Min(4, remainingNormal); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end); stateForUpdate.ChangePalletPhase = 1; } // Phase 3: ååçµè¯ï¼ä»æºå°åï¼ä» targetNormalCount+1 å¼å§éå¢ï¼ else if (stateForUpdate.ChangePalletPhase == 3) { int fakeCount = targetTotal - targetNormalCount; int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount); int remainingFake = fakeCount - completedFake; if (remainingFake <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåB宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int pickCount = Math.Min(4, remainingFake); var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount); await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end); stateForUpdate.CurrentBatchIndex += pickCount; stateForUpdate.ChangePalletPhase = 4; } // Phase 4: æ¾åçµè¯å°5å·ä½ï¼ä½¿ç¨ PositionIndexï¼ else if (stateForUpdate.ChangePalletPhase == 4) { int fakeCount = targetTotal - targetNormalCount; int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount); int remainingFake = fakeCount - completedFake; if (remainingFake <= 0) { stateForUpdate.ChangePalletPhase = 0; stateForUpdate.CurrentBatchIndex = 1; stateForUpdate.IsInFakeBatteryMode = false; _logger.LogInformation("HandlePutFinishedStateAsyncï¼æµåB宿ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake)); if (positions.Count == 0) { _logger.LogError("HandlePutFinishedStateAsyncï¼æ å¯ç¨åçµè¯ç¹ä½ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int start = positions.Min(); int end = positions.Max(); await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, "5", start, end); stateForUpdate.ChangePalletPhase = 3; } } } ``` - [ ] **Step 2: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj ``` Expected: 0 errors - [ ] **Step 3: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs git commit -m "feat(Robot): RobotWorkflowOrchestrator éå HandlePutFinishedStateAsync å®ç°æ¹æ¬¡æä»¤ä¸åæµå" ``` --- ## Task 8: RobotWorkflowOrchestrator éå HandlePickFinishedStateAsync ç ChangePallet 忝 **Files:** - Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs` - [ ] **Step 1: ä¿®æ¹ HandlePickFinishedStateAsync æ¹æ³** æ¾å° `HandlePickFinishedStateAsync` æ¹æ³ï¼ç°æä»£ç 约第162-199è¡ï¼ï¼å°æ´ä¸ªæ¹æ³æ¿æ¢ä¸ºï¼ ```csharp /// <summary> /// å¤çåè´§å®æåçæ¾è´§æä»¤ /// </summary> /// <remarks> /// æ ¹æ®ä»»å¡ç±»åå³å®æ¾è´§æä»¤æ ¼å¼ï¼ /// - æ¢çä»»å¡ï¼ChangePalletï¼ï¼ä½¿ç¨æ¹æ¬¡æ ¼å¼ SendPutWithBatchAsync /// - ç»çä»»å¡ï¼GroupPalletï¼ï¼ä½¿ç¨åææ ¼å¼ Putbattery,{ç®æ å°å} /// - å ¶ä»ä»»å¡ï¼ä½¿ç¨åææ ¼å¼ /// </remarks> /// <param name="task">å½åä»»å¡</param> /// <param name="ipAddress">æºå¨äºº IP å°å</param> private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress) { string taskString; // æ¢çä»»å¡ä½¿ç¨æ¹æ¬¡æ ¼å¼ if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) { int targetNormalCount = task.RobotTaskTotalNum; var state = _stateManager.GetState(ipAddress); int currentCompletedCount = state?.RobotTaskTotalNum ?? 0; bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010"; // æµåAï¼æ¾åçµè¯å°ç®æ æçï¼Phase 2ï¼ if (isFlowA && state?.ChangePalletPhase == 2) { int remaining = 48 - currentCompletedCount; if (remaining <= 0) return; // è®¡ç®æ¹æ¬¡ç¼å·ï¼ä» targetNormalCount + 1 å¼å§ int batchStart = targetNormalCount + 1; int putCount = Math.Min(4, remaining); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end); return; } // æµåB Phase 4ï¼æ¾åçµè¯å°5å·ä½ if (!isFlowA && state?.ChangePalletPhase == 4) { int fakeCount = 48 - targetNormalCount; int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount); int remainingFake = fakeCount - completedFake; if (remainingFake <= 0) return; var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake)); if (positions.Count == 0) { _logger.LogError("HandlePickFinishedStateAsyncï¼æ å¯ç¨åçµè¯ç¹ä½ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); return; } int start = positions.Min(); int end = positions.Max(); await _taskProcessor.SendPutWithBatchAsync(task, state, "5", start, end); return; } // æµåB Phase 2ï¼æ¾æ£å¸¸çµè¯å°ç®æ æç if (!isFlowA && state?.ChangePalletPhase == 2) { int remainingNormal = targetNormalCount - currentCompletedCount; if (remainingNormal <= 0) return; int batchStart = state.CurrentBatchIndex - (state.CurrentBatchIndex - 1) / 4 * 4; int putCount = Math.Min(4, remainingNormal); var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount); await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end); return; } // é»è®¤ï¼ä½¿ç¨åææ ¼å¼ taskString = $"Putbattery,{task.RobotTargetAddress}"; } else { // 鿢çä»»å¡ï¼ä½¿ç¨åææ ¼å¼ taskString = $"Putbattery,{task.RobotTargetAddress}"; } bool result = await _clientManager.SendToClientAsync(ipAddress, taskString); if (result) { _logger.LogInformation("HandlePickFinishedStateAsyncï¼ä¸åæ¾è´§æä»¤æåï¼æä»¤: {TaskString}ï¼ä»»å¡å·: {TaskNum}", taskString, task.RobotTaskNum); QuartzLogger.Info($"ä¸åæ¾è´§æä»¤æåï¼æä»¤: {taskString}", task.RobotRoadway); task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode(); var stateToUpdate = _stateManager.GetState(ipAddress); if (stateToUpdate != null) { stateToUpdate.CurrentTask = task; if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate)) { await _robotTaskService.UpdateRobotTaskAsync(task); } } } else { _logger.LogError("HandlePickFinishedStateAsyncï¼ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {TaskString}ï¼ä»»å¡å·: {TaskNum}", taskString, task.RobotTaskNum); QuartzLogger.Error($"ä¸åæ¾è´§æä»¤å¤±è´¥ï¼æä»¤: {taskString}", task.RobotRoadway); } } ``` - [ ] **Step 2: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj ``` Expected: 0 errors - [ ] **Step 3: Commit** ```bash git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs git commit -m "feat(Robot): RobotWorkflowOrchestrator éå HandlePickFinishedStateAsync æ¯ææ¹æ¬¡æä»¤" ``` --- ## Task 9: 注åä¾èµæ³¨å ¥ **Files:** - Modify: AutofacModuleRegister.cs ææ£æ¥ç°ææ³¨å - [ ] **Step 1: æ£æ¥ FakeBatteryPositionService æ¯å¦å·²èªå¨æ³¨å** ```bash grep -r "FakeBatteryPositionService" D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server\WIDESEAWCS_Core --include="*.cs" ``` Expected: 妿项ç®ä½¿ç¨ `AsImplementedInterfaces()` èªå¨æ«æï¼è¯¥æå¡å·²èªå¨æ³¨åã - [ ] **Step 2: æ£æ¥ RobotPrefixCommandHandler æé 彿°åæ´åæ¯å¦éè¦é¢å¤æ³¨å** ç±äºä» æ°å¢äºä¾èµåæ°ï¼å¦æé¡¹ç®ä½¿ç¨ Autofac èªå¨è£ é ï¼åæ°èªå¨æ³¨å ¥ï¼ï¼åæ éä¿®æ¹ã妿éè¦æå¨æ³¨åï¼æ·»å ï¼ ```csharp // ç¡®ä¿ IFakeBatteryPositionService 已被注åï¼é常éè¿ AsImplementedInterfaces èªå¨å®æï¼ // RobotPrefixCommandHandler çæ°æé 彿°åæ°ä¼èªå¨è£ é ``` - [ ] **Step 3: éªè¯ç¼è¯** ```bash dotnet build WIDESEAWCS_Server.sln ``` Expected: 0 errors - [ ] **Step 4: Commit**ï¼ä» 彿å®é æ¹å¨æ¶ææäº¤ï¼ ```bash git add <ä¿®æ¹çæä»¶> git commit -m "feat(Robot): 注åä¾èµæ³¨å ¥ï¼å¦ææ¹å¨ï¼" ``` --- ## Task 10: éªè¯æ´ä½æå»º - [ ] **Step 1: 宿´æå»º** ```bash cd D:\Git\ShanMeiXinNengYuan\Code dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln ``` Expected: 0 errors, 0 warnings - [ ] **Step 2: æ£æ¥æµè¯** ```bash dotnet test WCS/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj ``` Expected: ç°ææµè¯éè¿ - [ ] **Step 3: æäº¤å®ææ è®°** ```bash git commit --allow-empty -m "feat(Robot): æ¢ç任塿¹æ¬¡æä»¤ä¸åæµåå®ç°å®æ" ``` --- ## éå½ï¼æµè¯éªè¯æ¥éª¤ ### æå¨éªè¯æµç¨ 1. **æµåAæµè¯ï¼RobotSourceAddressLineCode = 11001ï¼** - targetNormalCount = 11 - 颿ï¼Phase1 ååçµè¯ï¼Pickbattery,5,{pos}ï¼â Phase2 æ¾åçµè¯ï¼Putbattery,{ç®æ },12-15ï¼â 循ç¯ç´å°48 2. **æµåBæµè¯ï¼RobotSourceAddressLineCode != 11001/11010ï¼** - targetNormalCount = 11 - 颿ï¼Phase1 忣叏ï¼Pickbattery,{æº},1-4ï¼â Phase2 æ¾æ£å¸¸ï¼Putbattery,{ç®æ },1-4ï¼â å¾ªç¯ â Phase3 ååï¼Pickbattery,{æº},12-15ï¼â Phase4 æ¾åå°5å·ä½ï¼Putbattery,5,{pos}ï¼â 循ç¯ç´å°åæ¶å®37个åçµè¯ 3. **è¾¹çæ¡ä»¶** - targetNormalCount = 48ï¼èµ°åæé»è¾ - targetNormalCount = 1ï¼æ¹æ¬¡ 1-0 æ ¼å¼ --- **Plan complete.** Code/docs/superpowers/specs/2026-04-16-change-pallet-batch-command-design.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,180 @@ # æ¢ç任塿¹æ¬¡æä»¤ä¸åæµå设计 ## æ¦è¿° 对æ¢çä»»å¡ï¼ChangePalletï¼çåè´§/æ¾è´§æä»¤æ ¼å¼è¿è¡åçº§ï¼æ¯ææ¹æ¬¡ç¼å·åæ»æ°æä»¤ï¼å¹¶æ ¹æ® `RobotSourceAddressLineCode` åºåä¸¤ç§æµåã ## èæ¯ å½åæ¢çä»»å¡å®ç°ï¼ä¸ä¸è½®è¿ä»£ï¼ä» å¤çäº `HandlePutFinishedStateAsync` ä¸çåçµè¯è¡¥å é»è¾ï¼æä»¤æ ¼å¼ä¸ºç®åç `Pickbattery,{å°å}` å `Pickbattery,5,{start}-{end}`ãç°éè¦ï¼ 1. æææ¢çåè´§/æ¾è´§æä»¤ç»ä¸ä¸ºæ¹æ¬¡æ ¼å¼ `{Command},{ä½ç½®},{start}-{end}` 2. æ¯æ¹ååéæ»æ°æä»¤ `PickTotalNum,{N}` / `PutTotalNum,{N}` 3. æ ¹æ®æºå°å线ä½ç¼ç åºå两ç§å®å ¨ä¸åçæä½æµå ## æä»¤æ ¼å¼ ### æ¹æ¬¡æä»¤ | æä»¤ç±»å | æ ¼å¼ | ç¤ºä¾ | |----------|------|------| | åè´§æ»æ° | `PickTotalNum,{N}` | `PickTotalNum,11` | | æ¾è´§æ»æ° | `PutTotalNum,{N}` | `PutTotalNum,11` | | åè´§æ¹æ¬¡ | `Pickbattery,{ä½ç½®},{start}-{end}` | `Pickbattery,3,1-4` | | æ¾è´§æ¹æ¬¡ | `Putbattery,{ä½ç½®},{start}-{end}` | `Putbattery,6,5-8` | | åè´§å个 | `Pickbattery,{ä½ç½®},{n}-0` | `Pickbattery,3,11-0` | | æ¾è´§å个 | `Putbattery,{ä½ç½®},{n}-0` | `Putbattery,6,11-0` | - `PickTotalNum/PutTotalNum` ä» æ¢çä»»å¡åéï¼æ¯æ¹å/æ¾ä¹åé½å - `N` = çå®çµè¯æ°éï¼å³ `task.RobotTaskTotalNum`ï¼ï¼æºå¨äººåºä»¶ç¨æ¤å¼å¤ææç䏿£å¸¸çµè¯æ»æ°ï¼ä¸¤ç§æµåååéç¸åç N å¼ - æ¯æ¹æå¤4个ï¼ä¸æ»¡4个æå®é æ°åï¼å©1ä¸ªæ¶ end=0 ### æ¹æ¬¡ç¼å·è®¡ç® `BuildBatchRange(currentIndex, remaining)` â `(start, end)`: - `remaining >= 4` â `(currentIndex, currentIndex + 3)` - `remaining > 1` â `(currentIndex, currentIndex + remaining - 1)` - `remaining == 1` â `(currentIndex, 0)` 示ä¾ï¼targetNormalCount=11ï¼ï¼ç¬¬1æ¹ 1-4ï¼ç¬¬2æ¹ 5-8ï¼ç¬¬3æ¹ 9-11 示ä¾ï¼targetNormalCount=9ï¼ï¼ç¬¬1æ¹ 1-4ï¼ç¬¬2æ¹ 5-8ï¼ç¬¬3æ¹ 9-0ï¼å个ï¼end=0ï¼ ## ä¸¤ç§æµå ### æµåAï¼è¡¥åçµè¯å°ç®æ æç **æ¡ä»¶ï¼** `RobotSourceAddressLineCode == "11001" || "11010"` **åºæ¯ï¼** ç®æ æçæ N 个æ£å¸¸çµè¯ï¼N < 48ï¼ï¼éä»5å·ä½åçµè¯æçååçµè¯è¡¥æ»¡48个ã **é¶æ®µæµè½¬ï¼** ``` [ååçµè¯] Pickbattery,5,{positionIndex} â ä»5å·ä½åï¼ç¼å·ç¨å¹³é¢ç¹ä½è¡¨PositionIndex â [æ¾åçµè¯] Putbattery,{ç®æ å°å},{N+1}-{N+4} â æ¾å°ç®æ æçï¼ç¼å·ä»æ£å¸¸æ°+1éå¢ â éå¤ç´å°è¡¥æ»¡48个 ``` åçµè¯æ°é = `48 - task.RobotTaskTotalNum` ### æµåBï¼åæ£å¸¸çµè¯ + åæ¶åçµè¯ **æ¡ä»¶ï¼** `RobotSourceAddressLineCode != "11001" && != "11010"` **åºæ¯ï¼** æºæçåæ¬æ»¡48ä¸ªï¼æ£å¸¸çµè¯ + åçµè¯æ··åï¼ï¼å åèµ°æ£å¸¸çµè¯æ¾å°ç®æ æçï¼åææºæçä¸å©ä½çåçµè¯ååºæ¾å5å·ä½ã **é¶æ®µæµè½¬ï¼** ``` Phase 1 - 忣叏çµè¯: [忣叏] Pickbattery,{æºå°å},{1}-{4} â ç¼å·ä»1éå¢ â [æ¾æ£å¸¸] Putbattery,{ç®æ å°å},{1}-{4} â ç¼å·ä»1éå¢ â éå¤ç´å° N 个æ£å¸¸çµè¯å ¨é¨åå® Phase 2 - åæ¶åçµè¯: [ååçµè¯] Pickbattery,{æºå°å},{N+1}-{N+4} â ç¼å·ä»æ£å¸¸æ°+1ç»§ç»éå¢ â [æ¾åçµè¯] Putbattery,5,{positionIndex} â æ¾å5å·ä½ï¼ç¼å·ç¨å¹³é¢ç¹ä½è¡¨PositionIndex â éå¤ç´å°åçµè¯å ¨é¨åæ¶ï¼48-N ä¸ªï¼ ``` ## ç¶æç®¡ç ### RobotSocketState æ°å¢å段 ```csharp /// <summary> /// å½åæ¹æ¬¡èµ·å§ç¼å·ï¼ç¨äºéå¢è®¡ç®åè´§/æ¾è´§ç¼å·ï¼ /// </summary> public int CurrentBatchIndex { get; set; } = 1; /// <summary> /// æ¢çä»»å¡å½åé¶æ®µ /// </summary> /// <remarks> /// 0: æªå¼å§ /// 1: 忣叏çµè¯ / ååçµè¯ï¼æµåAï¼ /// 2: æ¾æ£å¸¸çµè¯ / æ¾åçµè¯ï¼æµåAï¼ /// 3: ååçµè¯ï¼æµåB Phase2ï¼ /// 4: æ¾åçµè¯å°5å·ä½ï¼æµåB Phase2ï¼ /// </remarks> public int ChangePalletPhase { get; set; } ``` ### CurrentBatchIndex çå½å¨æ **æµåAï¼** - Phase 0â1ï¼`CurrentBatchIndex = 1`ï¼åå§åï¼ç¨äº PositionIndex æ¥è¯¢èµ·ç¹ï¼ - Phase 1â2ï¼ä¸éç½®ï¼æ¾è´§ç¼å·ä» `targetNormalCount + 1` å¼å§ï¼ç± Orchestrator 计ç®ï¼ - Phase 2â1ï¼`CurrentBatchIndex += æ¬æ¹æ°é`ï¼éå¢ï¼ä¸ä¸æ¹ç»§ç»ï¼ - 宿â0ï¼`CurrentBatchIndex = 1`ï¼éç½®ï¼ **æµåBï¼** - Phase 0â1ï¼`CurrentBatchIndex = 1`ï¼åæ£å¸¸çµè¯ä»1å¼å§ï¼ - Phase 1â2ï¼ä¸éç½®ï¼æ¾è´§ç¼å·ä¸åè´§ç¼å·ä¸è´ï¼ - Phase 2â1ï¼`CurrentBatchIndex += æ¬æ¹æ°é`ï¼éå¢ï¼ - Phase 2â3ï¼æ£å¸¸çµè¯åå®ï¼å³ `state.RobotTaskTotalNum >= targetNormalCount` æ¶ putfinished 宿å触åï¼ï¼`CurrentBatchIndex = targetNormalCount + 1`ï¼åçµè¯ä»æ£å¸¸æ°+1å¼å§ï¼ - Phase 3â4ï¼ä¸éç½®ï¼æ¾åçµè¯ç¨ PositionIndexï¼ç± Orchestrator 计ç®ï¼ - Phase 4â3ï¼`CurrentBatchIndex += æ¬æ¹æ°é`ï¼éå¢ï¼ - 宿â0ï¼`CurrentBatchIndex = 1`ï¼éç½®ï¼ ### é¶æ®µæµè½¬ç¶ææº **æµåAï¼** ``` Phase 0 â Phase 1ï¼ååçµè¯from 5å·ä½ï¼â Phase 2ï¼æ¾åçµè¯to ç®æ ï¼â Phase 1 â ... â Phase 0ï¼å®æï¼ ``` **æµåBï¼** ``` Phase 0 â Phase 1ï¼åæ£å¸¸from æºï¼â Phase 2ï¼æ¾æ£å¸¸to ç®æ ï¼â Phase 1 â ... â Phase 3ï¼ååçµè¯from æºï¼â Phase 4ï¼æ¾åçµè¯to 5å·ä½ï¼â Phase 3 â ... â Phase 0ï¼å®æï¼ ``` ## ä»£ç æ¹å¨èå´ | æä»¶ | æ¹å¨ | |------|------| | `RobotSocketState.cs` | +`CurrentBatchIndex`, +`ChangePalletPhase` | | `RobotTaskProcessor.cs` | +`BuildBatchRange()`, +`SendPickWithBatchAsync()`, +`SendPutWithBatchAsync()`, ä¿®æ¹ç°æåçµè¯æ¹æ³ | | `RobotWorkflowOrchestrator.cs` | éå ChangePallet 忝ï¼HandlePutFinishedStateAsync + HandlePickFinishedStateAsyncï¼ | | `RobotPrefixCommandHandler.cs` | HandlePutFinishedAsync ä¸åºåæ¢çé¶æ®µï¼åçµè¯æ¾è´§ä¸è°ç¨ ChangePalletAsync APIï¼ä¸éå¢ `RobotTaskTotalNum`ï¼HandlePickFinishedAsync ä¸åçµè¯åè´§ä¸è°ç¨æç API | | `RobotSimpleCommandHandler.cs` | allpickfinished/allputfinished ä¸å¢å `ChangePalletPhase` å®å«ï¼ä» å½ Phase==0ï¼ææé¶æ®µå®æï¼æ¶æè§¦åå ¥åºåå é¤ä»»å¡ï¼ä¸é´é¶æ®µä¸å¤ç | | `IFakeBatteryPositionService.cs` | +`MarkAsAvailable(List<int> positions)` æ¹æ³ | | `FakeBatteryPositionService.cs` | +`MarkAsAvailable` å®ç° | | `IFakeBatteryPositionRepository.cs` | +`MarkAsAvailable(List<int> positions)` æ¹æ³ | | `FakeBatteryPositionRepository.cs` | +`MarkAsAvailable` å®ç°ï¼å°æå®ç¹ä½ IsUsed 设为 falseï¼ | ## å½ä»¤å¤çå¨äº¤äº ### RobotPrefixCommandHandler åæ´ **pickfinished å¤çï¼** - å½ `ChangePalletPhase == 3`ï¼æµåBååçµè¯ï¼æ¶ï¼ä¸è°ç¨æç APIï¼ä» æ´æ°ç¶æ - å ¶ä»é¶æ®µä¿æç°æé»è¾ **putfinished å¤çï¼** - 夿æµåï¼éè¿ `state.CurrentTask.RobotSourceAddressLineCode` å¤ææ¯æµåAè¿æ¯æµåB - å½ `ChangePalletPhase == 2` 䏿µåBï¼æ¾æ£å¸¸çµè¯ï¼æ¶ï¼æ£å¸¸è°ç¨ ChangePalletAsync APIï¼`state.RobotTaskTotalNum += positions.Length` - å½ `ChangePalletPhase == 4`ï¼æµåBæ¾åçµè¯å°5å·ä½ï¼æ¶ï¼ä¸è°ç¨ APIï¼ä¸éå¢ `RobotTaskTotalNum`ï¼è°ç¨ `MarkAsAvailable(positions)` éæ¾ç¹ä½ - å½ `ChangePalletPhase == 2` 䏿µåAï¼æ¾åçµè¯å°ç®æ ï¼æ¶ï¼ä¸è°ç¨ APIï¼ä¸éå¢ `RobotTaskTotalNum` ### RobotSimpleCommandHandler åæ´ **allpickfinished / allputfinishedï¼** - å¢å å®å«æ¡ä»¶ï¼ä» å½ `state.ChangePalletPhase == 0`ï¼ææé¶æ®µå®æï¼æ¶ï¼ææ§è¡å ¥åºåä¼ åä»»å¡å é¤ - ä¸é´é¶æ®µæ¶å° allpickfinished/allputfinished æ¶ï¼ä» æ´æ° `state.CurrentAction`ï¼ä¸è§¦åå ¥åºé»è¾ - ä»»å¡å®ææ¶é¢å¤éç½®ï¼`state.ChangePalletPhase = 0`, `state.CurrentBatchIndex = 1`, `state.IsInFakeBatteryMode = false` ## è¾¹çæ¡ä»¶ - `task.RobotTaskTotalNum == 48`ï¼ç´æ¥èµ°åæé»è¾ï¼ä¸è¿å ¥æ¹æ¬¡æ¨¡å¼ - `task.RobotTaskTotalNum == 0`ï¼æµåA 补满48个åçµè¯ï¼æµåB è·³è¿ Phase 1/2ï¼ç´æ¥è¿å ¥ Phase 3/4 åæ¶48个åçµè¯ - åçµè¯å¹³é¢ç¹ä½ä¸è¶³ï¼è®°å½é误æ¥å¿ï¼ä¸æ¢å½åæ¹æ¬¡ - åçµè¯ç¹ä½ç¢çåï¼`GetNextAvailable` è¦æ±åè¡è¿ç»ï¼å¦æè¯·æ±4个æ¾ä¸å°ï¼ä¾æ¬¡å°è¯3ã2ã1个 - æ¹æ¬¡ç¼å·æº¢åºï¼è¶ è¿48ï¼ï¼ä¸åºåçï¼ä½éé²å¾¡æ§æ£æ¥ ÏîÄ¿×ÊÁÏ/¼¼ÊõÐÒé/~$Ñé֤ƽ̨ÎïÁ÷²Ö´¢ÏµÍ³¼¼Êõ¹æ¸ñÊé-11.24.docxBinary files differ
ÏîÄ¿×ÊÁÏ/É豸ÐÒé/»úеÊÖÐÒé/~$½»»¥Á÷³Ì±í(1).xlsxBinary files differ
ÏîÄ¿×ÊÁÏ/É豸ÐÒé/»úеÊÖÐÒé/½»»¥Á÷³Ì±í(1).xlsxBinary files differ