diff --git a/Cs_HMI/AGVCSharp.sln b/Cs_HMI/AGVCSharp.sln
index 0cc2418..2e7614e 100644
--- a/Cs_HMI/AGVCSharp.sln
+++ b/Cs_HMI/AGVCSharp.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.14.36310.24
+# Visual Studio Express 15 for Windows Desktop
+VisualStudioVersion = 15.0.36324.19
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sub", "Sub", "{C423C39A-44E7-4F09-B2F7-7943975FF948}"
EndProject
@@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "솔루션 항목", "솔루
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVNavigationCore", "AGVNavigationCore\AGVNavigationCore.csproj", "{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logic", "Logic", "{E5C75D32-5AD6-44DD-8F27-E32023206EBB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -220,6 +222,9 @@ Global
{14E8C9A5-013E-49BA-B435-EFEFC77DD623} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
{EB77976F-4DE4-46A5-8B25-D07226204C32} = {7AF32085-E7A6-4D06-BA6E-C6B1EBAEA99A}
{9365803B-933D-4237-93C7-B502C855A71C} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
+ {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
+ {B2C3D4E5-0000-0000-0000-000000000000} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
+ {C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5B1FD72-356F-4840-83E8-B070AC21C8D9}
diff --git a/Cs_HMI/AGVMapEditor/AGVMapEditor.csproj b/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj
similarity index 100%
rename from Cs_HMI/AGVMapEditor/AGVMapEditor.csproj
rename to Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj
diff --git a/Cs_HMI/AGVMapEditor/Forms/MainForm.Designer.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.Designer.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Forms/MainForm.Designer.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.Designer.cs
diff --git a/Cs_HMI/AGVMapEditor/Forms/MainForm.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Forms/MainForm.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs
diff --git a/Cs_HMI/AGVMapEditor/Forms/MainForm.resx b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.resx
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Forms/MainForm.resx
rename to Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.resx
diff --git a/Cs_HMI/AGVMapEditor/Models/EditorSettings.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Models/EditorSettings.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Models/EditorSettings.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Models/EditorSettings.cs
diff --git a/Cs_HMI/AGVMapEditor/Models/MapImage.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Models/MapImage.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Models/MapImage.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Models/MapImage.cs
diff --git a/Cs_HMI/AGVMapEditor/Models/MapLabel.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Models/MapLabel.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Models/MapLabel.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Models/MapLabel.cs
diff --git a/Cs_HMI/AGVMapEditor/Models/NodePropertyWrapper.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Models/NodePropertyWrapper.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Models/NodePropertyWrapper.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Models/NodePropertyWrapper.cs
diff --git a/Cs_HMI/AGVMapEditor/Program.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Program.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Program.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Program.cs
diff --git a/Cs_HMI/AGVMapEditor/Properties/AssemblyInfo.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Properties/AssemblyInfo.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Properties/AssemblyInfo.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Properties/AssemblyInfo.cs
diff --git a/Cs_HMI/AGVMapEditor/Properties/Resources.Designer.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Properties/Resources.Designer.cs
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Properties/Resources.Designer.cs
rename to Cs_HMI/AGVLogic/AGVMapEditor/Properties/Resources.Designer.cs
diff --git a/Cs_HMI/AGVMapEditor/Properties/Resources.resx b/Cs_HMI/AGVLogic/AGVMapEditor/Properties/Resources.resx
similarity index 100%
rename from Cs_HMI/AGVMapEditor/Properties/Resources.resx
rename to Cs_HMI/AGVLogic/AGVMapEditor/Properties/Resources.resx
diff --git a/Cs_HMI/AGVMapEditor/build.bat b/Cs_HMI/AGVLogic/AGVMapEditor/build.bat
similarity index 100%
rename from Cs_HMI/AGVMapEditor/build.bat
rename to Cs_HMI/AGVLogic/AGVMapEditor/build.bat
diff --git a/Cs_HMI/AGVMapEditor/packages.config b/Cs_HMI/AGVLogic/AGVMapEditor/packages.config
similarity index 100%
rename from Cs_HMI/AGVMapEditor/packages.config
rename to Cs_HMI/AGVLogic/AGVMapEditor/packages.config
diff --git a/Cs_HMI/AGVNavigationCore/AGVNavigationCore.csproj b/Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj
similarity index 98%
rename from Cs_HMI/AGVNavigationCore/AGVNavigationCore.csproj
rename to Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj
index bfea9ae..7ab4b7d 100644
--- a/Cs_HMI/AGVNavigationCore/AGVNavigationCore.csproj
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj
@@ -73,6 +73,8 @@
UserControl
+
+
diff --git a/Cs_HMI/AGVNavigationCore/Controls/AGVState.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/AGVState.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Controls/AGVState.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Controls/AGVState.cs
diff --git a/Cs_HMI/AGVNavigationCore/Controls/IAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/IAGV.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Controls/IAGV.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Controls/IAGV.cs
diff --git a/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Designer.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Designer.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Designer.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Designer.cs
diff --git a/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
diff --git a/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs
diff --git a/Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs
diff --git a/Cs_HMI/AGVNavigationCore/Models/Enums.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Models/Enums.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/IMovableAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/IMovableAGV.cs
new file mode 100644
index 0000000..e699d97
--- /dev/null
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/IMovableAGV.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using AGVNavigationCore.Controls;
+using AGVNavigationCore.PathFinding;
+using AGVNavigationCore.PathFinding.Core;
+
+namespace AGVNavigationCore.Models
+{
+ ///
+ /// 이동 가능한 AGV 인터페이스
+ /// 실제 AGV와 시뮬레이션 AGV 모두 구현해야 하는 기본 인터페이스
+ ///
+ public interface IMovableAGV
+ {
+ #region Events
+
+ ///
+ /// AGV 상태 변경 이벤트
+ ///
+ event EventHandler StateChanged;
+
+ ///
+ /// 위치 변경 이벤트
+ ///
+ event EventHandler<(Point, AgvDirection, MapNode)> PositionChanged;
+
+ ///
+ /// RFID 감지 이벤트
+ ///
+ event EventHandler RfidDetected;
+
+ ///
+ /// 경로 완료 이벤트
+ ///
+ event EventHandler PathCompleted;
+
+ ///
+ /// 오류 발생 이벤트
+ ///
+ event EventHandler ErrorOccurred;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// AGV ID
+ ///
+ string AgvId { get; }
+
+ ///
+ /// 현재 위치
+ ///
+ Point CurrentPosition { get; set; }
+
+ ///
+ /// 현재 방향 (모터 방향)
+ ///
+ AgvDirection CurrentDirection { get; set; }
+
+ ///
+ /// 현재 상태
+ ///
+ AGVState CurrentState { get; set; }
+
+ ///
+ /// 현재 속도
+ ///
+ float CurrentSpeed { get; }
+
+ ///
+ /// 배터리 레벨 (0-100%)
+ ///
+ float BatteryLevel { get; set; }
+
+ ///
+ /// 현재 경로
+ ///
+ AGVPathResult CurrentPath { get; }
+
+ ///
+ /// 현재 노드 ID
+ ///
+ string CurrentNodeId { get; }
+
+ ///
+ /// 목표 위치
+ ///
+ Point? TargetPosition { get; }
+
+ ///
+ /// 목표 노드 ID
+ ///
+ string TargetNodeId { get; }
+
+ ///
+ /// 도킹 방향
+ ///
+ DockingDirection DockingDirection { get; }
+
+ #endregion
+
+ #region Sensor Input Methods (실제 AGV에서 호출)
+
+ ///
+ /// 현재 위치 설정 (실제 위치 센서에서)
+ ///
+ void SetCurrentPosition(Point position);
+
+ ///
+ /// 감지된 RFID 설정 (실제 RFID 센서에서)
+ ///
+ void SetDetectedRfid(string rfidId);
+
+ ///
+ /// 모터 방향 설정 (모터 컨트롤러에서)
+ ///
+ void SetMotorDirection(AgvDirection direction);
+
+ ///
+ /// 배터리 레벨 설정 (BMS에서)
+ ///
+ void SetBatteryLevel(float percentage);
+
+ #endregion
+
+ #region State Query Methods
+
+ ///
+ /// 현재 위치 조회
+ ///
+ Point GetCurrentPosition();
+
+ ///
+ /// 현재 상태 조회
+ ///
+ AGVState GetCurrentState();
+
+ ///
+ /// 현재 노드 ID 조회
+ ///
+ string GetCurrentNodeId();
+
+ ///
+ /// AGV 상태 정보 문자열 조회
+ ///
+ string GetStatus();
+
+ #endregion
+
+ #region Path Execution Methods
+
+ ///
+ /// 경로 실행
+ ///
+ void ExecutePath(AGVPathResult path, List mapNodes);
+
+ ///
+ /// 경로 정지
+ ///
+ void StopPath();
+
+ ///
+ /// 긴급 정지
+ ///
+ void EmergencyStop();
+
+ #endregion
+
+ #region Update Method
+
+ ///
+ /// 프레임 업데이트 (외부에서 주기적으로 호출)
+ /// 이 방식으로 타이머에 의존하지 않고 외부에서 제어 가능
+ ///
+ /// 마지막 업데이트 이후 경과 시간 (밀리초)
+ void Update(float deltaTimeMs);
+
+ #endregion
+
+ #region Manual Control Methods (테스트용)
+
+ ///
+ /// 수동 이동
+ ///
+ void MoveTo(Point targetPosition);
+
+ ///
+ /// 수동 회전
+ ///
+ void Rotate(AgvDirection direction);
+
+ ///
+ /// 충전 시작
+ ///
+ void StartCharging();
+
+ ///
+ /// 충전 종료
+ ///
+ void StopCharging();
+
+ #endregion
+
+ #region Cleanup
+
+ ///
+ /// 리소스 정리
+ ///
+ void Dispose();
+
+ #endregion
+ }
+}
diff --git a/Cs_HMI/AGVNavigationCore/Models/MapLoader.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Models/MapLoader.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs
diff --git a/Cs_HMI/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Models/MapNode.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
diff --git a/Cs_HMI/AGVSimulator/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs
similarity index 79%
rename from Cs_HMI/AGVSimulator/Models/VirtualAGV.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs
index a6dc50e..0e1d3fe 100644
--- a/Cs_HMI/AGVSimulator/Models/VirtualAGV.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs
@@ -2,20 +2,19 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
-using AGVMapEditor.Models;
+using AGVNavigationCore.Controls;
using AGVNavigationCore.Models;
using AGVNavigationCore.PathFinding;
using AGVNavigationCore.PathFinding.Core;
-using AGVNavigationCore.Controls;
-namespace AGVSimulator.Models
+namespace AGVNavigationCore.Models
{
-
///
- /// 가상 AGV 클래스
- /// 실제 AGV의 동작을 시뮬레이션
+ /// 가상 AGV 클래스 (코어 비즈니스 로직)
+ /// 실제 AGV와 시뮬레이터 모두에서 사용 가능한 공용 로직
+ /// 시뮬레이션과 실제 동작이 동일하게 동작하도록 설계됨
///
- public class VirtualAGV : IAGV
+ public class VirtualAGV : IMovableAGV, IAGV
{
#region Events
@@ -66,8 +65,7 @@ namespace AGVSimulator.Models
private MapNode _targetNode;
// 이동 관련
- private System.Windows.Forms.Timer _moveTimer;
- private DateTime _lastMoveTime;
+ private DateTime _lastUpdateTime;
private Point _moveStartPosition;
private Point _moveTargetPosition;
private float _moveProgress;
@@ -78,7 +76,7 @@ namespace AGVSimulator.Models
// 시뮬레이션 설정
private readonly float _moveSpeed = 50.0f; // 픽셀/초
private readonly float _rotationSpeed = 90.0f; // 도/초
- private readonly int _updateInterval = 50; // ms
+ private bool _isMoving;
#endregion
@@ -130,7 +128,7 @@ namespace AGVSimulator.Models
///
/// 현재 노드 ID
///
- public string CurrentNodeId => _currentNode.NodeId;
+ public string CurrentNodeId => _currentNode?.NodeId;
///
/// 목표 위치
@@ -145,7 +143,7 @@ namespace AGVSimulator.Models
///
/// 목표 노드 ID
///
- public string TargetNodeId => _targetNode.NodeId;
+ public string TargetNodeId => _targetNode?.NodeId;
///
/// 도킹 방향
@@ -170,34 +168,93 @@ namespace AGVSimulator.Models
_currentState = AGVState.Idle;
_currentSpeed = 0;
_dockingDirection = DockingDirection.Forward; // 기본값: 전진 도킹
- _currentNode = null; // = string.Empty;
- _targetNode = null;// string.Empty;
-
- InitializeTimer();
+ _currentNode = null;
+ _targetNode = null;
+ _isMoving = false;
+ _lastUpdateTime = DateTime.Now;
}
#endregion
- #region Initialization
+ #region Public Methods - 센서/RFID 입력 (실제 AGV에서 호출)
- private void InitializeTimer()
+ ///
+ /// 현재 위치 설정 (실제 AGV 센서에서)
+ ///
+ public void SetCurrentPosition(Point position)
{
- _moveTimer = new System.Windows.Forms.Timer();
- _moveTimer.Interval = _updateInterval;
- _moveTimer.Tick += OnMoveTimer_Tick;
- _lastMoveTime = DateTime.Now;
+ _currentPosition = position;
+ }
+
+ ///
+ /// 감지된 RFID 설정 (실제 RFID 센서에서)
+ ///
+ public void SetDetectedRfid(string rfidId)
+ {
+ RfidDetected?.Invoke(this, rfidId);
+ }
+
+ ///
+ /// 모터 방향 설정 (실제 모터 컨트롤러에서)
+ ///
+ public void SetMotorDirection(AgvDirection direction)
+ {
+ _currentDirection = direction;
+ }
+
+ ///
+ /// 배터리 레벨 설정 (실제 BMS에서)
+ ///
+ public void SetBatteryLevel(float percentage)
+ {
+ BatteryLevel = Math.Max(0, Math.Min(100, percentage));
+
+ // 배터리 부족 경고
+ if (BatteryLevel < 20.0f && _currentState != AGVState.Charging)
+ {
+ OnError($"배터리 부족: {BatteryLevel:F1}%");
+ }
}
#endregion
- #region Public Methods
+ #region Public Methods - 상태 조회
+
+ ///
+ /// 현재 위치 조회
+ ///
+ public Point GetCurrentPosition() => _currentPosition;
+
+ ///
+ /// 현재 상태 조회
+ ///
+ public AGVState GetCurrentState() => _currentState;
+
+ ///
+ /// 현재 노드 ID 조회
+ ///
+ public string GetCurrentNodeId() => _currentNode?.NodeId;
+
+ ///
+ /// AGV 정보 조회
+ ///
+ public string GetStatus()
+ {
+ return $"AGV[{_agvId}] 위치:({_currentPosition.X},{_currentPosition.Y}) " +
+ $"방향:{_currentDirection} 상태:{_currentState} " +
+ $"속도:{_currentSpeed:F1} 배터리:{BatteryLevel:F1}%";
+ }
+
+ #endregion
+
+ #region Public Methods - 경로 실행
///
/// 경로 실행 시작
///
/// 실행할 경로
/// 맵 노드 목록
- public void StartPath(AGVPathResult path, List mapNodes)
+ public void ExecutePath(AGVPathResult path, List mapNodes)
{
if (path == null || !path.Success)
{
@@ -220,13 +277,14 @@ namespace AGVSimulator.Models
// 목표 노드 설정 (경로의 마지막 노드)
if (_remainingNodes.Count > 1)
{
- var _targetNodeId = _remainingNodes[_remainingNodes.Count - 1];
- var targetNode = mapNodes.FirstOrDefault(n => n.NodeId == _targetNodeId);
+ var targetNodeId = _remainingNodes[_remainingNodes.Count - 1];
+ var targetNode = mapNodes.FirstOrDefault(n => n.NodeId == targetNodeId);
// 목표 노드의 타입에 따라 도킹 방향 결정
if (targetNode != null)
{
_dockingDirection = GetDockingDirection(targetNode.Type);
+ _targetNode = targetNode;
}
}
@@ -239,12 +297,20 @@ namespace AGVSimulator.Models
}
}
+ ///
+ /// 간단한 경로 실행 (경로 객체 없이 노드만)
+ ///
+ public void StartPath(AGVPathResult path, List mapNodes)
+ {
+ ExecutePath(path, mapNodes);
+ }
+
///
/// 경로 정지
///
public void StopPath()
{
- _moveTimer.Stop();
+ _isMoving = false;
_currentPath = null;
_remainingNodes?.Clear();
SetState(AGVState.Idle);
@@ -260,6 +326,30 @@ namespace AGVSimulator.Models
OnError("긴급 정지가 실행되었습니다.");
}
+ #endregion
+
+ #region Public Methods - 프레임 업데이트 (외부에서 정기적으로 호출)
+
+ ///
+ /// 프레임 업데이트 (외부에서 주기적으로 호출)
+ /// 이 방식으로 타이머에 의존하지 않고 외부에서 제어 가능
+ ///
+ /// 마지막 업데이트 이후 경과 시간 (밀리초)
+ public void Update(float deltaTimeMs)
+ {
+ var deltaTime = deltaTimeMs / 1000.0f; // 초 단위로 변환
+
+ UpdateMovement(deltaTime);
+ UpdateBattery(deltaTime);
+
+ // 위치 변경 이벤트 발생
+ PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
+ }
+
+ #endregion
+
+ #region Public Methods - 수동 제어 (테스트용)
+
///
/// 수동 이동 (테스트용)
///
@@ -272,7 +362,7 @@ namespace AGVSimulator.Models
_moveProgress = 0;
SetState(AGVState.Moving);
- _moveTimer.Start();
+ _isMoving = true;
}
///
@@ -285,51 +375,18 @@ namespace AGVSimulator.Models
return;
SetState(AGVState.Rotating);
-
- // 시뮬레이션: 즉시 방향 변경 (실제로는 시간이 걸림)
_currentDirection = direction;
-
- System.Threading.Thread.Sleep(500); // 회전 시간 시뮬레이션
SetState(AGVState.Idle);
}
-
-
-
///
- /// AGV 위치 직접 설정 (시뮬레이터용)
- /// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함
- ///
- /// 새로운 위치
- /// 모터이동방향
- public void SetPosition(MapNode node, Point newPosition, AgvDirection motorDirection)
- {
- // 현재 위치를 이전 위치로 저장 (리프트 방향 계산용)
- if (_currentPosition != Point.Empty)
- {
- _targetPosition = _currentPosition; // 이전 위치 (previousPos 역할)
- _targetDirection = _currentDirection;
- _targetNode = node;
- }
-
- // 새로운 위치 설정
- _currentPosition = newPosition;
- _currentDirection = motorDirection;
- _currentNode = node;
-
- // 위치 변경 이벤트 발생
- PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
- }
-
- ///
- /// 충전 시작 (시뮬레이션)
+ /// 충전 시작
///
public void StartCharging()
{
if (_currentState == AGVState.Idle)
{
SetState(AGVState.Charging);
- // 충전 시뮬레이션 시작
}
}
@@ -344,14 +401,34 @@ namespace AGVSimulator.Models
}
}
+ #endregion
+
+ #region Public Methods - AGV 위치 설정 (시뮬레이터용)
+
///
- /// AGV 정보 조회
+ /// AGV 위치 직접 설정
+ /// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함
///
- public string GetStatus()
+ /// 현재 노드
+ /// 새로운 위치
+ /// 모터이동방향
+ public void SetPosition(MapNode node, Point newPosition, AgvDirection motorDirection)
{
- return $"AGV[{_agvId}] 위치:({_currentPosition.X},{_currentPosition.Y}) " +
- $"방향:{_currentDirection} 상태:{_currentState} " +
- $"속도:{_currentSpeed:F1} 배터리:{BatteryLevel:F1}%";
+ // 현재 위치를 이전 위치로 저장 (리프트 방향 계산용)
+ if (_currentPosition != Point.Empty)
+ {
+ _targetPosition = _currentPosition; // 이전 위치
+ _targetDirection = _currentDirection;
+ _targetNode = node;
+ }
+
+ // 새로운 위치 설정
+ _currentPosition = newPosition;
+ _currentDirection = motorDirection;
+ _currentNode = node;
+
+ // 위치 변경 이벤트 발생
+ PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
}
///
@@ -359,12 +436,10 @@ namespace AGVSimulator.Models
///
public string SimulateRfidReading(List mapNodes)
{
- // 현재 위치에서 가장 가까운 노드 찾기
var closestNode = FindClosestNode(_currentPosition, mapNodes);
if (closestNode == null)
return null;
- // 해당 노드의 RFID 정보 반환 (MapNode에 RFID 정보 포함)
return closestNode.HasRfid() ? closestNode.RfidId : null;
}
@@ -375,26 +450,13 @@ namespace AGVSimulator.Models
private void StartMovement()
{
SetState(AGVState.Moving);
- _moveTimer.Start();
- _lastMoveTime = DateTime.Now;
- }
-
- private void OnMoveTimer_Tick(object sender, EventArgs e)
- {
- var now = DateTime.Now;
- var deltaTime = (float)(now - _lastMoveTime).TotalSeconds;
- _lastMoveTime = now;
-
- UpdateMovement(deltaTime);
- UpdateBattery(deltaTime);
-
- // 위치 변경 이벤트 발생
- PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
+ _isMoving = true;
+ _lastUpdateTime = DateTime.Now;
}
private void UpdateMovement(float deltaTime)
{
- if (_currentState != AGVState.Moving)
+ if (_currentState != AGVState.Moving || !_isMoving)
return;
// 목표 위치까지의 거리 계산
@@ -450,12 +512,6 @@ namespace AGVSimulator.Models
}
BatteryLevel = Math.Max(0, BatteryLevel);
-
- // 배터리 부족 경고
- if (BatteryLevel < 20.0f && _currentState != AGVState.Charging)
- {
- OnError($"배터리 부족: {BatteryLevel:F1}%");
- }
}
private void ProcessNextNode()
@@ -463,7 +519,7 @@ namespace AGVSimulator.Models
if (_remainingNodes == null || _currentNodeIndex >= _remainingNodes.Count - 1)
{
// 경로 완료
- _moveTimer.Stop();
+ _isMoving = false;
SetState(AGVState.Idle);
PathCompleted?.Invoke(this, _currentPath);
return;
@@ -475,10 +531,8 @@ namespace AGVSimulator.Models
// RFID 감지 시뮬레이션
RfidDetected?.Invoke(this, $"RFID_{nextNodeId}");
- //_currentNodeId = nextNodeId;
// 다음 목표 위치 설정 (실제로는 맵에서 좌표 가져와야 함)
- // 여기서는 간단히 현재 위치에서 랜덤 오프셋으로 설정
var random = new Random();
_moveTargetPosition = new Point(
_currentPosition.X + random.Next(-100, 100),
@@ -504,7 +558,6 @@ namespace AGVSimulator.Models
}
}
- // 일정 거리 내에 있는 노드만 반환
return closestDistance < 50.0f ? closestNode : null;
}
@@ -529,11 +582,11 @@ namespace AGVSimulator.Models
switch (nodeType)
{
case NodeType.Charging:
- return DockingDirection.Forward; // 충전기: 전진 도킹
+ return DockingDirection.Forward;
case NodeType.Docking:
- return DockingDirection.Backward; // 장비 (로더, 클리너, 오프로더, 버퍼): 후진 도킹
+ return DockingDirection.Backward;
default:
- return DockingDirection.Forward; // 기본값: 전진
+ return DockingDirection.Forward;
}
}
@@ -552,10 +605,9 @@ namespace AGVSimulator.Models
///
public void Dispose()
{
- _moveTimer?.Stop();
- _moveTimer?.Dispose();
+ StopPath();
}
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Core/PathNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/PathNode.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Core/PathNode.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/PathNode.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
similarity index 97%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
index f435d81..e1a6b44 100644
--- a/Cs_HMI/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
@@ -33,7 +33,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// AGV 경로 계산
///
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode,
- MapNode prevNode)
+ MapNode prevNode, AgvDirection currentDirection = AgvDirection.Forward)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
@@ -52,7 +52,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 통합된 경로 계획 함수 사용
- AGVPathResult result = PlanPath(startNode, targetNode, prevNode, requiredDirection);
+ AGVPathResult result = PlanPath(startNode, targetNode, prevNode, requiredDirection, currentDirection);
result.CalculationTimeMs = stopwatch.ElapsedMilliseconds;
@@ -90,10 +90,10 @@ namespace AGVNavigationCore.PathFinding.Planning
///
/// 통합 경로 계획 (직접 경로 또는 방향 전환 경로)
///
- private AGVPathResult PlanPath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection? requiredDirection = null)
+ private AGVPathResult PlanPath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection? requiredDirection = null, AgvDirection currentDirection = AgvDirection.Forward)
{
- bool needDirectionChange = false;// requiredDirection.HasValue && (currentDirection != requiredDirection.Value);
+ bool needDirectionChange = requiredDirection.HasValue && (currentDirection != requiredDirection.Value);
//현재 위치에서 목적지까지의 최단 거리 모록을 찾는다.
var DirectPathResult = _basicPathfinder.FindPath(startNode.NodeId, targetNode.NodeId);
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Validation/DockingValidationResult.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Validation/DockingValidationResult.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Validation/DockingValidationResult.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Validation/DockingValidationResult.cs
diff --git a/Cs_HMI/AGVNavigationCore/PathFinding/Validation/PathValidationResult.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Validation/PathValidationResult.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/PathFinding/Validation/PathValidationResult.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Validation/PathValidationResult.cs
diff --git a/Cs_HMI/AGVNavigationCore/Properties/AssemblyInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Properties/AssemblyInfo.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Properties/AssemblyInfo.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Properties/AssemblyInfo.cs
diff --git a/Cs_HMI/AGVNavigationCore/README.md b/Cs_HMI/AGVLogic/AGVNavigationCore/README.md
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/README.md
rename to Cs_HMI/AGVLogic/AGVNavigationCore/README.md
diff --git a/Cs_HMI/AGVNavigationCore/Utils/DockingValidator.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/Utils/DockingValidator.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs
diff --git a/Cs_HMI/AGVNavigationCore/Utils/LiftCalculator.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/LiftCalculator.cs
similarity index 99%
rename from Cs_HMI/AGVNavigationCore/Utils/LiftCalculator.cs
rename to Cs_HMI/AGVLogic/AGVNavigationCore/Utils/LiftCalculator.cs
index 0029c53..bddde3c 100644
--- a/Cs_HMI/AGVNavigationCore/Utils/LiftCalculator.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/LiftCalculator.cs
@@ -12,6 +12,100 @@ namespace AGVNavigationCore.Utils
///
public static class LiftCalculator
{
+
+ ///
+ /// 경로 예측 기반 리프트 방향 계산
+ /// 현재 노드에서 연결된 다음 노드들을 분석하여 리프트 방향 결정
+ ///
+ /// 현재 위치
+ /// 이전 위치
+ /// 모터 방향
+ /// 맵 노드 리스트 (경로 예측용)
+ /// 위치 허용 오차
+ /// 리프트 계산 결과
+ public static LiftCalculationResult CalculateLiftInfoWithPathPrediction(
+ Point currentPos, Point previousPos, AgvDirection motorDirection,
+ List mapNodes, int tolerance = 10)
+ {
+ if (mapNodes == null || mapNodes.Count == 0)
+ {
+ // 맵 노드 정보가 없으면 기존 방식 사용
+ return CalculateLiftInfo(previousPos, currentPos, motorDirection);
+ }
+
+ // 현재 위치에 해당하는 노드 찾기
+ var currentNode = FindNodeByPosition(mapNodes, currentPos, tolerance);
+
+ if (currentNode == null)
+ {
+ // 현재 노드를 찾을 수 없으면 기존 방식 사용
+ return CalculateLiftInfo(previousPos, currentPos, motorDirection);
+ }
+
+ // 이전 위치에 해당하는 노드 찾기
+ var previousNode = FindNodeByPosition(mapNodes, previousPos, tolerance);
+
+ Point targetPosition;
+ string calculationMethod;
+
+ // 모터 방향에 따른 예측 방향 결정
+ if (motorDirection == AgvDirection.Backward)
+ {
+ // 후진 모터: AGV가 리프트 쪽(목표 위치)으로 이동
+ // 경로 예측 없이 단순히 현재→목표 방향 사용
+ return CalculateLiftInfo(currentPos, previousPos, motorDirection);
+ }
+ else
+ {
+ // 전진 모터: 기존 로직 (다음 노드 예측)
+ var nextNodes = GetConnectedNodes(mapNodes, currentNode);
+
+ // 이전 노드 제외 (되돌아가는 방향 제외)
+ if (previousNode != null)
+ {
+ nextNodes = nextNodes.Where(n => n.NodeId != previousNode.NodeId).ToList();
+ }
+
+ if (nextNodes.Count == 1)
+ {
+ // 직선 경로: 다음 노드 방향으로 예측
+ targetPosition = nextNodes.First().Position;
+ calculationMethod = $"전진 경로 예측 ({currentNode.NodeId}→{nextNodes.First().NodeId})";
+ }
+ else if (nextNodes.Count > 1)
+ {
+ // 갈래길: 이전 위치 기반 계산 사용
+ var prevResult = CalculateLiftInfo(previousPos, currentPos, motorDirection);
+ prevResult.CalculationMethod += " (전진 갈래길)";
+ return prevResult;
+ }
+ else
+ {
+ // 연결된 노드가 없으면 기존 방식 사용
+ return CalculateLiftInfo(previousPos, currentPos, motorDirection);
+ }
+ }
+
+ // 리프트 각도 계산
+ var angleRadians = CalculateLiftAngleRadians(currentPos, targetPosition, motorDirection);
+ var angleDegrees = angleRadians * 180.0 / Math.PI;
+
+ // 0-360도 범위로 정규화
+ while (angleDegrees < 0) angleDegrees += 360;
+ while (angleDegrees >= 360) angleDegrees -= 360;
+
+ var directionString = AngleToDirectionString(angleDegrees);
+
+ return new LiftCalculationResult
+ {
+ AngleRadians = angleRadians,
+ AngleDegrees = angleDegrees,
+ DirectionString = directionString,
+ CalculationMethod = calculationMethod,
+ MotorDirection = motorDirection
+ };
+ }
+
///
/// AGV 이동 방향과 모터 방향을 기반으로 리프트 각도 계산
///
@@ -146,98 +240,7 @@ namespace AGVNavigationCore.Utils
};
}
- ///
- /// 경로 예측 기반 리프트 방향 계산
- /// 현재 노드에서 연결된 다음 노드들을 분석하여 리프트 방향 결정
- ///
- /// 현재 위치
- /// 이전 위치
- /// 모터 방향
- /// 맵 노드 리스트 (경로 예측용)
- /// 위치 허용 오차
- /// 리프트 계산 결과
- public static LiftCalculationResult CalculateLiftInfoWithPathPrediction(
- Point currentPos, Point previousPos, AgvDirection motorDirection,
- List mapNodes, int tolerance = 10)
- {
- if (mapNodes == null || mapNodes.Count == 0)
- {
- // 맵 노드 정보가 없으면 기존 방식 사용
- return CalculateLiftInfo(previousPos, currentPos, motorDirection);
- }
-
- // 현재 위치에 해당하는 노드 찾기
- var currentNode = FindNodeByPosition(mapNodes, currentPos, tolerance);
-
- if (currentNode == null)
- {
- // 현재 노드를 찾을 수 없으면 기존 방식 사용
- return CalculateLiftInfo(previousPos, currentPos, motorDirection);
- }
-
- // 이전 위치에 해당하는 노드 찾기
- var previousNode = FindNodeByPosition(mapNodes, previousPos, tolerance);
-
- Point targetPosition;
- string calculationMethod;
-
- // 모터 방향에 따른 예측 방향 결정
- if (motorDirection == AgvDirection.Backward)
- {
- // 후진 모터: AGV가 리프트 쪽(목표 위치)으로 이동
- // 경로 예측 없이 단순히 현재→목표 방향 사용
- return CalculateLiftInfo(currentPos, previousPos, motorDirection);
- }
- else
- {
- // 전진 모터: 기존 로직 (다음 노드 예측)
- var nextNodes = GetConnectedNodes(mapNodes, currentNode);
-
- // 이전 노드 제외 (되돌아가는 방향 제외)
- if (previousNode != null)
- {
- nextNodes = nextNodes.Where(n => n.NodeId != previousNode.NodeId).ToList();
- }
-
- if (nextNodes.Count == 1)
- {
- // 직선 경로: 다음 노드 방향으로 예측
- targetPosition = nextNodes.First().Position;
- calculationMethod = $"전진 경로 예측 ({currentNode.NodeId}→{nextNodes.First().NodeId})";
- }
- else if (nextNodes.Count > 1)
- {
- // 갈래길: 이전 위치 기반 계산 사용
- var prevResult = CalculateLiftInfo(previousPos, currentPos, motorDirection);
- prevResult.CalculationMethod += " (전진 갈래길)";
- return prevResult;
- }
- else
- {
- // 연결된 노드가 없으면 기존 방식 사용
- return CalculateLiftInfo(previousPos, currentPos, motorDirection);
- }
- }
-
- // 리프트 각도 계산
- var angleRadians = CalculateLiftAngleRadians(currentPos, targetPosition, motorDirection);
- var angleDegrees = angleRadians * 180.0 / Math.PI;
-
- // 0-360도 범위로 정규화
- while (angleDegrees < 0) angleDegrees += 360;
- while (angleDegrees >= 360) angleDegrees -= 360;
-
- var directionString = AngleToDirectionString(angleDegrees);
-
- return new LiftCalculationResult
- {
- AngleRadians = angleRadians,
- AngleDegrees = angleDegrees,
- DirectionString = directionString,
- CalculationMethod = calculationMethod,
- MotorDirection = motorDirection
- };
- }
+
///
/// 위치 기반 노드 찾기
diff --git a/Cs_HMI/AGVNavigationCore/build.bat b/Cs_HMI/AGVLogic/AGVNavigationCore/build.bat
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/build.bat
rename to Cs_HMI/AGVLogic/AGVNavigationCore/build.bat
diff --git a/Cs_HMI/AGVNavigationCore/packages.config b/Cs_HMI/AGVLogic/AGVNavigationCore/packages.config
similarity index 100%
rename from Cs_HMI/AGVNavigationCore/packages.config
rename to Cs_HMI/AGVLogic/AGVNavigationCore/packages.config
diff --git a/Cs_HMI/AGVSimulator/AGVSimulator.csproj b/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj
similarity index 100%
rename from Cs_HMI/AGVSimulator/AGVSimulator.csproj
rename to Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj
diff --git a/Cs_HMI/AGVSimulator/Forms/SimulatorForm.Designer.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs
similarity index 100%
rename from Cs_HMI/AGVSimulator/Forms/SimulatorForm.Designer.cs
rename to Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs
diff --git a/Cs_HMI/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs
similarity index 97%
rename from Cs_HMI/AGVSimulator/Forms/SimulatorForm.cs
rename to Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs
index 4f4dbdb..e6734c7 100644
--- a/Cs_HMI/AGVSimulator/Forms/SimulatorForm.cs
+++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs
@@ -9,10 +9,10 @@ using AGVNavigationCore.Models;
using AGVNavigationCore.Controls;
using AGVNavigationCore.PathFinding;
using AGVNavigationCore.Utils;
-using AGVSimulator.Models;
using Newtonsoft.Json;
using AGVNavigationCore.PathFinding.Planning;
using AGVNavigationCore.PathFinding.Core;
+using AGVSimulator.Models;
namespace AGVSimulator.Forms
{
@@ -302,8 +302,22 @@ namespace AGVSimulator.Forms
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
var currentDirection = selectedAGV?.CurrentDirection ?? AgvDirection.Forward;
+ // AGV의 이전 위치에서 가장 가까운 노드 찾기
+ MapNode prevNode = startNode; // 기본값으로 시작 노드 사용
+ if (selectedAGV != null && _mapNodes != null && _mapNodes.Count > 0)
+ {
+ // AGV 현재 위치에서 가장 가까운 노드 찾기
+ var agvPos = selectedAGV.CurrentPosition;
+ prevNode = _mapNodes.OrderBy(n =>
+ Math.Sqrt(Math.Pow(n.Position.X - agvPos.X, 2) +
+ Math.Pow(n.Position.Y - agvPos.Y, 2))).FirstOrDefault();
+
+ if (prevNode == null)
+ prevNode = startNode;
+ }
+
// 고급 경로 계획 사용 (노드 객체 직접 전달)
- var advancedResult = _advancedPathfinder.FindPath(startNode, targetNode, currentDirection);
+ var advancedResult = _advancedPathfinder.FindPath(startNode, targetNode, prevNode, currentDirection);
if (advancedResult.Success)
{
@@ -519,8 +533,18 @@ namespace AGVSimulator.Forms
private void OnSimulationTimer_Tick(object sender, EventArgs e)
{
- // 시뮬레이션 업데이트는 각 AGV의 내부 타이머에서 처리됨
+ // 모든 AGV의 업데이트 메서드 호출 (100ms 간격)
+ if (_agvList != null)
+ {
+ foreach (var agv in _agvList)
+ {
+ agv.Update(100); // 100ms 간격으로 업데이트
+ }
+ }
+
+ // UI 업데이트
UpdateUI();
+ _simulatorCanvas.Invalidate(); // 화면 다시 그리기
}
#endregion
diff --git a/Cs_HMI/AGVSimulator/Forms/SimulatorForm.resx b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx
similarity index 100%
rename from Cs_HMI/AGVSimulator/Forms/SimulatorForm.resx
rename to Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx
diff --git a/Cs_HMI/AGVSimulator/Models/SimulationState.cs b/Cs_HMI/AGVLogic/AGVSimulator/Models/SimulationState.cs
similarity index 100%
rename from Cs_HMI/AGVSimulator/Models/SimulationState.cs
rename to Cs_HMI/AGVLogic/AGVSimulator/Models/SimulationState.cs
diff --git a/Cs_HMI/AGVSimulator/Models/SimulatorConfig.cs b/Cs_HMI/AGVLogic/AGVSimulator/Models/SimulatorConfig.cs
similarity index 100%
rename from Cs_HMI/AGVSimulator/Models/SimulatorConfig.cs
rename to Cs_HMI/AGVLogic/AGVSimulator/Models/SimulatorConfig.cs
diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVSimulator/Models/VirtualAGV.cs
new file mode 100644
index 0000000..82d9934
--- /dev/null
+++ b/Cs_HMI/AGVLogic/AGVSimulator/Models/VirtualAGV.cs
@@ -0,0 +1,561 @@
+//using System;
+//using System.Collections.Generic;
+//using System.Drawing;
+//using System.Linq;
+//using AGVMapEditor.Models;
+//using AGVNavigationCore.Models;
+//using AGVNavigationCore.PathFinding;
+//using AGVNavigationCore.PathFinding.Core;
+//using AGVNavigationCore.Controls;
+
+//namespace AGVSimulator.Models
+//{
+
+// ///
+// /// 가상 AGV 클래스
+// /// 실제 AGV의 동작을 시뮬레이션
+// ///
+// public class VirtualAGV : IAGV
+// {
+// #region Events
+
+// ///
+// /// AGV 상태 변경 이벤트
+// ///
+// public event EventHandler StateChanged;
+
+// ///
+// /// 위치 변경 이벤트
+// ///
+// public event EventHandler<(Point, AgvDirection, MapNode)> PositionChanged;
+
+// ///
+// /// RFID 감지 이벤트
+// ///
+// public event EventHandler RfidDetected;
+
+// ///
+// /// 경로 완료 이벤트
+// ///
+// public event EventHandler PathCompleted;
+
+// ///
+// /// 오류 발생 이벤트
+// ///
+// public event EventHandler ErrorOccurred;
+
+// #endregion
+
+// #region Fields
+
+// private string _agvId;
+// private Point _currentPosition;
+// private Point _targetPosition;
+// private string _targetId;
+// private string _currentId;
+// private AgvDirection _currentDirection;
+// private AgvDirection _targetDirection;
+// private AGVState _currentState;
+// private float _currentSpeed;
+
+// // 경로 관련
+// private AGVPathResult _currentPath;
+// private List _remainingNodes;
+// private int _currentNodeIndex;
+// private MapNode _currentNode;
+// private MapNode _targetNode;
+
+// // 이동 관련
+// private System.Windows.Forms.Timer _moveTimer;
+// private DateTime _lastMoveTime;
+// private Point _moveStartPosition;
+// private Point _moveTargetPosition;
+// private float _moveProgress;
+
+// // 도킹 관련
+// private DockingDirection _dockingDirection;
+
+// // 시뮬레이션 설정
+// private readonly float _moveSpeed = 50.0f; // 픽셀/초
+// private readonly float _rotationSpeed = 90.0f; // 도/초
+// private readonly int _updateInterval = 50; // ms
+
+// #endregion
+
+// #region Properties
+
+// ///
+// /// AGV ID
+// ///
+// public string AgvId => _agvId;
+
+// ///
+// /// 현재 위치
+// ///
+// public Point CurrentPosition
+// {
+// get => _currentPosition;
+// set => _currentPosition = value;
+// }
+
+// ///
+// /// 현재 방향
+// /// 모터의 동작 방향
+// ///
+// public AgvDirection CurrentDirection
+// {
+// get => _currentDirection;
+// set => _currentDirection = value;
+// }
+
+// ///
+// /// 현재 상태
+// ///
+// public AGVState CurrentState
+// {
+// get => _currentState;
+// set => _currentState = value;
+// }
+
+// ///
+// /// 현재 속도
+// ///
+// public float CurrentSpeed => _currentSpeed;
+
+// ///
+// /// 현재 경로
+// ///
+// public AGVPathResult CurrentPath => _currentPath;
+
+// ///
+// /// 현재 노드 ID
+// ///
+// public string CurrentNodeId => _currentNode.NodeId;
+
+// ///
+// /// 목표 위치
+// ///
+// public Point? TargetPosition => _targetPosition;
+
+// ///
+// /// 배터리 레벨 (시뮬레이션)
+// ///
+// public float BatteryLevel { get; set; } = 100.0f;
+
+// ///
+// /// 목표 노드 ID
+// ///
+// public string TargetNodeId => _targetNode.NodeId;
+
+// ///
+// /// 도킹 방향
+// ///
+// public DockingDirection DockingDirection => _dockingDirection;
+
+// #endregion
+
+// #region Constructor
+
+// ///
+// /// 생성자
+// ///
+// /// AGV ID
+// /// 시작 위치
+// /// 시작 방향
+// public VirtualAGV(string agvId, Point startPosition, AgvDirection startDirection = AgvDirection.Forward)
+// {
+// _agvId = agvId;
+// _currentPosition = startPosition;
+// _currentDirection = startDirection;
+// _currentState = AGVState.Idle;
+// _currentSpeed = 0;
+// _dockingDirection = DockingDirection.Forward; // 기본값: 전진 도킹
+// _currentNode = null; // = string.Empty;
+// _targetNode = null;// string.Empty;
+
+// InitializeTimer();
+// }
+
+// #endregion
+
+// #region Initialization
+
+// private void InitializeTimer()
+// {
+// _moveTimer = new System.Windows.Forms.Timer();
+// _moveTimer.Interval = _updateInterval;
+// _moveTimer.Tick += OnMoveTimer_Tick;
+// _lastMoveTime = DateTime.Now;
+// }
+
+// #endregion
+
+// #region Public Methods
+
+// ///
+// /// 경로 실행 시작
+// ///
+// /// 실행할 경로
+// /// 맵 노드 목록
+// public void StartPath(AGVPathResult path, List mapNodes)
+// {
+// if (path == null || !path.Success)
+// {
+// OnError("유효하지 않은 경로입니다.");
+// return;
+// }
+
+// _currentPath = path;
+// _remainingNodes = new List(path.Path);
+// _currentNodeIndex = 0;
+
+// // 시작 노드와 목표 노드 설정
+// if (_remainingNodes.Count > 0)
+// {
+// var startNode = mapNodes.FirstOrDefault(n => n.NodeId == _remainingNodes[0]);
+// if (startNode != null)
+// {
+// _currentNode = startNode;
+
+// // 목표 노드 설정 (경로의 마지막 노드)
+// if (_remainingNodes.Count > 1)
+// {
+// var _targetNodeId = _remainingNodes[_remainingNodes.Count - 1];
+// var targetNode = mapNodes.FirstOrDefault(n => n.NodeId == _targetNodeId);
+
+// // 목표 노드의 타입에 따라 도킹 방향 결정
+// if (targetNode != null)
+// {
+// _dockingDirection = GetDockingDirection(targetNode.Type);
+// }
+// }
+
+// StartMovement();
+// }
+// else
+// {
+// OnError($"시작 노드를 찾을 수 없습니다: {_remainingNodes[0]}");
+// }
+// }
+// }
+
+// ///
+// /// 경로 정지
+// ///
+// public void StopPath()
+// {
+// _moveTimer.Stop();
+// _currentPath = null;
+// _remainingNodes?.Clear();
+// SetState(AGVState.Idle);
+// _currentSpeed = 0;
+// }
+
+// ///
+// /// 긴급 정지
+// ///
+// public void EmergencyStop()
+// {
+// StopPath();
+// OnError("긴급 정지가 실행되었습니다.");
+// }
+
+// ///
+// /// 수동 이동 (테스트용)
+// ///
+// /// 목표 위치
+// public void MoveTo(Point targetPosition)
+// {
+// _targetPosition = targetPosition;
+// _moveStartPosition = _currentPosition;
+// _moveTargetPosition = targetPosition;
+// _moveProgress = 0;
+
+// SetState(AGVState.Moving);
+// _moveTimer.Start();
+// }
+
+// ///
+// /// 수동 회전 (테스트용)
+// ///
+// /// 회전 방향
+// public void Rotate(AgvDirection direction)
+// {
+// if (_currentState != AGVState.Idle)
+// return;
+
+// SetState(AGVState.Rotating);
+
+// // 시뮬레이션: 즉시 방향 변경 (실제로는 시간이 걸림)
+// _currentDirection = direction;
+
+// System.Threading.Thread.Sleep(500); // 회전 시간 시뮬레이션
+// SetState(AGVState.Idle);
+// }
+
+
+
+
+// ///
+// /// AGV 위치 직접 설정 (시뮬레이터용)
+// /// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함
+// ///
+// /// 새로운 위치
+// /// 모터이동방향
+// public void SetPosition(MapNode node, Point newPosition, AgvDirection motorDirection)
+// {
+// // 현재 위치를 이전 위치로 저장 (리프트 방향 계산용)
+// if (_currentPosition != Point.Empty)
+// {
+// _targetPosition = _currentPosition; // 이전 위치 (previousPos 역할)
+// _targetDirection = _currentDirection;
+// _targetNode = node;
+// }
+
+// // 새로운 위치 설정
+// _currentPosition = newPosition;
+// _currentDirection = motorDirection;
+// _currentNode = node;
+
+// // 위치 변경 이벤트 발생
+// PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
+// }
+
+// ///
+// /// 충전 시작 (시뮬레이션)
+// ///
+// public void StartCharging()
+// {
+// if (_currentState == AGVState.Idle)
+// {
+// SetState(AGVState.Charging);
+// // 충전 시뮬레이션 시작
+// }
+// }
+
+// ///
+// /// 충전 종료
+// ///
+// public void StopCharging()
+// {
+// if (_currentState == AGVState.Charging)
+// {
+// SetState(AGVState.Idle);
+// }
+// }
+
+// ///
+// /// AGV 정보 조회
+// ///
+// public string GetStatus()
+// {
+// return $"AGV[{_agvId}] 위치:({_currentPosition.X},{_currentPosition.Y}) " +
+// $"방향:{_currentDirection} 상태:{_currentState} " +
+// $"속도:{_currentSpeed:F1} 배터리:{BatteryLevel:F1}%";
+// }
+
+// ///
+// /// 현재 RFID 시뮬레이션 (현재 위치 기준)
+// ///
+// public string SimulateRfidReading(List mapNodes)
+// {
+// // 현재 위치에서 가장 가까운 노드 찾기
+// var closestNode = FindClosestNode(_currentPosition, mapNodes);
+// if (closestNode == null)
+// return null;
+
+// // 해당 노드의 RFID 정보 반환 (MapNode에 RFID 정보 포함)
+// return closestNode.HasRfid() ? closestNode.RfidId : null;
+// }
+
+// #endregion
+
+// #region Private Methods
+
+// private void StartMovement()
+// {
+// SetState(AGVState.Moving);
+// _moveTimer.Start();
+// _lastMoveTime = DateTime.Now;
+// }
+
+// private void OnMoveTimer_Tick(object sender, EventArgs e)
+// {
+// var now = DateTime.Now;
+// var deltaTime = (float)(now - _lastMoveTime).TotalSeconds;
+// _lastMoveTime = now;
+
+// UpdateMovement(deltaTime);
+// UpdateBattery(deltaTime);
+
+// // 위치 변경 이벤트 발생
+// PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
+// }
+
+// private void UpdateMovement(float deltaTime)
+// {
+// if (_currentState != AGVState.Moving)
+// return;
+
+// // 목표 위치까지의 거리 계산
+// var distance = CalculateDistance(_currentPosition, _moveTargetPosition);
+
+// if (distance < 5.0f) // 도달 임계값
+// {
+// // 목표 도달
+// _currentPosition = _moveTargetPosition;
+// _currentSpeed = 0;
+
+// // 다음 노드로 이동
+// ProcessNextNode();
+// }
+// else
+// {
+// // 계속 이동
+// var moveDistance = _moveSpeed * deltaTime;
+// var direction = new PointF(
+// _moveTargetPosition.X - _currentPosition.X,
+// _moveTargetPosition.Y - _currentPosition.Y
+// );
+
+// // 정규화
+// var length = (float)Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y);
+// if (length > 0)
+// {
+// direction.X /= length;
+// direction.Y /= length;
+// }
+
+// // 새 위치 계산
+// _currentPosition = new Point(
+// (int)(_currentPosition.X + direction.X * moveDistance),
+// (int)(_currentPosition.Y + direction.Y * moveDistance)
+// );
+
+// _currentSpeed = _moveSpeed;
+// }
+// }
+
+// private void UpdateBattery(float deltaTime)
+// {
+// // 배터리 소모 시뮬레이션
+// if (_currentState == AGVState.Moving)
+// {
+// BatteryLevel -= 0.1f * deltaTime; // 이동시 소모
+// }
+// else if (_currentState == AGVState.Charging)
+// {
+// BatteryLevel += 5.0f * deltaTime; // 충전
+// BatteryLevel = Math.Min(100.0f, BatteryLevel);
+// }
+
+// BatteryLevel = Math.Max(0, BatteryLevel);
+
+// // 배터리 부족 경고
+// if (BatteryLevel < 20.0f && _currentState != AGVState.Charging)
+// {
+// OnError($"배터리 부족: {BatteryLevel:F1}%");
+// }
+// }
+
+// private void ProcessNextNode()
+// {
+// if (_remainingNodes == null || _currentNodeIndex >= _remainingNodes.Count - 1)
+// {
+// // 경로 완료
+// _moveTimer.Stop();
+// SetState(AGVState.Idle);
+// PathCompleted?.Invoke(this, _currentPath);
+// return;
+// }
+
+// // 다음 노드로 이동
+// _currentNodeIndex++;
+// var nextNodeId = _remainingNodes[_currentNodeIndex];
+
+// // RFID 감지 시뮬레이션
+// RfidDetected?.Invoke(this, $"RFID_{nextNodeId}");
+// //_currentNodeId = nextNodeId;
+
+// // 다음 목표 위치 설정 (실제로는 맵에서 좌표 가져와야 함)
+// // 여기서는 간단히 현재 위치에서 랜덤 오프셋으로 설정
+// var random = new Random();
+// _moveTargetPosition = new Point(
+// _currentPosition.X + random.Next(-100, 100),
+// _currentPosition.Y + random.Next(-100, 100)
+// );
+// }
+
+// private MapNode FindClosestNode(Point position, List mapNodes)
+// {
+// if (mapNodes == null || mapNodes.Count == 0)
+// return null;
+
+// MapNode closestNode = null;
+// float closestDistance = float.MaxValue;
+
+// foreach (var node in mapNodes)
+// {
+// var distance = CalculateDistance(position, node.Position);
+// if (distance < closestDistance)
+// {
+// closestDistance = distance;
+// closestNode = node;
+// }
+// }
+
+// // 일정 거리 내에 있는 노드만 반환
+// return closestDistance < 50.0f ? closestNode : null;
+// }
+
+// private float CalculateDistance(Point from, Point to)
+// {
+// var dx = to.X - from.X;
+// var dy = to.Y - from.Y;
+// return (float)Math.Sqrt(dx * dx + dy * dy);
+// }
+
+// private void SetState(AGVState newState)
+// {
+// if (_currentState != newState)
+// {
+// _currentState = newState;
+// StateChanged?.Invoke(this, newState);
+// }
+// }
+
+// private DockingDirection GetDockingDirection(NodeType nodeType)
+// {
+// switch (nodeType)
+// {
+// case NodeType.Charging:
+// return DockingDirection.Forward; // 충전기: 전진 도킹
+// case NodeType.Docking:
+// return DockingDirection.Backward; // 장비 (로더, 클리너, 오프로더, 버퍼): 후진 도킹
+// default:
+// return DockingDirection.Forward; // 기본값: 전진
+// }
+// }
+
+// private void OnError(string message)
+// {
+// SetState(AGVState.Error);
+// ErrorOccurred?.Invoke(this, message);
+// }
+
+// #endregion
+
+// #region Cleanup
+
+// ///
+// /// 리소스 정리
+// ///
+// public void Dispose()
+// {
+// _moveTimer?.Stop();
+// _moveTimer?.Dispose();
+// }
+
+// #endregion
+// }
+//}
\ No newline at end of file
diff --git a/Cs_HMI/AGVSimulator/Program.cs b/Cs_HMI/AGVLogic/AGVSimulator/Program.cs
similarity index 100%
rename from Cs_HMI/AGVSimulator/Program.cs
rename to Cs_HMI/AGVLogic/AGVSimulator/Program.cs
diff --git a/Cs_HMI/AGVSimulator/Properties/AssemblyInfo.cs b/Cs_HMI/AGVLogic/AGVSimulator/Properties/AssemblyInfo.cs
similarity index 100%
rename from Cs_HMI/AGVSimulator/Properties/AssemblyInfo.cs
rename to Cs_HMI/AGVLogic/AGVSimulator/Properties/AssemblyInfo.cs
diff --git a/Cs_HMI/AGVSimulator/build.bat b/Cs_HMI/AGVLogic/AGVSimulator/build.bat
similarity index 100%
rename from Cs_HMI/AGVSimulator/build.bat
rename to Cs_HMI/AGVLogic/AGVSimulator/build.bat
diff --git a/Cs_HMI/AGVSimulator/packages.config b/Cs_HMI/AGVLogic/AGVSimulator/packages.config
similarity index 100%
rename from Cs_HMI/AGVSimulator/packages.config
rename to Cs_HMI/AGVLogic/AGVSimulator/packages.config
diff --git a/Cs_HMI/AGVLogic/CLAUDE.md b/Cs_HMI/AGVLogic/CLAUDE.md
new file mode 100644
index 0000000..348ca06
--- /dev/null
+++ b/Cs_HMI/AGVLogic/CLAUDE.md
@@ -0,0 +1,221 @@
+# CLAUDE.md (AGVLogic 폴더)
+
+이 파일은 AGVLogic 폴더에서 개발 중인 AGV 관련 프로젝트들을 위한 개발 가이드입니다.
+
+**현재 폴더 위치**: `C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\AGVLogic\`
+**맵데이터**: `../Data/NewMap.agvmap` 파일을 기준으로 사용
+
+---
+
+## 프로젝트 개요
+
+현재 AGVLogic 폴더에서 다음 3개의 독립 프로젝트를 개발 중입니다:
+
+### 1. AGVMapEditor (맵 에디터)
+**위치**: `./AGVMapEditor/`
+**실행파일**: `./AGVMapEditor/bin/Debug/AGVMapEditor.exe`
+
+#### 핵심 기능
+- **맵 노드 관리**: 논리적 노드 생성, 연결, 속성 설정
+- **RFID 매핑**: 물리적 RFID ID ↔ 논리적 노드 ID 매핑
+- **시각적 편집**: 드래그앤드롭으로 노드 배치 및 연결
+- **JSON 저장**: 맵 데이터를 JSON 형식으로 저장/로드
+- **노드 연결 관리**: 연결 목록 표시 및 직접 삭제 기능
+
+#### 핵심 클래스
+- **MapNode**: 논리적 맵 노드 (NodeId, 위치, 타입, 연결 정보)
+- **RfidMapping**: RFID 물리적 ID ↔ 논리적 노드 ID 매핑
+- **NodeResolver**: RFID ID를 통한 노드 해석기
+- **MapCanvas**: 시각적 맵 편집 컨트롤
+
+### 2. AGVNavigationCore (경로 탐색 라이브러리)
+**위치**: `./AGVNavigationCore/`
+
+#### 핵심 기능
+- **A* 경로 탐색**: 최적 경로 계산 알고리즘
+- **방향 제어**: 전진/후진 모터 방향 결정
+- **도킹 검증**: 충전기/장비 도킹 방향 검증
+- **리프트 계산**: AGV 리프트 각도 계산
+- **경로 최적화**: 회전 구간 회피 등 고급 옵션
+
+#### 핵심 클래스
+- **PathFinding/Core/AStarPathfinder.cs**: A* 알고리즘 구현
+- **PathFinding/Planning/AGVPathfinder.cs**: 경로 탐색 메인 클래스
+- **PathFinding/Planning/DirectionChangePlanner.cs**: 방향 변경 계획
+- **Utils/LiftCalculator.cs**: 리프트 각도 계산
+- **Utils/DockingValidator.cs**: 도킹 유효성 검증
+- **Controls/UnifiedAGVCanvas.cs**: 맵 및 AGV 시각화
+
+### 3. AGVSimulator (AGV 시뮬레이터)
+**위치**: `./AGVSimulator/`
+**실행파일**: `./AGVSimulator/bin/Debug/AGVSimulator.exe`
+
+#### 핵심 기능
+- **가상 AGV 시뮬레이션**: 실시간 AGV 움직임 및 상태 관리
+- **맵 시각화**: 맵 에디터에서 생성한 맵 파일 로드 및 표시
+- **경로 실행**: 계산된 경로를 따라 AGV 시뮬레이션
+- **상태 모니터링**: AGV 상태, 위치, 배터리 등 실시간 표시
+
+#### 핵심 클래스
+- **VirtualAGV**: 가상 AGV 동작 시뮬레이션 (이동, 회전, 도킹, 충전)
+- **SimulatorCanvas**: AGV 및 맵 시각화 캔버스
+- **SimulatorForm**: 시뮬레이터 메인 인터페이스
+- **SimulationState**: 시뮬레이션 상태 관리
+
+#### AGV 상태
+- **Idle**: 대기
+- **Moving**: 이동 중
+- **Rotating**: 회전 중
+- **Docking**: 도킹 중
+- **Charging**: 충전 중
+- **Error**: 오류
+
+---
+
+## AGV 방향 제어 및 도킹 시스템
+
+### AGV 하드웨어 레이아웃
+```
+LIFT --- AGV --- MONITOR
+ ↑ ↑ ↑
+후진시 AGV본체 전진시
+도달위치 도달위치
+```
+
+### 모터 방향과 이동 방향
+- **전진 모터 (Forward)**: AGV가 모니터 방향으로 이동 (→)
+- **후진 모터 (Backward)**: AGV가 리프트 방향으로 이동 (←)
+
+### 도킹 방향 규칙
+- **충전기 (Charging)**: 전진 도킹 (Forward) - 모니터가 충전기 면
+- **장비 (Docking)**: 후진 도킹 (Backward) - 리프트가 장비 면
+
+### 핵심 계산 파일들
+1. **LiftCalculator.cs** - 리프트 방향 계산
+ - `CalculateLiftAngleRadians(Point currentPos, Point targetPos, AgvDirection motorDirection)`
+
+2. **DirectionChangePlanner.cs** - 도킹 방향 결정
+ - `GetRequiredDockingDirection(string targetNodeId)` - 노드타입별 도킹 방향 반환
+
+3. **VirtualAGV.cs** - AGV 위치/방향 관리
+ - `SetPosition(Point newPosition)` - AGV 위치 및 방향 설정
+
+---
+
+## AGVNavigationCore 프로젝트 구조
+
+### 📁 폴더 구조
+```
+AGVNavigationCore/
+├── Controls/
+│ ├── UnifiedAGVCanvas.cs # AGV 및 맵 시각화 메인 캔버스
+│ ├── UnifiedAGVCanvas.Events.cs # 그리기 및 이벤트 처리
+│ ├── UnifiedAGVCanvas.Mouse.cs # 마우스 인터랙션
+│ ├── AGVState.cs # AGV 상태 정의
+│ └── IAGV.cs # AGV 인터페이스
+│
+├── Models/
+│ ├── MapNode.cs # 맵 노드 데이터 모델
+│ ├── MapLoader.cs # JSON 맵 파일 로더
+│ └── Enums.cs # 열거형 정의 (NodeType, AgvDirection 등)
+│
+├── Utils/
+│ ├── LiftCalculator.cs # 리프트 각도 계산
+│ └── DockingValidator.cs # 도킹 유효성 검증
+│
+└── PathFinding/
+ ├── Analysis/
+ │ └── JunctionAnalyzer.cs # 교차점 분석
+ │
+ ├── Core/
+ │ ├── AStarPathfinder.cs # A* 알고리즘
+ │ ├── PathNode.cs # 경로 노드
+ │ └── AGVPathResult.cs # 경로 계산 결과
+ │
+ ├── Planning/
+ │ ├── AGVPathfinder.cs # 경로 탐색 메인 클래스
+ │ ├── AdvancedAGVPathfinder.cs # 고급 경로 탐색
+ │ ├── DirectionChangePlanner.cs # 방향 변경 계획
+ │ ├── NodeMotorInfo.cs # 노드별 모터 정보
+ │ └── PathfindingOptions.cs # 경로 탐색 옵션
+ │
+ └── Validation/
+ ├── DockingValidationResult.cs # 도킹 검증 결과
+ └── PathValidationResult.cs # 경로 검증 결과
+```
+
+### 🎯 클래스 배치 원칙
+
+#### PathFinding/Validation/
+- **검증 결과 클래스**: `*ValidationResult.cs` 패턴 사용
+- **패턴**: 정적 팩토리 메서드 (CreateValid, CreateInvalid, CreateNotRequired)
+- **속성**: IsValid, ValidationError, 관련 상세 정보
+
+#### PathFinding/Planning/
+- **경로 계획 클래스**: 실제 경로 탐색 및 계획 로직
+- **방향 변경 로직**: DirectionChangePlanner.cs
+- **경로 최적화**: 경로 생성과 관련된 전략
+
+#### PathFinding/Core/
+- **핵심 알고리즘**: A* 알고리즘 등 기본 경로 탐색
+- **기본 경로 탐색**: 단순한 점-to-점 경로 계산
+
+#### PathFinding/Analysis/
+- **경로 분석**: 생성된 경로의 품질 및 특성 분석
+- **성능 분석**: 경로 효율성 및 최적화 분석
+
+---
+
+## 개발 워크플로우
+
+### 권장 개발 순서
+1. **맵 데이터 준비**: AGVMapEditor로 맵 노드 배치 및 RFID 매핑 설정
+2. **경로 탐색 구현**: AGVNavigationCore에서 경로 계산 알고리즘 개발
+3. **시뮬레이션 테스트**: AGVSimulator로 AGV 동작 검증
+4. **메인 프로젝트 통합**: 개발 완료 후 부모 폴더(Cs_HMI)에 병합
+
+### 중요한 개발 패턴
+- **이벤트 기반 아키텍처**: UI 업데이트는 이벤트를 통해 자동화
+- **상태 관리**: _hasChanges 플래그로 변경사항 추적
+- **에러 처리**: 사용자 확인 다이얼로그와 상태바 메시지 활용
+- **코드 재사용**: UnifiedAGVCanvas를 맵에디터와 시뮬레이터에서 공통 사용
+
+### 주의사항
+- **PathFinding 로직 변경시**: 반드시 시뮬레이터에서 테스트 후 적용
+- **노드 연결 관리**: 물리적 RFID와 논리적 노드 ID 분리 원칙 유지
+- **JSON 파일 형식**: 맵 데이터는 MapNodes, RfidMappings 두 섹션으로 구성
+- **좌표 시스템**: 줌/팬 상태에서 좌표 변환 정확성 지속 모니터링
+
+---
+
+## 최근 구현 완료 기능
+
+### ✅ 회전 구간 회피 기능 (PathFinding)
+- **목적**: AGV 회전 오류를 피하기 위한 선택적 회전 구간 회피
+- **파일**: `PathFinding/PathfindingOptions.cs`
+- **UI**: AGVSimulator에 "회전 구간 회피" 체크박스
+
+### ✅ 맵 에디터 마우스 좌표 오차 수정
+- **문제**: 줌 인/아웃 시 노드 선택 히트 영역이 너무 작음
+- **해결**: 최소 화면 히트 영역(20픽셀) 보장
+- **파일**: `AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs`
+
+### ✅ 노드 연결 관리 시스템
+- **기능**: 노드 연결 목록 표시 및 삭제
+- **파일들**:
+ - `AGVMapEditor/Forms/MainForm.cs` - UI 및 이벤트 처리
+ - `UnifiedAGVCanvas.cs` - 편집 모드 및 이벤트 정의
+ - `UnifiedAGVCanvas.Mouse.cs` - 마우스 연결 삭제 기능
+
+---
+
+## 향후 개발 우선순위
+
+1. **방향 전환 기능**: AGV 현재 방향과 목표 방향 불일치 시 회전 노드 경유 로직
+2. **맵 검증 기능**: 연결 무결성, 고립된 노드, 순환 경로 등 검증
+3. **성능 최적화**: 대형 맵에서 경로 계산 및 연결 목록 표시 성능 개선
+4. **실시간 동기화**: 맵 에디터와 시뮬레이터 간 실시간 맵 동기화
+
+---
+
+**최종 업데이트**: 2025-10-23 - AGVLogic 폴더 기준으로 정리
diff --git a/Cs_HMI/E2ETEST.md b/Cs_HMI/AGVLogic/E2ETEST.md
similarity index 100%
rename from Cs_HMI/E2ETEST.md
rename to Cs_HMI/AGVLogic/E2ETEST.md
diff --git a/Cs_HMI/PATHSCENARIO.md b/Cs_HMI/AGVLogic/PATHSCENARIO.md
similarity index 100%
rename from Cs_HMI/PATHSCENARIO.md
rename to Cs_HMI/AGVLogic/PATHSCENARIO.md
diff --git a/Cs_HMI/PROJECT_SUMMARY.md b/Cs_HMI/AGVLogic/PROJECT_SUMMARY.md
similarity index 100%
rename from Cs_HMI/PROJECT_SUMMARY.md
rename to Cs_HMI/AGVLogic/PROJECT_SUMMARY.md
diff --git a/Cs_HMI/TODO.md b/Cs_HMI/AGVLogic/TODO.md
similarity index 100%
rename from Cs_HMI/TODO.md
rename to Cs_HMI/AGVLogic/TODO.md
diff --git a/Cs_HMI/AGVLogic/build.bat b/Cs_HMI/AGVLogic/build.bat
new file mode 100644
index 0000000..44627b5
--- /dev/null
+++ b/Cs_HMI/AGVLogic/build.bat
@@ -0,0 +1,13 @@
+@echo off
+echo Building AGV C# HMI Project...
+
+REM Set MSBuild path
+REM set MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
+
+set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\MSBuild.exe"
+
+
+REM Rebuild Debug x86 configuration (VS-style Rebuild)
+%MSBUILD% AGVCSharp.sln -property:Configuration=Debug -property:Platform=x86 -verbosity:quiet -nologo -t:Rebuild
+
+pause
\ No newline at end of file
diff --git a/Cs_HMI/AGVLogic/run_claude.bat b/Cs_HMI/AGVLogic/run_claude.bat
new file mode 100644
index 0000000..29a2144
--- /dev/null
+++ b/Cs_HMI/AGVLogic/run_claude.bat
@@ -0,0 +1 @@
+claude --dangerously-skip-permissions
diff --git a/Cs_HMI/CHANGELOG.md b/Cs_HMI/CHANGELOG.md
index b87d286..9c8e599 100644
--- a/Cs_HMI/CHANGELOG.md
+++ b/Cs_HMI/CHANGELOG.md
@@ -2,6 +2,101 @@
이 파일은 AGV HMI 시스템의 주요 변경 사항을 기록합니다.
+## [2025.10.22] - VirtualAGV를 코어 라이브러리로 마이그레이션
+
+### ⚠️ 중요 변경
+- **VirtualAGV를 AGVNavigationCore 라이브러리로 이동**
+ - 이전: `AGVSimulator\Models\VirtualAGV.cs` (시뮬레이터 전용)
+ - 현재: `AGVNavigationCore\Models\VirtualAGV.cs` (공용 코어 라이브러리)
+ - **의미**: 실제 AGV 제어 시스템과 시뮬레이터에서 동일한 비즈니스 로직 사용
+
+### ✨ 새로운 기능
+- **IMovableAGV 인터페이스 추가**
+ - **위치**: `AGVNavigationCore\Models\IMovableAGV.cs` (신규)
+ - **목적**: 실제 AGV와 시뮬레이션 AGV의 계약 정의
+ - **주요 메서드**:
+ ```csharp
+ // 센서 입력
+ void SetCurrentPosition(Point position); // 위치 센서
+ void SetDetectedRfid(string rfidId); // RFID 센서
+ void SetMotorDirection(AgvDirection direction); // 모터
+ void SetBatteryLevel(float percentage); // BMS
+
+ // 프레임 업데이트 (외부에서 호출)
+ void Update(float deltaTimeMs);
+ ```
+
+### 🔧 기술적 개선사항
+- **타이머 의존성 제거**
+ - System.Windows.Forms.Timer 제거
+ - 외부에서 Update(deltaTimeMs)를 주기적으로 호출하는 방식으로 변경
+ - 장점: 타이머 없이도 동작 가능, 실제 시스템과 시뮬레이터 모두 적용 가능
+
+- **UI 의존성 제거**
+ - EventHandler 이외의 UI 라이브러리 제거
+ - 순수 .NET 라이브러리로 변환 (System 네임스페이스만 사용)
+
+- **프레임 업데이트 방식 개선**
+ - 이전: 내부 타이머로 자동 업데이트
+ - 현재: 외부에서 Update(100)을 정기적으로 호출
+ ```csharp
+ // AGVSimulator\Forms\SimulatorForm.cs의 OnSimulationTimer_Tick
+ foreach (var agv in _agvList)
+ {
+ agv.Update(100); // 100ms 간격으로 업데이트
+ }
+ ```
+
+### 📝 아키텍처 변화
+```
+Before:
+ AGVSimulator (시뮬레이터 전용)
+ └── VirtualAGV (시뮬레이터용)
+ └── System.Windows.Forms.Timer
+
+After:
+ AGVNavigationCore (공용 코어)
+ ├── IMovableAGV (인터페이스)
+ └── VirtualAGV (구현체)
+ └── Update(deltaTime) 메서드만
+
+ AGVSimulator (시뮬레이터)
+ └── SimulatorForm.OnSimulationTimer_Tick
+ └── agv.Update(100)
+
+ Project (실제 AGV)
+ └── AgvController
+ └── agv.Update(deltaTime)
+```
+
+### 🎯 향후 활용
+1. **실제 AGV 시스템에서**:
+ ```csharp
+ var agv = new VirtualAGV("AGV1", startPos);
+
+ // 하드웨어 센서에서
+ agv.SetCurrentPosition(sensorData.Position);
+ agv.SetDetectedRfid(rfidSensor.Value);
+ agv.SetMotorDirection(motorController.Direction);
+ agv.SetBatteryLevel(bms.Level);
+
+ // 제어 루프에서
+ agv.Update(deltaTime);
+ ```
+
+2. **시뮬레이션 테스트**:
+ - 시뮬레이터에서 완벽하게 동작한 VirtualAGV
+ - 실제 AGV에 그대로 적용 가능
+ - 테스트 신뢰도 향상
+
+### 📋 변경 파일
+- ✨ 추가: `AGVNavigationCore\Models\VirtualAGV.cs`
+- ✨ 추가: `AGVNavigationCore\Models\IMovableAGV.cs`
+- 📝 수정: `AGVNavigationCore\AGVNavigationCore.csproj` (프로젝트 파일 업데이트)
+- 📝 수정: `AGVSimulator\Forms\SimulatorForm.cs` (Update 호출 추가)
+
+---
+
## [2024.12.16] - AGV 방향 표시 수정 및 타겟계산 기능 추가
### 🐛 버그 수정
diff --git a/Cs_HMI/SubProject/AGVControl/agvControl.csproj b/Cs_HMI/SubProject/AGVControl/agvControl.csproj
index 90480cc..87f5699 100644
--- a/Cs_HMI/SubProject/AGVControl/agvControl.csproj
+++ b/Cs_HMI/SubProject/AGVControl/agvControl.csproj
@@ -92,7 +92,6 @@
-
diff --git a/Cs_HMI/build.bat b/Cs_HMI/build.bat
index 4ce74ec..44627b5 100644
--- a/Cs_HMI/build.bat
+++ b/Cs_HMI/build.bat
@@ -1,16 +1,11 @@
@echo off
echo Building AGV C# HMI Project...
-REM Check if Visual Studio 2022 is installed
-if not exist "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe" (
- echo Visual Studio 2022 Professional not found!
- echo Please install Visual Studio 2022 Professional or update the MSBuild path.
- pause
- exit /b 1
-)
-
REM Set MSBuild path
-set MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
+REM set MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
+
+set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\MSBuild.exe"
+
REM Rebuild Debug x86 configuration (VS-style Rebuild)
%MSBUILD% AGVCSharp.sln -property:Configuration=Debug -property:Platform=x86 -verbosity:quiet -nologo -t:Rebuild