diff --git a/Cs_HMI/Project/StateMachine/_AGV.cs b/Cs_HMI/Project/StateMachine/_AGV.cs index 14f8f0b..440bab7 100644 --- a/Cs_HMI/Project/StateMachine/_AGV.cs +++ b/Cs_HMI/Project/StateMachine/_AGV.cs @@ -35,58 +35,58 @@ namespace Project } } - ePosition ParsePosition(uint tag, out string dirBW) - { - var tagstr = tag.ToString(); - var tagno = tagstr.Substring(0, tagstr.Length - 1); + //ePosition ParsePosition(uint tag, out string dirBW) + //{ + // var tagstr = tag.ToString(); + // var tagno = tagstr.Substring(0, tagstr.Length - 1); - if (tag == PUB.setting.TAGNOT) { dirBW = "0"; return ePosition.NOT; } + // if (tag == PUB.setting.TAGNOT) { dirBW = "0"; return ePosition.NOT; } - else if (tag == PUB.setting.TAG_QA_QC) { dirBW = "0"; return ePosition.QA_QC; } - else if (tag == PUB.setting.TAG_QC_F1) { dirBW = "0"; return ePosition.QC_F1; } - else if (tag == PUB.setting.TAG_F1_F2) { dirBW = "0"; return ePosition.F1_F2; } - else if (tag == PUB.setting.TAG_F2_F3) { dirBW = "0"; return ePosition.F2_F3; } - else if (tag == PUB.setting.TAG_F3_F4) { dirBW = "0"; return ePosition.F3_F4; } - else if (tag == PUB.setting.TAG_F4_F5) { dirBW = "0"; return ePosition.F4_F5; } + // else if (tag == PUB.setting.TAG_QA_QC) { dirBW = "0"; return ePosition.QA_QC; } + // else if (tag == PUB.setting.TAG_QC_F1) { dirBW = "0"; return ePosition.QC_F1; } + // else if (tag == PUB.setting.TAG_F1_F2) { dirBW = "0"; return ePosition.F1_F2; } + // else if (tag == PUB.setting.TAG_F2_F3) { dirBW = "0"; return ePosition.F2_F3; } + // else if (tag == PUB.setting.TAG_F3_F4) { dirBW = "0"; return ePosition.F3_F4; } + // else if (tag == PUB.setting.TAG_F4_F5) { dirBW = "0"; return ePosition.F4_F5; } - else if (tag == PUB.setting.TAGQAB) { dirBW = "0"; return ePosition.QA; } - else if (tag == PUB.setting.TAGQCB) { dirBW = "0"; return ePosition.QC; } - else if (tag == PUB.setting.TAGF1B) { dirBW = "0"; return ePosition.F1; } - else if (tag == PUB.setting.TAGF2B) { dirBW = "0"; return ePosition.F2; } - else if (tag == PUB.setting.TAGF3B) { dirBW = "0"; return ePosition.F3; } - else if (tag == PUB.setting.TAGF4B) { dirBW = "0"; return ePosition.F4; } - else if (tag == PUB.setting.TAGF5B) { dirBW = "0"; return ePosition.F5; } - //else if (tag == PUB.setting.TAGQAA) { dirBW = "1"; return ePosition.QA; } - //else if (tag == PUB.setting.TAGCHA) { dirBW = "1"; return ePosition.CHARGE; } - else if (tag == PUB.setting.TAGQAA) { dirBW = "1"; return ePosition.QA; } - else if (tag == PUB.setting.TAGQCA) { dirBW = "1"; return ePosition.QC; } - else if (tag == PUB.setting.TAGF1A) { dirBW = "1"; return ePosition.F1; } - else if (tag == PUB.setting.TAGF2A) { dirBW = "1"; return ePosition.F2; } - else if (tag == PUB.setting.TAGF3A) { dirBW = "1"; return ePosition.F3; } - else if (tag == PUB.setting.TAGF4A) { dirBW = "1"; return ePosition.F4; } - else if (tag == PUB.setting.TAGF5A) { dirBW = "1"; return ePosition.F5; } - else if (tag == PUB.setting.TAGPOT) { dirBW = "0"; return ePosition.POT; } - else - { - if (tag > 9350 && tag < 9400) - { - dirBW = "0"; return ePosition.QC_F1; - } - else if (tag > 9250 && tag < 9300) - { - dirBW = "0"; return ePosition.QA_QC; - } - else if (tag > 9000 && tag < 9100) - { - dirBW = "0"; return ePosition.NOT; - } - else - { - dirBW = "0"; return ePosition.NONE; - } + // else if (tag == PUB.setting.TAGQAB) { dirBW = "0"; return ePosition.QA; } + // else if (tag == PUB.setting.TAGQCB) { dirBW = "0"; return ePosition.QC; } + // else if (tag == PUB.setting.TAGF1B) { dirBW = "0"; return ePosition.F1; } + // else if (tag == PUB.setting.TAGF2B) { dirBW = "0"; return ePosition.F2; } + // else if (tag == PUB.setting.TAGF3B) { dirBW = "0"; return ePosition.F3; } + // else if (tag == PUB.setting.TAGF4B) { dirBW = "0"; return ePosition.F4; } + // else if (tag == PUB.setting.TAGF5B) { dirBW = "0"; return ePosition.F5; } + // //else if (tag == PUB.setting.TAGQAA) { dirBW = "1"; return ePosition.QA; } + // //else if (tag == PUB.setting.TAGCHA) { dirBW = "1"; return ePosition.CHARGE; } + // else if (tag == PUB.setting.TAGQAA) { dirBW = "1"; return ePosition.QA; } + // else if (tag == PUB.setting.TAGQCA) { dirBW = "1"; return ePosition.QC; } + // else if (tag == PUB.setting.TAGF1A) { dirBW = "1"; return ePosition.F1; } + // else if (tag == PUB.setting.TAGF2A) { dirBW = "1"; return ePosition.F2; } + // else if (tag == PUB.setting.TAGF3A) { dirBW = "1"; return ePosition.F3; } + // else if (tag == PUB.setting.TAGF4A) { dirBW = "1"; return ePosition.F4; } + // else if (tag == PUB.setting.TAGF5A) { dirBW = "1"; return ePosition.F5; } + // else if (tag == PUB.setting.TAGPOT) { dirBW = "0"; return ePosition.POT; } + // else + // { + // if (tag > 9350 && tag < 9400) + // { + // dirBW = "0"; return ePosition.QC_F1; + // } + // else if (tag > 9250 && tag < 9300) + // { + // dirBW = "0"; return ePosition.QA_QC; + // } + // else if (tag > 9000 && tag < 9100) + // { + // dirBW = "0"; return ePosition.NOT; + // } + // else + // { + // dirBW = "0"; return ePosition.NONE; + // } - } - } + // } + //} bool _charging = false; @@ -196,51 +196,53 @@ namespace Project case arDev.Narumi.DataType.TAG: { //자동 실행 중이다. - PUB.log.Add($"RFID값에서 위치정보를 추출할 수 있도록 해야한다"); + PUB.log.Add($"AGV 태그수신 : {PUB.AGV.data.TagNo}"); PUB.Result.LastTAG = PUB.AGV.data.TagNo.ToString(); - var curpos = ParsePosition(PUB.AGV.data.TagNo, out string dirForward); - if (curpos != PUB.Result.CurrentPos) - { - PUB.log.Add($"현재위치변경 {PUB.Result.CurrentPos} -> {curpos}"); - PUB.Result.CurrentPos = curpos; - } - - PUB.Result.CurrentPosCW = dirForward; - ctlPos1.SetPositionDeActive(); - ctlPos1.SetPosition(PUB.Result.CurrentPos); - ctlPos1.SetDirection(dirForward); - ctlPos1.Invalidate(); - - - - //pot not를 보면 일단 바로 멈추게한다 + //POT/NOT 보면 일단 바로 멈추게한다 if (PUB.Result.CurrentPos == ePosition.POT || PUB.Result.CurrentPos == ePosition.NOT) { - PUB.AGV.AGVMoveStop("TAG:pot/not 발견으로 즉시 멈춤"); + var logEMsg = $"Stop by [POT/NOT]"; + PUB.AGV.AGVMoveStop(logEMsg); + PUB.log.AddE(logEMsg); } - //자동, 상하차 모드일때 RFID 가 타겟위치에 올때는 - 멈춤을 설정해준다 - if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == true && - PUB.Result.CurrentPos == PUB.Result.TargetPos && - PUB.Result.TargetPos != ePosition.NONE && - (PUB.sm.RunStep == ERunStep.GODOWN || - PUB.sm.RunStep == ERunStep.GOUP || - PUB.sm.RunStep == ERunStep.GOHOME || - PUB.sm.RunStep == ERunStep.GOCHARGE)) + //맵데이터에서 현재 위치를 찾는다 + if (PUB.mapctl.SetCurrentPosition(PUB.AGV.data.TagNo) == false) { - if (PUB.AGV.data.Sts == 'F' && dirForward == "0") //아래로 내려오고있음 - { - PUB.AGV.AGVMoveStop("AGV_DataReceive", arDev.Narumi.eStopOpt.MarkStop); - PUB.Speak( Lang.다음마크위치에서정지합니다); - } - else if (PUB.AGV.data.Sts == 'B' && dirForward == "1") - { - //VAR.BOOL[eVarBool.FLAG_NEXTSTOP_MARK] = true; - PUB.AGV.AGVMoveStop("AGV_DataReceive", arDev.Narumi.eStopOpt.MarkStop); - PUB.Speak(Lang.다음마크위치에서정지합니다); - } + if (VAR.BOOL[eVarBool.FLAG_AUTORUN] && PUB.AGV.system1.agv_run) + PUB.AGV.AGVMoveStop("unknown tag no"); + + //존재하지 않는 태그가 읽히면 관련 오류를 표시한다. } + else + { + //위치는 찾았다 해당 위치가 내 목적지라면 mark stop기능으로 전환한다 + + } + + + ////자동, 상하차 모드일때 RFID 가 타겟위치에 올때는 - 멈춤을 설정해준다 + //if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == true && + // PUB.Result.CurrentPos == PUB.Result.TargetPos && + // PUB.Result.TargetPos != ePosition.NONE && + // (PUB.sm.RunStep == ERunStep.GODOWN || + // PUB.sm.RunStep == ERunStep.GOUP || + // PUB.sm.RunStep == ERunStep.GOHOME || + // PUB.sm.RunStep == ERunStep.GOCHARGE)) + //{ + // if (PUB.AGV.data.Sts == 'F' && dirForward == "0") //아래로 내려오고있음 + // { + // PUB.AGV.AGVMoveStop("AGV_DataReceive", arDev.Narumi.eStopOpt.MarkStop); + // PUB.Speak( Lang.다음마크위치에서정지합니다); + // } + // else if (PUB.AGV.data.Sts == 'B' && dirForward == "1") + // { + // //VAR.BOOL[eVarBool.FLAG_NEXTSTOP_MARK] = true; + // PUB.AGV.AGVMoveStop("AGV_DataReceive", arDev.Narumi.eStopOpt.MarkStop); + // PUB.Speak(Lang.다음마크위치에서정지합니다); + // } + //} } break; diff --git a/Cs_HMI/Project/StateMachine/_BMS.cs b/Cs_HMI/Project/StateMachine/_BMS.cs index 1b4ccea..ec85211 100644 --- a/Cs_HMI/Project/StateMachine/_BMS.cs +++ b/Cs_HMI/Project/StateMachine/_BMS.cs @@ -158,7 +158,7 @@ namespace Project private void Bms_BMSDataReceive(object sender, EventArgs e) { - + PUB.mapctl.agv.BatteryLevel = PUB.BMS.Current_Level; if (PUB.BMS.Current_Level <= PUB.setting.ChargeStartLevel) { //배터리 레벨이 기준보다 낮다면 경고를 활성화 한다 diff --git a/Cs_HMI/Project/ViewForm/fAgv.Designer.cs b/Cs_HMI/Project/ViewForm/fAgv.Designer.cs index 77ccf4d..c3715d3 100644 --- a/Cs_HMI/Project/ViewForm/fAgv.Designer.cs +++ b/Cs_HMI/Project/ViewForm/fAgv.Designer.cs @@ -34,8 +34,10 @@ this.richTextBox2 = new System.Windows.Forms.RichTextBox(); this.richTextBox3 = new System.Windows.Forms.RichTextBox(); this.richTextBox4 = new System.Windows.Forms.RichTextBox(); + this.label1 = new System.Windows.Forms.Label(); this.timer1 = new System.Windows.Forms.Timer(this.components); this.panel1 = new System.Windows.Forms.Panel(); + this.lbIP = new System.Windows.Forms.Label(); this.button7 = new System.Windows.Forms.Button(); this.button6 = new System.Windows.Forms.Button(); this.button5 = new System.Windows.Forms.Button(); @@ -44,7 +46,6 @@ this.button1 = new System.Windows.Forms.Button(); this.button8 = new System.Windows.Forms.Button(); this.button4 = new System.Windows.Forms.Button(); - this.label1 = new System.Windows.Forms.Label(); this.tableLayoutPanel1.SuspendLayout(); this.panel1.SuspendLayout(); this.SuspendLayout(); @@ -111,6 +112,18 @@ this.richTextBox4.TabIndex = 1; this.richTextBox4.Text = ""; // + // label1 + // + this.tableLayoutPanel1.SetColumnSpan(this.label1, 2); + this.label1.Dock = System.Windows.Forms.DockStyle.Fill; + this.label1.ForeColor = System.Drawing.Color.White; + this.label1.Location = new System.Drawing.Point(3, 460); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(518, 50); + this.label1.TabIndex = 2; + this.label1.Text = "label1"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // // timer1 // this.timer1.Interval = 200; @@ -118,6 +131,7 @@ // // panel1 // + this.panel1.Controls.Add(this.lbIP); this.panel1.Controls.Add(this.button7); this.panel1.Controls.Add(this.button6); this.panel1.Controls.Add(this.button5); @@ -132,6 +146,18 @@ this.panel1.Size = new System.Drawing.Size(1050, 67); this.panel1.TabIndex = 7; // + // lbIP + // + this.lbIP.Dock = System.Windows.Forms.DockStyle.Fill; + this.lbIP.Font = new System.Drawing.Font("Tahoma", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.lbIP.ForeColor = System.Drawing.Color.White; + this.lbIP.Location = new System.Drawing.Point(300, 0); + this.lbIP.Name = "lbIP"; + this.lbIP.Size = new System.Drawing.Size(250, 67); + this.lbIP.TabIndex = 8; + this.lbIP.Text = "000.000.000.000"; + this.lbIP.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // // button7 // this.button7.Dock = System.Windows.Forms.DockStyle.Right; @@ -220,18 +246,6 @@ this.button4.UseVisualStyleBackColor = true; this.button4.Click += new System.EventHandler(this.button4_Click); // - // label1 - // - this.tableLayoutPanel1.SetColumnSpan(this.label1, 2); - this.label1.Dock = System.Windows.Forms.DockStyle.Fill; - this.label1.ForeColor = System.Drawing.Color.White; - this.label1.Location = new System.Drawing.Point(3, 460); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(518, 50); - this.label1.TabIndex = 2; - this.label1.Text = "label1"; - this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // // fAgv // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -269,5 +283,6 @@ private System.Windows.Forms.Button button7; private System.Windows.Forms.Button button8; private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label lbIP; } } \ No newline at end of file diff --git a/Cs_HMI/Project/ViewForm/fAgv.cs b/Cs_HMI/Project/ViewForm/fAgv.cs index 29645f7..6209be2 100644 --- a/Cs_HMI/Project/ViewForm/fAgv.cs +++ b/Cs_HMI/Project/ViewForm/fAgv.cs @@ -35,6 +35,7 @@ namespace Project.ViewForm private void timer1_Tick(object sender, EventArgs e) { timer1.Stop(); + lbIP.Text = PUB.IP; label1.Text = PUB.AGV.LastSTS; richTextBox1.Text = PUB.AGV.system0.ToString(); richTextBox2.Text = PUB.AGV.system1.ToString(); diff --git a/Cs_HMI/Project/ViewForm/fAuto.cs b/Cs_HMI/Project/ViewForm/fAuto.cs index 167310f..a5d3634 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.cs +++ b/Cs_HMI/Project/ViewForm/fAuto.cs @@ -70,13 +70,12 @@ namespace Project.ViewForm } private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e) { - switch (e.DataType) { case arDev.Narumi.DataType.TAG: - var tagno = PUB.AGV.data.TagNo; - PUB.log.Add($"[auto] recv tag : {tagno}"); - PUB.mapctl.SetCurrentPosition(tagno); + //_AGV 파일에서 위치조정을 함 + //var tagno = PUB.AGV.data.TagNo; + //PUB.mapctl.SetCurrentPosition(tagno); break; } } diff --git a/Cs_HMI/Project/fSetup.Designer.cs b/Cs_HMI/Project/fSetup.Designer.cs index 8511491..aa5a324 100644 --- a/Cs_HMI/Project/fSetup.Designer.cs +++ b/Cs_HMI/Project/fSetup.Designer.cs @@ -484,6 +484,7 @@ this.tbagvchannel.Name = "tbagvchannel"; this.tbagvchannel.Size = new System.Drawing.Size(164, 54); this.tbagvchannel.TabIndex = 77; + this.tbagvchannel.Click += new System.EventHandler(this.tbagvaddr_Click); // // tbagvpanid // @@ -492,6 +493,7 @@ this.tbagvpanid.Name = "tbagvpanid"; this.tbagvpanid.Size = new System.Drawing.Size(164, 54); this.tbagvpanid.TabIndex = 76; + this.tbagvpanid.Click += new System.EventHandler(this.tbagvaddr_Click); // // tbagvaddr // @@ -500,6 +502,7 @@ this.tbagvaddr.Name = "tbagvaddr"; this.tbagvaddr.Size = new System.Drawing.Size(164, 54); this.tbagvaddr.TabIndex = 75; + this.tbagvaddr.Click += new System.EventHandler(this.tbagvaddr_Click); // // button7 // diff --git a/Cs_HMI/Project/fSetup.cs b/Cs_HMI/Project/fSetup.cs index f3b0958..f23cdf2 100644 --- a/Cs_HMI/Project/fSetup.cs +++ b/Cs_HMI/Project/fSetup.cs @@ -662,5 +662,14 @@ namespace Project } + + private void tbagvaddr_Click(object sender, EventArgs e) + { + var tb = sender as TextBox; + var rlt = UTIL.InputBox("input vlaue", tb.Text); + if (rlt.Item1 == false) return; + if (rlt.Item2.isEmpty()) return; + tb.Text = rlt.Item2; + } } } diff --git a/Cs_HMI/SubProject/AGVControl/AGVControl.sln b/Cs_HMI/SubProject/AGVControl/AGVControl.sln new file mode 100644 index 0000000..51009aa --- /dev/null +++ b/Cs_HMI/SubProject/AGVControl/AGVControl.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "agvControl", "agvControl.csproj", "{EB23DC0D-9A07-407C-762D-DF1CC8B3D822}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EB23DC0D-9A07-407C-762D-DF1CC8B3D822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB23DC0D-9A07-407C-762D-DF1CC8B3D822}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB23DC0D-9A07-407C-762D-DF1CC8B3D822}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB23DC0D-9A07-407C-762D-DF1CC8B3D822}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {586342A4-FEA6-4584-82F4-39BA06630ABB} + EndGlobalSection +EndGlobal diff --git a/Cs_HMI/SubProject/AGVControl/MapControl.cs b/Cs_HMI/SubProject/AGVControl/MapControl.cs index 0e1f9f8..2107526 100644 --- a/Cs_HMI/SubProject/AGVControl/MapControl.cs +++ b/Cs_HMI/SubProject/AGVControl/MapControl.cs @@ -24,6 +24,7 @@ namespace AGVControl public bool IsBidirectional { get; set; } public float Distance { get; set; } public List IntermediateRFIDs { get; set; } = new List(); + public override bool Equals(object obj) { @@ -81,7 +82,7 @@ namespace AGVControl private const int SELECTION_DISTANCE = 15; // 선택 가능 거리를 늘림 private bool isDraggingText = false; private Point dragOffset; - private List currentPath; + // private List currentPath; public MapText SelectedText => selectedText; @@ -210,6 +211,7 @@ namespace AGVControl public RFIDPoint FindRFIDPoint(uint rfidValue) { + if (rfidPoints == null || rfidPoints.Any() == false) return null; return rfidPoints.FirstOrDefault(r => r.RFIDValue == rfidValue); } @@ -220,6 +222,57 @@ namespace AGVControl { agv.CurrentPosition = rfidPoint.Location; agv.CurrentRFID = rfidValue; + + // 목적지가 설정되어 있고 경로가 있는 경우 검증 + if (agv.TargetPosition != Point.Empty && agv.CurrentPath.Count > 0) + { + // 현재 위치가 경로에 있는지 확인 + bool isOnPath = agv.CurrentPath.Contains(agv.CurrentPosition); + + if (!isOnPath) + { + // 경로를 벗어났으므로 새로운 경로 계산 + var pathResult = CalculatePath(agv.CurrentRFID, agv.TargetRFID); + if (pathResult.Success) + { + SetCurrentPath(pathResult.Path); + } + } + + // AGV의 방향 결정 + if (agv.CurrentPath.Count > 0) + { + // 다음 목적지 찾기 + var nextPoint = agv.CurrentPath[0]; + + // 회전 가능 여부를 고려하여 방향 결정 + agv.TargetDirection = DetermineDirection(agv.CurrentPosition, nextPoint, agv.TargetPosition); + } + } + + // 목적지 RFID에 도착했고, 해당 RFID에 고정방향이 있으면 TargetDirection을 강제 설정 + if (agv.TargetRFID == rfidValue) + { + var destRFID = FindRFIDPoint(rfidValue); + if (destRFID != null && destRFID.FixedDirection.HasValue) + { + agv.TargetDirection = destRFID.FixedDirection.Value; + } + } + + this.Invalidate(); + return true; + } + return false; + } + + public bool SetTargetPosition(uint rfidValue) + { + var rfidPoint = FindRFIDPoint(rfidValue); + if (rfidPoint != null) + { + agv.TargetPosition = rfidPoint.Location; + agv.TargetRFID = rfidValue; this.Invalidate(); return true; } @@ -566,7 +619,7 @@ namespace AGVControl if (path.Count > 0) { - DrawPath(path); + SetCurrentPath(path); } } @@ -605,22 +658,22 @@ namespace AGVControl return retval; } - var path = CalculatePath(startPoint.Location, endPoint.Location); - if (path != null && path.Count > 0) + retval.Path = CalculatePath(startPoint.Location, endPoint.Location); + if (retval.Path != null && retval.Path.Any()) { - DrawPath(path); + //SetCurrentPath(retval.Path); - // 경로 상의 모든 RFID 값을 가져옴 - var rfidPath = new List(); - foreach (var point in path) - { - var rfid = GetRFIDPoints() - .FirstOrDefault(r => r.Location == point); - if (rfid != null) - { - rfidPath.Add(rfid.RFIDValue); - } - } + //// 경로 상의 모든 RFID 값을 가져옴 + //var rfidPath = new List(); + //foreach (var point in path) + //{ + // var rfid = GetRFIDPoints() + // .FirstOrDefault(r => r.Location == point); + // if (rfid != null) + // { + // rfidPath.Add(rfid.RFIDValue); + // } + //} retval.Success = true; } else @@ -683,8 +736,13 @@ namespace AGVControl } else { + SetCurrentPath(rlt.Path); //현재 경로로 설정함 MessageBox.Show($"경로가 계산되었습니다.\nRFID 순서: {string.Join(" -> ", rlt.Path)}", "경로 계산", MessageBoxButtons.OK, MessageBoxIcon.Information); + if (SetTargetPosition(vend) == false) + { + MessageBox.Show("목적지 설정 실패"); + } } break; case "pos": @@ -700,12 +758,12 @@ namespace AGVControl od.Filter = "path data|*.route"; od.FilterIndex = 0; od.RestoreDirectory = true; - if(filename.isEmpty()==false) + if (filename.isEmpty() == false) { od.FileName = System.IO.Path.GetFileName(filename); od.InitialDirectory = System.IO.Path.GetDirectoryName(filename); } - + if (od.ShowDialog() == DialogResult.OK) { filename = od.FileName; @@ -734,7 +792,7 @@ namespace AGVControl if (od.ShowDialog() == DialogResult.OK) { - + this.LoadFromFile(filename, out string errmsg); if (errmsg.isEmpty() == false) UTIL.MsgE(errmsg); this.Invalidate(); @@ -935,20 +993,21 @@ namespace AGVControl if (e.Button == MouseButtons.Left) { - // 화면 좌표를 맵 좌표로 변환 var mapPoint = ScreenToMap(e.Location); //RFID 포인트 찾기 var selected_rfid = rfidPoints.Where(t => t.Bounds.Expand(SELECTION_DISTANCE, SELECTION_DISTANCE).Contains(mapPoint)).FirstOrDefault(); if (selected_rfid != null) { - var rlt = AR.UTIL.InputBox("rfid value", selected_rfid.RFIDValue.ToString()); - if (rlt.Item1 == true) - { - selected_rfid.RFIDValue = uint.Parse(rlt.Item2);// dialog.InputText; - ProcessRFIDConnections(); - this.Invalidate(); - } + // 고정방향 순환 변경: 없음→전진→후진→없음 + if (!selected_rfid.FixedDirection.HasValue) + selected_rfid.FixedDirection = Direction.Forward; + else if (selected_rfid.FixedDirection == Direction.Forward) + selected_rfid.FixedDirection = Direction.Backward; + else + selected_rfid.FixedDirection = null; + + this.Invalidate(); return; } @@ -959,7 +1018,7 @@ namespace AGVControl var rlt = AR.UTIL.InputBox("input", selected_txt.Text); if (rlt.Item1 == true) { - selected_txt.Text = rlt.Item2;// dialog.InputText; + selected_txt.Text = rlt.Item2; this.Invalidate(); } return; @@ -1108,9 +1167,9 @@ namespace AGVControl } } - public void DrawPath(List path) + public void SetCurrentPath(List path) { - currentPath = path; + agv.CurrentPath = path; this.Invalidate(); } @@ -1130,6 +1189,7 @@ namespace AGVControl DrawPath(e.Graphics); DrawAGV(e.Graphics); + DrawTargetFlag(e.Graphics); // 목적지 깃발 그리기 추가 // 선택된 개체 강조 표시 if (selectedRFID != null) @@ -1180,51 +1240,85 @@ namespace AGVControl var MarkerSize = 5; var half = MarkerSize / 2f; rfid.Bounds = new RectangleF(rfid.Location.X - half, rfid.Location.Y - half, MarkerSize, MarkerSize); - g.FillEllipse(Brushes.Green, rfid.Bounds); - //g.DrawString(rfid.RFIDValue, Font, Brushes.WhiteSmoke, rfid.Location.X + 5, rfid.Location.Y - 5); + + // 회전 가능 여부에 따라 다른 색상 사용 + using (var brush = new SolidBrush(rfid.IsRotatable ? Color.Yellow : Color.Green)) + { + 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)) + { + g.DrawEllipse(pen, rfid.Bounds.Expand(5,5)); + } + } } - // RFID 포인트 그리기 + // RFID 값 표시 foreach (var rfid in rfidPoints) { - //g.FillEllipse(Brushes.Green, rfid.Location.X - 3, rfid.Location.Y - 3, 6, 6); 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.DrawRectangle(Pens.Red, rect); g.DrawString(tagstr, Font, Brushes.WhiteSmoke, rect, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center, }); } - } private void DrawAGV(Graphics g) { var agvsize = 30; var halfsize = (int)(agvsize / 2); - var rect = new Rectangle(agv.CurrentPosition.X - halfsize, - agv.CurrentPosition.Y - halfsize, - agvsize, agvsize); - var recti = new Rectangle(rect.X + 5, rect.Y + 5, rect.Width - 10, rect.Height - 10); + // AGV의 현재 위치를 중심으로 하는 원 + var circleRect = new Rectangle( + agv.CurrentPosition.X - halfsize, + agv.CurrentPosition.Y - halfsize, + agvsize, agvsize); - using (var sb = new SolidBrush(Color.FromArgb(150, Color.Gold))) - g.FillEllipse(sb, rect); + // 삼각형 화살표를 위한 포인트 배열 + Point[] trianglePoints = new Point[3]; + var arrowSize = halfsize - 5; // 삼각형 크기 - using (var pen = new Pen(Color.Black)) - g.DrawEllipse(pen, rect); + // 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 sb = new SolidBrush(Color.FromArgb(150, Color.White))) - g.FillEllipse(sb, recti); - using (var pen = new Pen(Color.Black)) - g.DrawEllipse(pen, recti); + + // 원 그리기 + 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)) + g.DrawPolygon(arrowPen, trianglePoints); + + //g.DrawImage(Properties.Resources.ico_navi_40, circleRect); } private void DrawCustomLines(Graphics g) @@ -1358,7 +1452,7 @@ namespace AGVControl private void DrawPath(Graphics g) { - if (currentPath == null || currentPath.Count < 2) + if (agv.CurrentPath == null || agv.CurrentPath.Count < 2) return; Color pathColor = Color.FromArgb(100, Color.Lime); @@ -1367,9 +1461,9 @@ namespace AGVControl using (Pen pathPen = new Pen(pathColor, pathWidth)) { pathPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; - for (int i = 0; i < currentPath.Count - 1; i++) + for (int i = 0; i < agv.CurrentPath.Count - 1; i++) { - g.DrawLine(pathPen, currentPath[i], currentPath[i + 1]); + g.DrawLine(pathPen, agv.CurrentPath[i], agv.CurrentPath[i + 1]); } } } @@ -1728,5 +1822,85 @@ namespace AGVControl g.DrawString(text, font, Brushes.Black, rect, format); } } + + private void DrawTargetFlag(Graphics g) + { + if (agv.TargetPosition == Point.Empty) return; + + // 바닥에 흰색 원 그리기 + using (var baseBrush = new SolidBrush(Color.Red)) + using (var basePen = new Pen(Color.Black, 1)) + { + var baseSize = 8; + g.FillEllipse(baseBrush, + agv.TargetPosition.X - baseSize / 2, + agv.TargetPosition.Y - baseSize / 2, + baseSize, baseSize); + g.DrawEllipse(basePen, + agv.TargetPosition.X - baseSize / 2, + agv.TargetPosition.Y - baseSize / 2, + baseSize, baseSize); + } + + // 깃대 그리기 (길이를 2/3로 줄임) + using (var polePen = new Pen(Color.Brown, 3)) + { + var poleLength = 27; // 40 * 2/3 ≈ 27 + g.DrawLine(polePen, + agv.TargetPosition.X, + agv.TargetPosition.Y, + agv.TargetPosition.X, + agv.TargetPosition.Y - poleLength); + } + + // 깃발 그리기 + Point[] flagPoints = new Point[3]; + flagPoints[0] = new Point(agv.TargetPosition.X, agv.TargetPosition.Y - 27); // 깃대 길이에 맞춤 + flagPoints[1] = new Point(agv.TargetPosition.X + 20, agv.TargetPosition.Y - 22); + flagPoints[2] = new Point(agv.TargetPosition.X, agv.TargetPosition.Y - 17); + + using (var flagBrush = new SolidBrush(Color.Red)) + using (var flagPen = new Pen(Color.DarkRed, 1)) + { + g.FillPolygon(flagBrush, flagPoints); + g.DrawPolygon(flagPen, flagPoints); + } + } + + // 회전 가능 여부 토글 함수 + public void ToggleRotatable(uint rfidValue) + { + var rfidPoint = FindRFIDPoint(rfidValue); + if (rfidPoint != null) + { + rfidPoint.IsRotatable = !rfidPoint.IsRotatable; + this.Invalidate(); + } + } + + // 회전 가능 여부 확인 함수 + public bool IsPointRotatable(uint rfidValue) + { + var rfidPoint = FindRFIDPoint(rfidValue); + return rfidPoint?.IsRotatable ?? false; + } + + // 경로 계산 시 회전 가능 여부를 고려하여 방향 결정 + private Direction DetermineDirection(Point current, Point next, Point target) + { + // 현재 위치가 회전 가능한 구간인 경우 + var currentRFID = rfidPoints.FirstOrDefault(p => p.Location == current); + if (currentRFID?.IsRotatable ?? false) + { + // 목적지 방향으로 직접 방향 결정 + if (target.X > current.X) return Direction.Forward; + if (target.X < current.X) return Direction.Backward; + if (target.Y > current.Y) return Direction.Forward; + if (target.Y < current.Y) return Direction.Backward; + } + + // 회전 불가능한 구간인 경우 현재 진행 방향 유지 + return agv.CurrentDirection; + } } } diff --git a/Cs_HMI/SubProject/AGVControl/Models/AGV.cs b/Cs_HMI/SubProject/AGVControl/Models/AGV.cs index 82423e4..1d1ada4 100644 --- a/Cs_HMI/SubProject/AGVControl/Models/AGV.cs +++ b/Cs_HMI/SubProject/AGVControl/Models/AGV.cs @@ -8,11 +8,22 @@ namespace AGVControl.Models { public Point CurrentPosition { get; set; } public uint CurrentRFID { get; set; } + public float BatteryLevel { get; set; } = 0f; + /// + /// AGV에서 방향값이 수산됩니다. + /// public Direction CurrentDirection { get; set; } + + /// + /// 현재위치가 수산되면 목적지까지의 방향값이 계산됩니다. + /// + public Direction TargetDirection { get; set; } public bool IsMoving { get; set; } public List CurrentPath { get; set; } public List PlannedPath { get; set; } public List PathRFIDs { get; set; } + public Point TargetPosition { get; set; } + public uint TargetRFID { get; set; } public AGV() { @@ -20,6 +31,9 @@ namespace AGVControl.Models PlannedPath = new List(); PathRFIDs = new List(); CurrentDirection = Direction.Forward; + TargetPosition = Point.Empty; + TargetRFID = 0; + TargetDirection = Direction.Forward; } public void Move() @@ -31,16 +45,6 @@ namespace AGVControl.Models } } - public void SetNewPath(List path) - { - CurrentPath = new List(path); - } - - public void ClearPlannedPath() - { - PlannedPath.Clear(); - PathRFIDs.Clear(); - } } public enum Direction diff --git a/Cs_HMI/SubProject/AGVControl/Models/RFIDPoint.cs b/Cs_HMI/SubProject/AGVControl/Models/RFIDPoint.cs index a9361cd..fae5d37 100644 --- a/Cs_HMI/SubProject/AGVControl/Models/RFIDPoint.cs +++ b/Cs_HMI/SubProject/AGVControl/Models/RFIDPoint.cs @@ -1,6 +1,6 @@ using System.Drawing; using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace AGVControl.Models { public class RFIDPoint @@ -9,6 +9,15 @@ namespace AGVControl.Models public uint RFIDValue { get; set; } public string NextRFID { get; set; } // 다음 RFID 포인트의 값 public bool IsBidirectional { get; set; } // 양방향 연결 여부 + public bool IsRotatable { get; set; } // 회전 가능 여부 + public Direction? FixedDirection { get; set; } // 고정 방향(없으면 null) public RectangleF Bounds { get; set; } + + public RFIDPoint() + { + IsRotatable = false; // 기본값은 회전 불가능 + IsBidirectional = true; // 기본값은 양방향 + FixedDirection = null; + } } } \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVControl/Properties/Resources.Designer.cs b/Cs_HMI/SubProject/AGVControl/Properties/Resources.Designer.cs new file mode 100644 index 0000000..97968f8 --- /dev/null +++ b/Cs_HMI/SubProject/AGVControl/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// 이 코드는 도구를 사용하여 생성되었습니다. +// 런타임 버전:4.0.30319.42000 +// +// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 +// 이러한 변경 내용이 손실됩니다. +// +//------------------------------------------------------------------------------ + +namespace AGVControl.Properties { + using System; + + + /// + /// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다. + /// + // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder + // 클래스에서 자동으로 생성되었습니다. + // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을 + // 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AGVControl.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을 + /// 재정의합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// System.Drawing.Bitmap 형식의 지역화된 리소스를 찾습니다. + /// + internal static System.Drawing.Bitmap ico_navi_40 { + get { + object obj = ResourceManager.GetObject("ico_navi_40", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/Cs_HMI/SubProject/AGVControl/Properties/Resources.resx b/Cs_HMI/SubProject/AGVControl/Properties/Resources.resx new file mode 100644 index 0000000..cfb902b --- /dev/null +++ b/Cs_HMI/SubProject/AGVControl/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\ico_navi_40.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVControl/Resources/ico_navi_40.png b/Cs_HMI/SubProject/AGVControl/Resources/ico_navi_40.png new file mode 100644 index 0000000..a419591 Binary files /dev/null and b/Cs_HMI/SubProject/AGVControl/Resources/ico_navi_40.png differ diff --git a/Cs_HMI/SubProject/AGVControl/agvControl.csproj b/Cs_HMI/SubProject/AGVControl/agvControl.csproj index 2a23f17..900ffa3 100644 --- a/Cs_HMI/SubProject/AGVControl/agvControl.csproj +++ b/Cs_HMI/SubProject/AGVControl/agvControl.csproj @@ -77,6 +77,11 @@ MyRadioButton.cs + + True + True + Resources.resx + Component @@ -94,6 +99,10 @@ MapControl.cs + + ResXFileCodeGenerator + Resources.Designer.cs + @@ -101,5 +110,8 @@ arCommUtil + + + \ No newline at end of file