..
This commit is contained in:
@@ -39,6 +39,9 @@ namespace Project
|
|||||||
bool _charging = false;
|
bool _charging = false;
|
||||||
private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e)
|
private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (PUB.mapctl != null)
|
||||||
|
PUB.mapctl.PredictNextAction();
|
||||||
|
|
||||||
switch (e.DataType)
|
switch (e.DataType)
|
||||||
{
|
{
|
||||||
case arDev.Narumi.DataType.STS:
|
case arDev.Narumi.DataType.STS:
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Project
|
|||||||
{
|
{
|
||||||
if (PUB.setting.XBE_ID == tID)
|
if (PUB.setting.XBE_ID == tID)
|
||||||
{
|
{
|
||||||
if (uint.TryParse(targstr, out uint tagno))
|
if (ushort.TryParse(targstr, out ushort tagno))
|
||||||
{
|
{
|
||||||
if (PUB.mapctl.SetCurrentPosition(tagno) == true)
|
if (PUB.mapctl.SetCurrentPosition(tagno) == true)
|
||||||
{
|
{
|
||||||
|
|||||||
1
Cs_HMI/Project/ViewForm/fAuto.Designer.cs
generated
1
Cs_HMI/Project/ViewForm/fAuto.Designer.cs
generated
@@ -41,7 +41,6 @@ namespace Project.ViewForm
|
|||||||
// timer1
|
// timer1
|
||||||
//
|
//
|
||||||
timer1.Interval = 200;
|
timer1.Interval = 200;
|
||||||
timer1.Tick += timer1_Tick;
|
|
||||||
//
|
//
|
||||||
// ctlAuto1
|
// ctlAuto1
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Project.ViewForm
|
|||||||
// ctlAuto1.dev_plc = PUB.PLC;
|
// ctlAuto1.dev_plc = PUB.PLC;
|
||||||
ctlAuto1.dev_bms = PUB.BMS;
|
ctlAuto1.dev_bms = PUB.BMS;
|
||||||
ctlAuto1.dev_xbe = PUB.XBE;
|
ctlAuto1.dev_xbe = PUB.XBE;
|
||||||
this.timer1.Start();
|
|
||||||
PUB.AGV.DataReceive += AGV_DataReceive;
|
PUB.AGV.DataReceive += AGV_DataReceive;
|
||||||
|
|
||||||
|
|
||||||
@@ -67,6 +67,8 @@ namespace Project.ViewForm
|
|||||||
if (rlt == false) AR.UTIL.MsgE(errmsg);
|
if (rlt == false) AR.UTIL.MsgE(errmsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.timer1.Start();
|
||||||
}
|
}
|
||||||
private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e)
|
private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -106,34 +108,6 @@ namespace Project.ViewForm
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool tmrun = false;
|
bool tmrun = false;
|
||||||
private void timer1_Tick(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (this.Visible == false) return;
|
|
||||||
if (tmrun == true) return;
|
|
||||||
tmrun = true;
|
|
||||||
|
|
||||||
this.ctlAuto1.OnUpdateMode = true;
|
|
||||||
|
|
||||||
if (this.ctlAuto1.Scean == CtlAuto.eScean.Progress)
|
|
||||||
{
|
|
||||||
ctlAuto1.ProgressVal = PUB.Result.SMSG_ProgressValue;
|
|
||||||
ctlAuto1.ProgressMax = PUB.Result.SMSG_ProgressMax;
|
|
||||||
ctlAuto1.StatusMessage = VAR.STR?.Get(eVarString.StatusMessage) ?? string.Empty;
|
|
||||||
}
|
|
||||||
this.ctlAuto1.StopMessage = string.Empty;
|
|
||||||
|
|
||||||
if (PUB.sm.Step == StateMachine.eSMStep.RUN)
|
|
||||||
{
|
|
||||||
this.ctlAuto1.runStep = PUB.sm.RunStep;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.ctlAuto1.runStep = ERunStep.READY;
|
|
||||||
}
|
|
||||||
this.ctlAuto1.OnUpdateMode = false;
|
|
||||||
this.ctlAuto1.Invalidate();
|
|
||||||
tmrun = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fAuto_VisibleChanged(object sender, EventArgs e)
|
private void fAuto_VisibleChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
@@ -141,5 +115,37 @@ namespace Project.ViewForm
|
|||||||
if (timer1.Enabled) timer1.Start();
|
if (timer1.Enabled) timer1.Start();
|
||||||
else timer1.Stop();
|
else timer1.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void timer1_Tick_1(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
//if (this.Visible == false) return;
|
||||||
|
//if (tmrun == true) return;
|
||||||
|
//tmrun = true;
|
||||||
|
|
||||||
|
//this.ctlAuto1.OnUpdateMode = true;
|
||||||
|
|
||||||
|
//if (this.ctlAuto1.Scean == CtlAuto.eScean.Progress)
|
||||||
|
//{
|
||||||
|
// ctlAuto1.ProgressVal = PUB.Result.SMSG_ProgressValue;
|
||||||
|
// ctlAuto1.ProgressMax = PUB.Result.SMSG_ProgressMax;
|
||||||
|
// ctlAuto1.StatusMessage = VAR.STR?.Get(eVarString.StatusMessage) ?? string.Empty;
|
||||||
|
//}
|
||||||
|
//this.ctlAuto1.StopMessage = string.Empty;
|
||||||
|
|
||||||
|
//if (PUB.sm.Step == StateMachine.eSMStep.RUN)
|
||||||
|
//{
|
||||||
|
// this.ctlAuto1.runStep = PUB.sm.RunStep;
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// this.ctlAuto1.runStep = ERunStep.READY;
|
||||||
|
//}
|
||||||
|
//this.ctlAuto1.OnUpdateMode = false;
|
||||||
|
//this.ctlAuto1.Invalidate();
|
||||||
|
|
||||||
|
//PUB.mapctl.PredictNextAction();
|
||||||
|
|
||||||
|
//tmrun = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ namespace arDev
|
|||||||
{
|
{
|
||||||
//221123 chi 숫자로변경
|
//221123 chi 숫자로변경
|
||||||
var tagnostr = rcvdNow.Substring(3);
|
var tagnostr = rcvdNow.Substring(3);
|
||||||
if (uint.TryParse(tagnostr, out uint tagnoint))
|
if (ushort.TryParse(tagnostr, out ushort tagnoint))
|
||||||
{
|
{
|
||||||
var Changed = !old_TagString.Equals(tagnostr);
|
var Changed = !old_TagString.Equals(tagnostr);
|
||||||
data.TagString = tagnostr;
|
data.TagString = tagnostr;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace arDev
|
|||||||
|
|
||||||
|
|
||||||
public string TagString { get; set; } = string.Empty;
|
public string TagString { get; set; } = string.Empty;
|
||||||
public uint TagNo { get; set; } = 0;
|
public ushort TagNo { get; set; } = 0;
|
||||||
public string CallString { get; set; } = string.Empty;
|
public string CallString { get; set; } = string.Empty;
|
||||||
public int CallNo { get; set; } = -1;
|
public int CallNo { get; set; } = -1;
|
||||||
public string CCAString { get; set; } = string.Empty;
|
public string CCAString { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.Design;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Design;
|
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.NetworkInformation;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Security.Permissions;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using AGVControl.Models;
|
using AGVControl.Models;
|
||||||
using AR;
|
using AR;
|
||||||
using static System.Net.Mime.MediaTypeNames;
|
|
||||||
|
|
||||||
namespace AGVControl
|
namespace AGVControl
|
||||||
{
|
{
|
||||||
@@ -242,7 +237,7 @@ namespace AGVControl
|
|||||||
var tag = AR.UTIL.InputBox("input rfid tag value");
|
var tag = AR.UTIL.InputBox("input rfid tag value");
|
||||||
if (tag.Item1 && tag.Item2 != "" && uint.TryParse(tag.Item2, out uint val) == true)
|
if (tag.Item1 && tag.Item2 != "" && uint.TryParse(tag.Item2, out uint val) == true)
|
||||||
{
|
{
|
||||||
var targetRFID = SetCurrentPosition(val);
|
var targetRFID = SetCurrentPosition((ushort)val);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "save":
|
case "save":
|
||||||
@@ -285,8 +280,7 @@ namespace AGVControl
|
|||||||
|
|
||||||
if (od.ShowDialog() == DialogResult.OK)
|
if (od.ShowDialog() == DialogResult.OK)
|
||||||
{
|
{
|
||||||
|
this.LoadFromFile(od.FileName, out string errmsg);
|
||||||
this.LoadFromFile(filename, out string errmsg);
|
|
||||||
if (errmsg.isEmpty() == false) UTIL.MsgE(errmsg);
|
if (errmsg.isEmpty() == false) UTIL.MsgE(errmsg);
|
||||||
this.Invalidate();
|
this.Invalidate();
|
||||||
}
|
}
|
||||||
@@ -729,21 +723,77 @@ namespace AGVControl
|
|||||||
return rfidPoints.FirstOrDefault(r => r.RFIDValue == rfidValue);
|
return rfidPoints.FirstOrDefault(r => r.RFIDValue == rfidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetCurrentPosition(uint rfidValue)
|
public bool SetCurrentPosition(UInt16 rfidValue)
|
||||||
{
|
{
|
||||||
var rfidPoint = FindRFIDPoint(rfidValue);
|
var rfidPoint = FindRFIDPoint(rfidValue);
|
||||||
if (rfidPoint != null)
|
if (rfidPoint != null)
|
||||||
{
|
{
|
||||||
// 이전 위치 저장 (방향 검증용)
|
// 이동 경로에 추가 (위치 업데이트보다 먼저)
|
||||||
Point previousPosition = agv.CurrentPosition;
|
agv.AddToMovementHistory(rfidValue, rfidPoint.Location, this.agv.CurrentDirection);
|
||||||
uint previousRFID = agv.CurrentRFID;
|
|
||||||
|
|
||||||
// AGV 위치 업데이트
|
// AGV 위치 업데이트
|
||||||
agv.CurrentPosition = rfidPoint.Location;
|
agv.CurrentPosition = rfidPoint.Location;
|
||||||
agv.CurrentRFID = rfidValue;
|
agv.CurrentRFID = rfidValue;
|
||||||
|
|
||||||
// 이동 경로에 추가
|
// --- 동체 방향(BodyAngle) 결정 로직 ---
|
||||||
agv.AddToMovementHistory(rfidValue, rfidPoint.Location);
|
if (!agv.BodyAngle.HasValue && agv.MovementHistory.Count >= 2)
|
||||||
|
{
|
||||||
|
// 두 번째 RFID가 인식된 시점
|
||||||
|
var history = agv.PositionHistory.Skip(Math.Max(0, agv.PositionHistory.Count - 2)).Take(2).ToList();
|
||||||
|
Point firstPos = history[0];
|
||||||
|
Point secondPos = history[1];
|
||||||
|
|
||||||
|
// 두 점 사이의 각도 계산
|
||||||
|
float deltaX = secondPos.X - firstPos.X;
|
||||||
|
float deltaY = secondPos.Y - firstPos.Y;
|
||||||
|
float baseAngle = (float)Math.Atan2(deltaY, deltaX) * 180f / (float)Math.PI;
|
||||||
|
|
||||||
|
// 모터 방향(CurrentDirection)에 따라 최종 BodyAngle 결정
|
||||||
|
if (agv.CurrentDirection == Direction.Backward)
|
||||||
|
{
|
||||||
|
// 후진 중이었다면, 몸체는 반대 방향을 보고 있었음
|
||||||
|
agv.BodyAngle = (baseAngle + 180) % 360;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 전진 또는 미정의 상태였다면, 몸체는 이동 방향을 보고 있었음
|
||||||
|
agv.BodyAngle = baseAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AGV의 모터 방향 결정
|
||||||
|
if (agv.MovementHistory.Count > 1)
|
||||||
|
{
|
||||||
|
// 다음 목적지 찾기
|
||||||
|
var lastP1 = agv.MovementHistory.Last();
|
||||||
|
var lastP2 = agv.MovementHistory.Skip(agv.MovementHistory.Count - 2).Take(1).First();
|
||||||
|
|
||||||
|
// 모터 각도 계산 및 업데이트
|
||||||
|
var prePoint = rfidPoints.Where(t => t.RFIDValue == lastP2.rfid).FirstOrDefault();
|
||||||
|
if (prePoint != null)
|
||||||
|
{
|
||||||
|
float deltaX;
|
||||||
|
float deltaY;
|
||||||
|
if (agv.CurrentDirection == Direction.Forward)
|
||||||
|
{
|
||||||
|
deltaX = agv.CurrentPosition.X - prePoint.Bounds.X;
|
||||||
|
deltaY = agv.CurrentPosition.Y - prePoint.Bounds.Y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
deltaX = prePoint.Bounds.X - agv.CurrentPosition.X;
|
||||||
|
deltaY = prePoint.Bounds.Y - agv.CurrentPosition.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
agv.MotorAngle = (float)Math.Atan2(deltaY, deltaX) * 180f / (float)Math.PI;
|
||||||
|
|
||||||
|
// 회전 가능 여부를 고려하여 방향 결정
|
||||||
|
//agv.TargetDirection = DetermineDirection(agv.CurrentPosition, nextPoint, agv.TargetPosition);//
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 목적지가 설정되어 있고 경로가 있는 경우 검증
|
// 목적지가 설정되어 있고 경로가 있는 경우 검증
|
||||||
if (agv.TargetPosition != Point.Empty && agv.CurrentPath.Count > 0)
|
if (agv.TargetPosition != Point.Empty && agv.CurrentPath.Count > 0)
|
||||||
@@ -761,15 +811,7 @@ namespace AGVControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AGV의 방향 결정
|
|
||||||
if (agv.CurrentPath.Count > 0)
|
|
||||||
{
|
|
||||||
// 다음 목적지 찾기
|
|
||||||
var nextPoint = agv.CurrentPath[0];
|
|
||||||
|
|
||||||
// 회전 가능 여부를 고려하여 방향 결정
|
|
||||||
agv.TargetDirection = DetermineDirection(agv.CurrentPosition, nextPoint, agv.TargetPosition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 목적지 RFID에 도착했고, 해당 RFID에 고정방향이 있으면 TargetDirection을 강제 설정
|
// 목적지 RFID에 도착했고, 해당 RFID에 고정방향이 있으면 TargetDirection을 강제 설정
|
||||||
@@ -782,8 +824,8 @@ namespace AGVControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 방향 검증 및 정정 (이전 위치가 있고 경로가 있는 경우)
|
// 방향 검증 및 정정 (세 번째 이동부터, BodyAngle이 결정된 후)
|
||||||
if (previousRFID != 0 && agv.CurrentPath.Count > 0)
|
if (agv.BodyAngle.HasValue && agv.MovementHistory.Count > 2)
|
||||||
{
|
{
|
||||||
// RFID 연결 정보 기반 예상 방향 계산
|
// RFID 연결 정보 기반 예상 방향 계산
|
||||||
Direction? expectedDirection = null;
|
Direction? expectedDirection = null;
|
||||||
@@ -906,37 +948,50 @@ namespace AGVControl
|
|||||||
|
|
||||||
private float Distance(Point a, Point b)
|
private float Distance(Point a, Point b)
|
||||||
{
|
{
|
||||||
// RFID 라인을 통한 연결 확인
|
var rfidA = rfidPoints.FirstOrDefault(p => p.Location == a);
|
||||||
var rfidLines = GetRFIDLines();
|
var rfidB = rfidPoints.FirstOrDefault(p => p.Location == b);
|
||||||
var directConnection = rfidLines.FirstOrDefault(line =>
|
|
||||||
(line.StartPoint == a && line.EndPoint == b) ||
|
|
||||||
(line.StartPoint == b && line.EndPoint == a));
|
|
||||||
|
|
||||||
if (directConnection != null)
|
if (rfidA == null || rfidB == null) return float.MaxValue;
|
||||||
|
|
||||||
|
var connection = rfidConnections.FirstOrDefault(c =>
|
||||||
|
(c.StartRFID == rfidA.RFIDValue && c.EndRFID == rfidB.RFIDValue) ||
|
||||||
|
(c.IsBidirectional && c.StartRFID == rfidB.RFIDValue && c.EndRFID == rfidA.RFIDValue));
|
||||||
|
|
||||||
|
if (connection != null)
|
||||||
{
|
{
|
||||||
return directConnection.Distance;
|
return connection.Distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 직접 연결되지 않은 경우 매우 큰 값 반환
|
|
||||||
return float.MaxValue;
|
return float.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Point> GetNeighbors(Point point)
|
private List<Point> GetNeighbors(Point point)
|
||||||
{
|
{
|
||||||
var neighbors = new List<Point>();
|
var neighbors = new List<Point>();
|
||||||
var rfidLines = GetRFIDLines();
|
var currentRfidPoint = rfidPoints.FirstOrDefault(p => p.Location == point);
|
||||||
|
if (currentRfidPoint == null) return neighbors;
|
||||||
|
|
||||||
// RFID 라인에서 이웃 노드 찾기
|
uint currentRfid = currentRfidPoint.RFIDValue;
|
||||||
foreach (var line in rfidLines)
|
|
||||||
|
foreach (var connection in rfidConnections)
|
||||||
{
|
{
|
||||||
if (line.StartPoint == point)
|
uint neighborRfidVal = 0;
|
||||||
|
if (connection.StartRFID == currentRfid)
|
||||||
{
|
{
|
||||||
neighbors.Add(line.EndPoint);
|
neighborRfidVal = connection.EndRFID;
|
||||||
}
|
}
|
||||||
else if (line.EndPoint == point)
|
else if (connection.EndRFID == currentRfid && connection.IsBidirectional)
|
||||||
{
|
{
|
||||||
// 양방향 연결인 경우에만 시작점을 이웃으로 추가
|
neighborRfidVal = connection.StartRFID;
|
||||||
neighbors.Add(line.StartPoint);
|
}
|
||||||
|
|
||||||
|
if (neighborRfidVal != 0)
|
||||||
|
{
|
||||||
|
var neighborRfidPoint = rfidPoints.FirstOrDefault(p => p.RFIDValue == neighborRfidVal);
|
||||||
|
if (neighborRfidPoint != null)
|
||||||
|
{
|
||||||
|
neighbors.Add(neighborRfidPoint.Location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1227,6 +1282,7 @@ namespace AGVControl
|
|||||||
|
|
||||||
DrawPath(e.Graphics);
|
DrawPath(e.Graphics);
|
||||||
DrawAGV(e.Graphics);
|
DrawAGV(e.Graphics);
|
||||||
|
DrawAGVMotor(e.Graphics);
|
||||||
DrawTargetFlag(e.Graphics); // 목적지 깃발 그리기 추가
|
DrawTargetFlag(e.Graphics); // 목적지 깃발 그리기 추가
|
||||||
|
|
||||||
// 선택된 개체 강조 표시
|
// 선택된 개체 강조 표시
|
||||||
@@ -1267,6 +1323,16 @@ namespace AGVControl
|
|||||||
// 툴바 버튼 그리기
|
// 툴바 버튼 그리기
|
||||||
foreach (var item in this.toolbarRects)
|
foreach (var item in this.toolbarRects)
|
||||||
DrawToolbarButton(e.Graphics, item.Bounds, item.Title, item.isHovering);
|
DrawToolbarButton(e.Graphics, item.Bounds, item.Title, item.isHovering);
|
||||||
|
|
||||||
|
//예측값디스플레잉(임시)
|
||||||
|
|
||||||
|
if (PredictResult != null)
|
||||||
|
{
|
||||||
|
var str = $"{PredictResult.ReasonCode}|{PredictResult.MoveState}|{PredictResult.Direction}|Next:{PredictResult.NextRFID}";
|
||||||
|
var strsize = e.Graphics.MeasureString(str, this.Font);
|
||||||
|
e.Graphics.DrawString(str, this.Font, Brushes.Red, this.Right - strsize.Width - 10, this.Bottom - strsize.Height - 10);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRFIDPoints(Graphics g)
|
private void DrawRFIDPoints(Graphics g)
|
||||||
@@ -1336,84 +1402,212 @@ namespace AGVControl
|
|||||||
agv.CurrentPosition.Y - halfsize,
|
agv.CurrentPosition.Y - halfsize,
|
||||||
agvsize, agvsize);
|
agvsize, agvsize);
|
||||||
|
|
||||||
// AGV 몸체 회전 각도 계산
|
if (agv.BodyAngle.HasValue)
|
||||||
float bodyRotation = 0f;
|
|
||||||
if (agv.CurrentPath != null && agv.CurrentPath.Count > 1)
|
|
||||||
{
|
{
|
||||||
// 현재 위치에서 다음 목적지 방향 계산
|
// --- BodyAngle이 결정된 경우: AGV를 회전시켜 그림 ---
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 그래픽스 상태 저장
|
|
||||||
var originalTransform = g.Transform;
|
var originalTransform = g.Transform;
|
||||||
|
|
||||||
// AGV 위치로 이동하고 회전
|
|
||||||
g.TranslateTransform(agv.CurrentPosition.X, agv.CurrentPosition.Y);
|
g.TranslateTransform(agv.CurrentPosition.X, agv.CurrentPosition.Y);
|
||||||
g.RotateTransform(bodyRotation);
|
g.RotateTransform(agv.BodyAngle.Value + 90); // 리프트가 위쪽(0, -1)을 기본으로 하므로 90도 보정
|
||||||
|
|
||||||
// 원 그리기 (회전된 상태)
|
// 원 그리기 (회전된 좌표계 기준)
|
||||||
Color bgcolor = agv.BatteryLevel > 80 ? Color.Lime : (agv.BatteryLevel > 60 ? Color.Gold : Color.Tomato);
|
Color bgcolor = agv.BatteryLevel > 80 ? Color.Lime : (agv.BatteryLevel > 60 ? Color.Gold : Color.Tomato);
|
||||||
using (var circleBrush = new SolidBrush(Color.FromArgb(150, bgcolor)))
|
using (var circleBrush = new SolidBrush(Color.FromArgb(150, bgcolor)))
|
||||||
g.FillEllipse(circleBrush, -halfsize, -halfsize, agvsize, agvsize);
|
g.FillEllipse(circleBrush, -halfsize, -halfsize, agvsize, agvsize);
|
||||||
using (var circlePen = new Pen(Color.Black, 2))
|
using (var circlePen = new Pen(Color.Black, 2))
|
||||||
g.DrawEllipse(circlePen, -halfsize, -halfsize, agvsize, agvsize);
|
g.DrawEllipse(circlePen, -halfsize, -halfsize, agvsize, agvsize);
|
||||||
|
|
||||||
// 리프트 그리기 (회전된 상태, 항상 전진 방향쪽)
|
// 리프트 그리기 (회전된 좌표계 기준)
|
||||||
var liftWidth = circleRect.Width;
|
var liftWidth = circleRect.Width;
|
||||||
var liftHeight = (int)(circleRect.Height * 0.4);
|
var liftHeight = (int)(circleRect.Height * 0.4);
|
||||||
var liftOffset = halfsize + 1;
|
var liftOffset = halfsize + 1;
|
||||||
|
var liftRect = new Rectangle(-liftWidth / 2, -halfsize - liftOffset, liftWidth, liftHeight);
|
||||||
var liftRect = new Rectangle(
|
|
||||||
-liftWidth / 2,
|
|
||||||
-halfsize - liftOffset,
|
|
||||||
liftWidth, liftHeight);
|
|
||||||
|
|
||||||
using (var liftBrush = new SolidBrush(Color.FromArgb(200, Color.DarkGray)))
|
using (var liftBrush = new SolidBrush(Color.FromArgb(200, Color.DarkGray)))
|
||||||
g.FillRectangle(liftBrush, liftRect);
|
g.FillRectangle(liftBrush, liftRect);
|
||||||
using (var liftPen = new Pen(Color.Black, 1))
|
using (var liftPen = new Pen(Color.Black, 1))
|
||||||
g.DrawRectangle(liftPen, liftRect);
|
g.DrawRectangle(liftPen, liftRect);
|
||||||
|
|
||||||
// 리프트 연결선 그리기
|
|
||||||
using (var connectionPen = new Pen(Color.Black, 2))
|
using (var connectionPen = new Pen(Color.Black, 2))
|
||||||
{
|
|
||||||
g.DrawLine(connectionPen, 0, -halfsize, 0, -halfsize - liftOffset);
|
g.DrawLine(connectionPen, 0, -halfsize, 0, -halfsize - liftOffset);
|
||||||
|
|
||||||
|
g.Transform = originalTransform; // 그래픽스 상태 복원
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// --- BodyAngle이 결정되지 않은 경우: 기본 방향으로 그림 ---
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 그래픽스 상태 복원
|
// 과거 이동 경로 화살표 그리기
|
||||||
g.Transform = originalTransform;
|
DrawMovementHistoryArrows(g);
|
||||||
|
}
|
||||||
|
|
||||||
// 삼각형 화살표 그리기 (현재 이동 방향, 회전하지 않음)
|
private void DrawAGVMotor(Graphics g)
|
||||||
|
{
|
||||||
|
var agvsize = 30;
|
||||||
|
var halfsize = (int)(agvsize / 2);
|
||||||
|
|
||||||
|
// AGV의 모터 각도를 가져옴
|
||||||
|
float motorAngle = agv.MotorAngle;
|
||||||
|
|
||||||
|
var gState = g.Save();
|
||||||
|
g.TranslateTransform(agv.CurrentPosition.X, agv.CurrentPosition.Y);
|
||||||
|
g.RotateTransform(motorAngle + 90); // 삼각형이 위쪽(Y축 음수)을 향하도록 90도 보정
|
||||||
|
|
||||||
|
// 삼각형 포인트 계산 (회전 중심점 0,0 기준)
|
||||||
Point[] trianglePoints = new Point[3];
|
Point[] trianglePoints = new Point[3];
|
||||||
var arrowSize = halfsize - 5;
|
var arrowSize = halfsize - 5;
|
||||||
|
trianglePoints[0] = new Point(0, -arrowSize); // 꼭짓점
|
||||||
|
trianglePoints[1] = new Point(-arrowSize, arrowSize); // 왼쪽 아래
|
||||||
|
trianglePoints[2] = new Point(arrowSize, arrowSize); // 오른쪽 아래
|
||||||
|
|
||||||
// AGV의 현재 이동 방향에 따라 삼각형 포인트 계산
|
// 삼각형 그리기
|
||||||
switch (agv.CurrentDirection)
|
|
||||||
{
|
|
||||||
case Direction.Forward:
|
|
||||||
trianglePoints[0] = new Point(agv.CurrentPosition.X, agv.CurrentPosition.Y - arrowSize);
|
|
||||||
trianglePoints[1] = new Point(agv.CurrentPosition.X - arrowSize, agv.CurrentPosition.Y + arrowSize);
|
|
||||||
trianglePoints[2] = new Point(agv.CurrentPosition.X + arrowSize, agv.CurrentPosition.Y + arrowSize);
|
|
||||||
break;
|
|
||||||
case Direction.Backward:
|
|
||||||
trianglePoints[0] = new Point(agv.CurrentPosition.X, agv.CurrentPosition.Y + arrowSize);
|
|
||||||
trianglePoints[1] = new Point(agv.CurrentPosition.X - arrowSize, agv.CurrentPosition.Y - arrowSize);
|
|
||||||
trianglePoints[2] = new Point(agv.CurrentPosition.X + arrowSize, agv.CurrentPosition.Y - arrowSize);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var arrowBrush = new SolidBrush(Color.FromArgb(200, Color.White)))
|
using (var arrowBrush = new SolidBrush(Color.FromArgb(200, Color.White)))
|
||||||
g.FillPolygon(arrowBrush, trianglePoints);
|
g.FillPolygon(arrowBrush, trianglePoints);
|
||||||
using (var arrowPen = new Pen(Color.Black, 2))
|
using (var arrowPen = new Pen(Color.Black, 2))
|
||||||
g.DrawPolygon(arrowPen, trianglePoints);
|
g.DrawPolygon(arrowPen, trianglePoints);
|
||||||
|
|
||||||
//g.DrawImage(Properties.Resources.ico_navi_40, circleRect);
|
g.Restore(gState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 과거 이동 경로를 화살표로 표시
|
||||||
|
private void DrawMovementHistoryArrows(Graphics g)
|
||||||
|
{
|
||||||
|
if (agv.MovementHistory.Count < 2 || agv.PositionHistory.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 최근 3개의 이동 경로 표시 (가장 오래된 것부터)
|
||||||
|
int startIndex = Math.Max(0, agv.MovementHistory.Count - 3);
|
||||||
|
|
||||||
|
for (int i = startIndex; i < agv.MovementHistory.Count - 1; i++)
|
||||||
|
{
|
||||||
|
var startRFID = agv.MovementHistory[i];
|
||||||
|
var endRFID = agv.MovementHistory[i + 1];
|
||||||
|
var startPos = agv.PositionHistory[i];
|
||||||
|
var endPos = agv.PositionHistory[i + 1];
|
||||||
|
|
||||||
|
// 시간에 따른 투명도 계산
|
||||||
|
int age = agv.MovementHistory.Count - 1 - i;
|
||||||
|
int alpha = Math.Max(50, 255 - (age * 50));
|
||||||
|
|
||||||
|
var directConnection = rfidConnections.FirstOrDefault(c =>
|
||||||
|
(c.StartRFID == startRFID.rfid && c.EndRFID == endRFID.rfid) ||
|
||||||
|
(c.IsBidirectional && c.StartRFID == endRFID.rfid && c.EndRFID == startRFID.rfid));
|
||||||
|
|
||||||
|
if (directConnection != null)
|
||||||
|
{
|
||||||
|
// 직접 연결된 경우: 실선 화살표
|
||||||
|
Color arrowColor = (directConnection.StartRFID == startRFID.rfid) ? Color.Green : Color.Red;
|
||||||
|
arrowColor = Color.FromArgb(alpha, arrowColor);
|
||||||
|
DrawArrow(g, startPos, endPos, arrowColor, 3);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 직접 연결되지 않은 경우: 경로 탐색 후 점선 화살표 체인
|
||||||
|
var pathResult = CalculatePath(startRFID.rfid, endRFID.rfid);
|
||||||
|
if (pathResult.Success && pathResult.Path != null && pathResult.Path.Count > 1)
|
||||||
|
{
|
||||||
|
// 경로의 첫 단계 방향으로 전체 색상 결정
|
||||||
|
Color arrowColor = Color.Gray;
|
||||||
|
var firstStepEndPoint = pathResult.Path[1];
|
||||||
|
var firstStepEndRfidPoint = rfidPoints.FirstOrDefault(p => p.Location == firstStepEndPoint);
|
||||||
|
if (firstStepEndRfidPoint != null)
|
||||||
|
{
|
||||||
|
var firstStepConnection = rfidConnections.FirstOrDefault(c =>
|
||||||
|
(c.StartRFID == startRFID.rfid && c.EndRFID == firstStepEndRfidPoint.RFIDValue) ||
|
||||||
|
(c.IsBidirectional && c.StartRFID == firstStepEndRfidPoint.RFIDValue && c.EndRFID == startRFID.rfid));
|
||||||
|
|
||||||
|
if (firstStepConnection != null)
|
||||||
|
{
|
||||||
|
arrowColor = (firstStepConnection.StartRFID == startRFID.rfid) ? Color.Green : Color.Red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arrowColor = Color.FromArgb(alpha, arrowColor);
|
||||||
|
|
||||||
|
// 경로의 각 세그먼트를 점선 화살표로 그리기
|
||||||
|
for (int j = 0; j < pathResult.Path.Count - 1; j++)
|
||||||
|
{
|
||||||
|
Point segmentStart = pathResult.Path[j];
|
||||||
|
Point segmentEnd = pathResult.Path[j + 1];
|
||||||
|
DrawDashedArrow(g, segmentStart, segmentEnd, arrowColor, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 점선 화살표 그리기 헬퍼 메서드
|
||||||
|
private void DrawDashedArrow(Graphics g, Point start, Point end, Color color, int width)
|
||||||
|
{
|
||||||
|
using (var pen = new Pen(color, width))
|
||||||
|
{
|
||||||
|
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
|
||||||
|
|
||||||
|
// 선 그리기
|
||||||
|
g.DrawLine(pen, start, end);
|
||||||
|
|
||||||
|
// 화살표 머리 그리기 (실선으로)
|
||||||
|
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
|
||||||
|
var arrowSize = 8;
|
||||||
|
var angle = Math.Atan2(end.Y - start.Y, end.X - start.X);
|
||||||
|
var arrowAngle = Math.PI / 6; // 30도
|
||||||
|
|
||||||
|
// 화살표 끝점에서 약간 뒤로 이동
|
||||||
|
var arrowStart = new PointF(
|
||||||
|
end.X - (float)(arrowSize * Math.Cos(angle)),
|
||||||
|
end.Y - (float)(arrowSize * Math.Sin(angle))
|
||||||
|
);
|
||||||
|
|
||||||
|
// 화살표 날개 그리기
|
||||||
|
var arrow1 = new PointF(
|
||||||
|
arrowStart.X - (float)(arrowSize * Math.Cos(angle - arrowAngle)),
|
||||||
|
arrowStart.Y - (float)(arrowSize * Math.Sin(angle - arrowAngle))
|
||||||
|
);
|
||||||
|
var arrow2 = new PointF(
|
||||||
|
arrowStart.X - (float)(arrowSize * Math.Cos(angle + arrowAngle)),
|
||||||
|
arrowStart.Y - (float)(arrowSize * Math.Sin(angle + arrowAngle))
|
||||||
|
);
|
||||||
|
|
||||||
|
g.DrawLine(pen, arrowStart, arrow1);
|
||||||
|
g.DrawLine(pen, arrowStart, arrow2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실선 화살표 그리기 헬퍼 메서드
|
||||||
|
private void DrawArrow(Graphics g, Point start, Point end, Color color, int width)
|
||||||
|
{
|
||||||
|
using (var pen = new Pen(color, width))
|
||||||
|
{
|
||||||
|
// 선 그리기
|
||||||
|
g.DrawLine(pen, start, end);
|
||||||
|
|
||||||
|
// 화살표 머리 그리기
|
||||||
|
var arrowSize = 8;
|
||||||
|
var angle = Math.Atan2(end.Y - start.Y, end.X - start.X);
|
||||||
|
var arrowAngle = Math.PI / 6; // 30도
|
||||||
|
|
||||||
|
// 화살표 끝점에서 약간 뒤로 이동
|
||||||
|
var arrowStart = new PointF(
|
||||||
|
end.X - (float)(arrowSize * Math.Cos(angle)),
|
||||||
|
end.Y - (float)(arrowSize * Math.Sin(angle))
|
||||||
|
);
|
||||||
|
|
||||||
|
// 화살표 날개 그리기
|
||||||
|
var arrow1 = new PointF(
|
||||||
|
arrowStart.X - (float)(arrowSize * Math.Cos(angle - arrowAngle)),
|
||||||
|
arrowStart.Y - (float)(arrowSize * Math.Sin(angle - arrowAngle))
|
||||||
|
);
|
||||||
|
var arrow2 = new PointF(
|
||||||
|
arrowStart.X - (float)(arrowSize * Math.Cos(angle + arrowAngle)),
|
||||||
|
arrowStart.Y - (float)(arrowSize * Math.Sin(angle + arrowAngle))
|
||||||
|
);
|
||||||
|
|
||||||
|
g.DrawLine(pen, arrowStart, arrow1);
|
||||||
|
g.DrawLine(pen, arrowStart, arrow2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCustomLines(Graphics g)
|
private void DrawCustomLines(Graphics g)
|
||||||
@@ -2016,13 +2210,14 @@ namespace AGVControl
|
|||||||
return agv.CurrentDirection;
|
return agv.CurrentDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AGVActionPrediction PredictResult = null;
|
||||||
// AGV 행동 예측 함수
|
// AGV 행동 예측 함수
|
||||||
public AGVActionPrediction PredictNextAction()
|
public AGVActionPrediction PredictNextAction()
|
||||||
{
|
{
|
||||||
// 1. 위치를 모를 때 (CurrentRFID가 0 또는 미설정)
|
// 1. 위치를 모를 때 (CurrentRFID가 0 또는 미설정)
|
||||||
if (agv.CurrentRFID == 0)
|
if (agv.CurrentRFID == 0)
|
||||||
{
|
{
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = Direction.Backward,
|
Direction = Direction.Backward,
|
||||||
NextRFID = null,
|
NextRFID = null,
|
||||||
@@ -2030,12 +2225,13 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.NoPosition,
|
ReasonCode = AGVActionReasonCode.NoPosition,
|
||||||
MoveState = AGVMoveState.Run
|
MoveState = AGVMoveState.Run
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 경로가 없거나 현재 위치가 경로에 없음
|
// 2. 경로가 없거나 현재 위치가 경로에 없음
|
||||||
if (agv.CurrentPath == null || agv.CurrentPath.Count < 2 || agv.CurrentPosition == Point.Empty)
|
if (agv.CurrentPath == null || agv.CurrentPath.Count < 2 || agv.CurrentPosition == Point.Empty)
|
||||||
{
|
{
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = agv.CurrentDirection,
|
Direction = agv.CurrentDirection,
|
||||||
NextRFID = null,
|
NextRFID = null,
|
||||||
@@ -2043,13 +2239,14 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.NoPath,
|
ReasonCode = AGVActionReasonCode.NoPath,
|
||||||
MoveState = AGVMoveState.Stop
|
MoveState = AGVMoveState.Stop
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 경로상에서 다음 RFID 예측
|
// 3. 경로상에서 다음 RFID 예측
|
||||||
int idx = agv.CurrentPath.FindIndex(p => p == agv.CurrentPosition);
|
int idx = agv.CurrentPath.FindIndex(p => p == agv.CurrentPosition);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = agv.CurrentDirection,
|
Direction = agv.CurrentDirection,
|
||||||
NextRFID = null,
|
NextRFID = null,
|
||||||
@@ -2057,6 +2254,7 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.NotOnPath,
|
ReasonCode = AGVActionReasonCode.NotOnPath,
|
||||||
MoveState = AGVMoveState.Stop
|
MoveState = AGVMoveState.Stop
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 목적지 도달 전, 방향 미리 판단 및 회전 위치 예측
|
// 4. 목적지 도달 전, 방향 미리 판단 및 회전 위치 예측
|
||||||
@@ -2091,7 +2289,7 @@ namespace AGVControl
|
|||||||
if (idx == lastRotatableIdx)
|
if (idx == lastRotatableIdx)
|
||||||
{
|
{
|
||||||
var rfid = rfidPoints.FirstOrDefault(r => r.Location == agv.CurrentPath[lastRotatableIdx]);
|
var rfid = rfidPoints.FirstOrDefault(r => r.Location == agv.CurrentPath[lastRotatableIdx]);
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = agv.CurrentDirection,
|
Direction = agv.CurrentDirection,
|
||||||
NextRFID = rfid?.RFIDValue,
|
NextRFID = rfid?.RFIDValue,
|
||||||
@@ -2099,6 +2297,7 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.NeedTurn,
|
ReasonCode = AGVActionReasonCode.NeedTurn,
|
||||||
MoveState = AGVMoveState.Stop
|
MoveState = AGVMoveState.Stop
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
else if (idx < lastRotatableIdx)
|
else if (idx < lastRotatableIdx)
|
||||||
{
|
{
|
||||||
@@ -2109,7 +2308,7 @@ namespace AGVControl
|
|||||||
Direction moveDir = (Math.Abs(moveDeltaX) > Math.Abs(moveDeltaY)) ?
|
Direction moveDir = (Math.Abs(moveDeltaX) > Math.Abs(moveDeltaY)) ?
|
||||||
(moveDeltaX > 0 ? Direction.Forward : Direction.Backward) :
|
(moveDeltaX > 0 ? Direction.Forward : Direction.Backward) :
|
||||||
(moveDeltaY > 0 ? Direction.Forward : Direction.Backward);
|
(moveDeltaY > 0 ? Direction.Forward : Direction.Backward);
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = moveDir,
|
Direction = moveDir,
|
||||||
NextRFID = rfid?.RFIDValue,
|
NextRFID = rfid?.RFIDValue,
|
||||||
@@ -2117,10 +2316,11 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.Normal,
|
ReasonCode = AGVActionReasonCode.Normal,
|
||||||
MoveState = AGVMoveState.Run
|
MoveState = AGVMoveState.Run
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 회전 가능한 위치가 없음 (STOP)
|
// 회전 가능한 위치가 없음 (STOP)
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = agv.CurrentDirection,
|
Direction = agv.CurrentDirection,
|
||||||
NextRFID = null,
|
NextRFID = null,
|
||||||
@@ -2128,6 +2328,7 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.NoTurnPoint,
|
ReasonCode = AGVActionReasonCode.NoTurnPoint,
|
||||||
MoveState = AGVMoveState.Stop
|
MoveState = AGVMoveState.Stop
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2135,7 +2336,7 @@ namespace AGVControl
|
|||||||
// 5. 목적지 도달 시(방향이 맞는 경우) (STOP)
|
// 5. 목적지 도달 시(방향이 맞는 경우) (STOP)
|
||||||
if (idx == agv.CurrentPath.Count - 1)
|
if (idx == agv.CurrentPath.Count - 1)
|
||||||
{
|
{
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = agv.CurrentDirection,
|
Direction = agv.CurrentDirection,
|
||||||
NextRFID = null,
|
NextRFID = null,
|
||||||
@@ -2143,6 +2344,7 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.Arrived,
|
ReasonCode = AGVActionReasonCode.Arrived,
|
||||||
MoveState = AGVMoveState.Stop
|
MoveState = AGVMoveState.Stop
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 일반 경로 주행 (RUN)
|
// 6. 일반 경로 주행 (RUN)
|
||||||
@@ -2156,7 +2358,7 @@ namespace AGVControl
|
|||||||
(deltaX > 0 ? Direction.Forward : Direction.Backward) :
|
(deltaX > 0 ? Direction.Forward : Direction.Backward) :
|
||||||
(deltaY > 0 ? Direction.Forward : Direction.Backward);
|
(deltaY > 0 ? Direction.Forward : Direction.Backward);
|
||||||
|
|
||||||
return new AGVActionPrediction
|
PredictResult = new AGVActionPrediction
|
||||||
{
|
{
|
||||||
Direction = nextDir,
|
Direction = nextDir,
|
||||||
NextRFID = nextRFID,
|
NextRFID = nextRFID,
|
||||||
@@ -2164,6 +2366,7 @@ namespace AGVControl
|
|||||||
ReasonCode = AGVActionReasonCode.Normal,
|
ReasonCode = AGVActionReasonCode.Normal,
|
||||||
MoveState = AGVMoveState.Run
|
MoveState = AGVMoveState.Run
|
||||||
};
|
};
|
||||||
|
return PredictResult;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,19 @@ namespace AGVControl.Models
|
|||||||
public enum Direction
|
public enum Direction
|
||||||
{
|
{
|
||||||
Forward = 0,
|
Forward = 0,
|
||||||
Backward = 1
|
Backward = 1,
|
||||||
|
Stop = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct movehistorydata
|
||||||
|
{
|
||||||
|
public UInt16 rfid { get; set; }
|
||||||
|
public Direction direction { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"RFID:{rfid},DIR:{direction}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AGV
|
public class AGV
|
||||||
@@ -24,17 +36,18 @@ namespace AGVControl.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 현재위치가 수산되면 목적지까지의 방향값이 계산됩니다.
|
/// 현재위치가 수산되면 목적지까지의 방향값이 계산됩니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Direction TargetDirection { get; set; }
|
public Direction TargetDirection { get; set; } = Direction.Stop;
|
||||||
public bool IsMoving { get; set; }
|
public bool IsMoving { get; set; }
|
||||||
public List<Point> CurrentPath { get; set; } = new List<Point>();
|
public List<Point> CurrentPath { get; set; } = new List<Point>();
|
||||||
public List<Point> PlannedPath { get; set; }
|
public List<Point> PlannedPath { get; set; }
|
||||||
public List<string> PathRFIDs { get; set; }
|
public List<string> PathRFIDs { get; set; }
|
||||||
public Point TargetPosition { get; set; }
|
public Point TargetPosition { get; set; }
|
||||||
public uint TargetRFID { get; set; }
|
public uint TargetRFID { get; set; }
|
||||||
|
public float? BodyAngle { get; set; } = null;
|
||||||
|
public float MotorAngle { get; set; } = 0f;
|
||||||
// 이동 경로 기록을 위한 새로운 속성들
|
// 이동 경로 기록을 위한 새로운 속성들
|
||||||
public List<uint> MovementHistory { get; set; } = new List<uint>();
|
public List<movehistorydata> MovementHistory { get; } = new List<movehistorydata>();
|
||||||
public List<Point> PositionHistory { get; set; } = new List<Point>();
|
public List<Point> PositionHistory { get; } = new List<Point>();
|
||||||
public const int HISTORY_SIZE = 4; // 최근 4개 위치 기록
|
public const int HISTORY_SIZE = 4; // 최근 4개 위치 기록
|
||||||
|
|
||||||
public AGV()
|
public AGV()
|
||||||
@@ -46,6 +59,7 @@ namespace AGVControl.Models
|
|||||||
TargetPosition = Point.Empty;
|
TargetPosition = Point.Empty;
|
||||||
TargetRFID = 0;
|
TargetRFID = 0;
|
||||||
TargetDirection = Direction.Forward;
|
TargetDirection = Direction.Forward;
|
||||||
|
BodyAngle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Move()
|
public void Move()
|
||||||
@@ -58,13 +72,13 @@ namespace AGVControl.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 이동 경로에 새로운 RFID 추가
|
// 이동 경로에 새로운 RFID 추가
|
||||||
public void AddToMovementHistory(uint rfidValue, Point position)
|
public void AddToMovementHistory(UInt16 rfidValue, Point position, Direction direction)
|
||||||
{
|
{
|
||||||
// 중복 RFID가 연속으로 들어오는 경우 무시
|
// 중복 RFID가 연속으로 들어오는 경우 무시
|
||||||
if (MovementHistory.Count > 0 && MovementHistory[MovementHistory.Count - 1] == rfidValue)
|
if (MovementHistory.Count > 0 && MovementHistory.Last().rfid == rfidValue)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MovementHistory.Add(rfidValue);
|
MovementHistory.Add(new movehistorydata { rfid = rfidValue, direction = direction }) ;
|
||||||
PositionHistory.Add(position);
|
PositionHistory.Add(position);
|
||||||
|
|
||||||
// 기록 크기 제한
|
// 기록 크기 제한
|
||||||
@@ -73,6 +87,14 @@ namespace AGVControl.Models
|
|||||||
MovementHistory.RemoveAt(0);
|
MovementHistory.RemoveAt(0);
|
||||||
PositionHistory.RemoveAt(0);
|
PositionHistory.RemoveAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//최초방향과 마지막 방향이 일치하지 않으면 그 이전의 데이터는 삭제한다.
|
||||||
|
if(MovementHistory.Count > 2 && MovementHistory.First().direction != MovementHistory.Last().direction)
|
||||||
|
{
|
||||||
|
var lastTwo = MovementHistory.Skip(MovementHistory.Count - 2).Take(2).ToArray(); // [9, 10]
|
||||||
|
MovementHistory.Clear();
|
||||||
|
MovementHistory.AddRange(lastTwo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 연결 정보 기반 실제 이동 방향 계산
|
// 연결 정보 기반 실제 이동 방향 계산
|
||||||
@@ -107,23 +129,30 @@ namespace AGVControl.Models
|
|||||||
return true; // 검증 불가능한 경우
|
return true; // 검증 불가능한 경우
|
||||||
|
|
||||||
// 최근 두 RFID 값 가져오기
|
// 최근 두 RFID 값 가져오기
|
||||||
var recentRFIDs = MovementHistory.Skip(Math.Max(0, MovementHistory.Count - 2)).Take(2).ToList();
|
var recentRFIDs = MovementHistory.Skip( MovementHistory.Count - 2).Take(2).ToList();
|
||||||
if (recentRFIDs.Count < 2)
|
if (recentRFIDs.Count < 2)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var previousRFID = recentRFIDs[0];
|
var previousRFID = recentRFIDs[0];
|
||||||
var currentRFID = recentRFIDs[1];
|
var currentRFID = recentRFIDs[1];
|
||||||
|
|
||||||
var actualDirection = CalculateActualDirectionByConnection(currentRFID, previousRFID, connections);
|
var actualDirection = CalculateActualDirectionByConnection(currentRFID.rfid, previousRFID.rfid, connections);
|
||||||
if (!actualDirection.HasValue)
|
if (!actualDirection.HasValue)
|
||||||
return true; // 연결 정보로 방향 판단 불가
|
return true; // 연결 정보로 방향 판단 불가
|
||||||
|
|
||||||
// 방향이 일치하지 않는 경우
|
// 방향이 일치하지 않는 경우
|
||||||
if (actualDirection.Value != expectedDirection)
|
if (actualDirection.Value != expectedDirection)
|
||||||
{
|
{
|
||||||
// AGV 방향을 실제 이동 방향으로 정정
|
// AGV 모터 방향을 실제 이동 방향으로 정정
|
||||||
CurrentDirection = actualDirection.Value;
|
CurrentDirection = actualDirection.Value;
|
||||||
TargetDirection = actualDirection.Value;
|
TargetDirection = actualDirection.Value;
|
||||||
|
|
||||||
|
// 몸체 방향도 180도 회전 (결정된 경우에만)
|
||||||
|
if (BodyAngle.HasValue)
|
||||||
|
{
|
||||||
|
BodyAngle = (BodyAngle.Value + 180) % 360;
|
||||||
|
}
|
||||||
|
|
||||||
return false; // 정정됨을 알림
|
return false; // 정정됨을 알림
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,11 +174,11 @@ namespace AGVControl.Models
|
|||||||
var currentRFID = recentRFIDs[1];
|
var currentRFID = recentRFIDs[1];
|
||||||
|
|
||||||
// RFID 값의 증가/감소로 방향 판단
|
// RFID 값의 증가/감소로 방향 판단
|
||||||
if (currentRFID > prevRFID)
|
if (currentRFID.rfid > prevRFID.rfid)
|
||||||
{
|
{
|
||||||
return Direction.Forward; // RFID 값이 증가하면 전진
|
return Direction.Forward; // RFID 값이 증가하면 전진
|
||||||
}
|
}
|
||||||
else if (currentRFID < prevRFID)
|
else if (currentRFID.rfid < prevRFID.rfid)
|
||||||
{
|
{
|
||||||
return Direction.Backward; // RFID 값이 감소하면 후진
|
return Direction.Backward; // RFID 값이 감소하면 후진
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user