feat: Implement AGV command prediction system for real-time control

- 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 <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-03 17:34:35 +09:00
parent c3f8c7117d
commit 3712c290c9
7 changed files with 555 additions and 25 deletions

View File

@@ -45,6 +45,7 @@ namespace AGVSimulator.Forms
/// </summary>
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;
}
}

View File

@@ -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}";
}
}
/// <summary>

View File

@@ -137,9 +137,27 @@
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<data name="btPredict.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
</value>
</data>
<metadata name="_statusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>237, 17</value>
</metadata>
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>352, 17</value>
</metadata>
</root>