diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs index 15c9ecd..86aa676 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs @@ -69,4 +69,90 @@ namespace AGVNavigationCore.Models /// 충전기 Charger } + + /// + /// 모터 명령 열거형 (실제 AGV 제어용) + /// + public enum MotorCommand + { + /// 정지 + Stop, + /// 전진 (Forward - 모니터 방향) + Forward, + /// 후진 (Backward - 리프트 방향) + Backward + } + + /// + /// 마그넷 위치 열거형 (실제 AGV 제어용) + /// + public enum MagnetPosition + { + /// 직진 (Straight) + S, + /// 왼쪽 (Left) + L, + /// 오른쪽 (Right) + R + } + + /// + /// 속도 레벨 열거형 (실제 AGV 제어용) + /// + public enum SpeedLevel + { + /// 저속 (Low) + L, + /// 중속 (Medium) + M, + /// 고속 (High) + H + } + + /// + /// AGV 제어 명령 클래스 (실제 AGV 제어용) + /// Predict() 메서드가 반환하는 다음 동작 명령 + /// + public class AGVCommand + { + /// 모터 명령 (정지/전진/후진) + public MotorCommand Motor { get; set; } + + /// 마그넷 위치 (직진/왼쪽/오른쪽) + public MagnetPosition Magnet { get; set; } + + /// 속도 레벨 (저속/중속/고속) + public SpeedLevel Speed { get; set; } + + /// 명령 이유 (디버깅/로깅용) + public string Reason { get; set; } + + /// + /// 생성자 + /// + public AGVCommand(MotorCommand motor, MagnetPosition magnet, SpeedLevel speed, string reason = "") + { + Motor = motor; + Magnet = magnet; + Speed = speed; + Reason = reason; + } + + /// + /// 기본 생성자 + /// + public AGVCommand() + { + Motor = MotorCommand.Stop; + Magnet = MagnetPosition.S; + Speed = SpeedLevel.L; + Reason = ""; + } + + public override string ToString() + { + return $"Motor:{Motor}, Magnet:{Magnet}, Speed:{Speed}" + + (string.IsNullOrEmpty(Reason) ? "" : $" ({Reason})"); + } + } } \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs index f200ce6..848a453 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs @@ -75,6 +75,10 @@ namespace AGVNavigationCore.Models private readonly float _moveSpeed = 50.0f; // 픽셀/초 private bool _isMoving; + // RFID 위치 추적 (실제 AGV용) + private List _detectedRfids = new List(); // 감지된 RFID 목록 + private bool _isPositionConfirmed = false; // 위치 확정 여부 (RFID 2개 이상 감지) + #endregion #region Properties @@ -159,6 +163,16 @@ namespace AGVNavigationCore.Models /// public DockingDirection DockingDirection => _dockingDirection; + /// + /// 위치 확정 여부 (RFID 2개 이상 감지 시 true) + /// + public bool IsPositionConfirmed => _isPositionConfirmed; + + /// + /// 감지된 RFID 개수 + /// + public int DetectedRfidCount => _detectedRfids.Count; + #endregion #region Constructor @@ -200,6 +214,18 @@ namespace AGVNavigationCore.Models /// public void SetDetectedRfid(string rfidId) { + // RFID 목록에 추가 (중복 제거) + if (!_detectedRfids.Contains(rfidId)) + { + _detectedRfids.Add(rfidId); + } + + // RFID 2개 이상 감지 시 위치 확정 + if (_detectedRfids.Count >= 2 && !_isPositionConfirmed) + { + _isPositionConfirmed = true; + } + RfidDetected?.Invoke(this, rfidId); } @@ -225,6 +251,172 @@ namespace AGVNavigationCore.Models } } + /// + /// 다음 동작 예측 (실제 AGV 제어용) + /// AGV가 지속적으로 호출하여 현재 상태와 예측 상태를 일치시킴 + /// + /// 다음에 수행할 모터/마그넷/속도 명령 + public AGVCommand Predict() + { + // 1. 위치 미확정 상태 (RFID 2개 미만 감지) + if (!_isPositionConfirmed) + { + // 항상 전진 + 저속으로 이동 (RFID 감지 대기) + return new AGVCommand( + MotorCommand.Forward, + MagnetPosition.S, // 직진 + SpeedLevel.L, // 저속 + $"위치 미확정 (RFID {_detectedRfids.Count}/2) - 전진하여 RFID 탐색" + ); + } + + // 2. 위치 확정됨 + 경로 없음 → 정지 (목적지 미설정 상태) + if (_currentPath == null || (_currentPath.DetailedPath?.Count ?? 0) < 1) + { + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + $"위치 확정 완료 (목적지 미설정) - 현재:{_currentNode?.NodeId ?? "알수없음"}" + ); + } + + // 3. 위치 확정됨 + 경로 있음 + 남은 노드 없음 → 정지 (목적지 도착) + var lastNode = _currentPath.DetailedPath.Last(); + if (_currentPath.DetailedPath.Where(t => t.seq < lastNode.seq && t.IsPass == false).Any() == false) + { + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + $"목적지 도착 - 최종:{_currentNode?.NodeId ?? "알수없음"}" + ); + } + + // 4. 경로이탈 + var TargetNode = _currentPath.DetailedPath.Where(t => t.IsPass == false && t.NodeId.Equals(_currentNode.NodeId)).FirstOrDefault(); + if (TargetNode == null) + { + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + $"(재탐색요청)경로이탈 현재위치:{_currentNode.NodeId}" + ); + } + + // 5. 방향체크 + if(CurrentDirection != TargetNode.MotorDirection) + { + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + $"(재탐색요청)모터방향 불일치 현재위치:{_currentNode.NodeId}" + ); + } + + + //this.CurrentNodeId + + return GetCommandFromPath(CurrentNodeId, "경로 실행 시작"); + + // 4. 위치 확정 + 경로 실행 중 → 현재 상태에 따른 명령 예측 + switch (_currentState) + { + case AGVState.Idle: + // 🔥 경로가 있다면 이동 시작 (경로 실행 대기 중) + if (_currentPath != null && _remainingNodes != null && _remainingNodes.Count > 0) + { + // DetailedPath에서 다음 노드 정보 가져오기 + return GetCommandFromPath(_remainingNodes[0], "경로 실행 시작"); + } + + // 경로가 없으면 대기 + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + "대기 중 (경로 없음)" + ); + + case AGVState.Moving: + { + // 이동 중 - DetailedPath에서 현재/다음 노드 정보 가져오기 + if (_currentPath != null && _remainingNodes != null && _remainingNodes.Count > 0) + { + return GetCommandFromPath(_remainingNodes[0], "이동 중"); + } + + // 경로 정보가 없으면 기본 명령 (fallback) + var motorCmd = _currentDirection == AgvDirection.Forward + ? MotorCommand.Forward + : MotorCommand.Backward; + + return new AGVCommand( + motorCmd, + MagnetPosition.S, + SpeedLevel.M, + $"이동 중 (DetailedPath 없음)" + ); + } + + case AGVState.Rotating: + // 회전 중 - 정지 상태에서 마그넷만 조정 + MagnetPosition magnetPos = MagnetPosition.S; + if (_currentDirection == AgvDirection.Left) + magnetPos = MagnetPosition.L; + else if (_currentDirection == AgvDirection.Right) + magnetPos = MagnetPosition.R; + + return new AGVCommand( + MotorCommand.Stop, // 회전은 정지 상태에서 + magnetPos, + SpeedLevel.L, + $"회전 중 ({_currentDirection})" + ); + + case AGVState.Docking: + { + // 도킹 중 - 저속으로 전진 또는 후진 + var dockingMotor = _dockingDirection == DockingDirection.Forward + ? MotorCommand.Forward + : MotorCommand.Backward; + + return new AGVCommand( + dockingMotor, + MagnetPosition.S, + SpeedLevel.L, // 도킹은 항상 저속 + $"도킹 중 ({_dockingDirection})" + ); + } + + case AGVState.Charging: + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + "충전 중" + ); + + case AGVState.Error: + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + "오류 발생" + ); + + default: + return new AGVCommand( + MotorCommand.Stop, + MagnetPosition.S, + SpeedLevel.L, + "알 수 없는 상태" + ); + } + } + #endregion #region Public Methods - 상태 조회 @@ -258,8 +450,29 @@ namespace AGVNavigationCore.Models #region Public Methods - 경로 실행 - - + /// + /// 경로 설정 (실제 AGV 및 시뮬레이터에서 사용) + /// + /// 실행할 경로 + public void SetPath(AGVPathResult path) + { + if (path == null) + { + OnError("경로가 null입니다."); + return; + } + + _currentPath = path; + _remainingNodes = path.Path.Select(n => n.NodeId).ToList(); // MapNode → NodeId 변환 + _currentNodeIndex = 0; + + // 경로 시작 노드가 현재 노드와 다른 경우 경고 + if (_currentNode != null && _remainingNodes.Count > 0 && _remainingNodes[0] != _currentNode.NodeId) + { + OnError($"경로 시작 노드({_remainingNodes[0]})와 현재 노드({_currentNode.NodeId})가 다릅니다."); + } + } + /// /// 경로 정지 /// @@ -388,6 +601,31 @@ namespace AGVNavigationCore.Models _currentDirection = motorDirection; _currentNode = node; + // 🔥 노드 ID를 RFID로 간주하여 감지 목록에 추가 (시뮬레이터용) + if (!string.IsNullOrEmpty(node.NodeId) && !_detectedRfids.Contains(node.NodeId)) + { + _detectedRfids.Add(node.NodeId); + } + + // 🔥 RFID 2개 이상 감지 시 위치 확정 + if (_detectedRfids.Count >= 2 && !_isPositionConfirmed) + { + _isPositionConfirmed = true; + } + + //현재 경로값이 있는지 확인한다. + if (CurrentPath != null && CurrentPath.DetailedPath != null && CurrentPath.DetailedPath.Any()) + { + var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.NodeId && t.IsPass == false); + if (item != null) + { + //item.IsPass = true; + + //이전노드는 모두 지나친걸로 한다 + CurrentPath.DetailedPath.Where(t => t.seq < item.seq).ToList().ForEach(t => t.IsPass = true); + } + } + // 위치 변경 이벤트 발생 PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode)); } @@ -408,6 +646,89 @@ namespace AGVNavigationCore.Models #region Private Methods + /// + /// DetailedPath에서 노드 정보를 찾아 AGVCommand 생성 + /// + private AGVCommand GetCommandFromPath(string targetNodeId, string actionDescription) + { + // DetailedPath가 없으면 기본 명령 반환 + if (_currentPath == null || _currentPath.DetailedPath == null || _currentPath.DetailedPath.Count == 0) + { + var defaultMotor = _currentDirection == AgvDirection.Forward + ? MotorCommand.Forward + : MotorCommand.Backward; + + return new AGVCommand( + defaultMotor, + MagnetPosition.S, + SpeedLevel.M, + $"{actionDescription} (DetailedPath 없음)" + ); + } + + // DetailedPath에서 targetNodeId에 해당하는 NodeMotorInfo 찾기 + // 지나가지 않은 경로를 찾는다 + var nodeInfo = _currentPath.DetailedPath.FirstOrDefault(n => n.NodeId == targetNodeId && n.IsPass == false); + + if (nodeInfo == null) + { + // 못 찾으면 기본 명령 반환 + var defaultMotor = _currentDirection == AgvDirection.Forward + ? MotorCommand.Forward + : MotorCommand.Backward; + + return new AGVCommand( + defaultMotor, + MagnetPosition.S, + SpeedLevel.M, + $"{actionDescription} (노드 {targetNodeId} 정보 없음)" + ); + } + + // MotorDirection → MotorCommand 변환 + MotorCommand motorCmd; + switch (nodeInfo.MotorDirection) + { + case AgvDirection.Forward: + motorCmd = MotorCommand.Forward; + break; + case AgvDirection.Backward: + motorCmd = MotorCommand.Backward; + break; + default: + motorCmd = MotorCommand.Stop; + break; + } + + // MagnetDirection → MagnetPosition 변换 + MagnetPosition magnetPos; + switch (nodeInfo.MagnetDirection) + { + case PathFinding.Planning.MagnetDirection.Left: + magnetPos = MagnetPosition.L; + break; + case PathFinding.Planning.MagnetDirection.Right: + magnetPos = MagnetPosition.R; + break; + case PathFinding.Planning.MagnetDirection.Straight: + default: + magnetPos = MagnetPosition.S; + break; + } + + // 속도 결정 (회전 노드면 저속, 일반 이동은 중속) + SpeedLevel speed = nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint + ? SpeedLevel.L + : SpeedLevel.M; + + return new AGVCommand( + motorCmd, + magnetPos, + speed, + $"{actionDescription} → {targetNodeId} (Motor:{motorCmd}, Magnet:{magnetPos})" + ); + } + private void StartMovement() { SetState(AGVState.Moving); @@ -559,7 +880,7 @@ namespace AGVNavigationCore.Models #endregion - + #region Cleanup diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index 177ec0f..76b37e2 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -111,7 +111,7 @@ namespace AGVNavigationCore.PathFinding.Planning return null; } - public AGVPathResult FindPath_test(MapNode startNode, MapNode targetNode, + public AGVPathResult FindPath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection, bool crossignore = false) { // 입력 검증 @@ -154,6 +154,8 @@ namespace AGVNavigationCore.PathFinding.Planning { MakeDetailData(pathResult, currentDirection); MakeMagnetDirection(pathResult); + for (int i = 0; i < pathResult.DetailedPath.Count; i++) + pathResult.DetailedPath[i].seq = i + 1; return pathResult; } } @@ -182,6 +184,8 @@ namespace AGVNavigationCore.PathFinding.Planning { MakeDetailData(pathResult, ReverseDirection); MakeMagnetDirection(pathResult); + for (int i = 0; i < pathResult.DetailedPath.Count; i++) + pathResult.DetailedPath[i].seq = i + 1; return pathResult; } } @@ -196,6 +200,8 @@ namespace AGVNavigationCore.PathFinding.Planning { MakeDetailData(pathResult, currentDirection); MakeMagnetDirection(pathResult); + for (int i = 0; i < pathResult.DetailedPath.Count; i++) + pathResult.DetailedPath[i].seq = i + 1; return pathResult; } @@ -220,7 +226,7 @@ namespace AGVNavigationCore.PathFinding.Planning Path0.PrevDirection = prevDirection; MakeDetailData(Path0, prevDirection); - var Path1 = FindPath_test(nextNodeForward, targetNode, startNode, prevDirection, currentDirection, true); + var Path1 = FindPath(nextNodeForward, targetNode, startNode, prevDirection, currentDirection, true); Path1.PrevNode = startNode; Path1.PrevDirection = prevDirection; //MakeDetailData(Path1, ReverseDirection); @@ -228,6 +234,9 @@ namespace AGVNavigationCore.PathFinding.Planning var combinedResult0 = Path0; combinedResult0 = _basicPathfinder.CombineResults(combinedResult0, Path1); MakeMagnetDirection(combinedResult0); + + for (int i = 0; i < combinedResult0.DetailedPath.Count; i++) + combinedResult0.DetailedPath[i].seq = i + 1; return combinedResult0; } else if (nextNodeBackward != null) @@ -404,6 +413,8 @@ namespace AGVNavigationCore.PathFinding.Planning MakeMagnetDirection(combinedResult); + for (int i = 0; i < combinedResult.DetailedPath.Count; i++) + combinedResult.DetailedPath[i].seq = i + 1; return combinedResult; @@ -427,7 +438,7 @@ namespace AGVNavigationCore.PathFinding.Planning string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null; // 노드 정보 생성 (현재 방향 유지) - var nodeInfo = new NodeMotorInfo( + var nodeInfo = new NodeMotorInfo(i+1, nodeId, RfidId, currentDirection, nextNodeId, diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs index b8096e0..cdcbd7e 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs @@ -28,6 +28,10 @@ namespace AGVNavigationCore.PathFinding.Planning /// public class NodeMotorInfo { + /// + /// 일련번호 + /// + public int seq { get; set; } /// /// 노드 ID /// @@ -68,13 +72,19 @@ namespace AGVNavigationCore.PathFinding.Planning /// public bool RequiresSpecialAction { get; set; } + /// + /// 해당노드가 인식되면 이 값이 셋팅됩니다. + /// + public bool IsPass { get; set; } + /// /// 특수 동작 설명 /// public string SpecialActionDescription { get; set; } - public NodeMotorInfo(string nodeId,string rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight) + public NodeMotorInfo(int seqno,string nodeId,string rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight) { + seq = seqno; NodeId = nodeId; RfidId = rfid; MotorDirection = motorDirection; @@ -84,6 +94,7 @@ namespace AGVNavigationCore.PathFinding.Planning IsDirectionChangePoint = false; RequiresSpecialAction = false; SpecialActionDescription = string.Empty; + IsPass = false; } diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs index 0ebf626..8f33df4 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs @@ -45,6 +45,7 @@ namespace AGVSimulator.Forms /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SimulatorForm)); this._menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -77,6 +78,7 @@ namespace AGVSimulator.Forms this.resetZoomToolStripButton = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripButton1 = new System.Windows.Forms.ToolStripButton(); + this.btPredict = new System.Windows.Forms.ToolStripButton(); this._statusStrip = new System.Windows.Forms.StatusStrip(); this._statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); this._coordLabel = new System.Windows.Forms.ToolStripStatusLabel(); @@ -112,6 +114,8 @@ namespace AGVSimulator.Forms this._agvInfoTitleLabel = new System.Windows.Forms.Label(); this._liftDirectionLabel = new System.Windows.Forms.Label(); this._motorDirectionLabel = new System.Windows.Forms.Label(); + this.lbPredict = new System.Windows.Forms.RichTextBox(); + this.timer1 = new System.Windows.Forms.Timer(this.components); this._menuStrip.SuspendLayout(); this._toolStrip.SuspendLayout(); this._statusStrip.SuspendLayout(); @@ -119,6 +123,7 @@ namespace AGVSimulator.Forms this._statusGroup.SuspendLayout(); this._pathGroup.SuspendLayout(); this._agvControlGroup.SuspendLayout(); + this._canvasPanel.SuspendLayout(); this._agvInfoPanel.SuspendLayout(); this.SuspendLayout(); // @@ -131,7 +136,7 @@ namespace AGVSimulator.Forms this.helpToolStripMenuItem}); this._menuStrip.Location = new System.Drawing.Point(0, 0); this._menuStrip.Name = "_menuStrip"; - this._menuStrip.Size = new System.Drawing.Size(1200, 24); + this._menuStrip.Size = new System.Drawing.Size(1034, 24); this._menuStrip.TabIndex = 0; this._menuStrip.Text = "menuStrip"; // @@ -279,10 +284,11 @@ namespace AGVSimulator.Forms this.fitToMapToolStripButton, this.resetZoomToolStripButton, this.toolStripSeparator5, - this.toolStripButton1}); + this.toolStripButton1, + this.btPredict}); this._toolStrip.Location = new System.Drawing.Point(0, 24); this._toolStrip.Name = "_toolStrip"; - this._toolStrip.Size = new System.Drawing.Size(1200, 25); + this._toolStrip.Size = new System.Drawing.Size(1034, 25); this._toolStrip.TabIndex = 1; this._toolStrip.Text = "toolStrip"; // @@ -387,19 +393,28 @@ namespace AGVSimulator.Forms this.toolStripButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton1.Image"))); this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta; this.toolStripButton1.Name = "toolStripButton1"; - this.toolStripButton1.Size = new System.Drawing.Size(75, 22); - this.toolStripButton1.Text = "경로예측"; + this.toolStripButton1.Size = new System.Drawing.Size(111, 22); + this.toolStripButton1.Text = "전체경로테스트"; this.toolStripButton1.Click += new System.EventHandler(this.toolStripButton1_Click); // + // btPredict + // + this.btPredict.Image = ((System.Drawing.Image)(resources.GetObject("btPredict.Image"))); + this.btPredict.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btPredict.Name = "btPredict"; + this.btPredict.Size = new System.Drawing.Size(107, 22); + this.btPredict.Text = "다음 행동 예측"; + this.btPredict.Click += new System.EventHandler(this.btPredict_Click); + // // _statusStrip // this._statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this._statusLabel, this._coordLabel, this.prb1}); - this._statusStrip.Location = new System.Drawing.Point(0, 778); + this._statusStrip.Location = new System.Drawing.Point(0, 689); this._statusStrip.Name = "_statusStrip"; - this._statusStrip.Size = new System.Drawing.Size(1200, 22); + this._statusStrip.Size = new System.Drawing.Size(1034, 22); this._statusStrip.TabIndex = 2; this._statusStrip.Text = "statusStrip"; // @@ -426,9 +441,9 @@ namespace AGVSimulator.Forms this._controlPanel.Controls.Add(this._pathGroup); this._controlPanel.Controls.Add(this._agvControlGroup); this._controlPanel.Dock = System.Windows.Forms.DockStyle.Right; - this._controlPanel.Location = new System.Drawing.Point(967, 49); + this._controlPanel.Location = new System.Drawing.Point(801, 49); this._controlPanel.Name = "_controlPanel"; - this._controlPanel.Size = new System.Drawing.Size(233, 729); + this._controlPanel.Size = new System.Drawing.Size(233, 640); this._controlPanel.TabIndex = 3; // // _statusGroup @@ -679,10 +694,11 @@ namespace AGVSimulator.Forms // // _canvasPanel // + this._canvasPanel.Controls.Add(this.lbPredict); this._canvasPanel.Dock = System.Windows.Forms.DockStyle.Fill; this._canvasPanel.Location = new System.Drawing.Point(0, 129); this._canvasPanel.Name = "_canvasPanel"; - this._canvasPanel.Size = new System.Drawing.Size(967, 649); + this._canvasPanel.Size = new System.Drawing.Size(801, 560); this._canvasPanel.TabIndex = 4; // // _agvInfoPanel @@ -696,7 +712,7 @@ namespace AGVSimulator.Forms this._agvInfoPanel.Dock = System.Windows.Forms.DockStyle.Top; this._agvInfoPanel.Location = new System.Drawing.Point(0, 49); this._agvInfoPanel.Name = "_agvInfoPanel"; - this._agvInfoPanel.Size = new System.Drawing.Size(967, 80); + this._agvInfoPanel.Size = new System.Drawing.Size(801, 80); this._agvInfoPanel.TabIndex = 5; // // _pathDebugLabel @@ -741,11 +757,25 @@ namespace AGVSimulator.Forms this._motorDirectionLabel.TabIndex = 2; this._motorDirectionLabel.Text = "모터 방향: -"; // + // lbPredict + // + this.lbPredict.Dock = System.Windows.Forms.DockStyle.Bottom; + this.lbPredict.Location = new System.Drawing.Point(0, 513); + this.lbPredict.Name = "lbPredict"; + this.lbPredict.Size = new System.Drawing.Size(801, 47); + this.lbPredict.TabIndex = 0; + this.lbPredict.Text = ""; + // + // timer1 + // + this.timer1.Interval = 500; + this.timer1.Tick += new System.EventHandler(this.timer1_Tick); + // // SimulatorForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1200, 800); + this.ClientSize = new System.Drawing.Size(1034, 711); this.Controls.Add(this._canvasPanel); this.Controls.Add(this._agvInfoPanel); this.Controls.Add(this._controlPanel); @@ -770,6 +800,7 @@ namespace AGVSimulator.Forms this._pathGroup.PerformLayout(); this._agvControlGroup.ResumeLayout(false); this._agvControlGroup.PerformLayout(); + this._canvasPanel.ResumeLayout(false); this._agvInfoPanel.ResumeLayout(false); this._agvInfoPanel.PerformLayout(); this.ResumeLayout(false); @@ -845,5 +876,8 @@ namespace AGVSimulator.Forms private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; private System.Windows.Forms.ToolStripButton toolStripButton1; private System.Windows.Forms.ToolStripProgressBar prb1; + private System.Windows.Forms.ToolStripButton btPredict; + private System.Windows.Forms.RichTextBox lbPredict; + private System.Windows.Forms.Timer timer1; } } \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs index c3b5c05..7ed36de 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs @@ -174,6 +174,8 @@ namespace AGVSimulator.Forms _statusLabel.Text = "시뮬레이션 실행 중"; Console.WriteLine("시뮬레이션 실행"); UpdateUI(); + + timer1.Start(); } private void OnStopSimulation_Click(object sender, EventArgs e) @@ -317,7 +319,7 @@ namespace AGVSimulator.Forms var prevDir = selectedAGV.PrevDirection; // 고급 경로 계획 사용 (노드 객체 직접 전달) - var advancedResult = _advancedPathfinder.FindPath_test(startNode, targetNode, prevNode, prevDir, currentDirection); + var advancedResult = _advancedPathfinder.FindPath(startNode, targetNode, prevNode, prevDir, currentDirection); _simulatorCanvas.FitToNodes(); if (advancedResult.Success) @@ -325,10 +327,6 @@ namespace AGVSimulator.Forms // 도킹 검증이 없는 경우 추가 검증 수행 if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired) { - if (advancedResult.Path.Count < 1) - { - - } advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes); } @@ -336,6 +334,9 @@ namespace AGVSimulator.Forms _pathLengthLabel.Text = $"경로 길이: {advancedResult.TotalDistance:F1}"; _statusLabel.Text = $"경로 계산 완료 ({advancedResult.CalculationTimeMs}ms)"; + // 🔥 VirtualAGV에도 경로 설정 (Predict()가 동작하려면 필요) + selectedAGV.SetPath(advancedResult); + // 도킹 검증 결과 확인 및 UI 표시 CheckAndDisplayDockingValidation(advancedResult); @@ -357,6 +358,12 @@ namespace AGVSimulator.Forms _simulatorCanvas.CurrentPath = null; _pathLengthLabel.Text = "경로 길이: -"; _statusLabel.Text = "경로 지움"; + + // 🔥 VirtualAGV의 경로도 정지 + if (_agvList != null && _agvList.Count > 0) + { + _agvList[0].StopPath(); + } } private void OnTargetCalc_Click(object sender, EventArgs e) @@ -590,7 +597,6 @@ namespace AGVSimulator.Forms // AGV 위치 및 방향 설정 _simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, targetNode, selectedDirection); - _simulatorCanvas.UpdateAGVDirection(selectedAGV.AgvId, selectedDirection); // VirtualAGV 객체의 위치와 방향 업데이트 selectedAGV.SetPosition(targetNode, selectedDirection); // 이전 위치 기억하도록 @@ -1576,6 +1582,49 @@ namespace AGVSimulator.Forms logForm.AppendLog($"성공률: {(double)successCount / totalTests * 100:F1}%"); } + private void btPredict_Click(object sender, EventArgs e) + { + // 다음 행동 예측 + if (_agvList == null || _agvList.Count == 0) + { + MessageBox.Show("AGV가 없습니다.", "예측 오류", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + // 첫 번째 AGV의 다음 행동 예측 + var agv = _agvList[0]; + var command = agv.Predict(); + + //this.lbPredict.Text = $"MOT:{command.Motor},MAG:{command.Magnet},SPD:{command.Speed}:{command.Reason}"; + + // 예측 결과 표시 + var message = $"[다음 행동 예측]\n\n" + + $"모터: {command.Motor}\n" + + $"마그넷: {command.Magnet}\n" + + $"속도: {command.Speed}\n" + + $"이유: {command.Reason}\n\n" + + $"---\n" + + $"현재 상태: {agv.CurrentState}\n" + + $"현재 방향: {agv.CurrentDirection}\n" + + $"위치 확정: {agv.IsPositionConfirmed} (RFID {agv.DetectedRfidCount}개)\n" + + $"현재 노드: {agv.CurrentNodeId ?? "없음"}"; + + Console.WriteLine(message); + } + + private void timer1_Tick(object sender, EventArgs e) + { + if (_agvList == null || _agvList.Count == 0) + { + // MessageBox.Show("AGV가 없습니다.", "예측 오류", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + // 첫 번째 AGV의 다음 행동 예측 + var agv = _agvList[0]; + var command = agv.Predict(); + this.lbPredict.Text = $"Motor:{command.Motor},Magnet:{command.Magnet},Speed:{command.Speed} : {command.Reason}"; + } } /// diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx index 3d64678..5d703c1 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx @@ -137,9 +137,27 @@ HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC nOccAdABIDXXE1nzAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK + YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X + /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t + I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM + cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh + 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD + lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A + HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb + 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC + nOccAdABIDXXE1nzAAAAAElFTkSuQmCC 237, 17 + + 352, 17 + \ No newline at end of file