This commit is contained in:
backuppc
2026-01-30 16:58:14 +09:00
parent 3a8cbd3283
commit faf13f5c37
22 changed files with 1137 additions and 417 deletions

View File

@@ -197,6 +197,12 @@
<Compile Include="Dialog\fCounter.Designer.cs">
<DependentUpon>fCounter.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fSetCurrentPosition.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fSetCurrentPosition.Designer.cs">
<DependentUpon>fSetCurrentPosition.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fXbeeSetting.cs">
<SubType>Form</SubType>
</Compile>
@@ -412,6 +418,9 @@
<EmbeddedResource Include="Dialog\fCounter.resx">
<DependentUpon>fCounter.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fSetCurrentPosition.resx">
<DependentUpon>fSetCurrentPosition.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fXbeeSetting.resx">
<DependentUpon>fXbeeSetting.cs</DependentUpon>
</EmbeddedResource>
@@ -573,9 +582,7 @@
<Name>CommData</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="StateMachine\Display\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>

View File

@@ -181,20 +181,20 @@ namespace Project.Device
/// 오류코드를 호스트에 전송합니다
/// </summary>
/// <param name="errcode"></param>
public void SendError(ENIGProtocol.AGVErrorCode errcode, string errormessage)
public void SendError(ENIGProtocol.AGVErrorCode errcode)
{
// Update global error state so it persists in Status messages
PUB.Result.RunStepErrorCode = errcode;
PUB.Result.ResultMessage = errormessage;
//PUB.Result.ResultMessage = errormessage;
var id = PUB.setting.XBE_ID;
byte cmd = (byte)ENIGProtocol.AGVCommandEH.Error;
if (errormessage.Length > 30) errormessage = errormessage.Substring(0, 29);
// if (errormessage.Length > 30) errormessage = errormessage.Substring(0, 29);
var data = new List<byte>();
data.Add((byte)errcode);
var datamsg = System.Text.Encoding.Default.GetBytes(errormessage);
data.AddRange(datamsg);
//var datamsg = System.Text.Encoding.Default.GetBytes(errormessage);
// data.AddRange(datamsg);
var packet = proto.CreatePacket(id, cmd, data.ToArray());
Send(packet);

View File

@@ -0,0 +1,230 @@
namespace Project.Dialog
{
partial class fSetCurrentPosition
{
/// <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);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.mainLayout = new System.Windows.Forms.TableLayoutPanel();
this.controlPanel = new System.Windows.Forms.Panel();
this._lblStatus = new System.Windows.Forms.Label();
this.btnForward = new System.Windows.Forms.Button();
this.btnBackward = new System.Windows.Forms.Button();
this.btnOK = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.btnTurnR90 = new System.Windows.Forms.Button();
this.lblTurn = new System.Windows.Forms.Label();
this.btnTurnNone = new System.Windows.Forms.Button();
this.btnTurnL90 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.mainLayout.SuspendLayout();
this.controlPanel.SuspendLayout();
this.SuspendLayout();
//
// mainLayout
//
this.mainLayout.ColumnCount = 1;
this.mainLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.mainLayout.Controls.Add(this.controlPanel, 0, 2);
this.mainLayout.Controls.Add(this.label1, 0, 0);
this.mainLayout.Dock = System.Windows.Forms.DockStyle.Fill;
this.mainLayout.Location = new System.Drawing.Point(0, 0);
this.mainLayout.Name = "mainLayout";
this.mainLayout.RowCount = 3;
this.mainLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F));
this.mainLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 84.60411F));
this.mainLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 15.39589F));
this.mainLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.mainLayout.Size = new System.Drawing.Size(1264, 761);
this.mainLayout.TabIndex = 0;
//
// controlPanel
//
this.controlPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(30)))), ((int)(((byte)(30)))));
this.controlPanel.Controls.Add(this._lblStatus);
this.controlPanel.Controls.Add(this.btnForward);
this.controlPanel.Controls.Add(this.btnBackward);
this.controlPanel.Controls.Add(this.btnOK);
this.controlPanel.Controls.Add(this.btnCancel);
this.controlPanel.Controls.Add(this.btnTurnR90);
this.controlPanel.Controls.Add(this.lblTurn);
this.controlPanel.Controls.Add(this.btnTurnNone);
this.controlPanel.Controls.Add(this.btnTurnL90);
this.controlPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.controlPanel.Location = new System.Drawing.Point(3, 652);
this.controlPanel.Name = "controlPanel";
this.controlPanel.Padding = new System.Windows.Forms.Padding(10);
this.controlPanel.Size = new System.Drawing.Size(1258, 106);
this.controlPanel.TabIndex = 1;
//
// _lblStatus
//
this._lblStatus.Font = new System.Drawing.Font("맑은 고딕", 12F, System.Drawing.FontStyle.Bold);
this._lblStatus.ForeColor = System.Drawing.Color.White;
this._lblStatus.Location = new System.Drawing.Point(7, 7);
this._lblStatus.Margin = new System.Windows.Forms.Padding(3, 10, 20, 0);
this._lblStatus.Name = "_lblStatus";
this._lblStatus.Size = new System.Drawing.Size(653, 42);
this._lblStatus.TabIndex = 0;
this._lblStatus.Text = "위치를 선택해 주세요.";
this._lblStatus.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnForward
//
this.btnForward.BackColor = System.Drawing.Color.DarkSlateGray;
this.btnForward.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnForward.ForeColor = System.Drawing.Color.White;
this.btnForward.Location = new System.Drawing.Point(721, 51);
this.btnForward.Name = "btnForward";
this.btnForward.Size = new System.Drawing.Size(120, 38);
this.btnForward.TabIndex = 1;
this.btnForward.Text = "전진 (Forward)";
this.btnForward.UseVisualStyleBackColor = false;
//
// btnBackward
//
this.btnBackward.BackColor = System.Drawing.Color.DarkSlateGray;
this.btnBackward.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnBackward.ForeColor = System.Drawing.Color.White;
this.btnBackward.Location = new System.Drawing.Point(847, 51);
this.btnBackward.Name = "btnBackward";
this.btnBackward.Size = new System.Drawing.Size(126, 38);
this.btnBackward.TabIndex = 2;
this.btnBackward.Text = "후진 (Backward)";
this.btnBackward.UseVisualStyleBackColor = false;
//
// btnOK
//
this.btnOK.BackColor = System.Drawing.Color.DarkGreen;
this.btnOK.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnOK.ForeColor = System.Drawing.Color.White;
this.btnOK.Location = new System.Drawing.Point(7, 51);
this.btnOK.Margin = new System.Windows.Forms.Padding(50, 3, 3, 3);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(150, 38);
this.btnOK.TabIndex = 7;
this.btnOK.Text = "설정 완료";
this.btnOK.UseVisualStyleBackColor = false;
//
// btnCancel
//
this.btnCancel.BackColor = System.Drawing.Color.Maroon;
this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnCancel.ForeColor = System.Drawing.Color.White;
this.btnCancel.Location = new System.Drawing.Point(163, 51);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(100, 38);
this.btnCancel.TabIndex = 8;
this.btnCancel.Text = "취소";
this.btnCancel.UseVisualStyleBackColor = false;
//
// btnTurnR90
//
this.btnTurnR90.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(60)))), ((int)(((byte)(60)))));
this.btnTurnR90.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnTurnR90.ForeColor = System.Drawing.Color.White;
this.btnTurnR90.Location = new System.Drawing.Point(893, 7);
this.btnTurnR90.Name = "btnTurnR90";
this.btnTurnR90.Size = new System.Drawing.Size(80, 40);
this.btnTurnR90.TabIndex = 6;
this.btnTurnR90.Text = "R90";
this.btnTurnR90.UseVisualStyleBackColor = false;
//
// lblTurn
//
this.lblTurn.AutoSize = true;
this.lblTurn.ForeColor = System.Drawing.Color.White;
this.lblTurn.Location = new System.Drawing.Point(677, 20);
this.lblTurn.Margin = new System.Windows.Forms.Padding(20, 10, 5, 0);
this.lblTurn.Name = "lblTurn";
this.lblTurn.Size = new System.Drawing.Size(35, 12);
this.lblTurn.TabIndex = 3;
this.lblTurn.Text = "Turn:";
//
// btnTurnNone
//
this.btnTurnNone.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(60)))), ((int)(((byte)(60)))));
this.btnTurnNone.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnTurnNone.ForeColor = System.Drawing.Color.White;
this.btnTurnNone.Location = new System.Drawing.Point(721, 7);
this.btnTurnNone.Name = "btnTurnNone";
this.btnTurnNone.Size = new System.Drawing.Size(80, 40);
this.btnTurnNone.TabIndex = 4;
this.btnTurnNone.Text = "None";
this.btnTurnNone.UseVisualStyleBackColor = false;
//
// btnTurnL90
//
this.btnTurnL90.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(60)))), ((int)(((byte)(60)))));
this.btnTurnL90.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnTurnL90.ForeColor = System.Drawing.Color.White;
this.btnTurnL90.Location = new System.Drawing.Point(807, 7);
this.btnTurnL90.Name = "btnTurnL90";
this.btnTurnL90.Size = new System.Drawing.Size(80, 40);
this.btnTurnL90.TabIndex = 5;
this.btnTurnL90.Text = "L90";
this.btnTurnL90.UseVisualStyleBackColor = false;
//
// label1
//
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Font = new System.Drawing.Font("맑은 고딕", 12F, System.Drawing.FontStyle.Bold);
this.label1.Location = new System.Drawing.Point(3, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(1258, 40);
this.label1.TabIndex = 2;
this.label1.Text = "현재 위치에 해당하는 노드를 선택하세요";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// fSetCurrentPosition
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1264, 761);
this.Controls.Add(this.mainLayout);
this.Name = "fSetCurrentPosition";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "AGV 시작 위치 및 방향 설정";
this.mainLayout.ResumeLayout(false);
this.controlPanel.ResumeLayout(false);
this.controlPanel.PerformLayout();
this.ResumeLayout(false);
}
private System.Windows.Forms.TableLayoutPanel mainLayout;
private System.Windows.Forms.Panel controlPanel;
private System.Windows.Forms.Label _lblStatus;
private System.Windows.Forms.Button btnForward;
private System.Windows.Forms.Button btnBackward;
private System.Windows.Forms.Label lblTurn;
private System.Windows.Forms.Button btnTurnNone;
private System.Windows.Forms.Button btnTurnL90;
private System.Windows.Forms.Button btnTurnR90;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Label label1;
}
}

View File

@@ -0,0 +1,128 @@
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;
using AGVNavigationCore.Models;
using AGVNavigationCore.Controls;
namespace Project.Dialog
{
public partial class fSetCurrentPosition : Form
{
private UnifiedAGVCanvas _canvas;
private MapNode _selectedNode;
private AgvDirection _selectedDirection = AgvDirection.Forward;
private AGVTurn _selectedTurn = AGVTurn.None;
public MapNode SelectedNode => _selectedNode;
public AgvDirection SelectedDirection => _selectedDirection;
public AGVTurn SelectedTurn => _selectedTurn;
public fSetCurrentPosition()
{
InitializeComponent();
if (PUB.setting.FullScreen) this.WindowState = FormWindowState.Maximized;
SetupCanvas();
WireEvents();
this.Load += FSetCurrentPosition_Load;
}
private void SetupCanvas()
{
_canvas = new UnifiedAGVCanvas
{
Dock = DockStyle.Fill,
Mode = UnifiedAGVCanvas.CanvasMode.Run,
BackColor = Color.FromArgb(45, 45, 48),
ShowGrid=false,
};
mainLayout.Controls.Add(_canvas, 1, 0);
}
private void WireEvents()
{
_canvas.NodeSelect += _canvas_NodeSelect;
btnForward.Click += (s, e) => { _selectedDirection = AgvDirection.Forward; UpdateStatus(); };
btnBackward.Click += (s, e) => { _selectedDirection = AgvDirection.Backward; UpdateStatus(); };
btnTurnNone.Click += (s, e) => { _selectedTurn = AGVTurn.None; UpdateStatus(); };
btnTurnL90.Click += (s, e) => { _selectedTurn = AGVTurn.L90; UpdateStatus(); };
btnTurnR90.Click += (s, e) => { _selectedTurn = AGVTurn.R90; UpdateStatus(); };
btnOK.Click += BtnOK_Click;
btnCancel.Click += (s, e) => { this.DialogResult = DialogResult.Cancel; this.Close(); };
}
private void FSetCurrentPosition_Load(object sender, EventArgs e)
{
// 맵 데이터 강제 복사 (PUB과 동일한 맵 사용)
if (PUB._mapCanvas != null)
{
_canvas.SetMapData(PUB._mapCanvas.Nodes, PUB._mapCanvas.Labels, PUB._mapCanvas.Images, PUB._mapCanvas.Marks, PUB._mapCanvas.Magnets);
}
// 마지막 위치 로드
var lastPos = PUB.LoadLastPosition();
if (lastPos != null)
{
_selectedNode = _canvas.Nodes.FirstOrDefault(n => n.Id == lastPos.NodeId);
_selectedDirection = lastPos.Direction;
_selectedTurn = lastPos.Turn;
if (_selectedNode != null)
{
_canvas.SelectedNode = _selectedNode;
_canvas.PanToNode(_selectedNode.Id);
UpdateStatus(isRestore: true);
}
}
this.Show();
_canvas.ResetZoom();
}
private void _canvas_NodeSelect(object sender, NodeBase node, MouseEventArgs e)
{
if (node is MapNode mapNode && mapNode.IsNavigationNode())
{
_selectedNode = mapNode;
_canvas.SelectedNode = mapNode;
UpdateStatus();
}
}
private void UpdateStatus(bool isRestore = false)
{
if (_selectedNode == null)
{
_lblStatus.Text = "위치를 선택해 주세요.";
_lblStatus.ForeColor = Color.White;
return;
}
string dirStr = _selectedDirection == AgvDirection.Forward ? "전진" : "후진";
_lblStatus.Text = $"{(isRestore ? "[] " : "")}위치: {_selectedNode.Id} ({_selectedNode.ID2}) | 방향: {dirStr} | Turn: {_selectedTurn}";
_lblStatus.ForeColor = isRestore ? Color.Yellow : Color.Cyan;
// 캔버스에 가상 AGV 미리보기 (선택 사항)
_canvas.Invalidate();
}
private void BtnOK_Click(object sender, EventArgs e)
{
if (_selectedNode == null)
{
MessageBox.Show("AGV의 현재 위치를 맵에서 클릭하여 선택해 주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
this.DialogResult = DialogResult.OK;
this.Close();
}
}
}

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>

View File

@@ -8,6 +8,8 @@ using System.Media;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System;
using System.IO;
using Newtonsoft.Json;
#if SPEECH
using Microsoft.Speech.Synthesis;
@@ -23,6 +25,14 @@ using System.Drawing;
namespace Project
{
public class LastPositionData
{
public string NodeId { get; set; }
public AgvDirection Direction { get; set; }
public AGVTurn Turn { get; set; }
public DateTime SaveTime { get; set; }
}
public static class PUB
{
//public static Device.CFlag flag;
@@ -633,72 +643,8 @@ namespace Project
if (rfidValue.isEmpty()) return null;
return _mapNodes.Where(t => t.RfidId.Equals(rfidValue)).FirstOrDefault();
}
public static List<MapNode> FindByNodeAlias(string alias)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_mapNodes == null || _mapNodes.Any() == false) return null;
if (alias.isEmpty()) return null;
var lst = _mapNodes.Where(t => t.AliasName.Equals(alias));
if (lst.Any() == false) return null;
return lst.ToList();
}
public static List<MapNode> FindByNodeType(AGVNavigationCore.Models.MapNode type)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_mapNodes == null || _mapNodes.Any() == false) return null;
var lst = _mapNodes.Where(t => t.Type.Equals(type));
if (lst.Any() == false) return null;
return lst.ToList();
}
/// <summary>
/// RFID 읽기 시 해당 노드 위치로 AGV 업데이트
/// </summary>
/// <param name="rfidId">읽은 RFID ID</param>
/// <param name="motorDirection">모터 방향 (Forward/Backward)</param>
/// <returns>업데이트 성공 여부</returns>
public static bool UpdateAGVFromRFID(ushort rfidId, AgvDirection motorDirection = AgvDirection.Forward)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_virtualAGV == null || _mapNodes == null) return false;
// RFID에 해당하는 노드 찾기
var node = _mapNodes.FirstOrDefault(n => n.RfidId == rfidId);
if (node != null)
{
_virtualAGV.SetPosition(node, motorDirection);
RefreshAGVCanvas();
log.Add($"[AGV] RFID {rfidId} 감지 → 노드 {node.Id} 위치 업데이트 (방향: {motorDirection})");
return true;
}
log.Add($"[AGV] RFID {rfidId}에 해당하는 노드를 찾을 수 없음");
return false;
}
/// <summary>
/// 노드ID로 AGV 위치 업데이트
/// </summary>
/// <param name="nodeId">노드 ID</param>
/// <param name="motorDirection">모터 방향 (Forward/Backward)</param>
/// <returns>업데이트 성공 여부</returns>
public static bool UpdateAGVToNode(string nodeId, AgvDirection motorDirection = AgvDirection.Forward)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_virtualAGV == null || _mapNodes == null) return false;
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node != null)
{
_virtualAGV.SetPosition(node, motorDirection);
RefreshAGVCanvas();
log.Add($"[AGV] 노드 {nodeId} 위치로 이동 (방향: {motorDirection})");
return true;
}
return false;
}
/// <summary>
/// 모든 로그를 플러시 합니다.
@@ -718,10 +664,14 @@ namespace Project
/// <param name="direction">새로운 방향</param>
public static void UpdateAGVDirection(AgvDirection direction)
{
if (_virtualAGV == null) return;
_virtualAGV.CurrentDirection = direction;
RefreshAGVCanvas();
if (_virtualAGV == null) return;
if(_virtualAGV.CurrentDirection != direction)
{
PUB.log.Add($"[PUB] AGV Direction Change {_virtualAGV.CurrentDirection}->{direction}");
_virtualAGV.CurrentDirection = direction;
RefreshAGVCanvas();
}
}
/// <summary>
@@ -732,8 +682,13 @@ namespace Project
{
if (_virtualAGV == null) return;
_virtualAGV.CurrentState = state;
RefreshAGVCanvas();
if(_virtualAGV.CurrentState != state)
{
PUB.log.Add($"[PUB] AGV State Change {_virtualAGV.CurrentState}->{state}");
_virtualAGV.CurrentState = state;
RefreshAGVCanvas();
}
}
/// <summary>
@@ -760,6 +715,51 @@ namespace Project
}
#region "Starting Position Persistence"
private static string LastPosFilePath => Path.Combine(UTIL.CurrentPath, "Data", "last_pos.json");
public static void SaveLastPosition()
{
if (_virtualAGV == null || _virtualAGV.CurrentNode == null) return;
try
{
var data = new LastPositionData
{
NodeId = _virtualAGV.CurrentNode.Id,
Direction = _virtualAGV.CurrentDirection,
Turn = _virtualAGV.Turn,
SaveTime = DateTime.Now
};
string json = JsonConvert.SerializeObject(data, Formatting.Indented);
File.WriteAllText(LastPosFilePath, json);
}
catch (Exception ex)
{
log.AddE($"[PUB] Failed to save last position: {ex.Message}");
}
}
public static LastPositionData LoadLastPosition()
{
try
{
if (!File.Exists(LastPosFilePath)) return null;
string json = File.ReadAllText(LastPosFilePath);
return JsonConvert.DeserializeObject<LastPositionData>(json);
}
catch (Exception ex)
{
log.AddE($"[PUB] Failed to load last position: {ex.Message}");
return null;
}
}
#endregion
#endregion
}

View File

@@ -39,6 +39,21 @@ namespace Project
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//카트감지
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOnEnter)
{
//가지러 들어가야하는데 이미 카트가 감지되어있다면 진행할 수 없다.
if (PUB.AGV.signal2.cart_detect1 || PUB.AGV.signal2.cart_detect2)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.CART_EXIST, $"[{funcname}-{PUB.sm.RunStepSeq}] 이미 카트가 감지되어 진입할 수 없습니다");
return false;
}
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//모션 전후진 제어
if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90)
@@ -69,27 +84,27 @@ namespace Project
else
{
//하드웨어 상태 확인
var turnState = PUB.AGV.TurnInformation?.State ?? arDev.eNarumiTurn.None;
//var turnState = PUB.AGV.TurnInformation?.StateNew ?? arDev.eNarumiTurn.None;
if (turnState == arDev.eNarumiTurn.Left || turnState == arDev.eNarumiTurn.LeftIng)
{
//이미 좌회전 중이거나 완료된 하드웨어 상태
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] 하드웨어 좌회전 상태 확인됨({turnState}). 명령을 건너뜁니다.");
PUB.sm.UpdateRunStepSeq();
}
else if (turnState == arDev.eNarumiTurn.Right || turnState == arDev.eNarumiTurn.RightIng)
{
//비정상 상태 (우회전 중?)
SetRunStepError(ENIGProtocol.AGVErrorCode.TURN_FAIL, $"[{funcname}-{PUB.sm.RunStepSeq}] 턴 방향 불일치(Current:{turnState}). 우회전 상태에서 좌회전을 시도할 수 없습니다.");
}
else
{
//정상 (None) -> 턴 명령 실행
PUB.AGV.AGVMoveLeft180Turn();
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV Left Turn");
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
}
//if (turnState == arDev.eNarumiTurn.Left || turnState == arDev.eNarumiTurn.LeftIng)
//{
// //이미 좌회전 중이거나 완료된 하드웨어 상태
// PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] 하드웨어 좌회전 상태 확인됨({turnState}). 명령을 건너뜁니다.");
// PUB.sm.UpdateRunStepSeq();
//}
//else if (turnState == arDev.eNarumiTurn.Right || turnState == arDev.eNarumiTurn.RightIng)
//{
// //비정상 상태 (우회전 중?)
// SetRunStepError(ENIGProtocol.AGVErrorCode.TURN_FAIL, $"[{funcname}-{PUB.sm.RunStepSeq}] 턴 방향 불일치(Current:{turnState}). 우회전 상태에서 좌회전을 시도할 수 없습니다.");
//}
//else
//{
//정상 (None) -> 턴 명령 실행
PUB.AGV.AGVMoveLeft180Turn();
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV Left Turn");
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
//}
}
PUB._mapCanvas.SetAlertMessage($"턴 진행 중");
return false;
@@ -99,29 +114,31 @@ namespace Project
//이미 완료된 상태라면 대기 과정을 건너뛴다.
if (PUB._virtualAGV.Turn == AGVNavigationCore.Models.AGVTurn.L90)
{
PUB.sm.UpdateRunStepSeq();
return false;
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] 이미 턴이완료된 상태입니다");
}
//왼쪽턴이 완료되지 않은경우
if (PUB.AGV.TurnInformation.State != arDev.eNarumiTurn.Left)
else
{
//움직임 확인을 위해 3초간은 검증을 유예한다
if (PUB.AGV.TurnInformation.Runtime.TotalSeconds < 3) return false;
//턴 이동 상태가 확인되어야 한다.
var overtime = 30;
if (PUB.AGV.TurnInformation.Runtime.TotalSeconds > overtime)
//왼쪽턴이 완료되지 않은경우
if (PUB.AGV.TurnInformation.State != arDev.eNarumiTurn.Left)
{
//30초동안 AGV까 움직이지 않았다면 오류 처리한다.
SetRunStepError(ENIGProtocol.AGVErrorCode.TURN_FAIL, $"[{funcname}] {overtime}초이내 턴 감지 안됨");
//움직임 확인을 위해 3초간은 검증을 유예한다
if (PUB.AGV.TurnInformation.Runtime.TotalSeconds < 3) return false;
//턴 이동 상태가 확인되어야 한다.
var overtime = 30;
if (PUB.AGV.TurnInformation.Runtime.TotalSeconds > overtime)
{
//30초동안 AGV까 움직이지 않았다면 오류 처리한다.
SetRunStepError(ENIGProtocol.AGVErrorCode.TURN_FAIL, $"[{funcname}] {overtime}초이내 턴 감지 안됨");
return false;
}
else PUB._mapCanvas.SetAlertMessage($"턴 진행 중({PUB.AGV.TurnInformation.Runtime.TotalSeconds:N0}/{overtime})");
return false;
}
else PUB._mapCanvas.SetAlertMessage($"턴 진행 중({PUB.AGV.TurnInformation.Runtime.TotalSeconds:N0}/{overtime})");
return false;
PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.L90; //턴완료
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] Turn(left) 완료");
}
PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.L90; //턴완료
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] Turn(left) 완료");
PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다.
return false;
}
@@ -174,7 +191,7 @@ namespace Project
else if (PUB.sm.RunStepSeq == idx++)
{
//저속이동 (후진 진입)
// [Smart Restart] 재시작 시 안전 검사
// 이미 턴을 완료했고(L90), 현재 마크 센서가 감지된다면(ON),
// 이미 목적지(Mark 2)에 도착한 것으로 간주하여 후진을 생략한다.
@@ -203,7 +220,13 @@ namespace Project
return false;
}
//후진이동을한다
//후진이동을한다
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//이동확인을 한다.
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] 도킹을 위해 후진 이동 시작 (Dir:Backward, Spd:Low)");
PUB._mapCanvas.SetAlertMessage($"도킹을 위해 후진 이동 시작");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
@@ -213,9 +236,9 @@ namespace Project
else if (PUB.sm.RunStepSeq == idx++)
{
//이동확인을 한다.
if(PUB.AGV.system1.agv_run == false)
if (PUB.AGV.system1.agv_run == false)
{
if(seqtime.TotalSeconds > 3)
if (seqtime.TotalSeconds > 10)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.AGV_RUN_FAIL);
}

View File

@@ -80,69 +80,85 @@ namespace Project
}
else if (PUB.sm.RunStepSeq == idx++)
{
//빈 상태로 아웃해야한다.
var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
if (PUB.AGV.signal1.mark_sensor == false)
{
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)
//빈 상태로 아웃해야한다.
var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
SetRunStepError(ENIGProtocol.AGVErrorCode.AGV_SPEED_SET_FAIL);
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)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.AGV_SPEED_SET_FAIL);
}
return false;
}
return false;
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV 이동 설정 완료 (Dir:Forward, Spd:Low)");
}
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV 이동 설정 완료 (Dir:Forward, Spd:Low)");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//전진이동
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV 전진 구동 시작");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
if (PUB.AGV.signal1.mark_sensor == false)
{
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV 전진 구동 시작");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//AGV구동을 확인하고 마크스탑을 설정한다.
if (PUB.AGV.system1.agv_run == false)
if (PUB.AGV.signal1.mark_sensor == false)
{
if (seqtime.TotalSeconds > 3)
if (PUB.AGV.system1.agv_run == false)
{
//구동이확인되지 않으면 오류처리를 한다.
SetRunStepError(ENIGProtocol.AGVErrorCode.AGV_RUN_FAIL);
if (seqtime.TotalSeconds > 3)
{
//구동이확인되지 않으면 오류처리를 한다.
SetRunStepError(ENIGProtocol.AGVErrorCode.AGV_RUN_FAIL);
}
return false;
}
return false;
}
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV 구동 확인 완료");
//마크스탑설정
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] MarkStop 명령 전송 (진출 정지 장소)");
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV 구동 확인 완료");
//마크스탑설정
PUB.AGV.AGVMoveStop(funcname, arDev.Narumi.eStopOpt.MarkStop);
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] MarkStop 명령 전송 (진출 정지 장소)");
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//마크스탑신호가 3초이내로 들어와야 한다
if (PUB.AGV.data.Speed != 'S')
if (PUB.AGV.signal1.mark_sensor == false)
{
if (seqtime.TotalSeconds > 3)
//마크스탑신호가 3초이내로 들어와야 한다
if (PUB.AGV.data.Speed != 'S')
{
SetRunStepError(ENIGProtocol.AGVErrorCode.MARK_TIMEOUT);
if (seqtime.TotalSeconds > 3)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.MARK_TIMEOUT);
return false;
}
return false;
}
return false;
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] MarkStop 신구 확인 완료");
}
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] MarkStop 신구 확인 완료");
else PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}]마크스탑이 미리 들어와있어. 이동및 스탑을 하지 않습니다");
PUB.sm.UpdateRunStepSeq();
return false;
}
@@ -173,18 +189,18 @@ namespace Project
else
{
//하드웨어 상태 확인
var turnState = PUB.AGV.TurnInformation?.State ?? arDev.eNarumiTurn.None;
if (turnState == arDev.eNarumiTurn.Right)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.TURN_FAIL, $"[{funcname}-{PUB.sm.RunStepSeq}] 턴 방향 불일치(Current:{turnState}). 버퍼진출시에는 LEFT-TURN 상태여야 합니다");
return false;
}
else
{
//정상 (None) -> 턴 명령 실행
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV Right Turn 명령 전송");
PUB.AGV.AGVMoveRight180Turn();
}
//var turnState = PUB.AGV.TurnInformation?.StateOld ?? arDev.eNarumiTurn.None;
//if (turnState == arDev.eNarumiTurn.Right)
//{
// SetRunStepError(ENIGProtocol.AGVErrorCode.TURN_FAIL, $"[{funcname}-{PUB.sm.RunStepSeq}] 턴 방향 불일치(Current:{turnState}). 버퍼진출시에는 LEFT-TURN 상태여야 합니다");
// return false;
//}
//else
//{
//정상 (None) -> 턴 명령 실행
PUB.log.Add($"[{funcname}-{PUB.sm.RunStepSeq}] AGV Right Turn 명령 전송");
PUB.AGV.AGVMoveRight180Turn();
//}
}
PUB.sm.UpdateRunStepSeq();
return false;

View File

@@ -80,6 +80,8 @@ namespace Project
{
errmsg = ENIGProtocol.AGVUtility.GetAGVErrorMessage(ecode);
}
PUB.Result.ResultMessage = errmsg;
PUB.XBE.SendError(ecode);
PUB.AGV.AGVMoveStop(errmsg);
PUB.log.AddE(errmsg);

View File

@@ -61,12 +61,8 @@ namespace Project
var syncDir = PUB.AGV.data.Direction == 'B' ? AgvDirection.Backward : AgvDirection.Forward;
// [Sync] Update VirtualAGV Direction
if (PUB._virtualAGV != null)
{
if (PUB._virtualAGV.CurrentDirection != syncDir)
PUB.UpdateAGVDirection(syncDir);
}
if (PUB._virtualAGV != null && PUB._virtualAGV.CurrentDirection != syncDir)
PUB.UpdateAGVDirection(syncDir);
// [Sync] Update VirtualAGV State
AGVState syncState = AGVState.Idle;
@@ -107,7 +103,7 @@ namespace Project
VAR.BOOL[eVarBool.FLAG_CHARGEONA] = agv_chg;
}
//충전기위치오류
if (PUB.AGV.error.Charger_pos_error != VAR.BOOL[eVarBool.CHG_POSERR])
{
if (PUB.AGV.error.Charger_pos_error)
@@ -117,15 +113,6 @@ namespace Project
VAR.BOOL[eVarBool.CHG_POSERR] = PUB.AGV.error.Charger_pos_error;
}
////나르미가 멈췄다면 다음 마크 이동 기능이 OFF 된다
//if (agv_stp)
//{
// if (VAR.BOOL[eVarBool.NEXTSTOP_MARK])
// {
// VAR.BOOL[eVarBool.NEXTSTOP_MARK] = false;
// PUB.logagv.Add($"NEXTSTOP_MARK 변경({VAR.BOOL[eVarBool.NEXTSTOP_MARK]})");
// }
//}
//마크센서 상태가 변경이 되었다면
if (VAR.BOOL[eVarBool.MARK_SENSOR] != PUB.AGV.signal1.mark_sensor)
@@ -155,9 +142,9 @@ namespace Project
case arDev.Narumi.DataType.TAG:
{
//자동 실행 중이다.
PUB.Result.LastTAG = PUB.AGV.data.TagNo;//.ToString("0000");
PUB.log.Add($"[_AGV] AGV 태그수신 : {PUB.AGV.data.TagNo} LastTag:{PUB.Result.LastTAG}");
//POT/NOT 보면 일단 바로 멈추게한다
if (PUB.Result.CurrentPos == ePosition.POT || PUB.Result.CurrentPos == ePosition.NOT)
{
@@ -197,8 +184,7 @@ namespace Project
var MotDireciton = PUB.AGV.data.Direction == 'B' ? AgvDirection.Backward : AgvDirection.Forward;
PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, CurrentNode, MotDireciton);
PUB._virtualAGV.SetPosition(CurrentNode, MotDireciton);
//방향을 다시 확인하여. 기존 경로의 무결성을 검증한다, 필요한 경우 다시 계산할 필요가 있다.
PUB.SaveLastPosition();
}
//태그를 읽었다면 상태를 바로 전송한다
@@ -264,7 +250,6 @@ namespace Project
$"S/W 턴 상태: {PUB._virtualAGV.Turn}\n" +
$"H/W 턴 상태: {PUB.AGV.TurnInformation?.State}\n" +
$"위치 확정: {PUB._virtualAGV.IsPositionConfirmed} (RFID {PUB._virtualAGV.DetectedRfidCount}개)\n" +
$"대상 노드: {targetpos}\n" +
$"상세 경로: {pathdetail}";

View File

@@ -206,7 +206,6 @@ namespace Project
if (PUB._mapCanvas != null)
PUB._mapCanvas.SetSyncStatus("설정 동기화", 0f, "환경설정 값으로 AGV컨트롤러를 설정 합니다");
}));
}
if (_SM_RUN_SYNC(runStepisFirst, PUB.sm.GetRunSteptime))
{

View File

@@ -39,9 +39,17 @@ namespace Project
//대상디바이스
var TargetID = data[0];
//해당 패킷의 대상이 나라면 처리한다.
//해당 패킷의 대상이 내가아니면 처리하지 않는다
if (PUB.setting.XBE_ID != TargetID) return;
//자동실행모드가 아니라면 무조건 에러를 반환한다
if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.ManualMode, "현재 자동실행 모드가 아닙니다");
return;
}
switch (cmd)
{
@@ -56,8 +64,8 @@ namespace Project
var node = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagValue);
if (node == null)
{
PUB.log.AddE($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}");
var ermsg = ($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
SetRunStepError(ENIGProtocol.AGVErrorCode.EmptyNode, ermsg);
return;
}
else
@@ -83,16 +91,16 @@ namespace Project
var currNode = PUB._virtualAGV.CurrentNode;
if (currNode == null)
{
PUB.log.AddE($"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Current Node");
var msg = $"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다";
SetRunStepError(ENIGProtocol.AGVErrorCode.EmptyNode, msg);
return;
}
var targetNode = PUB._virtualAGV.TargetNode;
if (targetNode == null)
{
PUB.log.AddE($"[{logPrefix}-{cmd}] 목표 노드를 알 수 없습니다");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Target Node");
var msg = $"[{logPrefix}-{cmd}] 목표 노드를 알 수 없습니다";
SetRunStepError(ENIGProtocol.AGVErrorCode.EmptyNode, msg);
return;
}
@@ -119,7 +127,7 @@ namespace Project
PUB.sm.SetNewRunStep(nextStep);
}
break;
case ENIGProtocol.AGVCommandHE.PickOnExit:
case ENIGProtocol.AGVCommandHE.PickOnExit:
case ENIGProtocol.AGVCommandHE.PickOffExit:
{
Resultclear();
@@ -129,16 +137,16 @@ namespace Project
var currNode = PUB._virtualAGV.CurrentNode;
if (currNode == null)
{
PUB.log.AddE($"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Current Node");
var msg = $"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다";
SetRunStepError(ENIGProtocol.AGVErrorCode.EmptyNode, msg);
return;
}
var targetNode = PUB._virtualAGV.TargetNode;
if (targetNode == null)
{
PUB.log.AddE($"[{logPrefix}-{cmd}] 목표 노드를 알 수 없습니다");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Target Node");
var msg = $"[{logPrefix}-{cmd}] 목표 노드를 알 수 없습니다";
SetRunStepError(ENIGProtocol.AGVErrorCode.EmptyNode, msg);
return;
}
@@ -198,8 +206,7 @@ namespace Project
//자동상태가아니라면 처리하지 않는다.
if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false)
{
PUB.log.AddE($"[{logPrefix}-Goto] 자동실행상태가 아닙니다");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.ManualMode, $"{currTag}");
SetRunStepError(ENIGProtocol.AGVErrorCode.ManualMode, $"[{logPrefix}-Goto] 자동실행상태가 아닙니다");
return;
}
@@ -207,8 +214,7 @@ namespace Project
PUB._virtualAGV.TargetNode = targetNode;
if (targetNode == null)
{
PUB.log.AddE($"[{logPrefix}-Goto] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}");
SetRunStepError(ENIGProtocol.AGVErrorCode.EmptyNode, $"[{logPrefix}-Goto] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
return;
}
@@ -348,8 +354,7 @@ namespace Project
break;
default:
PUB.logagv.AddE($"Unknown Command : {cmd} Sender:{e.ReceivedPacket.ID}, Target:{data[0]}");
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"{cmd}");
SetRunStepError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"Unknown Command : {cmd} Sender:{e.ReceivedPacket.ID}, Target:{data[0]}");
break;
}
}

View File

@@ -289,21 +289,17 @@ namespace Project
PUB._mapCanvas.MapFileName = filePath.FullName;
// 🔥 가상 AGV 초기화 (첫 노드 위치에 생성)
if (PUB._virtualAGV == null && PUB._mapCanvas.Nodes.Count > 0)
if (PUB._virtualAGV == null)
{
var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(n => n.IsNavigationNode());
if (startNode != null)
{
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward);
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, Point.Empty, AgvDirection.Forward);
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
//PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
// 캔버스에 AGV 리스트 설정
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
PUB._mapCanvas.AGVList = agvList;
// 캔버스에 AGV 리스트 설정
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
PUB._mapCanvas.AGVList = agvList;
PUB.log.Add($"가상 AGV 생성: {startNode.Id} 위치");
}
PUB.log.Add($"가상 AGV 생성: PointZero 위치");
}
else if (PUB._virtualAGV != null)
{
@@ -313,6 +309,25 @@ namespace Project
PUB._mapCanvas.AGVList = agvList;
}
PUB.log.Add($"맵 파일 로드 완료: {filePath.Name}, 노드 수: {result.Nodes.Count}");
// 🔥 초기 위치 설정 및 확인 화면 표시
this.BeginInvoke(new Action(() =>
{
using (var f = new Dialog.fSetCurrentPosition())
{
if (f.ShowDialog() == DialogResult.OK)
{
if (f.SelectedNode != null)
{
PUB._virtualAGV.Turn = f.SelectedTurn;
PUB._virtualAGV.SetPosition(f.SelectedNode, f.SelectedDirection);
PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, f.SelectedNode, f.SelectedDirection);
PUB.SaveLastPosition();
PUB.log.Add($"[초기위치] 설정 완료: {f.SelectedNode.Id}, {f.SelectedDirection}, Turn:{f.SelectedTurn}");
}
}
}
}));
}
else
{