enhance: Display RFID values in path UI and improve AGV lift direction visualization

- Replace NodeID with RFID values in path display for better field mapping
- Add ComboBoxItem<T> class for {rfid} - [{node}] format in combo boxes
- Implement GetRfidByNodeId helper method for NodeID to RFID conversion
- Enhanced UpdatePathDebugInfo to show both RFID and NodeID information
- Improved path visualization with RFID-based route display
- Users can now easily match displayed paths with physical RFID tags

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ChiKyun Kim
2025-09-12 15:36:01 +09:00
parent de0e39e030
commit 1add9ed59a
9 changed files with 1626 additions and 98 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AGVNavigationCore.Models;
namespace AGVNavigationCore.PathFinding
@@ -24,6 +25,11 @@ namespace AGVNavigationCore.PathFinding
/// </summary>
public List<AgvDirection> Commands { get; set; }
/// <summary>
/// 노드별 모터방향 정보 목록
/// </summary>
public List<NodeMotorInfo> NodeMotorInfos { get; set; }
/// <summary>
/// 총 거리
/// </summary>
@@ -57,6 +63,7 @@ namespace AGVNavigationCore.PathFinding
Success = false;
Path = new List<string>();
Commands = new List<AgvDirection>();
NodeMotorInfos = new List<NodeMotorInfo>();
TotalDistance = 0;
CalculationTimeMs = 0;
EstimatedTimeSeconds = 0;
@@ -87,6 +94,31 @@ namespace AGVNavigationCore.PathFinding
return result;
}
/// <summary>
/// 성공 결과 생성 (노드별 모터방향 정보 포함)
/// </summary>
/// <param name="path">경로</param>
/// <param name="commands">AGV 명령어 목록</param>
/// <param name="nodeMotorInfos">노드별 모터방향 정보</param>
/// <param name="totalDistance">총 거리</param>
/// <param name="calculationTimeMs">계산 시간</param>
/// <returns>성공 결과</returns>
public static AGVPathResult CreateSuccess(List<string> path, List<AgvDirection> commands, List<NodeMotorInfo> nodeMotorInfos, float totalDistance, long calculationTimeMs)
{
var result = new AGVPathResult
{
Success = true,
Path = new List<string>(path),
Commands = new List<AgvDirection>(commands),
NodeMotorInfos = new List<NodeMotorInfo>(nodeMotorInfos),
TotalDistance = totalDistance,
CalculationTimeMs = calculationTimeMs
};
result.CalculateMetrics();
return result;
}
/// <summary>
/// 실패 결과 생성
/// </summary>

View File

@@ -159,7 +159,8 @@ namespace AGVNavigationCore.PathFinding
}
var agvCommands = GenerateAGVCommands(result.Path, targetDirection ?? AgvDirection.Forward);
return AGVPathResult.CreateSuccess(result.Path, agvCommands, result.TotalDistance, stopwatch.ElapsedMilliseconds);
var nodeMotorInfos = GenerateNodeMotorInfos(result.Path);
return AGVPathResult.CreateSuccess(result.Path, agvCommands, nodeMotorInfos, result.TotalDistance, stopwatch.ElapsedMilliseconds);
}
/// <summary>
@@ -182,7 +183,8 @@ namespace AGVNavigationCore.PathFinding
}
var agvCommands = GenerateAGVCommands(result.Path, actualTargetDirection);
return AGVPathResult.CreateSuccess(result.Path, agvCommands, result.TotalDistance, stopwatch.ElapsedMilliseconds);
var nodeMotorInfos = GenerateNodeMotorInfos(result.Path);
return AGVPathResult.CreateSuccess(result.Path, agvCommands, nodeMotorInfos, result.TotalDistance, stopwatch.ElapsedMilliseconds);
}
/// <summary>
@@ -264,6 +266,92 @@ namespace AGVNavigationCore.PathFinding
return AgvDirection.Right;
}
/// <summary>
/// 노드별 모터방향 정보 생성
/// </summary>
/// <param name="path">경로 노드 목록</param>
/// <returns>노드별 모터방향 정보 목록</returns>
private List<NodeMotorInfo> GenerateNodeMotorInfos(List<string> path)
{
var nodeMotorInfos = new List<NodeMotorInfo>();
if (path.Count < 2) return nodeMotorInfos;
for (int i = 0; i < path.Count; i++)
{
var currentNodeId = path[i];
string nextNodeId = i < path.Count - 1 ? path[i + 1] : null;
AgvDirection motorDirection;
if (i == path.Count - 1)
{
// 마지막 노드: 도킹/충전 노드 타입에 따라 결정
if (_nodeMap.ContainsKey(currentNodeId))
{
var currentNode = _nodeMap[currentNodeId];
motorDirection = GetRequiredDirectionForNode(currentNode);
}
else
{
motorDirection = AgvDirection.Forward;
}
}
else
{
// 중간 노드: 다음 노드와의 관계를 고려한 모터방향 결정
motorDirection = CalculateMotorDirection(currentNodeId, nextNodeId);
}
nodeMotorInfos.Add(new NodeMotorInfo(currentNodeId, motorDirection, nextNodeId));
}
return nodeMotorInfos;
}
/// <summary>
/// 현재 노드에서 다음 노드로 이동할 때의 모터방향 계산
/// </summary>
/// <param name="currentNodeId">현재 노드 ID</param>
/// <param name="nextNodeId">다음 노드 ID</param>
/// <returns>모터방향</returns>
private AgvDirection CalculateMotorDirection(string currentNodeId, string nextNodeId)
{
if (!_nodeMap.ContainsKey(currentNodeId) || !_nodeMap.ContainsKey(nextNodeId))
{
return AgvDirection.Forward;
}
var currentNode = _nodeMap[currentNodeId];
var nextNode = _nodeMap[nextNodeId];
// 현재 노드와 다음 노드의 위치를 기반으로 이동 방향 계산
var dx = nextNode.Position.X - currentNode.Position.X;
var dy = nextNode.Position.Y - currentNode.Position.Y;
var moveAngle = Math.Atan2(dy, dx);
// AGV의 구조: 리프트 ↔ AGV 몸체 ↔ 모니터
// 전진: 모니터 방향으로 이동 (리프트에서 멀어짐)
// 후진: 리프트 방향으로 이동 (리프트에 가까워짐)
// 다음 노드가 특수 노드인지 확인
if (nextNode.Type == NodeType.Charging)
{
// 충전기: 전진으로 도킹
return AgvDirection.Forward;
}
else if (nextNode.Type == NodeType.Docking)
{
// 도킹 스테이션: 후진으로 도킹
return AgvDirection.Backward;
}
else
{
// 일반 이동: 기본적으로 전진
// 향후 경로 패턴 분석을 통해 더 정확한 방향 결정 가능
return AgvDirection.Forward;
}
}
/// <summary>
/// 경로 유효성 검증
/// </summary>

View File

@@ -0,0 +1,32 @@
using AGVNavigationCore.Models;
namespace AGVNavigationCore.PathFinding
{
/// <summary>
/// 노드별 모터방향 정보
/// </summary>
public class NodeMotorInfo
{
/// <summary>
/// 노드 ID
/// </summary>
public string NodeId { get; set; }
/// <summary>
/// 해당 노드에서의 모터방향
/// </summary>
public AgvDirection MotorDirection { get; set; }
/// <summary>
/// 다음 노드 ID (경로예측용)
/// </summary>
public string NextNodeId { get; set; }
public NodeMotorInfo(string nodeId, AgvDirection motorDirection, string nextNodeId = null)
{
NodeId = nodeId;
MotorDirection = motorDirection;
NextNodeId = nextNodeId;
}
}
}