add temp1, temp2

This commit is contained in:
chi
2025-06-20 11:56:34 +09:00
parent 0ae0faf050
commit f7615396d5
22 changed files with 1800 additions and 802 deletions

View File

@@ -0,0 +1,3 @@
{
"dotnet.preferCSharpExtension": true
}

View File

@@ -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

View File

@@ -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

View File

@@ -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; // 기본값은 종단 아님
}
}
}

View File

@@ -200,6 +200,14 @@ namespace arDev
Current_LevelA = LastReceiveBuffer[23]; //<- 23번자료는 byte이므로 소수점이잇는 데이터로 직접 계산한다
Current_DataTime = DateTime.Now;
//250620 jwlee 추가
int temp1 = (LastReceiveBuffer[27] << 8) | LastReceiveBuffer[28];
int temp2 = (LastReceiveBuffer[29] << 8) | LastReceiveBuffer[30];
Current_temp1 = (temp1 - 2731) / 10f;
Current_temp2 = (temp2 - 2731) / 10f;
CheckManualCharge();
Recv0 = true;
@@ -333,6 +341,16 @@ namespace arDev
/// </summary>
public int Current_Amp { get; set; }
/// <summary>
/// BMS 온도값1
/// </summary>
public double Current_temp1 { get; set; }
/// <summary>
/// BMS 온도값2
/// </summary>
public double Current_temp2 { get; set; }
/// <summary>
/// 총 전류량
/// </summary>

View File

@@ -46,12 +46,7 @@
<Compile Include="BMSInformationEventArgs.cs" />
<Compile Include="BMS.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CommData\CommData.csproj">
<Project>{14e8c9a5-013e-49ba-b435-efefc77dd623}</Project>
<Name>CommData</Name>
</ProjectReference>
<Compile Include="RS232.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,485 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
public abstract partial class arRS232 : IDisposable
{
protected System.IO.Ports.SerialPort _device;
protected ManualResetEvent _mre;
protected byte[] LastReceiveBuffer = new byte[] { };
/// <summary>
/// 최종 전송 메세지
/// </summary>
public byte[] lastSendBuffer = new byte[] { };
protected List<byte> tempBuffer = new List<byte>();
protected Boolean findSTX = false;
public string errorMessage { get; set; }
public DateTime LastConnTime { get; set; }
public DateTime LastConnTryTime { get; set; }
public DateTime lastSendTime;
/// <summary>
/// 메세지 수신시 사용하는 내부버퍼
/// </summary>
protected List<byte> _buffer = new List<byte>();
// public byte[] LastRecvData;
public string LastRecvString
{
get
{
if (LastReceiveBuffer == null) return String.Empty;
else return System.Text.Encoding.Default.GetString(LastReceiveBuffer);
}
}
/// <summary>
/// 마지막으로 데이터를 받은 시간
/// </summary>
public DateTime lastRecvTime;
public int WriteError = 0;
public string WriteErrorMessage = string.Empty;
public int WaitTimeout { get; set; } = 1000;
public int MinRecvLength { get; set; } = 1;
/// <summary>
/// 포트이름
/// </summary>
[Description("시리얼 포트 이름")]
[Category("설정"), DisplayName("Port Name")]
public string PortName
{
get
{
if (_device == null) return string.Empty;
else return _device.PortName;
}
set
{
if (this.IsOpen)
{
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 포트이름을 변경할 수 없습니다", true));
}
else if (String.IsNullOrEmpty(value) == false)
_device.PortName = value;
else
{
Message?.Invoke(this, new MessageEventArgs("No PortName", true));
}
}
}
public int BaudRate
{
get
{
if (_device == null) return 0;
else return _device.BaudRate;
}
set
{
if (this.IsOpen)
{
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 BaudRate(를) 변경할 수 없습니다", true));
}
else if (value != 0)
_device.BaudRate = value;
else Message?.Invoke(this, new MessageEventArgs("No baud rate", true));
}
}
public arRS232()
{
_device = new System.IO.Ports.SerialPort();
this.BaudRate = 9600;
_device.DataReceived += barcode_DataReceived;
_device.ErrorReceived += this.barcode_ErrorReceived;
_device.WriteTimeout = 5000;
_device.ReadTimeout = 5000;
// _device.DtrEnable = false;
_device.ReadBufferSize = 8192;
_device.WriteBufferSize = 8192;
errorMessage = string.Empty;
lastRecvTime = DateTime.Parse("1982-11-23");
LastConnTime = DateTime.Parse("1982-11-23");
LastConnTryTime = DateTime.Parse("1982-11-23");
lastRecvTime = DateTime.Parse("1982-11-23");
this._mre = new ManualResetEvent(true);
}
~arRS232()
{
Dispose(false);
}
// Flag: Has Dispose already been called?
bool disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
// Free any other managed objects here.
//
}
_device.DataReceived -= barcode_DataReceived;
_device.ErrorReceived -= this.barcode_ErrorReceived;
// Free any unmanaged objects here.
//
disposed = true;
}
public Boolean Open()
{
try
{
_device.Open();
return IsOpen;
}
catch (Exception ex)
{
errorMessage = ex.Message;
Message.Invoke(this, new MessageEventArgs(ex.Message, true));
return false;
}
}
public string GetHexString(Byte[] input)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (byte b in input)
sb.Append(" " + b.ToString("X2"));
return sb.ToString();
}
/// <summary>
/// 포트가 열려있는지 확인
/// </summary>
[Description("현재 시리얼포트가 열려있는지 확인합니다")]
[Category("정보"), DisplayName("Port Open")]
public Boolean IsOpen
{
get
{
if (_device == null) return false;
return _device.IsOpen;
}
}
public virtual void Close(Boolean PortClose = true)
{
if (_device != null && _device.IsOpen)
{
_device.DiscardInBuffer();
_device.DiscardOutBuffer();
if (PortClose) _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
}
}
protected Boolean RaiseRecvData()
{
return RaiseRecvData(LastReceiveBuffer.ToArray(), false);
}
/// <summary>
/// 수신받은 메세지를 발생 시킵니다
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual Boolean RaiseRecvData(byte[] Data, bool udpatelastbuffer)
{
//181206 - 최종수신 메세지 기록
lastRecvTime = DateTime.Now;
if (udpatelastbuffer && Data != null)
{
if (LastReceiveBuffer == null || LastReceiveBuffer.Length != Data.Length)
{
LastReceiveBuffer = new byte[Data.Length];
Array.Copy(Data, LastReceiveBuffer, Data.Length);
}
}
try
{
Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
if (ProcessRecvData(Data) == false)
{
//Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
Message?.Invoke(this, new MessageEventArgs(this.errorMessage, true)); //errormessage
return false;
}
else
{
return true;
}
}
catch (Exception ex)
{
this.errorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
return false;
}
}
/// <summary>
/// 수신받은 자료를 처리한다
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public abstract bool ProcessRecvData(byte[] data);
#region "Internal Events"
void barcode_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
{
Message?.Invoke(this, new MessageEventArgs(e.ToString(), true));
}
byte[] buffer = new byte[] { };
void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
try
{
int ReadCount = _device.BytesToRead;
buffer = new byte[ReadCount];
_device.Read(buffer, 0, buffer.Length);
System.Text.StringBuilder LogMsg = new StringBuilder();
byte[] remainBuffer;
Repeat:
if (CustomParser(buffer, out remainBuffer))
{
//분석완료이므로 받은 데이터를 버퍼에 기록한다
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
tempBuffer.Clear();
//수신메세지발생
RaiseRecvData();
if (remainBuffer != null && remainBuffer.Length > 0)
{
//버퍼를 변경해서 다시 전송을 해준다.
Array.Resize(ref buffer, remainBuffer.Length);
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
}
}
}
catch (Exception ex)
{
if (IsOpen)
{
//_device.DiscardInBuffer();
//_device.DiscardOutBuffer();
}
errorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
}
}
#endregion
#region "External Events"
/// <summary>
/// 오류 및 기타 일반 메세지
/// </summary>
public event EventHandler<MessageEventArgs> Message;
#endregion
#region "Event Args"
/// <summary>
/// 데이터를 수신할떄 사용함(RAW 포함)
/// </summary>
public class ReceiveDataEventArgs : EventArgs
{
private byte[] _buffer = null;
/// <summary>
/// 바이트배열의 버퍼값
/// </summary>
public byte[] Value { get { return _buffer; } }
/// <summary>
/// 버퍼(바이트배열)의 데이터를 문자로 반환합니다.
/// </summary>
public string StrValue
{
get
{
//return string.Empty;
if (_buffer == null || _buffer.Length < 1) return string.Empty;
else return System.Text.Encoding.Default.GetString(_buffer);
}
}
public ReceiveDataEventArgs(byte[] buffer)
{
_buffer = buffer;
}
}
/// <summary>
/// 메세지를 강제 발생
/// </summary>
/// <param name="mt"></param>
/// <param name="message"></param>
protected virtual void RaiseMessage(MessageType mt, string message)
{
this.Message?.Invoke(this, new MessageEventArgs(mt, message));
}
public enum MessageType
{
Normal,
Error,
Send,
Recv,
}
public class MessageEventArgs : EventArgs
{
public MessageType MsgType { get; set; }
private string _message = string.Empty;
/// <summary>
/// Recv,Send,Normal,Error 모두 지원
/// </summary>
public string Message { get { return _message; } }
private byte[] _data = null;
/// <summary>
/// Recv,Send에서만 값이 존재 합니다
/// </summary>
public byte[] Data { get { return _data; } }
public MessageEventArgs(string Message, bool isError = false)
{
if (isError) MsgType = MessageType.Error;
else MsgType = MessageType.Normal;
_message = Message;
}
public MessageEventArgs(MessageType msgtype, string Message)
{
MsgType = msgtype;
_message = Message;
_data = System.Text.Encoding.Default.GetBytes(Message);
}
public MessageEventArgs(byte[] buffer, bool isRecv = true)
{
if (isRecv) MsgType = MessageType.Recv;
else MsgType = MessageType.Send;
_data = new byte[buffer.Length];
Array.Copy(buffer, _data, Data.Length);
_message = System.Text.Encoding.Default.GetString(_data);
}
}
#endregion
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
/// <summary>
/// 데이터수신시간이 설정값보다 x 2.5를 초과하면 false 가 반환됨
/// </summary>
public Boolean IsValid
{
get
{
if (IsOpen == false) return false;
if (lastRecvTime.Year == 1982) return false;
var ts = DateTime.Now - lastRecvTime;
if (ts.TotalSeconds > 5) return false;
return true;
}
}
protected bool WriteData(string cmd)
{
return WriteData(System.Text.Encoding.Default.GetBytes(cmd));
}
/// <summary>
/// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신)
/// </summary>
protected Boolean WriteData(byte[] data)
{
Boolean bRet = false;
//171205 : 타임아웃시간추가
if (!_mre.WaitOne(WaitTimeout))
{
errorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms";
this.Message?.Invoke(this, new MessageEventArgs(errorMessage, true));
return false;
}
_mre.Reset();
//Array.Resize(ref data, data.Length + 2);
try
{
lastSendTime = DateTime.Now;
if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113
else Array.Resize(ref lastSendBuffer, data.Length);
Array.Copy(data, lastSendBuffer, data.Length);
for (int i = 0; i < data.Length; i++)
_device.Write(data, i, 1);
//_device.Write(data, 0, data.Length);
//171113
this.Message?.Invoke(this, new MessageEventArgs(data, false));
bRet = true;
WriteError = 0;
WriteErrorMessage = string.Empty;
}
catch (Exception ex)
{
// this.isinit = false;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
bRet = false;
WriteError += 1; //연속쓰기오류횟수
WriteErrorMessage = ex.Message;
}
finally
{
_mre.Set();
}
return bRet;
}
}