diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs index 28f5d78..6ad2a96 100644 --- a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs +++ b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs @@ -36,20 +36,20 @@ namespace AGVMapEditor.Forms { public string FromNodeId { get; set; } public string FromNodeName { get; set; } - public string FromRfidId { get; set; } + public ushort FromRfidId { get; set; } public string ToNodeId { get; set; } public string ToNodeName { get; set; } - public string ToRfidId { get; set; } + public ushort ToRfidId { get; set; } public string ConnectionType { get; set; } public override string ToString() { // RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시 - string fromDisplay = !string.IsNullOrEmpty(FromRfidId) + string fromDisplay = FromRfidId > 0 ? $"{FromRfidId}({FromNodeName})" : $"---({FromNodeId})"; - string toDisplay = !string.IsNullOrEmpty(ToRfidId) + string toDisplay = ToRfidId > 0 ? $"{ToRfidId}({ToNodeName})" : $"---({ToNodeId})"; @@ -420,7 +420,7 @@ namespace AGVMapEditor.Forms return; } - var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? ""; + var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? 0; var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.", "삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question); @@ -1107,8 +1107,8 @@ namespace AGVMapEditor.Forms // RFID 값 변경시 중복 검사 if (e.ChangedItem.PropertyDescriptor.Name == "RFID") { - string newRfidValue = e.ChangedItem.Value?.ToString(); - if (!string.IsNullOrEmpty(newRfidValue) && CheckRfidDuplicate(newRfidValue)) + var newRfidValue = ushort.Parse(e.ChangedItem.Value?.ToString()); + if (newRfidValue != 0 && CheckRfidDuplicate(newRfidValue)) { // 중복된 RFID 값 발견 MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.", @@ -1174,29 +1174,15 @@ namespace AGVMapEditor.Forms /// /// 검사할 RFID 값 /// 중복되면 true, 아니면 false - 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; // 현재 편집 중인 노드 제외하고 중복 검사 string currentNodeId = null; 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; foreach (var node in this._mapCanvas.Nodes) { @@ -1205,7 +1191,7 @@ namespace AGVMapEditor.Forms continue; // 같은 RFID 값을 가진 노드가 있는지 확인 - if (!string.IsNullOrEmpty(node.RfidId) && node.RfidId.Equals(rfidValue, StringComparison.OrdinalIgnoreCase)) + if (node.RfidId != 0 && node.RfidId == rfidValue) { duplicateCount++; break; // 하나라도 발견되면 중복 diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index 26014c2..79eb226 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -1140,7 +1140,8 @@ namespace AGVNavigationCore.Controls // 위쪽에 표시할 이름 (노드의 Name 속성) - string TopIDText = node.HasRfid() ? node.RfidId : $"[{node.Id}]"; + string TopIDText = node.HasRfid() ? node.RfidId.ToString("0000") : $"[{node.Id}]"; + // 아래쪽에 표시할 값 (RFID 우선, 없으면 노드ID) string BottomLabelText = node.Text; diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs index cdfd576..c6d81be 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs @@ -121,7 +121,7 @@ namespace AGVNavigationCore.Controls if (hitNode == null) return; - if (hitNode.Type == NodeType.Normal) + if (hitNode.Type == NodeType.Normal) { HandleNormalNodeDoubleClick(hitNode as MapNode); } @@ -146,15 +146,19 @@ namespace AGVNavigationCore.Controls private void HandleNormalNodeDoubleClick(MapNode node) { // RFID 입력창 표시 - string currentRfid = node.RfidId ?? ""; + var currentRfid = node.RfidId; string newRfid = Microsoft.VisualBasic.Interaction.InputBox( $"노드 '{node.RfidId}[{node.Id}]'의 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); Invalidate(); } @@ -229,7 +233,7 @@ namespace AGVNavigationCore.Controls return; } - + } // 팬 시작 (좌클릭 - 모드에 따라) @@ -272,7 +276,7 @@ namespace AGVNavigationCore.Controls // 호버 업데이트 var newHoveredNode = GetItemAt(worldPoint); - bool hoverChanged = (newHoveredNode != _hoveredNode) ; + bool hoverChanged = (newHoveredNode != _hoveredNode); if (hoverChanged) { @@ -700,8 +704,8 @@ namespace AGVNavigationCore.Controls { // 연결선을 클릭했을 때 삭제 확인 var (fromNode, toNode) = connection.Value; - string fromDisplay = !string.IsNullOrEmpty(fromNode.RfidId) ? fromNode.RfidId : fromNode.Id; - string toDisplay = !string.IsNullOrEmpty(toNode.RfidId) ? toNode.RfidId : toNode.Id; + string fromDisplay = fromNode.HasRfid() ? fromNode.RfidId.ToString("0000") : fromNode.Id; + string toDisplay = toNode.HasRfid() ? toNode.RfidId.ToString("0000") : toNode.Id; var result = MessageBox.Show( $"연결을 삭제하시겠습니까?\n\n{fromDisplay} ↔ {toDisplay}", diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs index bff437e..4555ef3 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs @@ -851,13 +851,13 @@ namespace AGVNavigationCore.Controls return; // RFID값과 해당 노드의 인덱스를 저장 - var rfidToNodeIndex = new Dictionary>(); + var rfidToNodeIndex = new Dictionary>(); // 모든 노드의 RFID값 수집 for (int i = 0; i < _nodes.Count; i++) { var node = _nodes[i]; - if (!string.IsNullOrEmpty(node.RfidId)) + if (node.HasRfid()) { if (!rfidToNodeIndex.ContainsKey(node.RfidId)) { diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs index 641c7fd..51e3702 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs @@ -83,7 +83,7 @@ namespace AGVNavigationCore.Models [Category("RFID 정보")] [Description("물리적 RFID 태그 ID입니다.")] - public string RfidId { get; set; } = string.Empty; + public UInt16 RfidId { get; set; } = 0; [Category("노드 텍스트"), DisplayName("TextColor")] @@ -161,7 +161,7 @@ namespace AGVNavigationCore.Models public bool HasRfid() { - return !string.IsNullOrEmpty(RfidId); + return RfidId > 0; } } } \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs index e658cdb..af428dd 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs @@ -611,18 +611,7 @@ namespace AGVNavigationCore.Models PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode)); } - /// - /// 현재 RFID 시뮬레이션 (현재 위치 기준) - /// - public string SimulateRfidReading(List mapNodes) - { - var closestNode = FindClosestNode(_currentPosition, mapNodes); - if (closestNode == null) - return null; - - return closestNode.HasRfid() ? closestNode.RfidId : null; - } - + #endregion @@ -630,10 +619,11 @@ namespace AGVNavigationCore.Models /// /// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용) /// - public string GetRfidByNodeId(List _mapNodes, string nodeId) + public ushort GetRfidByNodeId(List _mapNodes, string 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; } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index 4bf93e8..0ad07df 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -447,7 +447,7 @@ namespace AGVNavigationCore.PathFinding.Planning { var node = path1.Path[i]; 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; // 노드 정보 생성 (현재 방향 유지) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs index 3fdc685..79a1686 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs @@ -770,9 +770,9 @@ namespace AGVNavigationCore.PathFinding.Planning private string GetDisplayName(string 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})"; } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs index 760dba0..597fc25 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs @@ -40,7 +40,7 @@ namespace AGVNavigationCore.PathFinding.Planning /// /// RFID Value /// - public string RfidId { get; set; } + public ushort RfidId { get; set; } /// /// 해당 노드에서의 모터방향 @@ -87,7 +87,7 @@ namespace AGVNavigationCore.PathFinding.Planning /// 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; NodeId = nodeId; diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs index 2dd7faf..028ffb9 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs @@ -204,7 +204,7 @@ namespace AGVNavigationCore.Utils } Console.WriteLine( - $"\n 최종선택: {bestNode?.RfidId ?? "null"}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})"); + $"\n 최종선택: {bestNode?.RfidId ?? 0}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})"); Console.WriteLine( $"[GetNextNodeByDirection] ========== 다음 노드 선택 종료 ==========\n"); diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalPathfinderTest.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalPathfinderTest.cs index 672fb6c..08b28b9 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalPathfinderTest.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalPathfinderTest.cs @@ -16,12 +16,12 @@ namespace AGVNavigationCore.Utils public class DirectionalPathfinderTest { private List _allNodes; - private Dictionary _nodesByRfidId; + private Dictionary _nodesByRfidId; private AGVDirectionCalculator _calculator; public DirectionalPathfinderTest() { - _nodesByRfidId = new Dictionary(); + _nodesByRfidId = new Dictionary(); _calculator = new AGVDirectionCalculator(); } @@ -52,7 +52,7 @@ namespace AGVNavigationCore.Utils // RFID ID로 인덱싱 foreach (var node in _allNodes) { - if (!string.IsNullOrEmpty(node.RfidId)) + if (node.HasRfid()) { _nodesByRfidId[node.RfidId] = node; } @@ -71,7 +71,7 @@ namespace AGVNavigationCore.Utils /// /// 테스트: RFID 번호로 노드를 찾고, 다음 노드를 계산 /// - public void TestDirectionalMovement(string previousRfidId, string currentRfidId, AgvDirection direction) + public void TestDirectionalMovement(ushort previousRfidId, ushort currentRfidId, AgvDirection direction) { Console.WriteLine($"\n========================================"); Console.WriteLine($"테스트: {previousRfidId} → {currentRfidId} (방향: {direction})"); @@ -140,7 +140,7 @@ namespace AGVNavigationCore.Utils /// /// 특정 RFID 노드의 상세 정보 출력 /// - public void PrintNodeInfo(string rfidId) + public void PrintNodeInfo(ushort rfidId) { if (!_nodesByRfidId.TryGetValue(rfidId, out var node)) { diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/GetNextNodeIdTest.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/GetNextNodeIdTest.cs index 454a652..43fba4f 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/GetNextNodeIdTest.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/GetNextNodeIdTest.cs @@ -28,10 +28,10 @@ namespace AGVNavigationCore.Utils Console.WriteLine("================================================\n"); // 테스트 노드 생성 - var node001 = new MapNode { Id = "N001", RfidId = "001", Position = new Point(65, 229), ConnectedNodes = new List { "N002" } }; - var node002 = new MapNode { Id = "N002", RfidId = "002", Position = new Point(206, 244), ConnectedNodes = new List { "N001", "N003" } }; - var node003 = new MapNode { Id = "N003", RfidId = "003", Position = new Point(278, 278), ConnectedNodes = new List { "N002", "N004" } }; - var node004 = new MapNode { Id = "N004", RfidId = "004", Position = new Point(380, 340), ConnectedNodes = new List { "N003", "N022", "N031" } }; + var node001 = new MapNode { Id = "N001", RfidId = 001, Position = new Point(65, 229), ConnectedNodes = new List { "N002" } }; + var node002 = new MapNode { Id = "N002", RfidId = 002, Position = new Point(206, 244), ConnectedNodes = new List { "N001", "N003" } }; + var node003 = new MapNode { Id = "N003", RfidId = 003, Position = new Point(278, 278), ConnectedNodes = new List { "N002", "N004" } }; + var node004 = new MapNode { Id = "N004", RfidId = 004, Position = new Point(380, 340), ConnectedNodes = new List { "N003", "N022", "N031" } }; var allNodes = new List { node001, node002, node003, node004 }; @@ -114,7 +114,7 @@ namespace AGVNavigationCore.Utils AgvDirection motorDir = currentMotorDirection ?? direction; 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($"현재 모터 방향: {motorDir}"); Console.WriteLine($"요청 방향: {direction}"); diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/TestRunner.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/TestRunner.cs index f621f78..2cdf995 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/TestRunner.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/TestRunner.cs @@ -29,26 +29,26 @@ namespace AGVNavigationCore.Utils tester.PrintAllNodes(); // 테스트 시나리오 1: 001 → 002 → Forward (003 기대) - tester.PrintNodeInfo("001"); - tester.PrintNodeInfo("002"); - tester.TestDirectionalMovement("001", "002", AgvDirection.Forward); + tester.PrintNodeInfo(001); + tester.PrintNodeInfo(002); + tester.TestDirectionalMovement(001, 002, AgvDirection.Forward); // 테스트 시나리오 2: 002 → 001 → Backward (000 또는 이전 기대) - tester.TestDirectionalMovement("002", "001", AgvDirection.Backward); + tester.TestDirectionalMovement(002, 001, AgvDirection.Backward); // 테스트 시나리오 3: 002 → 003 → Forward - tester.PrintNodeInfo("003"); - tester.TestDirectionalMovement("002", "003", AgvDirection.Forward); + tester.PrintNodeInfo(003); + tester.TestDirectionalMovement(002, 003, AgvDirection.Forward); // 테스트 시나리오 4: 003 → 004 → Forward - tester.PrintNodeInfo("004"); - tester.TestDirectionalMovement("003", "004", AgvDirection.Forward); + tester.PrintNodeInfo(004); + tester.TestDirectionalMovement(003, 004, AgvDirection.Forward); // 테스트 시나리오 5: 003 → 004 → Right (030 기대) - tester.TestDirectionalMovement("003", "004", AgvDirection.Right); + tester.TestDirectionalMovement(003, 004, AgvDirection.Right); // 테스트 시나리오 6: 004 → 003 → Backward - tester.TestDirectionalMovement("004", "003", AgvDirection.Backward); + tester.TestDirectionalMovement(004, 003, AgvDirection.Backward); Console.WriteLine("\n\n=== 테스트 완료 ==="); } diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs index 4aa4eb1..cec54cb 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs @@ -635,7 +635,7 @@ namespace AGVSimulator.Forms /// /// 맵 스캔 모드에서 RFID로부터 노드 생성 /// - private void CreateNodeFromRfidScan(string rfidId, VirtualAGV selectedAGV) + private void CreateNodeFromRfidScan(ushort rfidId, VirtualAGV selectedAGV) { try { @@ -644,7 +644,7 @@ namespace AGVSimulator.Forms var currentDirection = directionItem?.Direction ?? AgvDirection.Forward; // 중복 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) { // 이미 존재하는 노드로 이동 @@ -805,7 +805,7 @@ namespace AGVSimulator.Forms if (agv.CurrentNodeId != null && agv.CurrentNodeId != _lastSentNodeId) { var rfid = GetRfidByNodeId(agv.CurrentNodeId); - if (!string.IsNullOrEmpty(rfid)) + if (rfid > 0) { SendTag(rfid); _lastSentNodeId = agv.CurrentNodeId; @@ -842,7 +842,7 @@ namespace AGVSimulator.Forms // RFID 값 확인 var rfidId = _rfidTextBox.Text.Trim(); - if (string.IsNullOrEmpty(rfidId)) + if (ushort.TryParse(rfidId,out ushort rfidvalue)==false) { MessageBox.Show("RFID 값을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; @@ -856,13 +856,13 @@ namespace AGVSimulator.Forms // 맵 스캔 모드일 때: 노드 자동 생성 if (_isMapScanMode) { - CreateNodeFromRfidScan(rfidId, selectedAGV); + CreateNodeFromRfidScan(rfidvalue, selectedAGV); this._simulatorCanvas.FitToNodes(); return; } // 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) { MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}", @@ -1255,10 +1255,12 @@ namespace AGVSimulator.Forms /// /// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용) /// - private string GetRfidByNodeId(string nodeId) + private ushort GetRfidByNodeId(string 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; } /// @@ -1267,9 +1269,9 @@ namespace AGVSimulator.Forms private string GetDisplayName(string 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})"; } @@ -1366,7 +1368,7 @@ namespace AGVSimulator.Forms { var info = advancedResult.DetailedPath[i]; 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(); if (info.CanRotate) flags.Add("회전가능"); @@ -1616,12 +1618,8 @@ namespace AGVSimulator.Forms /// private string GetNodeDisplayName(MapNode node) { - if (node == null) - return "-"; - - if (!string.IsNullOrEmpty(node.RfidId)) - return node.RfidId; - + if (node == null) return "-"; + if (node.HasRfid()) return node.RfidId.ToString("0000"); return $"({node.Id})"; } @@ -1792,7 +1790,7 @@ namespace AGVSimulator.Forms this.Invoke((MethodInvoker)delegate { // RFID 텍스트박스에 값 입력 - _rfidTextBox.Text = nodeA.RfidId; + _rfidTextBox.Text = nodeA.RfidId.ToString(); // 방향 콤보박스 선택 SetDirectionComboBox(direction); @@ -1808,7 +1806,7 @@ namespace AGVSimulator.Forms this.Invoke((MethodInvoker)delegate { // RFID 텍스트박스에 값 입력 - _rfidTextBox.Text = nodeB.RfidId; + _rfidTextBox.Text = nodeB.RfidId.ToString(); // 방향 콤보박스 선택 SetDirectionComboBox(direction); @@ -2415,19 +2413,18 @@ namespace AGVSimulator.Forms catch { } } - public void SendTag(string tagno) + public void SendTag(ushort tagno) { if (_emulatorPort == null || !_emulatorPort.IsOpen) return; - tagno = tagno.PadLeft(6, '0'); - if (tagno.Length > 6) tagno = tagno.Substring(0, 6); + var tagnostr = tagno.ToString("000000"); var barr = new List(); barr.Add(0x02); barr.Add((byte)'T'); barr.Add((byte)'A'); 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(0x03); diff --git a/Cs_HMI/Project/AGV4.csproj b/Cs_HMI/Project/AGV4.csproj index d5bd2c9..119d01b 100644 --- a/Cs_HMI/Project/AGV4.csproj +++ b/Cs_HMI/Project/AGV4.csproj @@ -226,9 +226,6 @@ fSystem.cs - - Form - Form @@ -363,9 +360,6 @@ Form - - Form - Form diff --git a/Cs_HMI/Project/Class/CResult.cs b/Cs_HMI/Project/Class/CResult.cs index 0eb814d..4cde1ac 100644 --- a/Cs_HMI/Project/Class/CResult.cs +++ b/Cs_HMI/Project/Class/CResult.cs @@ -22,11 +22,31 @@ namespace Project public Color SMSG_ShadowColor = Color.Transparent; public float SMSG_ProgressValue = 0; public string SMSG_Tag = string.Empty; - //public event EventHandler SMSG_Update; - //public void UpdateStatusMessage() - //{ - // 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; + /// /// 작업시작시간 /// @@ -52,9 +72,6 @@ namespace Project } } - //public DateTime ChargeStartTime = DateTime.Parse("1982-11-23"); - - #region "AGV Status Value" public string PLC1_RawData { get; set; } public string PLC2_RawData { get; set; } @@ -62,25 +79,7 @@ namespace Project #endregion - /// - /// 이동대상위치(상차,하차,충전) - /// - 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 { get @@ -94,7 +93,7 @@ namespace Project } } - private char _comandKit { get; set; } + public char CommandKit { get @@ -109,28 +108,7 @@ 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 { get @@ -161,11 +139,7 @@ namespace Project _currentposcw = value; } } - public string Memo; - - public eResult ResultCode { get; set; } - public eECode ResultErrorCode; - public string ResultMessage { get; set; } + #region "SetResultMessage" @@ -203,14 +177,7 @@ namespace Project #endregion - - - public Boolean isError { get; set; } - - public int retry = 0; - public DateTime retryTime; - public Device.Socket.Message RecvMessage; - + public CResult() { this.Clear(); diff --git a/Cs_HMI/Project/Device/Xbee.cs b/Cs_HMI/Project/Device/Xbee.cs index cd67b98..4e99c9f 100644 --- a/Cs_HMI/Project/Device/Xbee.cs +++ b/Cs_HMI/Project/Device/Xbee.cs @@ -38,6 +38,8 @@ namespace Project.Device public Xbee() { + this.WriteTimeout = 500; + this.ReadTimeout = 500; this.DataReceived += Xbee_DataReceived; proto = new EEProtocol(); proto.OnDataReceived += Proto_OnDataReceived; @@ -166,12 +168,18 @@ namespace Project.Device public bool CleanerInComplete { get; set; } public bool CleanerOutComplete { get; set; } + ManualResetEvent sendlock = new ManualResetEvent(true); + /// /// AGV상태를 Xbee 로 전송한다 /// public void SendStatus() { + if (this.IsOpen == false) return; + if ( sendlock.WaitOne() == false) return; + sendlock.Reset(); + /* Mode[1] : 0=manual, 1=auto RunSt[1] : 0=stop, 1=run, 2=error @@ -246,7 +254,9 @@ namespace Project.Device var cmd = (byte)ENIGProtocol.AGVCommandEH.Status; var packet = proto.CreatePacket(PUB.setting.XBE_ID, cmd, data); 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; } catch (Exception ex) @@ -254,6 +264,11 @@ namespace Project.Device errorMessage = ex.Message; PUB.logxbee.AddE(errorMessage); } + finally + { + sendlock.Set(); + } + } diff --git a/Cs_HMI/Project/Device/_DeviceManagement.cs b/Cs_HMI/Project/Device/_DeviceManagement.cs deleted file mode 100644 index 93af0a2..0000000 --- a/Cs_HMI/Project/Device/_DeviceManagement.cs +++ /dev/null @@ -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 -{ - /// - /// 장치 연결 및 상태 전송을 담당하는 별도 태스크 - /// - public partial class fMain - { - // 장치 관리 태스크 관련 - private Task deviceManagementTask; - private CancellationTokenSource deviceManagementCts; - private volatile bool isDeviceManagementRunning = false; - - /// - /// 장치 관리 태스크 시작 (IDLE 상태 진입 시 호출) - /// - 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", "장치 관리 태스크 시작"); - } - - /// - /// 장치 관리 태스크 종료 - /// File : /device/_DeviceManagement.cs - /// - 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", "장치 관리 태스크 종료"); - } - } - - /// - /// 장치 관리 워커 (별도 태스크에서 실행) - /// - 장치 연결 관리 (AGV, XBee, BMS) - /// - 자동 상태 전송 (XBee, BMS) - /// - 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", "종료"); - } - - /// - /// 장치 연결 상태 관리 - /// - 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}"); - } - } - - /// - /// 시리얼 포트 연결 (arDev.arRS232) - /// - 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; - } - - /// - /// 시리얼 포트 연결 (Device.Xbee) - /// - 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; - } - } - } -} diff --git a/Cs_HMI/Project/Dialog/DriveDetector.cs b/Cs_HMI/Project/Dialog/DriveDetector.cs deleted file mode 100644 index 91ab200..0000000 --- a/Cs_HMI/Project/Dialog/DriveDetector.cs +++ /dev/null @@ -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 -{ - /// - /// Hidden Form which we use to receive Windows messages about flash drives - /// - internal class DetectorForm : Form - { - private Label label1; - private DriveDetector mDetector = null; - - /// - /// Set up the hidden form. - /// - /// DriveDetector object which will receive notification about USB drives, see WndProc - 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; - } - - /// - /// 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. - /// - 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); - - /// - /// Our class for passing in custom arguments to our event handlers - /// - /// - public class DriveDetectorEventArgs : EventArgs - { - - - public DriveDetectorEventArgs() - { - Cancel = false; - Drive = ""; - HookQueryRemove = false; - } - - /// - /// Get/Set the value indicating that the event should be cancelled - /// Only in QueryRemove handler. - /// - public bool Cancel; - - /// - /// Drive letter for the device which caused this event - /// - public string Drive; - - /// - /// Set to true in your DeviceArrived event handler if you wish to receive the - /// QueryRemove event for this drive. - /// - public bool HookQueryRemove; - - } - - - /// - /// 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. - /// - class DriveDetector : IDisposable - { - /// - /// Events signalized to the client app. - /// Add handlers for these events in your form to be notified of removable device events - /// - public event DriveDetectorEventHandler DeviceArrived; - public event DriveDetectorEventHandler DeviceRemoved; - public event DriveDetectorEventHandler QueryRemove; - - /// - /// 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. - /// - public DriveDetector() - { - DetectorForm frm = new DetectorForm(this); - frm.Show(); // will be hidden immediatelly - Init(frm, null); - } - - /// - /// Alternate constructor. - /// Pass in your Form and DriveDetector will not create hidden form. - /// - /// object which will receive Windows messages. - /// Pass "this" as this argument from your form class. - public DriveDetector(Control control) - { - Init(control, null); - } - - /// - /// Consructs DriveDetector object setting also path to file which should be opened - /// when registering for query remove. - /// - ///object which will receive Windows messages. - /// Pass "this" as this argument from your form class. - /// 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" - public DriveDetector(Control control, string FileToOpen) - { - Init(control, FileToOpen); - } - - /// - /// init the DriveDetector object - /// - /// - 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 = ""; - } - - /// - /// Gets the value indicating whether the query remove event will be fired. - /// - public bool IsQueryHooked - { - get - { - if (mDeviceNotifyHandle == IntPtr.Zero) - return false; - else - return true; - } - } - - /// - /// Gets letter of drive which is currently hooked. Empty string if none. - /// See also IsQueryHooked. - /// - public string HookedDrive - { - get - { - return mCurrentDrive; - } - } - - /// - /// 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) - /// - public FileStream OpenedFile - { - get - { - return mFileOnFlash; - } - } - - /// - /// 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). - /// - /// 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. - /// true if hooked ok, false otherwise - 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; - } - - /// - /// Unhooks any currently hooked drive so that the query remove - /// message is not generated for it. - /// - public void DisableQueryRemove() - { - if (mDeviceNotifyHandle != IntPtr.Zero) - { - RegisterForDeviceChange(false, null); - } - } - - - /// - /// Unregister and close the file we may have opened on the removable drive. - /// Garbage collector will call this method. - /// - public void Dispose() - { - RegisterForDeviceChange(false, null); - } - - - #region WindowProc - /// - /// Message handler which must be called from client form. - /// Processes Windows messages and calls event handlers. - /// - /// - 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 - - /// - /// New: 28.10.2007 - handle to root directory of flash drive which is opened - /// for device notification - /// - private IntPtr mDirHandle = IntPtr.Zero; - - /// - /// Class which contains also handle to the file opened on the flash drive - /// - private FileStream mFileOnFlash = null; - - /// - /// Name of the file to try to open on the removable drive for query remove registration - /// - private string mFileToOpen; - - /// - /// Handle to file which we keep opened on the drive if query remove message is required by the client - /// - private IntPtr mDeviceNotifyHandle; - - /// - /// Handle of the window which receives messages from Windows. This will be a form. - /// - private IntPtr mRecipientHandle; - - /// - /// Drive which is currently hooked for query remove - /// - 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 - - /// - /// 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 - /// - /// drive for which to register. - 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; - } - - - } - - - /// - /// New version which gets the handle automatically for specified directory - /// Only for registering! Unregister with the old version of this function... - /// - /// - /// e.g. C:\\dir - 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); - - } - - /// - /// Registers to be notified when the volume is about to be removed - /// This is requierd if you want to get the QUERY REMOVE messages - /// - /// true to register, false to unregister - /// handle of a file opened on the removable drive - 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; - } - } - - } - - /// - /// 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. - /// - /// - /// - 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 - /// - /// Searches for any file in a given path and returns its full path - /// - /// drive to search - /// path of the file or empty string - 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 - /// - /// WinAPI functions - /// - 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 - ); - - /// - /// Opens a directory, returns it's handle or zero. - /// - /// path to the directory, e.g. "C:\\dir" - /// handle to the directory. Close it with CloseHandle(). - 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 - - } -} diff --git a/Cs_HMI/Project/PUB.cs b/Cs_HMI/Project/PUB.cs index e41d1a8..32edef8 100644 --- a/Cs_HMI/Project/PUB.cs +++ b/Cs_HMI/Project/PUB.cs @@ -647,7 +647,7 @@ namespace Project /// 읽은 RFID ID /// 모터 방향 (Forward/Backward) /// 업데이트 성공 여부 - 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; if (_virtualAGV == null || _mapNodes == null) return false; diff --git a/Cs_HMI/Project/StateMachine/Step/_STEP_CLOSE.cs b/Cs_HMI/Project/StateMachine/Step/_STEP_CLOSE.cs index 2f89adc..40022ac 100644 --- a/Cs_HMI/Project/StateMachine/Step/_STEP_CLOSE.cs +++ b/Cs_HMI/Project/StateMachine/Step/_STEP_CLOSE.cs @@ -16,10 +16,7 @@ namespace Project private void _STEP_CLOSING_START(eSMStep step) { PUB.bShutdown = true; - - // 장치 관리 태스크 종료 - StopDeviceManagementTask(); - + PUB.AddEEDB("프로그램 종료"); PUB.log.Add("Program Close"); PUB.LogFlush(); diff --git a/Cs_HMI/Project/StateMachine/_AGV.cs b/Cs_HMI/Project/StateMachine/_AGV.cs index 0931bac..f7e081d 100644 --- a/Cs_HMI/Project/StateMachine/_AGV.cs +++ b/Cs_HMI/Project/StateMachine/_AGV.cs @@ -19,10 +19,8 @@ namespace Project private void AGV_Message(object sender, arDev.Narumi.MessageEventArgs e) { if (e.MsgType == arDev.arRS232.MessageType.Normal) - PUB.logagv.AddE(e.Message); else if (e.MsgType == arDev.arRS232.MessageType.Normal) - PUB.logagv.Add(e.Message); else if (e.MsgType == arDev.arRS232.MessageType.Recv) { @@ -33,7 +31,6 @@ namespace Project PUB.logagv.Add("AGV-TX", e.Message); else { - PUB.logagv.Add(e.MsgType.ToString(), e.Message); } } @@ -44,6 +41,7 @@ namespace Project { try { + VAR.TIME.Set(eVarTime.LastRecv_AGV, DateTime.Now); //데이터 파싱 switch (e.DataType) { @@ -160,8 +158,8 @@ namespace Project case arDev.Narumi.DataType.TAG: { //자동 실행 중이다. - - 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}"); //POT/NOT 보면 일단 바로 멈추게한다 if (PUB.Result.CurrentPos == ePosition.POT || PUB.Result.CurrentPos == ePosition.NOT) @@ -172,7 +170,7 @@ namespace Project } //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) { //없는 노드는 자동으로 추가한다 @@ -237,7 +235,6 @@ namespace Project PUB._mapCanvas.PredictMessage = message; } - } catch (Exception ex) { @@ -245,4 +242,4 @@ namespace Project } } } -} +} \ No newline at end of file diff --git a/Cs_HMI/Project/StateMachine/_Loop.cs b/Cs_HMI/Project/StateMachine/_Loop.cs index f97eb72..0bee820 100644 --- a/Cs_HMI/Project/StateMachine/_Loop.cs +++ b/Cs_HMI/Project/StateMachine/_Loop.cs @@ -154,9 +154,7 @@ namespace Project UpdateStatusMessage("준비 완료", Color.Red, Color.Gold); if (startuptime.Year == 1982) startuptime = DateTime.Now; - // 장치 관리 태스크 시작 (IDLE 진입 시 한 번만) - StartDeviceManagementTask(); - + // 동기화 모드 종료 (혹시 남아있을 경우) if (PUB._mapCanvas != null) { diff --git a/Cs_HMI/Project/StateMachine/_SPS.cs b/Cs_HMI/Project/StateMachine/_SPS.cs index 8a3d287..9ba6c5b 100644 --- a/Cs_HMI/Project/StateMachine/_SPS.cs +++ b/Cs_HMI/Project/StateMachine/_SPS.cs @@ -21,22 +21,230 @@ namespace Project { DateTime chargesynctime = DateTime.Now; DateTime agvsendstarttime = DateTime.Now; + DateTime lastXbeStatusSendTime = DateTime.Now; + DateTime lastBmsQueryTime = DateTime.Now; 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는 이제 간단한 작업만 수행 // 장치 연결 및 상태 전송은 별도 태스크(_DeviceManagement.cs)에서 처리 + // 장치 연결이 별도로 존재할때 1회 수신 후 통신이 전체 먹통되는 증상이 있어 우선 복귀 251215 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) { PUB.log.AddE($"sm_SPS Exception: {ex.Message}"); } } + + /// + /// 시리얼 포트 연결 (arDev.arRS232) + /// + 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; + } + + /// + /// 시리얼 포트 연결 (Device.Xbee) + /// + 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; + } + } + } } diff --git a/Cs_HMI/Project/StateMachine/_Xbee.cs b/Cs_HMI/Project/StateMachine/_Xbee.cs index a60030a..7c2a75c 100644 --- a/Cs_HMI/Project/StateMachine/_Xbee.cs +++ b/Cs_HMI/Project/StateMachine/_Xbee.cs @@ -48,20 +48,24 @@ namespace Project if (data.Length > 4) { var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1); - var node = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currTag); - if (node == null) + if (ushort.TryParse(currTag, out ushort currtagValue)) { - PUB.log.AddE($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}"); - PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}"); - return; - } - else - { - PUB.log.AddI($"XBEE:현재위치설정:[{node.RfidId}]{node.Id}"); - } + var node = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagValue); + if (node == null) + { + PUB.log.AddE($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}"); + PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}"); + return; + } + else + { + PUB.log.AddI($"XBEE:현재위치설정:[{node.RfidId}]{node.Id}"); + } - PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, node, PUB._virtualAGV.CurrentDirection); - PUB._virtualAGV.SetPosition(node, PUB._virtualAGV.CurrentDirection); + PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, 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}"); break; @@ -111,58 +115,61 @@ namespace Project if (data.Length > 4) { var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1); - var targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currTag); - - - //자동상태가아니라면 처리하지 않는다. - if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false) + if(ushort.TryParse(currTag, out ushort currtagvalue)) { - PUB.log.AddE($"[{logPrefix}-Goto] 자동실행상태가 아닙니다"); - PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.ManualMode, $"{currTag}"); - } + var targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagvalue); - //목적지 - PUB._virtualAGV.TargetNode = targetNode; - if (targetNode == null) - { - PUB.log.AddE($"[{logPrefix}-Goto] 노드정보를 찾을 수 없습니다 RFID:{currTag}"); - PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}"); - return; - } - ///출발지 - var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB._virtualAGV.CurrentNode.Id); - PUB._virtualAGV.StartNode = startNode; - if (startNode == null) - { - PUB.log.AddE($"[{logPrefix}-Goto] 시작노드가 없습니다(현재위치 없음) NodeID:{PUB._virtualAGV.CurrentNode.Id}"); - } - - if (startNode != null) - { - //시작위치가 존재한다면 경로를 예측한다. - var rltGoto = CalcPath(startNode, targetNode); - if (rltGoto.result == null) + //자동상태가아니라면 처리하지 않는다. + if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false) { - PUB.log.AddE($"[{logPrefix}-Goto] 경로예측실패 {rltGoto.message}"); + PUB.log.AddE($"[{logPrefix}-Goto] 자동실행상태가 아닙니다"); + PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.ManualMode, $"{currTag}"); } - else + + //목적지 + PUB._virtualAGV.TargetNode = targetNode; + if (targetNode == null) { - //경로예측을 화면에 표시해준다. - PUB._virtualAGV.SetPath(rltGoto.result); - var pathWithRfid = rltGoto.result.GetSimplePath().Select(nodeId => PUB._virtualAGV.GetRfidByNodeId(PUB._mapCanvas.Nodes, nodeId)).ToList(); - PUB.log.Add($"경로예측결과:{pathWithRfid}"); + PUB.log.AddE($"[{logPrefix}-Goto] 노드정보를 찾을 수 없습니다 RFID:{currTag}"); + PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}"); + return; } + + ///출발지 + var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB._virtualAGV.CurrentNode.RfidId); + PUB._virtualAGV.StartNode = startNode; + if (startNode == null) + { + PUB.log.AddE($"[{logPrefix}-Goto] 시작노드가 없습니다(현재위치 없음) NodeID:{PUB._virtualAGV.CurrentNode.Id}"); + } + + if (startNode != null) + { + //시작위치가 존재한다면 경로를 예측한다. + var rltGoto = CalcPath(startNode, targetNode); + if (rltGoto.result == null) + { + PUB.log.AddE($"[{logPrefix}-Goto] 경로예측실패 {rltGoto.message}"); + } + else + { + //경로예측을 화면에 표시해준다. + PUB._virtualAGV.SetPath(rltGoto.result); + var pathWithRfid = rltGoto.result.GetSimplePath().Select(nodeId => PUB._virtualAGV.GetRfidByNodeId(PUB._mapCanvas.Nodes, nodeId)).ToList(); + PUB.log.Add($"경로예측결과:{pathWithRfid}"); + } + } + + //대상이동으로 처리한다. + PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO); + + //Move to + PUB.log.Add($"[{logPrefix}-Goto] {startNode.RfidId} -> {targetNode.RfidId}"); } - - //대상이동으로 처리한다. - PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO); - - //Move to - PUB.log.Add($"[{logPrefix}-Goto] {startNode.RfidId} -> {targetNode.RfidId}"); - + else PUB.log.AddE($"[{logPrefix}-Goto] TagString Value Error:{data}"); } - else PUB.log.AddE($"[{logPrefix}-Goto] TagString Lenght Errorr:{data.Length}"); + else PUB.log.AddE($"[{logPrefix}-Goto] TagString Lenght Error:{data.Length}"); break; case ENIGProtocol.AGVCommandHE.Stop: //stop diff --git a/Cs_HMI/Project/ViewForm/fAgv.Designer.cs b/Cs_HMI/Project/ViewForm/fAgv.Designer.cs index cb33828..a53c9e5 100644 --- a/Cs_HMI/Project/ViewForm/fAgv.Designer.cs +++ b/Cs_HMI/Project/ViewForm/fAgv.Designer.cs @@ -48,13 +48,14 @@ this.button4 = new System.Windows.Forms.Button(); this.button9 = new System.Windows.Forms.Button(); this.panel2 = new System.Windows.Forms.Panel(); - this.button16 = new System.Windows.Forms.Button(); - this.button10 = new System.Windows.Forms.Button(); + this.button15 = new System.Windows.Forms.Button(); + this.button14 = new System.Windows.Forms.Button(); this.button11 = new System.Windows.Forms.Button(); this.button12 = new System.Windows.Forms.Button(); this.button13 = new System.Windows.Forms.Button(); - this.button14 = new System.Windows.Forms.Button(); - this.button15 = new System.Windows.Forms.Button(); + this.button10 = new System.Windows.Forms.Button(); + this.button16 = new System.Windows.Forms.Button(); + this.lbPortName = new System.Windows.Forms.Label(); this.tableLayoutPanel1.SuspendLayout(); this.panel1.SuspendLayout(); this.panel2.SuspendLayout(); @@ -269,6 +270,7 @@ // // panel2 // + this.panel2.Controls.Add(this.lbPortName); this.panel2.Controls.Add(this.button15); this.panel2.Controls.Add(this.button14); this.panel2.Controls.Add(this.button11); @@ -283,27 +285,27 @@ this.panel2.Size = new System.Drawing.Size(1050, 58); this.panel2.TabIndex = 8; // - // button16 + // button15 // - this.button16.Dock = System.Windows.Forms.DockStyle.Left; - this.button16.Location = new System.Drawing.Point(0, 0); - this.button16.Name = "button16"; - this.button16.Size = new System.Drawing.Size(162, 58); - this.button16.TabIndex = 0; - this.button16.Text = "백턴유지시간"; - this.button16.UseVisualStyleBackColor = true; - this.button16.Click += new System.EventHandler(this.button16_Click); + this.button15.Dock = System.Windows.Forms.DockStyle.Left; + this.button15.Location = new System.Drawing.Point(523, 0); + this.button15.Name = "button15"; + this.button15.Size = new System.Drawing.Size(84, 58); + this.button15.TabIndex = 14; + this.button15.Text = "Mag Off"; + this.button15.UseVisualStyleBackColor = true; + this.button15.Click += new System.EventHandler(this.button15_Click); // - // button10 + // button14 // - this.button10.Dock = System.Windows.Forms.DockStyle.Left; - this.button10.Location = new System.Drawing.Point(162, 0); - this.button10.Name = "button10"; - this.button10.Size = new System.Drawing.Size(162, 58); - this.button10.TabIndex = 1; - this.button10.Text = "GateOut Off Time"; - this.button10.UseVisualStyleBackColor = true; - this.button10.Click += new System.EventHandler(this.button10_Click); + this.button14.Dock = System.Windows.Forms.DockStyle.Left; + this.button14.Location = new System.Drawing.Point(439, 0); + this.button14.Name = "button14"; + this.button14.Size = new System.Drawing.Size(84, 58); + this.button14.TabIndex = 13; + this.button14.Text = "Mag On"; + this.button14.UseVisualStyleBackColor = true; + this.button14.Click += new System.EventHandler(this.button14_Click); // // button11 // @@ -338,27 +340,39 @@ this.button13.UseVisualStyleBackColor = true; this.button13.Click += new System.EventHandler(this.button13_Click); // - // button14 + // button10 // - this.button14.Dock = System.Windows.Forms.DockStyle.Left; - this.button14.Location = new System.Drawing.Point(439, 0); - this.button14.Name = "button14"; - this.button14.Size = new System.Drawing.Size(84, 58); - this.button14.TabIndex = 13; - this.button14.Text = "Mag On"; - this.button14.UseVisualStyleBackColor = true; - this.button14.Click += new System.EventHandler(this.button14_Click); + this.button10.Dock = System.Windows.Forms.DockStyle.Left; + this.button10.Location = new System.Drawing.Point(162, 0); + this.button10.Name = "button10"; + this.button10.Size = new System.Drawing.Size(162, 58); + this.button10.TabIndex = 1; + this.button10.Text = "GateOut Off Time"; + this.button10.UseVisualStyleBackColor = true; + this.button10.Click += new System.EventHandler(this.button10_Click); // - // button15 + // button16 // - this.button15.Dock = System.Windows.Forms.DockStyle.Left; - this.button15.Location = new System.Drawing.Point(523, 0); - this.button15.Name = "button15"; - this.button15.Size = new System.Drawing.Size(84, 58); - this.button15.TabIndex = 14; - this.button15.Text = "Mag Off"; - this.button15.UseVisualStyleBackColor = true; - this.button15.Click += new System.EventHandler(this.button15_Click); + this.button16.Dock = System.Windows.Forms.DockStyle.Left; + this.button16.Location = new System.Drawing.Point(0, 0); + this.button16.Name = "button16"; + this.button16.Size = new System.Drawing.Size(162, 58); + this.button16.TabIndex = 0; + this.button16.Text = "백턴유지시간"; + this.button16.UseVisualStyleBackColor = true; + 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 // @@ -409,5 +423,6 @@ private System.Windows.Forms.Button button11; private System.Windows.Forms.Button button12; private System.Windows.Forms.Button button13; + private System.Windows.Forms.Label lbPortName; } } \ No newline at end of file diff --git a/Cs_HMI/Project/ViewForm/fAgv.cs b/Cs_HMI/Project/ViewForm/fAgv.cs index 2ee59b6..b0f4888 100644 --- a/Cs_HMI/Project/ViewForm/fAgv.cs +++ b/Cs_HMI/Project/ViewForm/fAgv.cs @@ -42,7 +42,7 @@ namespace Project.ViewForm richTextBox2.Rtf = PUB.AGV.system1.ToRtfString(); richTextBox3.Rtf = CombineRtfStrings(PUB.AGV.signal.ToRtfString(), PUB.AGV.data.ToRtfString()); richTextBox4.Rtf = PUB.AGV.error.ToRtfString(); - + lbPortName.Text = $"AGV:{PUB.setting.Port_AGV}\nBMS:{PUB.setting.Port_BAT}"; timer1.Start(); } diff --git a/Cs_HMI/Project/ViewForm/fAuto.cs b/Cs_HMI/Project/ViewForm/fAuto.cs index 54b7d0d..4e9d3c9 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.cs +++ b/Cs_HMI/Project/ViewForm/fAuto.cs @@ -30,28 +30,11 @@ namespace Project.ViewForm 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() { - // RfidMappings 제거 - MapNode에 통합됨 - - // 이벤트 연결 - //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; + PUB._mapCanvas.NodeSelect += OnNodeSelected;; // 스플리터 패널에 맵 캔버스 추가 panel1.Controls.Add(PUB._mapCanvas); @@ -153,84 +136,7 @@ namespace Project.ViewForm 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(); - 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 { 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 { 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; //if (files.Any() == false) //{ diff --git a/Cs_HMI/Project/fMain.cs b/Cs_HMI/Project/fMain.cs index 1a26117..62b90eb 100644 --- a/Cs_HMI/Project/fMain.cs +++ b/Cs_HMI/Project/fMain.cs @@ -33,7 +33,6 @@ namespace Project bool remoteClose = false; bool forceClose = false; - readonly usbdetect.DriveDetector usbdet; public fMain() { InitializeComponent(); @@ -57,19 +56,13 @@ namespace Project 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.Dock = DockStyle.Fill; PUB._mapCanvas.ShowGrid = false; PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32); PUB._mapCanvas.ForeColor = Color.White; - - this.panTopMenu.MouseMove += LbTitle_MouseMove; this.panTopMenu.MouseUp += LbTitle_MouseUp; this.panTopMenu.MouseDown += LbTitle_MouseDown; @@ -82,33 +75,7 @@ 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) { // 장치 관리 태스크는 _STEP_CLOSING_START에서 종료됨 @@ -182,6 +149,9 @@ namespace Project VAR.STR[eVarString.SWVersion] = Application.ProductVersion; + + AutoLoadMapData(); + //SETTING H/W this.IOState.ItemClick += gridView2_ItemClick; VAR.BOOL.PropertyChanged += BOOL_PropertyChanged; @@ -280,7 +250,7 @@ namespace Project //수량표시 PUB.counter.PropertyChanged += (s1, e1) => Update_Count(); Update_Count(); - + PUB.log.Add("프로그램 실행 기록 추가"); PUB.CheckNRegister3(Application.ProductName, "chi", Application.ProductVersion); @@ -290,6 +260,71 @@ namespace Project 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 { 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 { 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" private Boolean fMove = false;