..
This commit is contained in:
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="rfidValue">검사할 RFID 값</param>
|
||||
/// <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;
|
||||
|
||||
// 현재 편집 중인 노드 제외하고 중복 검사
|
||||
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; // 하나라도 발견되면 중복
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -851,13 +851,13 @@ namespace AGVNavigationCore.Controls
|
||||
return;
|
||||
|
||||
// RFID값과 해당 노드의 인덱스를 저장
|
||||
var rfidToNodeIndex = new Dictionary<string, List<int>>();
|
||||
var rfidToNodeIndex = new Dictionary<ushort, List<int>>();
|
||||
|
||||
// 모든 노드의 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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -611,18 +611,7 @@ namespace AGVNavigationCore.Models
|
||||
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
|
||||
|
||||
|
||||
@@ -630,10 +619,11 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
|
||||
/// </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);
|
||||
return node?.HasRfid() == true ? node.RfidId : nodeId;
|
||||
if ((node?.HasRfid() ?? false) == false) return 0;
|
||||
return node.RfidId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
// 노드 정보 생성 (현재 방향 유지)
|
||||
|
||||
@@ -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})";
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// <summary>
|
||||
/// RFID Value
|
||||
/// </summary>
|
||||
public string RfidId { get; set; }
|
||||
public ushort RfidId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 해당 노드에서의 모터방향
|
||||
@@ -87,7 +87,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ namespace AGVNavigationCore.Utils
|
||||
public class DirectionalPathfinderTest
|
||||
{
|
||||
private List<MapNode> _allNodes;
|
||||
private Dictionary<string, MapNode> _nodesByRfidId;
|
||||
private Dictionary<ushort, MapNode> _nodesByRfidId;
|
||||
private AGVDirectionCalculator _calculator;
|
||||
|
||||
public DirectionalPathfinderTest()
|
||||
{
|
||||
_nodesByRfidId = new Dictionary<string, MapNode>();
|
||||
_nodesByRfidId = new Dictionary<ushort, MapNode>();
|
||||
_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
|
||||
/// <summary>
|
||||
/// 테스트: RFID 번호로 노드를 찾고, 다음 노드를 계산
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// 특정 RFID 노드의 상세 정보 출력
|
||||
/// </summary>
|
||||
public void PrintNodeInfo(string rfidId)
|
||||
public void PrintNodeInfo(ushort rfidId)
|
||||
{
|
||||
if (!_nodesByRfidId.TryGetValue(rfidId, out var node))
|
||||
{
|
||||
|
||||
@@ -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<string> { "N002" } };
|
||||
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 node004 = new MapNode { Id = "N004", RfidId = "004", Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } };
|
||||
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 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 allNodes = new List<MapNode> { 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}");
|
||||
|
||||
@@ -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=== 테스트 완료 ===");
|
||||
}
|
||||
|
||||
@@ -635,7 +635,7 @@ namespace AGVSimulator.Forms
|
||||
/// <summary>
|
||||
/// 맵 스캔 모드에서 RFID로부터 노드 생성
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<string>();
|
||||
if (info.CanRotate) flags.Add("회전가능");
|
||||
@@ -1616,12 +1618,8 @@ namespace AGVSimulator.Forms
|
||||
/// </summary>
|
||||
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<byte>();
|
||||
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);
|
||||
|
||||
@@ -226,9 +226,6 @@
|
||||
<Compile Include="Dialog\fSystem.Designer.cs">
|
||||
<DependentUpon>fSystem.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\DriveDetector.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fVolume.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -363,9 +360,6 @@
|
||||
<Compile Include="StateMachine\_SPS.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Device\_DeviceManagement.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="StateMachine\_Loop.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
@@ -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);
|
||||
//}
|
||||
/// <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;
|
||||
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>
|
||||
@@ -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
|
||||
|
||||
/// <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
|
||||
{
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AGV상태를 Xbee 로 전송한다
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -647,7 +647,7 @@ namespace Project
|
||||
/// <param name="rfidId">읽은 RFID ID</param>
|
||||
/// <param name="motorDirection">모터 방향 (Forward/Backward)</param>
|
||||
/// <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;
|
||||
if (_virtualAGV == null || _mapNodes == null) return false;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,9 +154,7 @@ namespace Project
|
||||
UpdateStatusMessage("준비 완료", Color.Red, Color.Gold);
|
||||
if (startuptime.Year == 1982) startuptime = DateTime.Now;
|
||||
|
||||
// 장치 관리 태스크 시작 (IDLE 진입 시 한 번만)
|
||||
StartDeviceManagementTask();
|
||||
|
||||
|
||||
// 동기화 모드 종료 (혹시 남아있을 경우)
|
||||
if (PUB._mapCanvas != null)
|
||||
{
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
95
Cs_HMI/Project/ViewForm/fAgv.Designer.cs
generated
95
Cs_HMI/Project/ViewForm/fAgv.Designer.cs
generated
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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;
|
||||
//if (files.Any() == false)
|
||||
//{
|
||||
|
||||
@@ -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<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"
|
||||
|
||||
private Boolean fMove = false;
|
||||
|
||||
Reference in New Issue
Block a user