From 3712c290c92afbe0af0900fcd60a412dd5258b46 Mon Sep 17 00:00:00 2001 From: backuppc Date: Mon, 3 Nov 2025 17:34:35 +0900 Subject: [PATCH] feat: Implement AGV command prediction system for real-time control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add motor/magnet/speed enums and AGVCommand class for AGV control - Implement Predict() method for next action prediction based on path and state - Add RFID position tracking (requires 2 RFIDs for position confirmation) - Add SetPath() method to VirtualAGV for path management - Implement GetCommandFromPath() to extract motor/magnet/speed from DetailedPath - Add real-time prediction display in SimulatorForm (timer1_Tick) - Support automatic forward movement at low speed when position unconfirmed - Support stop command when destination reached or no destination set Key Features: - Position unconfirmed (RFID < 2): Forward + Straight + Low speed - Position confirmed + no destination: Stop (position found) - Position confirmed + destination reached: Stop (arrived) - Path execution: Motor/Magnet/Speed from DetailedPath NodeMotorInfo - Rotation nodes: Automatic low speed - Junction handling: Magnet direction (Left/Right/Straight) ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../AGVNavigationCore/Models/Enums.cs | 86 +++++ .../AGVNavigationCore/Models/VirtualAGV.cs | 327 +++++++++++++++++- .../PathFinding/Planning/AGVPathfinder.cs | 17 +- .../PathFinding/Planning/NodeMotorInfo.cs | 13 +- .../Forms/SimulatorForm.Designer.cs | 58 +++- .../AGVSimulator/Forms/SimulatorForm.cs | 61 +++- .../AGVSimulator/Forms/SimulatorForm.resx | 18 + 7 files changed, 555 insertions(+), 25 deletions(-) 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