diff --git a/Cs_HMI/Project/AGV4.csproj b/Cs_HMI/Project/AGV4.csproj index 5f8703d..ef768c8 100644 --- a/Cs_HMI/Project/AGV4.csproj +++ b/Cs_HMI/Project/AGV4.csproj @@ -289,10 +289,10 @@ - + Form - + Form diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs index f88b6a3..9132652 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs @@ -231,7 +231,7 @@ namespace Project case ERunStep.CLEANER_IN: //클리너도킹 case ERunStep.UNLOADER_IN: //언로더도킹 case ERunStep.LOADER_IN: //로더도킹 - if (_SM_RUN_LOADER_IN(runStepisFirst, PUB.sm.GetRunSteptime)) + if (_SM_RUN_ENTER(runStepisFirst, PUB.sm.GetRunSteptime)) { PUB.Speak(Lang.버퍼도킹이완료되었습니다); @@ -246,7 +246,7 @@ namespace Project case ERunStep.CLEANER_OUT: //클리너아웃 case ERunStep.UNLOADER_OUT: //언로더아웃 case ERunStep.LOADER_OUT: //로더아웃 - if (_SM_RUN_LOADER_OUT(runStepisFirst, PUB.sm.GetRunSteptime)) + if (_SM_RUN_EXIT(runStepisFirst, PUB.sm.GetRunSteptime)) { PUB.Speak(Lang.버퍼도킹해제완료); diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs index 3bdd592..f8578d2 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs @@ -60,18 +60,52 @@ namespace Project } else if (PUB.sm.RunStepSeq == idx++) { - if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90) + //이미 턴이 되어있는지 확인한다 (재진입 시 중복 실행 방지) + if (PUB._virtualAGV.Turn == AGVNavigationCore.Models.AGVTurn.L90) { - PUB.AGV.AGVMoveLeft180Turn(); - PUB.log.Add("AGV Left Turn"); - VAR.TIME.Update(eVarTime.LastTurnCommandTime); + PUB.log.Add($"[{funcname}] 이미 Left Turn 완료 상태입니다. 턴 명령을 건너뜁니다."); + PUB.sm.UpdateRunStepSeq(); + } + else + { + //하드웨어 상태 확인 + var turnState = PUB.AGV.TurnInformation?.State ?? arDev.eNarumiTurn.None; + + if (turnState == arDev.eNarumiTurn.Left || turnState == arDev.eNarumiTurn.LeftIng) + { + //이미 좌회전 중이거나 완료된 하드웨어 상태 + PUB.log.Add($"[{funcname}] 하드웨어 좌회전 상태 확인됨({turnState}). 명령을 건너뜁니다."); + PUB.sm.UpdateRunStepSeq(); + } + else if (turnState == arDev.eNarumiTurn.Right || turnState == arDev.eNarumiTurn.RightIng) + { + //비정상 상태 (우회전 중?) + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] 턴 방향 불일치(Current:{turnState}). 우회전 상태에서 좌회전을 시도할 수 없습니다."); + PUB._mapCanvas.SetAlertMessage("Turn 방향 오류"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + else + { + //정상 (None) -> 턴 명령 실행 + PUB.AGV.AGVMoveLeft180Turn(); + PUB.log.Add("AGV Left Turn"); + VAR.TIME.Update(eVarTime.LastTurnCommandTime); + PUB.sm.UpdateRunStepSeq(); + } } PUB._mapCanvas.SetAlertMessage($"턴 진행 중"); - PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다. return false; } else if (PUB.sm.RunStepSeq == idx++) { + //이미 완료된 상태라면 대기 과정을 건너뛴다. + if (PUB._virtualAGV.Turn == AGVNavigationCore.Models.AGVTurn.L90) + { + PUB.sm.UpdateRunStepSeq(); + return false; + } + //왼쪽턴이 완료되지 않은경우 if (PUB.AGV.TurnInformation.State != arDev.eNarumiTurn.Left) { @@ -82,7 +116,7 @@ namespace Project var overtime = 30; if (PUB.AGV.TurnInformation.Runtime.TotalSeconds > overtime) { - //5초동안 AGV까 움직이지 않았다면 오류 처리한다. + //30초동안 AGV까 움직이지 않았다면 오류 처리한다. PUB.AGV.AGVMoveStop($"[bufferin] {overtime}초이내 턴 감지 안됨"); PUB.log.AddE($"[{funcname}] {overtime}초이내 턴 감지 안됨"); PUB._mapCanvas.SetAlertMessage($"턴 완료 확인 불가(최대:{overtime}초)"); @@ -148,6 +182,17 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //저속이동 (후진 진입) + + // [Smart Restart] 재시작 시 안전 검사 + // 이미 턴을 완료했고(L90), 현재 마크 센서가 감지된다면(ON), + // 이미 목적지(Mark 2)에 도착한 것으로 간주하여 후진을 생략한다. + if (PUB._virtualAGV.Turn == AGVNavigationCore.Models.AGVTurn.L90 && PUB.AGV.signal1.mark_sensor == true) + { + PUB.log.Add($"[{funcname}] 이미 목적지 도착 확인됨(Turn:L90, Sensor:ON). 후진 이동을 생략합니다."); + PUB.sm.UpdateRunStepSeq(); + return false; + } + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs index 2295d4b..adbdec7 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs @@ -116,16 +116,36 @@ namespace Project } else if (PUB.sm.RunStepSeq == idx++) { - //마크스탑 - PUB.AGV.AGVMoveStop("buffer out", arDev.Narumi.eStopOpt.MarkStop); + //AGV구동을 확인하고 마크스탑을 설정한다. + if (PUB.AGV.system1.agv_run == false) + { + if (seqtime.TotalSeconds > 3) + { + //구동이확인되지 않으면 오류처리를 한다. + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] AGV 전진 구동이 확인되지 않습니다"); + PUB._mapCanvas.SetAlertMessage("agv 전진실패"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + //마크스탑설정 + PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop); PUB.sm.UpdateRunStepSeq(); return false; } else if (PUB.sm.RunStepSeq == idx++) { - //이동확인 - if (PUB.AGV.system1.agv_run == false) + //마크스탑신호가 3초이내로 들어와야 한다 + if (PUB.AGV.data.Speed != 'S') { + if (seqtime.TotalSeconds > 3) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] MARK STOP신호가 확인되지 않습니다"); + PUB._mapCanvas.SetAlertMessage("mark stop 신호 확인 안됨"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } return false; } PUB.sm.UpdateRunStepSeq(); @@ -133,9 +153,16 @@ namespace Project } else if (PUB.sm.RunStepSeq == idx++) { - //멈춤확인 + //AGV가 멈출때까지 기다린다. if (PUB.AGV.system1.agv_run == true) { + if (seqtime.TotalSeconds > 15) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] AGV가 멈추지 않아 강제종료 합니다"); + PUB._mapCanvas.SetAlertMessage("agv가 멈추지 않아 강제 종료"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } return false; } PUB.sm.UpdateRunStepSeq(); @@ -144,8 +171,38 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //우측으로 180도 턴 - PUB.AGV.AGVMoveRight180Turn(); - PUB.sm.UpdateRunStepSeq(); + //이미 턴이 되어있는지 확인한다 (재진입 시 중복 실행 방지) + if (PUB._virtualAGV.Turn == AGVNavigationCore.Models.AGVTurn.R90) + { + PUB.log.Add($"[{funcname}] 이미 Right Turn 완료 상태입니다. 턴 명령을 건너뜁니다."); + PUB.sm.UpdateRunStepSeq(); + } + else + { + //하드웨어 상태 확인 + var turnState = PUB.AGV.TurnInformation?.State ?? arDev.eNarumiTurn.None; + + if (turnState == arDev.eNarumiTurn.Right || turnState == arDev.eNarumiTurn.RightIng) + { + //이미 우회전 중이거나 완료된 하드웨어 상태 + PUB.log.Add($"[{funcname}] 하드웨어 우회전 상태 확인됨({turnState}). 명령을 건너뜁니다."); + PUB.sm.UpdateRunStepSeq(); + } + else if (turnState == arDev.eNarumiTurn.Left || turnState == arDev.eNarumiTurn.LeftIng) + { + //비정상 상태 (좌회전 중?) + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] 턴 방향 불일치(Current:{turnState}). 좌회전 상태에서 우회전을 시도할 수 없습니다."); + PUB._mapCanvas.SetAlertMessage("Turn 방향 오류"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + else + { + //정상 (None) -> 턴 명령 실행 + PUB.AGV.AGVMoveRight180Turn(); + PUB.sm.UpdateRunStepSeq(); + } + } return false; } else if (PUB.sm.RunStepSeq == idx++) @@ -153,6 +210,27 @@ namespace Project //이동확인 if (PUB.AGV.system1.agv_run == false) { + if (seqtime.TotalSeconds > 3) + { + //만약 이미 완료된 상태라서 건너뛰었다면 run이 안될 수 있다. + //하지만 위에서 건너뜀 -> UpdateRunStepSeq -> 바로 여기로 옴. + //건너뛴 경우(Turn == R90), Run 확인을 할 필요가 없으므로... + //로직 보완: 턴 상태가 이미 완료라면 이동확인도 스킵해야 함? + //아니면 위 단계에서 완료 상태면 '다음 다음' 단계로 점프? + //간단하게: Turn이 R90이면 그냥 통과시킴. + + if (PUB._virtualAGV.Turn == AGVNavigationCore.Models.AGVTurn.R90) + { + //이미 완료된 상태이므로 이동 확인 패스 + PUB.sm.UpdateRunStepSeq(); + return false; + } + + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] Turn 구동 실패"); + PUB._mapCanvas.SetAlertMessage("Turn 구동 실패"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } return false; } PUB.sm.UpdateRunStepSeq(); @@ -163,8 +241,23 @@ namespace Project //멈춤확인 if (PUB.AGV.system1.agv_run == true) { + if (seqtime.TotalSeconds > 25) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] Turn 완료 실패 (Timeout)"); + PUB._mapCanvas.SetAlertMessage("Turn 완료 실패"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } return false; } + + //턴 완료 상태 업데이트 + if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.R90) + { + PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.R90; + PUB.log.Add($"[{funcname}] Turn State Updated to R90"); + } + PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs similarity index 98% rename from Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs rename to Cs_HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs index 832bed2..6c66498 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs @@ -12,9 +12,9 @@ namespace Project public partial class fMain { /// - /// 로더 진입 + /// 장비로 진입한다. - /// - public Boolean _SM_RUN_LOADER_IN(bool isFirst, TimeSpan seqTime) + public Boolean _SM_RUN_ENTER(bool isFirst, TimeSpan seqTime) { var idx = 1; var funcname = PUB.sm.RunStep.ToString(); diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs similarity index 98% rename from Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs rename to Cs_HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs index 7b2109c..fe07632 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs @@ -12,9 +12,9 @@ namespace Project public partial class fMain { /// - /// 로더 배출 + /// 장비엣 빠젼나온다. /// - public Boolean _SM_RUN_LOADER_OUT(bool isFirst, TimeSpan seqTime) + public Boolean _SM_RUN_EXIT(bool isFirst, TimeSpan seqTime) { var idx = 1; var funcname = PUB.sm.RunStep.ToString(); diff --git a/Cs_HMI/Project/StateMachine/Step/_Util.cs b/Cs_HMI/Project/StateMachine/Step/_Util.cs index e4a6f09..bd3df71 100644 --- a/Cs_HMI/Project/StateMachine/Step/_Util.cs +++ b/Cs_HMI/Project/StateMachine/Step/_Util.cs @@ -203,22 +203,28 @@ namespace Project { if (PUB.AGV.system1.agv_run == false) { - //목적지도착완료시 - if (PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.TargetNode.Id) + // 경로가 존재한다면, 경로의 마지막 노드에 도착했는지 확인한다. + if (PUB._virtualAGV.CurrentPath != null && PUB._virtualAGV.CurrentPath.DetailedPath.Any()) { - var node = PUB._mapCanvas.Nodes.Where(t => t.Id == PUB._virtualAGV.CurrentNodeId).FirstOrDefault(); - var rfid = node?.ID2 ?? "(X)"; - PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료) Node:{rfid}"); - return true; - } - - //목적지가 버퍼라면 그 앞에 멈춘다 - if (PUB._virtualAGV.TargetNode.StationType == AGVNavigationCore.Models.StationType.Buffer && - PUB._virtualAGV.CurrentPath != null && PUB._virtualAGV.CurrentPath.DetailedPath.Any()) - { - if (PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.CurrentPath.DetailedPath.Last().NodeId) + var lastInfo = PUB._virtualAGV.CurrentPath.DetailedPath.Last(); + // 위치와 방향이 모두 일치해야 완료된 것으로 본다. + if (PUB._virtualAGV.CurrentNode.Id == lastInfo.NodeId && + PUB._virtualAGV.CurrentDirection == lastInfo.MotorDirection) { - PUB.log.AddI($"목표(버퍼) 도착 및 정지 확인됨(MarkStop 완료). Node:{PUB._virtualAGV.CurrentNodeID2}"); + var node = PUB._mapCanvas.Nodes.Where(t => t.Id == PUB._virtualAGV.CurrentNodeId).FirstOrDefault(); + var rfid = node?.ID2 ?? "(X)"; + PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료) Node:{rfid}, Dir:{PUB._virtualAGV.CurrentDirection}"); + return true; + } + } + else + { + // 경로 정보가 없다면 단순히 목적지 ID와 비교한다 (Fallback) + if (PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.TargetNode.Id) + { + var node = PUB._mapCanvas.Nodes.Where(t => t.Id == PUB._virtualAGV.CurrentNodeId).FirstOrDefault(); + var rfid = node?.ID2 ?? "(X)"; + PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료, No Path Info) Node:{rfid}"); return true; } }