This commit is contained in:
backuppc
2025-12-15 17:34:43 +09:00
parent 9db88e5d6b
commit a7f938ff19
29 changed files with 535 additions and 1556 deletions

View File

@@ -36,20 +36,20 @@ namespace AGVMapEditor.Forms
{ {
public string FromNodeId { get; set; } public string FromNodeId { get; set; }
public string FromNodeName { get; set; } public string FromNodeName { get; set; }
public string FromRfidId { get; set; } public ushort FromRfidId { get; set; }
public string ToNodeId { get; set; } public string ToNodeId { get; set; }
public string ToNodeName { get; set; } public string ToNodeName { get; set; }
public string ToRfidId { get; set; } public ushort ToRfidId { get; set; }
public string ConnectionType { get; set; } public string ConnectionType { get; set; }
public override string ToString() public override string ToString()
{ {
// RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시 // RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시
string fromDisplay = !string.IsNullOrEmpty(FromRfidId) string fromDisplay = FromRfidId > 0
? $"{FromRfidId}({FromNodeName})" ? $"{FromRfidId}({FromNodeName})"
: $"---({FromNodeId})"; : $"---({FromNodeId})";
string toDisplay = !string.IsNullOrEmpty(ToRfidId) string toDisplay = ToRfidId > 0
? $"{ToRfidId}({ToNodeName})" ? $"{ToRfidId}({ToNodeName})"
: $"---({ToNodeId})"; : $"---({ToNodeId})";
@@ -420,7 +420,7 @@ namespace AGVMapEditor.Forms
return; return;
} }
var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? ""; var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? 0;
var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.", var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
"삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question); "삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
@@ -1107,8 +1107,8 @@ namespace AGVMapEditor.Forms
// RFID 값 변경시 중복 검사 // RFID 값 변경시 중복 검사
if (e.ChangedItem.PropertyDescriptor.Name == "RFID") if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
{ {
string newRfidValue = e.ChangedItem.Value?.ToString(); var newRfidValue = ushort.Parse(e.ChangedItem.Value?.ToString());
if (!string.IsNullOrEmpty(newRfidValue) && CheckRfidDuplicate(newRfidValue)) if (newRfidValue != 0 && CheckRfidDuplicate(newRfidValue))
{ {
// 중복된 RFID 값 발견 // 중복된 RFID 값 발견
MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.", MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.",
@@ -1174,29 +1174,15 @@ namespace AGVMapEditor.Forms
/// </summary> /// </summary>
/// <param name="rfidValue">검사할 RFID 값</param> /// <param name="rfidValue">검사할 RFID 값</param>
/// <returns>중복되면 true, 아니면 false</returns> /// <returns>중복되면 true, 아니면 false</returns>
private bool CheckRfidDuplicate(string rfidValue) private bool CheckRfidDuplicate(ushort rfidValue)
{ {
if (string.IsNullOrEmpty(rfidValue) || this._mapCanvas.Nodes == null) if (rfidValue == 0 || this._mapCanvas.Nodes == null)
return false; return false;
// 현재 편집 중인 노드 제외하고 중복 검사 // 현재 편집 중인 노드 제외하고 중복 검사
string currentNodeId = null; string currentNodeId = null;
var selectedObject = _propertyGrid.SelectedObject; var selectedObject = _propertyGrid.SelectedObject;
// 다양한 PropertyWrapper 타입 처리
//if (selectedObject is NodePropertyWrapper nodeWrapper)
//{
// currentNodeId = nodeWrapper.WrappedNode?.NodeId;
//}
//else if (selectedObject is LabelNodePropertyWrapper labelWrapper)
//{
// currentNodeId = labelWrapper.WrappedNode?.NodeId;
//}
//else if (selectedObject is ImageNodePropertyWrapper imageWrapper)
//{
// currentNodeId = imageWrapper.WrappedNode?.NodeId;
//}
int duplicateCount = 0; int duplicateCount = 0;
foreach (var node in this._mapCanvas.Nodes) foreach (var node in this._mapCanvas.Nodes)
{ {
@@ -1205,7 +1191,7 @@ namespace AGVMapEditor.Forms
continue; continue;
// 같은 RFID 값을 가진 노드가 있는지 확인 // 같은 RFID 값을 가진 노드가 있는지 확인
if (!string.IsNullOrEmpty(node.RfidId) && node.RfidId.Equals(rfidValue, StringComparison.OrdinalIgnoreCase)) if (node.RfidId != 0 && node.RfidId == rfidValue)
{ {
duplicateCount++; duplicateCount++;
break; // 하나라도 발견되면 중복 break; // 하나라도 발견되면 중복

View File

@@ -1140,7 +1140,8 @@ namespace AGVNavigationCore.Controls
// 위쪽에 표시할 이름 (노드의 Name 속성) // 위쪽에 표시할 이름 (노드의 Name 속성)
string TopIDText = node.HasRfid() ? node.RfidId : $"[{node.Id}]"; string TopIDText = node.HasRfid() ? node.RfidId.ToString("0000") : $"[{node.Id}]";
// 아래쪽에 표시할 값 (RFID 우선, 없으면 노드ID) // 아래쪽에 표시할 값 (RFID 우선, 없으면 노드ID)
string BottomLabelText = node.Text; string BottomLabelText = node.Text;

View File

@@ -146,15 +146,19 @@ namespace AGVNavigationCore.Controls
private void HandleNormalNodeDoubleClick(MapNode node) private void HandleNormalNodeDoubleClick(MapNode node)
{ {
// RFID 입력창 표시 // RFID 입력창 표시
string currentRfid = node.RfidId ?? ""; var currentRfid = node.RfidId;
string newRfid = Microsoft.VisualBasic.Interaction.InputBox( string newRfid = Microsoft.VisualBasic.Interaction.InputBox(
$"노드 '{node.RfidId}[{node.Id}]'의 RFID를 입력하세요:", $"노드 '{node.RfidId}[{node.Id}]'의 RFID를 입력하세요:",
"RFID 설정", "RFID 설정",
currentRfid); currentRfid.ToString());
if (!string.IsNullOrWhiteSpace(newRfid) && newRfid != currentRfid) if (ushort.TryParse(newRfid, out ushort newrfidvalue) == false) return;
if (newrfidvalue < 1) return;
if (newrfidvalue != currentRfid)
{ {
node.RfidId = newRfid.Trim(); node.RfidId = newrfidvalue;
MapChanged?.Invoke(this, EventArgs.Empty); MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate(); Invalidate();
} }
@@ -700,8 +704,8 @@ namespace AGVNavigationCore.Controls
{ {
// 연결선을 클릭했을 때 삭제 확인 // 연결선을 클릭했을 때 삭제 확인
var (fromNode, toNode) = connection.Value; var (fromNode, toNode) = connection.Value;
string fromDisplay = !string.IsNullOrEmpty(fromNode.RfidId) ? fromNode.RfidId : fromNode.Id; string fromDisplay = fromNode.HasRfid() ? fromNode.RfidId.ToString("0000") : fromNode.Id;
string toDisplay = !string.IsNullOrEmpty(toNode.RfidId) ? toNode.RfidId : toNode.Id; string toDisplay = toNode.HasRfid() ? toNode.RfidId.ToString("0000") : toNode.Id;
var result = MessageBox.Show( var result = MessageBox.Show(
$"연결을 삭제하시겠습니까?\n\n{fromDisplay} ↔ {toDisplay}", $"연결을 삭제하시겠습니까?\n\n{fromDisplay} ↔ {toDisplay}",

View File

@@ -851,13 +851,13 @@ namespace AGVNavigationCore.Controls
return; return;
// RFID값과 해당 노드의 인덱스를 저장 // RFID값과 해당 노드의 인덱스를 저장
var rfidToNodeIndex = new Dictionary<string, List<int>>(); var rfidToNodeIndex = new Dictionary<ushort, List<int>>();
// 모든 노드의 RFID값 수집 // 모든 노드의 RFID값 수집
for (int i = 0; i < _nodes.Count; i++) for (int i = 0; i < _nodes.Count; i++)
{ {
var node = _nodes[i]; var node = _nodes[i];
if (!string.IsNullOrEmpty(node.RfidId)) if (node.HasRfid())
{ {
if (!rfidToNodeIndex.ContainsKey(node.RfidId)) if (!rfidToNodeIndex.ContainsKey(node.RfidId))
{ {

View File

@@ -83,7 +83,7 @@ namespace AGVNavigationCore.Models
[Category("RFID 정보")] [Category("RFID 정보")]
[Description("물리적 RFID 태그 ID입니다.")] [Description("물리적 RFID 태그 ID입니다.")]
public string RfidId { get; set; } = string.Empty; public UInt16 RfidId { get; set; } = 0;
[Category("노드 텍스트"), DisplayName("TextColor")] [Category("노드 텍스트"), DisplayName("TextColor")]
@@ -161,7 +161,7 @@ namespace AGVNavigationCore.Models
public bool HasRfid() public bool HasRfid()
{ {
return !string.IsNullOrEmpty(RfidId); return RfidId > 0;
} }
} }
} }

View File

@@ -611,17 +611,6 @@ namespace AGVNavigationCore.Models
PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode)); PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
} }
/// <summary>
/// 현재 RFID 시뮬레이션 (현재 위치 기준)
/// </summary>
public string SimulateRfidReading(List<MapNode> mapNodes)
{
var closestNode = FindClosestNode(_currentPosition, mapNodes);
if (closestNode == null)
return null;
return closestNode.HasRfid() ? closestNode.RfidId : null;
}
#endregion #endregion
@@ -630,10 +619,11 @@ namespace AGVNavigationCore.Models
/// <summary> /// <summary>
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용) /// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
/// </summary> /// </summary>
public string GetRfidByNodeId(List<MapNode> _mapNodes, string nodeId) public ushort GetRfidByNodeId(List<MapNode> _mapNodes, string nodeId)
{ {
var node = _mapNodes?.FirstOrDefault(n => n.Id == nodeId); var node = _mapNodes?.FirstOrDefault(n => n.Id == nodeId);
return node?.HasRfid() == true ? node.RfidId : nodeId; if ((node?.HasRfid() ?? false) == false) return 0;
return node.RfidId;
} }

View File

@@ -447,7 +447,7 @@ namespace AGVNavigationCore.PathFinding.Planning
{ {
var node = path1.Path[i]; var node = path1.Path[i];
string nodeId = node.Id; string nodeId = node.Id;
string RfidId = node.RfidId; var RfidId = node.RfidId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null; string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 노드 정보 생성 (현재 방향 유지) // 노드 정보 생성 (현재 방향 유지)

View File

@@ -770,9 +770,9 @@ namespace AGVNavigationCore.PathFinding.Planning
private string GetDisplayName(string nodeId) private string GetDisplayName(string nodeId)
{ {
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId); var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node != null && !string.IsNullOrEmpty(node.RfidId)) if (node != null && node.HasRfid())
{ {
return node.RfidId; return node.RfidId.ToString("0000");
} }
return $"({nodeId})"; return $"({nodeId})";
} }

View File

@@ -40,7 +40,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// <summary> /// <summary>
/// RFID Value /// RFID Value
/// </summary> /// </summary>
public string RfidId { get; set; } public ushort RfidId { get; set; }
/// <summary> /// <summary>
/// 해당 노드에서의 모터방향 /// 해당 노드에서의 모터방향
@@ -87,7 +87,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// </summary> /// </summary>
public string SpecialActionDescription { get; set; } public string SpecialActionDescription { get; set; }
public NodeMotorInfo(int seqno,string nodeId,string rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight) public NodeMotorInfo(int seqno,string nodeId,ushort rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight)
{ {
seq = seqno; seq = seqno;
NodeId = nodeId; NodeId = nodeId;

View File

@@ -204,7 +204,7 @@ namespace AGVNavigationCore.Utils
} }
Console.WriteLine( Console.WriteLine(
$"\n 최종선택: {bestNode?.RfidId ?? "null"}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})"); $"\n 최종선택: {bestNode?.RfidId ?? 0}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})");
Console.WriteLine( Console.WriteLine(
$"[GetNextNodeByDirection] ========== 다음 노드 선택 종료 ==========\n"); $"[GetNextNodeByDirection] ========== 다음 노드 선택 종료 ==========\n");

View File

@@ -16,12 +16,12 @@ namespace AGVNavigationCore.Utils
public class DirectionalPathfinderTest public class DirectionalPathfinderTest
{ {
private List<MapNode> _allNodes; private List<MapNode> _allNodes;
private Dictionary<string, MapNode> _nodesByRfidId; private Dictionary<ushort, MapNode> _nodesByRfidId;
private AGVDirectionCalculator _calculator; private AGVDirectionCalculator _calculator;
public DirectionalPathfinderTest() public DirectionalPathfinderTest()
{ {
_nodesByRfidId = new Dictionary<string, MapNode>(); _nodesByRfidId = new Dictionary<ushort, MapNode>();
_calculator = new AGVDirectionCalculator(); _calculator = new AGVDirectionCalculator();
} }
@@ -52,7 +52,7 @@ namespace AGVNavigationCore.Utils
// RFID ID로 인덱싱 // RFID ID로 인덱싱
foreach (var node in _allNodes) foreach (var node in _allNodes)
{ {
if (!string.IsNullOrEmpty(node.RfidId)) if (node.HasRfid())
{ {
_nodesByRfidId[node.RfidId] = node; _nodesByRfidId[node.RfidId] = node;
} }
@@ -71,7 +71,7 @@ namespace AGVNavigationCore.Utils
/// <summary> /// <summary>
/// 테스트: RFID 번호로 노드를 찾고, 다음 노드를 계산 /// 테스트: RFID 번호로 노드를 찾고, 다음 노드를 계산
/// </summary> /// </summary>
public void TestDirectionalMovement(string previousRfidId, string currentRfidId, AgvDirection direction) public void TestDirectionalMovement(ushort previousRfidId, ushort currentRfidId, AgvDirection direction)
{ {
Console.WriteLine($"\n========================================"); Console.WriteLine($"\n========================================");
Console.WriteLine($"테스트: {previousRfidId} → {currentRfidId} (방향: {direction})"); Console.WriteLine($"테스트: {previousRfidId} → {currentRfidId} (방향: {direction})");
@@ -140,7 +140,7 @@ namespace AGVNavigationCore.Utils
/// <summary> /// <summary>
/// 특정 RFID 노드의 상세 정보 출력 /// 특정 RFID 노드의 상세 정보 출력
/// </summary> /// </summary>
public void PrintNodeInfo(string rfidId) public void PrintNodeInfo(ushort rfidId)
{ {
if (!_nodesByRfidId.TryGetValue(rfidId, out var node)) if (!_nodesByRfidId.TryGetValue(rfidId, out var node))
{ {

View File

@@ -28,10 +28,10 @@ namespace AGVNavigationCore.Utils
Console.WriteLine("================================================\n"); Console.WriteLine("================================================\n");
// 테스트 노드 생성 // 테스트 노드 생성
var node001 = new MapNode { Id = "N001", RfidId = "001", Position = new Point(65, 229), ConnectedNodes = new List<string> { "N002" } }; var node001 = new MapNode { Id = "N001", RfidId = 001, Position = new Point(65, 229), ConnectedNodes = new List<string> { "N002" } };
var node002 = new MapNode { Id = "N002", RfidId = "002", Position = new Point(206, 244), ConnectedNodes = new List<string> { "N001", "N003" } }; var node002 = new MapNode { Id = "N002", RfidId = 002, Position = new Point(206, 244), ConnectedNodes = new List<string> { "N001", "N003" } };
var node003 = new MapNode { Id = "N003", RfidId = "003", Position = new Point(278, 278), ConnectedNodes = new List<string> { "N002", "N004" } }; var node003 = new MapNode { Id = "N003", RfidId = 003, Position = new Point(278, 278), ConnectedNodes = new List<string> { "N002", "N004" } };
var node004 = new MapNode { Id = "N004", RfidId = "004", Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } }; var node004 = new MapNode { Id = "N004", RfidId = 004, Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } };
var allNodes = new List<MapNode> { node001, node002, node003, node004 }; var allNodes = new List<MapNode> { node001, node002, node003, node004 };
@@ -114,7 +114,7 @@ namespace AGVNavigationCore.Utils
AgvDirection motorDir = currentMotorDirection ?? direction; AgvDirection motorDir = currentMotorDirection ?? direction;
Console.WriteLine($"설명: {description}"); Console.WriteLine($"설명: {description}");
Console.WriteLine($"이전 위치: {prevPos} (RFID: {allNodes.First(n => n.Position == prevPos)?.RfidId ?? "?"})"); Console.WriteLine($"이전 위치: {prevPos} (RFID: {allNodes.First(n => n.Position == prevPos)?.RfidId.ToString("0000") ?? "?"})");
Console.WriteLine($"현재 노드: {currentNode.Id} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}"); Console.WriteLine($"현재 노드: {currentNode.Id} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
Console.WriteLine($"현재 모터 방향: {motorDir}"); Console.WriteLine($"현재 모터 방향: {motorDir}");
Console.WriteLine($"요청 방향: {direction}"); Console.WriteLine($"요청 방향: {direction}");

View File

@@ -29,26 +29,26 @@ namespace AGVNavigationCore.Utils
tester.PrintAllNodes(); tester.PrintAllNodes();
// 테스트 시나리오 1: 001 → 002 → Forward (003 기대) // 테스트 시나리오 1: 001 → 002 → Forward (003 기대)
tester.PrintNodeInfo("001"); tester.PrintNodeInfo(001);
tester.PrintNodeInfo("002"); tester.PrintNodeInfo(002);
tester.TestDirectionalMovement("001", "002", AgvDirection.Forward); tester.TestDirectionalMovement(001, 002, AgvDirection.Forward);
// 테스트 시나리오 2: 002 → 001 → Backward (000 또는 이전 기대) // 테스트 시나리오 2: 002 → 001 → Backward (000 또는 이전 기대)
tester.TestDirectionalMovement("002", "001", AgvDirection.Backward); tester.TestDirectionalMovement(002, 001, AgvDirection.Backward);
// 테스트 시나리오 3: 002 → 003 → Forward // 테스트 시나리오 3: 002 → 003 → Forward
tester.PrintNodeInfo("003"); tester.PrintNodeInfo(003);
tester.TestDirectionalMovement("002", "003", AgvDirection.Forward); tester.TestDirectionalMovement(002, 003, AgvDirection.Forward);
// 테스트 시나리오 4: 003 → 004 → Forward // 테스트 시나리오 4: 003 → 004 → Forward
tester.PrintNodeInfo("004"); tester.PrintNodeInfo(004);
tester.TestDirectionalMovement("003", "004", AgvDirection.Forward); tester.TestDirectionalMovement(003, 004, AgvDirection.Forward);
// 테스트 시나리오 5: 003 → 004 → Right (030 기대) // 테스트 시나리오 5: 003 → 004 → Right (030 기대)
tester.TestDirectionalMovement("003", "004", AgvDirection.Right); tester.TestDirectionalMovement(003, 004, AgvDirection.Right);
// 테스트 시나리오 6: 004 → 003 → Backward // 테스트 시나리오 6: 004 → 003 → Backward
tester.TestDirectionalMovement("004", "003", AgvDirection.Backward); tester.TestDirectionalMovement(004, 003, AgvDirection.Backward);
Console.WriteLine("\n\n=== 테스트 완료 ==="); Console.WriteLine("\n\n=== 테스트 완료 ===");
} }

View File

@@ -635,7 +635,7 @@ namespace AGVSimulator.Forms
/// <summary> /// <summary>
/// 맵 스캔 모드에서 RFID로부터 노드 생성 /// 맵 스캔 모드에서 RFID로부터 노드 생성
/// </summary> /// </summary>
private void CreateNodeFromRfidScan(string rfidId, VirtualAGV selectedAGV) private void CreateNodeFromRfidScan(ushort rfidId, VirtualAGV selectedAGV)
{ {
try try
{ {
@@ -644,7 +644,7 @@ namespace AGVSimulator.Forms
var currentDirection = directionItem?.Direction ?? AgvDirection.Forward; var currentDirection = directionItem?.Direction ?? AgvDirection.Forward;
// 중복 RFID 확인 // 중복 RFID 확인
var existingNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase)); var existingNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId == rfidId);
if (existingNode != null) if (existingNode != null)
{ {
// 이미 존재하는 노드로 이동 // 이미 존재하는 노드로 이동
@@ -805,7 +805,7 @@ namespace AGVSimulator.Forms
if (agv.CurrentNodeId != null && agv.CurrentNodeId != _lastSentNodeId) if (agv.CurrentNodeId != null && agv.CurrentNodeId != _lastSentNodeId)
{ {
var rfid = GetRfidByNodeId(agv.CurrentNodeId); var rfid = GetRfidByNodeId(agv.CurrentNodeId);
if (!string.IsNullOrEmpty(rfid)) if (rfid > 0)
{ {
SendTag(rfid); SendTag(rfid);
_lastSentNodeId = agv.CurrentNodeId; _lastSentNodeId = agv.CurrentNodeId;
@@ -842,7 +842,7 @@ namespace AGVSimulator.Forms
// RFID 값 확인 // RFID 값 확인
var rfidId = _rfidTextBox.Text.Trim(); var rfidId = _rfidTextBox.Text.Trim();
if (string.IsNullOrEmpty(rfidId)) if (ushort.TryParse(rfidId,out ushort rfidvalue)==false)
{ {
MessageBox.Show("RFID 값을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); MessageBox.Show("RFID 값을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return; return;
@@ -856,13 +856,13 @@ namespace AGVSimulator.Forms
// 맵 스캔 모드일 때: 노드 자동 생성 // 맵 스캔 모드일 때: 노드 자동 생성
if (_isMapScanMode) if (_isMapScanMode)
{ {
CreateNodeFromRfidScan(rfidId, selectedAGV); CreateNodeFromRfidScan(rfidvalue, selectedAGV);
this._simulatorCanvas.FitToNodes(); this._simulatorCanvas.FitToNodes();
return; return;
} }
// RFID에 해당하는 노드 직접 찾기 // RFID에 해당하는 노드 직접 찾기
var targetNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase)); var targetNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId == rfidvalue);
if (targetNode == null) if (targetNode == null)
{ {
MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}", MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}",
@@ -1255,10 +1255,12 @@ namespace AGVSimulator.Forms
/// <summary> /// <summary>
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용) /// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
/// </summary> /// </summary>
private string GetRfidByNodeId(string nodeId) private ushort GetRfidByNodeId(string nodeId)
{ {
var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId); var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId);
return node?.HasRfid() == true ? node.RfidId : nodeId; if (node == null) return 0;
if (node.HasRfid()) return node.RfidId;
else return 0;
} }
/// <summary> /// <summary>
@@ -1267,9 +1269,9 @@ namespace AGVSimulator.Forms
private string GetDisplayName(string nodeId) private string GetDisplayName(string nodeId)
{ {
var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId); var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId);
if (node != null && !string.IsNullOrEmpty(node.RfidId)) if (node != null && node.HasRfid())
{ {
return node.RfidId; return node.RfidId.ToString("0000");
} }
return $"({nodeId})"; return $"({nodeId})";
} }
@@ -1366,7 +1368,7 @@ namespace AGVSimulator.Forms
{ {
var info = advancedResult.DetailedPath[i]; var info = advancedResult.DetailedPath[i];
var rfidId = GetRfidByNodeId(info.NodeId); var rfidId = GetRfidByNodeId(info.NodeId);
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId) : "END"; var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId).ToString("0000") : "-END-";
var flags = new List<string>(); var flags = new List<string>();
if (info.CanRotate) flags.Add("회전가능"); if (info.CanRotate) flags.Add("회전가능");
@@ -1616,12 +1618,8 @@ namespace AGVSimulator.Forms
/// </summary> /// </summary>
private string GetNodeDisplayName(MapNode node) private string GetNodeDisplayName(MapNode node)
{ {
if (node == null) if (node == null) return "-";
return "-"; if (node.HasRfid()) return node.RfidId.ToString("0000");
if (!string.IsNullOrEmpty(node.RfidId))
return node.RfidId;
return $"({node.Id})"; return $"({node.Id})";
} }
@@ -1792,7 +1790,7 @@ namespace AGVSimulator.Forms
this.Invoke((MethodInvoker)delegate this.Invoke((MethodInvoker)delegate
{ {
// RFID 텍스트박스에 값 입력 // RFID 텍스트박스에 값 입력
_rfidTextBox.Text = nodeA.RfidId; _rfidTextBox.Text = nodeA.RfidId.ToString();
// 방향 콤보박스 선택 // 방향 콤보박스 선택
SetDirectionComboBox(direction); SetDirectionComboBox(direction);
@@ -1808,7 +1806,7 @@ namespace AGVSimulator.Forms
this.Invoke((MethodInvoker)delegate this.Invoke((MethodInvoker)delegate
{ {
// RFID 텍스트박스에 값 입력 // RFID 텍스트박스에 값 입력
_rfidTextBox.Text = nodeB.RfidId; _rfidTextBox.Text = nodeB.RfidId.ToString();
// 방향 콤보박스 선택 // 방향 콤보박스 선택
SetDirectionComboBox(direction); SetDirectionComboBox(direction);
@@ -2415,19 +2413,18 @@ namespace AGVSimulator.Forms
catch { } catch { }
} }
public void SendTag(string tagno) public void SendTag(ushort tagno)
{ {
if (_emulatorPort == null || !_emulatorPort.IsOpen) return; if (_emulatorPort == null || !_emulatorPort.IsOpen) return;
tagno = tagno.PadLeft(6, '0'); var tagnostr = tagno.ToString("000000");
if (tagno.Length > 6) tagno = tagno.Substring(0, 6);
var barr = new List<byte>(); var barr = new List<byte>();
barr.Add(0x02); barr.Add(0x02);
barr.Add((byte)'T'); barr.Add((byte)'T');
barr.Add((byte)'A'); barr.Add((byte)'A');
barr.Add((byte)'G'); barr.Add((byte)'G');
barr.AddRange(System.Text.Encoding.Default.GetBytes(tagno)); barr.AddRange(System.Text.Encoding.Default.GetBytes(tagnostr));
barr.Add((byte)'*'); barr.Add((byte)'*');
barr.Add((byte)'*'); barr.Add((byte)'*');
barr.Add(0x03); barr.Add(0x03);

View File

@@ -226,9 +226,6 @@
<Compile Include="Dialog\fSystem.Designer.cs"> <Compile Include="Dialog\fSystem.Designer.cs">
<DependentUpon>fSystem.cs</DependentUpon> <DependentUpon>fSystem.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Dialog\DriveDetector.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fVolume.cs"> <Compile Include="Dialog\fVolume.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -363,9 +360,6 @@
<Compile Include="StateMachine\_SPS.cs"> <Compile Include="StateMachine\_SPS.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Device\_DeviceManagement.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\_Loop.cs"> <Compile Include="StateMachine\_Loop.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>

View File

@@ -22,11 +22,31 @@ namespace Project
public Color SMSG_ShadowColor = Color.Transparent; public Color SMSG_ShadowColor = Color.Transparent;
public float SMSG_ProgressValue = 0; public float SMSG_ProgressValue = 0;
public string SMSG_Tag = string.Empty; public string SMSG_Tag = string.Empty;
//public event EventHandler SMSG_Update; /// <summary>
//public void UpdateStatusMessage() /// 이동대상위치(상차,하차,충전)
//{ /// </summary>
// SMSG_Update?.Invoke(null, null); private ePosition _targetPos = ePosition.NONE;
//} public string result_message = "";
public double result_progressmax = 0;
public double result_progressvalue = 0;
public DateTime StopMessageTimePLC = DateTime.Parse("1982-11-23");
public DateTime StopMessageTimeSWR = DateTime.Parse("1982-11-23");
public string StopMessagePLC = string.Empty;
public string StopMessageSWR = string.Empty;
private ePosition _currentpos = ePosition.NONE;
private string _currentposcw = string.Empty;
public ushort LastTAG { get; set; } = 0;
public ePosition NextPos = ePosition.NONE;
private char _comandKit { get; set; }
public string Memo;
public eResult ResultCode { get; set; }
public eECode ResultErrorCode;
public string ResultMessage { get; set; }
public Boolean isError { get; set; }
public int retry = 0;
public DateTime retryTime;
public Device.Socket.Message RecvMessage;
/// <summary> /// <summary>
/// 작업시작시간 /// 작업시작시간
/// </summary> /// </summary>
@@ -52,9 +72,6 @@ namespace Project
} }
} }
//public DateTime ChargeStartTime = DateTime.Parse("1982-11-23");
#region "AGV Status Value" #region "AGV Status Value"
public string PLC1_RawData { get; set; } public string PLC1_RawData { get; set; }
public string PLC2_RawData { get; set; } public string PLC2_RawData { get; set; }
@@ -62,25 +79,7 @@ namespace Project
#endregion #endregion
/// <summary>
/// 이동대상위치(상차,하차,충전)
/// </summary>
private ePosition _targetPos = ePosition.NONE;
public string result_message = "";
public double result_progressmax = 0;
public double result_progressvalue = 0;
public DateTime StopMessageTimePLC = DateTime.Parse("1982-11-23");
public DateTime StopMessageTimeSWR = DateTime.Parse("1982-11-23");
public string StopMessagePLC = string.Empty;
public string StopMessageSWR = string.Empty;
//public DateTime LastChar
//geTime = DateTime.Parse("1982-11-23");
public ePosition NextPos = ePosition.NONE;
public ePosition TargetPos public ePosition TargetPos
{ {
get get
@@ -94,7 +93,7 @@ namespace Project
} }
} }
private char _comandKit { get; set; }
public char CommandKit public char CommandKit
{ {
get get
@@ -110,27 +109,6 @@ namespace Project
} }
//private ePosition _currentPos = ePosition.NONE;
//public ePosition CurrentPos
//{
// get
// {
// return _currentPos;
// }
// set
// {
// if (_currentPos != value) //값이바뀔떄만 메세지 220628
// PUB.log.Add(string.Format("현재위치 설정:{0}->{1}", _currentPos, value));
// _currentPos = value;
// }
//}
private ePosition _currentpos = ePosition.NONE;
private string _currentposcw = string.Empty;
// public ePosition LastPos = ePosition.NONE;
public string LastTAG { get; set; } = string.Empty;
public ePosition CurrentPos public ePosition CurrentPos
{ {
get get
@@ -161,11 +139,7 @@ namespace Project
_currentposcw = value; _currentposcw = value;
} }
} }
public string Memo;
public eResult ResultCode { get; set; }
public eECode ResultErrorCode;
public string ResultMessage { get; set; }
#region "SetResultMessage" #region "SetResultMessage"
@@ -204,13 +178,6 @@ namespace Project
#endregion #endregion
public Boolean isError { get; set; }
public int retry = 0;
public DateTime retryTime;
public Device.Socket.Message RecvMessage;
public CResult() public CResult()
{ {
this.Clear(); this.Clear();

View File

@@ -38,6 +38,8 @@ namespace Project.Device
public Xbee() public Xbee()
{ {
this.WriteTimeout = 500;
this.ReadTimeout = 500;
this.DataReceived += Xbee_DataReceived; this.DataReceived += Xbee_DataReceived;
proto = new EEProtocol(); proto = new EEProtocol();
proto.OnDataReceived += Proto_OnDataReceived; proto.OnDataReceived += Proto_OnDataReceived;
@@ -166,12 +168,18 @@ namespace Project.Device
public bool CleanerInComplete { get; set; } public bool CleanerInComplete { get; set; }
public bool CleanerOutComplete { get; set; } public bool CleanerOutComplete { get; set; }
ManualResetEvent sendlock = new ManualResetEvent(true);
/// <summary> /// <summary>
/// AGV상태를 Xbee 로 전송한다 /// AGV상태를 Xbee 로 전송한다
/// </summary> /// </summary>
public void SendStatus() public void SendStatus()
{ {
if (this.IsOpen == false) return;
if ( sendlock.WaitOne() == false) return;
sendlock.Reset();
/* /*
Mode[1] : 0=manual, 1=auto Mode[1] : 0=manual, 1=auto
RunSt[1] : 0=stop, 1=run, 2=error RunSt[1] : 0=stop, 1=run, 2=error
@@ -246,7 +254,9 @@ namespace Project.Device
var cmd = (byte)ENIGProtocol.AGVCommandEH.Status; var cmd = (byte)ENIGProtocol.AGVCommandEH.Status;
var packet = proto.CreatePacket(PUB.setting.XBE_ID, cmd, data); var packet = proto.CreatePacket(PUB.setting.XBE_ID, cmd, data);
if (Send(packet)) if (Send(packet))
PUB.logxbee.AddI($"Send status {packet.Length} {packet.HexString()}"); PUB.logxbee.AddI($"Send status [O] : {packet.Length} {packet.HexString()}");
else
PUB.logxbee.AddE($"Send status [X] : {packet.Length} {packet.HexString()}");
LastStatusSendTime = DateTime.Now; LastStatusSendTime = DateTime.Now;
} }
catch (Exception ex) catch (Exception ex)
@@ -254,6 +264,11 @@ namespace Project.Device
errorMessage = ex.Message; errorMessage = ex.Message;
PUB.logxbee.AddE(errorMessage); PUB.logxbee.AddE(errorMessage);
} }
finally
{
sendlock.Set();
}
} }

View File

@@ -1,323 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Animation;
using AR;
using arCtl;
using COMM;
using Project.StateMachine;
namespace Project
{
/// <summary>
/// 장치 연결 및 상태 전송을 담당하는 별도 태스크
/// </summary>
public partial class fMain
{
// 장치 관리 태스크 관련
private Task deviceManagementTask;
private CancellationTokenSource deviceManagementCts;
private volatile bool isDeviceManagementRunning = false;
/// <summary>
/// 장치 관리 태스크 시작 (IDLE 상태 진입 시 호출)
/// </summary>
public void StartDeviceManagementTask()
{
if (isDeviceManagementRunning)
{
PUB.log.Add("DeviceManagement", "이미 실행 중입니다.");
return;
}
isDeviceManagementRunning = true;
deviceManagementCts = new CancellationTokenSource();
deviceManagementTask = Task.Factory.StartNew(
() => DeviceManagementWorker(deviceManagementCts.Token),
deviceManagementCts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default
);
PUB.log.Add("DeviceManagement", "장치 관리 태스크 시작");
}
/// <summary>
/// 장치 관리 태스크 종료
/// File : /device/_DeviceManagement.cs
/// </summary>
public void StopDeviceManagementTask()
{
if (!isDeviceManagementRunning)
return;
isDeviceManagementRunning = false;
try
{
deviceManagementCts?.Cancel();
if (deviceManagementTask != null)
{
if (!deviceManagementTask.Wait(3000)) // 3초 대기
{
PUB.log.AddE("DeviceManagement:태스크 종료 대기 시간 초과");
}
}
}
catch (Exception ex)
{
PUB.log.AddE($"DeviceManagement 종료 중 오류: {ex.Message}");
}
finally
{
deviceManagementCts?.Dispose();
deviceManagementCts = null;
deviceManagementTask = null;
PUB.log.Add("DeviceManagement", "장치 관리 태스크 종료");
}
}
/// <summary>
/// 장치 관리 워커 (별도 태스크에서 실행)
/// - 장치 연결 관리 (AGV, XBee, BMS)
/// - 자동 상태 전송 (XBee, BMS)
/// </summary>
private void DeviceManagementWorker(CancellationToken cancellationToken)
{
PUB.log.Add("DeviceManagementWorker", "시작");
DateTime lastXbeStatusSendTime = DateTime.Now;
DateTime lastBmsQueryTime = DateTime.Now;
while (!cancellationToken.IsCancellationRequested && isDeviceManagementRunning)
{
try
{
// 상태머신이 IDLE 이상이고 CLOSING 미만일 때만 동작
if (PUB.sm.Step >= eSMStep.IDLE && PUB.sm.Step < eSMStep.CLOSING)
{
// ========== 1. 장치 연결 관리 ==========
ManageDeviceConnections();
// ========== 2. XBee 상태 전송 ==========
if (PUB.XBE != null && PUB.XBE.IsOpen)
{
var tsXbe = DateTime.Now - lastXbeStatusSendTime;
if (tsXbe.TotalSeconds >= PUB.setting.interval_xbe)
{
lastXbeStatusSendTime = DateTime.Now;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PUB.XBE.SendStatus();
}
catch (Exception ex)
{
PUB.log.AddE($"XBee SendStatus 오류: {ex.Message}");
}
});
}
}
// ========== 3. BMS 쿼리 및 배터리 경고 ==========
if (PUB.BMS != null && PUB.BMS.IsOpen)
{
var tsBms = DateTime.Now - lastBmsQueryTime;
if (tsBms.TotalSeconds >= PUB.setting.interval_bms)
{
lastBmsQueryTime = DateTime.Now;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PUB.BMS.SendQuery();
}
catch (Exception ex)
{
PUB.log.AddE($"BMS SendQuery 오류: {ex.Message}");
}
});
}
// 배터리 경고음
try
{
Update_BatteryWarnSpeak();
}
catch (Exception ex)
{
PUB.log.AddE($"BatteryWarnSpeak 오류: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
PUB.log.AddE($"DeviceManagementWorker 오류: {ex.Message}");
}
// 1초 대기 (또는 취소 요청 시 즉시 종료)
try
{
Task.Delay(1000, cancellationToken).Wait();
}
catch (OperationCanceledException)
{
break;
}
}
PUB.log.Add("DeviceManagementWorker", "종료");
}
/// <summary>
/// 장치 연결 상태 관리
/// </summary>
private void ManageDeviceConnections()
{
try
{
// AGV 연결
ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV);
// XBee 연결
ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE,
eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, eVarTime.LastRecv_XBE);
// BMS 연결
if (PUB.BMS.IsOpen == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT);
if (ts.TotalSeconds > 3)
{
PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}");
PUB.BMS.PortName = PUB.setting.Port_BAT;
if (PUB.BMS.Open())
PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})");
VAR.TIME.Update(eVarTime.LastConn_BAT);
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
}
}
else if (PUB.BMS.IsValid == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5))
{
PUB.log.Add("BMS 자동 연결 해제 (응답 없음)");
PUB.BMS.Close();
VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5));
}
}
}
catch (Exception ex)
{
PUB.log.AddE($"ManageDeviceConnections 오류: {ex.Message}");
}
}
/// <summary>
/// 시리얼 포트 연결 (arDev.arRS232)
/// </summary>
bool ConnectSerialPort(arDev.arRS232 dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
{
if(port.isEmpty()) return false;
if (dev.IsOpen == false && port.isEmpty() == false)
{
var tsPLC = VAR.TIME.RUN(conntry);
if (tsPLC.TotalSeconds > 5)
{
VAR.TIME.Update(conntry);
try
{
VAR.TIME.Update(recvtime);
dev.PortName = port;
dev.BaudRate = baud;
if (dev.Open())
{
PUB.log.Add(port, $"[AGV:{port}:{baud}] 연결 완료");
}
else
{
//존재하지 않는 포트라면 sync를 벗어난다
var ports = System.IO.Ports.SerialPort.GetPortNames().Select(t => t.ToLower()).ToList();
if (ports.Contains(PUB.setting.Port_AGV.ToLower()) == false)
{
return false;
}
else
{
var errmessage = dev.errorMessage;
PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}");
}
}
VAR.TIME.Update(conn);
VAR.TIME.Update(conntry);
}
catch (Exception ex)
{
PUB.log.AddE(ex.Message);
}
}
}
else if (dev.PortName.Equals(port) == false)
{
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
dev.Close();
VAR.TIME.Update(conntry);
}
return true;
}
/// <summary>
/// 시리얼 포트 연결 (Device.Xbee)
/// </summary>
void ConnectSerialPort(Device.Xbee dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
{
if (dev.IsOpen == false && port.isEmpty() == false)
{
var tsPLC = VAR.TIME.RUN(conntry);
if (tsPLC.TotalSeconds > 5)
{
VAR.TIME.Update(conntry);
try
{
VAR.TIME.Update(recvtime);
dev.PortName = port;
dev.BaudRate = baud;
if (dev.Open())
{
PUB.log.Add(port, $"[XBEE:{port}:{baud}] 연결 완료");
}
else
{
var errmessage = dev.errorMessage;
PUB.log.AddE($"[XBEE:{port}:{baud}] {errmessage}");
}
VAR.TIME.Update(conn);
VAR.TIME.Update(conntry);
}
catch (Exception ex)
{
PUB.log.AddE(ex.Message);
}
}
}
else if (dev.PortName.Equals(port) == false)
{
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
dev.Close();
VAR.TIME[(int)conntry] = DateTime.Now;
}
}
}
}

View File

@@ -1,815 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms; // required for Message
using System.Runtime.InteropServices; // required for Marshal
using System.IO;
using Microsoft.Win32.SafeHandles;
// DriveDetector - rev. 1, Oct. 31 2007
namespace usbdetect
{
/// <summary>
/// Hidden Form which we use to receive Windows messages about flash drives
/// </summary>
internal class DetectorForm : Form
{
private Label label1;
private DriveDetector mDetector = null;
/// <summary>
/// Set up the hidden form.
/// </summary>
/// <param name="detector">DriveDetector object which will receive notification about USB drives, see WndProc</param>
public DetectorForm(DriveDetector detector)
{
mDetector = detector;
this.MinimizeBox = false;
this.MaximizeBox = false;
this.ShowInTaskbar = false;
this.ShowIcon = false;
this.FormBorderStyle = FormBorderStyle.None;
this.Load += new System.EventHandler(this.Load_Form);
this.Activated += new EventHandler(this.Form_Activated);
}
private void Load_Form(object sender, EventArgs e)
{
// We don't really need this, just to display the label in designer ...
InitializeComponent();
// Create really small form, invisible anyway.
this.Size = new System.Drawing.Size(5, 5);
}
private void Form_Activated(object sender, EventArgs e)
{
this.Visible = false;
}
/// <summary>
/// This function receives all the windows messages for this window (form).
/// We call the DriveDetector from here so that is can pick up the messages about
/// drives arrived and removed.
/// </summary>
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (mDetector != null)
{
mDetector.WndProc(ref m);
}
}
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 30);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(377, 12);
this.label1.TabIndex = 0;
this.label1.Text = "This is invisible form. To see DriveDetector code click View Code";
//
// DetectorForm
//
this.ClientSize = new System.Drawing.Size(435, 80);
this.Controls.Add(this.label1);
this.Name = "DetectorForm";
this.ResumeLayout(false);
this.PerformLayout();
}
} // class DetectorForm
// Delegate for event handler to handle the device events
public delegate void DriveDetectorEventHandler(Object sender, DriveDetectorEventArgs e);
/// <summary>
/// Our class for passing in custom arguments to our event handlers
///
/// </summary>
public class DriveDetectorEventArgs : EventArgs
{
public DriveDetectorEventArgs()
{
Cancel = false;
Drive = "";
HookQueryRemove = false;
}
/// <summary>
/// Get/Set the value indicating that the event should be cancelled
/// Only in QueryRemove handler.
/// </summary>
public bool Cancel;
/// <summary>
/// Drive letter for the device which caused this event
/// </summary>
public string Drive;
/// <summary>
/// Set to true in your DeviceArrived event handler if you wish to receive the
/// QueryRemove event for this drive.
/// </summary>
public bool HookQueryRemove;
}
/// <summary>
/// Detects insertion or removal of removable drives.
/// Use it in 1 or 2 steps:
/// 1) Create instance of this class in your project and add handlers for the
/// DeviceArrived, DeviceRemoved and QueryRemove events.
/// AND (if you do not want drive detector to creaate a hidden form))
/// 2) Override WndProc in your form and call DriveDetector's WndProc from there.
/// If you do not want to do step 2, just use the DriveDetector constructor without arguments and
/// it will create its own invisible form to receive messages from Windows.
/// </summary>
class DriveDetector : IDisposable
{
/// <summary>
/// Events signalized to the client app.
/// Add handlers for these events in your form to be notified of removable device events
/// </summary>
public event DriveDetectorEventHandler DeviceArrived;
public event DriveDetectorEventHandler DeviceRemoved;
public event DriveDetectorEventHandler QueryRemove;
/// <summary>
/// The easiest way to use DriveDetector.
/// It will create hidden form for processing Windows messages about USB drives
/// You do not need to override WndProc in your form.
/// </summary>
public DriveDetector()
{
DetectorForm frm = new DetectorForm(this);
frm.Show(); // will be hidden immediatelly
Init(frm, null);
}
/// <summary>
/// Alternate constructor.
/// Pass in your Form and DriveDetector will not create hidden form.
/// </summary>
/// <param name="control">object which will receive Windows messages.
/// Pass "this" as this argument from your form class.</param>
public DriveDetector(Control control)
{
Init(control, null);
}
/// <summary>
/// Consructs DriveDetector object setting also path to file which should be opened
/// when registering for query remove.
/// </summary>
///<param name="control">object which will receive Windows messages.
/// Pass "this" as this argument from your form class.</param>
/// <param name="FileToOpen">Optional. Name of a file on the removable drive which should be opened.
/// If null, root directory of the drive will be opened. Opening a file is needed for us
/// to be able to register for the query remove message. TIP: For files use relative path without drive letter.
/// e.g. "SomeFolder\file_on_flash.txt"</param>
public DriveDetector(Control control, string FileToOpen)
{
Init(control, FileToOpen);
}
/// <summary>
/// init the DriveDetector object
/// </summary>
/// <param name="intPtr"></param>
private void Init(Control control, string fileToOpen)
{
mFileToOpen = fileToOpen;
mFileOnFlash = null;
mDeviceNotifyHandle = IntPtr.Zero;
mRecipientHandle = control.Handle;
mDirHandle = IntPtr.Zero; // handle to the root directory of the flash drive which we open
mCurrentDrive = "";
}
/// <summary>
/// Gets the value indicating whether the query remove event will be fired.
/// </summary>
public bool IsQueryHooked
{
get
{
if (mDeviceNotifyHandle == IntPtr.Zero)
return false;
else
return true;
}
}
/// <summary>
/// Gets letter of drive which is currently hooked. Empty string if none.
/// See also IsQueryHooked.
/// </summary>
public string HookedDrive
{
get
{
return mCurrentDrive;
}
}
/// <summary>
/// Gets the file stream for file which this class opened on a drive to be notified
/// about it's removal.
/// This will be null unless you specified a file to open (DriveDetector opens root directory of the flash drive)
/// </summary>
public FileStream OpenedFile
{
get
{
return mFileOnFlash;
}
}
/// <summary>
/// Hooks specified drive to receive a message when it is being removed.
/// This can be achieved also by setting e.HookQueryRemove to true in your
/// DeviceArrived event handler.
/// By default DriveDetector will open the root directory of the flash drive to obtain notification handle
/// from Windows (to learn when the drive is about to be removed).
/// </summary>
/// <param name="fileOnDrive">Drive letter or relative path to a file on the drive which should be
/// used to get a handle - required for registering to receive query remove messages.
/// If only drive letter is specified (e.g. "D:\\", root directory of the drive will be opened.</param>
/// <returns>true if hooked ok, false otherwise</returns>
public bool EnableQueryRemove(string fileOnDrive)
{
if (fileOnDrive == null || fileOnDrive.Length == 0)
throw new ArgumentException("Drive path must be supplied to register for Query remove.");
if ( fileOnDrive.Length == 2 && fileOnDrive[1] == ':' )
fileOnDrive += '\\'; // append "\\" if only drive letter with ":" was passed in.
if (mDeviceNotifyHandle != IntPtr.Zero)
{
// Unregister first...
RegisterForDeviceChange(false, null);
}
if (Path.GetFileName(fileOnDrive).Length == 0 ||!File.Exists(fileOnDrive))
mFileToOpen = null; // use root directory...
else
mFileToOpen = fileOnDrive;
RegisterQuery(Path.GetPathRoot(fileOnDrive));
if (mDeviceNotifyHandle == IntPtr.Zero)
return false; // failed to register
return true;
}
/// <summary>
/// Unhooks any currently hooked drive so that the query remove
/// message is not generated for it.
/// </summary>
public void DisableQueryRemove()
{
if (mDeviceNotifyHandle != IntPtr.Zero)
{
RegisterForDeviceChange(false, null);
}
}
/// <summary>
/// Unregister and close the file we may have opened on the removable drive.
/// Garbage collector will call this method.
/// </summary>
public void Dispose()
{
RegisterForDeviceChange(false, null);
}
#region WindowProc
/// <summary>
/// Message handler which must be called from client form.
/// Processes Windows messages and calls event handlers.
/// </summary>
/// <param name="m"></param>
public void WndProc(ref Message m)
{
int devType;
char c;
if (m.Msg == WM_DEVICECHANGE)
{
// WM_DEVICECHANGE can have several meanings depending on the WParam value...
switch (m.WParam.ToInt32())
{
//
// New device has just arrived
//
case DBT_DEVICEARRIVAL:
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME vol;
vol = (DEV_BROADCAST_VOLUME)
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
// Get the drive letter
c = DriveMaskToLetter(vol.dbcv_unitmask);
//
// Call the client event handler
//
// We should create copy of the event before testing it and
// calling the delegate - if any
DriveDetectorEventHandler tempDeviceArrived = DeviceArrived;
if ( tempDeviceArrived != null )
{
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
e.Drive = c + ":\\";
tempDeviceArrived(this, e);
// Register for query remove if requested
if (e.HookQueryRemove)
{
// If something is already hooked, unhook it now
if (mDeviceNotifyHandle != IntPtr.Zero)
{
RegisterForDeviceChange(false, null);
}
RegisterQuery(c + ":\\");
}
} // if has event handler
}
break;
//
// Device is about to be removed
// Any application can cancel the removal
//
case DBT_DEVICEQUERYREMOVE:
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_HANDLE)
{
// TODO: we could get the handle for which this message is sent
// from vol.dbch_handle and compare it against a list of handles for
// which we have registered the query remove message (?)
//DEV_BROADCAST_HANDLE vol;
//vol = (DEV_BROADCAST_HANDLE)
// Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HANDLE));
// if ( vol.dbch_handle ....
//
// Call the event handler in client
//
DriveDetectorEventHandler tempQuery = QueryRemove;
if (tempQuery != null)
{
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
e.Drive = mCurrentDrive; // drive which is hooked
tempQuery(this, e);
// If the client wants to cancel, let Windows know
if (e.Cancel)
{
m.Result = (IntPtr)BROADCAST_QUERY_DENY;
}
else
{
// Change 28.10.2007: Unregister the notification, this will
// close the handle to file or root directory also.
// We have to close it anyway to allow the removal so
// even if some other app cancels the removal we would not know about it...
RegisterForDeviceChange(false, null); // will also close the mFileOnFlash
}
}
}
break;
//
// Device has been removed
//
case DBT_DEVICEREMOVECOMPLETE:
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_VOLUME)
{
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME vol;
vol = (DEV_BROADCAST_VOLUME)
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
c = DriveMaskToLetter(vol.dbcv_unitmask);
//
// Call the client event handler
//
DriveDetectorEventHandler tempDeviceRemoved = DeviceRemoved;
if (tempDeviceRemoved != null)
{
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
e.Drive = c + ":\\";
tempDeviceRemoved(this, e);
}
// TODO: we could unregister the notify handle here if we knew it is the
// right drive which has been just removed
//RegisterForDeviceChange(false, null);
}
}
break;
}
}
}
#endregion
#region Private Area
/// <summary>
/// New: 28.10.2007 - handle to root directory of flash drive which is opened
/// for device notification
/// </summary>
private IntPtr mDirHandle = IntPtr.Zero;
/// <summary>
/// Class which contains also handle to the file opened on the flash drive
/// </summary>
private FileStream mFileOnFlash = null;
/// <summary>
/// Name of the file to try to open on the removable drive for query remove registration
/// </summary>
private string mFileToOpen;
/// <summary>
/// Handle to file which we keep opened on the drive if query remove message is required by the client
/// </summary>
private IntPtr mDeviceNotifyHandle;
/// <summary>
/// Handle of the window which receives messages from Windows. This will be a form.
/// </summary>
private IntPtr mRecipientHandle;
/// <summary>
/// Drive which is currently hooked for query remove
/// </summary>
private string mCurrentDrive;
// Win32 constants
private const int DBT_DEVTYP_DEVICEINTERFACE = 5;
private const int DBT_DEVTYP_HANDLE = 6;
private const int BROADCAST_QUERY_DENY = 0x424D5144;
private const int WM_DEVICECHANGE = 0x0219;
private const int DBT_DEVICEARRIVAL = 0x8000; // system detected a new device
private const int DBT_DEVICEQUERYREMOVE = 0x8001; // Preparing to remove (any program can disable the removal)
private const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // removed
private const int DBT_DEVTYP_VOLUME = 0x00000002; // drive type is logical volume
/// <summary>
/// Registers for receiving the query remove message for a given drive.
/// We need to open a handle on that drive and register with this handle.
/// Client can specify this file in mFileToOpen or we will open root directory of the drive
/// </summary>
/// <param name="drive">drive for which to register. </param>
private void RegisterQuery(string drive)
{
bool register = true;
if (mFileToOpen == null)
{
// Change 28.10.2007 - Open the root directory if no file specified - leave mFileToOpen null
// If client gave us no file, let's pick one on the drive...
//mFileToOpen = GetAnyFile(drive);
//if (mFileToOpen.Length == 0)
// return; // no file found on the flash drive
}
else
{
// Make sure the path in mFileToOpen contains valid drive
// If there is a drive letter in the path, it may be different from the actual
// letter assigned to the drive now. We will cut it off and merge the actual drive
// with the rest of the path.
if (mFileToOpen.Contains(":"))
{
string tmp = mFileToOpen.Substring(3);
string root = Path.GetPathRoot(drive);
mFileToOpen = Path.Combine(root, tmp);
}
else
mFileToOpen = Path.Combine(drive, mFileToOpen);
}
try
{
//mFileOnFlash = new FileStream(mFileToOpen, FileMode.Open);
// Change 28.10.2007 - Open the root directory
if (mFileToOpen == null) // open root directory
mFileOnFlash = null;
else
mFileOnFlash = new FileStream(mFileToOpen, FileMode.Open);
}
catch (Exception)
{
// just do not register if the file could not be opened
register = false;
}
if (register)
{
//RegisterForDeviceChange(true, mFileOnFlash.SafeFileHandle);
//mCurrentDrive = drive;
// Change 28.10.2007 - Open the root directory
if (mFileOnFlash == null)
RegisterForDeviceChange(drive);
else
// old version
RegisterForDeviceChange(true, mFileOnFlash.SafeFileHandle);
mCurrentDrive = drive;
}
}
/// <summary>
/// New version which gets the handle automatically for specified directory
/// Only for registering! Unregister with the old version of this function...
/// </summary>
/// <param name="register"></param>
/// <param name="dirPath">e.g. C:\\dir</param>
private void RegisterForDeviceChange(string dirPath)
{
IntPtr handle = Native.OpenDirectory(dirPath);
if (handle == IntPtr.Zero)
{
mDeviceNotifyHandle = IntPtr.Zero;
return;
}
else
mDirHandle = handle; // save handle for closing it when unregistering
// Register for handle
DEV_BROADCAST_HANDLE data = new DEV_BROADCAST_HANDLE();
data.dbch_devicetype = DBT_DEVTYP_HANDLE;
data.dbch_reserved = 0;
data.dbch_nameoffset = 0;
//data.dbch_data = null;
//data.dbch_eventguid = 0;
data.dbch_handle = handle;
data.dbch_hdevnotify = (IntPtr)0;
int size = Marshal.SizeOf(data);
data.dbch_size = size;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(data, buffer, true);
mDeviceNotifyHandle = Native.RegisterDeviceNotification(mRecipientHandle, buffer, 0);
}
/// <summary>
/// Registers to be notified when the volume is about to be removed
/// This is requierd if you want to get the QUERY REMOVE messages
/// </summary>
/// <param name="register">true to register, false to unregister</param>
/// <param name="fileHandle">handle of a file opened on the removable drive</param>
private void RegisterForDeviceChange(bool register, SafeFileHandle fileHandle)
{
if (register)
{
// Register for handle
DEV_BROADCAST_HANDLE data = new DEV_BROADCAST_HANDLE();
data.dbch_devicetype = DBT_DEVTYP_HANDLE;
data.dbch_reserved = 0;
data.dbch_nameoffset = 0;
//data.dbch_data = null;
//data.dbch_eventguid = 0;
data.dbch_handle = fileHandle.DangerousGetHandle(); //Marshal. fileHandle;
data.dbch_hdevnotify = (IntPtr)0;
int size = Marshal.SizeOf(data);
data.dbch_size = size;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(data, buffer, true);
mDeviceNotifyHandle = Native.RegisterDeviceNotification(mRecipientHandle, buffer, 0);
}
else
{
// close the directory handle
if (mDirHandle != IntPtr.Zero)
{
Native.CloseDirectoryHandle(mDirHandle);
// string er = Marshal.GetLastWin32Error().ToString();
}
// unregister
if (mDeviceNotifyHandle != IntPtr.Zero)
{
Native.UnregisterDeviceNotification(mDeviceNotifyHandle);
}
mDeviceNotifyHandle = IntPtr.Zero;
mDirHandle = IntPtr.Zero;
mCurrentDrive = "";
if (mFileOnFlash != null)
{
mFileOnFlash.Close();
mFileOnFlash = null;
}
}
}
/// <summary>
/// Gets drive letter from a bit mask where bit 0 = A, bit 1 = B etc.
/// There can actually be more than one drive in the mask but we
/// just use the last one in this case.
/// </summary>
/// <param name="mask"></param>
/// <returns></returns>
private static char DriveMaskToLetter(int mask)
{
char letter;
string drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 1 = A
// 2 = B
// 4 = C...
int cnt = 0;
int pom = mask / 2;
while (pom != 0)
{
// while there is any bit set in the mask
// shift it to the righ...
pom = pom / 2;
cnt++;
}
if (cnt < drives.Length)
letter = drives[cnt];
else
letter = '?';
return letter;
}
/* 28.10.2007 - no longer needed
/// <summary>
/// Searches for any file in a given path and returns its full path
/// </summary>
/// <param name="drive">drive to search</param>
/// <returns>path of the file or empty string</returns>
private string GetAnyFile(string drive)
{
string file = "";
// First try files in the root
string[] files = Directory.GetFiles(drive);
if (files.Length == 0)
{
// if no file in the root, search whole drive
files = Directory.GetFiles(drive, "*.*", SearchOption.AllDirectories);
}
if (files.Length > 0)
file = files[0]; // get the first file
// return empty string if no file found
return file;
}*/
#endregion
#region Native Win32 API
/// <summary>
/// WinAPI functions
/// </summary>
private class Native
{
// HDEVNOTIFY RegisterDeviceNotification(HANDLE hRecipient,LPVOID NotificationFilter,DWORD Flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, uint Flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint UnregisterDeviceNotification(IntPtr hHandle);
//
// CreateFile - MSDN
const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING = 3;
const uint FILE_SHARE_READ = 0x00000001;
const uint FILE_SHARE_WRITE = 0x00000002;
const uint FILE_ATTRIBUTE_NORMAL = 128;
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
// should be "static extern unsafe"
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
uint SecurityAttributes, // Security Attributes
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
int hTemplateFile // handle to template file
);
[DllImport("kernel32", SetLastError = true)]
static extern bool CloseHandle(
IntPtr hObject // handle to object
);
/// <summary>
/// Opens a directory, returns it's handle or zero.
/// </summary>
/// <param name="dirPath">path to the directory, e.g. "C:\\dir"</param>
/// <returns>handle to the directory. Close it with CloseHandle().</returns>
static public IntPtr OpenDirectory(string dirPath)
{
// open the existing file for reading
IntPtr handle = CreateFile(
dirPath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
0);
if ( handle == INVALID_HANDLE_VALUE)
return IntPtr.Zero;
else
return handle;
}
public static bool CloseDirectoryHandle(IntPtr handle)
{
return CloseHandle(handle);
}
}
// Structure with information for RegisterDeviceNotification.
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HANDLE
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
public IntPtr dbch_handle;
public IntPtr dbch_hdevnotify;
public Guid dbch_eventguid;
public long dbch_nameoffset;
//public byte[] dbch_data[1]; // = new byte[1];
public byte dbch_data;
public byte dbch_data1;
}
// Struct for parameters of the WM_DEVICECHANGE message
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_VOLUME
{
public int dbcv_size;
public int dbcv_devicetype;
public int dbcv_reserved;
public int dbcv_unitmask;
}
#endregion
}
}

View File

@@ -647,7 +647,7 @@ namespace Project
/// <param name="rfidId">읽은 RFID ID</param> /// <param name="rfidId">읽은 RFID ID</param>
/// <param name="motorDirection">모터 방향 (Forward/Backward)</param> /// <param name="motorDirection">모터 방향 (Forward/Backward)</param>
/// <returns>업데이트 성공 여부</returns> /// <returns>업데이트 성공 여부</returns>
public static bool UpdateAGVFromRFID(string rfidId, AgvDirection motorDirection = AgvDirection.Forward) public static bool UpdateAGVFromRFID(ushort rfidId, AgvDirection motorDirection = AgvDirection.Forward)
{ {
var _mapNodes = PUB._mapCanvas.Nodes; var _mapNodes = PUB._mapCanvas.Nodes;
if (_virtualAGV == null || _mapNodes == null) return false; if (_virtualAGV == null || _mapNodes == null) return false;

View File

@@ -17,9 +17,6 @@ namespace Project
{ {
PUB.bShutdown = true; PUB.bShutdown = true;
// 장치 관리 태스크 종료
StopDeviceManagementTask();
PUB.AddEEDB("프로그램 종료"); PUB.AddEEDB("프로그램 종료");
PUB.log.Add("Program Close"); PUB.log.Add("Program Close");
PUB.LogFlush(); PUB.LogFlush();

View File

@@ -19,10 +19,8 @@ namespace Project
private void AGV_Message(object sender, arDev.Narumi.MessageEventArgs e) private void AGV_Message(object sender, arDev.Narumi.MessageEventArgs e)
{ {
if (e.MsgType == arDev.arRS232.MessageType.Normal) if (e.MsgType == arDev.arRS232.MessageType.Normal)
PUB.logagv.AddE(e.Message); PUB.logagv.AddE(e.Message);
else if (e.MsgType == arDev.arRS232.MessageType.Normal) else if (e.MsgType == arDev.arRS232.MessageType.Normal)
PUB.logagv.Add(e.Message); PUB.logagv.Add(e.Message);
else if (e.MsgType == arDev.arRS232.MessageType.Recv) else if (e.MsgType == arDev.arRS232.MessageType.Recv)
{ {
@@ -33,7 +31,6 @@ namespace Project
PUB.logagv.Add("AGV-TX", e.Message); PUB.logagv.Add("AGV-TX", e.Message);
else else
{ {
PUB.logagv.Add(e.MsgType.ToString(), e.Message); PUB.logagv.Add(e.MsgType.ToString(), e.Message);
} }
} }
@@ -44,6 +41,7 @@ namespace Project
{ {
try try
{ {
VAR.TIME.Set(eVarTime.LastRecv_AGV, DateTime.Now);
//데이터 파싱 //데이터 파싱
switch (e.DataType) switch (e.DataType)
{ {
@@ -161,7 +159,7 @@ namespace Project
{ {
//자동 실행 중이다. //자동 실행 중이다.
PUB.Result.LastTAG = PUB.AGV.data.TagNo.ToString("0000"); PUB.Result.LastTAG = PUB.AGV.data.TagNo;//.ToString("0000");
PUB.log.Add($"AGV 태그수신 : {PUB.AGV.data.TagNo} LastTag:{PUB.Result.LastTAG}"); PUB.log.Add($"AGV 태그수신 : {PUB.AGV.data.TagNo} LastTag:{PUB.Result.LastTAG}");
//POT/NOT 보면 일단 바로 멈추게한다 //POT/NOT 보면 일단 바로 멈추게한다
if (PUB.Result.CurrentPos == ePosition.POT || PUB.Result.CurrentPos == ePosition.NOT) if (PUB.Result.CurrentPos == ePosition.POT || PUB.Result.CurrentPos == ePosition.NOT)
@@ -172,7 +170,7 @@ namespace Project
} }
//virtual agv setting //virtual agv setting
var CurrentNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId.Equals(PUB.Result.LastTAG, StringComparison.OrdinalIgnoreCase)); var CurrentNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB.Result.LastTAG);
if (CurrentNode == null) if (CurrentNode == null)
{ {
//없는 노드는 자동으로 추가한다 //없는 노드는 자동으로 추가한다
@@ -237,7 +235,6 @@ namespace Project
PUB._mapCanvas.PredictMessage = message; PUB._mapCanvas.PredictMessage = message;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -154,8 +154,6 @@ namespace Project
UpdateStatusMessage("준비 완료", Color.Red, Color.Gold); UpdateStatusMessage("준비 완료", Color.Red, Color.Gold);
if (startuptime.Year == 1982) startuptime = DateTime.Now; if (startuptime.Year == 1982) startuptime = DateTime.Now;
// 장치 관리 태스크 시작 (IDLE 진입 시 한 번만)
StartDeviceManagementTask();
// 동기화 모드 종료 (혹시 남아있을 경우) // 동기화 모드 종료 (혹시 남아있을 경우)
if (PUB._mapCanvas != null) if (PUB._mapCanvas != null)

View File

@@ -21,22 +21,230 @@ namespace Project
{ {
DateTime chargesynctime = DateTime.Now; DateTime chargesynctime = DateTime.Now;
DateTime agvsendstarttime = DateTime.Now; DateTime agvsendstarttime = DateTime.Now;
DateTime lastXbeStatusSendTime = DateTime.Now;
DateTime lastBmsQueryTime = DateTime.Now;
void sm_SPS(object sender, EventArgs e) void sm_SPS(object sender, EventArgs e)
{ {
if (PUB.sm.Step < eSMStep.IDLE || PUB.sm.Step >= eSMStep.CLOSING) return; if (PUB.sm == null || PUB.sm.Step < eSMStep.IDLE || PUB.sm.Step >= eSMStep.CLOSING || PUB.bShutdown == true) return;
// SPS는 이제 간단한 작업만 수행 // SPS는 이제 간단한 작업만 수행
// 장치 연결 및 상태 전송은 별도 태스크(_DeviceManagement.cs)에서 처리 // 장치 연결 및 상태 전송은 별도 태스크(_DeviceManagement.cs)에서 처리
// 장치 연결이 별도로 존재할때 1회 수신 후 통신이 전체 먹통되는 증상이 있어 우선 복귀 251215
try try
{ {
// 여기에 SPS에서 처리해야 할 간단한 작업만 남김
// 현재는 비어있음 - 필요한 경우 추가 // ========== 1. 장치 연결 관리 ==========
// AGV 연결
ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV);
// XBee 연결
ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE,
eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, eVarTime.LastRecv_XBE);
// BMS 연결
if (PUB.BMS.IsOpen == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT);
if (ts.TotalSeconds > 3)
{
PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}");
PUB.BMS.PortName = PUB.setting.Port_BAT;
if (PUB.BMS.Open())
PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})");
VAR.TIME.Update(eVarTime.LastConn_BAT);
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
}
}
else if (PUB.BMS.IsValid == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5))
{
this.BeginInvoke(new Action(() => {
PUB.log.Add("BMS 자동 연결 해제 (응답 없음)");
PUB.BMS.Close();
}));
VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5));
}
}
// ========== 2. XBee 상태 전송 ==========
if (PUB.XBE != null && PUB.XBE.IsOpen)
{
var tsXbe = DateTime.Now - lastXbeStatusSendTime;
if (tsXbe.TotalSeconds >= PUB.setting.interval_xbe)
{
lastXbeStatusSendTime = DateTime.Now;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PUB.XBE.SendStatus();
}
catch (Exception ex)
{
PUB.log.AddE($"XBee SendStatus 오류: {ex.Message}");
}
});
}
}
// ========== 3. BMS 쿼리 및 배터리 경고 ==========
if (PUB.BMS != null && PUB.BMS.IsOpen)
{
var tsBms = DateTime.Now - lastBmsQueryTime;
if (tsBms.TotalSeconds >= PUB.setting.interval_bms)
{
lastBmsQueryTime = DateTime.Now;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PUB.BMS.SendQuery();
}
catch (Exception ex)
{
PUB.log.AddE($"BMS SendQuery 오류: {ex.Message}");
}
});
}
// 배터리 경고음
try
{
Update_BatteryWarnSpeak();
}
catch (Exception ex)
{
PUB.log.AddE($"BatteryWarnSpeak 오류: {ex.Message}");
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
PUB.log.AddE($"sm_SPS Exception: {ex.Message}"); PUB.log.AddE($"sm_SPS Exception: {ex.Message}");
} }
} }
/// <summary>
/// 시리얼 포트 연결 (arDev.arRS232)
/// </summary>
bool ConnectSerialPort(arDev.arRS232 dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
{
if (port.isEmpty()) return false;
if (dev.IsOpen == false && port.isEmpty() == false)
{
var tsPLC = VAR.TIME.RUN(conntry);
if (tsPLC.TotalSeconds > 5)
{
VAR.TIME.Update(conntry);
try
{
VAR.TIME.Update(recvtime);
dev.PortName = port;
dev.BaudRate = baud;
PUB.log.Add($"Connect to {port}:{baud}");
if (dev.Open())
{
PUB.log.Add(port, $"[AGV:{port}:{baud}] 연결 완료");
}
else
{
//존재하지 않는 포트라면 sync를 벗어난다
var ports = System.IO.Ports.SerialPort.GetPortNames().Select(t => t.ToLower()).ToList();
if (ports.Contains(PUB.setting.Port_AGV.ToLower()) == false)
{
return false;
}
else
{
var errmessage = dev.errorMessage;
PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}");
}
}
VAR.TIME.Update(conn);
VAR.TIME.Update(conntry);
}
catch (Exception ex)
{
PUB.log.AddE(ex.Message);
}
}
}
else if (dev.PortName.Equals(port) == false)
{
this.BeginInvoke(new Action(() => {
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
dev.Close();
}));
VAR.TIME.Update(conntry);
}
else if (dev.IsOpen)
{
//연결은 되었으나 통신이 지난지 10초가 지났다면 자동종료한다
var tsRecv = VAR.TIME.RUN(recvtime);
var tsConn = VAR.TIME.RUN(conntry);
if (tsRecv.TotalSeconds > 10 && tsConn.TotalSeconds > 5)
{
this.BeginInvoke(new Action(() => {
PUB.log.Add($"{port} 자동 연결 해제 (응답 없음)");
dev.Close();
}));
VAR.TIME.Set(conntry, DateTime.Now);
}
}
return true;
}
/// <summary>
/// 시리얼 포트 연결 (Device.Xbee)
/// </summary>
void ConnectSerialPort(Device.Xbee dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
{
if (dev.IsOpen == false && port.isEmpty() == false)
{
var tsPLC = VAR.TIME.RUN(conntry);
if (tsPLC.TotalSeconds > 5)
{
VAR.TIME.Update(conntry);
try
{
VAR.TIME.Update(recvtime);
dev.PortName = port;
dev.BaudRate = baud;
if (dev.Open())
{
PUB.log.Add(port, $"[XBEE:{port}:{baud}] 연결 완료");
}
else
{
var errmessage = dev.errorMessage;
PUB.log.AddE($"[XBEE:{port}:{baud}] {errmessage}");
}
VAR.TIME.Update(conn);
VAR.TIME.Update(conntry);
}
catch (Exception ex)
{
PUB.log.AddE(ex.Message);
}
}
}
else if (dev.PortName.Equals(port) == false)
{
this.BeginInvoke(new Action(() => {
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
dev.Close();
}));
VAR.TIME[(int)conntry] = DateTime.Now;
}
}
} }
} }

View File

@@ -48,7 +48,9 @@ namespace Project
if (data.Length > 4) if (data.Length > 4)
{ {
var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1); var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1);
var node = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currTag); if (ushort.TryParse(currTag, out ushort currtagValue))
{
var node = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagValue);
if (node == null) if (node == null)
{ {
PUB.log.AddE($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}"); PUB.log.AddE($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
@@ -63,6 +65,8 @@ namespace Project
PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, node, PUB._virtualAGV.CurrentDirection); PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, node, PUB._virtualAGV.CurrentDirection);
PUB._virtualAGV.SetPosition(node, PUB._virtualAGV.CurrentDirection); PUB._virtualAGV.SetPosition(node, PUB._virtualAGV.CurrentDirection);
} }
else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Value Errorr:{data}");
}
else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Lenght Errorr:{data.Length}"); else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Lenght Errorr:{data.Length}");
break; break;
@@ -111,7 +115,9 @@ namespace Project
if (data.Length > 4) if (data.Length > 4)
{ {
var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1); var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1);
var targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currTag); if(ushort.TryParse(currTag, out ushort currtagvalue))
{
var targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagvalue);
//자동상태가아니라면 처리하지 않는다. //자동상태가아니라면 처리하지 않는다.
@@ -131,7 +137,7 @@ namespace Project
} }
///출발지 ///출발지
var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB._virtualAGV.CurrentNode.Id); var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB._virtualAGV.CurrentNode.RfidId);
PUB._virtualAGV.StartNode = startNode; PUB._virtualAGV.StartNode = startNode;
if (startNode == null) if (startNode == null)
{ {
@@ -160,9 +166,10 @@ namespace Project
//Move to //Move to
PUB.log.Add($"[{logPrefix}-Goto] {startNode.RfidId} -> {targetNode.RfidId}"); PUB.log.Add($"[{logPrefix}-Goto] {startNode.RfidId} -> {targetNode.RfidId}");
} }
else PUB.log.AddE($"[{logPrefix}-Goto] TagString Lenght Errorr:{data.Length}"); else PUB.log.AddE($"[{logPrefix}-Goto] TagString Value Error:{data}");
}
else PUB.log.AddE($"[{logPrefix}-Goto] TagString Lenght Error:{data.Length}");
break; break;
case ENIGProtocol.AGVCommandHE.Stop: //stop case ENIGProtocol.AGVCommandHE.Stop: //stop

View File

@@ -48,13 +48,14 @@
this.button4 = new System.Windows.Forms.Button(); this.button4 = new System.Windows.Forms.Button();
this.button9 = new System.Windows.Forms.Button(); this.button9 = new System.Windows.Forms.Button();
this.panel2 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel();
this.button16 = new System.Windows.Forms.Button(); this.button15 = new System.Windows.Forms.Button();
this.button10 = new System.Windows.Forms.Button(); this.button14 = new System.Windows.Forms.Button();
this.button11 = new System.Windows.Forms.Button(); this.button11 = new System.Windows.Forms.Button();
this.button12 = new System.Windows.Forms.Button(); this.button12 = new System.Windows.Forms.Button();
this.button13 = new System.Windows.Forms.Button(); this.button13 = new System.Windows.Forms.Button();
this.button14 = new System.Windows.Forms.Button(); this.button10 = new System.Windows.Forms.Button();
this.button15 = new System.Windows.Forms.Button(); this.button16 = new System.Windows.Forms.Button();
this.lbPortName = new System.Windows.Forms.Label();
this.tableLayoutPanel1.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout();
this.panel1.SuspendLayout(); this.panel1.SuspendLayout();
this.panel2.SuspendLayout(); this.panel2.SuspendLayout();
@@ -269,6 +270,7 @@
// //
// panel2 // panel2
// //
this.panel2.Controls.Add(this.lbPortName);
this.panel2.Controls.Add(this.button15); this.panel2.Controls.Add(this.button15);
this.panel2.Controls.Add(this.button14); this.panel2.Controls.Add(this.button14);
this.panel2.Controls.Add(this.button11); this.panel2.Controls.Add(this.button11);
@@ -283,27 +285,27 @@
this.panel2.Size = new System.Drawing.Size(1050, 58); this.panel2.Size = new System.Drawing.Size(1050, 58);
this.panel2.TabIndex = 8; this.panel2.TabIndex = 8;
// //
// button16 // button15
// //
this.button16.Dock = System.Windows.Forms.DockStyle.Left; this.button15.Dock = System.Windows.Forms.DockStyle.Left;
this.button16.Location = new System.Drawing.Point(0, 0); this.button15.Location = new System.Drawing.Point(523, 0);
this.button16.Name = "button16"; this.button15.Name = "button15";
this.button16.Size = new System.Drawing.Size(162, 58); this.button15.Size = new System.Drawing.Size(84, 58);
this.button16.TabIndex = 0; this.button15.TabIndex = 14;
this.button16.Text = "백턴유지시간"; this.button15.Text = "Mag Off";
this.button16.UseVisualStyleBackColor = true; this.button15.UseVisualStyleBackColor = true;
this.button16.Click += new System.EventHandler(this.button16_Click); this.button15.Click += new System.EventHandler(this.button15_Click);
// //
// button10 // button14
// //
this.button10.Dock = System.Windows.Forms.DockStyle.Left; this.button14.Dock = System.Windows.Forms.DockStyle.Left;
this.button10.Location = new System.Drawing.Point(162, 0); this.button14.Location = new System.Drawing.Point(439, 0);
this.button10.Name = "button10"; this.button14.Name = "button14";
this.button10.Size = new System.Drawing.Size(162, 58); this.button14.Size = new System.Drawing.Size(84, 58);
this.button10.TabIndex = 1; this.button14.TabIndex = 13;
this.button10.Text = "GateOut Off Time"; this.button14.Text = "Mag On";
this.button10.UseVisualStyleBackColor = true; this.button14.UseVisualStyleBackColor = true;
this.button10.Click += new System.EventHandler(this.button10_Click); this.button14.Click += new System.EventHandler(this.button14_Click);
// //
// button11 // button11
// //
@@ -338,27 +340,39 @@
this.button13.UseVisualStyleBackColor = true; this.button13.UseVisualStyleBackColor = true;
this.button13.Click += new System.EventHandler(this.button13_Click); this.button13.Click += new System.EventHandler(this.button13_Click);
// //
// button14 // button10
// //
this.button14.Dock = System.Windows.Forms.DockStyle.Left; this.button10.Dock = System.Windows.Forms.DockStyle.Left;
this.button14.Location = new System.Drawing.Point(439, 0); this.button10.Location = new System.Drawing.Point(162, 0);
this.button14.Name = "button14"; this.button10.Name = "button10";
this.button14.Size = new System.Drawing.Size(84, 58); this.button10.Size = new System.Drawing.Size(162, 58);
this.button14.TabIndex = 13; this.button10.TabIndex = 1;
this.button14.Text = "Mag On"; this.button10.Text = "GateOut Off Time";
this.button14.UseVisualStyleBackColor = true; this.button10.UseVisualStyleBackColor = true;
this.button14.Click += new System.EventHandler(this.button14_Click); this.button10.Click += new System.EventHandler(this.button10_Click);
// //
// button15 // button16
// //
this.button15.Dock = System.Windows.Forms.DockStyle.Left; this.button16.Dock = System.Windows.Forms.DockStyle.Left;
this.button15.Location = new System.Drawing.Point(523, 0); this.button16.Location = new System.Drawing.Point(0, 0);
this.button15.Name = "button15"; this.button16.Name = "button16";
this.button15.Size = new System.Drawing.Size(84, 58); this.button16.Size = new System.Drawing.Size(162, 58);
this.button15.TabIndex = 14; this.button16.TabIndex = 0;
this.button15.Text = "Mag Off"; this.button16.Text = "백턴유지시간";
this.button15.UseVisualStyleBackColor = true; this.button16.UseVisualStyleBackColor = true;
this.button15.Click += new System.EventHandler(this.button15_Click); this.button16.Click += new System.EventHandler(this.button16_Click);
//
// lbPortName
//
this.lbPortName.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbPortName.Font = new System.Drawing.Font("Tahoma", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.lbPortName.ForeColor = System.Drawing.Color.White;
this.lbPortName.Location = new System.Drawing.Point(607, 0);
this.lbPortName.Name = "lbPortName";
this.lbPortName.Size = new System.Drawing.Size(203, 58);
this.lbPortName.TabIndex = 15;
this.lbPortName.Text = "--";
this.lbPortName.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// //
// fAgv // fAgv
// //
@@ -409,5 +423,6 @@
private System.Windows.Forms.Button button11; private System.Windows.Forms.Button button11;
private System.Windows.Forms.Button button12; private System.Windows.Forms.Button button12;
private System.Windows.Forms.Button button13; private System.Windows.Forms.Button button13;
private System.Windows.Forms.Label lbPortName;
} }
} }

View File

@@ -42,7 +42,7 @@ namespace Project.ViewForm
richTextBox2.Rtf = PUB.AGV.system1.ToRtfString(); richTextBox2.Rtf = PUB.AGV.system1.ToRtfString();
richTextBox3.Rtf = CombineRtfStrings(PUB.AGV.signal.ToRtfString(), PUB.AGV.data.ToRtfString()); richTextBox3.Rtf = CombineRtfStrings(PUB.AGV.signal.ToRtfString(), PUB.AGV.data.ToRtfString());
richTextBox4.Rtf = PUB.AGV.error.ToRtfString(); richTextBox4.Rtf = PUB.AGV.error.ToRtfString();
lbPortName.Text = $"AGV:{PUB.setting.Port_AGV}\nBMS:{PUB.setting.Port_BAT}";
timer1.Start(); timer1.Start();
} }

View File

@@ -30,28 +30,11 @@ namespace Project.ViewForm
InitializeMapCanvas(); InitializeMapCanvas();
//PUB.mapctl = new AGVControl.MapControl();
//PUB.mapctl.Dock = DockStyle.Fill;
//PUB.mapctl.Visible = true;
//PUB.mapctl.Font = this.panel1.Font;
//PUB.mapctl.BackColor = Color.FromArgb(32, 32, 32);
//this.panel1.Controls.Add(PUB.mapctl);
} }
private void InitializeMapCanvas() private void InitializeMapCanvas()
{ {
// RfidMappings 제거 - MapNode에 통합됨 PUB._mapCanvas.NodeSelect += OnNodeSelected;;
// 이벤트 연결
//PUB._mapCanvas.NodeAdded += OnNodeAdded;
// 이벤트 연결
//PUB._mapCanvas.NodeAdded += OnNodeAdded;
PUB._mapCanvas.NodeSelect += OnNodeSelected;
//PUB._mapCanvas.NodeMoved += OnNodeMoved;
//PUB._mapCanvas.NodeDeleted += OnNodeDeleted;
//PUB._mapCanvas.ConnectionDeleted += OnConnectionDeleted;
//PUB._mapCanvas.ImageNodeDoubleClicked += OnImageNodeDoubleClicked;
//PUB._mapCanvas.MapChanged += OnMapChanged;
// 스플리터 패널에 맵 캔버스 추가 // 스플리터 패널에 맵 캔버스 추가
panel1.Controls.Add(PUB._mapCanvas); panel1.Controls.Add(PUB._mapCanvas);
@@ -153,83 +136,6 @@ namespace Project.ViewForm
PUB.AGV.DataReceive += AGV_DataReceive; PUB.AGV.DataReceive += AGV_DataReceive;
//auto load
var mapPath = new System.IO.DirectoryInfo("route");
if (mapPath.Exists == false) mapPath.Create();
//맵파일로딩
if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.json");
System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile);
if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName, "default.json"));
if (filePath.Exists == false) //그래도없다면 맵폴더에서 파일을 찾아본다.
{
var files = mapPath.GetFiles("*.json");
if (files.Any()) filePath = files[0];
}
if (filePath.Exists)
{
var result = MapLoader.LoadMapFromFile(filePath.FullName);
if (result.Success)
{
if (PUB._mapCanvas.Nodes == null) PUB._mapCanvas.Nodes = new List<MapNode>();
else PUB._mapCanvas.Nodes.Clear();
PUB._mapCanvas.Nodes.AddRange(result.Nodes);
// 맵 캔버스에 데이터 설정
PUB._mapCanvas.Nodes = PUB._mapCanvas.Nodes;
PUB._mapCanvas.MapFileName = filePath.FullName;
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
if (result.Settings != null)
{
PUB._mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
PUB._mapCanvas.ShowGrid = result.Settings.ShowGrid;
}
// 🔥 가상 AGV 초기화 (첫 노드 위치에 생성)
if (PUB._virtualAGV == null && PUB._mapCanvas.Nodes.Count > 0)
{
var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(n => n.IsNavigationNode());
if (startNode != null)
{
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward);
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
// 캔버스에 AGV 리스트 설정
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
PUB._mapCanvas.AGVList = agvList;
PUB.log.Add($"가상 AGV 생성: {startNode.Id} 위치");
}
}
else if (PUB._virtualAGV != null)
{
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
// 기존 AGV가 있으면 캔버스에 다시 연결
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
PUB._mapCanvas.AGVList = agvList;
}
// 맵 로드 후 자동으로 맵에 맞춤
PUB._mapCanvas.FitToNodes();
PUB.log.Add($"맵 파일 로드 완료: {filePath.Name}, 노드 수: {result.Nodes.Count}");
}
else
{
PUB.log.Add($"맵 파일 로딩 실패: {result.ErrorMessage}");
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
PUB.log.Add($"맵 파일을 찾을 수 없습니다: {filePath.FullName}");
}
//var fn = string.Empty; //var fn = string.Empty;
//if (files.Any() == false) //if (files.Any() == false)

View File

@@ -33,7 +33,6 @@ namespace Project
bool remoteClose = false; bool remoteClose = false;
bool forceClose = false; bool forceClose = false;
readonly usbdetect.DriveDetector usbdet;
public fMain() public fMain()
{ {
InitializeComponent(); InitializeComponent();
@@ -57,10 +56,6 @@ namespace Project
if (DateTime.Now > PUB.LastInputTime) PUB.LastInputTime = DateTime.Now; if (DateTime.Now > PUB.LastInputTime) PUB.LastInputTime = DateTime.Now;
}; };
usbdet = new usbdetect.DriveDetector(this);
usbdet.DeviceArrived += Usbdet_DeviceArrived;
usbdet.DeviceRemoved += Usbdet_DeviceRemoved;
PUB._mapCanvas = new AGVNavigationCore.Controls.UnifiedAGVCanvas(); PUB._mapCanvas = new AGVNavigationCore.Controls.UnifiedAGVCanvas();
PUB._mapCanvas.Dock = DockStyle.Fill; PUB._mapCanvas.Dock = DockStyle.Fill;
@@ -68,8 +63,6 @@ namespace Project
PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32); PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32);
PUB._mapCanvas.ForeColor = Color.White; PUB._mapCanvas.ForeColor = Color.White;
this.panTopMenu.MouseMove += LbTitle_MouseMove; this.panTopMenu.MouseMove += LbTitle_MouseMove;
this.panTopMenu.MouseUp += LbTitle_MouseUp; this.panTopMenu.MouseUp += LbTitle_MouseUp;
this.panTopMenu.MouseDown += LbTitle_MouseDown; this.panTopMenu.MouseDown += LbTitle_MouseDown;
@@ -82,32 +75,6 @@ namespace Project
} }
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (usbdet != null)
{
usbdet.WndProc(ref m);
}
}
private void Usbdet_DeviceRemoved(object sender, usbdetect.DriveDetectorEventArgs e)
{
//throw new NotImplementedException();
Console.WriteLine(e.Drive);
}
private void Usbdet_DeviceArrived(object sender, usbdetect.DriveDetectorEventArgs e)
{
//throw new NotImplementedException();
using (var fUpdate = new Dialog.fUpdateForm(e.Drive))
if (fUpdate.ShowDialog() == DialogResult.Yes)
{
//종료한다
remoteClose = true;
this.Close();
}
}
private void __Closing(object sender, FormClosingEventArgs e) private void __Closing(object sender, FormClosingEventArgs e)
{ {
@@ -182,6 +149,9 @@ namespace Project
VAR.STR[eVarString.SWVersion] = Application.ProductVersion; VAR.STR[eVarString.SWVersion] = Application.ProductVersion;
AutoLoadMapData();
//SETTING H/W //SETTING H/W
this.IOState.ItemClick += gridView2_ItemClick; this.IOState.ItemClick += gridView2_ItemClick;
VAR.BOOL.PropertyChanged += BOOL_PropertyChanged; VAR.BOOL.PropertyChanged += BOOL_PropertyChanged;
@@ -290,6 +260,71 @@ namespace Project
PUB.AddEEDB("프로그램 시작"); PUB.AddEEDB("프로그램 시작");
} }
void AutoLoadMapData()
{
//auto load
var mapPath = new System.IO.DirectoryInfo("route");
if (mapPath.Exists == false) mapPath.Create();
//맵파일로딩
if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.json");
System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile);
if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName, "default.json"));
if (filePath.Exists == false) //그래도없다면 맵폴더에서 파일을 찾아본다.
{
var files = mapPath.GetFiles("*.json");
if (files.Any()) filePath = files[0];
}
if (filePath.Exists)
{
var result = MapLoader.LoadMapFromFile(filePath.FullName);
if (result.Success)
{
PUB._mapCanvas.SetMapLoadResult(result);
PUB._mapCanvas.MapFileName = filePath.FullName;
// 🔥 가상 AGV 초기화 (첫 노드 위치에 생성)
if (PUB._virtualAGV == null && PUB._mapCanvas.Nodes.Count > 0)
{
var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(n => n.IsNavigationNode());
if (startNode != null)
{
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward);
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
// 캔버스에 AGV 리스트 설정
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
PUB._mapCanvas.AGVList = agvList;
PUB.log.Add($"가상 AGV 생성: {startNode.Id} 위치");
}
}
else if (PUB._virtualAGV != null)
{
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
// 기존 AGV가 있으면 캔버스에 다시 연결
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
PUB._mapCanvas.AGVList = agvList;
}
PUB.log.Add($"맵 파일 로드 완료: {filePath.Name}, 노드 수: {result.Nodes.Count}");
}
else
{
PUB.log.Add($"맵 파일 로딩 실패: {result.ErrorMessage}");
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
PUB.log.Add($"맵 파일을 찾을 수 없습니다: {filePath.FullName}");
}
}
#region "Mouse Form Move" #region "Mouse Form Move"
private Boolean fMove = false; private Boolean fMove = false;