diff --git a/AGVEmulator/AGVEmulator.csproj b/AGVEmulator/AGVEmulator.csproj index 5a22417..b8e128e 100644 --- a/AGVEmulator/AGVEmulator.csproj +++ b/AGVEmulator/AGVEmulator.csproj @@ -34,6 +34,9 @@ 4 false + + icons8-robot-80.ico + ..\Cs_HMI\DLL\arControl.Net4.dll @@ -136,5 +139,8 @@ ENIGProtocol + + + \ No newline at end of file diff --git a/AGVEmulator/DevXBE.cs b/AGVEmulator/DevXBE.cs index 2bdfd5f..efd096f 100644 --- a/AGVEmulator/DevXBE.cs +++ b/AGVEmulator/DevXBE.cs @@ -61,29 +61,52 @@ namespace AGVEmulator /// public void SendGotoTag(byte id, uint tag) { - var idSTR = id.ToString("X2"); - var tagSTR = tag.ToString("0000"); - var dataStr = $"{idSTR}{tagSTR}"; - Send(ENIGProtocol.AGVCommandHE.Goto, dataStr); + //var idSTR = id.ToString("X2"); + //var tagSTR = tag.ToString("0000"); + //var dataStr = $"{idSTR}{tagSTR}"; + var data = new List(); + data.Add(id); + data.AddRange(System.Text.Encoding.Default.GetBytes(tag.ToString("0000"))); + Send(ENIGProtocol.AGVCommandHE.Goto, data.ToArray()); } + + /// + /// 카트를 가지러 들어간다 + /// + /// + public void SendPickOn(byte id) + { + var data = new List(); + data.Add(id); + Send(ENIGProtocol.AGVCommandHE.PickOn, data.ToArray()); + } + + /// + /// 카트를 내려놓는다 + /// + /// + public void SendPickOff(byte id) + { + var data = new List(); + data.Add(id); + Send(ENIGProtocol.AGVCommandHE.PickOff, data.ToArray()); + } + public void SendCurrentPos(byte id, uint tag) { - var idSTR = id.ToString("X2"); - var tagSTR = tag.ToString("0000"); - var dataStr = $"{idSTR}{tagSTR}"; - Send(ENIGProtocol.AGVCommandHE.SetCurrent, dataStr); + var data = new List(); + data.Add(id); + data.AddRange(System.Text.Encoding.Default.GetBytes(tag.ToString("0000"))); + Send(ENIGProtocol.AGVCommandHE.SetCurrent, data.ToArray()); } - private void Send(ENIGProtocol.AGVCommandHE Command, string datastr) + private void Send(ENIGProtocol.AGVCommandHE Command, byte[] data) { byte id = 0; byte cmd = (byte)Command; //move to target - byte[] data = null; - if (datastr != null && string.IsNullOrEmpty(datastr) == false) - data = System.Text.Encoding.Default.GetBytes(datastr); var packet = proto.CreatePacket(id, cmd, data); if (WriteData(packet, false)) { - var hexstr = System.Text.Encoding.Default.GetString(data); + var hexstr =(data == null || data.Any()==false) ? string.Empty : System.Text.Encoding.Default.GetString(data); RaiseMessage(MessageType.Send, $"ID:{id},CMD:{cmd},DATA:{hexstr}"); } } diff --git a/AGVEmulator/fMain.Designer.cs b/AGVEmulator/fMain.Designer.cs index 1757da5..c06e5f9 100644 --- a/AGVEmulator/fMain.Designer.cs +++ b/AGVEmulator/fMain.Designer.cs @@ -32,7 +32,6 @@ namespace AGVEmulator private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(fMain)); AGVEmulator.UC.AgvViewer.ptdata ptdata57 = new AGVEmulator.UC.AgvViewer.ptdata(); AGVEmulator.UC.AgvViewer.ptdata ptdata58 = new AGVEmulator.UC.AgvViewer.ptdata(); AGVEmulator.UC.AgvViewer.ptdata ptdata59 = new AGVEmulator.UC.AgvViewer.ptdata(); @@ -61,6 +60,7 @@ namespace AGVEmulator AGVEmulator.UC.AgvViewer.ptdata ptdata82 = new AGVEmulator.UC.AgvViewer.ptdata(); AGVEmulator.UC.AgvViewer.ptdata ptdata83 = new AGVEmulator.UC.AgvViewer.ptdata(); AGVEmulator.UC.AgvViewer.ptdata ptdata84 = new AGVEmulator.UC.AgvViewer.ptdata(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(fMain)); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.rtBMS = new arCtl.LogTextBox(); this.panel1 = new System.Windows.Forms.Panel(); @@ -86,6 +86,7 @@ namespace AGVEmulator this.label2 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.trackBar1 = new System.Windows.Forms.TrackBar(); + this.serBMS = new AGVEmulator.SerialConn(); this.rtAGV = new arCtl.LogTextBox(); this.panel4 = new System.Windows.Forms.Panel(); this.groupBox9 = new System.Windows.Forms.GroupBox(); @@ -127,6 +128,7 @@ namespace AGVEmulator this.button4 = new System.Windows.Forms.Button(); this.groupBox3 = new System.Windows.Forms.GroupBox(); this.rtCAL = new arCtl.LogTextBox(); + this.serCAL = new AGVEmulator.SerialConn(); this.timer1 = new System.Windows.Forms.Timer(this.components); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage4 = new System.Windows.Forms.TabPage(); @@ -134,6 +136,8 @@ namespace AGVEmulator this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); this.rtAGVPro = new arCtl.LogTextBox(); this.panel12 = new System.Windows.Forms.Panel(); + this.agvViewer1 = new AGVEmulator.UC.AgvViewer(); + this.serAGV = new AGVEmulator.SerialConn(); this.tabPage2 = new System.Windows.Forms.TabPage(); this.tabPage3 = new System.Windows.Forms.TabPage(); this.panel3 = new System.Windows.Forms.Panel(); @@ -152,6 +156,10 @@ namespace AGVEmulator this.toolStrip1 = new System.Windows.Forms.ToolStrip(); this.toolStripButton1 = new System.Windows.Forms.ToolStripButton(); this.toolStripButton2 = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripButton3 = new System.Windows.Forms.ToolStripButton(); + this.toolStripButton4 = new System.Windows.Forms.ToolStripButton(); + this.toolStripButton5 = new System.Windows.Forms.ToolStripButton(); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripStatusLabel8 = new System.Windows.Forms.ToolStripStatusLabel(); this.sbAGV = new System.Windows.Forms.ToolStripStatusLabel(); @@ -159,14 +167,8 @@ namespace AGVEmulator this.sbBMS = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel(); this.sbCAL = new System.Windows.Forms.ToolStripStatusLabel(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.toolStripButton3 = new System.Windows.Forms.ToolStripButton(); - this.agvViewer1 = new AGVEmulator.UC.AgvViewer(); - this.serAGV = new AGVEmulator.SerialConn(); - this.serBMS = new AGVEmulator.SerialConn(); - this.serCAL = new AGVEmulator.SerialConn(); - this.toolStripButton4 = new System.Windows.Forms.ToolStripButton(); - this.toolStripButton5 = new System.Windows.Forms.ToolStripButton(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); this.groupBox1.SuspendLayout(); this.panel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trbT2)).BeginInit(); @@ -501,6 +503,18 @@ namespace AGVEmulator this.trackBar1.Value = 7000; this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll); // + // serBMS + // + this.serBMS.BaudRate = 9600; + this.serBMS.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.serBMS.dev = null; + this.serBMS.Dock = System.Windows.Forms.DockStyle.Top; + this.serBMS.Location = new System.Drawing.Point(3, 17); + this.serBMS.Name = "serBMS"; + this.serBMS.PortName = "COM31"; + this.serBMS.Size = new System.Drawing.Size(1134, 84); + this.serBMS.TabIndex = 1; + // // rtAGV // this.rtAGV.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24))))); @@ -984,6 +998,18 @@ namespace AGVEmulator this.rtCAL.TabIndex = 2; this.rtCAL.Text = ""; // + // serCAL + // + this.serCAL.BaudRate = 9600; + this.serCAL.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.serCAL.dev = null; + this.serCAL.Dock = System.Windows.Forms.DockStyle.Top; + this.serCAL.Location = new System.Drawing.Point(3, 17); + this.serCAL.Name = "serCAL"; + this.serCAL.PortName = "COM41"; + this.serCAL.Size = new System.Drawing.Size(776, 84); + this.serCAL.TabIndex = 1; + // // timer1 // this.timer1.Interval = 200; @@ -1069,6 +1095,149 @@ namespace AGVEmulator this.panel12.Size = new System.Drawing.Size(1140, 120); this.panel12.TabIndex = 5; // + // agvViewer1 + // + this.agvViewer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.agvViewer1.FontMrk = new System.Drawing.Font("Microsoft Sans Serif", 7F); + this.agvViewer1.FontTag = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.agvViewer1.lastmark = ""; + this.agvViewer1.lastmarkdir = ""; + this.agvViewer1.lasttag = ""; + this.agvViewer1.lasttagdir = ""; + ptdata57.active = false; + ptdata57.data = "NOT"; + ptdata57.pos = 30F; + ptdata58.active = false; + ptdata58.data = "QA"; + ptdata58.pos = 200F; + ptdata59.active = false; + ptdata59.data = "CHG"; + ptdata59.pos = 300F; + ptdata60.active = false; + ptdata60.data = "QC"; + ptdata60.pos = 400F; + ptdata61.active = false; + ptdata61.data = "#FVI-1"; + ptdata61.pos = 500F; + ptdata62.active = false; + ptdata62.data = "#FVI-2"; + ptdata62.pos = 600F; + ptdata63.active = false; + ptdata63.data = "#FVI-3"; + ptdata63.pos = 700F; + ptdata64.active = false; + ptdata64.data = "#FVI-4"; + ptdata64.pos = 800F; + ptdata65.active = false; + ptdata65.data = "#FVI-5"; + ptdata65.pos = 900F; + ptdata66.active = false; + ptdata66.data = "POT"; + ptdata66.pos = 970F; + this.agvViewer1.listMRK = new AGVEmulator.UC.AgvViewer.ptdata[] { + ptdata57, + ptdata58, + ptdata59, + ptdata60, + ptdata61, + ptdata62, + ptdata63, + ptdata64, + ptdata65, + ptdata66}; + ptdata67.active = false; + ptdata67.data = "9000"; + ptdata67.pos = 80F; + ptdata68.active = false; + ptdata68.data = "9001"; + ptdata68.pos = 120F; + ptdata69.active = false; + ptdata69.data = "9010"; + ptdata69.pos = 180F; + ptdata70.active = false; + ptdata70.data = "9011"; + ptdata70.pos = 220F; + ptdata71.active = false; + ptdata71.data = "9020"; + ptdata71.pos = 280F; + ptdata72.active = false; + ptdata72.data = "9021"; + ptdata72.pos = 320F; + ptdata73.active = false; + ptdata73.data = "9030"; + ptdata73.pos = 380F; + ptdata74.active = false; + ptdata74.data = "9031"; + ptdata74.pos = 420F; + ptdata75.active = false; + ptdata75.data = "9040"; + ptdata75.pos = 480F; + ptdata76.active = false; + ptdata76.data = "9041"; + ptdata76.pos = 520F; + ptdata77.active = false; + ptdata77.data = "9050"; + ptdata77.pos = 580F; + ptdata78.active = false; + ptdata78.data = "9051"; + ptdata78.pos = 620F; + ptdata79.active = false; + ptdata79.data = "9060"; + ptdata79.pos = 680F; + ptdata80.active = false; + ptdata80.data = "9061"; + ptdata80.pos = 720F; + ptdata81.active = false; + ptdata81.data = "9070"; + ptdata81.pos = 780F; + ptdata82.active = false; + ptdata82.data = "9071"; + ptdata82.pos = 820F; + ptdata83.active = false; + ptdata83.data = "9000"; + ptdata83.pos = 10F; + ptdata84.active = false; + ptdata84.data = "9001"; + ptdata84.pos = 50F; + this.agvViewer1.listTAG = new AGVEmulator.UC.AgvViewer.ptdata[] { + ptdata67, + ptdata68, + ptdata69, + ptdata70, + ptdata71, + ptdata72, + ptdata73, + ptdata74, + ptdata75, + ptdata76, + ptdata77, + ptdata78, + ptdata79, + ptdata80, + ptdata81, + ptdata82, + ptdata83, + ptdata84}; + this.agvViewer1.Location = new System.Drawing.Point(241, 0); + this.agvViewer1.Name = "agvViewer1"; + this.agvViewer1.Size = new System.Drawing.Size(899, 120); + this.agvViewer1.StopbyMark = false; + this.agvViewer1.TabIndex = 0; + this.agvViewer1.Text = "agvViewer1"; + this.agvViewer1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.agvViewer1_MouseDown); + // + // serAGV + // + this.serAGV.BaudRate = 9600; + this.serAGV.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.serAGV.dev = null; + this.serAGV.Dock = System.Windows.Forms.DockStyle.Left; + this.serAGV.Location = new System.Drawing.Point(0, 0); + this.serAGV.Name = "serAGV"; + this.serAGV.PortName = "COM21"; + this.serAGV.Size = new System.Drawing.Size(241, 120); + this.serAGV.TabIndex = 0; + // // tabPage2 // this.tabPage2.Controls.Add(this.groupBox1); @@ -1093,6 +1262,8 @@ namespace AGVEmulator // // panel3 // + this.panel3.Controls.Add(this.button3); + this.panel3.Controls.Add(this.button2); this.panel3.Controls.Add(this.nudIDAgv); this.panel3.Controls.Add(this.label7); this.panel3.Controls.Add(this.numericUpDown2); @@ -1288,6 +1459,38 @@ namespace AGVEmulator this.toolStripButton2.Text = "All Close"; this.toolStripButton2.Click += new System.EventHandler(this.toolStripButton2_Click); // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); + // + // toolStripButton3 + // + this.toolStripButton3.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton3.Image"))); + this.toolStripButton3.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButton3.Name = "toolStripButton3"; + this.toolStripButton3.Size = new System.Drawing.Size(84, 22); + this.toolStripButton3.Text = "Open Map"; + this.toolStripButton3.Click += new System.EventHandler(this.toolStripButton3_Click); + // + // toolStripButton4 + // + this.toolStripButton4.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton4.Image"))); + this.toolStripButton4.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButton4.Name = "toolStripButton4"; + this.toolStripButton4.Size = new System.Drawing.Size(117, 22); + this.toolStripButton4.Text = "Change Rotation"; + this.toolStripButton4.Click += new System.EventHandler(this.toolStripButton4_Click); + // + // toolStripButton5 + // + this.toolStripButton5.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton5.Image"))); + this.toolStripButton5.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButton5.Name = "toolStripButton5"; + this.toolStripButton5.Size = new System.Drawing.Size(87, 22); + this.toolStripButton5.Text = "SetPosition"; + this.toolStripButton5.Click += new System.EventHandler(this.toolStripButton5_Click); + // // statusStrip1 // this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1339,204 +1542,27 @@ namespace AGVEmulator this.sbCAL.Size = new System.Drawing.Size(19, 17); this.sbCAL.Text = "●"; // - // toolStripSeparator1 + // button2 // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); + this.button2.Location = new System.Drawing.Point(246, 295); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(86, 38); + this.button2.TabIndex = 14; + this.button2.Tag = "--"; + this.button2.Text = "Pick On"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); // - // toolStripButton3 + // button3 // - this.toolStripButton3.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton3.Image"))); - this.toolStripButton3.ImageTransparentColor = System.Drawing.Color.Magenta; - this.toolStripButton3.Name = "toolStripButton3"; - this.toolStripButton3.Size = new System.Drawing.Size(84, 22); - this.toolStripButton3.Text = "Open Map"; - this.toolStripButton3.Click += new System.EventHandler(this.toolStripButton3_Click); - // - // agvViewer1 - // - this.agvViewer1.Dock = System.Windows.Forms.DockStyle.Fill; - this.agvViewer1.FontMrk = new System.Drawing.Font("Microsoft Sans Serif", 7F); - this.agvViewer1.FontTag = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.agvViewer1.lastmark = ""; - this.agvViewer1.lastmarkdir = ""; - this.agvViewer1.lasttag = ""; - this.agvViewer1.lasttagdir = ""; - ptdata57.active = false; - ptdata57.data = "NOT"; - ptdata57.pos = 30F; - ptdata58.active = false; - ptdata58.data = "QA"; - ptdata58.pos = 200F; - ptdata59.active = false; - ptdata59.data = "CHG"; - ptdata59.pos = 300F; - ptdata60.active = false; - ptdata60.data = "QC"; - ptdata60.pos = 400F; - ptdata61.active = false; - ptdata61.data = "#FVI-1"; - ptdata61.pos = 500F; - ptdata62.active = false; - ptdata62.data = "#FVI-2"; - ptdata62.pos = 600F; - ptdata63.active = false; - ptdata63.data = "#FVI-3"; - ptdata63.pos = 700F; - ptdata64.active = false; - ptdata64.data = "#FVI-4"; - ptdata64.pos = 800F; - ptdata65.active = false; - ptdata65.data = "#FVI-5"; - ptdata65.pos = 900F; - ptdata66.active = false; - ptdata66.data = "POT"; - ptdata66.pos = 970F; - this.agvViewer1.listMRK = new AGVEmulator.UC.AgvViewer.ptdata[] { - ptdata57, - ptdata58, - ptdata59, - ptdata60, - ptdata61, - ptdata62, - ptdata63, - ptdata64, - ptdata65, - ptdata66}; - ptdata67.active = false; - ptdata67.data = "9000"; - ptdata67.pos = 80F; - ptdata68.active = false; - ptdata68.data = "9001"; - ptdata68.pos = 120F; - ptdata69.active = false; - ptdata69.data = "9010"; - ptdata69.pos = 180F; - ptdata70.active = false; - ptdata70.data = "9011"; - ptdata70.pos = 220F; - ptdata71.active = false; - ptdata71.data = "9020"; - ptdata71.pos = 280F; - ptdata72.active = false; - ptdata72.data = "9021"; - ptdata72.pos = 320F; - ptdata73.active = false; - ptdata73.data = "9030"; - ptdata73.pos = 380F; - ptdata74.active = false; - ptdata74.data = "9031"; - ptdata74.pos = 420F; - ptdata75.active = false; - ptdata75.data = "9040"; - ptdata75.pos = 480F; - ptdata76.active = false; - ptdata76.data = "9041"; - ptdata76.pos = 520F; - ptdata77.active = false; - ptdata77.data = "9050"; - ptdata77.pos = 580F; - ptdata78.active = false; - ptdata78.data = "9051"; - ptdata78.pos = 620F; - ptdata79.active = false; - ptdata79.data = "9060"; - ptdata79.pos = 680F; - ptdata80.active = false; - ptdata80.data = "9061"; - ptdata80.pos = 720F; - ptdata81.active = false; - ptdata81.data = "9070"; - ptdata81.pos = 780F; - ptdata82.active = false; - ptdata82.data = "9071"; - ptdata82.pos = 820F; - ptdata83.active = false; - ptdata83.data = "9000"; - ptdata83.pos = 10F; - ptdata84.active = false; - ptdata84.data = "9001"; - ptdata84.pos = 50F; - this.agvViewer1.listTAG = new AGVEmulator.UC.AgvViewer.ptdata[] { - ptdata67, - ptdata68, - ptdata69, - ptdata70, - ptdata71, - ptdata72, - ptdata73, - ptdata74, - ptdata75, - ptdata76, - ptdata77, - ptdata78, - ptdata79, - ptdata80, - ptdata81, - ptdata82, - ptdata83, - ptdata84}; - this.agvViewer1.Location = new System.Drawing.Point(241, 0); - this.agvViewer1.Name = "agvViewer1"; - this.agvViewer1.Size = new System.Drawing.Size(899, 120); - this.agvViewer1.StopbyMark = false; - this.agvViewer1.TabIndex = 0; - this.agvViewer1.Text = "agvViewer1"; - this.agvViewer1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.agvViewer1_MouseDown); - // - // serAGV - // - this.serAGV.BaudRate = 9600; - this.serAGV.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.serAGV.dev = null; - this.serAGV.Dock = System.Windows.Forms.DockStyle.Left; - this.serAGV.Location = new System.Drawing.Point(0, 0); - this.serAGV.Name = "serAGV"; - this.serAGV.PortName = "COM20"; - this.serAGV.Size = new System.Drawing.Size(241, 120); - this.serAGV.TabIndex = 0; - // - // serBMS - // - this.serBMS.BaudRate = 9600; - this.serBMS.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.serBMS.dev = null; - this.serBMS.Dock = System.Windows.Forms.DockStyle.Top; - this.serBMS.Location = new System.Drawing.Point(3, 17); - this.serBMS.Name = "serBMS"; - this.serBMS.PortName = "COM40"; - this.serBMS.Size = new System.Drawing.Size(1134, 84); - this.serBMS.TabIndex = 1; - // - // serCAL - // - this.serCAL.BaudRate = 9600; - this.serCAL.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.serCAL.dev = null; - this.serCAL.Dock = System.Windows.Forms.DockStyle.Top; - this.serCAL.Location = new System.Drawing.Point(3, 17); - this.serCAL.Name = "serCAL"; - this.serCAL.PortName = "COM50"; - this.serCAL.Size = new System.Drawing.Size(776, 84); - this.serCAL.TabIndex = 1; - // - // toolStripButton4 - // - this.toolStripButton4.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton4.Image"))); - this.toolStripButton4.ImageTransparentColor = System.Drawing.Color.Magenta; - this.toolStripButton4.Name = "toolStripButton4"; - this.toolStripButton4.Size = new System.Drawing.Size(117, 22); - this.toolStripButton4.Text = "Change Rotation"; - this.toolStripButton4.Click += new System.EventHandler(this.toolStripButton4_Click); - // - // toolStripButton5 - // - this.toolStripButton5.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton5.Image"))); - this.toolStripButton5.ImageTransparentColor = System.Drawing.Color.Magenta; - this.toolStripButton5.Name = "toolStripButton5"; - this.toolStripButton5.Size = new System.Drawing.Size(87, 22); - this.toolStripButton5.Text = "SetPosition"; - this.toolStripButton5.Click += new System.EventHandler(this.toolStripButton5_Click); + this.button3.Location = new System.Drawing.Point(246, 339); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(86, 38); + this.button3.TabIndex = 15; + this.button3.Tag = "--"; + this.button3.Text = "Pick Off"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); // // fMain // @@ -1706,6 +1732,8 @@ namespace AGVEmulator private ToolStripButton toolStripButton3; private ToolStripButton toolStripButton4; private ToolStripButton toolStripButton5; + private Button button3; + private Button button2; } } diff --git a/AGVEmulator/fMain.cs b/AGVEmulator/fMain.cs index b5cf7e8..352e443 100644 --- a/AGVEmulator/fMain.cs +++ b/AGVEmulator/fMain.cs @@ -351,8 +351,8 @@ namespace AGVEmulator private void AgvViewer1_TagTouched(object sender, UC.AgvViewer.TagArgs e) { logAGV.Add($"tag touch:{e.Data}"); - numericUpDown1.Text = e.Data;// decimal.Parse(e.Data); - button18.PerformClick(); + //numericUpDown1.Text = e.Data;// decimal.Parse(e.Data); + //button18.PerformClick(); UpdateVisualAgvPosition(e.Data); } @@ -559,7 +559,7 @@ namespace AGVEmulator -private void UpdateVisualAGV() + private void UpdateVisualAGV() { if (_visualAgv != null) { @@ -619,18 +619,18 @@ private void UpdateVisualAGV() // Send Tag if (node.Id != numericUpDown1.Text) { - if (int.TryParse(node.Id, out int tag)) - { - AGV.SendTag(node.Id); - numericUpDown1.Text = node.Id; + if (int.TryParse(node.Id, out int tag)) + { + //AGV.SendTag(node.Id); + numericUpDown1.Text = node.Id; - // Snap to node - _currentPosF = node.Position; - _visualAgv.CurrentPosition = node.Position; + // Snap to node + _currentPosF = node.Position; + _visualAgv.CurrentPosition = node.Position; - // Decide Next Move (Turn/Straight) - DecideNextMove(node); - } + // Decide Next Move (Turn/Straight) + DecideNextMove(node); + } } } } @@ -793,7 +793,7 @@ private void UpdateVisualAGV() private void toolStripButton3_Click(object sender, EventArgs e) { var file = @"C:\Data\Amkor\AGV4\route\NewMap.agvmap"; - if(System.IO.File.Exists(file)==false) + if (System.IO.File.Exists(file) == false) { var od = new OpenFileDialog(); od.Filter = "json|*.json"; @@ -916,6 +916,19 @@ private void UpdateVisualAGV() } } } + + private void button2_Click(object sender, EventArgs e) + { + var target = (byte)nudIDAgv.Value; + this.XBE.SendPickOn(target); + } + + private void button3_Click(object sender, EventArgs e) + { + var target = (byte)nudIDAgv.Value; + this.XBE.SendPickOff(target); + } + private void trbT2_Scroll(object sender, EventArgs e) { Temp2 = (UInt16)trbT2.Value; diff --git a/AGVEmulator/icons8-robot-80.ico b/AGVEmulator/icons8-robot-80.ico new file mode 100644 index 0000000..a84bc77 Binary files /dev/null and b/AGVEmulator/icons8-robot-80.ico differ diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index da3bcf1..fe97a93 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -78,6 +78,8 @@ namespace AGVNavigationCore.Controls // 노드 라벨 그리기 (가장 나중 - 선이 텍스트를 가리지 않게) DrawNodeLabels(g); DrawLabels(g); // 추가: 텍스트 라벨 + + } finally { @@ -93,6 +95,12 @@ namespace AGVNavigationCore.Controls { DrawSyncScreen(g); } + + //예측문자는 디버깅시에만 표시한다. + if (string.IsNullOrEmpty(PredictMessage) == false && System.Diagnostics.Debugger.IsAttached) + { + g.DrawString(this.PredictMessage, this.Font, Brushes.White, 10, 10); + } } private void DrawSyncScreen(Graphics g) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs index 798e760..305c3ea 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs @@ -478,6 +478,17 @@ namespace AGVNavigationCore.Controls } } + /// + /// 상세경로가 설정되어있는가? + /// + /// + public bool HasPath() + { + if (_currentPath == null) return false; + if (_currentPath.DetailedPath == null) return false; + return _currentPath.DetailedPath.Any(); + } + /// /// 모든 경로 목록 (다중 AGV 경로 표시용) /// diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs index 1d84d99..35583af 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs @@ -19,7 +19,7 @@ namespace AGVNavigationCore.Models [Description("표시할 텍스트입니다.")] public string Text { get; set; } = ""; - public StationType StationType { get; set; } + public StationType StationType { get; set; } [Browsable(false)] public bool CanDocking @@ -103,7 +103,7 @@ namespace AGVNavigationCore.Models { Type = NodeType.Normal; } - + public MapNode(string nodeId, Point position, StationType type) : base(nodeId, position) { Type = NodeType.Normal; @@ -152,6 +152,18 @@ namespace AGVNavigationCore.Models return $"RFID:{RfidId}(NODE:{Id}): AS:{AliasName} ({Type}) at ({Position.X}, {Position.Y})"; } + /// + /// RFID(*ID) + /// + public string ID2 + { + get + { + if (HasRfid()) return $"{this.RfidId:0000}(*{this.Id})"; + else return $"(*{this.Id})"; + } + } + public bool IsNavigationNode() { // 이제 MapNode는 항상 내비게이션 노드임 (Label, Image 분리됨) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/NodeBase.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/NodeBase.cs index 86fac5b..f1928ad 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/NodeBase.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/NodeBase.cs @@ -40,13 +40,12 @@ namespace AGVNavigationCore.Models [JsonIgnore] public bool IsSelected { get; set; } = false; - [Browsable(false)] [JsonIgnore] public bool IsHovered { get; set; } = false; - + public NodeBase() { diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs index 0cb21db..49f8208 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs @@ -83,9 +83,9 @@ namespace AGVNavigationCore.Models // 에뮬레이터용 추가 속성 public double Angle { get; set; } = 0; // 0 = Right, 90 = Down, 180 = Left, 270 = Up (Standard Math) - // But AGV Direction: Forward usually means "Front of AGV". - // Let's assume Angle is the orientation of the AGV in degrees. - + // But AGV Direction: Forward usually means "Front of AGV". + // Let's assume Angle is the orientation of the AGV in degrees. + public bool IsStopMarkOn { get; set; } = false; #endregion @@ -143,6 +143,11 @@ namespace AGVNavigationCore.Models /// public AGVPathResult CurrentPath => _currentPath; + public void ClearPath() + { + _currentPath = null; + } + /// /// 현재 노드 ID /// @@ -153,6 +158,18 @@ namespace AGVNavigationCore.Models /// public string CurrentNodeId => _currentNode?.Id; + /// + /// 현재노드의 RFID(id)값을 표시합니다 없는경우 (X)가 표시됩니다 + /// + public string CurrentNodeID2 + { + get + { + if (_currentNode == null) return "(X)"; + return _currentNode.ID2; + } + } + /// /// 이전 위치 /// @@ -282,7 +299,7 @@ namespace AGVNavigationCore.Models if (_currentNode == null) return false; if (_currentPath == null) return false; var 미완료된처음노드 = _currentPath.DetailedPath.Where(t => t.IsPass == false).OrderBy(t => t.seq).FirstOrDefault(); - if (미완료된처음노드 == null) return false; + if (미완료된처음노드 == null) return false; 미완료된처음노드.IsPass = true; Console.WriteLine($"미완료된처음노드를 true러치합니다"); return true; @@ -295,6 +312,7 @@ namespace AGVNavigationCore.Models /// 다음에 수행할 모터/마그넷/속도 명령 public AGVCommand Predict() { + // 1. 위치 미확정 상태 (RFID 2개 미만 감지) if (!_isPositionConfirmed) { @@ -311,12 +329,17 @@ namespace AGVNavigationCore.Models // 2. 위치 확정됨 + 경로 없음 → 정지 (목적지 미설정 상태) if (_currentPath == null || (_currentPath.DetailedPath?.Count ?? 0) < 1) { + var curpos = "알수없음"; + if (_currentNode != null) + { + curpos = _currentNode.HasRfid() ? $"RFID #{_currentNode.RfidId} (*{_currentNode.Id})" : $"(*{_currentNode.Id})"; + } return new AGVCommand( MotorCommand.Stop, MagnetPosition.S, SpeedLevel.L, eAGVCommandReason.NoPath, - $"위치 확정 완료 (목적지 미설정) - 현재:{_currentNode?.Id ?? "알수없음"}" + $"(목적지 미설정) - 현재={curpos}" ); } @@ -325,6 +348,9 @@ namespace AGVNavigationCore.Models if (_currentPath.DetailedPath.Where(t => t.seq < lastNode.seq && t.IsPass == false).Any() == false) { // 마지막 노드에 도착했는지 확인 (현재 노드가 마지막 노드와 같은지) + + + if (_currentNode != null && _currentNode.Id == lastNode.NodeId) { if (lastNode.IsPass) //이미완료되었다. @@ -334,7 +360,7 @@ namespace AGVNavigationCore.Models MagnetPosition.S, SpeedLevel.L, eAGVCommandReason.Complete, - $"목적지 도착 - 최종:{_currentNode?.Id ?? "알수없음"}" + $"목적지 도착 - 최종:{CurrentNodeID2}" ); } else @@ -345,7 +371,7 @@ namespace AGVNavigationCore.Models MagnetPosition.S, SpeedLevel.L, eAGVCommandReason.MarkStop, - $"목적지 도착 전(MarkStop) - 최종:{_currentNode?.Id ?? "알수없음"}" + $"목적지 도착 전(MarkStop) - 최종:{CurrentNodeID2}" ); } @@ -361,7 +387,7 @@ namespace AGVNavigationCore.Models MagnetPosition.S, SpeedLevel.L, eAGVCommandReason.PathOut, - $"(재탐색요청)경로이탈 현재위치:{_currentNode.Id}" + $"(재탐색요청)경로이탈 현재위치:{CurrentNodeID2}" ); } @@ -403,6 +429,18 @@ namespace AGVNavigationCore.Models #region Public Methods - 경로 실행 + /// + /// 경로가 설정되어있는지? + /// + /// + public bool HasPath() + { + if (_currentPath == null) return false; + if (_currentPath.DetailedPath == null) return false; + return _currentPath.DetailedPath.Any(); + + } + /// /// 경로 설정 (실제 AGV 및 시뮬레이터에서 사용) /// @@ -411,6 +449,9 @@ namespace AGVNavigationCore.Models { if (path == null) { + _currentPath = null; + _remainingNodes.Clear();// = null; + _currentNodeIndex = 0; OnError("경로가 null입니다."); return; } @@ -612,7 +653,7 @@ namespace AGVNavigationCore.Models PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode)); } - + #endregion diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs index 4578313..6f146e5 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs @@ -322,7 +322,7 @@ namespace AGVNavigationCore.PathFinding.Core } return Path?.Select(n => n.Id).ToList() ?? new List(); } - + /// /// 문자열 표현 /// diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs index 5c5cb71..cda4182 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs @@ -123,6 +123,8 @@ namespace AGVNavigationCore.PathFinding.Planning if (RequiresSpecialAction) result += $" [특수동작:{SpecialActionDescription}]"; + if (IsPass) result += "(O)"; + return result; } } diff --git a/Cs_HMI/Project/Class/Lang.cs b/Cs_HMI/Project/Class/Lang.cs index addf803..f619b08 100644 --- a/Cs_HMI/Project/Class/Lang.cs +++ b/Cs_HMI/Project/Class/Lang.cs @@ -55,10 +55,10 @@ namespace Project public static string 상차 = "상차"; public static string 하차 = "하차"; - public static string 홈검색완료 = "홈 검색 완료"; + public static string 이동완료 = "이동 완료"; public static string 오버로드감지 = "오버로드 감지"; - public static string 상차작업을시작합니다 { get { return string.Format("{0} 작업을 시작 합니다",상차); } } - public static string 하차작업을시작합니다 { get { return string.Format("{0} 작업을 시작 합니다", 하차); } } + public static string 진출을시작합니다 { get { return "진출 시작"; } } + public static string 진입을작합니다 { get { return "진입 시작"; } } public static string 안전커버를내려주세요 = "안전 커버를 내려 주세요"; public static string 안전커버를올려주세요 = "안전 커버를 올려 주세요"; public static string 안전커버를올리면상차가완료됩니다 = "안전 커버를 올리면 상차가 완료 됩니다"; diff --git a/Cs_HMI/Project/Device/Xbee.cs b/Cs_HMI/Project/Device/Xbee.cs index bcd5993..06760a7 100644 --- a/Cs_HMI/Project/Device/Xbee.cs +++ b/Cs_HMI/Project/Device/Xbee.cs @@ -14,6 +14,36 @@ using System.Windows.Forms; namespace Project.Device { + public enum eDocStep : byte + { + NotSet = 0, + + /// + /// 투입준비됨 + /// + InReady = 10, + + /// + /// 투입완료 + /// + InComplete = 11, + + /// + /// 투입(진행중) + /// + InIng = 12, + + /// + /// 진출완료 + /// + OutComplete = 21, + + /// + /// 진출중 + /// + OutIng = 22, + } + public class Xbee : SerialPort, arDev.ISerialComm { public string buffer = string.Empty; @@ -166,17 +196,10 @@ namespace Project.Device Send(packet); } - public bool BufferInReady { get; set; } - public bool BufferInComplete { get; set; } - public bool BufferOutComplete { get; set; } - public bool BufferReadyError { get; set; } - - public bool LoaderInComplete { get; set; } - public bool LoaderOutComplete { get; set; } - public bool UnloaderInComplete { get; set; } - public bool UnloaderOutComplete { get; set; } - public bool CleanerInComplete { get; set; } - public bool CleanerOutComplete { get; set; } + public eDocStep StepBuffer { get; set; } = eDocStep.NotSet; + public eDocStep StepUnloader { get; set; } = eDocStep.NotSet; + public eDocStep StepLoader { get; set; } = eDocStep.NotSet; + public eDocStep StepCleaner { get; set; } = eDocStep.NotSet; ManualResetEvent sendlock = new ManualResetEvent(true); diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs index e7c0fe9..5100d7e 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN.cs @@ -52,6 +52,7 @@ namespace Project VAR.TIME.Update(eVarTime.ReadyStart); else VAR.TIME.Update(eVarTime.RunStart); + VAR.I32[eVarInt32.PathValidationError] = 0; } //자동모드에서 대기상태 (추가동작없음) @@ -90,7 +91,12 @@ namespace Project } //현재위치를 모르는 상태라면 이동하여 현재 위치를 찾는다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return; + if (_SM_RUN_POSCHK(isFirst, stepTime) == false) + { + PUB.Result.result_message = "현재 위치 확인 중"; + PUB.Result.result_progressmax = 0; + return; + } //나머지 상황체크 switch (PUB.sm.RunStep) @@ -109,33 +115,46 @@ namespace Project var target = PUB._virtualAGV.TargetNode; PUB.log.Add($"목적지({target.RfidId}) 도착완료 타입:{target.Type}, 출발지:{PUB._virtualAGV.StartNode.RfidId}"); + PUB.XBE.StepLoader = Device.eDocStep.NotSet; + PUB.XBE.StepCleaner = Device.eDocStep.NotSet; + PUB.XBE.StepUnloader = Device.eDocStep.NotSet; + PUB.XBE.StepBuffer = Device.eDocStep.NotSet; + switch (target.StationType) { case AGVNavigationCore.Models.StationType.Buffer: - //현재위치가 마지막경로의 NODEID와 일치해야한다 - if (PUB._virtualAGV.CurrentPath == null) - { - PUB.log.AddAT("목적지 버퍼이동완료 했지만 상세경로가 없습니다"); - PUB.XBE.BufferInComplete = false; - PUB.XBE.BufferOutComplete = false; - break; - } var lastPath = PUB._virtualAGV.CurrentPath.DetailedPath.LastOrDefault(); if (lastPath.NodeId.Equals(PUB._virtualAGV.CurrentNode.Id)) { //버퍼진입전 노드에 도착완료했따 - PUB.XBE.BufferInReady = true; - PUB.XBE.BufferReadyError = false; + PUB.XBE.StepBuffer = Device.eDocStep.InReady; } else { //마지막위치가 아닌 다른 위치에 있으니 버퍼 작업을 할 수없다 - PUB.log.AddAT("목적지 버퍼이동완료 했지만 마지막 노드가 아닙니다"); - PUB.XBE.BufferInReady = false; - PUB.XBE.BufferReadyError = true; + PUB.XBE.StepBuffer = Device.eDocStep.NotSet; + PUB.log.AddE($"목적지가 버퍼이나 노드가 불일치 한다 오류사항"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); } - PUB.XBE.BufferInComplete = false; - PUB.XBE.BufferOutComplete = false; + break; + case AGVNavigationCore.Models.StationType.Charger: + + break; + + case AGVNavigationCore.Models.StationType.Loader: + PUB.XBE.StepLoader = Device.eDocStep.InReady; + break; + + case AGVNavigationCore.Models.StationType.Clearner: + PUB.XBE.StepCleaner = Device.eDocStep.InReady; + break; + + case AGVNavigationCore.Models.StationType.UnLoader: + PUB.XBE.StepUnloader = Device.eDocStep.InReady; + break; + + default: + PUB.log.Add($"정의되지 않은 스테이션 입니다({target.StationType}) "); break; } @@ -144,7 +163,7 @@ namespace Project } break; - case ERunStep.MARKSTOPB: //후진방향으로 마크스탑 + case ERunStep.MARKSTOPB: //후진방향으로 마크스탑 case ERunStep.MARKSTOPF: //전진방향으로 마크스탑 //이동중이지 않다면 먼저 이동을 진행한다 @@ -169,10 +188,6 @@ namespace Project } else if (_SM_RUN_GOCHARGE(runStepisFirst, PUB.sm.GetRunSteptime)) { - //230601 - //if (PUB.Result != null && PUB.sm != null) - // EEMStatus.AddEEDBSQL(PUB.sm.Step, PUB.sm.RunStep.ToString(), PUB.Result.TargetPos.ToString()); - PUB.Speak(Lang.충전을시작합니다); PUB.sm.SetNewRunStep(ERunStep.CHARGECHECK); return; @@ -207,16 +222,7 @@ namespace Project //충전상태가 활성화되었으므로 대기상태로 전환한다 PUB.sm.ClearRunStep(); - //충전기위치에서 OFF를 한 경우이다 - if (PUB.Result.CurrentPos != ePosition.CHARGE || PUB.Result.TargetPos != ePosition.CHARGE) - { - PUB.Result.CurrentPos = ePosition.NONE; - } - else - { - PUB.Result.TargetPos = ePosition.QC; - } - PUB.Result.CurrentPosCW = "1"; + //대기상태로 전환 PUB.sm.SetNewRunStep(ERunStep.READY); PUB.log.AddAT("충전 해제로 대기상태로 전환 합니다"); } @@ -228,8 +234,7 @@ namespace Project PUB.Speak(Lang.버퍼도킹해제완료); //도킹완료상태를 업데이트한다. - PUB.XBE.LoaderInComplete = false; - PUB.XBE.LoaderOutComplete = true; + PUB.XBE.StepLoader = Device.eDocStep.OutComplete; //대기상태로 전환 PUB.sm.SetNewRunStep(ERunStep.READY); @@ -243,23 +248,10 @@ namespace Project PUB.Speak(Lang.버퍼도킹이완료되었습니다); //도킹완료상태를 업데이트한다. - PUB.XBE.LoaderInComplete = true; + PUB.XBE.StepLoader = Device.eDocStep.InComplete; - //로더아웃으로 자동 진행하지 않음 (ACS 명령 대기) - if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff) - { - PUB.sm.SetNewRunStep(ERunStep.READY); - PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; // Command consumed - } - else - { - // Legacy behavior or Goto command: Auto-exit? - // User said separation is key. Let's Stop here too or keep legacy for GOTO? - // Assuming GOTO might rely on this, but safer to STOP if we want strict separation. - // However, let's keep legacy behavior for GOTO if possible, but for PickOn/Off we STOP. - PUB.sm.ClearRunStep(); - PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT); - } + //대기상태로 전환 + PUB.sm.SetNewRunStep(ERunStep.READY); return; } break; @@ -270,8 +262,7 @@ namespace Project PUB.Speak(Lang.버퍼도킹해제완료); //도킹완료상태를 업데이트한다. - PUB.XBE.UnloaderInComplete = false; - PUB.XBE.UnloaderOutComplete = true; + PUB.XBE.StepUnloader = Device.eDocStep.OutComplete; //대기상태로 전환 PUB.sm.SetNewRunStep(ERunStep.READY); @@ -285,19 +276,10 @@ namespace Project PUB.Speak(Lang.버퍼도킹이완료되었습니다); //도킹완료상태를 업데이트한다. - PUB.XBE.UnloaderInComplete = true; + PUB.XBE.StepUnloader = Device.eDocStep.InComplete; - //언로더아웃으로 자동 진행하지 않음 - if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff) - { - PUB.sm.SetNewRunStep(ERunStep.READY); - PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; - } - else - { - PUB.sm.ClearRunStep(); - PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT); - } + //대기상태로 전환 + PUB.sm.SetNewRunStep(ERunStep.READY); return; } break; @@ -308,8 +290,7 @@ namespace Project PUB.Speak(Lang.버퍼도킹해제완료); //도킹완료상태를 업데이트한다. - PUB.XBE.CleanerInComplete = false; - PUB.XBE.CleanerOutComplete = true; + PUB.XBE.StepCleaner = Device.eDocStep.OutComplete; //대기상태로 전환 PUB.sm.SetNewRunStep(ERunStep.READY); @@ -323,19 +304,10 @@ namespace Project PUB.Speak(Lang.버퍼도킹이완료되었습니다); //도킹완료상태를 업데이트한다. - PUB.XBE.CleanerInComplete = true; + PUB.XBE.StepCleaner = Device.eDocStep.InComplete; //클리너아웃으로 자동 진행하지 않음 - if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff) - { - PUB.sm.SetNewRunStep(ERunStep.READY); - PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; - } - else - { - PUB.sm.ClearRunStep(); - PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT); - } + PUB.sm.SetNewRunStep(ERunStep.READY); return; } break; @@ -346,13 +318,13 @@ namespace Project PUB.Speak(Lang.버퍼도킹해제완료); //도킹완료상태를 업데이트한다. - PUB.XBE.BufferInComplete = false; - PUB.XBE.BufferOutComplete = true; + PUB.XBE.StepBuffer = Device.eDocStep.OutComplete; //대기상태로 전환 PUB.sm.SetNewRunStep(ERunStep.READY); return; } + else PUB.XBE.StepBuffer = Device.eDocStep.OutIng; break; case ERunStep.BUFFER_IN: //버퍼도킹 @@ -361,28 +333,16 @@ namespace Project PUB.Speak(Lang.버퍼도킹이완료되었습니다); //도킹완료상태를 업데이트한다. - PUB.XBE.BufferInComplete = true; + PUB.XBE.StepBuffer = Device.eDocStep.InComplete; - //버퍼아웃으로 자동 진행하지 않음 - if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff) - { - PUB.sm.SetNewRunStep(ERunStep.READY); - PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; - } - else - { - PUB.sm.ClearRunStep(); - PUB.sm.SetNewRunStep(ERunStep.BUFFER_OUT); - } + //대기상태로 전환 + PUB.sm.SetNewRunStep(ERunStep.READY); return; } + else PUB.XBE.StepBuffer = Device.eDocStep.InIng; break; - - } - - } }//cvass diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs index 3df0ffa..e1a947f 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_IN.cs @@ -11,26 +11,17 @@ namespace Project { public partial class fMain { - /// - /// 버퍼도킹 - /// - /// - /// - /// - public Boolean _SM_RUN_BUFFER_IN(bool isFirst, TimeSpan stepTime) + public Boolean _SM_RUN_BUFFER_IN(bool isFirst, TimeSpan seqtime) { var funcname = "_SM_RUN_BUFFER_IN"; var idx = 1; //충전 상태가 OFF되어야 동작하게한다 - if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false; + if (_SM_RUN_CHGOFF(isFirst, seqtime) == false) return false; //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - /* * 버퍼IN시퀀스 * 1. 회전이 진행되지 않았다면 회전을 진행한다. @@ -41,8 +32,8 @@ namespace Project if (PUB.sm.RunStepSeq == idx++) { - PUB.log.Add("버퍼도킹시작"); - PUB.Speak(Lang.하차작업을시작합니다); + PUB.log.Add($"[{funcname}] 버퍼진입시작({PUB.NextWorkCmd}) Turn:{PUB._virtualAGV.Turn}"); + PUB.Speak(Lang.진입을작합니다); PUB.sm.UpdateRunStepSeq(); return false; } @@ -70,9 +61,9 @@ namespace Project { if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90) { - VAR.TIME.Update(eVarTime.LastTurnCommandTime); PUB.AGV.AGVMoveLeft180Turn(); PUB.log.Add("AGV Left Turn"); + VAR.TIME.Update(eVarTime.LastTurnCommandTime); PUB.sm.UpdateRunStepSeq(); } else PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다. @@ -80,59 +71,35 @@ namespace Project } else if (PUB.sm.RunStepSeq == idx++) { - if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90) + //왼쪽턴이 완료되지 않은경우 + if (PUB.AGV.TurnInformation.State != arDev.eNarumiTurn.Left) { - //5초이내에 턴이동 상태가 확인되어야 한다. - if (PUB.AGV.system1.agv_run == false) + //움직임 확인을 위해 3초간은 검증을 유예한다 + if (PUB.AGV.TurnInformation.Runtime.TotalSeconds < 3) return false; + + //턴 이동 상태가 확인되어야 한다. + var overtime = 10; + if (PUB.AGV.TurnInformation.Runtime.TotalSeconds > overtime) { - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 5) - { - //5초동안 AGV까 움직이지 않았다면 오류 처리한다. - PUB.AGV.AGVMoveStop("5초이내 턴 감지 안됨"); - PUB.log.AddE("5초이내 턴 감지 안됨"); - PUB.sm.SetNewRunStep(ERunStep.ERROR); - return false; - } + //5초동안 AGV까 움직이지 않았다면 오류 처리한다. + PUB.AGV.AGVMoveStop($"[bufferin] {overtime}초이내 턴 감지 안됨"); + PUB.log.AddE($"[{funcname}] {overtime}초이내 턴 감지 안됨"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); return false; } - } - PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다. - return false; - } - else if (PUB.sm.RunStepSeq == idx++) - { - //턴이완료되었느닞 확인한다. - if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90) - { - //10초 이상 가동하고 있다면 문제이다 - if (PUB.AGV.system1.agv_run == true) - { - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 15) - { - PUB.AGV.AGVMoveStop("15초이내 턴 이 완료되지 않음"); - PUB.log.AddE("5초이내 턴 완료 확인 안됨"); - PUB.sm.SetNewRunStep(ERunStep.ERROR); - return false; - } - return false; - } - else PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.L90; - } - PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다. - return false; - } - else if (PUB.sm.RunStepSeq == idx++) - { - // [PickOn/PickOff] 초기 리프트 동작 - var liftCmd = arDev.Narumi.LiftCommand.DN; - if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff) - { - liftCmd = arDev.Narumi.LiftCommand.UP; + return false; } + PUB.log.Add($"[{funcname}] Turn(left) 완료"); + PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다. + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + //버퍼에들어갈때에는 가져다 놓을때도 가지러 갈때에도 리프트는 내려서 들어간다 + var liftCmd = arDev.Narumi.LiftCommand.DN; PUB.AGV.LiftControl(liftCmd); + PUB.log.Add($"[{funcname}] 리프트를 내립니다"); VAR.TIME.Update(eVarTime.LastTurnCommandTime); PUB.sm.UpdateRunStepSeq(); return false; @@ -140,12 +107,32 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //리프트 센서 확인 - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 10) - { - // Timebound check - } - PUB.log.Add("리프트 동작 확인 완료"); + if (PUB.AGV.signal1.lift_down == false) + { + if (seqtime.TotalSeconds > 20) + { + PUB.log.AddAT($"[{funcname}] 리프트가 내려가지 않아 1회 재시도 합니다"); + PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN); + //재시도를 했으니 다음으로 진행하게한다 + } + else return false; + } + PUB.sm.UpdateRunStepSeq(); + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + //리프트 센서 확인 + if (PUB.AGV.signal1.lift_down == false) + { + if (seqtime.TotalSeconds > 20) + { + PUB.log.AddE($"[{funcname}] 리프트가 내려가지 않습니다"); + PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } PUB.sm.UpdateRunStepSeq(); return false; } @@ -160,26 +147,32 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //저속이동 (후진 진입) - var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Backward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); - if (moveset == false) + + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) { - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 10) + if (ret >= arDev.eNarumiCommandResult.Error) { PUB.AGV.AGVMoveStop(funcname); - PUB.log.AddE("AGV속도설정이 완료되지 않았습니다"); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); PUB.sm.SetNewRunStep(ERunStep.ERROR); - return false; } + return false; } + + //후진이동을한다 PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward); + + //마크스탑으로 이동 PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop); + VAR.TIME.Update(eVarTime.LastTurnCommandTime); PUB.sm.UpdateRunStepSeq(); return false; @@ -193,7 +186,7 @@ namespace Project if (ts.TotalSeconds > 3) { PUB.AGV.AGVMoveStop(funcname); - PUB.log.AddE("MARK STOP신호가 확인되지 않습니다"); + PUB.log.AddE($"[{funcname}] MARK STOP신호가 확인되지 않습니다"); PUB.sm.SetNewRunStep(ERunStep.ERROR); return false; } @@ -211,7 +204,7 @@ namespace Project if (ts.TotalSeconds > 10) { PUB.AGV.AGVMoveStop(funcname); - PUB.log.AddE("AGV가 멈추지 않아 강제종료 합니다"); + PUB.log.AddE($"[{funcname}] AGV가 멈추지 않아 강제종료 합니다"); PUB.sm.SetNewRunStep(ERunStep.ERROR); return false; } @@ -223,45 +216,22 @@ namespace Project } else if (PUB.sm.RunStepSeq == idx++) { - // [Action] 진입 완료 후 리프트 동작 (Pick/Drop) - PUB.log.Add("버퍼 진입 완료. 작업 수행(Lift Pick/Drop)"); - - var liftCmd = arDev.Narumi.LiftCommand.UP; - if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff) - { - liftCmd = arDev.Narumi.LiftCommand.DN; - } - - PUB.AGV.LiftControl(liftCmd); - VAR.TIME.Update(eVarTime.LastTurnCommandTime); + if (seqtime.TotalSeconds < 2) return false; + PUB.log.Add($"[{funcname}] 작업({PUB.NextWorkCmd}) 완료. 대기 상태로 전환 (퇴출 명령 대기)"); PUB.sm.UpdateRunStepSeq(); return false; } else if (PUB.sm.RunStepSeq == idx++) { - // 리프트 동작 대기 - // TODO: 실제 센서 확인 로직 추가 필요 - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds < 2) return false; - - PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환 (퇴출 명령 대기)"); - PUB.sm.UpdateRunStepSeq(); - return false; - } - else if (PUB.sm.RunStepSeq == idx++) - { - //완료되었다. (ACS에 보내야함) - PUB.log.Add("버퍼 진입 및 작업 완료"); + //완료되었다. + PUB.log.Add($"[{funcname}] 버퍼 진입 및 작업 완료"); PUB.sm.UpdateRunStepSeq(); return false; } // 작업을 마치고 설비 안에 멈춰있는 상태. // ACS가 이 상태를 확인하고 NextWorkCmd로 퇴출(Out) 명령을 보내야 함. - PUB.AddEEDB($"버퍼작업완료({PUB.Result.TargetPos})"); - return true; - - PUB.AddEEDB($"버퍼투입완료({PUB.Result.TargetPos})"); + PUB.AddEEDB($"[{funcname}] 버퍼작업완료({PUB.Result.TargetPos})"); return true; } } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs index 6de2b83..6e7bf8f 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_BUFFER_OUT.cs @@ -11,30 +11,97 @@ namespace Project { public partial class fMain { - - public Boolean _SM_RUN_BUFFER_OUT(bool isFirst, TimeSpan stepTime) + public Boolean _SM_RUN_BUFFER_OUT(bool isFirst, TimeSpan seqtime) { + var funcname = "_SM_RUN_BUFFER_IN"; + var idx = 1; //충전 상태가 OFF되어야 동작하게한다 - if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false; + if (_SM_RUN_CHGOFF(isFirst, seqtime) == false) return false; //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; + /* + * 버퍼 OUT 시퀀스 + * 1-1. PickOn 라면 리프트를 Up 한다 + * 1-2. PickOff 라면 리프트를 Down 한다 + * 2.전진-저속-마크다운 + * 3.Turn-Right-180 + */ - var idx = 1; if (PUB.sm.RunStepSeq == idx++) + { + PUB.log.Add($"[{funcname}] 버퍼진출시작({PUB.NextWorkCmd}) Turn:{PUB._virtualAGV.Turn}"); + PUB.Speak(Lang.진출을시작합니다); + PUB.sm.UpdateRunStepSeq(); + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + arDev.Narumi.LiftCommand lift = PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn ? arDev.Narumi.LiftCommand.UP : arDev.Narumi.LiftCommand.DN; + PUB.log.Add($"[{funcname}] 리프트제어 {lift}"); + PUB.AGV.LiftControl(lift); + PUB.sm.UpdateRunStepSeq(); + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + //리프트 센서 확인 + var checksensor = PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn ? PUB.AGV.signal1.lift_up : PUB.AGV.signal1.lift_down; + if (checksensor == false) + { + if (seqtime.TotalSeconds > 20) + { + PUB.log.AddAT($"[{funcname}] 리프트가 완료되지 않아 1회 재시도 합니다"); + arDev.Narumi.LiftCommand lift = PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn ? arDev.Narumi.LiftCommand.UP : arDev.Narumi.LiftCommand.DN; + PUB.AGV.LiftControl(lift); + } + else return false; + } + PUB.sm.UpdateRunStepSeq(); + return false; + } + else if (PUB.sm.RunStepSeq == idx++) + { + //리프트 센서 확인 + var checksensor = PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn ? PUB.AGV.signal1.lift_up : PUB.AGV.signal1.lift_down; + if (checksensor == false) + { + if (seqtime.TotalSeconds > 20) + { + PUB.log.AddE($"[{funcname}] 리프트가 동작하지 않습니다"); + PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + PUB.sm.UpdateRunStepSeq(); + return false; + } + else if (PUB.sm.RunStepSeq == idx++) { //빈 상태로 아웃해야한다. - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) + { + if (ret >= arDev.eNarumiCommandResult.Error) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + + PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_IN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_IN.cs index 2981a69..38e4e01 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_IN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_IN.cs @@ -25,9 +25,6 @@ namespace Project //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - /* * 클리너 IN 시퀀스 (버퍼 복사 - 턴 제거) * 1. LIFT DOWN @@ -78,24 +75,25 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //저속이동 (후진 진입) - var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Backward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); - if (moveset == false) + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) { - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 10) + if (ret >= arDev.eNarumiCommandResult.Error) { PUB.AGV.AGVMoveStop(funcname); - PUB.log.AddE("AGV속도설정이 완료되지 않았습니다"); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); PUB.sm.SetNewRunStep(ERunStep.ERROR); - return false; } + return false; } + PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward); PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop); VAR.TIME.Update(eVarTime.LastTurnCommandTime); diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_OUT.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_OUT.cs index 2a743ff..a92c947 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_OUT.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_CLEANER_OUT.cs @@ -16,26 +16,37 @@ namespace Project /// public Boolean _SM_RUN_CLEANER_OUT(bool isFirst, TimeSpan stepTime) { + var funcname = "CLEANEROUT"; + //충전 상태가 OFF되어야 동작하게한다 if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false; //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - var idx = 1; if (PUB.sm.RunStepSeq == idx++) { //빈 상태로 아웃해야한다. - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) + { + if (ret >= arDev.eNarumiCommandResult.Error) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOCHARGE.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOCHARGE.cs index cd1e4a9..4fdee1d 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOCHARGE.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOCHARGE.cs @@ -14,11 +14,11 @@ namespace Project DateTime tm_gocharge_command = DateTime.Now; public Boolean _SM_RUN_GOCHARGE(bool isFirst, TimeSpan stepTime) { + var funcname = "GOCHARGE"; if (runStepisFirst) { //홈을 찾도록 항상 위치를 지워버리자 PUB.Result.CurrentPos = ePosition.NONE; - PUB.Result.CurrentPos = ePosition.NONE; } //HW 연결오류 @@ -57,13 +57,7 @@ namespace Project return false; } - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) - { - PUB.Result.result_message = "충전기 검색 전 현재 위치 검색"; - PUB.Result.result_progressmax = 0; - return false; - } + //충전작업진행상태 //220629 //if (PUB.flag.get(EFlag.FLAG_GO_CHAGER_TEMP) == false) @@ -119,17 +113,26 @@ namespace Project { PUB.log.Add($"충전:충전기 검색을 위한 전진시작"); PUB.Speak(Lang.충전기를검색합니다); - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Speed = arDev.Narumi.eMoveSpd.Low, Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 1, }); - PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward); - //PUB.Result.TargetPos = ePosition.CHARGE; - VAR.TIME.Update(eVarTime.ChargeSearch); + if(ret != arDev.eNarumiCommandResult.Success) + { + if(ret >= arDev.eNarumiCommandResult.Error) + { + PUB.log.AddE($"[{funcname}] AGV속도설정 실패"); + PUB.AGV.AGVMoveStop("err"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward); + VAR.TIME.Update(eVarTime.ChargeSearch); PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOHOME.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOHOME.cs index 5a86d6c..0120e07 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOHOME.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOHOME.cs @@ -72,8 +72,8 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //QC까지 모두 완료되었다.(완전히 정차할때까지 기다린다) - PUB.Speak(Lang.홈검색완료, true); - PUB.AddEEDB($"홈검색완료({PUB.Result.TargetPos})"); + PUB.Speak(Lang.이동완료, true); + PUB.AddEEDB($"이동완료({PUB.Result.TargetPos})"); PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs index 6f39bed..21fc0a7 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_GOTO.cs @@ -67,8 +67,8 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //QC까지 모두 완료되었다.(완전히 정차할때까지 기다린다) - PUB.Speak(Lang.홈검색완료, true); - PUB.AddEEDB($"홈검색완료({PUB.Result.TargetPos})"); + PUB.Speak(Lang.이동완료, true); + PUB.AddEEDB($"이동완료({PUB._virtualAGV.TargetNode.ID2})"); PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs index 6200f41..cc5fd0d 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_IN.cs @@ -25,9 +25,7 @@ namespace Project //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - + /* * 로더 IN 시퀀스 (버퍼 복사 - 턴 제거) * 1. LIFT DOWN @@ -84,23 +82,22 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //저속이동 (후진 진입) - var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Backward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); - if (moveset == false) + if (ret != arDev.eNarumiCommandResult.Success) { - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 10) + if (ret >= arDev.eNarumiCommandResult.Error) { PUB.AGV.AGVMoveStop(funcname); PUB.log.AddE("AGV속도설정이 완료되지 않았습니다"); PUB.sm.SetNewRunStep(ERunStep.ERROR); - return false; } + return false; } PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward); PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop); diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs index 2487ec0..1b39129 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_LOADER_OUT.cs @@ -16,27 +16,37 @@ namespace Project /// public Boolean _SM_RUN_LOADER_OUT(bool isFirst, TimeSpan stepTime) { - + var funcname = "LOADEROUT"; //충전 상태가 OFF되어야 동작하게한다 if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false; //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - var idx = 1; if (PUB.sm.RunStepSeq == idx++) { //빈 상태로 아웃해야한다. - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) + { + if (ret >= arDev.eNarumiCommandResult.Error) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + + PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs index e7ae26e..e8468e8 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_POSCHK.cs @@ -16,9 +16,6 @@ namespace Project if (PUB._virtualAGV.CurrentNode != null && PUB._virtualAGV.PrevNode != null) return true; - //최소2개의 노드정보가 있어야 진행가능하므로 prevNode 값이 있는지 확인한다. - - //이동을 하지 않고있다면 전진을 진행한다 if (PUB.AGV.system1.agv_run == false) { @@ -26,14 +23,17 @@ namespace Project if (ts.TotalSeconds > 5) { PUB.log.Add($"현재위치를 몰라 전진 이동 합니다"); - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 1, Speed = arDev.Narumi.eMoveSpd.Low, }); - PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward); + if(ret == arDev.eNarumiCommandResult.Success) + { + PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward); + } VAR.TIME.Update(eVarTime.LastRunCommandTime); } } diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_IN.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_IN.cs index 352f4f5..5ccd751 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_IN.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_IN.cs @@ -25,9 +25,6 @@ namespace Project //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - /* * 언로더 IN 시퀀스 (버퍼 복사 - 턴 제거) * 1. LIFT DOWN @@ -78,24 +75,27 @@ namespace Project else if (PUB.sm.RunStepSeq == idx++) { //저속이동 (후진 진입) - var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Backward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); - if (moveset == false) + + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) { - var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime); - if (ts.TotalSeconds > 10) + if (ret >= arDev.eNarumiCommandResult.Error) { PUB.AGV.AGVMoveStop(funcname); - PUB.log.AddE("AGV속도설정이 완료되지 않았습니다"); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); PUB.sm.SetNewRunStep(ERunStep.ERROR); - return false; } + return false; } + + PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward); PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop); VAR.TIME.Update(eVarTime.LastTurnCommandTime); diff --git a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_OUT.cs b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_OUT.cs index eedea06..e57ab00 100644 --- a/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_OUT.cs +++ b/Cs_HMI/Project/StateMachine/Step/_SM_RUN_UNLOADER_OUT.cs @@ -16,27 +16,37 @@ namespace Project /// public Boolean _SM_RUN_UNLOADER_OUT(bool isFirst, TimeSpan stepTime) { - + var funcname = "UNLOADEROUT"; //충전 상태가 OFF되어야 동작하게한다 if (_SM_RUN_CHGOFF(isFirst, stepTime) == false) return false; //라이더멈춤이 설정되어있다면 음성으로 알려준다 if (CheckLiderStop() == false) return false; - //현재 위치가 결정되어있는지 체크한다 - if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return false; - var idx = 1; if (PUB.sm.RunStepSeq == idx++) { //빈 상태로 아웃해야한다. - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 0, Speed = arDev.Narumi.eMoveSpd.Low, }); + //명령이 실패되었다면 재시도를 한다 + if (ret != arDev.eNarumiCommandResult.Success) + { + if (ret >= arDev.eNarumiCommandResult.Error) + { + PUB.AGV.AGVMoveStop(funcname); + PUB.log.AddE($"[{funcname}] AGV속도설정이 완료되지 않았습니다"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } + return false; + } + + PUB.sm.UpdateRunStepSeq(); return false; } diff --git a/Cs_HMI/Project/StateMachine/Step/_Util.cs b/Cs_HMI/Project/StateMachine/Step/_Util.cs index 64e7d53..9ff11f7 100644 --- a/Cs_HMI/Project/StateMachine/Step/_Util.cs +++ b/Cs_HMI/Project/StateMachine/Step/_Util.cs @@ -34,7 +34,7 @@ namespace Project return false; } - + return true; } @@ -44,7 +44,7 @@ namespace Project /// public bool CheckLiderStop() { - + if (PUB.AGV.system1.stop_by_front_detect == true) { var tsSpeak = DateTime.Now - LastSpeakTime; @@ -67,8 +67,9 @@ namespace Project /// Boolean UpdateMotionPositionForMark(string sender) { - //현재위치를 모르는 상태라면 이동하여 현재 위치를 찾는다 - if (_SM_RUN_POSCHK(false, new TimeSpan()) == false) return false; + ////현재위치를 모르는 상태라면 처리하지 않는다 + if (PUB._virtualAGV.CurrentNode == null || PUB._virtualAGV.PrevNode == null) + return false; //현재위치노드 오류 var currentNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNode.Id); @@ -119,6 +120,7 @@ namespace Project } else { + //계산은 되었으나 현재위치가 전체 경로 없다면 시작노드를 현재로 변경해야한다. PUB._mapCanvas.CurrentPath = PathResult.result; PUB._virtualAGV.SetPath(PathResult.result); } @@ -133,19 +135,50 @@ namespace Project { PUB.AGV.AGVMoveStop("Path Integrity Fail"); } - PUB.log.AddE($"경로 무결성 오류"); - PUB.sm.SetNewRunStep(ERunStep.READY); + PUB.log.AddE($"경로 무결성 오류로 인해 경로를 삭제 합니다"); + Console.WriteLine($"경로 무결성 오류로 인해 경로를 삭제 합니다"); + PUB._virtualAGV.SetPath(null); + VAR.I32[eVarInt32.PathValidationError] += 1; + if (VAR.I32[eVarInt32.PathValidationError] > 50) + { + PUB.log.AddE($"연속 경로 무결성 오류로 인해 중지 합니다"); + PUB.sm.SetNewRunStep(ERunStep.ERROR); + } return false; } + else VAR.I32[eVarInt32.PathValidationError] = 0; + + //현재위치 기준으로 재 계산하여. 더 최적화된 루트가 있다면 처리를 해준다. + if (PUB._virtualAGV.CurrentPath.DetailedPath.Count > 5) + { + var PathResult2 = CalcPath(PUB._virtualAGV.CurrentNode, PUB._virtualAGV.TargetNode); + if (PathResult2.result != null && PathResult2.result.Success) + { + //절반이상 경로가 짧을때에는 재계산을 하게한다 + var halfcnt = (int)(PUB._virtualAGV.CurrentPath.DetailedPath.Count / 2.0); + if (PathResult2.result.DetailedPath.Count < halfcnt) + { + var msg = $"단축경로가 확인되었습니다. 경로를 삭제 합니다"; + PUB.log.AddE(msg); + Console.WriteLine(msg); + PUB._virtualAGV.SetPath(null); + } + return false; + } + } + - //predict 를 이용하여 다음 이동을 모두 확인한다. var nextAction = PUB._virtualAGV.Predict(); - if(nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.PathOut) + if (nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.PathOut) { //경로이탈 - PUB._virtualAGV.CurrentPath.DetailedPath.Clear(); + var logmessage = $"경로이탈감지 시작노드를 현재위치로 설정합니다 START:{PUB._virtualAGV.StartNode},CURRENT:{PUB._virtualAGV.CurrentNode}"; + PUB.log.AddE(logmessage); + Console.WriteLine(logmessage); + PUB._virtualAGV.ClearPath();//.DetailedPath.Clear(); + PUB._virtualAGV.StartNode = PUB._virtualAGV.CurrentNode; return false; } @@ -158,7 +191,7 @@ namespace Project $"현재 상태: {PUB._virtualAGV.CurrentState}\n" + $"현재 방향: {PUB._virtualAGV.CurrentDirection}\n" + $"위치 확정: {PUB._virtualAGV.IsPositionConfirmed} (RFID {PUB._virtualAGV.DetectedRfidCount}개)\n" + - $"현재 노드: {PUB._virtualAGV.CurrentNode.Id ?? "없음"}"; + $"현재 노드: {PUB._virtualAGV.CurrentNodeID2}"; //모터에서 정지를 요청했다 if (nextAction.Motor == AGVNavigationCore.Models.MotorCommand.Stop) @@ -181,13 +214,33 @@ namespace Project // 목적지 도착 여부 확인 // 현재 노드가 타겟 노드와 같고, 위치가 확정된 상태라면 도착으로 간주 // 단, AGV가 실제로 멈췄는지 확인 (agv_run == false) - if (PUB._virtualAGV.IsPositionConfirmed && - PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.TargetNode.Id) + if (PUB._virtualAGV.IsPositionConfirmed) { if (PUB.AGV.system1.agv_run == false) { - PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료). Node:{PUB._virtualAGV.CurrentNode.Id}"); - return true; + //목적지도착완료시 + if (PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.TargetNode.Id) + { + var node = PUB._mapCanvas.Nodes.Where(t => t.Id == PUB._virtualAGV.CurrentNodeId).FirstOrDefault(); + var rfid = node?.ID2 ?? "(X)"; + PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료) Node:{rfid}"); + return true; + } + + //목적지가 버퍼라면 그 앞에 멈춘다 + if (PUB._virtualAGV.TargetNode.StationType == AGVNavigationCore.Models.StationType.Buffer && + PUB._virtualAGV.CurrentPath != null && PUB._virtualAGV.CurrentPath.DetailedPath.Any()) + { + if (PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.CurrentPath.DetailedPath.Last().NodeId) + { + PUB.log.AddI($"목표(버퍼) 도착 및 정지 확인됨(MarkStop 완료). Node:{PUB._virtualAGV.CurrentNodeID2}"); + return true; + } + } + } + else + { + //아직 멈추지 않았다면 기다린다. } } @@ -213,14 +266,17 @@ namespace Project PUB.AGV.data.Direction != dir.ToString()[0] || PUB.AGV.data.Speed != spd.ToString()[0]) { - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = bunki, Direction = dir, PBSSensor = 1, Speed = spd, }); - PUB.log.Add($"Predict Run Setting = bunki:{bunki},dir:{dir},pbs:1,spd:{spd}"); + if (ret == arDev.eNarumiCommandResult.Success) + PUB.log.Add($"Predict Run Setting = bunki:{bunki},dir:{dir},pbs:1,spd:{spd}"); + else + PUB.log.AddE($"Predict Run Setting = bunki:{bunki},dir:{dir},pbs:1,spd:{spd}"); } // AGV가 정지 상태라면 구동 시작 @@ -250,14 +306,18 @@ namespace Project PUB.AGV.error.Emergency == false && PUB.AGV.system1.agv_run == false) { - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Forward, PBSSensor = 1, Speed = arDev.Narumi.eMoveSpd.Low, }); - PUB.AGV.AGVMoveRun( arDev.Narumi.eRunOpt.Forward);// + if (ret == arDev.eNarumiCommandResult.Success) + { + PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);// + } + tm_gocharge_command = DateTime.Now; } } @@ -278,14 +338,15 @@ namespace Project PUB.AGV.error.Emergency == false && PUB.AGV.system1.agv_run == false) { - PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData + var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData { Bunki = arDev.Narumi.eBunki.Strate, Direction = arDev.Narumi.eMoveDir.Backward, PBSSensor = 1, Speed = arDev.Narumi.eMoveSpd.Low, }); - PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);// + if (ret == arDev.eNarumiCommandResult.Success) + PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);// LastCommandTime = DateTime.Now; } } @@ -311,7 +372,7 @@ namespace Project // 만약 수행되지 않았다면 여기서 수행. if (pathResult.DockingValidation == null) { - pathResult.DockingValidation = AGVNavigationCore.Utils.DockingValidator.ValidateDockingDirection(pathResult, PUB._mapCanvas.Nodes); + pathResult.DockingValidation = AGVNavigationCore.Utils.DockingValidator.ValidateDockingDirection(pathResult, PUB._mapCanvas.Nodes); } // 검증 결과 확인 diff --git a/Cs_HMI/Project/StateMachine/_AGV.cs b/Cs_HMI/Project/StateMachine/_AGV.cs index f8912fa..8c4a593 100644 --- a/Cs_HMI/Project/StateMachine/_AGV.cs +++ b/Cs_HMI/Project/StateMachine/_AGV.cs @@ -66,7 +66,7 @@ namespace Project if (PUB._virtualAGV.CurrentDirection != syncDir) PUB.UpdateAGVDirection(syncDir); } - + // [Sync] Update VirtualAGV State AGVState syncState = AGVState.Idle; @@ -106,7 +106,7 @@ namespace Project PUB.log.Add($"충전상태전환 {agv_chg}"); VAR.BOOL[eVarBool.FLAG_CHARGEONA] = agv_chg; } - + if (PUB.AGV.error.Charger_pos_error != VAR.BOOL[eVarBool.CHG_POSERR]) { @@ -199,9 +199,11 @@ namespace Project else { //모터방향 확인해서 UI와 AGV클래스에 적용한다 - var MotDireciton = PUB.AGV.data.Direction == 'B' ? AGVNavigationCore.Models.AgvDirection.Backward : AGVNavigationCore.Models.AgvDirection.Forward; + var MotDireciton = PUB.AGV.data.Direction == 'B' ? AgvDirection.Backward : AgvDirection.Forward; PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, CurrentNode, MotDireciton); PUB._virtualAGV.SetPosition(CurrentNode, MotDireciton); + + //방향을 다시 확인하여. 기존 경로의 무결성을 검증한다, 필요한 경우 다시 계산할 필요가 있다. } //태그를 읽었다면 상태를 바로 전송한다 @@ -221,16 +223,42 @@ namespace Project if (PUB._mapCanvas != null && PUB._virtualAGV != null) { var nextAction = PUB._virtualAGV.Predict(); - var message = $"[다음 행동 예측]\n\n" + - $"모터: {nextAction.Motor}\n" + + var message = $"[다음 행동 예측]\n\n"; + + if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false) + message += "[수동모드]\n\n"; + + var node = PUB._virtualAGV.CurrentNode; + var curpos = PUB._virtualAGV.CurrentNodeID2; + var targetpos = PUB._virtualAGV.TargetNode?.ID2 ?? "(X)"; + + var pathdetail = ""; + if (PUB._virtualAGV.CurrentPath != null && PUB._virtualAGV.CurrentPath.DetailedPath.Any()) + { + var idx = 0; + foreach (var item in PUB._virtualAGV.CurrentPath.DetailedPath) + { + idx += 1; + if (pathdetail.isEmpty() == false) pathdetail += "->"; + if (idx % 6 == 0) pathdetail += "\n"; + pathdetail += $"{item.RfidId:0000}({item.MotorDirection.ToString().Substring(0, 1)}{item.MagnetDirection.ToString().Substring(0, 1)}{item.Speed.ToString().Substring(0, 1)})"; + if (item.IsPass) pathdetail += "(O)"; + } + } + + message += $"모터: {nextAction.Motor}\n" + $"마그넷: {nextAction.Magnet}\n" + $"속도: {nextAction.Speed}\n" + - $"이유: {nextAction.Message}\n\n" + + $"이유: {nextAction.Message}\n" + + $"상태머신:{PUB.sm.Step}:{PUB.sm.RunStep}:{PUB.sm.RunStepSeq}\n"+ $"---\n" + $"현재 상태: {PUB._virtualAGV.CurrentState}\n" + $"현재 방향: {PUB._virtualAGV.CurrentDirection}\n" + + $"턴: {PUB._virtualAGV.Turn}\n" + $"위치 확정: {PUB._virtualAGV.IsPositionConfirmed} (RFID {PUB._virtualAGV.DetectedRfidCount}개)\n" + - $"현재 노드: {PUB._virtualAGV.CurrentNode.Id ?? "없음"}"; + $"현재 노드: {curpos}\n" + + $"대상 노드: {targetpos}\n" + + $"상세 경로: {pathdetail}"; PUB._mapCanvas.PredictMessage = message; } diff --git a/Cs_HMI/Project/StateMachine/_Loop.cs b/Cs_HMI/Project/StateMachine/_Loop.cs index ac9de15..2038fde 100644 --- a/Cs_HMI/Project/StateMachine/_Loop.cs +++ b/Cs_HMI/Project/StateMachine/_Loop.cs @@ -49,8 +49,15 @@ namespace Project else if (PUB.DriveSpeed) { var rlt = PUB.AGV.AGVCommand("SSH", PUB.setting.SPD_H.ToString("0000")); - if (rlt) PUB.DriveSpeed = false; - PUB.log.Add($"Screen Change and DriveSpeed Off:{rlt}"); + if (rlt == arDev.eNarumiCommandResult.Success) + { + PUB.DriveSpeed = false; + PUB.log.Add($"Screen Change and DriveSpeed Off:{rlt}"); + } else + { + PUB.log.AddE($"Screen Change and DriveSpeed Off:{rlt}"); + } + } MenuAuto.ForeColor = Color.FromArgb(180, 180, 180); diff --git a/Cs_HMI/Project/StateMachine/_Xbee.cs b/Cs_HMI/Project/StateMachine/_Xbee.cs index 9a0b813..c3bb679 100644 --- a/Cs_HMI/Project/StateMachine/_Xbee.cs +++ b/Cs_HMI/Project/StateMachine/_Xbee.cs @@ -79,25 +79,37 @@ namespace Project var currNode = PUB._virtualAGV.CurrentNode; if (currNode == null) { - PUB.log.AddE($"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다 NodeID:{PUB._virtualAGV.CurrentNode.Id}"); - PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Node"); + PUB.log.AddE($"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다"); + PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Current Node"); return; } - PUB.NextWorkCmd = cmd; - ERunStep nextStep = ERunStep.READY; + var targetNode = PUB._virtualAGV.TargetNode; + if (targetNode == null) + { + PUB.log.AddE($"[{logPrefix}-{cmd}] 목표 노드를 알 수 없습니다"); + PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Target Node"); + return; + } - switch (currNode.StationType) + //버퍼의 경우 직전에 멈추기 때문에 스테이션종류를 보정해준다 + var StationType = currNode.StationType; + if (StationType == StationType.Normal && targetNode.StationType == StationType.Buffer) + StationType = StationType.Buffer; + + ERunStep nextStep = ERunStep.READY; + switch (StationType) { case StationType.Loader: nextStep = ERunStep.LOADER_IN; break; case StationType.UnLoader: nextStep = ERunStep.UNLOADER_IN; break; case StationType.Buffer: nextStep = ERunStep.BUFFER_IN; break; case StationType.Clearner: nextStep = ERunStep.CLEANER_IN; break; default: - PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({currNode.Type})은 작업을 지원하지 않습니다."); + PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다."); return; } + PUB.NextWorkCmd = cmd; PUB.log.AddI($"작업 시작: {nextStep} (Type: {cmd})"); PUB.sm.SetNewRunStep(nextStep); } @@ -118,10 +130,10 @@ namespace Project { var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1).Trim(); MapNode targetNode = null; - if(cmd == ENIGProtocol.AGVCommandHE.GotoAlias) + if (cmd == ENIGProtocol.AGVCommandHE.GotoAlias) { targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.AliasName == currTag); - } + } else { if (ushort.TryParse(currTag, out ushort currtagvalue)) @@ -155,14 +167,14 @@ namespace Project { PUB.log.AddE($"[{logPrefix}-Goto] 시작노드가 없습니다(현재위치 없음) NodeID:{PUB._virtualAGV.CurrentNode.Id}"); } - + //대상이동으로 처리한다. - if(PUB.sm.RunStep != ERunStep.GOTO) - { + if (PUB.sm.RunStep != ERunStep.GOTO) + { PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO); PUB.sm.ResetRunStepSeq(); } - + //Move to PUB.log.Add($"[{logPrefix}-{cmd}] {startNode.RfidId} -> {targetNode.RfidId}"); @@ -242,9 +254,15 @@ namespace Project if (Lidar == 0) bunkidata.PBSSensor = 0; else bunkidata.PBSSensor = 2; - PUB.log.Add($"[{logPrefix}-AutoMove] DIR:{bunkidata.Direction}-{bunkidata.Bunki},SPD:{bunkidata.Speed}"); - PUB.AGV.AGVMoveSet(bunkidata); - PUB.AGV.AGVMoveRun((MotDirection == 0 ? arDev.Narumi.eRunOpt.Backward : arDev.Narumi.eRunOpt.Forward)); + PUB.log.Add($"[{logPrefix}-AutoMove] DIR:{bunkidata.Direction}-{bunkidata.Bunki},SPD:{bunkidata.Speed}"); + + if (PUB.AGV.AGVMoveSet(bunkidata) != eNarumiCommandResult.Success) + PUB.log.AddE($"AGV속도설정실패로 인해 자동전환이 실패되었습니다"); + else + { + PUB.log.Add($"XBE 에서 자동모드로 전환합니다"); + PUB.AGV.AGVMoveRun((MotDirection == 0 ? arDev.Narumi.eRunOpt.Backward : arDev.Narumi.eRunOpt.Forward)); + } break; case ENIGProtocol.AGVCommandHE.MarkStop: //Set MarkStop @@ -274,7 +292,7 @@ namespace Project PUB.log.AddI($"충전을 시작합니다"); } break; - + default: PUB.logagv.AddE($"Unknown Command : {cmd} Sender:{e.ReceivedPacket.ID}, Target:{data[0]}"); PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"{cmd}"); @@ -323,7 +341,7 @@ namespace Project advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes); //마지막대상이 버퍼라면 시퀀스처리를 해야한다 - if (targetNode.StationType == StationType.Buffer&& advancedResult.DetailedPath.Any()) + if (targetNode.StationType == StationType.Buffer && advancedResult.DetailedPath.Any()) { var lastDetailPath = advancedResult.DetailedPath.Last(); if (lastDetailPath.NodeId == targetNode.Id) //마지막노드 재확인 @@ -349,7 +367,7 @@ namespace Project //UpdateAdvancedPathDebugInfo(advancedResult); } - else if(advancedResult != null) + else if (advancedResult != null) { // 경로 실패시 디버깅 정보 초기화 //_pathDebugLabel.Text = $"경로: 실패 - {advancedResult.ErrorMessage}"; diff --git a/Cs_HMI/Project/ViewForm/fManual.cs b/Cs_HMI/Project/ViewForm/fManual.cs index 46c08f7..cb35c52 100644 --- a/Cs_HMI/Project/ViewForm/fManual.cs +++ b/Cs_HMI/Project/ViewForm/fManual.cs @@ -200,8 +200,8 @@ namespace Project.ViewForm var dlg = UTIL.MsgQ("자동 진행을 시작할까요?\n우측 옵션을 확인 하세요"); if (dlg != DialogResult.Yes) return; var opt = makeopt(); - PUB.AGV.AGVMoveSet(opt); - PUB.AGV.AGVMoveRun(opt.Direction == arDev.Narumi.eMoveDir.Forward ? arDev.Narumi.eRunOpt.Forward : arDev.Narumi.eRunOpt.Backward); + if (PUB.AGV.AGVMoveSet(opt) == eNarumiCommandResult.Success) + PUB.AGV.AGVMoveRun(opt.Direction == arDev.Narumi.eMoveDir.Forward ? arDev.Narumi.eRunOpt.Forward : arDev.Narumi.eRunOpt.Backward); } else { @@ -350,7 +350,7 @@ namespace Project.ViewForm PUB.log.Add($"사용자 설정 : {bunkiopt}"); if (PUB.DriveSpeed) { - if (PUB.AGV.AGVCommand("SSH", PUB.setting.SPD_H.ToString("0000")) == true) + if (PUB.AGV.AGVCommand("SSH", PUB.setting.SPD_H.ToString("0000")) == eNarumiCommandResult.Success) PUB.DriveSpeed = false; } @@ -361,7 +361,7 @@ namespace Project.ViewForm private void arLabel1_Click_1(object sender, EventArgs e) { - if (PUB.AGV.AGVCommand("SSH", PUB.setting.SPD_DRIVE.ToString("0000")) == false) + if (PUB.AGV.AGVCommand("SSH", PUB.setting.SPD_DRIVE.ToString("0000")) != eNarumiCommandResult.Success) { AR.UTIL.MsgE("고속속도에 주행속도 연결 실패\n잠시 후 다시 시도하세요"); return; @@ -369,12 +369,18 @@ namespace Project.ViewForm if (this.bunkiopt == null) bunkiopt = makeopt(); bunkiopt.Speed = arDev.Narumi.eMoveSpd.High; - PUB.AGV.AGVMoveSet(bunkiopt); - PUB.log.Add($"고속주행설정 : {bunkiopt}"); - PUB.DriveSpeed = true; + if (PUB.AGV.AGVMoveSet(bunkiopt) == eNarumiCommandResult.Success) + { + PUB.log.Add($"고속주행설정 : {bunkiopt}"); + PUB.DriveSpeed = true; - AR.UTIL.MsgI($"주행속도({PUB.setting.SPD_DRIVE})가 '고속' 속도에 연결되었습니다.\n" + - $"창을 이동하거나 '설정적용'을 누르면 원래속도({PUB.setting.SPD_H})로 변경 됩니다"); + AR.UTIL.MsgI($"주행속도({PUB.setting.SPD_DRIVE})가 '고속' 속도에 연결되었습니다.\n" + + $"창을 이동하거나 '설정적용'을 누르면 원래속도({PUB.setting.SPD_H})로 변경 됩니다"); + } + else + { + UTIL.MsgE($"고속 속도 설정이 실패되었습니다"); + } } private void btLeft180_Click(object sender, EventArgs e) @@ -398,7 +404,7 @@ namespace Project.ViewForm private void button2_Click(object sender, EventArgs e) { - PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.OFF); + PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.OFF); } } } diff --git a/Cs_HMI/Project/fMain.cs b/Cs_HMI/Project/fMain.cs index 0144139..77ee9ef 100644 --- a/Cs_HMI/Project/fMain.cs +++ b/Cs_HMI/Project/fMain.cs @@ -432,10 +432,7 @@ namespace Project //충전상태확인 if (PUB.CheckManualChargeMode() == false) return; - PUB.popup.needClose = true; - //PUB.sm.bPause = false; - //PUB.sm.ClearRunStep(); - //PUB.sm.SetNewRunStep(ERunStep.READY); + PUB.popup.needClose = true; PUB.sm.SetNewStep(eSMStep.RUN); PUB.Speak(Lang.자동전환, addlog: false); diff --git a/Cs_HMI/Project/fSetup.cs b/Cs_HMI/Project/fSetup.cs index 5f4471e..a3cf266 100644 --- a/Cs_HMI/Project/fSetup.cs +++ b/Cs_HMI/Project/fSetup.cs @@ -471,7 +471,7 @@ namespace Project if (tagStr == "XBE") tbPortXBE.Text = newPort; else if (tagStr == "AGV") tbPortAGV.Text = newPort; - // else if (tagStr == "PLC") tbPortPLC.Text = newPort; + // else if (tagStr == "PLC") tbPortPLC.Text = newPort; else if (tagStr == "BAT") tbportBMS.Text = newPort; } @@ -511,7 +511,7 @@ namespace Project } try { - if (PUB.mplayer.SoundLocation.isEmpty()==false) PUB.mplayer.Stop(); + if (PUB.mplayer.SoundLocation.isEmpty() == false) PUB.mplayer.Stop(); PUB.mplayer.SoundLocation = tbMusic.Text;// (new Uri(tbMusic.Text)); PUB.mplayer.Play(); PUB.bPlayMusic = true; @@ -535,14 +535,14 @@ namespace Project if (this.tbPortAGV.Text == "COM21") { this.tbPortAGV.Text = "COM11"; - // this.tbPortPLC.Text = "COM6"; + // this.tbPortPLC.Text = "COM6"; this.tbportBMS.Text = "COM15"; this.tbPortXBE.Text = "COM18"; } else { this.tbPortAGV.Text = "COM21"; - // this.tbPortPLC.Text = "COM31"; + // this.tbPortPLC.Text = "COM31"; this.tbportBMS.Text = "COM41"; this.tbPortXBE.Text = "COM51"; } @@ -615,35 +615,35 @@ namespace Project private void button7_Click(object sender, EventArgs e) { var value = tbagvaddr.Text.PadLeft(4, '0'); - if (PUB.AGV.AGVCommand("SAD", value)) + if (PUB.AGV.AGVCommand("SAD", value) == arDev.eNarumiCommandResult.Success) { PUB.setting.AGV_ADDRESS = value; PUB.setting.Save(); } - + } private void button5_Click(object sender, EventArgs e) { var value = tbagvpanid.Text.PadLeft(4, '0'); - if (PUB.AGV.AGVCommand("SPN", value)) + if (PUB.AGV.AGVCommand("SPN", value) == arDev.eNarumiCommandResult.Success) { PUB.setting.AGV_PANID = value; PUB.setting.Save(); } - + } private void button6_Click(object sender, EventArgs e) { var value = tbagvchannel.Text.PadLeft(4, '0'); - if (PUB.AGV.AGVCommand("SCH", value)) + if (PUB.AGV.AGVCommand("SCH", value) == arDev.eNarumiCommandResult.Success) { PUB.setting.AGV_CHANNEL = value; PUB.setting.Save(); } - + } private void tbagvaddr_Click(object sender, EventArgs e) @@ -659,7 +659,7 @@ namespace Project { var value = (ushort)vcGDS.Value; var rlt = PUB.AGV.TurnGDSCenterScope(value); - if (rlt == false) UTIL.MsgE("Error"); + if (rlt != arDev.eNarumiCommandResult.Success) UTIL.MsgE($"Error\n{rlt}"); else UTIL.MsgI("OK"); } } diff --git a/Cs_HMI/SubProject/AGV/Command.cs b/Cs_HMI/SubProject/AGV/Command.cs index 8f06845..2aeb027 100644 --- a/Cs_HMI/SubProject/AGV/Command.cs +++ b/Cs_HMI/SubProject/AGV/Command.cs @@ -12,20 +12,75 @@ using AR; namespace arDev { + public enum eNarumiTurn + { + None = 0, + LeftIng, + Left, + RightIng, + Right, + } + public class NarumiTurnInfo + { + public DateTime Start { get; set; } + public DateTime End { get; set; } + public TimeSpan Runtime + { + get + { + if (End.Year < 2000) return DateTime.Now - Start; + if (End < Start) return DateTime.Now - Start; + else return End - Start; + } + } + public eNarumiTurn State { get; set; } + public NarumiTurnInfo() + { + Start = new DateTime(1982, 11, 23); + End = new DateTime(1982, 11, 23); + State = eNarumiTurn.None; + } + } + public class NarumiCommandTime + { + public DateTime Time { get; set; } + public ushort count { get; set; } + public NarumiCommandTime(ushort cnt) + { + Time = DateTime.Now; + this.count = cnt; + } + } + + /// + /// error 이상은모두 처리불가한 오류 조건을 의미한다 + /// + public enum eNarumiCommandResult : byte + { + Fail = 0, + Success = 1, + Wait = 2, + Error = 100, + Timeout, + + } public partial class Narumi { + public NarumiTurnInfo TurnInformation { get; set; } = null; + Dictionary SendCommandFailList { get; set; } = new Dictionary(); - public bool AGVMoveSet(BunkiData opt) + public eNarumiCommandResult AGVMoveSet(BunkiData opt) { - return AddCommand(eAgvCmd.MoveSet, opt.ToString()); + var param = opt.ToString(); + return AddCommand(eAgvCmd.MoveSet, param); } - public bool AGVMoveManual(ManulOpt opt, Speed spd, Sensor ss) + public eNarumiCommandResult AGVMoveManual(ManulOpt opt, Speed spd, Sensor ss) { var param = opt.ToString() + spd.ToString()[0] + ((int)ss).ToString(); return AddCommand(eAgvCmd.ManualMove, param); } - public bool AGVMoveRun(eRunOpt opt = eRunOpt.NotSet) + public eNarumiCommandResult AGVMoveRun(eRunOpt opt = eRunOpt.NotSet) { System.Text.StringBuilder sb = new StringBuilder(); if (opt == eRunOpt.Backward) sb.Append("B"); @@ -35,7 +90,7 @@ namespace arDev return AddCommand(eAgvCmd.MoveStart, sb.ToString()); } - public bool AGVSetSpeed(eSetSpeed item, int speed) + public eNarumiCommandResult AGVSetSpeed(eSetSpeed item, int speed) { string cmd = "SS"; if (item == eSetSpeed.Rotation) cmd = "SRS"; @@ -43,7 +98,7 @@ namespace arDev cmd += speed.ToString("0000"); return AddCommand(cmd); } - public bool AGVMoveStop(string Reason, eStopOpt opt = eStopOpt.Stop) + public eNarumiCommandResult AGVMoveStop(string Reason, eStopOpt opt = eStopOpt.Stop) { System.Text.StringBuilder sb = new StringBuilder(); if (opt == eStopOpt.MarkStop) sb.Append("MS"); @@ -53,147 +108,187 @@ namespace arDev else sb.Append("0S"); sb.Append("00"); sb.Append("0000"); //재기동시간 - //if (opt == eStopOpt.MarkStop) - // VAR.BOOL[eVarBool.NEXTSTOP_MARK] = true; //동작중이면 이 멈춤 명령을 기록으로 남긴다 230116 if (this.system1.agv_run) RaiseMessage(MessageType.Normal, $"stop command from {Reason}"); - return AddCommand(eAgvCmd.MoveStop, sb.ToString()); + var retval = AddCommand(eAgvCmd.MoveStop, sb.ToString()); + if (retval == eNarumiCommandResult.Success && opt == eStopOpt.MarkStop) + VAR.BOOL[eVarBool.NEXTSTOP_MARK] = true; + + return retval; } - public bool AGVCommand(string cmd, string data) + public eNarumiCommandResult AGVCommand(string cmd, string data) { return AddCommand(cmd + data); } - public bool LiftControl(LiftCommand cmd) + public eNarumiCommandResult LiftControl(LiftCommand cmd) { return AddCommand(eAgvCmd.LiftControl, cmd.ToString()); } - public bool AGVCharge(int chargetID, bool on, int waittime = 3) + public eNarumiCommandResult AGVCharge(int chargetID, bool on, int waittime = 3) { if (on) return AddCommand(eAgvCmd.ChargeOn, chargetID.ToString("0000")); else return AddCommand(eAgvCmd.ChargeOf, chargetID.ToString("0000")); } - public bool SetBackturnTime(int time) + public eNarumiCommandResult SetBackturnTime(int time) { return AddCommand(eAgvCmd.BackTrunResumeTime, time.ToString("0000")); } - public bool SetGateOutOffTime(int time) + public eNarumiCommandResult SetGateOutOffTime(int time) { return AddCommand(eAgvCmd.GateoutTime, time.ToString("0000")); } - public bool TurnGDSCenterScope(UInt16 time) + public eNarumiCommandResult TurnGDSCenterScope(UInt16 time) { if (time > 2000) time = 2000; return AddCommand(eAgvCmd.TurnGDSCenterScope, time.ToString("0000")); } - public bool AGVMoveLeft180Turn() + public eNarumiCommandResult AGVMoveLeft180Turn() { return AddCommand(eAgvCmd.TurnLeft); } - public bool AGVMoveRight180Turn() + public eNarumiCommandResult AGVMoveRight180Turn() { return AddCommand(eAgvCmd.TurnRight); } - public bool AGVMoveBack180Turn(bool leftTurn) + public eNarumiCommandResult AGVMoveBack180Turn(bool leftTurn) { var dir = leftTurn ? "L" : "R"; return AddCommand(eAgvCmd.BackAndTurn, dir); } - - - public bool AGVErrorReset() + public eNarumiCommandResult AGVErrorReset() { return AddCommand(eAgvCmd.ErrorReset, "FFFF"); } - public bool AGVTowerLamp(bool on) + public eNarumiCommandResult AGVTowerLamp(bool on) { return AddCommand(eAgvCmd.TowerLamp, (on ? "I" : "O")); } - public bool AGVSetAddress(int value) + public eNarumiCommandResult AGVSetAddress(int value) { return AddCommand($"SAD{value:0000}"); } - public bool AGVSetPanID(string value) + public eNarumiCommandResult AGVSetPanID(string value) { value = value.PadLeft(4, '0'); return AddCommand($"SPN{value}"); } - public bool AGVSetChannel(string value) + public eNarumiCommandResult AGVSetChannel(string value) { value = value.PadLeft(4, '0'); return AddCommand($"SCH{value}"); } - //public bool AGVGateOutTimer(int value) - //{ - // return AddCommand($"SGT{value:0000}"); - //} - /// - /// 정지감속주기 및 상수 - /// - /// - public bool AGVSetStopAcc(int interval, int value) - { - var cmds = new string[] { - $"SSK{interval:0000}", - $"SCK{value:0000}" - }; - return AddCommand( cmds); - } - public bool AGVSetTagReinputTime(int value) + public eNarumiCommandResult AGVSetTagReinputTime(int value) { return AddCommand($"STT{value:0000}"); } - public bool AGVSetPID(eSetPIDSpeed speed, int P, int I, int D) - { - var cmd = "S"; - var cmd2 = ""; - if (speed == eSetPIDSpeed.High) cmd2 = "K"; - else if (speed == eSetPIDSpeed.Middle) cmd2 = "M"; - else if (speed == eSetPIDSpeed.Low) cmd2 = "L"; - else if (speed == eSetPIDSpeed.Stop) cmd2 = "S"; - var cmds = new string[] { - $"{cmd}P{cmd2}{P:0000}", - $"{cmd}I{cmd2}{I:0000}", - $"{cmd}D{cmd2}{D:0000}", - }; - return AddCommand( cmds); - } - - /// - /// 전송을시도한 시간 - /// - //public Dictionary LastCommandTime { get; set; } - - /// - /// 전송에 성공한 명령시간 - /// - //public Dictionary LastCommandOKTime { get; set; } - protected bool AddCommand(params string[] cmds) + ///// + ///// 전송에 성공한 명령시간 + ///// + ////public Dictionary LastCommandOKTime { get; set; } + protected bool SendCommand(string cmdline) { bool ret = true; - ACKData = string.Empty; //회신값 제거 - //LastCommandTime.Add(cmd, DateTime.Now); - foreach (var cmdline in cmds) - { - var fullcmd = MakeCheckSum(cmdline); - //commandQueue.Enqueue(fullcmd); - if (WriteData(fullcmd) == false) ret = false; - //else LastCommandOKTime.Add(cmd, DateTime.Now); - System.Threading.Thread.Sleep(1); - } + ACKData = string.Empty; + var fullcmd = MakeCheckSum(cmdline); + if (WriteData(fullcmd) == false) ret = false; + System.Threading.Thread.Sleep(1); return ret; } + ManualResetEvent mrecmd = new ManualResetEvent(true); + public eNarumiCommandResult AddCommand(string cmd, int waitms = 1) + { + if (mrecmd.WaitOne(waitms) == false) + { + //다른명령을 처리하는 중이므로 대기상태로 반환한다 + return eNarumiCommandResult.Wait; + } - protected bool AddCommand(eAgvCmd command, BunkiData param) + //다른신호를 처리하지 못하게한다. + mrecmd.Reset(); + + try + { + if (SendCommandFailList.ContainsKey(cmd) == false) + { + //실패기록이 없다 + var ret = SendCommand(cmd); + if (ret == false) + { + SendCommandFailList.Add(cmd, new NarumiCommandTime(1)); + return eNarumiCommandResult.Fail; + } + else return eNarumiCommandResult.Success; + } + else + { + //실패기록이 존재한다. + //1.동일 명령이 아니면 바로 전송한다 + var precmd = SendCommandFailList[cmd]; + + //동일명령의 실패기록이 존재한다. + //2초간의 간격을 둔다 + var ts = DateTime.Now - precmd.Time; + if (ts.TotalSeconds < 2) + { + precmd.Time = DateTime.Now; + SendCommandFailList[cmd] = precmd; + return eNarumiCommandResult.Wait; //대기한다 + } + else + { + //오류가 누적되었다. + var ret = SendCommand(cmd); + if (ret == false) + { + precmd.Time = DateTime.Now; + precmd.count += 1; + SendCommandFailList[cmd] = precmd; + + //5회연속 실패했다면 타임아웃처리한다 + if (precmd.count > 5) + { + //타임아웃 + return eNarumiCommandResult.Timeout; + } + else + { + //실패 + return eNarumiCommandResult.Fail; + } + } + else + { + //전송이성공했으니 키를 제거한다. + SendCommandFailList.Remove(cmd); + return eNarumiCommandResult.Success; + } + } + + } + } + catch (Exception ex) + { + Console.WriteLine($"narumi addCommand error : {ex.Message}"); + return eNarumiCommandResult.Error; + } + finally + { + mrecmd.Set(); + } + } + + + protected eNarumiCommandResult AddCommand(eAgvCmd command, BunkiData param) { return AddCommand(command, param.ToString()); } @@ -204,15 +299,15 @@ namespace arDev /// /// /// - protected bool AddCommand(BunkiData param) + protected eNarumiCommandResult AddCommand(BunkiData param) { return AddCommand(eAgvCmd.MoveSet, param.ToString()); } - protected bool AddCommand(eAgvCmd command, string param = "") + protected eNarumiCommandResult AddCommand(eAgvCmd command, string param = "") { string cmdString; - bool retval = false; + eNarumiCommandResult retval = eNarumiCommandResult.Error; switch (command) { case eAgvCmd.ErrorReset: @@ -247,10 +342,24 @@ namespace arDev case eAgvCmd.TurnLeft: cmdString = $"CTL0000"; retval = AddCommand(cmdString); + if (retval == eNarumiCommandResult.Success) + { + if (TurnInformation == null) TurnInformation = new NarumiTurnInfo(); + TurnInformation.Start = DateTime.Now; + TurnInformation.End = new DateTime(1982, 11, 23); + TurnInformation.State = eNarumiTurn.LeftIng; + } break; case eAgvCmd.TurnRight: cmdString = $"CTR0000"; retval = AddCommand(cmdString); + if (retval == eNarumiCommandResult.Success) + { + if (TurnInformation == null) TurnInformation = new NarumiTurnInfo(); + TurnInformation.Start = DateTime.Now; + TurnInformation.End = new DateTime(1982, 11, 23); + TurnInformation.State = eNarumiTurn.RightIng; + } break; case eAgvCmd.BackAndTurn: if (param.isEmpty()) param = "L"; diff --git a/Cs_HMI/SubProject/AGV/Narumi.cs b/Cs_HMI/SubProject/AGV/Narumi.cs index 48d588f..97684da 100644 --- a/Cs_HMI/SubProject/AGV/Narumi.cs +++ b/Cs_HMI/SubProject/AGV/Narumi.cs @@ -229,7 +229,7 @@ namespace arDev private void RevSTS(Dataframe frame) { LastSTS = frame.DataString; - string rcvdNow = frame.DataString.Replace("\0",""); + string rcvdNow = frame.DataString.Replace("\0", ""); byte[] bRcvData = frame.Buffer; var encoding = System.Text.Encoding.Default; try @@ -258,21 +258,39 @@ namespace arDev error.SetValue(nDataTemp); idx += 4; - data.Speed = rcvdNow.Substring(idx, 1)[0]; idx += 1; //L,M.H data.Sts = rcvdNow.Substring(idx, 1)[0]; idx += 1; //S(직진),L(좌분기),R(우분기) data.Direction = rcvdNow.Substring(idx, 1)[0]; idx += 1; //F,B,L,R data.guidesensor = int.Parse(rcvdNow.Substring(idx, 1)); idx += 1; //가이드 좌측부터 1~9 nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16); - signal1.SetValue(nDataTemp); idx += 2; + signal1.SetValue(nDataTemp); idx += 2; - if(idx <= rcvdNow.Length-2) + //agv가 멈춰있고 마크센서가 들어온경우, 턴 작업이었다면 턴 셋팅을 한다 + if (system1.agv_run == false && system1.agv_stop == true && + TurnInformation != null && signal1.mark_sensor) + { + if (TurnInformation.Start.Year > 2000) + { + if (TurnInformation.State == eNarumiTurn.LeftIng || TurnInformation.State == eNarumiTurn.RightIng) + { + TurnInformation.End = DateTime.Now; + if (TurnInformation.State == eNarumiTurn.LeftIng) TurnInformation.State = eNarumiTurn.Left; + if (TurnInformation.State == eNarumiTurn.RightIng) TurnInformation.State = eNarumiTurn.Right; + } + } + else + { + //시작시간이 설정되지 않았다면 처리하지 않는다. + } + } + + if (idx <= rcvdNow.Length - 2) { nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16); signal2.SetValue(nDataTemp); } - + DataReceive?.Invoke(this, new DataEventArgs(DataType.STS)); @@ -462,7 +480,7 @@ namespace arDev OFF } - + private bool CheckSum(byte[] bData) { if (bData.Length < 2) // 데이터 길이가 2이하일 경우 비정상 처리 diff --git a/Cs_HMI/SubProject/CommData/Enum.cs b/Cs_HMI/SubProject/CommData/Enum.cs index 41181ef..e62aa47 100644 --- a/Cs_HMI/SubProject/CommData/Enum.cs +++ b/Cs_HMI/SubProject/CommData/Enum.cs @@ -13,6 +13,7 @@ namespace COMM SumQty, ChargeWaitSec, SyncItemCount, + PathValidationError, } public enum eVarUInt32 { diff --git a/Cs_HMI/SubProject/EnigProtocol b/Cs_HMI/SubProject/EnigProtocol index 14ff055..bca75dd 160000 --- a/Cs_HMI/SubProject/EnigProtocol +++ b/Cs_HMI/SubProject/EnigProtocol @@ -1 +1 @@ -Subproject commit 14ff055fa98a3e692c796d961133635e21e9ff69 +Subproject commit bca75ddd6a85bb8bf7c196e8ac8bb2eea52ba522 diff --git a/Document/NewMap.json b/Document/NewMap.json new file mode 100644 index 0000000..5799617 --- /dev/null +++ b/Document/NewMap.json @@ -0,0 +1,1005 @@ +{ + "Nodes": [ + { + "Id": "N001", + "Text": "Unloader", + "Position": "298, 270", + "Type": 0, + "StationType": 3, + "ConnectedNodes": [ + "12", + "14" + ], + "RfidId": 1, + "NodeTextForeColor": "White", + "NodeTextFontSize": 30, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N010", + "Text": "Cleaner", + "Position": "298, 446", + "Type": 0, + "StationType": 2, + "ConnectedNodes": [ + "5", + "15" + ], + "RfidId": 11, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 1, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N014", + "Text": "Loader", + "Position": "520, 653", + "Type": 0, + "StationType": 1, + "ConnectedNodes": [ + "6", + "16" + ], + "RfidId": 8, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N019", + "Text": "Chg #1", + "Position": "402, 375", + "Type": 0, + "StationType": 5, + "ConnectedNodes": [], + "RfidId": 15, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 1, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N026", + "Text": "Chg #2", + "Position": "541, 570", + "Type": 0, + "StationType": 5, + "ConnectedNodes": [], + "RfidId": 19, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 1, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N018", + "Text": "", + "Position": "213, 630", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N030", + "N005", + "1" + ], + "RfidId": 34, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N005", + "Text": "", + "Position": "113, 629", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N018", + "N020", + "N029" + ], + "RfidId": 33, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N020", + "Text": "", + "Position": "38, 626", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N005", + "N021", + "N028" + ], + "RfidId": 32, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N021", + "Text": "", + "Position": "-54, 626", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N020", + "N027", + "11" + ], + "RfidId": 31, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N027", + "Text": "Buf #1", + "Position": "-76, 676", + "Type": 0, + "StationType": 4, + "ConnectedNodes": [ + "N021" + ], + "RfidId": 41, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N028", + "Text": "Buf #2", + "Position": "12, 676", + "Type": 0, + "StationType": 4, + "ConnectedNodes": [ + "N020" + ], + "RfidId": 40, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N029", + "Text": "Buf #3", + "Position": "89, 674", + "Type": 0, + "StationType": 4, + "ConnectedNodes": [ + "N005" + ], + "RfidId": 39, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "N030", + "Text": "Buf #4", + "Position": "183, 675", + "Type": 0, + "StationType": 4, + "ConnectedNodes": [ + "N018" + ], + "RfidId": 38, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": true, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "1", + "Text": "", + "Position": "285, 628", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N018", + "2" + ], + "RfidId": 2, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "2", + "Text": "", + "Position": "354, 628", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "1", + "3" + ], + "RfidId": 4, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "3", + "Text": "", + "Position": "400, 578", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "2", + "4" + ], + "RfidId": 3, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "4", + "Text": "", + "Position": "400, 499", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "3", + "5" + ], + "RfidId": 5, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "5", + "Text": "", + "Position": "462, 451", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N010", + "4", + "6", + "7" + ], + "RfidId": 6, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "6", + "Text": "", + "Position": "518, 519", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N014", + "5" + ], + "RfidId": 13, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "7", + "Text": "", + "Position": "517, 400", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "5", + "8" + ], + "RfidId": 7, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "8", + "Text": "", + "Position": "474, 353", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "7", + "9", + "13" + ], + "RfidId": 9, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "9", + "Text": "", + "Position": "517, 311", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "8", + "10" + ], + "RfidId": 10, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "10", + "Text": "", + "Position": "458, 268", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "9", + "12" + ], + "RfidId": 12, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "12", + "Text": "", + "Position": "389, 270", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "N001", + "10" + ], + "RfidId": 16, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + }, + { + "Id": "13", + "Text": "", + "Position": "350, 353", + "Type": 0, + "StationType": 0, + "ConnectedNodes": [ + "8" + ], + "RfidId": 17, + "NodeTextForeColor": "", + "NodeTextFontSize": 7, + "AliasName": "", + "SpeedLimit": 0, + "CanDocking": false, + "DockDirection": 0, + "CanTurnLeft": true, + "CanTurnRight": true, + "DisableCross": false, + "IsActive": true + } + ], + "Labels": [ + { + "Id": "11", + "Type": 1, + "Text": "Label", + "Position": "-120, 625", + "ForeColor": "White", + "BackColor": "Transparent", + "FontFamily": "Arial", + "FontSize": 7, + "FontStyle": 0, + "Padding": 5 + }, + { + "Id": "14", + "Type": 1, + "Text": "Label", + "Position": "224, 272", + "ForeColor": "White", + "BackColor": "Transparent", + "FontFamily": "Arial", + "FontSize": 7, + "FontStyle": 0, + "Padding": 5 + }, + { + "Id": "15", + "Type": 1, + "Text": "Label", + "Position": "247, 449", + "ForeColor": "White", + "BackColor": "Transparent", + "FontFamily": "Arial", + "FontSize": 7, + "FontStyle": 0, + "Padding": 5 + }, + { + "Id": "16", + "Type": 1, + "Text": "Label", + "Position": "517, 721", + "ForeColor": "White", + "BackColor": "Transparent", + "FontFamily": "Arial", + "FontSize": 7, + "FontStyle": 0, + "Padding": 5 + }, + { + "Id": "LBL001", + "Type": 1, + "Text": "Amkor Technology Korea", + "Position": "180, 105", + "ForeColor": "White", + "BackColor": "MidnightBlue", + "FontFamily": "Arial", + "FontSize": 20, + "FontStyle": 0, + "Padding": 5 + } + ], + "Images": [ + { + "Id": "IMG001", + "Type": 2, + "Name": "Image", + "Position": "633, 310", + "ImagePath": "", + "ImageBase64": "", + "Scale": "1, 1", + "Opacity": 1, + "Rotation": 0 + } + ], + "Magnets": [ + { + "Id": "5a0edec2-7ac3-4c99-bbb4-8debde0c1d07", + "Type": 4, + "P1": { + "X": 358.5378151260505, + "Y": 628.438429217841 + }, + "P2": { + "X": -119.57759581805529, + "Y": 626.7844959888976 + }, + "ControlPoint": null + }, + { + "Id": "def7c4b9-86db-42eb-aae6-0c6c9bedcc30", + "Type": 4, + "P1": { + "X": -75.0847526191485, + "Y": 716.1415561436951 + }, + "P2": { + "X": -73.1298113651053, + "Y": 561.6311923370746 + }, + "ControlPoint": null + }, + { + "Id": "624327ee-be0f-4373-b60a-786a93c1eabf", + "Type": 4, + "P1": { + "X": 12.69302515862924, + "Y": 717.2526672548062 + }, + "P2": { + "X": 11.870188634894689, + "Y": 560.2502399561221 + }, + "ControlPoint": null + }, + { + "Id": "f1e885ae-55f7-42e9-b3aa-648541e97da0", + "Type": 4, + "P1": { + "X": 90.47080293640698, + "Y": 716.1415561436951 + }, + "P2": { + "X": 89.72733149203751, + "Y": 560.9169066227888 + }, + "ControlPoint": null + }, + { + "Id": "dc3e8061-2c99-4f24-ac9b-4020dd91fa8b", + "Type": 4, + "P1": { + "X": 185.470802936407, + "Y": 725.0304450325839 + }, + "P2": { + "X": 181.87018863489462, + "Y": 563.059763765646 + }, + "ControlPoint": null + }, + { + "Id": "f4c97a5a-2c2c-4b5e-9dd5-332b1670b827", + "Type": 4, + "P1": { + "X": 343.98784548784573, + "Y": 353.9221611721613 + }, + "P2": { + "X": 472.59413991107186, + "Y": 353.73144759338567 + }, + "ControlPoint": null + }, + { + "Id": "a5424add-e8c9-483c-a5db-733dca1b8f57", + "Type": 4, + "P1": { + "X": 519.3272283272282, + "Y": 720.2549019607841 + }, + "P2": { + "X": 516.1655684825004, + "Y": 309.44573330767145 + }, + "ControlPoint": null + }, + { + "Id": "0bbb27a4-2355-4294-9f2d-a40e4d3d2930", + "Type": 4, + "P1": { + "X": 249.6240475640633, + "Y": 449.77059623383167 + }, + "P2": { + "X": 518.0855860256017, + "Y": 452.84751931075476 + }, + "ControlPoint": null + }, + { + "Id": "92527d4a-8e63-404f-86e8-37568bd4790e", + "Type": 4, + "P1": { + "X": 225.77789371790945, + "Y": 271.3090577722932 + }, + "P2": { + "X": 463.6655684825004, + "Y": 269.44573330767145 + }, + "ControlPoint": null + }, + { + "Id": "0db9553a-f203-478d-8c17-e07f00987828", + "Type": 4, + "P1": { + "X": 463.41452991452985, + "Y": 270.2549019607843 + }, + "P2": { + "X": 515.0811965811965, + "Y": 310.25490196078425 + }, + "ControlPoint": { + "X": 517.8589743589743, + "Y": 266.3660130718954 + } + }, + { + "Id": "0650b2cb-57f9-44ec-9787-fab878cf2b47", + "Type": 4, + "P1": { + "X": 473.41452991452985, + "Y": 353.032679738562 + }, + "P2": { + "X": 515.6367521367521, + "Y": 399.1437908496731 + }, + "ControlPoint": { + "X": 521.7478632478632, + "Y": 351.3660130718954 + } + }, + { + "Id": "7007db10-b61b-4726-9775-417951454ddf", + "Type": 4, + "P1": { + "X": 465.08119658119654, + "Y": 450.2549019607842 + }, + "P2": { + "X": 518.2491858425353, + "Y": 519.0597150914111 + }, + "ControlPoint": { + "X": 522.8589743589743, + "Y": 450.8104575163398 + } + }, + { + "Id": "4ef4bfd0-8fc4-48a5-a490-92b35c7fd1c3", + "Type": 4, + "P1": { + "X": 473.3084256253576, + "Y": 351.58859045052856 + }, + "P2": { + "X": 515.4512827682147, + "Y": 309.8028761648143 + }, + "ControlPoint": { + "X": 519.0227113396433, + "Y": 358.01716187909994 + } + }, + { + "Id": "7d18ae7e-7926-4cf4-8dd6-861462e31352", + "Type": 4, + "P1": { + "X": 464.73699705392903, + "Y": 450.5171618790999 + }, + "P2": { + "X": 516.5227113396433, + "Y": 402.6600190219571 + }, + "ControlPoint": { + "X": 516.1655684825004, + "Y": 454.44573330767133 + } + }, + { + "Id": "532be14d-170e-45c5-adcf-0d555b83b010", + "Type": 4, + "P1": { + "X": 399.76697627462613, + "Y": 521.7210880257198 + }, + "P2": { + "X": 462.6173982141941, + "Y": 449.66152357736206 + }, + "ControlPoint": { + "X": 391.9923982141941, + "Y": 447.16152357736206 + } + }, + { + "Id": "086955e0-0542-4ab5-9ccf-8880e749722a", + "Type": 4, + "P1": { + "X": 399.0526905603404, + "Y": 523.5068023114342 + }, + "P2": { + "X": 400.1113258122239, + "Y": 580.622765294022 + }, + "ControlPoint": null + }, + { + "Id": "2ac7d720-a6df-4174-be74-15ddfe96459b", + "Type": 4, + "P1": { + "X": 353.8613258122239, + "Y": 629.3727652940219 + }, + "P2": { + "X": 400.1113258122239, + "Y": 576.872765294022 + }, + "ControlPoint": { + "X": 404.4863258122239, + "Y": 630.6227652940219 + } + } + ], + "Marks": [ + { + "Id": "2cb51787-c8cf-4ddb-97f0-b71f519d47dc", + "Type": 3, + "Position": "520, 690", + "X": 520, + "Y": 690, + "Rotation": -178.01940688369234 + }, + { + "Id": "f704ebe0-1653-4559-b06f-1eaecafbefba", + "Type": 3, + "Position": "-74, 624", + "X": -74, + "Y": 624, + "Rotation": 90 + }, + { + "Id": "d5b27365-79a2-4351-84c3-6767941ec0be", + "Type": 3, + "Position": "12, 625", + "X": 12, + "Y": 625, + "Rotation": 90 + }, + { + "Id": "0367cafb-9f85-4440-b6b4-c802a58e6181", + "Type": 3, + "Position": "91, 625", + "X": 91, + "Y": 625, + "Rotation": 89.2872271068898 + }, + { + "Id": "1f4ab2c9-07f8-4675-802d-9b4824b55198", + "Type": 3, + "Position": "183, 622", + "X": 183, + "Y": 622, + "Rotation": 88.40516772072262 + }, + { + "Id": "15fddfa4-ff74-48ff-b922-4aacdce1960b", + "Type": 3, + "Position": "275, 269", + "X": 275, + "Y": 269, + "Rotation": 91.71222012905176 + }, + { + "Id": "4b699847-36d4-471c-b990-4ad37967c2dc", + "Type": 3, + "Position": "89, 697", + "X": 89, + "Y": 697, + "Rotation": 0.3824344779617803 + }, + { + "Id": "a9f68317-f1c2-47d8-b029-348b5428be9f", + "Type": 3, + "Position": "183, 699", + "X": 183, + "Y": 699, + "Rotation": -1.3380194104322385 + }, + { + "Id": "fe227205-2a65-4ba9-bb4a-4efb4ed0a7b0", + "Type": 3, + "Position": "9, 699", + "X": 9, + "Y": 699, + "Rotation": 0.8431103833306963 + }, + { + "Id": "5dd29191-798c-480c-b066-7947bfcc4fb7", + "Type": 3, + "Position": "-74, 697", + "X": -74, + "Y": 697, + "Rotation": 1.659829660758831 + }, + { + "Id": "649729f0-ff04-4e11-8869-f6a39d815427", + "Type": 3, + "Position": "-71, 596", + "X": -71, + "Y": 596, + "Rotation": 0 + }, + { + "Id": "2bb9a821-f86b-4190-a182-64abe2c940ed", + "Type": 3, + "Position": "10, 601", + "X": 10, + "Y": 601, + "Rotation": 0 + }, + { + "Id": "821598e1-091a-4884-96fe-6ed5f43c4f62", + "Type": 3, + "Position": "91, 598", + "X": 91, + "Y": 598, + "Rotation": 0 + }, + { + "Id": "66c1bbee-89a8-45a9-b585-ddfd59768f6b", + "Type": 3, + "Position": "184, 596", + "X": 184, + "Y": 596, + "Rotation": 0 + }, + { + "Id": "06a10f46-bda8-4b0f-9e7a-63d66bd2f7e4", + "Type": 3, + "Position": "381, 355", + "X": 381, + "Y": 355, + "Rotation": 91.24536426676838 + }, + { + "Id": "835b8982-042b-4e2e-a83b-19b32e55cd5b", + "Type": 3, + "Position": "519, 550", + "X": 519, + "Y": 550, + "Rotation": 0 + } + ], + "Settings": { + "BackgroundColorArgb": -14671840, + "ShowGrid": false + }, + "CreatedDate": "2025-12-23T04:16:13.079Z", + "Version": "1.3" +} \ No newline at end of file