refactor: Consolidate RFID mapping and add bidirectional pathfinding
Major improvements to AGV navigation system: • Consolidated RFID management into MapNode, removing duplicate RfidMapping class • Enhanced MapNode with RFID metadata fields (RfidStatus, RfidDescription) • Added automatic bidirectional connection generation in pathfinding algorithms • Updated all components to use unified MapNode-based RFID system • Added command line argument support for AGVMapEditor auto-loading files • Fixed pathfinding failures by ensuring proper node connectivity Technical changes: - Removed RfidMapping class and dependencies across all projects - Updated AStarPathfinder with EnsureBidirectionalConnections() method - Modified MapLoader to use AssignAutoRfidIds() for RFID automation - Enhanced UnifiedAGVCanvas, SimulatorForm, and MainForm for MapNode integration - Improved data consistency and reduced memory footprint 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
275
Cs_HMI/AGVNavigationCore/PathFinding/RfidBasedPathfinder.cs
Normal file
275
Cs_HMI/AGVNavigationCore/PathFinding/RfidBasedPathfinder.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AGVNavigationCore.Models;
|
||||
|
||||
namespace AGVNavigationCore.PathFinding
|
||||
{
|
||||
/// <summary>
|
||||
/// RFID 기반 AGV 경로 탐색기
|
||||
/// 실제 현장에서 AGV가 RFID를 읽어서 위치를 파악하는 방식에 맞춤
|
||||
/// </summary>
|
||||
public class RfidBasedPathfinder
|
||||
{
|
||||
private AGVPathfinder _agvPathfinder;
|
||||
private AStarPathfinder _astarPathfinder;
|
||||
private Dictionary<string, string> _rfidToNodeMap; // RFID -> NodeId
|
||||
private Dictionary<string, string> _nodeToRfidMap; // NodeId -> RFID
|
||||
private List<MapNode> _mapNodes;
|
||||
|
||||
/// <summary>
|
||||
/// AGV 현재 방향
|
||||
/// </summary>
|
||||
public AgvDirection CurrentDirection
|
||||
{
|
||||
get => _agvPathfinder.CurrentDirection;
|
||||
set => _agvPathfinder.CurrentDirection = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 회전 비용 가중치
|
||||
/// </summary>
|
||||
public float RotationCostWeight
|
||||
{
|
||||
get => _agvPathfinder.RotationCostWeight;
|
||||
set => _agvPathfinder.RotationCostWeight = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 생성자
|
||||
/// </summary>
|
||||
public RfidBasedPathfinder()
|
||||
{
|
||||
_agvPathfinder = new AGVPathfinder();
|
||||
_astarPathfinder = new AStarPathfinder();
|
||||
_rfidToNodeMap = new Dictionary<string, string>();
|
||||
_nodeToRfidMap = new Dictionary<string, string>();
|
||||
_mapNodes = new List<MapNode>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 맵 노드 설정 (MapNode의 RFID 정보 직접 사용)
|
||||
/// </summary>
|
||||
/// <param name="mapNodes">맵 노드 목록</param>
|
||||
public void SetMapNodes(List<MapNode> mapNodes)
|
||||
{
|
||||
// 기존 pathfinder에 맵 노드 설정
|
||||
_agvPathfinder.SetMapNodes(mapNodes);
|
||||
_astarPathfinder.SetMapNodes(mapNodes);
|
||||
|
||||
// MapNode의 RFID 정보로 매핑 구성
|
||||
_mapNodes = mapNodes ?? new List<MapNode>();
|
||||
_rfidToNodeMap.Clear();
|
||||
_nodeToRfidMap.Clear();
|
||||
|
||||
foreach (var node in _mapNodes.Where(n => n.IsActive && n.HasRfid()))
|
||||
{
|
||||
_rfidToNodeMap[node.RfidId] = node.NodeId;
|
||||
_nodeToRfidMap[node.NodeId] = node.RfidId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID 기반 AGV 경로 계산
|
||||
/// </summary>
|
||||
/// <param name="startRfidId">시작 RFID</param>
|
||||
/// <param name="endRfidId">목적지 RFID</param>
|
||||
/// <param name="targetDirection">목적지 도착 방향</param>
|
||||
/// <returns>RFID 기반 AGV 경로 계산 결과</returns>
|
||||
public RfidPathResult FindAGVPath(string startRfidId, string endRfidId, AgvDirection? targetDirection = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// RFID를 NodeId로 변환
|
||||
if (!_rfidToNodeMap.TryGetValue(startRfidId, out string startNodeId))
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"시작 RFID를 찾을 수 없습니다: {startRfidId}", 0);
|
||||
}
|
||||
|
||||
if (!_rfidToNodeMap.TryGetValue(endRfidId, out string endNodeId))
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"목적지 RFID를 찾을 수 없습니다: {endRfidId}", 0);
|
||||
}
|
||||
|
||||
// NodeId 기반으로 경로 계산
|
||||
var nodeResult = _agvPathfinder.FindAGVPath(startNodeId, endNodeId, targetDirection);
|
||||
|
||||
// 결과를 RFID 기반으로 변환
|
||||
return ConvertToRfidResult(nodeResult, startRfidId, endRfidId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"RFID 기반 경로 계산 중 오류: {ex.Message}", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 가장 가까운 충전소로의 RFID 기반 경로 찾기
|
||||
/// </summary>
|
||||
/// <param name="startRfidId">시작 RFID</param>
|
||||
/// <returns>RFID 기반 경로 계산 결과</returns>
|
||||
public RfidPathResult FindPathToChargingStation(string startRfidId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_rfidToNodeMap.TryGetValue(startRfidId, out string startNodeId))
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"시작 RFID를 찾을 수 없습니다: {startRfidId}", 0);
|
||||
}
|
||||
|
||||
var nodeResult = _agvPathfinder.FindPathToChargingStation(startNodeId);
|
||||
return ConvertToRfidResult(nodeResult, startRfidId, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"충전소 경로 계산 중 오류: {ex.Message}", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 장비 타입의 도킹 스테이션으로의 RFID 기반 경로 찾기
|
||||
/// </summary>
|
||||
/// <param name="startRfidId">시작 RFID</param>
|
||||
/// <param name="stationType">장비 타입</param>
|
||||
/// <returns>RFID 기반 경로 계산 결과</returns>
|
||||
public RfidPathResult FindPathToDockingStation(string startRfidId, StationType stationType)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_rfidToNodeMap.TryGetValue(startRfidId, out string startNodeId))
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"시작 RFID를 찾을 수 없습니다: {startRfidId}", 0);
|
||||
}
|
||||
|
||||
var nodeResult = _agvPathfinder.FindPathToDockingStation(startNodeId, stationType);
|
||||
return ConvertToRfidResult(nodeResult, startRfidId, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"도킹 스테이션 경로 계산 중 오류: {ex.Message}", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 RFID 목적지 중 가장 가까운 곳으로의 경로 찾기
|
||||
/// </summary>
|
||||
/// <param name="startRfidId">시작 RFID</param>
|
||||
/// <param name="targetRfidIds">목적지 후보 RFID 목록</param>
|
||||
/// <returns>RFID 기반 경로 계산 결과</returns>
|
||||
public RfidPathResult FindNearestPath(string startRfidId, List<string> targetRfidIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_rfidToNodeMap.TryGetValue(startRfidId, out string startNodeId))
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"시작 RFID를 찾을 수 없습니다: {startRfidId}", 0);
|
||||
}
|
||||
|
||||
// RFID 목록을 NodeId 목록으로 변환
|
||||
var targetNodeIds = new List<string>();
|
||||
foreach (var rfidId in targetRfidIds)
|
||||
{
|
||||
if (_rfidToNodeMap.TryGetValue(rfidId, out string nodeId))
|
||||
{
|
||||
targetNodeIds.Add(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetNodeIds.Count == 0)
|
||||
{
|
||||
return RfidPathResult.CreateFailure("유효한 목적지 RFID가 없습니다", 0);
|
||||
}
|
||||
|
||||
var pathResult = _astarPathfinder.FindNearestPath(startNodeId, targetNodeIds);
|
||||
if (!pathResult.Success)
|
||||
{
|
||||
return RfidPathResult.CreateFailure(pathResult.ErrorMessage, pathResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// AGV 명령어 생성을 위해 AGV pathfinder 사용
|
||||
var endNodeId = pathResult.Path.Last();
|
||||
var agvResult = _agvPathfinder.FindAGVPath(startNodeId, endNodeId);
|
||||
|
||||
return ConvertToRfidResult(agvResult, startRfidId, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RfidPathResult.CreateFailure($"최근접 경로 계산 중 오류: {ex.Message}", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID 매핑 상태 확인 (MapNode 기반)
|
||||
/// </summary>
|
||||
/// <param name="rfidId">확인할 RFID</param>
|
||||
/// <returns>MapNode 또는 null</returns>
|
||||
public MapNode GetRfidMapping(string rfidId)
|
||||
{
|
||||
return _mapNodes.FirstOrDefault(n => n.RfidId == rfidId && n.IsActive && n.HasRfid());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID로 NodeId 조회
|
||||
/// </summary>
|
||||
/// <param name="rfidId">RFID</param>
|
||||
/// <returns>NodeId 또는 null</returns>
|
||||
public string GetNodeIdByRfid(string rfidId)
|
||||
{
|
||||
return _rfidToNodeMap.TryGetValue(rfidId, out string nodeId) ? nodeId : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NodeId로 RFID 조회
|
||||
/// </summary>
|
||||
/// <param name="nodeId">NodeId</param>
|
||||
/// <returns>RFID 또는 null</returns>
|
||||
public string GetRfidByNodeId(string nodeId)
|
||||
{
|
||||
return _nodeToRfidMap.TryGetValue(nodeId, out string rfidId) ? rfidId : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 활성화된 RFID 목록 반환
|
||||
/// </summary>
|
||||
/// <returns>활성화된 RFID 목록</returns>
|
||||
public List<string> GetActiveRfidList()
|
||||
{
|
||||
return _mapNodes.Where(n => n.IsActive && n.HasRfid()).Select(n => n.RfidId).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NodeId 기반 결과를 RFID 기반 결과로 변환
|
||||
/// </summary>
|
||||
private RfidPathResult ConvertToRfidResult(AGVPathResult nodeResult, string startRfidId, string endRfidId)
|
||||
{
|
||||
if (!nodeResult.Success)
|
||||
{
|
||||
return RfidPathResult.CreateFailure(nodeResult.ErrorMessage, nodeResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// NodeId 경로를 RFID 경로로 변환
|
||||
var rfidPath = new List<string>();
|
||||
foreach (var nodeId in nodeResult.Path)
|
||||
{
|
||||
if (_nodeToRfidMap.TryGetValue(nodeId, out string rfidId))
|
||||
{
|
||||
rfidPath.Add(rfidId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 매핑이 없는 경우 NodeId를 그대로 사용 (경고 로그 필요)
|
||||
rfidPath.Add($"[{nodeId}]");
|
||||
}
|
||||
}
|
||||
|
||||
return RfidPathResult.CreateSuccess(
|
||||
rfidPath,
|
||||
nodeResult.Commands,
|
||||
nodeResult.TotalDistance,
|
||||
nodeResult.CalculationTimeMs,
|
||||
nodeResult.EstimatedTimeSeconds,
|
||||
nodeResult.RotationCount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user