add files
This commit is contained in:
@@ -35,8 +35,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "솔루션 항목", "솔루
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build.bat = build.bat
|
||||
CLAUDE.md = CLAUDE.md
|
||||
TODO.md = TODO.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVNavigationCore", "AGVNavigationCore\AGVNavigationCore.csproj", "{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -191,6 +194,18 @@ Global
|
||||
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|x86.Build.0 = Debug|x86
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x86.ActiveCfg = Release|x86
|
||||
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -717,73 +717,107 @@ namespace AGVNavigationCore.PathFinding
|
||||
|
||||
var startNode = _nodeMap[startNodeId];
|
||||
|
||||
// 시작 노드에서 회전이 불가능하면 회전 가능한 가까운 노드 찾기
|
||||
if (!startNode.CanRotate)
|
||||
// 방향 전환을 위한 가장 가까운 교차로 찾기
|
||||
var targetNodeId = originalResult.Path.LastOrDefault();
|
||||
var junctionNode = FindNearestJunctionForDirectionChange(startNodeId, targetNodeId);
|
||||
if (junctionNode == null)
|
||||
{
|
||||
var rotationNode = FindNearestRotationNode(startNodeId);
|
||||
if (rotationNode == null)
|
||||
{
|
||||
return AGVPathResult.CreateFailure("방향 전환을 위한 회전 가능 노드를 찾을 수 없습니다.", originalResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// 회전 노드로의 경로 추가
|
||||
var pathToRotationNode = _pathfinder.FindPath(startNodeId, rotationNode.NodeId);
|
||||
if (!pathToRotationNode.Success)
|
||||
{
|
||||
return AGVPathResult.CreateFailure("방향 전환 노드로의 경로를 찾을 수 없습니다.", originalResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// 회전 노드에서 원래 목적지로의 경로 계산
|
||||
var pathFromRotationNode = _pathfinder.FindPath(rotationNode.NodeId, originalResult.Path.Last());
|
||||
if (!pathFromRotationNode.Success)
|
||||
{
|
||||
return AGVPathResult.CreateFailure("방향 전환 후 목적지로의 경로를 찾을 수 없습니다.", originalResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// 전체 경로 조합
|
||||
var combinedPath = new List<string>();
|
||||
combinedPath.AddRange(pathToRotationNode.Path);
|
||||
combinedPath.AddRange(pathFromRotationNode.Path.Skip(1)); // 중복 노드 제거
|
||||
|
||||
var combinedDistance = pathToRotationNode.TotalDistance + pathFromRotationNode.TotalDistance;
|
||||
var combinedCommands = GenerateAGVCommandsWithDirectionChange(combinedPath, currentDirection, targetDirection, rotationNode.NodeId);
|
||||
var nodeMotorInfos = GenerateNodeMotorInfos(combinedPath);
|
||||
|
||||
return AGVPathResult.CreateSuccess(combinedPath, combinedCommands, nodeMotorInfos, combinedDistance, originalResult.CalculationTimeMs);
|
||||
return AGVPathResult.CreateFailure("방향 전환을 위한 교차로를 찾을 수 없습니다.", originalResult.CalculationTimeMs);
|
||||
}
|
||||
else
|
||||
|
||||
// 교차로로의 경로 추가
|
||||
var pathToJunction = _pathfinder.FindPath(startNodeId, junctionNode.NodeId);
|
||||
if (!pathToJunction.Success)
|
||||
{
|
||||
// 시작 노드에서 바로 방향 전환
|
||||
var commandsWithRotation = GenerateAGVCommandsWithDirectionChange(originalResult.Path, currentDirection, targetDirection, startNodeId);
|
||||
return AGVPathResult.CreateSuccess(originalResult.Path, commandsWithRotation, originalResult.NodeMotorInfos, originalResult.TotalDistance, originalResult.CalculationTimeMs);
|
||||
return AGVPathResult.CreateFailure("방향 전환 교차로로의 경로를 찾을 수 없습니다.", originalResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// 교차로에서 원래 목적지로의 경로 계산
|
||||
var pathFromJunction = _pathfinder.FindPath(junctionNode.NodeId, originalResult.Path.Last());
|
||||
if (!pathFromJunction.Success)
|
||||
{
|
||||
return AGVPathResult.CreateFailure("방향 전환 후 목적지로의 경로를 찾을 수 없습니다.", originalResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
// 전체 경로 조합
|
||||
var combinedPath = new List<string>();
|
||||
combinedPath.AddRange(pathToJunction.Path);
|
||||
combinedPath.AddRange(pathFromJunction.Path.Skip(1)); // 중복 노드 제거
|
||||
|
||||
var combinedDistance = pathToJunction.TotalDistance + pathFromJunction.TotalDistance;
|
||||
var combinedCommands = GenerateAGVCommandsWithDirectionChange(combinedPath, currentDirection, targetDirection, junctionNode.NodeId);
|
||||
var nodeMotorInfos = GenerateNodeMotorInfos(combinedPath);
|
||||
|
||||
return AGVPathResult.CreateSuccess(combinedPath, combinedCommands, nodeMotorInfos, combinedDistance, originalResult.CalculationTimeMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 가장 가까운 회전 가능 노드 찾기
|
||||
/// 방향 전환을 위한 가장 가까운 교차로 찾기
|
||||
/// </summary>
|
||||
/// <param name="fromNodeId">시작 노드 ID</param>
|
||||
/// <returns>가장 가까운 회전 가능 노드</returns>
|
||||
private MapNode FindNearestRotationNode(string fromNodeId)
|
||||
/// <param name="targetNodeId">목적지 노드 ID (경로상 교차로 우선 검색용)</param>
|
||||
/// <returns>가장 가까운 교차로 노드</returns>
|
||||
private MapNode FindNearestJunctionForDirectionChange(string fromNodeId, string targetNodeId = null)
|
||||
{
|
||||
var rotationNodes = _nodeMap.Values.Where(n => n.CanRotate && n.IsActive).ToList();
|
||||
|
||||
// 1단계: 시작점에서 목적지까지 직접 경로상의 교차로 우선 검색
|
||||
if (!string.IsNullOrEmpty(targetNodeId))
|
||||
{
|
||||
var directPath = _pathfinder.FindPath(fromNodeId, targetNodeId);
|
||||
if (directPath.Success)
|
||||
{
|
||||
foreach (var nodeId in directPath.Path)
|
||||
{
|
||||
var node = _nodeMap.ContainsKey(nodeId) ? _nodeMap[nodeId] : null;
|
||||
if (node != null && IsJunction(node))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2단계: 경로상에 교차로가 없으면 거리가 가장 가까운 교차로 찾기
|
||||
var junctionNodes = _nodeMap.Values.Where(n => n.IsActive && IsJunction(n)).ToList();
|
||||
|
||||
MapNode nearestNode = null;
|
||||
var shortestDistance = float.MaxValue;
|
||||
|
||||
foreach (var rotationNode in rotationNodes)
|
||||
foreach (var junctionNode in junctionNodes)
|
||||
{
|
||||
var pathResult = _pathfinder.FindPath(fromNodeId, rotationNode.NodeId);
|
||||
var pathResult = _pathfinder.FindPath(fromNodeId, junctionNode.NodeId);
|
||||
if (pathResult.Success && pathResult.TotalDistance < shortestDistance)
|
||||
{
|
||||
shortestDistance = pathResult.TotalDistance;
|
||||
nearestNode = rotationNode;
|
||||
nearestNode = junctionNode;
|
||||
}
|
||||
}
|
||||
|
||||
return nearestNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드가 교차로인지 판단 (3개 이상 연결된 노드)
|
||||
/// </summary>
|
||||
/// <param name="node">검사할 노드</param>
|
||||
/// <returns>교차로이면 true</returns>
|
||||
private bool IsJunction(MapNode node)
|
||||
{
|
||||
// 연결된 노드 수 계산 (단방향 + 양방향)
|
||||
var connectedCount = node.ConnectedNodes.Count;
|
||||
|
||||
// 역방향 연결도 고려
|
||||
foreach (var otherNode in _nodeMap.Values)
|
||||
{
|
||||
if (otherNode.NodeId != node.NodeId && otherNode.ConnectedNodes.Contains(node.NodeId))
|
||||
{
|
||||
connectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 3개 이상 연결되어 있으면 교차로
|
||||
return connectedCount >= 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 방향 전환을 포함한 AGV 명령어 생성
|
||||
/// </summary>
|
||||
|
||||
@@ -553,7 +553,7 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>rem xcopy "$(TargetDir)*.exe" "\\192.168.1.80\Amkor\AGV2" /Y
|
||||
rem xcopy "$(TargetDir)*.dll" "\\192.168.1.80\Amkor\AGV2" /Y</PostBuildEvent>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -146,6 +146,9 @@ namespace AGVControl.Models
|
||||
MovementHistory.Clear();
|
||||
MovementHistory.AddRange(lastTwo);
|
||||
}
|
||||
|
||||
// 위치 업데이트 시 자동으로 히스토리 파일에 저장
|
||||
SaveHistoryOnUpdate();
|
||||
}
|
||||
|
||||
// 연결 정보 기반 실제 이동 방향 계산
|
||||
@@ -233,6 +236,98 @@ namespace AGVControl.Models
|
||||
}
|
||||
}
|
||||
|
||||
// 위치 히스토리 파일 저장 (최근 3개만 저장)
|
||||
public void SavePositionHistory(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 최근 3개의 히스토리만 저장
|
||||
var recentHistory = MovementHistory.Skip(Math.Max(0, MovementHistory.Count - 3)).ToList();
|
||||
|
||||
var lines = new List<string>();
|
||||
lines.Add($"# AGV Position History - {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
lines.Add("# Format: RFID,Direction,X,Y,Timestamp");
|
||||
|
||||
foreach (var history in recentHistory)
|
||||
{
|
||||
lines.Add($"{history.Value},{history.Direction},{history.Location.X},{history.Location.Y},{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllLines(filePath, lines);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 로그 기록 (실제 환경에서는 로깅 시스템 사용)
|
||||
System.Diagnostics.Debug.WriteLine($"SavePositionHistory Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 위치 히스토리 파일 로드
|
||||
public bool LoadPositionHistory(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
return false;
|
||||
|
||||
var lines = System.IO.File.ReadAllLines(filePath);
|
||||
MovementHistory.Clear();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
// 주석 라인 건너뛰기
|
||||
if (line.StartsWith("#") || string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
var parts = line.Split(',');
|
||||
if (parts.Length >= 4)
|
||||
{
|
||||
if (UInt16.TryParse(parts[0], out UInt16 rfidValue) &&
|
||||
Enum.TryParse<AgvDir>(parts[1], out AgvDir direction) &&
|
||||
int.TryParse(parts[2], out int x) &&
|
||||
int.TryParse(parts[3], out int y))
|
||||
{
|
||||
MovementHistory.Add(new movehistorydata
|
||||
{
|
||||
Value = rfidValue,
|
||||
Direction = direction,
|
||||
Location = new Point(x, y)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MovementHistory.Count > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"LoadPositionHistory Error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 시작 시 위치 히스토리 자동 로드
|
||||
public void LoadHistoryOnStartup()
|
||||
{
|
||||
string historyFilePath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
|
||||
"agv_position_history.dat"
|
||||
);
|
||||
|
||||
LoadPositionHistory(historyFilePath);
|
||||
}
|
||||
|
||||
// 위치 업데이트 시 자동 저장
|
||||
public void SaveHistoryOnUpdate()
|
||||
{
|
||||
string historyFilePath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
|
||||
"agv_position_history.dat"
|
||||
);
|
||||
|
||||
SavePositionHistory(historyFilePath);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Test_BMS
|
||||
InitializeComponent();
|
||||
bms = new arDev.BMS();
|
||||
bms.BMSDataReceive += Bms_BMSDataReceive;
|
||||
bms.Message += Bms_Message;
|
||||
//bms.Message += Bms_Message;
|
||||
}
|
||||
|
||||
private void Bms_Message(object sender, arDev.arRS232.MessageEventArgs e)
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>xcopy "$(TargetDir)*.exe" "\\192.168.1.80\Amkor\AGV2\Test\BMS" /Y
|
||||
xcopy "$(TargetDir)*.dll" "\\192.168.1.80\Amkor\AGV2\Test\BMS" /Y</PostBuildEvent>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
echo Building V2GDecoder VC++ Project...
|
||||
echo Building AGV C# HMI Project...
|
||||
|
||||
REM Check if Visual Studio 2022 is installed
|
||||
if not exist "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe" (
|
||||
|
||||
Reference in New Issue
Block a user