using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using AGVMapEditor.Models; namespace AGVSimulator.Models { /// /// 가상 AGV 상태 /// public enum AGVState { Idle, // 대기 Moving, // 이동 중 Rotating, // 회전 중 Docking, // 도킹 중 Charging, // 충전 중 Error // 오류 } /// /// 가상 AGV 클래스 /// 실제 AGV의 동작을 시뮬레이션 /// public class VirtualAGV { #region Events /// /// AGV 상태 변경 이벤트 /// public event EventHandler StateChanged; /// /// 위치 변경 이벤트 /// public event EventHandler 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 AgvDirection _currentDirection; private AGVState _currentState; private float _currentSpeed; // 경로 관련 private PathResult _currentPath; private List _remainingNodes; private int _currentNodeIndex; private string _currentNodeId; // 이동 관련 private System.Windows.Forms.Timer _moveTimer; private DateTime _lastMoveTime; private Point _moveStartPosition; private Point _moveTargetPosition; private float _moveProgress; // 시뮬레이션 설정 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 => _currentPosition; /// /// 현재 방향 /// public AgvDirection CurrentDirection => _currentDirection; /// /// 현재 상태 /// public AGVState CurrentState => _currentState; /// /// 현재 속도 /// public float CurrentSpeed => _currentSpeed; /// /// 현재 경로 /// public PathResult CurrentPath => _currentPath; /// /// 현재 노드 ID /// public string CurrentNodeId => _currentNodeId; /// /// 목표 위치 /// public Point TargetPosition => _targetPosition; /// /// 배터리 레벨 (시뮬레이션) /// public float BatteryLevel { get; set; } = 100.0f; #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; 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(PathResult path, List mapNodes) { if (path == null || !path.Success) { OnError("유효하지 않은 경로입니다."); return; } _currentPath = path; _remainingNodes = new List(path.NodeSequence); _currentNodeIndex = 0; // 시작 노드 위치로 이동 if (_remainingNodes.Count > 0) { var startNode = mapNodes.FirstOrDefault(n => n.NodeId == _remainingNodes[0]); if (startNode != null) { _currentNodeId = startNode.NodeId; 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); } /// /// 충전 시작 (시뮬레이션) /// 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, List rfidMappings) { // 현재 위치에서 가장 가까운 노드 찾기 var closestNode = FindClosestNode(_currentPosition, mapNodes); if (closestNode == null) return null; // 해당 노드의 RFID 매핑 찾기 var mapping = rfidMappings.FirstOrDefault(m => m.LogicalNodeId == closestNode.NodeId); return mapping?.RfidId; } #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); } 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 void OnError(string message) { SetState(AGVState.Error); ErrorOccurred?.Invoke(this, message); } #endregion #region Cleanup /// /// 리소스 정리 /// public void Dispose() { _moveTimer?.Stop(); _moveTimer?.Dispose(); } #endregion } }