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;
}
}