diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
index 79eb226..da3bcf1 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs
@@ -815,6 +815,9 @@ namespace AGVNavigationCore.Controls
case StationType.Charger:
DrawTriangleNodeShape(g, node, brush);
break;
+ case StationType.Limit:
+ DrawRectangleNodeShape(g, node, brush);
+ break;
default:
DrawCircleNodeShape(g, node, brush);
break;
@@ -823,6 +826,83 @@ namespace AGVNavigationCore.Controls
}
+ private void DrawRectangleNodeShape(Graphics g, MapNode node, Brush brush)
+ {
+ // 드래그 중인 노드는 약간 크게 그리기
+ bool isDraggingThisNode = _isDragging && node == _selectedNode;
+ int sizeAdjustment = isDraggingThisNode ? 4 : 0;
+
+ var rect = new Rectangle(
+ node.Position.X - NODE_RADIUS - sizeAdjustment,
+ node.Position.Y - NODE_RADIUS - sizeAdjustment,
+ NODE_SIZE + sizeAdjustment * 2,
+ NODE_SIZE + sizeAdjustment * 2
+ );
+
+ // 드래그 중인 노드의 그림자 효과
+ if (isDraggingThisNode)
+ {
+ var shadowRect = new Rectangle(rect.X + 3, rect.Y + 3, rect.Width, rect.Height);
+ using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
+ {
+ g.FillRectangle(shadowBrush, shadowRect);
+ }
+ }
+
+ // 노드 그리기
+ g.FillRectangle(brush, rect);
+ g.DrawRectangle(Pens.Black, rect);
+
+ // 드래그 중인 노드 강조 (가장 강력한 효과)
+ if (isDraggingThisNode)
+ {
+ // 청록색 두꺼운 테두리
+ g.DrawRectangle(new Pen(Color.Cyan, 3), rect);
+ // 펄스 효과
+ var pulseRect = new Rectangle(rect.X - 4, rect.Y - 4, rect.Width + 8, rect.Height + 8);
+ g.DrawRectangle(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
+ }
+ // 선택된 노드 강조 (단일 또는 다중)
+ else if (node == _selectedNode || (_selectedNodes != null && _selectedNodes.Contains(node)))
+ {
+ g.DrawRectangle(_selectedNodePen, rect);
+ }
+
+ // 목적지 노드 강조
+ if (node == _destinationNode)
+ {
+ // 금색 테두리로 목적지 강조
+ g.DrawRectangle(_destinationNodePen, rect);
+
+ // 펄싱 효과를 위한 추가 원 그리기
+ var pulseRect = new Rectangle(rect.X - 3, rect.Y - 3, rect.Width + 6, rect.Height + 6);
+ g.DrawRectangle(new Pen(Color.Gold, 2) { DashStyle = DashStyle.Dash }, pulseRect);
+ }
+
+ // 호버된 노드 강조 (드래그 중이 아닐 때만)
+ if (node == _hoveredNode && !isDraggingThisNode)
+ {
+ var hoverRect = new Rectangle(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4);
+ g.DrawRectangle(new Pen(Color.Orange, 2), hoverRect);
+ }
+
+ // RFID 중복 노드 표시 (빨간 X자)
+ if (_duplicateRfidNodes.Contains(node.Id))
+ {
+ DrawDuplicateRfidMarker(g, node);
+ }
+
+ // CanCross 가능 노드 표시 (교차지점으로 사용 가능)
+ if (node.DisableCross == true)
+ {
+ var crossRect = new Rectangle(rect.X - 3, rect.Y - 3, rect.Width + 6, rect.Height + 6);
+ g.DrawRectangle(new Pen(Color.DeepSkyBlue, 3), crossRect);
+ }
+
+ g.DrawLine(Pens.Black, rect.X, rect.Y, rect.Right, rect.Bottom);
+ g.DrawLine(Pens.Black, rect.Right, rect.Top, rect.X, rect.Bottom);
+
+ }
private void DrawCircleNodeShape(Graphics g, MapNode node, Brush brush)
{
// 드래그 중인 노드는 약간 크게 그리기
@@ -1472,9 +1552,10 @@ namespace AGVNavigationCore.Controls
case StationType.Normal: bgColor = Color.DeepSkyBlue; break;
case StationType.Charger: bgColor = Color.Tomato; break;
case StationType.Loader:
- case StationType.UnLoader: bgColor = Color.Gold ; break;
- case StationType.Clearner: bgColor = Color.DeepSkyBlue ; break;
+ case StationType.UnLoader: bgColor = Color.Gold; break;
+ case StationType.Clearner: bgColor = Color.DeepSkyBlue; break;
case StationType.Buffer: bgColor = Color.WhiteSmoke; break;
+ case StationType.Limit: bgColor = Color.Red; break;
default: bgColor = Color.White; break;
}
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs
index b46a4b5..b3bea81 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs
@@ -73,7 +73,13 @@ namespace AGVNavigationCore.Models
/// 버퍼
Buffer,
/// 충전기
- Charger
+ Charger,
+
+ ///
+ /// 끝점(더이상 이동불가)
+ ///
+ Limit,
+
}
///
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
index 51e3702..1d84d99 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs
@@ -149,7 +149,7 @@ namespace AGVNavigationCore.Models
public override string ToString()
{
- return $"{RfidId}({Id}): {AliasName} ({Type}) at ({Position.X}, {Position.Y})";
+ return $"RFID:{RfidId}(NODE:{Id}): AS:{AliasName} ({Type}) at ({Position.X}, {Position.Y})";
}
public bool IsNavigationNode()
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
index 0ad07df..6514dd5 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs
@@ -464,6 +464,8 @@ namespace AGVNavigationCore.PathFinding.Planning
{
nodeInfo.Speed = mapNode.SpeedLimit;
}
+
+ detailedPath1.Add(nodeInfo);
}
// path1에 상세 경로 정보 설정
diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs
index 597fc25..5c5cb71 100644
--- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs
+++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs
@@ -108,7 +108,7 @@ namespace AGVNavigationCore.PathFinding.Planning
///
public override string ToString()
{
- var result = $"{RfidId}[{NodeId}]:{MotorDirection}";
+ var result = $"R{RfidId}[N{NodeId}]:{MotorDirection}";
// 마그넷 방향이 직진이 아닌 경우 표시
if (MagnetDirection != MagnetDirection.Straight)
diff --git a/Cs_HMI/Project/StateMachine/Step/_Util.cs b/Cs_HMI/Project/StateMachine/Step/_Util.cs
index 519bc6b..64e7d53 100644
--- a/Cs_HMI/Project/StateMachine/Step/_Util.cs
+++ b/Cs_HMI/Project/StateMachine/Step/_Util.cs
@@ -117,6 +117,11 @@ namespace Project
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
+ else
+ {
+ PUB._mapCanvas.CurrentPath = PathResult.result;
+ PUB._virtualAGV.SetPath(PathResult.result);
+ }
PUB.log.AddI($"경로생성 {PUB._virtualAGV.StartNode.RfidId} -> {PUB._virtualAGV.TargetNode.RfidId}");
}
@@ -133,8 +138,16 @@ namespace Project
return false;
}
+
+
//predict 를 이용하여 다음 이동을 모두 확인한다.
var nextAction = PUB._virtualAGV.Predict();
+ if(nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.PathOut)
+ {
+ //경로이탈
+ PUB._virtualAGV.CurrentPath.DetailedPath.Clear();
+ return false;
+ }
var message = $"[다음 행동 예측]\n\n" +
$"모터: {nextAction.Motor}\n" +
diff --git a/Cs_HMI/Project/StateMachine/_SPS.cs b/Cs_HMI/Project/StateMachine/_SPS.cs
index 2b55fa3..47f7f86 100644
--- a/Cs_HMI/Project/StateMachine/_SPS.cs
+++ b/Cs_HMI/Project/StateMachine/_SPS.cs
@@ -72,7 +72,7 @@ namespace Project
else if (PUB.BMS.IsValid == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
- if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5))
+ if (ts.TotalSeconds > ( Math.Max(10,PUB.setting.interval_bms) * 2.5))
{
this.BeginInvoke(new Action(() =>
{
diff --git a/Cs_HMI/Project/StateMachine/_Xbee.cs b/Cs_HMI/Project/StateMachine/_Xbee.cs
index 00504e7..2634f70 100644
--- a/Cs_HMI/Project/StateMachine/_Xbee.cs
+++ b/Cs_HMI/Project/StateMachine/_Xbee.cs
@@ -113,7 +113,7 @@ namespace Project
case ENIGProtocol.AGVCommandHE.GotoAlias:
case ENIGProtocol.AGVCommandHE.Goto: //move to tag
- var datalength = cmd == ENIGProtocol.AGVCommandHE.GotoAlias ? 1 : 4;
+ var datalength = cmd == ENIGProtocol.AGVCommandHE.GotoAlias ? 2 : 1;
if (data.Length > datalength)
{
var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1).Trim();
@@ -255,7 +255,7 @@ namespace Project
break;
default:
- PUB.logagv.AddE($"Unknown Command : {cmd}");
+ PUB.logagv.AddE($"Unknown Command : {cmd} Sender:{e.ReceivedPacket.ID}, Target:{data[0]}");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"{cmd}");
break;
}
@@ -301,6 +301,19 @@ namespace Project
if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired)
advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes);
+ //마지막대상이 버퍼라면 시퀀스처리를 해야한다
+ if (targetNode.StationType == StationType.Buffer&& advancedResult.DetailedPath.Any())
+ {
+ var lastDetailPath = advancedResult.DetailedPath.Last();
+ if (lastDetailPath.NodeId == targetNode.Id) //마지막노드 재확인
+ {
+ //버퍼에 도킹할때에는 마지막 노드에서 멈추고 시퀀스를 적용해야한다
+ advancedResult.DetailedPath = advancedResult.DetailedPath.Take(advancedResult.DetailedPath.Count - 1).ToList();
+ Console.WriteLine("최종위치가 버퍼이므로 마지막 RFID에서 멈추도록 합니다");
+ }
+ }
+
+
_simulatorCanvas.CurrentPath = advancedResult;
//_pathLengthLabel.Text = $"경로 길이: {advancedResult.TotalDistance:F1}";
//_statusLabel.Text = $"경로 계산 완료 ({advancedResult.CalculationTimeMs}ms)";