refactor: Move AGV development projects to separate AGVLogic folder
- Reorganized AGVMapEditor, AGVNavigationCore, AGVSimulator into AGVLogic folder - Removed deleted project files from root folder tracking - Updated CLAUDE.md with AGVLogic-specific development guidelines - Clean separation of independent project development from main codebase - Projects now ready for independent development and future integration 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -73,6 +73,8 @@
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Models\Enums.cs" />
|
||||
<Compile Include="Models\IMovableAGV.cs" />
|
||||
<Compile Include="Models\VirtualAGV.cs" />
|
||||
<Compile Include="Models\MapLoader.cs" />
|
||||
<Compile Include="Models\MapNode.cs" />
|
||||
<Compile Include="PathFinding\Planning\AGVPathfinder.cs" />
|
||||
215
Cs_HMI/AGVLogic/AGVNavigationCore/Models/IMovableAGV.cs
Normal file
215
Cs_HMI/AGVLogic/AGVNavigationCore/Models/IMovableAGV.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 이동 가능한 AGV 인터페이스
|
||||
/// 실제 AGV와 시뮬레이션 AGV 모두 구현해야 하는 기본 인터페이스
|
||||
/// </summary>
|
||||
public interface IMovableAGV
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// AGV 상태 변경 이벤트
|
||||
/// </summary>
|
||||
event EventHandler<AGVState> StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 위치 변경 이벤트
|
||||
/// </summary>
|
||||
event EventHandler<(Point, AgvDirection, MapNode)> PositionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// RFID 감지 이벤트
|
||||
/// </summary>
|
||||
event EventHandler<string> RfidDetected;
|
||||
|
||||
/// <summary>
|
||||
/// 경로 완료 이벤트
|
||||
/// </summary>
|
||||
event EventHandler<AGVPathResult> PathCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 오류 발생 이벤트
|
||||
/// </summary>
|
||||
event EventHandler<string> ErrorOccurred;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// AGV ID
|
||||
/// </summary>
|
||||
string AgvId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 현재 위치
|
||||
/// </summary>
|
||||
Point CurrentPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 현재 방향 (모터 방향)
|
||||
/// </summary>
|
||||
AgvDirection CurrentDirection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 현재 상태
|
||||
/// </summary>
|
||||
AGVState CurrentState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 현재 속도
|
||||
/// </summary>
|
||||
float CurrentSpeed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 배터리 레벨 (0-100%)
|
||||
/// </summary>
|
||||
float BatteryLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 현재 경로
|
||||
/// </summary>
|
||||
AGVPathResult CurrentPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 현재 노드 ID
|
||||
/// </summary>
|
||||
string CurrentNodeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 목표 위치
|
||||
/// </summary>
|
||||
Point? TargetPosition { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 목표 노드 ID
|
||||
/// </summary>
|
||||
string TargetNodeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 도킹 방향
|
||||
/// </summary>
|
||||
DockingDirection DockingDirection { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sensor Input Methods (실제 AGV에서 호출)
|
||||
|
||||
/// <summary>
|
||||
/// 현재 위치 설정 (실제 위치 센서에서)
|
||||
/// </summary>
|
||||
void SetCurrentPosition(Point position);
|
||||
|
||||
/// <summary>
|
||||
/// 감지된 RFID 설정 (실제 RFID 센서에서)
|
||||
/// </summary>
|
||||
void SetDetectedRfid(string rfidId);
|
||||
|
||||
/// <summary>
|
||||
/// 모터 방향 설정 (모터 컨트롤러에서)
|
||||
/// </summary>
|
||||
void SetMotorDirection(AgvDirection direction);
|
||||
|
||||
/// <summary>
|
||||
/// 배터리 레벨 설정 (BMS에서)
|
||||
/// </summary>
|
||||
void SetBatteryLevel(float percentage);
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Query Methods
|
||||
|
||||
/// <summary>
|
||||
/// 현재 위치 조회
|
||||
/// </summary>
|
||||
Point GetCurrentPosition();
|
||||
|
||||
/// <summary>
|
||||
/// 현재 상태 조회
|
||||
/// </summary>
|
||||
AGVState GetCurrentState();
|
||||
|
||||
/// <summary>
|
||||
/// 현재 노드 ID 조회
|
||||
/// </summary>
|
||||
string GetCurrentNodeId();
|
||||
|
||||
/// <summary>
|
||||
/// AGV 상태 정보 문자열 조회
|
||||
/// </summary>
|
||||
string GetStatus();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Path Execution Methods
|
||||
|
||||
/// <summary>
|
||||
/// 경로 실행
|
||||
/// </summary>
|
||||
void ExecutePath(AGVPathResult path, List<MapNode> mapNodes);
|
||||
|
||||
/// <summary>
|
||||
/// 경로 정지
|
||||
/// </summary>
|
||||
void StopPath();
|
||||
|
||||
/// <summary>
|
||||
/// 긴급 정지
|
||||
/// </summary>
|
||||
void EmergencyStop();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update Method
|
||||
|
||||
/// <summary>
|
||||
/// 프레임 업데이트 (외부에서 주기적으로 호출)
|
||||
/// 이 방식으로 타이머에 의존하지 않고 외부에서 제어 가능
|
||||
/// </summary>
|
||||
/// <param name="deltaTimeMs">마지막 업데이트 이후 경과 시간 (밀리초)</param>
|
||||
void Update(float deltaTimeMs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Manual Control Methods (테스트용)
|
||||
|
||||
/// <summary>
|
||||
/// 수동 이동
|
||||
/// </summary>
|
||||
void MoveTo(Point targetPosition);
|
||||
|
||||
/// <summary>
|
||||
/// 수동 회전
|
||||
/// </summary>
|
||||
void Rotate(AgvDirection direction);
|
||||
|
||||
/// <summary>
|
||||
/// 충전 시작
|
||||
/// </summary>
|
||||
void StartCharging();
|
||||
|
||||
/// <summary>
|
||||
/// 충전 종료
|
||||
/// </summary>
|
||||
void StopCharging();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup
|
||||
|
||||
/// <summary>
|
||||
/// 리소스 정리
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 가상 AGV 클래스
|
||||
/// 실제 AGV의 동작을 시뮬레이션
|
||||
/// 가상 AGV 클래스 (코어 비즈니스 로직)
|
||||
/// 실제 AGV와 시뮬레이터 모두에서 사용 가능한 공용 로직
|
||||
/// 시뮬레이션과 실제 동작이 동일하게 동작하도록 설계됨
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// 현재 노드 ID
|
||||
/// </summary>
|
||||
public string CurrentNodeId => _currentNode.NodeId;
|
||||
public string CurrentNodeId => _currentNode?.NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// 목표 위치
|
||||
@@ -145,7 +143,7 @@ namespace AGVSimulator.Models
|
||||
/// <summary>
|
||||
/// 목표 노드 ID
|
||||
/// </summary>
|
||||
public string TargetNodeId => _targetNode.NodeId;
|
||||
public string TargetNodeId => _targetNode?.NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// 도킹 방향
|
||||
@@ -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()
|
||||
/// <summary>
|
||||
/// 현재 위치 설정 (실제 AGV 센서에서)
|
||||
/// </summary>
|
||||
public void SetCurrentPosition(Point position)
|
||||
{
|
||||
_moveTimer = new System.Windows.Forms.Timer();
|
||||
_moveTimer.Interval = _updateInterval;
|
||||
_moveTimer.Tick += OnMoveTimer_Tick;
|
||||
_lastMoveTime = DateTime.Now;
|
||||
_currentPosition = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 감지된 RFID 설정 (실제 RFID 센서에서)
|
||||
/// </summary>
|
||||
public void SetDetectedRfid(string rfidId)
|
||||
{
|
||||
RfidDetected?.Invoke(this, rfidId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모터 방향 설정 (실제 모터 컨트롤러에서)
|
||||
/// </summary>
|
||||
public void SetMotorDirection(AgvDirection direction)
|
||||
{
|
||||
_currentDirection = direction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 배터리 레벨 설정 (실제 BMS에서)
|
||||
/// </summary>
|
||||
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 - 상태 조회
|
||||
|
||||
/// <summary>
|
||||
/// 현재 위치 조회
|
||||
/// </summary>
|
||||
public Point GetCurrentPosition() => _currentPosition;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 상태 조회
|
||||
/// </summary>
|
||||
public AGVState GetCurrentState() => _currentState;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 노드 ID 조회
|
||||
/// </summary>
|
||||
public string GetCurrentNodeId() => _currentNode?.NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// AGV 정보 조회
|
||||
/// </summary>
|
||||
public string GetStatus()
|
||||
{
|
||||
return $"AGV[{_agvId}] 위치:({_currentPosition.X},{_currentPosition.Y}) " +
|
||||
$"방향:{_currentDirection} 상태:{_currentState} " +
|
||||
$"속도:{_currentSpeed:F1} 배터리:{BatteryLevel:F1}%";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods - 경로 실행
|
||||
|
||||
/// <summary>
|
||||
/// 경로 실행 시작
|
||||
/// </summary>
|
||||
/// <param name="path">실행할 경로</param>
|
||||
/// <param name="mapNodes">맵 노드 목록</param>
|
||||
public void StartPath(AGVPathResult path, List<MapNode> mapNodes)
|
||||
public void ExecutePath(AGVPathResult path, List<MapNode> 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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 간단한 경로 실행 (경로 객체 없이 노드만)
|
||||
/// </summary>
|
||||
public void StartPath(AGVPathResult path, List<MapNode> mapNodes)
|
||||
{
|
||||
ExecutePath(path, mapNodes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 경로 정지
|
||||
/// </summary>
|
||||
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 - 프레임 업데이트 (외부에서 정기적으로 호출)
|
||||
|
||||
/// <summary>
|
||||
/// 프레임 업데이트 (외부에서 주기적으로 호출)
|
||||
/// 이 방식으로 타이머에 의존하지 않고 외부에서 제어 가능
|
||||
/// </summary>
|
||||
/// <param name="deltaTimeMs">마지막 업데이트 이후 경과 시간 (밀리초)</param>
|
||||
public void Update(float deltaTimeMs)
|
||||
{
|
||||
var deltaTime = deltaTimeMs / 1000.0f; // 초 단위로 변환
|
||||
|
||||
UpdateMovement(deltaTime);
|
||||
UpdateBattery(deltaTime);
|
||||
|
||||
// 위치 변경 이벤트 발생
|
||||
PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods - 수동 제어 (테스트용)
|
||||
|
||||
/// <summary>
|
||||
/// 수동 이동 (테스트용)
|
||||
/// </summary>
|
||||
@@ -272,7 +362,7 @@ namespace AGVSimulator.Models
|
||||
_moveProgress = 0;
|
||||
|
||||
SetState(AGVState.Moving);
|
||||
_moveTimer.Start();
|
||||
_isMoving = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -285,51 +375,18 @@ namespace AGVSimulator.Models
|
||||
return;
|
||||
|
||||
SetState(AGVState.Rotating);
|
||||
|
||||
// 시뮬레이션: 즉시 방향 변경 (실제로는 시간이 걸림)
|
||||
_currentDirection = direction;
|
||||
|
||||
System.Threading.Thread.Sleep(500); // 회전 시간 시뮬레이션
|
||||
SetState(AGVState.Idle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AGV 위치 직접 설정 (시뮬레이터용)
|
||||
/// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함
|
||||
/// </summary>
|
||||
/// <param name="newPosition">새로운 위치</param>
|
||||
/// <param name="motorDirection">모터이동방향</param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 충전 시작 (시뮬레이션)
|
||||
/// 충전 시작
|
||||
/// </summary>
|
||||
public void StartCharging()
|
||||
{
|
||||
if (_currentState == AGVState.Idle)
|
||||
{
|
||||
SetState(AGVState.Charging);
|
||||
// 충전 시뮬레이션 시작
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,14 +401,34 @@ namespace AGVSimulator.Models
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods - AGV 위치 설정 (시뮬레이터용)
|
||||
|
||||
/// <summary>
|
||||
/// AGV 정보 조회
|
||||
/// AGV 위치 직접 설정
|
||||
/// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함
|
||||
/// </summary>
|
||||
public string GetStatus()
|
||||
/// <param name="node">현재 노드</param>
|
||||
/// <param name="newPosition">새로운 위치</param>
|
||||
/// <param name="motorDirection">모터이동방향</param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -359,12 +436,10 @@ namespace AGVSimulator.Models
|
||||
/// </summary>
|
||||
public string SimulateRfidReading(List<MapNode> 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
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_moveTimer?.Stop();
|
||||
_moveTimer?.Dispose();
|
||||
StopPath();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// AGV 경로 계산
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// 통합 경로 계획 (직접 경로 또는 방향 전환 경로)
|
||||
/// </summary>
|
||||
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);
|
||||
@@ -12,6 +12,100 @@ namespace AGVNavigationCore.Utils
|
||||
/// </summary>
|
||||
public static class LiftCalculator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 경로 예측 기반 리프트 방향 계산
|
||||
/// 현재 노드에서 연결된 다음 노드들을 분석하여 리프트 방향 결정
|
||||
/// </summary>
|
||||
/// <param name="currentPos">현재 위치</param>
|
||||
/// <param name="previousPos">이전 위치</param>
|
||||
/// <param name="motorDirection">모터 방향</param>
|
||||
/// <param name="mapNodes">맵 노드 리스트 (경로 예측용)</param>
|
||||
/// <param name="tolerance">위치 허용 오차</param>
|
||||
/// <returns>리프트 계산 결과</returns>
|
||||
public static LiftCalculationResult CalculateLiftInfoWithPathPrediction(
|
||||
Point currentPos, Point previousPos, AgvDirection motorDirection,
|
||||
List<MapNode> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 이동 방향과 모터 방향을 기반으로 리프트 각도 계산
|
||||
/// </summary>
|
||||
@@ -146,98 +240,7 @@ namespace AGVNavigationCore.Utils
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 경로 예측 기반 리프트 방향 계산
|
||||
/// 현재 노드에서 연결된 다음 노드들을 분석하여 리프트 방향 결정
|
||||
/// </summary>
|
||||
/// <param name="currentPos">현재 위치</param>
|
||||
/// <param name="previousPos">이전 위치</param>
|
||||
/// <param name="motorDirection">모터 방향</param>
|
||||
/// <param name="mapNodes">맵 노드 리스트 (경로 예측용)</param>
|
||||
/// <param name="tolerance">위치 허용 오차</param>
|
||||
/// <returns>리프트 계산 결과</returns>
|
||||
public static LiftCalculationResult CalculateLiftInfoWithPathPrediction(
|
||||
Point currentPos, Point previousPos, AgvDirection motorDirection,
|
||||
List<MapNode> 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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 위치 기반 노드 찾기
|
||||
@@ -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
|
||||
561
Cs_HMI/AGVLogic/AGVSimulator/Models/VirtualAGV.cs
Normal file
561
Cs_HMI/AGVLogic/AGVSimulator/Models/VirtualAGV.cs
Normal file
@@ -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
|
||||
//{
|
||||
|
||||
// /// <summary>
|
||||
// /// 가상 AGV 클래스
|
||||
// /// 실제 AGV의 동작을 시뮬레이션
|
||||
// /// </summary>
|
||||
// public class VirtualAGV : IAGV
|
||||
// {
|
||||
// #region Events
|
||||
|
||||
// /// <summary>
|
||||
// /// AGV 상태 변경 이벤트
|
||||
// /// </summary>
|
||||
// public event EventHandler<AGVState> StateChanged;
|
||||
|
||||
// /// <summary>
|
||||
// /// 위치 변경 이벤트
|
||||
// /// </summary>
|
||||
// public event EventHandler<(Point, AgvDirection, MapNode)> PositionChanged;
|
||||
|
||||
// /// <summary>
|
||||
// /// RFID 감지 이벤트
|
||||
// /// </summary>
|
||||
// public event EventHandler<string> RfidDetected;
|
||||
|
||||
// /// <summary>
|
||||
// /// 경로 완료 이벤트
|
||||
// /// </summary>
|
||||
// public event EventHandler<AGVPathResult> PathCompleted;
|
||||
|
||||
// /// <summary>
|
||||
// /// 오류 발생 이벤트
|
||||
// /// </summary>
|
||||
// public event EventHandler<string> 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<string> _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
|
||||
|
||||
// /// <summary>
|
||||
// /// AGV ID
|
||||
// /// </summary>
|
||||
// public string AgvId => _agvId;
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 위치
|
||||
// /// </summary>
|
||||
// public Point CurrentPosition
|
||||
// {
|
||||
// get => _currentPosition;
|
||||
// set => _currentPosition = value;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 방향
|
||||
// /// 모터의 동작 방향
|
||||
// /// </summary>
|
||||
// public AgvDirection CurrentDirection
|
||||
// {
|
||||
// get => _currentDirection;
|
||||
// set => _currentDirection = value;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 상태
|
||||
// /// </summary>
|
||||
// public AGVState CurrentState
|
||||
// {
|
||||
// get => _currentState;
|
||||
// set => _currentState = value;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 속도
|
||||
// /// </summary>
|
||||
// public float CurrentSpeed => _currentSpeed;
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 경로
|
||||
// /// </summary>
|
||||
// public AGVPathResult CurrentPath => _currentPath;
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 노드 ID
|
||||
// /// </summary>
|
||||
// public string CurrentNodeId => _currentNode.NodeId;
|
||||
|
||||
// /// <summary>
|
||||
// /// 목표 위치
|
||||
// /// </summary>
|
||||
// public Point? TargetPosition => _targetPosition;
|
||||
|
||||
// /// <summary>
|
||||
// /// 배터리 레벨 (시뮬레이션)
|
||||
// /// </summary>
|
||||
// public float BatteryLevel { get; set; } = 100.0f;
|
||||
|
||||
// /// <summary>
|
||||
// /// 목표 노드 ID
|
||||
// /// </summary>
|
||||
// public string TargetNodeId => _targetNode.NodeId;
|
||||
|
||||
// /// <summary>
|
||||
// /// 도킹 방향
|
||||
// /// </summary>
|
||||
// public DockingDirection DockingDirection => _dockingDirection;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Constructor
|
||||
|
||||
// /// <summary>
|
||||
// /// 생성자
|
||||
// /// </summary>
|
||||
// /// <param name="agvId">AGV ID</param>
|
||||
// /// <param name="startPosition">시작 위치</param>
|
||||
// /// <param name="startDirection">시작 방향</param>
|
||||
// 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
|
||||
|
||||
// /// <summary>
|
||||
// /// 경로 실행 시작
|
||||
// /// </summary>
|
||||
// /// <param name="path">실행할 경로</param>
|
||||
// /// <param name="mapNodes">맵 노드 목록</param>
|
||||
// public void StartPath(AGVPathResult path, List<MapNode> mapNodes)
|
||||
// {
|
||||
// if (path == null || !path.Success)
|
||||
// {
|
||||
// OnError("유효하지 않은 경로입니다.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// _currentPath = path;
|
||||
// _remainingNodes = new List<string>(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]}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 경로 정지
|
||||
// /// </summary>
|
||||
// public void StopPath()
|
||||
// {
|
||||
// _moveTimer.Stop();
|
||||
// _currentPath = null;
|
||||
// _remainingNodes?.Clear();
|
||||
// SetState(AGVState.Idle);
|
||||
// _currentSpeed = 0;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 긴급 정지
|
||||
// /// </summary>
|
||||
// public void EmergencyStop()
|
||||
// {
|
||||
// StopPath();
|
||||
// OnError("긴급 정지가 실행되었습니다.");
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 수동 이동 (테스트용)
|
||||
// /// </summary>
|
||||
// /// <param name="targetPosition">목표 위치</param>
|
||||
// public void MoveTo(Point targetPosition)
|
||||
// {
|
||||
// _targetPosition = targetPosition;
|
||||
// _moveStartPosition = _currentPosition;
|
||||
// _moveTargetPosition = targetPosition;
|
||||
// _moveProgress = 0;
|
||||
|
||||
// SetState(AGVState.Moving);
|
||||
// _moveTimer.Start();
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 수동 회전 (테스트용)
|
||||
// /// </summary>
|
||||
// /// <param name="direction">회전 방향</param>
|
||||
// public void Rotate(AgvDirection direction)
|
||||
// {
|
||||
// if (_currentState != AGVState.Idle)
|
||||
// return;
|
||||
|
||||
// SetState(AGVState.Rotating);
|
||||
|
||||
// // 시뮬레이션: 즉시 방향 변경 (실제로는 시간이 걸림)
|
||||
// _currentDirection = direction;
|
||||
|
||||
// System.Threading.Thread.Sleep(500); // 회전 시간 시뮬레이션
|
||||
// SetState(AGVState.Idle);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// AGV 위치 직접 설정 (시뮬레이터용)
|
||||
// /// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함
|
||||
// /// </summary>
|
||||
// /// <param name="newPosition">새로운 위치</param>
|
||||
// /// <param name="motorDirection">모터이동방향</param>
|
||||
// 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));
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 충전 시작 (시뮬레이션)
|
||||
// /// </summary>
|
||||
// public void StartCharging()
|
||||
// {
|
||||
// if (_currentState == AGVState.Idle)
|
||||
// {
|
||||
// SetState(AGVState.Charging);
|
||||
// // 충전 시뮬레이션 시작
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 충전 종료
|
||||
// /// </summary>
|
||||
// public void StopCharging()
|
||||
// {
|
||||
// if (_currentState == AGVState.Charging)
|
||||
// {
|
||||
// SetState(AGVState.Idle);
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// AGV 정보 조회
|
||||
// /// </summary>
|
||||
// public string GetStatus()
|
||||
// {
|
||||
// return $"AGV[{_agvId}] 위치:({_currentPosition.X},{_currentPosition.Y}) " +
|
||||
// $"방향:{_currentDirection} 상태:{_currentState} " +
|
||||
// $"속도:{_currentSpeed:F1} 배터리:{BatteryLevel:F1}%";
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 현재 RFID 시뮬레이션 (현재 위치 기준)
|
||||
// /// </summary>
|
||||
// public string SimulateRfidReading(List<MapNode> 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<MapNode> 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
|
||||
|
||||
// /// <summary>
|
||||
// /// 리소스 정리
|
||||
// /// </summary>
|
||||
// public void Dispose()
|
||||
// {
|
||||
// _moveTimer?.Stop();
|
||||
// _moveTimer?.Dispose();
|
||||
// }
|
||||
|
||||
// #endregion
|
||||
// }
|
||||
//}
|
||||
221
Cs_HMI/AGVLogic/CLAUDE.md
Normal file
221
Cs_HMI/AGVLogic/CLAUDE.md
Normal file
@@ -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 폴더 기준으로 정리
|
||||
13
Cs_HMI/AGVLogic/build.bat
Normal file
13
Cs_HMI/AGVLogic/build.bat
Normal file
@@ -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
|
||||
1
Cs_HMI/AGVLogic/run_claude.bat
Normal file
1
Cs_HMI/AGVLogic/run_claude.bat
Normal file
@@ -0,0 +1 @@
|
||||
claude --dangerously-skip-permissions
|
||||
@@ -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 방향 표시 수정 및 타겟계산 기능 추가
|
||||
|
||||
### 🐛 버그 수정
|
||||
|
||||
@@ -92,7 +92,6 @@
|
||||
<Compile Include="Models\CustomLine.cs" />
|
||||
<Compile Include="Models\enumStruct.cs" />
|
||||
<Compile Include="Models\MagnetLine.cs" />
|
||||
<Compile Include="Models\MapData.cs" />
|
||||
<Compile Include="Models\MapElements.cs" />
|
||||
<Compile Include="Models\MapText.cs" />
|
||||
<Compile Include="Models\PathResult.cs" />
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user