This commit is contained in:
chi
2025-06-30 13:48:19 +09:00
parent 02e71d7446
commit 00dd50192b
29 changed files with 2397 additions and 1136 deletions

View File

@@ -0,0 +1,103 @@
namespace AGVControl.Dialog
{
partial class fMapDesign
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(fMapDesign));
this.panel1 = new System.Windows.Forms.Panel();
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.btOK = new System.Windows.Forms.ToolStripButton();
this.toolStrip1.SuspendLayout();
this.SuspendLayout();
//
// panel1
//
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Font = new System.Drawing.Font("Tahoma", 8F, System.Drawing.FontStyle.Bold);
this.panel1.Location = new System.Drawing.Point(0, 47);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(888, 547);
this.panel1.TabIndex = 22;
//
// toolStrip1
//
this.toolStrip1.ImageScalingSize = new System.Drawing.Size(40, 40);
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.btOK});
this.toolStrip1.Location = new System.Drawing.Point(0, 0);
this.toolStrip1.Name = "toolStrip1";
this.toolStrip1.Size = new System.Drawing.Size(888, 47);
this.toolStrip1.TabIndex = 23;
this.toolStrip1.Text = "toolStrip1";
//
// statusStrip1
//
this.statusStrip1.Location = new System.Drawing.Point(0, 594);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(888, 22);
this.statusStrip1.TabIndex = 24;
this.statusStrip1.Text = "statusStrip1";
//
// btOK
//
this.btOK.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
this.btOK.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
this.btOK.Image = ((System.Drawing.Image)(resources.GetObject("btOK.Image")));
this.btOK.ImageTransparentColor = System.Drawing.Color.Magenta;
this.btOK.Name = "btOK";
this.btOK.Size = new System.Drawing.Size(44, 44);
this.btOK.Text = "toolStripButton1";
this.btOK.Click += new System.EventHandler(this.btOK_Click);
//
// fMapDesign
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(888, 616);
this.Controls.Add(this.panel1);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.toolStrip1);
this.Name = "fMapDesign";
this.Text = "fMapDesign";
this.Load += new System.EventHandler(this.fMapDesign_Load);
this.toolStrip1.ResumeLayout(false);
this.toolStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.ToolStrip toolStrip1;
private System.Windows.Forms.ToolStripButton btOK;
private System.Windows.Forms.StatusStrip statusStrip1;
}
}

View File

@@ -0,0 +1,48 @@
using AR;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AGVControl.Dialog
{
public partial class fMapDesign : Form
{
AGVControl.MapControl mapctl;
public fMapDesign(string fn)
{
InitializeComponent();
mapctl = new AGVControl.MapControl();
mapctl.Dock = DockStyle.Fill;
mapctl.Visible = true;
mapctl.Font = this.panel1.Font;
mapctl.BackColor = Color.FromArgb(32, 32, 32);
this.panel1.Controls.Add(mapctl);
if (System.IO.File.Exists(fn))
{
//auto load
var rlt = mapctl.LoadFromFile(fn, out string errmsg);
if (rlt == false) AR.UTIL.MsgE(errmsg);
}
}
private void fMapDesign_Load(object sender, EventArgs e)
{
}
private void btOK_Click(object sender, EventArgs e)
{
this.mapctl.SaveToFile(this.mapctl.Filename);
DialogResult = DialogResult.OK;
}
}
}

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="btOK.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
TgDQASA1MVpwzwAAAABJRU5ErkJggg==
</value>
</data>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>124, 17</value>
</metadata>
</root>

View File

@@ -0,0 +1,158 @@
namespace AGVControl.Dialog
{
partial class fPropertyRFIDPoint
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.propertyGrid1 = new System.Windows.Forms.PropertyGrid();
this.comboBox1 = new System.Windows.Forms.ComboBox();
this.propertyGrid2 = new System.Windows.Forms.PropertyGrid();
this.panel1 = new System.Windows.Forms.Panel();
this.label2 = new System.Windows.Forms.Label();
this.panel2 = new System.Windows.Forms.Panel();
this.label1 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
this.SuspendLayout();
//
// propertyGrid1
//
this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill;
this.propertyGrid1.Location = new System.Drawing.Point(0, 31);
this.propertyGrid1.Name = "propertyGrid1";
this.propertyGrid1.Size = new System.Drawing.Size(346, 456);
this.propertyGrid1.TabIndex = 0;
//
// comboBox1
//
this.comboBox1.Dock = System.Windows.Forms.DockStyle.Top;
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.Font = new System.Drawing.Font("굴림", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point(1, 32);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size(519, 32);
this.comboBox1.TabIndex = 1;
this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged);
//
// propertyGrid2
//
this.propertyGrid2.Dock = System.Windows.Forms.DockStyle.Fill;
this.propertyGrid2.Location = new System.Drawing.Point(1, 64);
this.propertyGrid2.Name = "propertyGrid2";
this.propertyGrid2.Size = new System.Drawing.Size(519, 362);
this.propertyGrid2.TabIndex = 2;
//
// panel1
//
this.panel1.BackColor = System.Drawing.Color.Gray;
this.panel1.Controls.Add(this.propertyGrid2);
this.panel1.Controls.Add(this.button1);
this.panel1.Controls.Add(this.comboBox1);
this.panel1.Controls.Add(this.label2);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(346, 0);
this.panel1.Name = "panel1";
this.panel1.Padding = new System.Windows.Forms.Padding(1);
this.panel1.Size = new System.Drawing.Size(521, 487);
this.panel1.TabIndex = 3;
//
// label2
//
this.label2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.label2.Dock = System.Windows.Forms.DockStyle.Top;
this.label2.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label2.Location = new System.Drawing.Point(1, 1);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(519, 31);
this.label2.TabIndex = 3;
this.label2.Text = "Connection Information";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// panel2
//
this.panel2.Controls.Add(this.propertyGrid1);
this.panel2.Controls.Add(this.label1);
this.panel2.Dock = System.Windows.Forms.DockStyle.Left;
this.panel2.Location = new System.Drawing.Point(0, 0);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(346, 487);
this.panel2.TabIndex = 4;
//
// label1
//
this.label1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.label1.Dock = System.Windows.Forms.DockStyle.Top;
this.label1.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label1.Location = new System.Drawing.Point(0, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(346, 31);
this.label1.TabIndex = 0;
this.label1.Text = "Point Information";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// button1
//
this.button1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.button1.Font = new System.Drawing.Font("Arial Rounded MT Bold", 27.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.button1.Location = new System.Drawing.Point(1, 426);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(519, 60);
this.button1.TabIndex = 4;
this.button1.Text = "P1 ↔ P2";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// fPropertyRFIDPoint
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(867, 487);
this.Controls.Add(this.panel1);
this.Controls.Add(this.panel2);
this.Name = "fPropertyRFIDPoint";
this.Text = "fPropertyRFIDPoint";
this.Load += new System.EventHandler(this.fPropertyRFIDPoint_Load);
this.panel1.ResumeLayout(false);
this.panel2.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PropertyGrid propertyGrid1;
private System.Windows.Forms.ComboBox comboBox1;
private System.Windows.Forms.PropertyGrid propertyGrid2;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button button1;
}
}

View File

@@ -0,0 +1,55 @@
using AGVControl.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AGVControl.Dialog
{
public partial class fPropertyRFIDPoint : Form
{
RFIDPoint RFIDPt;
List<RFIDConnection> Connections;
public fPropertyRFIDPoint(RFIDPoint point, List<RFIDConnection> connection)
{
InitializeComponent();
this.RFIDPt = point;
this.Connections = connection;
this.propertyGrid1.SelectedObject = point;
this.KeyPreview = true;
this.KeyDown += (s1, e1) => {
if (e1.KeyCode == Keys.Escape) this.Close();
};
}
private void fPropertyRFIDPoint_Load(object sender, EventArgs e)
{
foreach (var item in Connections)
comboBox1.Items.Add($"{item.P1.Value} ↔ {item.P2.Value}");
if(comboBox1.Items.Count > 0)
comboBox1.SelectedIndex = 0;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
this.propertyGrid2.SelectedObject = this.Connections[this.comboBox1.SelectedIndex];
}
private void button1_Click(object sender, EventArgs e)
{
var item = this.Connections[this.comboBox1.SelectedIndex];
var p1 = item.P1;
var p2 = item.P2;
item.P2 = p1;// item.P1;
item.P1 = p2;// p1;
this.propertyGrid2.SelectedObject = item;
this.Validate();
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

File diff suppressed because it is too large Load Diff

View File

@@ -3,25 +3,37 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TextBox;
namespace AGVControl
{
public static class MapControlManager
public class MapControlManager
{
public static AGVActionPrediction PredictResult = null;
public static AGV agv = new AGV();
public static List<RFIDPoint> RFIDPoints = new List<RFIDPoint>();
public static HashSet<RFIDConnection> rfidConnections = new HashSet<RFIDConnection>();
public AGVActionPrediction PredictResult = null;
public AGV agv = new AGV();
public List<RFIDPoint> RFIDPoints = new List<RFIDPoint>();
public HashSet<RFIDConnection> rfidConnections = new HashSet<RFIDConnection>();
private static ManualResetEvent mrepredict = new ManualResetEvent(true);
private ManualResetEvent mrepredict = new ManualResetEvent(true);
public MapControlManager()
{
}
~MapControlManager()
{
}
/// <summary>
/// 목표지정으로 모터방향이 이동하고 있는가?
/// history 데이터가 있어야 하며 기준데이터가 없는 경우 null 반환
/// </summary>
/// <returns></returns>
public static bool? IsMotDirection_To_Target()
public bool? IsMotDirection_To_Target()
{
if (agv.MovementHistory.Any() == false || agv.MovementHistory.Count < 2) return null;
if (agv.MainPath.Any() == false) return null;
@@ -38,27 +50,24 @@ namespace AGVControl
//지정된경로 반대방향으로 이동하고 있다
return preidx < curidx;
}
public static float GetDistance(Point p1, Point p2)
public float GetDistance(Point p1, Point p2)
{
float dx = p1.X - p2.X;
float dy = p1.Y - p2.Y;
return (float)Math.Sqrt(dx * dx + dy * dy); // double을 float로 명시적 캐스팅
}
public static AGVActionPrediction PredictNextAction()
public AGVActionPrediction PredictNextAction()
{
if (mrepredict.WaitOne(1) == false)
{
PredictResult = new AGVActionPrediction
{
Reason = "이전 작업이 완료되지 않았습니다",
ReasonCode = AGVActionReasonCode.busy,
MoveState = AGVMoveState.Stop,
Direction = agv.CurrentMOTDirection,
};
PredictResult = CreatePrediction("이전 작업이 완료되지 않았습니다",
AGVActionReasonCode.busy,
AGVMoveState.Stop,
agv.CurrentMOTDirection, false);
return PredictResult;
}
mrepredict.Reset();
try
{
@@ -68,58 +77,70 @@ namespace AGVControl
// 1. 위치를 모를 때 (CurrentRFID가 0 또는 미설정)
if (agv.CurrentRFID.Value == 0)
{
PredictResult = new AGVActionPrediction
{
Direction = Direction.Backward,
NextRFID = null,
Reason = "AGV 위치 미확정(처음 기동)",
ReasonCode = AGVActionReasonCode.NoPosition,
MoveState = AGVMoveState.Run
};
PredictResult = CreatePrediction("AGV 위치 미확정(처음 기동)",
AGVActionReasonCode.NoPosition,
AGVMoveState.Run,
Direction.Backward, true,
moveSpeed: AgvSpeed.Low,
moveDiv: AgvRunDirection.Straight);
return PredictResult;
}
//2. 이동방향을 모른다
if (agv.MovementHistory.Any() == false || agv.MovementHistory.Count < 2)
{
PredictResult = new AGVActionPrediction
{
Direction = Direction.Backward,
NextRFID = null,
Reason = "AGV이동방향 알수없음",
ReasonCode = AGVActionReasonCode.NoDirection,
MoveState = AGVMoveState.Run
};
PredictResult = CreatePrediction("AGV이동방향 알수없음",
AGVActionReasonCode.NoDirection,
AGVMoveState.Run,
Direction.Backward, true,
moveSpeed: AgvSpeed.Low,
moveDiv: AgvRunDirection.Straight);
return PredictResult;
}
// 3. 경로가 없거나 현재 위치가 경로에 없음
if ((agv.MainPath?.Count ?? 0) < 2)
if ((agv.MainPath?.Count ?? 0) < 2 || agv.MainPath.Last().Value != agv.TargetRFID.Value)
{
PredictResult = new AGVActionPrediction
//목적지가 없다면 진행할 수 없다
if (agv.TargetRFID == null)
{
Direction = agv.CurrentMOTDirection,
NextRFID = null,
Reason = "경로 없음 또는 현재 위치 미확정",
ReasonCode = AGVActionReasonCode.NoPath,
MoveState = AGVMoveState.Stop
};
return PredictResult;
PredictResult = CreatePrediction("경로 없음 또는 현재 위치 미확정",
AGVActionReasonCode.NoPath,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true);
return PredictResult;
}
else
{
//목적지가 있는데 경로가 없다면 경로 예측을 진행해야한다.
var CurPt = agv.MovementHistory.Last();
var prlt = CalculatePath(CurPt, agv.TargetRFID);
if (prlt.Success == false)
{
PredictResult = CreatePrediction("목적지 경로 예측 실패",
AGVActionReasonCode.Unknown,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true,
nextRFID: agv.TargetRFID);
return PredictResult;
}
else
{
//신규목적지에 대한 경로예측이 성공했다
agv.SubPath.Clear();
agv.MainPath = prlt.Path;
}
}
}
// 4. 경로상에서 다음 RFID 예측
int idx = agv.MainPath.FindIndex(p => p.Value == agv.CurrentRFID.Value);
if (idx < 0)
{
PredictResult = new AGVActionPrediction
{
Direction = agv.CurrentMOTDirection,
NextRFID = null,
Reason = "현재 위치가 경로에 없음",
ReasonCode = AGVActionReasonCode.NotOnPath,
MoveState = AGVMoveState.Stop
};
PredictResult = CreatePrediction("현재 위치가 경로에 없음",
AGVActionReasonCode.NotOnPath,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true);
return PredictResult;
}
@@ -135,30 +156,25 @@ namespace AGVControl
//모션이동방향이 맞는가?
var IsMotDir = IsMotDirection_To_Target() ?? false;
var PrePT = agv.MovementHistory.Skip(agv.MovementHistory.Count - 1).First();
var PrePT = agv.MovementHistory.Skip(agv.MovementHistory.Count - 2).First();
var curPT = agv.MovementHistory.Last();
//리프트방향이 맞지 않다면 회전가능한 위치로 이동을 해야한다
if (IsLiftDir == false)
{
AgvSpeed? agv_spd = null;
AgvRunDirection? agv_dir = null;
//회전가능한 위치로 이동을 해야한다
//1. 가까운 회전위치를 찾는다
var nearTurnPoint = RFIDPoints.Where(t => t.IsRotatable)?.OrderBy(t => GetDistance(t.Location, agv.CurrentRFID.Location)).FirstOrDefault() ?? null;
if (nearTurnPoint == null)
{
PredictResult= new AGVActionPrediction
{
Direction = agv.CurrentMOTDirection,
NextRFID = null,
Reason = "회전 가능한 위치가 없습니다",
ReasonCode = AGVActionReasonCode.NoTurnPoint,
MoveState = AGVMoveState.Stop
};
PredictResult = CreatePrediction("회전 가능한 위치가 없습니다",
AGVActionReasonCode.NoTurnPoint,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true);
return PredictResult;
}
@@ -168,22 +184,19 @@ namespace AGVControl
if (agv.CurrentRFID.Value != nearTurnPoint.Value)
{
if (agv.SubPath.Any() == false || agv.SubPath.Count < 2 ||
agv.SubPath.First().Value != PrePT.Value ||
agv.SubPath.First().Value != agv.CurrentRFID.Value ||
agv.SubPath.Last().Value != nearTurnPoint.Value)
{
var rlt = CalculatePath(PrePT, nearTurnPoint, true); //이전포인트도 추가를 해준다
var rlt = CalculatePath(agv.CurrentRFID, nearTurnPoint); //이전포인트도 추가를 해준다
if (rlt.Success) agv.SubPath = rlt.Path;
else
{
agv.SubPath.Clear();
PredictResult= new AGVActionPrediction
{
Direction = agv.CurrentMOTDirection,
NextRFID = nearTurnPoint,
Reason = "회전 위치까지의 경로를 계산할 수 없습니다",
ReasonCode = AGVActionReasonCode.PathCalcError,
MoveState = AGVMoveState.Stop
};
PredictResult = CreatePrediction("회전 위치까지의 경로를 계산할 수 없습니다",
AGVActionReasonCode.PathCalcError,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true,
nextRFID: nearTurnPoint);
return PredictResult;
}
}
@@ -194,6 +207,9 @@ namespace AGVControl
var preidx = agv.SubPath.FindIndex(t => t.Value == PrePT.Value);
Direction newdirection = agv.CurrentMOTDirection;
string message = "턴위치로 이동중";
if (preidx > curidx)
{
//지정경로를 거꾸로 이동하고 있다
@@ -202,31 +218,44 @@ namespace AGVControl
else
newdirection = Direction.Forward;
message += "(방향전환)";
}
PredictResult= new AGVActionPrediction
{
Direction = newdirection,
NextRFID = nearTurnPoint,
Reason = message,
ReasonCode = AGVActionReasonCode.MoveForTurn,
MoveState = AGVMoveState.Run,
};
//도로정보를 확인하여 속도와 분기명령을 실행한다
var roadinfo = GetRoadInfo(agv.SubPath, curPT);
agv_spd = roadinfo.spd;
agv_dir = roadinfo.dir;
PredictResult = CreatePrediction(message,
AGVActionReasonCode.MoveForTurn,
AGVMoveState.Run,
newdirection, true,
moveSpeed: agv_spd,
moveDiv: agv_dir,
nextRFID: nearTurnPoint);
return PredictResult;
}
var roadinfo2 = GetRoadInfo(agv.SubPath.Any() ? agv.SubPath : agv.MainPath, curPT);
agv_spd = roadinfo2.spd;
agv_dir = roadinfo2.dir;
PredictResult = new AGVActionPrediction
{
Direction = agv.CurrentMOTDirection,
NextRFID = nearTurnPoint,
Reason = "턴 완료 대기",
ReasonCode = AGVActionReasonCode.NeedTurn,
MoveState = AGVMoveState.Stop
};
PredictResult = CreatePrediction("턴 완료 대기",
AGVActionReasonCode.NeedTurn,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true,
moveSpeed: agv_spd,
moveDiv: agv_dir,
nextRFID: nearTurnPoint);
return PredictResult;
}
//보조이동선제거
if (agv.SubPath != null && agv.SubPath.Any())
agv.SubPath.Clear();
//3. 목적지위치까지 이동이 완료되지 않았다면 계속 이동을 하게한다
if (agv.CurrentRFID.Value != destRFID.Value)
{
@@ -247,39 +276,35 @@ namespace AGVControl
//경로상 바로 다음 위치를 확인한다
var nexstRFID = agv.MainPath.Skip(agv.MainPath.FindIndex(t => t.Value == curPT.Value) + 1).First();
PredictResult = new AGVActionPrediction
{
Direction = newdirection,
NextRFID = nexstRFID,
Reason = message,
ReasonCode = AGVActionReasonCode.Normal,
MoveState = AGVMoveState.Run,
};
var roadinfo = GetRoadInfo(agv.MainPath, curPT);
PredictResult = CreatePrediction(message,
AGVActionReasonCode.Normal,
AGVMoveState.Run,
newdirection, true,
moveSpeed: roadinfo.spd,
moveDiv: roadinfo.dir,
nextRFID: nexstRFID);
return PredictResult;
}
// 5. 목적지 도달 시
PredictResult = new AGVActionPrediction
{
Direction = agv.CurrentMOTDirection,
NextRFID = destRFID,
Reason = "경로의 마지막 지점(목적지 도달)",
ReasonCode = AGVActionReasonCode.Arrived,
MoveState = AGVMoveState.Stop
};
PredictResult = CreatePrediction("경로의 마지막 지점(목적지 도달)",
AGVActionReasonCode.Arrived,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true,
nextRFID: destRFID);
return PredictResult;
}
catch (Exception ex)
{
PredictResult = new AGVActionPrediction
{
Direction = agv.CurrentMOTDirection,
NextRFID = null,
Reason = $"ERR:{ex.Message}",
ReasonCode = AGVActionReasonCode.Unknown,
MoveState = AGVMoveState.Stop
};
PredictResult = CreatePrediction($"ERR:{ex.Message}",
AGVActionReasonCode.Unknown,
AGVMoveState.Stop,
agv.CurrentMOTDirection, true);
return PredictResult;
}
finally
@@ -289,13 +314,43 @@ namespace AGVControl
}
(AgvSpeed? spd, AgvRunDirection? dir, RFIDConnection info) GetRoadInfo(List<RFIDPoint> paths, RFIDPoint curPT)
{
//도로정보를 확인하여 속도와 분기명령을 실행한다
AgvSpeed? agv_spd = null;
AgvRunDirection? agv_div = null;
RFIDConnection info = null;
var nextpt = paths.Skip(paths.FindIndex(t => t.Value == curPT.Value) + 1).FirstOrDefault();
if (nextpt != null)
{
var p1 = rfidConnections.Where(t => t.P1.Value == curPT.Value && t.P2.Value == nextpt.Value).FirstOrDefault();
if (p1 != null)
{
//positive
agv_spd = p1.MoveSpeedP;
agv_div = p1.MoveDirectionP;
info = p1;
}
var p2 = rfidConnections.Where(t => t.P2.Value == curPT.Value && t.P1.Value == nextpt.Value).FirstOrDefault();
if (p2 != null)
{
//negative
agv_spd = p2.MoveSpeedN;
agv_div = p2.MoveDirectionN;
info = p2;
}
}
return (agv_spd, agv_div, info);
}
/// <summary>
/// 이웃포인터를 반환합니다
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public static List<RFIDPoint> GetNeighbors(RFIDPoint pt)
public List<RFIDPoint> GetNeighbors(RFIDPoint pt)
{
var neighbors = new List<RFIDPoint>();
@@ -322,17 +377,17 @@ namespace AGVControl
return neighbors.Distinct().ToList();
}
public static RFIDPoint FindRFIDPoint(uint rfidValue)
public RFIDPoint FindRFIDPoint(uint rfidValue)
{
if (RFIDPoints == null || RFIDPoints.Any() == false) return null;
return RFIDPoints.FirstOrDefault(r => r.Value == rfidValue);
}
private static float Heuristic(Point a, Point b)
private float Heuristic(Point a, Point b)
{
return (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));
}
private static PathResult ReconstructPath(Dictionary<uint, RFIDPoint> cameFrom, RFIDPoint current)
private PathResult ReconstructPath(Dictionary<uint, RFIDPoint> cameFrom, RFIDPoint current)
{
var path = new List<RFIDPoint> { current };
while (cameFrom.ContainsKey(current.Value))
@@ -348,7 +403,7 @@ namespace AGVControl
Path = path,
};
}
public static PathResult CalculatePath(RFIDPoint start, RFIDPoint end, bool autorun)
public PathResult CalculatePath(RFIDPoint start, RFIDPoint end)
{
var openList = new List<RFIDPoint> { start };
var closedList = new List<RFIDPoint>();
@@ -410,7 +465,7 @@ namespace AGVControl
};
}
public static PathResult CalculatePath(uint tagStrt, uint tagEnd)
public PathResult CalculatePath(uint tagStrt, uint tagEnd)
{
var retval = new PathResult
{
@@ -426,7 +481,7 @@ namespace AGVControl
return retval;
}
retval = CalculatePath(startPoint, endPoint, false);
retval = CalculatePath(startPoint, endPoint);
if (retval.Success == false)
retval.Message = "경로를 찾을 수 없습니다";
@@ -439,7 +494,7 @@ namespace AGVControl
/// 이동경로정보가 없거나 목적지가 없으면 null 이 반환됨
/// </summary>
/// <returns></returns>
public static bool? IsLiftDirectionMatch()
public bool? IsLiftDirectionMatch()
{
if (agv.MovementHistory.Any() && agv.MovementHistory.Count > 1)
@@ -498,7 +553,40 @@ namespace AGVControl
}
}
// Changed 속성 설정을 위한 헬퍼 메서드
private bool IsPredictionChanged(AGVActionPrediction newPrediction)
{
if (PredictResult == null) return true; // 이전 예측이 없으면 변경됨
// Idx와 Changed를 제외하고 비교
return !PredictResult.Equals(newPrediction);
}
private AGVActionPrediction CreatePrediction(
string reason,
AGVActionReasonCode reasonCode,
AGVMoveState moveState,
Direction direction, bool IDXUpdate = true,
AgvSpeed? moveSpeed = null,
AgvRunDirection? moveDiv = null,
RFIDPoint nextRFID = null
)
{
var newPrediction = new AGVActionPrediction
{
NextRFID = nextRFID,
Reason = reason,
ReasonCode = reasonCode,
MoveState = moveState,
Direction = direction,
MoveSpeed = moveSpeed,
MoveDiv = moveDiv,
Idx = IDXUpdate ? (PredictResult?.Idx + 1 ?? 1) : (PredictResult?.Idx ?? 0)
};
newPrediction.Changed = IsPredictionChanged(newPrediction);
return newPrediction;
}
}
}

View File

@@ -1,248 +1,242 @@
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Security.Permissions;
using System.Windows.Forms;
namespace AGVControl.Models
{
public enum Direction
{
Forward = 0,
Backward = 1,
Stop = 2
}
//public class CRFIDData
//{
// public UInt16 Value { get; set; }
// public Point Location { get; set; }
// public override string ToString()
// {
// return $"RFID:{Value},P:{Location.X},{Location.Y}";
// }
//}
public class movehistorydata : RFIDPoint
{
public Direction Direction { get; set; }
public override string ToString()
{
return $"RFID:{Value},DIR:{Direction},P:{Location.X},{Location.Y}";
}
}
public class AGV
{
/// <summary>
/// RFID 번호
/// </summary>
public RFIDPoint CurrentRFID { get; set; }
/// <summary>
/// 목적지가 셋팅된경우 해당 값
/// </summary>
public RFIDPoint TargetRFID { get; set; }
/// <summary>
/// 배터리잔량(%)
/// </summary>
public float BatteryLevel { get; set; } = 0f;
/// <summary>
/// 배터리온도(board)
/// </summary>
public double BatteryTemp1 { get; set; } = 0;
/// <summary>
/// 배터리온도(cell)
/// </summary>
public double BatteryTemp2 { get; set; } = 0;
/// <summary>
/// AGV
/// </summary>
public Direction CurrentAGVDirection { get; set; }
/// <summary>
/// AGV모터 방향
/// 외부에서 값이 상시 업데이트 됩니다.
/// </summary>
public Direction CurrentMOTDirection { get; set; }
/// <summary>
/// 현재위치가 수산되면 목적지까지의 방향값이 계산됩니다.
/// </summary>
public Direction TargetDirection { get; set; } = Direction.Stop;
public bool IsMoving { get; set; }
public bool IsMarkCheck { get; set; }
public bool IsTargetDirectionMatch { get; set; }
/// <summary>
/// 메인경로
/// 경로검색으로 입력된 경로
/// </summary>
public List<RFIDPoint> MainPath { get; set; } = new List<RFIDPoint>();
/// <summary>
/// 메인경로외에 거쳐가는 중간 경로
/// </summary>
public List<RFIDPoint> SubPath { get; set; }
public List<string> PathRFIDs { get; set; }
// 이동 경로 기록을 위한 새로운 속성들
public List<movehistorydata> MovementHistory { get; } = new List<movehistorydata>();
public const int HISTORY_SIZE = 10; // 최근 4개 위치 기록
public AGV()
{
MainPath = new List<RFIDPoint>();
SubPath = new List<RFIDPoint>();
PathRFIDs = new List<string>();
CurrentRFID = new RFIDPoint();
TargetRFID = new RFIDPoint();
TargetDirection = Direction.Forward;
// BodyAngle = null;
}
// 이동 경로에 새로운 RFID 추가
public void AddToMovementHistory(UInt16 rfidValue, Point position, Direction direction)
{
// 중복 RFID가 연속으로 들어오는 경우 무시
if (MovementHistory.Count > 0 && MovementHistory.Last().Value == rfidValue)
return;
MovementHistory.Add(new movehistorydata { Value = rfidValue, Direction = direction, Location = position });
// 기록 크기 제한
if (MovementHistory.Count > HISTORY_SIZE)
{
MovementHistory.RemoveAt(0);
}
//최초방향과 마지막 방향이 일치하지 않으면 그 이전의 데이터는 삭제한다.
if (MovementHistory.Count > 2 && MovementHistory.First().Direction != MovementHistory.Last().Direction)
{
var lastTwo = MovementHistory.Skip(MovementHistory.Count - 2).Take(2).ToArray(); // [9, 10]
MovementHistory.Clear();
MovementHistory.AddRange(lastTwo);
}
}
// 연결 정보 기반 실제 이동 방향 계산
public Direction? CalculateActualDirectionByConnection(uint currentRFID, uint previousRFID, List<RFIDConnection> connections)
{
if (connections == null || connections.Count == 0)
return null;
// 이전 RFID에서 현재 RFID로의 연결 확인
var connection = connections.FirstOrDefault(c =>
(c.P1.Value == previousRFID && c.P2.Value == currentRFID) ||
(c.P1.Value == currentRFID && c.P2.Value == previousRFID));
if (connection == null)
return null; // 연결되지 않은 경로
// 연결 방향에 따라 실제 이동 방향 결정
if (connection.P1.Value == previousRFID && connection.P2.Value == currentRFID)
{
return Direction.Forward; // Start -> End 방향으로 이동
}
else
{
return Direction.Backward; // End -> Start 방향으로 이동
}
}
// 연결 정보 기반 방향 불일치 검증 및 정정
public bool ValidateAndCorrectDirectionByConnection(Direction expectedDirection, List<RFIDConnection> connections)
{
if (MovementHistory.Count < 2 || connections == null)
return true; // 검증 불가능한 경우
// 최근 두 RFID 값 가져오기
var recentRFIDs = MovementHistory.Skip(MovementHistory.Count - 2).Take(2).ToList();
if (recentRFIDs.Count < 2)
return true;
var previousRFID = recentRFIDs[0];
var currentRFID = recentRFIDs[1];
var actualDirection = CalculateActualDirectionByConnection(currentRFID.Value, previousRFID.Value, connections);
if (!actualDirection.HasValue)
return true; // 연결 정보로 방향 판단 불가
// 방향이 일치하지 않는 경우
if (actualDirection.Value != expectedDirection)
{
// AGV 모터 방향을 실제 이동 방향으로 정정
CurrentAGVDirection = actualDirection.Value;
TargetDirection = actualDirection.Value;
return false; // 정정됨을 알림
}
return true; // 방향 일치
}
// RFID 순서 기반 실제 이동 방향 계산 (기존 메서드 - 호환성 유지)
public Direction? CalculateActualDirectionByRFID()
{
if (MovementHistory.Count < 2)
return null;
// 최근 두 RFID 값으로부터 실제 이동 방향 계산
var recentRFIDs = MovementHistory.Skip(Math.Max(0, MovementHistory.Count - 2)).Take(2).ToList();
if (recentRFIDs.Count < 2)
return null;
var prevRFID = recentRFIDs[0];
var currentRFID = recentRFIDs[1];
// RFID 값의 증가/감소로 방향 판단
if (currentRFID.Value > prevRFID.Value)
{
return Direction.Forward; // RFID 값이 증가하면 전진
}
else if (currentRFID.Value < prevRFID.Value)
{
return Direction.Backward; // RFID 값이 감소하면 후진
}
else
{
return null; // 같은 RFID 값이면 방향 판단 불가
}
}
}
public class PathNode
{
public Point Location { get; set; }
public string RFID { get; set; }
public double G { get; set; } // 시작점에서 현재 노드까지의 비용
public double H { get; set; } // 현재 노드에서 목표점까지의 예상 비용
public double F => G + H; // 총 비용
public PathNode Parent { get; set; }
public PathNode(Point location, string rfid)
{
Location = location;
RFID = rfid;
G = 0;
H = 0;
Parent = null;
}
}
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Security.Permissions;
using System.Windows.Forms;
namespace AGVControl.Models
{
//public class CRFIDData
//{
// public UInt16 Value { get; set; }
// public Point Location { get; set; }
// public override string ToString()
// {
// return $"RFID:{Value},P:{Location.X},{Location.Y}";
// }
//}
public class movehistorydata : RFIDPoint
{
public Direction Direction { get; set; }
public override string ToString()
{
return $"RFID:{Value},DIR:{Direction},P:{Location.X},{Location.Y}";
}
}
public class AGV
{
/// <summary>
/// RFID 번호
/// </summary>
public RFIDPoint CurrentRFID { get; set; }
/// <summary>
/// 목적지가 셋팅된경우 해당 값
/// </summary>
public RFIDPoint TargetRFID { get; set; }
/// <summary>
/// 배터리잔량(%)
/// </summary>
public float BatteryLevel { get; set; } = 0f;
/// <summary>
/// 배터리온도(board)
/// </summary>
public double BatteryTemp1 { get; set; } = 0;
/// <summary>
/// 배터리온도(cell)
/// </summary>
public double BatteryTemp2 { get; set; } = 0;
/// <summary>
/// AGV
/// </summary>
public Direction CurrentAGVDirection { get; set; }
/// <summary>
/// AGV모터 방향
/// 외부에서 값이 상시 업데이트 됩니다.
/// </summary>
public Direction CurrentMOTDirection { get; set; }
/// <summary>
/// 현재위치가 수산되면 목적지까지의 방향값이 계산됩니다.
/// </summary>
public Direction TargetDirection { get; set; } = Direction.Stop;
public bool IsMoving { get; set; }
public bool IsMarkCheck { get; set; }
public bool IsTargetDirectionMatch { get; set; }
/// <summary>
/// 메인경로
/// 경로검색으로 입력된 경로
/// </summary>
public List<RFIDPoint> MainPath { get; set; } = new List<RFIDPoint>();
/// <summary>
/// 메인경로외에 거쳐가는 중간 경로
/// </summary>
public List<RFIDPoint> SubPath { get; set; }
public List<string> PathRFIDs { get; set; }
// 이동 경로 기록을 위한 새로운 속성들
public List<movehistorydata> MovementHistory { get; } = new List<movehistorydata>();
public const int HISTORY_SIZE = 10; // 최근 4개 위치 기록
public AGV()
{
MainPath = new List<RFIDPoint>();
SubPath = new List<RFIDPoint>();
PathRFIDs = new List<string>();
CurrentRFID = new RFIDPoint();
TargetRFID = new RFIDPoint();
TargetDirection = Direction.Forward;
// BodyAngle = null;
}
// 이동 경로에 새로운 RFID 추가
public void AddToMovementHistory(UInt16 rfidValue, Point position, Direction direction)
{
// 중복 RFID가 연속으로 들어오는 경우 무시
if (MovementHistory.Count > 0 && MovementHistory.Last().Value == rfidValue)
return;
MovementHistory.Add(new movehistorydata { Value = rfidValue, Direction = direction, Location = position });
// 기록 크기 제한
if (MovementHistory.Count > HISTORY_SIZE)
{
MovementHistory.RemoveAt(0);
}
//최초방향과 마지막 방향이 일치하지 않으면 그 이전의 데이터는 삭제한다.
if (MovementHistory.Count > 2 && MovementHistory.First().Direction != MovementHistory.Last().Direction)
{
var lastTwo = MovementHistory.Skip(MovementHistory.Count - 2).Take(2).ToArray(); // [9, 10]
MovementHistory.Clear();
MovementHistory.AddRange(lastTwo);
}
}
// 연결 정보 기반 실제 이동 방향 계산
public Direction? CalculateActualDirectionByConnection(uint currentRFID, uint previousRFID, List<RFIDConnection> connections)
{
if (connections == null || connections.Count == 0)
return null;
// 이전 RFID에서 현재 RFID로의 연결 확인
var connection = connections.FirstOrDefault(c =>
(c.P1.Value == previousRFID && c.P2.Value == currentRFID) ||
(c.P1.Value == currentRFID && c.P2.Value == previousRFID));
if (connection == null)
return null; // 연결되지 않은 경로
// 연결 방향에 따라 실제 이동 방향 결정
if (connection.P1.Value == previousRFID && connection.P2.Value == currentRFID)
{
return Direction.Forward; // Start -> End 방향으로 이동
}
else
{
return Direction.Backward; // End -> Start 방향으로 이동
}
}
// 연결 정보 기반 방향 불일치 검증 및 정정
public bool ValidateAndCorrectDirectionByConnection(Direction expectedDirection, List<RFIDConnection> connections)
{
if (MovementHistory.Count < 2 || connections == null)
return true; // 검증 불가능한 경우
// 최근 두 RFID 값 가져오기
var recentRFIDs = MovementHistory.Skip(MovementHistory.Count - 2).Take(2).ToList();
if (recentRFIDs.Count < 2)
return true;
var previousRFID = recentRFIDs[0];
var currentRFID = recentRFIDs[1];
var actualDirection = CalculateActualDirectionByConnection(currentRFID.Value, previousRFID.Value, connections);
if (!actualDirection.HasValue)
return true; // 연결 정보로 방향 판단 불가
// 방향이 일치하지 않는 경우
if (actualDirection.Value != expectedDirection)
{
// AGV 모터 방향을 실제 이동 방향으로 정정
CurrentAGVDirection = actualDirection.Value;
TargetDirection = actualDirection.Value;
return false; // 정정됨을 알림
}
return true; // 방향 일치
}
// RFID 순서 기반 실제 이동 방향 계산 (기존 메서드 - 호환성 유지)
public Direction? CalculateActualDirectionByRFID()
{
if (MovementHistory.Count < 2)
return null;
// 최근 두 RFID 값으로부터 실제 이동 방향 계산
var recentRFIDs = MovementHistory.Skip(Math.Max(0, MovementHistory.Count - 2)).Take(2).ToList();
if (recentRFIDs.Count < 2)
return null;
var prevRFID = recentRFIDs[0];
var currentRFID = recentRFIDs[1];
// RFID 값의 증가/감소로 방향 판단
if (currentRFID.Value > prevRFID.Value)
{
return Direction.Forward; // RFID 값이 증가하면 전진
}
else if (currentRFID.Value < prevRFID.Value)
{
return Direction.Backward; // RFID 값이 감소하면 후진
}
else
{
return null; // 같은 RFID 값이면 방향 판단 불가
}
}
}
public class PathNode
{
public Point Location { get; set; }
public string RFID { get; set; }
public double G { get; set; } // 시작점에서 현재 노드까지의 비용
public double H { get; set; } // 현재 노드에서 목표점까지의 예상 비용
public double F => G + H; // 총 비용
public PathNode Parent { get; set; }
public PathNode(Point location, string rfid)
{
Location = location;
RFID = rfid;
G = 0;
H = 0;
Parent = null;
}
}
}

View File

@@ -1,4 +1,5 @@
using AGVControl.Models;
using System;
namespace AGVControl
{
@@ -9,6 +10,48 @@ namespace AGVControl
public string Reason { get; set; }
public AGVActionReasonCode ReasonCode { get; set; }
public AGVMoveState MoveState { get; set; } // RUN 또는 STOP
}
public AgvSpeed? MoveSpeed { get; set; }
public AgvRunDirection? MoveDiv { get; set; }
public UInt32 Idx { get; set; }
public bool Changed { get; set; }
// override object.Equals
public bool Equals(AGVActionPrediction obj)
{
// null 체크
if (obj == null) return false;
// 참조가 같으면 true
if (ReferenceEquals(this, obj)) return true;
// 핵심 속성들만 비교 (Idx, Changed 제외)
if (obj.Direction != this.Direction) return false;
if (obj.ReasonCode != this.ReasonCode) return false;
if (obj.MoveState != this.MoveState) return false;
if (obj.MoveSpeed != this.MoveSpeed) return false;
if (obj.MoveDiv != this.MoveDiv) return false;
// NextRFID 비교 (null 체크 포함)
if (obj.NextRFID == null || this.NextRFID == null)
{
if (obj.NextRFID != this.NextRFID) return false; // 하나만 null이면 false
}
else
{
if (obj.NextRFID.Value != this.NextRFID.Value) return false;
}
// Reason 비교 (null 체크 포함)
if (obj.Reason == null || this.Reason == null)
{
if (obj.Reason != this.Reason) return false; // 하나만 null이면 false
}
else
{
if (obj.Reason != this.Reason) return false;
}
return true;
}
}
}

View File

@@ -1,9 +1,75 @@
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace AGVControl.Models
{
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>Ư<EFBFBD><C6AF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class RoadInformation
{
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public RFIDPoint P1 { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public RFIDPoint P2 { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EBBFA9>
/// </summary>
public bool Enable { get; set; }
/// <summary>
/// AGV<47><56> <20>̵<EFBFBD><CCB5><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD><EFBFBD>Ʈ<EFBFBD><C6AE><EFBFBD><EFBFBD>)
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ġ<EFBFBD><C4A1> <20><><EFBFBD><EFBFBD> <20>ش<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20>ֵ<EFBFBD>
/// </summary>
public Direction? LiftDirection { get; set; }
/// <summary>
/// AGV<47>̵<EFBFBD><CCB5><EFBFBD> <20>ӵ<EFBFBD> (high, middle, low)
/// </summary>
public AgvSpeed? MoveSpeed { get; set; }
/// <summary>
/// AGV<47>̵<EFBFBD><CCB5><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28>ºб<C2BA>, <20><><EFBFBD><EFBFBD>, <20><><EFBFBD>б<EFBFBD>)
/// </summary>
public AgvRunDirection? MoveDirection { get; set; }
public RoadInformation()
{
P1 = null;
P2 = null;
LiftDirection = null;
MoveSpeed = null;
MoveDirection = null;
Enable = false;
}
/// <summary>
/// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ǿ<EFBFBD><C7BE>ִ<EFBFBD><D6B4><EFBFBD>
/// </summary>
public bool HasValue
{
get
{
if (P1 == null || P2 == null) return false;
if (LiftDirection == null && MoveSpeed == null && MoveDirection == null) return false;
return true;
}
}
}
public class MagnetLine
{
public Point StartPoint { get; set; }

View File

@@ -1,14 +1,195 @@
using AGVControl.Models;
using AR;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.Tracing;
namespace AGVControl
{
public class RFIDConnection
{
/// <summary>
/// 시작지점
/// </summary>
public RFIDPoint P1 { get; set; }
/// <summary>
/// 종료지점
/// </summary>
public RFIDPoint P2 { get; set; }
public bool DisableP1_to_P2 { get; set; }
public bool DisableP2_to_P1 { get; set; }
public float Distance { get; set; }
/// <summary>
/// 도로의 사용여부
/// </summary>
public bool EnableP { get; set; }
public bool EnableN { get; set; }
/// <summary>
/// AGV의 이동방향(리프트방향)
/// 목적지 방향과의 일치를 위해 해당 방향을 설정할 수 있따
/// </summary>
public Direction? LiftDirectionP { get; set; }
public Direction? LiftDirectionN { get; set; }
/// <summary>
/// AGV이동시 속도 (high, middle, low)
/// </summary>
public AgvSpeed? MoveSpeedP { get; set; }
public AgvSpeed? MoveSpeedN { get; set; }
/// <summary>
/// AGV이동시 방향모드(좌분기, 전진, 우분기)
/// </summary>
public AgvRunDirection? MoveDirectionP { get; set; }
public AgvRunDirection? MoveDirectionN { get; set; }
/// <summary>
/// 파일저장 및 불러오기시 사용하는 문자열로 반환
/// </summary>
[Browsable(false)]
public string DataFileString
{
get
{
var str_enbP = EnableP ? "1" : "0";
var str_liftP = "";
if (LiftDirectionP != null) str_liftP = ((int)LiftDirectionP).ToString();
var str_movespeedP = "";
if (MoveSpeedP != null) str_movespeedP = ((int)MoveSpeedP).ToString();
var str_movdirP = "";
if (MoveDirectionP != null) str_movdirP = ((int)MoveDirectionP).ToString();
var str_enbN = EnableP ? "1" : "0";
var str_liftN = "";
if (LiftDirectionN != null) str_liftN = ((int)LiftDirectionN).ToString();
var str_movespeedN = "";
if (MoveSpeedN != null) str_movespeedN = ((int)MoveSpeedN).ToString();
var str_movdirN = "";
if (MoveDirectionN != null) str_movdirN = ((int)MoveDirectionN).ToString();
var PStr= $"{P1.Location.X},{P1.Location.Y},{P2.Location.X},{P2.Location.Y}," + //location
$"{P1.Value},{P2.Value}," + //rfid values
$"{str_enbP};{str_liftP};{str_movespeedP};{str_movdirP}," +
$"{str_enbN};{str_liftN};{str_movespeedN};{str_movdirN}";
return $"{PStr}";
}
set
{
var buf = value.Split(',');
if (buf.Length >= 2)
{
var p1x = int.Parse(buf[0]);
var p1y = int.Parse(buf[1]);
var p2x = int.Parse(buf[2]);
var p2y = int.Parse(buf[3]);
var p1v = uint.Parse(buf[4]);
var p2v = uint.Parse(buf[5]);
if (P1 == null) P1 = new RFIDPoint();
P1.Location = new System.Drawing.Point(p1x, p1y);
P1.Value = p1v;
if (P2 == null) P2 = new RFIDPoint();
P2.Location = new System.Drawing.Point(p2x, p2y);
P2.Value = p2v;
if (buf[6].Contains(";")) //양방향 정보
{
var StrP = buf[6].Split(';');
var StrN = buf[7].Split(';');
//Positive
this.EnableP = StrP[0] == "1";
if (StrP[1].isEmpty()) LiftDirectionP = null;
else LiftDirectionP = (Direction)int.Parse(StrP[1]);
if (StrP[2].isEmpty()) MoveSpeedP = null;
else MoveSpeedP = (AgvSpeed)int.Parse(StrP[2]);
if (StrP[3].isEmpty()) MoveDirectionP = null;
else MoveDirectionP = (AgvRunDirection)int.Parse(StrP[3]);
//Negative
this.EnableN = StrN[0] == "1";
if (StrN[1].isEmpty()) LiftDirectionN = null;
else LiftDirectionN = (Direction)int.Parse(StrN[1]);
if (StrN[2].isEmpty()) MoveSpeedN = null;
else MoveSpeedN = (AgvSpeed)int.Parse(StrN[2]);
if (StrN[3].isEmpty()) MoveDirectionN = null;
else MoveDirectionN = (AgvRunDirection)int.Parse(StrN[3]);
}
else
{
this.EnableP = buf[6] == "1";
if (buf[7].isEmpty()) LiftDirectionP = null;
else LiftDirectionP = (Direction)int.Parse(buf[7]);
if (buf[8].isEmpty()) MoveSpeedP = null;
else MoveSpeedP = (AgvSpeed)int.Parse(buf[8]);
if (buf[9].isEmpty()) MoveDirectionP = null;
else MoveDirectionP = (AgvRunDirection)int.Parse(buf[9]);
this.EnableN = this.EnableP;
this.LiftDirectionN = this.LiftDirectionP;
this.MoveSpeedN = this.MoveSpeedP;
this.MoveDirectionN = this.MoveDirectionP;
}
}
}
}
public RFIDConnection(string dataline = "")
{
P1 = null;
P2 = null;
LiftDirectionP = null;
MoveSpeedP = null;
MoveDirectionP = null;
EnableP = false;
LiftDirectionN = null;
MoveSpeedN = null;
MoveDirectionP = null;
EnableP = false;
if (dataline.isEmpty() == false) DataFileString = dataline;
}
/// <summary>
/// 값이 설정되어있는지
/// </summary>
public bool HasValue
{
get
{
if (P1 == null || P2 == null) return false;
if (LiftDirectionP == null && MoveSpeedP == null && MoveDirectionP == null &&
LiftDirectionN == null && MoveSpeedN == null && MoveDirectionN == null) return false;
return true;
}
}
public override bool Equals(object obj)
@@ -27,7 +208,7 @@ namespace AGVControl
public override string ToString()
{
//연결정보를 확인
return $"{P1.Value} ↔ {P2.Value},P1-2:{(DisableP1_to_P2 ? "X" : "O")},P2-1:{(DisableP2_to_P1 ? "X" : "O")}";
return $"{P1.Value} ↔ {P2.Value},ENB:{(EnableP ? "O" : "X")}|{(EnableN ? "O" : "X")}";
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Drawing;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace AGVControl.Models
{
public class RFIDPoint
@@ -11,7 +12,9 @@ namespace AGVControl.Models
public bool IsBidirectional { get; set; } // 양방향 연결 여부
public bool IsRotatable { get; set; } // 회전 가능 여부
public Direction? FixedDirection { get; set; } // 고정 방향(없으면 null)
public bool IsTerminal { get; set; } // 종단 여부
public bool IsTerminal { get; set; } // 종단 여부
[Browsable(false)]
public RectangleF Bounds { get; set; }
public void Clear()
{

View File

@@ -6,12 +6,32 @@ using System.Threading.Tasks;
namespace AGVControl
{
public enum Direction
{
Forward = 0,
Backward = 1,
Stop = 2
}
public enum AGVMoveState
{
Stop = 0,
Run
}
public enum AgvSpeed
{
High,
Middle,
Low,
}
public enum AgvRunDirection
{
Straight,
Left,
Right,
}
public enum AGVActionReasonCode
{

View File

@@ -45,6 +45,18 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Dialog\fMapDesign.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fMapDesign.Designer.cs">
<DependentUpon>fMapDesign.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fPropertyRFIDPoint.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fPropertyRFIDPoint.Designer.cs">
<DependentUpon>fPropertyRFIDPoint.cs</DependentUpon>
</Compile>
<Compile Include="MapControlManager.cs" />
<Compile Include="Models\AGVActionPrediction.cs" />
<Compile Include="BatteryLevelGauge.cs">
@@ -103,6 +115,12 @@
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Dialog\fMapDesign.resx">
<DependentUpon>fMapDesign.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fPropertyRFIDPoint.resx">
<DependentUpon>fPropertyRFIDPoint.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="MapControl.resx">
<DependentUpon>MapControl.cs</DependentUpon>
</EmbeddedResource>