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