Compare commits
23 Commits
703e1387bf
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3408e3fc30 | ||
|
|
5a4c73e4df | ||
|
|
34d1bdf504 | ||
|
|
3cae423736 | ||
|
|
d777adc219 | ||
|
|
b62cd5f52e | ||
|
|
32217c8501 | ||
|
|
9274727fa9 | ||
| 2a44ba28a8 | |||
| 4bdc36040d | |||
| 384f2affcb | |||
|
|
51579591a2 | ||
|
|
cef2fa8095 | ||
|
|
1f37871336 | ||
| eb0e08d290 | |||
|
|
4153362588 | ||
| 67a48531ad | |||
|
|
a7f938ff19 | ||
|
|
9db88e5d6b | ||
| a8cb952ea4 | |||
| 3c8eae889c | |||
| 764fbbd204 | |||
| 34b038c4be |
@@ -26,7 +26,6 @@ namespace AGVEmulator
|
||||
|
||||
// Map Control
|
||||
private UnifiedAGVCanvas _agvCanvas;
|
||||
private List<MapNode> _mapNodes;
|
||||
private VirtualAGV _visualAgv;
|
||||
|
||||
// Emulator State
|
||||
@@ -385,14 +384,12 @@ namespace AGVEmulator
|
||||
try
|
||||
{
|
||||
var mapresult = MapLoader.LoadMapFromFile(mapPath);
|
||||
_mapNodes = mapresult.Nodes;
|
||||
_agvCanvas.Nodes = _mapNodes;
|
||||
_agvCanvas.FitToNodes();
|
||||
_agvCanvas.SetMapLoadResult(mapresult);//.Nodes = _mapNodes;
|
||||
|
||||
// Initialize Visual AGV
|
||||
if (_mapNodes.Count > 0)
|
||||
if (_agvCanvas.Nodes.Count > 0)
|
||||
{
|
||||
_visualAgv = new VirtualAGV("AGV01", _mapNodes[0].Position);
|
||||
_visualAgv = new VirtualAGV("AGV01", _agvCanvas.Nodes[0].Position);
|
||||
_agvCanvas.AGVList = new List<IAGV> { _visualAgv };
|
||||
}
|
||||
}
|
||||
@@ -405,16 +402,16 @@ namespace AGVEmulator
|
||||
|
||||
void UpdateVisualAgvPosition(string tag)
|
||||
{
|
||||
if (_visualAgv == null || _mapNodes == null) return;
|
||||
if (_visualAgv == null || _agvCanvas.Nodes == null) return;
|
||||
|
||||
// Find node by tag
|
||||
// Assuming NodeId might be the tag or contain it
|
||||
var node = _mapNodes.FirstOrDefault(n => n.NodeId == tag || n.NodeId.EndsWith(tag));
|
||||
var node = _agvCanvas.Nodes.FirstOrDefault(n => n.Id == tag || n.Id.EndsWith(tag));
|
||||
|
||||
// If not found, try to parse tag as int and match
|
||||
if (node == null && int.TryParse(tag, out int tagNum))
|
||||
{
|
||||
node = _mapNodes.FirstOrDefault(n => n.NodeId == tagNum.ToString());
|
||||
node = _agvCanvas.Nodes.FirstOrDefault(n => n.Id == tagNum.ToString());
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
@@ -575,7 +572,7 @@ private void UpdateVisualAGV()
|
||||
|
||||
private void UpdateEmulatorLogic()
|
||||
{
|
||||
if (_visualAgv == null || _mapNodes == null) return;
|
||||
if (_visualAgv == null || _agvCanvas.Nodes == null) return;
|
||||
|
||||
// Initialize float position if needed
|
||||
if (_currentPosF.IsEmpty) _currentPosF = _visualAgv.CurrentPosition;
|
||||
@@ -614,18 +611,18 @@ private void UpdateVisualAGV()
|
||||
_visualAgv.CurrentPosition = Point.Round(_currentPosF);
|
||||
|
||||
// Check for Nodes (RFID Trigger)
|
||||
foreach (var node in _mapNodes)
|
||||
foreach (var node in _agvCanvas.Nodes)
|
||||
{
|
||||
double dist = Math.Sqrt(Math.Pow(node.Position.X - _visualAgv.CurrentPosition.X, 2) + Math.Pow(node.Position.Y - _visualAgv.CurrentPosition.Y, 2));
|
||||
if (dist < 15) // Hit Node
|
||||
{
|
||||
// Send Tag
|
||||
if (node.NodeId != numericUpDown1.Text)
|
||||
if (node.Id != numericUpDown1.Text)
|
||||
{
|
||||
if (int.TryParse(node.NodeId, out int tag))
|
||||
if (int.TryParse(node.Id, out int tag))
|
||||
{
|
||||
AGV.SendTag(node.NodeId);
|
||||
numericUpDown1.Text = node.NodeId;
|
||||
AGV.SendTag(node.Id);
|
||||
numericUpDown1.Text = node.Id;
|
||||
|
||||
// Snap to node
|
||||
_currentPosF = node.Position;
|
||||
@@ -665,14 +662,14 @@ private void UpdateVisualAGV()
|
||||
while (_targetAngle < 0) _targetAngle += 360;
|
||||
}
|
||||
|
||||
private void _agvCanvas_NodeRightClicked(object sender, MapNode e)
|
||||
private void _agvCanvas_NodeRightClicked(object sender, NodeBase e)
|
||||
{
|
||||
if (e != null && _visualAgv != null)
|
||||
{
|
||||
_visualAgv.CurrentPosition = e.Position;
|
||||
_currentPosF = e.Position;
|
||||
|
||||
if (int.TryParse(e.NodeId, out int tag))
|
||||
if (int.TryParse(e.Id, out int tag))
|
||||
{
|
||||
numericUpDown1.Text = tag.ToString();
|
||||
}
|
||||
@@ -796,6 +793,13 @@ private void UpdateVisualAGV()
|
||||
private void toolStripButton3_Click(object sender, EventArgs e)
|
||||
{
|
||||
var file = @"C:\Data\Amkor\AGV4\route\NewMap.agvmap";
|
||||
if(System.IO.File.Exists(file)==false)
|
||||
{
|
||||
var od = new OpenFileDialog();
|
||||
od.Filter = "json|*.json";
|
||||
if (od.ShowDialog() != DialogResult.OK) return;
|
||||
file = od.FileName;
|
||||
}
|
||||
LoadMapFile(file);
|
||||
}
|
||||
private string _currentMapFilePath;
|
||||
@@ -809,13 +813,8 @@ private void UpdateVisualAGV()
|
||||
{
|
||||
Console.WriteLine($"Map File Load : {filePath}");
|
||||
|
||||
_mapNodes = result.Nodes;
|
||||
_currentMapFilePath = filePath;
|
||||
|
||||
// RFID 자동 할당 제거 - 에디터에서 설정한 값 그대로 사용
|
||||
|
||||
// 시뮬레이터 캔버스에 맵 설정
|
||||
this._agvCanvas.Nodes = _mapNodes;
|
||||
this._agvCanvas.SetMapLoadResult(result);// = _mapNodes;
|
||||
|
||||
// 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
@@ -871,17 +870,17 @@ private void UpdateVisualAGV()
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(input) && _mapNodes != null && _visualAgv != null)
|
||||
if (!string.IsNullOrEmpty(input) && _agvCanvas.Nodes != null && _visualAgv != null)
|
||||
{
|
||||
var node = _mapNodes.FirstOrDefault(n => n.NodeId == input);
|
||||
var node = _agvCanvas.Nodes.FirstOrDefault(n => n.Id == input);
|
||||
if (node != null)
|
||||
{
|
||||
_visualAgv.CurrentPosition = node.Position;
|
||||
_currentPosF = node.Position;
|
||||
numericUpDown1.Text = node.NodeId;
|
||||
numericUpDown1.Text = node.Id;
|
||||
|
||||
// Auto-orient: Prefer Right (0) or Up (270)
|
||||
var neighbors = _mapNodes.Where(n => n != node &&
|
||||
var neighbors = _agvCanvas.Nodes.Where(n => n != node &&
|
||||
Math.Sqrt(Math.Pow(n.Position.X - node.Position.X, 2) + Math.Pow(n.Position.Y - node.Position.Y, 2)) < 150)
|
||||
.ToList();
|
||||
|
||||
|
||||
3
Cs_HMI/.cursorrules
Normal file
3
Cs_HMI/.cursorrules
Normal file
@@ -0,0 +1,3 @@
|
||||
# User Preferences
|
||||
- **프로젝트 컴파일은 build.bat 를 사용하세요(윈도우 환경)
|
||||
- **콘솔명령 실행시에는 항상 cmd /c 를 사용해서 실행하세요
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Express 15 for Windows Desktop
|
||||
VisualStudioVersion = 15.0.36324.19
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36804.6 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sub", "Sub", "{C423C39A-44E7-4F09-B2F7-7943975FF948}"
|
||||
EndProject
|
||||
@@ -38,8 +38,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVSimulator", "AGVLogic\AG
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "arCommUtil", "SubProject\commutil\arCommUtil.csproj", "{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Supertonic.Netfx48", "SubProject\SuperTonic\Supertonic.Netfx48.csproj", "{19675E19-EB91-493E-88C3-32B3C094B749}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -182,18 +180,6 @@ Global
|
||||
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x64.Build.0 = Release|Any CPU
|
||||
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x86.ActiveCfg = Release|x86
|
||||
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x86.Build.0 = Release|x86
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Debug|x64.Build.0 = Debug|x64
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Debug|x86.Build.0 = Debug|Win32
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Release|x64.ActiveCfg = Release|x64
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Release|x64.Build.0 = Release|x64
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Release|x86.ActiveCfg = Release|Win32
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -207,7 +193,6 @@ Global
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
|
||||
{B2C3D4E5-0000-0000-0000-000000000000} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
|
||||
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
|
||||
{19675E19-EB91-493E-88C3-32B3C094B749} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B5B1FD72-356F-4840-83E8-B070AC21C8D9}
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace AGVMapEditor.Forms
|
||||
/// </summary>
|
||||
public partial class ImageEditorForm : Form
|
||||
{
|
||||
private MapNode _targetNode;
|
||||
private MapImage _targetNode;
|
||||
|
||||
public ImageEditorForm(MapNode imageNode = null)
|
||||
public ImageEditorForm(MapImage imageNode = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
_targetNode = imageNode;
|
||||
@@ -25,7 +25,7 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
LoadImageFromNode(imageNode);
|
||||
}
|
||||
|
||||
|
||||
this.KeyPreview = true;
|
||||
this.KeyDown += (s1, e1) => {
|
||||
if (e1.KeyCode == Keys.Escape) this.Close();
|
||||
@@ -38,7 +38,7 @@ namespace AGVMapEditor.Forms
|
||||
imageCanvas.BrushSize = trackBrush.Value;
|
||||
imageCanvas.BrushModeEnabled = chkBrushMode.Checked;
|
||||
imageCanvas.BackColor = Color.FromArgb(32,32,32);
|
||||
|
||||
|
||||
// 이벤트 연결
|
||||
chkBrushMode.CheckedChanged += (s, e) => imageCanvas.BrushModeEnabled = chkBrushMode.Checked;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace AGVMapEditor.Forms
|
||||
imageCanvas.BrushSize = trackBrush.Value;
|
||||
}
|
||||
|
||||
private void LoadImageFromNode(MapNode node)
|
||||
private void LoadImageFromNode(MapImage node)
|
||||
{
|
||||
if (node.LoadedImage != null)
|
||||
{
|
||||
@@ -66,7 +66,7 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void LoadImageFromFile(string filePath)
|
||||
{
|
||||
try
|
||||
@@ -160,7 +160,7 @@ namespace AGVMapEditor.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
if (_targetNode != null && _targetNode.Type == NodeType.Image)
|
||||
if (_targetNode != null)
|
||||
{
|
||||
// 표시 크기로 리사이즈된 이미지 가져오기
|
||||
var finalImage = imageCanvas.GetResizedImage();
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Windows.Forms;
|
||||
using AGVMapEditor.Models;
|
||||
using AGVNavigationCore.Controls;
|
||||
using AGVNavigationCore.Models;
|
||||
using MapImage = AGVNavigationCore.Models.MapImage;
|
||||
using MapLabel = AGVNavigationCore.Models.MapLabel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AGVMapEditor.Forms
|
||||
@@ -18,11 +20,11 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private List<MapNode> _mapNodes;
|
||||
// private List<MapNode> this._mapCanvas.Nodes;
|
||||
private UnifiedAGVCanvas _mapCanvas;
|
||||
|
||||
// 현재 선택된 노드
|
||||
private MapNode _selectedNode;
|
||||
private NodeBase _selectedNode;
|
||||
|
||||
// 파일 경로
|
||||
private string _currentMapFile = string.Empty;
|
||||
@@ -34,20 +36,20 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
public string FromNodeId { get; set; }
|
||||
public string FromNodeName { get; set; }
|
||||
public string FromRfidId { get; set; }
|
||||
public ushort FromRfidId { get; set; }
|
||||
public string ToNodeId { get; set; }
|
||||
public string ToNodeName { get; set; }
|
||||
public string ToRfidId { get; set; }
|
||||
public ushort ToRfidId { get; set; }
|
||||
public string ConnectionType { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시
|
||||
string fromDisplay = !string.IsNullOrEmpty(FromRfidId)
|
||||
string fromDisplay = FromRfidId > 0
|
||||
? $"{FromRfidId}({FromNodeName})"
|
||||
: $"---({FromNodeId})";
|
||||
|
||||
string toDisplay = !string.IsNullOrEmpty(ToRfidId)
|
||||
string toDisplay = ToRfidId > 0
|
||||
? $"{ToRfidId}({ToNodeName})"
|
||||
: $"---({ToNodeId})";
|
||||
|
||||
@@ -93,20 +95,18 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region Initialization
|
||||
|
||||
private void InitializeData()
|
||||
{
|
||||
_mapNodes = new List<MapNode>();
|
||||
// this._mapCanvas.Nodes = new List<MapNode>();
|
||||
}
|
||||
|
||||
private void InitializeMapCanvas()
|
||||
{
|
||||
_mapCanvas = new UnifiedAGVCanvas();
|
||||
_mapCanvas.Dock = DockStyle.Fill;
|
||||
_mapCanvas.Nodes = _mapNodes;
|
||||
// RfidMappings 제거 - MapNode에 통합됨
|
||||
|
||||
// 이벤트 연결
|
||||
_mapCanvas.NodeAdded += OnNodeAdded;
|
||||
@@ -115,12 +115,14 @@ namespace AGVMapEditor.Forms
|
||||
_mapCanvas.NodeMoved += OnNodeMoved;
|
||||
_mapCanvas.NodeDeleted += OnNodeDeleted;
|
||||
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
||||
_mapCanvas.ImageNodeDoubleClicked += OnImageNodeDoubleClicked;
|
||||
_mapCanvas.ImageDoubleClicked += OnImageDoubleClicked;
|
||||
_mapCanvas.MapChanged += OnMapChanged;
|
||||
|
||||
// 스플리터 패널에 맵 캔버스 추가
|
||||
panel1.Controls.Add(_mapCanvas);
|
||||
|
||||
// ...
|
||||
|
||||
// 툴바 버튼 이벤트 연결
|
||||
WireToolbarButtonEvents();
|
||||
}
|
||||
@@ -160,7 +162,7 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
|
||||
RefreshNodeList();
|
||||
// 속성 변경 시 이벤트 연결
|
||||
_propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
|
||||
@@ -174,7 +176,7 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeAdded(object sender, MapNode node)
|
||||
private void OnNodeAdded(object sender, NodeBase node)
|
||||
{
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
@@ -199,7 +201,7 @@ namespace AGVMapEditor.Forms
|
||||
// }
|
||||
//}
|
||||
|
||||
private void OnNodesSelected(object sender, List<MapNode> nodes)
|
||||
private void OnNodesSelected(object sender, List<NodeBase> nodes)
|
||||
{
|
||||
// 다중 선택 시 처리
|
||||
if (nodes == null || nodes.Count == 0)
|
||||
@@ -227,14 +229,14 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeMoved(object sender, MapNode node)
|
||||
private void OnNodeMoved(object sender, NodeBase node)
|
||||
{
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshNodeList();
|
||||
}
|
||||
|
||||
private void OnNodeDeleted(object sender, MapNode node)
|
||||
private void OnNodeDeleted(object sender, NodeBase node)
|
||||
{
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
@@ -258,20 +260,17 @@ namespace AGVMapEditor.Forms
|
||||
UpdateNodeProperties(); // 연결 정보 업데이트
|
||||
}
|
||||
|
||||
private void OnImageNodeDoubleClicked(object sender, MapNode node)
|
||||
private void OnImageDoubleClicked(object sender, MapImage image)
|
||||
{
|
||||
// 이미지 노드 더블클릭 시 이미지 편집창 표시
|
||||
if (node != null && node.Type == NodeType.Image)
|
||||
using (var editor = new ImageEditorForm(image))
|
||||
{
|
||||
using (var editor = new ImageEditorForm(node))
|
||||
if (editor.ShowDialog(this) == DialogResult.OK)
|
||||
{
|
||||
if (editor.ShowDialog(this) == DialogResult.OK)
|
||||
{
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
|
||||
UpdateNodeProperties(); // 속성 업데이트
|
||||
}
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
|
||||
UpdateNodeProperties(); // 속성 업데이트
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,12 +400,11 @@ namespace AGVMapEditor.Forms
|
||||
private void AddNewNode()
|
||||
{
|
||||
var nodeId = GenerateNodeId();
|
||||
var nodeName = $"노드{_mapNodes.Count + 1}";
|
||||
var position = new Point(100 + _mapNodes.Count * 50, 100 + _mapNodes.Count * 50);
|
||||
var position = new Point(100 + this._mapCanvas.Nodes.Count * 50, 100 + this._mapCanvas.Nodes.Count * 50);
|
||||
|
||||
var node = new MapNode(nodeId, nodeName, position, NodeType.Normal);
|
||||
var node = new MapNode(nodeId, position, StationType.Normal);
|
||||
|
||||
_mapNodes.Add(node);
|
||||
this._mapCanvas.Nodes.Add(node);
|
||||
_hasChanges = true;
|
||||
|
||||
RefreshNodeList();
|
||||
@@ -422,13 +420,14 @@ namespace AGVMapEditor.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
var result = MessageBox.Show($"노드 '{_selectedNode.Name}'를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
|
||||
var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? 0;
|
||||
var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
|
||||
"삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 노드 제거
|
||||
_mapNodes.Remove(_selectedNode);
|
||||
_mapCanvas.RemoveItem(_selectedNode);
|
||||
_selectedNode = null;
|
||||
_hasChanges = true;
|
||||
|
||||
@@ -441,15 +440,15 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void AddConnectionToSelectedNode()
|
||||
{
|
||||
if (_selectedNode == null)
|
||||
if (!(_selectedNode is MapNode selectedMapNode))
|
||||
{
|
||||
MessageBox.Show("연결을 추가할 노드를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show("연결을 추가할 노드(MapNode)를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 다른 노드들 중에서 선택
|
||||
var availableNodes = _mapNodes.Where(n => n.NodeId != _selectedNode.NodeId &&
|
||||
!_selectedNode.ConnectedNodes.Contains(n.NodeId)).ToList();
|
||||
var availableNodes = this._mapCanvas.Nodes.Where(n => n.Id != selectedMapNode.Id &&
|
||||
!selectedMapNode.ConnectedNodes.Contains(n.Id)).ToList();
|
||||
|
||||
if (availableNodes.Count == 0)
|
||||
{
|
||||
@@ -458,13 +457,13 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
|
||||
// 간단한 선택 다이얼로그 (실제로는 별도 폼을 만들어야 함)
|
||||
var nodeNames = availableNodes.Select(n => $"{n.NodeId}: {n.Name}").ToArray();
|
||||
var nodeNames = availableNodes.Select(n => $"{n.Id}: {n.RfidId}").ToArray();
|
||||
var input = Microsoft.VisualBasic.Interaction.InputBox("연결할 노드를 선택하세요:", "노드 연결", nodeNames[0]);
|
||||
|
||||
var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.NodeId));
|
||||
var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.Id));
|
||||
if (targetNode != null)
|
||||
{
|
||||
_selectedNode.AddConnection(targetNode.NodeId);
|
||||
selectedMapNode.AddConnection(targetNode.Id);
|
||||
_hasChanges = true;
|
||||
RefreshMapCanvas();
|
||||
UpdateNodeProperties();
|
||||
@@ -474,25 +473,25 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void RemoveConnectionFromSelectedNode()
|
||||
{
|
||||
if (_selectedNode == null || _selectedNode.ConnectedNodes.Count == 0)
|
||||
if (!(_selectedNode is MapNode selectedMapNode) || selectedMapNode.ConnectedNodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("연결을 제거할 노드를 선택하거나 연결된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 연결된 노드들 중에서 선택
|
||||
var connectedNodeNames = _selectedNode.ConnectedNodes.Select(connectedNodeId =>
|
||||
var connectedNodeNames = selectedMapNode.ConnectedNodes.Select(connectedNodeId =>
|
||||
{
|
||||
var node = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
|
||||
return node != null ? $"{node.NodeId}: {node.Name}" : connectedNodeId;
|
||||
var node = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectedNodeId);
|
||||
return node != null ? $"{node.Id}: {node.RfidId}" : connectedNodeId;
|
||||
}).ToArray();
|
||||
|
||||
var input = Microsoft.VisualBasic.Interaction.InputBox("제거할 연결을 선택하세요:", "연결 제거", connectedNodeNames[0]);
|
||||
|
||||
var targetNodeId = input.Split(':')[0];
|
||||
if (_selectedNode.ConnectedNodes.Contains(targetNodeId))
|
||||
if (selectedMapNode.ConnectedNodes.Contains(targetNodeId))
|
||||
{
|
||||
_selectedNode.RemoveConnection(targetNodeId);
|
||||
selectedMapNode.RemoveConnection(targetNodeId);
|
||||
_hasChanges = true;
|
||||
RefreshMapCanvas();
|
||||
UpdateNodeProperties();
|
||||
@@ -509,7 +508,7 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
nodeId = $"N{counter:D3}";
|
||||
counter++;
|
||||
} while (_mapNodes.Any(n => n.NodeId == nodeId));
|
||||
} while (this._mapCanvas.Nodes.Any(n => n.Id == nodeId));
|
||||
|
||||
return nodeId;
|
||||
}
|
||||
@@ -520,7 +519,7 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void NewMap()
|
||||
{
|
||||
_mapNodes.Clear();
|
||||
this._mapCanvas.Nodes.Clear();
|
||||
_selectedNode = null;
|
||||
_currentMapFile = string.Empty;
|
||||
_hasChanges = false;
|
||||
@@ -533,7 +532,7 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
if (CheckSaveChanges())
|
||||
{
|
||||
_mapNodes.Clear();
|
||||
this._mapCanvas.Nodes.Clear();
|
||||
_selectedNode = null;
|
||||
_currentMapFile = string.Empty;
|
||||
_hasChanges = false;
|
||||
@@ -547,8 +546,8 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "AGV Map Files (*.agvmap)|*.agvmap|All Files (*.*)|*.*",
|
||||
DefaultExt = "agvmap",
|
||||
Filter = "AGV Map Files (*.json)|*.json|All Files (*.*)|*.*",
|
||||
DefaultExt = "json",
|
||||
};
|
||||
|
||||
|
||||
@@ -596,9 +595,9 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "AGV Map Files (*.agvmap)|*.agvmap",
|
||||
DefaultExt = "agvmap",
|
||||
FileName = "NewMap.agvmap"
|
||||
Filter = "AGV Map Files (*.json)|*.json",
|
||||
DefaultExt = "json",
|
||||
FileName = "NewMap.json"
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() == DialogResult.OK)
|
||||
@@ -624,18 +623,8 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_mapNodes = result.Nodes;
|
||||
|
||||
// 맵 캔버스에 데이터 설정
|
||||
_mapCanvas.Nodes = _mapNodes;
|
||||
// RfidMappings 제거됨 - MapNode에 통합
|
||||
|
||||
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
{
|
||||
_mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
|
||||
_mapCanvas.ShowGrid = result.Settings.ShowGrid;
|
||||
}
|
||||
_mapCanvas.SetMapLoadResult(result);
|
||||
|
||||
// 현재 파일 경로 업데이트
|
||||
_currentMapFile = filePath;
|
||||
@@ -648,8 +637,8 @@ namespace AGVMapEditor.Forms
|
||||
UpdateNodeList();
|
||||
RefreshNodeConnectionList();
|
||||
|
||||
// 맵 로드 후 자동으로 맵에 맞춤
|
||||
_mapCanvas.FitToNodes();
|
||||
|
||||
|
||||
|
||||
UpdateStatusBar($"맵 파일을 성공적으로 로드했습니다: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
@@ -693,7 +682,11 @@ namespace AGVMapEditor.Forms
|
||||
ShowGrid = _mapCanvas.ShowGrid
|
||||
};
|
||||
|
||||
if (MapLoader.SaveMapToFile(filePath, _mapNodes, settings))
|
||||
if (MapLoader.SaveMapToFile(filePath,
|
||||
_mapCanvas.Nodes, _mapCanvas.Labels,
|
||||
_mapCanvas.Images, _mapCanvas.Marks,
|
||||
_mapCanvas.Magnets,
|
||||
settings))
|
||||
{
|
||||
// 현재 파일 경로 업데이트
|
||||
_currentMapFile = filePath;
|
||||
@@ -718,7 +711,7 @@ namespace AGVMapEditor.Forms
|
||||
private void UpdateRfidMappings()
|
||||
{
|
||||
// RFID 자동 할당 제거 - 사용자가 직접 입력한 값 유지
|
||||
// MapLoader.AssignAutoRfidIds(_mapNodes);
|
||||
// MapLoader.AssignAutoRfidIds(this._mapCanvas.Nodes);
|
||||
}
|
||||
|
||||
private bool CheckSaveChanges()
|
||||
@@ -780,14 +773,14 @@ namespace AGVMapEditor.Forms
|
||||
private void RefreshNodeList()
|
||||
{
|
||||
listBoxNodes.DataSource = null;
|
||||
listBoxNodes.DataSource = _mapNodes;
|
||||
listBoxNodes.DataSource = this._mapCanvas.Nodes;
|
||||
listBoxNodes.DisplayMember = "DisplayText";
|
||||
listBoxNodes.ValueMember = "NodeId";
|
||||
listBoxNodes.ValueMember = "Id";
|
||||
|
||||
// 노드 목록 클릭 이벤트 연결
|
||||
listBoxNodes.SelectedIndexChanged -= ListBoxNodes_SelectedIndexChanged;
|
||||
listBoxNodes.SelectedIndexChanged += ListBoxNodes_SelectedIndexChanged;
|
||||
|
||||
|
||||
// 노드 타입별 색상 적용
|
||||
listBoxNodes.DrawMode = DrawMode.OwnerDrawFixed;
|
||||
listBoxNodes.DrawItem -= ListBoxNodes_DrawItem;
|
||||
@@ -812,14 +805,14 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
e.DrawBackground();
|
||||
|
||||
if (e.Index >= 0 && e.Index < _mapNodes.Count)
|
||||
if (e.Index >= 0 && e.Index < this._mapCanvas.Items.Count)
|
||||
{
|
||||
var node = _mapNodes[e.Index];
|
||||
|
||||
var node = this._mapCanvas.Items[e.Index];
|
||||
|
||||
// 노드 타입에 따른 색상 설정
|
||||
Color foreColor = Color.Black;
|
||||
Color backColor = e.BackColor;
|
||||
|
||||
|
||||
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
|
||||
{
|
||||
backColor = SystemColors.Highlight;
|
||||
@@ -827,22 +820,30 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
else
|
||||
{
|
||||
backColor = Color.White;
|
||||
switch (node.Type)
|
||||
{
|
||||
case NodeType.Normal:
|
||||
foreColor = Color.Black;
|
||||
backColor = Color.White;
|
||||
|
||||
var item = node as MapNode;
|
||||
if (item.StationType == StationType.Normal)
|
||||
foreColor = Color.DimGray;
|
||||
else if (item.StationType == StationType.Charger)
|
||||
foreColor = Color.Red;
|
||||
else
|
||||
foreColor = Color.DarkGreen;
|
||||
break;
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
foreColor = Color.DarkGreen;
|
||||
backColor = Color.LightGreen;
|
||||
|
||||
case NodeType.Label:
|
||||
case NodeType.Mark:
|
||||
case NodeType.Image:
|
||||
foreColor = Color.DarkBlue;
|
||||
break;
|
||||
case NodeType.Charging:
|
||||
case NodeType.Magnet:
|
||||
foreColor = Color.DarkMagenta;
|
||||
break;
|
||||
default:
|
||||
foreColor = Color.DarkRed;
|
||||
backColor = Color.LightPink;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -854,17 +855,21 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
|
||||
// 텍스트 그리기 (노드ID - 노드명 - RFID 순서)
|
||||
var displayText = node.NodeId;
|
||||
|
||||
if (!string.IsNullOrEmpty(node.Name))
|
||||
string displayText;
|
||||
if (node.Type == NodeType.Normal)
|
||||
{
|
||||
displayText += $" - {node.Name}";
|
||||
var item = node as MapNode;
|
||||
if (item.HasRfid())
|
||||
displayText = $"[{node.Id}-{item.RfidId}] {item.StationType}";
|
||||
else
|
||||
displayText = $"[{node.Id}] {item.StationType}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(node.RfidId))
|
||||
else if (node.Type == NodeType.Label)
|
||||
{
|
||||
displayText += $" - [{node.RfidId}]";
|
||||
var item = node as MapLabel;
|
||||
displayText = $"{item.Type.ToString().ToUpper()} - {item.Text}";
|
||||
}
|
||||
else displayText = $"{node.Type.ToString().ToUpper()}";
|
||||
|
||||
using (var brush = new SolidBrush(foreColor))
|
||||
{
|
||||
@@ -881,31 +886,31 @@ namespace AGVMapEditor.Forms
|
||||
var processedPairs = new HashSet<string>();
|
||||
|
||||
// 모든 노드의 연결 정보를 수집 (중복 방지)
|
||||
foreach (var fromNode in _mapNodes)
|
||||
foreach (var fromNode in this._mapCanvas.Nodes)
|
||||
{
|
||||
foreach (var toNodeId in fromNode.ConnectedNodes)
|
||||
{
|
||||
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
|
||||
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == toNodeId);
|
||||
if (toNode != null)
|
||||
{
|
||||
// 중복 체크 (단일 연결만 표시)
|
||||
string pairKey1 = $"{fromNode.NodeId}-{toNode.NodeId}";
|
||||
string pairKey2 = $"{toNode.NodeId}-{fromNode.NodeId}";
|
||||
string pairKey1 = $"{fromNode.Id}-{toNode.Id}";
|
||||
string pairKey2 = $"{toNode.Id}-{fromNode.Id}";
|
||||
|
||||
if (!processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2))
|
||||
{
|
||||
// 사전 순으로 정렬하여 일관성 있게 표시
|
||||
var (firstNode, secondNode) = string.Compare(fromNode.NodeId, toNode.NodeId) < 0
|
||||
var (firstNode, secondNode) = string.Compare(fromNode.Id, toNode.Id) < 0
|
||||
? (fromNode, toNode)
|
||||
: (toNode, fromNode);
|
||||
|
||||
connections.Add(new NodeConnectionInfo
|
||||
{
|
||||
FromNodeId = firstNode.NodeId,
|
||||
FromNodeName = firstNode.Name,
|
||||
FromNodeId = firstNode.Id,
|
||||
FromNodeName = "",
|
||||
FromRfidId = firstNode.RfidId,
|
||||
ToNodeId = secondNode.NodeId,
|
||||
ToNodeName = secondNode.Name,
|
||||
ToNodeId = secondNode.Id,
|
||||
ToNodeName = "",
|
||||
ToRfidId = secondNode.RfidId,
|
||||
ConnectionType = "양방향" // 모든 연결이 양방향
|
||||
});
|
||||
@@ -940,7 +945,7 @@ namespace AGVMapEditor.Forms
|
||||
_mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId);
|
||||
|
||||
// 연결된 노드들을 맵에서 하이라이트 표시 (선택적)
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
|
||||
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
|
||||
if (fromNode != null)
|
||||
{
|
||||
_selectedNode = fromNode;
|
||||
@@ -1002,7 +1007,7 @@ namespace AGVMapEditor.Forms
|
||||
private void UpdateImageEditButton()
|
||||
{
|
||||
// ToolStripButton으로 변경됨
|
||||
btnEditImage.Enabled = (_selectedNode != null && _selectedNode.Type == NodeType.Image);
|
||||
btnEditImage.Enabled = (_mapCanvas.SelectedImage != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1019,9 +1024,10 @@ namespace AGVMapEditor.Forms
|
||||
/// </summary>
|
||||
private void BtnToolbarEditImage_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (_selectedNode != null && _selectedNode.Type == NodeType.Image)
|
||||
var selectedImage = _mapCanvas.SelectedImage;
|
||||
if (selectedImage != null)
|
||||
{
|
||||
using (var editor = new ImageEditorForm(_selectedNode))
|
||||
using (var editor = new ImageEditorForm(selectedImage))
|
||||
{
|
||||
if (editor.ShowDialog(this) == DialogResult.OK)
|
||||
{
|
||||
@@ -1059,7 +1065,7 @@ namespace AGVMapEditor.Forms
|
||||
if (listBoxNodes != null)
|
||||
{
|
||||
listBoxNodes.DataSource = null;
|
||||
listBoxNodes.DataSource = _mapNodes;
|
||||
listBoxNodes.DataSource = this._mapCanvas.Items;
|
||||
listBoxNodes.DisplayMember = "DisplayText";
|
||||
}
|
||||
}
|
||||
@@ -1101,8 +1107,8 @@ namespace AGVMapEditor.Forms
|
||||
// RFID 값 변경시 중복 검사
|
||||
if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
|
||||
{
|
||||
string newRfidValue = e.ChangedItem.Value?.ToString();
|
||||
if (!string.IsNullOrEmpty(newRfidValue) && CheckRfidDuplicate(newRfidValue))
|
||||
var newRfidValue = ushort.Parse(e.ChangedItem.Value?.ToString());
|
||||
if (newRfidValue != 0 && CheckRfidDuplicate(newRfidValue))
|
||||
{
|
||||
// 중복된 RFID 값 발견
|
||||
MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.",
|
||||
@@ -1121,13 +1127,13 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
// 🔥 다중 선택 여부 확인 및 선택된 노드들 저장
|
||||
bool isMultiSelect = _propertyGrid.SelectedObjects is MapNode[]; // _propertyGrid.SelectedObject is MapNode[];
|
||||
|
||||
|
||||
//var a = _propertyGrid.SelectedObject;
|
||||
List<MapNode> selectedNodes = null;
|
||||
List<NodeBase> selectedNodes = null;
|
||||
if (isMultiSelect)
|
||||
{
|
||||
// 캔버스에서 현재 선택된 노드들 가져오기
|
||||
selectedNodes = new List<MapNode>(_mapCanvas.SelectedNodes);
|
||||
selectedNodes = new List<NodeBase>(_mapCanvas.SelectedNodes);
|
||||
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 다중 선택 노드 수: {selectedNodes.Count}");
|
||||
}
|
||||
|
||||
@@ -1144,7 +1150,7 @@ namespace AGVMapEditor.Forms
|
||||
// 🔥 다중 선택인 경우 MultiNodePropertyWrapper를 다시 생성하여 바인딩
|
||||
if (isMultiSelect && selectedNodes != null && selectedNodes.Count > 0)
|
||||
{
|
||||
// System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
|
||||
// System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
|
||||
//var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
|
||||
_propertyGrid.SelectedObjects = selectedNodes.ToArray();// multiWrapper;
|
||||
}
|
||||
@@ -1153,9 +1159,9 @@ namespace AGVMapEditor.Forms
|
||||
_propertyGrid.Refresh();
|
||||
|
||||
// 🔥 단일 선택인 경우에만 노드를 다시 선택 (다중 선택은 캔버스에서 유지)
|
||||
if (!isMultiSelect && currentSelectedNode != null)
|
||||
if (!isMultiSelect && currentSelectedNode is MapNode mapNode)
|
||||
{
|
||||
var nodeIndex = _mapNodes.IndexOf(currentSelectedNode);
|
||||
var nodeIndex = this._mapCanvas.Nodes.IndexOf(mapNode);
|
||||
if (nodeIndex >= 0)
|
||||
{
|
||||
listBoxNodes.SelectedIndex = nodeIndex;
|
||||
@@ -1168,38 +1174,24 @@ namespace AGVMapEditor.Forms
|
||||
/// </summary>
|
||||
/// <param name="rfidValue">검사할 RFID 값</param>
|
||||
/// <returns>중복되면 true, 아니면 false</returns>
|
||||
private bool CheckRfidDuplicate(string rfidValue)
|
||||
private bool CheckRfidDuplicate(ushort rfidValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rfidValue) || _mapNodes == null)
|
||||
if (rfidValue == 0 || this._mapCanvas.Nodes == null)
|
||||
return false;
|
||||
|
||||
// 현재 편집 중인 노드 제외하고 중복 검사
|
||||
string currentNodeId = null;
|
||||
var selectedObject = _propertyGrid.SelectedObject;
|
||||
|
||||
// 다양한 PropertyWrapper 타입 처리
|
||||
//if (selectedObject is NodePropertyWrapper nodeWrapper)
|
||||
//{
|
||||
// currentNodeId = nodeWrapper.WrappedNode?.NodeId;
|
||||
//}
|
||||
//else if (selectedObject is LabelNodePropertyWrapper labelWrapper)
|
||||
//{
|
||||
// currentNodeId = labelWrapper.WrappedNode?.NodeId;
|
||||
//}
|
||||
//else if (selectedObject is ImageNodePropertyWrapper imageWrapper)
|
||||
//{
|
||||
// currentNodeId = imageWrapper.WrappedNode?.NodeId;
|
||||
//}
|
||||
|
||||
int duplicateCount = 0;
|
||||
foreach (var node in _mapNodes)
|
||||
foreach (var node in this._mapCanvas.Nodes)
|
||||
{
|
||||
// 현재 편집 중인 노드는 제외
|
||||
if (node.NodeId == currentNodeId)
|
||||
if (node.Id == currentNodeId)
|
||||
continue;
|
||||
|
||||
// 같은 RFID 값을 가진 노드가 있는지 확인
|
||||
if (!string.IsNullOrEmpty(node.RfidId) && node.RfidId.Equals(rfidValue, StringComparison.OrdinalIgnoreCase))
|
||||
if (node.RfidId != 0 && node.RfidId == rfidValue)
|
||||
{
|
||||
duplicateCount++;
|
||||
break; // 하나라도 발견되면 중복
|
||||
@@ -1234,23 +1226,23 @@ namespace AGVMapEditor.Forms
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 단일 연결 삭제
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
|
||||
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.ToNodeId);
|
||||
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
|
||||
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.ToNodeId);
|
||||
|
||||
if (fromNode != null && toNode != null)
|
||||
{
|
||||
// 양방향 연결 삭제 (양쪽 방향 모두 제거)
|
||||
bool removed = false;
|
||||
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id))
|
||||
{
|
||||
fromNode.RemoveConnection(toNode.NodeId);
|
||||
fromNode.RemoveConnection(toNode.Id);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
if (toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
{
|
||||
toNode.RemoveConnection(fromNode.NodeId);
|
||||
toNode.RemoveConnection(fromNode.Id);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
@@ -1277,108 +1269,18 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Multi-Node Selection
|
||||
|
||||
private void ShowMultiNodeContextMenu(List<MapNode> nodes)
|
||||
{
|
||||
// 다중 선택 시 간단한 다이얼로그 표시
|
||||
var result = MessageBox.Show(
|
||||
$"{nodes.Count}개의 노드가 선택되었습니다.\n\n" +
|
||||
"일괄 속성 변경을 하시겠습니까?\n\n" +
|
||||
"예: 글자색 변경\n" +
|
||||
"아니오: 배경색 변경\n" +
|
||||
"취소: 닫기",
|
||||
"다중 노드 속성 변경",
|
||||
MessageBoxButtons.YesNoCancel,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
BatchChangeForeColor();
|
||||
}
|
||||
else if (result == DialogResult.No)
|
||||
{
|
||||
BatchChangeBackColor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들의 글자색 일괄 변경
|
||||
/// </summary>
|
||||
public void BatchChangeForeColor()
|
||||
{
|
||||
var selectedNodes = _mapCanvas.SelectedNodes;
|
||||
if (selectedNodes == null || selectedNodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var colorDialog = new ColorDialog())
|
||||
{
|
||||
colorDialog.Color = selectedNodes[0].ForeColor;
|
||||
if (colorDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
foreach (var node in selectedNodes)
|
||||
{
|
||||
node.ForeColor = colorDialog.Color;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshMapCanvas();
|
||||
MessageBox.Show($"{selectedNodes.Count}개 노드의 글자색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들의 배경색 일괄 변경
|
||||
/// </summary>
|
||||
public void BatchChangeBackColor()
|
||||
{
|
||||
var selectedNodes = _mapCanvas.SelectedNodes;
|
||||
if (selectedNodes == null || selectedNodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var colorDialog = new ColorDialog())
|
||||
{
|
||||
colorDialog.Color = selectedNodes[0].DisplayColor;
|
||||
if (colorDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
foreach (var node in selectedNodes)
|
||||
{
|
||||
node.DisplayColor = colorDialog.Color;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshMapCanvas();
|
||||
MessageBox.Show($"{selectedNodes.Count}개 노드의 배경색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void allTurnLeftRightCrossOnToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
//모든노드의 trun left/right/ cross 속성을 true로 변경합니다
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
if (this._mapCanvas.Nodes == null || this._mapCanvas.Nodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("맵에 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"모든 노드({_mapNodes.Count}개)의 회전/교차 속성을 활성화하시겠습니까?\n\n" +
|
||||
$"모든 노드({this._mapCanvas.Nodes.Count}개)의 회전/교차 속성을 활성화하시겠습니까?\n\n" +
|
||||
"• CanTurnLeft = true\n" +
|
||||
"• CanTurnRight = true\n" +
|
||||
"• DisableCross = false",
|
||||
@@ -1388,20 +1290,20 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
foreach (var node in _mapNodes)
|
||||
foreach (var node in this._mapCanvas.Nodes)
|
||||
{
|
||||
node.CanTurnLeft = true;
|
||||
node.CanTurnRight = true;
|
||||
node.DisableCross =false;
|
||||
node.DisableCross = false;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshMapCanvas();
|
||||
|
||||
|
||||
MessageBox.Show(
|
||||
$"{_mapNodes.Count}개 노드의 회전/교차 속성이 모두 활성화되었습니다.",
|
||||
$"{this._mapCanvas.Nodes.Count}개 노드의 회전/교차 속성이 모두 활성화되었습니다.",
|
||||
"완료",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
|
||||
@@ -145,15 +145,15 @@
|
||||
<data name="btnSelect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHrSURBVDhPldBNiBJhHMfx/ykKKohQ18tEhygyWGQRJwh1
|
||||
fcHAt7qJ3TOj6FJ0akP06C1kz0EdDLbS3IbaxJdhA2V3jaRiYfPeIXPGeTss/mqElfbZ2HY/p/8883wf
|
||||
Hh6iAxh/5I+pq7M32fV9aeIcp4pzd8xZWb0UUZuOrjnrTe6s3OAOdpgqzn5WRYegNs5X9Y1rUJrcg1Hd
|
||||
PhzVTqfYvf+ktC4s6J9SML7dhbH1CNp6BMP3J7Ufdctxdu+U0r44o4qOmto8B30jDuN7Dq1ncXTLN7BV
|
||||
vwWtMw/pw1FI72iRbXdRGtyC1r2O1vMEDMPAeDzG+usU9K+38UugL4MqnWGbKbk1Y5FrNiEajaLT6UDX
|
||||
dfR6PdSfRmBs3odUpZH0lni2m9BW7Nxo5VQ5kUggnU6jUCigVCqhujgPY/MhBsu0LS3T2rBCj9l2Kh6P
|
||||
T2Ke5+H3+xGLxaCuRSDX7JCFExhUiGObqZ3Y7XbD5/PBarWaV34yeEPbUoUuS2XqDl9Shu0mkskk8vn8
|
||||
JPZ6vbBYLDDX5QpdkV6RYM54QUd+LtFVtqVwOIxcLrcn3jFcotDf37sEAgFks1m4XC54PJ498b4ymQz6
|
||||
/T6cTidCodDhYpN5QLvdRrFYnDwY+/+/gsHgPfPaNpvt8PEfvwFcBQshDC9YfwAAAABJRU5ErkJggg==
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHrSURBVDhPldBLaBNBHMfx/0kUVBBJ0lxWPIhihBJKyAqS
|
||||
pHkQIS+9hXg3RhQviicrITnmJqFnQQ8RqiamRqkhj6VCQtuIQaVQcxdq3d3s61Dy0w002KnU9nP67+x8
|
||||
h2GIDmD0kT+mLk/fZNf3pQkznCrM3DFnZflSRG05euast7izcpM72GGqMP1ZFRw1tXm+qq9dg9LiHgwb
|
||||
dnFYP51i9/6T0r4wp39Kwfh2F8bGI2irEYjvTmo/Gpbj7N4JpXNxShUcdbV1DvpaHMb3HNrP4uiVb2Cj
|
||||
cQtadxbSh6OQ3tM82+6iNLk5rXcd7ecJGIaB0WiE1dcp6F9v41eNvmxV6QzbTMjtKYtct9Wi0Si63S50
|
||||
XUe/30fjaQTG+n1IVRpKb4lnuzFtyc4Nl06VE4kE0uk0CoUCSqUSqvOzMNYfYnORtqVFWhEr9JhtJ+Lx
|
||||
+DjmeR5+vx+xWAzqSgRy3Q65dgJbFeLYZmIndrvd8Pl8sFqt5pWfbL6hbalCl6Uy9cSXlGG7sWQyiXw+
|
||||
P469Xi8sFgvMdblCV6RXVDNnvKAjPxfoKttSOBxGLpfbE+8QFyj09/cugUAA2WwWLpcLHo9nT7yvTCaD
|
||||
wWAAp9OJUCh0uNhkHtDpdFAsFscPxv7/r2AweM+8ts1mO3z8x29OYwsb4/6fnQAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="btnMove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
@@ -174,75 +174,75 @@
|
||||
<data name="btnAddLabel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHISURBVDhPnZJfb9JgFMb3JbzV+C2WNPtk3jgXs0vdsuAW
|
||||
7zajKaEXTWjhvShpilBIA+GvtAWxIyAgJjNBW6WQx5yXtBPREX2SpnnPe57fc9KevXa7jVarhUajgXq9
|
||||
jlqthmq1ikqlQu97e7vUbDaxWq22nuFwyCGGYdwNoWQyDAYDnux5XgyhmmVZd0NoZGqmNDqXy+XKfD5H
|
||||
GIa8TkDTNP8OodRfGn8UCoVBEAT49PEbCmzK7/r9PvL5/J8hlByNvFwuQebP0wAvjlycPHLwNruG9Ho9
|
||||
6LoOVVU3IZZl+bPZjDctFguenHji4vJZH6/PPJwfdWOI67rQNG0TUiqVDkzT9KfTdZNdv+HJVycfIJ5f
|
||||
b0EcxwFjbBNSLBYFXdf9yWTCm7qtL0gcOnh1egtJHLow1PV9p9OBoigQRfEWYhiGoGmaPx6PtyBvEh5e
|
||||
HvdwcfwOYbjkU2QyGaRSqQcxgJTL5QTGmD8ajWLI2WMbF0+7uDrt4us8iM2iKD7cMEdijAnZbNanTeQf
|
||||
rnmDy+fv8T0Id5sjKYoipNNpnzYx+sVkVlV1tzmSLMv7siz7tGC2bf+bOZIkSfuSJPn/ZY5EkGQyef/3
|
||||
eqSfzGgWuCCdbTAAAAAASUVORK5CYII=
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHISURBVDhPnZJfa9pgFMb7JXa7sW9RkH2y3awro5ddS3Et
|
||||
u2vHRsRcBEw0iEHiYpSg+Hcm0blUtOocdOCWbEZ5xnnlTXFule2BEN7znuf3HJKz12630Wq10Gg0UK/X
|
||||
UavVUK1WUalU6P1gb5eazSZWq9XWMxwOGUTX9fshlEyGwWDAkj3PiyBUsyzrfgiNTM2URudyuVyZz+cI
|
||||
w5DVCWia5t8hlMobDcP4aRjGIAgCfL75DkOdsrt+v49CofBnCCXzkZfLJcj8ZRrg1aGLk6cO3mfWkF6v
|
||||
h3w+D0VRNiGWZfmz2Yw1LRYLlhx/7uLyuI+3Zx7OD7sRxHVd5HK5TUipVHpimqY/na6b7PotS746+QTh
|
||||
/HoL4jgOVFXdhBSLxZimaf5kMmFN3dZXxA8cvDm9g8QPXOjK+r7T6UCWZQiCcAfRdT2WzWb98Xi8BXkX
|
||||
9/D6qIeLow8IwyWbIp1OI5lMPooAJE3TYplMxh+NRhHk7JmNixddXJ128W0eRGZBEB5vmLlUVWUQ2kT2
|
||||
4Zq3uHz5ET+CcLeZS5blWCqV8mkT+S8ms6Iou81ckiTtS5Lk04LZtv1vZi5RFPdFUfT/y8xFkEQi8fD3
|
||||
Otcvn84Wo7k6b1AAAAAASUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<data name="btnAddImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG6SURBVDhPnZLditpQFIXnJXrb0rcYCH2wXhSZwly2U0rp
|
||||
O0TMRUr+zkUyaUSjBEWNwR9ExGpVvCiDzAl6cljlnCGZWtuRdkEI2Xuvb23IvkiSBP1+H71eD91uF51O
|
||||
B+12G61WS7yfXZxTHMfgnJ88i8VCQoIgeBoikoVhPp/L5NlsVkBELYqipyFiZTEs0sR3s9ls7XY7MMZk
|
||||
XQDDMPw7RKT+Mriv1WrzNE3BfnwHSxzZm06nqFarf4aI5HzlLMsgzPxug8y+QvblNVhiy95kMoHv+7As
|
||||
6xgSRRHdbrdy6HA4yOTMKoF778C/fgS3rwrIeDyG67rHkEaj8SoMQ7rZbB4g3zoymd/egFc/n0BGoxEI
|
||||
IceQer2u+L5P1+v1A2QZg5kl8NsPjxCrBBYbsj8YDGCaJlRVfYQEQaC4rktXq9UpJPgETq5xb16DZ0xu
|
||||
Yds2KpXKiwIg5HmeQgihy+WygByMN+DOW6TuDfZ0V5hVVX15ZM5FCFEcx6HiEiVk0QPz3oPv0/PmXKZp
|
||||
KoZhUHGJ+S8WZsuyzptz6bp+qes6FQc2HA7/zZxL07RLTdPof5lzCUi5XH7+ez3XT8GUHaqSv5fjAAAA
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG6SURBVDhPnZLditpQFIXnJXrb0rcYkD5YL4pMYS7bKaXM
|
||||
O0TMRUp+ETM2EqMERY3BH0TE0ap4UQaZE/TksIZzhpPBOjPSLgghe+/1rQ3ZZ3Eco9frodvtotPpoN1u
|
||||
o9Vqodls8vebs1OKogiMsaNnPp8LiOd5r0N4MjfMZjORPJ1OMwivhWH4OoSvzId5Gv9uNBrN7XYLSqmo
|
||||
c2AQBC9DeKoc9H1/5/v+LEkS0D+/QWNb9CaTCarV6vMQnixXTtMU3Mzu1kitC6Q/P4LGluiNx2NUKhWY
|
||||
pnkICcOQbDYbMbTf70VyaubB3C9gv76DWRcZZDQaoVwuH0Lq9fqHIAjIer1+hNy2RTK7uQKrXh9BhsMh
|
||||
HMc5hNRqtZzrumS1Wj1CFhGokQe7+fYEMfOgkS76/X4fhmFAUZQniOd5uVKpRJbL5THE+wHmXOLeuARL
|
||||
qdjCsiwUi8V3GYDLdd2cbdtksVhkkL3+Ccz+jKR8hR3ZZmZFUd4fmKUcxxEQfokCMu+Cul/Bdslps5Rh
|
||||
GDld1wm/RPmLudk0zdNmKU3TzjVNI/zABoPBv5mlVFU9V1WV/JdZikMKhcLbv+tSD5T6HZWMaVplAAAA
|
||||
AElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="btnAddNode.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHdSURBVDhPnZLda9NgFMb3T3ir+F8Mgn/PbmQO0Vt1KKKC
|
||||
zo/dbX7hVUqzmbVJ+84lxpQ2rSGlbVr7QSklbW1LQdHqW9YPHnlfSDROV/SBEHLOeX7PgZyVSqWCcrmM
|
||||
UqmEYrGIQqGAfD4Px3HY+8zKMrmui8ViceLpdrscYprm6RCWzAye5/HkdrsdQFjNtu3TIWxlNszS2Hcu
|
||||
l3PG4zFmsxmvM6BlWX+HsNRfBo/T6bQ3mUww/NzBO3ef91qtFlKp1J8hLNlfeT6fg5lHXz7iYWwDd/fW
|
||||
YLh7vNdsNmEYBlRVDUNs26aj0YgPTadTDD51sBW7hJ031/Bcv4lHsY0A0mg0oGlaGJLNZi9YlkWHwyEf
|
||||
+uC9x739NTw9uoFXxh280G+FIPV6HYSQMCSTyQiGYdDBYMCHap08HsjreHa0GUC2DtahFSO8X61WoSgK
|
||||
RFH8CTFNU9A0jfb7/ROQl29vY1u9gicHVzGbT/kWiUQC0Wj0XABg0nVdIITQXq8XQO6/vojH8cvYPdzE
|
||||
t+9fA7MoiudDZl+EECGZTFJ2iXxdz8Hu4XVMjulysy9FUYR4PE7ZJfq/mJlVVV1u9iXL8qosy5QdWK1W
|
||||
+zezL0mSViVJov9l9sUgkUjk7O91Xz8AO/kPZbC5CrgAAAAASUVORK5CYII=
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHdSURBVDhPnZLda9NgFMb3T3ir+F8Min/PbmQO0VvdUEQF
|
||||
nV93bk7xKqXZzNqkzbrGmJKmXUhpm9Z+UEpJW9tSULT6lvWDR94X3kicrugDIeSc8/yeAzkrlUoF5XIZ
|
||||
pVIJxWIRhUIB+XwejuPQ94WVZXJdF4vF4szT7XYZxDCM8yE0mRo8z2PJ7Xbbh9CabdvnQ+jKdJim0e9c
|
||||
LueMx2PMZjNWp0DLsv4Ooal80DTNU9M0vclkguGXDj64B6zXarWQTqf/DKHJfOX5fA5qHn39hCfRDTzY
|
||||
X4Pu7rNes9mErutQFCUIsW2bjEYjNjSdTjH43MF29BpeJm9hT7uDp9ENH9JoNJBKpYKQbDZ7xbIsMhwO
|
||||
2dBH7wQPD9awe7yJt/p9vNbuBiD1eh2qqgYhmUwmpGkaGQwGbKjWyeOxtI5Xx1s+ZPtwHalimPWr1Spk
|
||||
WYYgCL8ghmGEkskk6ff7ZyBv3t/DC+UGnh/exGw+ZVvE43FEIpFLPoBK07RQIpEgvV7Phzx6dxXPYtex
|
||||
c7SF7z+++WZBEC4HzFyqqjIIvUS2rudg5+g2JqdkuZlLluVQLBYj9BL5L6ZmRVGWm7kkSVqVJInQA6vV
|
||||
av9m5hJFcVUURfJfZi4KCYfDF3+vc/0ED18PUDextaQAAAAASUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<data name="btnDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGvSURBVDhP7Y+/TxphGMdR4A9wKh0cNHHuP9ChKXQxsWEw
|
||||
oYMOjR06dGg6QNuhdHLQsLgZ8GVFBzcqRGtDD+447o7jwGAMIkV+ncevpa0mGr7N+0YNOX+0f0C/yWd5
|
||||
83y+7/NYLP/DEg6HnYSQNCEE/0g5FAo9vy4ghBQVRUC9XmI0GpRDRrNZZrRaR9D1CnT9BwoFhZbUhguM
|
||||
SqUInk8ikUgwOI5DMplEKpUCz/NIp9MQRRGKorCSYDB4airYhygKTDRLmUwGsiwzWdNyMIzazYJq9QCy
|
||||
LEEQBCZSSZIkJmWzWeRyOWiahr29Atrtxs2C4+MSVFVhEv2NSqqqMjGfz0Ne/YTv81PYcdrx7cU4Im9n
|
||||
z69k7O5uo9NpotultNDr6ej3Ty4xUFpfRt77GKfRAAbFGH5F3kF68wgbvjmwgr8RdTvwOxoAVmYA3xiw
|
||||
OIH20hN8cT/A9Rn3Jf7UNhgomxhO3+8AfTfP3pq4y1r7ubYA+B0481rQ81pQfW1F7Jm1bp69NZzn4Wfx
|
||||
5eR50zcB44Md5Vcj+Dptu9hyjX40z94ZzjP+Pu60VujadKMr+Q87TKpZvTdvaAAAAABJRU5ErkJggg==
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGvSURBVDhP7Y89T1phGIZpgR/QqTiQ6NDZP+BgCiZd2jA0
|
||||
sUNNBx0cjIMDtB3EycGGxY2Evmym7eBGhfgVeuB8cM7hcIDQGKSU7+NBYWlrEw23ed+oMUdr/QHeybW8
|
||||
ea77fR6b7T4s0WjUQwgRCSG4I5VIJPLisoAQUlJVAc1mmdFqUfYZ7XaF0en8gGFUYRg/USiotKRxtcCs
|
||||
Vkvg+RSSySSD4zikUimk02nwPA9RFCFJElRVZSXhcPjYUvAdkiQw0SplMhkoisJkXc/BNBvXC2q1PSiK
|
||||
DEEQmEglWZaZlM1mkcvloOs6isUCut3W9YJ6vQxNU5lEf6OSpmlMzOfzUMKL+Db1BFseJ3ZfubE2//Lk
|
||||
QsbOziYOD9s4OqJ00OsZ6PcPzjFR/vwBef8YjmMhDEpx/P60AHluFF8Cr8EK/kfM58KfWAhYfQ4EHgHL
|
||||
I+iujOOr7zEuz7gtG08dg4G6jqvpB12g79bZG5Pw2hu/Pk4DQRf++m3o+W2ozdoRn7A3rbM3hpscWpLe
|
||||
DJ+0AyMw3zlRmXmA7WeO0w3vw/fW2X+Gm3S/TXjsVbo23ehCPgMKrqo38mZYEwAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="btnEditImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAL5SURBVDhPfdLfT5tVHMdx/gPvjVcmGpPF6I033pip000X
|
||||
f+A0uGUlk0zjxCzGzbIiDpnbM6iUQiGUFqhd2YbtBo4yYMN2W4FtFR54oMimMhkDpA10fdqn7RlB9jZt
|
||||
tA4y/STfq/M9r5Oc7zfP4R2tdnTI4TqPrJraHlKu9WV2y6q9Q67J+yfNXSORcCRyX0smSabE/9ayJojE
|
||||
EjS6h2M5IPNyQkvS0D1HdbdC+fnvcY5YcI9/wymlgvqAkY9bbXzeMoC+ZYqlhKDWM6bmgJo2Wc3ojefn
|
||||
kHr7cA5baJ84gH34IxqvF2EN7sPYf4R9TScxOG4QUR8CJFKCpt55yntcnFYqsAb3Unt1N8bA+0hXCjAF
|
||||
PqOw7lvKTvxCWE1T6xldD6gpgb1vgcNeJ9/JZZiHdEiX3+Nrfz5f/ZiP5CtGZ67E0e7i5skCfjt3FKXl
|
||||
FZSGTbosEEsKWi78geTtwRI4Ts1QEeX+dzBcfJPDvgK+/KGEhhMHWfTrUSe6QVsgNnGGwaqt4SxwVxM4
|
||||
+hex9IxT4nYgde2nyrcXqX8PpZ5PMLUWszRZzspcF4uXjaRDHfx5exCl+UORl5l1NCFw+sJZpM6r8EWF
|
||||
jmOml5GMm2mu20p0soy19CDpW8VEf/qUG64iFNvO5aDlrSezwHJcYHC/gb59OwdOvYat9DlCjh1Mtm3h
|
||||
bugQa+IaqelCxJ1dJKYMTFm33x+r2fJs9hOrXSNqNC4ITF/K1VCok4Fz+1kc1v99WYeY3Ul8spRbTa/j
|
||||
cJ7VclMwZYH0OsB/qZKpCwdZSwcQM0Xcu7MbNXSIX62vMj87g9n9wBjrPfJsNBYnrgniyXuM+V383GNg
|
||||
vNfMfPBdUjO7iColTDe/zfLC7yxlV3nk31W2d8pVtjOjA5nlyMjuypcgcZubth34DY8yZN2G79gLa61O
|
||||
r5Y5rz87qto7ZXMO2JgjHzy1unKtnpVgLcGjmzm95zGtT7/pmY19/5n85x8RUuETqxePb1vtr37xSnfJ
|
||||
049v7HkwfwEdnYukisiEOQAAAABJRU5ErkJggg==
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAL4SURBVDhPfdLxU9N1HMdx/oN+7/qp7vzF6+qX7vqts9K0
|
||||
vDKzjvIcepx1GZ3XpQ1HJGL6FRbjKwOBDfg2CKMNJRkChps6JB34hcEWWmGIgIODue/23faRI3x229US
|
||||
znrdvX/6vD+Pz93n/c5R3MMVSrs6J7tUzdL8mHKsLtmpavazamXOP2noHJy/Fw4/1BMJEknxv7WoC+aj
|
||||
cWrb/NEskH45rieo6ZqmoitAyfkfcNyw4hz9mtOBUqp9Zj5usvF5Yz/GxnEW4gLZNaJlgcpmVUvrteen
|
||||
kXp6cQxZaRs7gH3oI2qv51Pn34e57yj76lsxKTeZ1x4DxJOC+p4ZSrpb+D5QSp1/Lyd/3oXZ9z7SlVws
|
||||
vs/Iq/qG4uZfmdNSyK7h1YCWFNh7ZznsdvCtWow8YEC6/B5HvNv56uJ2JE8BBrkMpa2FW625/H7uGIHG
|
||||
TQRq1hsyQDQhaLxwD8ndjdV3gsqBfEq872D66S0Oe3L58sdCapoPEvYa0ca6QJ8lOtaOr3zzXAa4rwuU
|
||||
vjDW7lEKnQpS537KPXuR+vZQ5PoES1MBC6ESlqY7CV82kwqe5c87VxmxfShy0rOOxAUOz1wGqXIH+KLU
|
||||
wHHLq0jmDTRUbSYSKmYldZXU7QIig59ysyWfgC130W/dti4DLMYEJuebGNu2cuD069iKXiCo7CD03Ubu
|
||||
Bw+xIq6RnMhD3N1JfNxE6NTWhyOVG5/PfGKF44YWiQl8E5eyNRDsoP/cfsJDxr8vGxBTHxALFXG7/g0U
|
||||
5YyenYIlA6RWAd5LZYxfOMhKyoeYzOfB3V1owUP8VvcaM1OTyM5HxljtUqci0RgxXRBLPGDE28Iv3SZG
|
||||
e2Rm/O+SnNxJJFDIRMPbLM7+wUJmlQf/XWV7h1puax/uTy9HWnaWvQLxO9yy7cBrepKBui14jr+00qS4
|
||||
9fR51Zlhzd6hyllgbY7uXre8dK2aJf9J/Mc20Jr3lN5rXP/c2r7/zLYXnxBS3jPLvSe2LPdVvHylq/DZ
|
||||
p9f2PJq/AD40i0VffXQ/AAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="btnConnect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
@@ -263,15 +263,15 @@
|
||||
<data name="btnDeleteConnection.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHxSURBVDhP7Y7Ni1JhGMVvXadgWrWzRcS0S0FyFYHgOH4g
|
||||
fgQXQl0NRIr1SqB9zGh34XAXs7gwgRfMVUILsY22mhmlDHTcRGQrbXVnkCKkSIOmtMk88QyMmDj/QQde
|
||||
eDjndw4vx/3XWHa7/Qxj7IEkSd1sNgtRFNuMsSuMMa0oiip5lBFD7D9lnU53yu12y6lUCp1OB6qqolqt
|
||||
IplM7oqiWGu1WgPy6vU6iCGWOuMBo9F4LRqNfms2m91AILDldDqHoVAIiqIgkUiAbvIoy+VybWKpMzkg
|
||||
S5LUVxQl7PF45k0m0/dKpYJGo4FarYZCoQDyKCOGWOqMBwwGw+1wONxLp9P3BUEoWyyWA4fDAUEQDh/d
|
||||
5FFGDLHUGQ/o9fpLXq/3vSzLvXw+/7VYLCKTySAWiyEejx/epXWGV9cv/Hm5NIfnjvmDp4unH40HOI7j
|
||||
zWbzZZfLtRcMBvcjkUifMfbL5/Mt+/1+6+Nl6+Ddvavob25g1Crhx7O7eBPRDV9YNXcmR47Vto1Xf25u
|
||||
AIoXWD0LrC/gi7yILQu/O83OVHlJMxq9LWJSvaQW5E+zM1W28R/2n9wEkloMVjh0Vzi0b/Eo2fmP0+xM
|
||||
7fjPrb2+cfH3p9UFfE7MQQ2eQMWlGW7bTj6cZo/Vjv98vGzl9+jb9KOj8l/xBxqBzigbjwAAAABJRU5E
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHxSURBVDhP7Y7Ni1JhGMVvXadgWrWzRdQsU5BcRW4cR72E
|
||||
H8FtoS6iFinWKwn2MaPdheJikIEJvGCuElpcaKOtTKUMdNxEZBut1Z1BipAiDZrSJvPEM5CYOP9BB154
|
||||
OOd3Di/H/ddEdrv9GGPsbiKR6OVyOUiS1GGMnWOMaSVJUsmjjBhi/ynrdLojTqdzI51Oo9vtQlVV1Go1
|
||||
xOPxbUmS6q1Wa0heo9EAMcRSZzJgNBovhsPhr+12u+fz+Z4KgjAKBAKQZRmxWAx0k0eZoigdYqkzPbCR
|
||||
TCYHsiwHXS7Xoslk+latVtFsNlGv15HP50EeZcQQS53JgMFguBEMBvuZTOaOKIoVi8WyJwgCRFHcf3ST
|
||||
RxkxxFJnMqDX68+43e53qVSqryjKl0KhgGw2i0gkgmg0un+X1xleXDr1+/nKAp4Ii3uPlo/enwxwHMeb
|
||||
zeazDodjx+/374ZCoQFj7KfH47ni9XqtDy5bhm9un8eguInx2zK+P76FVyHd6JlVc3N65ECVbLz6o7gJ
|
||||
yG5g7TiwvoTPG8soWvjtWXauSiua8fh1AdPqx7Ugf5adq4qNf7/78BoQ12K4yqG3yqFznUfZzn+YZedq
|
||||
y3si8fLq6V8f15bwKbYA1X8I1QuaUcl2+N4se6C2vCejFSu/Q9+mH/0t/wFGlxos/Pd5kgAAAABJRU5E
|
||||
rkJggg==
|
||||
</value>
|
||||
</data>
|
||||
@@ -287,18 +287,18 @@
|
||||
<data name="btnFitMap.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKYSURBVDhPhZBfSFNRHMfvY0RPEfQSFD2tngKj9WBZmDqX
|
||||
em1t6tJtDV2WSRZGKN1ScV7JyhVZMnuYIJua0aXQ1Oamzj/DtnvuMlKocJ47aMWI9FwrCC4njrqX66Iv
|
||||
nJfD9/P9fX8/itpUyFOm5nvNHOi1JMJ9VjnkschzLlMi2FXMBTvOqpO+lBI8Jpvgtsb5HjMIdZ9jwi6j
|
||||
asapUwW79EzQaQDTncXxgKPQpuTWRSZvwKUsH0UHwDLSAyjVCKJUHYGr+fNwZX/gAc1OOQrj/rvarU14
|
||||
j5kjk8NRpAIQXQIQ1QsiugHEtesClK4JolQqRH/sm2ynga9Vyyl5CngsiVB3CROJIR2AqG4Dlmo34Wog
|
||||
oioApVO+Fi3jZfMTSp4K91rksNOoEkTpcnJqcgXSSICokrQYachW+dvyZCVPvXWbZXIwYiQgWJaukDAe
|
||||
oovrfyKqiECpmAT42NytAbOu0kTwiZ7hxTXNRgtUtQ7HkA2IqFyAq+d5ER0fvpnFjDDZW1eY7dRzsx0G
|
||||
QA4lQKloE6wQxFUrgQFE9LvYzz2vm3J/D9Vlzil5KuAoUE8/1sUn2mmWX/q1V4hJJ0llAUqGCETpBJ5w
|
||||
6P7MD9Rgf+0J/KIm3ajMoMbbCmwBBx333TkNvM05DNmXvOH6TGb8Hi0vDNbjWNiJl6bv47FmGr8sV+9W
|
||||
ZlB+Vqt+Y9dyoy25CXKsMbtGHryds+JtysOfxlvx1w89+NuiG3/0NuIB6zHZkyoklbirGabJ9hIcnXHg
|
||||
+PtuvBx8hOeeWjGbpfr86kLadqU/pZ5XpZvG7Gfw4tAtvDDUgKceFuGBysPfnxkO7lB6/6l+2xGTuywN
|
||||
jzZqcJ9V/cVtTNul9PxX/eVHd3aVHLK7LBnbkn9/ARsspRTlWFT8AAAAAElFTkSuQmCC
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKYSURBVDhPhZBfSFNRHMfvY0RPEfQSFD2tniKh9WBZmM5W
|
||||
es02del1ii7LJAsjtG6pOCdZuiJLZg8TbFMzuiSKlm7m/LPU3XOWkUKF89xBK0ak51pBcDlx1L1cF33h
|
||||
vBy+n+/v+/sxzIZm3flasatAAF3m6Ey3WZl2c4rfyUX97TmCv/WsNuaLK+jmLPBpUUTsLACzHef4gNOk
|
||||
mXRkafztBt7vMIKJtpyIz55pUXNropPX4TybGML7wBI2ACRXQEkuD6KV9Dm0vNd3n7WN29mI965+cxPR
|
||||
XSDQyYEQ1gCELwKEq6GErwNp9RpE8lUoyXkw9GPPWAsLPI16Qc0zwG2Oznbk8sEwzgIIV63DcuUGXA4k
|
||||
XAaQfMLToOdfNaRH1Twz08UpAYdJAyX5UmxqbAXaCCJcSlsM1aRqvE16Rc0zb135Cj0YNVIQLMmXaZiI
|
||||
8IW1PwmXBJGcQwM8Nt3mgElnXtT/2MCL0mraegtctgaHsQVIuBiilUJRwkcHb6bwQzdSN68w1WYQplqN
|
||||
gB4KIjl7AyyB0koRhQHC7Lvwz139dSd/D1QlT6t5xmfP0E48yoy8aWFt4uKv3TAsH6eVIZKNQYQTKext
|
||||
zvwz11tBvJXHyIuKRJM6gxltyrD47GzEc+cUGK7X8XRf+gark/nRe+nKfH81CQccZHGimYzUs+RlsXan
|
||||
OoPx2vTa11a9MNSgi9JjjVhTlb7buuXhutPk02gj+fqhk3xbcJGPw7Wk13xEcccLiSfhShI31pJLQpN2
|
||||
EnnfQZb8D8n0kyJiS9F87jufsFXtj6vnZYnciPUMWRi4ReYHasj4g2zSW3rw+zPj/m1q7z/VYznEufIT
|
||||
yKvaNNJdqP3iMiXsUHv+q57iw9vbcw9YneakLbG/v5ifpNsR5bepAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="toolStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
@@ -307,7 +307,7 @@
|
||||
<data name="btnNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAU1JREFUOE9j+E8GYEAGIIGrzz+AJYrW6IIxCMw6cO1/x/oTYFw2fQsKRjEE2eTp
|
||||
wQAADsEBuJFr7QAAAU1JREFUOE9j+E8GYEAGIIGrzz+AJYrW6IIxCMw6cO1/x/oTYFw2fQsKRjEE2eTp
|
||||
p73/Tz3pgSz0/+nbryi4c/eb/+EFE8FycANAtk475fV/0nH3//1HXP53HrCHu+TA/b9w/OnbL7ABKIbA
|
||||
DJhw1PV/9wGH/y17rP/XbTNHMWD3HQgGGQDDQYnVCANgoGGHxf+qzcbIQnADtt36A8Ybr//BNAAUYCCA
|
||||
HIgwZ6NrXn3lN6YBoJBGBzBnwzTCNMMM8AzNBquDGwASxGcrCC+5CMGg2EAxABS3bz5+wzAAm+Z55yAG
|
||||
@@ -319,7 +319,7 @@
|
||||
<data name="btnOpen.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAhFJREFUOE/dz71rU1EYBvAO7t38E1xaizQfIh1cBHFyEV2sDiJoyCApFBVBO6QN
|
||||
wQAADsEBuJFr7QAAAhFJREFUOE/dz71rU1EYBvAO7t38E1xaizQfIh1cBHFyEV2sDiJoyCApFBVBO6QN
|
||||
iIGSqCDSKpSKtlb8QBCHEge1rW1iE733Jmlsmhh6g839vud+nHseSapJXFwFX3g45wzv7z1vT8//Ufwr
|
||||
X6acGna23p3tyhmn8Hpoo/h0YO/GE1/vH3nr611cPLynDQgvghZjHgAGsGZ27y6po/o+ZFc+hKzfqa1e
|
||||
JtWlkTL/fPBkB3gWkBklsKpxGKVJGJvTMMvzYK4E5ulgVAOjyu7bbYBR2xYWgrk2kH8cUD1HgVFKQi8m
|
||||
@@ -334,7 +334,7 @@
|
||||
<data name="btnReopen.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAUZJREFUOE+lkzFLw0AYhvt3HP0DLqWDdCuECjooBRdBXJwEZ3VRBykUBCfRpSi4
|
||||
wQAADsEBuJFr7QAAAUZJREFUOE+lkzFLw0AYhvt3HP0DLqWDdCuECjooBRdBXJwEZ3VRBykUBCfRpSi4
|
||||
uXTSIYNiRSgFBxU6qHSoCMHiyfPJm9zFgKAHT0Mu9z53+fK15P4wSv5gojcYuvbNo3EU37tW587YaF8a
|
||||
q3unAYGEG4JInl7e3HKrFsCc5rlunj+7+spuJpFAiw4HiynN7mwqamxX3OsoMUEg4Ydjs0Ds9+cNBFud
|
||||
yK4SwOg9cbWFtUzA+7Lg4uHTkGgnrqdwf9YbG4UCJiWQhJ0JcwoJjrsfocA/+m8QlmA6WsoEfuH8owoF
|
||||
@@ -369,7 +369,7 @@
|
||||
<data name="btnSaveAs.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAyZJREFUOE9tku1PW1Ucx+8r/Tt86wvRxRjjEmPMULeZNAsQXczipoCdDhxZlg0Z
|
||||
wQAADsEBuJFr7QAAAyZJREFUOE9tku1PW1Ucx+8r/Tt86wvRxRjjEmPMULeZNAsQXczipoCdDhxZlg0Z
|
||||
m7CisBYfthcDXLrBlJktUbIBGQ+hAn2cq3uga3lqBxQuUCil9OHc9t7bj2mjy3B+k29OTnK+n9/35Byp
|
||||
+NTowffN7iGDxb1hsLgxWNzC0OoWBotHGMwusafF4dt5fPzHF8uuPyf9n3Y32cfXtsR6NCG0aELhX69v
|
||||
5S0IyFvU31yg/MJ06r2q/uf/m5f2Njsi8VRGc81l8SyouOdUHI8zjM4o3PYnUVTonUxT3zPPp+en089A
|
||||
|
||||
@@ -78,7 +78,12 @@
|
||||
<Compile Include="Models\IMovableAGV.cs" />
|
||||
<Compile Include="Models\VirtualAGV.cs" />
|
||||
<Compile Include="Models\MapLoader.cs" />
|
||||
<Compile Include="Models\MapMagnet.cs" />
|
||||
<Compile Include="Models\MapMark.cs" />
|
||||
<Compile Include="Models\MapNode.cs" />
|
||||
<Compile Include="Models\NodeBase.cs" />
|
||||
<Compile Include="Models\MapLabel.cs" />
|
||||
<Compile Include="Models\MapImage.cs" />
|
||||
<Compile Include="PathFinding\Planning\AGVPathfinder.cs" />
|
||||
<Compile Include="PathFinding\Planning\DirectionChangePlanner.cs" />
|
||||
<Compile Include="PathFinding\Planning\DirectionalPathfinder.cs" />
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
// 이동 경로 정보 추가
|
||||
Point? PrevPosition { get; }
|
||||
string CurrentNodeId { get; }
|
||||
string PrevNodeId { get; }
|
||||
MapNode CurrentNode { get; }
|
||||
MapNode PrevNode { get; }
|
||||
DockingDirection DockingDirection { get; }
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
using AGVNavigationCore.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AGVNavigationCore.Models;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace AGVNavigationCore.Controls
|
||||
{
|
||||
@@ -15,7 +17,7 @@ namespace AGVNavigationCore.Controls
|
||||
Focus(); // 포커스 설정
|
||||
|
||||
var worldPoint = ScreenToWorld(e.Location);
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
|
||||
// 에뮬레이터 모드 처리
|
||||
if (_canvasMode == CanvasMode.Emulator)
|
||||
@@ -48,7 +50,11 @@ namespace AGVNavigationCore.Controls
|
||||
// 마지막 선택된 노드 업데이트 (단일 참조용)
|
||||
_selectedNode = _selectedNodes.Count > 0 ? _selectedNodes[_selectedNodes.Count - 1] : null;
|
||||
|
||||
// 다중 선택 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
|
||||
// 단일/다중 선택 이벤트 발생
|
||||
if (_selectedNodes.Count == 1)
|
||||
{
|
||||
NodeSelect?.Invoke(this, _selectedNodes[0], e);
|
||||
}
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -59,7 +65,8 @@ namespace AGVNavigationCore.Controls
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
|
||||
// NodesSelected 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
|
||||
// 단일/다중 선택 이벤트 발생
|
||||
NodeSelect?.Invoke(this, hitNode, e);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -94,7 +101,7 @@ namespace AGVNavigationCore.Controls
|
||||
break;
|
||||
|
||||
case EditMode.Connect:
|
||||
HandleConnectClick(hitNode);
|
||||
HandleConnectClick(hitNode as MapNode);
|
||||
break;
|
||||
|
||||
case EditMode.Delete:
|
||||
@@ -110,53 +117,51 @@ namespace AGVNavigationCore.Controls
|
||||
private void UnifiedAGVCanvas_MouseDoubleClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
var worldPoint = ScreenToWorld(e.Location);
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
|
||||
if (hitNode != null)
|
||||
// 가동 모드에서는 더블클릭 편집 방지
|
||||
if (_canvasMode == CanvasMode.Run) return;
|
||||
|
||||
if (hitNode == null) return;
|
||||
|
||||
if (hitNode.Type == NodeType.Normal)
|
||||
{
|
||||
// 노드 타입별 더블클릭 액션
|
||||
switch (hitNode.Type)
|
||||
{
|
||||
case NodeType.Normal:
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
case NodeType.Charging:
|
||||
HandleNormalNodeDoubleClick(hitNode);
|
||||
break;
|
||||
|
||||
case NodeType.Label:
|
||||
HandleLabelNodeDoubleClick(hitNode);
|
||||
break;
|
||||
|
||||
case NodeType.Image:
|
||||
HandleImageNodeDoubleClick(hitNode);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 기본 동작: 노드 선택 이벤트 발생
|
||||
_selectedNode = hitNode;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
break;
|
||||
}
|
||||
HandleNormalNodeDoubleClick(hitNode as MapNode);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Label)
|
||||
{
|
||||
HandleLabelDoubleClick(hitNode as MapLabel);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Image)
|
||||
{
|
||||
HandleImageDoubleClick(hitNode as MapImage);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Mark)
|
||||
{
|
||||
HandleMarkDoubleClick(hitNode as MapMark);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Magnet)
|
||||
{
|
||||
HandleMagnetDoubleClick(hitNode as MapMagnet);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleNormalNodeDoubleClick(MapNode node)
|
||||
{
|
||||
// RFID 입력창 표시
|
||||
string currentRfid = node.RfidId ?? "";
|
||||
var currentRfid = node.RfidId;
|
||||
string newRfid = Microsoft.VisualBasic.Interaction.InputBox(
|
||||
$"노드 '{node.Name}'의 RFID를 입력하세요:",
|
||||
$"노드 '{node.RfidId}[{node.Id}]'의 RFID를 입력하세요:",
|
||||
"RFID 설정",
|
||||
currentRfid);
|
||||
currentRfid.ToString());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newRfid) && newRfid != currentRfid)
|
||||
if (ushort.TryParse(newRfid, out ushort newrfidvalue) == false) return;
|
||||
if (newrfidvalue < 1) return;
|
||||
|
||||
|
||||
if (newrfidvalue != currentRfid)
|
||||
{
|
||||
node.RfidId = newRfid.Trim();
|
||||
node.RfidId = newrfidvalue;
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -167,11 +172,19 @@ namespace AGVNavigationCore.Controls
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
}
|
||||
private void HandleMarkDoubleClick(MapMark label)
|
||||
{
|
||||
//TODO:
|
||||
}
|
||||
private void HandleMagnetDoubleClick(MapMagnet label)
|
||||
{
|
||||
//TODO:
|
||||
}
|
||||
|
||||
private void HandleLabelNodeDoubleClick(MapNode node)
|
||||
private void HandleLabelDoubleClick(MapLabel label)
|
||||
{
|
||||
// 라벨 텍스트 입력창 표시
|
||||
string currentText = node.LabelText ?? "새 라벨";
|
||||
string currentText = label.Text ?? "새 라벨";
|
||||
string newText = Microsoft.VisualBasic.Interaction.InputBox(
|
||||
"라벨 텍스트를 입력하세요:",
|
||||
"라벨 편집",
|
||||
@@ -179,28 +192,24 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newText) && newText != currentText)
|
||||
{
|
||||
node.LabelText = newText.Trim();
|
||||
label.Text = newText.Trim();
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
_selectedNode = label;
|
||||
LabelDoubleClicked?.Invoke(this, label);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void HandleImageNodeDoubleClick(MapNode node)
|
||||
private void HandleImageDoubleClick(MapImage image)
|
||||
{
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
_selectedNode = image;
|
||||
|
||||
// 이미지 편집 이벤트 발생 (MainForm에서 처리)
|
||||
ImageNodeDoubleClicked?.Invoke(this, node);
|
||||
ImageDoubleClicked?.Invoke(this, image);
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void UnifiedAGVCanvas_MouseDown(object sender, MouseEventArgs e)
|
||||
@@ -211,24 +220,23 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
if (_editMode == EditMode.Move)
|
||||
{
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
// 1. 노드 선택 확인
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
if (hitNode != null)
|
||||
{
|
||||
_isDragging = true;
|
||||
_isPanning = false; // 🔥 팬 모드 비활성화 - 중요!
|
||||
_isPanning = false;
|
||||
_selectedNode = hitNode;
|
||||
_dragStartPosition = hitNode.Position; // 원래 위치 저장 (고스트용)
|
||||
_dragOffset = new Point(
|
||||
worldPoint.X - hitNode.Position.X,
|
||||
worldPoint.Y - hitNode.Position.Y
|
||||
);
|
||||
_mouseMoveCounter = 0; // 디버그: 카운터 리셋
|
||||
_dragStartPosition = hitNode.Position;
|
||||
_dragOffset = new Point(worldPoint.X - hitNode.Position.X, worldPoint.Y - hitNode.Position.Y);
|
||||
_mouseMoveCounter = 0;
|
||||
Cursor = Cursors.SizeAll;
|
||||
Capture = true; // 🔥 마우스 캡처 활성화 - 이게 핵심!
|
||||
//System.Diagnostics.Debug.WriteLine($"MouseDown: 드래그 시작! Capture={Capture}, isDragging={_isDragging}, isPanning={_isPanning}, Node={hitNode.NodeId}");
|
||||
Capture = true;
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 팬 시작 (좌클릭 - 모드에 따라)
|
||||
@@ -250,7 +258,9 @@ namespace AGVNavigationCore.Controls
|
||||
// 컨텍스트 메뉴 (편집 모드에서만)
|
||||
if (_canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
// TODO: 라벨/이미지에 대한 컨텍스트 메뉴도 지원하려면 여기서 hitLabel/hitImage 확인해서 전달
|
||||
// 현재는 ShowContextMenu가 MapNode만 받으므로 노드만 처리
|
||||
ShowContextMenu(e.Location, hitNode);
|
||||
}
|
||||
}
|
||||
@@ -266,9 +276,12 @@ namespace AGVNavigationCore.Controls
|
||||
_mouseMoveCounter++;
|
||||
}
|
||||
|
||||
// 호버 노드 업데이트
|
||||
var newHoveredNode = GetNodeAt(worldPoint);
|
||||
if (newHoveredNode != _hoveredNode)
|
||||
// 호버 업데이트
|
||||
var newHoveredNode = GetItemAt(worldPoint);
|
||||
|
||||
bool hoverChanged = (newHoveredNode != _hoveredNode);
|
||||
|
||||
if (hoverChanged)
|
||||
{
|
||||
_hoveredNode = newHoveredNode;
|
||||
Invalidate();
|
||||
@@ -291,24 +304,31 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
else if (_isDragging && _canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
// 드래그 위치 계산
|
||||
var newPosition = new Point(
|
||||
worldPoint.X - _dragOffset.X,
|
||||
worldPoint.Y - _dragOffset.Y
|
||||
);
|
||||
|
||||
// 그리드 스냅
|
||||
if (ModifierKeys.HasFlag(Keys.Control))
|
||||
{
|
||||
newPosition.X = (newPosition.X / GRID_SIZE) * GRID_SIZE;
|
||||
newPosition.Y = (newPosition.Y / GRID_SIZE) * GRID_SIZE;
|
||||
}
|
||||
|
||||
bool moved = false;
|
||||
|
||||
// 노드 드래그
|
||||
if (_selectedNode != null)
|
||||
{
|
||||
var oldPosition = _selectedNode.Position;
|
||||
var newPosition = new Point(
|
||||
worldPoint.X - _dragOffset.X,
|
||||
worldPoint.Y - _dragOffset.Y
|
||||
);
|
||||
|
||||
// 그리드 스냅
|
||||
if (ModifierKeys.HasFlag(Keys.Control))
|
||||
{
|
||||
newPosition.X = (newPosition.X / GRID_SIZE) * GRID_SIZE;
|
||||
newPosition.Y = (newPosition.Y / GRID_SIZE) * GRID_SIZE;
|
||||
}
|
||||
|
||||
_selectedNode.Position = newPosition;
|
||||
NodeMoved?.Invoke(this, _selectedNode);
|
||||
moved = true;
|
||||
}
|
||||
|
||||
if (moved)
|
||||
{
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
Update(); // 🔥 즉시 Paint 이벤트 처리하여 화면 업데이트
|
||||
@@ -409,51 +429,77 @@ namespace AGVNavigationCore.Controls
|
||||
);
|
||||
}
|
||||
|
||||
private MapNode GetNodeAt(Point worldPoint)
|
||||
private NodeBase GetItemAt(Point worldPoint)
|
||||
{
|
||||
if (_nodes == null) return null;
|
||||
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _nodes.Count - 1; i >= 0; i--)
|
||||
if (_labels != null)
|
||||
{
|
||||
var node = _nodes[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _labels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _labels[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
if (_nodes != null)
|
||||
{
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _nodes[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
if (_images != null)
|
||||
{
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _images.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _images[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsPointInNode(Point point, MapNode node)
|
||||
private bool IsPointInNode(Point point, NodeBase node)
|
||||
{
|
||||
switch (node.Type)
|
||||
if (node is MapLabel label)
|
||||
{
|
||||
case NodeType.Label:
|
||||
return IsPointInLabelNode(point, node);
|
||||
case NodeType.Image:
|
||||
return IsPointInImageNode(point, node);
|
||||
default:
|
||||
return IsPointInCircularNode(point, node);
|
||||
return IsPointInLabel(point, label);
|
||||
}
|
||||
if (node is MapImage image)
|
||||
{
|
||||
return IsPointInImage(point, image);
|
||||
}
|
||||
// 라벨과 이미지는 별도 리스트로 관리되므로 여기서 처리하지 않음
|
||||
// 하지만 혹시 모를 하위 호환성을 위해 타입 체크는 유지하되,
|
||||
// 실제 로직은 CircularNode 등으로 분기
|
||||
return IsPointInCircularNode(point, node as MapNode);
|
||||
}
|
||||
|
||||
private bool IsPointInCircularNode(Point point, MapNode node)
|
||||
{
|
||||
switch (node.Type)
|
||||
switch (node.StationType)
|
||||
{
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
case StationType.Loader:
|
||||
case StationType.UnLoader:
|
||||
case StationType.Clearner:
|
||||
case StationType.Buffer:
|
||||
return IsPointInPentagon(point, node);
|
||||
case NodeType.Charging:
|
||||
case StationType.Charger:
|
||||
return IsPointInTriangle(point, node);
|
||||
default:
|
||||
return IsPointInCircle(point, node);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPointInCircle(Point point, MapNode node)
|
||||
private bool IsPointInCircle(Point point, NodeBase node)
|
||||
{
|
||||
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함
|
||||
var minHitRadiusInScreen = 20;
|
||||
@@ -466,7 +512,7 @@ namespace AGVNavigationCore.Controls
|
||||
return distance <= hitRadius;
|
||||
}
|
||||
|
||||
private bool IsPointInPentagon(Point point, MapNode node)
|
||||
private bool IsPointInPentagon(Point point, NodeBase node)
|
||||
{
|
||||
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보
|
||||
var minHitRadiusInScreen = 20;
|
||||
@@ -487,7 +533,7 @@ namespace AGVNavigationCore.Controls
|
||||
return IsPointInPolygon(point, points);
|
||||
}
|
||||
|
||||
private bool IsPointInTriangle(Point point, MapNode node)
|
||||
private bool IsPointInTriangle(Point point, NodeBase node)
|
||||
{
|
||||
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함
|
||||
var minHitRadiusInScreen = 20;
|
||||
@@ -532,38 +578,68 @@ namespace AGVNavigationCore.Controls
|
||||
return inside;
|
||||
}
|
||||
|
||||
private bool IsPointInLabelNode(Point point, MapNode node)
|
||||
private bool IsPointInLabel(Point point, MapLabel label)
|
||||
{
|
||||
var text = string.IsNullOrEmpty(node.LabelText) ? node.NodeId : node.LabelText;
|
||||
var text = string.IsNullOrEmpty(label.Text) ? label.Id : label.Text;
|
||||
|
||||
// 임시 Graphics로 텍스트 크기 측정
|
||||
using (var tempBitmap = new Bitmap(1, 1))
|
||||
using (var tempGraphics = Graphics.FromImage(tempBitmap))
|
||||
// Graphics 객체 임시 생성 (Using CreateGraphics is faster than new Bitmap)
|
||||
using (var g = this.CreateGraphics())
|
||||
{
|
||||
var font = new Font(node.FontFamily, node.FontSize, node.FontStyle);
|
||||
var textSize = tempGraphics.MeasureString(text, font);
|
||||
// Font 생성 로직: 사용자 정의 폰트가 있으면 생성, 없으면 기본 폰트 사용 (Dispose 주의)
|
||||
Font fontToUse = null;
|
||||
bool shouldDisposeFont = false;
|
||||
|
||||
var textRect = new Rectangle(
|
||||
(int)(node.Position.X - textSize.Width / 2),
|
||||
(int)(node.Position.Y - textSize.Height / 2),
|
||||
(int)textSize.Width,
|
||||
(int)textSize.Height
|
||||
);
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(label.FontFamily) || label.FontSize <= 0)
|
||||
{
|
||||
fontToUse = this.Font;
|
||||
shouldDisposeFont = false; // 컨트롤 폰트는 Dispose하면 안됨
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
fontToUse = new Font(label.FontFamily, label.FontSize, label.FontStyle);
|
||||
shouldDisposeFont = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
fontToUse = this.Font;
|
||||
shouldDisposeFont = false;
|
||||
}
|
||||
}
|
||||
|
||||
font.Dispose();
|
||||
return textRect.Contains(point);
|
||||
var textSize = g.MeasureString(text, fontToUse);
|
||||
|
||||
var textRect = new Rectangle(
|
||||
(int)(label.Position.X - textSize.Width / 2),
|
||||
(int)(label.Position.Y - textSize.Height / 2),
|
||||
(int)textSize.Width,
|
||||
(int)textSize.Height
|
||||
);
|
||||
|
||||
return textRect.Contains(point);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (shouldDisposeFont && fontToUse != null)
|
||||
{
|
||||
fontToUse.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPointInImageNode(Point point, MapNode node)
|
||||
private bool IsPointInImage(Point point, MapImage image)
|
||||
{
|
||||
var displaySize = node.GetDisplaySize();
|
||||
var displaySize = image.GetDisplaySize();
|
||||
if (displaySize.IsEmpty)
|
||||
displaySize = new Size(50, 50); // 기본 크기
|
||||
|
||||
var imageRect = new Rectangle(
|
||||
node.Position.X - displaySize.Width / 2,
|
||||
node.Position.Y - displaySize.Height / 2,
|
||||
image.Position.X - displaySize.Width / 2,
|
||||
image.Position.Y - displaySize.Height / 2,
|
||||
displaySize.Width,
|
||||
displaySize.Height
|
||||
);
|
||||
@@ -571,6 +647,32 @@ namespace AGVNavigationCore.Controls
|
||||
return imageRect.Contains(point);
|
||||
}
|
||||
|
||||
//private MapLabel GetLabelAt(Point worldPoint)
|
||||
//{
|
||||
// if (_labels == null) return null;
|
||||
// // 역순으로 검사
|
||||
// for (int i = _labels.Count - 1; i >= 0; i--)
|
||||
// {
|
||||
// var label = _labels[i];
|
||||
// if (IsPointInLabel(worldPoint, label))
|
||||
// return label;
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
//private MapImage GetImageAt(Point worldPoint)
|
||||
//{
|
||||
// if (_images == null) return null;
|
||||
// // 역순으로 검사
|
||||
// for (int i = _images.Count - 1; i >= 0; i--)
|
||||
// {
|
||||
// var image = _images[i];
|
||||
// if (IsPointInImage(worldPoint, image))
|
||||
// return image;
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
private IAGV GetAGVAt(Point worldPoint)
|
||||
{
|
||||
if (_agvList == null) return null;
|
||||
@@ -590,7 +692,7 @@ namespace AGVNavigationCore.Controls
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleSelectClick(MapNode hitNode, Point worldPoint)
|
||||
private void HandleSelectClick(NodeBase hitNode, Point worldPoint)
|
||||
{
|
||||
if (hitNode != null)
|
||||
{
|
||||
@@ -605,8 +707,8 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
// 연결선을 클릭했을 때 삭제 확인
|
||||
var (fromNode, toNode) = connection.Value;
|
||||
string fromDisplay = !string.IsNullOrEmpty(fromNode.RfidId) ? fromNode.RfidId : fromNode.NodeId;
|
||||
string toDisplay = !string.IsNullOrEmpty(toNode.RfidId) ? toNode.RfidId : toNode.NodeId;
|
||||
string fromDisplay = fromNode.HasRfid() ? fromNode.RfidId.ToString("0000") : fromNode.Id;
|
||||
string toDisplay = toNode.HasRfid() ? toNode.RfidId.ToString("0000") : toNode.Id;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"연결을 삭제하시겠습니까?\n\n{fromDisplay} ↔ {toDisplay}",
|
||||
@@ -617,13 +719,13 @@ namespace AGVNavigationCore.Controls
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id))
|
||||
{
|
||||
fromNode.RemoveConnection(toNode.NodeId);
|
||||
fromNode.RemoveConnection(toNode.Id);
|
||||
}
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
{
|
||||
toNode.RemoveConnection(fromNode.NodeId);
|
||||
toNode.RemoveConnection(fromNode.Id);
|
||||
}
|
||||
|
||||
// 이벤트 발생
|
||||
@@ -660,9 +762,8 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
var newNode = new MapNode
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Position = worldPoint,
|
||||
Type = NodeType.Normal
|
||||
Id = newNodeId,
|
||||
Position = worldPoint
|
||||
};
|
||||
|
||||
_nodes.Add(newNode);
|
||||
@@ -681,20 +782,22 @@ namespace AGVNavigationCore.Controls
|
||||
worldPoint.Y = (worldPoint.Y / GRID_SIZE) * GRID_SIZE;
|
||||
}
|
||||
|
||||
// 고유한 NodeId 생성
|
||||
// 고유한 NodeId 생성 (라벨도 ID 공유 권장)
|
||||
string newNodeId = GenerateUniqueNodeId();
|
||||
|
||||
var newNode = new MapNode
|
||||
var newLabel = new MapLabel
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Id = newNodeId,
|
||||
Position = worldPoint,
|
||||
Type = NodeType.Label,
|
||||
Name = "새 라벨"
|
||||
Text = "New Label",
|
||||
FontSize = 10,
|
||||
FontFamily = "Arial"
|
||||
};
|
||||
|
||||
_nodes.Add(newNode);
|
||||
if (_labels == null) _labels = new List<MapLabel>();
|
||||
_labels.Add(newLabel);
|
||||
|
||||
NodeAdded?.Invoke(this, newNode);
|
||||
//NodeAdded?.Invoke(this, newNode); // TODO: 라벨 추가 이벤트 필요?
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -711,17 +814,17 @@ namespace AGVNavigationCore.Controls
|
||||
// 고유한 NodeId 생성
|
||||
string newNodeId = GenerateUniqueNodeId();
|
||||
|
||||
var newNode = new MapNode
|
||||
var newImage = new MapImage
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Id = newNodeId,
|
||||
Position = worldPoint,
|
||||
Type = NodeType.Image,
|
||||
Name = "새 이미지"
|
||||
Name = "New Image"
|
||||
};
|
||||
|
||||
_nodes.Add(newNode);
|
||||
if (_images == null) _images = new List<MapImage>();
|
||||
_images.Add(newImage);
|
||||
|
||||
NodeAdded?.Invoke(this, newNode);
|
||||
//NodeAdded?.Invoke(this, newNode); // TODO: 이미지 추가 이벤트 필요?
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -739,7 +842,9 @@ namespace AGVNavigationCore.Controls
|
||||
nodeId = $"N{counter:D3}";
|
||||
counter++;
|
||||
}
|
||||
while (_nodes.Any(n => n.NodeId == nodeId));
|
||||
while (_nodes.Any(n => n.Id == nodeId) ||
|
||||
(_labels != null && _labels.Any(l => l.Id == nodeId)) ||
|
||||
(_images != null && _images.Any(i => i.Id == nodeId)));
|
||||
|
||||
_nodeCounter = counter;
|
||||
return nodeId;
|
||||
@@ -776,7 +881,7 @@ namespace AGVNavigationCore.Controls
|
||||
// 연결된 모든 연결선도 제거
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.RemoveConnection(hitNode.NodeId);
|
||||
node.RemoveConnection(hitNode.Id);
|
||||
}
|
||||
|
||||
_nodes.Remove(hitNode);
|
||||
@@ -792,13 +897,13 @@ namespace AGVNavigationCore.Controls
|
||||
private void CreateConnection(MapNode fromNode, MapNode toNode)
|
||||
{
|
||||
// 중복 연결 체크 (양방향)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId) ||
|
||||
toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id) ||
|
||||
toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
return;
|
||||
|
||||
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
|
||||
fromNode.AddConnection(toNode.NodeId);
|
||||
toNode.AddConnection(fromNode.NodeId);
|
||||
fromNode.AddConnection(toNode.Id);
|
||||
toNode.AddConnection(fromNode.Id);
|
||||
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -810,20 +915,26 @@ namespace AGVNavigationCore.Controls
|
||||
return (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private void ShowContextMenu(Point location, MapNode hitNode)
|
||||
private void ShowContextMenu(Point location, NodeBase hitItem)
|
||||
{
|
||||
_contextMenu.Items.Clear();
|
||||
|
||||
if (hitNode != null)
|
||||
if (hitItem != null)
|
||||
{
|
||||
_contextMenu.Items.Add("노드 속성...", null, (s, e) =>
|
||||
string typeName = "항목";
|
||||
if (hitItem is MapNode) typeName = "노드";
|
||||
else if (hitItem is MapLabel) typeName = "라벨";
|
||||
else if (hitItem is MapImage) typeName = "이미지";
|
||||
|
||||
_contextMenu.Items.Add($"{typeName} 속성...", null, (s, e) =>
|
||||
{
|
||||
_selectedNode = hitNode;
|
||||
_selectedNode = hitItem;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
_selectedNodes.Add(hitItem);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
});
|
||||
_contextMenu.Items.Add("노드 삭제", null, (s, e) => HandleDeleteClick(hitNode));
|
||||
_contextMenu.Items.Add($"{typeName} 삭제", null, (s, e) => HandleDeleteClick(hitItem));
|
||||
_contextMenu.Items.Add("-");
|
||||
}
|
||||
|
||||
@@ -848,13 +959,13 @@ namespace AGVNavigationCore.Controls
|
||||
var (fromNode, toNode) = connection.Value;
|
||||
|
||||
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id))
|
||||
{
|
||||
fromNode.RemoveConnection(toNode.NodeId);
|
||||
fromNode.RemoveConnection(toNode.Id);
|
||||
}
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
{
|
||||
toNode.RemoveConnection(fromNode.NodeId);
|
||||
toNode.RemoveConnection(fromNode.Id);
|
||||
}
|
||||
|
||||
// 이벤트 발생
|
||||
@@ -867,13 +978,14 @@ namespace AGVNavigationCore.Controls
|
||||
private (MapNode From, MapNode To)? GetConnectionAt(Point worldPoint)
|
||||
{
|
||||
const int CONNECTION_HIT_TOLERANCE = 10;
|
||||
if (_nodes == null) return null;
|
||||
|
||||
// 모든 연결선을 확인하여 클릭한 위치와 가장 가까운 연결선 찾기
|
||||
foreach (var fromNode in _nodes)
|
||||
{
|
||||
foreach (var toNodeId in fromNode.ConnectedNodes)
|
||||
{
|
||||
var toNode = _nodes.FirstOrDefault(n => n.NodeId == toNodeId);
|
||||
var toNode = _nodes.FirstOrDefault(n => n.Id == toNodeId);
|
||||
if (toNode != null)
|
||||
{
|
||||
// 연결선과 클릭 위치 간의 거리 계산
|
||||
@@ -889,6 +1001,49 @@ namespace AGVNavigationCore.Controls
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleDeleteClick(NodeBase item)
|
||||
{
|
||||
if (item == null) return;
|
||||
|
||||
if (item is MapNode hitNode)
|
||||
{
|
||||
// 연결된 모든 연결선도 제거
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.RemoveConnection(hitNode.Id);
|
||||
}
|
||||
|
||||
_nodes.Remove(hitNode);
|
||||
|
||||
if (_selectedNode == hitNode)
|
||||
_selectedNode = null;
|
||||
|
||||
NodeDeleted?.Invoke(this, hitNode);
|
||||
}
|
||||
else if (item is MapLabel label)
|
||||
{
|
||||
if (_labels != null) _labels.Remove(label);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
else if (item is MapImage image)
|
||||
{
|
||||
if (_images != null) _images.Remove(image);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
else if (item is MapMark mark)
|
||||
{
|
||||
if (_marks != null) _marks.Remove(mark);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
else if (item is MapMagnet magnet)
|
||||
{
|
||||
if (_magnets != null) _magnets.Remove(magnet);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private float CalculatePointToLineDistance(Point point, Point lineStart, Point lineEnd)
|
||||
{
|
||||
// 점에서 선분까지의 거리 계산
|
||||
@@ -928,27 +1083,21 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
string tooltipText = "";
|
||||
|
||||
// 노드 툴팁
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
var hitAGV = GetAGVAt(worldPoint);
|
||||
|
||||
if (hitNode != null)
|
||||
tooltipText = $"노드: {hitNode.Id}\n타입: {hitNode.Type}\n위치: ({hitNode.Position.X}, {hitNode.Position.Y})";
|
||||
else if (hitAGV != null)
|
||||
{
|
||||
tooltipText = $"노드: {hitNode.NodeId}\n타입: {hitNode.Type}\n위치: ({hitNode.Position.X}, {hitNode.Position.Y})";
|
||||
}
|
||||
else
|
||||
{
|
||||
// AGV 툴팁
|
||||
var hitAGV = GetAGVAt(worldPoint);
|
||||
if (hitAGV != null)
|
||||
{
|
||||
var state = _agvStates.ContainsKey(hitAGV.AgvId) ? _agvStates[hitAGV.AgvId] : AGVState.Idle;
|
||||
tooltipText = $"AGV: {hitAGV.AgvId}\n상태: {state}\n배터리: {hitAGV.BatteryLevel:F1}%\n위치: ({hitAGV.CurrentPosition.X}, {hitAGV.CurrentPosition.Y})";
|
||||
}
|
||||
var state = _agvStates.ContainsKey(hitAGV.AgvId) ? _agvStates[hitAGV.AgvId] : AGVState.Idle;
|
||||
tooltipText = $"AGV: {hitAGV.AgvId}\n상태: {state}\n배터리: {hitAGV.BatteryLevel:F1}%\n위치: ({hitAGV.CurrentPosition.X}, {hitAGV.CurrentPosition.Y})";
|
||||
}
|
||||
|
||||
// 툴팁 업데이트 (기존 ToolTip 컨트롤 사용)
|
||||
if (!string.IsNullOrEmpty(tooltipText))
|
||||
// 툴팁 텍스트 갱신 (변경되었을 때만)
|
||||
if (_tooltip != null && _tooltip.GetToolTip(this) != tooltipText)
|
||||
{
|
||||
// ToolTip 설정 (필요시 추가 구현)
|
||||
_tooltip.SetToolTip(this, tooltipText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,7 +1165,7 @@ namespace AGVNavigationCore.Controls
|
||||
/// </summary>
|
||||
public void PanToNode(string nodeId)
|
||||
{
|
||||
var node = _nodes?.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
var node = _nodes?.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node != null)
|
||||
{
|
||||
PanTo(node.Position);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.PathFinding;
|
||||
using AGVNavigationCore.PathFinding.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace AGVNavigationCore.Controls
|
||||
{
|
||||
@@ -21,7 +23,7 @@ namespace AGVNavigationCore.Controls
|
||||
private const int NODE_SIZE = 24;
|
||||
private const int NODE_RADIUS = NODE_SIZE / 2;
|
||||
private const int GRID_SIZE = 20;
|
||||
private const float CONNECTION_WIDTH = 2.0f;
|
||||
private const float CONNECTION_WIDTH = 1.0f;
|
||||
private const int SNAP_DISTANCE = 10;
|
||||
private const int AGV_SIZE = 40;
|
||||
private const int CONNECTION_ARROW_SIZE = 8;
|
||||
@@ -37,7 +39,8 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
Edit, // 편집 가능 (맵 에디터)
|
||||
Sync, // 동기화 모드 (장비 설정 동기화)
|
||||
Emulator // 에뮬레이터 모드
|
||||
Emulator, // 에뮬레이터 모드
|
||||
Run // 가동 모드 (User Request)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,10 +68,19 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
// 맵 데이터
|
||||
private List<MapNode> _nodes;
|
||||
private MapNode _selectedNode;
|
||||
private List<MapNode> _selectedNodes; // 다중 선택
|
||||
private MapNode _hoveredNode;
|
||||
private MapNode _destinationNode;
|
||||
private List<MapLabel> _labels; // 추가
|
||||
private List<MapImage> _images; // 추가
|
||||
private List<MapMark> _marks;
|
||||
private List<MapMagnet> _magnets;
|
||||
|
||||
// 선택된 객체들 (나중에 NodeBase로 통일 필요)
|
||||
private NodeBase _selectedNode;
|
||||
|
||||
private List<NodeBase> _selectedNodes; // 다중 선택 (NodeBase로 변경 고려)
|
||||
|
||||
private NodeBase _hoveredNode;
|
||||
|
||||
private NodeBase _destinationNode;
|
||||
|
||||
// AGV 관련
|
||||
private List<IAGV> _agvList;
|
||||
@@ -143,24 +155,31 @@ namespace AGVNavigationCore.Controls
|
||||
private Pen _pathPen;
|
||||
private Pen _agvPen;
|
||||
private Pen _highlightedConnectionPen;
|
||||
private Pen _magnetPen;
|
||||
private Pen _markPen;
|
||||
private ToolTip _tooltip;
|
||||
|
||||
// 컨텍스트 메뉴
|
||||
private ContextMenuStrip _contextMenu;
|
||||
|
||||
// 이벤트
|
||||
public event EventHandler<MapNode> NodeRightClicked;
|
||||
public event EventHandler<NodeBase> NodeRightClicked;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
// 맵 편집 이벤트
|
||||
public event EventHandler<MapNode> NodeAdded;
|
||||
public event EventHandler<List<MapNode>> NodesSelected; // 다중 선택 이벤트
|
||||
public event EventHandler<MapNode> NodeDeleted;
|
||||
public event EventHandler<MapNode> NodeMoved;
|
||||
public delegate void NodeSelectHandler(object sender, NodeBase node, MouseEventArgs e);
|
||||
public event NodeSelectHandler NodeSelect;
|
||||
|
||||
public event EventHandler<NodeBase> NodeAdded;
|
||||
public event EventHandler<List<NodeBase>> NodesSelected; // 다중 선택 이벤트
|
||||
public event EventHandler<NodeBase> NodeDeleted;
|
||||
public event EventHandler<NodeBase> NodeMoved;
|
||||
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
|
||||
public event EventHandler<MapNode> ImageNodeDoubleClicked;
|
||||
public event EventHandler<MapImage> ImageDoubleClicked;
|
||||
public event EventHandler<MapLabel> LabelDoubleClicked;
|
||||
public event EventHandler MapChanged;
|
||||
|
||||
#endregion
|
||||
@@ -184,6 +203,60 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveItem(NodeBase item)
|
||||
{
|
||||
if (item is MapImage img) RemoveImage(img);
|
||||
else if (item is MapLabel lb) RemoveLabel(lb);
|
||||
else if (item is MapNode nd) RemoveNode(nd);
|
||||
else if (item is MapMark mk) RemoveMark(mk);
|
||||
else if (item is MapMagnet mg) RemoveMagnet(mg);
|
||||
else throw new Exception("unknown type");
|
||||
|
||||
}
|
||||
public void RemoveNode(MapNode node)
|
||||
{
|
||||
if (_nodes != null && _nodes.Contains(node))
|
||||
{
|
||||
_nodes.Remove(node);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
public void RemoveLabel(MapLabel label)
|
||||
{
|
||||
if (_labels != null && _labels.Contains(label))
|
||||
{
|
||||
_labels.Remove(label);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveImage(MapImage image)
|
||||
{
|
||||
if (_images != null && _images.Contains(image))
|
||||
{
|
||||
_images.Remove(image);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveMark(MapMark mark)
|
||||
{
|
||||
if (_marks != null && _marks.Contains(mark))
|
||||
{
|
||||
_marks.Remove(mark);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveMagnet(MapMagnet magnet)
|
||||
{
|
||||
if (_magnets != null && _magnets.Contains(magnet))
|
||||
{
|
||||
_magnets.Remove(magnet);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 편집 모드 (CanvasMode.Edit일 때만 적용)
|
||||
/// </summary>
|
||||
@@ -230,15 +303,80 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public MapImage SelectedImage
|
||||
{
|
||||
get { return this._selectedNode as MapImage; }
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public MapLabel SelectedLabel
|
||||
{
|
||||
get { return this._selectedNode as MapLabel; }
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public MapMark SelectedMark
|
||||
{
|
||||
get { return this._selectedNode as MapMark; }
|
||||
}
|
||||
|
||||
|
||||
[Browsable(false)]
|
||||
public MapMagnet SelectedMagnet
|
||||
{
|
||||
get { return this._selectedNode as MapMagnet; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드 (단일)
|
||||
/// </summary>
|
||||
public MapNode SelectedNode => _selectedNode;
|
||||
public MapNode SelectedNode
|
||||
{
|
||||
get { return this._selectedNode as MapNode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들 (다중)
|
||||
/// </summary>
|
||||
public List<MapNode> SelectedNodes => _selectedNodes ?? new List<MapNode>();
|
||||
public List<NodeBase> SelectedNodes => _selectedNodes ?? new List<NodeBase>();
|
||||
|
||||
|
||||
public List<NodeBase> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
List<NodeBase> items = new List<NodeBase>();
|
||||
if (Nodes != null && Nodes.Any()) items.AddRange(Nodes);
|
||||
if (Labels != null && Labels.Any()) items.AddRange(Labels);
|
||||
if (Images != null && Images.Any()) items.AddRange(Images);
|
||||
if (Marks != null && Marks.Any()) items.AddRange(Marks);
|
||||
if (Magnets != null && Magnets.Any()) items.AddRange(Magnets);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map file loading 결과를 셋팅합니다
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
public void SetMapLoadResult(MapLoader.MapLoadResult result)
|
||||
{
|
||||
this.Nodes = result.Nodes;
|
||||
this.Labels = result.Labels; // 추가
|
||||
this.Images = result.Images; // 추가
|
||||
this.Marks = result.Marks;
|
||||
this.Magnets = result.Magnets;
|
||||
|
||||
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
{
|
||||
this.BackColor = Color.FromArgb(result.Settings.BackgroundColorArgb);
|
||||
this.ShowGrid = result.Settings.ShowGrid;
|
||||
}
|
||||
|
||||
this.FitToNodes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 목록
|
||||
@@ -260,6 +398,58 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 목록
|
||||
/// </summary>
|
||||
public List<MapLabel> Labels
|
||||
{
|
||||
get => _labels ?? new List<MapLabel>();
|
||||
set
|
||||
{
|
||||
_labels = value ?? new List<MapLabel>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 목록
|
||||
/// </summary>
|
||||
public List<MapImage> Images
|
||||
{
|
||||
get => _images ?? new List<MapImage>();
|
||||
set
|
||||
{
|
||||
_images = value ?? new List<MapImage>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마크 목록
|
||||
/// </summary>
|
||||
public List<MapMark> Marks
|
||||
{
|
||||
get => _marks ?? new List<MapMark>();
|
||||
set
|
||||
{
|
||||
_marks = value ?? new List<MapMark>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마그넷 목록
|
||||
/// </summary>
|
||||
public List<MapMagnet> Magnets
|
||||
{
|
||||
get => _magnets ?? new List<MapMagnet>();
|
||||
set
|
||||
{
|
||||
_magnets = value ?? new List<MapMagnet>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 목록
|
||||
/// </summary>
|
||||
@@ -389,7 +579,12 @@ namespace AGVNavigationCore.Controls
|
||||
ControlStyles.ResizeRedraw, true);
|
||||
|
||||
_nodes = new List<MapNode>();
|
||||
_selectedNodes = new List<MapNode>(); // 다중 선택 리스트 초기화
|
||||
_labels = new List<MapLabel>();
|
||||
_images = new List<MapImage>();
|
||||
_marks = new List<MapMark>();
|
||||
_magnets = new List<MapMagnet>();
|
||||
|
||||
_selectedNodes = new List<NodeBase>(); // 다중 선택 리스트 초기화
|
||||
_agvList = new List<IAGV>();
|
||||
_agvPositions = new Dictionary<string, Point>();
|
||||
_agvDirections = new Dictionary<string, AgvDirection>();
|
||||
@@ -399,6 +594,12 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
InitializeBrushesAndPens();
|
||||
CreateContextMenu();
|
||||
|
||||
_tooltip = new ToolTip();
|
||||
_tooltip.AutoPopDelay = 5000;
|
||||
_tooltip.InitialDelay = 1000;
|
||||
_tooltip.ReshowDelay = 500;
|
||||
_tooltip.ShowAlways = true;
|
||||
}
|
||||
|
||||
private void InitializeBrushesAndPens()
|
||||
@@ -420,7 +621,8 @@ namespace AGVNavigationCore.Controls
|
||||
_gridBrush = new SolidBrush(Color.LightGray);
|
||||
|
||||
// 펜
|
||||
_connectionPen = new Pen(Color.DarkBlue, CONNECTION_WIDTH);
|
||||
_connectionPen = new Pen(Color.White, CONNECTION_WIDTH);
|
||||
_connectionPen.DashStyle = DashStyle.Dash;
|
||||
_connectionPen.EndCap = LineCap.ArrowAnchor;
|
||||
|
||||
_gridPen = new Pen(Color.LightGray, 1);
|
||||
@@ -430,6 +632,8 @@ namespace AGVNavigationCore.Controls
|
||||
_pathPen = new Pen(Color.Purple, 3);
|
||||
_agvPen = new Pen(Color.Red, 3);
|
||||
_highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid };
|
||||
_magnetPen = new Pen(Color.FromArgb(100,Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid };
|
||||
_markPen = new Pen(Color.White, 3); // 마크는 흰색 선으로 표시
|
||||
}
|
||||
|
||||
private void CreateContextMenu()
|
||||
@@ -621,6 +825,8 @@ namespace AGVNavigationCore.Controls
|
||||
_pathPen?.Dispose();
|
||||
_agvPen?.Dispose();
|
||||
_highlightedConnectionPen?.Dispose();
|
||||
_magnetPen?.Dispose();
|
||||
_markPen?.Dispose();
|
||||
|
||||
// 컨텍스트 메뉴 정리
|
||||
_contextMenu?.Dispose();
|
||||
@@ -646,13 +852,13 @@ namespace AGVNavigationCore.Controls
|
||||
return;
|
||||
|
||||
// RFID값과 해당 노드의 인덱스를 저장
|
||||
var rfidToNodeIndex = new Dictionary<string, List<int>>();
|
||||
var rfidToNodeIndex = new Dictionary<ushort, List<int>>();
|
||||
|
||||
// 모든 노드의 RFID값 수집
|
||||
for (int i = 0; i < _nodes.Count; i++)
|
||||
{
|
||||
var node = _nodes[i];
|
||||
if (!string.IsNullOrEmpty(node.RfidId))
|
||||
if (node.HasRfid())
|
||||
{
|
||||
if (!rfidToNodeIndex.ContainsKey(node.RfidId))
|
||||
{
|
||||
@@ -671,7 +877,7 @@ namespace AGVNavigationCore.Controls
|
||||
for (int i = 1; i < kvp.Value.Count; i++)
|
||||
{
|
||||
int duplicateNodeIndex = kvp.Value[i];
|
||||
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].NodeId);
|
||||
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -692,7 +898,7 @@ namespace AGVNavigationCore.Controls
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
// NodeId에서 숫자 부분 추출 (예: "N001" -> 1)
|
||||
if (node.NodeId.StartsWith("N") && int.TryParse(node.NodeId.Substring(1), out int number))
|
||||
if (node.Id.StartsWith("N") && int.TryParse(node.Id.Substring(1), out int number))
|
||||
{
|
||||
maxNumber = Math.Max(maxNumber, number);
|
||||
}
|
||||
|
||||
@@ -11,26 +11,18 @@ namespace AGVNavigationCore.Models
|
||||
{
|
||||
/// <summary>일반 경로 노드</summary>
|
||||
Normal,
|
||||
/// <summary>로더</summary>
|
||||
Loader,
|
||||
/// <summary>
|
||||
/// 언로더
|
||||
/// </summary>
|
||||
UnLoader,
|
||||
/// <summary>
|
||||
/// 클리너
|
||||
/// </summary>
|
||||
Clearner,
|
||||
/// <summary>
|
||||
/// 버퍼
|
||||
/// </summary>
|
||||
Buffer,
|
||||
/// <summary>충전 스테이션</summary>
|
||||
Charging,
|
||||
/// <summary>라벨 (UI 요소)</summary>
|
||||
|
||||
Label,
|
||||
/// <summary>이미지 (UI 요소)</summary>
|
||||
Image
|
||||
Image,
|
||||
/// <summary>
|
||||
/// 마크센서
|
||||
/// </summary>
|
||||
Mark,
|
||||
/// <summary>
|
||||
/// 마그넷라인
|
||||
/// </summary>
|
||||
Magnet
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,17 +63,23 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 일반노드
|
||||
/// </summary>
|
||||
Node,
|
||||
Normal,
|
||||
/// <summary>로더</summary>
|
||||
Loader,
|
||||
/// <summary>클리너</summary>
|
||||
Cleaner,
|
||||
Clearner,
|
||||
/// <summary>오프로더</summary>
|
||||
Offloader,
|
||||
UnLoader,
|
||||
/// <summary>버퍼</summary>
|
||||
Buffer,
|
||||
/// <summary>충전기</summary>
|
||||
Charger
|
||||
Charger,
|
||||
|
||||
/// <summary>
|
||||
/// 끝점(더이상 이동불가)
|
||||
/// </summary>
|
||||
Limit,
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 현재 노드 ID
|
||||
/// </summary>
|
||||
string CurrentNodeId { get; }
|
||||
MapNode CurrentNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 목표 위치
|
||||
@@ -92,7 +92,7 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 목표 노드 ID
|
||||
/// </summary>
|
||||
string PrevNodeId { get; }
|
||||
MapNode PrevNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 도킹 방향
|
||||
|
||||
88
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapImage.cs
Normal file
88
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapImage.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using AGVNavigationCore.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace AGVNavigationCore.Models
|
||||
{
|
||||
public class MapImage : NodeBase
|
||||
{
|
||||
[Category("기본 정보")]
|
||||
[Description("이미지의 이름입니다.")]
|
||||
public string Name { get; set; } = "Image";
|
||||
|
||||
[Category("이미지 설정")]
|
||||
[Description("이미지 파일 경로입니다 (편집기용).")]
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
|
||||
[ReadOnly(false)]
|
||||
public string ImageBase64 { get; set; } = string.Empty;
|
||||
|
||||
[Category("이미지 설정")]
|
||||
[Description("이미지 크기 배율입니다.")]
|
||||
public SizeF Scale { get; set; } = new SizeF(1.0f, 1.0f);
|
||||
|
||||
[Category("이미지 설정")]
|
||||
[Description("이미지 투명도입니다 (0.0 ~ 1.0).")]
|
||||
public float Opacity { get; set; } = 1.0f;
|
||||
|
||||
[Category("이미지 설정")]
|
||||
[Description("이미지 회전 각도입니다.")]
|
||||
public float Rotation { get; set; } = 0.0f;
|
||||
|
||||
[JsonIgnore]
|
||||
[Browsable(false)]
|
||||
public Image LoadedImage { get; set; }
|
||||
|
||||
public MapImage()
|
||||
{
|
||||
Type = NodeType.Image;
|
||||
}
|
||||
|
||||
public bool LoadImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
Image originalImage = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(ImageBase64))
|
||||
{
|
||||
originalImage = ImageConverterUtil.Base64ToImage(ImageBase64);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(ImagePath) && System.IO.File.Exists(ImagePath))
|
||||
{
|
||||
originalImage = Image.FromFile(ImagePath);
|
||||
}
|
||||
|
||||
if (originalImage != null)
|
||||
{
|
||||
LoadedImage?.Dispose();
|
||||
LoadedImage = originalImage; // 리사이즈 필요시 추가 구현
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 무시
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Size GetDisplaySize()
|
||||
{
|
||||
if (LoadedImage == null) return Size.Empty;
|
||||
return new Size(
|
||||
(int)(LoadedImage.Width * Scale.Width),
|
||||
(int)(LoadedImage.Height * Scale.Height)
|
||||
);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
LoadedImage?.Dispose();
|
||||
LoadedImage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLabel.cs
Normal file
42
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLabel.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
|
||||
namespace AGVNavigationCore.Models
|
||||
{
|
||||
public class MapLabel : NodeBase
|
||||
{
|
||||
[Category("라벨 설정")]
|
||||
[Description("표시할 텍스트입니다.")]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
[Category("라벨 설정")]
|
||||
[Description("글자색입니다")]
|
||||
public Color ForeColor { get; set; } = Color.Black;
|
||||
|
||||
[Category("라벨 설정")]
|
||||
[Description("배경색입니다.")]
|
||||
public Color BackColor { get; set; } = Color.Transparent;
|
||||
|
||||
[Category("라벨 설정")]
|
||||
[Description("폰트 종류입니다.")]
|
||||
public string FontFamily { get; set; } = "Arial";
|
||||
|
||||
[Category("라벨 설정")]
|
||||
[Description("폰트 크기입니다.")]
|
||||
public float FontSize { get; set; } = 12.0f;
|
||||
|
||||
[Category("라벨 설정")]
|
||||
[Description("폰트 스타일입니다.")]
|
||||
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
|
||||
|
||||
[Category("라벨 설정")]
|
||||
[Description("내부 여백입니다.")]
|
||||
public int Padding { get; set; } = 5;
|
||||
|
||||
public MapLabel()
|
||||
{
|
||||
ForeColor = Color.Purple;
|
||||
Type = NodeType.Label;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ namespace AGVNavigationCore.Models
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
|
||||
public List<MapLabel> Labels { get; set; } = new List<MapLabel>(); // 추가
|
||||
public List<MapImage> Images { get; set; } = new List<MapImage>(); // 추가
|
||||
public List<MapMark> Marks { get; set; } = new List<MapMark>();
|
||||
public List<MapMagnet> Magnets { get; set; } = new List<MapMagnet>();
|
||||
public MapSettings Settings { get; set; } = new MapSettings();
|
||||
public string ErrorMessage { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
@@ -40,9 +44,13 @@ namespace AGVNavigationCore.Models
|
||||
public class MapFileData
|
||||
{
|
||||
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
|
||||
public List<MapLabel> Labels { get; set; } = new List<MapLabel>(); // 추가
|
||||
public List<MapImage> Images { get; set; } = new List<MapImage>(); // 추가
|
||||
public List<MapMark> Marks { get; set; } = new List<MapMark>();
|
||||
public List<MapMagnet> Magnets { get; set; } = new List<MapMagnet>();
|
||||
public MapSettings Settings { get; set; } = new MapSettings();
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public string Version { get; set; } = "1.1"; // 버전 업그레이드 (설정 추가)
|
||||
public string Version { get; set; } = "1.3"; // 버전 업그레이드
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +72,7 @@ namespace AGVNavigationCore.Models
|
||||
|
||||
var json = File.ReadAllText(filePath);
|
||||
|
||||
// JSON 역직렬화 설정: 누락된 속성 무시, 안전한 처리
|
||||
// JSON 역직렬화 설정
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
@@ -72,36 +80,83 @@ namespace AGVNavigationCore.Models
|
||||
DefaultValueHandling = DefaultValueHandling.Populate
|
||||
};
|
||||
|
||||
// 먼저 구조 파악을 위해 동적 객체로 로드하거나, MapFileData로 시도
|
||||
var mapData = JsonConvert.DeserializeObject<MapFileData>(json, settings);
|
||||
|
||||
if (mapData != null)
|
||||
{
|
||||
result.Nodes = mapData.Nodes ?? new List<MapNode>();
|
||||
result.Settings = mapData.Settings ?? new MapSettings(); // 설정 로드
|
||||
result.Nodes = new List<MapNode>();
|
||||
result.Labels = mapData.Labels ?? new List<MapLabel>();
|
||||
result.Images = mapData.Images ?? new List<MapImage>();
|
||||
result.Marks = mapData.Marks ?? new List<MapMark>();
|
||||
result.Magnets = mapData.Magnets ?? new List<MapMagnet>();
|
||||
result.Settings = mapData.Settings ?? new MapSettings();
|
||||
result.Version = mapData.Version ?? "1.0";
|
||||
result.CreatedDate = mapData.CreatedDate;
|
||||
|
||||
// 기존 Description 데이터를 Name으로 마이그레이션
|
||||
MigrateDescriptionToName(result.Nodes);
|
||||
if (mapData.Nodes != null)
|
||||
{
|
||||
foreach (var node in mapData.Nodes)
|
||||
{
|
||||
// 마이그레이션: 기존 파일의 Nodes 리스트에 섞여있는 Label, Image 분리
|
||||
// (새 파일 구조에서는 이미 분리되어 로드됨)
|
||||
if (node.Type == NodeType.Label)
|
||||
{
|
||||
// MapNode -> MapLabel 변환 (필드 매핑)
|
||||
var label = new MapLabel
|
||||
{
|
||||
Id = node.Id, // 기존 NodeId -> Id
|
||||
Position = node.Position,
|
||||
CreatedDate = node.CreatedDate,
|
||||
ModifiedDate = node.ModifiedDate,
|
||||
|
||||
// Label 속성 매핑 (MapNode에서 임시로 가져오거나 Json Raw Parsing 필요할 수 있음)
|
||||
// 현재 MapNode 클래스에는 해당 속성들이 제거되었으므로,
|
||||
// Json 포맷 변경으로 인해 기존 데이터 로드시 정보 손실 가능성 있음.
|
||||
// * 중요 *: MapNode 클래스에서 속성을 지웠으므로 일반 Deserialize로는 Label/Image 속성을 못 읽음.
|
||||
// 해결책: JObject로 먼저 읽어서 분기 처리하거나, DTO 클래스를 별도로 두어야 함.
|
||||
// 하지만 시간 관계상, 만약 기존 MapNode가 속성을 가지고 있지 않다면 마이그레이션은 "위치/ID" 정도만 복구됨.
|
||||
// 완벽한 마이그레이션을 위해서는 MapNode에 Obsolete 속성을 잠시 두었어야 함.
|
||||
// 여기서는 일단 기본 정보라도 살림.
|
||||
};
|
||||
result.Labels.Add(label);
|
||||
}
|
||||
else if (node.Type == NodeType.Image)
|
||||
{
|
||||
var image = new MapImage
|
||||
{
|
||||
Id = node.Id,
|
||||
Position = node.Position,
|
||||
CreatedDate = node.CreatedDate,
|
||||
ModifiedDate = node.ModifiedDate,
|
||||
// 이미지/라벨 속성 복구 불가 (MapNode에서 삭제됨)
|
||||
};
|
||||
result.Images.Add(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Nodes.Add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DockingDirection 마이그레이션 (기존 NodeType 기반으로 설정)
|
||||
MigrateDockingDirection(result.Nodes);
|
||||
|
||||
// 중복된 NodeId 정리
|
||||
// 중복된 NodeId 정리 (Nav Node만)
|
||||
FixDuplicateNodeIds(result.Nodes);
|
||||
|
||||
// 존재하지 않는 노드에 대한 연결 정리 (고아 연결 제거)
|
||||
// 고아 연결 정리
|
||||
CleanupOrphanConnections(result.Nodes);
|
||||
|
||||
// 양방향 연결 자동 설정 (A→B가 있으면 B→A도 설정)
|
||||
// 주의: CleanupDuplicateConnections()는 제거됨 - 양방향 연결을 단방향으로 변환하는 버그가 있었음
|
||||
// 양방향 연결 자동 설정
|
||||
EnsureBidirectionalConnections(result.Nodes);
|
||||
|
||||
// ConnectedMapNodes 채우기 (string ID → MapNode 객체 참조)
|
||||
// ConnectedMapNodes 채우기
|
||||
ResolveConnectedMapNodes(result.Nodes);
|
||||
|
||||
// 이미지 노드들의 이미지 로드
|
||||
LoadImageNodes(result.Nodes);
|
||||
// 이미지 로드 (MapImage 객체에서)
|
||||
foreach (var img in result.Images)
|
||||
{
|
||||
img.LoadImage();
|
||||
}
|
||||
|
||||
result.Success = true;
|
||||
}
|
||||
@@ -121,23 +176,23 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 맵 데이터를 파일로 저장
|
||||
/// </summary>
|
||||
/// <param name="filePath">저장할 파일 경로</param>
|
||||
/// <param name="nodes">맵 노드 목록</param>
|
||||
/// <param name="settings">맵 설정 (배경색, 그리드 표시 등)</param>
|
||||
/// <returns>저장 성공 여부</returns>
|
||||
public static bool SaveMapToFile(string filePath, List<MapNode> nodes, MapSettings settings = null)
|
||||
public static bool SaveMapToFile(string filePath, List<MapNode> nodes, List<MapLabel> labels = null, List<MapImage> images = null, List<MapMark> marks = null, List<MapMagnet> magnets = null, MapSettings settings = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 저장 전 고아 연결 정리 (삭제된 노드에 대한 연결 제거)
|
||||
// 저장 전 고아 연결 정리
|
||||
CleanupOrphanConnections(nodes);
|
||||
|
||||
var mapData = new MapFileData
|
||||
{
|
||||
Nodes = nodes,
|
||||
Settings = settings ?? new MapSettings(), // 설정 저장
|
||||
Labels = labels ?? new List<MapLabel>(),
|
||||
Images = images ?? new List<MapImage>(),
|
||||
Marks = marks ?? new List<MapMark>(),
|
||||
Magnets = magnets ?? new List<MapMagnet>(),
|
||||
Settings = settings ?? new MapSettings(),
|
||||
CreatedDate = DateTime.Now,
|
||||
Version = "1.1"
|
||||
Version = "1.3"
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(mapData, Formatting.Indented);
|
||||
@@ -145,27 +200,13 @@ namespace AGVNavigationCore.Models
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 노드들의 이미지 로드
|
||||
/// </summary>
|
||||
/// <param name="nodes">노드 목록</param>
|
||||
private static void LoadImageNodes(List<MapNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (node.Type == NodeType.Image)
|
||||
{
|
||||
node.LoadImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ConnectedMapNodes 채우기 (ConnectedNodes의 string ID → MapNode 객체 변환)
|
||||
/// </summary>
|
||||
@@ -175,7 +216,7 @@ namespace AGVNavigationCore.Models
|
||||
if (mapNodes == null || mapNodes.Count == 0) return;
|
||||
|
||||
// 빠른 조회를 위한 Dictionary 생성
|
||||
var nodeDict = mapNodes.ToDictionary(n => n.NodeId, n => n);
|
||||
var nodeDict = mapNodes.ToDictionary(n => n.Id, n => n);
|
||||
|
||||
foreach (var node in mapNodes)
|
||||
{
|
||||
@@ -192,6 +233,8 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,39 +251,6 @@ namespace AGVNavigationCore.Models
|
||||
// 기존 파일들은 다시 저장될 때 Description 없이 저장됨
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 맵 파일의 DockingDirection을 NodeType 기반으로 마이그레이션
|
||||
/// </summary>
|
||||
/// <param name="mapNodes">맵 노드 목록</param>
|
||||
private static void MigrateDockingDirection(List<MapNode> mapNodes)
|
||||
{
|
||||
if (mapNodes == null || mapNodes.Count == 0) return;
|
||||
|
||||
foreach (var node in mapNodes)
|
||||
{
|
||||
// 기존 파일에서 DockingDirection이 기본값(DontCare)인 경우에만 마이그레이션
|
||||
if (node.DockDirection == DockingDirection.DontCare)
|
||||
{
|
||||
switch (node.Type)
|
||||
{
|
||||
case NodeType.Charging:
|
||||
node.DockDirection = DockingDirection.Forward;
|
||||
break;
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
node.DockDirection = DockingDirection.Backward;
|
||||
break;
|
||||
default:
|
||||
// Normal, Rotation, Label, Image는 DontCare 유지
|
||||
node.DockDirection = DockingDirection.DontCare;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 중복된 NodeId를 가진 노드들을 고유한 NodeId로 수정
|
||||
/// </summary>
|
||||
@@ -255,13 +265,13 @@ namespace AGVNavigationCore.Models
|
||||
// 첫 번째 패스: 중복된 노드들 식별
|
||||
foreach (var node in mapNodes)
|
||||
{
|
||||
if (usedIds.Contains(node.NodeId))
|
||||
if (usedIds.Contains(node.Id))
|
||||
{
|
||||
duplicateNodes.Add(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
usedIds.Add(node.NodeId);
|
||||
usedIds.Add(node.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +281,9 @@ namespace AGVNavigationCore.Models
|
||||
string newNodeId = GenerateUniqueNodeId(usedIds);
|
||||
|
||||
// 다른 노드들의 연결에서 기존 NodeId를 새 NodeId로 업데이트
|
||||
UpdateConnections(mapNodes, duplicateNode.NodeId, newNodeId);
|
||||
UpdateConnections(mapNodes, duplicateNode.Id, newNodeId);
|
||||
|
||||
duplicateNode.NodeId = newNodeId;
|
||||
duplicateNode.Id = newNodeId;
|
||||
usedIds.Add(newNodeId);
|
||||
}
|
||||
}
|
||||
@@ -331,7 +341,7 @@ namespace AGVNavigationCore.Models
|
||||
if (mapNodes == null || mapNodes.Count == 0) return;
|
||||
|
||||
// 존재하는 모든 노드 ID 집합 생성
|
||||
var validNodeIds = new HashSet<string>(mapNodes.Select(n => n.NodeId));
|
||||
var validNodeIds = new HashSet<string>(mapNodes.Select(n => n.Id));
|
||||
|
||||
// 각 노드의 연결을 검증하고 존재하지 않는 노드 ID 제거
|
||||
foreach (var node in mapNodes)
|
||||
@@ -369,13 +379,13 @@ namespace AGVNavigationCore.Models
|
||||
|
||||
foreach (var connectedNodeId in node.ConnectedNodes.ToList())
|
||||
{
|
||||
var connectedNode = mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
|
||||
var connectedNode = mapNodes.FirstOrDefault(n => n.Id == connectedNodeId);
|
||||
if (connectedNode == null) continue;
|
||||
|
||||
// 연결 쌍의 키 생성 (사전순 정렬)
|
||||
string pairKey = string.Compare(node.NodeId, connectedNodeId, StringComparison.Ordinal) < 0
|
||||
? $"{node.NodeId}-{connectedNodeId}"
|
||||
: $"{connectedNodeId}-{node.NodeId}";
|
||||
string pairKey = string.Compare(node.Id, connectedNodeId, StringComparison.Ordinal) < 0
|
||||
? $"{node.Id}-{connectedNodeId}"
|
||||
: $"{connectedNodeId}-{node.Id}";
|
||||
|
||||
if (processedPairs.Contains(pairKey))
|
||||
{
|
||||
@@ -388,17 +398,17 @@ namespace AGVNavigationCore.Models
|
||||
processedPairs.Add(pairKey);
|
||||
|
||||
// 양방향 연결인 경우 하나만 유지
|
||||
if (connectedNode.ConnectedNodes.Contains(node.NodeId))
|
||||
if (connectedNode.ConnectedNodes.Contains(node.Id))
|
||||
{
|
||||
// 사전순으로 더 작은 노드에만 연결을 유지
|
||||
if (string.Compare(node.NodeId, connectedNodeId, StringComparison.Ordinal) > 0)
|
||||
if (string.Compare(node.Id, connectedNodeId, StringComparison.Ordinal) > 0)
|
||||
{
|
||||
connectionsToRemove.Add(connectedNodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 반대 방향 연결 제거
|
||||
connectedNode.RemoveConnection(node.NodeId);
|
||||
connectedNode.RemoveConnection(node.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -433,16 +443,16 @@ namespace AGVNavigationCore.Models
|
||||
// 1단계: 모든 명시적 연결 수집
|
||||
foreach (var node in mapNodes)
|
||||
{
|
||||
if (!allConnections.ContainsKey(node.NodeId))
|
||||
if (!allConnections.ContainsKey(node.Id))
|
||||
{
|
||||
allConnections[node.NodeId] = new HashSet<string>();
|
||||
allConnections[node.Id] = new HashSet<string>();
|
||||
}
|
||||
|
||||
if (node.ConnectedNodes != null)
|
||||
{
|
||||
foreach (var connectedId in node.ConnectedNodes)
|
||||
{
|
||||
allConnections[node.NodeId].Add(connectedId);
|
||||
allConnections[node.Id].Add(connectedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,10 +468,10 @@ namespace AGVNavigationCore.Models
|
||||
// 이 노드를 연결하는 모든 노드 찾기
|
||||
foreach (var otherNodeId in allConnections.Keys)
|
||||
{
|
||||
if (otherNodeId == node.NodeId) continue;
|
||||
if (otherNodeId == node.Id) continue;
|
||||
|
||||
// 다른 노드가 이 노드를 연결하고 있다면
|
||||
if (allConnections[otherNodeId].Contains(node.NodeId))
|
||||
if (allConnections[otherNodeId].Contains(node.Id))
|
||||
{
|
||||
// 이 노드의 ConnectedNodes에 그 노드를 추가 (중복 방지)
|
||||
if (!node.ConnectedNodes.Contains(otherNodeId))
|
||||
|
||||
72
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapMagnet.cs
Normal file
72
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapMagnet.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AGVNavigationCore.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 맵 상의 마그넷(Magnet) 정보를 나타내는 클래스
|
||||
/// </summary>
|
||||
public class MapMagnet : NodeBase
|
||||
{
|
||||
|
||||
public MapMagnet() {
|
||||
Type = NodeType.Magnet;
|
||||
}
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("시작점 좌표")]
|
||||
public MagnetPoint P1 { get; set; } = new MagnetPoint();
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("끝점 좌표")]
|
||||
public MagnetPoint P2 { get; set; } = new MagnetPoint();
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("제어점 좌표 (곡선인 경우)")]
|
||||
public MagnetPoint ControlPoint { get; set; } = null;
|
||||
|
||||
public class MagnetPoint
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override Point Position
|
||||
{
|
||||
get => new Point((int)P1.X, (int)P1.Y);
|
||||
set
|
||||
{
|
||||
double dx = value.X - P1.X;
|
||||
double dy = value.Y - P1.Y;
|
||||
|
||||
P1.X += dx;
|
||||
P1.Y += dy;
|
||||
P2.X += dx;
|
||||
P2.Y += dy;
|
||||
|
||||
if (ControlPoint != null)
|
||||
{
|
||||
ControlPoint.X += dx;
|
||||
ControlPoint.Y += dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시작점 Point 반환
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public Point StartPoint => new Point((int)P1.X, (int)P1.Y);
|
||||
|
||||
/// <summary>
|
||||
/// 끝점 Point 반환
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public Point EndPoint => new Point((int)P2.X, (int)P2.Y);
|
||||
}
|
||||
}
|
||||
37
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapMark.cs
Normal file
37
Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapMark.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
|
||||
namespace AGVNavigationCore.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 맵 상의 마크(Mark) 정보를 나타내는 클래스
|
||||
/// </summary>
|
||||
public class MapMark : NodeBase
|
||||
{
|
||||
// Id is inherited from NodeBase
|
||||
public MapMark() {
|
||||
Type = NodeType.Mark;
|
||||
}
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("마크의 X 좌표")]
|
||||
public double X
|
||||
{
|
||||
get => Position.X;
|
||||
set => Position = new Point((int)value, Position.Y);
|
||||
}
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("마크의 Y 좌표")]
|
||||
public double Y
|
||||
{
|
||||
get => Position.Y;
|
||||
set => Position = new Point(Position.X, (int)value);
|
||||
}
|
||||
|
||||
[Category("위치 정보")]
|
||||
[Description("마크의 회전 각도")]
|
||||
public double Rotation { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using AGVNavigationCore.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AGVNavigationCore.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 맵 노드 정보를 관리하는 클래스
|
||||
/// 논리적 노드로서 실제 맵의 위치와 속성을 정의
|
||||
/// 맵 노드 정보를 관리하는 클래스 (주행 경로용 노드)
|
||||
/// </summary>
|
||||
public class MapNode
|
||||
public class MapNode : NodeBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 논리적 노드 ID (맵 에디터에서 관리하는 고유 ID)
|
||||
/// 예: "N001", "N002", "LOADER1", "CHARGER1"
|
||||
/// </summary>
|
||||
public string NodeId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 표시 이름 (사용자 친화적)
|
||||
/// 예: "로더1", "충전기1", "교차점A", "회전지점1"
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 맵 상의 위치 좌표 (픽셀 단위)
|
||||
/// </summary>
|
||||
public Point Position { get; set; } = Point.Empty;
|
||||
[Category("라벨 설정")]
|
||||
[Description("표시할 텍스트입니다.")]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 노드 타입
|
||||
/// </summary>
|
||||
public NodeType Type { get; set; } = NodeType.Normal;
|
||||
public StationType StationType { get; set; }
|
||||
|
||||
[Browsable(false)]
|
||||
public bool CanDocking
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Type == NodeType.Buffer) return true;
|
||||
if (Type == NodeType.Loader) return true;
|
||||
if (Type == NodeType.UnLoader) return true;
|
||||
if (Type == NodeType.Clearner) return true;
|
||||
if (Type == NodeType.Charging) return true;
|
||||
if (StationType == StationType.Buffer) return true;
|
||||
if (StationType == StationType.Loader) return true;
|
||||
if (StationType == StationType.UnLoader) return true;
|
||||
if (StationType == StationType.Clearner) return true;
|
||||
if (StationType == StationType.Charger) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 도킹 방향 (도킹/충전 노드인 경우에만 Forward/Backward, 일반 노드는 DontCare)
|
||||
/// </summary>
|
||||
[Category("노드 설정")]
|
||||
[Description("도킹/충전 노드의 진입 방향입니다.")]
|
||||
public DockingDirection DockDirection { get; set; } = DockingDirection.DontCare;
|
||||
|
||||
/// <summary>
|
||||
/// 연결된 노드 ID 목록 (경로 정보)
|
||||
/// </summary>
|
||||
[Category("연결 정보")]
|
||||
[Description("연결된 노드 ID 목록입니다.")]
|
||||
[ReadOnly(true)]
|
||||
public List<string> ConnectedNodes { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 연결된 노드 객체 목록 (런타임 전용, JSON 무시)
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
[Browsable(false)]
|
||||
public List<MapNode> ConnectedMapNodes { get; set; } = new List<MapNode>();
|
||||
|
||||
/// <summary>
|
||||
/// 회전 가능 여부 (180도 회전 가능한 지점)
|
||||
/// </summary>
|
||||
[Category("주행 설정")]
|
||||
[Description("제자리 회전(좌) 가능 여부입니다.")]
|
||||
public bool CanTurnLeft { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 회전 가능 여부 (180도 회전 가능한 지점)
|
||||
/// </summary>
|
||||
[Category("주행 설정")]
|
||||
[Description("제자리 회전(우) 가능 여부입니다.")]
|
||||
public bool CanTurnRight { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 교차로로 이용가능한지
|
||||
/// </summary>
|
||||
[Category("주행 설정")]
|
||||
[Description("교차로 주행 가능 여부입니다.")]
|
||||
public bool DisableCross
|
||||
{
|
||||
get
|
||||
@@ -85,216 +67,61 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
set { _disablecross = value; }
|
||||
}
|
||||
|
||||
private bool _disablecross = false;
|
||||
|
||||
/// <summary>
|
||||
/// 장비 ID (도킹/충전 스테이션인 경우)
|
||||
/// 예: "LOADER1", "CLEANER1", "BUFFER1", "CHARGER1"
|
||||
/// </summary>
|
||||
public string NodeAlias { get; set; } = string.Empty;
|
||||
[Category("주행 설정")]
|
||||
[Description("노드 통과 시 제한 속도입니다.")]
|
||||
public SpeedLevel SpeedLimit { get; set; } = SpeedLevel.M;
|
||||
|
||||
/// <summary>
|
||||
/// 장비 타입 (도킹/충전 스테이션인 경우)
|
||||
/// </summary>
|
||||
// public StationType? StationType { get; set; } = null;
|
||||
[Category("노드 설정")]
|
||||
[Description("장비 ID 또는 별칭입니다.")]
|
||||
public string AliasName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 생성 일자
|
||||
/// </summary>
|
||||
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 수정 일자
|
||||
/// </summary>
|
||||
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 노드 활성화 여부
|
||||
/// </summary>
|
||||
[Category("기본 정보")]
|
||||
[Description("노드 사용 여부입니다.")]
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 색상 (맵 에디터 표시용)
|
||||
/// </summary>
|
||||
public Color DisplayColor { get; set; } = Color.Blue;
|
||||
[Category("RFID 정보")]
|
||||
[Description("물리적 RFID 태그 ID입니다.")]
|
||||
public UInt16 RfidId { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// RFID 태그 ID (이 노드에 매핑된 RFID)
|
||||
/// </summary>
|
||||
public string RfidId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// RFID 상태 (정상, 손상, 교체예정 등)
|
||||
/// </summary>
|
||||
public string RfidStatus { get; set; } = "정상";
|
||||
|
||||
/// <summary>
|
||||
/// RFID 설치 위치 설명 (현장 작업자용)
|
||||
/// 예: "로더1번 앞", "충전기2번 입구", "복도 교차점" 등
|
||||
/// </summary>
|
||||
public string RfidDescription { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 텍스트 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
public string LabelText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 폰트 패밀리 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
public string FontFamily { get; set; } = "Arial";
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 폰트 크기 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
public float FontSize { get; set; } = 12.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 폰트 스타일 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 전경색 (모든 노드 타입에서 사용)
|
||||
/// </summary>
|
||||
public Color ForeColor { get; set; } = Color.Black;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 배경색 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
public Color BackColor { get; set; } = Color.Transparent;
|
||||
[Category("노드 텍스트"), DisplayName("TextColor")]
|
||||
[Description("텍스트 색상입니다.")]
|
||||
public Color NodeTextForeColor { get; set; } = Color.Black;
|
||||
|
||||
private float _textFontSize = 7.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 폰트 크기 (모든 노드 타입의 텍스트 표시에 사용, 픽셀 단위)
|
||||
/// 0 이하의 값이 설정되면 기본값 7.0f로 자동 설정
|
||||
/// </summary>
|
||||
public float TextFontSize
|
||||
[Category("노드 텍스트"), DisplayName("TextSize")]
|
||||
[Description("일반 노드 텍스트의 크기입니다.")]
|
||||
public float NodeTextFontSize
|
||||
{
|
||||
get => _textFontSize;
|
||||
set => _textFontSize = value > 0 ? value : 7.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 볼드체 여부 (모든 노드 타입의 텍스트 표시에 사용)
|
||||
/// </summary>
|
||||
public bool TextFontBold { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 이름 말풍선 배경색 (하단에 표시되는 노드 이름의 배경색)
|
||||
/// </summary>
|
||||
public Color NameBubbleBackColor { get; set; } = Color.Gold;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 이름 말풍선 글자색 (하단에 표시되는 노드 이름의 글자색)
|
||||
/// </summary>
|
||||
public Color NameBubbleForeColor { get; set; } = Color.Black;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 배경 표시 여부 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
public bool ShowBackground { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 패딩 (NodeType.Label인 경우 사용, 픽셀 단위)
|
||||
/// </summary>
|
||||
public int Padding { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 파일 경로 (편집용, 저장시엔 사용되지 않음)
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Base64 인코딩된 이미지 데이터 (JSON 저장용)
|
||||
/// </summary>
|
||||
public string ImageBase64 { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 크기 배율 (NodeType.Image인 경우 사용)
|
||||
/// </summary>
|
||||
public SizeF Scale { get; set; } = new SizeF(1.0f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 투명도 (NodeType.Image인 경우 사용, 0.0~1.0)
|
||||
/// </summary>
|
||||
public float Opacity { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 회전 각도 (NodeType.Image인 경우 사용, 도 단위)
|
||||
/// </summary>
|
||||
public float Rotation { get; set; } = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 로딩된 이미지 (런타임에서만 사용, JSON 직렬화 제외)
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
public Image LoadedImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 기본 생성자
|
||||
/// </summary>
|
||||
public MapNode()
|
||||
public MapNode() : base()
|
||||
{
|
||||
Type = NodeType.Normal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 매개변수 생성자
|
||||
/// </summary>
|
||||
/// <param name="nodeId">노드 ID</param>
|
||||
/// <param name="name">노드 이름</param>
|
||||
/// <param name="position">위치</param>
|
||||
/// <param name="type">노드 타입</param>
|
||||
public MapNode(string nodeId, string name, Point position, NodeType type)
|
||||
|
||||
public MapNode(string nodeId, Point position, StationType type) : base(nodeId, position)
|
||||
{
|
||||
NodeId = nodeId;
|
||||
Name = name;
|
||||
Position = position;
|
||||
Type = type;
|
||||
CreatedDate = DateTime.Now;
|
||||
ModifiedDate = DateTime.Now;
|
||||
|
||||
// 타입별 기본 색상 설정
|
||||
SetDefaultColorByType(type);
|
||||
Type = NodeType.Normal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 타입에 따른 기본 색상 설정
|
||||
/// </summary>
|
||||
/// <param name="type">노드 타입</param>
|
||||
public void SetDefaultColorByType(NodeType type)
|
||||
[Category("기본 정보")]
|
||||
[JsonIgnore]
|
||||
[ReadOnly(true), Browsable(false)]
|
||||
public bool isDockingNode
|
||||
{
|
||||
switch (type)
|
||||
get
|
||||
{
|
||||
case NodeType.Normal:
|
||||
DisplayColor = Color.Blue;
|
||||
break;
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
case NodeType.Loader:
|
||||
DisplayColor = Color.Green;
|
||||
break;
|
||||
case NodeType.Charging:
|
||||
DisplayColor = Color.Red;
|
||||
break;
|
||||
case NodeType.Label:
|
||||
DisplayColor = Color.Purple;
|
||||
break;
|
||||
case NodeType.Image:
|
||||
DisplayColor = Color.Brown;
|
||||
break;
|
||||
if (StationType == StationType.Charger || StationType == StationType.Buffer ||
|
||||
StationType == StationType.Clearner || StationType == StationType.Loader ||
|
||||
StationType == StationType.UnLoader) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다른 노드와의 연결 추가
|
||||
/// </summary>
|
||||
/// <param name="nodeId">연결할 노드 ID</param>
|
||||
public void AddConnection(string nodeId)
|
||||
{
|
||||
if (!ConnectedNodes.Contains(nodeId))
|
||||
@@ -304,10 +131,6 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다른 노드와의 연결 제거
|
||||
/// </summary>
|
||||
/// <param name="nodeId">연결 해제할 노드 ID</param>
|
||||
public void RemoveConnection(string nodeId)
|
||||
{
|
||||
if (ConnectedNodes.Remove(nodeId))
|
||||
@@ -316,290 +139,29 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 도킹 스테이션 설정
|
||||
///// </summary>
|
||||
///// <param name="stationId">장비 ID</param>
|
||||
///// <param name="stationType">장비 타입</param>
|
||||
///// <param name="dockDirection">도킹 방향</param>
|
||||
//public void SetDockingStation(string stationId, StationType stationType, DockingDirection dockDirection)
|
||||
//{
|
||||
// Type = NodeType.Docking;
|
||||
// NodeAlias = stationId;
|
||||
// DockDirection = dockDirection;
|
||||
// SetDefaultColorByType(NodeType.Docking);
|
||||
// ModifiedDate = DateTime.Now;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 충전 스테이션 설정
|
||||
/// </summary>
|
||||
/// <param name="stationId">충전기 ID</param>
|
||||
public void SetChargingStation(string stationId)
|
||||
{
|
||||
Type = NodeType.Charging;
|
||||
NodeAlias = stationId;
|
||||
DockDirection = DockingDirection.Forward; // 충전기는 항상 전진 도킹
|
||||
SetDefaultColorByType(NodeType.Charging);
|
||||
StationType = StationType.Charger;
|
||||
Id = stationId;
|
||||
DockDirection = DockingDirection.Forward;
|
||||
ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 문자열 표현
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{RfidId}({NodeId}): {Name} ({Type}) at ({Position.X}, {Position.Y})";
|
||||
return $"RFID:{RfidId}(NODE:{Id}): AS:{AliasName} ({Type}) at ({Position.X}, {Position.Y})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 리스트박스 표시용 텍스트 (노드ID - 설명 - RFID 순서)
|
||||
/// </summary>
|
||||
public string DisplayText
|
||||
{
|
||||
get
|
||||
{
|
||||
var displayText = NodeId;
|
||||
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
displayText += $" - {Name}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(RfidId))
|
||||
{
|
||||
displayText += $" - [{RfidId}]";
|
||||
}
|
||||
|
||||
return displayText;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 복사
|
||||
/// </summary>
|
||||
/// <returns>복사된 노드</returns>
|
||||
public MapNode Clone()
|
||||
{
|
||||
var clone = new MapNode
|
||||
{
|
||||
NodeId = NodeId,
|
||||
Name = Name,
|
||||
Position = Position,
|
||||
Type = Type,
|
||||
DockDirection = DockDirection,
|
||||
ConnectedNodes = new List<string>(ConnectedNodes),
|
||||
|
||||
CanTurnLeft = CanTurnLeft,
|
||||
CanTurnRight = CanTurnRight,
|
||||
DisableCross = DisableCross,
|
||||
|
||||
NodeAlias = NodeAlias,
|
||||
CreatedDate = CreatedDate,
|
||||
ModifiedDate = ModifiedDate,
|
||||
IsActive = IsActive,
|
||||
DisplayColor = DisplayColor,
|
||||
RfidId = RfidId,
|
||||
RfidStatus = RfidStatus,
|
||||
RfidDescription = RfidDescription,
|
||||
LabelText = LabelText,
|
||||
FontFamily = FontFamily,
|
||||
FontSize = FontSize,
|
||||
FontStyle = FontStyle,
|
||||
ForeColor = ForeColor,
|
||||
BackColor = BackColor,
|
||||
TextFontSize = TextFontSize,
|
||||
TextFontBold = TextFontBold,
|
||||
NameBubbleBackColor = NameBubbleBackColor,
|
||||
NameBubbleForeColor = NameBubbleForeColor,
|
||||
ShowBackground = ShowBackground,
|
||||
Padding = Padding,
|
||||
ImagePath = ImagePath,
|
||||
ImageBase64 = ImageBase64,
|
||||
Scale = Scale,
|
||||
Opacity = Opacity,
|
||||
Rotation = Rotation
|
||||
};
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 로드 (Base64 또는 파일 경로에서, 256x256 이상일 경우 자동 리사이즈)
|
||||
/// </summary>
|
||||
/// <returns>로드 성공 여부</returns>
|
||||
public bool LoadImage()
|
||||
{
|
||||
if (Type != NodeType.Image) return false;
|
||||
|
||||
try
|
||||
{
|
||||
Image originalImage = null;
|
||||
|
||||
// 1. 먼저 Base64 데이터 시도
|
||||
if (!string.IsNullOrEmpty(ImageBase64))
|
||||
{
|
||||
originalImage = ImageConverterUtil.Base64ToImage(ImageBase64);
|
||||
}
|
||||
// 2. Base64가 없으면 파일 경로에서 로드
|
||||
else if (!string.IsNullOrEmpty(ImagePath) && System.IO.File.Exists(ImagePath))
|
||||
{
|
||||
originalImage = Image.FromFile(ImagePath);
|
||||
}
|
||||
|
||||
if (originalImage != null)
|
||||
{
|
||||
LoadedImage?.Dispose();
|
||||
|
||||
// 이미지 크기 체크 및 리사이즈
|
||||
if (originalImage.Width > 256 || originalImage.Height > 256)
|
||||
{
|
||||
LoadedImage = ResizeImage(originalImage, 256, 256);
|
||||
originalImage.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadedImage = originalImage;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 이미지 로드 실패
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 리사이즈 (비율 유지)
|
||||
/// </summary>
|
||||
/// <param name="image">원본 이미지</param>
|
||||
/// <param name="maxWidth">최대 너비</param>
|
||||
/// <param name="maxHeight">최대 높이</param>
|
||||
/// <returns>리사이즈된 이미지</returns>
|
||||
private Image ResizeImage(Image image, int maxWidth, int maxHeight)
|
||||
{
|
||||
// 비율 계산
|
||||
double ratioX = (double)maxWidth / image.Width;
|
||||
double ratioY = (double)maxHeight / image.Height;
|
||||
double ratio = Math.Min(ratioX, ratioY);
|
||||
|
||||
// 새로운 크기 계산
|
||||
int newWidth = (int)(image.Width * ratio);
|
||||
int newHeight = (int)(image.Height * ratio);
|
||||
|
||||
// 리사이즈된 이미지 생성
|
||||
var resizedImage = new Bitmap(newWidth, newHeight);
|
||||
using (var graphics = Graphics.FromImage(resizedImage))
|
||||
{
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
|
||||
}
|
||||
|
||||
return resizedImage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실제 표시될 크기 계산 (이미지 노드인 경우)
|
||||
/// </summary>
|
||||
/// <returns>실제 크기</returns>
|
||||
public Size GetDisplaySize()
|
||||
{
|
||||
if (Type != NodeType.Image || LoadedImage == null) return Size.Empty;
|
||||
|
||||
return new Size(
|
||||
(int)(LoadedImage.Width * Scale.Width),
|
||||
(int)(LoadedImage.Height * Scale.Height)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파일 경로에서 이미지를 Base64로 변환하여 저장
|
||||
/// </summary>
|
||||
/// <param name="filePath">이미지 파일 경로</param>
|
||||
/// <returns>변환 성공 여부</returns>
|
||||
public bool ConvertImageToBase64(string filePath)
|
||||
{
|
||||
if (Type != NodeType.Image) return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageBase64 = ImageConverterUtil.FileToBase64(filePath, System.Drawing.Imaging.ImageFormat.Png);
|
||||
ImagePath = filePath; // 편집용으로 경로 유지
|
||||
|
||||
return !string.IsNullOrEmpty(ImageBase64);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 리소스 정리
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
LoadedImage?.Dispose();
|
||||
LoadedImage = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 경로 찾기에 사용 가능한 노드인지 확인
|
||||
/// (라벨, 이미지 노드는 경로 찾기에서 제외)
|
||||
/// </summary>
|
||||
public bool IsNavigationNode()
|
||||
{
|
||||
return Type != NodeType.Label && Type != NodeType.Image && IsActive;
|
||||
// 이제 MapNode는 항상 내비게이션 노드임 (Label, Image 분리됨)
|
||||
// 하지만 기존 로직 호환성을 위해 Active 체크만 유지
|
||||
return IsActive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID가 할당되어 있는지 확인
|
||||
/// </summary>
|
||||
public bool HasRfid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(RfidId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID 정보 설정
|
||||
/// </summary>
|
||||
/// <param name="rfidId">RFID ID</param>
|
||||
/// <param name="rfidDescription">설치 위치 설명</param>
|
||||
/// <param name="rfidStatus">RFID 상태</param>
|
||||
public void SetRfidInfo(string rfidId, string rfidDescription = "", string rfidStatus = "정상")
|
||||
{
|
||||
RfidId = rfidId;
|
||||
RfidDescription = rfidDescription;
|
||||
RfidStatus = rfidStatus;
|
||||
ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID 정보 삭제
|
||||
/// </summary>
|
||||
public void ClearRfidInfo()
|
||||
{
|
||||
RfidId = string.Empty;
|
||||
RfidDescription = string.Empty;
|
||||
RfidStatus = "정상";
|
||||
ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID 기반 표시 텍스트 (RFID ID 우선, 없으면 노드ID)
|
||||
/// </summary>
|
||||
public string GetRfidDisplayText()
|
||||
{
|
||||
return HasRfid() ? RfidId : NodeId;
|
||||
return RfidId > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Cs_HMI/AGVLogic/AGVNavigationCore/Models/NodeBase.cs
Normal file
61
Cs_HMI/AGVLogic/AGVNavigationCore/Models/NodeBase.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AGVNavigationCore.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 맵 상의 모든 객체의 최상위 기본 클래스
|
||||
/// 위치, 선택 상태, 기본 식별자 등을 관리
|
||||
/// </summary>
|
||||
public abstract class NodeBase
|
||||
{
|
||||
[Category("기본 정보")]
|
||||
[Description("객체의 고유 ID입니다.")]
|
||||
[ReadOnly(true)]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[Category("기본 정보")]
|
||||
public NodeType Type { protected set; get; } = NodeType.Normal;
|
||||
|
||||
[Category("기본 정보")]
|
||||
[Description("객체의 좌표(X, Y)입니다.")]
|
||||
public virtual Point Position { get; set; } = Point.Empty;
|
||||
|
||||
[Category("기본 정보")]
|
||||
[Description("객체 생성 일자입니다.")]
|
||||
[JsonIgnore]
|
||||
[ReadOnly(true), Browsable(false)]
|
||||
public DateTime CreatedDate { get; set; } = DateTime.Now;
|
||||
|
||||
[Category("기본 정보")]
|
||||
[Description("객체 수정 일자입니다.")]
|
||||
[JsonIgnore]
|
||||
[ReadOnly(true), Browsable(false)]
|
||||
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
||||
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public bool IsSelected { get; set; } = false;
|
||||
|
||||
|
||||
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public bool IsHovered { get; set; } = false;
|
||||
|
||||
|
||||
|
||||
public NodeBase()
|
||||
{
|
||||
}
|
||||
|
||||
public NodeBase(string id, Point position)
|
||||
{
|
||||
Id = id;
|
||||
Position = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,12 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 현재 노드 ID
|
||||
/// </summary>
|
||||
public string CurrentNodeId => _currentNode?.NodeId;
|
||||
public MapNode CurrentNode => _currentNode;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 노드 ID (CurrentNode.Id)
|
||||
/// </summary>
|
||||
public string CurrentNodeId => _currentNode?.Id;
|
||||
|
||||
/// <summary>
|
||||
/// 이전 위치
|
||||
@@ -158,10 +163,6 @@ namespace AGVNavigationCore.Models
|
||||
/// </summary>
|
||||
public float BatteryLevel { get; set; } = 100.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 이전 노드 ID
|
||||
/// </summary>
|
||||
public string PrevNodeId => _prevNode?.NodeId;
|
||||
|
||||
/// <summary>
|
||||
/// 이전 노드
|
||||
@@ -188,6 +189,11 @@ namespace AGVNavigationCore.Models
|
||||
/// </summary>
|
||||
public int DetectedRfidCount => _detectedRfids.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 배터리 부족 경고 임계값 (%)
|
||||
/// </summary>
|
||||
public float LowBatteryThreshold { get; set; } = 20.0f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -262,9 +268,9 @@ namespace AGVNavigationCore.Models
|
||||
BatteryLevel = Math.Max(0, Math.Min(100, percentage));
|
||||
|
||||
// 배터리 부족 경고
|
||||
if (BatteryLevel < 20.0f && _currentState != AGVState.Charging)
|
||||
if (BatteryLevel < LowBatteryThreshold && _currentState != AGVState.Charging)
|
||||
{
|
||||
OnError($"배터리 부족: {BatteryLevel:F1}%");
|
||||
OnError($"배터리 부족: {BatteryLevel:F1}% (기준: {LowBatteryThreshold}%)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +280,7 @@ namespace AGVNavigationCore.Models
|
||||
public bool SetCurrentNodeMarkStop()
|
||||
{
|
||||
if (_currentNode == null) return false;
|
||||
if (_currentPath == null) return false;
|
||||
var 미완료된처음노드 = _currentPath.DetailedPath.Where(t => t.IsPass == false).OrderBy(t => t.seq).FirstOrDefault();
|
||||
if (미완료된처음노드 == null) return false;
|
||||
미완료된처음노드.IsPass = true;
|
||||
@@ -309,7 +316,7 @@ namespace AGVNavigationCore.Models
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.L,
|
||||
eAGVCommandReason.NoPath,
|
||||
$"위치 확정 완료 (목적지 미설정) - 현재:{_currentNode?.NodeId ?? "알수없음"}"
|
||||
$"위치 확정 완료 (목적지 미설정) - 현재:{_currentNode?.Id ?? "알수없음"}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,7 +325,7 @@ namespace AGVNavigationCore.Models
|
||||
if (_currentPath.DetailedPath.Where(t => t.seq < lastNode.seq && t.IsPass == false).Any() == false)
|
||||
{
|
||||
// 마지막 노드에 도착했는지 확인 (현재 노드가 마지막 노드와 같은지)
|
||||
if (_currentNode != null && _currentNode.NodeId == lastNode.NodeId)
|
||||
if (_currentNode != null && _currentNode.Id == lastNode.NodeId)
|
||||
{
|
||||
if (lastNode.IsPass) //이미완료되었다.
|
||||
{
|
||||
@@ -327,7 +334,7 @@ namespace AGVNavigationCore.Models
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.L,
|
||||
eAGVCommandReason.Complete,
|
||||
$"목적지 도착 - 최종:{_currentNode?.NodeId ?? "알수없음"}"
|
||||
$"목적지 도착 - 최종:{_currentNode?.Id ?? "알수없음"}"
|
||||
);
|
||||
}
|
||||
else
|
||||
@@ -338,7 +345,7 @@ namespace AGVNavigationCore.Models
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.L,
|
||||
eAGVCommandReason.MarkStop,
|
||||
$"목적지 도착 전(MarkStop) - 최종:{_currentNode?.NodeId ?? "알수없음"}"
|
||||
$"목적지 도착 전(MarkStop) - 최종:{_currentNode?.Id ?? "알수없음"}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -346,7 +353,7 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
|
||||
// 4. 경로이탈
|
||||
var TargetNode = _currentPath.DetailedPath.Where(t => t.IsPass == false && t.NodeId.Equals(_currentNode.NodeId)).FirstOrDefault();
|
||||
var TargetNode = _currentPath.DetailedPath.Where(t => t.IsPass == false && t.NodeId.Equals(_currentNode.Id)).FirstOrDefault();
|
||||
if (TargetNode == null)
|
||||
{
|
||||
return new AGVCommand(
|
||||
@@ -354,11 +361,11 @@ namespace AGVNavigationCore.Models
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.L,
|
||||
eAGVCommandReason.PathOut,
|
||||
$"(재탐색요청)경로이탈 현재위치:{_currentNode.NodeId}"
|
||||
$"(재탐색요청)경로이탈 현재위치:{_currentNode.Id}"
|
||||
);
|
||||
}
|
||||
|
||||
return GetCommandFromPath(CurrentNodeId, "경로 실행 시작");
|
||||
return GetCommandFromPath(CurrentNode, "경로 실행 시작");
|
||||
|
||||
|
||||
}
|
||||
@@ -409,13 +416,13 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
|
||||
_currentPath = path;
|
||||
_remainingNodes = path.Path.Select(n => n.NodeId).ToList(); // MapNode → NodeId 변환
|
||||
_remainingNodes = path.Path.Select(n => n.Id).ToList(); // MapNode → NodeId 변환
|
||||
_currentNodeIndex = 0;
|
||||
|
||||
// 경로 시작 노드가 현재 노드와 다른 경우 경고
|
||||
if (_currentNode != null && _remainingNodes.Count > 0 && _remainingNodes[0] != _currentNode.NodeId)
|
||||
if (_currentNode != null && _remainingNodes.Count > 0 && _remainingNodes[0] != _currentNode.Id)
|
||||
{
|
||||
OnError($"경로 시작 노드({_remainingNodes[0]})와 현재 노드({_currentNode.NodeId})가 다릅니다.");
|
||||
OnError($"경로 시작 노드({_remainingNodes[0]})와 현재 노드({_currentNode.Id})가 다릅니다.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,7 +557,7 @@ namespace AGVNavigationCore.Models
|
||||
public void SetPosition(MapNode node, AgvDirection motorDirection)
|
||||
{
|
||||
// 현재 위치를 이전 위치로 저장 (리프트 방향 계산용)
|
||||
if (_currentNode != null && _currentNode.NodeId != node.NodeId)
|
||||
if (_currentNode != null && _currentNode.Id != node.Id)
|
||||
{
|
||||
_prevPosition = _currentPosition; // 이전 위치
|
||||
_prevNode = _currentNode;
|
||||
@@ -569,9 +576,9 @@ namespace AGVNavigationCore.Models
|
||||
_currentNode = node;
|
||||
|
||||
// 🔥 노드 ID를 RFID로 간주하여 감지 목록에 추가 (시뮬레이터용)
|
||||
if (!string.IsNullOrEmpty(node.NodeId) && !_detectedRfids.Contains(node.NodeId))
|
||||
if (!string.IsNullOrEmpty(node.Id) && !_detectedRfids.Contains(node.Id))
|
||||
{
|
||||
_detectedRfids.Add(node.NodeId);
|
||||
_detectedRfids.Add(node.Id);
|
||||
}
|
||||
|
||||
// 🔥 RFID 2개 이상 감지 시 위치 확정
|
||||
@@ -583,9 +590,18 @@ namespace AGVNavigationCore.Models
|
||||
//현재 경로값이 있는지 확인한다.
|
||||
if (CurrentPath != null && CurrentPath.DetailedPath != null && CurrentPath.DetailedPath.Any())
|
||||
{
|
||||
var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.NodeId && t.IsPass == false);
|
||||
var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.Id && t.IsPass == false);
|
||||
if (item != null)
|
||||
{
|
||||
// [PathJump Check] 점프한 노드 개수 확인
|
||||
// 현재 노드(item)보다 이전인데 아직 IsPass가 안 된 노드의 개수
|
||||
int skippedCount = CurrentPath.DetailedPath.Count(t => t.seq < item.seq && t.IsPass == false);
|
||||
if (skippedCount > 2)
|
||||
{
|
||||
OnError($"PathJump: {skippedCount}개의 노드를 건너뛰었습니다. (허용: 2개, 현재노드: {node.Id})");
|
||||
return;
|
||||
}
|
||||
|
||||
//item.IsPass = true;
|
||||
//이전노드는 모두 지나친걸로 한다
|
||||
CurrentPath.DetailedPath.Where(t => t.seq < item.seq).ToList().ForEach(t => t.IsPass = true);
|
||||
@@ -596,18 +612,7 @@ namespace AGVNavigationCore.Models
|
||||
PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 RFID 시뮬레이션 (현재 위치 기준)
|
||||
/// </summary>
|
||||
public string SimulateRfidReading(List<MapNode> mapNodes)
|
||||
{
|
||||
var closestNode = FindClosestNode(_currentPosition, mapNodes);
|
||||
if (closestNode == null)
|
||||
return null;
|
||||
|
||||
return closestNode.HasRfid() ? closestNode.RfidId : null;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -615,10 +620,11 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
|
||||
/// </summary>
|
||||
public string GetRfidByNodeId(List<MapNode> _mapNodes, string nodeId)
|
||||
public ushort GetRfidByNodeId(List<MapNode> _mapNodes, string nodeId)
|
||||
{
|
||||
var node = _mapNodes?.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
return node?.HasRfid() == true ? node.RfidId : nodeId;
|
||||
var node = _mapNodes?.FirstOrDefault(n => n.Id == nodeId);
|
||||
if ((node?.HasRfid() ?? false) == false) return 0;
|
||||
return node.RfidId;
|
||||
}
|
||||
|
||||
|
||||
@@ -628,19 +634,16 @@ namespace AGVNavigationCore.Models
|
||||
/// <summary>
|
||||
/// DetailedPath에서 노드 정보를 찾아 AGVCommand 생성
|
||||
/// </summary>
|
||||
private AGVCommand GetCommandFromPath(string targetNodeId, string actionDescription)
|
||||
private AGVCommand GetCommandFromPath(MapNode targetNode, string actionDescription)
|
||||
{
|
||||
// DetailedPath가 없으면 기본 명령 반환
|
||||
if (_currentPath == null || _currentPath.DetailedPath == null || _currentPath.DetailedPath.Count == 0)
|
||||
{
|
||||
var defaultMotor = _currentDirection == AgvDirection.Forward
|
||||
? MotorCommand.Forward
|
||||
: MotorCommand.Backward;
|
||||
|
||||
// [Refactor] Predict와 일관성 유지: 경로가 없으면 정지
|
||||
return new AGVCommand(
|
||||
defaultMotor,
|
||||
MotorCommand.Stop,
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.M,
|
||||
SpeedLevel.L,
|
||||
eAGVCommandReason.NoPath,
|
||||
$"{actionDescription} (DetailedPath 없음)"
|
||||
);
|
||||
@@ -648,7 +651,7 @@ namespace AGVNavigationCore.Models
|
||||
|
||||
// DetailedPath에서 targetNodeId에 해당하는 NodeMotorInfo 찾기
|
||||
// 지나가지 않은 경로를 찾는다
|
||||
var nodeInfo = _currentPath.DetailedPath.FirstOrDefault(n => n.NodeId == targetNodeId && n.IsPass == false);
|
||||
var nodeInfo = _currentPath.DetailedPath.FirstOrDefault(n => n.NodeId == targetNode.Id && n.IsPass == false);
|
||||
|
||||
if (nodeInfo == null)
|
||||
{
|
||||
@@ -662,7 +665,7 @@ namespace AGVNavigationCore.Models
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.M,
|
||||
eAGVCommandReason.NoTarget,
|
||||
$"{actionDescription} (노드 {targetNodeId} 정보 없음)"
|
||||
$"{actionDescription} (노드 {targetNode.Id} 정보 없음)"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -697,17 +700,20 @@ namespace AGVNavigationCore.Models
|
||||
break;
|
||||
}
|
||||
|
||||
// 속도 결정 (회전 노드면 저속, 일반 이동은 중속)
|
||||
SpeedLevel speed = nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint
|
||||
? SpeedLevel.L
|
||||
: SpeedLevel.M;
|
||||
// [Speed Control] NodeMotorInfo에 설정된 속도 사용
|
||||
// 단, 회전 구간 등에서 안전을 위해 강제 감속이 필요한 경우 로직 추가 가능
|
||||
// 현재는 사용자 설정 우선
|
||||
SpeedLevel speed = nodeInfo.Speed;
|
||||
|
||||
// Optional: 회전 시 강제 감속 로직 (사용자 요청에 따라 주석 처리 또는 제거 가능)
|
||||
// if (nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint) speed = SpeedLevel.L;
|
||||
|
||||
return new AGVCommand(
|
||||
motorCmd,
|
||||
magnetPos,
|
||||
speed,
|
||||
eAGVCommandReason.Normal,
|
||||
$"{actionDescription} → {targetNodeId} (Motor:{motorCmd}, Magnet:{magnetPos})"
|
||||
$"{actionDescription} → {targetNode.Id} (Motor:{motorCmd}, Magnet:{magnetPos})"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -864,21 +870,6 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
}
|
||||
|
||||
private DockingDirection GetDockingDirection(NodeType nodeType)
|
||||
{
|
||||
switch (nodeType)
|
||||
{
|
||||
case NodeType.Charging:
|
||||
return DockingDirection.Forward;
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
return DockingDirection.Backward;
|
||||
default:
|
||||
return DockingDirection.Forward;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnError(string message)
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
|
||||
if (node.IsNavigationNode())
|
||||
{
|
||||
var junctionInfo = AnalyzeNode(node);
|
||||
_junctions[node.NodeId] = junctionInfo;
|
||||
_junctions[node.Id] = junctionInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
|
||||
/// </summary>
|
||||
private JunctionInfo AnalyzeNode(MapNode node)
|
||||
{
|
||||
var junction = new JunctionInfo(node.NodeId);
|
||||
var junction = new JunctionInfo(node.Id);
|
||||
|
||||
// 양방향 연결을 고려하여 모든 연결된 노드 찾기
|
||||
var connectedNodes = GetAllConnectedNodes(node);
|
||||
@@ -96,16 +96,16 @@ namespace AGVNavigationCore.PathFinding.Analysis
|
||||
{
|
||||
if (connectedNode != null)
|
||||
{
|
||||
connected.Add(connectedNode.NodeId);
|
||||
connected.Add(connectedNode.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// 역방향 연결된 노드들 (다른 노드에서 이 노드로 연결)
|
||||
foreach (var otherNode in _mapNodes)
|
||||
{
|
||||
if (otherNode.NodeId != node.NodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == node.NodeId))
|
||||
if (otherNode.Id != node.Id && otherNode.ConnectedMapNodes.Any(n => n.Id == node.Id))
|
||||
{
|
||||
connected.Add(otherNode.NodeId);
|
||||
connected.Add(otherNode.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
|
||||
|
||||
foreach (var connectedId in connectedNodes)
|
||||
{
|
||||
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedId);
|
||||
var connectedNode = _mapNodes.FirstOrDefault(n => n.Id == connectedId);
|
||||
if (connectedNode != null)
|
||||
{
|
||||
double angle = CalculateAngle(junctionNode.Position, connectedNode.Position);
|
||||
@@ -226,9 +226,9 @@ namespace AGVNavigationCore.PathFinding.Analysis
|
||||
return MagnetDirection.Straight;
|
||||
|
||||
// 실제 각도 기반으로 마그넷 방향 계산
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == fromNodeId);
|
||||
var currentNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNodeId);
|
||||
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.Id == fromNodeId);
|
||||
var currentNode = _mapNodes.FirstOrDefault(n => n.Id == currentNodeId);
|
||||
var toNode = _mapNodes.FirstOrDefault(n => n.Id == toNodeId);
|
||||
|
||||
if (fromNode == null || currentNode == null || toNode == null)
|
||||
return MagnetDirection.Straight;
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
{
|
||||
return DetailedPath.Select(n => n.NodeId).ToList();
|
||||
}
|
||||
return Path?.Select(n => n.NodeId).ToList() ?? new List<string>();
|
||||
return Path?.Select(n => n.Id).ToList() ?? new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -50,36 +50,36 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
// 모든 네비게이션 노드를 PathNode로 변환하고 양방향 연결 생성
|
||||
foreach (var mapNode in _mapNodes)
|
||||
{
|
||||
_mapNodeLookup[mapNode.NodeId] = mapNode; // Add to lookup table
|
||||
_mapNodeLookup[mapNode.Id] = mapNode; // Add to lookup table
|
||||
|
||||
if (mapNode.IsNavigationNode())
|
||||
{
|
||||
var pathNode = new PathNode(mapNode.NodeId, mapNode.Position);
|
||||
_nodeMap[mapNode.NodeId] = pathNode;
|
||||
var pathNode = new PathNode(mapNode.Id, mapNode.Position);
|
||||
_nodeMap[mapNode.Id] = pathNode;
|
||||
}
|
||||
}
|
||||
|
||||
// 단일 연결을 양방향으로 확장
|
||||
foreach (var mapNode in _mapNodes)
|
||||
{
|
||||
if (mapNode.IsNavigationNode() && _nodeMap.ContainsKey(mapNode.NodeId))
|
||||
if (mapNode.IsNavigationNode() && _nodeMap.ContainsKey(mapNode.Id))
|
||||
{
|
||||
var pathNode = _nodeMap[mapNode.NodeId];
|
||||
var pathNode = _nodeMap[mapNode.Id];
|
||||
|
||||
foreach (var connectedNode in mapNode.ConnectedMapNodes)
|
||||
{
|
||||
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.NodeId))
|
||||
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.Id))
|
||||
{
|
||||
// 양방향 연결 생성 (단일 연결이 양방향을 의미)
|
||||
if (!pathNode.ConnectedNodes.Contains(connectedNode.NodeId))
|
||||
if (!pathNode.ConnectedNodes.Contains(connectedNode.Id))
|
||||
{
|
||||
pathNode.ConnectedNodes.Add(connectedNode.NodeId);
|
||||
pathNode.ConnectedNodes.Add(connectedNode.Id);
|
||||
}
|
||||
|
||||
var connectedPathNode = _nodeMap[connectedNode.NodeId];
|
||||
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId))
|
||||
var connectedPathNode = _nodeMap[connectedNode.Id];
|
||||
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.Id))
|
||||
{
|
||||
connectedPathNode.ConnectedNodes.Add(mapNode.NodeId);
|
||||
connectedPathNode.ConnectedNodes.Add(mapNode.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,8 +371,8 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
var combinedDetailedPath = new List<NodeMotorInfo>(previousResult.DetailedPath ?? new List<NodeMotorInfo>());
|
||||
|
||||
// 이전 경로의 마지막 노드와 현재 경로의 시작 노드 비교
|
||||
string lastNodeOfPrevious = previousResult.Path[previousResult.Path.Count - 1].NodeId;
|
||||
string firstNodeOfCurrent = currentResult.Path[0].NodeId;
|
||||
string lastNodeOfPrevious = previousResult.Path[previousResult.Path.Count - 1].Id;
|
||||
string firstNodeOfCurrent = currentResult.Path[0].Id;
|
||||
|
||||
if (lastNodeOfPrevious == firstNodeOfCurrent)
|
||||
{
|
||||
@@ -508,8 +508,8 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
float totalDistance = 0;
|
||||
for (int i = 0; i < path.Count - 1; i++)
|
||||
{
|
||||
var nodeId1 = path[i].NodeId;
|
||||
var nodeId2 = path[i + 1].NodeId;
|
||||
var nodeId1 = path[i].Id;
|
||||
var nodeId2 = path[i + 1].Id;
|
||||
|
||||
if (_nodeMap.ContainsKey(nodeId1) && _nodeMap.ContainsKey(nodeId2))
|
||||
{
|
||||
@@ -577,7 +577,7 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
return null;
|
||||
|
||||
// 교차로 노드 찾기
|
||||
var junctionNode = _mapNodes.FirstOrDefault(n => n.NodeId == junctionNodeId);
|
||||
var junctionNode = _mapNodes.FirstOrDefault(n => n.Id == junctionNodeId);
|
||||
if (junctionNode == null || junctionNode.ConnectedNodes == null || junctionNode.ConnectedNodes.Count == 0)
|
||||
return null;
|
||||
|
||||
@@ -602,7 +602,7 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
if (connectedNodeId == targetNodeId) continue;
|
||||
|
||||
// 조건 3, 4, 5: 존재하고, 활성 상태이고, 네비게이션 가능
|
||||
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
|
||||
var connectedNode = _mapNodes.FirstOrDefault(n => n.Id == connectedNodeId);
|
||||
if (connectedNode != null && connectedNode.IsActive && connectedNode.IsNavigationNode())
|
||||
{
|
||||
alternateNodes.Add(connectedNode);
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
n.DisableCross == false &&
|
||||
n.ConnectedNodes.Count >= 3 &&
|
||||
n.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false &&
|
||||
n.NodeId != startNode.NodeId
|
||||
n.Id != startNode.Id
|
||||
).ToList();
|
||||
|
||||
// docking 포인트가 연결된 노드는 제거한다.
|
||||
@@ -103,7 +103,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
pathNode.ConnectedNodes.Count >= 3 &&
|
||||
pathNode.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false)
|
||||
{
|
||||
if (pathNode.NodeId.Equals(StartNode.NodeId) == false)
|
||||
if (pathNode.Id.Equals(StartNode.Id) == false)
|
||||
return pathNode;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,12 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
return null;
|
||||
}
|
||||
|
||||
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode)
|
||||
{
|
||||
// 기본값으로 경로 탐색 (이전 위치 = 현재 위치, 방향 = 전진)
|
||||
return FindPath(startNode, targetNode, startNode, AgvDirection.Forward, AgvDirection.Forward, false);
|
||||
}
|
||||
|
||||
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode,
|
||||
MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection, bool crossignore = false)
|
||||
{
|
||||
@@ -121,13 +127,17 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
return AGVPathResult.CreateFailure("목적지 노드가 null입니다.", 0, 0);
|
||||
if (prevNode == null)
|
||||
return AGVPathResult.CreateFailure("이전위치 노드가 null입니다.", 0, 0);
|
||||
if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection))
|
||||
if (targetNode.isDockingNode == false && targetNode.Type != NodeType.Normal)
|
||||
return AGVPathResult.CreateFailure("이동 가능한 노드가 아닙니다", 0, 0);
|
||||
|
||||
var tnode = targetNode as MapNode;
|
||||
if (startNode.Id == targetNode.Id && tnode.DockDirection.MatchAGVDirection(prevDirection))
|
||||
return AGVPathResult.CreateSuccess(new List<MapNode> { startNode, startNode }, new List<AgvDirection>(), 0, 0);
|
||||
|
||||
var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
|
||||
|
||||
//1.목적지까지의 최단거리 경로를 찾는다.
|
||||
var pathResult = _basicPathfinder.FindPathAStar(startNode.NodeId, targetNode.NodeId);
|
||||
var pathResult = _basicPathfinder.FindPathAStar(startNode.Id, targetNode.Id);
|
||||
pathResult.PrevNode = prevNode;
|
||||
pathResult.PrevDirection = prevDirection;
|
||||
if (!pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0)
|
||||
@@ -146,11 +156,11 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
|
||||
//2.AGV방향과 목적지에 설정된 방향이 일치하면 그대로 진행하면된다.(목적지에 방향이 없는 경우에도 그대로 진행)
|
||||
if (targetNode.DockDirection == DockingDirection.DontCare ||
|
||||
(targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) ||
|
||||
(targetNode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward))
|
||||
if (tnode.DockDirection == DockingDirection.DontCare ||
|
||||
(tnode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) ||
|
||||
(tnode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward))
|
||||
{
|
||||
if ((nextNodeForward?.NodeId ?? string.Empty) == pathResult.Path[1].NodeId) //예측경로와 다음진행방향 경로가 일치하면 해당 방향이 맞다
|
||||
if ((nextNodeForward?.Id ?? string.Empty) == pathResult.Path[1].Id) //예측경로와 다음진행방향 경로가 일치하면 해당 방향이 맞다
|
||||
{
|
||||
MakeDetailData(pathResult, currentDirection);
|
||||
MakeMagnetDirection(pathResult);
|
||||
@@ -177,10 +187,10 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
//뒤로 이동시 경로상의 처음 만나는 노드가 같다면 그 방향으로 이동하면 된다.
|
||||
// ⚠️ 단, 현재 방향과 목적지 도킹 방향이 일치해야 함!
|
||||
if (nextNodeBackward != null && pathResult.Path.Count > 1 &&
|
||||
nextNodeBackward.NodeId == pathResult.Path[1].NodeId) // ✅ 추가: 현재도 Backward여야 함
|
||||
nextNodeBackward.Id == pathResult.Path[1].Id) // ✅ 추가: 현재도 Backward여야 함
|
||||
{
|
||||
if (targetNode.DockDirection == DockingDirection.Forward && ReverseDirection == AgvDirection.Forward ||
|
||||
targetNode.DockDirection == DockingDirection.Backward && ReverseDirection == AgvDirection.Backward)
|
||||
if (tnode.DockDirection == DockingDirection.Forward && ReverseDirection == AgvDirection.Forward ||
|
||||
tnode.DockDirection == DockingDirection.Backward && ReverseDirection == AgvDirection.Backward)
|
||||
{
|
||||
MakeDetailData(pathResult, ReverseDirection);
|
||||
MakeMagnetDirection(pathResult);
|
||||
@@ -191,12 +201,12 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
}
|
||||
|
||||
if (nextNodeForward != null && pathResult.Path.Count > 1 &&
|
||||
nextNodeForward.NodeId == pathResult.Path[1].NodeId &&
|
||||
targetNode.DockDirection == DockingDirection.Forward &&
|
||||
nextNodeForward.Id == pathResult.Path[1].Id &&
|
||||
tnode.DockDirection == DockingDirection.Forward &&
|
||||
currentDirection == AgvDirection.Forward) // ✅ 추가: 현재도 Forward여야 함
|
||||
{
|
||||
if (targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward ||
|
||||
targetNode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward)
|
||||
if (tnode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward ||
|
||||
tnode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward)
|
||||
{
|
||||
MakeDetailData(pathResult, currentDirection);
|
||||
MakeMagnetDirection(pathResult);
|
||||
@@ -221,7 +231,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
//진행방향으로 이동했을때 나오는 노드를 사용한다.
|
||||
if (nextNodeForward != null)
|
||||
{
|
||||
var Path0 = _basicPathfinder.FindPathAStar(startNode.NodeId, nextNodeForward.NodeId);
|
||||
var Path0 = _basicPathfinder.FindPathAStar(startNode.Id, nextNodeForward.Id);
|
||||
Path0.PrevNode = prevNode;
|
||||
Path0.PrevDirection = prevDirection;
|
||||
MakeDetailData(Path0, prevDirection);
|
||||
@@ -259,7 +269,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
JunctionInPath = FindNearestJunction(startNode);
|
||||
|
||||
//종료노드로부터 가까운 교차로 검색
|
||||
if (JunctionInPath == null) JunctionInPath = FindNearestJunction(targetNode);
|
||||
if (JunctionInPath == null) JunctionInPath = FindNearestJunction(tnode);
|
||||
}
|
||||
if (JunctionInPath == null)
|
||||
return AGVPathResult.CreateFailure("교차로가 없어 경로계산을 할 수 없습니다", 0, 0);
|
||||
@@ -267,7 +277,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
//경유지를 포함하여 경로를 다시 계산한다.
|
||||
|
||||
//1.시작위치 - 교차로(여기까지는 현재 방향으로 그대로 이동을 한다)
|
||||
var path1 = _basicPathfinder.FindPathAStar(startNode.NodeId, JunctionInPath.NodeId);
|
||||
var path1 = _basicPathfinder.FindPathAStar(startNode.Id, JunctionInPath.Id);
|
||||
path1.PrevNode = prevNode;
|
||||
path1.PrevDirection = prevDirection;
|
||||
|
||||
@@ -278,7 +288,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
// ReverseCheck = false; //현재 진행 방향으로 이동해야한다
|
||||
// MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
|
||||
//}
|
||||
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId))
|
||||
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.Id.Equals(path1.Path[1].Id))
|
||||
{
|
||||
ReverseCheck = true; //현재 방향의 반대방향으로 이동해야한다
|
||||
MakeDetailData(path1, ReverseDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
|
||||
@@ -295,7 +305,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
|
||||
//2.교차로 - 종료위치
|
||||
var path2 = _basicPathfinder.FindPathAStar(JunctionInPath.NodeId, targetNode.NodeId);
|
||||
var path2 = _basicPathfinder.FindPathAStar(JunctionInPath.Id, targetNode.Id);
|
||||
path2.PrevNode = prevNode;
|
||||
path2.PrevDirection = prevDirection;
|
||||
|
||||
@@ -315,9 +325,9 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
|
||||
//3.방향전환을 위환 대체 노드찾기
|
||||
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
|
||||
path1.Path[path1.Path.Count - 2].NodeId,
|
||||
path2.Path[1].NodeId);
|
||||
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.Id,
|
||||
path1.Path[path1.Path.Count - 2].Id,
|
||||
path2.Path[1].Id);
|
||||
|
||||
//4. path1 + tempnode + path2 가 최종 위치가 된다.
|
||||
if (tempNode == null)
|
||||
@@ -332,7 +342,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
//if (tempNode != null)
|
||||
{
|
||||
// 교차로 → 대체노드 경로 계산
|
||||
var pathToTemp = _basicPathfinder.FindPathAStar(JunctionInPath.NodeId, tempNode.NodeId);
|
||||
var pathToTemp = _basicPathfinder.FindPathAStar(JunctionInPath.Id, tempNode.Id);
|
||||
pathToTemp.PrevNode = JunctionInPath;
|
||||
pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
|
||||
if (!pathToTemp.Success)
|
||||
@@ -348,7 +358,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp);
|
||||
|
||||
// 대체노드 → 교차로 경로 계산 (역방향)
|
||||
var pathFromTemp = _basicPathfinder.FindPathAStar(tempNode.NodeId, JunctionInPath.NodeId);
|
||||
var pathFromTemp = _basicPathfinder.FindPathAStar(tempNode.Id, JunctionInPath.Id);
|
||||
pathFromTemp.PrevNode = JunctionInPath;
|
||||
pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
|
||||
if (!pathFromTemp.Success)
|
||||
@@ -362,14 +372,14 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
//현재까지 노드에서 목적지까지의 방향이 일치하면 그대로 사용한다.
|
||||
bool temp3ok = false;
|
||||
var TempCheck3 = _basicPathfinder.FindPathAStar(combinedResult.Path.Last().NodeId, targetNode.NodeId);
|
||||
if (TempCheck3.Path.First().NodeId.Equals(combinedResult.Path.Last().NodeId))
|
||||
var TempCheck3 = _basicPathfinder.FindPathAStar(combinedResult.Path.Last().Id, targetNode.Id);
|
||||
if (TempCheck3.Path.First().Id.Equals(combinedResult.Path.Last().Id))
|
||||
{
|
||||
if (targetNode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
|
||||
if (tnode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
|
||||
{
|
||||
temp3ok = true;
|
||||
}
|
||||
else if (targetNode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
|
||||
else if (tnode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
|
||||
{
|
||||
temp3ok = true;
|
||||
}
|
||||
@@ -381,11 +391,11 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
if (temp3ok == false)
|
||||
{
|
||||
//목적지와 방향이 맞지 않다. 그러므로 대체노드를 추가로 더 찾아야한다.
|
||||
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
|
||||
combinedResult.Path[combinedResult.Path.Count - 2].NodeId,
|
||||
path2.Path[1].NodeId);
|
||||
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.Id,
|
||||
combinedResult.Path[combinedResult.Path.Count - 2].Id,
|
||||
path2.Path[1].Id);
|
||||
|
||||
var pathToTemp2 = _basicPathfinder.FindPathAStar(JunctionInPath.NodeId, tempNode2.NodeId);
|
||||
var pathToTemp2 = _basicPathfinder.FindPathAStar(JunctionInPath.Id, tempNode2.Id);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection);
|
||||
else MakeDetailData(pathToTemp2, ReverseDirection);
|
||||
|
||||
@@ -400,7 +410,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection;
|
||||
}
|
||||
|
||||
var pathToTemp3 = _basicPathfinder.FindPathAStar(tempNode2.NodeId, JunctionInPath.NodeId);
|
||||
var pathToTemp3 = _basicPathfinder.FindPathAStar(tempNode2.Id, JunctionInPath.Id);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection);
|
||||
else MakeDetailData(pathToTemp3, currentDirection);
|
||||
|
||||
@@ -420,12 +430,15 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
|
||||
/// </summary>
|
||||
/// <param name="path1"></param>
|
||||
/// <param name="currentDirection"></param>
|
||||
private void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
|
||||
public void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
|
||||
{
|
||||
if (path1.Success && path1.Path != null && path1.Path.Count > 0)
|
||||
{
|
||||
@@ -433,9 +446,9 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
for (int i = 0; i < path1.Path.Count; i++)
|
||||
{
|
||||
var node = path1.Path[i];
|
||||
string nodeId = node.NodeId;
|
||||
string RfidId = node.RfidId;
|
||||
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null;
|
||||
string nodeId = node.Id;
|
||||
var RfidId = node.RfidId;
|
||||
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
|
||||
|
||||
// 노드 정보 생성 (현재 방향 유지)
|
||||
var nodeInfo = new NodeMotorInfo(i + 1,
|
||||
@@ -445,6 +458,13 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
MagnetDirection.Straight
|
||||
);
|
||||
|
||||
// [Speed Control] MapNode의 속도 설정 적용
|
||||
var mapNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (mapNode != null)
|
||||
{
|
||||
nodeInfo.Speed = mapNode.SpeedLimit;
|
||||
}
|
||||
|
||||
detailedPath1.Add(nodeInfo);
|
||||
}
|
||||
|
||||
@@ -465,13 +485,13 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
for (int i = 0; i < path1.DetailedPath.Count; i++)
|
||||
{
|
||||
var detailPath = path1.DetailedPath[i];
|
||||
string nodeId = path1.Path[i].NodeId;
|
||||
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null;
|
||||
string nodeId = path1.Path[i].Id;
|
||||
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
|
||||
|
||||
// 마그넷 방향 계산 (3개 이상 연결된 교차로에서만 좌/우 가중치 적용)
|
||||
if (i > 0 && nextNodeId != null)
|
||||
{
|
||||
string prevNodeId = path1.Path[i - 1].NodeId;
|
||||
string prevNodeId = path1.Path[i - 1].Id;
|
||||
if (path1.DetailedPath[i - 1].MotorDirection != detailPath.MotorDirection)
|
||||
detailPath.MagnetDirection = MagnetDirection.Straight;
|
||||
else
|
||||
|
||||
@@ -91,14 +91,14 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
// 직접 경로에 갈림길이 포함된 경우 그 갈림길에서 방향 전환
|
||||
foreach (var node in directPath2.Path.Skip(1).Take(directPath2.Path.Count - 2)) // 시작과 끝 제외
|
||||
{
|
||||
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.NodeId);
|
||||
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.Id);
|
||||
if (junctionInfo != null && junctionInfo.IsJunction)
|
||||
{
|
||||
// 간단한 방향 전환: 직접 경로 사용하되 방향 전환 노드 표시
|
||||
return DirectionChangePlan.CreateSuccess(
|
||||
directPath2.Path,
|
||||
node.NodeId,
|
||||
$"갈림길 {node.NodeId}에서 방향 전환: {currentDirection} → {requiredDirection}"
|
||||
node.Id,
|
||||
$"갈림길 {node.Id}에서 방향 전환: {currentDirection} → {requiredDirection}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -165,14 +165,14 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
{
|
||||
foreach (var node in directPath.Path.Skip(2)) // 시작점과 다음 노드는 제외
|
||||
{
|
||||
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.NodeId);
|
||||
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.Id);
|
||||
if (junctionInfo != null && junctionInfo.IsJunction)
|
||||
{
|
||||
// 직진 경로상에서는 더 엄격한 조건 적용
|
||||
if (!suitableJunctions.Contains(node.NodeId) &&
|
||||
HasMultipleExitOptions(node.NodeId))
|
||||
if (!suitableJunctions.Contains(node.Id) &&
|
||||
HasMultipleExitOptions(node.Id))
|
||||
{
|
||||
suitableJunctions.Add(node.NodeId);
|
||||
suitableJunctions.Add(node.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,7 +226,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// </summary>
|
||||
private List<string> GetAllConnectedNodes(string nodeId)
|
||||
{
|
||||
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node == null) return new List<string>();
|
||||
|
||||
var connected = new HashSet<string>();
|
||||
@@ -236,16 +236,16 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
{
|
||||
if (connectedNode != null)
|
||||
{
|
||||
connected.Add(connectedNode.NodeId);
|
||||
connected.Add(connectedNode.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// 역방향 연결
|
||||
foreach (var otherNode in _mapNodes)
|
||||
{
|
||||
if (otherNode.NodeId != nodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == nodeId))
|
||||
if (otherNode.Id != nodeId && otherNode.ConnectedMapNodes.Any(n => n.Id == nodeId))
|
||||
{
|
||||
connected.Add(otherNode.NodeId);
|
||||
connected.Add(otherNode.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
string actualDirectionChangeNode = FindActualDirectionChangeNode(changePath, junctionNodeId);
|
||||
|
||||
string description = $"갈림길 {GetDisplayName(junctionNodeId)}를 통해 {GetDisplayName(actualDirectionChangeNode)}에서 방향 전환: {currentDirection} → {requiredDirection}";
|
||||
System.Diagnostics.Debug.WriteLine($"[DirectionChangePlanner] ✅ 유효한 방향전환 경로: {string.Join(" → ", changePath.Select(n => n.NodeId))}");
|
||||
System.Diagnostics.Debug.WriteLine($"[DirectionChangePlanner] ✅ 유효한 방향전환 경로: {string.Join(" → ", changePath.Select(n => n.Id))}");
|
||||
return DirectionChangePlan.CreateSuccess(changePath, actualDirectionChangeNode, description);
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
// 2. 인근 갈림길을 통한 우회인지, 직진 경로상 갈림길인지 판단
|
||||
var directPath = _pathfinder.FindPathAStar(startNodeId, targetNodeId);
|
||||
bool isNearbyDetour = !directPath.Success || !directPath.Path.Any(n => n.NodeId == junctionNodeId);
|
||||
bool isNearbyDetour = !directPath.Success || !directPath.Path.Any(n => n.Id == junctionNodeId);
|
||||
|
||||
if (isNearbyDetour)
|
||||
{
|
||||
@@ -376,17 +376,17 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
if (currentDirection != requiredDirection)
|
||||
{
|
||||
string fromNodeId = toJunctionPath.Path.Count >= 2 ?
|
||||
toJunctionPath.Path[toJunctionPath.Path.Count - 2].NodeId : startNodeId;
|
||||
toJunctionPath.Path[toJunctionPath.Path.Count - 2].Id : startNodeId;
|
||||
|
||||
var changeSequence = GenerateDirectionChangeSequence(junctionNodeId, fromNodeId, currentDirection, requiredDirection);
|
||||
if (changeSequence.Count > 1)
|
||||
{
|
||||
fullPath.AddRange(changeSequence.Skip(1).Select(nodeId => _mapNodes.FirstOrDefault(n => n.NodeId == nodeId)).Where(n => n != null));
|
||||
fullPath.AddRange(changeSequence.Skip(1).Select(nodeId => _mapNodes.FirstOrDefault(n => n.Id == nodeId)).Where(n => n != null));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 갈림길에서 목표점까지의 경로
|
||||
string lastNode = fullPath.LastOrDefault()?.NodeId ?? junctionNodeId;
|
||||
string lastNode = fullPath.LastOrDefault()?.Id ?? junctionNodeId;
|
||||
var fromJunctionPath = _pathfinder.FindPathAStar(lastNode, targetNodeId);
|
||||
if (fromJunctionPath.Success && fromJunctionPath.Path.Count > 1)
|
||||
{
|
||||
@@ -461,8 +461,8 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
// 왔던 길(excludeNodeId)를 제외한 노드 중에서 최적의 우회 노드 선택
|
||||
// 우선순위: 1) 막다른 길이 아닌 노드 (우회 후 복귀 가능) 2) 직진방향 3) 목적지 방향
|
||||
|
||||
var junctionNode = _mapNodes.FirstOrDefault(n => n.NodeId == junctionNodeId);
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == excludeNodeId);
|
||||
var junctionNode = _mapNodes.FirstOrDefault(n => n.Id == junctionNodeId);
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.Id == excludeNodeId);
|
||||
|
||||
if (junctionNode == null || fromNode == null)
|
||||
return availableNodes.FirstOrDefault();
|
||||
@@ -478,7 +478,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
{
|
||||
if (nodeId == excludeNodeId) continue; // 왔던 길 제외
|
||||
|
||||
var candidateNode = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
var candidateNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (candidateNode == null) continue;
|
||||
|
||||
// 갈림길에서 후보 노드로의 방향 벡터 계산 (junctionNode → candidateNode)
|
||||
@@ -561,11 +561,11 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
return junctionNodeId; // 기본값으로 갈림길 반환
|
||||
|
||||
// 갈림길이 두 번 나타나는 위치 찾기
|
||||
int firstJunctionIndex = changePath.FindIndex(n => n.NodeId == junctionNodeId);
|
||||
int firstJunctionIndex = changePath.FindIndex(n => n.Id == junctionNodeId);
|
||||
int lastJunctionIndex = -1;
|
||||
for (int i = changePath.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (changePath[i].NodeId == junctionNodeId)
|
||||
if (changePath[i].Id == junctionNodeId)
|
||||
{
|
||||
lastJunctionIndex = i;
|
||||
break;
|
||||
@@ -577,7 +577,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
firstJunctionIndex != lastJunctionIndex && lastJunctionIndex - firstJunctionIndex == 2)
|
||||
{
|
||||
// 첫 번째와 두 번째 갈림길 사이에 있는 노드가 실제 방향전환 노드
|
||||
string detourNode = changePath[firstJunctionIndex + 1].NodeId;
|
||||
string detourNode = changePath[firstJunctionIndex + 1].Id;
|
||||
return detourNode;
|
||||
}
|
||||
|
||||
@@ -647,11 +647,11 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
}
|
||||
|
||||
string errorMessage = $"되돌아가기 패턴 검출 ({backtrackingPatterns.Count}개): {string.Join(", ", issues)}";
|
||||
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 경로: {string.Join(" → ", path.Select(n => n.NodeId))}");
|
||||
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 경로: {string.Join(" → ", path.Select(n => n.Id))}");
|
||||
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 되돌아가기 패턴: {errorMessage}");
|
||||
|
||||
return PathValidationResult.CreateInvalidWithBacktracking(
|
||||
path.Select(n => n.NodeId).ToList(), backtrackingPatterns, startNodeId, "", junctionNodeId, errorMessage);
|
||||
path.Select(n => n.Id).ToList(), backtrackingPatterns, startNodeId, "", junctionNodeId, errorMessage);
|
||||
}
|
||||
|
||||
// 2. 연속된 중복 노드 검증
|
||||
@@ -670,13 +670,13 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
}
|
||||
|
||||
// 4. 갈림길 포함 여부 검증
|
||||
if (!path.Any(n => n.NodeId == junctionNodeId))
|
||||
if (!path.Any(n => n.Id == junctionNodeId))
|
||||
{
|
||||
return PathValidationResult.CreateInvalid(startNodeId, "", $"갈림길 {junctionNodeId}이 경로에 포함되지 않음");
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[PathValidation] ✅ 유효한 경로: {string.Join(" → ", path.Select(n => n.NodeId))}");
|
||||
return PathValidationResult.CreateValid(path.Select(n => n.NodeId).ToList(), startNodeId, "", junctionNodeId);
|
||||
System.Diagnostics.Debug.WriteLine($"[PathValidation] ✅ 유효한 경로: {string.Join(" → ", path.Select(n => n.Id))}");
|
||||
return PathValidationResult.CreateValid(path.Select(n => n.Id).ToList(), startNodeId, "", junctionNodeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -688,9 +688,9 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
for (int i = 0; i < path.Count - 2; i++)
|
||||
{
|
||||
string nodeA = path[i].NodeId;
|
||||
string nodeB = path[i + 1].NodeId;
|
||||
string nodeC = path[i + 2].NodeId;
|
||||
string nodeA = path[i].Id;
|
||||
string nodeB = path[i + 1].Id;
|
||||
string nodeC = path[i + 2].Id;
|
||||
|
||||
// A → B → A 패턴 검출
|
||||
if (nodeA == nodeC && nodeA != nodeB)
|
||||
@@ -712,9 +712,9 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
for (int i = 0; i < path.Count - 1; i++)
|
||||
{
|
||||
if (path[i].NodeId == path[i + 1].NodeId)
|
||||
if (path[i].Id == path[i + 1].Id)
|
||||
{
|
||||
duplicates.Add(path[i].NodeId);
|
||||
duplicates.Add(path[i].Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,12 +728,12 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
{
|
||||
for (int i = 0; i < path.Count - 1; i++)
|
||||
{
|
||||
string currentNode = path[i].NodeId;
|
||||
string nextNode = path[i + 1].NodeId;
|
||||
string currentNode = path[i].Id;
|
||||
string nextNode = path[i + 1].Id;
|
||||
|
||||
// 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedMapNodes 리스트 사용)
|
||||
var currentMapNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNode);
|
||||
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.NodeId == nextNode))
|
||||
var currentMapNode = _mapNodes.FirstOrDefault(n => n.Id == currentNode);
|
||||
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.Id == nextNode))
|
||||
{
|
||||
return PathValidationResult.CreateInvalid(currentNode, nextNode, $"노드 {currentNode}와 {nextNode} 사이에 연결이 없음");
|
||||
}
|
||||
@@ -769,10 +769,10 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// <returns>표시할 이름</returns>
|
||||
private string GetDisplayName(string nodeId)
|
||||
{
|
||||
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
if (node != null && !string.IsNullOrEmpty(node.RfidId))
|
||||
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node != null && node.HasRfid())
|
||||
{
|
||||
return node.RfidId;
|
||||
return node.RfidId.ToString("0000");
|
||||
}
|
||||
return $"({nodeId})";
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
// 연결된 노드 중 현재 노드가 아닌 것들만 필터링
|
||||
var candidateNodes = allNodes.Where(n =>
|
||||
connectedNodeIds.Contains(n.NodeId) && n.NodeId != currentNode.NodeId
|
||||
connectedNodeIds.Contains(n.Id) && n.Id != currentNode.Id
|
||||
).ToList();
|
||||
|
||||
if (candidateNodes.Count == 0)
|
||||
@@ -88,7 +88,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
if (movementLength < 0.001f) // 거의 이동하지 않음
|
||||
{
|
||||
return candidateNodes[0].NodeId; // 첫 번째 연결 노드 반환
|
||||
return candidateNodes[0].Id; // 첫 번째 연결 노드 반환
|
||||
}
|
||||
|
||||
var normalizedMovement = new PointF(
|
||||
@@ -138,7 +138,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
|
||||
// 가장 높은 점수를 가진 노드 반환
|
||||
var bestCandidate = scoredCandidates.OrderByDescending(x => x.score).First();
|
||||
return bestCandidate.node.NodeId;
|
||||
return bestCandidate.node.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -40,13 +40,18 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// <summary>
|
||||
/// RFID Value
|
||||
/// </summary>
|
||||
public string RfidId { get; set; }
|
||||
public ushort RfidId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 해당 노드에서의 모터방향
|
||||
/// </summary>
|
||||
public AgvDirection MotorDirection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 해당 노드에서의 제한 속도
|
||||
/// </summary>
|
||||
public SpeedLevel Speed { get; set; } = SpeedLevel.M;
|
||||
|
||||
/// <summary>
|
||||
/// 마그넷 센서 방향 제어 (갈림길 처리용)
|
||||
/// </summary>
|
||||
@@ -82,7 +87,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// </summary>
|
||||
public string SpecialActionDescription { get; set; }
|
||||
|
||||
public NodeMotorInfo(int seqno,string nodeId,string rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight)
|
||||
public NodeMotorInfo(int seqno,string nodeId,ushort rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight)
|
||||
{
|
||||
seq = seqno;
|
||||
NodeId = nodeId;
|
||||
@@ -103,7 +108,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var result = $"{RfidId}[{NodeId}]:{MotorDirection}";
|
||||
var result = $"R{RfidId}[N{NodeId}]:{MotorDirection}";
|
||||
|
||||
// 마그넷 방향이 직진이 아닌 경우 표시
|
||||
if (MagnetDirection != MagnetDirection.Straight)
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace AGVNavigationCore.Utils
|
||||
List<MapNode> candidateNodes = new List<MapNode>();
|
||||
if (prevDirection == direction)
|
||||
{
|
||||
candidateNodes = connectedMapNodes.Where(n => n.NodeId != prevNode.NodeId).ToList();
|
||||
candidateNodes = connectedMapNodes.Where(n => n.Id != prevNode.Id).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -112,9 +112,9 @@ namespace AGVNavigationCore.Utils
|
||||
Console.WriteLine(
|
||||
$"\n[GetNextNodeByDirection] ========== 다음 노드 선택 시작 ==========");
|
||||
Console.WriteLine(
|
||||
$" 현재노드: {currentNode.RfidId}[{currentNode.NodeId}]({currentNode.Position.X:F1}, {currentNode.Position.Y:F1})");
|
||||
$" 현재노드: {currentNode.RfidId}[{currentNode.Id}]({currentNode.Position.X:F1}, {currentNode.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
$" 이전노드: {prevNode.RfidId}[{prevNode.NodeId}]({prevNode.Position.X:F1}, {prevNode.Position.Y:F1})");
|
||||
$" 이전노드: {prevNode.RfidId}[{prevNode.Id}]({prevNode.Position.X:F1}, {prevNode.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
$" 이동벡터: ({movementVector.X:F2}, {movementVector.Y:F2}) → 정규화: ({normalizedMovement.X:F3}, {normalizedMovement.Y:F3})");
|
||||
Console.WriteLine(
|
||||
@@ -159,7 +159,7 @@ namespace AGVNavigationCore.Utils
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"\n [후보] {candidate.RfidId}[{candidate.NodeId}]({candidate.Position.X:F1}, {candidate.Position.Y:F1})");
|
||||
$"\n [후보] {candidate.RfidId}[{candidate.Id}]({candidate.Position.X:F1}, {candidate.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
$" 벡터: ({toNextVector.X:F2}, {toNextVector.Y:F2}), 길이: {toNextLength:F2}");
|
||||
Console.WriteLine(
|
||||
@@ -204,7 +204,7 @@ namespace AGVNavigationCore.Utils
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"\n 최종선택: {bestNode?.RfidId ?? "null"}[{bestNode?.NodeId ?? "null"}] (점수: {bestScore:F4})");
|
||||
$"\n 최종선택: {bestNode?.RfidId ?? 0}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})");
|
||||
Console.WriteLine(
|
||||
$"[GetNextNodeByDirection] ========== 다음 노드 선택 종료 ==========\n");
|
||||
|
||||
@@ -445,15 +445,15 @@ namespace AGVNavigationCore.Utils
|
||||
// 선택 이유 생성
|
||||
if (prevMotorDirection.HasValue && direction == prevMotorDirection)
|
||||
{
|
||||
reason = $"모터 방향 일관성 유지 ({direction}) → {candidate.NodeId}";
|
||||
reason = $"모터 방향 일관성 유지 ({direction}) → {candidate.Id}";
|
||||
}
|
||||
else if (prevMotorDirection.HasValue)
|
||||
{
|
||||
reason = $"모터 방향 변경 ({prevMotorDirection} → {direction}) → {candidate.NodeId}";
|
||||
reason = $"모터 방향 변경 ({prevMotorDirection} → {direction}) → {candidate.Id}";
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = $"방향 기반 선택 ({direction}) → {candidate.NodeId}";
|
||||
reason = $"방향 기반 선택 ({direction}) → {candidate.Id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,22 +11,22 @@ namespace AGVNavigationCore.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// DirectionalPathfinder 테스트 클래스
|
||||
/// NewMap.agvmap을 로드하여 방향별 다음 노드를 검증
|
||||
/// NewMap.json 로드하여 방향별 다음 노드를 검증
|
||||
/// </summary>
|
||||
public class DirectionalPathfinderTest
|
||||
{
|
||||
private List<MapNode> _allNodes;
|
||||
private Dictionary<string, MapNode> _nodesByRfidId;
|
||||
private Dictionary<ushort, MapNode> _nodesByRfidId;
|
||||
private AGVDirectionCalculator _calculator;
|
||||
|
||||
public DirectionalPathfinderTest()
|
||||
{
|
||||
_nodesByRfidId = new Dictionary<string, MapNode>();
|
||||
_nodesByRfidId = new Dictionary<ushort, MapNode>();
|
||||
_calculator = new AGVDirectionCalculator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NewMap.agvmap 파일 로드
|
||||
/// NewMap.json 파일 로드
|
||||
/// </summary>
|
||||
public bool LoadMapFile(string filePath)
|
||||
{
|
||||
@@ -52,7 +52,7 @@ namespace AGVNavigationCore.Utils
|
||||
// RFID ID로 인덱싱
|
||||
foreach (var node in _allNodes)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(node.RfidId))
|
||||
if (node.HasRfid())
|
||||
{
|
||||
_nodesByRfidId[node.RfidId] = node;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ namespace AGVNavigationCore.Utils
|
||||
/// <summary>
|
||||
/// 테스트: RFID 번호로 노드를 찾고, 다음 노드를 계산
|
||||
/// </summary>
|
||||
public void TestDirectionalMovement(string previousRfidId, string currentRfidId, AgvDirection direction)
|
||||
public void TestDirectionalMovement(ushort previousRfidId, ushort currentRfidId, AgvDirection direction)
|
||||
{
|
||||
Console.WriteLine($"\n========================================");
|
||||
Console.WriteLine($"테스트: {previousRfidId} → {currentRfidId} (방향: {direction})");
|
||||
@@ -90,8 +90,8 @@ namespace AGVNavigationCore.Utils
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"이전 노드: {previousNode.NodeId} (RFID: {previousNode.RfidId}) - 위치: {previousNode.Position}");
|
||||
Console.WriteLine($"현재 노드: {currentNode.NodeId} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
|
||||
Console.WriteLine($"이전 노드: {previousNode.Id} (RFID: {previousNode.RfidId}) - 위치: {previousNode.Position}");
|
||||
Console.WriteLine($"현재 노드: {currentNode.Id} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
|
||||
Console.WriteLine($"이동 벡터: ({currentNode.Position.X - previousNode.Position.X}, " +
|
||||
$"{currentNode.Position.Y - previousNode.Position.Y})");
|
||||
|
||||
@@ -111,10 +111,10 @@ namespace AGVNavigationCore.Utils
|
||||
}
|
||||
|
||||
// 다음 노드 정보 출력
|
||||
var nextNode = _allNodes.FirstOrDefault(n => n.NodeId == nextNodeId);
|
||||
var nextNode = _allNodes.FirstOrDefault(n => n.Id == nextNodeId);
|
||||
if (nextNode != null)
|
||||
{
|
||||
Console.WriteLine($"✓ 다음 노드: {nextNode.NodeId} (RFID: {nextNode.RfidId}) - 위치: {nextNode.Position}");
|
||||
Console.WriteLine($"✓ 다음 노드: {nextNode.Id} (RFID: {nextNode.RfidId}) - 위치: {nextNode.Position}");
|
||||
Console.WriteLine($" ├─ 노드 타입: {GetNodeTypeName(nextNode.Type)}");
|
||||
Console.WriteLine($" └─ 연결된 노드: {string.Join(", ", nextNode.ConnectedNodes)}");
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace AGVNavigationCore.Utils
|
||||
Console.WriteLine("\n========== 모든 노드 정보 ==========");
|
||||
foreach (var node in _allNodes.OrderBy(n => n.RfidId))
|
||||
{
|
||||
Console.WriteLine($"{node.RfidId:D3} → {node.NodeId} ({GetNodeTypeName(node.Type)})");
|
||||
Console.WriteLine($"{node.RfidId:D3} → {node.Id} ({GetNodeTypeName(node.Type)})");
|
||||
Console.WriteLine($" 위치: {node.Position}, 연결: {string.Join(", ", node.ConnectedNodes)}");
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ namespace AGVNavigationCore.Utils
|
||||
/// <summary>
|
||||
/// 특정 RFID 노드의 상세 정보 출력
|
||||
/// </summary>
|
||||
public void PrintNodeInfo(string rfidId)
|
||||
public void PrintNodeInfo(ushort rfidId)
|
||||
{
|
||||
if (!_nodesByRfidId.TryGetValue(rfidId, out var node))
|
||||
{
|
||||
@@ -149,8 +149,9 @@ namespace AGVNavigationCore.Utils
|
||||
}
|
||||
|
||||
Console.WriteLine($"\n========== RFID {rfidId} 상세 정보 ==========");
|
||||
Console.WriteLine($"노드 ID: {node.NodeId}");
|
||||
Console.WriteLine($"이름: {node.Name}");
|
||||
Console.WriteLine($"노드 ID: {node.Id}");
|
||||
Console.WriteLine($"RFID: {node.RfidId}");
|
||||
Console.WriteLine($"ALIAS: {node.AliasName}");
|
||||
Console.WriteLine($"위치: {node.Position}");
|
||||
Console.WriteLine($"타입: {GetNodeTypeName(node.Type)}");
|
||||
Console.WriteLine($"TurnLeft/Right/교차로 : {(node.CanTurnLeft ? "O":"X")}/{(node.CanTurnRight ? "O" : "X")}/{(node.DisableCross ? "X" : "O")}");
|
||||
@@ -165,7 +166,7 @@ namespace AGVNavigationCore.Utils
|
||||
{
|
||||
foreach (var connectedId in node.ConnectedNodes)
|
||||
{
|
||||
var connectedNode = _allNodes.FirstOrDefault(n => n.NodeId == connectedId);
|
||||
var connectedNode = _allNodes.FirstOrDefault(n => n.Id == connectedId);
|
||||
if (connectedNode != null)
|
||||
{
|
||||
Console.WriteLine($" → {connectedId} (RFID: {connectedNode.RfidId}) - 위치: {connectedNode.Position}");
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace AGVNavigationCore.Utils
|
||||
return DockingValidationResult.CreateNotRequired();
|
||||
}
|
||||
if (pathResult.DetailedPath.Any() == false && pathResult.Path.Any() && pathResult.Path.Count == 2 &&
|
||||
pathResult.Path[0].NodeId == pathResult.Path[1].NodeId)
|
||||
pathResult.Path[0].Id == pathResult.Path[1].Id)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 검증 불필요: 동일포인트");
|
||||
return DockingValidationResult.CreateNotRequired();
|
||||
@@ -44,7 +44,7 @@ namespace AGVNavigationCore.Utils
|
||||
return DockingValidationResult.CreateNotRequired();
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {LastNode.NodeId} 타입:{LastNode.Type} ({(int)LastNode.Type})");
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {LastNode.Id} 타입:{LastNode.Type} ({(int)LastNode.Type})");
|
||||
|
||||
//detail 경로 이동 예측 검증
|
||||
for (int i = 0; i < pathResult.DetailedPath.Count - 1; i++)
|
||||
@@ -52,8 +52,8 @@ namespace AGVNavigationCore.Utils
|
||||
var curNodeId = pathResult.DetailedPath[i].NodeId;
|
||||
var nextNodeId = pathResult.DetailedPath[i + 1].NodeId;
|
||||
|
||||
var curNode = mapNodes?.FirstOrDefault(n => n.NodeId == curNodeId);
|
||||
var nextNode = mapNodes?.FirstOrDefault(n => n.NodeId == nextNodeId);
|
||||
var curNode = mapNodes?.FirstOrDefault(n => n.Id == curNodeId);
|
||||
var nextNode = mapNodes?.FirstOrDefault(n => n.Id == nextNodeId);
|
||||
|
||||
if (curNode != null && nextNode != null)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ namespace AGVNavigationCore.Utils
|
||||
else
|
||||
{
|
||||
var prevNodeId = pathResult.DetailedPath[i - 1].NodeId;
|
||||
prevNode = mapNodes?.FirstOrDefault(n => n.NodeId == prevNodeId);
|
||||
prevNode = mapNodes?.FirstOrDefault(n => n.Id == prevNodeId);
|
||||
prevDir = pathResult.DetailedPath[i - 1].MotorDirection;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace AGVNavigationCore.Utils
|
||||
Console.WriteLine(
|
||||
$"\n[ValidateDockingDirection] 경로 검증 단계 {i}:");
|
||||
Console.WriteLine(
|
||||
$" 이전→현재→다음: {prevNode.NodeId}({prevNode.RfidId}) → {curNode.NodeId}({curNode.RfidId}) → {nextNode.NodeId}({nextNode.RfidId})");
|
||||
$" 이전→현재→다음: {prevNode.Id}({prevNode.RfidId}) → {curNode.Id}({curNode.RfidId}) → {nextNode.Id}({nextNode.RfidId})");
|
||||
Console.WriteLine(
|
||||
$" 현재 노드 위치: ({curNode.Position.X:F1}, {curNode.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
@@ -96,16 +96,16 @@ namespace AGVNavigationCore.Utils
|
||||
);
|
||||
|
||||
Console.WriteLine(
|
||||
$" [예상] GetNextNodeByDirection 결과: {expectedNextNode?.NodeId ?? "null"}");
|
||||
$" [예상] GetNextNodeByDirection 결과: {expectedNextNode?.Id ?? "null"}");
|
||||
Console.WriteLine(
|
||||
$" [실제] DetailedPath 다음 노드: {nextNode.RfidId}[{nextNode.NodeId}]");
|
||||
$" [실제] DetailedPath 다음 노드: {nextNode.RfidId}[{nextNode.Id}]");
|
||||
|
||||
if (expectedNextNode != null && !expectedNextNode.NodeId.Equals(nextNode.NodeId))
|
||||
if (expectedNextNode != null && !expectedNextNode.Id.Equals(nextNode.Id))
|
||||
{
|
||||
string error =
|
||||
$"[DockingValidator] ⚠️ 경로 방향 불일치" +
|
||||
$"\n현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.NodeId ?? string.Empty)}] " +
|
||||
$"\n예상다음={expectedNextNode.RfidId}[{expectedNextNode.NodeId}] 실제다음={nextNode.RfidId}[{nextNodeId}]";
|
||||
$"\n현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.Id ?? string.Empty)}] " +
|
||||
$"\n예상다음={expectedNextNode.RfidId}[{expectedNextNode.Id}] 실제다음={nextNode.RfidId}[{nextNodeId}]";
|
||||
Console.WriteLine(
|
||||
$"[ValidateDockingDirection] ❌ 경로 방향 불일치 검출!");
|
||||
Console.WriteLine(
|
||||
@@ -118,7 +118,7 @@ namespace AGVNavigationCore.Utils
|
||||
$" 현재→실제: ({(nextNode.Position.X - curNode.Position.X):F2}, {(nextNode.Position.Y - curNode.Position.Y):F2})");
|
||||
Console.WriteLine($"[ValidateDockingDirection] 에러메시지: {error}");
|
||||
return DockingValidationResult.CreateInvalid(
|
||||
LastNode.NodeId,
|
||||
LastNode.Id,
|
||||
LastNode.Type,
|
||||
pathResult.DetailedPath[i].MotorDirection,
|
||||
pathResult.DetailedPath[i].MotorDirection,
|
||||
@@ -150,12 +150,12 @@ namespace AGVNavigationCore.Utils
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 필요한 도킹 방향: {requiredDirection}");
|
||||
|
||||
var LastNodeInfo = pathResult.DetailedPath.Last();
|
||||
if (LastNodeInfo.NodeId != LastNode.NodeId)
|
||||
if (LastNodeInfo.NodeId != LastNode.Id)
|
||||
{
|
||||
string error = $"마지막 노드의 도킹방향과 경로정보의 노드ID 불일치: 필요={LastNode.NodeId}, 계산됨={LastNodeInfo.NodeId }";
|
||||
string error = $"마지막 노드의 도킹방향과 경로정보의 노드ID 불일치: 필요={LastNode.Id}, 계산됨={LastNodeInfo.NodeId }";
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}");
|
||||
return DockingValidationResult.CreateInvalid(
|
||||
LastNode.NodeId,
|
||||
LastNode.Id,
|
||||
LastNode.Type,
|
||||
requiredDirection,
|
||||
LastNodeInfo.MotorDirection,
|
||||
@@ -167,7 +167,7 @@ namespace AGVNavigationCore.Utils
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ✅ 도킹 검증 성공");
|
||||
return DockingValidationResult.CreateValid(
|
||||
LastNode.NodeId,
|
||||
LastNode.Id,
|
||||
LastNode.Type,
|
||||
requiredDirection,
|
||||
LastNodeInfo.MotorDirection);
|
||||
@@ -177,7 +177,7 @@ namespace AGVNavigationCore.Utils
|
||||
string error = $"도킹 방향 불일치: 필요={GetDirectionText(requiredDirection)}, 계산됨={GetDirectionText(LastNodeInfo.MotorDirection)}";
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}");
|
||||
return DockingValidationResult.CreateInvalid(
|
||||
LastNode.NodeId,
|
||||
LastNode.Id,
|
||||
LastNode.Type,
|
||||
requiredDirection,
|
||||
LastNodeInfo.MotorDirection,
|
||||
@@ -264,7 +264,7 @@ namespace AGVNavigationCore.Utils
|
||||
var deltaY = lastNode.Position.Y - secondLastNode.Position.Y;
|
||||
var distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 마지막 구간: {secondLastNode.NodeId} → {lastNode.NodeId}, 벡터: ({deltaX}, {deltaY}), 거리: {distance:F2}");
|
||||
System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 마지막 구간: {secondLastNode.Id} → {lastNode.Id}, 벡터: ({deltaX}, {deltaY}), 거리: {distance:F2}");
|
||||
|
||||
// 이동 거리가 매우 작으면 현재 방향 유지
|
||||
if (distance < 1.0)
|
||||
|
||||
@@ -28,10 +28,10 @@ namespace AGVNavigationCore.Utils
|
||||
Console.WriteLine("================================================\n");
|
||||
|
||||
// 테스트 노드 생성
|
||||
var node001 = new MapNode { NodeId = "N001", RfidId = "001", Position = new Point(65, 229), ConnectedNodes = new List<string> { "N002" } };
|
||||
var node002 = new MapNode { NodeId = "N002", RfidId = "002", Position = new Point(206, 244), ConnectedNodes = new List<string> { "N001", "N003" } };
|
||||
var node003 = new MapNode { NodeId = "N003", RfidId = "003", Position = new Point(278, 278), ConnectedNodes = new List<string> { "N002", "N004" } };
|
||||
var node004 = new MapNode { NodeId = "N004", RfidId = "004", Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } };
|
||||
var node001 = new MapNode { Id = "N001", RfidId = 001, Position = new Point(65, 229), ConnectedNodes = new List<string> { "N002" } };
|
||||
var node002 = new MapNode { Id = "N002", RfidId = 002, Position = new Point(206, 244), ConnectedNodes = new List<string> { "N001", "N003" } };
|
||||
var node003 = new MapNode { Id = "N003", RfidId = 003, Position = new Point(278, 278), ConnectedNodes = new List<string> { "N002", "N004" } };
|
||||
var node004 = new MapNode { Id = "N004", RfidId = 004, Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } };
|
||||
|
||||
var allNodes = new List<MapNode> { node001, node002, node003, node004 };
|
||||
|
||||
@@ -114,8 +114,8 @@ namespace AGVNavigationCore.Utils
|
||||
AgvDirection motorDir = currentMotorDirection ?? direction;
|
||||
|
||||
Console.WriteLine($"설명: {description}");
|
||||
Console.WriteLine($"이전 위치: {prevPos} (RFID: {allNodes.First(n => n.Position == prevPos)?.RfidId ?? "?"})");
|
||||
Console.WriteLine($"현재 노드: {currentNode.NodeId} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
|
||||
Console.WriteLine($"이전 위치: {prevPos} (RFID: {allNodes.First(n => n.Position == prevPos)?.RfidId.ToString("0000") ?? "?"})");
|
||||
Console.WriteLine($"현재 노드: {currentNode.Id} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
|
||||
Console.WriteLine($"현재 모터 방향: {motorDir}");
|
||||
Console.WriteLine($"요청 방향: {direction}");
|
||||
|
||||
@@ -128,32 +128,32 @@ namespace AGVNavigationCore.Utils
|
||||
Console.WriteLine($"이동 벡터: ({movementVector.X}, {movementVector.Y})");
|
||||
|
||||
// 각 후보 노드에 대한 점수 계산
|
||||
Console.WriteLine($"\n현재 노드({currentNode.NodeId})의 ConnectedNodes: {string.Join(", ", currentNode.ConnectedNodes)}");
|
||||
Console.WriteLine($"\n현재 노드({currentNode.Id})의 ConnectedNodes: {string.Join(", ", currentNode.ConnectedNodes)}");
|
||||
Console.WriteLine($"가능한 다음 노드들:");
|
||||
|
||||
var candidateNodes = allNodes.Where(n =>
|
||||
currentNode.ConnectedNodes.Contains(n.NodeId) && n.NodeId != currentNode.NodeId
|
||||
currentNode.ConnectedNodes.Contains(n.Id) && n.Id != currentNode.Id
|
||||
).ToList();
|
||||
|
||||
foreach (var candidate in candidateNodes)
|
||||
{
|
||||
var score = CalculateScoreAndPrint(movementVector, currentNode.Position, candidate, direction);
|
||||
string isExpected = (candidate.NodeId == expectedNextNode.NodeId) ? " ← 예상 노드" : "";
|
||||
Console.WriteLine($" {candidate.NodeId} (RFID: {candidate.RfidId}) - 위치: {candidate.Position} - 점수: {score:F1}{isExpected}");
|
||||
string isExpected = (candidate.Id == expectedNextNode.Id) ? " ← 예상 노드" : "";
|
||||
Console.WriteLine($" {candidate.Id} (RFID: {candidate.RfidId}) - 위치: {candidate.Position} - 점수: {score:F1}{isExpected}");
|
||||
}
|
||||
|
||||
// 최고 점수 노드 선택
|
||||
var bestCandidate = GetBestCandidate(movementVector, currentNode.Position, candidateNodes, direction);
|
||||
|
||||
Console.WriteLine($"\n✓ 선택된 노드: {bestCandidate.NodeId} (RFID: {bestCandidate.RfidId})");
|
||||
Console.WriteLine($"\n✓ 선택된 노드: {bestCandidate.Id} (RFID: {bestCandidate.RfidId})");
|
||||
|
||||
if (bestCandidate.NodeId == expectedNextNode.NodeId)
|
||||
if (bestCandidate.Id == expectedNextNode.Id)
|
||||
{
|
||||
Console.WriteLine($"✅ 정답! ({expectedNodeIdStr})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"❌ 오답! 예상: {expectedNextNode.NodeId}, 실제: {bestCandidate.NodeId}");
|
||||
Console.WriteLine($"❌ 오답! 예상: {expectedNextNode.Id}, 실제: {bestCandidate.Id}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,14 +63,14 @@ namespace AGVNavigationCore.Utils
|
||||
// 이전 노드 제외 (되돌아가는 방향 제외)
|
||||
if (previousNode != null)
|
||||
{
|
||||
nextNodes = nextNodes.Where(n => n.NodeId != previousNode.NodeId).ToList();
|
||||
nextNodes = nextNodes.Where(n => n.Id != previousNode.Id).ToList();
|
||||
}
|
||||
|
||||
if (nextNodes.Count == 1)
|
||||
{
|
||||
// 직선 경로: 다음 노드 방향으로 예측
|
||||
targetPosition = nextNodes.First().Position;
|
||||
calculationMethod = $"전진 경로 예측 ({currentNode.NodeId}→{nextNodes.First().NodeId})";
|
||||
calculationMethod = $"전진 경로 예측 ({currentNode.Id}→{nextNodes.First().Id})";
|
||||
}
|
||||
else if (nextNodes.Count > 1)
|
||||
{
|
||||
@@ -268,7 +268,7 @@ namespace AGVNavigationCore.Utils
|
||||
|
||||
foreach (var nodeId in currentNode.ConnectedNodes)
|
||||
{
|
||||
var connectedNode = mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
var connectedNode = mapNodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (connectedNode != null)
|
||||
{
|
||||
connectedNodes.Add(connectedNode);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace AGVNavigationCore.Utils
|
||||
{
|
||||
public void RunTests()
|
||||
{
|
||||
string mapFilePath = @"C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\Data\NewMap.agvmap";
|
||||
string mapFilePath = @"C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\Data\NewMap.json";
|
||||
|
||||
var tester = new DirectionalPathfinderTest();
|
||||
|
||||
@@ -29,26 +29,26 @@ namespace AGVNavigationCore.Utils
|
||||
tester.PrintAllNodes();
|
||||
|
||||
// 테스트 시나리오 1: 001 → 002 → Forward (003 기대)
|
||||
tester.PrintNodeInfo("001");
|
||||
tester.PrintNodeInfo("002");
|
||||
tester.TestDirectionalMovement("001", "002", AgvDirection.Forward);
|
||||
tester.PrintNodeInfo(001);
|
||||
tester.PrintNodeInfo(002);
|
||||
tester.TestDirectionalMovement(001, 002, AgvDirection.Forward);
|
||||
|
||||
// 테스트 시나리오 2: 002 → 001 → Backward (000 또는 이전 기대)
|
||||
tester.TestDirectionalMovement("002", "001", AgvDirection.Backward);
|
||||
tester.TestDirectionalMovement(002, 001, AgvDirection.Backward);
|
||||
|
||||
// 테스트 시나리오 3: 002 → 003 → Forward
|
||||
tester.PrintNodeInfo("003");
|
||||
tester.TestDirectionalMovement("002", "003", AgvDirection.Forward);
|
||||
tester.PrintNodeInfo(003);
|
||||
tester.TestDirectionalMovement(002, 003, AgvDirection.Forward);
|
||||
|
||||
// 테스트 시나리오 4: 003 → 004 → Forward
|
||||
tester.PrintNodeInfo("004");
|
||||
tester.TestDirectionalMovement("003", "004", AgvDirection.Forward);
|
||||
tester.PrintNodeInfo(004);
|
||||
tester.TestDirectionalMovement(003, 004, AgvDirection.Forward);
|
||||
|
||||
// 테스트 시나리오 5: 003 → 004 → Right (030 기대)
|
||||
tester.TestDirectionalMovement("003", "004", AgvDirection.Right);
|
||||
tester.TestDirectionalMovement(003, 004, AgvDirection.Right);
|
||||
|
||||
// 테스트 시나리오 6: 004 → 003 → Backward
|
||||
tester.TestDirectionalMovement("004", "003", AgvDirection.Backward);
|
||||
tester.TestDirectionalMovement(004, 003, AgvDirection.Backward);
|
||||
|
||||
Console.WriteLine("\n\n=== 테스트 완료 ===");
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
|
||||
private UnifiedAGVCanvas _simulatorCanvas;
|
||||
private List<MapNode> _mapNodes;
|
||||
private AGVPathfinder _advancedPathfinder;
|
||||
private List<VirtualAGV> _agvList;
|
||||
private SimulationState _simulationState;
|
||||
@@ -158,7 +157,7 @@ namespace AGVSimulator.Forms
|
||||
_config = SimulatorConfig.Load();
|
||||
|
||||
// 데이터 초기화
|
||||
_mapNodes = new List<MapNode>();
|
||||
|
||||
_agvList = new List<VirtualAGV>();
|
||||
_simulationState = new SimulationState();
|
||||
_currentMapFilePath = string.Empty;
|
||||
@@ -232,7 +231,7 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
using (var openDialog = new OpenFileDialog())
|
||||
{
|
||||
openDialog.Filter = "AGV Map Files (*.agvmap)|*.agvmap|모든 파일 (*.*)|*.*";
|
||||
openDialog.Filter = "AGV Map Files (*.json)|*.json|모든 파일 (*.*)|*.*";
|
||||
openDialog.Title = "맵 파일 열기";
|
||||
|
||||
if (openDialog.ShowDialog() == DialogResult.OK)
|
||||
@@ -305,14 +304,14 @@ namespace AGVSimulator.Forms
|
||||
|
||||
private void OnAddAGV_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("먼저 맵을 로드해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var agvId = $"AGV{_agvList.Count + 1:D2}";
|
||||
var startPosition = _mapNodes.First().Position; // 첫 번째 노드에서 시작
|
||||
var startPosition = _simulatorCanvas.Nodes.First().Position; // 첫 번째 노드에서 시작
|
||||
|
||||
var newAGV = new VirtualAGV(agvId, startPosition);
|
||||
_agvList.Add(newAGV);
|
||||
@@ -396,7 +395,7 @@ namespace AGVSimulator.Forms
|
||||
|
||||
if (_advancedPathfinder == null)
|
||||
{
|
||||
_advancedPathfinder = new AGVPathfinder(_mapNodes);
|
||||
_advancedPathfinder = new AGVPathfinder(_simulatorCanvas.Nodes);
|
||||
}
|
||||
|
||||
// 현재 AGV 방향 가져오기
|
||||
@@ -420,14 +419,14 @@ namespace AGVSimulator.Forms
|
||||
// 도킹 검증이 없는 경우 추가 검증 수행
|
||||
if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired)
|
||||
{
|
||||
advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes);
|
||||
advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _simulatorCanvas.Nodes);
|
||||
}
|
||||
|
||||
//마지막대상이 버퍼라면 시퀀스처리를 해야한다
|
||||
if(targetNode.Type == NodeType.Buffer)
|
||||
if(targetNode.StationType == StationType.Buffer)
|
||||
{
|
||||
var lastDetailPath = advancedResult.DetailedPath.Last();
|
||||
if(lastDetailPath.NodeId == targetNode.NodeId) //마지막노드 재확인
|
||||
if(lastDetailPath.NodeId == targetNode.Id) //마지막노드 재확인
|
||||
{
|
||||
//버퍼에 도킹할때에는 마지막 노드에서 멈추고 시퀀스를 적용해야한다
|
||||
advancedResult.DetailedPath = advancedResult.DetailedPath.Take(advancedResult.DetailedPath.Count - 1).ToList();
|
||||
@@ -492,7 +491,7 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTargetNodeSelected(object sender, List<MapNode> selectedNodes)
|
||||
private void OnTargetNodeSelected(object sender, List<NodeBase> selectedNodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -511,9 +510,9 @@ namespace AGVSimulator.Forms
|
||||
if (selectedNode == null) return;
|
||||
|
||||
// 목적지를 선택된 노드로 설정
|
||||
SetTargetNodeInCombo(selectedNode.NodeId);
|
||||
SetTargetNodeInCombo(selectedNode.Id);
|
||||
|
||||
var displayText = GetDisplayName(selectedNode.NodeId);
|
||||
var displayText = GetDisplayName(selectedNode.Id);
|
||||
_statusLabel.Text = $"타겟계산 - 목적지: {displayText}";
|
||||
|
||||
// 자동으로 경로 계산 수행
|
||||
@@ -578,7 +577,7 @@ namespace AGVSimulator.Forms
|
||||
for (int i = 0; i < _startNodeCombo.Items.Count; i++)
|
||||
{
|
||||
var item = _startNodeCombo.Items[i].ToString();
|
||||
if (item.Contains($"[{closestNode.NodeId}]"))
|
||||
if (item.Contains($"[{closestNode.Id}]"))
|
||||
{
|
||||
_startNodeCombo.SelectedIndex = i;
|
||||
break;
|
||||
@@ -598,13 +597,13 @@ namespace AGVSimulator.Forms
|
||||
/// </summary>
|
||||
private MapNode FindClosestNode(Point position)
|
||||
{
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
|
||||
return null;
|
||||
|
||||
MapNode closestNode = null;
|
||||
double closestDistance = double.MaxValue;
|
||||
|
||||
foreach (var node in _mapNodes)
|
||||
foreach (var node in _simulatorCanvas.Nodes)
|
||||
{
|
||||
var distance = Math.Sqrt(Math.Pow(node.Position.X - position.X, 2) +
|
||||
Math.Pow(node.Position.Y - position.Y, 2));
|
||||
@@ -636,7 +635,7 @@ namespace AGVSimulator.Forms
|
||||
/// <summary>
|
||||
/// 맵 스캔 모드에서 RFID로부터 노드 생성
|
||||
/// </summary>
|
||||
private void CreateNodeFromRfidScan(string rfidId, VirtualAGV selectedAGV)
|
||||
private void CreateNodeFromRfidScan(ushort rfidId, VirtualAGV selectedAGV)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -645,11 +644,11 @@ namespace AGVSimulator.Forms
|
||||
var currentDirection = directionItem?.Direction ?? AgvDirection.Forward;
|
||||
|
||||
// 중복 RFID 확인
|
||||
var existingNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
|
||||
var existingNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId == rfidId);
|
||||
if (existingNode != null)
|
||||
{
|
||||
// 이미 존재하는 노드로 이동
|
||||
Program.WriteLine($"[맵 스캔] RFID '{rfidId}'는 이미 존재합니다 (노드: {existingNode.NodeId})");
|
||||
Program.WriteLine($"[맵 스캔] RFID '{rfidId}'는 이미 존재합니다 (노드: {existingNode.Id})");
|
||||
|
||||
// 기존 노드로 AGV 위치 설정
|
||||
_simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, existingNode, currentDirection);
|
||||
@@ -659,7 +658,7 @@ namespace AGVSimulator.Forms
|
||||
_lastNodeAddTime = DateTime.Now;
|
||||
_lastScanDirection = currentDirection; // 방향 업데이트
|
||||
|
||||
_statusLabel.Text = $"기존 노드로 이동: {existingNode.NodeId} [{GetDirectionSymbol(currentDirection)}]";
|
||||
_statusLabel.Text = $"기존 노드로 이동: {existingNode.Id} [{GetDirectionSymbol(currentDirection)}]";
|
||||
_rfidTextBox.Text = "";
|
||||
return;
|
||||
}
|
||||
@@ -720,28 +719,26 @@ namespace AGVSimulator.Forms
|
||||
var newNodeId = $"{_scanNodeCounter:D3}";
|
||||
var newNode = new MapNode
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Id = newNodeId,
|
||||
RfidId = rfidId,
|
||||
Position = new Point(newX, newY),
|
||||
Type = NodeType.Normal,
|
||||
IsActive = true,
|
||||
Name = $"N{_scanNodeCounter}"
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
// 맵에 추가
|
||||
if (_mapNodes == null)
|
||||
_mapNodes = new List<MapNode>();
|
||||
if (_simulatorCanvas.Nodes == null)
|
||||
_simulatorCanvas.Nodes = new List<MapNode>();
|
||||
|
||||
_mapNodes.Add(newNode);
|
||||
_simulatorCanvas.Nodes.Add(newNode);
|
||||
|
||||
// 이전 노드와 연결 생성
|
||||
if (_lastScannedNode != null)
|
||||
{
|
||||
// 양방향 연결 (ConnectedNodes에 추가 - JSON 저장됨)
|
||||
_lastScannedNode.AddConnection(newNode.NodeId);
|
||||
newNode.AddConnection(_lastScannedNode.NodeId);
|
||||
_lastScannedNode.AddConnection(newNode.Id);
|
||||
newNode.AddConnection(_lastScannedNode.Id);
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 연결 생성: {_lastScannedNode.NodeId} ↔ {newNode.NodeId}");
|
||||
Program.WriteLine($"[맵 스캔] 연결 생성: {_lastScannedNode.Id} ↔ {newNode.Id}");
|
||||
}
|
||||
|
||||
// AGV 위치 설정
|
||||
@@ -749,10 +746,10 @@ namespace AGVSimulator.Forms
|
||||
selectedAGV.SetPosition(newNode, currentDirection);
|
||||
|
||||
// 캔버스 업데이트
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
_simulatorCanvas.Nodes = _simulatorCanvas.Nodes;
|
||||
|
||||
// 화면을 새 노드 위치로 이동
|
||||
_simulatorCanvas.PanToNode(newNode.NodeId);
|
||||
_simulatorCanvas.PanToNode(newNode.Id);
|
||||
_simulatorCanvas.Invalidate();
|
||||
|
||||
// 상태 업데이트
|
||||
@@ -764,10 +761,10 @@ namespace AGVSimulator.Forms
|
||||
// UI 업데이트
|
||||
UpdateNodeComboBoxes();
|
||||
|
||||
_statusLabel.Text = $"노드 생성: {newNode.NodeId} (RFID: {rfidId}) [{GetDirectionSymbol(currentDirection)}] - 총 {_mapNodes.Count}개";
|
||||
_statusLabel.Text = $"노드 생성: {newNode.Id} (RFID: {rfidId}) [{GetDirectionSymbol(currentDirection)}] - 총 {_simulatorCanvas.Nodes.Count}개";
|
||||
_rfidTextBox.Text = "";
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 노드 생성 완료: {newNode.NodeId} (RFID: {rfidId}) at ({newX}, {newY}), 방향: {currentDirection}");
|
||||
Program.WriteLine($"[맵 스캔] 노드 생성 완료: {newNode.Id} (RFID: {rfidId}) at ({newX}, {newY}), 방향: {currentDirection}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -808,7 +805,7 @@ namespace AGVSimulator.Forms
|
||||
if (agv.CurrentNodeId != null && agv.CurrentNodeId != _lastSentNodeId)
|
||||
{
|
||||
var rfid = GetRfidByNodeId(agv.CurrentNodeId);
|
||||
if (!string.IsNullOrEmpty(rfid))
|
||||
if (rfid > 0)
|
||||
{
|
||||
SendTag(rfid);
|
||||
_lastSentNodeId = agv.CurrentNodeId;
|
||||
@@ -845,7 +842,7 @@ namespace AGVSimulator.Forms
|
||||
|
||||
// RFID 값 확인
|
||||
var rfidId = _rfidTextBox.Text.Trim();
|
||||
if (string.IsNullOrEmpty(rfidId))
|
||||
if (ushort.TryParse(rfidId,out ushort rfidvalue)==false)
|
||||
{
|
||||
MessageBox.Show("RFID 값을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
@@ -859,13 +856,13 @@ namespace AGVSimulator.Forms
|
||||
// 맵 스캔 모드일 때: 노드 자동 생성
|
||||
if (_isMapScanMode)
|
||||
{
|
||||
CreateNodeFromRfidScan(rfidId, selectedAGV);
|
||||
CreateNodeFromRfidScan(rfidvalue, selectedAGV);
|
||||
this._simulatorCanvas.FitToNodes();
|
||||
return;
|
||||
}
|
||||
|
||||
// RFID에 해당하는 노드 직접 찾기
|
||||
var targetNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
|
||||
var targetNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId == rfidvalue);
|
||||
if (targetNode == null)
|
||||
{
|
||||
MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}",
|
||||
@@ -875,15 +872,15 @@ namespace AGVSimulator.Forms
|
||||
|
||||
|
||||
//이전위치와 동일한지 체크한다.
|
||||
if (selectedAGV.CurrentNodeId == targetNode.NodeId && selectedAGV.CurrentDirection == selectedDirection)
|
||||
if (selectedAGV.CurrentNodeId == targetNode.Id && selectedAGV.CurrentDirection == selectedDirection)
|
||||
{
|
||||
Program.WriteLine($"이전 노드위치와 모터의 방향이 동일하여 현재 위치 변경이 취소됩니다(NODE:{targetNode.NodeId},RFID:{targetNode.RfidId},DIR:{selectedDirection})");
|
||||
Program.WriteLine($"이전 노드위치와 모터의 방향이 동일하여 현재 위치 변경이 취소됩니다(NODE:{targetNode.Id},RFID:{targetNode.RfidId},DIR:{selectedDirection})");
|
||||
return;
|
||||
}
|
||||
|
||||
// 콘솔 출력 (상세한 리프트 방향 계산 과정)
|
||||
Program.WriteLine($"[AGV-{selectedAGV.AgvId}] 위치 설정:");
|
||||
Program.WriteLine($" RFID: {rfidId} → 노드: {targetNode.NodeId}");
|
||||
Program.WriteLine($" RFID: {rfidId} → 노드: {targetNode.Id}");
|
||||
Program.WriteLine($" 위치: ({targetNode.Position.X}, {targetNode.Position.Y})");
|
||||
Program.WriteLine($" 방향: {selectedDirectionItem?.DisplayText ?? "전진"} ({selectedDirection})");
|
||||
|
||||
@@ -912,14 +909,14 @@ namespace AGVSimulator.Forms
|
||||
CalculateLiftDirectionDetailed(selectedAGV);
|
||||
Program.WriteLine("");
|
||||
|
||||
_statusLabel.Text = $"{selectedAGV.AgvId} 위치를 RFID '{rfidId}' (노드: {targetNode.NodeId}), 방향: {selectedDirectionItem?.DisplayText ?? "전진"}로 설정했습니다.";
|
||||
_statusLabel.Text = $"{selectedAGV.AgvId} 위치를 RFID '{rfidId}' (노드: {targetNode.Id}), 방향: {selectedDirectionItem?.DisplayText ?? "전진"}로 설정했습니다.";
|
||||
_rfidTextBox.Text = ""; // 입력 필드 초기화
|
||||
|
||||
// 시뮬레이터 캔버스의 해당 노드로 이동
|
||||
//_simulatorCanvas.PanToNode(targetNode.NodeId);
|
||||
|
||||
// 시작 노드 콤보박스를 현재 위치로 자동 선택
|
||||
SetStartNodeToCombo(targetNode.NodeId);
|
||||
SetStartNodeToCombo(targetNode.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -948,19 +945,18 @@ namespace AGVSimulator.Forms
|
||||
|
||||
private string GetAvailableRfidList()
|
||||
{
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
|
||||
return "매핑된 RFID가 없습니다.";
|
||||
|
||||
var nodesWithRfid = _mapNodes.Where(n => n.HasRfid()).ToList();
|
||||
var nodesWithRfid = _simulatorCanvas.Nodes.Where(n => n.HasRfid()).ToList();
|
||||
if (nodesWithRfid.Count == 0)
|
||||
return "RFID가 할당된 노드가 없습니다.";
|
||||
|
||||
// 처음 10개의 RFID만 표시 (노드 이름 포함)
|
||||
var rfidList = nodesWithRfid.Take(10).Select(n =>
|
||||
var rfidList = nodesWithRfid.Take(10).Select((Func<MapNode, string>)(n =>
|
||||
{
|
||||
var nodeNamePart = !string.IsNullOrEmpty(n.Name) ? $" {n.Name}" : "";
|
||||
return $"- {n.RfidId} → {n.NodeId}{nodeNamePart}";
|
||||
});
|
||||
return $"- {n.RfidId} → {n.Id}";
|
||||
}));
|
||||
var result = string.Join("\n", rfidList);
|
||||
|
||||
if (nodesWithRfid.Count > 10)
|
||||
@@ -979,13 +975,13 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
Console.WriteLine($"Map File Load : {filePath}");
|
||||
|
||||
_mapNodes = result.Nodes;
|
||||
_simulatorCanvas.Nodes = result.Nodes;
|
||||
_currentMapFilePath = filePath;
|
||||
|
||||
// RFID 자동 할당 제거 - 에디터에서 설정한 값 그대로 사용
|
||||
|
||||
// 시뮬레이터 캔버스에 맵 설정
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
_simulatorCanvas.SetMapLoadResult(result);//.Nodes = _simulatorCanvas.Nodes;
|
||||
|
||||
// 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
@@ -1053,15 +1049,14 @@ namespace AGVSimulator.Forms
|
||||
_startNodeCombo.Items.Clear();
|
||||
_targetNodeCombo.Items.Clear();
|
||||
|
||||
if (_mapNodes != null)
|
||||
if (_simulatorCanvas.Nodes != null)
|
||||
{
|
||||
foreach (var node in _mapNodes)
|
||||
foreach (var node in _simulatorCanvas.Nodes)
|
||||
{
|
||||
if (node.IsActive && node.HasRfid())
|
||||
{
|
||||
// {rfid} - [{node}] {name} 형식으로 ComboBoxItem 생성
|
||||
var nodeNamePart = !string.IsNullOrEmpty(node.Name) ? $" {node.Name}" : "";
|
||||
var displayText = $"{node.RfidId} - [{node.NodeId}]{nodeNamePart}";
|
||||
var displayText = $"{node.RfidId} - [{node.Id}]";
|
||||
var item = new ComboBoxItem<MapNode>(node, displayText);
|
||||
|
||||
_startNodeCombo.Items.Add(item);
|
||||
@@ -1112,7 +1107,7 @@ namespace AGVSimulator.Forms
|
||||
|
||||
// RFID 위치 설정 관련
|
||||
var hasSelectedAGV = _agvListCombo.SelectedItem != null;
|
||||
var hasRfidNodes = _mapNodes != null && _mapNodes.Any(n => n.HasRfid());
|
||||
var hasRfidNodes = _simulatorCanvas.Nodes != null && _simulatorCanvas.Nodes.Any(n => n.HasRfid());
|
||||
|
||||
_setPositionButton.Enabled = hasSelectedAGV && hasRfidNodes;
|
||||
_rfidTextBox.Enabled = hasSelectedAGV && hasRfidNodes;
|
||||
@@ -1185,7 +1180,7 @@ namespace AGVSimulator.Forms
|
||||
|
||||
// 경로 예측 기반 LiftCalculator를 사용하여 리프트 방향 계산
|
||||
var liftInfo = AGVNavigationCore.Utils.LiftCalculator.CalculateLiftInfoWithPathPrediction(
|
||||
currentPos, prevPos.Value, agv.CurrentDirection, _mapNodes);
|
||||
currentPos, prevPos.Value, agv.CurrentDirection, _simulatorCanvas.Nodes);
|
||||
|
||||
// 이동 각도 계산 (표시용)
|
||||
var moveAngleRad = Math.Atan2(dy, dx);
|
||||
@@ -1229,7 +1224,7 @@ namespace AGVSimulator.Forms
|
||||
|
||||
// 경로 예측 기반 LiftCalculator를 사용하여 리프트 방향 계산
|
||||
var liftInfo = AGVNavigationCore.Utils.LiftCalculator.CalculateLiftInfoWithPathPrediction(
|
||||
currentPos, targetPos.Value, agv.CurrentDirection, _mapNodes);
|
||||
currentPos, targetPos.Value, agv.CurrentDirection, _simulatorCanvas.Nodes);
|
||||
|
||||
// 도킹 방향 정보 추가
|
||||
string dockingInfo = dockingDirection == DockingDirection.Forward ? "전진도킹" : "후진도킹";
|
||||
@@ -1260,10 +1255,12 @@ namespace AGVSimulator.Forms
|
||||
/// <summary>
|
||||
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
|
||||
/// </summary>
|
||||
private string GetRfidByNodeId(string nodeId)
|
||||
private ushort GetRfidByNodeId(string nodeId)
|
||||
{
|
||||
var node = _mapNodes?.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
return node?.HasRfid() == true ? node.RfidId : nodeId;
|
||||
var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node == null) return 0;
|
||||
if (node.HasRfid()) return node.RfidId;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1271,10 +1268,10 @@ namespace AGVSimulator.Forms
|
||||
/// </summary>
|
||||
private string GetDisplayName(string nodeId)
|
||||
{
|
||||
var node = _mapNodes?.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
if (node != null && !string.IsNullOrEmpty(node.RfidId))
|
||||
var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node != null && node.HasRfid())
|
||||
{
|
||||
return node.RfidId;
|
||||
return node.RfidId.ToString("0000");
|
||||
}
|
||||
return $"({nodeId})";
|
||||
}
|
||||
@@ -1371,7 +1368,7 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
var info = advancedResult.DetailedPath[i];
|
||||
var rfidId = GetRfidByNodeId(info.NodeId);
|
||||
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId) : "END";
|
||||
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId).ToString("0000") : "-END-";
|
||||
|
||||
var flags = new List<string>();
|
||||
if (info.CanRotate) flags.Add("회전가능");
|
||||
@@ -1544,7 +1541,7 @@ namespace AGVSimulator.Forms
|
||||
private async void toolStripButton1_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 맵과 AGV 확인
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("맵 데이터가 없습니다. 먼저 맵을 로드해주세요.", "알림",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
@@ -1560,8 +1557,7 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
|
||||
// 도킹 타겟 노드 찾기
|
||||
var dockingTargets = _mapNodes.Where(n =>
|
||||
n.Type == NodeType.Charging || n.Type == NodeType.Loader || n.Type == NodeType.UnLoader || n.Type == NodeType.Clearner || n.Type == NodeType.Buffer).ToList();
|
||||
var dockingTargets = _simulatorCanvas.Nodes.Where(n => n.isDockingNode).ToList();
|
||||
|
||||
if (dockingTargets.Count == 0)
|
||||
{
|
||||
@@ -1622,13 +1618,9 @@ namespace AGVSimulator.Forms
|
||||
/// </summary>
|
||||
private string GetNodeDisplayName(MapNode node)
|
||||
{
|
||||
if (node == null)
|
||||
return "-";
|
||||
|
||||
if (!string.IsNullOrEmpty(node.RfidId))
|
||||
return node.RfidId;
|
||||
|
||||
return $"({node.NodeId})";
|
||||
if (node == null) return "-";
|
||||
if (node.HasRfid()) return node.RfidId.ToString("0000");
|
||||
return $"({node.Id})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1655,7 +1647,7 @@ namespace AGVSimulator.Forms
|
||||
for (int i = 0; i < _targetNodeCombo.Items.Count; i++)
|
||||
{
|
||||
var item = _targetNodeCombo.Items[i] as ComboBoxItem<MapNode>;
|
||||
if (item?.Value?.NodeId == nodeId)
|
||||
if (item?.Value?.Id == nodeId)
|
||||
{
|
||||
_targetNodeCombo.SelectedIndex = i;
|
||||
return;
|
||||
@@ -1669,7 +1661,7 @@ namespace AGVSimulator.Forms
|
||||
private PathTestLogItem CreateTestResultFromUI(MapNode prevNode, MapNode targetNode,
|
||||
string directionName, (bool result, string message) calcResult)
|
||||
{
|
||||
var currentNode = _mapNodes.FirstOrDefault(n => n.NodeId ==
|
||||
var currentNode = _simulatorCanvas.Nodes.FirstOrDefault(n => n.Id ==
|
||||
(_agvListCombo.SelectedItem as VirtualAGV)?.CurrentNodeId);
|
||||
|
||||
var logItem = new PathTestLogItem
|
||||
@@ -1678,7 +1670,7 @@ namespace AGVSimulator.Forms
|
||||
MotorDirection = directionName,
|
||||
CurrentPosition = GetNodeDisplayName(currentNode),
|
||||
TargetPosition = GetNodeDisplayName(targetNode),
|
||||
DockingPosition = targetNode.Type == NodeType.Charging ? "충전기" : "장비"
|
||||
DockingPosition = targetNode.StationType == StationType.Charger ? "충전기" : "장비"
|
||||
};
|
||||
|
||||
if (calcResult.result)
|
||||
@@ -1688,7 +1680,7 @@ namespace AGVSimulator.Forms
|
||||
if (currentPath != null && currentPath.Success)
|
||||
{
|
||||
// 도킹 검증
|
||||
var dockingValidation = DockingValidator.ValidateDockingDirection(currentPath, _mapNodes);
|
||||
var dockingValidation = DockingValidator.ValidateDockingDirection(currentPath, _simulatorCanvas.Nodes);
|
||||
|
||||
if (dockingValidation.IsValid)
|
||||
{
|
||||
@@ -1729,7 +1721,7 @@ namespace AGVSimulator.Forms
|
||||
var pairs = new List<(MapNode, MapNode)>();
|
||||
var processedPairs = new HashSet<string>();
|
||||
|
||||
foreach (var nodeA in _mapNodes)
|
||||
foreach (var nodeA in _simulatorCanvas.Nodes)
|
||||
{
|
||||
if (nodeA.ConnectedMapNodes == null || nodeA.ConnectedMapNodes.Count == 0)
|
||||
continue;
|
||||
@@ -1741,8 +1733,8 @@ namespace AGVSimulator.Forms
|
||||
continue;
|
||||
|
||||
// 중복 방지 (A→B와 B→A를 같은 것으로 간주)
|
||||
var pairKey1 = $"{nodeA.NodeId}→{nodeB.NodeId}";
|
||||
var pairKey2 = $"{nodeB.NodeId}→{nodeA.NodeId}";
|
||||
var pairKey1 = $"{nodeA.Id}→{nodeB.Id}";
|
||||
var pairKey2 = $"{nodeB.Id}→{nodeA.Id}";
|
||||
|
||||
if (nodeA.HasRfid() && nodeB.HasRfid() && !processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2))
|
||||
{
|
||||
@@ -1798,7 +1790,7 @@ namespace AGVSimulator.Forms
|
||||
this.Invoke((MethodInvoker)delegate
|
||||
{
|
||||
// RFID 텍스트박스에 값 입력
|
||||
_rfidTextBox.Text = nodeA.RfidId;
|
||||
_rfidTextBox.Text = nodeA.RfidId.ToString();
|
||||
|
||||
// 방향 콤보박스 선택
|
||||
SetDirectionComboBox(direction);
|
||||
@@ -1814,7 +1806,7 @@ namespace AGVSimulator.Forms
|
||||
this.Invoke((MethodInvoker)delegate
|
||||
{
|
||||
// RFID 텍스트박스에 값 입력
|
||||
_rfidTextBox.Text = nodeB.RfidId;
|
||||
_rfidTextBox.Text = nodeB.RfidId.ToString();
|
||||
|
||||
// 방향 콤보박스 선택
|
||||
SetDirectionComboBox(direction);
|
||||
@@ -1848,7 +1840,7 @@ namespace AGVSimulator.Forms
|
||||
prb1.Value = (int)((double)currentTest / totalTests * 100);
|
||||
|
||||
// 목표 노드 콤보박스 선택
|
||||
SetTargetNodeComboBox(dockingTarget.NodeId);
|
||||
SetTargetNodeComboBox(dockingTarget.Id);
|
||||
|
||||
// 경로 계산 버튼 클릭 (실제 사용자 동작)
|
||||
var calcResult = CalcPath();
|
||||
@@ -1955,9 +1947,9 @@ namespace AGVSimulator.Forms
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 기존 맵 데이터 삭제
|
||||
_mapNodes?.Clear();
|
||||
_mapNodes = new List<MapNode>();
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
_simulatorCanvas.Nodes?.Clear();
|
||||
_simulatorCanvas.Nodes = new List<MapNode>();
|
||||
_simulatorCanvas.Nodes = _simulatorCanvas.Nodes;
|
||||
_currentMapFilePath = string.Empty;
|
||||
UpdateNodeComboBoxes();
|
||||
_statusLabel.Text = "맵 초기화 완료 - 스캔 모드 시작";
|
||||
@@ -1981,16 +1973,16 @@ namespace AGVSimulator.Forms
|
||||
_isMapScanMode = false;
|
||||
btMakeMap.Text = "맵 생성";
|
||||
btMakeMap.BackColor = SystemColors.Control;
|
||||
_statusLabel.Text = $"맵 스캔 완료 - {_mapNodes?.Count ?? 0}개 노드 생성됨";
|
||||
_statusLabel.Text = $"맵 스캔 완료 - {_simulatorCanvas.Nodes?.Count ?? 0}개 노드 생성됨";
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 스캔 모드 종료 - 총 {_mapNodes?.Count ?? 0}개 노드");
|
||||
Program.WriteLine($"[맵 스캔] 스캔 모드 종료 - 총 {_simulatorCanvas.Nodes?.Count ?? 0}개 노드");
|
||||
|
||||
// 맵 저장 권장
|
||||
if (_mapNodes != null && _mapNodes.Count > 0)
|
||||
if (_simulatorCanvas.Nodes != null && _simulatorCanvas.Nodes.Count > 0)
|
||||
{
|
||||
var saveResult = MessageBox.Show(
|
||||
$"맵 스캔이 완료되었습니다.\n\n" +
|
||||
$"생성된 노드: {_mapNodes.Count}개\n\n" +
|
||||
$"생성된 노드: {_simulatorCanvas.Nodes.Count}개\n\n" +
|
||||
"맵을 저장하시겠습니까?",
|
||||
"맵 저장",
|
||||
MessageBoxButtons.YesNo,
|
||||
@@ -2014,11 +2006,11 @@ namespace AGVSimulator.Forms
|
||||
try
|
||||
{
|
||||
// MapLoader의 표준 저장 메서드 사용 (AGVMapEditor와 동일한 형식)
|
||||
bool success = MapLoader.SaveMapToFile(filePath, _mapNodes);
|
||||
bool success = MapLoader.SaveMapToFile(filePath, _simulatorCanvas.Nodes);
|
||||
|
||||
if (success)
|
||||
{
|
||||
Program.WriteLine($"[맵 저장] 파일 저장 완료: {filePath} ({_mapNodes.Count}개 노드)");
|
||||
Program.WriteLine($"[맵 저장] 파일 저장 완료: {filePath} ({_simulatorCanvas.Nodes.Count}개 노드)");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2035,7 +2027,7 @@ namespace AGVSimulator.Forms
|
||||
private void btMapSaveAs_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 맵 데이터 확인
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("저장할 맵 데이터가 없습니다.", "알림",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
@@ -2044,9 +2036,9 @@ namespace AGVSimulator.Forms
|
||||
|
||||
using (var saveDialog = new SaveFileDialog())
|
||||
{
|
||||
saveDialog.Filter = "AGV Map Files (*.agvmap)|*.agvmap|모든 파일 (*.*)|*.*";
|
||||
saveDialog.Filter = "AGV Map Files (*.json)|*.json|모든 파일 (*.*)|*.*";
|
||||
saveDialog.Title = "맵 파일 저장";
|
||||
saveDialog.DefaultExt = "agvmap";
|
||||
saveDialog.DefaultExt = "json";
|
||||
|
||||
// 현재 파일이 있으면 기본 파일명으로 설정
|
||||
if (!string.IsNullOrEmpty(_currentMapFilePath))
|
||||
@@ -2057,7 +2049,7 @@ namespace AGVSimulator.Forms
|
||||
else
|
||||
{
|
||||
// 기본 파일명: 날짜_시간 형식
|
||||
saveDialog.FileName = $"ScanMap_{DateTime.Now:yyyyMMdd_HHmmss}.agvmap";
|
||||
saveDialog.FileName = $"ScanMap_{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
||||
}
|
||||
|
||||
if (saveDialog.ShowDialog() == DialogResult.OK)
|
||||
@@ -2421,19 +2413,18 @@ namespace AGVSimulator.Forms
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void SendTag(string tagno)
|
||||
public void SendTag(ushort tagno)
|
||||
{
|
||||
if (_emulatorPort == null || !_emulatorPort.IsOpen) return;
|
||||
|
||||
tagno = tagno.PadLeft(6, '0');
|
||||
if (tagno.Length > 6) tagno = tagno.Substring(0, 6);
|
||||
var tagnostr = tagno.ToString("000000");
|
||||
|
||||
var barr = new List<byte>();
|
||||
barr.Add(0x02);
|
||||
barr.Add((byte)'T');
|
||||
barr.Add((byte)'A');
|
||||
barr.Add((byte)'G');
|
||||
barr.AddRange(System.Text.Encoding.Default.GetBytes(tagno));
|
||||
barr.AddRange(System.Text.Encoding.Default.GetBytes(tagnostr));
|
||||
barr.Add((byte)'*');
|
||||
barr.Add((byte)'*');
|
||||
barr.Add(0x03);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Nodes": [
|
||||
{
|
||||
"NodeId": "N001",
|
||||
"Id": "N001",
|
||||
"Name": "UNLOADER",
|
||||
"Position": "65, 229",
|
||||
"Type": 2,
|
||||
@@ -40,7 +40,7 @@
|
||||
"DisplayText": "N001 - UNLOADER - [0001]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N002",
|
||||
"Id": "N002",
|
||||
"Name": "N002",
|
||||
"Position": "190, 230",
|
||||
"Type": 0,
|
||||
@@ -80,7 +80,7 @@
|
||||
"DisplayText": "N002 - N002 - [0002]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N003",
|
||||
"Id": "N003",
|
||||
"Name": "N003",
|
||||
"Position": "296, 266",
|
||||
"Type": 0,
|
||||
@@ -120,7 +120,7 @@
|
||||
"DisplayText": "N003 - N003 - [0003]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N004",
|
||||
"Id": "N004",
|
||||
"Name": "N004",
|
||||
"Position": "388, 330",
|
||||
"Type": 0,
|
||||
@@ -162,7 +162,7 @@
|
||||
"DisplayText": "N004 - N004 - [0004]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N006",
|
||||
"Id": "N006",
|
||||
"Name": "N006",
|
||||
"Position": "530, 220",
|
||||
"Type": 0,
|
||||
@@ -202,7 +202,7 @@
|
||||
"DisplayText": "N006 - N006 - [0013]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N007",
|
||||
"Id": "N007",
|
||||
"Name": "N007",
|
||||
"Position": "589, 184",
|
||||
"Type": 0,
|
||||
@@ -242,7 +242,7 @@
|
||||
"DisplayText": "N007 - N007 - [0014]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N008",
|
||||
"Id": "N008",
|
||||
"Name": "N008",
|
||||
"Position": "282, 452",
|
||||
"Type": 0,
|
||||
@@ -282,7 +282,7 @@
|
||||
"DisplayText": "N008 - N008 - [0009]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N009",
|
||||
"Id": "N009",
|
||||
"Name": "N009",
|
||||
"Position": "183, 465",
|
||||
"Type": 0,
|
||||
@@ -322,7 +322,7 @@
|
||||
"DisplayText": "N009 - N009 - [0010]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N010",
|
||||
"Id": "N010",
|
||||
"Name": "TOPS",
|
||||
"Position": "52, 466",
|
||||
"Type": 3,
|
||||
@@ -361,7 +361,7 @@
|
||||
"DisplayText": "N010 - TOPS - [0011]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N011",
|
||||
"Id": "N011",
|
||||
"Name": "N011",
|
||||
"Position": "481, 399",
|
||||
"Type": 0,
|
||||
@@ -402,7 +402,7 @@
|
||||
"DisplayText": "N011 - N011 - [0005]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N012",
|
||||
"Id": "N012",
|
||||
"Name": "N012",
|
||||
"Position": "559, 464",
|
||||
"Type": 0,
|
||||
@@ -442,7 +442,7 @@
|
||||
"DisplayText": "N012 - N012 - [0006]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N013",
|
||||
"Id": "N013",
|
||||
"Name": "N013",
|
||||
"Position": "640, 513",
|
||||
"Type": 0,
|
||||
@@ -482,7 +482,7 @@
|
||||
"DisplayText": "N013 - N013 - [0007]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N014",
|
||||
"Id": "N014",
|
||||
"Name": "LOADER",
|
||||
"Position": "728, 573",
|
||||
"Type": 1,
|
||||
@@ -521,7 +521,7 @@
|
||||
"DisplayText": "N014 - LOADER - [0008]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N019",
|
||||
"Id": "N019",
|
||||
"Name": "CHARGER #2",
|
||||
"Position": "679, 199",
|
||||
"Type": 5,
|
||||
@@ -560,7 +560,7 @@
|
||||
"DisplayText": "N019 - CHARGER #2 - [0015]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N022",
|
||||
"Id": "N022",
|
||||
"Name": "N022",
|
||||
"Position": "461, 267",
|
||||
"Type": 0,
|
||||
@@ -601,7 +601,7 @@
|
||||
"DisplayText": "N022 - N022 - [0012]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N023",
|
||||
"Id": "N023",
|
||||
"Name": "N023",
|
||||
"Position": "418, 206",
|
||||
"Type": 0,
|
||||
@@ -641,7 +641,7 @@
|
||||
"DisplayText": "N023 - N023 - [0016]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N024",
|
||||
"Id": "N024",
|
||||
"Name": "N024",
|
||||
"Position": "476, 141",
|
||||
"Type": 0,
|
||||
@@ -681,7 +681,7 @@
|
||||
"DisplayText": "N024 - N024 - [0017]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N025",
|
||||
"Id": "N025",
|
||||
"Name": "N025",
|
||||
"Position": "548, 99",
|
||||
"Type": 0,
|
||||
@@ -721,7 +721,7 @@
|
||||
"DisplayText": "N025 - N025 - [0018]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N026",
|
||||
"Id": "N026",
|
||||
"Name": "CHARGER #1",
|
||||
"Position": "670, 88",
|
||||
"Type": 5,
|
||||
@@ -760,7 +760,7 @@
|
||||
"DisplayText": "N026 - CHARGER #1 - [0019]"
|
||||
},
|
||||
{
|
||||
"NodeId": "LBL001",
|
||||
"Id": "LBL001",
|
||||
"Name": "Amkor Technology Korea",
|
||||
"Position": "183, 103",
|
||||
"Type": 6,
|
||||
@@ -797,7 +797,7 @@
|
||||
"DisplayText": "LBL001 - Amkor Technology Korea"
|
||||
},
|
||||
{
|
||||
"NodeId": "IMG001",
|
||||
"Id": "IMG001",
|
||||
"Name": "logo",
|
||||
"Position": "633, 310",
|
||||
"Type": 7,
|
||||
@@ -834,7 +834,7 @@
|
||||
"DisplayText": "IMG001 - logo"
|
||||
},
|
||||
{
|
||||
"NodeId": "N015",
|
||||
"Id": "N015",
|
||||
"Name": "",
|
||||
"Position": "448, 476",
|
||||
"Type": 0,
|
||||
@@ -874,7 +874,7 @@
|
||||
"DisplayText": "N015 - [0037]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N016",
|
||||
"Id": "N016",
|
||||
"Name": "",
|
||||
"Position": "425, 524",
|
||||
"Type": 0,
|
||||
@@ -914,7 +914,7 @@
|
||||
"DisplayText": "N016 - [0036]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N017",
|
||||
"Id": "N017",
|
||||
"Name": "",
|
||||
"Position": "389, 559",
|
||||
"Type": 0,
|
||||
@@ -954,7 +954,7 @@
|
||||
"DisplayText": "N017 - [0035]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N018",
|
||||
"Id": "N018",
|
||||
"Name": "",
|
||||
"Position": "315, 562",
|
||||
"Type": 0,
|
||||
@@ -995,7 +995,7 @@
|
||||
"DisplayText": "N018 - [0034]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N005",
|
||||
"Id": "N005",
|
||||
"Name": "",
|
||||
"Position": "227, 560",
|
||||
"Type": 0,
|
||||
@@ -1036,7 +1036,7 @@
|
||||
"DisplayText": "N005 - [0033]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N020",
|
||||
"Id": "N020",
|
||||
"Name": "",
|
||||
"Position": "142, 557",
|
||||
"Type": 0,
|
||||
@@ -1077,7 +1077,7 @@
|
||||
"DisplayText": "N020 - [0032]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N021",
|
||||
"Id": "N021",
|
||||
"Name": "",
|
||||
"Position": "60, 559",
|
||||
"Type": 0,
|
||||
@@ -1117,7 +1117,7 @@
|
||||
"DisplayText": "N021 - [0031]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N027",
|
||||
"Id": "N027",
|
||||
"Name": "BUF1",
|
||||
"Position": "61, 645",
|
||||
"Type": 4,
|
||||
@@ -1156,7 +1156,7 @@
|
||||
"DisplayText": "N027 - BUF1 - [0041]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N028",
|
||||
"Id": "N028",
|
||||
"Name": "BUF2",
|
||||
"Position": "141, 643",
|
||||
"Type": 4,
|
||||
@@ -1195,7 +1195,7 @@
|
||||
"DisplayText": "N028 - BUF2 - [0040]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N029",
|
||||
"Id": "N029",
|
||||
"Name": "BUF3",
|
||||
"Position": "229, 638",
|
||||
"Type": 4,
|
||||
@@ -1234,7 +1234,7 @@
|
||||
"DisplayText": "N029 - BUF3 - [0039]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N030",
|
||||
"Id": "N030",
|
||||
"Name": "BUF4",
|
||||
"Position": "316, 638",
|
||||
"Type": 4,
|
||||
@@ -1273,7 +1273,7 @@
|
||||
"DisplayText": "N030 - BUF4 - [0038]"
|
||||
},
|
||||
{
|
||||
"NodeId": "N031",
|
||||
"Id": "N031",
|
||||
"Name": "",
|
||||
"Position": "337, 397",
|
||||
"Type": 0,
|
||||
|
||||
File diff suppressed because one or more lines are too long
1135
Cs_HMI/Data/NewMap_2.json
Normal file
1135
Cs_HMI/Data/NewMap_2.json
Normal file
File diff suppressed because it is too large
Load Diff
1219
Cs_HMI/Data/NewMap_3.json
Normal file
1219
Cs_HMI/Data/NewMap_3.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -186,6 +186,7 @@
|
||||
<Compile Include="Device\BMS.cs" />
|
||||
<Compile Include="Device\BMSInformationEventArgs.cs" />
|
||||
<Compile Include="Device\CFlag.cs" />
|
||||
<Compile Include="Device\BMSSerialComm.cs" />
|
||||
<Compile Include="Device\xbee.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
@@ -226,9 +227,6 @@
|
||||
<Compile Include="Dialog\fSystem.Designer.cs">
|
||||
<DependentUpon>fSystem.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\DriveDetector.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fVolume.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -363,9 +361,6 @@
|
||||
<Compile Include="StateMachine\_SPS.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Device\_DeviceManagement.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="StateMachine\_Loop.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
||||
@@ -222,8 +222,6 @@ namespace Project
|
||||
|
||||
#region "Charge"
|
||||
[Browsable(false)]
|
||||
public int chargerpos { get; set; }
|
||||
[Browsable(false)]
|
||||
public int ChargetWaitSec { get; set; }
|
||||
[Browsable(false)]
|
||||
public int ChargeEmergencyLevel { get; set; }
|
||||
@@ -240,6 +238,9 @@ namespace Project
|
||||
[Browsable(false)]
|
||||
public int ChargeSearchTime { get; set; }
|
||||
|
||||
[Category("Charge"), DisplayName("Low Battery Limit"), Description("배터리 부족 경고 알림 기준 (%)")]
|
||||
public int BatteryLimit_Low { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region "AGV"
|
||||
@@ -427,10 +428,10 @@ namespace Project
|
||||
if (ChargeEmergencyLevel == 0) ChargeEmergencyLevel = 30;
|
||||
if (interval_bms == 0) interval_bms = 10;
|
||||
|
||||
//충전은 10분간격으로 재시도 한다
|
||||
if (ChargeRetryTerm == 0) ChargeRetryTerm = 600;
|
||||
if (alarmSoundTerm == 0) alarmSoundTerm = 15; //기본 15초
|
||||
if (ChargeSearchTime == 0) ChargeSearchTime = 25;
|
||||
if (BatteryLimit_Low == 0) BatteryLimit_Low = 20;
|
||||
//최대 충전진행 시간(기본 1시간)
|
||||
if (ChargeMaxTime == 0) ChargeMaxTime = 3600;
|
||||
// if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;
|
||||
|
||||
@@ -22,11 +22,31 @@ namespace Project
|
||||
public Color SMSG_ShadowColor = Color.Transparent;
|
||||
public float SMSG_ProgressValue = 0;
|
||||
public string SMSG_Tag = string.Empty;
|
||||
//public event EventHandler SMSG_Update;
|
||||
//public void UpdateStatusMessage()
|
||||
//{
|
||||
// SMSG_Update?.Invoke(null, null);
|
||||
//}
|
||||
/// <summary>
|
||||
/// 이동대상위치(상차,하차,충전)
|
||||
/// </summary>
|
||||
private ePosition _targetPos = ePosition.NONE;
|
||||
public string result_message = "";
|
||||
public double result_progressmax = 0;
|
||||
public double result_progressvalue = 0;
|
||||
public DateTime StopMessageTimePLC = DateTime.Parse("1982-11-23");
|
||||
public DateTime StopMessageTimeSWR = DateTime.Parse("1982-11-23");
|
||||
public string StopMessagePLC = string.Empty;
|
||||
public string StopMessageSWR = string.Empty;
|
||||
private ePosition _currentpos = ePosition.NONE;
|
||||
private string _currentposcw = string.Empty;
|
||||
public ushort LastTAG { get; set; } = 0;
|
||||
public ePosition NextPos = ePosition.NONE;
|
||||
private char _comandKit { get; set; }
|
||||
public string Memo;
|
||||
public eResult ResultCode { get; set; }
|
||||
public eECode ResultErrorCode;
|
||||
public string ResultMessage { get; set; }
|
||||
public Boolean isError { get; set; }
|
||||
public int retry = 0;
|
||||
public DateTime retryTime;
|
||||
public Device.Socket.Message RecvMessage;
|
||||
|
||||
/// <summary>
|
||||
/// 작업시작시간
|
||||
/// </summary>
|
||||
@@ -52,9 +72,6 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
//public DateTime ChargeStartTime = DateTime.Parse("1982-11-23");
|
||||
|
||||
|
||||
#region "AGV Status Value"
|
||||
public string PLC1_RawData { get; set; }
|
||||
public string PLC2_RawData { get; set; }
|
||||
@@ -62,25 +79,7 @@ namespace Project
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 이동대상위치(상차,하차,충전)
|
||||
/// </summary>
|
||||
private ePosition _targetPos = ePosition.NONE;
|
||||
|
||||
|
||||
public string result_message = "";
|
||||
public double result_progressmax = 0;
|
||||
public double result_progressvalue = 0;
|
||||
|
||||
|
||||
public DateTime StopMessageTimePLC = DateTime.Parse("1982-11-23");
|
||||
public DateTime StopMessageTimeSWR = DateTime.Parse("1982-11-23");
|
||||
public string StopMessagePLC = string.Empty;
|
||||
public string StopMessageSWR = string.Empty;
|
||||
//public DateTime LastChar
|
||||
//geTime = DateTime.Parse("1982-11-23");
|
||||
|
||||
public ePosition NextPos = ePosition.NONE;
|
||||
|
||||
public ePosition TargetPos
|
||||
{
|
||||
get
|
||||
@@ -94,7 +93,7 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
private char _comandKit { get; set; }
|
||||
|
||||
public char CommandKit
|
||||
{
|
||||
get
|
||||
@@ -109,28 +108,7 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//private ePosition _currentPos = ePosition.NONE;
|
||||
//public ePosition CurrentPos
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// return _currentPos;
|
||||
// }
|
||||
// set
|
||||
// {
|
||||
// if (_currentPos != value) //값이바뀔떄만 메세지 220628
|
||||
// PUB.log.Add(string.Format("현재위치 설정:{0}->{1}", _currentPos, value));
|
||||
|
||||
// _currentPos = value;
|
||||
// }
|
||||
//}
|
||||
|
||||
private ePosition _currentpos = ePosition.NONE;
|
||||
private string _currentposcw = string.Empty;
|
||||
|
||||
// public ePosition LastPos = ePosition.NONE;
|
||||
public string LastTAG { get; set; } = string.Empty;
|
||||
|
||||
public ePosition CurrentPos
|
||||
{
|
||||
get
|
||||
@@ -161,11 +139,7 @@ namespace Project
|
||||
_currentposcw = value;
|
||||
}
|
||||
}
|
||||
public string Memo;
|
||||
|
||||
public eResult ResultCode { get; set; }
|
||||
public eECode ResultErrorCode;
|
||||
public string ResultMessage { get; set; }
|
||||
|
||||
|
||||
#region "SetResultMessage"
|
||||
|
||||
@@ -203,14 +177,7 @@ namespace Project
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
public Boolean isError { get; set; }
|
||||
|
||||
public int retry = 0;
|
||||
public DateTime retryTime;
|
||||
public Device.Socket.Message RecvMessage;
|
||||
|
||||
|
||||
public CResult()
|
||||
{
|
||||
this.Clear();
|
||||
|
||||
@@ -9,12 +9,12 @@ using System.CodeDom;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public class BMS : arRS232
|
||||
public class BMS : BMSSerialComm
|
||||
{
|
||||
public BMS()
|
||||
{
|
||||
|
||||
MinRecvLength = 34;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,28 +55,60 @@ namespace arDev
|
||||
{
|
||||
tempBuffer.Add(incomByte);
|
||||
|
||||
var queylen = QueryIndex == 0 ? 34 : 23;
|
||||
if (tempBuffer.Count == queylen)
|
||||
if (tempBuffer.Count > 7)
|
||||
{
|
||||
if (incomByte != 0x77)
|
||||
byte len = tempBuffer[3];
|
||||
if (tempBuffer.Count >= 4 + len + 3) // Start+Reg+Status+Len + Data + Chk(2) + End
|
||||
{
|
||||
//종단기호가 맞지 않다. 이자료는 폐기한다.
|
||||
var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
|
||||
RaiseMessage(MessageType.Error, $"discard : {hexstr}");
|
||||
tempBuffer.Clear();
|
||||
if (tempBuffer.Last() == 0x77)
|
||||
{
|
||||
//데이터가 맞게 수신됨
|
||||
LastReceiveBuffer = tempBuffer.ToArray();
|
||||
bComplete = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//종단기호가 맞지 않다. 이자료는 폐기한다.
|
||||
var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
|
||||
RaiseMessage(MessageType.Error, $"discard : {hexstr}");
|
||||
tempBuffer.Clear();
|
||||
}
|
||||
findSTX = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//데이터가 맞게 수신됨
|
||||
LastReceiveBuffer = tempBuffer.ToArray();
|
||||
bComplete = true;
|
||||
}
|
||||
findSTX = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//아직 모자르므로 대기한다
|
||||
}
|
||||
|
||||
// [22 - 12 - 27 14:32:49] open: True
|
||||
//[22 - 12 - 27 14:32:49] Send: DD A5 03 00 FF FD 77 0D
|
||||
//[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D
|
||||
//[22 - 12 - 27 14:32:50] Recv: 26.61v,81.4 %
|
||||
//[22 - 12 - 27 14:32:50] Recv: DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77
|
||||
//[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D
|
||||
//[22 - 12 - 27 14:32:51] Recv: 26.61v,81.4 %
|
||||
//[22 - 12 - 27 14:32:51] Recv: DD 03 00 1B 0A 65 00 00 21 63 29 04 00 00 2C 92 00 00 00 00 00 00 28 51 03 08 02 0B 69 0B 66 FC 9C 77
|
||||
|
||||
|
||||
//var queylen = QueryIndex == 0 ? 34 : 23;
|
||||
//if (tempBuffer.Count == queylen)
|
||||
//{
|
||||
// if (incomByte != 0x77)
|
||||
// {
|
||||
// //종단기호가 맞지 않다. 이자료는 폐기한다.
|
||||
// var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
|
||||
// RaiseMessage(MessageType.Error, $"discard : {hexstr}");
|
||||
// tempBuffer.Clear();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //데이터가 맞게 수신됨
|
||||
// LastReceiveBuffer = tempBuffer.ToArray();
|
||||
// bComplete = true;
|
||||
// }
|
||||
// findSTX = false;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// //아직 모자르므로 대기한다
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +138,7 @@ namespace arDev
|
||||
else
|
||||
{
|
||||
var rxstr = string.Join(" ", data.Select(t => t.ToString("X2")));
|
||||
RaiseMessage(MessageType.Recv, $"Querh:{QueryIndex},Data:{rxstr}" );
|
||||
RaiseMessage(MessageType.Recv, rxstr);
|
||||
}
|
||||
|
||||
if (QueryIndex == 0)
|
||||
@@ -179,6 +211,13 @@ namespace arDev
|
||||
batH = (UInt16)(batH | batL);
|
||||
Current_Volt = (float)(batH / 100.0);
|
||||
|
||||
//충방전전류
|
||||
Int16 batHi = (Int16)LastReceiveBuffer[6];
|
||||
Int16 batLi = (Int16)LastReceiveBuffer[7];
|
||||
batHi = (Int16)(batHi << 8);
|
||||
batHi = (Int16)(batHi | batLi);
|
||||
Charge_Amp = (float)(batHi / 100.0);
|
||||
|
||||
//잔량확인
|
||||
batH = (UInt16)LastReceiveBuffer[8];
|
||||
batL = (UInt16)LastReceiveBuffer[9];
|
||||
@@ -224,107 +263,80 @@ namespace arDev
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _autocharge = false;
|
||||
public Boolean AutoCharge
|
||||
{
|
||||
get { return _autocharge; }
|
||||
set { _autocharge = false; }
|
||||
}
|
||||
|
||||
//public void ClearManualChargeCheckValue()
|
||||
//{
|
||||
// chk_timee = new DateTime(1982, 11, 23);
|
||||
// chk_times = new DateTime(1982, 11, 23);
|
||||
// chk_valuee = 0f;
|
||||
// chk_values = 0f;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 충전중인지?
|
||||
/// </summary>
|
||||
public bool IsCharging { get; private set; }
|
||||
DateTime ChargeStart = DateTime.Now;
|
||||
DateTime ChargeEnd = DateTime.Now;
|
||||
void CheckManualCharge()
|
||||
{
|
||||
if (AutoCharge)
|
||||
//충방전전력이 1보다 크면 충전으로 한다.
|
||||
if (Charge_Amp > 0.1)
|
||||
{
|
||||
if (chk_timee.Year != 1982)
|
||||
//기존에 충전상태가 OFF였다면 충전중으로 알려준다
|
||||
if (IsCharging == false)
|
||||
{
|
||||
chk_timee = new DateTime(1982, 11, 23);
|
||||
chk_valuee = 999f;
|
||||
IsCharging = true;
|
||||
ChargeStart = DateTime.Now;
|
||||
ChargeEnd = new DateTime(1982, 11, 23);
|
||||
try
|
||||
{
|
||||
ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeStart, true, Current_Level));
|
||||
}
|
||||
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
|
||||
}
|
||||
if (chk_times.Year != 1982)
|
||||
else
|
||||
{
|
||||
chk_times = new DateTime(1982, 11, 23);
|
||||
chk_values = 999f;
|
||||
//충전상태가 유지되고 있다.
|
||||
}
|
||||
}
|
||||
if (chk_times.Year == 1982)
|
||||
{
|
||||
chk_times = DateTime.Now;
|
||||
chk_values = Current_Level;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chk_timee.Year == 1982)
|
||||
//충전이해제되었다.. 단 바로 해제하지않고 1초정도 텀을 주고 OFF한다.
|
||||
if (IsCharging)
|
||||
{
|
||||
if ((Current_Level - chk_values) >= 0.1)
|
||||
if (ChargeEnd.Year == 1982)
|
||||
{
|
||||
//충전중이다
|
||||
chk_timee = DateTime.Now;
|
||||
chk_valuee = Current_Level;
|
||||
try
|
||||
{
|
||||
ChargeDetect?.Invoke(this, new ChargetDetectArgs(chk_times, chk_values, chk_timee, chk_valuee));
|
||||
}
|
||||
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
|
||||
|
||||
}
|
||||
else if ((Current_Level - chk_values) <= -0.1)
|
||||
{
|
||||
//방전중이다
|
||||
if (chk_times.Year != 1982) chk_times = new DateTime(1982, 11, 23);
|
||||
if (chk_timee.Year != 1982) chk_timee = new DateTime(1982, 11, 23);
|
||||
ChargeEnd = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
//아직 변화가 없으니 종료일을 기록하지 않는다
|
||||
var ts = DateTime.Now - ChargeEnd;
|
||||
if (ts.TotalSeconds > 2) //충전종료시그널후 2초후에 충전off를 알린다.
|
||||
{
|
||||
ChargeEnd = DateTime.Now;
|
||||
IsCharging = false;
|
||||
try
|
||||
{
|
||||
ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeEnd, false, Current_Level));
|
||||
}
|
||||
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//이미 종료일이 셋팅된 상태이다
|
||||
if ((Current_Level - chk_valuee) >= 0.1)
|
||||
{
|
||||
//종료시간을 시작값에 넣는다
|
||||
chk_times = chk_timee;
|
||||
chk_values = chk_valuee;
|
||||
|
||||
chk_timee = DateTime.Now;
|
||||
chk_valuee = Current_Level;
|
||||
try
|
||||
{
|
||||
ChargeDetect?.Invoke(this, new ChargetDetectArgs(chk_times, chk_values, chk_timee, chk_valuee));
|
||||
}
|
||||
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
|
||||
}
|
||||
else if ((Current_Level - chk_valuee) <= -0.1)
|
||||
{
|
||||
//방전중이다
|
||||
if (chk_times.Year != 1982) chk_times = new DateTime(1982, 11, 23);
|
||||
if (chk_timee.Year != 1982) chk_timee = new DateTime(1982, 11, 23);
|
||||
}
|
||||
else
|
||||
{
|
||||
//아직 변화가 없으니 종료일을 기록하지 않는다
|
||||
}
|
||||
//방전상태가 유지되고 있다.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public DateTime chk_times { get; set; } = new DateTime(1982, 11, 23);
|
||||
public DateTime chk_timee { get; set; } = new DateTime(1982, 11, 23);
|
||||
public float chk_values { get; set; } = 0f;
|
||||
public float chk_valuee { get; set; } = 0f;
|
||||
|
||||
public float Charge_Amp { get; set; } = 0f;
|
||||
public Int16 Charge_watt
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Int16)((Charge_Amp) * Current_Volt);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 전압
|
||||
/// </summary>
|
||||
@@ -407,7 +419,8 @@ namespace arDev
|
||||
cmd.Add(0xFF);
|
||||
cmd.Add(0xFD);
|
||||
cmd.Add(0x77);
|
||||
cmd.Add(0x0D);
|
||||
//cmd.Add(0x0D);
|
||||
//_device.DiscardInBuffer();
|
||||
return WriteData(cmd.ToArray());
|
||||
}
|
||||
|
||||
@@ -423,7 +436,8 @@ namespace arDev
|
||||
cmd.Add(0xFF);
|
||||
cmd.Add(0xFC);
|
||||
cmd.Add(0x77);
|
||||
cmd.Add(0x0D);
|
||||
//cmd.Add(0x0D);
|
||||
//_device.DiscardInBuffer();
|
||||
return WriteData(cmd.ToArray());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,14 @@ namespace arDev
|
||||
{
|
||||
public class ChargetDetectArgs : EventArgs
|
||||
{
|
||||
public DateTime times { get; set; }
|
||||
public DateTime timee { get; set; }
|
||||
public float values { get; set; }
|
||||
public float valuee { get; set; }
|
||||
public ChargetDetectArgs(DateTime times, float values, DateTime timee, float valuee)
|
||||
public DateTime time { get; set; }
|
||||
public float level { get; set; }
|
||||
public bool Detected { get; set; }
|
||||
public ChargetDetectArgs(DateTime times, bool detected, float values)
|
||||
{
|
||||
this.times = times;
|
||||
this.times = timee;
|
||||
this.values = values;
|
||||
this.valuee = valuee;
|
||||
this.time = times;
|
||||
this.level = values;
|
||||
this.Detected = detected;
|
||||
}
|
||||
}
|
||||
public class BMSInformationEventArgs : EventArgs
|
||||
|
||||
607
Cs_HMI/Project/Device/BMSSerialComm.cs
Normal file
607
Cs_HMI/Project/Device/BMSSerialComm.cs
Normal file
@@ -0,0 +1,607 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public abstract class BMSSerialComm : ISerialComm, IDisposable
|
||||
{
|
||||
protected System.IO.Ports.SerialPort _device;
|
||||
protected ManualResetEvent _mre;
|
||||
protected byte[] LastReceiveBuffer = new byte[] { };
|
||||
/// <summary>
|
||||
/// 최종 전송 메세지
|
||||
/// </summary>
|
||||
public byte[] lastSendBuffer = new byte[] { };
|
||||
//public int ValidCheckTimeMSec { get; set; } = 5000;
|
||||
protected List<byte> tempBuffer = new List<byte>();
|
||||
protected Boolean findSTX = false;
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime LastConnTime { get; set; }
|
||||
public DateTime LastConnTryTime { get; set; }
|
||||
public DateTime lastSendTime;
|
||||
/// <summary>
|
||||
/// 메세지 수신시 사용하는 내부버퍼
|
||||
/// </summary>
|
||||
protected List<byte> _buffer = new List<byte>();
|
||||
/// <summary>
|
||||
/// 데이터조회간격(초)
|
||||
/// </summary>
|
||||
public float ScanInterval { get; set; }
|
||||
|
||||
// public byte[] LastRecvData;
|
||||
public string LastRecvString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LastReceiveBuffer == null) return String.Empty;
|
||||
else return System.Text.Encoding.Default.GetString(LastReceiveBuffer);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 마지막으로 데이터를 받은 시간
|
||||
/// </summary>
|
||||
public DateTime lastRecvTime;
|
||||
|
||||
|
||||
public int WriteError = 0;
|
||||
public string WriteErrorMessage = string.Empty;
|
||||
public int WaitTimeout { get; set; } = 1000;
|
||||
public int MinRecvLength { get; set; } = 1;
|
||||
|
||||
// Polling Thread related
|
||||
protected Thread _recvThread;
|
||||
protected volatile bool _isReading = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 포트이름
|
||||
/// </summary>
|
||||
[Description("시리얼 포트 이름")]
|
||||
[Category("설정"), DisplayName("Port Name")]
|
||||
public string PortName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_device == null) return string.Empty;
|
||||
else return _device.PortName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.IsOpen)
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 포트이름을 변경할 수 없습니다", true));
|
||||
}
|
||||
else if (String.IsNullOrEmpty(value) == false)
|
||||
_device.PortName = value;
|
||||
else
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs("No PortName", true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int BaudRate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_device == null) return 0;
|
||||
else return _device.BaudRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.IsOpen)
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 BaudRate(를) 변경할 수 없습니다", true));
|
||||
}
|
||||
else if (value != 0)
|
||||
_device.BaudRate = value;
|
||||
else Message?.Invoke(this, new MessageEventArgs("No baud rate", true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BMSSerialComm()
|
||||
{
|
||||
_device = new System.IO.Ports.SerialPort();
|
||||
this.BaudRate = 9600;
|
||||
ScanInterval = 10;
|
||||
// _device.DataReceived += barcode_DataReceived; // Removed event handler
|
||||
_device.ErrorReceived += this.barcode_ErrorReceived;
|
||||
_device.WriteTimeout = 3000;
|
||||
_device.ReadTimeout = 3000;
|
||||
_device.ReadBufferSize = 8192;
|
||||
_device.WriteBufferSize = 8192;
|
||||
//_device.DiscardInBuffer();
|
||||
//_device.DiscardOutBuffer();
|
||||
ErrorMessage = string.Empty;
|
||||
lastRecvTime = DateTime.Parse("1982-11-23");
|
||||
LastConnTime = DateTime.Parse("1982-11-23");
|
||||
LastConnTryTime = DateTime.Parse("1982-11-23");
|
||||
lastRecvTime = DateTime.Parse("1982-11-23");
|
||||
this._mre = new ManualResetEvent(true);
|
||||
}
|
||||
|
||||
~BMSSerialComm()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
|
||||
// Flag: Has Dispose already been called?
|
||||
bool disposed = false;
|
||||
|
||||
// Public implementation of Dispose pattern callable by consumers.
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// Protected implementation of Dispose pattern.
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// Free any other managed objects here.
|
||||
//
|
||||
}
|
||||
|
||||
// Stop reading thread
|
||||
_isReading = false;
|
||||
|
||||
// _device.DataReceived -= barcode_DataReceived; // Removed event handler
|
||||
_device.ErrorReceived -= this.barcode_ErrorReceived;
|
||||
|
||||
if (_recvThread != null && _recvThread.IsAlive)
|
||||
{
|
||||
_recvThread.Join(500);
|
||||
}
|
||||
|
||||
if (_device != null)
|
||||
{
|
||||
if (_device.IsOpen) _device.Close();
|
||||
_device.Dispose();
|
||||
}
|
||||
|
||||
// Free any unmanaged objects here.
|
||||
//
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
public Boolean Open()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_device.IsOpen == false)
|
||||
{
|
||||
_device.Open();
|
||||
}
|
||||
|
||||
if (_device.IsOpen)
|
||||
{
|
||||
// Start polling thread
|
||||
if (_isReading == false)
|
||||
{
|
||||
_isReading = true;
|
||||
_recvThread = new Thread(ReadPort);
|
||||
_recvThread.IsBackground = true;
|
||||
_recvThread.Start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = ex.Message;
|
||||
Message.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public string GetHexString(Byte[] input)
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
foreach (byte b in input)
|
||||
sb.Append(" " + b.ToString("X2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 포트가 열려있는지 확인
|
||||
/// </summary>
|
||||
[Description("현재 시리얼포트가 열려있는지 확인합니다")]
|
||||
[Category("정보"), DisplayName("Port Open")]
|
||||
public Boolean IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_device == null) return false;
|
||||
return _device.IsOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isReading = false; // Stop thread loop
|
||||
|
||||
if (_recvThread != null && _recvThread.IsAlive)
|
||||
{
|
||||
if (!_recvThread.Join(500)) // Wait for thread to finish
|
||||
{
|
||||
// _recvThread.Abort(); // Avoid Abort if possible
|
||||
}
|
||||
}
|
||||
|
||||
if (_device != null && _device.IsOpen)
|
||||
{
|
||||
_device.DiscardInBuffer();
|
||||
_device.DiscardOutBuffer();
|
||||
_device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected Boolean RaiseRecvData()
|
||||
{
|
||||
return RaiseRecvData(LastReceiveBuffer.ToArray(), false);
|
||||
}
|
||||
/// <summary>
|
||||
/// 수신받은 메세지를 발생 시킵니다
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Boolean RaiseRecvData(byte[] Data, bool udpatelastbuffer)
|
||||
{
|
||||
//181206 - 최종수신 메세지 기록
|
||||
lastRecvTime = DateTime.Now;
|
||||
if (udpatelastbuffer && Data != null)
|
||||
{
|
||||
if (LastReceiveBuffer == null || LastReceiveBuffer.Length != Data.Length)
|
||||
{
|
||||
LastReceiveBuffer = new byte[Data.Length];
|
||||
Array.Copy(Data, LastReceiveBuffer, Data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// UI update might need Invoke if this event handler updates UI directly,
|
||||
// but usually the subscriber handles Invoke.
|
||||
// Since we are running on a background thread now, subscribers must be aware.
|
||||
Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
|
||||
if (ProcessRecvData(Data) == false)
|
||||
{
|
||||
//Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
|
||||
Message?.Invoke(this, new MessageEventArgs(this.ErrorMessage, true)); //errormessage
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수신받은 자료를 처리한다
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool ProcessRecvData(byte[] data);
|
||||
|
||||
#region "Internal Events"
|
||||
|
||||
void barcode_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs(e.ToString(), true));
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[] { };
|
||||
|
||||
// Replaced with ReadPort Loop
|
||||
/*
|
||||
void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
int ReadCount = _device.BytesToRead;
|
||||
|
||||
buffer = new byte[ReadCount];
|
||||
_device.Read(buffer, 0, buffer.Length);
|
||||
|
||||
System.Text.StringBuilder LogMsg = new StringBuilder();
|
||||
|
||||
byte[] remainBuffer;
|
||||
Repeat:
|
||||
if (CustomParser(buffer, out remainBuffer))
|
||||
{
|
||||
//분석완료이므로 받은 데이터를 버퍼에 기록한다
|
||||
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
|
||||
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
|
||||
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
|
||||
tempBuffer.Clear();
|
||||
|
||||
//수신메세지발생
|
||||
RaiseRecvData();
|
||||
if (remainBuffer != null && remainBuffer.Length > 0)
|
||||
{
|
||||
//버퍼를 변경해서 다시 전송을 해준다.
|
||||
Array.Resize(ref buffer, remainBuffer.Length);
|
||||
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
|
||||
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//if (IsOpen)
|
||||
//{
|
||||
// //_device.DiscardInBuffer();
|
||||
// //_device.DiscardOutBuffer();
|
||||
//}
|
||||
ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
void ReadPort()
|
||||
{
|
||||
while (_isReading)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_device == null || !_device.IsOpen)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
int readCount = _device.BytesToRead;
|
||||
if (readCount > 0)
|
||||
{
|
||||
byte[] buffer = new byte[readCount];
|
||||
_device.Read(buffer, 0, buffer.Length);
|
||||
|
||||
byte[] remainBuffer;
|
||||
Repeat:
|
||||
if (CustomParser(buffer, out remainBuffer))
|
||||
{
|
||||
//분석완료이므로 받은 데이터를 버퍼에 기록한다
|
||||
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
|
||||
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
|
||||
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
|
||||
tempBuffer.Clear();
|
||||
|
||||
//수신메세지발생
|
||||
RaiseRecvData();
|
||||
if (remainBuffer != null && remainBuffer.Length > 0)
|
||||
{
|
||||
//버퍼를 변경해서 다시 전송을 해준다.
|
||||
buffer = new byte[remainBuffer.Length]; // Reallocate buffer for remaining data
|
||||
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
|
||||
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(20); // Data 없음, 대기
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Thread 상에서 Exception 발생 시 로그 남기고 계속 진행 여부 결정
|
||||
// 여기서는 에러 메시지 발생시키고 Sleep
|
||||
ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region "External Events"
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 오류 및 기타 일반 메세지
|
||||
/// </summary>
|
||||
public event EventHandler<MessageEventArgs> Message;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Event Args"
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 수신할떄 사용함(RAW 포함)
|
||||
/// </summary>
|
||||
public class ReceiveDataEventArgs : EventArgs
|
||||
{
|
||||
private byte[] _buffer = null;
|
||||
|
||||
/// <summary>
|
||||
/// 바이트배열의 버퍼값
|
||||
/// </summary>
|
||||
public byte[] Value { get { return _buffer; } }
|
||||
|
||||
/// <summary>
|
||||
/// 버퍼(바이트배열)의 데이터를 문자로 반환합니다.
|
||||
/// </summary>
|
||||
public string StrValue
|
||||
{
|
||||
get
|
||||
{
|
||||
//return string.Empty;
|
||||
|
||||
if (_buffer == null || _buffer.Length < 1) return string.Empty;
|
||||
else return System.Text.Encoding.Default.GetString(_buffer);
|
||||
}
|
||||
}
|
||||
public ReceiveDataEventArgs(byte[] buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메세지를 강제 발생
|
||||
/// </summary>
|
||||
/// <param name="mt"></param>
|
||||
/// <param name="message"></param>
|
||||
protected virtual void RaiseMessage(MessageType mt, string message)
|
||||
{
|
||||
this.Message?.Invoke(this, new MessageEventArgs(mt, message));
|
||||
}
|
||||
public enum MessageType
|
||||
{
|
||||
Normal,
|
||||
Error,
|
||||
Send,
|
||||
Recv,
|
||||
}
|
||||
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
public MessageType MsgType { get; set; }
|
||||
private string _message = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Recv,Send,Normal,Error 모두 지원
|
||||
/// </summary>
|
||||
public string Message { get { return _message; } }
|
||||
|
||||
private byte[] _data = null;
|
||||
|
||||
/// <summary>
|
||||
/// Recv,Send에서만 값이 존재 합니다
|
||||
/// </summary>
|
||||
public byte[] Data { get { return _data; } }
|
||||
public MessageEventArgs(string Message, bool isError = false)
|
||||
{
|
||||
if (isError) MsgType = MessageType.Error;
|
||||
else MsgType = MessageType.Normal;
|
||||
_message = Message;
|
||||
}
|
||||
public MessageEventArgs(MessageType msgtype, string Message)
|
||||
{
|
||||
MsgType = msgtype;
|
||||
_message = Message;
|
||||
_data = System.Text.Encoding.Default.GetBytes(Message);
|
||||
}
|
||||
|
||||
public MessageEventArgs(byte[] buffer, bool isRecv = true)
|
||||
{
|
||||
if (isRecv) MsgType = MessageType.Recv;
|
||||
else MsgType = MessageType.Send;
|
||||
_data = new byte[buffer.Length];
|
||||
Array.Copy(buffer, _data, Data.Length);
|
||||
_message = System.Text.Encoding.Default.GetString(_data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다
|
||||
/// </summary>
|
||||
public Boolean IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsOpen == false) return false;
|
||||
if (lastRecvTime.Year == 1982) return false;
|
||||
var ts = DateTime.Now - lastRecvTime;
|
||||
if (ts.TotalSeconds > (this.ScanInterval * 2.5)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected bool WriteData(string cmd)
|
||||
{
|
||||
return WriteData(System.Text.Encoding.Default.GetBytes(cmd));
|
||||
}
|
||||
/// <summary>
|
||||
/// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신)
|
||||
/// </summary>
|
||||
protected Boolean WriteData(byte[] data)
|
||||
{
|
||||
Boolean bRet = false;
|
||||
|
||||
//171205 : 타임아웃시간추가
|
||||
if (!_mre.WaitOne(WaitTimeout))
|
||||
{
|
||||
ErrorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms";
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ErrorMessage, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
_mre.Reset();
|
||||
|
||||
//Array.Resize(ref data, data.Length + 2);
|
||||
|
||||
try
|
||||
{
|
||||
lastSendTime = DateTime.Now;
|
||||
if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113
|
||||
else Array.Resize(ref lastSendBuffer, data.Length);
|
||||
Array.Copy(data, lastSendBuffer, data.Length);
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
_device.Write(data, i, 1);
|
||||
|
||||
//_device.Write(data, 0, data.Length);
|
||||
|
||||
//171113
|
||||
this.Message?.Invoke(this, new MessageEventArgs(data, false));
|
||||
|
||||
bRet = true;
|
||||
WriteError = 0;
|
||||
WriteErrorMessage = string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// this.isinit = false;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
bRet = false;
|
||||
WriteError += 1; //연속쓰기오류횟수
|
||||
WriteErrorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mre.Set();
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -14,11 +14,11 @@ using System.Windows.Forms;
|
||||
|
||||
namespace Project.Device
|
||||
{
|
||||
public class Xbee : SerialPort
|
||||
public class Xbee : SerialPort, arDev.ISerialComm
|
||||
{
|
||||
public string buffer = string.Empty;
|
||||
public System.Text.StringBuilder newbuffer = new StringBuilder();
|
||||
public string errorMessage = string.Empty;
|
||||
public string ErrorMessage { get; set; } = string.Empty;
|
||||
public DateTime LastStatusSendTime { get; set; } = DateTime.Now;
|
||||
private EEProtocol proto;
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace Project.Device
|
||||
|
||||
public Xbee()
|
||||
{
|
||||
this.WriteTimeout = 500;
|
||||
this.ReadTimeout = 500;
|
||||
this.DataReceived += Xbee_DataReceived;
|
||||
proto = new EEProtocol();
|
||||
proto.OnDataReceived += Proto_OnDataReceived;
|
||||
@@ -65,11 +67,23 @@ namespace Project.Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
ErrorMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public new bool Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Close();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
public new bool Open()
|
||||
{
|
||||
try
|
||||
@@ -79,8 +93,8 @@ namespace Project.Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
PUB.logxbee.AddE(errorMessage);
|
||||
ErrorMessage = ex.Message;
|
||||
PUB.logxbee.AddE(ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -92,19 +106,14 @@ namespace Project.Device
|
||||
var cmd = e.ReceivedPacket.Command.ToString("X2");
|
||||
var id = e.ReceivedPacket.ID.ToString("X2");
|
||||
PUB.logxbee.Add("RX", $"{hexstrRaw}\nID:{id},CMD:{cmd},DATA:{hexstr}");
|
||||
|
||||
ProtocReceived?.Invoke(this, e);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void Proto_OnMessage(object sender, EEProtocol.MessageEventArgs e)
|
||||
{
|
||||
MessageReceived?.Invoke(this, new MessageArgs(e.IsError, e.Message));
|
||||
}
|
||||
|
||||
|
||||
private void Xbee_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
|
||||
{
|
||||
var dev = sender as System.IO.Ports.SerialPort;
|
||||
@@ -112,6 +121,7 @@ namespace Project.Device
|
||||
dev.Read(buffer, 0, buffer.Length);
|
||||
proto.ProcessReceivedData(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이동완료 신호 전송
|
||||
/// </summary>
|
||||
@@ -147,10 +157,12 @@ namespace Project.Device
|
||||
byte cmd = (byte)ENIGProtocol.AGVCommandEH.Error;
|
||||
if (errormessage.Length > 30) errormessage = errormessage.Substring(0, 29);
|
||||
|
||||
var data = new byte[] { (byte)errcode };
|
||||
var data = new List<byte>();
|
||||
data.Add((byte)errcode);
|
||||
var datamsg = System.Text.Encoding.Default.GetBytes(errormessage);
|
||||
data.AddRange(datamsg);
|
||||
|
||||
var packet = proto.CreatePacket(id, cmd, data);
|
||||
var packet = proto.CreatePacket(id, cmd, data.ToArray());
|
||||
Send(packet);
|
||||
}
|
||||
|
||||
@@ -166,12 +178,18 @@ namespace Project.Device
|
||||
public bool CleanerInComplete { get; set; }
|
||||
public bool CleanerOutComplete { get; set; }
|
||||
|
||||
ManualResetEvent sendlock = new ManualResetEvent(true);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AGV상태를 Xbee 로 전송한다
|
||||
/// </summary>
|
||||
public void SendStatus()
|
||||
{
|
||||
if (this.IsOpen == false) return;
|
||||
if (sendlock.WaitOne() == false) return;
|
||||
sendlock.Reset();
|
||||
|
||||
/*
|
||||
Mode[1] : 0=manual, 1=auto
|
||||
RunSt[1] : 0=stop, 1=run, 2=error
|
||||
@@ -222,17 +240,17 @@ namespace Project.Device
|
||||
data[5] = (byte)((VAR.BOOL[eVarBool.FLAG_CHARGEONA] || VAR.BOOL[eVarBool.FLAG_CHARGEONM]) ? 1 : 0);
|
||||
|
||||
// CartSt
|
||||
if (PUB.AGV.signal.cart_detect1 && PUB.AGV.signal.cart_detect2)
|
||||
if (PUB.AGV.signal2.cart_detect1 && PUB.AGV.signal2.cart_detect2)
|
||||
data[6] = 1; // 센서두개가 모두 감지되는 경우
|
||||
else if (PUB.AGV.signal.cart_detect1 == false && PUB.AGV.signal.cart_detect2 == false)
|
||||
else if (PUB.AGV.signal2.cart_detect1 == false && PUB.AGV.signal2.cart_detect2 == false)
|
||||
data[6] = 0; // 센서두개가 모두 감지되지 않는 경우
|
||||
else
|
||||
data[6] = 2; // 센서하나만 감지되는 경우
|
||||
|
||||
// LiftSt
|
||||
if (PUB.AGV.signal.lift_up)
|
||||
if (PUB.AGV.signal1.lift_up)
|
||||
data[7] = 1; // 위로 올라가는 경우
|
||||
else if (PUB.AGV.signal.lift_down)
|
||||
else if (PUB.AGV.signal1.lift_down)
|
||||
data[7] = 0; // 아래로 내려가는 경우
|
||||
else
|
||||
data[7] = 2; // unknown (기본값)
|
||||
@@ -246,14 +264,21 @@ namespace Project.Device
|
||||
var cmd = (byte)ENIGProtocol.AGVCommandEH.Status;
|
||||
var packet = proto.CreatePacket(PUB.setting.XBE_ID, cmd, data);
|
||||
if (Send(packet))
|
||||
PUB.logxbee.AddI($"Send status {packet.Length} {packet.HexString()}");
|
||||
PUB.logxbee.AddI($"Send status [O] : {packet.Length} {packet.HexString()}");
|
||||
else
|
||||
PUB.logxbee.AddE($"Send status [X] : {packet.Length} {packet.HexString()}");
|
||||
LastStatusSendTime = DateTime.Now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
PUB.logxbee.AddE(errorMessage);
|
||||
ErrorMessage = ex.Message;
|
||||
PUB.logxbee.AddE(ErrorMessage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
sendlock.Set();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Animation;
|
||||
using AR;
|
||||
using arCtl;
|
||||
using COMM;
|
||||
using Project.StateMachine;
|
||||
|
||||
namespace Project
|
||||
{
|
||||
/// <summary>
|
||||
/// 장치 연결 및 상태 전송을 담당하는 별도 태스크
|
||||
/// </summary>
|
||||
public partial class fMain
|
||||
{
|
||||
// 장치 관리 태스크 관련
|
||||
private Task deviceManagementTask;
|
||||
private CancellationTokenSource deviceManagementCts;
|
||||
private volatile bool isDeviceManagementRunning = false;
|
||||
|
||||
/// <summary>
|
||||
/// 장치 관리 태스크 시작 (IDLE 상태 진입 시 호출)
|
||||
/// </summary>
|
||||
public void StartDeviceManagementTask()
|
||||
{
|
||||
if (isDeviceManagementRunning)
|
||||
{
|
||||
PUB.log.Add("DeviceManagement", "이미 실행 중입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
isDeviceManagementRunning = true;
|
||||
deviceManagementCts = new CancellationTokenSource();
|
||||
|
||||
deviceManagementTask = Task.Factory.StartNew(
|
||||
() => DeviceManagementWorker(deviceManagementCts.Token),
|
||||
deviceManagementCts.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
|
||||
PUB.log.Add("DeviceManagement", "장치 관리 태스크 시작");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 장치 관리 태스크 종료
|
||||
/// File : /device/_DeviceManagement.cs
|
||||
/// </summary>
|
||||
public void StopDeviceManagementTask()
|
||||
{
|
||||
if (!isDeviceManagementRunning)
|
||||
return;
|
||||
|
||||
isDeviceManagementRunning = false;
|
||||
|
||||
try
|
||||
{
|
||||
deviceManagementCts?.Cancel();
|
||||
|
||||
if (deviceManagementTask != null)
|
||||
{
|
||||
if (!deviceManagementTask.Wait(3000)) // 3초 대기
|
||||
{
|
||||
PUB.log.AddE("DeviceManagement:태스크 종료 대기 시간 초과");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"DeviceManagement 종료 중 오류: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
deviceManagementCts?.Dispose();
|
||||
deviceManagementCts = null;
|
||||
deviceManagementTask = null;
|
||||
PUB.log.Add("DeviceManagement", "장치 관리 태스크 종료");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 장치 관리 워커 (별도 태스크에서 실행)
|
||||
/// - 장치 연결 관리 (AGV, XBee, BMS)
|
||||
/// - 자동 상태 전송 (XBee, BMS)
|
||||
/// </summary>
|
||||
private void DeviceManagementWorker(CancellationToken cancellationToken)
|
||||
{
|
||||
PUB.log.Add("DeviceManagementWorker", "시작");
|
||||
|
||||
DateTime lastXbeStatusSendTime = DateTime.Now;
|
||||
DateTime lastBmsQueryTime = DateTime.Now;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested && isDeviceManagementRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 상태머신이 IDLE 이상이고 CLOSING 미만일 때만 동작
|
||||
if (PUB.sm.Step >= eSMStep.IDLE && PUB.sm.Step < eSMStep.CLOSING)
|
||||
{
|
||||
// ========== 1. 장치 연결 관리 ==========
|
||||
ManageDeviceConnections();
|
||||
|
||||
// ========== 2. XBee 상태 전송 ==========
|
||||
if (PUB.XBE != null && PUB.XBE.IsOpen)
|
||||
{
|
||||
var tsXbe = DateTime.Now - lastXbeStatusSendTime;
|
||||
if (tsXbe.TotalSeconds >= PUB.setting.interval_xbe)
|
||||
{
|
||||
lastXbeStatusSendTime = DateTime.Now;
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PUB.XBE.SendStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"XBee SendStatus 오류: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. BMS 쿼리 및 배터리 경고 ==========
|
||||
if (PUB.BMS != null && PUB.BMS.IsOpen)
|
||||
{
|
||||
var tsBms = DateTime.Now - lastBmsQueryTime;
|
||||
if (tsBms.TotalSeconds >= PUB.setting.interval_bms)
|
||||
{
|
||||
lastBmsQueryTime = DateTime.Now;
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PUB.BMS.SendQuery();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"BMS SendQuery 오류: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 배터리 경고음
|
||||
try
|
||||
{
|
||||
Update_BatteryWarnSpeak();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"BatteryWarnSpeak 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"DeviceManagementWorker 오류: {ex.Message}");
|
||||
}
|
||||
|
||||
// 1초 대기 (또는 취소 요청 시 즉시 종료)
|
||||
try
|
||||
{
|
||||
Task.Delay(1000, cancellationToken).Wait();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
PUB.log.Add("DeviceManagementWorker", "종료");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 장치 연결 상태 관리
|
||||
/// </summary>
|
||||
private void ManageDeviceConnections()
|
||||
{
|
||||
try
|
||||
{
|
||||
// AGV 연결
|
||||
ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
|
||||
eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV);
|
||||
|
||||
// XBee 연결
|
||||
ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE,
|
||||
eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, eVarTime.LastRecv_XBE);
|
||||
|
||||
// BMS 연결
|
||||
if (PUB.BMS.IsOpen == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT);
|
||||
if (ts.TotalSeconds > 3)
|
||||
{
|
||||
PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}");
|
||||
PUB.BMS.PortName = PUB.setting.Port_BAT;
|
||||
if (PUB.BMS.Open())
|
||||
PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})");
|
||||
|
||||
VAR.TIME.Update(eVarTime.LastConn_BAT);
|
||||
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
|
||||
}
|
||||
}
|
||||
else if (PUB.BMS.IsValid == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
|
||||
if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5))
|
||||
{
|
||||
PUB.log.Add("BMS 자동 연결 해제 (응답 없음)");
|
||||
PUB.BMS.Close();
|
||||
VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"ManageDeviceConnections 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시리얼 포트 연결 (arDev.arRS232)
|
||||
/// </summary>
|
||||
void ConnectSerialPort(arDev.arRS232 dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
|
||||
{
|
||||
if (dev.IsOpen == false && port.isEmpty() == false)
|
||||
{
|
||||
var tsPLC = VAR.TIME.RUN(conntry);
|
||||
if (tsPLC.TotalSeconds > 5)
|
||||
{
|
||||
VAR.TIME.Update(conntry);
|
||||
try
|
||||
{
|
||||
VAR.TIME.Update(recvtime);
|
||||
dev.PortName = port;
|
||||
dev.BaudRate = baud;
|
||||
if (dev.Open())
|
||||
{
|
||||
PUB.log.Add(port, $"[AGV:{port}:{baud}] 연결 완료");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errmessage = dev.errorMessage;
|
||||
PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}");
|
||||
}
|
||||
VAR.TIME.Update(conn);
|
||||
VAR.TIME.Update(conntry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (dev.PortName.Equals(port) == false)
|
||||
{
|
||||
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
|
||||
dev.Close();
|
||||
VAR.TIME.Update(conntry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시리얼 포트 연결 (Device.Xbee)
|
||||
/// </summary>
|
||||
void ConnectSerialPort(Device.Xbee dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
|
||||
{
|
||||
if (dev.IsOpen == false && port.isEmpty() == false)
|
||||
{
|
||||
var tsPLC = VAR.TIME.RUN(conntry);
|
||||
if (tsPLC.TotalSeconds > 5)
|
||||
{
|
||||
VAR.TIME.Update(conntry);
|
||||
try
|
||||
{
|
||||
VAR.TIME.Update(recvtime);
|
||||
dev.PortName = port;
|
||||
dev.BaudRate = baud;
|
||||
if (dev.Open())
|
||||
{
|
||||
PUB.log.Add(port, $"[XBEE:{port}:{baud}] 연결 완료");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errmessage = dev.errorMessage;
|
||||
PUB.log.AddE($"[XBEE:{port}:{baud}] {errmessage}");
|
||||
}
|
||||
VAR.TIME.Update(conn);
|
||||
VAR.TIME.Update(conntry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (dev.PortName.Equals(port) == false)
|
||||
{
|
||||
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
|
||||
dev.Close();
|
||||
VAR.TIME[(int)conntry] = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,815 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms; // required for Message
|
||||
using System.Runtime.InteropServices; // required for Marshal
|
||||
using System.IO;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
// DriveDetector - rev. 1, Oct. 31 2007
|
||||
|
||||
namespace usbdetect
|
||||
{
|
||||
/// <summary>
|
||||
/// Hidden Form which we use to receive Windows messages about flash drives
|
||||
/// </summary>
|
||||
internal class DetectorForm : Form
|
||||
{
|
||||
private Label label1;
|
||||
private DriveDetector mDetector = null;
|
||||
|
||||
/// <summary>
|
||||
/// Set up the hidden form.
|
||||
/// </summary>
|
||||
/// <param name="detector">DriveDetector object which will receive notification about USB drives, see WndProc</param>
|
||||
public DetectorForm(DriveDetector detector)
|
||||
{
|
||||
mDetector = detector;
|
||||
this.MinimizeBox = false;
|
||||
this.MaximizeBox = false;
|
||||
this.ShowInTaskbar = false;
|
||||
this.ShowIcon = false;
|
||||
this.FormBorderStyle = FormBorderStyle.None;
|
||||
this.Load += new System.EventHandler(this.Load_Form);
|
||||
this.Activated += new EventHandler(this.Form_Activated);
|
||||
}
|
||||
|
||||
private void Load_Form(object sender, EventArgs e)
|
||||
{
|
||||
// We don't really need this, just to display the label in designer ...
|
||||
InitializeComponent();
|
||||
|
||||
// Create really small form, invisible anyway.
|
||||
this.Size = new System.Drawing.Size(5, 5);
|
||||
}
|
||||
|
||||
private void Form_Activated(object sender, EventArgs e)
|
||||
{
|
||||
this.Visible = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function receives all the windows messages for this window (form).
|
||||
/// We call the DriveDetector from here so that is can pick up the messages about
|
||||
/// drives arrived and removed.
|
||||
/// </summary>
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
base.WndProc(ref m);
|
||||
|
||||
if (mDetector != null)
|
||||
{
|
||||
mDetector.WndProc(ref m);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(13, 30);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(377, 12);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "This is invisible form. To see DriveDetector code click View Code";
|
||||
//
|
||||
// DetectorForm
|
||||
//
|
||||
this.ClientSize = new System.Drawing.Size(435, 80);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Name = "DetectorForm";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
} // class DetectorForm
|
||||
|
||||
|
||||
// Delegate for event handler to handle the device events
|
||||
public delegate void DriveDetectorEventHandler(Object sender, DriveDetectorEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Our class for passing in custom arguments to our event handlers
|
||||
///
|
||||
/// </summary>
|
||||
public class DriveDetectorEventArgs : EventArgs
|
||||
{
|
||||
|
||||
|
||||
public DriveDetectorEventArgs()
|
||||
{
|
||||
Cancel = false;
|
||||
Drive = "";
|
||||
HookQueryRemove = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/Set the value indicating that the event should be cancelled
|
||||
/// Only in QueryRemove handler.
|
||||
/// </summary>
|
||||
public bool Cancel;
|
||||
|
||||
/// <summary>
|
||||
/// Drive letter for the device which caused this event
|
||||
/// </summary>
|
||||
public string Drive;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true in your DeviceArrived event handler if you wish to receive the
|
||||
/// QueryRemove event for this drive.
|
||||
/// </summary>
|
||||
public bool HookQueryRemove;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Detects insertion or removal of removable drives.
|
||||
/// Use it in 1 or 2 steps:
|
||||
/// 1) Create instance of this class in your project and add handlers for the
|
||||
/// DeviceArrived, DeviceRemoved and QueryRemove events.
|
||||
/// AND (if you do not want drive detector to creaate a hidden form))
|
||||
/// 2) Override WndProc in your form and call DriveDetector's WndProc from there.
|
||||
/// If you do not want to do step 2, just use the DriveDetector constructor without arguments and
|
||||
/// it will create its own invisible form to receive messages from Windows.
|
||||
/// </summary>
|
||||
class DriveDetector : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Events signalized to the client app.
|
||||
/// Add handlers for these events in your form to be notified of removable device events
|
||||
/// </summary>
|
||||
public event DriveDetectorEventHandler DeviceArrived;
|
||||
public event DriveDetectorEventHandler DeviceRemoved;
|
||||
public event DriveDetectorEventHandler QueryRemove;
|
||||
|
||||
/// <summary>
|
||||
/// The easiest way to use DriveDetector.
|
||||
/// It will create hidden form for processing Windows messages about USB drives
|
||||
/// You do not need to override WndProc in your form.
|
||||
/// </summary>
|
||||
public DriveDetector()
|
||||
{
|
||||
DetectorForm frm = new DetectorForm(this);
|
||||
frm.Show(); // will be hidden immediatelly
|
||||
Init(frm, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alternate constructor.
|
||||
/// Pass in your Form and DriveDetector will not create hidden form.
|
||||
/// </summary>
|
||||
/// <param name="control">object which will receive Windows messages.
|
||||
/// Pass "this" as this argument from your form class.</param>
|
||||
public DriveDetector(Control control)
|
||||
{
|
||||
Init(control, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consructs DriveDetector object setting also path to file which should be opened
|
||||
/// when registering for query remove.
|
||||
/// </summary>
|
||||
///<param name="control">object which will receive Windows messages.
|
||||
/// Pass "this" as this argument from your form class.</param>
|
||||
/// <param name="FileToOpen">Optional. Name of a file on the removable drive which should be opened.
|
||||
/// If null, root directory of the drive will be opened. Opening a file is needed for us
|
||||
/// to be able to register for the query remove message. TIP: For files use relative path without drive letter.
|
||||
/// e.g. "SomeFolder\file_on_flash.txt"</param>
|
||||
public DriveDetector(Control control, string FileToOpen)
|
||||
{
|
||||
Init(control, FileToOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// init the DriveDetector object
|
||||
/// </summary>
|
||||
/// <param name="intPtr"></param>
|
||||
private void Init(Control control, string fileToOpen)
|
||||
{
|
||||
mFileToOpen = fileToOpen;
|
||||
mFileOnFlash = null;
|
||||
mDeviceNotifyHandle = IntPtr.Zero;
|
||||
mRecipientHandle = control.Handle;
|
||||
mDirHandle = IntPtr.Zero; // handle to the root directory of the flash drive which we open
|
||||
mCurrentDrive = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value indicating whether the query remove event will be fired.
|
||||
/// </summary>
|
||||
public bool IsQueryHooked
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mDeviceNotifyHandle == IntPtr.Zero)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets letter of drive which is currently hooked. Empty string if none.
|
||||
/// See also IsQueryHooked.
|
||||
/// </summary>
|
||||
public string HookedDrive
|
||||
{
|
||||
get
|
||||
{
|
||||
return mCurrentDrive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file stream for file which this class opened on a drive to be notified
|
||||
/// about it's removal.
|
||||
/// This will be null unless you specified a file to open (DriveDetector opens root directory of the flash drive)
|
||||
/// </summary>
|
||||
public FileStream OpenedFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return mFileOnFlash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hooks specified drive to receive a message when it is being removed.
|
||||
/// This can be achieved also by setting e.HookQueryRemove to true in your
|
||||
/// DeviceArrived event handler.
|
||||
/// By default DriveDetector will open the root directory of the flash drive to obtain notification handle
|
||||
/// from Windows (to learn when the drive is about to be removed).
|
||||
/// </summary>
|
||||
/// <param name="fileOnDrive">Drive letter or relative path to a file on the drive which should be
|
||||
/// used to get a handle - required for registering to receive query remove messages.
|
||||
/// If only drive letter is specified (e.g. "D:\\", root directory of the drive will be opened.</param>
|
||||
/// <returns>true if hooked ok, false otherwise</returns>
|
||||
public bool EnableQueryRemove(string fileOnDrive)
|
||||
{
|
||||
if (fileOnDrive == null || fileOnDrive.Length == 0)
|
||||
throw new ArgumentException("Drive path must be supplied to register for Query remove.");
|
||||
|
||||
if ( fileOnDrive.Length == 2 && fileOnDrive[1] == ':' )
|
||||
fileOnDrive += '\\'; // append "\\" if only drive letter with ":" was passed in.
|
||||
|
||||
if (mDeviceNotifyHandle != IntPtr.Zero)
|
||||
{
|
||||
// Unregister first...
|
||||
RegisterForDeviceChange(false, null);
|
||||
}
|
||||
|
||||
if (Path.GetFileName(fileOnDrive).Length == 0 ||!File.Exists(fileOnDrive))
|
||||
mFileToOpen = null; // use root directory...
|
||||
else
|
||||
mFileToOpen = fileOnDrive;
|
||||
|
||||
RegisterQuery(Path.GetPathRoot(fileOnDrive));
|
||||
if (mDeviceNotifyHandle == IntPtr.Zero)
|
||||
return false; // failed to register
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unhooks any currently hooked drive so that the query remove
|
||||
/// message is not generated for it.
|
||||
/// </summary>
|
||||
public void DisableQueryRemove()
|
||||
{
|
||||
if (mDeviceNotifyHandle != IntPtr.Zero)
|
||||
{
|
||||
RegisterForDeviceChange(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unregister and close the file we may have opened on the removable drive.
|
||||
/// Garbage collector will call this method.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
RegisterForDeviceChange(false, null);
|
||||
}
|
||||
|
||||
|
||||
#region WindowProc
|
||||
/// <summary>
|
||||
/// Message handler which must be called from client form.
|
||||
/// Processes Windows messages and calls event handlers.
|
||||
/// </summary>
|
||||
/// <param name="m"></param>
|
||||
public void WndProc(ref Message m)
|
||||
{
|
||||
int devType;
|
||||
char c;
|
||||
|
||||
if (m.Msg == WM_DEVICECHANGE)
|
||||
{
|
||||
// WM_DEVICECHANGE can have several meanings depending on the WParam value...
|
||||
switch (m.WParam.ToInt32())
|
||||
{
|
||||
|
||||
//
|
||||
// New device has just arrived
|
||||
//
|
||||
case DBT_DEVICEARRIVAL:
|
||||
|
||||
devType = Marshal.ReadInt32(m.LParam, 4);
|
||||
if (devType == DBT_DEVTYP_VOLUME)
|
||||
{
|
||||
DEV_BROADCAST_VOLUME vol;
|
||||
vol = (DEV_BROADCAST_VOLUME)
|
||||
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
|
||||
|
||||
// Get the drive letter
|
||||
c = DriveMaskToLetter(vol.dbcv_unitmask);
|
||||
|
||||
|
||||
//
|
||||
// Call the client event handler
|
||||
//
|
||||
// We should create copy of the event before testing it and
|
||||
// calling the delegate - if any
|
||||
DriveDetectorEventHandler tempDeviceArrived = DeviceArrived;
|
||||
if ( tempDeviceArrived != null )
|
||||
{
|
||||
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
|
||||
e.Drive = c + ":\\";
|
||||
tempDeviceArrived(this, e);
|
||||
|
||||
// Register for query remove if requested
|
||||
if (e.HookQueryRemove)
|
||||
{
|
||||
// If something is already hooked, unhook it now
|
||||
if (mDeviceNotifyHandle != IntPtr.Zero)
|
||||
{
|
||||
RegisterForDeviceChange(false, null);
|
||||
}
|
||||
|
||||
RegisterQuery(c + ":\\");
|
||||
}
|
||||
} // if has event handler
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Device is about to be removed
|
||||
// Any application can cancel the removal
|
||||
//
|
||||
case DBT_DEVICEQUERYREMOVE:
|
||||
|
||||
devType = Marshal.ReadInt32(m.LParam, 4);
|
||||
if (devType == DBT_DEVTYP_HANDLE)
|
||||
{
|
||||
// TODO: we could get the handle for which this message is sent
|
||||
// from vol.dbch_handle and compare it against a list of handles for
|
||||
// which we have registered the query remove message (?)
|
||||
//DEV_BROADCAST_HANDLE vol;
|
||||
//vol = (DEV_BROADCAST_HANDLE)
|
||||
// Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HANDLE));
|
||||
// if ( vol.dbch_handle ....
|
||||
|
||||
|
||||
//
|
||||
// Call the event handler in client
|
||||
//
|
||||
DriveDetectorEventHandler tempQuery = QueryRemove;
|
||||
if (tempQuery != null)
|
||||
{
|
||||
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
|
||||
e.Drive = mCurrentDrive; // drive which is hooked
|
||||
tempQuery(this, e);
|
||||
|
||||
// If the client wants to cancel, let Windows know
|
||||
if (e.Cancel)
|
||||
{
|
||||
m.Result = (IntPtr)BROADCAST_QUERY_DENY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change 28.10.2007: Unregister the notification, this will
|
||||
// close the handle to file or root directory also.
|
||||
// We have to close it anyway to allow the removal so
|
||||
// even if some other app cancels the removal we would not know about it...
|
||||
RegisterForDeviceChange(false, null); // will also close the mFileOnFlash
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
//
|
||||
// Device has been removed
|
||||
//
|
||||
case DBT_DEVICEREMOVECOMPLETE:
|
||||
|
||||
devType = Marshal.ReadInt32(m.LParam, 4);
|
||||
if (devType == DBT_DEVTYP_VOLUME)
|
||||
{
|
||||
devType = Marshal.ReadInt32(m.LParam, 4);
|
||||
if (devType == DBT_DEVTYP_VOLUME)
|
||||
{
|
||||
DEV_BROADCAST_VOLUME vol;
|
||||
vol = (DEV_BROADCAST_VOLUME)
|
||||
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
|
||||
c = DriveMaskToLetter(vol.dbcv_unitmask);
|
||||
|
||||
//
|
||||
// Call the client event handler
|
||||
//
|
||||
DriveDetectorEventHandler tempDeviceRemoved = DeviceRemoved;
|
||||
if (tempDeviceRemoved != null)
|
||||
{
|
||||
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
|
||||
e.Drive = c + ":\\";
|
||||
tempDeviceRemoved(this, e);
|
||||
}
|
||||
|
||||
// TODO: we could unregister the notify handle here if we knew it is the
|
||||
// right drive which has been just removed
|
||||
//RegisterForDeviceChange(false, null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region Private Area
|
||||
|
||||
/// <summary>
|
||||
/// New: 28.10.2007 - handle to root directory of flash drive which is opened
|
||||
/// for device notification
|
||||
/// </summary>
|
||||
private IntPtr mDirHandle = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Class which contains also handle to the file opened on the flash drive
|
||||
/// </summary>
|
||||
private FileStream mFileOnFlash = null;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the file to try to open on the removable drive for query remove registration
|
||||
/// </summary>
|
||||
private string mFileToOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Handle to file which we keep opened on the drive if query remove message is required by the client
|
||||
/// </summary>
|
||||
private IntPtr mDeviceNotifyHandle;
|
||||
|
||||
/// <summary>
|
||||
/// Handle of the window which receives messages from Windows. This will be a form.
|
||||
/// </summary>
|
||||
private IntPtr mRecipientHandle;
|
||||
|
||||
/// <summary>
|
||||
/// Drive which is currently hooked for query remove
|
||||
/// </summary>
|
||||
private string mCurrentDrive;
|
||||
|
||||
|
||||
// Win32 constants
|
||||
private const int DBT_DEVTYP_DEVICEINTERFACE = 5;
|
||||
private const int DBT_DEVTYP_HANDLE = 6;
|
||||
private const int BROADCAST_QUERY_DENY = 0x424D5144;
|
||||
private const int WM_DEVICECHANGE = 0x0219;
|
||||
private const int DBT_DEVICEARRIVAL = 0x8000; // system detected a new device
|
||||
private const int DBT_DEVICEQUERYREMOVE = 0x8001; // Preparing to remove (any program can disable the removal)
|
||||
private const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // removed
|
||||
private const int DBT_DEVTYP_VOLUME = 0x00000002; // drive type is logical volume
|
||||
|
||||
/// <summary>
|
||||
/// Registers for receiving the query remove message for a given drive.
|
||||
/// We need to open a handle on that drive and register with this handle.
|
||||
/// Client can specify this file in mFileToOpen or we will open root directory of the drive
|
||||
/// </summary>
|
||||
/// <param name="drive">drive for which to register. </param>
|
||||
private void RegisterQuery(string drive)
|
||||
{
|
||||
bool register = true;
|
||||
|
||||
if (mFileToOpen == null)
|
||||
{
|
||||
// Change 28.10.2007 - Open the root directory if no file specified - leave mFileToOpen null
|
||||
// If client gave us no file, let's pick one on the drive...
|
||||
//mFileToOpen = GetAnyFile(drive);
|
||||
//if (mFileToOpen.Length == 0)
|
||||
// return; // no file found on the flash drive
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure the path in mFileToOpen contains valid drive
|
||||
// If there is a drive letter in the path, it may be different from the actual
|
||||
// letter assigned to the drive now. We will cut it off and merge the actual drive
|
||||
// with the rest of the path.
|
||||
if (mFileToOpen.Contains(":"))
|
||||
{
|
||||
string tmp = mFileToOpen.Substring(3);
|
||||
string root = Path.GetPathRoot(drive);
|
||||
mFileToOpen = Path.Combine(root, tmp);
|
||||
}
|
||||
else
|
||||
mFileToOpen = Path.Combine(drive, mFileToOpen);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
//mFileOnFlash = new FileStream(mFileToOpen, FileMode.Open);
|
||||
// Change 28.10.2007 - Open the root directory
|
||||
if (mFileToOpen == null) // open root directory
|
||||
mFileOnFlash = null;
|
||||
else
|
||||
mFileOnFlash = new FileStream(mFileToOpen, FileMode.Open);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// just do not register if the file could not be opened
|
||||
register = false;
|
||||
}
|
||||
|
||||
|
||||
if (register)
|
||||
{
|
||||
//RegisterForDeviceChange(true, mFileOnFlash.SafeFileHandle);
|
||||
//mCurrentDrive = drive;
|
||||
// Change 28.10.2007 - Open the root directory
|
||||
if (mFileOnFlash == null)
|
||||
RegisterForDeviceChange(drive);
|
||||
else
|
||||
// old version
|
||||
RegisterForDeviceChange(true, mFileOnFlash.SafeFileHandle);
|
||||
|
||||
mCurrentDrive = drive;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// New version which gets the handle automatically for specified directory
|
||||
/// Only for registering! Unregister with the old version of this function...
|
||||
/// </summary>
|
||||
/// <param name="register"></param>
|
||||
/// <param name="dirPath">e.g. C:\\dir</param>
|
||||
private void RegisterForDeviceChange(string dirPath)
|
||||
{
|
||||
IntPtr handle = Native.OpenDirectory(dirPath);
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
mDeviceNotifyHandle = IntPtr.Zero;
|
||||
return;
|
||||
}
|
||||
else
|
||||
mDirHandle = handle; // save handle for closing it when unregistering
|
||||
|
||||
// Register for handle
|
||||
DEV_BROADCAST_HANDLE data = new DEV_BROADCAST_HANDLE();
|
||||
data.dbch_devicetype = DBT_DEVTYP_HANDLE;
|
||||
data.dbch_reserved = 0;
|
||||
data.dbch_nameoffset = 0;
|
||||
//data.dbch_data = null;
|
||||
//data.dbch_eventguid = 0;
|
||||
data.dbch_handle = handle;
|
||||
data.dbch_hdevnotify = (IntPtr)0;
|
||||
int size = Marshal.SizeOf(data);
|
||||
data.dbch_size = size;
|
||||
IntPtr buffer = Marshal.AllocHGlobal(size);
|
||||
Marshal.StructureToPtr(data, buffer, true);
|
||||
|
||||
mDeviceNotifyHandle = Native.RegisterDeviceNotification(mRecipientHandle, buffer, 0);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers to be notified when the volume is about to be removed
|
||||
/// This is requierd if you want to get the QUERY REMOVE messages
|
||||
/// </summary>
|
||||
/// <param name="register">true to register, false to unregister</param>
|
||||
/// <param name="fileHandle">handle of a file opened on the removable drive</param>
|
||||
private void RegisterForDeviceChange(bool register, SafeFileHandle fileHandle)
|
||||
{
|
||||
if (register)
|
||||
{
|
||||
// Register for handle
|
||||
DEV_BROADCAST_HANDLE data = new DEV_BROADCAST_HANDLE();
|
||||
data.dbch_devicetype = DBT_DEVTYP_HANDLE;
|
||||
data.dbch_reserved = 0;
|
||||
data.dbch_nameoffset = 0;
|
||||
//data.dbch_data = null;
|
||||
//data.dbch_eventguid = 0;
|
||||
data.dbch_handle = fileHandle.DangerousGetHandle(); //Marshal. fileHandle;
|
||||
data.dbch_hdevnotify = (IntPtr)0;
|
||||
int size = Marshal.SizeOf(data);
|
||||
data.dbch_size = size;
|
||||
IntPtr buffer = Marshal.AllocHGlobal(size);
|
||||
Marshal.StructureToPtr(data, buffer, true);
|
||||
|
||||
mDeviceNotifyHandle = Native.RegisterDeviceNotification(mRecipientHandle, buffer, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// close the directory handle
|
||||
if (mDirHandle != IntPtr.Zero)
|
||||
{
|
||||
Native.CloseDirectoryHandle(mDirHandle);
|
||||
// string er = Marshal.GetLastWin32Error().ToString();
|
||||
}
|
||||
|
||||
// unregister
|
||||
if (mDeviceNotifyHandle != IntPtr.Zero)
|
||||
{
|
||||
Native.UnregisterDeviceNotification(mDeviceNotifyHandle);
|
||||
}
|
||||
|
||||
|
||||
mDeviceNotifyHandle = IntPtr.Zero;
|
||||
mDirHandle = IntPtr.Zero;
|
||||
|
||||
mCurrentDrive = "";
|
||||
if (mFileOnFlash != null)
|
||||
{
|
||||
mFileOnFlash.Close();
|
||||
mFileOnFlash = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets drive letter from a bit mask where bit 0 = A, bit 1 = B etc.
|
||||
/// There can actually be more than one drive in the mask but we
|
||||
/// just use the last one in this case.
|
||||
/// </summary>
|
||||
/// <param name="mask"></param>
|
||||
/// <returns></returns>
|
||||
private static char DriveMaskToLetter(int mask)
|
||||
{
|
||||
char letter;
|
||||
string drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
// 1 = A
|
||||
// 2 = B
|
||||
// 4 = C...
|
||||
int cnt = 0;
|
||||
int pom = mask / 2;
|
||||
while (pom != 0)
|
||||
{
|
||||
// while there is any bit set in the mask
|
||||
// shift it to the righ...
|
||||
pom = pom / 2;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
if (cnt < drives.Length)
|
||||
letter = drives[cnt];
|
||||
else
|
||||
letter = '?';
|
||||
|
||||
return letter;
|
||||
}
|
||||
|
||||
/* 28.10.2007 - no longer needed
|
||||
/// <summary>
|
||||
/// Searches for any file in a given path and returns its full path
|
||||
/// </summary>
|
||||
/// <param name="drive">drive to search</param>
|
||||
/// <returns>path of the file or empty string</returns>
|
||||
private string GetAnyFile(string drive)
|
||||
{
|
||||
string file = "";
|
||||
// First try files in the root
|
||||
string[] files = Directory.GetFiles(drive);
|
||||
if (files.Length == 0)
|
||||
{
|
||||
// if no file in the root, search whole drive
|
||||
files = Directory.GetFiles(drive, "*.*", SearchOption.AllDirectories);
|
||||
}
|
||||
|
||||
if (files.Length > 0)
|
||||
file = files[0]; // get the first file
|
||||
|
||||
// return empty string if no file found
|
||||
return file;
|
||||
}*/
|
||||
#endregion
|
||||
|
||||
|
||||
#region Native Win32 API
|
||||
/// <summary>
|
||||
/// WinAPI functions
|
||||
/// </summary>
|
||||
private class Native
|
||||
{
|
||||
// HDEVNOTIFY RegisterDeviceNotification(HANDLE hRecipient,LPVOID NotificationFilter,DWORD Flags);
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, uint Flags);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern uint UnregisterDeviceNotification(IntPtr hHandle);
|
||||
|
||||
//
|
||||
// CreateFile - MSDN
|
||||
const uint GENERIC_READ = 0x80000000;
|
||||
const uint OPEN_EXISTING = 3;
|
||||
const uint FILE_SHARE_READ = 0x00000001;
|
||||
const uint FILE_SHARE_WRITE = 0x00000002;
|
||||
const uint FILE_ATTRIBUTE_NORMAL = 128;
|
||||
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
|
||||
static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
||||
|
||||
|
||||
// should be "static extern unsafe"
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
static extern IntPtr CreateFile(
|
||||
string FileName, // file name
|
||||
uint DesiredAccess, // access mode
|
||||
uint ShareMode, // share mode
|
||||
uint SecurityAttributes, // Security Attributes
|
||||
uint CreationDisposition, // how to create
|
||||
uint FlagsAndAttributes, // file attributes
|
||||
int hTemplateFile // handle to template file
|
||||
);
|
||||
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
static extern bool CloseHandle(
|
||||
IntPtr hObject // handle to object
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a directory, returns it's handle or zero.
|
||||
/// </summary>
|
||||
/// <param name="dirPath">path to the directory, e.g. "C:\\dir"</param>
|
||||
/// <returns>handle to the directory. Close it with CloseHandle().</returns>
|
||||
static public IntPtr OpenDirectory(string dirPath)
|
||||
{
|
||||
// open the existing file for reading
|
||||
IntPtr handle = CreateFile(
|
||||
dirPath,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
0,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
|
||||
0);
|
||||
|
||||
if ( handle == INVALID_HANDLE_VALUE)
|
||||
return IntPtr.Zero;
|
||||
else
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
public static bool CloseDirectoryHandle(IntPtr handle)
|
||||
{
|
||||
return CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Structure with information for RegisterDeviceNotification.
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DEV_BROADCAST_HANDLE
|
||||
{
|
||||
public int dbch_size;
|
||||
public int dbch_devicetype;
|
||||
public int dbch_reserved;
|
||||
public IntPtr dbch_handle;
|
||||
public IntPtr dbch_hdevnotify;
|
||||
public Guid dbch_eventguid;
|
||||
public long dbch_nameoffset;
|
||||
//public byte[] dbch_data[1]; // = new byte[1];
|
||||
public byte dbch_data;
|
||||
public byte dbch_data1;
|
||||
}
|
||||
|
||||
// Struct for parameters of the WM_DEVICECHANGE message
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DEV_BROADCAST_VOLUME
|
||||
{
|
||||
public int dbcv_size;
|
||||
public int dbcv_devicetype;
|
||||
public int dbcv_reserved;
|
||||
public int dbcv_unitmask;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
140
Cs_HMI/Project/Dialog/fLog.Designer.cs
generated
140
Cs_HMI/Project/Dialog/fLog.Designer.cs
generated
@@ -31,16 +31,15 @@
|
||||
this.rtsys = new arCtl.LogTextBox();
|
||||
this.rtTx = new arCtl.LogTextBox();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.rtAGV = new arCtl.LogTextBox();
|
||||
this.rtBMS = new arCtl.LogTextBox();
|
||||
this.rtXbee = new arCtl.LogTextBox();
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.rtXbee = new arCtl.LogTextBox();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.panel3 = new System.Windows.Forms.Panel();
|
||||
this.panel4 = new System.Windows.Forms.Panel();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.rtBMS = new arCtl.LogTextBox();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
this.panel4 = new System.Windows.Forms.Panel();
|
||||
this.rtAGV = new arCtl.LogTextBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.panel1.SuspendLayout();
|
||||
this.panel2.SuspendLayout();
|
||||
@@ -99,8 +98,7 @@
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtsys, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtTx, 2, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.panel1, 1, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.panel2, 3, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.panel3, 2, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.panel2, 2, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.panel4, 0, 1);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
|
||||
@@ -111,45 +109,15 @@
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(681, 495);
|
||||
this.tableLayoutPanel1.TabIndex = 2;
|
||||
//
|
||||
// rtAGV
|
||||
// panel1
|
||||
//
|
||||
this.rtAGV.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
|
||||
this.rtAGV.ColorList = new arCtl.sLogMessageColor[0];
|
||||
this.rtAGV.DateFormat = "mm:ss.fff";
|
||||
this.rtAGV.DefaultColor = System.Drawing.Color.LightGray;
|
||||
this.rtAGV.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtAGV.EnableDisplayTimer = false;
|
||||
this.rtAGV.EnableGubunColor = true;
|
||||
this.rtAGV.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.rtAGV.ListFormat = "[{0}] {1}";
|
||||
this.rtAGV.Location = new System.Drawing.Point(0, 14);
|
||||
this.rtAGV.MaxListCount = ((ushort)(1000));
|
||||
this.rtAGV.MaxTextLength = ((uint)(400000u));
|
||||
this.rtAGV.MessageInterval = 50;
|
||||
this.rtAGV.Name = "rtAGV";
|
||||
this.rtAGV.Size = new System.Drawing.Size(164, 129);
|
||||
this.rtAGV.TabIndex = 2;
|
||||
this.rtAGV.Text = "";
|
||||
//
|
||||
// rtBMS
|
||||
//
|
||||
this.rtBMS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
|
||||
this.rtBMS.ColorList = new arCtl.sLogMessageColor[0];
|
||||
this.rtBMS.DateFormat = "mm:ss.fff";
|
||||
this.rtBMS.DefaultColor = System.Drawing.Color.LightGray;
|
||||
this.rtBMS.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtBMS.EnableDisplayTimer = false;
|
||||
this.rtBMS.EnableGubunColor = true;
|
||||
this.rtBMS.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.rtBMS.ListFormat = "[{0}] {1}";
|
||||
this.rtBMS.Location = new System.Drawing.Point(0, 14);
|
||||
this.rtBMS.MaxListCount = ((ushort)(1000));
|
||||
this.rtBMS.MaxTextLength = ((uint)(400000u));
|
||||
this.rtBMS.MessageInterval = 50;
|
||||
this.rtBMS.Name = "rtBMS";
|
||||
this.rtBMS.Size = new System.Drawing.Size(165, 129);
|
||||
this.rtBMS.TabIndex = 2;
|
||||
this.rtBMS.Text = "";
|
||||
this.panel1.Controls.Add(this.rtXbee);
|
||||
this.panel1.Controls.Add(this.label1);
|
||||
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.panel1.Location = new System.Drawing.Point(173, 349);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(164, 143);
|
||||
this.panel1.TabIndex = 3;
|
||||
//
|
||||
// rtXbee
|
||||
//
|
||||
@@ -171,16 +139,6 @@
|
||||
this.rtXbee.TabIndex = 2;
|
||||
this.rtXbee.Text = "";
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.Controls.Add(this.rtXbee);
|
||||
this.panel1.Controls.Add(this.label1);
|
||||
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.panel1.Location = new System.Drawing.Point(173, 349);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(164, 143);
|
||||
this.panel1.TabIndex = 3;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
@@ -193,21 +151,44 @@
|
||||
//
|
||||
// panel2
|
||||
//
|
||||
this.tableLayoutPanel1.SetColumnSpan(this.panel2, 2);
|
||||
this.panel2.Controls.Add(this.rtBMS);
|
||||
this.panel2.Controls.Add(this.label3);
|
||||
this.panel2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.panel2.Location = new System.Drawing.Point(513, 349);
|
||||
this.panel2.Location = new System.Drawing.Point(343, 349);
|
||||
this.panel2.Name = "panel2";
|
||||
this.panel2.Size = new System.Drawing.Size(165, 143);
|
||||
this.panel2.Size = new System.Drawing.Size(335, 143);
|
||||
this.panel2.TabIndex = 4;
|
||||
//
|
||||
// panel3
|
||||
// rtBMS
|
||||
//
|
||||
this.panel3.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.panel3.Location = new System.Drawing.Point(343, 349);
|
||||
this.panel3.Name = "panel3";
|
||||
this.panel3.Size = new System.Drawing.Size(164, 143);
|
||||
this.panel3.TabIndex = 5;
|
||||
this.rtBMS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
|
||||
this.rtBMS.ColorList = new arCtl.sLogMessageColor[0];
|
||||
this.rtBMS.DateFormat = "mm:ss.fff";
|
||||
this.rtBMS.DefaultColor = System.Drawing.Color.LightGray;
|
||||
this.rtBMS.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtBMS.EnableDisplayTimer = false;
|
||||
this.rtBMS.EnableGubunColor = true;
|
||||
this.rtBMS.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.rtBMS.ListFormat = "[{0}] {1}";
|
||||
this.rtBMS.Location = new System.Drawing.Point(0, 14);
|
||||
this.rtBMS.MaxListCount = ((ushort)(1000));
|
||||
this.rtBMS.MaxTextLength = ((uint)(400000u));
|
||||
this.rtBMS.MessageInterval = 50;
|
||||
this.rtBMS.Name = "rtBMS";
|
||||
this.rtBMS.Size = new System.Drawing.Size(335, 129);
|
||||
this.rtBMS.TabIndex = 2;
|
||||
this.rtBMS.Text = "";
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.label3.Location = new System.Drawing.Point(0, 0);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(335, 14);
|
||||
this.label3.TabIndex = 3;
|
||||
this.label3.Text = "BMS";
|
||||
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// panel4
|
||||
//
|
||||
@@ -219,6 +200,26 @@
|
||||
this.panel4.Size = new System.Drawing.Size(164, 143);
|
||||
this.panel4.TabIndex = 6;
|
||||
//
|
||||
// rtAGV
|
||||
//
|
||||
this.rtAGV.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
|
||||
this.rtAGV.ColorList = new arCtl.sLogMessageColor[0];
|
||||
this.rtAGV.DateFormat = "mm:ss.fff";
|
||||
this.rtAGV.DefaultColor = System.Drawing.Color.LightGray;
|
||||
this.rtAGV.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtAGV.EnableDisplayTimer = false;
|
||||
this.rtAGV.EnableGubunColor = true;
|
||||
this.rtAGV.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.rtAGV.ListFormat = "[{0}] {1}";
|
||||
this.rtAGV.Location = new System.Drawing.Point(0, 14);
|
||||
this.rtAGV.MaxListCount = ((ushort)(1000));
|
||||
this.rtAGV.MaxTextLength = ((uint)(400000u));
|
||||
this.rtAGV.MessageInterval = 50;
|
||||
this.rtAGV.Name = "rtAGV";
|
||||
this.rtAGV.Size = new System.Drawing.Size(164, 129);
|
||||
this.rtAGV.TabIndex = 2;
|
||||
this.rtAGV.Text = "";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
@@ -229,16 +230,6 @@
|
||||
this.label2.Text = "AGV";
|
||||
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.label3.Location = new System.Drawing.Point(0, 0);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(165, 14);
|
||||
this.label3.TabIndex = 3;
|
||||
this.label3.Text = "BMS";
|
||||
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// fLog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
|
||||
@@ -269,7 +260,6 @@
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Panel panel2;
|
||||
private System.Windows.Forms.Panel panel3;
|
||||
private System.Windows.Forms.Panel panel4;
|
||||
private System.Windows.Forms.Label label3;
|
||||
private System.Windows.Forms.Label label2;
|
||||
|
||||
@@ -31,7 +31,12 @@ namespace Project
|
||||
public static bool AutRebootAlreay = false;
|
||||
public static bool DriveSpeed = false;
|
||||
public static AGVNavigationCore.Controls.UnifiedAGVCanvas _mapCanvas;
|
||||
public static List<MapNode> _mapNodes;
|
||||
//public static List<MapNode> _mapNodes;
|
||||
|
||||
/// <summary>
|
||||
/// 다음 작업 명령 (PickOn/PickOff)
|
||||
/// </summary>
|
||||
public static ENIGProtocol.AGVCommandHE NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
|
||||
/// <summary>
|
||||
/// 가상 AGV (시뮬레이션용)
|
||||
@@ -607,26 +612,30 @@ namespace Project
|
||||
#region VirtualAGV 실제 데이터 동기화
|
||||
public static MapNode FindByNodeID(string nodeidx)
|
||||
{
|
||||
var _mapNodes = PUB._mapCanvas.Nodes;
|
||||
if (_mapNodes == null || _mapNodes.Any() == false) return null;
|
||||
if (nodeidx.isEmpty()) return null;
|
||||
return _mapNodes.Where(t => t.NodeId.Equals(nodeidx)).FirstOrDefault();
|
||||
return _mapNodes.Where(t => t.Id.Equals(nodeidx)).FirstOrDefault();
|
||||
}
|
||||
public static MapNode FindByRFID(string rfidValue)
|
||||
{
|
||||
var _mapNodes = PUB._mapCanvas.Nodes;
|
||||
if (_mapNodes == null || _mapNodes.Any() == false) return null;
|
||||
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.NodeAlias.Equals(alias));
|
||||
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;
|
||||
@@ -638,8 +647,9 @@ namespace Project
|
||||
/// <param name="rfidId">읽은 RFID ID</param>
|
||||
/// <param name="motorDirection">모터 방향 (Forward/Backward)</param>
|
||||
/// <returns>업데이트 성공 여부</returns>
|
||||
public static bool UpdateAGVFromRFID(string rfidId, AgvDirection motorDirection = AgvDirection.Forward)
|
||||
public static bool UpdateAGVFromRFID(ushort rfidId, AgvDirection motorDirection = AgvDirection.Forward)
|
||||
{
|
||||
var _mapNodes = PUB._mapCanvas.Nodes;
|
||||
if (_virtualAGV == null || _mapNodes == null) return false;
|
||||
|
||||
// RFID에 해당하는 노드 찾기
|
||||
@@ -649,7 +659,7 @@ namespace Project
|
||||
_virtualAGV.SetPosition(node, motorDirection);
|
||||
RefreshAGVCanvas();
|
||||
|
||||
log.Add($"[AGV] RFID {rfidId} 감지 → 노드 {node.NodeId} 위치 업데이트 (방향: {motorDirection})");
|
||||
log.Add($"[AGV] RFID {rfidId} 감지 → 노드 {node.Id} 위치 업데이트 (방향: {motorDirection})");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -665,9 +675,10 @@ namespace Project
|
||||
/// <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.NodeId == nodeId);
|
||||
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node != null)
|
||||
{
|
||||
_virtualAGV.SetPosition(node, motorDirection);
|
||||
@@ -724,7 +735,7 @@ namespace Project
|
||||
{
|
||||
if (_virtualAGV == null) return;
|
||||
|
||||
_virtualAGV.BatteryLevel = batteryLevel;
|
||||
_virtualAGV.SetBatteryLevel(batteryLevel);
|
||||
RefreshAGVCanvas();
|
||||
}
|
||||
|
||||
@@ -739,42 +750,7 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 AGV의 노드 ID 가져오기
|
||||
/// </summary>
|
||||
/// <returns>현재 노드 ID</returns>
|
||||
public static string GetCurrentAGVNodeId()
|
||||
{
|
||||
return _virtualAGV?.CurrentNodeId ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 AGV 위치 가져오기
|
||||
/// </summary>
|
||||
/// <returns>현재 위치</returns>
|
||||
public static Point GetCurrentAGVPosition()
|
||||
{
|
||||
return _virtualAGV?.CurrentPosition ?? Point.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 AGV 방향 가져오기
|
||||
/// </summary>
|
||||
/// <returns>현재 방향</returns>
|
||||
public static AgvDirection GetCurrentAGVDirection()
|
||||
{
|
||||
return _virtualAGV?.CurrentDirection ?? AgvDirection.Forward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 AGV 상태 가져오기
|
||||
/// </summary>
|
||||
/// <returns>현재 상태</returns>
|
||||
public static AGVState GetCurrentAGVState()
|
||||
{
|
||||
return _virtualAGV?.CurrentState ?? AGVState.Idle;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
@@ -19,20 +19,6 @@ namespace Project
|
||||
|
||||
private void _SM_RUN(Boolean isFirst, TimeSpan stepTime)
|
||||
{
|
||||
//중단기능이 동작이라면 처리하지 않는다.
|
||||
if (PUB.sm.bPause)
|
||||
{
|
||||
System.Threading.Thread.Sleep(200);
|
||||
return;
|
||||
}
|
||||
|
||||
//가동불가 조건 확인
|
||||
if (CheckStopCondition() == false)
|
||||
{
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
//HW 연결오류
|
||||
if (PUB.AGV.IsOpen == false)
|
||||
{
|
||||
@@ -41,17 +27,16 @@ namespace Project
|
||||
return;
|
||||
}
|
||||
|
||||
//이머전시상태라면 stop 처리한다.
|
||||
if (PUB.AGV.error.Emergency &&
|
||||
PUB.AGV.system1.agv_stop == true &&
|
||||
PUB.AGV.system1.stop_by_front_detect == false)
|
||||
//가동불가 조건 확인
|
||||
if (CheckStopCondition() == false) return;
|
||||
|
||||
//중단기능이 동작이라면 처리하지 않는다.
|
||||
if (PUB.sm.bPause)
|
||||
{
|
||||
PUB.Speak(Lang.비상정지로인해작업을중단합니다);
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
System.Threading.Thread.Sleep(200);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//스텝이 변경되었다면?
|
||||
if (PUB.sm.RunStep != PUB.sm.RunStepNew)
|
||||
{
|
||||
@@ -123,49 +108,37 @@ namespace Project
|
||||
//목적지가 BUFFER라면 버퍼투입대기위치까지 완료했다는 시그널을 보낸다.
|
||||
var target = PUB._virtualAGV.TargetNode;
|
||||
PUB.log.Add($"목적지({target.RfidId}) 도착완료 타입:{target.Type}, 출발지:{PUB._virtualAGV.StartNode.RfidId}");
|
||||
if (target.Type == AGVNavigationCore.Models.NodeType.Buffer)
|
||||
{
|
||||
|
||||
//현재위치가 마지막경로의 NODEID와 일치해야한다
|
||||
var lastPath = PUB._virtualAGV.CurrentPath.DetailedPath.LastOrDefault();
|
||||
if (lastPath.NodeId.Equals(PUB._virtualAGV.CurrentNodeId))
|
||||
{
|
||||
//버퍼진입전 노드에 도착완료했따
|
||||
PUB.XBE.BufferInReady = true;
|
||||
PUB.XBE.BufferReadyError = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//마지막위치가 아닌 다른 위치에 있으니 버퍼 작업을 할 수없다
|
||||
PUB.log.AddAT("목적지 버퍼이동완료 했지만 마지막 노드가 아닙니다");
|
||||
PUB.XBE.BufferInReady = false;
|
||||
PUB.XBE.BufferReadyError = true;
|
||||
}
|
||||
PUB.XBE.BufferInComplete = false;
|
||||
PUB.XBE.BufferOutComplete = false;
|
||||
switch (target.StationType)
|
||||
{
|
||||
case AGVNavigationCore.Models.StationType.Buffer:
|
||||
//현재위치가 마지막경로의 NODEID와 일치해야한다
|
||||
if (PUB._virtualAGV.CurrentPath == null)
|
||||
{
|
||||
PUB.log.AddAT("목적지 버퍼이동완료 했지만 상세경로가 없습니다");
|
||||
PUB.XBE.BufferInComplete = false;
|
||||
PUB.XBE.BufferOutComplete = false;
|
||||
break;
|
||||
}
|
||||
var lastPath = PUB._virtualAGV.CurrentPath.DetailedPath.LastOrDefault();
|
||||
if (lastPath.NodeId.Equals(PUB._virtualAGV.CurrentNode.Id))
|
||||
{
|
||||
//버퍼진입전 노드에 도착완료했따
|
||||
PUB.XBE.BufferInReady = true;
|
||||
PUB.XBE.BufferReadyError = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//마지막위치가 아닌 다른 위치에 있으니 버퍼 작업을 할 수없다
|
||||
PUB.log.AddAT("목적지 버퍼이동완료 했지만 마지막 노드가 아닙니다");
|
||||
PUB.XBE.BufferInReady = false;
|
||||
PUB.XBE.BufferReadyError = true;
|
||||
}
|
||||
PUB.XBE.BufferInComplete = false;
|
||||
PUB.XBE.BufferOutComplete = false;
|
||||
break;
|
||||
}
|
||||
|
||||
else if (target.Type == AGVNavigationCore.Models.NodeType.Charging)
|
||||
{
|
||||
|
||||
}
|
||||
else if (target.Type == AGVNavigationCore.Models.NodeType.Loader)
|
||||
{
|
||||
|
||||
}
|
||||
else if (target.Type == AGVNavigationCore.Models.NodeType.Clearner)
|
||||
{
|
||||
|
||||
}
|
||||
else if (target.Type == AGVNavigationCore.Models.NodeType.UnLoader)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//목적지다 다른 형태이다
|
||||
|
||||
}
|
||||
PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.None;
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
}
|
||||
@@ -272,9 +245,21 @@ namespace Project
|
||||
//도킹완료상태를 업데이트한다.
|
||||
PUB.XBE.LoaderInComplete = true;
|
||||
|
||||
//로더아웃으로 자동 진행 합니다
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT);
|
||||
//로더아웃으로 자동 진행하지 않음 (ACS 명령 대기)
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; // Command consumed
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy behavior or Goto command: Auto-exit?
|
||||
// User said separation is key. Let's Stop here too or keep legacy for GOTO?
|
||||
// Assuming GOTO might rely on this, but safer to STOP if we want strict separation.
|
||||
// However, let's keep legacy behavior for GOTO if possible, but for PickOn/Off we STOP.
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -302,9 +287,17 @@ namespace Project
|
||||
//도킹완료상태를 업데이트한다.
|
||||
PUB.XBE.UnloaderInComplete = true;
|
||||
|
||||
//언로더아웃으로 자동 진행 합니다
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT);
|
||||
//언로더아웃으로 자동 진행하지 않음
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -332,9 +325,17 @@ namespace Project
|
||||
//도킹완료상태를 업데이트한다.
|
||||
PUB.XBE.CleanerInComplete = true;
|
||||
|
||||
//클리너아웃으로 자동 진행 합니다
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT);
|
||||
//클리너아웃으로 자동 진행하지 않음
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -362,9 +363,17 @@ namespace Project
|
||||
//도킹완료상태를 업데이트한다.
|
||||
PUB.XBE.BufferInComplete = true;
|
||||
|
||||
//버퍼아웃으로 자동 진행 합니다
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.BUFFER_OUT);
|
||||
//버퍼아웃으로 자동 진행하지 않음
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.sm.ClearRunStep();
|
||||
PUB.sm.SetNewRunStep(ERunStep.BUFFER_OUT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -125,28 +125,27 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트를 내린다.
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
||||
// [PickOn/PickOff] 초기 리프트 동작
|
||||
var liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트다운센서를 확인한다.
|
||||
var liftdown = true;
|
||||
if (liftdown == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PUB.log.Add("리프트 하강 완료");
|
||||
//리프트 센서 확인
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
// Timebound check
|
||||
}
|
||||
PUB.log.Add("리프트 동작 확인 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
@@ -160,7 +159,7 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//저속이동
|
||||
//저속이동 (후진 진입)
|
||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
@@ -224,11 +223,43 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//완료되었다. (ACS에 보내야함)
|
||||
PUB.log.Add("버퍼투입완료");
|
||||
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||
PUB.log.Add("버퍼 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||
|
||||
var liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// 리프트 동작 대기
|
||||
// TODO: 실제 센서 확인 로직 추가 필요
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds < 2) return false;
|
||||
|
||||
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환 (퇴출 명령 대기)");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//완료되었다. (ACS에 보내야함)
|
||||
PUB.log.Add("버퍼 진입 및 작업 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 작업을 마치고 설비 안에 멈춰있는 상태.
|
||||
// ACS가 이 상태를 확인하고 NextWorkCmd로 퇴출(Out) 명령을 보내야 함.
|
||||
PUB.AddEEDB($"버퍼작업완료({PUB.Result.TargetPos})");
|
||||
return true;
|
||||
|
||||
PUB.AddEEDB($"버퍼투입완료({PUB.Result.TargetPos})");
|
||||
return true;
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace Project
|
||||
// 1분 타임아웃 체크
|
||||
if (stepTime.TotalMinutes >= 1)
|
||||
{
|
||||
PUB.XBE.errorMessage = $"충전해제가 실패되었습니다(1분)";
|
||||
PUB.log.AddE(PUB.XBE.errorMessage);
|
||||
PUB.XBE.ErrorMessage = $"충전해제가 실패되었습니다(1분)";
|
||||
PUB.log.AddE(PUB.XBE.ErrorMessage);
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -43,28 +43,27 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트를 내린다.
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
||||
// [PickOn/PickOff] 초기 리프트 동작
|
||||
var liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트다운센서를 확인한다.
|
||||
var liftdown = true;
|
||||
if (liftdown == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PUB.log.Add("리프트 하강 완료");
|
||||
//리프트 센서 확인
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
// Timebound check
|
||||
}
|
||||
PUB.log.Add("리프트 동작 확인 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
@@ -78,7 +77,7 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//저속이동
|
||||
//저속이동 (후진 진입)
|
||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
@@ -141,14 +140,40 @@ namespace Project
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||
PUB.log.Add("클리너 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||
|
||||
var liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// 리프트 동작 대기
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds < 2) return false;
|
||||
|
||||
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//완료되었다.
|
||||
PUB.log.Add("클리너진입완료");
|
||||
PUB.log.Add("클리너 진입 및 작업 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
|
||||
PUB.AddEEDB($"클리너진입완료({PUB.Result.TargetPos})");
|
||||
PUB.AddEEDB($"클리너작업완료({PUB.Result.TargetPos})");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,18 @@ namespace Project
|
||||
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//이미 충전중이라면 바로 완료 처리한다 (사용자 요청)
|
||||
if (VAR.BOOL[eVarBool.FLAG_CHARGEONA] == true || PUB.AGV.system1.Battery_charging == true)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
PUB.log.Add("이미 충전 중이므로 충전 시퀀스를 종료하고 준비 상태로 전환합니다.");
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//충전 상태가 OFF되어야 동작하게한다
|
||||
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
|
||||
@@ -64,7 +75,7 @@ namespace Project
|
||||
if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
var targetnode = PUB.FindByRFID(PUB.setting.NodeMAP_RFID_Charger);
|
||||
if(targetnode == null)
|
||||
if (targetnode == null)
|
||||
{
|
||||
PUB.log.AddE($"충전기 노드가 설정되지 않았습니다");
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
@@ -75,15 +86,15 @@ namespace Project
|
||||
PUB._virtualAGV.TargetNode = targetnode;
|
||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
PUB.log.Add($"충전:대상위치 QC 시작");
|
||||
PUB.log.Add($"충전기 위치로 이동 목표:{targetnode.RfidId}");
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//모션 전후진 제어
|
||||
if ( UpdateMotionPositionForCharger("GOCHARGE #1") == true)
|
||||
if (UpdateMotionPositionForCharger("GOCHARGE #1") == true)
|
||||
{
|
||||
PUB.log.Add($"충전:충전기 검색 전 QC위치 확인 완료");
|
||||
PUB.log.Add($"충전기 목표위치 이동 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
}
|
||||
else
|
||||
@@ -106,72 +117,41 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
if (PUB.setting.chargerpos == 0) //down search
|
||||
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
|
||||
PUB.Speak(Lang.충전기를검색합니다);
|
||||
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
|
||||
PUB.Speak(Lang.충전기를검색합니다);
|
||||
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
Direction = arDev.Narumi.eMoveDir.Forward,
|
||||
PBSSensor = 1,
|
||||
});
|
||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
|
||||
//PUB.Result.TargetPos = ePosition.CHARGE;
|
||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||
}
|
||||
else if (PUB.setting.chargerpos == 2) //up search
|
||||
{
|
||||
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
|
||||
PUB.Speak(Lang.충전기를검색합니다);
|
||||
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
Direction = arDev.Narumi.eMoveDir.Backward,
|
||||
PBSSensor = 1,
|
||||
});
|
||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
|
||||
//PUB.Result.TargetPos = ePosition.CHARGE;
|
||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.Add($"충전기위치가 QC위치로 설정되어 있습니다");
|
||||
}
|
||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
Direction = arDev.Narumi.eMoveDir.Forward,
|
||||
PBSSensor = 1,
|
||||
});
|
||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
|
||||
//PUB.Result.TargetPos = ePosition.CHARGE;
|
||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
if (PUB.setting.chargerpos != 1)
|
||||
{
|
||||
if (PUB.AGV.system1.agv_run)
|
||||
{
|
||||
PUB.log.Add($"충전:AGV기동확인으로 마크정지신호설정");
|
||||
PUB.Speak(Lang.다음마크위치에서정지합니다);
|
||||
PUB.AGV.AGVMoveStop("SM_RUN_GOCHARGE", arDev.Narumi.eStopOpt.MarkStop);
|
||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (VAR.TIME.RUN(eVarTime.ChargeSearch).TotalSeconds > 5)
|
||||
{
|
||||
//5초이상 이곳에서 대기한다면 다시 돌려준다
|
||||
PUB.sm.UpdateRunStepSeq(-1);
|
||||
PUB.log.Add($"충전:AGV기동확인 안됨, 롤백 다시 이동할 수 있게 함");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (PUB.AGV.system1.agv_run)
|
||||
{
|
||||
PUB.log.Add($"충전:AGV기동확인으로 마크정지신호설정");
|
||||
PUB.Speak(Lang.다음마크위치에서정지합니다);
|
||||
PUB.AGV.AGVMoveStop("SM_RUN_GOCHARGE", arDev.Narumi.eStopOpt.MarkStop);
|
||||
VAR.TIME.Update(eVarTime.ChargeSearch);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (VAR.TIME.RUN(eVarTime.ChargeSearch).TotalSeconds > 5)
|
||||
{
|
||||
//5초이상 이곳에서 대기한다면 다시 돌려준다
|
||||
PUB.sm.UpdateRunStepSeq(-1);
|
||||
PUB.log.Add($"충전:AGV기동확인 안됨, 롤백 다시 이동할 수 있게 함");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -259,7 +239,6 @@ namespace Project
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Project
|
||||
{
|
||||
///명령어 재전송 간격(기본 2초)
|
||||
var CommandInterval = 2;
|
||||
var funcName = "_SM_RUN_GOTO";
|
||||
|
||||
//충전 상태가 OFF되어야 동작하게한다
|
||||
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
|
||||
@@ -29,269 +30,48 @@ namespace Project
|
||||
VAR.TIME.Update(eVarTime.CheckGotoTargetSet);
|
||||
}
|
||||
|
||||
//PUB._virtualAGV.
|
||||
//라이더멈춤이 설정되어있다면 음성으로 알려준다 200409
|
||||
if (PUB.AGV.system1.stop_by_front_detect == true)
|
||||
{
|
||||
var tsSpeak = DateTime.Now - LastSpeakTime;
|
||||
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
|
||||
{
|
||||
PUB.Speak(Lang.전방에물체가감지되었습니다);
|
||||
LastSpeakTime = DateTime.Now;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//목적지가 설정되었는지 체크한다.
|
||||
//Z if (PUB.mapctl.Manager.agv.TargetRFID.IsEmpty)
|
||||
// {
|
||||
// //최대 5초간 설정여부를 확인하고
|
||||
// if (VAR.TIME.RUN(eVarTime.CheckGotoTargetSet).TotalSeconds > 5)
|
||||
// {
|
||||
// //실패시에는 READY로 전환한다.
|
||||
// PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
// PUB.Speak(Lang.목적지가없어대기상태로전환합니다);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
//var idx = 1;
|
||||
//var BeforePredictIdx = -1;
|
||||
//var predict = PUB.mapctl.Manager.PredictResult;
|
||||
//if (PUB.sm.RunStepSeq == idx++)
|
||||
//{
|
||||
// PUB.Speak(Lang.위치로이동합니다);
|
||||
// PUB.log.Add($"목적지 위치 이동시작({PUB.mapctl.Manager.agv.TargetRFID.Value})");
|
||||
// VAR.TIME.Update(eVarTime.CheckGotoTargetSet);
|
||||
// VAR.TIME.Set(eVarTime.SendGotoCommand, DateTime.Now.AddDays(-1));
|
||||
// PUB.sm.UpdateRunStepSeq();
|
||||
// return false;
|
||||
//}
|
||||
//else if (PUB.sm.RunStepSeq == idx++)
|
||||
//{
|
||||
// //멈춰야하는경우
|
||||
// if (predict.MoveState == AGVControl.AGVMoveState.Stop)
|
||||
// {
|
||||
// if (PUB.AGV.system1.agv_run)
|
||||
// {
|
||||
// if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > 2)
|
||||
// {
|
||||
// PUB.Speak("AGV Stop");
|
||||
// PUB.AGV.AGVMoveStop("Predict", arDev.Narumi.eStopOpt.Stop);
|
||||
// VAR.TIME.Update(eVarTime.SendGotoCommand);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //완료되었거나 턴을진행해야한다
|
||||
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.Arrived ||
|
||||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
|
||||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
|
||||
// {
|
||||
// GotoTurnStep = 0;
|
||||
// GotoTurnSetTime = DateTime.Now.AddDays(-1);
|
||||
// PUB.sm.UpdateRunStepSeq();
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// else //이동해야하는 경우
|
||||
// {
|
||||
// //속도와 방향이 불일치하는 경우 다시 설정한다 (속도: H,L,M,[S]
|
||||
// AGVControl.AgvDir AGV_Direction = (AGVControl.AgvDir)PUB.AGV.data.Direction;
|
||||
// AGVControl.AgvSpeed AGV_Speed = (AGVControl.AgvSpeed)PUB.AGV.data.Speed;
|
||||
// AGVControl.AgvSts AGV_Sts = (AGVControl.AgvSts)PUB.AGV.data.Sts;
|
||||
|
||||
// //상태값이 바뀌었다면 전송을 해야한다
|
||||
// if (predict.Direction != AGV_Direction || predict.MoveSpeed != AGV_Speed || predict.MoveDiv != AGV_Sts)
|
||||
// {
|
||||
// if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > CommandInterval)
|
||||
// {
|
||||
// arDev.Narumi.eBunki v_bunki = arDev.Narumi.eBunki.Strate;
|
||||
// if (predict.MoveDiv == AGVControl.AgvSts.Straight) v_bunki = arDev.Narumi.eBunki.Strate;
|
||||
// else if (predict.MoveDiv == AGVControl.AgvSts.Left) v_bunki = arDev.Narumi.eBunki.Left;
|
||||
// else if (predict.MoveDiv == AGVControl.AgvSts.Right) v_bunki = arDev.Narumi.eBunki.Right;
|
||||
|
||||
// arDev.Narumi.eMoveDir v_dir = arDev.Narumi.eMoveDir.Backward;
|
||||
// if (predict.Direction == AGVControl.AgvDir.Forward) v_dir = arDev.Narumi.eMoveDir.Forward;
|
||||
// else if (predict.Direction == AGVControl.AgvDir.Backward) v_dir = arDev.Narumi.eMoveDir.Backward;
|
||||
|
||||
// arDev.Narumi.eMoveSpd v_spd = arDev.Narumi.eMoveSpd.Low;
|
||||
// if (predict.MoveSpeed == AGVControl.AgvSpeed.Middle) v_spd = arDev.Narumi.eMoveSpd.Middle;
|
||||
// else if (predict.MoveSpeed == AGVControl.AgvSpeed.High) v_spd = arDev.Narumi.eMoveSpd.High;
|
||||
// else if (predict.MoveSpeed == AGVControl.AgvSpeed.Low) v_spd = arDev.Narumi.eMoveSpd.Low;
|
||||
|
||||
// //이동셋팅을 해준다
|
||||
// PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
// {
|
||||
// Bunki = v_bunki,
|
||||
// Direction = v_dir,
|
||||
// PBSSensor = 1,
|
||||
// Speed = v_spd,
|
||||
// });
|
||||
|
||||
// if (predict.MoveSpeed == AGVControl.AgvSpeed.MarkStop)
|
||||
// {
|
||||
// PUB.AGV.AGVMoveStop("Predict", arDev.Narumi.eStopOpt.Stop);
|
||||
// }
|
||||
// VAR.TIME.Update(eVarTime.SendGotoCommand);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// //정지상태라면 이동 명령을 전달한다
|
||||
// if (PUB.AGV.system1.agv_run == false)
|
||||
// {
|
||||
// if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > CommandInterval)
|
||||
// {
|
||||
// PUB.Speak("AGV Start");
|
||||
|
||||
// arDev.Narumi.eRunOpt v_dir = arDev.Narumi.eRunOpt.Backward;
|
||||
// if (predict.Direction == AGVControl.AgvDir.Forward) v_dir = arDev.Narumi.eRunOpt.Forward;
|
||||
// else if (predict.Direction == AGVControl.AgvDir.Backward) v_dir = arDev.Narumi.eRunOpt.Backward;
|
||||
|
||||
// PUB.AGV.AGVMoveRun(v_dir);
|
||||
// VAR.TIME.Update(eVarTime.SendGotoCommand);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// //예측이 업데이트되지 않으면 오류 처리해야한다
|
||||
// if (BeforePredictIdx == -1) BeforePredictIdx = (int)predict.Idx;
|
||||
// else if (BeforePredictIdx != predict.Idx) //이전사용한 IDX와 다르다면 예측이 실행된 경우이다
|
||||
// BeforePredictIdx = (int)predict.Idx;
|
||||
// else
|
||||
// {
|
||||
// //5초이상 예측값이 업데이트되지 않으면 오류 처리한다.
|
||||
// var tsPredict = DateTime.Now - predict.CreateTime;
|
||||
// if (tsPredict.TotalSeconds > 5)
|
||||
// {
|
||||
// PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.PredictFix, Lang.예측값이계산되지않아이동을중단합니다);
|
||||
// PUB.Speak(Lang.예측값이계산되지않아이동을중단합니다);
|
||||
// PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return false;
|
||||
//}
|
||||
//else if (PUB.sm.RunStepSeq == idx++)
|
||||
//{
|
||||
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.Arrived)
|
||||
// {
|
||||
// PUB.Speak(Lang.목적지이동이완료되었습니다);
|
||||
// PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
// PUB.sm.UpdateRunStepSeq();
|
||||
// }
|
||||
// else if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
|
||||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
|
||||
// {
|
||||
// //턴을 해야하는 경우이다
|
||||
// //좌턴을 기본으로 진행하며, 좌턴이동 후 마크스탑을 입력한다
|
||||
// if (GotoTurnStep == 0)
|
||||
// {
|
||||
// //턴을 한적이 없으므로 턴을 먼저 진행한다
|
||||
// arDev.Narumi.eMoveDir moveDir = arDev.Narumi.eMoveDir.Backward;
|
||||
// if (predict.Direction == AGVControl.AgvDir.Forward) moveDir = arDev.Narumi.eMoveDir.Forward;
|
||||
// if (PUB.AGV.data.Sts != 'L' || PUB.AGV.data.Speed != 'L' || PUB.AGV.data.Direction != moveDir.ToString()[0])
|
||||
// {
|
||||
// //셋팅이 다르다면 3초간격으로 전송한다
|
||||
// var tsTurnSet = DateTime.Now - GotoTurnSetTime;
|
||||
// if (tsTurnSet.TotalSeconds > 3)
|
||||
// {
|
||||
// PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
// {
|
||||
// Bunki = arDev.Narumi.eBunki.Left,
|
||||
// Direction = moveDir,
|
||||
// PBSSensor = 1,
|
||||
// Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
// });
|
||||
// GotoTurnSetTime = DateTime.Now;
|
||||
// PUB.log.Add("Turn Bunki Set");
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// PUB.mapctl.Manager.agv.CurrentRFID.TurnOK = false;
|
||||
// PUB.mapctl.Manager.agv.CurrentRFID.TurnStart = DateTime.Now;
|
||||
// PUB.sm.UpdateRunStepSeq(); //셋팅이 맞으니 다음스텝으로 진행한다
|
||||
// GotoTurnStep += 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else PUB.sm.UpdateRunStepSeq();
|
||||
// return false;
|
||||
//}
|
||||
//else if (PUB.sm.RunStepSeq == idx++)
|
||||
//{
|
||||
// //턴이완료되길 기다린다.
|
||||
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
|
||||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
|
||||
// {
|
||||
// //(최소5초는 기다리고 판단한다)
|
||||
// if (stepTime.TotalSeconds < 5) return false;
|
||||
|
||||
// //최대30초는 기다려준다
|
||||
// if (stepTime.TotalSeconds > 30)
|
||||
// {
|
||||
// var ermsg = "Turn Timeout(30sec)";
|
||||
// PUB.log.AddE(ermsg);
|
||||
// PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.TurnTimeout, ermsg);
|
||||
// PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
// }
|
||||
|
||||
// //모션이 멈추었다면 턴이완료된것이다.
|
||||
// if (PUB.AGV.system1.agv_stop)
|
||||
// {
|
||||
// if (PUB.AGV.system1.Mark1_check == false && PUB.AGV.system1.Mark2_check == false)
|
||||
// {
|
||||
// PUB.log.AddE($"Turn 완료이나 Mark 센서가 확인되지 않았습니다");
|
||||
// }
|
||||
// GotoTurnStep += 1;
|
||||
// PUB.sm.UpdateRunStepSeq();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //아직 이동중이므로 대기한다
|
||||
// }
|
||||
// }
|
||||
// else PUB.sm.UpdateRunStepSeq(); //기타사항은 다음으로 넘어간다
|
||||
// return false;
|
||||
//}
|
||||
//else if (PUB.sm.RunStepSeq == idx++)
|
||||
//{
|
||||
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
|
||||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
|
||||
// {
|
||||
// if (GotoTurnStep < 2)
|
||||
// {
|
||||
// PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.TurnError, "턴시퀀스 완료 실패");
|
||||
// PUB.log.AddE($"턴완료시퀀스가 2가아닙니다. 대기 상태로 강제 전환합니다");
|
||||
// PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// PUB.log.AddI("Turn Complete");
|
||||
// }
|
||||
|
||||
// //방향전환용 턴이라면 이동기록을 추가해서 방향이 맞도록 처리해주자
|
||||
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove)
|
||||
// {
|
||||
// var rfid = PUB.mapctl.Manager.agv.CurrentRFID;
|
||||
// var lastHistory = PUB.mapctl.Manager.agv.MovementHistory.Last();
|
||||
// //원래방향에서 반대로 처리한다
|
||||
// var revDir = lastHistory.Direction == AGVControl.AgvDir.Backward ? AGVControl.AgvDir.Forward : AGVControl.AgvDir.Backward;
|
||||
// PUB.mapctl.Manager.agv.AddToMovementHistory(rfid.Value, rfid.Location, revDir);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //이동용 RFID에서 턴명령이 들어있는경우였다
|
||||
// PUB.mapctl.Manager.agv.CurrentRFID.TurnEnd = DateTime.Now;
|
||||
// PUB.mapctl.Manager.agv.CurrentRFID.TurnOK = true;
|
||||
// }
|
||||
|
||||
// PUB.sm.UpdateRunStepSeq();
|
||||
// }
|
||||
// else PUB.sm.UpdateRunStepSeq();
|
||||
// return false;
|
||||
//}
|
||||
|
||||
|
||||
//좌턴이동명령 전송
|
||||
|
||||
//마크스탑전송
|
||||
|
||||
//마크스탑이 확인되면 나머지는 경로예측에 맡긴다
|
||||
var idx = 1;
|
||||
if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
if(PUB._virtualAGV.TargetNode == null)
|
||||
{
|
||||
PUB.log.Add($"대상노드가 없어 이동을할 수 없습니다");
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
return false;
|
||||
}
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//모션 전후진 제어
|
||||
if (UpdateMotionPositionForMark(funcName))
|
||||
{
|
||||
PUB.AGV.AGVMoveStop(funcName);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//QC까지 모두 완료되었다.(완전히 정차할때까지 기다린다)
|
||||
PUB.Speak(Lang.홈검색완료, true);
|
||||
PUB.AddEEDB($"홈검색완료({PUB.Result.TargetPos})");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,28 +43,33 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트를 내린다.
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
||||
// [PickOn/PickOff] 초기 리프트 동작
|
||||
var liftCmd = arDev.Narumi.LiftCommand.DN; // Default PickOn (Get -> Go Under -> Down)
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.UP; // PickOff (Put -> Carry In -> Up)
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트다운센서를 확인한다.
|
||||
var liftdown = true; // 센서 확인 로직이 주석처리되어 있거나 하드코딩되어 있었음 (버퍼 코드 참조)
|
||||
if (liftdown == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PUB.log.Add("리프트 하강 완료");
|
||||
//리프트 센서 확인 (Direction에 따라 다름)
|
||||
// TODO: UP 센서 확인 로직 추가 필요 시 구현. 현재는 시간체크만 유지하거나 DN확인만 있음.
|
||||
// 일단 기존 로직 유지하되, UP일 경우 스킵 고려
|
||||
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
// Timebound check
|
||||
// PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||
// Warning only?
|
||||
}
|
||||
|
||||
PUB.log.Add("리프트 동작 확인 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
@@ -78,7 +83,7 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//저속이동
|
||||
//저속이동 (후진 진입)
|
||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
@@ -141,14 +146,41 @@ namespace Project
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||
PUB.log.Add("로더 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||
|
||||
var liftCmd = arDev.Narumi.LiftCommand.UP; // Default PickOn (Lift Up to Pick)
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.DN; // PickOff (Lift Down to Drop)
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// 리프트 동작 대기
|
||||
// TODO: 센서 확인
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds < 2) return false; // 2초 대기
|
||||
|
||||
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//완료되었다.
|
||||
PUB.log.Add("로더진입완료");
|
||||
PUB.log.Add("로더 진입 및 작업 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
|
||||
PUB.AddEEDB($"로더진입완료({PUB.Result.TargetPos})");
|
||||
PUB.AddEEDB($"로더작업완료({PUB.Result.TargetPos})");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ namespace Project
|
||||
public Boolean _SM_RUN_POSCHK(bool isFirst, TimeSpan stepTime)
|
||||
{
|
||||
//현재위치가 설정되어있는지 확인한다, 현재위치값이 있는 경우 True 를 반환
|
||||
var currentnode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNodeId);
|
||||
if (currentnode != null) return true;
|
||||
if (PUB._virtualAGV.CurrentNode != null && PUB._virtualAGV.PrevNode != null)
|
||||
return true;
|
||||
|
||||
//최소2개의 노드정보가 있어야 진행가능하므로 prevNode 값이 있는지 확인한다.
|
||||
|
||||
|
||||
//이동을 하지 않고있다면 전진을 진행한다
|
||||
if (PUB.AGV.system1.agv_run == false)
|
||||
@@ -30,7 +33,7 @@ namespace Project
|
||||
PBSSensor = 1,
|
||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
});
|
||||
PUB.AGV.AGVMoveRun();
|
||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
|
||||
VAR.TIME.Update(eVarTime.LastRunCommandTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,14 @@ namespace Project
|
||||
if (PUB.AGV.IsOpen == false)
|
||||
{
|
||||
//agv connect
|
||||
ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
|
||||
var rlt = ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
|
||||
eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV);
|
||||
if (rlt == false)
|
||||
{
|
||||
//존재하지 않는 포트라면 sync를 벗어난다
|
||||
PUB.log.AddE($"AGV포트({PUB.setting.Port_AGV}) 가 존재하지않아 SYNC를 중단합니다");
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
}
|
||||
}
|
||||
else if (PUB.AGV.IsValid == true)
|
||||
{
|
||||
@@ -81,7 +87,7 @@ namespace Project
|
||||
|
||||
synlist.Add("SGS", PUB.setting.GDSValue.ToString("0000"));
|
||||
VAR.I32[eVarInt32.SyncItemCount] = synlist.Count;
|
||||
|
||||
|
||||
|
||||
PUB.AddEEDB($"SYNC시작({PUB.Result.TargetPos})");
|
||||
|
||||
@@ -129,7 +135,7 @@ namespace Project
|
||||
if (PUB.AGV.ACKData.Equals(item.Key))
|
||||
{
|
||||
synidx += 1;
|
||||
if(ts.TotalSeconds < 0.15) PUB.sm.UpdateRunStepSeq(-2); //싱크중에 추가 지연시간 확보
|
||||
if (ts.TotalSeconds < 0.15) PUB.sm.UpdateRunStepSeq(-2); //싱크중에 추가 지연시간 확보
|
||||
else PUB.sm.UpdateRunStepSeq(-1);
|
||||
LastCommandTime = DateTime.Now;
|
||||
}
|
||||
@@ -150,11 +156,11 @@ namespace Project
|
||||
{
|
||||
PUB.AddEEDB($"SYNC완료({PUB.Result.TargetPos})");
|
||||
UpdateProgressStatus(stepTime.TotalSeconds, 5, "SYNC : 완료");
|
||||
|
||||
|
||||
// 동기화 완료 시 캔버스 모드 복귀
|
||||
if (PUB._mapCanvas != null)
|
||||
PUB._mapCanvas.SetSyncStatus("동기화 완료!", 1.0f, "잠시 후 메인 화면으로 이동합니다.");
|
||||
|
||||
|
||||
LastCommandTime = DateTime.Now;
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
|
||||
@@ -43,28 +43,27 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트를 내린다.
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
|
||||
// [PickOn/PickOff] 초기 리프트 동작
|
||||
var liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//리프트다운센서를 확인한다.
|
||||
var liftdown = true;
|
||||
if (liftdown == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||
PUB.log.AddE("리프트 하강이 확인되지 않습니다(10초)");
|
||||
PUB.sm.SetNewRunStep(ERunStep.ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PUB.log.Add("리프트 하강 완료");
|
||||
//리프트 센서 확인
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds > 10)
|
||||
{
|
||||
// Timebound check
|
||||
}
|
||||
PUB.log.Add("리프트 동작 확인 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
@@ -78,7 +77,7 @@ namespace Project
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//저속이동
|
||||
//저속이동 (후진 진입)
|
||||
var moveset = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
@@ -141,14 +140,40 @@ namespace Project
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
|
||||
PUB.log.Add("언로더 진입 완료. 작업 수행(Lift Pick/Drop)");
|
||||
|
||||
var liftCmd = arDev.Narumi.LiftCommand.UP;
|
||||
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
|
||||
{
|
||||
liftCmd = arDev.Narumi.LiftCommand.DN;
|
||||
}
|
||||
|
||||
PUB.AGV.LiftControl(liftCmd);
|
||||
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
// 리프트 동작 대기
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
|
||||
if (ts.TotalSeconds < 2) return false;
|
||||
|
||||
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
else if (PUB.sm.RunStepSeq == idx++)
|
||||
{
|
||||
//완료되었다.
|
||||
PUB.log.Add("언로더진입완료");
|
||||
PUB.log.Add("언로더 진입 및 작업 완료");
|
||||
PUB.sm.UpdateRunStepSeq();
|
||||
return false;
|
||||
}
|
||||
|
||||
PUB.AddEEDB($"언로더진입완료({PUB.Result.TargetPos})");
|
||||
PUB.AddEEDB($"언로더작업완료({PUB.Result.TargetPos})");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,7 @@ namespace Project
|
||||
private void _STEP_CLOSING_START(eSMStep step)
|
||||
{
|
||||
PUB.bShutdown = true;
|
||||
|
||||
// 장치 관리 태스크 종료
|
||||
StopDeviceManagementTask();
|
||||
|
||||
|
||||
PUB.AddEEDB("프로그램 종료");
|
||||
PUB.log.Add("Program Close");
|
||||
PUB.LogFlush();
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using Project.StateMachine;
|
||||
using COMM;
|
||||
using AR;
|
||||
using AGVNavigationCore.Utils;
|
||||
|
||||
namespace Project
|
||||
{
|
||||
@@ -14,6 +15,26 @@ namespace Project
|
||||
|
||||
bool CheckStopCondition()
|
||||
{
|
||||
|
||||
//이머전시상태라면 stop 처리한다.
|
||||
if (PUB.AGV.error.Emergency &&
|
||||
PUB.AGV.system1.agv_stop == true &&
|
||||
PUB.AGV.system1.stop_by_front_detect == false)
|
||||
{
|
||||
PUB.Speak(Lang.비상정지로인해작업을중단합니다);
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
//수동충전상태라면 이동하지 못한다
|
||||
if (VAR.BOOL[eVarBool.FLAG_CHARGEONM])
|
||||
{
|
||||
PUB.Speak("수동 충전중이라 사용할 수 없습니다");
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -50,7 +71,7 @@ namespace Project
|
||||
if (_SM_RUN_POSCHK(false, new TimeSpan()) == false) return false;
|
||||
|
||||
//현재위치노드 오류
|
||||
var currentNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNodeId);
|
||||
var currentNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNode.Id);
|
||||
if (currentNode == null)
|
||||
{
|
||||
PUB.log.AddE($"현재위치노드가 없습니다");
|
||||
@@ -60,7 +81,7 @@ namespace Project
|
||||
|
||||
//시작노드값이 없다면 현재위치를 노드로 결정한다
|
||||
if (PUB._virtualAGV.StartNode == null)
|
||||
PUB._virtualAGV.StartNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNodeId);
|
||||
PUB._virtualAGV.StartNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNode.Id);
|
||||
|
||||
//시작노드가없다면 오류
|
||||
if (PUB._virtualAGV.StartNode == null)
|
||||
@@ -81,7 +102,7 @@ namespace Project
|
||||
//경로 생성(경로정보가 없거나 현재노드가 경로에 없는경우)
|
||||
if (PUB._virtualAGV.CurrentPath == null ||
|
||||
PUB._virtualAGV.CurrentPath.DetailedPath.Any() == false ||
|
||||
PUB._virtualAGV.CurrentPath.DetailedPath.Where(t => t.NodeId.Equals(currentNode.NodeId)).Any() == false)
|
||||
PUB._virtualAGV.CurrentPath.DetailedPath.Where(t => t.NodeId.Equals(currentNode.Id)).Any() == false)
|
||||
{
|
||||
if (PUB.AGV.system1.agv_run)
|
||||
{
|
||||
@@ -96,12 +117,37 @@ namespace Project
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB._mapCanvas.CurrentPath = PathResult.result;
|
||||
PUB._virtualAGV.SetPath(PathResult.result);
|
||||
}
|
||||
|
||||
PUB.log.AddI($"경로생성 {PUB._virtualAGV.StartNode.RfidId} -> {PUB._virtualAGV.TargetNode.RfidId}");
|
||||
}
|
||||
|
||||
//경로에 대한 무결성 검증
|
||||
if (CheckPathIntegrity(PUB._virtualAGV.CurrentPath) == false)
|
||||
{
|
||||
if (PUB.AGV.system1.agv_run)
|
||||
{
|
||||
PUB.AGV.AGVMoveStop("Path Integrity Fail");
|
||||
}
|
||||
PUB.log.AddE($"경로 무결성 오류");
|
||||
PUB.sm.SetNewRunStep(ERunStep.READY);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//predict 를 이용하여 다음 이동을 모두 확인한다.
|
||||
var nextAction = PUB._virtualAGV.Predict();
|
||||
if(nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.PathOut)
|
||||
{
|
||||
//경로이탈
|
||||
PUB._virtualAGV.CurrentPath.DetailedPath.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = $"[다음 행동 예측]\n\n" +
|
||||
$"모터: {nextAction.Motor}\n" +
|
||||
@@ -112,7 +158,7 @@ namespace Project
|
||||
$"현재 상태: {PUB._virtualAGV.CurrentState}\n" +
|
||||
$"현재 방향: {PUB._virtualAGV.CurrentDirection}\n" +
|
||||
$"위치 확정: {PUB._virtualAGV.IsPositionConfirmed} (RFID {PUB._virtualAGV.DetectedRfidCount}개)\n" +
|
||||
$"현재 노드: {PUB._virtualAGV.CurrentNodeId ?? "없음"}";
|
||||
$"현재 노드: {PUB._virtualAGV.CurrentNode.Id ?? "없음"}";
|
||||
|
||||
//모터에서 정지를 요청했다
|
||||
if (nextAction.Motor == AGVNavigationCore.Models.MotorCommand.Stop)
|
||||
@@ -122,7 +168,7 @@ namespace Project
|
||||
// 완료(Complete) 상태라면 MarkStop 전송
|
||||
if (nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.MarkStop)
|
||||
{
|
||||
PUB.log.Add("다음행동예측에서 MARK STOP이 확인되었습니다");
|
||||
PUB.log.Add("다음행동예측에서 MARK STOP이 확인되었습니다 (자동정지시퀀스)");
|
||||
PUB.AGV.AGVMoveStop(nextAction.Message, arDev.Narumi.eStopOpt.MarkStop);
|
||||
}
|
||||
else
|
||||
@@ -136,10 +182,11 @@ namespace Project
|
||||
// 현재 노드가 타겟 노드와 같고, 위치가 확정된 상태라면 도착으로 간주
|
||||
// 단, AGV가 실제로 멈췄는지 확인 (agv_run == false)
|
||||
if (PUB._virtualAGV.IsPositionConfirmed &&
|
||||
PUB._virtualAGV.CurrentNodeId == PUB._virtualAGV.TargetNode.NodeId)
|
||||
PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.TargetNode.Id)
|
||||
{
|
||||
if (PUB.AGV.system1.agv_run == false)
|
||||
{
|
||||
PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료). Node:{PUB._virtualAGV.CurrentNode.Id}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -210,14 +257,14 @@ namespace Project
|
||||
PBSSensor = 1,
|
||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
});
|
||||
PUB.AGV.AGVMoveRun();//
|
||||
PUB.AGV.AGVMoveRun( arDev.Narumi.eRunOpt.Forward);//
|
||||
tm_gocharge_command = DateTime.Now;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//현재위치가 충전위치이고, 움직이지 않았다면 완료된 경우라 할수 있따
|
||||
if (PUB._virtualAGV.CurrentNodeId.Equals(PUB.setting.NodeMAP_RFID_Charger) &&
|
||||
if (PUB._virtualAGV.CurrentNode.Id.Equals(PUB.setting.NodeMAP_RFID_Charger) &&
|
||||
VAR.BOOL[eVarBool.MARK_SENSOR] == true)
|
||||
{
|
||||
PUB.log.AddI("충전위치 검색 완료");
|
||||
@@ -231,7 +278,13 @@ namespace Project
|
||||
PUB.AGV.error.Emergency == false &&
|
||||
PUB.AGV.system1.agv_run == false)
|
||||
{
|
||||
//PUB.PLC.Move(Device.PLC.Rundirection.Backward, "UpdateMotionPosition #1(" + sender + ")");
|
||||
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
|
||||
{
|
||||
Bunki = arDev.Narumi.eBunki.Strate,
|
||||
Direction = arDev.Narumi.eMoveDir.Backward,
|
||||
PBSSensor = 1,
|
||||
Speed = arDev.Narumi.eMoveSpd.Low,
|
||||
});
|
||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);//
|
||||
LastCommandTime = DateTime.Now;
|
||||
}
|
||||
@@ -244,5 +297,36 @@ namespace Project
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 경로 무결성(도킹방향 등) 검증
|
||||
/// </summary>
|
||||
/// <param name="pathResult"></param>
|
||||
/// <returns></returns>
|
||||
private bool CheckPathIntegrity(AGVNavigationCore.PathFinding.Core.AGVPathResult pathResult)
|
||||
{
|
||||
if (pathResult == null) return false;
|
||||
|
||||
// CalcPath에서 이미 DockingValidator를 수행했을 수 있음.
|
||||
// 만약 수행되지 않았다면 여기서 수행.
|
||||
if (pathResult.DockingValidation == null)
|
||||
{
|
||||
pathResult.DockingValidation = AGVNavigationCore.Utils.DockingValidator.ValidateDockingDirection(pathResult, PUB._mapCanvas.Nodes);
|
||||
}
|
||||
|
||||
// 검증 결과 확인
|
||||
if (pathResult.DockingValidation != null && pathResult.DockingValidation.IsValidationRequired)
|
||||
{
|
||||
if (pathResult.DockingValidation.IsValid == false)
|
||||
{
|
||||
PUB.log.AddE($"[경로무결성오류] {pathResult.DockingValidation.ValidationError}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}//cvass
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Project.StateMachine;
|
||||
using COMM;
|
||||
using AR;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.Controls;
|
||||
|
||||
namespace Project
|
||||
{
|
||||
@@ -17,22 +18,19 @@ namespace Project
|
||||
{
|
||||
private void AGV_Message(object sender, arDev.Narumi.MessageEventArgs e)
|
||||
{
|
||||
if (e.MsgType == arDev.arRS232.MessageType.Normal)
|
||||
|
||||
if (e.MsgType == arDev.NarumiSerialComm.MessageType.Normal)
|
||||
PUB.logagv.AddE(e.Message);
|
||||
else if (e.MsgType == arDev.arRS232.MessageType.Normal)
|
||||
|
||||
else if (e.MsgType == arDev.NarumiSerialComm.MessageType.Normal)
|
||||
PUB.logagv.Add(e.Message);
|
||||
else if (e.MsgType == arDev.arRS232.MessageType.Recv)
|
||||
else if (e.MsgType == arDev.NarumiSerialComm.MessageType.Recv)
|
||||
{
|
||||
if (e.Message.Substring(1).StartsWith("STS") == false)
|
||||
PUB.logagv.Add("AGV-RX", e.Message);
|
||||
}
|
||||
else if (e.MsgType == arDev.arRS232.MessageType.Send)
|
||||
else if (e.MsgType == arDev.NarumiSerialComm.MessageType.Send)
|
||||
PUB.logagv.Add("AGV-TX", e.Message);
|
||||
else
|
||||
{
|
||||
|
||||
PUB.logagv.Add(e.MsgType.ToString(), e.Message);
|
||||
}
|
||||
}
|
||||
@@ -43,6 +41,7 @@ namespace Project
|
||||
{
|
||||
try
|
||||
{
|
||||
VAR.TIME.Set(eVarTime.LastRecv_AGV, DateTime.Now);
|
||||
//데이터 파싱
|
||||
switch (e.DataType)
|
||||
{
|
||||
@@ -54,11 +53,29 @@ namespace Project
|
||||
var agv_chg = PUB.AGV.system1.Battery_charging;
|
||||
var agv_stp = PUB.AGV.system1.agv_stop;
|
||||
var agv_run = PUB.AGV.system1.agv_run;
|
||||
var agv_mrk = PUB.AGV.signal.mark_sensor;
|
||||
var agv_mrk = PUB.AGV.signal1.mark_sensor;
|
||||
|
||||
|
||||
//if (chg_run && PUB.AGV.system1.agv_run) PUB.Speak("이동을 시작 합니다");
|
||||
VAR.BOOL[eVarBool.AGVDIR_BACK] = PUB.AGV.data.Direction == 'B';
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// [Sync] Update VirtualAGV State
|
||||
AGVState syncState = AGVState.Idle;
|
||||
if (PUB.AGV.error.Value > 0) syncState = AGVState.Error;
|
||||
else if (PUB.AGV.system1.Battery_charging) syncState = AGVState.Charging;
|
||||
else if (PUB.AGV.system1.agv_run) syncState = AGVState.Moving;
|
||||
|
||||
if (PUB._virtualAGV != null && PUB._virtualAGV.GetCurrentState() != syncState)
|
||||
PUB.UpdateAGVState(syncState);
|
||||
|
||||
if (VAR.BOOL[eVarBool.AGV_ERROR] != (agv_err > 0))
|
||||
{
|
||||
@@ -89,8 +106,7 @@ namespace Project
|
||||
PUB.log.Add($"충전상태전환 {agv_chg}");
|
||||
VAR.BOOL[eVarBool.FLAG_CHARGEONA] = agv_chg;
|
||||
}
|
||||
//자동충전해제시 곧바로 수동 충전되는 경우가 있어 자동 상태를 BMS에 넣는다 230118
|
||||
PUB.BMS.AutoCharge = agv_chg;
|
||||
|
||||
|
||||
if (PUB.AGV.error.Charger_pos_error != VAR.BOOL[eVarBool.CHG_POSERR])
|
||||
{
|
||||
@@ -112,10 +128,10 @@ namespace Project
|
||||
}
|
||||
|
||||
//마크센서 상태가 변경이 되었다면
|
||||
if (VAR.BOOL[eVarBool.MARK_SENSOR] != PUB.AGV.signal.mark_sensor)
|
||||
if (VAR.BOOL[eVarBool.MARK_SENSOR] != PUB.AGV.signal1.mark_sensor)
|
||||
{
|
||||
PUB.logagv.Add($"MARK_SENSOR 변경({PUB.AGV.signal.mark_sensor})");
|
||||
VAR.BOOL[eVarBool.MARK_SENSOR] = PUB.AGV.signal.mark_sensor;
|
||||
PUB.logagv.Add($"MARK_SENSOR 변경({PUB.AGV.signal1.mark_sensor})");
|
||||
VAR.BOOL[eVarBool.MARK_SENSOR] = PUB.AGV.signal1.mark_sensor;
|
||||
|
||||
//AGV가 멈췄고 마크센서가 ON되었다면 마지막 RFID 위치가 확정된경우이다
|
||||
if (agv_stp && VAR.BOOL[eVarBool.MARK_SENSOR])
|
||||
@@ -141,9 +157,9 @@ namespace Project
|
||||
case arDev.Narumi.DataType.TAG:
|
||||
{
|
||||
//자동 실행 중이다.
|
||||
PUB.log.Add($"AGV 태그수신 : {PUB.AGV.data.TagNo}");
|
||||
PUB.Result.LastTAG = PUB.AGV.data.TagNo.ToString();
|
||||
|
||||
PUB.Result.LastTAG = PUB.AGV.data.TagNo;//.ToString("0000");
|
||||
PUB.log.Add($"AGV 태그수신 : {PUB.AGV.data.TagNo} LastTag:{PUB.Result.LastTAG}");
|
||||
//POT/NOT 보면 일단 바로 멈추게한다
|
||||
if (PUB.Result.CurrentPos == ePosition.POT || PUB.Result.CurrentPos == ePosition.NOT)
|
||||
{
|
||||
@@ -153,29 +169,26 @@ namespace Project
|
||||
}
|
||||
|
||||
//virtual agv setting
|
||||
var CurrentNode = PUB._mapNodes.FirstOrDefault(t => t.RfidId.Equals(PUB.Result.LastTAG, StringComparison.OrdinalIgnoreCase));
|
||||
var CurrentNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB.Result.LastTAG);
|
||||
if (CurrentNode == null)
|
||||
{
|
||||
//없는 노드는 자동으로 추가한다
|
||||
var newNodeId = $"AUTO_{PUB.Result.LastTAG}";
|
||||
var newNode = new MapNode
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Id = newNodeId,
|
||||
RfidId = PUB.Result.LastTAG,
|
||||
Name = $"자동추가_{PUB.Result.LastTAG}",
|
||||
Type = NodeType.Normal,
|
||||
Position = new Point(100, 100), // 기본 위치
|
||||
IsActive = true,
|
||||
DisplayColor = Color.Orange, // 자동 추가된 노드는 오렌지색으로 표시
|
||||
CreatedDate = DateTime.Now,
|
||||
ModifiedDate = DateTime.Now
|
||||
};
|
||||
|
||||
// 맵 노드 리스트에 추가
|
||||
PUB._mapNodes.Add(newNode);
|
||||
PUB._mapCanvas.Nodes.Add(newNode);
|
||||
|
||||
// 캔버스에 노드 반영 (재설정)
|
||||
PUB._mapCanvas.Nodes = PUB._mapNodes;
|
||||
PUB._mapCanvas.Nodes = PUB._mapCanvas.Nodes;
|
||||
|
||||
// 로그 기록
|
||||
PUB.log.AddI($"RFID:{PUB.Result.LastTAG} 노드를 자동 추가했습니다 (NodeId: {newNodeId})");
|
||||
@@ -205,7 +218,7 @@ namespace Project
|
||||
}
|
||||
|
||||
//이 후 상황을 예측한다
|
||||
if (PUB._mapCanvas != null)
|
||||
if (PUB._mapCanvas != null && PUB._virtualAGV != null)
|
||||
{
|
||||
var nextAction = PUB._virtualAGV.Predict();
|
||||
var message = $"[다음 행동 예측]\n\n" +
|
||||
@@ -217,11 +230,10 @@ namespace Project
|
||||
$"현재 상태: {PUB._virtualAGV.CurrentState}\n" +
|
||||
$"현재 방향: {PUB._virtualAGV.CurrentDirection}\n" +
|
||||
$"위치 확정: {PUB._virtualAGV.IsPositionConfirmed} (RFID {PUB._virtualAGV.DetectedRfidCount}개)\n" +
|
||||
$"현재 노드: {PUB._virtualAGV.CurrentNodeId ?? "없음"}";
|
||||
$"현재 노드: {PUB._virtualAGV.CurrentNode.Id ?? "없음"}";
|
||||
|
||||
PUB._mapCanvas.PredictMessage = message;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -229,4 +241,4 @@ namespace Project
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,15 +25,18 @@ namespace Project
|
||||
DateTime lastbmstime = DateTime.Now;
|
||||
private void Bms_Message(object sender, arDev.BMS.MessageEventArgs e)
|
||||
{
|
||||
if (e.MsgType == arDev.arRS232.MessageType.Error) PUB.logbms.AddE( e.Message);
|
||||
|
||||
if (e.MsgType == arDev.BMSSerialComm.MessageType.Error) PUB.logbms.AddE(e.Message);
|
||||
else
|
||||
{
|
||||
VAR.TIME[eVarTime.LastRecv_BAT] = DateTime.Now;
|
||||
|
||||
var hexstr = e.Data.GetHexString().Trim();
|
||||
bool addlog = false;
|
||||
var logtimesec = 30;
|
||||
if (hexstr.StartsWith("DD 04"))
|
||||
{
|
||||
if (lastbms04.Equals(hexstr.Substring(0,5)) == false)
|
||||
if (lastbms04.Equals(hexstr.Substring(0, 5)) == false)
|
||||
{
|
||||
addlog = true;
|
||||
lastbms04 = "DD 04";
|
||||
@@ -133,35 +136,51 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
if(addlog)
|
||||
PUB.logbms.Add("BMS:" + hexstr);
|
||||
if (addlog)
|
||||
{
|
||||
//if (e.MsgType == arDev.arRS232.MessageType.Recv)
|
||||
// PUB.logbms.Add("RX", e.Data.GetHexString());
|
||||
//else if (e.MsgType == arDev.arRS232.MessageType.Send)
|
||||
// PUB.logbms.Add("TX", e.Data.GetHexString());
|
||||
//else
|
||||
{
|
||||
PUB.logbms.Add(e.MsgType.ToString(),e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
private void BMS_ChargeDetect(object sender, arDev.ChargetDetectArgs e)
|
||||
{
|
||||
//자동충전중이아니고 멈춰있다면 수동 충전으로 전환한다
|
||||
if (PUB.AGV.system1.Battery_charging == false && PUB.AGV.system1.agv_stop == true && VAR.BOOL[eVarBool.FLAG_CHARGEONM] == false)
|
||||
VAR.TIME[eVarTime.LastRecv_BAT] = DateTime.Now;
|
||||
if (e.Detected == true) //충전이 감지되었다.
|
||||
{
|
||||
if (PUB.setting.DetectManualCharge)
|
||||
if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false && VAR.BOOL[eVarBool.FLAG_CHARGEONM] == false)
|
||||
{
|
||||
VAR.BOOL[eVarBool.FLAG_CHARGEONM] = true;
|
||||
PUB.Speak(Lang.충전이감지되어수동충전으로전환합니다);
|
||||
if (PUB.AGV.system1.agv_run == true) PUB.AGV.AGVMoveStop("수동충전감지");
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.Add($"충전이 감지되었지만 메뉴얼 전환 비활성화됨");
|
||||
}
|
||||
|
||||
}
|
||||
else PUB.logbms.AddI("Battery Charge Off");
|
||||
|
||||
}
|
||||
|
||||
private void Bms_BMSDataReceive(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
|
||||
VAR.TIME[eVarTime.LastRecv_BAT] = DateTime.Now;
|
||||
//PUB.mapctl.Manager.agv.BatteryLevel = PUB.BMS.Current_Level;
|
||||
//PUB.mapctl.Manager.agv.BatteryTemp1 = PUB.BMS.Current_temp1;
|
||||
//PUB.mapctl.Manager.agv.BatteryTemp2 = PUB.BMS.Current_temp2;
|
||||
|
||||
// [Sync] Update VirtualAGV Battery
|
||||
PUB.UpdateAGVBattery(PUB.BMS.Current_Level);
|
||||
|
||||
if (PUB.BMS.Current_Level <= PUB.setting.ChargeStartLevel)
|
||||
{
|
||||
//배터리 레벨이 기준보다 낮다면 경고를 활성화 한다
|
||||
@@ -184,6 +203,7 @@ namespace Project
|
||||
}
|
||||
private void BMS_BMSCellDataReceive(object sender, arDev.BMSCelvoltageEventArgs e)
|
||||
{
|
||||
VAR.TIME[eVarTime.LastRecv_BAT] = DateTime.Now;
|
||||
EEMStatus.MakeBMSInformation_Cell();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace Project
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string lockstep = string.Empty;
|
||||
System.Threading.ManualResetEvent mreloop = new System.Threading.ManualResetEvent(true);
|
||||
void sm_Running(object sender, StateMachine.StateMachine.RunningEventArgs e)
|
||||
{
|
||||
|
||||
@@ -89,6 +90,11 @@ namespace Project
|
||||
}
|
||||
else PUB.sm.WaitFirstRun = false;
|
||||
|
||||
if (mreloop.WaitOne(1) == false) return;
|
||||
mreloop.Reset();
|
||||
|
||||
lockstep = e.Step.ToString();
|
||||
|
||||
//main loop
|
||||
switch (e.Step)
|
||||
{
|
||||
@@ -154,9 +160,7 @@ namespace Project
|
||||
UpdateStatusMessage("준비 완료", Color.Red, Color.Gold);
|
||||
if (startuptime.Year == 1982) startuptime = DateTime.Now;
|
||||
|
||||
// 장치 관리 태스크 시작 (IDLE 진입 시 한 번만)
|
||||
StartDeviceManagementTask();
|
||||
|
||||
|
||||
// 동기화 모드 종료 (혹시 남아있을 경우)
|
||||
if (PUB._mapCanvas != null)
|
||||
{
|
||||
@@ -188,11 +192,14 @@ namespace Project
|
||||
break;
|
||||
|
||||
case eSMStep.SYNC:
|
||||
if(e.isFirst)
|
||||
if (e.isFirst)
|
||||
{
|
||||
// 동기화 완료 시 캔버스 모드 복귀
|
||||
if (PUB._mapCanvas != null)
|
||||
PUB._mapCanvas.SetSyncStatus("설정 동기화", 0f, "환경설정 값으로 AGV컨트롤러를 설정 합니다");
|
||||
this.Invoke(new Action(() => {
|
||||
if (PUB._mapCanvas != null)
|
||||
PUB._mapCanvas.SetSyncStatus("설정 동기화", 0f, "환경설정 값으로 AGV컨트롤러를 설정 합니다");
|
||||
}));
|
||||
|
||||
}
|
||||
if (_SM_RUN_SYNC(runStepisFirst, PUB.sm.GetRunSteptime))
|
||||
{
|
||||
@@ -207,7 +214,6 @@ namespace Project
|
||||
PUB.Speak( Lang.초기화완료);
|
||||
|
||||
PUB.sm.SetNewStep(eSMStep.IDLE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -271,6 +277,8 @@ namespace Project
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
mreloop.Set();
|
||||
}
|
||||
|
||||
void DeleteFile(string path)
|
||||
|
||||
@@ -21,22 +21,205 @@ namespace Project
|
||||
{
|
||||
DateTime chargesynctime = DateTime.Now;
|
||||
DateTime agvsendstarttime = DateTime.Now;
|
||||
DateTime lastXbeStatusSendTime = DateTime.Now;
|
||||
DateTime lastBmsQueryTime = DateTime.Now;
|
||||
object connectobj = new object();
|
||||
|
||||
void sm_SPS(object sender, EventArgs e)
|
||||
{
|
||||
if (PUB.sm.Step < eSMStep.IDLE || PUB.sm.Step >= eSMStep.CLOSING) return;
|
||||
if (PUB.sm == null || PUB.sm.Step < eSMStep.IDLE || PUB.sm.Step >= eSMStep.CLOSING || PUB.bShutdown == true) return;
|
||||
|
||||
// SPS는 이제 간단한 작업만 수행
|
||||
// 장치 연결 및 상태 전송은 별도 태스크(_DeviceManagement.cs)에서 처리
|
||||
// 장치 연결이 별도로 존재할때 1회 수신 후 통신이 전체 먹통되는 증상이 있어 우선 복귀 251215
|
||||
try
|
||||
{
|
||||
// 여기에 SPS에서 처리해야 할 간단한 작업만 남김
|
||||
// 현재는 비어있음 - 필요한 경우 추가
|
||||
|
||||
// ========== 1. 장치 연결 관리 ==========
|
||||
// AGV 연결
|
||||
lock (connectobj)
|
||||
{
|
||||
ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
|
||||
eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV);
|
||||
}
|
||||
|
||||
|
||||
// XBee 연결
|
||||
lock (connectobj)
|
||||
{
|
||||
ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE,
|
||||
eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, null);
|
||||
}
|
||||
|
||||
|
||||
// BMS 연결
|
||||
lock (connectobj)
|
||||
{
|
||||
if (PUB.BMS.IsOpen == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT);
|
||||
if (ts.TotalSeconds > 3)
|
||||
{
|
||||
PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}");
|
||||
PUB.BMS.PortName = PUB.setting.Port_BAT;
|
||||
if (PUB.BMS.Open())
|
||||
PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})");
|
||||
|
||||
VAR.TIME.Update(eVarTime.LastConn_BAT);
|
||||
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
|
||||
}
|
||||
}
|
||||
else if (PUB.BMS.IsValid == false)
|
||||
{
|
||||
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
|
||||
if (ts.TotalSeconds > ( Math.Max(10,PUB.setting.interval_bms) * 2.5))
|
||||
{
|
||||
this.BeginInvoke(new Action(() =>
|
||||
{
|
||||
PUB.log.Add("BMS 자동 연결 해제 (응답 없음)");
|
||||
PUB.BMS.Close();
|
||||
}));
|
||||
VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 2. XBee 상태 전송 ==========
|
||||
if (PUB.XBE != null && PUB.XBE.IsOpen)
|
||||
{
|
||||
var tsXbe = DateTime.Now - lastXbeStatusSendTime;
|
||||
if (tsXbe.TotalSeconds >= PUB.setting.interval_xbe)
|
||||
{
|
||||
lastXbeStatusSendTime = DateTime.Now;
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PUB.XBE.SendStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"XBee SendStatus 오류: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. BMS 쿼리 및 배터리 경고 ==========
|
||||
if (PUB.BMS != null && PUB.BMS.IsOpen)
|
||||
{
|
||||
var tsBms = DateTime.Now - lastBmsQueryTime;
|
||||
if (tsBms.TotalSeconds >= PUB.setting.interval_bms)
|
||||
{
|
||||
lastBmsQueryTime = DateTime.Now;
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PUB.BMS.SendQuery();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"BMS SendQuery 오류: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 배터리 경고음
|
||||
try
|
||||
{
|
||||
Update_BatteryWarnSpeak();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"BatteryWarnSpeak 오류: {ex.Message}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE($"sm_SPS Exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 시리얼 포트 연결 (arDev.arRS232)
|
||||
/// </summary>
|
||||
bool ConnectSerialPort(arDev.ISerialComm dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime? recvtime)
|
||||
{
|
||||
if (port.isEmpty()) return false;
|
||||
|
||||
if (dev.IsOpen == false && port.isEmpty() == false)
|
||||
{
|
||||
var tsPLC = VAR.TIME.RUN(conntry);
|
||||
if (tsPLC.TotalSeconds > 5)
|
||||
{
|
||||
VAR.TIME.Update(conntry);
|
||||
try
|
||||
{
|
||||
if (recvtime != null) VAR.TIME.Update(recvtime);
|
||||
dev.PortName = port;
|
||||
dev.BaudRate = baud;
|
||||
PUB.log.Add($"Connect to {port}:{baud}");
|
||||
if (dev.Open())
|
||||
{
|
||||
if (recvtime != null) VAR.TIME[recvtime] = DateTime.Now; //값을 수신한것처럼한다
|
||||
PUB.log.Add(port, $"[{port}:{baud}] 연결 완료");
|
||||
}
|
||||
else
|
||||
{
|
||||
//존재하지 않는 포트라면 sync를 벗어난다
|
||||
var ports = System.IO.Ports.SerialPort.GetPortNames().Select(t => t.ToLower()).ToList();
|
||||
if (ports.Contains(PUB.setting.Port_AGV.ToLower()) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var errmessage = dev.ErrorMessage;
|
||||
PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}");
|
||||
}
|
||||
}
|
||||
VAR.TIME.Update(conn);
|
||||
VAR.TIME.Update(conntry);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PUB.log.AddE(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (dev.PortName.Equals(port) == false)
|
||||
{
|
||||
this.BeginInvoke(new Action(() =>
|
||||
{
|
||||
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
|
||||
VAR.TIME.Set(conntry, DateTime.Now);
|
||||
dev.Close();
|
||||
}));
|
||||
|
||||
VAR.TIME.Update(conntry);
|
||||
}
|
||||
else if (dev.IsOpen && recvtime != null)
|
||||
{
|
||||
//연결은 되었으나 통신이 지난지 10초가 지났다면 자동종료한다
|
||||
var tsRecv = VAR.TIME.RUN(recvtime);
|
||||
var tsConn = VAR.TIME.RUN(conntry);
|
||||
if (tsRecv.TotalSeconds > 30 && tsConn.TotalSeconds > 5)
|
||||
{
|
||||
this.BeginInvoke(new Action(() =>
|
||||
{
|
||||
PUB.log.Add($"{port} 자동 연결 해제 (응답 없음)");
|
||||
dev.Close();
|
||||
}));
|
||||
VAR.TIME.Set(conntry, DateTime.Now);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,10 +566,14 @@ namespace Project
|
||||
if (PUB.sm.Step > eSMStep.INIT)
|
||||
{
|
||||
//오류가 있다면 오류를 표시해준다.
|
||||
if (PUB.AGV.IsOpen == false)
|
||||
if (PUB.AGV.IsOpen == false )
|
||||
{
|
||||
UpdateStatusMessage("AGV 연결실패", Color.Tomato, Color.Black);
|
||||
}
|
||||
else if(PUB.AGV.IsValid==false)
|
||||
{
|
||||
UpdateStatusMessage("AGV 통신상태 불량", Color.Tomato, Color.Black);
|
||||
}
|
||||
else if (PUB.AGV.error.Emergency)
|
||||
{
|
||||
if (PUB.AGV.error.runerror_by_no_magent_line)
|
||||
@@ -581,6 +585,14 @@ namespace Project
|
||||
UpdateStatusMessage("비상 정지", Color.Tomato, Color.Black);
|
||||
}
|
||||
}
|
||||
else if (PUB.BMS != null || PUB.BMS.IsOpen==false)
|
||||
{
|
||||
UpdateStatusMessage("BMS가 연결되지 않았습니다", Color.Tomato, Color.Black);
|
||||
}
|
||||
else if (PUB.BMS != null || PUB.BMS.IsValid == false)
|
||||
{
|
||||
UpdateStatusMessage("BMS 통신상태 불량", Color.Tomato, Color.Black);
|
||||
}
|
||||
//else if (PUB.PLC.IsOpen == false)
|
||||
//{
|
||||
// UpdateStatusMessage(Lang.PLC연결실패, Color.Tomato, Color.Black);
|
||||
@@ -649,7 +661,7 @@ namespace Project
|
||||
stMsg = Lang.전방에물체가감지되었습니다;
|
||||
//else if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_EMG))
|
||||
// stMsg = Lang.비상정지신호가감지되었습니다;
|
||||
else if (PUB.AGV.signal.front_gate_out == true)
|
||||
else if (PUB.AGV.signal1.front_gate_out == true)
|
||||
stMsg = Lang.선로를이탈했습니다;
|
||||
else if (PUB.AGV.error.runerror_by_no_magent_line)
|
||||
stMsg = "마그네틱 라인을 벗어났습니다";
|
||||
|
||||
@@ -8,6 +8,7 @@ using AGVNavigationCore.Utils;
|
||||
using AR;
|
||||
using arDev;
|
||||
using COMM;
|
||||
using Project.StateMachine;
|
||||
|
||||
namespace Project
|
||||
{
|
||||
@@ -47,80 +48,128 @@ namespace Project
|
||||
if (data.Length > 4)
|
||||
{
|
||||
var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1);
|
||||
var node = PUB._mapNodes.FirstOrDefault(t => t.RfidId == currTag);
|
||||
if (node == null)
|
||||
if (ushort.TryParse(currTag, out ushort currtagValue))
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-SetCurrent] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.AddI($"XBEE:현재위치설정:[{node.RfidId}]{node.NodeId}");
|
||||
}
|
||||
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}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.AddI($"XBEE:현재위치설정:[{node.RfidId}]{node.Id}");
|
||||
}
|
||||
|
||||
PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, node, PUB._virtualAGV.CurrentDirection);
|
||||
PUB._virtualAGV.SetPosition(node, PUB._virtualAGV.CurrentDirection);
|
||||
PUB._mapCanvas.SetAGVPosition(PUB.setting.MCID, node, PUB._virtualAGV.CurrentDirection);
|
||||
PUB._virtualAGV.SetPosition(node, PUB._virtualAGV.CurrentDirection);
|
||||
}
|
||||
else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Value Errorr:{data}");
|
||||
}
|
||||
else PUB.log.AddE($"[{logPrefix}-SetCurrent] TagString Lenght Errorr:{data.Length}");
|
||||
break;
|
||||
|
||||
case ENIGProtocol.AGVCommandHE.Goto: //move to tag
|
||||
if (data.Length > 4)
|
||||
case ENIGProtocol.AGVCommandHE.PickOn: // 110
|
||||
case ENIGProtocol.AGVCommandHE.PickOff: // 111
|
||||
{
|
||||
var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1);
|
||||
var targetNode = PUB._mapNodes.FirstOrDefault(t => t.RfidId == currTag);
|
||||
PUB.log.AddI($"XBEE:작업명령수신:{cmd}");
|
||||
|
||||
|
||||
//자동상태가아니라면 처리하지 않는다.
|
||||
if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false)
|
||||
// 현재 위치 확인 (TargetNode가 아닌 CurrentNode 기준)
|
||||
var currNode = PUB._virtualAGV.CurrentNode;
|
||||
if (currNode == null)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 자동실행상태가 아닙니다");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.ManualMode, $"{currTag}");
|
||||
}
|
||||
|
||||
//목적지
|
||||
PUB._virtualAGV.TargetNode = targetNode;
|
||||
if (targetNode == null)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}");
|
||||
PUB.log.AddE($"[{logPrefix}-{cmd}] 현재 노드를 알 수 없습니다 NodeID:{PUB._virtualAGV.CurrentNode.Id}");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, "Unknown Node");
|
||||
return;
|
||||
}
|
||||
|
||||
///출발지
|
||||
var startNode = PUB._mapNodes.FirstOrDefault(t => t.RfidId == PUB._virtualAGV.CurrentNodeId);
|
||||
PUB._virtualAGV.StartNode = startNode;
|
||||
if (startNode == null)
|
||||
PUB.NextWorkCmd = cmd;
|
||||
ERunStep nextStep = ERunStep.READY;
|
||||
|
||||
switch (currNode.StationType)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 시작노드가 없습니다(현재위치 없음) NodeID:{PUB._virtualAGV.CurrentNodeId}");
|
||||
case StationType.Loader: nextStep = ERunStep.LOADER_IN; break;
|
||||
case StationType.UnLoader: nextStep = ERunStep.UNLOADER_IN; break;
|
||||
case StationType.Buffer: nextStep = ERunStep.BUFFER_IN; break;
|
||||
case StationType.Clearner: nextStep = ERunStep.CLEANER_IN; break;
|
||||
default:
|
||||
PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({currNode.Type})은 작업을 지원하지 않습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startNode != null)
|
||||
{
|
||||
//시작위치가 존재한다면 경로를 예측한다.
|
||||
var rltGoto = CalcPath(startNode, targetNode);
|
||||
if (rltGoto.result == null)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 경로예측실패 {rltGoto.message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//경로예측을 화면에 표시해준다.
|
||||
PUB._virtualAGV.SetPath(rltGoto.result);
|
||||
var pathWithRfid = rltGoto.result.GetSimplePath().Select(nodeId => PUB._virtualAGV.GetRfidByNodeId(PUB._mapNodes, nodeId)).ToList();
|
||||
PUB.log.Add($"경로예측결과:{pathWithRfid}");
|
||||
}
|
||||
}
|
||||
|
||||
//대상이동으로 처리한다.
|
||||
PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO);
|
||||
|
||||
//Move to
|
||||
PUB.log.Add($"[{logPrefix}-Goto] {startNode.RfidId} -> {targetNode.RfidId}");
|
||||
|
||||
PUB.log.AddI($"작업 시작: {nextStep} (Type: {cmd})");
|
||||
PUB.sm.SetNewRunStep(nextStep);
|
||||
}
|
||||
else PUB.log.AddE($"[{logPrefix}-Goto] TagString Lenght Errorr:{data.Length}");
|
||||
break;
|
||||
|
||||
case ENIGProtocol.AGVCommandHE.Charger: // 112
|
||||
{
|
||||
PUB.log.AddI($"XBEE:충전명령수신");
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Charger;
|
||||
PUB.sm.SetNewRunStep(ERunStep.GOCHARGE);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENIGProtocol.AGVCommandHE.GotoAlias:
|
||||
case ENIGProtocol.AGVCommandHE.Goto: //move to tag
|
||||
var datalength = cmd == ENIGProtocol.AGVCommandHE.GotoAlias ? 2 : 1;
|
||||
if (data.Length > datalength)
|
||||
{
|
||||
var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1).Trim();
|
||||
MapNode targetNode = null;
|
||||
if(cmd == ENIGProtocol.AGVCommandHE.GotoAlias)
|
||||
{
|
||||
targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.AliasName == currTag);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ushort.TryParse(currTag, out ushort currtagvalue))
|
||||
targetNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == currtagvalue);
|
||||
else PUB.log.Add($"targstring 이 숫자가 아니라서 대상을 설정할 수 없습니다 값:{currTag}");
|
||||
}
|
||||
|
||||
if (targetNode != null)
|
||||
{
|
||||
//자동상태가아니라면 처리하지 않는다.
|
||||
if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 자동실행상태가 아닙니다");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.ManualMode, $"{currTag}");
|
||||
return;
|
||||
}
|
||||
|
||||
//목적지
|
||||
PUB._virtualAGV.TargetNode = targetNode;
|
||||
if (targetNode == null)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 노드정보를 찾을 수 없습니다 RFID:{currTag}");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.EmptyNode, $"{currTag}");
|
||||
return;
|
||||
}
|
||||
|
||||
///출발지
|
||||
var startNode = PUB._mapCanvas.Nodes.FirstOrDefault(t => t.RfidId == PUB._virtualAGV.CurrentNode.RfidId);
|
||||
PUB._virtualAGV.StartNode = startNode;
|
||||
if (startNode == null)
|
||||
{
|
||||
PUB.log.AddE($"[{logPrefix}-Goto] 시작노드가 없습니다(현재위치 없음) NodeID:{PUB._virtualAGV.CurrentNode.Id}");
|
||||
}
|
||||
|
||||
//대상이동으로 처리한다.
|
||||
if(PUB.sm.RunStep != ERunStep.GOTO)
|
||||
{
|
||||
PUB.sm.SetNewRunStep(StateMachine.ERunStep.GOTO);
|
||||
PUB.sm.ResetRunStepSeq();
|
||||
}
|
||||
|
||||
|
||||
//Move to
|
||||
PUB.log.Add($"[{logPrefix}-{cmd}] {startNode.RfidId} -> {targetNode.RfidId}");
|
||||
}
|
||||
else PUB.log.AddE($"[{logPrefix}-{cmd}] 대상노드가 없습니다 {data}");
|
||||
}
|
||||
else PUB.log.AddE($"[{logPrefix}-{cmd}] Length Error:{data.Length}");
|
||||
break;
|
||||
|
||||
case ENIGProtocol.AGVCommandHE.Stop: //stop
|
||||
@@ -174,7 +223,7 @@ namespace Project
|
||||
|
||||
PUB.log.Add($"[{logPrefix}-AutoMove] DIR:{bunkidata.Direction}-{bunkidata.Bunki},SPD:{bunkidata.Speed}");
|
||||
PUB.AGV.AGVMoveSet(bunkidata);
|
||||
PUB.AGV.AGVMoveRun();
|
||||
PUB.AGV.AGVMoveRun((MotDirection == 0 ? arDev.Narumi.eRunOpt.Backward : arDev.Narumi.eRunOpt.Forward));
|
||||
break;
|
||||
|
||||
case ENIGProtocol.AGVCommandHE.MarkStop: //Set MarkStop
|
||||
@@ -204,7 +253,11 @@ namespace Project
|
||||
PUB.log.AddI($"충전을 시작합니다");
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
PUB.logagv.AddE($"Unknown Command : {cmd} Sender:{e.ReceivedPacket.ID}, Target:{data[0]}");
|
||||
PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"{cmd}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +271,7 @@ namespace Project
|
||||
AGVNavigationCore.PathFinding.Planning.AGVPathfinder _advancedPathfinder = null;
|
||||
(AGVNavigationCore.PathFinding.Core.AGVPathResult result, string message) CalcPath(MapNode startNode, MapNode targetNode)
|
||||
{
|
||||
var _mapNodes = PUB._mapNodes;
|
||||
var _mapNodes = PUB._mapCanvas.Nodes;
|
||||
// 시작 RFID가 없으면 AGV 현재 위치로 설정
|
||||
if (startNode == null || targetNode == null)
|
||||
return (null, "시작 RFID와 목표 RFID를 선택해주세요.");
|
||||
@@ -242,12 +295,25 @@ namespace Project
|
||||
|
||||
_simulatorCanvas.FitToNodes();
|
||||
string Message = string.Empty;
|
||||
if (advancedResult.Success)
|
||||
if (advancedResult != null && advancedResult.Success)
|
||||
{
|
||||
// 도킹 검증이 없는 경우 추가 검증 수행
|
||||
if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired)
|
||||
advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes);
|
||||
|
||||
//마지막대상이 버퍼라면 시퀀스처리를 해야한다
|
||||
if (targetNode.StationType == StationType.Buffer&& advancedResult.DetailedPath.Any())
|
||||
{
|
||||
var lastDetailPath = advancedResult.DetailedPath.Last();
|
||||
if (lastDetailPath.NodeId == targetNode.Id) //마지막노드 재확인
|
||||
{
|
||||
//버퍼에 도킹할때에는 마지막 노드에서 멈추고 시퀀스를 적용해야한다
|
||||
advancedResult.DetailedPath = advancedResult.DetailedPath.Take(advancedResult.DetailedPath.Count - 1).ToList();
|
||||
Console.WriteLine("최종위치가 버퍼이므로 마지막 RFID에서 멈추도록 합니다");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_simulatorCanvas.CurrentPath = advancedResult;
|
||||
//_pathLengthLabel.Text = $"경로 길이: {advancedResult.TotalDistance:F1}";
|
||||
//_statusLabel.Text = $"경로 계산 완료 ({advancedResult.CalculationTimeMs}ms)";
|
||||
@@ -262,12 +328,12 @@ namespace Project
|
||||
//UpdateAdvancedPathDebugInfo(advancedResult);
|
||||
|
||||
}
|
||||
else
|
||||
else if(advancedResult != null)
|
||||
{
|
||||
// 경로 실패시 디버깅 정보 초기화
|
||||
//_pathDebugLabel.Text = $"경로: 실패 - {advancedResult.ErrorMessage}";
|
||||
advancedResult = null;
|
||||
//_pathDebugLabel.Text = $"경로: 실패 - {advancedResult.ErrorMessage}";
|
||||
Message = $"경로를 찾을 수 없습니다:\n{advancedResult.ErrorMessage}";
|
||||
advancedResult = null;
|
||||
}
|
||||
|
||||
return (advancedResult, Message);
|
||||
|
||||
261
Cs_HMI/Project/ViewForm/fAgv.Designer.cs
generated
261
Cs_HMI/Project/ViewForm/fAgv.Designer.cs
generated
@@ -30,17 +30,18 @@
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.richTextBox1 = new System.Windows.Forms.RichTextBox();
|
||||
this.richTextBox2 = new System.Windows.Forms.RichTextBox();
|
||||
this.richTextBox3 = new System.Windows.Forms.RichTextBox();
|
||||
this.richTextBox4 = new System.Windows.Forms.RichTextBox();
|
||||
this.rtSystem0 = new System.Windows.Forms.RichTextBox();
|
||||
this.rtSystem1 = new System.Windows.Forms.RichTextBox();
|
||||
this.rtSignal1 = new System.Windows.Forms.RichTextBox();
|
||||
this.rtError = new System.Windows.Forms.RichTextBox();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.lbIP = new System.Windows.Forms.Label();
|
||||
this.button7 = new System.Windows.Forms.Button();
|
||||
this.button6 = new System.Windows.Forms.Button();
|
||||
this.button5 = new System.Windows.Forms.Button();
|
||||
this.button17 = new System.Windows.Forms.Button();
|
||||
this.lbIP = new System.Windows.Forms.Label();
|
||||
this.button3 = new System.Windows.Forms.Button();
|
||||
this.button2 = new System.Windows.Forms.Button();
|
||||
this.button1 = new System.Windows.Forms.Button();
|
||||
@@ -48,13 +49,16 @@
|
||||
this.button4 = new System.Windows.Forms.Button();
|
||||
this.button9 = new System.Windows.Forms.Button();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.button16 = new System.Windows.Forms.Button();
|
||||
this.button10 = new System.Windows.Forms.Button();
|
||||
this.lbPortName = new System.Windows.Forms.Label();
|
||||
this.button15 = new System.Windows.Forms.Button();
|
||||
this.button14 = new System.Windows.Forms.Button();
|
||||
this.button11 = new System.Windows.Forms.Button();
|
||||
this.button12 = new System.Windows.Forms.Button();
|
||||
this.button13 = new System.Windows.Forms.Button();
|
||||
this.button14 = new System.Windows.Forms.Button();
|
||||
this.button15 = new System.Windows.Forms.Button();
|
||||
this.button10 = new System.Windows.Forms.Button();
|
||||
this.button16 = new System.Windows.Forms.Button();
|
||||
this.rtData = new System.Windows.Forms.RichTextBox();
|
||||
this.rtSignal2 = new System.Windows.Forms.RichTextBox();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.panel1.SuspendLayout();
|
||||
this.panel2.SuspendLayout();
|
||||
@@ -67,11 +71,13 @@
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.richTextBox1, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.richTextBox2, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.richTextBox3, 2, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.richTextBox4, 3, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtSignal2, 2, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtSystem0, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtSystem1, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtSignal1, 2, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtError, 3, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 2);
|
||||
this.tableLayoutPanel1.Controls.Add(this.rtData, 3, 1);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
@@ -82,45 +88,47 @@
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(1050, 461);
|
||||
this.tableLayoutPanel1.TabIndex = 6;
|
||||
//
|
||||
// richTextBox1
|
||||
// rtSystem0
|
||||
//
|
||||
this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.richTextBox1.Location = new System.Drawing.Point(3, 3);
|
||||
this.richTextBox1.Name = "richTextBox1";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.richTextBox1, 2);
|
||||
this.richTextBox1.Size = new System.Drawing.Size(256, 404);
|
||||
this.richTextBox1.TabIndex = 1;
|
||||
this.richTextBox1.Text = "";
|
||||
this.rtSystem0.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtSystem0.Font = new System.Drawing.Font("Calibri", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
|
||||
this.rtSystem0.Location = new System.Drawing.Point(3, 3);
|
||||
this.rtSystem0.Name = "rtSystem0";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.rtSystem0, 2);
|
||||
this.rtSystem0.Size = new System.Drawing.Size(256, 404);
|
||||
this.rtSystem0.TabIndex = 1;
|
||||
this.rtSystem0.Text = "test2\ntest3\nteat\nasdfjalsdf\nasdjfklasdfj\nkalsdjfalksdjfa\nsdjfklasdjfklasjdf\n";
|
||||
//
|
||||
// richTextBox2
|
||||
// rtSystem1
|
||||
//
|
||||
this.richTextBox2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.richTextBox2.Location = new System.Drawing.Point(265, 3);
|
||||
this.richTextBox2.Name = "richTextBox2";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.richTextBox2, 2);
|
||||
this.richTextBox2.Size = new System.Drawing.Size(256, 404);
|
||||
this.richTextBox2.TabIndex = 1;
|
||||
this.richTextBox2.Text = "";
|
||||
this.rtSystem1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtSystem1.Font = new System.Drawing.Font("Calibri", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
|
||||
this.rtSystem1.Location = new System.Drawing.Point(265, 3);
|
||||
this.rtSystem1.Name = "rtSystem1";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.rtSystem1, 2);
|
||||
this.rtSystem1.Size = new System.Drawing.Size(256, 404);
|
||||
this.rtSystem1.TabIndex = 1;
|
||||
this.rtSystem1.Text = "test2\ntest3\nteat\nasdfjalsdf\nasdjfklasdfj\nkalsdjfalksdjfa\nsdjfklasdjfklasjdf\n";
|
||||
//
|
||||
// richTextBox3
|
||||
// rtSignal1
|
||||
//
|
||||
this.richTextBox3.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.richTextBox3.Location = new System.Drawing.Point(527, 3);
|
||||
this.richTextBox3.Name = "richTextBox3";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.richTextBox3, 2);
|
||||
this.richTextBox3.Size = new System.Drawing.Size(256, 404);
|
||||
this.richTextBox3.TabIndex = 1;
|
||||
this.richTextBox3.Text = "";
|
||||
this.rtSignal1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtSignal1.Font = new System.Drawing.Font("Calibri", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
|
||||
this.rtSignal1.Location = new System.Drawing.Point(527, 3);
|
||||
this.rtSignal1.Name = "rtSignal1";
|
||||
this.rtSignal1.Size = new System.Drawing.Size(256, 199);
|
||||
this.rtSignal1.TabIndex = 1;
|
||||
this.rtSignal1.Text = "test2\ntest3\nteat\nasdfjalsdf\nasdjfklasdfj\nkalsdjfalksdjfa\nsdjfklasdjfklasjdf\n";
|
||||
//
|
||||
// richTextBox4
|
||||
// rtError
|
||||
//
|
||||
this.richTextBox4.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.richTextBox4.Location = new System.Drawing.Point(789, 3);
|
||||
this.richTextBox4.Name = "richTextBox4";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.richTextBox4, 2);
|
||||
this.richTextBox4.Size = new System.Drawing.Size(258, 404);
|
||||
this.richTextBox4.TabIndex = 1;
|
||||
this.richTextBox4.Text = "";
|
||||
this.rtError.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtError.Font = new System.Drawing.Font("Calibri", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
|
||||
this.rtError.Location = new System.Drawing.Point(789, 3);
|
||||
this.rtError.Name = "rtError";
|
||||
this.rtError.Size = new System.Drawing.Size(258, 199);
|
||||
this.rtError.TabIndex = 1;
|
||||
this.rtError.Text = "test2\ntest3\nteat\nasdfjalsdf\nasdjfklasdfj\nkalsdjfalksdjfa\nsdjfklasdjfklasjdf\n";
|
||||
//
|
||||
// label1
|
||||
//
|
||||
@@ -141,10 +149,11 @@
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.Controls.Add(this.lbIP);
|
||||
this.panel1.Controls.Add(this.button7);
|
||||
this.panel1.Controls.Add(this.button6);
|
||||
this.panel1.Controls.Add(this.button5);
|
||||
this.panel1.Controls.Add(this.button17);
|
||||
this.panel1.Controls.Add(this.lbIP);
|
||||
this.panel1.Controls.Add(this.button3);
|
||||
this.panel1.Controls.Add(this.button2);
|
||||
this.panel1.Controls.Add(this.button1);
|
||||
@@ -156,22 +165,10 @@
|
||||
this.panel1.Size = new System.Drawing.Size(1050, 58);
|
||||
this.panel1.TabIndex = 7;
|
||||
//
|
||||
// lbIP
|
||||
//
|
||||
this.lbIP.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lbIP.Font = new System.Drawing.Font("Tahoma", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.lbIP.ForeColor = System.Drawing.Color.White;
|
||||
this.lbIP.Location = new System.Drawing.Point(252, 0);
|
||||
this.lbIP.Name = "lbIP";
|
||||
this.lbIP.Size = new System.Drawing.Size(398, 58);
|
||||
this.lbIP.TabIndex = 8;
|
||||
this.lbIP.Text = "000.000.000.000";
|
||||
this.lbIP.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// button7
|
||||
//
|
||||
this.button7.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.button7.Location = new System.Drawing.Point(650, 0);
|
||||
this.button7.Location = new System.Drawing.Point(570, 0);
|
||||
this.button7.Name = "button7";
|
||||
this.button7.Size = new System.Drawing.Size(80, 58);
|
||||
this.button7.TabIndex = 6;
|
||||
@@ -182,7 +179,7 @@
|
||||
// button6
|
||||
//
|
||||
this.button6.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.button6.Location = new System.Drawing.Point(730, 0);
|
||||
this.button6.Location = new System.Drawing.Point(650, 0);
|
||||
this.button6.Name = "button6";
|
||||
this.button6.Size = new System.Drawing.Size(80, 58);
|
||||
this.button6.TabIndex = 5;
|
||||
@@ -193,7 +190,7 @@
|
||||
// button5
|
||||
//
|
||||
this.button5.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.button5.Location = new System.Drawing.Point(810, 0);
|
||||
this.button5.Location = new System.Drawing.Point(730, 0);
|
||||
this.button5.Name = "button5";
|
||||
this.button5.Size = new System.Drawing.Size(80, 58);
|
||||
this.button5.TabIndex = 4;
|
||||
@@ -201,6 +198,29 @@
|
||||
this.button5.UseVisualStyleBackColor = true;
|
||||
this.button5.Click += new System.EventHandler(this.button5_Click);
|
||||
//
|
||||
// button17
|
||||
//
|
||||
this.button17.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.button17.Location = new System.Drawing.Point(810, 0);
|
||||
this.button17.Name = "button17";
|
||||
this.button17.Size = new System.Drawing.Size(80, 58);
|
||||
this.button17.TabIndex = 9;
|
||||
this.button17.Text = "Run(Bwd)";
|
||||
this.button17.UseVisualStyleBackColor = true;
|
||||
this.button17.Click += new System.EventHandler(this.button17_Click);
|
||||
//
|
||||
// lbIP
|
||||
//
|
||||
this.lbIP.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lbIP.Font = new System.Drawing.Font("Tahoma", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.lbIP.ForeColor = System.Drawing.Color.White;
|
||||
this.lbIP.Location = new System.Drawing.Point(252, 0);
|
||||
this.lbIP.Name = "lbIP";
|
||||
this.lbIP.Size = new System.Drawing.Size(638, 58);
|
||||
this.lbIP.TabIndex = 8;
|
||||
this.lbIP.Text = "000.000.000.000";
|
||||
this.lbIP.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// button3
|
||||
//
|
||||
this.button3.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -241,7 +261,7 @@
|
||||
this.button8.Name = "button8";
|
||||
this.button8.Size = new System.Drawing.Size(80, 58);
|
||||
this.button8.TabIndex = 7;
|
||||
this.button8.Text = "Run";
|
||||
this.button8.Text = "Run(Fwd)";
|
||||
this.button8.UseVisualStyleBackColor = true;
|
||||
this.button8.Click += new System.EventHandler(this.button8_Click);
|
||||
//
|
||||
@@ -269,6 +289,7 @@
|
||||
//
|
||||
// panel2
|
||||
//
|
||||
this.panel2.Controls.Add(this.lbPortName);
|
||||
this.panel2.Controls.Add(this.button15);
|
||||
this.panel2.Controls.Add(this.button14);
|
||||
this.panel2.Controls.Add(this.button11);
|
||||
@@ -283,27 +304,39 @@
|
||||
this.panel2.Size = new System.Drawing.Size(1050, 58);
|
||||
this.panel2.TabIndex = 8;
|
||||
//
|
||||
// button16
|
||||
// lbPortName
|
||||
//
|
||||
this.button16.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button16.Location = new System.Drawing.Point(0, 0);
|
||||
this.button16.Name = "button16";
|
||||
this.button16.Size = new System.Drawing.Size(162, 58);
|
||||
this.button16.TabIndex = 0;
|
||||
this.button16.Text = "백턴유지시간";
|
||||
this.button16.UseVisualStyleBackColor = true;
|
||||
this.button16.Click += new System.EventHandler(this.button16_Click);
|
||||
this.lbPortName.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.lbPortName.Font = new System.Drawing.Font("Tahoma", 15F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.lbPortName.ForeColor = System.Drawing.Color.White;
|
||||
this.lbPortName.Location = new System.Drawing.Point(607, 0);
|
||||
this.lbPortName.Name = "lbPortName";
|
||||
this.lbPortName.Size = new System.Drawing.Size(203, 58);
|
||||
this.lbPortName.TabIndex = 15;
|
||||
this.lbPortName.Text = "--";
|
||||
this.lbPortName.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// button10
|
||||
// button15
|
||||
//
|
||||
this.button10.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button10.Location = new System.Drawing.Point(162, 0);
|
||||
this.button10.Name = "button10";
|
||||
this.button10.Size = new System.Drawing.Size(162, 58);
|
||||
this.button10.TabIndex = 1;
|
||||
this.button10.Text = "GateOut Off Time";
|
||||
this.button10.UseVisualStyleBackColor = true;
|
||||
this.button10.Click += new System.EventHandler(this.button10_Click);
|
||||
this.button15.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button15.Location = new System.Drawing.Point(523, 0);
|
||||
this.button15.Name = "button15";
|
||||
this.button15.Size = new System.Drawing.Size(84, 58);
|
||||
this.button15.TabIndex = 14;
|
||||
this.button15.Text = "Mag Off";
|
||||
this.button15.UseVisualStyleBackColor = true;
|
||||
this.button15.Click += new System.EventHandler(this.button15_Click);
|
||||
//
|
||||
// button14
|
||||
//
|
||||
this.button14.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button14.Location = new System.Drawing.Point(439, 0);
|
||||
this.button14.Name = "button14";
|
||||
this.button14.Size = new System.Drawing.Size(84, 58);
|
||||
this.button14.TabIndex = 13;
|
||||
this.button14.Text = "Mag On";
|
||||
this.button14.UseVisualStyleBackColor = true;
|
||||
this.button14.Click += new System.EventHandler(this.button14_Click);
|
||||
//
|
||||
// button11
|
||||
//
|
||||
@@ -338,27 +371,47 @@
|
||||
this.button13.UseVisualStyleBackColor = true;
|
||||
this.button13.Click += new System.EventHandler(this.button13_Click);
|
||||
//
|
||||
// button14
|
||||
// button10
|
||||
//
|
||||
this.button14.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button14.Location = new System.Drawing.Point(439, 0);
|
||||
this.button14.Name = "button14";
|
||||
this.button14.Size = new System.Drawing.Size(84, 58);
|
||||
this.button14.TabIndex = 13;
|
||||
this.button14.Text = "Mag On";
|
||||
this.button14.UseVisualStyleBackColor = true;
|
||||
this.button14.Click += new System.EventHandler(this.button14_Click);
|
||||
this.button10.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button10.Location = new System.Drawing.Point(162, 0);
|
||||
this.button10.Name = "button10";
|
||||
this.button10.Size = new System.Drawing.Size(162, 58);
|
||||
this.button10.TabIndex = 1;
|
||||
this.button10.Text = "GateOut Off Time";
|
||||
this.button10.UseVisualStyleBackColor = true;
|
||||
this.button10.Click += new System.EventHandler(this.button10_Click);
|
||||
//
|
||||
// button15
|
||||
// button16
|
||||
//
|
||||
this.button15.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button15.Location = new System.Drawing.Point(523, 0);
|
||||
this.button15.Name = "button15";
|
||||
this.button15.Size = new System.Drawing.Size(84, 58);
|
||||
this.button15.TabIndex = 14;
|
||||
this.button15.Text = "Mag Off";
|
||||
this.button15.UseVisualStyleBackColor = true;
|
||||
this.button15.Click += new System.EventHandler(this.button15_Click);
|
||||
this.button16.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.button16.Location = new System.Drawing.Point(0, 0);
|
||||
this.button16.Name = "button16";
|
||||
this.button16.Size = new System.Drawing.Size(162, 58);
|
||||
this.button16.TabIndex = 0;
|
||||
this.button16.Text = "백턴유지시간";
|
||||
this.button16.UseVisualStyleBackColor = true;
|
||||
this.button16.Click += new System.EventHandler(this.button16_Click);
|
||||
//
|
||||
// rtData
|
||||
//
|
||||
this.rtData.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtData.Font = new System.Drawing.Font("Calibri", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
|
||||
this.rtData.Location = new System.Drawing.Point(789, 208);
|
||||
this.rtData.Name = "rtData";
|
||||
this.rtData.Size = new System.Drawing.Size(258, 199);
|
||||
this.rtData.TabIndex = 3;
|
||||
this.rtData.Text = "test2\ntest3\nteat\nasdfjalsdf\nasdjfklasdfj\nkalsdjfalksdjfa\nsdjfklasdjfklasjdf\n";
|
||||
//
|
||||
// rtSignal2
|
||||
//
|
||||
this.rtSignal2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.rtSignal2.Font = new System.Drawing.Font("Calibri", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(2)));
|
||||
this.rtSignal2.Location = new System.Drawing.Point(527, 208);
|
||||
this.rtSignal2.Name = "rtSignal2";
|
||||
this.rtSignal2.Size = new System.Drawing.Size(256, 199);
|
||||
this.rtSignal2.TabIndex = 4;
|
||||
this.rtSignal2.Text = "test2\ntest3\nteat\nasdfjalsdf\nasdjfklasdfj\nkalsdjfalksdjfa\nsdjfklasdjfklasjdf\n";
|
||||
//
|
||||
// fAgv
|
||||
//
|
||||
@@ -385,10 +438,10 @@
|
||||
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Timer timer1;
|
||||
private System.Windows.Forms.RichTextBox richTextBox1;
|
||||
private System.Windows.Forms.RichTextBox richTextBox2;
|
||||
private System.Windows.Forms.RichTextBox richTextBox3;
|
||||
private System.Windows.Forms.RichTextBox richTextBox4;
|
||||
private System.Windows.Forms.RichTextBox rtSystem0;
|
||||
private System.Windows.Forms.RichTextBox rtSystem1;
|
||||
private System.Windows.Forms.RichTextBox rtSignal1;
|
||||
private System.Windows.Forms.RichTextBox rtError;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Button button1;
|
||||
private System.Windows.Forms.Button button2;
|
||||
@@ -409,5 +462,9 @@
|
||||
private System.Windows.Forms.Button button11;
|
||||
private System.Windows.Forms.Button button12;
|
||||
private System.Windows.Forms.Button button13;
|
||||
private System.Windows.Forms.Label lbPortName;
|
||||
private System.Windows.Forms.Button button17;
|
||||
private System.Windows.Forms.RichTextBox rtData;
|
||||
private System.Windows.Forms.RichTextBox rtSignal2;
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,13 @@ namespace Project.ViewForm
|
||||
lbIP.Text = PUB.IP;
|
||||
label1.Text = PUB.AGV.LastSTS;
|
||||
|
||||
richTextBox1.Rtf = PUB.AGV.system0.ToRtfString();
|
||||
richTextBox2.Rtf = PUB.AGV.system1.ToRtfString();
|
||||
richTextBox3.Rtf = CombineRtfStrings(PUB.AGV.signal.ToRtfString(), PUB.AGV.data.ToRtfString());
|
||||
richTextBox4.Rtf = PUB.AGV.error.ToRtfString();
|
||||
|
||||
rtSystem0.Rtf = PUB.AGV.system0.ToRtfString();
|
||||
rtSystem1.Rtf = PUB.AGV.system1.ToRtfString();
|
||||
rtSignal1.Rtf = PUB.AGV.signal1.ToRtfString();
|
||||
rtSignal2.Rtf = PUB.AGV.signal2.ToRtfString();
|
||||
rtData.Rtf = PUB.AGV.data.ToRtfString();
|
||||
rtError.Rtf = PUB.AGV.error.ToRtfString();
|
||||
lbPortName.Text = $"AGV:{PUB.setting.Port_AGV}\nBMS:{PUB.setting.Port_BAT}";
|
||||
timer1.Start();
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ namespace Project.ViewForm
|
||||
|
||||
private void button8_Click(object sender, EventArgs e)
|
||||
{
|
||||
PUB.AGV.AGVMoveRun();
|
||||
PUB.AGV.AGVMoveRun( arDev.Narumi.eRunOpt.Forward);
|
||||
}
|
||||
|
||||
private void button9_Click(object sender, EventArgs e)
|
||||
@@ -190,5 +192,10 @@ namespace Project.ViewForm
|
||||
{
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.STP);
|
||||
}
|
||||
|
||||
private void button17_Click(object sender, EventArgs e)
|
||||
{
|
||||
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,42 +30,109 @@ namespace Project.ViewForm
|
||||
|
||||
InitializeMapCanvas();
|
||||
|
||||
//PUB.mapctl = new AGVControl.MapControl();
|
||||
//PUB.mapctl.Dock = DockStyle.Fill;
|
||||
//PUB.mapctl.Visible = true;
|
||||
//PUB.mapctl.Font = this.panel1.Font;
|
||||
//PUB.mapctl.BackColor = Color.FromArgb(32, 32, 32);
|
||||
//this.panel1.Controls.Add(PUB.mapctl);
|
||||
}
|
||||
|
||||
private void InitializeMapCanvas()
|
||||
{
|
||||
PUB._mapCanvas = new AGVNavigationCore.Controls.UnifiedAGVCanvas();
|
||||
PUB._mapCanvas.Dock = DockStyle.Fill;
|
||||
PUB._mapCanvas.ShowGrid = false;
|
||||
PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32);
|
||||
PUB._mapCanvas.ForeColor = Color.White;
|
||||
// RfidMappings 제거 - MapNode에 통합됨
|
||||
|
||||
// 이벤트 연결
|
||||
//PUB._mapCanvas.NodeAdded += OnNodeAdded;
|
||||
//PUB._mapCanvas.NodeSelected += OnNodeSelected;
|
||||
//PUB._mapCanvas.NodeMoved += OnNodeMoved;
|
||||
//PUB._mapCanvas.NodeDeleted += OnNodeDeleted;
|
||||
//PUB._mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
||||
//PUB._mapCanvas.ImageNodeDoubleClicked += OnImageNodeDoubleClicked;
|
||||
//PUB._mapCanvas.MapChanged += OnMapChanged;
|
||||
PUB._mapCanvas.NodeSelect += OnNodeSelected;;
|
||||
|
||||
// 스플리터 패널에 맵 캔버스 추가
|
||||
panel1.Controls.Add(PUB._mapCanvas);
|
||||
}
|
||||
|
||||
// 툴바 버튼 이벤트 연결
|
||||
//WireToolbarButtonEvents();
|
||||
|
||||
private void OnNodeSelected(object sender, NodeBase node, MouseEventArgs e)
|
||||
{
|
||||
if (node == null) return;
|
||||
var mapnode = node as MapNode;
|
||||
if (mapnode == null) return;
|
||||
|
||||
// [Run Mode] Left Click: AGV Operation
|
||||
if (PUB._mapCanvas.Mode == AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run && e.Button == MouseButtons.Left)
|
||||
{
|
||||
HandleRunModeClick(mapnode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Button != MouseButtons.Right) return;
|
||||
|
||||
// 도킹 가능한 노드인지 또는 작업 노드인지 확인
|
||||
if (mapnode.isDockingNode == false) return;
|
||||
|
||||
ContextMenuStrip menu = new ContextMenuStrip();
|
||||
|
||||
// PickOn
|
||||
var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)");
|
||||
pickOn.Click += (s, args) => ExecuteManualCommand(mapnode, ENIGProtocol.AGVCommandHE.PickOn);
|
||||
menu.Items.Add(pickOn);
|
||||
|
||||
// PickOff
|
||||
var pickOff = new ToolStripMenuItem("Pick Off (Move & Drop)");
|
||||
pickOff.Click += (s, args) => ExecuteManualCommand(mapnode, ENIGProtocol.AGVCommandHE.PickOff);
|
||||
menu.Items.Add(pickOff);
|
||||
|
||||
// Charge
|
||||
if (mapnode.StationType == StationType.Charger)
|
||||
{
|
||||
var charge = new ToolStripMenuItem("Charge (Move & Charge)");
|
||||
charge.Click += (s, args) => ExecuteManualCommand(mapnode, ENIGProtocol.AGVCommandHE.Charger);
|
||||
menu.Items.Add(charge);
|
||||
}
|
||||
|
||||
menu.Show(Cursor.Position);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void ExecuteManualCommand(MapNode targetNode, ENIGProtocol.AGVCommandHE cmd)
|
||||
{
|
||||
if (PUB._virtualAGV.CurrentNode == null)
|
||||
{
|
||||
MessageBox.Show("AGV의 현재 위치를 알 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
if (PUB.sm.Step == eSMStep.IDLE)
|
||||
{
|
||||
if (MessageBox.Show("현재 대기상태가 아닙니다. 강제로 실행하시겠습니까?", "Warning", MessageBoxButtons.YesNo) == DialogResult.No) return;
|
||||
}
|
||||
|
||||
if (targetNode.isDockingNode == false)
|
||||
{
|
||||
UTIL.MsgE("이동 가능한 노드가 아닙니다");
|
||||
}
|
||||
|
||||
// 1. 경로 생성
|
||||
var pathFinder = new AGVNavigationCore.PathFinding.Planning.AGVPathfinder(PUB._mapCanvas.Nodes);
|
||||
|
||||
// 현재위치에서 목표위치까지
|
||||
var result = pathFinder.FindPath(PUB._virtualAGV.CurrentNode, targetNode);
|
||||
|
||||
if (!result.Success || result.Path == null || result.Path.Count == 0)
|
||||
{
|
||||
MessageBox.Show("경로를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 상태 설정
|
||||
|
||||
// 2. 상태 설정
|
||||
if (targetNode is MapNode mapno)
|
||||
PUB.log.AddI($"[Manual Command] {cmd} to {mapno.RfidId}({targetNode.Id})");
|
||||
else
|
||||
PUB.log.AddI($"[Manual Command] {cmd} to ({targetNode.Id})");
|
||||
|
||||
// FindPathResult contains DetailedPath already.
|
||||
PUB._virtualAGV.SetPath(result);
|
||||
PUB._virtualAGV.TargetNode = targetNode as MapNode;
|
||||
|
||||
// 3. 작업 설정
|
||||
PUB.NextWorkCmd = cmd;
|
||||
|
||||
// 4. 실행
|
||||
PUB.sm.SetNewRunStep(ERunStep.GOTO); // GOTO -> Arrive -> _IN sequence execution
|
||||
}
|
||||
|
||||
|
||||
private void fAuto_Load(object sender, EventArgs e)
|
||||
{
|
||||
@@ -77,82 +144,7 @@ namespace Project.ViewForm
|
||||
PUB.AGV.DataReceive += AGV_DataReceive;
|
||||
|
||||
|
||||
//auto load
|
||||
var mapPath = new System.IO.DirectoryInfo("route");
|
||||
if (mapPath.Exists == false) mapPath.Create();
|
||||
|
||||
|
||||
//맵파일로딩
|
||||
if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.agvmap");
|
||||
System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile);
|
||||
if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName,"default.agvmap"));
|
||||
if(filePath.Exists==false) //그래도없다면 맵폴더에서 파일을 찾아본다.
|
||||
{
|
||||
var files = mapPath.GetFiles("*.agvmap");
|
||||
if (files.Any()) filePath = files[0];
|
||||
}
|
||||
|
||||
if (filePath.Exists)
|
||||
{
|
||||
var result = MapLoader.LoadMapFromFile(filePath.FullName);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
if (PUB._mapNodes == null) PUB._mapNodes = new List<MapNode>();
|
||||
else PUB._mapNodes.Clear();
|
||||
PUB._mapNodes.AddRange(result.Nodes);
|
||||
|
||||
// 맵 캔버스에 데이터 설정
|
||||
PUB._mapCanvas.Nodes = PUB._mapNodes;
|
||||
PUB._mapCanvas.MapFileName = filePath.FullName;
|
||||
|
||||
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
{
|
||||
PUB._mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
|
||||
PUB._mapCanvas.ShowGrid = result.Settings.ShowGrid;
|
||||
}
|
||||
|
||||
// 🔥 가상 AGV 초기화 (첫 노드 위치에 생성)
|
||||
if (PUB._virtualAGV == null && PUB._mapNodes.Count > 0)
|
||||
{
|
||||
var startNode = PUB._mapNodes.FirstOrDefault(n => n.IsNavigationNode());
|
||||
if (startNode != null)
|
||||
{
|
||||
PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward);
|
||||
PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward);
|
||||
|
||||
// 캔버스에 AGV 리스트 설정
|
||||
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
|
||||
PUB._mapCanvas.AGVList = agvList;
|
||||
|
||||
PUB.log.Add($"가상 AGV 생성: {startNode.NodeId} 위치");
|
||||
}
|
||||
}
|
||||
else if (PUB._virtualAGV != null)
|
||||
{
|
||||
// 기존 AGV가 있으면 캔버스에 다시 연결
|
||||
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
|
||||
PUB._mapCanvas.AGVList = agvList;
|
||||
}
|
||||
|
||||
// 맵 로드 후 자동으로 맵에 맞춤
|
||||
PUB._mapCanvas.FitToNodes();
|
||||
|
||||
PUB.log.Add($"맵 파일 로드 완료: {filePath.Name}, 노드 수: {result.Nodes.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.Add($"맵 파일 로딩 실패: {result.ErrorMessage}");
|
||||
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.Add($"맵 파일을 찾을 수 없습니다: {filePath.FullName}");
|
||||
}
|
||||
|
||||
|
||||
//var fn = string.Empty;
|
||||
//if (files.Any() == false)
|
||||
//{
|
||||
@@ -174,6 +166,9 @@ namespace Project.ViewForm
|
||||
//}
|
||||
|
||||
this.timer1.Start();
|
||||
|
||||
// Set Run Mode
|
||||
PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run;
|
||||
}
|
||||
private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e)
|
||||
{
|
||||
@@ -210,6 +205,9 @@ namespace Project.ViewForm
|
||||
PUB.sm.StepChanged -= Sm_StepChanged;
|
||||
this.ctlAuto1.ButtonClick -= CtlAuto1_ButtonClick;
|
||||
PUB.AGV.DataReceive -= AGV_DataReceive;
|
||||
|
||||
// Reset Mode to Edit
|
||||
PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Edit;
|
||||
}
|
||||
|
||||
bool tmrun = false;
|
||||
@@ -217,7 +215,15 @@ namespace Project.ViewForm
|
||||
private void fAuto_VisibleChanged(object sender, EventArgs e)
|
||||
{
|
||||
this.timer1.Enabled = this.Visible;
|
||||
if (timer1.Enabled) timer1.Start();
|
||||
if (timer1.Enabled)
|
||||
{
|
||||
timer1.Start();
|
||||
// 화면이 보일 때 Run 모드로 강제 설정
|
||||
if (PUB._mapCanvas.Mode != AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run)
|
||||
{
|
||||
PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run;
|
||||
}
|
||||
}
|
||||
else timer1.Stop();
|
||||
}
|
||||
|
||||
@@ -252,5 +258,49 @@ namespace Project.ViewForm
|
||||
|
||||
//tmrun = false;
|
||||
}
|
||||
|
||||
private void HandleRunModeClick(MapNode targetNode)
|
||||
{
|
||||
if (targetNode == null) return;
|
||||
|
||||
ENIGProtocol.AGVCommandHE targetCmd = ENIGProtocol.AGVCommandHE.Goto;
|
||||
string confirmMsg = "";
|
||||
|
||||
if (targetNode.StationType == StationType.Charger)
|
||||
{
|
||||
if (MessageBox.Show($"[{targetNode.Id}] 충전기로 이동하여 충전을 진행하시겠습니까?", "작업 확인", MessageBoxButtons.YesNo) == DialogResult.Yes)
|
||||
{
|
||||
targetCmd = ENIGProtocol.AGVCommandHE.Charger;
|
||||
ExecuteManualCommand(targetNode, targetCmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (targetNode.isDockingNode)
|
||||
{
|
||||
// Loader, Unloader, Buffer, Cleaner - Pick/Drop Selection
|
||||
ContextMenuStrip menu = new ContextMenuStrip();
|
||||
|
||||
var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)");
|
||||
pickOn.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOn);
|
||||
menu.Items.Add(pickOn);
|
||||
|
||||
var pickOff = new ToolStripMenuItem("Pick Off (Move & Drop)");
|
||||
pickOff.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOff);
|
||||
menu.Items.Add(pickOff);
|
||||
|
||||
menu.Show(Cursor.Position);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal Node
|
||||
if (MessageBox.Show($"[{targetNode.Id}] 노드로 이동하시겠습니까?", "이동 확인", MessageBoxButtons.YesNo) == DialogResult.Yes)
|
||||
{
|
||||
targetCmd = ENIGProtocol.AGVCommandHE.Goto;
|
||||
ExecuteManualCommand(targetNode, targetCmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Project.ViewForm
|
||||
{
|
||||
timer1.Stop();
|
||||
this.arLabel1.Text = PUB.BMS.Current_Level.ToString("N1") + "%";
|
||||
this.arLabel1.Sign = PUB.BMS.Current_Volt.ToString() + "v";
|
||||
this.arLabel1.Sign = $"{PUB.BMS.Current_Volt}v, {PUB.BMS.Charge_watt}w";// PUB.BMS.Current_Volt.ToString() + "v";
|
||||
this.cv1.Text = PUB.BMS.CellVoltage[0].ToString("N3") + "v";
|
||||
this.cv2.Text = PUB.BMS.CellVoltage[1].ToString("N3") + "v";
|
||||
this.cv3.Text = PUB.BMS.CellVoltage[2].ToString("N3") + "v";
|
||||
|
||||
313
Cs_HMI/Project/ViewForm/fFlag.Designer.cs
generated
313
Cs_HMI/Project/ViewForm/fFlag.Designer.cs
generated
@@ -29,34 +29,42 @@
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.listView1 = new System.Windows.Forms.ListView();
|
||||
this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.listView2 = new System.Windows.Forms.ListView();
|
||||
this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.listView3 = new System.Windows.Forms.ListView();
|
||||
this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.listView4 = new System.Windows.Forms.ListView();
|
||||
this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.columnHeader8 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||
this.dv1 = new System.Windows.Forms.DataGridView();
|
||||
this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dv2 = new System.Windows.Forms.DataGridView();
|
||||
this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dv3 = new System.Windows.Forms.DataGridView();
|
||||
this.dataGridViewTextBoxColumn3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn4 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dv4 = new System.Windows.Forms.DataGridView();
|
||||
this.dataGridViewTextBoxColumn5 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn6 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv1)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv2)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv3)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv4)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
this.tableLayoutPanel1.ColumnCount = 4;
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.listView1, 0, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.listView2, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.listView3, 2, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.listView4, 3, 0);
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 30F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 30F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.dv4, 2, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.dv3, 2, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.dv2, 1, 0);
|
||||
this.tableLayoutPanel1.Controls.Add(this.dv1, 0, 0);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
@@ -66,92 +74,167 @@
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(1050, 577);
|
||||
this.tableLayoutPanel1.TabIndex = 6;
|
||||
//
|
||||
// listView1
|
||||
//
|
||||
this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.columnHeader1,
|
||||
this.columnHeader2});
|
||||
this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.listView1.HideSelection = false;
|
||||
this.listView1.Location = new System.Drawing.Point(3, 3);
|
||||
this.listView1.Name = "listView1";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.listView1, 2);
|
||||
this.listView1.Size = new System.Drawing.Size(256, 571);
|
||||
this.listView1.TabIndex = 0;
|
||||
this.listView1.UseCompatibleStateImageBehavior = false;
|
||||
this.listView1.View = System.Windows.Forms.View.Details;
|
||||
this.listView1.SelectedIndexChanged += new System.EventHandler(this.listView1_SelectedIndexChanged);
|
||||
//
|
||||
// columnHeader1
|
||||
//
|
||||
this.columnHeader1.Width = 120;
|
||||
//
|
||||
// columnHeader2
|
||||
//
|
||||
this.columnHeader2.Width = 120;
|
||||
//
|
||||
// listView2
|
||||
//
|
||||
this.listView2.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.columnHeader3,
|
||||
this.columnHeader4});
|
||||
this.listView2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.listView2.HideSelection = false;
|
||||
this.listView2.Location = new System.Drawing.Point(265, 3);
|
||||
this.listView2.Name = "listView2";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.listView2, 2);
|
||||
this.listView2.Size = new System.Drawing.Size(256, 571);
|
||||
this.listView2.TabIndex = 0;
|
||||
this.listView2.UseCompatibleStateImageBehavior = false;
|
||||
this.listView2.View = System.Windows.Forms.View.Details;
|
||||
//
|
||||
// columnHeader3
|
||||
//
|
||||
this.columnHeader3.Width = 120;
|
||||
//
|
||||
// listView3
|
||||
//
|
||||
this.listView3.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.columnHeader5,
|
||||
this.columnHeader6});
|
||||
this.listView3.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.listView3.HideSelection = false;
|
||||
this.listView3.Location = new System.Drawing.Point(527, 3);
|
||||
this.listView3.Name = "listView3";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.listView3, 2);
|
||||
this.listView3.Size = new System.Drawing.Size(256, 571);
|
||||
this.listView3.TabIndex = 0;
|
||||
this.listView3.UseCompatibleStateImageBehavior = false;
|
||||
this.listView3.View = System.Windows.Forms.View.Details;
|
||||
//
|
||||
// columnHeader5
|
||||
//
|
||||
this.columnHeader5.Width = 120;
|
||||
//
|
||||
// listView4
|
||||
//
|
||||
this.listView4.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.columnHeader7,
|
||||
this.columnHeader8});
|
||||
this.listView4.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.listView4.HideSelection = false;
|
||||
this.listView4.Location = new System.Drawing.Point(789, 3);
|
||||
this.listView4.Name = "listView4";
|
||||
this.tableLayoutPanel1.SetRowSpan(this.listView4, 2);
|
||||
this.listView4.Size = new System.Drawing.Size(258, 571);
|
||||
this.listView4.TabIndex = 0;
|
||||
this.listView4.UseCompatibleStateImageBehavior = false;
|
||||
this.listView4.View = System.Windows.Forms.View.Details;
|
||||
//
|
||||
// columnHeader7
|
||||
//
|
||||
this.columnHeader7.Width = 120;
|
||||
//
|
||||
// timer1
|
||||
//
|
||||
this.timer1.Interval = 500;
|
||||
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
|
||||
//
|
||||
// dv1
|
||||
//
|
||||
this.dv1.AllowUserToAddRows = false;
|
||||
this.dv1.AllowUserToDeleteRows = false;
|
||||
this.dv1.AllowUserToResizeRows = false;
|
||||
this.dv1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dv1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.Column1,
|
||||
this.Column2});
|
||||
dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle4.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle4.Font = new System.Drawing.Font("Calibri", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
dataGridViewCellStyle4.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle4.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle4.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.dv1.DefaultCellStyle = dataGridViewCellStyle4;
|
||||
this.dv1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dv1.Location = new System.Drawing.Point(3, 3);
|
||||
this.dv1.Name = "dv1";
|
||||
this.dv1.ReadOnly = true;
|
||||
this.dv1.RowHeadersVisible = false;
|
||||
this.tableLayoutPanel1.SetRowSpan(this.dv1, 2);
|
||||
this.dv1.RowTemplate.Height = 23;
|
||||
this.dv1.Size = new System.Drawing.Size(309, 571);
|
||||
this.dv1.TabIndex = 1;
|
||||
//
|
||||
// Column1
|
||||
//
|
||||
this.Column1.HeaderText = "Column1";
|
||||
this.Column1.Name = "Column1";
|
||||
this.Column1.ReadOnly = true;
|
||||
//
|
||||
// Column2
|
||||
//
|
||||
this.Column2.HeaderText = "Column2";
|
||||
this.Column2.Name = "Column2";
|
||||
this.Column2.ReadOnly = true;
|
||||
//
|
||||
// dv2
|
||||
//
|
||||
this.dv2.AllowUserToAddRows = false;
|
||||
this.dv2.AllowUserToDeleteRows = false;
|
||||
this.dv2.AllowUserToResizeRows = false;
|
||||
this.dv2.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dv2.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.dataGridViewTextBoxColumn1,
|
||||
this.dataGridViewTextBoxColumn2});
|
||||
dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle3.Font = new System.Drawing.Font("Calibri", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.dv2.DefaultCellStyle = dataGridViewCellStyle3;
|
||||
this.dv2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dv2.Location = new System.Drawing.Point(318, 3);
|
||||
this.dv2.Name = "dv2";
|
||||
this.dv2.ReadOnly = true;
|
||||
this.dv2.RowHeadersVisible = false;
|
||||
this.tableLayoutPanel1.SetRowSpan(this.dv2, 2);
|
||||
this.dv2.RowTemplate.Height = 23;
|
||||
this.dv2.Size = new System.Drawing.Size(309, 571);
|
||||
this.dv2.TabIndex = 2;
|
||||
//
|
||||
// dataGridViewTextBoxColumn1
|
||||
//
|
||||
this.dataGridViewTextBoxColumn1.HeaderText = "Column1";
|
||||
this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1";
|
||||
this.dataGridViewTextBoxColumn1.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn2
|
||||
//
|
||||
this.dataGridViewTextBoxColumn2.HeaderText = "Column2";
|
||||
this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2";
|
||||
this.dataGridViewTextBoxColumn2.ReadOnly = true;
|
||||
//
|
||||
// dv3
|
||||
//
|
||||
this.dv3.AllowUserToAddRows = false;
|
||||
this.dv3.AllowUserToDeleteRows = false;
|
||||
this.dv3.AllowUserToResizeRows = false;
|
||||
this.dv3.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dv3.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.dataGridViewTextBoxColumn3,
|
||||
this.dataGridViewTextBoxColumn4});
|
||||
this.tableLayoutPanel1.SetColumnSpan(this.dv3, 2);
|
||||
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle2.Font = new System.Drawing.Font("Calibri", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.dv3.DefaultCellStyle = dataGridViewCellStyle2;
|
||||
this.dv3.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dv3.Location = new System.Drawing.Point(633, 3);
|
||||
this.dv3.Name = "dv3";
|
||||
this.dv3.ReadOnly = true;
|
||||
this.dv3.RowHeadersVisible = false;
|
||||
this.dv3.RowTemplate.Height = 23;
|
||||
this.dv3.Size = new System.Drawing.Size(414, 282);
|
||||
this.dv3.TabIndex = 3;
|
||||
//
|
||||
// dataGridViewTextBoxColumn3
|
||||
//
|
||||
this.dataGridViewTextBoxColumn3.HeaderText = "Column1";
|
||||
this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3";
|
||||
this.dataGridViewTextBoxColumn3.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn4
|
||||
//
|
||||
this.dataGridViewTextBoxColumn4.HeaderText = "Column2";
|
||||
this.dataGridViewTextBoxColumn4.Name = "dataGridViewTextBoxColumn4";
|
||||
this.dataGridViewTextBoxColumn4.ReadOnly = true;
|
||||
//
|
||||
// dv4
|
||||
//
|
||||
this.dv4.AllowUserToAddRows = false;
|
||||
this.dv4.AllowUserToDeleteRows = false;
|
||||
this.dv4.AllowUserToResizeRows = false;
|
||||
this.dv4.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dv4.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.dataGridViewTextBoxColumn5,
|
||||
this.dataGridViewTextBoxColumn6});
|
||||
this.tableLayoutPanel1.SetColumnSpan(this.dv4, 2);
|
||||
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle1.Font = new System.Drawing.Font("Calibri", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.dv4.DefaultCellStyle = dataGridViewCellStyle1;
|
||||
this.dv4.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dv4.Location = new System.Drawing.Point(633, 291);
|
||||
this.dv4.Name = "dv4";
|
||||
this.dv4.ReadOnly = true;
|
||||
this.dv4.RowHeadersVisible = false;
|
||||
this.dv4.RowTemplate.Height = 23;
|
||||
this.dv4.Size = new System.Drawing.Size(414, 283);
|
||||
this.dv4.TabIndex = 4;
|
||||
//
|
||||
// dataGridViewTextBoxColumn5
|
||||
//
|
||||
this.dataGridViewTextBoxColumn5.HeaderText = "Column1";
|
||||
this.dataGridViewTextBoxColumn5.Name = "dataGridViewTextBoxColumn5";
|
||||
this.dataGridViewTextBoxColumn5.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn6
|
||||
//
|
||||
this.dataGridViewTextBoxColumn6.HeaderText = "Column2";
|
||||
this.dataGridViewTextBoxColumn6.Name = "dataGridViewTextBoxColumn6";
|
||||
this.dataGridViewTextBoxColumn6.ReadOnly = true;
|
||||
//
|
||||
// fFlag
|
||||
//
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
@@ -165,6 +248,10 @@
|
||||
this.Load += new System.EventHandler(this.fFlag_Load);
|
||||
this.VisibleChanged += new System.EventHandler(this.fFlag_VisibleChanged);
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv1)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv2)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv3)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dv4)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
@@ -173,17 +260,17 @@
|
||||
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.Timer timer1;
|
||||
private System.Windows.Forms.ListView listView1;
|
||||
private System.Windows.Forms.ListView listView2;
|
||||
private System.Windows.Forms.ListView listView3;
|
||||
private System.Windows.Forms.ListView listView4;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader2;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader3;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader4;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader5;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader6;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader7;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader8;
|
||||
private System.Windows.Forms.DataGridView dv4;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn5;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn6;
|
||||
private System.Windows.Forms.DataGridView dv3;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn3;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn4;
|
||||
private System.Windows.Forms.DataGridView dv2;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2;
|
||||
private System.Windows.Forms.DataGridView dv1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn Column1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn Column2;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Project.ViewForm
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
|
||||
this.FormClosed += FIO_FormClosed;
|
||||
}
|
||||
|
||||
@@ -30,16 +30,18 @@ namespace Project.ViewForm
|
||||
|
||||
void MakeControl()
|
||||
{
|
||||
ListView[] lvs = new ListView[] { listView1, listView2, listView3, listView4 };
|
||||
foreach (var lv in lvs)
|
||||
DataGridView[] dvs = new DataGridView[] { dv1, dv2, dv3, dv4 };
|
||||
|
||||
foreach(var lv in dvs)
|
||||
{
|
||||
lv.Columns.Clear();
|
||||
lv.Columns.Add("Idx");
|
||||
lv.Columns.Add("Title");
|
||||
lv.Columns.Add("Value");
|
||||
lv.Columns.Add("idx", "*");
|
||||
lv.Columns.Add("Title", "Title");
|
||||
lv.Columns.Add("Value", "Value");
|
||||
lv.Columns[0].Width = 25;
|
||||
lv.Columns[1].Width = 150;
|
||||
lv.Columns[2].Width = 100;
|
||||
lv.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
|
||||
}
|
||||
Array valuelist;
|
||||
|
||||
@@ -47,38 +49,33 @@ namespace Project.ViewForm
|
||||
foreach (var item in valuelist)
|
||||
{
|
||||
var v = (COMM.eVarBool)item;
|
||||
var lv = listView1.Items.Add($"{(int)v}");
|
||||
lv.SubItems.Add($"{item}");
|
||||
lv.SubItems.Add("--");
|
||||
dv1.Rows.Add($"{(int)v}", item, "--");
|
||||
}
|
||||
|
||||
valuelist = Enum.GetValues(typeof(COMM.eVarInt32));
|
||||
foreach (var item in valuelist)
|
||||
{
|
||||
var v = (COMM.eVarInt32)item;
|
||||
var lv = listView2.Items.Add($"{(int)v}");
|
||||
lv.SubItems.Add($"{item}");
|
||||
lv.SubItems.Add("--");
|
||||
dv2.Rows.Add($"{(int)v}", item, "--");
|
||||
}
|
||||
|
||||
valuelist = Enum.GetValues(typeof(COMM.eVarString));
|
||||
foreach (var item in valuelist)
|
||||
{
|
||||
var v = (COMM.eVarString)item;
|
||||
var lv = listView3.Items.Add($"{(int)v}");
|
||||
lv.SubItems.Add($"{item}");
|
||||
lv.SubItems.Add("--");
|
||||
dv3.Rows.Add($"{(int)v}", item, "--");
|
||||
}
|
||||
|
||||
valuelist = Enum.GetValues(typeof(COMM.eVarTime));
|
||||
foreach (var item in valuelist)
|
||||
{
|
||||
var v = (COMM.eVarString)item;
|
||||
var lv = listView4.Items.Add($"{(int)v}");
|
||||
lv.SubItems.Add($"{item}");
|
||||
lv.SubItems.Add("--");
|
||||
dv4.Rows.Add($"{(int)v}", item, "--");
|
||||
}
|
||||
|
||||
foreach (var dv in dvs)
|
||||
dv.AutoResizeColumns();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -91,42 +88,32 @@ namespace Project.ViewForm
|
||||
private void timer1_Tick(object sender, EventArgs e)
|
||||
{
|
||||
timer1.Stop();
|
||||
listView1.SuspendLayout();
|
||||
foreach (ListViewItem item in listView1.Items)
|
||||
foreach(DataGridViewRow item in this.dv1.Rows)
|
||||
{
|
||||
var idx = int.Parse(item.SubItems[0].Text);
|
||||
var idx = int.Parse(item.Cells["idx"].Value.ToString());
|
||||
var v = VAR.BOOL.Get(idx);
|
||||
var desc = VAR.BOOL.GetCodeDesc(idx);
|
||||
item.SubItems[2].Text = v ? "O" : "X";
|
||||
item.Cells["value"].Value = v ? "O" : "X";
|
||||
}
|
||||
listView1.ResumeLayout();
|
||||
listView2.SuspendLayout();
|
||||
foreach (ListViewItem item in listView2.Items)
|
||||
|
||||
foreach (DataGridViewRow item in this.dv2.Rows)
|
||||
{
|
||||
var idx = int.Parse(item.SubItems[0].Text);
|
||||
var idx = int.Parse(item.Cells["idx"].Value.ToString());
|
||||
var v = VAR.I32.Get(idx);
|
||||
var desc = VAR.I32.GetCodeDesc(idx);
|
||||
item.SubItems[2].Text = v.ToString();
|
||||
item.Cells["value"].Value = v.ToString();
|
||||
}
|
||||
listView2.ResumeLayout();
|
||||
listView3.SuspendLayout();
|
||||
foreach (ListViewItem item in listView3.Items)
|
||||
|
||||
foreach (DataGridViewRow item in this.dv3.Rows)
|
||||
{
|
||||
var idx = int.Parse(item.SubItems[0].Text);
|
||||
var idx = int.Parse(item.Cells["idx"].Value.ToString());
|
||||
var v = VAR.STR.Get(idx);
|
||||
var desc = VAR.STR.GetCodeDesc(idx);
|
||||
item.SubItems[2].Text = v;
|
||||
item.Cells["value"].Value = v;
|
||||
}
|
||||
listView3.ResumeLayout();
|
||||
listView4.SuspendLayout();
|
||||
foreach (ListViewItem item in listView4.Items)
|
||||
foreach (DataGridViewRow item in this.dv4.Rows)
|
||||
{
|
||||
var idx = int.Parse(item.SubItems[0].Text);
|
||||
var idx = int.Parse(item.Cells["idx"].Value.ToString());
|
||||
var v = VAR.TIME.Get(idx);
|
||||
var desc = VAR.TIME.GetCodeDesc(idx);
|
||||
item.SubItems[2].Text = v.ToString("HH:mm:ss.fff");
|
||||
item.Cells["value"].Value = v.ToString("HH:mm:ss.fff");
|
||||
}
|
||||
listView4.ResumeLayout();
|
||||
timer1.Start();
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,30 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="dataGridViewTextBoxColumn5.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridViewTextBoxColumn6.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridViewTextBoxColumn3.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridViewTextBoxColumn4.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridViewTextBoxColumn1.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridViewTextBoxColumn2.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Column1.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="Column2.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="timer1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
|
||||
588
Cs_HMI/Project/ViewForm/fManual.Designer.cs
generated
588
Cs_HMI/Project/ViewForm/fManual.Designer.cs
generated
@@ -41,45 +41,45 @@
|
||||
this.btErrReset = new System.Windows.Forms.Button();
|
||||
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.btRight180 = new System.Windows.Forms.Button();
|
||||
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.btBack180 = new System.Windows.Forms.Button();
|
||||
this.btLeft180 = new System.Windows.Forms.Button();
|
||||
this.btRight180 = new System.Windows.Forms.Button();
|
||||
this.button2 = new System.Windows.Forms.Button();
|
||||
this.button1 = new System.Windows.Forms.Button();
|
||||
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
||||
this.radpbs2 = new AGVControl.MyRadioButton();
|
||||
this.panel9 = new System.Windows.Forms.Panel();
|
||||
this.radpbs1 = new AGVControl.MyRadioButton();
|
||||
this.panel3 = new System.Windows.Forms.Panel();
|
||||
this.radpbs0 = new AGVControl.MyRadioButton();
|
||||
this.grpSpeed = new System.Windows.Forms.GroupBox();
|
||||
this.radspdh = new AGVControl.MyRadioButton();
|
||||
this.panel8 = new System.Windows.Forms.Panel();
|
||||
this.radspdm = new AGVControl.MyRadioButton();
|
||||
this.panel4 = new System.Windows.Forms.Panel();
|
||||
this.radspdl = new AGVControl.MyRadioButton();
|
||||
this.grpBunki = new System.Windows.Forms.GroupBox();
|
||||
this.radright = new AGVControl.MyRadioButton();
|
||||
this.panel7 = new System.Windows.Forms.Panel();
|
||||
this.radstrai = new AGVControl.MyRadioButton();
|
||||
this.panel5 = new System.Windows.Forms.Panel();
|
||||
this.radleft = new AGVControl.MyRadioButton();
|
||||
this.groupBox2 = new System.Windows.Forms.GroupBox();
|
||||
this.arLabel1 = new arCtl.arLabel();
|
||||
this.panel12 = new System.Windows.Forms.Panel();
|
||||
this.panel6 = new System.Windows.Forms.Panel();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.radpbs2 = new AGVControl.MyRadioButton();
|
||||
this.radpbs1 = new AGVControl.MyRadioButton();
|
||||
this.radpbs0 = new AGVControl.MyRadioButton();
|
||||
this.radspdh = new AGVControl.MyRadioButton();
|
||||
this.radspdm = new AGVControl.MyRadioButton();
|
||||
this.radspdl = new AGVControl.MyRadioButton();
|
||||
this.radright = new AGVControl.MyRadioButton();
|
||||
this.radstrai = new AGVControl.MyRadioButton();
|
||||
this.radleft = new AGVControl.MyRadioButton();
|
||||
this.radbackward = new AGVControl.MyRadioButton();
|
||||
this.panel6 = new System.Windows.Forms.Panel();
|
||||
this.radforward = new AGVControl.MyRadioButton();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.guideSensor1 = new Narumi.UC.GuideSensor();
|
||||
this.btBack180 = new System.Windows.Forms.Button();
|
||||
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.button2 = new System.Windows.Forms.Button();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.panel2.SuspendLayout();
|
||||
this.tableLayoutPanel2.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.grpSpeed.SuspendLayout();
|
||||
this.grpBunki.SuspendLayout();
|
||||
this.groupBox2.SuspendLayout();
|
||||
this.tableLayoutPanel2.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
@@ -124,7 +124,7 @@
|
||||
this.btStart.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
|
||||
this.btStart.GradientRepeatBG = false;
|
||||
this.btStart.isButton = true;
|
||||
this.btStart.Location = new System.Drawing.Point(204, 201);
|
||||
this.btStart.Location = new System.Drawing.Point(204, 200);
|
||||
this.btStart.MouseDownColor = System.Drawing.Color.Yellow;
|
||||
this.btStart.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.btStart.msg = new string[] {
|
||||
@@ -146,7 +146,7 @@
|
||||
this.btStart.SignAlign = System.Drawing.ContentAlignment.BottomRight;
|
||||
this.btStart.SignColor = System.Drawing.Color.Yellow;
|
||||
this.btStart.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
|
||||
this.btStart.Size = new System.Drawing.Size(195, 192);
|
||||
this.btStart.Size = new System.Drawing.Size(195, 191);
|
||||
this.btStart.TabIndex = 6;
|
||||
this.btStart.Text = "RUN";
|
||||
this.btStart.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
@@ -171,7 +171,7 @@
|
||||
this.btRight.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
|
||||
this.btRight.GradientRepeatBG = false;
|
||||
this.btRight.isButton = true;
|
||||
this.btRight.Location = new System.Drawing.Point(405, 201);
|
||||
this.btRight.Location = new System.Drawing.Point(405, 200);
|
||||
this.btRight.MouseDownColor = System.Drawing.Color.Yellow;
|
||||
this.btRight.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.btRight.msg = null;
|
||||
@@ -191,7 +191,7 @@
|
||||
this.btRight.SignAlign = System.Drawing.ContentAlignment.BottomRight;
|
||||
this.btRight.SignColor = System.Drawing.Color.Yellow;
|
||||
this.btRight.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
|
||||
this.btRight.Size = new System.Drawing.Size(196, 192);
|
||||
this.btRight.Size = new System.Drawing.Size(196, 191);
|
||||
this.btRight.TabIndex = 0;
|
||||
this.btRight.Text = "좌회전";
|
||||
this.btRight.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
@@ -237,7 +237,7 @@
|
||||
this.btBack.SignAlign = System.Drawing.ContentAlignment.BottomRight;
|
||||
this.btBack.SignColor = System.Drawing.Color.Yellow;
|
||||
this.btBack.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
|
||||
this.btBack.Size = new System.Drawing.Size(195, 192);
|
||||
this.btBack.Size = new System.Drawing.Size(195, 191);
|
||||
this.btBack.TabIndex = 0;
|
||||
this.btBack.Text = "후진";
|
||||
this.btBack.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
@@ -262,7 +262,7 @@
|
||||
this.btForward.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
|
||||
this.btForward.GradientRepeatBG = false;
|
||||
this.btForward.isButton = true;
|
||||
this.btForward.Location = new System.Drawing.Point(204, 399);
|
||||
this.btForward.Location = new System.Drawing.Point(204, 397);
|
||||
this.btForward.MouseDownColor = System.Drawing.Color.Yellow;
|
||||
this.btForward.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.btForward.msg = null;
|
||||
@@ -282,7 +282,7 @@
|
||||
this.btForward.SignAlign = System.Drawing.ContentAlignment.BottomRight;
|
||||
this.btForward.SignColor = System.Drawing.Color.Yellow;
|
||||
this.btForward.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
|
||||
this.btForward.Size = new System.Drawing.Size(195, 192);
|
||||
this.btForward.Size = new System.Drawing.Size(195, 194);
|
||||
this.btForward.TabIndex = 0;
|
||||
this.btForward.Text = "전진";
|
||||
this.btForward.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
@@ -307,7 +307,7 @@
|
||||
this.btLeft.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
|
||||
this.btLeft.GradientRepeatBG = false;
|
||||
this.btLeft.isButton = true;
|
||||
this.btLeft.Location = new System.Drawing.Point(3, 201);
|
||||
this.btLeft.Location = new System.Drawing.Point(3, 200);
|
||||
this.btLeft.MouseDownColor = System.Drawing.Color.Yellow;
|
||||
this.btLeft.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.btLeft.msg = null;
|
||||
@@ -327,7 +327,7 @@
|
||||
this.btLeft.SignAlign = System.Drawing.ContentAlignment.BottomRight;
|
||||
this.btLeft.SignColor = System.Drawing.Color.Yellow;
|
||||
this.btLeft.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
|
||||
this.btLeft.Size = new System.Drawing.Size(195, 192);
|
||||
this.btLeft.Size = new System.Drawing.Size(195, 191);
|
||||
this.btLeft.TabIndex = 0;
|
||||
this.btLeft.Text = "우회전";
|
||||
this.btLeft.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
@@ -341,7 +341,7 @@
|
||||
this.btMarkStop.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.btMarkStop.Location = new System.Drawing.Point(3, 3);
|
||||
this.btMarkStop.Name = "btMarkStop";
|
||||
this.btMarkStop.Size = new System.Drawing.Size(195, 192);
|
||||
this.btMarkStop.Size = new System.Drawing.Size(195, 191);
|
||||
this.btMarkStop.TabIndex = 7;
|
||||
this.btMarkStop.Text = "마크정지";
|
||||
this.btMarkStop.UseVisualStyleBackColor = true;
|
||||
@@ -351,9 +351,9 @@
|
||||
//
|
||||
this.btchargeOff.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.btchargeOff.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.btchargeOff.Location = new System.Drawing.Point(405, 399);
|
||||
this.btchargeOff.Location = new System.Drawing.Point(405, 397);
|
||||
this.btchargeOff.Name = "btchargeOff";
|
||||
this.btchargeOff.Size = new System.Drawing.Size(196, 192);
|
||||
this.btchargeOff.Size = new System.Drawing.Size(196, 194);
|
||||
this.btchargeOff.TabIndex = 8;
|
||||
this.btchargeOff.Text = "충전해제";
|
||||
this.btchargeOff.UseVisualStyleBackColor = true;
|
||||
@@ -363,9 +363,9 @@
|
||||
//
|
||||
this.btChargeOn.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.btChargeOn.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.btChargeOn.Location = new System.Drawing.Point(3, 399);
|
||||
this.btChargeOn.Location = new System.Drawing.Point(3, 397);
|
||||
this.btChargeOn.Name = "btChargeOn";
|
||||
this.btChargeOn.Size = new System.Drawing.Size(195, 192);
|
||||
this.btChargeOn.Size = new System.Drawing.Size(195, 194);
|
||||
this.btChargeOn.TabIndex = 9;
|
||||
this.btChargeOn.Text = "충전";
|
||||
this.btChargeOn.UseVisualStyleBackColor = true;
|
||||
@@ -377,7 +377,7 @@
|
||||
this.btErrReset.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.btErrReset.Location = new System.Drawing.Point(405, 3);
|
||||
this.btErrReset.Name = "btErrReset";
|
||||
this.btErrReset.Size = new System.Drawing.Size(196, 192);
|
||||
this.btErrReset.Size = new System.Drawing.Size(196, 191);
|
||||
this.btErrReset.TabIndex = 10;
|
||||
this.btErrReset.Text = "오류소거";
|
||||
this.btErrReset.UseVisualStyleBackColor = true;
|
||||
@@ -403,19 +403,38 @@
|
||||
this.panel2.TabIndex = 9;
|
||||
this.panel2.Paint += new System.Windows.Forms.PaintEventHandler(this.panel2_Paint);
|
||||
//
|
||||
// btRight180
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
this.btRight180.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.btRight180.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.btRight180.Font = new System.Drawing.Font("맑은 고딕", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.btRight180.ForeColor = System.Drawing.Color.Black;
|
||||
this.btRight180.Location = new System.Drawing.Point(3, 62);
|
||||
this.btRight180.Name = "btRight180";
|
||||
this.btRight180.Size = new System.Drawing.Size(196, 53);
|
||||
this.btRight180.TabIndex = 6;
|
||||
this.btRight180.Text = "180도 우회전";
|
||||
this.btRight180.UseVisualStyleBackColor = false;
|
||||
this.btRight180.Click += new System.EventHandler(this.btRight180_Click);
|
||||
this.tableLayoutPanel2.ColumnCount = 2;
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.Controls.Add(this.btBack180, 1, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.btLeft180, 0, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.btRight180, 0, 1);
|
||||
this.tableLayoutPanel2.Controls.Add(this.button2, 1, 1);
|
||||
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 438);
|
||||
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
this.tableLayoutPanel2.RowCount = 2;
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.Size = new System.Drawing.Size(404, 118);
|
||||
this.tableLayoutPanel2.TabIndex = 12;
|
||||
//
|
||||
// btBack180
|
||||
//
|
||||
this.btBack180.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.btBack180.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.btBack180.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.btBack180.Font = new System.Drawing.Font("맑은 고딕", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.btBack180.ForeColor = System.Drawing.Color.Black;
|
||||
this.btBack180.Location = new System.Drawing.Point(205, 3);
|
||||
this.btBack180.Name = "btBack180";
|
||||
this.btBack180.Size = new System.Drawing.Size(196, 53);
|
||||
this.btBack180.TabIndex = 15;
|
||||
this.btBack180.Text = "자석 (ON)";
|
||||
this.btBack180.UseVisualStyleBackColor = false;
|
||||
this.btBack180.Click += new System.EventHandler(this.btBack180_Click);
|
||||
//
|
||||
// btLeft180
|
||||
//
|
||||
@@ -431,6 +450,35 @@
|
||||
this.btLeft180.UseVisualStyleBackColor = false;
|
||||
this.btLeft180.Click += new System.EventHandler(this.btLeft180_Click);
|
||||
//
|
||||
// btRight180
|
||||
//
|
||||
this.btRight180.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.btRight180.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.btRight180.Font = new System.Drawing.Font("맑은 고딕", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.btRight180.ForeColor = System.Drawing.Color.Black;
|
||||
this.btRight180.Location = new System.Drawing.Point(3, 62);
|
||||
this.btRight180.Name = "btRight180";
|
||||
this.btRight180.Size = new System.Drawing.Size(196, 53);
|
||||
this.btRight180.TabIndex = 6;
|
||||
this.btRight180.Text = "180도 우회전";
|
||||
this.btRight180.UseVisualStyleBackColor = false;
|
||||
this.btRight180.Click += new System.EventHandler(this.btRight180_Click);
|
||||
//
|
||||
// button2
|
||||
//
|
||||
this.button2.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.button2.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.button2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.button2.Font = new System.Drawing.Font("맑은 고딕", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.button2.ForeColor = System.Drawing.Color.Black;
|
||||
this.button2.Location = new System.Drawing.Point(205, 62);
|
||||
this.button2.Name = "button2";
|
||||
this.button2.Size = new System.Drawing.Size(196, 53);
|
||||
this.button2.TabIndex = 15;
|
||||
this.button2.Text = "자석(OFF)";
|
||||
this.button2.UseVisualStyleBackColor = false;
|
||||
this.button2.Click += new System.EventHandler(this.button2_Click);
|
||||
//
|
||||
// button1
|
||||
//
|
||||
this.button1.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
@@ -459,6 +507,25 @@
|
||||
this.groupBox1.TabStop = false;
|
||||
this.groupBox1.Text = "근접센서(PBS)";
|
||||
//
|
||||
// radpbs2
|
||||
//
|
||||
this.radpbs2.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radpbs2.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radpbs2.BorderRadius = 7;
|
||||
this.radpbs2.BorderSize = 2;
|
||||
this.radpbs2.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radpbs2.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radpbs2.CheckWidth = 30;
|
||||
this.radpbs2.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radpbs2.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radpbs2.Location = new System.Drawing.Point(278, 19);
|
||||
this.radpbs2.Name = "radpbs2";
|
||||
this.radpbs2.Size = new System.Drawing.Size(119, 66);
|
||||
this.radpbs2.TabIndex = 10;
|
||||
this.radpbs2.Text = "On(2)";
|
||||
this.radpbs2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radpbs2.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel9
|
||||
//
|
||||
this.panel9.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -467,6 +534,28 @@
|
||||
this.panel9.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel9.TabIndex = 12;
|
||||
//
|
||||
// radpbs1
|
||||
//
|
||||
this.radpbs1.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radpbs1.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radpbs1.BorderRadius = 7;
|
||||
this.radpbs1.BorderSize = 2;
|
||||
this.radpbs1.Checked = true;
|
||||
this.radpbs1.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radpbs1.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radpbs1.CheckWidth = 30;
|
||||
this.radpbs1.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radpbs1.Font = new System.Drawing.Font("맑은 고딕", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radpbs1.ForeColor = System.Drawing.Color.Blue;
|
||||
this.radpbs1.Location = new System.Drawing.Point(144, 19);
|
||||
this.radpbs1.Name = "radpbs1";
|
||||
this.radpbs1.Size = new System.Drawing.Size(119, 66);
|
||||
this.radpbs1.TabIndex = 9;
|
||||
this.radpbs1.TabStop = true;
|
||||
this.radpbs1.Text = "On(1)";
|
||||
this.radpbs1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radpbs1.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel3
|
||||
//
|
||||
this.panel3.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -475,6 +564,25 @@
|
||||
this.panel3.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel3.TabIndex = 11;
|
||||
//
|
||||
// radpbs0
|
||||
//
|
||||
this.radpbs0.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radpbs0.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radpbs0.BorderRadius = 7;
|
||||
this.radpbs0.BorderSize = 2;
|
||||
this.radpbs0.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radpbs0.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radpbs0.CheckWidth = 30;
|
||||
this.radpbs0.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radpbs0.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radpbs0.Location = new System.Drawing.Point(10, 19);
|
||||
this.radpbs0.Name = "radpbs0";
|
||||
this.radpbs0.Size = new System.Drawing.Size(119, 66);
|
||||
this.radpbs0.TabIndex = 8;
|
||||
this.radpbs0.Text = "Off(0)";
|
||||
this.radpbs0.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radpbs0.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// grpSpeed
|
||||
//
|
||||
this.grpSpeed.Controls.Add(this.radspdh);
|
||||
@@ -491,6 +599,25 @@
|
||||
this.grpSpeed.TabStop = false;
|
||||
this.grpSpeed.Text = "속도";
|
||||
//
|
||||
// radspdh
|
||||
//
|
||||
this.radspdh.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radspdh.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radspdh.BorderRadius = 7;
|
||||
this.radspdh.BorderSize = 2;
|
||||
this.radspdh.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radspdh.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radspdh.CheckWidth = 30;
|
||||
this.radspdh.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radspdh.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radspdh.Location = new System.Drawing.Point(278, 19);
|
||||
this.radspdh.Name = "radspdh";
|
||||
this.radspdh.Size = new System.Drawing.Size(119, 66);
|
||||
this.radspdh.TabIndex = 2;
|
||||
this.radspdh.Text = "고속";
|
||||
this.radspdh.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radspdh.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel8
|
||||
//
|
||||
this.panel8.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -499,6 +626,28 @@
|
||||
this.panel8.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel8.TabIndex = 13;
|
||||
//
|
||||
// radspdm
|
||||
//
|
||||
this.radspdm.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radspdm.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radspdm.BorderRadius = 7;
|
||||
this.radspdm.BorderSize = 2;
|
||||
this.radspdm.Checked = true;
|
||||
this.radspdm.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radspdm.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radspdm.CheckWidth = 30;
|
||||
this.radspdm.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radspdm.Font = new System.Drawing.Font("맑은 고딕", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radspdm.ForeColor = System.Drawing.Color.Blue;
|
||||
this.radspdm.Location = new System.Drawing.Point(144, 19);
|
||||
this.radspdm.Name = "radspdm";
|
||||
this.radspdm.Size = new System.Drawing.Size(119, 66);
|
||||
this.radspdm.TabIndex = 1;
|
||||
this.radspdm.TabStop = true;
|
||||
this.radspdm.Text = "중속";
|
||||
this.radspdm.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radspdm.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel4
|
||||
//
|
||||
this.panel4.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -507,6 +656,25 @@
|
||||
this.panel4.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel4.TabIndex = 12;
|
||||
//
|
||||
// radspdl
|
||||
//
|
||||
this.radspdl.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radspdl.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radspdl.BorderRadius = 7;
|
||||
this.radspdl.BorderSize = 2;
|
||||
this.radspdl.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radspdl.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radspdl.CheckWidth = 30;
|
||||
this.radspdl.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radspdl.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radspdl.Location = new System.Drawing.Point(10, 19);
|
||||
this.radspdl.Name = "radspdl";
|
||||
this.radspdl.Size = new System.Drawing.Size(119, 66);
|
||||
this.radspdl.TabIndex = 0;
|
||||
this.radspdl.Text = "저속";
|
||||
this.radspdl.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radspdl.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// grpBunki
|
||||
//
|
||||
this.grpBunki.Controls.Add(this.radright);
|
||||
@@ -523,6 +691,25 @@
|
||||
this.grpBunki.TabStop = false;
|
||||
this.grpBunki.Text = "분기";
|
||||
//
|
||||
// radright
|
||||
//
|
||||
this.radright.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radright.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radright.BorderRadius = 7;
|
||||
this.radright.BorderSize = 2;
|
||||
this.radright.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radright.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radright.CheckWidth = 30;
|
||||
this.radright.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radright.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radright.Location = new System.Drawing.Point(278, 19);
|
||||
this.radright.Name = "radright";
|
||||
this.radright.Size = new System.Drawing.Size(119, 66);
|
||||
this.radright.TabIndex = 4;
|
||||
this.radright.Text = "우측";
|
||||
this.radright.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radright.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel7
|
||||
//
|
||||
this.panel7.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -531,6 +718,28 @@
|
||||
this.panel7.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel7.TabIndex = 13;
|
||||
//
|
||||
// radstrai
|
||||
//
|
||||
this.radstrai.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radstrai.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radstrai.BorderRadius = 7;
|
||||
this.radstrai.BorderSize = 2;
|
||||
this.radstrai.Checked = true;
|
||||
this.radstrai.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radstrai.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radstrai.CheckWidth = 30;
|
||||
this.radstrai.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radstrai.Font = new System.Drawing.Font("맑은 고딕", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radstrai.ForeColor = System.Drawing.Color.Blue;
|
||||
this.radstrai.Location = new System.Drawing.Point(144, 19);
|
||||
this.radstrai.Name = "radstrai";
|
||||
this.radstrai.Size = new System.Drawing.Size(119, 66);
|
||||
this.radstrai.TabIndex = 3;
|
||||
this.radstrai.TabStop = true;
|
||||
this.radstrai.Text = "직진";
|
||||
this.radstrai.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radstrai.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel5
|
||||
//
|
||||
this.panel5.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
@@ -539,6 +748,25 @@
|
||||
this.panel5.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel5.TabIndex = 12;
|
||||
//
|
||||
// radleft
|
||||
//
|
||||
this.radleft.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radleft.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radleft.BorderRadius = 7;
|
||||
this.radleft.BorderSize = 2;
|
||||
this.radleft.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radleft.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radleft.CheckWidth = 30;
|
||||
this.radleft.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radleft.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radleft.Location = new System.Drawing.Point(10, 19);
|
||||
this.radleft.Name = "radleft";
|
||||
this.radleft.Size = new System.Drawing.Size(119, 66);
|
||||
this.radleft.TabIndex = 5;
|
||||
this.radleft.Text = "좌측";
|
||||
this.radleft.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radleft.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// groupBox2
|
||||
//
|
||||
this.groupBox2.Controls.Add(this.arLabel1);
|
||||
@@ -610,208 +838,6 @@
|
||||
this.panel12.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel12.TabIndex = 14;
|
||||
//
|
||||
// panel6
|
||||
//
|
||||
this.panel6.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.panel6.Location = new System.Drawing.Point(129, 19);
|
||||
this.panel6.Name = "panel6";
|
||||
this.panel6.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel6.TabIndex = 12;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.BackColor = System.Drawing.Color.Blue;
|
||||
this.label1.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.label1.Font = new System.Drawing.Font("맑은 고딕", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.label1.ForeColor = System.Drawing.Color.White;
|
||||
this.label1.Location = new System.Drawing.Point(607, 577);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
this.label1.Size = new System.Drawing.Size(404, 38);
|
||||
this.label1.TabIndex = 9;
|
||||
this.label1.Text = "----------";
|
||||
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// radpbs2
|
||||
//
|
||||
this.radpbs2.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radpbs2.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radpbs2.BorderRadius = 7;
|
||||
this.radpbs2.BorderSize = 2;
|
||||
this.radpbs2.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radpbs2.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radpbs2.CheckWidth = 30;
|
||||
this.radpbs2.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radpbs2.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radpbs2.Location = new System.Drawing.Point(278, 19);
|
||||
this.radpbs2.Name = "radpbs2";
|
||||
this.radpbs2.Size = new System.Drawing.Size(119, 66);
|
||||
this.radpbs2.TabIndex = 10;
|
||||
this.radpbs2.Text = "On(2)";
|
||||
this.radpbs2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radpbs2.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radpbs1
|
||||
//
|
||||
this.radpbs1.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radpbs1.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radpbs1.BorderRadius = 7;
|
||||
this.radpbs1.BorderSize = 2;
|
||||
this.radpbs1.Checked = true;
|
||||
this.radpbs1.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radpbs1.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radpbs1.CheckWidth = 30;
|
||||
this.radpbs1.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radpbs1.Font = new System.Drawing.Font("맑은 고딕", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radpbs1.ForeColor = System.Drawing.Color.Blue;
|
||||
this.radpbs1.Location = new System.Drawing.Point(144, 19);
|
||||
this.radpbs1.Name = "radpbs1";
|
||||
this.radpbs1.Size = new System.Drawing.Size(119, 66);
|
||||
this.radpbs1.TabIndex = 9;
|
||||
this.radpbs1.TabStop = true;
|
||||
this.radpbs1.Text = "On(1)";
|
||||
this.radpbs1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radpbs1.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radpbs0
|
||||
//
|
||||
this.radpbs0.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radpbs0.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radpbs0.BorderRadius = 7;
|
||||
this.radpbs0.BorderSize = 2;
|
||||
this.radpbs0.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radpbs0.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radpbs0.CheckWidth = 30;
|
||||
this.radpbs0.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radpbs0.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radpbs0.Location = new System.Drawing.Point(10, 19);
|
||||
this.radpbs0.Name = "radpbs0";
|
||||
this.radpbs0.Size = new System.Drawing.Size(119, 66);
|
||||
this.radpbs0.TabIndex = 8;
|
||||
this.radpbs0.Text = "Off(0)";
|
||||
this.radpbs0.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radpbs0.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radspdh
|
||||
//
|
||||
this.radspdh.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radspdh.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radspdh.BorderRadius = 7;
|
||||
this.radspdh.BorderSize = 2;
|
||||
this.radspdh.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radspdh.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radspdh.CheckWidth = 30;
|
||||
this.radspdh.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radspdh.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radspdh.Location = new System.Drawing.Point(278, 19);
|
||||
this.radspdh.Name = "radspdh";
|
||||
this.radspdh.Size = new System.Drawing.Size(119, 66);
|
||||
this.radspdh.TabIndex = 2;
|
||||
this.radspdh.Text = "고속";
|
||||
this.radspdh.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radspdh.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radspdm
|
||||
//
|
||||
this.radspdm.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radspdm.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radspdm.BorderRadius = 7;
|
||||
this.radspdm.BorderSize = 2;
|
||||
this.radspdm.Checked = true;
|
||||
this.radspdm.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radspdm.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radspdm.CheckWidth = 30;
|
||||
this.radspdm.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radspdm.Font = new System.Drawing.Font("맑은 고딕", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radspdm.ForeColor = System.Drawing.Color.Blue;
|
||||
this.radspdm.Location = new System.Drawing.Point(144, 19);
|
||||
this.radspdm.Name = "radspdm";
|
||||
this.radspdm.Size = new System.Drawing.Size(119, 66);
|
||||
this.radspdm.TabIndex = 1;
|
||||
this.radspdm.TabStop = true;
|
||||
this.radspdm.Text = "중속";
|
||||
this.radspdm.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radspdm.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radspdl
|
||||
//
|
||||
this.radspdl.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radspdl.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radspdl.BorderRadius = 7;
|
||||
this.radspdl.BorderSize = 2;
|
||||
this.radspdl.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radspdl.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radspdl.CheckWidth = 30;
|
||||
this.radspdl.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radspdl.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radspdl.Location = new System.Drawing.Point(10, 19);
|
||||
this.radspdl.Name = "radspdl";
|
||||
this.radspdl.Size = new System.Drawing.Size(119, 66);
|
||||
this.radspdl.TabIndex = 0;
|
||||
this.radspdl.Text = "저속";
|
||||
this.radspdl.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radspdl.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radright
|
||||
//
|
||||
this.radright.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radright.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radright.BorderRadius = 7;
|
||||
this.radright.BorderSize = 2;
|
||||
this.radright.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radright.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radright.CheckWidth = 30;
|
||||
this.radright.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radright.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radright.Location = new System.Drawing.Point(278, 19);
|
||||
this.radright.Name = "radright";
|
||||
this.radright.Size = new System.Drawing.Size(119, 66);
|
||||
this.radright.TabIndex = 4;
|
||||
this.radright.Text = "우측";
|
||||
this.radright.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radright.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radstrai
|
||||
//
|
||||
this.radstrai.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radstrai.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radstrai.BorderRadius = 7;
|
||||
this.radstrai.BorderSize = 2;
|
||||
this.radstrai.Checked = true;
|
||||
this.radstrai.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radstrai.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radstrai.CheckWidth = 30;
|
||||
this.radstrai.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radstrai.Font = new System.Drawing.Font("맑은 고딕", 16F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radstrai.ForeColor = System.Drawing.Color.Blue;
|
||||
this.radstrai.Location = new System.Drawing.Point(144, 19);
|
||||
this.radstrai.Name = "radstrai";
|
||||
this.radstrai.Size = new System.Drawing.Size(119, 66);
|
||||
this.radstrai.TabIndex = 3;
|
||||
this.radstrai.TabStop = true;
|
||||
this.radstrai.Text = "직진";
|
||||
this.radstrai.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radstrai.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radleft
|
||||
//
|
||||
this.radleft.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.radleft.Bordercolor = System.Drawing.Color.DimGray;
|
||||
this.radleft.BorderRadius = 7;
|
||||
this.radleft.BorderSize = 2;
|
||||
this.radleft.CheckOffColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.radleft.CheckOnColor = System.Drawing.Color.LimeGreen;
|
||||
this.radleft.CheckWidth = 30;
|
||||
this.radleft.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.radleft.Font = new System.Drawing.Font("맑은 고딕", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.radleft.Location = new System.Drawing.Point(10, 19);
|
||||
this.radleft.Name = "radleft";
|
||||
this.radleft.Size = new System.Drawing.Size(119, 66);
|
||||
this.radleft.TabIndex = 5;
|
||||
this.radleft.Text = "좌측";
|
||||
this.radleft.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radleft.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// radbackward
|
||||
//
|
||||
this.radbackward.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
@@ -831,6 +857,14 @@
|
||||
this.radbackward.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radbackward.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// panel6
|
||||
//
|
||||
this.panel6.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.panel6.Location = new System.Drawing.Point(129, 19);
|
||||
this.panel6.Name = "panel6";
|
||||
this.panel6.Size = new System.Drawing.Size(15, 66);
|
||||
this.panel6.TabIndex = 12;
|
||||
//
|
||||
// radforward
|
||||
//
|
||||
this.radforward.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
@@ -853,6 +887,20 @@
|
||||
this.radforward.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
this.radforward.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.BackColor = System.Drawing.Color.Blue;
|
||||
this.label1.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.label1.Font = new System.Drawing.Font("맑은 고딕", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this.label1.ForeColor = System.Drawing.Color.White;
|
||||
this.label1.Location = new System.Drawing.Point(607, 577);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Padding = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
this.label1.Size = new System.Drawing.Size(404, 38);
|
||||
this.label1.TabIndex = 9;
|
||||
this.label1.Text = "----------";
|
||||
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// guideSensor1
|
||||
//
|
||||
this.guideSensor1.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
@@ -865,54 +913,6 @@
|
||||
this.guideSensor1.TabIndex = 8;
|
||||
this.guideSensor1.Text = "guideSensor1";
|
||||
//
|
||||
// btBack180
|
||||
//
|
||||
this.btBack180.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.btBack180.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.btBack180.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.btBack180.Font = new System.Drawing.Font("맑은 고딕", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.btBack180.ForeColor = System.Drawing.Color.Black;
|
||||
this.btBack180.Location = new System.Drawing.Point(205, 3);
|
||||
this.btBack180.Name = "btBack180";
|
||||
this.btBack180.Size = new System.Drawing.Size(196, 53);
|
||||
this.btBack180.TabIndex = 15;
|
||||
this.btBack180.Text = "백후 180도 회전 (Left)";
|
||||
this.btBack180.UseVisualStyleBackColor = false;
|
||||
this.btBack180.Click += new System.EventHandler(this.btBack180_Click);
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
this.tableLayoutPanel2.ColumnCount = 2;
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.Controls.Add(this.btBack180, 1, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.btLeft180, 0, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.btRight180, 0, 1);
|
||||
this.tableLayoutPanel2.Controls.Add(this.button2, 1, 1);
|
||||
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 438);
|
||||
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
this.tableLayoutPanel2.RowCount = 2;
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.tableLayoutPanel2.Size = new System.Drawing.Size(404, 118);
|
||||
this.tableLayoutPanel2.TabIndex = 12;
|
||||
//
|
||||
// button2
|
||||
//
|
||||
this.button2.BackColor = System.Drawing.Color.WhiteSmoke;
|
||||
this.button2.Cursor = System.Windows.Forms.Cursors.Hand;
|
||||
this.button2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.button2.Font = new System.Drawing.Font("맑은 고딕", 10F, System.Drawing.FontStyle.Bold);
|
||||
this.button2.ForeColor = System.Drawing.Color.Black;
|
||||
this.button2.Location = new System.Drawing.Point(205, 62);
|
||||
this.button2.Name = "button2";
|
||||
this.button2.Size = new System.Drawing.Size(196, 53);
|
||||
this.button2.TabIndex = 15;
|
||||
this.button2.Text = "백후 180도 회전(Right)";
|
||||
this.button2.UseVisualStyleBackColor = false;
|
||||
this.button2.Click += new System.EventHandler(this.button2_Click);
|
||||
//
|
||||
// fManual
|
||||
//
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
@@ -930,11 +930,11 @@
|
||||
this.VisibleChanged += new System.EventHandler(this.fManual_VisibleChanged);
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
this.panel2.ResumeLayout(false);
|
||||
this.tableLayoutPanel2.ResumeLayout(false);
|
||||
this.groupBox1.ResumeLayout(false);
|
||||
this.grpSpeed.ResumeLayout(false);
|
||||
this.grpBunki.ResumeLayout(false);
|
||||
this.groupBox2.ResumeLayout(false);
|
||||
this.tableLayoutPanel2.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
@@ -79,12 +79,19 @@ namespace Project.ViewForm
|
||||
}
|
||||
if (PUB.CheckManualChargeMode() == false) return;
|
||||
arDev.Narumi.Speed spd = arDev.Narumi.Speed.Low;
|
||||
|
||||
if (radspdh.Checked) spd = arDev.Narumi.Speed.High;
|
||||
else if (radspdl.Checked) spd = arDev.Narumi.Speed.Mid;
|
||||
else if (radspdm.Checked) spd = arDev.Narumi.Speed.Mid;
|
||||
arDev.Narumi.Sensor ss = arDev.Narumi.Sensor.PBSOff;
|
||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.BS, spd, ss);
|
||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||
|
||||
// [Manual Safety] Clear previous auto-task state
|
||||
PUB._virtualAGV.TargetNode = null;
|
||||
PUB._virtualAGV.StopPath();
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; // Clear ACS Command
|
||||
PUB.sm.ClearRunStep(); // Clear RunStep sequence
|
||||
}
|
||||
|
||||
private void arLabel2_Click(object sender, EventArgs e)
|
||||
@@ -103,11 +110,17 @@ namespace Project.ViewForm
|
||||
if (PUB.CheckManualChargeMode() == false) return;
|
||||
arDev.Narumi.Speed spd = arDev.Narumi.Speed.Low;
|
||||
if (radspdh.Checked) spd = arDev.Narumi.Speed.High;
|
||||
else if (radspdl.Checked) spd = arDev.Narumi.Speed.Mid;
|
||||
else if (radspdm.Checked) spd = arDev.Narumi.Speed.Mid;
|
||||
arDev.Narumi.Sensor ss = arDev.Narumi.Sensor.PBSOff;
|
||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.FS, spd, ss);
|
||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||
|
||||
// [Manual Safety] Clear previous auto-task state
|
||||
PUB._virtualAGV.TargetNode = null;
|
||||
PUB._virtualAGV.StopPath();
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
PUB.sm.ClearRunStep();
|
||||
}
|
||||
|
||||
private void arLabel3_Click(object sender, EventArgs e)
|
||||
@@ -131,6 +144,12 @@ namespace Project.ViewForm
|
||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.RT, spd, ss);
|
||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||
|
||||
// [Manual Safety] Clear previous auto-task state
|
||||
PUB._virtualAGV.TargetNode = null;
|
||||
PUB._virtualAGV.StopPath();
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
PUB.sm.ClearRunStep();
|
||||
}
|
||||
|
||||
private void arLabel4_Click(object sender, EventArgs e)
|
||||
@@ -154,6 +173,12 @@ namespace Project.ViewForm
|
||||
if (radpbs0.Checked) ss = arDev.Narumi.Sensor.PBSOn;
|
||||
PUB.AGV.AGVMoveManual(arDev.Narumi.ManulOpt.LT, spd, ss);
|
||||
PUB.sm.SetNewStep(StateMachine.eSMStep.IDLE);
|
||||
|
||||
// [Manual Safety] Clear previous auto-task state
|
||||
PUB._virtualAGV.TargetNode = null;
|
||||
PUB._virtualAGV.StopPath();
|
||||
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
|
||||
PUB.sm.ClearRunStep();
|
||||
}
|
||||
|
||||
private void arLabel11_Click(object sender, EventArgs e)
|
||||
@@ -176,7 +201,7 @@ namespace Project.ViewForm
|
||||
if (dlg != DialogResult.Yes) return;
|
||||
var opt = makeopt();
|
||||
PUB.AGV.AGVMoveSet(opt);
|
||||
PUB.AGV.AGVMoveRun();
|
||||
PUB.AGV.AGVMoveRun(opt.Direction == arDev.Narumi.eMoveDir.Forward ? arDev.Narumi.eRunOpt.Forward : arDev.Narumi.eRunOpt.Backward);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -210,8 +235,8 @@ namespace Project.ViewForm
|
||||
|
||||
//마크정보
|
||||
guideSensor1.SensorValue = PUB.AGV.data.guidesensor;
|
||||
guideSensor1.LMark = PUB.AGV.signal.mark_sensor_1;
|
||||
guideSensor1.RMark = PUB.AGV.signal.mark_sensor_2;
|
||||
guideSensor1.LMark = PUB.AGV.signal1.mark_sensor_1;
|
||||
guideSensor1.RMark = PUB.AGV.signal1.mark_sensor_2;
|
||||
guideSensor1.Invalidate();
|
||||
|
||||
if (PUB.AGV.system1.agv_run || PUB.AGV.system1.agv_run_manual)
|
||||
@@ -368,12 +393,12 @@ namespace Project.ViewForm
|
||||
private void btBack180_Click(object sender, EventArgs e)
|
||||
{
|
||||
//[STX] C T B 0 0 0 0 9 9 [ETX]
|
||||
PUB.AGV.AGVMoveBack180Turn(true);
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.ON);// (;// (true);
|
||||
}
|
||||
|
||||
private void button2_Click(object sender, EventArgs e)
|
||||
{
|
||||
PUB.AGV.AGVMoveBack180Turn(false);
|
||||
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Project
|
||||
bool remoteClose = false;
|
||||
bool forceClose = false;
|
||||
|
||||
readonly usbdetect.DriveDetector usbdet;
|
||||
public fMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -57,9 +56,12 @@ namespace Project
|
||||
|
||||
if (DateTime.Now > PUB.LastInputTime) PUB.LastInputTime = DateTime.Now;
|
||||
};
|
||||
usbdet = new usbdetect.DriveDetector(this);
|
||||
usbdet.DeviceArrived += Usbdet_DeviceArrived;
|
||||
usbdet.DeviceRemoved += Usbdet_DeviceRemoved;
|
||||
|
||||
PUB._mapCanvas = new AGVNavigationCore.Controls.UnifiedAGVCanvas();
|
||||
PUB._mapCanvas.Dock = DockStyle.Fill;
|
||||
PUB._mapCanvas.ShowGrid = false;
|
||||
PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32);
|
||||
PUB._mapCanvas.ForeColor = Color.White;
|
||||
|
||||
this.panTopMenu.MouseMove += LbTitle_MouseMove;
|
||||
this.panTopMenu.MouseUp += LbTitle_MouseUp;
|
||||
@@ -73,32 +75,6 @@ namespace Project
|
||||
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
base.WndProc(ref m);
|
||||
if (usbdet != null)
|
||||
{
|
||||
usbdet.WndProc(ref m);
|
||||
}
|
||||
}
|
||||
|
||||
private void Usbdet_DeviceRemoved(object sender, usbdetect.DriveDetectorEventArgs e)
|
||||
{
|
||||
//throw new NotImplementedException();
|
||||
Console.WriteLine(e.Drive);
|
||||
}
|
||||
|
||||
private void Usbdet_DeviceArrived(object sender, usbdetect.DriveDetectorEventArgs e)
|
||||
{
|
||||
//throw new NotImplementedException();
|
||||
using (var fUpdate = new Dialog.fUpdateForm(e.Drive))
|
||||
if (fUpdate.ShowDialog() == DialogResult.Yes)
|
||||
{
|
||||
//종료한다
|
||||
remoteClose = true;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void __Closing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
@@ -173,6 +149,9 @@ namespace Project
|
||||
|
||||
VAR.STR[eVarString.SWVersion] = Application.ProductVersion;
|
||||
|
||||
|
||||
AutoLoadMapData();
|
||||
|
||||
//SETTING H/W
|
||||
this.IOState.ItemClick += gridView2_ItemClick;
|
||||
VAR.BOOL.PropertyChanged += BOOL_PropertyChanged;
|
||||
@@ -281,6 +260,71 @@ namespace Project
|
||||
PUB.AddEEDB("프로그램 시작");
|
||||
}
|
||||
|
||||
void AutoLoadMapData()
|
||||
{
|
||||
//auto load
|
||||
var mapPath = new System.IO.DirectoryInfo("route");
|
||||
if (mapPath.Exists == false) mapPath.Create();
|
||||
|
||||
//맵파일로딩
|
||||
if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.json");
|
||||
System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile);
|
||||
if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName, "default.json"));
|
||||
if (filePath.Exists == false) //그래도없다면 맵폴더에서 파일을 찾아본다.
|
||||
{
|
||||
var files = mapPath.GetFiles("*.json");
|
||||
if (files.Any()) filePath = files[0];
|
||||
}
|
||||
|
||||
if (filePath.Exists)
|
||||
{
|
||||
var result = MapLoader.LoadMapFromFile(filePath.FullName);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
PUB._mapCanvas.SetMapLoadResult(result);
|
||||
PUB._mapCanvas.MapFileName = filePath.FullName;
|
||||
|
||||
// 🔥 가상 AGV 초기화 (첫 노드 위치에 생성)
|
||||
if (PUB._virtualAGV == null && PUB._mapCanvas.Nodes.Count > 0)
|
||||
{
|
||||
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);
|
||||
|
||||
// 캔버스에 AGV 리스트 설정
|
||||
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
|
||||
PUB._mapCanvas.AGVList = agvList;
|
||||
|
||||
PUB.log.Add($"가상 AGV 생성: {startNode.Id} 위치");
|
||||
}
|
||||
}
|
||||
else if (PUB._virtualAGV != null)
|
||||
{
|
||||
PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low;
|
||||
// 기존 AGV가 있으면 캔버스에 다시 연결
|
||||
var agvList = new System.Collections.Generic.List<AGVNavigationCore.Controls.IAGV> { PUB._virtualAGV };
|
||||
PUB._mapCanvas.AGVList = agvList;
|
||||
}
|
||||
PUB.log.Add($"맵 파일 로드 완료: {filePath.Name}, 노드 수: {result.Nodes.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.Add($"맵 파일 로딩 실패: {result.ErrorMessage}");
|
||||
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PUB.log.Add($"맵 파일을 찾을 수 없습니다: {filePath.FullName}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region "Mouse Form Move"
|
||||
|
||||
private Boolean fMove = false;
|
||||
@@ -736,6 +780,12 @@ namespace Project
|
||||
{
|
||||
if (VAR.BOOL[eVarBool.FLAG_CHARGEONM])
|
||||
{
|
||||
if (PUB.BMS.IsValid && PUB.BMS.IsCharging)
|
||||
{
|
||||
UTIL.MsgE("현재 배터리에서 충전 상태가 감지되고 있어 해제할 수 없습니다");
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = UTIL.MsgQ("수동 충전을 해제 할까요?");
|
||||
if (dlg != DialogResult.Yes) return;
|
||||
VAR.BOOL[eVarBool.FLAG_CHARGEONM] = false;
|
||||
@@ -760,8 +810,8 @@ namespace Project
|
||||
//mapsave
|
||||
using (var sd = new SaveFileDialog())
|
||||
{
|
||||
sd.Filter = "AGV Map Files (*.agvmap)|*.agvmap|All Files (*.*)|*.*";
|
||||
sd.DefaultExt = "agvmap";
|
||||
sd.Filter = "AGV Map Files (*.json)|*.json|All Files (*.*)|*.*";
|
||||
sd.DefaultExt = "json";
|
||||
sd.FileName = PUB._mapCanvas.MapFileName;
|
||||
if (sd.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
@@ -776,8 +826,8 @@ namespace Project
|
||||
|
||||
var od = new OpenFileDialog
|
||||
{
|
||||
Filter = "AGV Map Files (*.agvmap)|*.agvmap|All Files (*.*)|*.*",
|
||||
DefaultExt = "agvmap",
|
||||
Filter = "AGV Map Files (*.json)|*.json|All Files (*.*)|*.*",
|
||||
DefaultExt = "json",
|
||||
FileName = PUB._mapCanvas.MapFileName,
|
||||
};
|
||||
|
||||
@@ -801,20 +851,9 @@ namespace Project
|
||||
if (result.Success)
|
||||
{
|
||||
var _mapCanvas = PUB._mapCanvas;
|
||||
PUB._mapNodes = result.Nodes;
|
||||
_mapCanvas.SetMapLoadResult(result);
|
||||
PUB.log.Add($"Set _mapNodes");
|
||||
|
||||
// 맵 캔버스에 데이터 설정
|
||||
_mapCanvas.Nodes = result.Nodes;
|
||||
// RfidMappings 제거됨 - MapNode에 통합
|
||||
|
||||
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
{
|
||||
_mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
|
||||
_mapCanvas.ShowGrid = result.Settings.ShowGrid;
|
||||
}
|
||||
|
||||
// 설정에 마지막 맵 파일 경로 저장
|
||||
PUB.setting.LastMapFile = filePath;
|
||||
PUB.setting.Save();
|
||||
@@ -856,7 +895,7 @@ namespace Project
|
||||
}
|
||||
|
||||
var _mapCanvas = PUB._mapCanvas;
|
||||
var _mapNodes = PUB._mapNodes;
|
||||
|
||||
|
||||
// 🔥 현재 캔버스 설정을 맵 파일에 저장
|
||||
var settings = new MapLoader.MapSettings
|
||||
@@ -865,7 +904,11 @@ namespace Project
|
||||
ShowGrid = _mapCanvas.ShowGrid
|
||||
};
|
||||
|
||||
if (MapLoader.SaveMapToFile(filePath, _mapNodes, settings))
|
||||
if (MapLoader.SaveMapToFile(filePath,
|
||||
_mapCanvas.Nodes, _mapCanvas.Labels,
|
||||
_mapCanvas.Images, _mapCanvas.Marks,
|
||||
_mapCanvas.Magnets,
|
||||
settings))
|
||||
{
|
||||
// 설정에 마지막 맵 파일 경로 저장
|
||||
PUB.setting.LastMapFile = filePath;
|
||||
|
||||
17
Cs_HMI/Project/fSetup.Designer.cs
generated
17
Cs_HMI/Project/fSetup.Designer.cs
generated
@@ -103,7 +103,6 @@
|
||||
this.valIntervalBMS = new AGVControl.ValueSelect();
|
||||
this.valIntervalXBE = new AGVControl.ValueSelect();
|
||||
this.tabPage3 = new System.Windows.Forms.TabPage();
|
||||
this.cmbChargerPos = new System.Windows.Forms.ComboBox();
|
||||
this.label58 = new System.Windows.Forms.Label();
|
||||
this.label19 = new System.Windows.Forms.Label();
|
||||
this.label44 = new System.Windows.Forms.Label();
|
||||
@@ -1562,7 +1561,6 @@
|
||||
// tabPage3
|
||||
//
|
||||
this.tabPage3.BackColor = System.Drawing.Color.DarkSlateBlue;
|
||||
this.tabPage3.Controls.Add(this.cmbChargerPos);
|
||||
this.tabPage3.Controls.Add(this.label58);
|
||||
this.tabPage3.Controls.Add(this.label19);
|
||||
this.tabPage3.Controls.Add(this.label44);
|
||||
@@ -1600,20 +1598,6 @@
|
||||
this.tabPage3.TabIndex = 4;
|
||||
this.tabPage3.Text = "충전";
|
||||
//
|
||||
// cmbChargerPos
|
||||
//
|
||||
this.cmbChargerPos.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.cmbChargerPos.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Bold);
|
||||
this.cmbChargerPos.FormattingEnabled = true;
|
||||
this.cmbChargerPos.Items.AddRange(new object[] {
|
||||
"HOME 아래",
|
||||
"HOME",
|
||||
"HOME 위"});
|
||||
this.cmbChargerPos.Location = new System.Drawing.Point(1004, 11);
|
||||
this.cmbChargerPos.Name = "cmbChargerPos";
|
||||
this.cmbChargerPos.Size = new System.Drawing.Size(217, 45);
|
||||
this.cmbChargerPos.TabIndex = 69;
|
||||
//
|
||||
// label58
|
||||
//
|
||||
this.label58.AutoSize = true;
|
||||
@@ -3611,7 +3595,6 @@
|
||||
private AGVControl.ValueSelect vcTagF5B;
|
||||
private AGVControl.ValueSelect vcTagF4F5;
|
||||
private System.Windows.Forms.Label label58;
|
||||
private System.Windows.Forms.ComboBox cmbChargerPos;
|
||||
private System.Windows.Forms.CheckBox chkClearPos;
|
||||
private System.Windows.Forms.Label label7;
|
||||
private AGVControl.ValueSelect vcXBID;
|
||||
|
||||
@@ -52,26 +52,19 @@ namespace Project
|
||||
{
|
||||
|
||||
//통신값 표시
|
||||
//tbPortBMS.Text = Pub.setting.Port_BMS;
|
||||
///tbPortPLC.Text = PUB.setting.Port_PLC;
|
||||
tbPortAGV.Text = PUB.setting.Port_AGV;
|
||||
tbPortXBE.Text = PUB.setting.Port_XBE;
|
||||
tbportBMS.Text = PUB.setting.Port_BAT;
|
||||
|
||||
// tbBaudPLC.Text = PUB.setting.Baud_PLC.ToString();
|
||||
tbBaudAGV.Text = PUB.setting.Baud_AGV.ToString();
|
||||
tbBaudXBE.Text = PUB.setting.Baud_XBE.ToString();
|
||||
tbBaudBAT.Text = PUB.setting.Baud_BAT.ToString();
|
||||
|
||||
//valueSelect1.Value = Pub.setting.interval_bms;
|
||||
valIntervalXBE.Value = PUB.setting.interval_xbe;
|
||||
vcpidDS.Value = PUB.setting.ZSpeed;
|
||||
//valueSelect4.Value = PUB.setting.interval_iostate;
|
||||
valIntervalBMS.Value = PUB.setting.interval_bms;
|
||||
tbChargerID.Value = PUB.setting.ChargerID;
|
||||
|
||||
cmbChargerPos.SelectedIndex = PUB.setting.chargerpos;
|
||||
|
||||
vcSCK.Value = PUB.setting.SCK;
|
||||
vcSSK.Value = PUB.setting.SSK;
|
||||
vcSTT.Value = PUB.setting.STT;
|
||||
@@ -218,28 +211,19 @@ namespace Project
|
||||
propertyGrid1.Invalidate();
|
||||
|
||||
//통신정보저장
|
||||
// Pub.setting.Port_BMS = tbPortBMS.Text;
|
||||
PUB.setting.Port_XBE = tbPortXBE.Text;
|
||||
//PUB.setting.Port_PLC = tbPortPLC.Text;
|
||||
PUB.setting.Port_AGV = tbPortAGV.Text;
|
||||
PUB.setting.Port_BAT = tbportBMS.Text;
|
||||
|
||||
// Pub.setting.Baud_bms = int.Parse(tbBaudBms.Text);
|
||||
PUB.setting.Baud_XBE = int.Parse(tbBaudXBE.Text);
|
||||
//PUB.setting.Baud_PLC = int.Parse(tbBaudPLC.Text);
|
||||
PUB.setting.Baud_AGV = int.Parse(tbBaudAGV.Text);
|
||||
PUB.setting.Baud_BAT = int.Parse(tbBaudBAT.Text);
|
||||
|
||||
PUB.setting.ChargerID = (int)(tbChargerID.Value);
|
||||
|
||||
//Pub.setting.interval_bms = (float)valueSelect1.Value;
|
||||
PUB.setting.interval_bms = (int)valIntervalBMS.Value;
|
||||
PUB.setting.interval_xbe = (float)valIntervalXBE.Value;
|
||||
PUB.setting.ZSpeed = (byte)vcpidDS.Value;
|
||||
//PUB.setting.interval_iostate = (byte)valueSelect4.Value;
|
||||
PUB.setting.chargerpos = this.cmbChargerPos.SelectedIndex;
|
||||
PUB.setting.AutoModeOffAndClearPosition = this.chkClearPos.Checked;
|
||||
//PUB.setting.MotorUpTime = (byte)valueSelect5.Value;
|
||||
|
||||
//AGV정보 저장
|
||||
PUB.setting.Enable_Speak = this.btSpeaker.ProgressValue != 0;
|
||||
@@ -294,7 +278,6 @@ namespace Project
|
||||
|
||||
//시스템옵션
|
||||
PUB.setting.ChargeMaxTime = (int)vcChargeMaxTime.Value;
|
||||
//Pub.setting.ChargeIdleInterval = (int)nudChargeIdleInterval.Value;
|
||||
PUB.setting.ChargeRetryTerm = (int)vcChargeRetryTerm.Value;
|
||||
|
||||
//초기화시간
|
||||
|
||||
@@ -5,425 +5,423 @@ using System.Text;
|
||||
|
||||
namespace Project.StateMachine
|
||||
{
|
||||
public partial class StateMachine : IDisposable
|
||||
{
|
||||
public DateTime UpdateTime;
|
||||
public TimeSpan RunSpeed = new TimeSpan(0);
|
||||
private Boolean bLoop = true;
|
||||
private DateTime StepStartTime;
|
||||
private byte _messageOption;
|
||||
public partial class StateMachine : IDisposable
|
||||
{
|
||||
public DateTime UpdateTime;
|
||||
public TimeSpan RunSpeed = new TimeSpan(0);
|
||||
private Boolean bLoop = true;
|
||||
private DateTime StepStartTime;
|
||||
private byte _messageOption;
|
||||
|
||||
private System.Threading.Thread worker;
|
||||
private Boolean firstRun = false;
|
||||
private System.Threading.Thread worker;
|
||||
private Boolean firstRun = false;
|
||||
|
||||
public Boolean WaitFirstRun = false;
|
||||
public Boolean WaitFirstRun = false;
|
||||
|
||||
|
||||
|
||||
public StateMachine()
|
||||
{
|
||||
WaitFirstRun = false;
|
||||
UpdateTime = DateTime.Now;
|
||||
_messageOption = 0xFF; //모든메세지가 오도록 한다.
|
||||
worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
|
||||
{
|
||||
IsBackground = false
|
||||
};
|
||||
StepStartTime = DateTime.Parse("1982-11-23");
|
||||
}
|
||||
public StateMachine()
|
||||
{
|
||||
WaitFirstRun = false;
|
||||
UpdateTime = DateTime.Now;
|
||||
_messageOption = 0xFF; //모든메세지가 오도록 한다.
|
||||
worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
|
||||
{
|
||||
IsBackground = false
|
||||
};
|
||||
StepStartTime = DateTime.Parse("1982-11-23");
|
||||
}
|
||||
|
||||
~StateMachine()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
~StateMachine()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#region "Dispose"
|
||||
#region "Dispose"
|
||||
|
||||
private bool disposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
// Check to see if Dispose has already been called.
|
||||
if (!this.disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Dispose managed resources.
|
||||
}
|
||||
|
||||
Stop();
|
||||
// unmanaged resources here.
|
||||
// If disposing is false,
|
||||
// only the following code is executed.
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if(bLoop != false) bLoop = false;
|
||||
//if (worker.IsAlive)
|
||||
// if (worker.Join(1000) == false)
|
||||
// worker.Abort();
|
||||
}
|
||||
public void Start()
|
||||
{
|
||||
if (worker.ThreadState == System.Threading.ThreadState.Stopped)
|
||||
{
|
||||
worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
|
||||
{
|
||||
IsBackground = false
|
||||
};
|
||||
}
|
||||
bLoop = true;
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
private Boolean _isthreadrun;
|
||||
public Boolean IsThreadRun { get { return _isthreadrun; } }
|
||||
public int LoopCount { get { return _loopCount; } }
|
||||
public DateTime LastLoopTime { get { return _lastLoopTime; } }
|
||||
|
||||
private int _loopCount = 0;
|
||||
private DateTime _lastLoopTime = DateTime.Now;
|
||||
|
||||
void Loop()
|
||||
{
|
||||
_isthreadrun = true;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start");
|
||||
|
||||
try
|
||||
{
|
||||
while (bLoop)
|
||||
{
|
||||
_loopCount++;
|
||||
_lastLoopTime = DateTime.Now;
|
||||
|
||||
// 루프 동작 확인용 로그 (10초마다)
|
||||
if (_loopCount % 10000 == 0)
|
||||
{
|
||||
RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}");
|
||||
}
|
||||
|
||||
RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨
|
||||
UpdateTime = DateTime.Now;
|
||||
|
||||
//항상 작동하는 경우
|
||||
try
|
||||
{
|
||||
var spsHandler = SPS;
|
||||
if (spsHandler != null)
|
||||
{
|
||||
var spsStartTime = DateTime.Now;
|
||||
var spsTask = System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
spsHandler(this, new EventArgs());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
});
|
||||
|
||||
// 타임아웃 1초
|
||||
if (!spsTask.Wait(1000))
|
||||
{
|
||||
var elapsed = (DateTime.Now - spsStartTime).TotalSeconds;
|
||||
RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
|
||||
//작동스텝이 변경되었다면 그것을 알림 처리한다.
|
||||
if (GetNewStep != Step)
|
||||
{
|
||||
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
||||
RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep));
|
||||
|
||||
StepApply();
|
||||
firstRun = true;
|
||||
StepStartTime = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
//팝업메세지의 종료를 기다리는 식에서 문제가 생긴다
|
||||
firstRun = WaitFirstRun;
|
||||
}
|
||||
|
||||
|
||||
//동작중에 발생하는 이벤트
|
||||
var runningHandler = Running;
|
||||
if (runningHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runningStartTime = DateTime.Now;
|
||||
var runningTask = System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
});
|
||||
|
||||
// 타임아웃 2초
|
||||
if (!runningTask.Wait(2000))
|
||||
{
|
||||
var elapsed = (DateTime.Now - runningStartTime).TotalSeconds;
|
||||
RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
} System.Threading.Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isthreadrun = false;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 상태머신의 작동상태
|
||||
/// </summary>
|
||||
public eSMStep Step { get { return _step; } }
|
||||
|
||||
/// <summary>
|
||||
/// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태)
|
||||
/// </summary>
|
||||
public byte StepValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)this._step;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 진행 스텝을 강제로 변경합니다.
|
||||
/// 일반적인 상태변화는 setNewStep 을 사용하세요.
|
||||
/// </summary>
|
||||
/// <param name="step"></param>
|
||||
public void SetCurrentStep(ERunStep step)
|
||||
{
|
||||
RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step));
|
||||
_runstepo = step;
|
||||
_runstepn = step;
|
||||
}
|
||||
|
||||
public void SetNewStep(eSMStep newstep_, bool force = false)
|
||||
{
|
||||
if (Step != newstep_)
|
||||
{
|
||||
if(force)
|
||||
private bool disposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
// Check to see if Dispose has already been called.
|
||||
if (!this.disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep));
|
||||
OldStep = _step;
|
||||
_step = newstep_;
|
||||
_newstep = newstep_;
|
||||
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = StepChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StepChangeEventArgs(OldStep, newstep_);
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try { handler(this, args); }
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
});
|
||||
}
|
||||
}
|
||||
// Dispose managed resources.
|
||||
}
|
||||
|
||||
Stop();
|
||||
// unmanaged resources here.
|
||||
// If disposing is false,
|
||||
// only the following code is executed.
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (bLoop != false) bLoop = false;
|
||||
//if (worker.IsAlive)
|
||||
// if (worker.Join(1000) == false)
|
||||
// worker.Abort();
|
||||
}
|
||||
public void Start()
|
||||
{
|
||||
if (worker.ThreadState == System.Threading.ThreadState.Stopped)
|
||||
{
|
||||
worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
|
||||
{
|
||||
IsBackground = false
|
||||
};
|
||||
}
|
||||
bLoop = true;
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
private Boolean _isthreadrun;
|
||||
public Boolean IsThreadRun { get { return _isthreadrun; } }
|
||||
public int LoopCount { get { return _loopCount; } }
|
||||
public DateTime LastLoopTime { get { return _lastLoopTime; } }
|
||||
|
||||
private int _loopCount = 0;
|
||||
private DateTime _lastLoopTime = DateTime.Now;
|
||||
|
||||
void Loop()
|
||||
{
|
||||
_isthreadrun = true;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start");
|
||||
|
||||
try
|
||||
{
|
||||
while (bLoop)
|
||||
{
|
||||
_loopCount++;
|
||||
_lastLoopTime = DateTime.Now;
|
||||
|
||||
// 루프 동작 확인용 로그 (10초마다)
|
||||
if (_loopCount % 10000 == 0)
|
||||
{
|
||||
RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}");
|
||||
}
|
||||
|
||||
RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨
|
||||
UpdateTime = DateTime.Now;
|
||||
|
||||
//항상 작동하는 경우
|
||||
try
|
||||
{
|
||||
var spsHandler = SPS;
|
||||
if (spsHandler != null)
|
||||
{
|
||||
var spsStartTime = DateTime.Now;
|
||||
var spsTask = System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
spsHandler(this, new EventArgs());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
});
|
||||
|
||||
// 타임아웃 1초
|
||||
if (!spsTask.Wait(1000))
|
||||
{
|
||||
var elapsed = (DateTime.Now - spsStartTime).TotalSeconds;
|
||||
RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
|
||||
//작동스텝이 변경되었다면 그것을 알림 처리한다.
|
||||
if (GetNewStep != Step)
|
||||
{
|
||||
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
||||
RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep));
|
||||
|
||||
StepApply();
|
||||
firstRun = true;
|
||||
StepStartTime = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
//팝업메세지의 종료를 기다리는 식에서 문제가 생긴다
|
||||
firstRun = WaitFirstRun;
|
||||
}
|
||||
|
||||
|
||||
//동작중에 발생하는 이벤트
|
||||
var runningHandler = Running;
|
||||
if (runningHandler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runningStartTime = DateTime.Now;
|
||||
var runningTask = System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
|
||||
//try
|
||||
//{
|
||||
runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime));
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
//}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// 타임아웃 2초
|
||||
if (!runningTask.Wait(2000))
|
||||
{
|
||||
var elapsed = (DateTime.Now - runningStartTime).TotalSeconds;
|
||||
RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
System.Threading.Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isthreadrun = false;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 상태머신의 작동상태
|
||||
/// </summary>
|
||||
public eSMStep Step { get { return _step; } }
|
||||
|
||||
/// <summary>
|
||||
/// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태)
|
||||
/// </summary>
|
||||
public byte StepValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)this._step;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 진행 스텝을 강제로 변경합니다.
|
||||
/// 일반적인 상태변화는 setNewStep 을 사용하세요.
|
||||
/// </summary>
|
||||
/// <param name="step"></param>
|
||||
public void SetCurrentStep(ERunStep step)
|
||||
{
|
||||
RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step));
|
||||
_runstepo = step;
|
||||
_runstepn = step;
|
||||
}
|
||||
|
||||
public void SetNewStep(eSMStep newstep_, bool force = false)
|
||||
{
|
||||
if (Step != newstep_)
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep));
|
||||
OldStep = _step;
|
||||
_step = newstep_;
|
||||
_newstep = newstep_;
|
||||
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = StepChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StepChangeEventArgs(OldStep, newstep_);
|
||||
try { handler(this, args); }
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_newstep != newstep_)
|
||||
{
|
||||
//바뀌도록 예약을 한다.
|
||||
_newstep = newstep_;
|
||||
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
||||
RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep));
|
||||
}
|
||||
else
|
||||
{
|
||||
//예약은 되어있는데 아직 바뀐것은 아니다.
|
||||
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
||||
RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (_newstep != newstep_)
|
||||
{
|
||||
//바뀌도록 예약을 한다.
|
||||
_newstep = newstep_;
|
||||
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
||||
RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep));
|
||||
}
|
||||
else
|
||||
{
|
||||
//예약은 되어있는데 아직 바뀐것은 아니다.
|
||||
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
||||
RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep));
|
||||
}
|
||||
}
|
||||
|
||||
#region "runstep"
|
||||
private int _runStepSeq = 0;
|
||||
public int RunStepSeq { get { return _runStepSeq; } }
|
||||
}
|
||||
}
|
||||
|
||||
#region "runstep"
|
||||
private int _runStepSeq = 0;
|
||||
public int RunStepSeq { get { return _runStepSeq; } }
|
||||
|
||||
public Stack<ERunStep> BackupRunStep = new Stack<ERunStep>();
|
||||
|
||||
private DateTime runStepStartTime = DateTime.Parse("1982-11-23");
|
||||
private ERunStep _runstepn = ERunStep.READY;
|
||||
private ERunStep _runstepo = ERunStep.READY;
|
||||
public ERunStep RunStep { get { return _runstepo; } }
|
||||
public ERunStep RunStepNew { get { return _runstepn; } }
|
||||
public void SetNewRunStep(ERunStep newStep)
|
||||
{
|
||||
if (_runstepn != newStep)
|
||||
{
|
||||
// Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep));
|
||||
_runstepn = newStep;
|
||||
}
|
||||
}
|
||||
public void ApplyRunStep()
|
||||
{
|
||||
if (_runstepn != _runstepo)
|
||||
{
|
||||
//Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn));
|
||||
_runStepSeq = 0;
|
||||
_runstepo = _runstepn;
|
||||
UpdateRunStepSeq();// runStepStartTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } }
|
||||
public void SetStepSeq(byte value)
|
||||
{
|
||||
_runStepSeq = value;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL))
|
||||
{
|
||||
if (_runStepSeq != value) //변화가잇는겨웅에만 처리 220628
|
||||
RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value));
|
||||
}
|
||||
private DateTime runStepStartTime = DateTime.Parse("1982-11-23");
|
||||
private ERunStep _runstepn = ERunStep.READY;
|
||||
private ERunStep _runstepo = ERunStep.READY;
|
||||
public ERunStep RunStep { get { return _runstepo; } }
|
||||
public ERunStep RunStepNew { get { return _runstepn; } }
|
||||
public void SetNewRunStep(ERunStep newStep)
|
||||
{
|
||||
if (_runstepn != newStep)
|
||||
{
|
||||
// Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep));
|
||||
_runstepn = newStep;
|
||||
}
|
||||
}
|
||||
public void ApplyRunStep()
|
||||
{
|
||||
if (_runstepn != _runstepo)
|
||||
{
|
||||
//Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn));
|
||||
_runStepSeq = 0;
|
||||
_runstepo = _runstepn;
|
||||
UpdateRunStepSeq();// runStepStartTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } }
|
||||
public void SetStepSeq(byte value)
|
||||
{
|
||||
_runStepSeq = value;
|
||||
if (GetMsgOpt(EMsgOpt.NORMAL))
|
||||
{
|
||||
if (_runStepSeq != value) //변화가잇는겨웅에만 처리 220628
|
||||
RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value));
|
||||
}
|
||||
|
||||
}
|
||||
public void UpdateRunStepStartTime()
|
||||
{
|
||||
runStepStartTime = DateTime.Now;
|
||||
// Pub.log.Add("Update RunStep Start Time");
|
||||
}
|
||||
public void UpdateRunStepSeq(int incvalue = 1)
|
||||
{
|
||||
_runStepSeq += incvalue;
|
||||
UpdateRunStepStartTime();
|
||||
// Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq));
|
||||
}
|
||||
public void ResetRunStepSeq()
|
||||
{
|
||||
_runStepSeq = 1;
|
||||
UpdateRunStepStartTime();
|
||||
}
|
||||
}
|
||||
public void UpdateRunStepStartTime()
|
||||
{
|
||||
runStepStartTime = DateTime.Now;
|
||||
// Pub.log.Add("Update RunStep Start Time");
|
||||
}
|
||||
public void UpdateRunStepSeq(int incvalue = 1)
|
||||
{
|
||||
_runStepSeq += incvalue;
|
||||
UpdateRunStepStartTime();
|
||||
// Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq));
|
||||
}
|
||||
public void ResetRunStepSeq()
|
||||
{
|
||||
_runStepSeq = 1;
|
||||
UpdateRunStepStartTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다
|
||||
/// 기본 스텝상태를 READY로 변경 합니다
|
||||
/// </summary>
|
||||
public void ClearRunStep()
|
||||
{
|
||||
_runStepSeq = 1;
|
||||
runStepStartTime = DateTime.Now;
|
||||
_runstepn = ERunStep.READY;
|
||||
_runstepo = ERunStep.READY;
|
||||
}
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다
|
||||
/// 기본 스텝상태를 READY로 변경 합니다
|
||||
/// </summary>
|
||||
public void ClearRunStep()
|
||||
{
|
||||
_runStepSeq = 1;
|
||||
runStepStartTime = DateTime.Now;
|
||||
_runstepn = ERunStep.READY;
|
||||
_runstepo = ERunStep.READY;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public eSMStep GetNewStep
|
||||
{
|
||||
get
|
||||
{
|
||||
return _newstep;
|
||||
}
|
||||
}
|
||||
private eSMStep _newstep = eSMStep.NOTSET;
|
||||
public eSMStep OldStep = eSMStep.NOTSET; //171214
|
||||
private eSMStep _step;
|
||||
public eSMStep GetNewStep
|
||||
{
|
||||
get
|
||||
{
|
||||
return _newstep;
|
||||
}
|
||||
}
|
||||
private eSMStep _newstep = eSMStep.NOTSET;
|
||||
public eSMStep OldStep = eSMStep.NOTSET; //171214
|
||||
private eSMStep _step;
|
||||
|
||||
/// <summary>
|
||||
/// newstep 의 값을 step 에 적용합니다.
|
||||
/// </summary>
|
||||
private void StepApply()
|
||||
{
|
||||
var ostep = _step;
|
||||
OldStep = _step; _step = _newstep;
|
||||
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = StepChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StepChangeEventArgs(ostep, _step);
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try { handler(this, args); }
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
});
|
||||
}
|
||||
} //171214
|
||||
/// <summary>
|
||||
/// newstep 의 값을 step 에 적용합니다.
|
||||
/// </summary>
|
||||
private void StepApply()
|
||||
{
|
||||
var ostep = _step;
|
||||
OldStep = _step; _step = _newstep;
|
||||
|
||||
/// <summary>
|
||||
/// 메세지 출력옵션을 변경 합니다.
|
||||
/// </summary>
|
||||
/// <param name="opt"></param>
|
||||
/// <param name="value"></param>
|
||||
public void SetMsgOpt(EMsgOpt opt, Boolean value)
|
||||
{
|
||||
byte pos = (byte)opt;
|
||||
if (value)
|
||||
_messageOption = (byte)(_messageOption | (1 << pos));
|
||||
else
|
||||
_messageOption = (byte)(_messageOption & ~(1 << pos));
|
||||
}
|
||||
public void SetMegOptOn() { _messageOption = 0xFF; }
|
||||
public void SetMsgOptOff() { _messageOption = 0; }
|
||||
public Boolean GetMsgOpt(EMsgOpt opt)
|
||||
{
|
||||
byte pos = (byte)opt;
|
||||
return (_messageOption & (1 << pos)) > 0;
|
||||
}
|
||||
public TimeSpan StepRunTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0);
|
||||
else return DateTime.Now - StepStartTime;
|
||||
}
|
||||
}
|
||||
// 비동기로 이벤트 발생 (블로킹 방지)
|
||||
var handler = StepChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StepChangeEventArgs(ostep, _step);
|
||||
try { handler(this, args); }
|
||||
catch { /* 이벤트 핸들러 예외 무시 */ }
|
||||
}
|
||||
} //171214
|
||||
|
||||
public Boolean bPause = false;
|
||||
/// <summary>
|
||||
/// 메세지 출력옵션을 변경 합니다.
|
||||
/// </summary>
|
||||
/// <param name="opt"></param>
|
||||
/// <param name="value"></param>
|
||||
public void SetMsgOpt(EMsgOpt opt, Boolean value)
|
||||
{
|
||||
byte pos = (byte)opt;
|
||||
if (value)
|
||||
_messageOption = (byte)(_messageOption | (1 << pos));
|
||||
else
|
||||
_messageOption = (byte)(_messageOption & ~(1 << pos));
|
||||
}
|
||||
public void SetMegOptOn() { _messageOption = 0xFF; }
|
||||
public void SetMsgOptOff() { _messageOption = 0; }
|
||||
public Boolean GetMsgOpt(EMsgOpt opt)
|
||||
{
|
||||
byte pos = (byte)opt;
|
||||
return (_messageOption & (1 << pos)) > 0;
|
||||
}
|
||||
public TimeSpan StepRunTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0);
|
||||
else return DateTime.Now - StepStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상태머신이 실제 동작중인지 확인합니다.
|
||||
/// 검사상태 : Step = Run, IsThreadRun, bPause = false
|
||||
/// </summary>
|
||||
public Boolean IsRun
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true;
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
public Boolean bPause = false;
|
||||
|
||||
/// <summary>
|
||||
/// 상태머신이 실제 동작중인지 확인합니다.
|
||||
/// 검사상태 : Step = Run, IsThreadRun, bPause = false
|
||||
/// </summary>
|
||||
public Boolean IsRun
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true;
|
||||
else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ using AR;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public partial class Narumi : arRS232
|
||||
public partial class Narumi
|
||||
{
|
||||
|
||||
public bool AGVMoveSet(BunkiData opt)
|
||||
@@ -236,12 +236,12 @@ namespace arDev
|
||||
case eAgvCmd.ChargeOf:
|
||||
cmdString = $"CBT{param}O0003"; ///0003=충전대기시간
|
||||
retval = AddCommand(cmdString);
|
||||
RaiseMessage(arRS232.MessageType.Normal, "충전취소전송");
|
||||
RaiseMessage(NarumiSerialComm.MessageType.Normal, "충전취소전송");
|
||||
break;
|
||||
case eAgvCmd.ChargeOn:
|
||||
cmdString = $"CBT{param}I0003"; ///0003=충전대기시간
|
||||
retval = AddCommand(cmdString);
|
||||
RaiseMessage(arRS232.MessageType.Normal, "충전명령전송");
|
||||
RaiseMessage(NarumiSerialComm.MessageType.Normal, "충전명령전송");
|
||||
break;
|
||||
|
||||
case eAgvCmd.TurnLeft:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public partial class Narumi
|
||||
public partial class Narumi
|
||||
{
|
||||
public class DataEventArgs : EventArgs
|
||||
{
|
||||
|
||||
69
Cs_HMI/SubProject/AGV/Dataframe.cs
Normal file
69
Cs_HMI/SubProject/AGV/Dataframe.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AR;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public partial class Narumi
|
||||
{
|
||||
public class Dataframe
|
||||
{
|
||||
public byte STX { get; private set; }
|
||||
public byte ETX { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
public string DataString { get; private set; }
|
||||
public byte[] checksum { get; private set; } = new byte[2];
|
||||
public bool Valid { get; private set; } = false;
|
||||
public string Message { get; private set; } = string.Empty;
|
||||
public byte[] Buffer { get; private set; }
|
||||
public string Cmd { get; private set; } = string.Empty;
|
||||
|
||||
public bool Parse(byte[] data, int MinRecvLength = 0)
|
||||
{
|
||||
if (data == null || data.Any() == false)
|
||||
{
|
||||
this.Message = string.Format("수신 데이터가 없습니다");
|
||||
return false;
|
||||
}
|
||||
else if (data.Length < 5)
|
||||
{
|
||||
this.Message = $"데이터의 길이가 5보다 작습니다 길이={data.Length}";
|
||||
return false;
|
||||
}
|
||||
else if (MinRecvLength > 0 && data.Length < MinRecvLength)
|
||||
{
|
||||
this.Message = $"데이터의 길이가 {MinRecvLength}보다 작습니다 길이={data.Length}";
|
||||
return false;
|
||||
}
|
||||
else if (data[0] != 0x02 || data[data.Length - 1] != 0x03)
|
||||
{
|
||||
this.Message = $"STX/ETX Error";
|
||||
return false;
|
||||
}
|
||||
Buffer = new byte[data.Length];
|
||||
Array.Copy(data, Buffer, data.Length);
|
||||
STX = data[0];
|
||||
ETX = data[data.Length - 1];
|
||||
Array.Copy(data, data.Length - 3, checksum, 0, 2);
|
||||
|
||||
Data = new byte[data.Length - 4];
|
||||
Array.Copy(data, 1, Data, 0, data.Length - 4);
|
||||
|
||||
if (data.Length > 2) Cmd = System.Text.Encoding.Default.GetString(Data, 0, 3);
|
||||
|
||||
this.DataString = System.Text.Encoding.Default.GetString(Data);
|
||||
|
||||
|
||||
Valid = true;
|
||||
return true;
|
||||
}
|
||||
public Dataframe(byte[] buffer = null, int minlen = 0)
|
||||
{
|
||||
if (buffer != null) Parse(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -47,8 +47,10 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DataEventArgs.cs" />
|
||||
<Compile Include="Dataframe.cs" />
|
||||
<Compile Include="EnumData.cs" />
|
||||
<Compile Include="Command.cs" />
|
||||
<Compile Include="NarumiSerialComm.cs" />
|
||||
<Compile Include="Structure\ErrorFlag.cs" />
|
||||
<Compile Include="Narumi.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
||||
@@ -8,18 +8,17 @@ using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using COMM;
|
||||
using AR;
|
||||
using System.Xml;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public partial class Narumi : arRS232
|
||||
public partial class Narumi : arDev.NarumiSerialComm
|
||||
{
|
||||
Hashtable SystemCheck, ErrorCheck;
|
||||
private Queue Errlog; // 에러발생시 코드 임시 저장용(쓰레드 동기화용)
|
||||
|
||||
|
||||
public int nBatteryNo { get; set; } = 0;
|
||||
|
||||
|
||||
public Narumi()
|
||||
{
|
||||
SystemCheck = new Hashtable();
|
||||
@@ -124,75 +123,18 @@ namespace arDev
|
||||
return bComplete;
|
||||
}
|
||||
|
||||
public class Dataframe
|
||||
{
|
||||
public byte STX { get; private set; }
|
||||
public byte ETX { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
public string DataString { get; private set; }
|
||||
public byte[] checksum { get; private set; } = new byte[2];
|
||||
public bool Valid { get; private set; } = false;
|
||||
public string Message { get; private set; } = string.Empty;
|
||||
public byte[] Buffer { get; private set; }
|
||||
public string Cmd { get; private set; } = string.Empty;
|
||||
|
||||
public bool Parse(byte[] data, int MinRecvLength = 0)
|
||||
{
|
||||
if (data == null || data.Any() == false)
|
||||
{
|
||||
this.Message = string.Format("수신 데이터가 없습니다");
|
||||
return false;
|
||||
}
|
||||
else if (data.Length < 5)
|
||||
{
|
||||
this.Message = $"데이터의 길이가 5보다 작습니다 길이={data.Length}";
|
||||
return false;
|
||||
}
|
||||
else if (MinRecvLength > 0 && data.Length < MinRecvLength)
|
||||
{
|
||||
this.Message = $"데이터의 길이가 {MinRecvLength}보다 작습니다 길이={data.Length}";
|
||||
return false;
|
||||
}
|
||||
else if (data[0] != 0x02 || data[data.Length - 1] != 0x03)
|
||||
{
|
||||
this.Message = $"STX/ETX Error";
|
||||
return false;
|
||||
}
|
||||
Buffer = new byte[data.Length];
|
||||
Array.Copy(data, Buffer, data.Length);
|
||||
STX = data[0];
|
||||
ETX = data[data.Length - 1];
|
||||
Array.Copy(data, data.Length - 3, checksum, 0, 2);
|
||||
|
||||
Data = new byte[data.Length - 4];
|
||||
Array.Copy(data, 1, Data, 0, data.Length - 4);
|
||||
|
||||
if (data.Length > 2) Cmd = System.Text.Encoding.Default.GetString(Data, 0, 3);
|
||||
|
||||
this.DataString = System.Text.Encoding.Default.GetString(Data);
|
||||
|
||||
|
||||
Valid = true;
|
||||
return true;
|
||||
}
|
||||
public Dataframe(byte[] buffer = null, int minlen = 0)
|
||||
{
|
||||
if (buffer != null) Parse(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ProcessRecvData(byte[] data)
|
||||
{
|
||||
//LastReceiveBuffer
|
||||
var frame = new Dataframe(data, MinRecvLength);
|
||||
if (frame.Valid == false)
|
||||
{
|
||||
RaiseMessage(arRS232.MessageType.Error, frame.Message);
|
||||
RaiseMessage(MessageType.Error, frame.Message);
|
||||
return false;
|
||||
}
|
||||
else if (frame.DataString.StartsWith("$") == false && CheckSum(data) == false)
|
||||
{
|
||||
RaiseMessage(arRS232.MessageType.Error, "Checksum Error MSG=" + frame.DataString);
|
||||
RaiseMessage(MessageType.Error, "Checksum Error MSG=" + frame.DataString);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -218,7 +160,7 @@ namespace arDev
|
||||
// $로 시작되는 AGV 상태 표시
|
||||
//var text_Sts_Etc = Encoding.Default.GetString(bRcvData, 3, bRcvData.Length - 2).TrimStart(' '); //20210311 김정만 - SmartX FrameWork 사용 안함으로 주석처리
|
||||
//var sMessageOther = Encoding.Default.GetString(bRcvData, 3, bRcvData.Length - 2).TrimStart(' ');
|
||||
RaiseMessage(arRS232.MessageType.Normal, "$메세지수신:" + frame.DataString);
|
||||
RaiseMessage(MessageType.Normal, "$메세지수신:" + frame.DataString);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -228,7 +170,7 @@ namespace arDev
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RaiseMessage(arRS232.MessageType.Error, ex.Message);
|
||||
RaiseMessage(MessageType.Error, ex.Message);
|
||||
retval = false;
|
||||
}
|
||||
}
|
||||
@@ -279,14 +221,15 @@ namespace arDev
|
||||
public SystemFlag1 system1 = new SystemFlag1();
|
||||
public ErrorFlag error = new ErrorFlag();
|
||||
public AgvData data = new AgvData();
|
||||
public Signal signal = new Signal();
|
||||
public Signal1 signal1 = new Signal1();
|
||||
public Signal2 signal2 = new Signal2();
|
||||
|
||||
#region [수신] STS(AGV상태정보) 분석
|
||||
public string LastSTS { get; set; } = string.Empty;
|
||||
private void RevSTS(Dataframe frame)
|
||||
{
|
||||
LastSTS = frame.DataString;
|
||||
string rcvdNow = frame.DataString;
|
||||
string rcvdNow = frame.DataString.Replace("\0","");
|
||||
byte[] bRcvData = frame.Buffer;
|
||||
var encoding = System.Text.Encoding.Default;
|
||||
try
|
||||
@@ -322,42 +265,15 @@ namespace arDev
|
||||
data.guidesensor = int.Parse(rcvdNow.Substring(idx, 1)); idx += 1; //가이드 좌측부터 1~9
|
||||
|
||||
nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16);
|
||||
signal.SetValue(nDataTemp);
|
||||
signal1.SetValue(nDataTemp); idx += 2;
|
||||
|
||||
//data.Sts = encoding.GetString(bRcvData, 19, 3); //20210311 김정만 - SmartX FrameWork 사용 안함으로 주석처리
|
||||
if(idx <= rcvdNow.Length-2)
|
||||
{
|
||||
nDataTemp = Convert.ToByte(rcvdNow.Substring(idx, 2), 16);
|
||||
signal2.SetValue(nDataTemp);
|
||||
}
|
||||
|
||||
|
||||
//var Sts_cSpeed = encoding.GetString(bRcvData, 19, 1)[0];
|
||||
//var Sts_cDirection = encoding.GetString(bRcvData, 20, 1)[0];
|
||||
//var Sts_cFB = encoding.GetString(bRcvData, 21, 1)[0];
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////가이드센서 정보 (22)//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//var Sts_nGuide = 0;
|
||||
//if (bRcvData[22] > 47 && bRcvData[22] < 58) { Sts_nGuide = Convert.ToInt32(encoding.GetString(bRcvData, 22, 1)); }
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////마크센서 & 포토센서 정보 (23~24)////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//nDataTemp = Convert.ToInt32(encoding.GetString(bRcvData, 23, 2), 16);
|
||||
|
||||
//data.Sts_bMark1 = Convert.ToBoolean(nDataTemp & 0x4);
|
||||
//data.Sts_bMark2 = Convert.ToBoolean(nDataTemp & 0x8);
|
||||
//data.Sts_bCargo = Convert.ToBoolean(nDataTemp & 0x10);
|
||||
|
||||
////포토센서
|
||||
//if (Sts_cFB == 'F')
|
||||
//{
|
||||
// system.Sts_nSenser = Convert.ToInt32(encoding.GetString(bRcvData, 26, 1));
|
||||
//}
|
||||
//else if (Sts_cFB == 'B')
|
||||
//{
|
||||
// system.Sts_nSenser = Convert.ToInt32(encoding.GetString(bRcvData, 27, 1));
|
||||
//}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//AGV 속도/분기/방향 (19~21)//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
DataReceive?.Invoke(this, new DataEventArgs(DataType.STS));
|
||||
|
||||
}
|
||||
|
||||
609
Cs_HMI/SubProject/AGV/NarumiSerialComm.cs
Normal file
609
Cs_HMI/SubProject/AGV/NarumiSerialComm.cs
Normal file
@@ -0,0 +1,609 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public abstract class NarumiSerialComm : ISerialComm, IDisposable
|
||||
{
|
||||
protected System.IO.Ports.SerialPort _device;
|
||||
protected ManualResetEvent _mre;
|
||||
protected byte[] LastReceiveBuffer = new byte[] { };
|
||||
/// <summary>
|
||||
/// 최종 전송 메세지
|
||||
/// </summary>
|
||||
public byte[] lastSendBuffer = new byte[] { };
|
||||
//public int ValidCheckTimeMSec { get; set; } = 5000;
|
||||
protected List<byte> tempBuffer = new List<byte>();
|
||||
protected Boolean findSTX = false;
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime LastConnTime { get; set; }
|
||||
public DateTime LastConnTryTime { get; set; }
|
||||
public DateTime lastSendTime;
|
||||
/// <summary>
|
||||
/// 메세지 수신시 사용하는 내부버퍼
|
||||
/// </summary>
|
||||
protected List<byte> _buffer = new List<byte>();
|
||||
/// <summary>
|
||||
/// 데이터조회간격(초)
|
||||
/// </summary>
|
||||
public float ScanInterval { get; set; }
|
||||
|
||||
// public byte[] LastRecvData;
|
||||
public string LastRecvString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LastReceiveBuffer == null) return String.Empty;
|
||||
else return System.Text.Encoding.Default.GetString(LastReceiveBuffer);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 마지막으로 데이터를 받은 시간
|
||||
/// </summary>
|
||||
public DateTime lastRecvTime;
|
||||
|
||||
|
||||
public int WriteError = 0;
|
||||
public string WriteErrorMessage = string.Empty;
|
||||
public int WaitTimeout { get; set; } = 1000;
|
||||
public int MinRecvLength { get; set; } = 1;
|
||||
|
||||
// Polling Thread related
|
||||
protected Thread _recvThread;
|
||||
protected volatile bool _isReading = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 포트이름
|
||||
/// </summary>
|
||||
[Description("시리얼 포트 이름")]
|
||||
[Category("설정"), DisplayName("Port Name")]
|
||||
public string PortName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_device == null) return string.Empty;
|
||||
else return _device.PortName;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.IsOpen)
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 포트이름을 변경할 수 없습니다", true));
|
||||
}
|
||||
else if (String.IsNullOrEmpty(value) == false)
|
||||
_device.PortName = value;
|
||||
else
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs("No PortName", true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int BaudRate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_device == null) return 0;
|
||||
else return _device.BaudRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.IsOpen)
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs("포트가 열려있어 BaudRate(를) 변경할 수 없습니다", true));
|
||||
}
|
||||
else if (value != 0)
|
||||
_device.BaudRate = value;
|
||||
else Message?.Invoke(this, new MessageEventArgs("No baud rate", true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public NarumiSerialComm()
|
||||
{
|
||||
_device = new System.IO.Ports.SerialPort();
|
||||
this.BaudRate = 57600;
|
||||
ScanInterval = 10;
|
||||
// _device.DataReceived += barcode_DataReceived; // Removed event handler
|
||||
_device.ErrorReceived += this.barcode_ErrorReceived;
|
||||
_device.WriteTimeout = 3000;
|
||||
_device.ReadTimeout = 3000;
|
||||
_device.ReadBufferSize = 8192;
|
||||
_device.WriteBufferSize = 8192;
|
||||
//_device.DiscardInBuffer();
|
||||
//_device.DiscardOutBuffer();
|
||||
ErrorMessage = string.Empty;
|
||||
lastRecvTime = DateTime.Parse("1982-11-23");
|
||||
LastConnTime = DateTime.Parse("1982-11-23");
|
||||
LastConnTryTime = DateTime.Parse("1982-11-23");
|
||||
lastRecvTime = DateTime.Parse("1982-11-23");
|
||||
this._mre = new ManualResetEvent(true);
|
||||
}
|
||||
|
||||
~NarumiSerialComm()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
|
||||
// Flag: Has Dispose already been called?
|
||||
bool disposed = false;
|
||||
|
||||
// Public implementation of Dispose pattern callable by consumers.
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// Protected implementation of Dispose pattern.
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// Free any other managed objects here.
|
||||
//
|
||||
}
|
||||
|
||||
// Stop reading thread
|
||||
_isReading = false;
|
||||
|
||||
// _device.DataReceived -= barcode_DataReceived; // Removed event handler
|
||||
_device.ErrorReceived -= this.barcode_ErrorReceived;
|
||||
|
||||
if (_recvThread != null && _recvThread.IsAlive)
|
||||
{
|
||||
_recvThread.Join(500);
|
||||
}
|
||||
|
||||
if (_device != null)
|
||||
{
|
||||
if (_device.IsOpen) _device.Close();
|
||||
_device.Dispose();
|
||||
}
|
||||
|
||||
// Free any unmanaged objects here.
|
||||
//
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
public Boolean Open()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_device.IsOpen == false)
|
||||
{
|
||||
_device.Open();
|
||||
}
|
||||
|
||||
if (_device.IsOpen)
|
||||
{
|
||||
// Start polling thread
|
||||
if (_isReading == false)
|
||||
{
|
||||
_isReading = true;
|
||||
_recvThread = new Thread(ReadPort);
|
||||
_recvThread.IsBackground = true;
|
||||
_recvThread.Start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = ex.Message;
|
||||
Message.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public string GetHexString(Byte[] input)
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
foreach (byte b in input)
|
||||
sb.Append(" " + b.ToString("X2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 포트가 열려있는지 확인
|
||||
/// </summary>
|
||||
[Description("현재 시리얼포트가 열려있는지 확인합니다")]
|
||||
[Category("정보"), DisplayName("Port Open")]
|
||||
public Boolean IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_device == null) return false;
|
||||
return _device.IsOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Close()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isReading = false; // Stop thread loop
|
||||
|
||||
if (_recvThread != null && _recvThread.IsAlive)
|
||||
{
|
||||
if (!_recvThread.Join(500)) // Wait for thread to finish
|
||||
{
|
||||
// _recvThread.Abort(); // Avoid Abort if possible
|
||||
}
|
||||
}
|
||||
|
||||
if (_device != null && _device.IsOpen)
|
||||
{
|
||||
_device.DiscardInBuffer();
|
||||
_device.DiscardOutBuffer();
|
||||
_device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected Boolean RaiseRecvData()
|
||||
{
|
||||
return RaiseRecvData(LastReceiveBuffer.ToArray(), false);
|
||||
}
|
||||
/// <summary>
|
||||
/// 수신받은 메세지를 발생 시킵니다
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Boolean RaiseRecvData(byte[] Data, bool udpatelastbuffer)
|
||||
{
|
||||
//181206 - 최종수신 메세지 기록
|
||||
lastRecvTime = DateTime.Now;
|
||||
if (udpatelastbuffer && Data != null)
|
||||
{
|
||||
if (LastReceiveBuffer == null || LastReceiveBuffer.Length != Data.Length)
|
||||
{
|
||||
LastReceiveBuffer = new byte[Data.Length];
|
||||
Array.Copy(Data, LastReceiveBuffer, Data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// UI update might need Invoke if this event handler updates UI directly,
|
||||
// but usually the subscriber handles Invoke.
|
||||
// Since we are running on a background thread now, subscribers must be aware.
|
||||
Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
|
||||
if (ProcessRecvData(Data) == false)
|
||||
{
|
||||
//Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
|
||||
Message?.Invoke(this, new MessageEventArgs(this.ErrorMessage, true)); //errormessage
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수신받은 자료를 처리한다
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool ProcessRecvData(byte[] data);
|
||||
|
||||
#region "Internal Events"
|
||||
|
||||
void barcode_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
|
||||
{
|
||||
Message?.Invoke(this, new MessageEventArgs(e.ToString(), true));
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[] { };
|
||||
|
||||
// Replaced with ReadPort Loop
|
||||
/*
|
||||
void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
int ReadCount = _device.BytesToRead;
|
||||
|
||||
buffer = new byte[ReadCount];
|
||||
_device.Read(buffer, 0, buffer.Length);
|
||||
|
||||
System.Text.StringBuilder LogMsg = new StringBuilder();
|
||||
|
||||
byte[] remainBuffer;
|
||||
Repeat:
|
||||
if (CustomParser(buffer, out remainBuffer))
|
||||
{
|
||||
//분석완료이므로 받은 데이터를 버퍼에 기록한다
|
||||
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
|
||||
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
|
||||
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
|
||||
tempBuffer.Clear();
|
||||
|
||||
//수신메세지발생
|
||||
RaiseRecvData();
|
||||
if (remainBuffer != null && remainBuffer.Length > 0)
|
||||
{
|
||||
//버퍼를 변경해서 다시 전송을 해준다.
|
||||
Array.Resize(ref buffer, remainBuffer.Length);
|
||||
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
|
||||
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//if (IsOpen)
|
||||
//{
|
||||
// //_device.DiscardInBuffer();
|
||||
// //_device.DiscardOutBuffer();
|
||||
//}
|
||||
ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
void ReadPort()
|
||||
{
|
||||
while (_isReading)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_device == null || !_device.IsOpen)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
int readCount = _device.BytesToRead;
|
||||
if (readCount > 0)
|
||||
{
|
||||
byte[] buffer = new byte[readCount];
|
||||
_device.Read(buffer, 0, buffer.Length);
|
||||
|
||||
byte[] remainBuffer;
|
||||
Repeat:
|
||||
if (CustomParser(buffer, out remainBuffer))
|
||||
{
|
||||
//분석완료이므로 받은 데이터를 버퍼에 기록한다
|
||||
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
|
||||
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
|
||||
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
|
||||
tempBuffer.Clear();
|
||||
|
||||
//수신메세지발생
|
||||
RaiseRecvData();
|
||||
if (remainBuffer != null && remainBuffer.Length > 0)
|
||||
{
|
||||
//버퍼를 변경해서 다시 전송을 해준다.
|
||||
buffer = new byte[remainBuffer.Length]; // Reallocate buffer for remaining data
|
||||
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
|
||||
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(20); // Data 없음, 대기
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Thread 상에서 Exception 발생 시 로그 남기고 계속 진행 여부 결정
|
||||
// 여기서는 에러 메시지 발생시키고 Sleep
|
||||
ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region "External Events"
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 오류 및 기타 일반 메세지
|
||||
/// </summary>
|
||||
public event EventHandler<MessageEventArgs> Message;
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Event Args"
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 수신할떄 사용함(RAW 포함)
|
||||
/// </summary>
|
||||
public class ReceiveDataEventArgs : EventArgs
|
||||
{
|
||||
private byte[] _buffer = null;
|
||||
|
||||
/// <summary>
|
||||
/// 바이트배열의 버퍼값
|
||||
/// </summary>
|
||||
public byte[] Value { get { return _buffer; } }
|
||||
|
||||
/// <summary>
|
||||
/// 버퍼(바이트배열)의 데이터를 문자로 반환합니다.
|
||||
/// </summary>
|
||||
public string StrValue
|
||||
{
|
||||
get
|
||||
{
|
||||
//return string.Empty;
|
||||
|
||||
if (_buffer == null || _buffer.Length < 1) return string.Empty;
|
||||
else return System.Text.Encoding.Default.GetString(_buffer);
|
||||
}
|
||||
}
|
||||
public ReceiveDataEventArgs(byte[] buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메세지를 강제 발생
|
||||
/// </summary>
|
||||
/// <param name="mt"></param>
|
||||
/// <param name="message"></param>
|
||||
protected virtual void RaiseMessage(MessageType mt, string message)
|
||||
{
|
||||
this.Message?.Invoke(this, new MessageEventArgs(mt, message));
|
||||
}
|
||||
public enum MessageType
|
||||
{
|
||||
Normal,
|
||||
Error,
|
||||
Send,
|
||||
Recv,
|
||||
}
|
||||
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
public MessageType MsgType { get; set; }
|
||||
private string _message = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Recv,Send,Normal,Error 모두 지원
|
||||
/// </summary>
|
||||
public string Message { get { return _message; } }
|
||||
|
||||
private byte[] _data = null;
|
||||
|
||||
/// <summary>
|
||||
/// Recv,Send에서만 값이 존재 합니다
|
||||
/// </summary>
|
||||
public byte[] Data { get { return _data; } }
|
||||
public MessageEventArgs(string Message, bool isError = false)
|
||||
{
|
||||
if (isError) MsgType = MessageType.Error;
|
||||
else MsgType = MessageType.Normal;
|
||||
_message = Message;
|
||||
}
|
||||
public MessageEventArgs(MessageType msgtype, string Message)
|
||||
{
|
||||
MsgType = msgtype;
|
||||
_message = Message;
|
||||
_data = System.Text.Encoding.Default.GetBytes(Message);
|
||||
}
|
||||
|
||||
public MessageEventArgs(byte[] buffer, bool isRecv = true)
|
||||
{
|
||||
if (isRecv) MsgType = MessageType.Recv;
|
||||
else MsgType = MessageType.Send;
|
||||
_data = new byte[buffer.Length];
|
||||
Array.Copy(buffer, _data, Data.Length);
|
||||
_message = System.Text.Encoding.Default.GetString(_data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다
|
||||
/// </summary>
|
||||
public Boolean IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsOpen == false) return false;
|
||||
if (lastRecvTime.Year == 1982) return false;
|
||||
var ts = DateTime.Now - lastRecvTime;
|
||||
if (ts.TotalSeconds > (this.ScanInterval * 2.5)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected bool WriteData(string cmd)
|
||||
{
|
||||
return WriteData(System.Text.Encoding.Default.GetBytes(cmd));
|
||||
}
|
||||
/// <summary>
|
||||
/// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신)
|
||||
/// </summary>
|
||||
protected Boolean WriteData(byte[] data)
|
||||
{
|
||||
Boolean bRet = false;
|
||||
|
||||
//171205 : 타임아웃시간추가
|
||||
if (!_mre.WaitOne(WaitTimeout))
|
||||
{
|
||||
ErrorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms";
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ErrorMessage, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
_mre.Reset();
|
||||
|
||||
//Array.Resize(ref data, data.Length + 2);
|
||||
|
||||
try
|
||||
{
|
||||
lastSendTime = DateTime.Now;
|
||||
if (lastSendBuffer == null) lastSendBuffer = new byte[data.Length]; //171113
|
||||
else Array.Resize(ref lastSendBuffer, data.Length);
|
||||
Array.Copy(data, lastSendBuffer, data.Length);
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
_device.Write(data, i, 1);
|
||||
|
||||
//_device.Write(data, 0, data.Length);
|
||||
|
||||
//171113
|
||||
this.Message?.Invoke(this, new MessageEventArgs(data, false));
|
||||
|
||||
bRet = true;
|
||||
WriteError = 0;
|
||||
WriteErrorMessage = string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// this.isinit = false;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
bRet = false;
|
||||
WriteError += 1; //연속쓰기오류횟수
|
||||
WriteErrorMessage = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_mre.Set();
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -27,13 +27,25 @@ namespace arDev
|
||||
/// <summary>
|
||||
/// 기동시 자석 감지 에러
|
||||
/// </summary>
|
||||
runerror_by_no_magent_line,
|
||||
runerror_by_no_magent_line=5,
|
||||
/// <summary>
|
||||
/// 호출제어기 통신 오류
|
||||
/// </summary>
|
||||
controller_comm_error = 11,
|
||||
controller_comm_error =6,
|
||||
|
||||
/// <summary>
|
||||
/// 도착경보기 통신 오류
|
||||
/// 배터리 저전압
|
||||
/// </summary>
|
||||
battery_low_voltage=7,
|
||||
|
||||
spare08=8,
|
||||
|
||||
lift_timeout=9,
|
||||
lift_driver_overcurrent=10,
|
||||
lift_driver_emergency = 11,
|
||||
|
||||
/// <summary>
|
||||
/// 도착경보기 통신 오류
|
||||
/// </summary>
|
||||
arrive_ctl_comm_error,
|
||||
|
||||
|
||||
@@ -4,10 +4,26 @@ namespace arDev
|
||||
{
|
||||
public partial class Narumi
|
||||
{
|
||||
|
||||
public class Signal
|
||||
public enum eSignal1
|
||||
{
|
||||
private COMM.Flag _value { get; set; } = new COMM.Flag(16);
|
||||
front_gate_out = 0,
|
||||
rear_gte_out,
|
||||
mark_sensor_1,
|
||||
mark_sensor_2,
|
||||
lift_down_sensor,
|
||||
lift_up_sensor,
|
||||
magnet_relay,
|
||||
charger_align_sensor,
|
||||
}
|
||||
public enum eSignal2
|
||||
{
|
||||
cart_detect1 = 0,
|
||||
cart_detect2,
|
||||
}
|
||||
|
||||
public class Signal1
|
||||
{
|
||||
private COMM.Flag _value { get; set; } = new COMM.Flag(8);
|
||||
public void SetValue(Int16 value) { this._value.writeValue(value); }
|
||||
public UInt16 Value
|
||||
{
|
||||
@@ -16,60 +32,37 @@ namespace arDev
|
||||
return (UInt16)_value.Value;
|
||||
}
|
||||
}
|
||||
public enum eflag
|
||||
{
|
||||
front_gate_out = 0,
|
||||
rear_sensor_out,
|
||||
mark_sensor_1,
|
||||
mark_sensor_2,
|
||||
front_left_sensor,
|
||||
front_right_sensor,
|
||||
front_center_sensor,
|
||||
charger_align_sensor,
|
||||
|
||||
lift_down,
|
||||
lift_up,
|
||||
magnet_on,
|
||||
ChargetSensor,
|
||||
cart_detect1,
|
||||
cart_detect2,
|
||||
Spare1,
|
||||
Spare2
|
||||
}
|
||||
|
||||
public bool GetValue(eflag idx)
|
||||
|
||||
public bool GetValue(eSignal1 idx)
|
||||
{
|
||||
return _value.Get((int)idx);
|
||||
}
|
||||
public bool GetChanged(eflag idx)
|
||||
public bool GetChanged(eSignal1 idx)
|
||||
{
|
||||
return _value.GetChanged((int)idx);
|
||||
}
|
||||
|
||||
public Boolean front_gate_out { get { return GetValue(eflag.front_gate_out); } }
|
||||
public Boolean rear_sensor_out { get { return GetValue(eflag.rear_sensor_out); } }
|
||||
public Boolean mark_sensor_1 { get { return GetValue(eflag.mark_sensor_1); } }
|
||||
public Boolean mark_sensor_2 { get { return GetValue(eflag.mark_sensor_2); } }
|
||||
public Boolean front_gate_out { get { return GetValue(eSignal1.front_gate_out); } }
|
||||
public Boolean rear_sensor_out { get { return GetValue(eSignal1.rear_gte_out); } }
|
||||
public Boolean mark_sensor_1 { get { return GetValue(eSignal1.mark_sensor_1); } }
|
||||
public Boolean mark_sensor_2 { get { return GetValue(eSignal1.mark_sensor_2); } }
|
||||
public Boolean mark_sensor { get { return mark_sensor_1 || mark_sensor_2; } }
|
||||
public Boolean front_left_sensor { get { return GetValue(eflag.front_left_sensor); } }
|
||||
public Boolean front_right_sensor { get { return GetValue(eflag.front_right_sensor); } }
|
||||
public Boolean front_center_sensor { get { return GetValue(eflag.front_center_sensor); } }
|
||||
public Boolean charger_align_sensor { get { return GetValue(eflag.charger_align_sensor); } }
|
||||
public Boolean lift_up { get { return GetValue(eflag.lift_up); } }
|
||||
public Boolean lift_down { get { return GetValue(eflag.lift_down); } }
|
||||
public Boolean magnet_on { get { return GetValue(eflag.magnet_on); } }
|
||||
public Boolean cart_detect1 { get { return GetValue(eflag.cart_detect1); } }
|
||||
public Boolean cart_detect2 { get { return GetValue(eflag.cart_detect2); } }
|
||||
public Boolean charger_align_sensor { get { return GetValue(eSignal1.charger_align_sensor); } }
|
||||
public Boolean lift_up { get { return GetValue(eSignal1.lift_up_sensor); } }
|
||||
public Boolean lift_down { get { return GetValue(eSignal1.lift_down_sensor); } }
|
||||
public Boolean magnet_on { get { return GetValue(eSignal1.magnet_relay); } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
//모든사태값을 탭으로 구분하여 문자를 생성한다
|
||||
var sb = new System.Text.StringBuilder();
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var def = Enum.IsDefined(typeof(eflag), i);
|
||||
if(def)
|
||||
var def = Enum.IsDefined(typeof(eSignal1), i);
|
||||
if (def)
|
||||
{
|
||||
var flag = (eflag)i;
|
||||
var flag = (eSignal1)i;
|
||||
var value = _value.Get(i);
|
||||
sb.AppendLine($"[{i:00}][{flag}] : {value}");
|
||||
}
|
||||
@@ -86,10 +79,87 @@ namespace arDev
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var def = Enum.IsDefined(typeof(eflag), i);
|
||||
var def = Enum.IsDefined(typeof(eSignal1), i);
|
||||
if (def)
|
||||
{
|
||||
var flag = (eflag)i;
|
||||
var flag = (eSignal1)i;
|
||||
var value = _value.Get(i);
|
||||
string line = $"[{i:00}][{flag}] : {value}";
|
||||
|
||||
// : true가 포함된 줄은 파란색
|
||||
if (value == true)
|
||||
{
|
||||
sb.AppendLine(@"\cf1 " + line + @"\cf0\line");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(line + @"\line");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine("}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class Signal2
|
||||
{
|
||||
private COMM.Flag _value { get; set; } = new COMM.Flag(8);
|
||||
public void SetValue(Int16 value) { this._value.writeValue(value); }
|
||||
public UInt16 Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return (UInt16)_value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool GetValue(eSignal2 idx)
|
||||
{
|
||||
return _value.Get((int)idx);
|
||||
}
|
||||
public bool GetChanged(eSignal2 idx)
|
||||
{
|
||||
return _value.GetChanged((int)idx);
|
||||
}
|
||||
|
||||
|
||||
public Boolean cart_detect1 { get { return GetValue(eSignal2.cart_detect1); } }
|
||||
public Boolean cart_detect2 { get { return GetValue(eSignal2.cart_detect2); } }
|
||||
public override string ToString()
|
||||
{
|
||||
//모든사태값을 탭으로 구분하여 문자를 생성한다
|
||||
var sb = new System.Text.StringBuilder();
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var def = Enum.IsDefined(typeof(eSignal2), i);
|
||||
if(def)
|
||||
{
|
||||
var flag = (eSignal2)i;
|
||||
var value = _value.Get(i);
|
||||
sb.AppendLine($"[{i:00}][{flag}] : {value}");
|
||||
}
|
||||
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ToRtfString()
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine(@"{\rtf1\ansi\deff0");
|
||||
sb.AppendLine(@"{\colortbl ;\red0\green0\blue255;}"); // Color 1 = Blue
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var def = Enum.IsDefined(typeof(eSignal2), i);
|
||||
if (def)
|
||||
{
|
||||
var flag = (eSignal2)i;
|
||||
var value = _value.Get(i);
|
||||
string line = $"[{i:00}][{flag}] : {value}";
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Flag.cs" />
|
||||
<Compile Include="ISerialComm.cs" />
|
||||
<Compile Include="RS232.cs" />
|
||||
<Compile Include="Var.cs" />
|
||||
<Compile Include="Enum.cs" />
|
||||
|
||||
18
Cs_HMI/SubProject/CommData/ISerialComm.cs
Normal file
18
Cs_HMI/SubProject/CommData/ISerialComm.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
/// <summary>
|
||||
/// 시리얼통신모듈의기본형태정의
|
||||
/// </summary>
|
||||
public interface ISerialComm
|
||||
{
|
||||
string PortName { get; set; }
|
||||
int BaudRate { get; set; }
|
||||
bool IsOpen { get; }
|
||||
string ErrorMessage { get; set; }
|
||||
|
||||
Boolean Open();
|
||||
Boolean Close();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ using System.Threading;
|
||||
|
||||
namespace arDev
|
||||
{
|
||||
public abstract partial class arRS232 : IDisposable
|
||||
|
||||
public abstract partial class arRS232 : ISerialComm, IDisposable
|
||||
{
|
||||
protected System.IO.Ports.SerialPort _device;
|
||||
protected ManualResetEvent _mre;
|
||||
@@ -19,7 +20,7 @@ namespace arDev
|
||||
//public int ValidCheckTimeMSec { get; set; } = 5000;
|
||||
protected List<byte> tempBuffer = new List<byte>();
|
||||
protected Boolean findSTX = false;
|
||||
public string errorMessage { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime LastConnTime { get; set; }
|
||||
public DateTime LastConnTryTime { get; set; }
|
||||
public DateTime lastSendTime;
|
||||
@@ -111,7 +112,7 @@ namespace arDev
|
||||
_device.ReadBufferSize = 8192;
|
||||
_device.WriteBufferSize = 8192;
|
||||
|
||||
errorMessage = string.Empty;
|
||||
ErrorMessage = string.Empty;
|
||||
lastRecvTime = DateTime.Parse("1982-11-23");
|
||||
LastConnTime = DateTime.Parse("1982-11-23");
|
||||
LastConnTryTime = DateTime.Parse("1982-11-23");
|
||||
@@ -164,7 +165,7 @@ namespace arDev
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
ErrorMessage = ex.Message;
|
||||
Message.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
return false;
|
||||
}
|
||||
@@ -191,14 +192,16 @@ namespace arDev
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close(Boolean PortClose = true)
|
||||
public virtual bool Close()
|
||||
{
|
||||
if (_device != null && _device.IsOpen)
|
||||
{
|
||||
_device.DiscardInBuffer();
|
||||
_device.DiscardOutBuffer();
|
||||
if (PortClose) _device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
|
||||
_device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
protected Boolean RaiseRecvData()
|
||||
{
|
||||
@@ -228,7 +231,7 @@ namespace arDev
|
||||
if (ProcessRecvData(Data) == false)
|
||||
{
|
||||
//Message?.Invoke(this, new MessageEventArgs(Data, true)); //recvmessage
|
||||
Message?.Invoke(this, new MessageEventArgs(this.errorMessage, true)); //errormessage
|
||||
Message?.Invoke(this, new MessageEventArgs(this.ErrorMessage, true)); //errormessage
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -239,7 +242,7 @@ namespace arDev
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.errorMessage = ex.Message;
|
||||
this.ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
return false;
|
||||
}
|
||||
@@ -301,7 +304,7 @@ namespace arDev
|
||||
// //_device.DiscardInBuffer();
|
||||
// //_device.DiscardOutBuffer();
|
||||
//}
|
||||
errorMessage = ex.Message;
|
||||
ErrorMessage = ex.Message;
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
|
||||
}
|
||||
|
||||
@@ -419,7 +422,7 @@ namespace arDev
|
||||
/// <summary>
|
||||
/// 포트가 열려있거나 데이터 수신시간이 없는경우 false를 반환합니다
|
||||
/// </summary>
|
||||
public Boolean IsValid
|
||||
public Boolean IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -444,8 +447,8 @@ namespace arDev
|
||||
//171205 : 타임아웃시간추가
|
||||
if (!_mre.WaitOne(WaitTimeout))
|
||||
{
|
||||
errorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms";
|
||||
this.Message?.Invoke(this, new MessageEventArgs(errorMessage, true));
|
||||
ErrorMessage = $"WriteData:MRE:WaitOne:TimeOut {WaitTimeout}ms";
|
||||
this.Message?.Invoke(this, new MessageEventArgs(ErrorMessage, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Submodule Cs_HMI/SubProject/CommUtil updated: ed05439991...b070b711f0
Submodule Cs_HMI/SubProject/EnigProtocol updated: 82bca1c90b...14ff055fa9
@@ -23,7 +23,7 @@ namespace Test_ACS
|
||||
LoadPortList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void InitializeProtocol()
|
||||
{
|
||||
@@ -61,6 +61,14 @@ namespace Test_ACS
|
||||
case AGVCommandEH.Status:
|
||||
UpdateAGVStatus(e.ReceivedPacket.Data);
|
||||
break;
|
||||
case AGVCommandEH.Error:
|
||||
var errorcode = (AGVErrorCode)e.ReceivedPacket.Data[0];
|
||||
var errorMessage = System.Text.Encoding.UTF8.GetString(e.ReceivedPacket.Data, 1, e.ReceivedPacket.Data.Length - 1);
|
||||
AddLog($"Error Received : {errorcode} ID:{e.ReceivedPacket.ID} MSG:{errorMessage}", LogType.Info);
|
||||
break;
|
||||
default:
|
||||
AddLog($"unknown command:{command}", LogType.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +161,7 @@ namespace Test_ACS
|
||||
serialPort.Close();
|
||||
btnConnect.Text = "연결";
|
||||
AddLog("포트 닫힘", LogType.Info);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -163,6 +172,7 @@ namespace Test_ACS
|
||||
serialPort.Open();
|
||||
btnConnect.Text = "해제";
|
||||
AddLog($"{serialPort.PortName}:{serialPort.BaudRate} 연결됨", LogType.Info);
|
||||
SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -178,22 +188,22 @@ namespace Test_ACS
|
||||
|
||||
private void cmbPort_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
//SaveSettings();
|
||||
}
|
||||
|
||||
private void txtBaudRate_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
//SaveSettings();
|
||||
}
|
||||
|
||||
private void txtRFID_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
//SaveSettings();
|
||||
}
|
||||
|
||||
private void txtAlias_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
//SaveSettings();
|
||||
}
|
||||
|
||||
private void rbAGV1_CheckedChanged(object sender, EventArgs e)
|
||||
@@ -201,7 +211,7 @@ namespace Test_ACS
|
||||
if (rbAGV1.Checked)
|
||||
{
|
||||
selectedAGV = 11;
|
||||
SaveSettings();
|
||||
//SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +220,7 @@ namespace Test_ACS
|
||||
if (rbAGV2.Checked)
|
||||
{
|
||||
selectedAGV = 12;
|
||||
SaveSettings();
|
||||
//SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +260,7 @@ namespace Test_ACS
|
||||
var aliasHex = string.Join("", aliasBytes.Select(b => b.ToString("X2")));
|
||||
var dataStr = targetID + aliasHex;
|
||||
SendCommand(AGVCommandHE.GotoAlias, dataStr);
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void btnStop_Click(object sender, EventArgs e)
|
||||
@@ -524,9 +535,6 @@ namespace Test_ACS
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
// 설정 저장
|
||||
SaveSettings();
|
||||
|
||||
if (serialPort != null && serialPort.IsOpen)
|
||||
{
|
||||
serialPort.Close();
|
||||
|
||||
1
Cs_HMI/TestProject/mcpServers
Submodule
1
Cs_HMI/TestProject/mcpServers
Submodule
Submodule Cs_HMI/TestProject/mcpServers added at 792c47442d
@@ -1,13 +1,14 @@
|
||||
@echo off
|
||||
|
||||
|
||||
echo Building AGV C# HMI Project...
|
||||
|
||||
REM Set MSBuild path
|
||||
REM set MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
|
||||
|
||||
|
||||
set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2017\WDExpress\MSBuild\15.0\Bin\MSBuild.exe"
|
||||
set MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
|
||||
set MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"
|
||||
|
||||
|
||||
REM Rebuild Debug x86 configuration (VS-style Rebuild)
|
||||
%MSBUILD% AGVCSharp.sln -property:Configuration=Debug -property:Platform=x86 -verbosity:quiet -nologo -t:Rebuild
|
||||
|
||||
pause
|
||||
%MSBUILD% "S:\Source\Amkor\ENIG\Cs_HMI\AGVCSharp.sln" -property:Configuration=Debug -property:Platform=x86 -verbosity:quiet -nologo -t:Rebuild
|
||||
78
Cs_HMI/docs/Charging_Sequence_Analysis.md
Normal file
78
Cs_HMI/docs/Charging_Sequence_Analysis.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 충전 시퀀스 분석 (Charging Sequence Analysis)
|
||||
|
||||
## 1. 개요 (Overview)
|
||||
이 문서는 AGV의 자동 및 수동 충전 프로세스에 대한 분석 결과를 기술합니다. UI 트리거부터 이동, 도킹, 충전 명령 전송 및 확인까지의 전체 흐름을 포함합니다.
|
||||
|
||||
## 2. UI 트리거 (UI Trigger)
|
||||
* **관련 컨트롤**: `btCharge` (자동충전/해제), `btChargeM` (수동충전 모드 토글)
|
||||
* **이벤트 핸들러**: `btCharge_Click`, `btChargeM_Click` (in `fMain.cs`)
|
||||
* **동작 로직**:
|
||||
* **자동 충전 (`btCharge`)**:
|
||||
* 이미 충전 관련 상태(`GOCHARGE`, `CHARGECHECK`)이거나 충전 중(`FLAG_CHARGEONA`)인 경우: → 충전 중지 여부 확인 후 `GOTO`(이동) 상태로 전환하여 충전 해제.
|
||||
* 대기 상태인 경우: → 충전 시작 여부 확인 후 `GOCHARGE` RunStep 설정 및 `RUN` 상태 시작.
|
||||
* **수동 충전 (`btChargeM`)**:
|
||||
* `FLAG_CHARGEONM` 플래그를 토글합니다. 자동 충전 중일 때는 진입 불가.
|
||||
|
||||
## 3. 상태 머신 로직 (State Machine Logic)
|
||||
|
||||
### 3.1 충전 이동 (`_SM_RUN_GOCHARGE.cs`)
|
||||
충전기를 찾아 이동하고 도킹하는 핵심 시퀀스입니다.
|
||||
|
||||
**사전 검사 (Prerequisites)**
|
||||
1. **초기화**: `PUB.Result.CurrentPos` 초기화.
|
||||
2. **하드웨어 체크**: AGV 연결 상태 확인.
|
||||
3. **충전 상태 확인 (최적화)**: 이미 충전 중(`FLAG_CHARGEONA` or `Battery_charging`)인 경우, 충전 시퀀스를 진행하지 않고 즉시 `READY` 상태로 전환합니다. (불필요한 충전 해제/재시작 방지).
|
||||
4. **충전 해제 확인 (`_SM_RUN_CHGOFF`)**: 충전 중이 아니라면(위 단계 통과), 확실한 시작을 위해 충전 해제 상태를 보장합니다.
|
||||
5. **위치 확인 (`_SM_RUN_POSCHK`)**: 현재 위치를 모를 경우 위치 찾기 수행.
|
||||
|
||||
**시퀀스 단계 (Step-by-Step)**
|
||||
1. **목표 설정**: `NodeMAP_RFID_Charger` 값을 읽어 충전기 노드를 목표(`TargetNode`)로 설정.
|
||||
2. **충전기 위치 이동**:
|
||||
* `UpdateMotionPositionForCharger` 함수를 사용해 충전기 앞단까지 이동.
|
||||
* 제한시간(`ChargeSearchTime`) 초과 시 실패 처리 (`CHARGEOFF` 이동).
|
||||
3. **정밀 검색 (Fine Search)**:
|
||||
* 항상 **전진(Forward)**으로 미세 조정 이동하며 충전기를 검색합니다. (경로 예측 시 전진 진입하도록 설정됨).
|
||||
* 음성 안내: "충전기를 검색합니다".
|
||||
4. **마크 정지 (Mark Stop)**:
|
||||
* AGV가 기동 중이면 `MarkStop` 명령 전송.
|
||||
* 정지할 때까지 대기.
|
||||
* 60초 내 정지 확인 안 되면 실패 처리.
|
||||
5. **위치 확정**:
|
||||
* 정지 후 3초 대기(센서 안정화).
|
||||
* 현재 위치를 `CHARGE`로 설정.
|
||||
6. **충전 명령**:
|
||||
* `AGVCharge(true)` 호출하여 하드웨어에 충전 시작 명령 전송.
|
||||
* `WAIT_CHARGEACK` 플래그 설정.
|
||||
7. **ACK 확인**:
|
||||
* AGV로부터 "CBT" 응답 대기.
|
||||
* 응답 없을 시 재전송(최대 5회), 실패 시 취소 처리.
|
||||
|
||||
### 3.2 충전 확인 (`_SM_RUN_GOCHARGECHECK.cs`)
|
||||
충전 명령 후 실제 배터리 충전이 시작되었는지 검증합니다.
|
||||
|
||||
* **조건**: `Battery_charging` 신호 및 `FLAG_CHARGEONA` 확인.
|
||||
* **에러 처리**:
|
||||
* `Charger_pos_error` (위치 에러) 또는 `Charger_run_error` 발생 시 취소.
|
||||
* 30초 타임아웃: 충전이 시작되지 않으면 `CHARGEOFF`로 전환하여 프로세스 취소.
|
||||
|
||||
### 3.3 충전 해제 (`_SM_RUN_CHGOFF.cs`)
|
||||
충전 상태를 안전하게 종료합니다.
|
||||
|
||||
* **로직**:
|
||||
* `Battery_charging`이 true면 `AGVCharge(false)` 전송.
|
||||
* 충전이 해제될 때까지 대기 (최대 1분).
|
||||
* 안전하게 해제되면 `true` 반환.
|
||||
|
||||
## 4. 이동 제어 상세 (Movement Control)
|
||||
* **핵심 함수**: `UpdateMotionPositionForCharger` (in `_Util.cs`)
|
||||
* **특징**:
|
||||
* 일반 이동과 달리 충전기 진입을 위한 전용 로직 사용.
|
||||
* `MARK_SENSOR`를 확인하여 충전 위치 도달 여부 판단.
|
||||
* **안전 로직 추가 (`CheckStopCondition`)**:
|
||||
* **비상 정지**: `Emergency` 상태이고 로봇이 멈췄을 경우 `IDLE`로 전환 및 중단.
|
||||
* **수동 충전 중**: `FLAG_CHARGEONM` 상태일 경우 이동 불가 처리.
|
||||
|
||||
## 5. 주요 설정 및 변수
|
||||
* `PUB.setting.NodeMAP_RFID_Charger`: 충전기 위치의 RFID 태그 ID.
|
||||
* `PUB.setting.chargerpos`: 충전 진입 방식 (0:전진, 2:후진, 1:QC위치).
|
||||
* `PUB.setting.ChargeSearchTime`: 충전기 검색 제한 시간.
|
||||
@@ -1,63 +0,0 @@
|
||||
# GOHOME 상태 머신 분석
|
||||
|
||||
## 개요
|
||||
`GOHOME` 상태 머신(`_SM_RUN_GOHOME`)은 AGV를 현재 위치에서 미리 정의된 "Home" 노드로 이동시키는 역할을 합니다. 이 과정은 `_Util.cs`와 `VirtualAGV.cs`에 정의된 공유 경로 탐색 및 이동 로직을 활용합니다.
|
||||
|
||||
## 로직 흐름
|
||||
|
||||
### 1. 초기화 및 안전 점검
|
||||
- **하드웨어 확인:** `PUB.AGV.IsOpen`을 확인합니다. 연결이 끊겨 있으면 에러 상태로 설정합니다.
|
||||
- **충전 해제:** `_SM_RUN_CHGOFF`를 호출하여 AGV가 물리적으로 충전기에 연결되어 있지 않은지 확인합니다.
|
||||
- *잠재적 문제:* 충전 센서가 고착(stuck)된 경우, 이 단계에서 무한 대기할 수 있습니다.
|
||||
- **Lidar 안전:** `PUB.AGV.system1.stop_by_front_detect`를 확인합니다. 장애물이 감지되면 제거될 때까지 실행을 일시 중지(false 반환)합니다.
|
||||
|
||||
### 2. 1단계: 목적지 설정
|
||||
- **홈 위치 조회:** `PUB.setting.NodeMAP_RFID_Home`에서 홈 노드 ID를 가져옵니다.
|
||||
- **유효성 검사:** 맵에서 홈 노드를 찾을 수 없는 경우, 에러를 기록하고 `READY` 상태로 초기화합니다.
|
||||
- **타겟 할당:** `PUB._virtualAGV.TargetNode`를 홈 노드로 설정합니다.
|
||||
|
||||
### 3. 2단계: 이동 실행
|
||||
- **위임:** `_Util.cs`의 `UpdateMotionPositionForMark("_SM_RUN_GOHOME")`를 호출합니다.
|
||||
- **경로 탐색:**
|
||||
- 경로가 없거나 현재 경로가 유효하지 않은 경우, `_Util.cs`가 `CurrentNode`에서 `TargetNode`까지의 새로운 경로를 계산합니다.
|
||||
- **예측 및 제어:**
|
||||
- `VirtualAGV.Predict()`가 현재 노드와 경로를 기반으로 다음 행동을 결정합니다.
|
||||
- **정지 조건:** `Predict()`가 `Stop`을 반환하는 경우:
|
||||
- `IsPositionConfirmed`가 true인지 확인합니다.
|
||||
- `CurrentNodeId`가 `TargetNode.NodeId`와 일치하는지 확인합니다.
|
||||
- 둘 다 true이면 `true`(도착)를 반환합니다.
|
||||
- **이동 조건:** `Predict()`가 이동 명령을 반환하는 경우:
|
||||
- 논리적 명령(좌/우/직진, 전진/후진, 속도)을 하드웨어 명령(`AGVMoveSet`)으로 변환합니다.
|
||||
- **최적화:** 상태가 변경된 경우에만 명령을 전송합니다 (최근 `_Util.cs` 업데이트에 구현됨).
|
||||
- AGV가 구동 중인지 확인합니다 (`AGVMoveRun`).
|
||||
|
||||
### 4. 3단계: 완료
|
||||
- **알림:** "홈 검색 완료" 음성을 출력합니다.
|
||||
- **로깅:** 데이터베이스에 기록을 추가합니다.
|
||||
- **전환:** 시퀀스를 업데이트하여 `GOHOME` 상태를 사실상 종료합니다.
|
||||
|
||||
## 잠재적 문제 및 위험 요소
|
||||
|
||||
### 1. 충전 센서에 의한 무한 대기
|
||||
- **위험:** 충전 신호가 활성화되어 있는 한 `_SM_RUN_CHGOFF` 호출은 계속 `false`를 반환합니다.
|
||||
- **시나리오:** 센서가 고장 나거나 시스템이 모르는 사이에 AGV가 수동으로 충전기에 밀려 올라간 경우, 여기서 멈출 수 있습니다.
|
||||
- **완화:** `_SM_RUN_CHGOFF` 확인에 타임아웃이나 수동 오버라이드 기능을 추가해야 합니다 (현재 스니펫에서는 보이지 않음).
|
||||
|
||||
### 2. 위치 상실 복구
|
||||
- **위험:** `_SM_RUN_POSCHK`(`UpdateMotionPositionForMark`에서 호출됨)가 태그를 찾지 못하면, AGV가 무한히 기어갈(crawl) 수 있거나 멈출 수 있습니다.
|
||||
- **메커니즘:** `_SM_RUN_POSCHK`는 태그를 찾기 위해 저속 전진 명령을 내립니다.
|
||||
- **시나리오:** AGV가 경로를 벗어났거나 데드존에 있는 경우, Lidar가 감지하지 못하면 충돌하거나 배회할 수 있습니다.
|
||||
|
||||
### 3. 경로 재계산 루프 (확인됨: 안전함)
|
||||
- **분석:** `_Util.cs` 코드를 확인한 결과, `CalcPath`가 실패하여 경로가 생성되지 않으면(`PathResult.result == null`) 즉시 `PUB.sm.SetNewRunStep(ERunStep.READY)`를 호출합니다.
|
||||
- **결론:** 경로 계산 실패 시 상태 머신이 `READY` 상태로 전환되므로, 무한 루프나 반복적인 재계산 시도는 발생하지 않습니다. 안전하게 정지합니다.
|
||||
|
||||
### 4. 통신 폭주 (해결됨)
|
||||
- **이전 위험:** 동일한 이동 명령의 지속적인 전송.
|
||||
- **해결:** 최근 `_Util.cs` 업데이트에서 `PUB.AGV.data`를 새 명령과 비교하여 변경 시에만 전송하도록 수정되었습니다.
|
||||
|
||||
## 권장 사항
|
||||
1. **충전 해제 타임아웃:** 센서 고장 시 무한 대기를 방지하기 위해 `_SM_RUN_CHGOFF` 확인에 타임아웃을 추가하십시오.
|
||||
2. **경로 실패 처리:** `CalcPath`가 null/empty를 반환하는지 명시적으로 확인하고, 무한 재시도 대신 에러와 함께 `GOHOME` 시퀀스를 중단하십시오.
|
||||
3. **도착 확인:** `_Util.cs`의 "도착" 확인이 "거의 도착" 시나리오(예: 태그 약간 앞에서 정지)에 대해 견고한지 확인하십시오. 현재 로직은 `CurrentNodeId` 업데이트에 의존하며, 이는 양호해 보입니다.
|
||||
|
||||
66
Cs_HMI/docs/Home_Move_Analysis.md
Normal file
66
Cs_HMI/docs/Home_Move_Analysis.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Home Move Sequence 분석 (Home Move Sequence Analysis)
|
||||
|
||||
## 1. 개요 (Overview)
|
||||
이 문서는 AGV의 "Home Move" 기능에 대한 분석 결과를 기술합니다. UI 트리거부터 내부 상태 머신(State Machine) 로직 및 이동 제어 흐름을 포함합니다.
|
||||
|
||||
## 2. UI 트리거 (UI Trigger)
|
||||
* **버튼 컨트롤**: `btHome` (in `fMain.Designer.cs`)
|
||||
* **이벤트 핸들러**: `brHome_Click` (in `fMain.cs`)
|
||||
* **동작**:
|
||||
* 사용자가 "홈" 버튼 클릭 시 실행됩니다.
|
||||
* **이미 Home 이동 중인 경우**: "홈 이동을 취소 할까요?" 팝업 → 확인 시 `IDLE` 상태로 전환 및 정지 (`AGVMoveStop`).
|
||||
* **대기 상태인 경우**: "홈 이동을 실행 할까요?" 팝업 → 확인 시 `GOHOME` RunStep으로 설정 및 `RUN` 상태 시작.
|
||||
* **조건**: 자동 충전 중이거나 수동 충전 모드가 아니어야 함 (`PUB.CheckManualChargeMode`).
|
||||
|
||||
## 3. 상태 머신 로직 (State Machine Logic)
|
||||
* **핵심 파일**: `Project\StateMachine\Step\_SM_RUN_GOHOME.cs`
|
||||
* **함수**: `_SM_RUN_GOHOME(bool isFirst, TimeSpan stepTime)`
|
||||
* **상태 흐름**:
|
||||
|
||||
### 초기 검사
|
||||
1. **하드웨어 연결**: `PUB.AGV.IsOpen` 체크. 연결 끊김 시 에러 처리.
|
||||
2. **충전 상태**: `_SM_RUN_CHGOFF`를 호출하여 충전기가 분리되었는지 확인.
|
||||
3. **장애물 감지**: `PUB.AGV.system1.stop_by_front_detect` (Lidar 감지)
|
||||
* 감지 시 "전방에 물체가 감지되었습니다" 음성 출력.
|
||||
* **주의**: 이 단계에서 명시적인 소프트웨어 정지 명령(`AGVMoveStop`)은 코드상에 보이지 않으며, 함수가 `false`를 리턴하여 이동 로직 진입을 막습니다. (하드웨어 자체 정지 기능에 의존하는 것으로 추정됨)
|
||||
|
||||
### 시퀀스 단계 (Sequence Steps)
|
||||
상태 머신은 `PUB.sm.RunStepSeq`를 통해 단계별로 진행됩니다.
|
||||
|
||||
**단계 1: 이동 준비**
|
||||
* 음성 안내: "홈으로 이동합니다"
|
||||
* **목표 설정**: `PUB.setting.NodeMAP_RFID_Home` 값을 읽어 목표 노드(`TargetNode`)로 설정.
|
||||
* 에러 처리: 홈 위치 설정이 없는 경우 에러 로그 기록 후 `READY` 상태로 복귀.
|
||||
|
||||
**단계 2: 이동 (Moving)**
|
||||
* **이동 함수**: `UpdateMotionPositionForMark("funcName")` 호출 (in `_Util.cs`)
|
||||
* 이 함수가 `true`를 반환할 때까지 반복 호출됨.
|
||||
* 이동 완료 시 `AGVMoveStop` 호출 후 다음 단계로 진행.
|
||||
|
||||
**단계 3: 완료 (Completion)**
|
||||
* 음성 안내: "홈 검색 완료"
|
||||
* 로그 기록: 홈 위치 도착 기록.
|
||||
* 상태 종료: `true` 반환 → 메인 루프에서 `READY` 상태로 전환.
|
||||
|
||||
## 4. 이동 제어 상세 (Movement Control Details)
|
||||
* **핵심 함수**: `UpdateMotionPositionForMark` (in `Project\StateMachine\Step\_Util.cs`)
|
||||
* **경로 계산**: 현재 위치와 목표 노드 간의 경로(`CalcPath`)를 계산.
|
||||
* **주행 예측 (Predict)**: `PUB._virtualAGV.Predict()`를 사용하여 다음 동작(직진, 회전, 정지 등)을 결정.
|
||||
* **제어 명령**:
|
||||
* `Predict` 결과에 따라 모터(`Forward/Backward`), 분기(Magnetic Guide), 속도를 설정.
|
||||
* 변경된 명령이 있을 경우에만 `PUB.AGV.AGVMoveSet`으로 하드웨어에 전송.
|
||||
* AGV가 정지 상태라면 `PUB.AGV.AGVMoveRun`으로 구동 시작.
|
||||
* **정지 조건**:
|
||||
* `Predict`가 `Stop` 명령을 반환하고,
|
||||
* 위치가 확정(`IsPositionConfirmed`)되었으며,
|
||||
* 현재 노드가 목표 노드와 일치하고,
|
||||
* 실제 AGV 하드웨어가 정지(`agv_run == false`)한 경우.
|
||||
|
||||
## 5. 잠재적 이슈 (Potential Issues)
|
||||
1. **장애물 감지 시 정지 처리**:
|
||||
* `_SM_RUN_GOHOME`에서 장애물 감지 시(`stop_by_front_detect`), 음성 출력 후 `return false`만 수행합니다.
|
||||
* 이전에 이동 명령이 전송된 상태라면, 소프트웨어에서 명시적으로 `AGVMoveStop`을 보내지 않으므로 AGV가 계속 이동하려 할 수 있습니다. (단, 하드웨어 센서가 직접 모터를 차단하도록 설계되었을 가능성이 높음)
|
||||
* **권장**: 안전을 위해 장애물 감지 루틴 내에서도 명시적으로 `AGVMoveStop`을 호출하는 것을 검토할 필요가 있습니다.
|
||||
|
||||
2. **경로 재생성 시 멈춤**:
|
||||
* `UpdateMotionPositionForMark`에서 경로를 재생성해야 할 경우, `AGVMoveStop("경로재생성")`을 호출하여 일시 정지 후 경로를 다시 계산합니다. 이는 안전한 동작입니다.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user