add temp1, temp2
This commit is contained in:
3
Cs_HMI/SubProject/AGVControl/.vscode/settings.json
vendored
Normal file
3
Cs_HMI/SubProject/AGVControl/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"dotnet.preferCSharpExtension": true
|
||||
}
|
||||
@@ -49,6 +49,12 @@ namespace AGVControl
|
||||
public List<Point> Path { get; set; }
|
||||
}
|
||||
|
||||
public enum AGVMoveState
|
||||
{
|
||||
Stop = 0,
|
||||
Run
|
||||
}
|
||||
|
||||
public enum AGVActionReasonCode
|
||||
{
|
||||
Unknown = 0,
|
||||
@@ -57,7 +63,8 @@ namespace AGVControl
|
||||
NotOnPath, // 현재 위치가 경로에 없음
|
||||
Arrived, // 경로의 마지막 지점(목적지 도달)
|
||||
Normal, // 정상(다음 RFID 있음)
|
||||
NeedTurn
|
||||
NeedTurn,
|
||||
NoTurnPoint,
|
||||
}
|
||||
|
||||
public class AGVActionPrediction
|
||||
@@ -66,6 +73,7 @@ namespace AGVControl
|
||||
public uint? NextRFID { get; set; }
|
||||
public string Reason { get; set; }
|
||||
public AGVActionReasonCode ReasonCode { get; set; }
|
||||
public AGVMoveState MoveState { get; set; } // RUN 또는 STOP
|
||||
}
|
||||
|
||||
public partial class MapControl : Control
|
||||
@@ -401,35 +409,17 @@ namespace AGVControl
|
||||
var selected_rfid = rfidPoints.Where(t => t.Bounds.Expand(SELECTION_DISTANCE, SELECTION_DISTANCE).Contains(mapPoint)).FirstOrDefault();
|
||||
if (selected_rfid != null)
|
||||
{
|
||||
var oldtagno = selected_rfid.RFIDValue;
|
||||
AR.UTIL.ShowProperty(selected_rfid);
|
||||
if (selected_rfid.RFIDValue != oldtagno)
|
||||
{
|
||||
var items1 = this.rfidConnections.Where(t=>t.StartRFID == oldtagno);
|
||||
foreach (var item in items1)
|
||||
item.StartRFID = selected_rfid.RFIDValue;
|
||||
|
||||
var items2 = this.rfidConnections.Where(t => t.EndRFID == oldtagno);
|
||||
foreach (var item in items2)
|
||||
item.EndRFID = selected_rfid.RFIDValue;
|
||||
}
|
||||
|
||||
UTIL.ShowPropertyDialog(selected_rfid);
|
||||
this.Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 텍스트 객체 찾기
|
||||
var selected_txt = mapTexts.Where(t => t.Bounds.Expand(SELECTION_DISTANCE, SELECTION_DISTANCE).Contains(mapPoint)).FirstOrDefault();
|
||||
if (selected_txt != null)
|
||||
{
|
||||
var rlt = AR.UTIL.InputBox("input", selected_txt.Text);
|
||||
if (rlt.Item1 == true)
|
||||
{
|
||||
selected_txt.Text = rlt.Item2;
|
||||
this.Invalidate();
|
||||
}
|
||||
UTIL.ShowPropertyDialog(selected_txt);
|
||||
this.Invalidate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -451,7 +441,7 @@ namespace AGVControl
|
||||
}
|
||||
else
|
||||
{
|
||||
zoom /= 1.1f;
|
||||
zoom /= 1.2f;
|
||||
}
|
||||
|
||||
zoom = Math.Max(0.1f, Math.Min(10.0f, zoom));
|
||||
@@ -744,9 +734,17 @@ namespace AGVControl
|
||||
var rfidPoint = FindRFIDPoint(rfidValue);
|
||||
if (rfidPoint != null)
|
||||
{
|
||||
// 이전 위치 저장 (방향 검증용)
|
||||
Point previousPosition = agv.CurrentPosition;
|
||||
uint previousRFID = agv.CurrentRFID;
|
||||
|
||||
// AGV 위치 업데이트
|
||||
agv.CurrentPosition = rfidPoint.Location;
|
||||
agv.CurrentRFID = rfidValue;
|
||||
|
||||
// 이동 경로에 추가
|
||||
agv.AddToMovementHistory(rfidValue, rfidPoint.Location);
|
||||
|
||||
// 목적지가 설정되어 있고 경로가 있는 경우 검증
|
||||
if (agv.TargetPosition != Point.Empty && agv.CurrentPath.Count > 0)
|
||||
{
|
||||
@@ -784,6 +782,52 @@ namespace AGVControl
|
||||
}
|
||||
}
|
||||
|
||||
// 방향 검증 및 정정 (이전 위치가 있고 경로가 있는 경우)
|
||||
if (previousRFID != 0 && agv.CurrentPath.Count > 0)
|
||||
{
|
||||
// RFID 연결 정보 기반 예상 방향 계산
|
||||
Direction? expectedDirection = null;
|
||||
int currentIdx = agv.CurrentPath.FindIndex(p => p == agv.CurrentPosition);
|
||||
if (currentIdx >= 0 && currentIdx < agv.CurrentPath.Count - 1)
|
||||
{
|
||||
// 현재 위치와 다음 위치의 RFID 찾기
|
||||
var currentRFIDPoint = FindRFIDPoint(agv.CurrentRFID);
|
||||
var nextPoint = agv.CurrentPath[currentIdx + 1];
|
||||
var nextRFIDPoint = rfidPoints.FirstOrDefault(p => p.Location == nextPoint);
|
||||
|
||||
if (currentRFIDPoint != null && nextRFIDPoint != null)
|
||||
{
|
||||
// rfidConnections에서 연결 정보 확인
|
||||
var connection = rfidConnections.FirstOrDefault(c =>
|
||||
(c.StartRFID == currentRFIDPoint.RFIDValue && c.EndRFID == nextRFIDPoint.RFIDValue) ||
|
||||
(c.IsBidirectional && c.StartRFID == nextRFIDPoint.RFIDValue && c.EndRFID == currentRFIDPoint.RFIDValue));
|
||||
|
||||
if (connection != null)
|
||||
{
|
||||
// 연결된 경로이므로 방향 결정
|
||||
if (connection.StartRFID == currentRFIDPoint.RFIDValue && connection.EndRFID == nextRFIDPoint.RFIDValue)
|
||||
{
|
||||
expectedDirection = Direction.Forward; // Start -> End 방향
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedDirection = Direction.Backward; // End -> Start 방향
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedDirection.HasValue)
|
||||
{
|
||||
bool directionValid = agv.ValidateAndCorrectDirectionByConnection(expectedDirection.Value, rfidConnections.ToList());
|
||||
if (!directionValid)
|
||||
{
|
||||
// 방향이 정정되었음을 로그로 기록
|
||||
Console.WriteLine($"AGV 방향 정정 (연결 정보 기반): 예상 {expectedDirection.Value} -> 실제 {agv.CurrentDirection}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Invalidate();
|
||||
return true;
|
||||
}
|
||||
@@ -1233,37 +1277,52 @@ namespace AGVControl
|
||||
var MarkerSize = 5;
|
||||
var half = MarkerSize / 2f;
|
||||
rfid.Bounds = new RectangleF(rfid.Location.X - half, rfid.Location.Y - half, MarkerSize, MarkerSize);
|
||||
|
||||
// 회전 가능 여부에 따라 다른 색상 사용
|
||||
using (var brush = new SolidBrush(rfid.IsRotatable ? Color.Yellow : Color.Green))
|
||||
|
||||
// 종단 RFID는 특별한 색상으로 표시
|
||||
Color pointColor;
|
||||
if (rfid.IsTerminal)
|
||||
{
|
||||
pointColor = Color.Orange; // 종단은 주황색
|
||||
}
|
||||
else if (rfid.IsRotatable)
|
||||
{
|
||||
pointColor = Color.Yellow; // 회전 가능은 노란색
|
||||
}
|
||||
else
|
||||
{
|
||||
pointColor = Color.Green; // 일반은 초록색
|
||||
}
|
||||
|
||||
using (var brush = new SolidBrush(pointColor))
|
||||
{
|
||||
g.FillEllipse(brush, rfid.Bounds);
|
||||
}
|
||||
|
||||
// 고정방향이 있으면 테두리 색상 표시
|
||||
if (rfid.FixedDirection.HasValue)
|
||||
{
|
||||
Color borderColor = rfid.FixedDirection.Value == Direction.Forward ? Color.DeepSkyBlue : Color.Gold; using (var pen = new Pen(borderColor, 2))
|
||||
Color borderColor = rfid.FixedDirection.Value == Direction.Forward ? Color.DeepSkyBlue : Color.Gold;
|
||||
using (var pen = new Pen(borderColor, 2))
|
||||
{
|
||||
g.DrawEllipse(pen, rfid.Bounds.Expand(5, 5));
|
||||
g.DrawEllipse(pen, rfid.Bounds.Expand(5,5));
|
||||
}
|
||||
}
|
||||
|
||||
// 종단 RFID는 특별한 테두리 표시
|
||||
if (rfid.IsTerminal)
|
||||
{
|
||||
using (var pen = new Pen(Color.Red, 3))
|
||||
{
|
||||
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
|
||||
g.DrawEllipse(pen, rfid.Bounds.Expand(8,8));
|
||||
}
|
||||
}
|
||||
|
||||
var str = rfid.RFIDValue.ToString();
|
||||
g.DrawString(str, this.Font, Brushes.DarkGray, rfid.Bounds.X, rfid.Bounds.Y+5);
|
||||
}
|
||||
|
||||
// RFID 값 표시
|
||||
foreach (var rfid in rfidPoints)
|
||||
{
|
||||
var tagstr = $"{rfid.RFIDValue}";
|
||||
var fsize = g.MeasureString(tagstr, Font);
|
||||
var rect = new RectangleF(rfid.Bounds.X - (fsize.Width / 2f) + 3,
|
||||
rfid.Bounds.Y + 6,
|
||||
fsize.Width,
|
||||
fsize.Height);
|
||||
g.DrawString(tagstr, Font, Brushes.WhiteSmoke, rect, new StringFormat
|
||||
{
|
||||
Alignment = StringAlignment.Center,
|
||||
LineAlignment = StringAlignment.Center,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawAGV(Graphics g)
|
||||
@@ -1277,11 +1336,64 @@ namespace AGVControl
|
||||
agv.CurrentPosition.Y - halfsize,
|
||||
agvsize, agvsize);
|
||||
|
||||
// 삼각형 화살표를 위한 포인트 배열
|
||||
Point[] trianglePoints = new Point[3];
|
||||
var arrowSize = halfsize - 5; // 삼각형 크기
|
||||
// AGV 몸체 회전 각도 계산
|
||||
float bodyRotation = 0f;
|
||||
if (agv.CurrentPath != null && agv.CurrentPath.Count > 1)
|
||||
{
|
||||
// 현재 위치에서 다음 목적지 방향 계산
|
||||
int currentIdx = agv.CurrentPath.FindIndex(p => p == agv.CurrentPosition);
|
||||
if (currentIdx >= 0 && currentIdx < agv.CurrentPath.Count - 1)
|
||||
{
|
||||
Point nextPoint = agv.CurrentPath[currentIdx + 1];
|
||||
float deltaX = nextPoint.X - agv.CurrentPosition.X;
|
||||
float deltaY = nextPoint.Y - agv.CurrentPosition.Y;
|
||||
bodyRotation = (float)Math.Atan2(deltaY, deltaX) * 180f / (float)Math.PI;
|
||||
}
|
||||
}
|
||||
|
||||
// AGV의 방향에 따라 삼각형 포인트 계산
|
||||
// 그래픽스 상태 저장
|
||||
var originalTransform = g.Transform;
|
||||
|
||||
// AGV 위치로 이동하고 회전
|
||||
g.TranslateTransform(agv.CurrentPosition.X, agv.CurrentPosition.Y);
|
||||
g.RotateTransform(bodyRotation);
|
||||
|
||||
// 원 그리기 (회전된 상태)
|
||||
Color bgcolor = agv.BatteryLevel > 80 ? Color.Lime : (agv.BatteryLevel > 60 ? Color.Gold : Color.Tomato);
|
||||
using (var circleBrush = new SolidBrush(Color.FromArgb(150, bgcolor)))
|
||||
g.FillEllipse(circleBrush, -halfsize, -halfsize, agvsize, agvsize);
|
||||
using (var circlePen = new Pen(Color.Black, 2))
|
||||
g.DrawEllipse(circlePen, -halfsize, -halfsize, agvsize, agvsize);
|
||||
|
||||
// 리프트 그리기 (회전된 상태, 항상 전진 방향쪽)
|
||||
var liftWidth = circleRect.Width;
|
||||
var liftHeight = (int)(circleRect.Height * 0.4);
|
||||
var liftOffset = halfsize + 1;
|
||||
|
||||
var liftRect = new Rectangle(
|
||||
-liftWidth / 2,
|
||||
-halfsize - liftOffset,
|
||||
liftWidth, liftHeight);
|
||||
|
||||
using (var liftBrush = new SolidBrush(Color.FromArgb(200, Color.DarkGray)))
|
||||
g.FillRectangle(liftBrush, liftRect);
|
||||
using (var liftPen = new Pen(Color.Black, 1))
|
||||
g.DrawRectangle(liftPen, liftRect);
|
||||
|
||||
// 리프트 연결선 그리기
|
||||
using (var connectionPen = new Pen(Color.Black, 2))
|
||||
{
|
||||
g.DrawLine(connectionPen, 0, -halfsize, 0, -halfsize - liftOffset);
|
||||
}
|
||||
|
||||
// 그래픽스 상태 복원
|
||||
g.Transform = originalTransform;
|
||||
|
||||
// 삼각형 화살표 그리기 (현재 이동 방향, 회전하지 않음)
|
||||
Point[] trianglePoints = new Point[3];
|
||||
var arrowSize = halfsize - 5;
|
||||
|
||||
// AGV의 현재 이동 방향에 따라 삼각형 포인트 계산
|
||||
switch (agv.CurrentDirection)
|
||||
{
|
||||
case Direction.Forward:
|
||||
@@ -1296,16 +1408,6 @@ namespace AGVControl
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 원 그리기
|
||||
Color bgcolor = agv.BatteryLevel > 80 ? Color.Lime : (agv.BatteryLevel > 60 ? Color.Gold : Color.Tomato);
|
||||
using (var circleBrush = new SolidBrush(Color.FromArgb(150, bgcolor)))
|
||||
g.FillEllipse(circleBrush, circleRect);
|
||||
using (var circlePen = new Pen(Color.Black, 2))
|
||||
g.DrawEllipse(circlePen, circleRect);
|
||||
|
||||
// 삼각형 화살표 그리기
|
||||
using (var arrowBrush = new SolidBrush(Color.FromArgb(200, Color.White)))
|
||||
g.FillPolygon(arrowBrush, trianglePoints);
|
||||
using (var arrowPen = new Pen(Color.Black, 2))
|
||||
@@ -1563,17 +1665,15 @@ namespace AGVControl
|
||||
lines.Add("[RFID_POINTS]");
|
||||
foreach (var point in rfidPoints)
|
||||
{
|
||||
lines.Add($"{point.Location.X},{point.Location.Y},{point.RFIDValue}");
|
||||
lines.Add($"{point.Location.X},{point.Location.Y},{point.RFIDValue},{point.IsRotatable},{point.FixedDirection},{point.IsTerminal}");
|
||||
}
|
||||
|
||||
// RFID 라인 저장
|
||||
lines.Add("[RFID_LINES]");
|
||||
foreach (var connection in rfidConnections)
|
||||
{
|
||||
var startPoint = rfidPoints.FirstOrDefault(p => p.RFIDValue == connection.StartRFID)?.Location ?? Point.Empty;
|
||||
var endPoint = rfidPoints.FirstOrDefault(p => p.RFIDValue == connection.EndRFID)?.Location ?? Point.Empty;
|
||||
if (startPoint.IsEmpty || endPoint.IsEmpty) continue;
|
||||
|
||||
var startPoint = rfidPoints.First(p => p.RFIDValue == connection.StartRFID).Location;
|
||||
var endPoint = rfidPoints.First(p => p.RFIDValue == connection.EndRFID).Location;
|
||||
lines.Add($"{startPoint.X},{startPoint.Y},{endPoint.X},{endPoint.Y}," +
|
||||
$"{connection.StartRFID},{connection.EndRFID},{connection.IsBidirectional},{connection.Distance}");
|
||||
}
|
||||
@@ -1625,19 +1725,38 @@ namespace AGVControl
|
||||
var validY = int.TryParse(rfidParts[1], out int valY);
|
||||
var validN = uint.TryParse(rfidParts[2], out uint valRfid);
|
||||
|
||||
|
||||
if (validX && validY && validN)
|
||||
{
|
||||
|
||||
|
||||
if (rfidPoints.Where(t => t.RFIDValue == valRfid).Any())
|
||||
{
|
||||
//이미존재한다
|
||||
var newvalue =
|
||||
sb.AppendLine($"rfid중복{valRfid}");
|
||||
}
|
||||
AddRFIDPoint(new Point(valX, valY), valRfid);
|
||||
|
||||
|
||||
var rfidPoint = new RFIDPoint
|
||||
{
|
||||
Location = new Point(valX, valY),
|
||||
RFIDValue = valRfid
|
||||
};
|
||||
|
||||
// 추가 속성 로드 (기본값 처리)
|
||||
if (rfidParts.Length >= 4)
|
||||
{
|
||||
bool isRotatable;
|
||||
bool.TryParse(rfidParts[3], out isRotatable);
|
||||
rfidPoint.IsRotatable = isRotatable;
|
||||
}
|
||||
if (rfidParts.Length >= 5 && !string.IsNullOrEmpty(rfidParts[4]))
|
||||
rfidPoint.FixedDirection = (Direction)Enum.Parse(typeof(Direction), rfidParts[4]);
|
||||
if (rfidParts.Length >= 6)
|
||||
{
|
||||
bool isTerminal;
|
||||
bool.TryParse(rfidParts[5], out isTerminal);
|
||||
rfidPoint.IsTerminal = isTerminal;
|
||||
}
|
||||
|
||||
rfidPoints.Add(rfidPoint);
|
||||
}
|
||||
else sb.AppendLine($"[{section}] {line}");
|
||||
}
|
||||
@@ -1908,7 +2027,8 @@ namespace AGVControl
|
||||
Direction = Direction.Backward,
|
||||
NextRFID = null,
|
||||
Reason = "AGV 위치 미확정(처음 기동)",
|
||||
ReasonCode = AGVActionReasonCode.NoPosition
|
||||
ReasonCode = AGVActionReasonCode.NoPosition,
|
||||
MoveState = AGVMoveState.Run
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1920,7 +2040,8 @@ namespace AGVControl
|
||||
Direction = agv.CurrentDirection,
|
||||
NextRFID = null,
|
||||
Reason = "경로 없음 또는 현재 위치 미확정",
|
||||
ReasonCode = AGVActionReasonCode.NoPath
|
||||
ReasonCode = AGVActionReasonCode.NoPath,
|
||||
MoveState = AGVMoveState.Stop
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1933,7 +2054,8 @@ namespace AGVControl
|
||||
Direction = agv.CurrentDirection,
|
||||
NextRFID = null,
|
||||
Reason = "현재 위치가 경로에 없음",
|
||||
ReasonCode = AGVActionReasonCode.NotOnPath
|
||||
ReasonCode = AGVActionReasonCode.NotOnPath,
|
||||
MoveState = AGVMoveState.Stop
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1948,7 +2070,11 @@ namespace AGVControl
|
||||
{
|
||||
// 목적지 바로 전 위치에서 목적지로 이동할 때의 방향
|
||||
var beforeDest = agv.CurrentPath[agv.CurrentPath.Count - 2];
|
||||
Direction arriveDir = (destPoint.X > beforeDest.X) ? Direction.Forward : Direction.Backward;
|
||||
float arriveDeltaX = destPoint.X - beforeDest.X;
|
||||
float arriveDeltaY = destPoint.Y - beforeDest.Y;
|
||||
Direction arriveDir = (Math.Abs(arriveDeltaX) > Math.Abs(arriveDeltaY)) ?
|
||||
(arriveDeltaX > 0 ? Direction.Forward : Direction.Backward) :
|
||||
(arriveDeltaY > 0 ? Direction.Forward : Direction.Backward);
|
||||
if (arriveDir != destRFID.FixedDirection.Value)
|
||||
{
|
||||
// 목적지 도달 전, 마지막 회전 가능한 RFID를 찾음
|
||||
@@ -1961,7 +2087,7 @@ namespace AGVControl
|
||||
}
|
||||
if (lastRotatableIdx >= 0)
|
||||
{
|
||||
// 회전 가능한 위치에 도달하면 NeedTurn 반환
|
||||
// 회전 가능한 위치에 도달하면 NeedTurn 반환 (STOP)
|
||||
if (idx == lastRotatableIdx)
|
||||
{
|
||||
var rfid = rfidPoints.FirstOrDefault(r => r.Location == agv.CurrentPath[lastRotatableIdx]);
|
||||
@@ -1970,35 +2096,43 @@ namespace AGVControl
|
||||
Direction = agv.CurrentDirection,
|
||||
NextRFID = rfid?.RFIDValue,
|
||||
Reason = "목적지 진입방향 맞추기 위해 회전 필요",
|
||||
ReasonCode = AGVActionReasonCode.NeedTurn
|
||||
ReasonCode = AGVActionReasonCode.NeedTurn,
|
||||
MoveState = AGVMoveState.Stop
|
||||
};
|
||||
}
|
||||
else if (idx < lastRotatableIdx)
|
||||
{
|
||||
// 회전 가능한 위치까지 이동 안내
|
||||
// 회전 가능한 위치까지 이동 안내 (RUN)
|
||||
var rfid = rfidPoints.FirstOrDefault(r => r.Location == agv.CurrentPath[lastRotatableIdx]);
|
||||
float moveDeltaX = agv.CurrentPath[lastRotatableIdx].X - agv.CurrentPosition.X;
|
||||
float moveDeltaY = agv.CurrentPath[lastRotatableIdx].Y - agv.CurrentPosition.Y;
|
||||
Direction moveDir = (Math.Abs(moveDeltaX) > Math.Abs(moveDeltaY)) ?
|
||||
(moveDeltaX > 0 ? Direction.Forward : Direction.Backward) :
|
||||
(moveDeltaY > 0 ? Direction.Forward : Direction.Backward);
|
||||
return new AGVActionPrediction
|
||||
{
|
||||
Direction = (agv.CurrentPath[lastRotatableIdx].X > agv.CurrentPosition.X) ? Direction.Forward : Direction.Backward,
|
||||
Direction = moveDir,
|
||||
NextRFID = rfid?.RFIDValue,
|
||||
Reason = "회전 가능한 위치로 이동 중",
|
||||
ReasonCode = AGVActionReasonCode.Normal
|
||||
ReasonCode = AGVActionReasonCode.Normal,
|
||||
MoveState = AGVMoveState.Run
|
||||
};
|
||||
}
|
||||
}
|
||||
// 회전 가능한 위치가 없음
|
||||
// 회전 가능한 위치가 없음 (STOP)
|
||||
return new AGVActionPrediction
|
||||
{
|
||||
Direction = agv.CurrentDirection,
|
||||
NextRFID = null,
|
||||
Reason = "경로상에 회전 가능한 위치가 없음",
|
||||
ReasonCode = AGVActionReasonCode.Unknown
|
||||
ReasonCode = AGVActionReasonCode.NoTurnPoint,
|
||||
MoveState = AGVMoveState.Stop
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 목적지 도달 시(방향이 맞는 경우)
|
||||
// 5. 목적지 도달 시(방향이 맞는 경우) (STOP)
|
||||
if (idx == agv.CurrentPath.Count - 1)
|
||||
{
|
||||
return new AGVActionPrediction
|
||||
@@ -2006,21 +2140,29 @@ namespace AGVControl
|
||||
Direction = agv.CurrentDirection,
|
||||
NextRFID = null,
|
||||
Reason = "경로의 마지막 지점(목적지 도달)",
|
||||
ReasonCode = AGVActionReasonCode.Arrived
|
||||
ReasonCode = AGVActionReasonCode.Arrived,
|
||||
MoveState = AGVMoveState.Stop
|
||||
};
|
||||
}
|
||||
|
||||
// 6. 일반 경로 주행
|
||||
// 6. 일반 경로 주행 (RUN)
|
||||
Point nextPoint = agv.CurrentPath[idx + 1];
|
||||
var nextRFID = rfidPoints.FirstOrDefault(r => r.Location == nextPoint)?.RFIDValue;
|
||||
Direction nextDir = (nextPoint.X > agv.CurrentPosition.X) ? Direction.Forward : Direction.Backward;
|
||||
|
||||
// X, Y 좌표 모두 고려한 방향 판단
|
||||
float deltaX = nextPoint.X - agv.CurrentPosition.X;
|
||||
float deltaY = nextPoint.Y - agv.CurrentPosition.Y;
|
||||
Direction nextDir = (Math.Abs(deltaX) > Math.Abs(deltaY)) ?
|
||||
(deltaX > 0 ? Direction.Forward : Direction.Backward) :
|
||||
(deltaY > 0 ? Direction.Forward : Direction.Backward);
|
||||
|
||||
return new AGVActionPrediction
|
||||
{
|
||||
Direction = nextDir,
|
||||
NextRFID = nextRFID,
|
||||
Reason = null,
|
||||
ReasonCode = AGVActionReasonCode.Normal
|
||||
ReasonCode = AGVActionReasonCode.Normal,
|
||||
MoveState = AGVMoveState.Run
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AGVControl.Models
|
||||
{
|
||||
public enum Direction
|
||||
{
|
||||
Forward = 0,
|
||||
Backward = 1
|
||||
}
|
||||
|
||||
public class AGV
|
||||
{
|
||||
public Point CurrentPosition { get; set; }
|
||||
@@ -19,12 +26,17 @@ namespace AGVControl.Models
|
||||
/// </summary>
|
||||
public Direction TargetDirection { get; set; }
|
||||
public bool IsMoving { get; set; }
|
||||
public List<Point> CurrentPath { get; set; }
|
||||
public List<Point> CurrentPath { get; set; } = new List<Point>();
|
||||
public List<Point> PlannedPath { get; set; }
|
||||
public List<string> PathRFIDs { get; set; }
|
||||
public Point TargetPosition { get; set; }
|
||||
public uint TargetRFID { get; set; }
|
||||
|
||||
// 이동 경로 기록을 위한 새로운 속성들
|
||||
public List<uint> MovementHistory { get; set; } = new List<uint>();
|
||||
public List<Point> PositionHistory { get; set; } = new List<Point>();
|
||||
public const int HISTORY_SIZE = 4; // 최근 4개 위치 기록
|
||||
|
||||
public AGV()
|
||||
{
|
||||
CurrentPath = new List<Point>();
|
||||
@@ -45,12 +57,179 @@ namespace AGVControl.Models
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 이동 경로에 새로운 RFID 추가
|
||||
public void AddToMovementHistory(uint rfidValue, Point position)
|
||||
{
|
||||
// 중복 RFID가 연속으로 들어오는 경우 무시
|
||||
if (MovementHistory.Count > 0 && MovementHistory[MovementHistory.Count - 1] == rfidValue)
|
||||
return;
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Forward,
|
||||
Backward
|
||||
MovementHistory.Add(rfidValue);
|
||||
PositionHistory.Add(position);
|
||||
|
||||
// 기록 크기 제한
|
||||
if (MovementHistory.Count > HISTORY_SIZE)
|
||||
{
|
||||
MovementHistory.RemoveAt(0);
|
||||
PositionHistory.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 연결 정보 기반 실제 이동 방향 계산
|
||||
public Direction? CalculateActualDirectionByConnection(uint currentRFID, uint previousRFID, List<RFIDConnection> connections)
|
||||
{
|
||||
if (connections == null || connections.Count == 0)
|
||||
return null;
|
||||
|
||||
// 이전 RFID에서 현재 RFID로의 연결 확인
|
||||
var connection = connections.FirstOrDefault(c =>
|
||||
(c.StartRFID == previousRFID && c.EndRFID == currentRFID) ||
|
||||
(c.IsBidirectional && c.StartRFID == currentRFID && c.EndRFID == previousRFID));
|
||||
|
||||
if (connection == null)
|
||||
return null; // 연결되지 않은 경로
|
||||
|
||||
// 연결 방향에 따라 실제 이동 방향 결정
|
||||
if (connection.StartRFID == previousRFID && connection.EndRFID == currentRFID)
|
||||
{
|
||||
return Direction.Forward; // Start -> End 방향으로 이동
|
||||
}
|
||||
else
|
||||
{
|
||||
return Direction.Backward; // End -> Start 방향으로 이동
|
||||
}
|
||||
}
|
||||
|
||||
// 연결 정보 기반 방향 불일치 검증 및 정정
|
||||
public bool ValidateAndCorrectDirectionByConnection(Direction expectedDirection, List<RFIDConnection> connections)
|
||||
{
|
||||
if (MovementHistory.Count < 2 || connections == null)
|
||||
return true; // 검증 불가능한 경우
|
||||
|
||||
// 최근 두 RFID 값 가져오기
|
||||
var recentRFIDs = MovementHistory.Skip(Math.Max(0, MovementHistory.Count - 2)).Take(2).ToList();
|
||||
if (recentRFIDs.Count < 2)
|
||||
return true;
|
||||
|
||||
var previousRFID = recentRFIDs[0];
|
||||
var currentRFID = recentRFIDs[1];
|
||||
|
||||
var actualDirection = CalculateActualDirectionByConnection(currentRFID, previousRFID, connections);
|
||||
if (!actualDirection.HasValue)
|
||||
return true; // 연결 정보로 방향 판단 불가
|
||||
|
||||
// 방향이 일치하지 않는 경우
|
||||
if (actualDirection.Value != expectedDirection)
|
||||
{
|
||||
// AGV 방향을 실제 이동 방향으로 정정
|
||||
CurrentDirection = actualDirection.Value;
|
||||
TargetDirection = actualDirection.Value;
|
||||
return false; // 정정됨을 알림
|
||||
}
|
||||
|
||||
return true; // 방향 일치
|
||||
}
|
||||
|
||||
// RFID 순서 기반 실제 이동 방향 계산 (기존 메서드 - 호환성 유지)
|
||||
public Direction? CalculateActualDirectionByRFID()
|
||||
{
|
||||
if (MovementHistory.Count < 2)
|
||||
return null;
|
||||
|
||||
// 최근 두 RFID 값으로부터 실제 이동 방향 계산
|
||||
var recentRFIDs = MovementHistory.Skip(Math.Max(0, MovementHistory.Count - 2)).Take(2).ToList();
|
||||
if (recentRFIDs.Count < 2)
|
||||
return null;
|
||||
|
||||
var prevRFID = recentRFIDs[0];
|
||||
var currentRFID = recentRFIDs[1];
|
||||
|
||||
// RFID 값의 증가/감소로 방향 판단
|
||||
if (currentRFID > prevRFID)
|
||||
{
|
||||
return Direction.Forward; // RFID 값이 증가하면 전진
|
||||
}
|
||||
else if (currentRFID < prevRFID)
|
||||
{
|
||||
return Direction.Backward; // RFID 값이 감소하면 후진
|
||||
}
|
||||
else
|
||||
{
|
||||
return null; // 같은 RFID 값이면 방향 판단 불가
|
||||
}
|
||||
}
|
||||
|
||||
// 경로상 RFID 순서 기반 예상 방향 계산
|
||||
public Direction? CalculateExpectedDirectionByRFID()
|
||||
{
|
||||
if (CurrentPath == null || CurrentPath.Count < 2)
|
||||
return null;
|
||||
|
||||
// 현재 위치의 RFID 찾기
|
||||
var currentRFID = CurrentRFID;
|
||||
if (currentRFID == 0)
|
||||
return null;
|
||||
|
||||
// 경로상 다음 RFID 찾기
|
||||
int currentIdx = CurrentPath.FindIndex(p => p == CurrentPosition);
|
||||
if (currentIdx < 0 || currentIdx >= CurrentPath.Count - 1)
|
||||
return null;
|
||||
|
||||
// 다음 위치의 RFID 찾기 (MapControl에서 RFID 정보 필요)
|
||||
// 이 부분은 MapControl에서 처리하도록 수정 필요
|
||||
return null;
|
||||
}
|
||||
|
||||
// 실제 이동 방향 계산 (기존 메서드 - 호환성 유지)
|
||||
public Direction? CalculateActualDirection()
|
||||
{
|
||||
if (MovementHistory.Count < 2)
|
||||
return null;
|
||||
|
||||
// 최근 두 위치로부터 실제 이동 방향 계산
|
||||
var recentPositions = PositionHistory.Skip(Math.Max(0, PositionHistory.Count - 2)).Take(2).ToList();
|
||||
if (recentPositions.Count < 2)
|
||||
return null;
|
||||
|
||||
var prevPos = recentPositions[0];
|
||||
var currentPos = recentPositions[1];
|
||||
|
||||
// X축 이동이 더 큰 경우
|
||||
if (Math.Abs(currentPos.X - prevPos.X) > Math.Abs(currentPos.Y - prevPos.Y))
|
||||
{
|
||||
return currentPos.X > prevPos.X ? Direction.Forward : Direction.Backward;
|
||||
}
|
||||
// Y축 이동이 더 큰 경우
|
||||
else
|
||||
{
|
||||
return currentPos.Y > prevPos.Y ? Direction.Forward : Direction.Backward;
|
||||
}
|
||||
}
|
||||
|
||||
// 경로상 예상 방향 계산
|
||||
public Direction? CalculateExpectedDirection()
|
||||
{
|
||||
if (CurrentPath == null || CurrentPath.Count < 2)
|
||||
return null;
|
||||
|
||||
int currentIdx = CurrentPath.FindIndex(p => p == CurrentPosition);
|
||||
if (currentIdx < 0 || currentIdx >= CurrentPath.Count - 1)
|
||||
return null;
|
||||
|
||||
var currentPos = CurrentPath[currentIdx];
|
||||
var nextPos = CurrentPath[currentIdx + 1];
|
||||
|
||||
// X축 이동이 더 큰 경우
|
||||
if (Math.Abs(nextPos.X - currentPos.X) > Math.Abs(nextPos.Y - currentPos.Y))
|
||||
{
|
||||
return nextPos.X > currentPos.X ? Direction.Forward : Direction.Backward;
|
||||
}
|
||||
// Y축 이동이 더 큰 경우
|
||||
else
|
||||
{
|
||||
return nextPos.Y > currentPos.Y ? Direction.Forward : Direction.Backward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PathNode
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace AGVControl.Models
|
||||
public bool IsBidirectional { get; set; } // 양방향 연결 여부
|
||||
public bool IsRotatable { get; set; } // 회전 가능 여부
|
||||
public Direction? FixedDirection { get; set; } // 고정 방향(없으면 null)
|
||||
public bool IsTerminal { get; set; } // 종단 여부
|
||||
public RectangleF Bounds { get; set; }
|
||||
|
||||
public RFIDPoint()
|
||||
@@ -18,6 +19,7 @@ namespace AGVControl.Models
|
||||
IsRotatable = false; // 기본값은 회전 불가능
|
||||
IsBidirectional = true; // 기본값은 양방향
|
||||
FixedDirection = null;
|
||||
IsTerminal = false; // 기본값은 종단 아님
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user