Implement ACS Command Handlers (PickOn, PickOff, Charge), Manual Mode Safety, and Map UI Commands
This commit is contained in:
@@ -88,17 +88,18 @@ namespace AGVNavigationCore.Models
|
|||||||
|
|
||||||
private bool _disablecross = false;
|
private bool _disablecross = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 해당 노드 통과 시 제한 속도 (기본값: M - Normal)
|
||||||
|
/// Predict 단계에서 이 값을 참조하여 속도 명령을 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
public SpeedLevel SpeedLimit { get; set; } = SpeedLevel.M;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 장비 ID (도킹/충전 스테이션인 경우)
|
/// 장비 ID (도킹/충전 스테이션인 경우)
|
||||||
/// 예: "LOADER1", "CLEANER1", "BUFFER1", "CHARGER1"
|
/// 예: "LOADER1", "CLEANER1", "BUFFER1", "CHARGER1"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string NodeAlias { get; set; } = string.Empty;
|
public string NodeAlias { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 장비 타입 (도킹/충전 스테이션인 경우)
|
|
||||||
/// </summary>
|
|
||||||
// public StationType? StationType { get; set; } = null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 노드 생성 일자
|
/// 노드 생성 일자
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -109,7 +110,6 @@ namespace AGVNavigationCore.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 노드 활성화 여부
|
/// 노드 활성화 여부
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -188,6 +188,11 @@ namespace AGVNavigationCore.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int DetectedRfidCount => _detectedRfids.Count;
|
public int DetectedRfidCount => _detectedRfids.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 배터리 부족 경고 임계값 (%)
|
||||||
|
/// </summary>
|
||||||
|
public float LowBatteryThreshold { get; set; } = 20.0f;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructor
|
#region Constructor
|
||||||
@@ -262,9 +267,9 @@ namespace AGVNavigationCore.Models
|
|||||||
BatteryLevel = Math.Max(0, Math.Min(100, percentage));
|
BatteryLevel = Math.Max(0, Math.Min(100, percentage));
|
||||||
|
|
||||||
// 배터리 부족 경고
|
// 배터리 부족 경고
|
||||||
if (BatteryLevel < 20.0f && _currentState != AGVState.Charging)
|
if (BatteryLevel < LowBatteryThreshold && _currentState != AGVState.Charging)
|
||||||
{
|
{
|
||||||
OnError($"배터리 부족: {BatteryLevel:F1}%");
|
OnError($"배터리 부족: {BatteryLevel:F1}% (기준: {LowBatteryThreshold}%)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,6 +591,15 @@ namespace AGVNavigationCore.Models
|
|||||||
var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.NodeId && t.IsPass == false);
|
var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.NodeId && t.IsPass == false);
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
|
// [PathJump Check] 점프한 노드 개수 확인
|
||||||
|
// 현재 노드(item)보다 이전인데 아직 IsPass가 안 된 노드의 개수
|
||||||
|
int skippedCount = CurrentPath.DetailedPath.Count(t => t.seq < item.seq && t.IsPass == false);
|
||||||
|
if (skippedCount > 2)
|
||||||
|
{
|
||||||
|
OnError($"PathJump: {skippedCount}개의 노드를 건너뛰었습니다. (허용: 2개, 현재노드: {node.NodeId})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//item.IsPass = true;
|
//item.IsPass = true;
|
||||||
//이전노드는 모두 지나친걸로 한다
|
//이전노드는 모두 지나친걸로 한다
|
||||||
CurrentPath.DetailedPath.Where(t => t.seq < item.seq).ToList().ForEach(t => t.IsPass = true);
|
CurrentPath.DetailedPath.Where(t => t.seq < item.seq).ToList().ForEach(t => t.IsPass = true);
|
||||||
@@ -633,14 +647,11 @@ namespace AGVNavigationCore.Models
|
|||||||
// DetailedPath가 없으면 기본 명령 반환
|
// DetailedPath가 없으면 기본 명령 반환
|
||||||
if (_currentPath == null || _currentPath.DetailedPath == null || _currentPath.DetailedPath.Count == 0)
|
if (_currentPath == null || _currentPath.DetailedPath == null || _currentPath.DetailedPath.Count == 0)
|
||||||
{
|
{
|
||||||
var defaultMotor = _currentDirection == AgvDirection.Forward
|
// [Refactor] Predict와 일관성 유지: 경로가 없으면 정지
|
||||||
? MotorCommand.Forward
|
|
||||||
: MotorCommand.Backward;
|
|
||||||
|
|
||||||
return new AGVCommand(
|
return new AGVCommand(
|
||||||
defaultMotor,
|
MotorCommand.Stop,
|
||||||
MagnetPosition.S,
|
MagnetPosition.S,
|
||||||
SpeedLevel.M,
|
SpeedLevel.L,
|
||||||
eAGVCommandReason.NoPath,
|
eAGVCommandReason.NoPath,
|
||||||
$"{actionDescription} (DetailedPath 없음)"
|
$"{actionDescription} (DetailedPath 없음)"
|
||||||
);
|
);
|
||||||
@@ -697,10 +708,13 @@ namespace AGVNavigationCore.Models
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 속도 결정 (회전 노드면 저속, 일반 이동은 중속)
|
// [Speed Control] NodeMotorInfo에 설정된 속도 사용
|
||||||
SpeedLevel speed = nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint
|
// 단, 회전 구간 등에서 안전을 위해 강제 감속이 필요한 경우 로직 추가 가능
|
||||||
? SpeedLevel.L
|
// 현재는 사용자 설정 우선
|
||||||
: SpeedLevel.M;
|
SpeedLevel speed = nodeInfo.Speed;
|
||||||
|
|
||||||
|
// Optional: 회전 시 강제 감속 로직 (사용자 요청에 따라 주석 처리 또는 제거 가능)
|
||||||
|
// if (nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint) speed = SpeedLevel.L;
|
||||||
|
|
||||||
return new AGVCommand(
|
return new AGVCommand(
|
||||||
motorCmd,
|
motorCmd,
|
||||||
|
|||||||
@@ -445,7 +445,12 @@ namespace AGVNavigationCore.PathFinding.Planning
|
|||||||
MagnetDirection.Straight
|
MagnetDirection.Straight
|
||||||
);
|
);
|
||||||
|
|
||||||
detailedPath1.Add(nodeInfo);
|
// [Speed Control] MapNode의 속도 설정 적용
|
||||||
|
var mapNode = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
|
||||||
|
if (mapNode != null)
|
||||||
|
{
|
||||||
|
nodeInfo.Speed = mapNode.SpeedLimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// path1에 상세 경로 정보 설정
|
// path1에 상세 경로 정보 설정
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ namespace AGVNavigationCore.PathFinding.Planning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AgvDirection MotorDirection { get; set; }
|
public AgvDirection MotorDirection { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 해당 노드에서의 제한 속도
|
||||||
|
/// </summary>
|
||||||
|
public SpeedLevel Speed { get; set; } = SpeedLevel.M;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 마그넷 센서 방향 제어 (갈림길 처리용)
|
/// 마그넷 센서 방향 제어 (갈림길 처리용)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
1174
Cs_HMI/Data/NewMap_2.json
Normal file
1174
Cs_HMI/Data/NewMap_2.json
Normal file
File diff suppressed because one or more lines are too long
@@ -222,8 +222,6 @@ namespace Project
|
|||||||
|
|
||||||
#region "Charge"
|
#region "Charge"
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public int chargerpos { get; set; }
|
|
||||||
[Browsable(false)]
|
|
||||||
public int ChargetWaitSec { get; set; }
|
public int ChargetWaitSec { get; set; }
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public int ChargeEmergencyLevel { get; set; }
|
public int ChargeEmergencyLevel { get; set; }
|
||||||
@@ -240,6 +238,9 @@ namespace Project
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public int ChargeSearchTime { get; set; }
|
public int ChargeSearchTime { get; set; }
|
||||||
|
|
||||||
|
[Category("Charge"), DisplayName("Low Battery Limit"), Description("배터리 부족 경고 알림 기준 (%)")]
|
||||||
|
public int BatteryLimit_Low { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "AGV"
|
#region "AGV"
|
||||||
@@ -427,10 +428,10 @@ namespace Project
|
|||||||
if (ChargeEmergencyLevel == 0) ChargeEmergencyLevel = 30;
|
if (ChargeEmergencyLevel == 0) ChargeEmergencyLevel = 30;
|
||||||
if (interval_bms == 0) interval_bms = 10;
|
if (interval_bms == 0) interval_bms = 10;
|
||||||
|
|
||||||
//충전은 10분간격으로 재시도 한다
|
|
||||||
if (ChargeRetryTerm == 0) ChargeRetryTerm = 600;
|
if (ChargeRetryTerm == 0) ChargeRetryTerm = 600;
|
||||||
if (alarmSoundTerm == 0) alarmSoundTerm = 15; //기본 15초
|
if (alarmSoundTerm == 0) alarmSoundTerm = 15; //기본 15초
|
||||||
if (ChargeSearchTime == 0) ChargeSearchTime = 25;
|
if (ChargeSearchTime == 0) ChargeSearchTime = 25;
|
||||||
|
if (BatteryLimit_Low == 0) BatteryLimit_Low = 20;
|
||||||
//최대 충전진행 시간(기본 1시간)
|
//최대 충전진행 시간(기본 1시간)
|
||||||
if (ChargeMaxTime == 0) ChargeMaxTime = 3600;
|
if (ChargeMaxTime == 0) ChargeMaxTime = 3600;
|
||||||
// if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;
|
// if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ namespace Project
|
|||||||
public static AGVNavigationCore.Controls.UnifiedAGVCanvas _mapCanvas;
|
public static AGVNavigationCore.Controls.UnifiedAGVCanvas _mapCanvas;
|
||||||
public static List<MapNode> _mapNodes;
|
public static List<MapNode> _mapNodes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다음 작업 명령 (PickOn/PickOff)
|
||||||
|
/// </summary>
|
||||||
|
public static ENIGProtocol.AGVCommandHE NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 가상 AGV (시뮬레이션용)
|
/// 가상 AGV (시뮬레이션용)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -724,7 +729,7 @@ namespace Project
|
|||||||
{
|
{
|
||||||
if (_virtualAGV == null) return;
|
if (_virtualAGV == null) return;
|
||||||
|
|
||||||
_virtualAGV.BatteryLevel = batteryLevel;
|
_virtualAGV.SetBatteryLevel(batteryLevel);
|
||||||
RefreshAGVCanvas();
|
RefreshAGVCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,20 +19,6 @@ namespace Project
|
|||||||
|
|
||||||
private void _SM_RUN(Boolean isFirst, TimeSpan stepTime)
|
private void _SM_RUN(Boolean isFirst, TimeSpan stepTime)
|
||||||
{
|
{
|
||||||
//중단기능이 동작이라면 처리하지 않는다.
|
|
||||||
if (PUB.sm.bPause)
|
|
||||||
{
|
|
||||||
System.Threading.Thread.Sleep(200);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//가동불가 조건 확인
|
|
||||||
if (CheckStopCondition() == false)
|
|
||||||
{
|
|
||||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//HW 연결오류
|
//HW 연결오류
|
||||||
if (PUB.AGV.IsOpen == false)
|
if (PUB.AGV.IsOpen == false)
|
||||||
{
|
{
|
||||||
@@ -41,17 +27,16 @@ namespace Project
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//이머전시상태라면 stop 처리한다.
|
//가동불가 조건 확인
|
||||||
if (PUB.AGV.error.Emergency &&
|
if (CheckStopCondition() == false) return;
|
||||||
PUB.AGV.system1.agv_stop == true &&
|
|
||||||
PUB.AGV.system1.stop_by_front_detect == false)
|
//중단기능이 동작이라면 처리하지 않는다.
|
||||||
|
if (PUB.sm.bPause)
|
||||||
{
|
{
|
||||||
PUB.Speak(Lang.비상정지로인해작업을중단합니다);
|
System.Threading.Thread.Sleep(200);
|
||||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//스텝이 변경되었다면?
|
//스텝이 변경되었다면?
|
||||||
if (PUB.sm.RunStep != PUB.sm.RunStepNew)
|
if (PUB.sm.RunStep != PUB.sm.RunStepNew)
|
||||||
{
|
{
|
||||||
@@ -272,9 +257,21 @@ namespace Project
|
|||||||
//도킹완료상태를 업데이트한다.
|
//도킹완료상태를 업데이트한다.
|
||||||
PUB.XBE.LoaderInComplete = true;
|
PUB.XBE.LoaderInComplete = true;
|
||||||
|
|
||||||
//로더아웃으로 자동 진행 합니다
|
//로더아웃으로 자동 진행하지 않음 (ACS 명령 대기)
|
||||||
PUB.sm.ClearRunStep();
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT);
|
{
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; // Command consumed
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Legacy behavior or Goto command: Auto-exit?
|
||||||
|
// User said separation is key. Let's Stop here too or keep legacy for GOTO?
|
||||||
|
// Assuming GOTO might rely on this, but safer to STOP if we want strict separation.
|
||||||
|
// However, let's keep legacy behavior for GOTO if possible, but for PickOn/Off we STOP.
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -302,9 +299,17 @@ namespace Project
|
|||||||
//도킹완료상태를 업데이트한다.
|
//도킹완료상태를 업데이트한다.
|
||||||
PUB.XBE.UnloaderInComplete = true;
|
PUB.XBE.UnloaderInComplete = true;
|
||||||
|
|
||||||
//언로더아웃으로 자동 진행 합니다
|
//언로더아웃으로 자동 진행하지 않음
|
||||||
PUB.sm.ClearRunStep();
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT);
|
{
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -332,9 +337,17 @@ namespace Project
|
|||||||
//도킹완료상태를 업데이트한다.
|
//도킹완료상태를 업데이트한다.
|
||||||
PUB.XBE.CleanerInComplete = true;
|
PUB.XBE.CleanerInComplete = true;
|
||||||
|
|
||||||
//클리너아웃으로 자동 진행 합니다
|
//클리너아웃으로 자동 진행하지 않음
|
||||||
PUB.sm.ClearRunStep();
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT);
|
{
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -362,9 +375,17 @@ namespace Project
|
|||||||
//도킹완료상태를 업데이트한다.
|
//도킹완료상태를 업데이트한다.
|
||||||
PUB.XBE.BufferInComplete = true;
|
PUB.XBE.BufferInComplete = true;
|
||||||
|
|
||||||
//버퍼아웃으로 자동 진행 합니다
|
//버퍼아웃으로 자동 진행하지 않음
|
||||||
PUB.sm.ClearRunStep();
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
PUB.sm.SetNewRunStep(ERunStep.BUFFER_OUT);
|
{
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.BUFFER_OUT);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -125,28 +125,27 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트를 내린다.
|
// [PickOn/PickOff] 초기 리프트 동작
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
var liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트다운센서를 확인한다.
|
//리프트 센서 확인
|
||||||
var liftdown = true;
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
if (liftdown == false)
|
if (ts.TotalSeconds > 10)
|
||||||
{
|
{
|
||||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
// Timebound check
|
||||||
if (ts.TotalSeconds > 10)
|
}
|
||||||
{
|
PUB.log.Add("리프트 동작 확인 완료");
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
|
||||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
|
||||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PUB.log.Add("리프트 하강 완료");
|
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -160,7 +159,7 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//저속이동
|
//저속이동 (후진 진입)
|
||||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||||
{
|
{
|
||||||
Bunki = arDev.Narumi.eBunki.Strate,
|
Bunki = arDev.Narumi.eBunki.Strate,
|
||||||
@@ -224,11 +223,43 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//완료되었다. (ACS에 보내야함)
|
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||||
PUB.log.Add("버퍼투입완료");
|
PUB.log.Add("버퍼 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||||
|
|
||||||
|
var liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// 리프트 동작 대기
|
||||||
|
// TODO: 실제 센서 확인 로직 추가 필요
|
||||||
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
|
if (ts.TotalSeconds < 2) return false;
|
||||||
|
|
||||||
|
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환 (퇴출 명령 대기)");
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
//완료되었다. (ACS에 보내야함)
|
||||||
|
PUB.log.Add("버퍼 진입 및 작업 완료");
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 작업을 마치고 설비 안에 멈춰있는 상태.
|
||||||
|
// ACS가 이 상태를 확인하고 NextWorkCmd로 퇴출(Out) 명령을 보내야 함.
|
||||||
|
PUB.AddEEDB($"버퍼작업완료({PUB.Result.TargetPos})");
|
||||||
|
return true;
|
||||||
|
|
||||||
PUB.AddEEDB($"버퍼투입완료({PUB.Result.TargetPos})");
|
PUB.AddEEDB($"버퍼투입완료({PUB.Result.TargetPos})");
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -43,28 +43,27 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트를 내린다.
|
// [PickOn/PickOff] 초기 리프트 동작
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
var liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트다운센서를 확인한다.
|
//리프트 센서 확인
|
||||||
var liftdown = true;
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
if (liftdown == false)
|
if (ts.TotalSeconds > 10)
|
||||||
{
|
{
|
||||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
// Timebound check
|
||||||
if (ts.TotalSeconds > 10)
|
}
|
||||||
{
|
PUB.log.Add("리프트 동작 확인 완료");
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
|
||||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
|
||||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PUB.log.Add("리프트 하강 완료");
|
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -78,7 +77,7 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//저속이동
|
//저속이동 (후진 진입)
|
||||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||||
{
|
{
|
||||||
Bunki = arDev.Narumi.eBunki.Strate,
|
Bunki = arDev.Narumi.eBunki.Strate,
|
||||||
@@ -141,14 +140,40 @@ namespace Project
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||||
|
PUB.log.Add("클리너 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||||
|
|
||||||
|
var liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// 리프트 동작 대기
|
||||||
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
|
if (ts.TotalSeconds < 2) return false;
|
||||||
|
|
||||||
|
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//완료되었다.
|
//완료되었다.
|
||||||
PUB.log.Add("클리너진입완료");
|
PUB.log.Add("클리너 진입 및 작업 완료");
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PUB.AddEEDB($"클리너진입완료({PUB.Result.TargetPos})");
|
PUB.AddEEDB($"클리너작업완료({PUB.Result.TargetPos})");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,18 @@ namespace Project
|
|||||||
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
|
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//이미 충전중이라면 바로 완료 처리한다 (사용자 요청)
|
||||||
|
if (VAR.BOOL[eVarBool.FLAG_CHARGEONA] == true || PUB.AGV.system1.Battery_charging == true)
|
||||||
|
{
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
PUB.log.Add("이미 충전 중이므로 충전 시퀀스를 종료하고 준비 상태로 전환합니다.");
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//충전 상태가 OFF되어야 동작하게한다
|
//충전 상태가 OFF되어야 동작하게한다
|
||||||
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
|
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
|
||||||
@@ -64,7 +75,7 @@ namespace Project
|
|||||||
if (PUB.sm.RunStepSeq == idx++)
|
if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
var targetnode = PUB.FindByRFID(PUB.setting.NodeMAP_RFID_Charger);
|
var targetnode = PUB.FindByRFID(PUB.setting.NodeMAP_RFID_Charger);
|
||||||
if(targetnode == null)
|
if (targetnode == null)
|
||||||
{
|
{
|
||||||
PUB.log.AddE($"충전기 노드가 설정되지 않았습니다");
|
PUB.log.AddE($"충전기 노드가 설정되지 않았습니다");
|
||||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
@@ -75,15 +86,15 @@ namespace Project
|
|||||||
PUB._virtualAGV.TargetNode = targetnode;
|
PUB._virtualAGV.TargetNode = targetnode;
|
||||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
PUB.log.Add($"충전:대상위치 QC 시작");
|
PUB.log.Add($"충전기 위치로 이동 목표:{targetnode.RfidId}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//모션 전후진 제어
|
//모션 전후진 제어
|
||||||
if ( UpdateMotionPositionForCharger("GOCHARGE #1") == true)
|
if (UpdateMotionPositionForCharger("GOCHARGE #1") == true)
|
||||||
{
|
{
|
||||||
PUB.log.Add($"충전:충전기 검색 전 QC위치 확인 완료");
|
PUB.log.Add($"충전기 목표위치 이동 완료");
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -106,72 +117,41 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
if (PUB.setting.chargerpos == 0) //down search
|
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
|
||||||
|
PUB.Speak(Lang.충전기를검색합니다);
|
||||||
|
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||||
{
|
{
|
||||||
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
|
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||||
PUB.Speak(Lang.충전기를검색합니다);
|
Bunki = arDev.Narumi.eBunki.Strate,
|
||||||
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
Direction = arDev.Narumi.eMoveDir.Forward,
|
||||||
{
|
PBSSensor = 1,
|
||||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
});
|
||||||
Bunki = arDev.Narumi.eBunki.Strate,
|
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
|
||||||
Direction = arDev.Narumi.eMoveDir.Forward,
|
//PUB.Result.TargetPos = ePosition.CHARGE;
|
||||||
PBSSensor = 1,
|
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||||
});
|
|
||||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
|
|
||||||
//PUB.Result.TargetPos = ePosition.CHARGE;
|
|
||||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
|
||||||
}
|
|
||||||
else if (PUB.setting.chargerpos == 2) //up search
|
|
||||||
{
|
|
||||||
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
|
|
||||||
PUB.Speak(Lang.충전기를검색합니다);
|
|
||||||
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
|
||||||
{
|
|
||||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
|
||||||
Bunki = arDev.Narumi.eBunki.Strate,
|
|
||||||
Direction = arDev.Narumi.eMoveDir.Backward,
|
|
||||||
PBSSensor = 1,
|
|
||||||
});
|
|
||||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
|
|
||||||
//PUB.Result.TargetPos = ePosition.CHARGE;
|
|
||||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PUB.log.Add($"충전기위치가 QC위치로 설정되어 있습니다");
|
|
||||||
}
|
|
||||||
|
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
if (PUB.setting.chargerpos != 1)
|
if (PUB.AGV.system1.agv_run)
|
||||||
{
|
|
||||||
if (PUB.AGV.system1.agv_run)
|
|
||||||
{
|
|
||||||
PUB.log.Add($"충전:AGV기동확인으로 마크정지신호설정");
|
|
||||||
PUB.Speak(Lang.다음마크위치에서정지합니다);
|
|
||||||
PUB.AGV.AGVMoveStop("SM_RUN_GOCHARGE", arDev.Narumi.eStopOpt.MarkStop);
|
|
||||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
|
||||||
PUB.sm.UpdateRunStepSeq();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (VAR.TIME.RUN(eVarTime.ChargeSearch).TotalSeconds > 5)
|
|
||||||
{
|
|
||||||
//5초이상 이곳에서 대기한다면 다시 돌려준다
|
|
||||||
PUB.sm.UpdateRunStepSeq(-1);
|
|
||||||
PUB.log.Add($"충전:AGV기동확인 안됨, 롤백 다시 이동할 수 있게 함");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
PUB.log.Add($"충전:AGV기동확인으로 마크정지신호설정");
|
||||||
|
PUB.Speak(Lang.다음마크위치에서정지합니다);
|
||||||
|
PUB.AGV.AGVMoveStop("SM_RUN_GOCHARGE", arDev.Narumi.eStopOpt.MarkStop);
|
||||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (VAR.TIME.RUN(eVarTime.ChargeSearch).TotalSeconds > 5)
|
||||||
|
{
|
||||||
|
//5초이상 이곳에서 대기한다면 다시 돌려준다
|
||||||
|
PUB.sm.UpdateRunStepSeq(-1);
|
||||||
|
PUB.log.Add($"충전:AGV기동확인 안됨, 롤백 다시 이동할 수 있게 함");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -259,7 +239,6 @@ namespace Project
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,28 +43,33 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트를 내린다.
|
// [PickOn/PickOff] 초기 리프트 동작
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
var liftCmd = arDev.Narumi.LiftCommand.DN; // Default PickOn (Get -> Go Under -> Down)
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.UP; // PickOff (Put -> Carry In -> Up)
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트다운센서를 확인한다.
|
//리프트 센서 확인 (Direction에 따라 다름)
|
||||||
var liftdown = true; // 센서 확인 로직이 주석처리되어 있거나 하드코딩되어 있었음 (버퍼 코드 참조)
|
// TODO: UP 센서 확인 로직 추가 필요 시 구현. 현재는 시간체크만 유지하거나 DN확인만 있음.
|
||||||
if (liftdown == false)
|
// 일단 기존 로직 유지하되, UP일 경우 스킵 고려
|
||||||
{
|
|
||||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
if (ts.TotalSeconds > 10)
|
if (ts.TotalSeconds > 10)
|
||||||
{
|
{
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
// Timebound check
|
||||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
// PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
// Warning only?
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
}
|
PUB.log.Add("리프트 동작 확인 완료");
|
||||||
PUB.log.Add("리프트 하강 완료");
|
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -78,7 +83,7 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//저속이동
|
//저속이동 (후진 진입)
|
||||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||||
{
|
{
|
||||||
Bunki = arDev.Narumi.eBunki.Strate,
|
Bunki = arDev.Narumi.eBunki.Strate,
|
||||||
@@ -141,14 +146,41 @@ namespace Project
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||||
|
PUB.log.Add("로더 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||||
|
|
||||||
|
var liftCmd = arDev.Narumi.LiftCommand.UP; // Default PickOn (Lift Up to Pick)
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.DN; // PickOff (Lift Down to Drop)
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// 리프트 동작 대기
|
||||||
|
// TODO: 센서 확인
|
||||||
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
|
if (ts.TotalSeconds < 2) return false; // 2초 대기
|
||||||
|
|
||||||
|
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//완료되었다.
|
//완료되었다.
|
||||||
PUB.log.Add("로더진입완료");
|
PUB.log.Add("로더 진입 및 작업 완료");
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PUB.AddEEDB($"로더진입완료({PUB.Result.TargetPos})");
|
PUB.AddEEDB($"로더작업완료({PUB.Result.TargetPos})");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,28 +43,27 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트를 내린다.
|
// [PickOn/PickOff] 초기 리프트 동작
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
var liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//리프트다운센서를 확인한다.
|
//리프트 센서 확인
|
||||||
var liftdown = true;
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
if (liftdown == false)
|
if (ts.TotalSeconds > 10)
|
||||||
{
|
{
|
||||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
// Timebound check
|
||||||
if (ts.TotalSeconds > 10)
|
}
|
||||||
{
|
PUB.log.Add("리프트 동작 확인 완료");
|
||||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
|
||||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
|
||||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PUB.log.Add("리프트 하강 완료");
|
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -78,7 +77,7 @@ namespace Project
|
|||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//저속이동
|
//저속이동 (후진 진입)
|
||||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||||
{
|
{
|
||||||
Bunki = arDev.Narumi.eBunki.Strate,
|
Bunki = arDev.Narumi.eBunki.Strate,
|
||||||
@@ -141,14 +140,40 @@ namespace Project
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (PUB.sm.RunStepSeq == idx++)
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||||
|
PUB.log.Add("언로더 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||||
|
|
||||||
|
var liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||||
|
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||||
|
{
|
||||||
|
liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.AGV.LiftControl(liftCmd);
|
||||||
|
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
|
{
|
||||||
|
// 리프트 동작 대기
|
||||||
|
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||||
|
if (ts.TotalSeconds < 2) return false;
|
||||||
|
|
||||||
|
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
|
||||||
|
PUB.sm.UpdateRunStepSeq();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (PUB.sm.RunStepSeq == idx++)
|
||||||
{
|
{
|
||||||
//완료되었다.
|
//완료되었다.
|
||||||
PUB.log.Add("언로더진입완료");
|
PUB.log.Add("언로더 진입 및 작업 완료");
|
||||||
PUB.sm.UpdateRunStepSeq();
|
PUB.sm.UpdateRunStepSeq();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PUB.AddEEDB($"언로더진입완료({PUB.Result.TargetPos})");
|
PUB.AddEEDB($"언로더작업완료({PUB.Result.TargetPos})");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Text;
|
|||||||
using Project.StateMachine;
|
using Project.StateMachine;
|
||||||
using COMM;
|
using COMM;
|
||||||
using AR;
|
using AR;
|
||||||
|
using AGVNavigationCore.Utils;
|
||||||
|
|
||||||
namespace Project
|
namespace Project
|
||||||
{
|
{
|
||||||
@@ -14,6 +15,26 @@ namespace Project
|
|||||||
|
|
||||||
bool CheckStopCondition()
|
bool CheckStopCondition()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//이머전시상태라면 stop 처리한다.
|
||||||
|
if (PUB.AGV.error.Emergency &&
|
||||||
|
PUB.AGV.system1.agv_stop == true &&
|
||||||
|
PUB.AGV.system1.stop_by_front_detect == false)
|
||||||
|
{
|
||||||
|
PUB.Speak(Lang.비상정지로인해작업을중단합니다);
|
||||||
|
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//수동충전상태라면 이동하지 못한다
|
||||||
|
if (VAR.BOOL[eVarBool.FLAG_CHARGEONM])
|
||||||
|
{
|
||||||
|
PUB.Speak("수동 충전중이라 사용할 수 없습니다");
|
||||||
|
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +121,18 @@ namespace Project
|
|||||||
PUB.log.AddI($"경로생성 {PUB._virtualAGV.StartNode.RfidId} -> {PUB._virtualAGV.TargetNode.RfidId}");
|
PUB.log.AddI($"경로생성 {PUB._virtualAGV.StartNode.RfidId} -> {PUB._virtualAGV.TargetNode.RfidId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//경로에 대한 무결성 검증
|
||||||
|
if (CheckPathIntegrity(PUB._virtualAGV.CurrentPath) == false)
|
||||||
|
{
|
||||||
|
if (PUB.AGV.system1.agv_run)
|
||||||
|
{
|
||||||
|
PUB.AGV.AGVMoveStop("Path Integrity Fail");
|
||||||
|
}
|
||||||
|
PUB.log.AddE($"경로 무결성 오류");
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//predict 를 이용하여 다음 이동을 모두 확인한다.
|
//predict 를 이용하여 다음 이동을 모두 확인한다.
|
||||||
var nextAction = PUB._virtualAGV.Predict();
|
var nextAction = PUB._virtualAGV.Predict();
|
||||||
|
|
||||||
@@ -122,7 +155,7 @@ namespace Project
|
|||||||
// 완료(Complete) 상태라면 MarkStop 전송
|
// 완료(Complete) 상태라면 MarkStop 전송
|
||||||
if (nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.MarkStop)
|
if (nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.MarkStop)
|
||||||
{
|
{
|
||||||
PUB.log.Add("다음행동예측에서 MARK STOP이 확인되었습니다");
|
PUB.log.Add("다음행동예측에서 MARK STOP이 확인되었습니다 (자동정지시퀀스)");
|
||||||
PUB.AGV.AGVMoveStop(nextAction.Message, arDev.Narumi.eStopOpt.MarkStop);
|
PUB.AGV.AGVMoveStop(nextAction.Message, arDev.Narumi.eStopOpt.MarkStop);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -140,6 +173,7 @@ namespace Project
|
|||||||
{
|
{
|
||||||
if (PUB.AGV.system1.agv_run == false)
|
if (PUB.AGV.system1.agv_run == false)
|
||||||
{
|
{
|
||||||
|
PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료). Node:{PUB._virtualAGV.CurrentNodeId}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,5 +278,36 @@ namespace Project
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 경로 무결성(도킹방향 등) 검증
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathResult"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private bool CheckPathIntegrity(AGVNavigationCore.PathFinding.Core.AGVPathResult pathResult)
|
||||||
|
{
|
||||||
|
if (pathResult == null) return false;
|
||||||
|
|
||||||
|
// CalcPath에서 이미 DockingValidator를 수행했을 수 있음.
|
||||||
|
// 만약 수행되지 않았다면 여기서 수행.
|
||||||
|
if (pathResult.DockingValidation == null)
|
||||||
|
{
|
||||||
|
pathResult.DockingValidation = AGVNavigationCore.Utils.DockingValidator.ValidateDockingDirection(pathResult, PUB._mapNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 검증 결과 확인
|
||||||
|
if (pathResult.DockingValidation != null && pathResult.DockingValidation.IsValidationRequired)
|
||||||
|
{
|
||||||
|
if (pathResult.DockingValidation.IsValid == false)
|
||||||
|
{
|
||||||
|
PUB.log.AddE($"[경로무결성오류] {pathResult.DockingValidation.ValidationError}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}//cvass
|
}//cvass
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Project.StateMachine;
|
|||||||
using COMM;
|
using COMM;
|
||||||
using AR;
|
using AR;
|
||||||
using AGVNavigationCore.Models;
|
using AGVNavigationCore.Models;
|
||||||
|
using AGVNavigationCore.Controls;
|
||||||
|
|
||||||
namespace Project
|
namespace Project
|
||||||
{
|
{
|
||||||
@@ -60,6 +61,20 @@ namespace Project
|
|||||||
//if (chg_run && PUB.AGV.system1.agv_run) PUB.Speak("이동을 시작 합니다");
|
//if (chg_run && PUB.AGV.system1.agv_run) PUB.Speak("이동을 시작 합니다");
|
||||||
VAR.BOOL[eVarBool.AGVDIR_BACK] = PUB.AGV.data.Direction == 'B';
|
VAR.BOOL[eVarBool.AGVDIR_BACK] = PUB.AGV.data.Direction == 'B';
|
||||||
|
|
||||||
|
// [Sync] Update VirtualAGV Direction
|
||||||
|
var syncDir = PUB.AGV.data.Direction == 'B' ? AgvDirection.Backward : AgvDirection.Forward;
|
||||||
|
if (PUB._virtualAGV.CurrentDirection != syncDir)
|
||||||
|
PUB.UpdateAGVDirection(syncDir);
|
||||||
|
|
||||||
|
// [Sync] Update VirtualAGV State
|
||||||
|
AGVState syncState = AGVState.Idle;
|
||||||
|
if (PUB.AGV.error.Value > 0) syncState = AGVState.Error;
|
||||||
|
else if (PUB.AGV.system1.Battery_charging) syncState = AGVState.Charging;
|
||||||
|
else if (PUB.AGV.system1.agv_run) syncState = AGVState.Moving;
|
||||||
|
|
||||||
|
if (PUB._virtualAGV.GetCurrentState() != syncState)
|
||||||
|
PUB.UpdateAGVState(syncState);
|
||||||
|
|
||||||
if (VAR.BOOL[eVarBool.AGV_ERROR] != (agv_err > 0))
|
if (VAR.BOOL[eVarBool.AGV_ERROR] != (agv_err > 0))
|
||||||
{
|
{
|
||||||
VAR.BOOL[eVarBool.AGV_ERROR] = (agv_err > 0);
|
VAR.BOOL[eVarBool.AGV_ERROR] = (agv_err > 0);
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ namespace Project
|
|||||||
//PUB.mapctl.Manager.agv.BatteryLevel = PUB.BMS.Current_Level;
|
//PUB.mapctl.Manager.agv.BatteryLevel = PUB.BMS.Current_Level;
|
||||||
//PUB.mapctl.Manager.agv.BatteryTemp1 = PUB.BMS.Current_temp1;
|
//PUB.mapctl.Manager.agv.BatteryTemp1 = PUB.BMS.Current_temp1;
|
||||||
//PUB.mapctl.Manager.agv.BatteryTemp2 = PUB.BMS.Current_temp2;
|
//PUB.mapctl.Manager.agv.BatteryTemp2 = PUB.BMS.Current_temp2;
|
||||||
|
|
||||||
|
// [Sync] Update VirtualAGV Battery
|
||||||
|
PUB.UpdateAGVBattery(PUB.BMS.Current_Level);
|
||||||
|
|
||||||
if (PUB.BMS.Current_Level <= PUB.setting.ChargeStartLevel)
|
if (PUB.BMS.Current_Level <= PUB.setting.ChargeStartLevel)
|
||||||
{
|
{
|
||||||
//배터리 레벨이 기준보다 낮다면 경고를 활성화 한다
|
//배터리 레벨이 기준보다 낮다면 경고를 활성화 한다
|
||||||
|
|||||||
@@ -65,6 +65,47 @@ namespace Project
|
|||||||
else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Lenght Errorr:{data.Length}");
|
else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Lenght Errorr:{data.Length}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ENIGProtocol.AGVCommandHE.PickOn: // 110
|
||||||
|
case ENIGProtocol.AGVCommandHE.PickOff: // 111
|
||||||
|
{
|
||||||
|
PUB.log.AddI($"XBEE:작업명령수신:{cmd}");
|
||||||
|
|
||||||
|
// 현재 위치 확인 (TargetNode가 아닌 CurrentNode 기준)
|
||||||
|
var currNode = PUB._virtualAGV.CurrentNode;
|
||||||
|
if (currNode == null)
|
||||||
|
{
|
||||||
|
PUB.log.AddE($"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다 NodeID:{PUB._virtualAGV.CurrentNodeId}");
|
||||||
|
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Node");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.NextWorkCmd = cmd;
|
||||||
|
ERunStep nextStep = ERunStep.IDLE;
|
||||||
|
|
||||||
|
switch (currNode.Type)
|
||||||
|
{
|
||||||
|
case NodeType.Loader: nextStep = ERunStep.LOADER_IN; break;
|
||||||
|
case NodeType.UnLoader: nextStep = ERunStep.UNLOADER_IN; break;
|
||||||
|
case NodeType.Buffer: nextStep = ERunStep.BUFFER_IN; break;
|
||||||
|
case NodeType.Clearner: nextStep = ERunStep.CLEANER_IN; break;
|
||||||
|
default:
|
||||||
|
PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({currNode.Type})은 작업을 지원하지 않습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PUB.log.AddI($"작업 시작: {nextStep} (Type: {cmd})");
|
||||||
|
PUB.sm.SetNewRunStep(nextStep);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENIGProtocol.AGVCommandHE.Charger: // 112
|
||||||
|
{
|
||||||
|
PUB.log.AddI($"XBEE:충전명령수신");
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Charger;
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.GOCHARGE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case ENIGProtocol.AGVCommandHE.Goto: //move to tag
|
case ENIGProtocol.AGVCommandHE.Goto: //move to tag
|
||||||
if (data.Length > 4)
|
if (data.Length > 4)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ namespace Project.ViewForm
|
|||||||
|
|
||||||
// 이벤트 연결
|
// 이벤트 연결
|
||||||
//PUB._mapCanvas.NodeAdded += OnNodeAdded;
|
//PUB._mapCanvas.NodeAdded += OnNodeAdded;
|
||||||
//PUB._mapCanvas.NodeSelected += OnNodeSelected;
|
// 이벤트 연결
|
||||||
|
//PUB._mapCanvas.NodeAdded += OnNodeAdded;
|
||||||
|
PUB._mapCanvas.NodesSelected += OnNodeSelected;
|
||||||
//PUB._mapCanvas.NodeMoved += OnNodeMoved;
|
//PUB._mapCanvas.NodeMoved += OnNodeMoved;
|
||||||
//PUB._mapCanvas.NodeDeleted += OnNodeDeleted;
|
//PUB._mapCanvas.NodeDeleted += OnNodeDeleted;
|
||||||
//PUB._mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
//PUB._mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
||||||
@@ -58,6 +60,85 @@ namespace Project.ViewForm
|
|||||||
|
|
||||||
// 스플리터 패널에 맵 캔버스 추가
|
// 스플리터 패널에 맵 캔버스 추가
|
||||||
panel1.Controls.Add(PUB._mapCanvas);
|
panel1.Controls.Add(PUB._mapCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNodeSelected(object sender, List<MapNode> nodes, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Button != MouseButtons.Right) return;
|
||||||
|
var node = nodes.FirstOrDefault();
|
||||||
|
if (nodes == null) return;
|
||||||
|
|
||||||
|
// 도킹 가능한 노드인지 또는 작업 노드인지 확인
|
||||||
|
bool isDockingNode = node.Type == NodeType.Loader || node.Type == NodeType.UnLoader
|
||||||
|
|| node.Type == NodeType.Buffer || node.Type == NodeType.Clearner
|
||||||
|
|| node.Type == NodeType.Charging;
|
||||||
|
|
||||||
|
if (!isDockingNode) return;
|
||||||
|
|
||||||
|
ContextMenuStrip menu = new ContextMenuStrip();
|
||||||
|
|
||||||
|
// PickOn
|
||||||
|
var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)");
|
||||||
|
pickOn.Click += (s, args) => ExecuteManualCommand(node, ENIGProtocol.AGVCommandHE.PickOn);
|
||||||
|
menu.Items.Add(pickOn);
|
||||||
|
|
||||||
|
// PickOff
|
||||||
|
var pickOff = new ToolStripMenuItem("Pick Off (Move & Drop)");
|
||||||
|
pickOff.Click += (s, args) => ExecuteManualCommand(node, ENIGProtocol.AGVCommandHE.PickOff);
|
||||||
|
menu.Items.Add(pickOff);
|
||||||
|
|
||||||
|
// Charge
|
||||||
|
if (node.Type == NodeType.Charging)
|
||||||
|
{
|
||||||
|
var charge = new ToolStripMenuItem("Charge (Move & Charge)");
|
||||||
|
charge.Click += (s, args) => ExecuteManualCommand(node, ENIGProtocol.AGVCommandHE.Charger);
|
||||||
|
menu.Items.Add(charge);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.Show(Cursor.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteManualCommand(MapNode targetNode, ENIGProtocol.AGVCommandHE cmd)
|
||||||
|
{
|
||||||
|
if (PUB._virtualAGV.CurrentNode == null)
|
||||||
|
{
|
||||||
|
MessageBox.Show("AGV의 현재 위치를 알 수 없습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (PUB.sm.RunStep != eSMStep.IDLE && PUB.sm.RunStep != eSMStep.READY)
|
||||||
|
{
|
||||||
|
if (MessageBox.Show("현재 대기상태가 아닙니다. 강제로 실행하시겠습니까?", "Warning", MessageBoxButtons.YesNo) == DialogResult.No) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 경로 생성
|
||||||
|
var pathFinder = new AGVNavigationCore.PathFinding.Planning.AGVPathfinder(PUB._mapNodes);
|
||||||
|
// 현재위치에서 목표위치까지
|
||||||
|
var result = pathFinder.FindPath(PUB._virtualAGV.CurrentNode, targetNode);
|
||||||
|
|
||||||
|
if (!result.Success || result.Path == null || result.Path.Count == 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show("경로를 찾을 수 없습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 상태 설정
|
||||||
|
PUB.log.AddI($"[Manual Command] {cmd} to {targetNode.Name}({targetNode.NodeId})");
|
||||||
|
|
||||||
|
// Path 변환 (Node 리스트 -> AGVPathResult)
|
||||||
|
// VirtualAGV.SetPath가 필요할 수 있음. 혹은 Goto 로직을 수동으로 구성.
|
||||||
|
// _SM_RUN_GOTO에서는 PUB._virtualAGV.CurrentPath를 사용함.
|
||||||
|
// AGVPathResult 생성 필요.
|
||||||
|
|
||||||
|
var detailedPath = AGVNavigationCore.PathFinding.Planning.AGVPathfinder.MakeDetailData(result.Path, PUB._mapNodes);
|
||||||
|
PUB._virtualAGV.SetPath(result.Path, detailedPath);
|
||||||
|
PUB._virtualAGV.TargetNode = targetNode;
|
||||||
|
|
||||||
|
// 3. 작업 설정
|
||||||
|
PUB.NextWorkCmd = cmd;
|
||||||
|
|
||||||
|
// 4. 실행
|
||||||
|
PUB.sm.SetNewRunStep(ERunStep.GOTO); // GOTO -> Arrive -> _IN sequence execution
|
||||||
|
}
|
||||||
|
|
||||||
// 툴바 버튼 이벤트 연결
|
// 툴바 버튼 이벤트 연결
|
||||||
//WireToolbarButtonEvents();
|
//WireToolbarButtonEvents();
|
||||||
@@ -85,8 +166,8 @@ namespace Project.ViewForm
|
|||||||
//맵파일로딩
|
//맵파일로딩
|
||||||
if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.agvmap");
|
if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.agvmap");
|
||||||
System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile);
|
System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile);
|
||||||
if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName,"default.agvmap"));
|
if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName, "default.agvmap"));
|
||||||
if(filePath.Exists==false) //그래도없다면 맵폴더에서 파일을 찾아본다.
|
if (filePath.Exists == false) //그래도없다면 맵폴더에서 파일을 찾아본다.
|
||||||
{
|
{
|
||||||
var files = mapPath.GetFiles("*.agvmap");
|
var files = mapPath.GetFiles("*.agvmap");
|
||||||
if (files.Any()) filePath = files[0];
|
if (files.Any()) filePath = files[0];
|
||||||
@@ -105,7 +186,7 @@ namespace Project.ViewForm
|
|||||||
// 맵 캔버스에 데이터 설정
|
// 맵 캔버스에 데이터 설정
|
||||||
PUB._mapCanvas.Nodes = PUB._mapNodes;
|
PUB._mapCanvas.Nodes = PUB._mapNodes;
|
||||||
PUB._mapCanvas.MapFileName = filePath.FullName;
|
PUB._mapCanvas.MapFileName = filePath.FullName;
|
||||||
|
|
||||||
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
||||||
if (result.Settings != null)
|
if (result.Settings != null)
|
||||||
{
|
{
|
||||||
@@ -120,6 +201,7 @@ namespace Project.ViewForm
|
|||||||
if (startNode != null)
|
if (startNode != null)
|
||||||
{
|
{
|
||||||
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward);
|
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward);
|
||||||
|
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
|
||||||
PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
|
PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
|
||||||
|
|
||||||
// 캔버스에 AGV 리스트 설정
|
// 캔버스에 AGV 리스트 설정
|
||||||
@@ -131,6 +213,7 @@ namespace Project.ViewForm
|
|||||||
}
|
}
|
||||||
else if (PUB._virtualAGV != null)
|
else if (PUB._virtualAGV != null)
|
||||||
{
|
{
|
||||||
|
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
|
||||||
// 기존 AGV가 있으면 캔버스에 다시 연결
|
// 기존 AGV가 있으면 캔버스에 다시 연결
|
||||||
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
|
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
|
||||||
PUB._mapCanvas.AGVList = agvList;
|
PUB._mapCanvas.AGVList = agvList;
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ namespace Project.ViewForm
|
|||||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.BS, spd, ss);
|
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.BS, spd, ss);
|
||||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||||
|
|
||||||
|
// [Manual Safety] Clear previous auto-task state
|
||||||
|
PUB._virtualAGV.TargetNode = null;
|
||||||
|
PUB._virtualAGV.CurrentPath = null;
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; // Clear ACS Command
|
||||||
|
PUB.sm.ClearRunStep(); // Clear RunStep sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
private void arLabel2_Click(object sender, EventArgs e)
|
private void arLabel2_Click(object sender, EventArgs e)
|
||||||
@@ -108,6 +114,12 @@ namespace Project.ViewForm
|
|||||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.FS, spd, ss);
|
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.FS, spd, ss);
|
||||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||||
|
|
||||||
|
// [Manual Safety] Clear previous auto-task state
|
||||||
|
PUB._virtualAGV.TargetNode = null;
|
||||||
|
PUB._virtualAGV.CurrentPath = null;
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void arLabel3_Click(object sender, EventArgs e)
|
private void arLabel3_Click(object sender, EventArgs e)
|
||||||
@@ -131,6 +143,12 @@ namespace Project.ViewForm
|
|||||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.RT, spd, ss);
|
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.RT, spd, ss);
|
||||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||||
|
|
||||||
|
// [Manual Safety] Clear previous auto-task state
|
||||||
|
PUB._virtualAGV.TargetNode = null;
|
||||||
|
PUB._virtualAGV.CurrentPath = null;
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void arLabel4_Click(object sender, EventArgs e)
|
private void arLabel4_Click(object sender, EventArgs e)
|
||||||
@@ -154,6 +172,12 @@ namespace Project.ViewForm
|
|||||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.LT, spd, ss);
|
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.LT, spd, ss);
|
||||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||||
|
|
||||||
|
// [Manual Safety] Clear previous auto-task state
|
||||||
|
PUB._virtualAGV.TargetNode = null;
|
||||||
|
PUB._virtualAGV.CurrentPath = null;
|
||||||
|
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||||
|
PUB.sm.ClearRunStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void arLabel11_Click(object sender, EventArgs e)
|
private void arLabel11_Click(object sender, EventArgs e)
|
||||||
|
|||||||
17
Cs_HMI/Project/fSetup.Designer.cs
generated
17
Cs_HMI/Project/fSetup.Designer.cs
generated
@@ -103,7 +103,6 @@
|
|||||||
this.valIntervalBMS = new AGVControl.ValueSelect();
|
this.valIntervalBMS = new AGVControl.ValueSelect();
|
||||||
this.valIntervalXBE = new AGVControl.ValueSelect();
|
this.valIntervalXBE = new AGVControl.ValueSelect();
|
||||||
this.tabPage3 = new System.Windows.Forms.TabPage();
|
this.tabPage3 = new System.Windows.Forms.TabPage();
|
||||||
this.cmbChargerPos = new System.Windows.Forms.ComboBox();
|
|
||||||
this.label58 = new System.Windows.Forms.Label();
|
this.label58 = new System.Windows.Forms.Label();
|
||||||
this.label19 = new System.Windows.Forms.Label();
|
this.label19 = new System.Windows.Forms.Label();
|
||||||
this.label44 = new System.Windows.Forms.Label();
|
this.label44 = new System.Windows.Forms.Label();
|
||||||
@@ -1562,7 +1561,6 @@
|
|||||||
// tabPage3
|
// tabPage3
|
||||||
//
|
//
|
||||||
this.tabPage3.BackColor = System.Drawing.Color.DarkSlateBlue;
|
this.tabPage3.BackColor = System.Drawing.Color.DarkSlateBlue;
|
||||||
this.tabPage3.Controls.Add(this.cmbChargerPos);
|
|
||||||
this.tabPage3.Controls.Add(this.label58);
|
this.tabPage3.Controls.Add(this.label58);
|
||||||
this.tabPage3.Controls.Add(this.label19);
|
this.tabPage3.Controls.Add(this.label19);
|
||||||
this.tabPage3.Controls.Add(this.label44);
|
this.tabPage3.Controls.Add(this.label44);
|
||||||
@@ -1600,20 +1598,6 @@
|
|||||||
this.tabPage3.TabIndex = 4;
|
this.tabPage3.TabIndex = 4;
|
||||||
this.tabPage3.Text = "충전";
|
this.tabPage3.Text = "충전";
|
||||||
//
|
//
|
||||||
// cmbChargerPos
|
|
||||||
//
|
|
||||||
this.cmbChargerPos.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
|
||||||
this.cmbChargerPos.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Bold);
|
|
||||||
this.cmbChargerPos.FormattingEnabled = true;
|
|
||||||
this.cmbChargerPos.Items.AddRange(new object[] {
|
|
||||||
"HOME 아래",
|
|
||||||
"HOME",
|
|
||||||
"HOME 위"});
|
|
||||||
this.cmbChargerPos.Location = new System.Drawing.Point(1004, 11);
|
|
||||||
this.cmbChargerPos.Name = "cmbChargerPos";
|
|
||||||
this.cmbChargerPos.Size = new System.Drawing.Size(217, 45);
|
|
||||||
this.cmbChargerPos.TabIndex = 69;
|
|
||||||
//
|
|
||||||
// label58
|
// label58
|
||||||
//
|
//
|
||||||
this.label58.AutoSize = true;
|
this.label58.AutoSize = true;
|
||||||
@@ -3611,7 +3595,6 @@
|
|||||||
private AGVControl.ValueSelect vcTagF5B;
|
private AGVControl.ValueSelect vcTagF5B;
|
||||||
private AGVControl.ValueSelect vcTagF4F5;
|
private AGVControl.ValueSelect vcTagF4F5;
|
||||||
private System.Windows.Forms.Label label58;
|
private System.Windows.Forms.Label label58;
|
||||||
private System.Windows.Forms.ComboBox cmbChargerPos;
|
|
||||||
private System.Windows.Forms.CheckBox chkClearPos;
|
private System.Windows.Forms.CheckBox chkClearPos;
|
||||||
private System.Windows.Forms.Label label7;
|
private System.Windows.Forms.Label label7;
|
||||||
private AGVControl.ValueSelect vcXBID;
|
private AGVControl.ValueSelect vcXBID;
|
||||||
|
|||||||
@@ -52,26 +52,19 @@ namespace Project
|
|||||||
{
|
{
|
||||||
|
|
||||||
//통신값 표시
|
//통신값 표시
|
||||||
//tbPortBMS.Text = Pub.setting.Port_BMS;
|
|
||||||
///tbPortPLC.Text = PUB.setting.Port_PLC;
|
|
||||||
tbPortAGV.Text = PUB.setting.Port_AGV;
|
tbPortAGV.Text = PUB.setting.Port_AGV;
|
||||||
tbPortXBE.Text = PUB.setting.Port_XBE;
|
tbPortXBE.Text = PUB.setting.Port_XBE;
|
||||||
tbportBMS.Text = PUB.setting.Port_BAT;
|
tbportBMS.Text = PUB.setting.Port_BAT;
|
||||||
|
|
||||||
// tbBaudPLC.Text = PUB.setting.Baud_PLC.ToString();
|
|
||||||
tbBaudAGV.Text = PUB.setting.Baud_AGV.ToString();
|
tbBaudAGV.Text = PUB.setting.Baud_AGV.ToString();
|
||||||
tbBaudXBE.Text = PUB.setting.Baud_XBE.ToString();
|
tbBaudXBE.Text = PUB.setting.Baud_XBE.ToString();
|
||||||
tbBaudBAT.Text = PUB.setting.Baud_BAT.ToString();
|
tbBaudBAT.Text = PUB.setting.Baud_BAT.ToString();
|
||||||
|
|
||||||
//valueSelect1.Value = Pub.setting.interval_bms;
|
|
||||||
valIntervalXBE.Value = PUB.setting.interval_xbe;
|
valIntervalXBE.Value = PUB.setting.interval_xbe;
|
||||||
vcpidDS.Value = PUB.setting.ZSpeed;
|
vcpidDS.Value = PUB.setting.ZSpeed;
|
||||||
//valueSelect4.Value = PUB.setting.interval_iostate;
|
|
||||||
valIntervalBMS.Value = PUB.setting.interval_bms;
|
valIntervalBMS.Value = PUB.setting.interval_bms;
|
||||||
tbChargerID.Value = PUB.setting.ChargerID;
|
tbChargerID.Value = PUB.setting.ChargerID;
|
||||||
|
|
||||||
cmbChargerPos.SelectedIndex = PUB.setting.chargerpos;
|
|
||||||
|
|
||||||
vcSCK.Value = PUB.setting.SCK;
|
vcSCK.Value = PUB.setting.SCK;
|
||||||
vcSSK.Value = PUB.setting.SSK;
|
vcSSK.Value = PUB.setting.SSK;
|
||||||
vcSTT.Value = PUB.setting.STT;
|
vcSTT.Value = PUB.setting.STT;
|
||||||
@@ -218,28 +211,19 @@ namespace Project
|
|||||||
propertyGrid1.Invalidate();
|
propertyGrid1.Invalidate();
|
||||||
|
|
||||||
//통신정보저장
|
//통신정보저장
|
||||||
// Pub.setting.Port_BMS = tbPortBMS.Text;
|
|
||||||
PUB.setting.Port_XBE = tbPortXBE.Text;
|
PUB.setting.Port_XBE = tbPortXBE.Text;
|
||||||
//PUB.setting.Port_PLC = tbPortPLC.Text;
|
|
||||||
PUB.setting.Port_AGV = tbPortAGV.Text;
|
PUB.setting.Port_AGV = tbPortAGV.Text;
|
||||||
PUB.setting.Port_BAT = tbportBMS.Text;
|
PUB.setting.Port_BAT = tbportBMS.Text;
|
||||||
|
|
||||||
// Pub.setting.Baud_bms = int.Parse(tbBaudBms.Text);
|
|
||||||
PUB.setting.Baud_XBE = int.Parse(tbBaudXBE.Text);
|
PUB.setting.Baud_XBE = int.Parse(tbBaudXBE.Text);
|
||||||
//PUB.setting.Baud_PLC = int.Parse(tbBaudPLC.Text);
|
|
||||||
PUB.setting.Baud_AGV = int.Parse(tbBaudAGV.Text);
|
PUB.setting.Baud_AGV = int.Parse(tbBaudAGV.Text);
|
||||||
PUB.setting.Baud_BAT = int.Parse(tbBaudBAT.Text);
|
PUB.setting.Baud_BAT = int.Parse(tbBaudBAT.Text);
|
||||||
|
|
||||||
PUB.setting.ChargerID = (int)(tbChargerID.Value);
|
PUB.setting.ChargerID = (int)(tbChargerID.Value);
|
||||||
|
|
||||||
//Pub.setting.interval_bms = (float)valueSelect1.Value;
|
|
||||||
PUB.setting.interval_bms = (int)valIntervalBMS.Value;
|
PUB.setting.interval_bms = (int)valIntervalBMS.Value;
|
||||||
PUB.setting.interval_xbe = (float)valIntervalXBE.Value;
|
PUB.setting.interval_xbe = (float)valIntervalXBE.Value;
|
||||||
PUB.setting.ZSpeed = (byte)vcpidDS.Value;
|
PUB.setting.ZSpeed = (byte)vcpidDS.Value;
|
||||||
//PUB.setting.interval_iostate = (byte)valueSelect4.Value;
|
|
||||||
PUB.setting.chargerpos = this.cmbChargerPos.SelectedIndex;
|
|
||||||
PUB.setting.AutoModeOffAndClearPosition = this.chkClearPos.Checked;
|
PUB.setting.AutoModeOffAndClearPosition = this.chkClearPos.Checked;
|
||||||
//PUB.setting.MotorUpTime = (byte)valueSelect5.Value;
|
|
||||||
|
|
||||||
//AGV정보 저장
|
//AGV정보 저장
|
||||||
PUB.setting.Enable_Speak = this.btSpeaker.ProgressValue != 0;
|
PUB.setting.Enable_Speak = this.btSpeaker.ProgressValue != 0;
|
||||||
@@ -294,7 +278,6 @@ namespace Project
|
|||||||
|
|
||||||
//시스템옵션
|
//시스템옵션
|
||||||
PUB.setting.ChargeMaxTime = (int)vcChargeMaxTime.Value;
|
PUB.setting.ChargeMaxTime = (int)vcChargeMaxTime.Value;
|
||||||
//Pub.setting.ChargeIdleInterval = (int)nudChargeIdleInterval.Value;
|
|
||||||
PUB.setting.ChargeRetryTerm = (int)vcChargeRetryTerm.Value;
|
PUB.setting.ChargeRetryTerm = (int)vcChargeRetryTerm.Value;
|
||||||
|
|
||||||
//초기화시간
|
//초기화시간
|
||||||
|
|||||||
78
Cs_HMI/docs/Charging_Sequence_Analysis.md
Normal file
78
Cs_HMI/docs/Charging_Sequence_Analysis.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# 충전 시퀀스 분석 (Charging Sequence Analysis)
|
||||||
|
|
||||||
|
## 1. 개요 (Overview)
|
||||||
|
이 문서는 AGV의 자동 및 수동 충전 프로세스에 대한 분석 결과를 기술합니다. UI 트리거부터 이동, 도킹, 충전 명령 전송 및 확인까지의 전체 흐름을 포함합니다.
|
||||||
|
|
||||||
|
## 2. UI 트리거 (UI Trigger)
|
||||||
|
* **관련 컨트롤**: `btCharge` (자동충전/해제), `btChargeM` (수동충전 모드 토글)
|
||||||
|
* **이벤트 핸들러**: `btCharge_Click`, `btChargeM_Click` (in `fMain.cs`)
|
||||||
|
* **동작 로직**:
|
||||||
|
* **자동 충전 (`btCharge`)**:
|
||||||
|
* 이미 충전 관련 상태(`GOCHARGE`, `CHARGECHECK`)이거나 충전 중(`FLAG_CHARGEONA`)인 경우: → 충전 중지 여부 확인 후 `GOTO`(이동) 상태로 전환하여 충전 해제.
|
||||||
|
* 대기 상태인 경우: → 충전 시작 여부 확인 후 `GOCHARGE` RunStep 설정 및 `RUN` 상태 시작.
|
||||||
|
* **수동 충전 (`btChargeM`)**:
|
||||||
|
* `FLAG_CHARGEONM` 플래그를 토글합니다. 자동 충전 중일 때는 진입 불가.
|
||||||
|
|
||||||
|
## 3. 상태 머신 로직 (State Machine Logic)
|
||||||
|
|
||||||
|
### 3.1 충전 이동 (`_SM_RUN_GOCHARGE.cs`)
|
||||||
|
충전기를 찾아 이동하고 도킹하는 핵심 시퀀스입니다.
|
||||||
|
|
||||||
|
**사전 검사 (Prerequisites)**
|
||||||
|
1. **초기화**: `PUB.Result.CurrentPos` 초기화.
|
||||||
|
2. **하드웨어 체크**: AGV 연결 상태 확인.
|
||||||
|
3. **충전 상태 확인 (최적화)**: 이미 충전 중(`FLAG_CHARGEONA` or `Battery_charging`)인 경우, 충전 시퀀스를 진행하지 않고 즉시 `READY` 상태로 전환합니다. (불필요한 충전 해제/재시작 방지).
|
||||||
|
4. **충전 해제 확인 (`_SM_RUN_CHGOFF`)**: 충전 중이 아니라면(위 단계 통과), 확실한 시작을 위해 충전 해제 상태를 보장합니다.
|
||||||
|
5. **위치 확인 (`_SM_RUN_POSCHK`)**: 현재 위치를 모를 경우 위치 찾기 수행.
|
||||||
|
|
||||||
|
**시퀀스 단계 (Step-by-Step)**
|
||||||
|
1. **목표 설정**: `NodeMAP_RFID_Charger` 값을 읽어 충전기 노드를 목표(`TargetNode`)로 설정.
|
||||||
|
2. **충전기 위치 이동**:
|
||||||
|
* `UpdateMotionPositionForCharger` 함수를 사용해 충전기 앞단까지 이동.
|
||||||
|
* 제한시간(`ChargeSearchTime`) 초과 시 실패 처리 (`CHARGEOFF` 이동).
|
||||||
|
3. **정밀 검색 (Fine Search)**:
|
||||||
|
* 항상 **전진(Forward)**으로 미세 조정 이동하며 충전기를 검색합니다. (경로 예측 시 전진 진입하도록 설정됨).
|
||||||
|
* 음성 안내: "충전기를 검색합니다".
|
||||||
|
4. **마크 정지 (Mark Stop)**:
|
||||||
|
* AGV가 기동 중이면 `MarkStop` 명령 전송.
|
||||||
|
* 정지할 때까지 대기.
|
||||||
|
* 60초 내 정지 확인 안 되면 실패 처리.
|
||||||
|
5. **위치 확정**:
|
||||||
|
* 정지 후 3초 대기(센서 안정화).
|
||||||
|
* 현재 위치를 `CHARGE`로 설정.
|
||||||
|
6. **충전 명령**:
|
||||||
|
* `AGVCharge(true)` 호출하여 하드웨어에 충전 시작 명령 전송.
|
||||||
|
* `WAIT_CHARGEACK` 플래그 설정.
|
||||||
|
7. **ACK 확인**:
|
||||||
|
* AGV로부터 "CBT" 응답 대기.
|
||||||
|
* 응답 없을 시 재전송(최대 5회), 실패 시 취소 처리.
|
||||||
|
|
||||||
|
### 3.2 충전 확인 (`_SM_RUN_GOCHARGECHECK.cs`)
|
||||||
|
충전 명령 후 실제 배터리 충전이 시작되었는지 검증합니다.
|
||||||
|
|
||||||
|
* **조건**: `Battery_charging` 신호 및 `FLAG_CHARGEONA` 확인.
|
||||||
|
* **에러 처리**:
|
||||||
|
* `Charger_pos_error` (위치 에러) 또는 `Charger_run_error` 발생 시 취소.
|
||||||
|
* 30초 타임아웃: 충전이 시작되지 않으면 `CHARGEOFF`로 전환하여 프로세스 취소.
|
||||||
|
|
||||||
|
### 3.3 충전 해제 (`_SM_RUN_CHGOFF.cs`)
|
||||||
|
충전 상태를 안전하게 종료합니다.
|
||||||
|
|
||||||
|
* **로직**:
|
||||||
|
* `Battery_charging`이 true면 `AGVCharge(false)` 전송.
|
||||||
|
* 충전이 해제될 때까지 대기 (최대 1분).
|
||||||
|
* 안전하게 해제되면 `true` 반환.
|
||||||
|
|
||||||
|
## 4. 이동 제어 상세 (Movement Control)
|
||||||
|
* **핵심 함수**: `UpdateMotionPositionForCharger` (in `_Util.cs`)
|
||||||
|
* **특징**:
|
||||||
|
* 일반 이동과 달리 충전기 진입을 위한 전용 로직 사용.
|
||||||
|
* `MARK_SENSOR`를 확인하여 충전 위치 도달 여부 판단.
|
||||||
|
* **안전 로직 추가 (`CheckStopCondition`)**:
|
||||||
|
* **비상 정지**: `Emergency` 상태이고 로봇이 멈췄을 경우 `IDLE`로 전환 및 중단.
|
||||||
|
* **수동 충전 중**: `FLAG_CHARGEONM` 상태일 경우 이동 불가 처리.
|
||||||
|
|
||||||
|
## 5. 주요 설정 및 변수
|
||||||
|
* `PUB.setting.NodeMAP_RFID_Charger`: 충전기 위치의 RFID 태그 ID.
|
||||||
|
* `PUB.setting.chargerpos`: 충전 진입 방식 (0:전진, 2:후진, 1:QC위치).
|
||||||
|
* `PUB.setting.ChargeSearchTime`: 충전기 검색 제한 시간.
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
# GOHOME 상태 머신 분석
|
|
||||||
|
|
||||||
## 개요
|
|
||||||
`GOHOME` 상태 머신(`_SM_RUN_GOHOME`)은 AGV를 현재 위치에서 미리 정의된 "Home" 노드로 이동시키는 역할을 합니다. 이 과정은 `_Util.cs`와 `VirtualAGV.cs`에 정의된 공유 경로 탐색 및 이동 로직을 활용합니다.
|
|
||||||
|
|
||||||
## 로직 흐름
|
|
||||||
|
|
||||||
### 1. 초기화 및 안전 점검
|
|
||||||
- **하드웨어 확인:** `PUB.AGV.IsOpen`을 확인합니다. 연결이 끊겨 있으면 에러 상태로 설정합니다.
|
|
||||||
- **충전 해제:** `_SM_RUN_CHGOFF`를 호출하여 AGV가 물리적으로 충전기에 연결되어 있지 않은지 확인합니다.
|
|
||||||
- *잠재적 문제:* 충전 센서가 고착(stuck)된 경우, 이 단계에서 무한 대기할 수 있습니다.
|
|
||||||
- **Lidar 안전:** `PUB.AGV.system1.stop_by_front_detect`를 확인합니다. 장애물이 감지되면 제거될 때까지 실행을 일시 중지(false 반환)합니다.
|
|
||||||
|
|
||||||
### 2. 1단계: 목적지 설정
|
|
||||||
- **홈 위치 조회:** `PUB.setting.NodeMAP_RFID_Home`에서 홈 노드 ID를 가져옵니다.
|
|
||||||
- **유효성 검사:** 맵에서 홈 노드를 찾을 수 없는 경우, 에러를 기록하고 `READY` 상태로 초기화합니다.
|
|
||||||
- **타겟 할당:** `PUB._virtualAGV.TargetNode`를 홈 노드로 설정합니다.
|
|
||||||
|
|
||||||
### 3. 2단계: 이동 실행
|
|
||||||
- **위임:** `_Util.cs`의 `UpdateMotionPositionForMark("_SM_RUN_GOHOME")`를 호출합니다.
|
|
||||||
- **경로 탐색:**
|
|
||||||
- 경로가 없거나 현재 경로가 유효하지 않은 경우, `_Util.cs`가 `CurrentNode`에서 `TargetNode`까지의 새로운 경로를 계산합니다.
|
|
||||||
- **예측 및 제어:**
|
|
||||||
- `VirtualAGV.Predict()`가 현재 노드와 경로를 기반으로 다음 행동을 결정합니다.
|
|
||||||
- **정지 조건:** `Predict()`가 `Stop`을 반환하는 경우:
|
|
||||||
- `IsPositionConfirmed`가 true인지 확인합니다.
|
|
||||||
- `CurrentNodeId`가 `TargetNode.NodeId`와 일치하는지 확인합니다.
|
|
||||||
- 둘 다 true이면 `true`(도착)를 반환합니다.
|
|
||||||
- **이동 조건:** `Predict()`가 이동 명령을 반환하는 경우:
|
|
||||||
- 논리적 명령(좌/우/직진, 전진/후진, 속도)을 하드웨어 명령(`AGVMoveSet`)으로 변환합니다.
|
|
||||||
- **최적화:** 상태가 변경된 경우에만 명령을 전송합니다 (최근 `_Util.cs` 업데이트에 구현됨).
|
|
||||||
- AGV가 구동 중인지 확인합니다 (`AGVMoveRun`).
|
|
||||||
|
|
||||||
### 4. 3단계: 완료
|
|
||||||
- **알림:** "홈 검색 완료" 음성을 출력합니다.
|
|
||||||
- **로깅:** 데이터베이스에 기록을 추가합니다.
|
|
||||||
- **전환:** 시퀀스를 업데이트하여 `GOHOME` 상태를 사실상 종료합니다.
|
|
||||||
|
|
||||||
## 잠재적 문제 및 위험 요소
|
|
||||||
|
|
||||||
### 1. 충전 센서에 의한 무한 대기
|
|
||||||
- **위험:** 충전 신호가 활성화되어 있는 한 `_SM_RUN_CHGOFF` 호출은 계속 `false`를 반환합니다.
|
|
||||||
- **시나리오:** 센서가 고장 나거나 시스템이 모르는 사이에 AGV가 수동으로 충전기에 밀려 올라간 경우, 여기서 멈출 수 있습니다.
|
|
||||||
- **완화:** `_SM_RUN_CHGOFF` 확인에 타임아웃이나 수동 오버라이드 기능을 추가해야 합니다 (현재 스니펫에서는 보이지 않음).
|
|
||||||
|
|
||||||
### 2. 위치 상실 복구
|
|
||||||
- **위험:** `_SM_RUN_POSCHK`(`UpdateMotionPositionForMark`에서 호출됨)가 태그를 찾지 못하면, AGV가 무한히 기어갈(crawl) 수 있거나 멈출 수 있습니다.
|
|
||||||
- **메커니즘:** `_SM_RUN_POSCHK`는 태그를 찾기 위해 저속 전진 명령을 내립니다.
|
|
||||||
- **시나리오:** AGV가 경로를 벗어났거나 데드존에 있는 경우, Lidar가 감지하지 못하면 충돌하거나 배회할 수 있습니다.
|
|
||||||
|
|
||||||
### 3. 경로 재계산 루프 (확인됨: 안전함)
|
|
||||||
- **분석:** `_Util.cs` 코드를 확인한 결과, `CalcPath`가 실패하여 경로가 생성되지 않으면(`PathResult.result == null`) 즉시 `PUB.sm.SetNewRunStep(ERunStep.READY)`를 호출합니다.
|
|
||||||
- **결론:** 경로 계산 실패 시 상태 머신이 `READY` 상태로 전환되므로, 무한 루프나 반복적인 재계산 시도는 발생하지 않습니다. 안전하게 정지합니다.
|
|
||||||
|
|
||||||
### 4. 통신 폭주 (해결됨)
|
|
||||||
- **이전 위험:** 동일한 이동 명령의 지속적인 전송.
|
|
||||||
- **해결:** 최근 `_Util.cs` 업데이트에서 `PUB.AGV.data`를 새 명령과 비교하여 변경 시에만 전송하도록 수정되었습니다.
|
|
||||||
|
|
||||||
## 권장 사항
|
|
||||||
1. **충전 해제 타임아웃:** 센서 고장 시 무한 대기를 방지하기 위해 `_SM_RUN_CHGOFF` 확인에 타임아웃을 추가하십시오.
|
|
||||||
2. **경로 실패 처리:** `CalcPath`가 null/empty를 반환하는지 명시적으로 확인하고, 무한 재시도 대신 에러와 함께 `GOHOME` 시퀀스를 중단하십시오.
|
|
||||||
3. **도착 확인:** `_Util.cs`의 "도착" 확인이 "거의 도착" 시나리오(예: 태그 약간 앞에서 정지)에 대해 견고한지 확인하십시오. 현재 로직은 `CurrentNodeId` 업데이트에 의존하며, 이는 양호해 보입니다.
|
|
||||||
|
|
||||||
66
Cs_HMI/docs/Home_Move_Analysis.md
Normal file
66
Cs_HMI/docs/Home_Move_Analysis.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Home Move Sequence 분석 (Home Move Sequence Analysis)
|
||||||
|
|
||||||
|
## 1. 개요 (Overview)
|
||||||
|
이 문서는 AGV의 "Home Move" 기능에 대한 분석 결과를 기술합니다. UI 트리거부터 내부 상태 머신(State Machine) 로직 및 이동 제어 흐름을 포함합니다.
|
||||||
|
|
||||||
|
## 2. UI 트리거 (UI Trigger)
|
||||||
|
* **버튼 컨트롤**: `btHome` (in `fMain.Designer.cs`)
|
||||||
|
* **이벤트 핸들러**: `brHome_Click` (in `fMain.cs`)
|
||||||
|
* **동작**:
|
||||||
|
* 사용자가 "홈" 버튼 클릭 시 실행됩니다.
|
||||||
|
* **이미 Home 이동 중인 경우**: "홈 이동을 취소 할까요?" 팝업 → 확인 시 `IDLE` 상태로 전환 및 정지 (`AGVMoveStop`).
|
||||||
|
* **대기 상태인 경우**: "홈 이동을 실행 할까요?" 팝업 → 확인 시 `GOHOME` RunStep으로 설정 및 `RUN` 상태 시작.
|
||||||
|
* **조건**: 자동 충전 중이거나 수동 충전 모드가 아니어야 함 (`PUB.CheckManualChargeMode`).
|
||||||
|
|
||||||
|
## 3. 상태 머신 로직 (State Machine Logic)
|
||||||
|
* **핵심 파일**: `Project\StateMachine\Step\_SM_RUN_GOHOME.cs`
|
||||||
|
* **함수**: `_SM_RUN_GOHOME(bool isFirst, TimeSpan stepTime)`
|
||||||
|
* **상태 흐름**:
|
||||||
|
|
||||||
|
### 초기 검사
|
||||||
|
1. **하드웨어 연결**: `PUB.AGV.IsOpen` 체크. 연결 끊김 시 에러 처리.
|
||||||
|
2. **충전 상태**: `_SM_RUN_CHGOFF`를 호출하여 충전기가 분리되었는지 확인.
|
||||||
|
3. **장애물 감지**: `PUB.AGV.system1.stop_by_front_detect` (Lidar 감지)
|
||||||
|
* 감지 시 "전방에 물체가 감지되었습니다" 음성 출력.
|
||||||
|
* **주의**: 이 단계에서 명시적인 소프트웨어 정지 명령(`AGVMoveStop`)은 코드상에 보이지 않으며, 함수가 `false`를 리턴하여 이동 로직 진입을 막습니다. (하드웨어 자체 정지 기능에 의존하는 것으로 추정됨)
|
||||||
|
|
||||||
|
### 시퀀스 단계 (Sequence Steps)
|
||||||
|
상태 머신은 `PUB.sm.RunStepSeq`를 통해 단계별로 진행됩니다.
|
||||||
|
|
||||||
|
**단계 1: 이동 준비**
|
||||||
|
* 음성 안내: "홈으로 이동합니다"
|
||||||
|
* **목표 설정**: `PUB.setting.NodeMAP_RFID_Home` 값을 읽어 목표 노드(`TargetNode`)로 설정.
|
||||||
|
* 에러 처리: 홈 위치 설정이 없는 경우 에러 로그 기록 후 `READY` 상태로 복귀.
|
||||||
|
|
||||||
|
**단계 2: 이동 (Moving)**
|
||||||
|
* **이동 함수**: `UpdateMotionPositionForMark("funcName")` 호출 (in `_Util.cs`)
|
||||||
|
* 이 함수가 `true`를 반환할 때까지 반복 호출됨.
|
||||||
|
* 이동 완료 시 `AGVMoveStop` 호출 후 다음 단계로 진행.
|
||||||
|
|
||||||
|
**단계 3: 완료 (Completion)**
|
||||||
|
* 음성 안내: "홈 검색 완료"
|
||||||
|
* 로그 기록: 홈 위치 도착 기록.
|
||||||
|
* 상태 종료: `true` 반환 → 메인 루프에서 `READY` 상태로 전환.
|
||||||
|
|
||||||
|
## 4. 이동 제어 상세 (Movement Control Details)
|
||||||
|
* **핵심 함수**: `UpdateMotionPositionForMark` (in `Project\StateMachine\Step\_Util.cs`)
|
||||||
|
* **경로 계산**: 현재 위치와 목표 노드 간의 경로(`CalcPath`)를 계산.
|
||||||
|
* **주행 예측 (Predict)**: `PUB._virtualAGV.Predict()`를 사용하여 다음 동작(직진, 회전, 정지 등)을 결정.
|
||||||
|
* **제어 명령**:
|
||||||
|
* `Predict` 결과에 따라 모터(`Forward/Backward`), 분기(Magnetic Guide), 속도를 설정.
|
||||||
|
* 변경된 명령이 있을 경우에만 `PUB.AGV.AGVMoveSet`으로 하드웨어에 전송.
|
||||||
|
* AGV가 정지 상태라면 `PUB.AGV.AGVMoveRun`으로 구동 시작.
|
||||||
|
* **정지 조건**:
|
||||||
|
* `Predict`가 `Stop` 명령을 반환하고,
|
||||||
|
* 위치가 확정(`IsPositionConfirmed`)되었으며,
|
||||||
|
* 현재 노드가 목표 노드와 일치하고,
|
||||||
|
* 실제 AGV 하드웨어가 정지(`agv_run == false`)한 경우.
|
||||||
|
|
||||||
|
## 5. 잠재적 이슈 (Potential Issues)
|
||||||
|
1. **장애물 감지 시 정지 처리**:
|
||||||
|
* `_SM_RUN_GOHOME`에서 장애물 감지 시(`stop_by_front_detect`), 음성 출력 후 `return false`만 수행합니다.
|
||||||
|
* 이전에 이동 명령이 전송된 상태라면, 소프트웨어에서 명시적으로 `AGVMoveStop`을 보내지 않으므로 AGV가 계속 이동하려 할 수 있습니다. (단, 하드웨어 센서가 직접 모터를 차단하도록 설계되었을 가능성이 높음)
|
||||||
|
* **권장**: 안전을 위해 장애물 감지 루틴 내에서도 명시적으로 `AGVMoveStop`을 호출하는 것을 검토할 필요가 있습니다.
|
||||||
|
|
||||||
|
2. **경로 재생성 시 멈춤**:
|
||||||
|
* `UpdateMotionPositionForMark`에서 경로를 재생성해야 할 경우, `AGVMoveStop("경로재생성")`을 호출하여 일시 정지 후 경로를 다시 계산합니다. 이는 안전한 동작입니다.
|
||||||
101
Cs_HMI/docs/Predict_Function_Analysis.md
Normal file
101
Cs_HMI/docs/Predict_Function_Analysis.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Predict() 함수 분석 보고서
|
||||||
|
|
||||||
|
## 1. 개요 (Overview)
|
||||||
|
`AGVNavigationCore.Models.VirtualAGV` 클래스의 `Predict()` 함수는 AGV의 현재 상태(위치, RFID 감지, 경로 등)를 기반으로 **다음 행동(이동, 정지, 속도 등)**을 결정하는 핵심 의사결정 함수입니다. 시스템은 이 함수를 주기적으로 호출하여 AGV가 경로를 이탈하지 않고 목적지까지 안전하게 이동하도록 제어합니다.
|
||||||
|
|
||||||
|
## 2. 논리 흐름 (Logic Flow)
|
||||||
|
|
||||||
|
`Predict()` 함수의 결정 로직은 다음 순서로 진행됩니다.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Start Predict] --> B{위치 확정 여부<br/>(RFID 2개 이상)}
|
||||||
|
B -- No --> C[UnknownPosition<br/>(전진/저속/탐색)]
|
||||||
|
B -- Yes --> D{현재 경로(Path) 존재?}
|
||||||
|
D -- No --> E[NoPath<br/>(정지)]
|
||||||
|
D -- Yes --> F{목적지 도착 임박?<br/>(이전 노드 모두 통과)}
|
||||||
|
F -- Yes --> G{현재 노드 == 최종 노드?}
|
||||||
|
G -- Yes --> H{최종 노드 완료(IsPass)?}
|
||||||
|
H -- Yes --> I[Complete<br/>(정지/완료)]
|
||||||
|
H -- No --> J[MarkStop<br/>(정지/마크센서대기)]
|
||||||
|
G -- No --> K[Logic Continue]
|
||||||
|
F -- No --> K
|
||||||
|
K --> L{경로 이탈 확인<br/>(현재노드가 경로에 존재?)}
|
||||||
|
L -- No --> M[PathOut<br/>(정지/재탐색요청)]
|
||||||
|
L -- Yes --> N[GetCommandFromPath<br/>(다음 노드 이동 명령)]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 단계별 상세 분석
|
||||||
|
|
||||||
|
1. **위치 미확정 (Position Check)**
|
||||||
|
* **조건**: `!_isPositionConfirmed` (감지된 RFID 개수 < 2)
|
||||||
|
* **행동**: `UnknownPosition` 리턴.
|
||||||
|
* **명령**: `Forward` (전진), `SpeedLevel.L` (저속).
|
||||||
|
* **목적**: RFID를 추가로 찾아 위치를 확정하기 위해 천천히 이동.
|
||||||
|
|
||||||
|
2. **경로 없음 (Path Check)**
|
||||||
|
* **조건**: `_currentPath`가 null이거나 비어있음.
|
||||||
|
* **행동**: `NoPath` 리턴.
|
||||||
|
* **명령**: `Stop`.
|
||||||
|
|
||||||
|
3. **목적지 도착 확인 (Goal Check)**
|
||||||
|
* **조건**: 경로상의 마지막 노드(`lastNode`) 이전의 모든 노드가 `IsPass == true` 상태인지 확인.
|
||||||
|
* **분기**:
|
||||||
|
* **최종 완료**: `_currentNode`가 마지막 노드이고, `IsPass == true`로 설정됨 -> `Complete` 리턴.
|
||||||
|
* **도착 직전(MarkStop)**: `_currentNode`가 마지막 노드이지만, 아직 `IsPass == false`임 -> `MarkStop` 리턴. 이는 물리적인 정지(Mark Sensor 감지)를 기다리는 상태임.
|
||||||
|
|
||||||
|
4. **경로 이탈 확인 (Deviation Check)**
|
||||||
|
* **로직**: 현재 경로(`DetailedPath`) 중에서 `_currentNode`와 ID가 일치하고 아직 지나가지 않은(`IsPass == false`) 노드를 찾음.
|
||||||
|
* **결과**: 찾지 못하면 `PathOut` 리턴 (경로 이탈로 간주하여 정지).
|
||||||
|
|
||||||
|
5. **이동 명령 생성 (Command Generation)**
|
||||||
|
* **함수**: `GetCommandFromPath(CurrentNodeId)` 호출.
|
||||||
|
* **로직**: 현재 노드에 정의된 이동 지침(모터 방향, 마그넷 분기, 속도 등)을 가져옴.
|
||||||
|
* **명령**: 해당 지침대로 `Normal` 명령 리턴.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 주요 메커니즘 및 의존성
|
||||||
|
|
||||||
|
### 3.1. 자동 패스 (Auto-Pass) 메커니즘
|
||||||
|
AGV가 중간 노드의 RFID를 놓치고 다음 노드로 건너뛰었을 때, 로직이 깨지지 않게 하는 중요한 안전장치입니다.
|
||||||
|
* **위치**: `SetPosition()` 함수 내 (Line 583~593).
|
||||||
|
* **동작**: AGV가 새로운 노드(B)에 도착했다고 보고되면, 경로상에서 B보다 앞선 모든 노드(A)의 `IsPass` 속성을 강제로 `true`로 변경합니다.
|
||||||
|
* **효과**: `Predict`의 "목적지 도착 확인" 로직(`All Previous Passed`)이 올바르게 작동하도록 보장합니다.
|
||||||
|
|
||||||
|
### 3.2. MarkStop 및 완료 처리
|
||||||
|
* AGV가 마지막 노드 RFID를 읽으면 `Predict`는 `MarkStop`을 리턴합니다 (아직 `IsPass`는 false).
|
||||||
|
* `_Util.cs` 등 제어부는 이를 보고 감속/정지 준비를 합니다.
|
||||||
|
* AGV가 물리적으로 마크 센서에 정지하면 `_AGV.cs`에서 `SetCurrentNodeMarkStop()`을 호출합니다.
|
||||||
|
* 이때 비로소 마지막 노드의 `IsPass`가 `true`가 됩니다.
|
||||||
|
* 다음 `Predict` 호출 시 `Complete`가 리턴되어 시퀀스가 종료됩니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 식별된 문제점 및 제안 사항 (Issues & Suggestions)
|
||||||
|
|
||||||
|
### 4.1. "Skipped Node" 시나리오의 잠재적 위험
|
||||||
|
* **현상**: `SetPosition`은 RFID 수신 시 호출되어 이전 노드들을 Auto-Pass 처리합니다. 하지만 만약 AGV가 경로를 이탈하여 엉뚱한 노드로 갔는데, 우연히 그 노드가 경로의 훨씬 뒤쪽 노드라면?
|
||||||
|
* **위험**: 중간의 모든 공정을 건너뛰고 바로 목적지 근처로 인식할 수 있습니다.
|
||||||
|
* **제안**: `SetPosition`에서 건너뛰는 노드의 개수가 너무 많거나(예: 3개 이상), 거리가 물리적으로 불가능할 정도로 멀다면 에러(`PathJump`)를 발생시키는 안전장치 추가 고려.
|
||||||
|
|
||||||
|
### 4.2. 하드코딩된 상수 (Magic Numbers)
|
||||||
|
* **코드**: `DetectedRfidCount >= 2`, `BatteryLevel < 20.0f` 등.
|
||||||
|
* **제안**: 이러한 값들을 `const` 또는 설정 파일(`PUB.setting`)로 관리하여 유지보수성을 높여야 합니다.
|
||||||
|
|
||||||
|
### 4.3. `GetCommandFromPath`의 Null 처리 불일치
|
||||||
|
* **현상**: `Predict`에서는 `PathOut`을 먼저 체크하지만, 기저에 있는 `GetCommandFromPath`에도 중복된 Null 체크/`NoTarget` 리턴 로직이 있음.
|
||||||
|
* **제안**: 로직의 일관성을 위해 검증 로직을 통일하거나, `Predict`에서 확실히 걸러낸 후 `GetCommandFromPath`는 데이터를 가져오는 역할만 하도록 단순화.
|
||||||
|
|
||||||
|
### 4.4. `Predict`의 "현재 노드 기준" 명령 생성
|
||||||
|
* **현상**: `GetCommandFromPath`는 `CurrentNodeId`를 인자로 받습니다. 즉, "현재 노드에서 수행해야 할 행동"을 반환합니다.
|
||||||
|
* **의문**: 만약 AGV가 노드와 노드 사이(Edge)에 있을 때, `CurrentNodeId`는 "지난 노드"입니다. 이때 "지난 노드"의 명령을 계속 수행하는 것이 맞는지(일반적으로는 맞음, Go Forward 등) 확인 필요.
|
||||||
|
* **확인**: 현재 로직은 맞습니다. 다음 RFID를 만날 때까지 이전 명령을 유지합니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 결론 (Conclusion)
|
||||||
|
|
||||||
|
`Predict()` 함수는 `SetPosition`(위치 업데이트 및 Auto-Pass) 및 `MarkStop` 처리 로직과 유기적으로 결합되어 있어 단독으로만 보면 이해하기 어려울 수 있습니다. 현재 로직은 **RFID 누락(Skip)에 대한 복구 능력**을 갖추고 있으며, **물리적 정지(MarkStop)와 논리적 완료(Complete)를 구분**하여 정밀한 제어를 가능하게 설계되어 있습니다.
|
||||||
|
|
||||||
|
다만, 매직 넘버 사용과 일부 중복된 검증 로직은 리팩토링을 통해 개선할 여지가 있습니다.
|
||||||
Reference in New Issue
Block a user